@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
TypeScript
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>;
}