UNPKG

@lifeart/gxt

Version:

<img align="right" width="95" height="95" alt="Philosopher’s stone, logo of PostCSS" src="./public/logo.png">

183 lines (181 loc) 8.25 kB
import { Component } from '../component-class'; import { ComponentLike, DOMApi } from '../types'; import { getFirstNode } from '../render-core'; import { Cell, MergedCell } from '../reactive'; import { RENDERED_NODES_PROPERTY, COMPONENT_ID_PROPERTY } from '../shared'; export { getFirstNode }; type GenericReturnType = Array<ComponentLike | Node> | ComponentLike | Node; export type InverseFn = (ctx: Component<any>) => GenericReturnType | null; type ListComponentArgs<T> = { tag: Cell<T[]> | MergedCell; key: string | null; ctx: Component<any>; ItemComponent: (item: T, index?: number | MergedCell) => GenericReturnType; inverseFn?: InverseFn; hasIndex?: boolean; }; type RenderTarget = HTMLElement | DocumentFragment; /** * Normalize an arbitrary `{{#each}}` input value into a real Array<T>. * * Glimmer-VM's `{{#each}}` semantics treat any non-array, non-iterable * value as falsy (renders the inverse). For iterables that aren't plain * arrays (Set, Map, custom Symbol.iterator classes, generators), the body * iterates with the spread elements. Ember's ArrayProxy is normalized via * its `.content` slot. * * Returns: * - The same array if `Array.isArray(value)` (covers plain arrays and * Ember `A()` / NativeArray, which IS-A Array). * - `[...value]` if `value[Symbol.iterator]` is callable. * - Recursive normalization on `value.content` for ArrayProxy-shaped * objects (defensive: falls back to `[]` if access throws or the proxy * is destroyed). * - `[]` for everything else (null, undefined, false, '', 0, NaN, true, * non-iterable strings, plain objects, functions, numbers). * * Strings are intentionally treated as `[]` — Glimmer's `{{#each}}` does * NOT iterate string characters, and the upstream tests require `'hello'` * to render the inverse block. */ export declare function normalizeIterableValue<T>(value: unknown): T[]; /** * Compute positions in `arr` that form the Longest Increasing Subsequence. * Items at these positions are already in correct relative order and don't * need to be relocated. O(n log n) time, O(n) space (reused). */ export declare function longestIncreasingSubsequence(arr: number[], out?: Set<number>): Set<number>; export declare class BasicListComponent<T extends { id: number; }> { keyMap: Map<string, GenericReturnType>; indexMap: Map<string, number>; indexFormulaMap: Map<string, MergedCell> | null; itemMarkers: Map<string, Comment>; markerSet: Set<Comment>; private _existKeys; private _existNewIdx; private _existOldIdx; private _itemKeys; private _lisResult; private _updatingKeys; private _moveSet; private _freshMoveKeys; private _processedKeys; protected _keysToRemove: string[]; protected _rowsToRemove: GenericReturnType[]; [RENDERED_NODES_PROPERTY]: Array<Node>; [COMPONENT_ID_PROPERTY]: number; ItemComponent: (item: T, index: number | MergedCell, ctx: Component<any>) => GenericReturnType; inverseFn: InverseFn | null; inverseContent: GenericReturnType | null; bottomMarker: Comment; topMarker: Comment; key: string; tag: Cell<T[]> | MergedCell; isFirstRender: boolean; get ctx(): this; protected keysForItems(items: T[], keyForItem: (item: T, index: number, items: T[]) => string): Set<string>; /** * Detach this list's child-id set before bulk destruction. * * This lets child destructors skip parent-sibling bookkeeping and avoids * allocating a replacement empty Set on every fast cleanup. */ protected detachTreeChildren(): void; /** * Fast-path for updates that preserve all existing items and only append * new ones at the end. * * We can safely skip the removal scan only when every old position still * points to the same key in the incoming list prefix. */ protected isAppendOnlySuperset(items: T[], amountOfKeys: number, keyForItem: (item: T, index: number, items: T[]) => string): boolean; private _relocateFragment; api: DOMApi; hasIndex: boolean; constructor({ tag, ctx, key, ItemComponent, inverseFn, hasIndex }: ListComponentArgs<T>, outlet: RenderTarget, topMarker: Comment); private relocateItem; protected removeMarker(key: string): void; /** * Per-`items[]` first-occurrence cache for duplicate-key qualification. * * Both `@identity` and explicit-key paths have to detect when a base key * (object identity, or the value of `item[this.key]`) has already been * seen at an earlier index in the *current* items array, so subsequent * occurrences can be position-qualified (`baseKey:i`) and treated as * distinct rows by the diff algorithm. * * Two-phase strategy to avoid per-syncList Map allocation in the * overwhelmingly common no-duplicates case (krausest, sane apps): * * 1. First call for a fresh items[] does a single pass over items[] * adding every base key to a reusable instance Set * (`_dupDetectSet`). If the Set's final size equals items.length, * there are no dupes — we set `_dupHasDupes = false` and return. * No Map is allocated; no entry object is allocated. * * 2. If dupes ARE detected, we lazily build the Map<baseKey, * firstIndex> on the SAME pass (using a reusable instance Map, * `_dupFirstIdxMap`) and set `_dupHasDupes = true`. * * The cached verdict is keyed by `_dupItemsRef`, an instance-level * single-slot identity cache. Per-row callers compare `items` against * `_dupItemsRef`; if they match, the cached verdict is consulted. Otherwise * detection runs. * * The cache is invalidated explicitly at the top of every `syncList` * (and `_dupItemsRef` is set to null) — it's intentionally narrow-scoped * to a single sync pass. Inside one syncList we may receive several calls * to `keyForItem` from `isAppendOnlySuperset`, `keysForItems`, and * `updateItems`; the first hit pays the O(n) detection cost, all * subsequent calls hit the cached verdict. */ protected _dupItemsRef: T[] | null; protected _dupHasDupes: boolean; protected _dupDetectSet: Set<string>; protected _dupFirstIdxMap: Map<string, number>; /** * Run dedup detection for `items[]` if it differs from the cached ref. * After return, `_dupHasDupes` and (if true) `_dupFirstIdxMap` are * populated. Returns true if dupes were detected. * * `_dupDetectSet` is cleared once at the start of detection. We don't * clear it on the no-dupes path (the contents are scratch and will be * cleared on next detection). We don't clear `_dupFirstIdxMap` on the * no-dupes path either — `_dupHasDupes === false` ensures callers won't * read it; we clear lazily on the next `_dupHasDupes = true` transition. */ private detectDupes; private setupKeyForItem; renderInverse(): void; destroyInverseSync(): void; /** * Remove all DOM nodes between topMarker and bottomMarker. * Used by destroyInverseSync/Async to ensure inverse content is fully cleaned up * regardless of RENDERED_NODES_PROPERTY state. */ protected clearInverseNodes(): void; destroyInverseAsync(): Promise<void>; keyForItem(item: T, index: number, items?: T[]): string; private getTargetNode; updateItems(items: T[], amountOfKeys: number, removedCount: number): void; } export declare class SyncListComponent<T extends { id: number; }> extends BasicListComponent<T> { private _syncInProgress; constructor(params: ListComponentArgs<T>, outlet: RenderTarget, topMarker: Comment); fastCleanup(): boolean; syncList(items: T[]): void; destroyItem(row: GenericReturnType, key: string): void; } export declare class AsyncListComponent<T extends { id: number; }> extends BasicListComponent<T> { destroyPromise: Promise<void[]> | null; constructor(params: ListComponentArgs<any>, outlet: RenderTarget, topMarker: Comment); fastCleanup(): Promise<boolean>; syncList(items: T[]): Promise<void>; destroyItem(row: GenericReturnType, key: string): Promise<void>; }