Teact: Support ref function, update types; TeactN: Support detaching containers

This commit is contained in:
Alexander Zinchuk 2022-11-07 23:00:38 +04:00
parent 694529ebde
commit abd92f2ae8
2 changed files with 61 additions and 11 deletions

View File

@ -108,7 +108,7 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
if (!$current && $new) {
if (isNewComponent || isNewFragment) {
if (isNewComponent) {
$new = initComponent(parentEl, $new as VirtualElementComponent, $parent, index) as typeof $new;
$new = initComponent(parentEl, $new as VirtualElementComponent, $parent, index) as unknown as typeof $new;
}
mountChildren(parentEl, $new as VirtualElementComponent | VirtualElementFragment, { nextSibling, fragment });
@ -127,7 +127,7 @@ function renderWithVirtual<T extends VirtualElement | undefined>(
if (isNewComponent || isNewFragment) {
if (isNewComponent) {
$new = initComponent(parentEl, $new as VirtualElementComponent, $parent, index) as typeof $new;
$new = initComponent(parentEl, $new as VirtualElementComponent, $parent, index) as unknown as typeof $new;
}
remount(parentEl, $current, undefined);
@ -262,6 +262,8 @@ function createNode($element: VirtualElementReal): Node {
if (typeof props.ref === 'object') {
props.ref.current = element;
} else if (typeof props.ref === 'function') {
props.ref(element);
}
processControlled(tag, props);
@ -557,7 +559,20 @@ function processControlled(tag: string, props: AnyLiteral) {
onChange?.(e);
if (value !== undefined) {
e.currentTarget.value = value;
const { selectionStart, selectionEnd } = e.currentTarget;
if (e.currentTarget.value !== value) {
e.currentTarget.value = value;
if (selectionStart !== undefined && selectionEnd !== undefined) {
e.currentTarget.setSelectionRange(selectionStart, selectionEnd);
// eslint-disable-next-line no-underscore-dangle
e.currentTarget.dataset.__teactSelectionStart = String(selectionStart);
// eslint-disable-next-line no-underscore-dangle
e.currentTarget.dataset.__teactSelectionEnd = String(selectionEnd);
}
}
}
if (checked !== undefined) {
@ -616,7 +631,15 @@ function setAttribute(element: HTMLElement, key: string, value: any) {
// An optimization attempt
} else if (key === 'value') {
if ((element as HTMLInputElement).value !== value) {
const {
__teactSelectionStart: selectionStart, __teactSelectionEnd: selectionEnd,
} = (element as HTMLInputElement).dataset;
(element as HTMLInputElement).value = value;
if (selectionStart !== undefined && selectionEnd !== undefined) {
(element as HTMLInputElement).setSelectionRange(Number(selectionStart), Number(selectionEnd));
}
}
} else if (key === 'style') {
element.style.cssText = value;

View File

@ -32,7 +32,10 @@ type ActionHandler = (
payload: any,
) => GlobalState | void | Promise<void>;
type MapStateToProps<OwnProps = undefined> = ((global: GlobalState, ownProps: OwnProps) => AnyLiteral);
type DetachWhenChanged = (current: any) => void;
type MapStateToProps<OwnProps = undefined> = (
(global: GlobalState, ownProps: OwnProps, detachWhenChanged: DetachWhenChanged) => AnyLiteral
);
let currentGlobal = {} as GlobalState;
@ -52,6 +55,9 @@ const containers = new Map<string, {
mappedProps?: Props;
forceUpdate: Function;
areMappedPropsChanged: boolean;
isDetached: boolean;
detachReason: any;
detachWhenChanged: DetachWhenChanged;
DEBUG_updates: number;
DEBUG_componentName: string;
}>();
@ -139,13 +145,21 @@ function updateContainers() {
// eslint-disable-next-line no-restricted-syntax
for (const container of containers.values()) {
const {
mapStateToProps, ownProps, mappedProps, forceUpdate,
mapStateToProps, ownProps, mappedProps, forceUpdate, isDetached, detachWhenChanged,
} = container;
if (isDetached) {
continue;
}
let newMappedProps;
try {
newMappedProps = mapStateToProps(currentGlobal, ownProps);
newMappedProps = mapStateToProps(currentGlobal, ownProps, detachWhenChanged);
if (container.isDetached) {
continue;
}
} catch (err: any) {
handleError(err);
@ -213,7 +227,7 @@ export function removeCallback(cb: Function) {
}
}
export function withGlobal<OwnProps>(
export function withGlobal<OwnProps extends AnyLiteral>(
mapStateToProps: MapStateToProps<OwnProps> = () => ({}),
) {
return (Component: FC) => {
@ -229,13 +243,25 @@ export function withGlobal<OwnProps>(
};
}, [id]);
let container = containers.get(id);
let container = containers.get(id)!;
if (!container) {
container = {
mapStateToProps,
ownProps: props,
areMappedPropsChanged: false,
forceUpdate,
isDetached: false,
detachReason: undefined,
// This allows to ignore changes in global during animation before unmount
detachWhenChanged: (current) => {
const { detachReason } = container!;
if (detachReason === undefined && current !== undefined) {
container!.detachReason = current;
} else if (detachReason !== undefined && detachReason !== current) {
container!.isDetached = true;
}
},
DEBUG_updates: 0,
DEBUG_componentName: Component.name,
};
@ -251,7 +277,7 @@ export function withGlobal<OwnProps>(
container.ownProps = props;
try {
container.mappedProps = mapStateToProps(currentGlobal, props);
container.mappedProps = mapStateToProps(currentGlobal, props, container.detachWhenChanged);
} catch (err: any) {
handleError(err);
}
@ -297,8 +323,9 @@ export function typify<ProjectGlobalState, ActionPayloads, NonTypedActionNames e
name: ActionName,
handler: ActionHandlers[ActionName],
) => void,
withGlobal: withGlobal as <OwnProps>(
mapStateToProps: ((global: ProjectGlobalState, ownProps: OwnProps) => AnyLiteral),
withGlobal: withGlobal as <OwnProps extends AnyLiteral>(
mapStateToProps: (
(global: ProjectGlobalState, ownProps: OwnProps, detachWhenChanged: DetachWhenChanged) => AnyLiteral),
) => (Component: FC) => FC<OwnProps>,
};
}