Update Teact from MyTonWallet

This commit is contained in:
Alexander Zinchuk 2025-11-22 12:54:10 +01:00
parent c59c6f02b5
commit 6054536ee9
2 changed files with 59 additions and 49 deletions

View File

@ -12,7 +12,6 @@ import type {
} from './teact';
import { DEBUG } from '../../config';
import { unique } from '../../util/iteratees';
import { addEventListener, removeAllDelegatedListeners, removeEventListener } from './dom-events';
import {
captureImmediateEffects,
@ -59,6 +58,8 @@ const headsByElement = new WeakMap<Element, VirtualDomHead>();
const extraClasses = new WeakMap<Element, Set<string>>();
const extraStyles = new WeakMap<Element, Record<string, string>>();
const uniqueChildKeysCache = new WeakMap<VirtualElementChildren, (keyof any)[]>();
let DEBUG_virtualTreeSize = 1;
function render($element: VirtualElement | undefined, parentEl: HTMLElement) {
@ -459,10 +460,6 @@ function renderChildren(
forceMoveToEnd = false,
namespace?: string,
) {
if (DEBUG) {
DEBUG_checkKeyUniqueness($new.children);
}
if (('props' in $new) && $new.props.teactFastList) {
renderFastListChildren($current, $new, currentContext, currentEl);
return;
@ -532,52 +529,31 @@ function renderFastListChildren(
const currentChildren = $current.children;
const newChildren = $new.children;
const newKeys = new Set();
for (const $newChild of newChildren) {
const key = 'props' in $newChild ? $newChild.props.key : undefined;
if (DEBUG && isParentElement($newChild)) {
// eslint-disable-next-line no-null/no-null
if (key === undefined || key === null) {
// eslint-disable-next-line no-console
console.warn('Missing `key` in `teactFastList`');
}
// Clear out duplicated keys to avoid incorrect elements matching
const currentKeysByIndex = getChildKeysByIndex(currentChildren);
const newKeysByIndex = getChildKeysByIndex(newChildren);
const newKeys = new Set(newKeysByIndex);
if (DEBUG) {
for (const $newChild of newChildren) {
if ($newChild.type === VirtualType.Fragment) {
throw new Error('[Teact] Fragment can not be child of container with `teactFastList`');
}
}
newKeys.add(key);
}
// Build a collection of old children that also remain in the new list
let currentRemainingIndex = 0;
const remainingByKey: Record<string, { $element: VirtualElement; index: number; orderKey?: number }> = {};
const remainingByKey: Record<keyof any, { $element: VirtualElement; index: number; orderKey?: number }> = {};
for (let i = 0, l = currentChildren.length; i < l; i++) {
const $currentChild = currentChildren[i];
let key = 'props' in $currentChild ? $currentChild.props.key : undefined;
// eslint-disable-next-line no-null/no-null
const isKeyPresent = key !== undefined && key !== null;
const key = currentKeysByIndex[i];
// First we process removed children
if (isKeyPresent && !newKeys.has(key)) {
if (!newKeys.has(key)) {
renderWithVirtual(currentEl, $currentChild, undefined, $new, currentContext, -1);
continue;
} else if (!isKeyPresent) {
const $newChild = newChildren[i];
const newChildKey = ($newChild && 'props' in $newChild) ? $newChild.props.key : undefined;
// If a non-key element remains at the same index we preserve it with a virtual `key`
if ($newChild && !newChildKey) {
key = `${INDEX_KEY_PREFIX}${i}`;
// Otherwise, we just remove it
} else {
renderWithVirtual(currentEl, $currentChild, undefined, $new, currentContext, -1);
continue;
}
}
// Then we build up info about remaining children
@ -595,7 +571,7 @@ function renderFastListChildren(
for (let i = 0, l = newChildren.length; i < l; i++) {
const $newChild = newChildren[i];
const key = 'props' in $newChild ? $newChild.props.key : `${INDEX_KEY_PREFIX}${i}`;
const key = newKeysByIndex[i];
const currentChildInfo = remainingByKey[key];
if (!currentChildInfo) {
@ -932,23 +908,57 @@ function DEBUG_addToVirtualTreeSize($current: VirtualElementParent | VirtualDomH
});
}
function DEBUG_checkKeyUniqueness(children: VirtualElementChildren) {
const firstChild = children[0];
if (firstChild && 'props' in firstChild && firstChild.props.key !== undefined) {
const keys = children.reduce((acc: any[], child) => {
if ('props' in child && child.props.key) {
acc.push(child.props.key);
/** Returns unique and not missing key for each child */
function getChildKeysByIndex(children: VirtualElementChildren) {
// The caching makes sense, because each children list is handled at least twice (as the new and the current children)
let uniqueKeysByIndex = uniqueChildKeysCache.get(children);
if (uniqueKeysByIndex) return uniqueKeysByIndex;
const seenKeys = new Set<any>();
const DEBUG_duplicatedKeys = new Set<any>();
uniqueKeysByIndex = children.map(($child, index) => {
let key = getElementKey($child);
if (isNullable(key)) {
if (DEBUG && isParentElement($child)) {
// eslint-disable-next-line no-console
console.warn('Missing `key` in `teactFastList`');
}
return acc;
}, []);
key = `${INDEX_KEY_PREFIX}${index}`;
} else {
if (seenKeys.has(key)) {
if (DEBUG) {
DEBUG_duplicatedKeys.add(key);
}
if (keys.length !== unique(keys).length) {
// eslint-disable-next-line no-console
console.warn('[Teact] Duplicated keys:', keys.filter((e, i, a) => a.indexOf(e) !== i), children);
throw new Error('[Teact] Children keys are not unique');
key = `${INDEX_KEY_PREFIX}${index}`;
} else {
seenKeys.add(key);
}
}
return key;
});
if (DEBUG && DEBUG_duplicatedKeys.size) {
// eslint-disable-next-line no-console
console.warn('[Teact] Duplicated keys:', [...DEBUG_duplicatedKeys], children);
throw new Error('[Teact] Children keys are not unique');
}
uniqueChildKeysCache.set(children, uniqueKeysByIndex);
return uniqueKeysByIndex;
}
function getElementKey($element: VirtualElement) {
return 'props' in $element ? $element.props.key : undefined;
}
function isNullable(value: unknown): value is undefined | null {
// eslint-disable-next-line no-null/no-null
return value === undefined || value === null;
}
const TeactDOM = { render };

View File

@ -1,4 +1,3 @@
/* eslint-disable react-x/no-unnecessary-use-prefix */
import type { ReactElement } from 'react';
import { DEBUG, DEBUG_MORE } from '../../config';
@ -137,6 +136,7 @@ export type TeactNode =
| string
| number
| boolean
| undefined
| TeactNode[];
type Effect = () => (NoneToVoidFunction | void);