import { Component } from '../component-class'; import { ComponentLike, DOMApi, RENDERING_CONTEXT_PROPERTY } from '../types'; import { getFirstNode } from '../render-core'; import { Cell, MergedCell, AnyCell } from '../reactive'; import { RENDERED_NODES_PROPERTY, COMPONENT_ID_PROPERTY } from '../shared'; import { StaticBlockDef } from '../static-block'; import { EachFrame, FrameSharedEntry } from './list-frames'; export { getFirstNode }; type GenericReturnType = Array | ComponentLike | Node; type RowContext = { [COMPONENT_ID_PROPERTY]: number; [RENDERED_NODES_PROPERTY]: Array; [RENDERING_CONTEXT_PROPERTY]: DOMApi; [key: symbol]: unknown; }; export type InverseFn = (ctx: Component) => GenericReturnType | null; type ListComponentArgs = { tag: Cell | MergedCell; key: string | null; ctx: Component; ItemComponent: (item: T, index?: number | MergedCell) => GenericReturnType; inverseFn?: InverseFn; hasIndex?: boolean; recycle?: (list: BasicListComponent, items: any[]) => void; block?: StaticBlockDef; blockValues?: (item: T, index: number | MergedCell, ctx: unknown) => readonly unknown[]; }; type RenderTarget = HTMLElement | DocumentFragment; export declare function normalizeIterableValue(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): Set; export declare class BasicListComponent { keyMap: Map; indexMap: Map; indexFormulaMap: Map | null; boundItemMap: Map | null; rowCtxMap: Map | null; itemMarkers: Map; markerSet: Set; private _registerMarkerHook; private _existKeys; private _existNewIdx; private _existOldIdx; private _itemKeys; private _lisResult; private _updatingKeys; private _moveSet; private _freshMoveKeys; private _processedKeys; protected _keysToRemove: string[]; protected _rowsToRemove: GenericReturnType[]; protected _appendOnlyVerdict: boolean; [RENDERED_NODES_PROPERTY]: Array; [COMPONENT_ID_PROPERTY]: number; ItemComponent: (item: T, index: number | MergedCell, ctx: Component) => GenericReturnType; inverseFn: InverseFn | null; inverseContent: GenericReturnType | null; bottomMarker: Comment; topMarker: Comment; key: string; tag: Cell | MergedCell; isFirstRender: boolean; get ctx(): this; protected keysForItems(items: T[], keyForItem: (item: T, index: number, items: T[]) => string): Set; /** * 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; /** * Resolve the ctx that the row body (`ItemComponent`) should register its * per-element binding-opcode destructors against. * * Allocate a per-row {@link RowContext} that is a child of this list in the * TREE and store it by key, so a clear/remove unsubscribes the row's * `class`/attr/text/event/modifier opcodes. * * IMPORTANT: this does NOT touch `setParentContext`. The leak is confined to * the row body's DIRECT element binding opcodes (which `_DOM` registers * against the ctx ARG, i.e. this returned rowCtx) — for a "stable" each-body * (single element, no `$_ucw` wrapper; the Krausest `` case). When the * body is `$_ucw`-wrapped (text / multi-child / nested-control rows), the UCW * is ITSELF a per-row destroyable already tracked in `keyMap` and torn down by * the existing `destroyElementSync(keyMapRow)` path, so there is no leak to * fix there — and the UCW must keep attaching to its lexical parent (the list * `self`, via the unchanged ambient `getParentContext()`). Re-parenting the * UCW under rowCtx regressed `toggling {{#each}}` / `{{#each-in}}` inverse * (else-branch) rendering, so we leave the parent-context chain untouched and * let rowCtx own ONLY the opcodes `_DOM` registers directly against it. */ protected rowBodyCtx(key: string): ComponentLike; /** * Tear down the per-row destructor-owner ctx for `key` (if any), firing the * row body's element binding opcodes AND cascading to its TREE children. * * `skipDom=false`: when the row body is `$_ucw`-wrapped (text / multi-child / * nested-control rows), the UCW component is `addToTree`'d UNDER rowCtx (see * `_component`'s `addToTree(ctx, instance)` with ctx = our rowCtx). So the * UCW (and its yielded/rendered DOM) is a tree child of rowCtx and MUST be * removed by this cascade — `skipDom=true` would mark it destroyed without * removing its DOM, and the caller's separate `destroyElementSync(row)` then * no-ops (the row IS that already-destroyed UCW), leaking the DOM (the * `{{#each}}{{yield}}` accumulation bug). For a stable `` body rowCtx has * no tree children + empty RENDERED_NODES, so this only fires its opcodes and * the raw `` node is removed by the caller's row path. Idempotent. */ protected destroyRowCtx(key: string): void; /** * Tear down EVERY tracked per-row ctx (used by the bulk clear paths). Runs * each row's element binding opcodes synchronously (skipDom — the row DOM is * removed by the bulk `clearChildren`) and detaches all row ctxs from the * tree, then clears the map. No-op when not tracking per-row ctxs. */ protected teardownAllRowCtxs(): 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; recycleImpl?: (list: BasicListComponent, items: any[]) => void; block: StaticBlockDef | null; blockValues: ((item: T, index: number | MergedCell, ctx: unknown) => readonly unknown[]) | null; frameMode: boolean; frames: Map> | null; frameShared: Map | null; constructor({ tag, ctx, key, ItemComponent, inverseFn, hasIndex, recycle, block, blockValues, }: ListComponentArgs, 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 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; protected _dupFirstIdxMap: Map; /** * 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; keyForItem(item: T, index: number, items?: T[]): string; private getTargetNode; /** * Construct the row body content for `item`. * * When the compiler attached a static-block fast path (see * src/core/static-block.ts), the row DOM is produced via cloneNode + slot * binding and the binding destructors register against the SAME per-row * `bodyCtx` contract `_DOM` uses (`registerDestructorBatch`), so teardown * is identical to the compiled-callback path. SSR and rehydration take the * compiled-callback fallback — the block path builds fresh DOM and cannot * adopt server-rendered nodes. */ protected buildRow(item: T, idx: number | MergedCell, bodyCtx: ComponentLike): GenericReturnType; private _buildAndInsertRow; updateItems(items: T[], amountOfKeys: number, removedCount: number): void; } export declare class SyncListComponent extends BasicListComponent { private _syncInProgress; constructor(params: ListComponentArgs, outlet: RenderTarget, topMarker: Comment); fastCleanup(): boolean; syncList(items: T[]): void; destroyItem(row: GenericReturnType, key: string): void; } export declare class AsyncListComponent extends BasicListComponent { destroyPromise: Promise | null; constructor(params: ListComponentArgs, outlet: RenderTarget, topMarker: Comment); fastCleanup(): Promise; syncList(items: T[]): Promise; destroyItem(row: GenericReturnType, key: string): Promise; }