From abd92f2ae844e2ce03f553782412616249fab129 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Mon, 7 Nov 2022 23:00:38 +0400 Subject: [PATCH] Teact: Support ref function, update types; TeactN: Support detaching containers --- src/lib/teact/teact-dom.ts | 29 ++++++++++++++++++++++--- src/lib/teact/teactn.tsx | 43 +++++++++++++++++++++++++++++++------- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/lib/teact/teact-dom.ts b/src/lib/teact/teact-dom.ts index 2c34a765b..ffa34b94f 100644 --- a/src/lib/teact/teact-dom.ts +++ b/src/lib/teact/teact-dom.ts @@ -108,7 +108,7 @@ function renderWithVirtual( 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( 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; diff --git a/src/lib/teact/teactn.tsx b/src/lib/teact/teactn.tsx index 9d9e3019c..6ea582e38 100644 --- a/src/lib/teact/teactn.tsx +++ b/src/lib/teact/teactn.tsx @@ -32,7 +32,10 @@ type ActionHandler = ( payload: any, ) => GlobalState | void | Promise; -type MapStateToProps = ((global: GlobalState, ownProps: OwnProps) => AnyLiteral); +type DetachWhenChanged = (current: any) => void; +type MapStateToProps = ( + (global: GlobalState, ownProps: OwnProps, detachWhenChanged: DetachWhenChanged) => AnyLiteral +); let currentGlobal = {} as GlobalState; @@ -52,6 +55,9 @@ const containers = new Map(); @@ -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( +export function withGlobal( mapStateToProps: MapStateToProps = () => ({}), ) { return (Component: FC) => { @@ -229,13 +243,25 @@ export function withGlobal( }; }, [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( 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 void, - withGlobal: withGlobal as ( - mapStateToProps: ((global: ProjectGlobalState, ownProps: OwnProps) => AnyLiteral), + withGlobal: withGlobal as ( + mapStateToProps: ( + (global: ProjectGlobalState, ownProps: OwnProps, detachWhenChanged: DetachWhenChanged) => AnyLiteral), ) => (Component: FC) => FC, }; }