balises 0.7.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/each.js CHANGED
@@ -243,6 +243,7 @@ function reorderNodes(entries, newKeys, prevKeys, marker, parent) {
243
243
  // This item needs to be moved
244
244
  for (let j = nodes.length - 1; j >= 0; j--) {
245
245
  parent.insertBefore(nodes[j], insertionPoint);
246
+ insertionPoint = nodes[j];
246
247
  }
247
248
  }
248
249
  // Update insertion point to the first node of current entry
@@ -1 +1 @@
1
- {"version":3,"file":"each.js","sourceRoot":"","sources":["../../src/each.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Keyed list rendering plugin for templates.\n *\n * Provides efficient, keyed list rendering with minimal DOM operations.\n * Supports signals, computeds, getters, and plain arrays.\n *\n * Two forms:\n * - Two-arg: uses object reference as key, render receives raw item\n * - Three-arg: uses explicit keyFn, render receives ReadonlySignal<T> for DOM reuse\n *\n * @example\n * ```ts\n * import { html as baseHtml, signal } from \"balises\";\n * import eachPlugin, { each } from \"balises/each\";\n *\n * const html = baseHtml.with(eachPlugin);\n * const items = signal([{ id: 1, name: \"Alice\" }, { id: 2, name: \"Bob\" }]);\n *\n * // Three-arg form: DOM reused when keys match\n * html`<ul>\n * ${each(items, i => i.id, itemSignal => html`<li>${() => itemSignal.value.name}</li>`)}\n * </ul>`.render();\n * ```\n */\n\nimport {\n computed,\n isSignal,\n signal,\n ReadonlySignal,\n type Reactive,\n} from \"./signals/index.js\";\nimport { Signal } from \"./signals/signal.js\";\nimport { Template, type InterpolationPlugin } from \"./template.js\";\n\n/** Marker symbol for each() descriptors */\nconst EACH = Symbol(\"each\");\n\n/** Type guard for primitives */\nconst isPrimitive = (v: unknown): v is string | number | boolean | symbol =>\n typeof v !== \"object\" && typeof v !== \"function\";\n\n/** Each descriptor - returned by each() */\nexport interface EachDescriptor<T> {\n readonly [EACH]: true;\n /** @internal List source (array, signal, or getter) */\n __list__: T[] | Reactive<T[]> | (() => T[]);\n /** @internal Key extraction function */\n __keyFn__: (item: T, index: number) => unknown;\n /** @internal Render function for each item (type varies by form) */\n __renderFn__:\n | ((item: T, index: number) => Template)\n | ((item: ReadonlySignal<T>, index: number) => Template);\n /** @internal Whether this is the three-arg (keyed) form */\n __keyed__: boolean;\n}\n\n/** Cache entry for list items */\ninterface CacheEntry<T> {\n nodes: Node[];\n dispose: () => void;\n item: T;\n itemSignal?: Signal<T>;\n}\n\n/**\n * Render content and insert nodes before marker.\n * Handles Templates and primitives.\n */\nfunction insertContent(\n marker: Comment,\n value: unknown,\n nodes: Node[],\n disposers: (() => void)[],\n): void {\n const parent = marker.parentNode!;\n\n for (const item of Array.isArray(value) ? value : [value]) {\n if (item instanceof Template) {\n const { fragment, dispose } = item.render();\n disposers.push(dispose);\n nodes.push(...fragment.childNodes);\n parent.insertBefore(fragment, marker);\n } else if (item != null && typeof item !== \"boolean\") {\n const node = document.createTextNode(String(item));\n nodes.push(node);\n parent.insertBefore(node, marker);\n }\n }\n}\n\n/**\n * Create a keyed list descriptor.\n *\n * Two-argument form: uses object reference as key (or index for primitives).\n * Render function receives the raw item. DOM is reused only when object reference matches.\n *\n * Three-argument form: uses explicit keyFn.\n * Render function receives a ReadonlySignal<T> wrapping the item.\n * DOM is reused when keys match, and the signal is updated with new item data.\n *\n * @example\n * ```ts\n * // Two-arg: object reference as key, receives raw item\n * each(items, item => html`<li>${item.name}</li>`)\n *\n * // Three-arg: explicit key, receives ReadonlySignal for DOM reuse\n * each(items, item => item.id, itemSignal => html`<li>${() => itemSignal.value.name}</li>`)\n * ```\n */\nexport function each<T>(\n list: T[] | Reactive<T[]> | (() => T[]),\n renderFn: (item: T, index: number) => Template,\n): EachDescriptor<T>;\nexport function each<T>(\n list: T[] | Reactive<T[]> | (() => T[]),\n keyFn: (item: T, index: number) => unknown,\n renderFn: (item: ReadonlySignal<T>, index: number) => Template,\n): EachDescriptor<T>;\nexport function each<T>(\n list: T[] | Reactive<T[]> | (() => T[]),\n keyFnOrRenderFn:\n | ((item: T, index: number) => unknown)\n | ((item: T, index: number) => Template),\n maybeRenderFn?: (item: ReadonlySignal<T>, index: number) => Template,\n): EachDescriptor<T> {\n const hasExplicitKeyFn = maybeRenderFn !== undefined;\n const keyFn = hasExplicitKeyFn\n ? (keyFnOrRenderFn as (item: T, index: number) => unknown)\n : (item: T, index: number) => (isPrimitive(item) ? index : item);\n const renderFn = hasExplicitKeyFn\n ? maybeRenderFn\n : (keyFnOrRenderFn as (item: T, index: number) => Template);\n\n return {\n [EACH]: true,\n __list__: list,\n __keyFn__: keyFn,\n __renderFn__: renderFn,\n __keyed__: hasExplicitKeyFn,\n };\n}\n\n/**\n * Plugin that handles each() descriptors.\n */\nconst eachPlugin: InterpolationPlugin = (value) => {\n if (!(value && typeof value === \"object\" && EACH in value)) return null;\n\n return (marker, disposers) => {\n const desc = value as EachDescriptor<unknown>;\n bindEach(desc, marker, disposers);\n };\n};\n\nexport default eachPlugin;\n\n/**\n * Bind an each() descriptor to a marker position.\n */\nfunction bindEach<T>(\n desc: EachDescriptor<T>,\n marker: Comment,\n disposers: (() => void)[],\n): void {\n const { __list__, __keyFn__, __renderFn__, __keyed__ } = desc;\n const parent = marker.parentNode!;\n\n // Get current array value\n const getList = (): T[] => {\n if (typeof __list__ === \"function\" && !isSignal(__list__)) {\n return (__list__ as () => T[])();\n }\n return isSignal(__list__) ? (__list__.value as T[]) : (__list__ as T[]);\n };\n\n // Wrap in computed for reactivity\n const listComputed = computed(getList);\n\n // Cache: key -> { nodes, dispose, item, itemSignal? }\n const cache = new Map<unknown, CacheEntry<T>>();\n\n // Track previous order for LIS-based reordering\n let prevKeys: unknown[] = [];\n\n const reconcile = () => {\n const items = listComputed.value;\n const seenKeys = new Set<unknown>();\n const newKeys: unknown[] = [];\n const newEntries: CacheEntry<T>[] = [];\n let hasDuplicateWarning = false;\n\n // Process items, create/reuse entries\n for (let i = 0; i < items.length; i++) {\n const item = items[i]!;\n const key = __keyFn__(item, i);\n\n // Warn and skip duplicates (only warn once)\n if (seenKeys.has(key)) {\n if (!hasDuplicateWarning) {\n console.warn(`[each] Duplicate key: ${String(key)}. Skipping.`);\n hasDuplicateWarning = true;\n }\n continue;\n }\n seenKeys.add(key);\n\n const cached = cache.get(key);\n\n if (__keyed__) {\n // Three-arg form: reuse if key exists, update signal\n if (cached) {\n cached.itemSignal!.value = item;\n cached.item = item;\n newKeys.push(key);\n newEntries.push(cached);\n } else {\n // Create new entry with signal\n const itemSignal = signal(item);\n const readonlySignal = new ReadonlySignal(itemSignal);\n const nodes: Node[] = [];\n const itemDisposers: (() => void)[] = [];\n insertContent(\n marker,\n (\n __renderFn__ as (\n item: ReadonlySignal<T>,\n index: number,\n ) => Template\n )(readonlySignal, i),\n nodes,\n itemDisposers,\n );\n const entry: CacheEntry<T> = {\n nodes,\n dispose: () => itemDisposers.forEach((d) => d()),\n item,\n itemSignal,\n };\n cache.set(key, entry);\n newKeys.push(key);\n newEntries.push(entry);\n }\n } else {\n // Two-arg form: reuse only if same object reference\n if (cached && cached.item === item) {\n newKeys.push(key);\n newEntries.push(cached);\n } else {\n // Dispose old entry if key exists but item changed\n if (cached) {\n cached.dispose();\n for (const node of cached.nodes) {\n (node as ChildNode).remove();\n }\n cache.delete(key);\n }\n // Create new entry\n const nodes: Node[] = [];\n const itemDisposers: (() => void)[] = [];\n insertContent(\n marker,\n (__renderFn__ as (item: T, index: number) => Template)(item, i),\n nodes,\n itemDisposers,\n );\n const entry: CacheEntry<T> = {\n nodes,\n dispose: () => itemDisposers.forEach((d) => d()),\n item,\n };\n cache.set(key, entry);\n newKeys.push(key);\n newEntries.push(entry);\n }\n }\n }\n\n // Remove stale entries\n for (const [key, entry] of cache) {\n if (!seenKeys.has(key)) {\n entry.dispose();\n for (const node of entry.nodes) {\n (node as ChildNode).remove();\n }\n cache.delete(key);\n }\n }\n\n // Reorder nodes using LIS optimization\n reorderNodes(newEntries, newKeys, prevKeys, marker, parent);\n prevKeys = newKeys;\n };\n\n // Initial render and subscribe\n reconcile();\n const unsub = listComputed.subscribe(reconcile);\n\n disposers.push(() => {\n unsub();\n listComputed.dispose();\n for (const entry of cache.values()) {\n entry.dispose();\n for (const node of entry.nodes) {\n (node as ChildNode).remove();\n }\n }\n cache.clear();\n });\n}\n\n/**\n * Reorder nodes to match the new order using LIS optimization.\n *\n * Uses Longest Increasing Subsequence to minimize DOM moves.\n * Only nodes that are NOT part of the LIS need to be moved.\n */\nfunction reorderNodes<T>(\n entries: CacheEntry<T>[],\n newKeys: unknown[],\n prevKeys: unknown[],\n marker: Comment,\n parent: ParentNode,\n): void {\n const len = entries.length;\n if (len === 0) return;\n\n // Skip reordering on first render - nodes are already in correct order\n if (prevKeys.length === 0) return;\n\n // Build a map of key -> old index\n const prevIndexMap = new Map<unknown, number>();\n for (let i = 0; i < prevKeys.length; i++) {\n prevIndexMap.set(prevKeys[i], i);\n }\n\n // Build array of old indices for keys that existed before (-1 for new)\n const oldIndices: number[] = [];\n let allNew = true;\n for (let i = 0; i < len; i++) {\n const oldIdx = prevIndexMap.get(newKeys[i]);\n oldIndices.push(oldIdx !== undefined ? ((allNew = false), oldIdx) : -1);\n }\n\n // Skip reordering if ALL items are new - nodes were just created in correct order\n if (allNew) {\n return;\n }\n\n // Find LIS of old indices (only considering items that existed before)\n const lisIndices = longestIncreasingSubsequence(oldIndices);\n const lisSet = new Set(lisIndices);\n\n // Move nodes that are NOT in the LIS\n // Process in reverse order, using insertBefore to place nodes\n let insertionPoint: Node | null = marker;\n\n for (let i = len - 1; i >= 0; i--) {\n const entry = entries[i]!;\n const nodes = entry.nodes;\n\n if (!lisSet.has(i)) {\n // This item needs to be moved\n for (let j = nodes.length - 1; j >= 0; j--) {\n parent.insertBefore(nodes[j]!, insertionPoint);\n }\n }\n\n // Update insertion point to the first node of current entry\n if (nodes.length > 0) {\n insertionPoint = nodes[0]!;\n }\n }\n}\n\n/**\n * Find the longest increasing subsequence indices.\n * Returns the indices in the input array that form the LIS.\n *\n * Uses binary search for O(n log n) complexity.\n * Items with value -1 (new items) are never part of the LIS.\n */\nfunction longestIncreasingSubsequence(arr: number[]): number[] {\n const len = arr.length;\n if (len === 0) return [];\n\n // tails[i] = index in arr of smallest tail element for LIS of length i+1\n const tails: number[] = [];\n // predecessor[i] = index in arr of element before arr[i] in the LIS\n const predecessor: number[] = new Array(len).fill(-1);\n\n for (let i = 0; i < len; i++) {\n const val = arr[i]!;\n // Skip new items (-1)\n if (val === -1) continue;\n\n // Binary search for position in tails\n let lo = 0;\n let hi = tails.length;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (arr[tails[mid]!]! < val) {\n lo = mid + 1;\n } else {\n hi = mid;\n }\n }\n\n // lo is the position where val should go\n if (lo > 0) {\n predecessor[i] = tails[lo - 1]!;\n }\n\n if (lo === tails.length) {\n tails.push(i);\n } else {\n tails[lo] = i;\n }\n }\n\n // Reconstruct LIS indices\n const result: number[] = [];\n let k = tails.length > 0 ? tails[tails.length - 1]! : -1;\n while (k !== -1) {\n result.push(k);\n k = predecessor[k]!;\n }\n result.reverse();\n\n return result;\n}\n"]}
1
+ {"version":3,"file":"each.js","sourceRoot":"","sources":["../../src/each.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,cAAc,GAEf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAA4B,MAAM,eAAe,CAAC;AAEnE,2CAA2C;AAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;AAE5B,gCAAgC;AAChC,MAAM,WAAW,GAAG,CAAC,CAAU,EAA2C,EAAE,CAC1E,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,UAAU,CAAC;AAyBnD;;;GAGG;AACH,SAAS,aAAa,CACpB,MAAe,EACf,KAAc,EACd,KAAa,EACb,SAAyB;IAEzB,MAAM,MAAM,GAAG,MAAM,CAAC,UAAW,CAAC;IAElC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1D,IAAI,IAAI,YAAY,QAAQ,EAAE,CAAC;YAC7B,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAC5C,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;YACnC,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACxC,CAAC;aAAM,IAAI,IAAI,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,SAAS,EAAE,CAAC;YACrD,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;YACnD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjB,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;AACH,CAAC;AA8BD,MAAM,UAAU,IAAI,CAClB,IAAuC,EACvC,eAE0C,EAC1C,aAAoE;IAEpE,MAAM,gBAAgB,GAAG,aAAa,KAAK,SAAS,CAAC;IACrD,MAAM,KAAK,GAAG,gBAAgB;QAC5B,CAAC,CAAE,eAAuD;QAC1D,CAAC,CAAC,CAAC,IAAO,EAAE,KAAa,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACnE,MAAM,QAAQ,GAAG,gBAAgB;QAC/B,CAAC,CAAC,aAAa;QACf,CAAC,CAAE,eAAwD,CAAC;IAE9D,OAAO;QACL,CAAC,IAAI,CAAC,EAAE,IAAI;QACZ,QAAQ,EAAE,IAAI;QACd,SAAS,EAAE,KAAK;QAChB,YAAY,EAAE,QAAQ;QACtB,SAAS,EAAE,gBAAgB;KAC5B,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,GAAwB,CAAC,KAAK,EAAE,EAAE;IAChD,IAAI,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,IAAI,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAExE,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE;QAC3B,MAAM,IAAI,GAAG,KAAgC,CAAC;QAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;IACpC,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,UAAU,CAAC;AAE1B;;GAEG;AACH,SAAS,QAAQ,CACf,IAAuB,EACvB,MAAe,EACf,SAAyB;IAEzB,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC9D,MAAM,MAAM,GAAG,MAAM,CAAC,UAAW,CAAC;IAElC,0BAA0B;IAC1B,MAAM,OAAO,GAAG,GAAQ,EAAE;QACxB,IAAI,OAAO,QAAQ,KAAK,UAAU,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1D,OAAQ,QAAsB,EAAE,CAAC;QACnC,CAAC;QACD,OAAO,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAE,QAAQ,CAAC,KAAa,CAAC,CAAC,CAAE,QAAgB,CAAC;IAC1E,CAAC,CAAC;IAEF,kCAAkC;IAClC,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IAEvC,sDAAsD;IACtD,MAAM,KAAK,GAAG,IAAI,GAAG,EAA0B,CAAC;IAEhD,gDAAgD;IAChD,IAAI,QAAQ,GAAc,EAAE,CAAC;IAE7B,MAAM,SAAS,GAAG,GAAG,EAAE;QACrB,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC;QACjC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAW,CAAC;QACpC,MAAM,OAAO,GAAc,EAAE,CAAC;QAC9B,MAAM,UAAU,GAAoB,EAAE,CAAC;QACvC,IAAI,mBAAmB,GAAG,KAAK,CAAC;QAEhC,sCAAsC;QACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;YACvB,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAE/B,4CAA4C;YAC5C,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtB,IAAI,CAAC,mBAAmB,EAAE,CAAC;oBACzB,OAAO,CAAC,IAAI,CAAC,yBAAyB,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;oBAChE,mBAAmB,GAAG,IAAI,CAAC;gBAC7B,CAAC;gBACD,SAAS;YACX,CAAC;YACD,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAElB,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAE9B,IAAI,SAAS,EAAE,CAAC;gBACd,qDAAqD;gBACrD,IAAI,MAAM,EAAE,CAAC;oBACX,MAAM,CAAC,UAAW,CAAC,KAAK,GAAG,IAAI,CAAC;oBAChC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;oBACnB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;oBAClB,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAC1B,CAAC;qBAAM,CAAC;oBACN,+BAA+B;oBAC/B,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;oBAChC,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC,UAAU,CAAC,CAAC;oBACtD,MAAM,KAAK,GAAW,EAAE,CAAC;oBACzB,MAAM,aAAa,GAAmB,EAAE,CAAC;oBACzC,aAAa,CACX,MAAM,EAEJ,YAID,CAAC,cAAc,EAAE,CAAC,CAAC,EACpB,KAAK,EACL,aAAa,CACd,CAAC;oBACF,MAAM,KAAK,GAAkB;wBAC3B,KAAK;wBACL,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;wBAChD,IAAI;wBACJ,UAAU;qBACX,CAAC;oBACF,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;oBACtB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;oBAClB,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACzB,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,oDAAoD;gBACpD,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;oBACnC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;oBAClB,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAC1B,CAAC;qBAAM,CAAC;oBACN,mDAAmD;oBACnD,IAAI,MAAM,EAAE,CAAC;wBACX,MAAM,CAAC,OAAO,EAAE,CAAC;wBACjB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;4BAC/B,IAAkB,CAAC,MAAM,EAAE,CAAC;wBAC/B,CAAC;wBACD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;oBACpB,CAAC;oBACD,mBAAmB;oBACnB,MAAM,KAAK,GAAW,EAAE,CAAC;oBACzB,MAAM,aAAa,GAAmB,EAAE,CAAC;oBACzC,aAAa,CACX,MAAM,EACL,YAAqD,CAAC,IAAI,EAAE,CAAC,CAAC,EAC/D,KAAK,EACL,aAAa,CACd,CAAC;oBACF,MAAM,KAAK,GAAkB;wBAC3B,KAAK;wBACL,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;wBAChD,IAAI;qBACL,CAAC;oBACF,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;oBACtB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;oBAClB,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACzB,CAAC;YACH,CAAC;QACH,CAAC;QAED,uBAAuB;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;YACjC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACvB,KAAK,CAAC,OAAO,EAAE,CAAC;gBAChB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;oBAC9B,IAAkB,CAAC,MAAM,EAAE,CAAC;gBAC/B,CAAC;gBACD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACpB,CAAC;QACH,CAAC;QAED,uCAAuC;QACvC,YAAY,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAC5D,QAAQ,GAAG,OAAO,CAAC;IACrB,CAAC,CAAC;IAEF,+BAA+B;IAC/B,SAAS,EAAE,CAAC;IACZ,MAAM,KAAK,GAAG,YAAY,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAEhD,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE;QAClB,KAAK,EAAE,CAAC;QACR,YAAY,CAAC,OAAO,EAAE,CAAC;QACvB,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACnC,KAAK,CAAC,OAAO,EAAE,CAAC;YAChB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;gBAC9B,IAAkB,CAAC,MAAM,EAAE,CAAC;YAC/B,CAAC;QACH,CAAC;QACD,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,SAAS,YAAY,CACnB,OAAwB,EACxB,OAAkB,EAClB,QAAmB,EACnB,MAAe,EACf,MAAkB;IAElB,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC;IAC3B,IAAI,GAAG,KAAK,CAAC;QAAE,OAAO;IAEtB,uEAAuE;IACvE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAElC,kCAAkC;IAClC,MAAM,YAAY,GAAG,IAAI,GAAG,EAAmB,CAAC;IAChD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACnC,CAAC;IAED,uEAAuE;IACvE,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5C,UAAU,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1E,CAAC;IAED,kFAAkF;IAClF,IAAI,MAAM,EAAE,CAAC;QACX,OAAO;IACT,CAAC;IAED,uEAAuE;IACvE,MAAM,UAAU,GAAG,4BAA4B,CAAC,UAAU,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IAEnC,qCAAqC;IACrC,8DAA8D;IAC9D,IAAI,cAAc,GAAgB,MAAM,CAAC;IAEzC,KAAK,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QAE1B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACnB,8BAA8B;YAC9B,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC3C,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,cAAc,CAAC,CAAC;gBAC/C,cAAc,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;YAC7B,CAAC;QACH,CAAC;QAED,4DAA4D;QAC5D,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,cAAc,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;QAC7B,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,4BAA4B,CAAC,GAAa;IACjD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC;IACvB,IAAI,GAAG,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEzB,yEAAyE;IACzE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,oEAAoE;IACpE,MAAM,WAAW,GAAa,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAEtD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAE,CAAC;QACpB,sBAAsB;QACtB,IAAI,GAAG,KAAK,CAAC,CAAC;YAAE,SAAS;QAEzB,sCAAsC;QACtC,IAAI,EAAE,GAAG,CAAC,CAAC;QACX,IAAI,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC;QACtB,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC;YACf,MAAM,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YAC3B,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAE,CAAE,GAAG,GAAG,EAAE,CAAC;gBAC5B,EAAE,GAAG,GAAG,GAAG,CAAC,CAAC;YACf,CAAC;iBAAM,CAAC;gBACN,EAAE,GAAG,GAAG,CAAC;YACX,CAAC;QACH,CAAC;QAED,yCAAyC;QACzC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;YACX,WAAW,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,GAAG,CAAC,CAAE,CAAC;QAClC,CAAC;QAED,IAAI,EAAE,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChB,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IAED,0BAA0B;IAC1B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzD,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QAChB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACf,CAAC,GAAG,WAAW,CAAC,CAAC,CAAE,CAAC;IACtB,CAAC;IACD,MAAM,CAAC,OAAO,EAAE,CAAC;IAEjB,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["/**\n * Keyed list rendering plugin for templates.\n *\n * Provides efficient, keyed list rendering with minimal DOM operations.\n * Supports signals, computeds, getters, and plain arrays.\n *\n * Two forms:\n * - Two-arg: uses object reference as key, render receives raw item\n * - Three-arg: uses explicit keyFn, render receives ReadonlySignal<T> for DOM reuse\n *\n * @example\n * ```ts\n * import { html as baseHtml, signal } from \"balises\";\n * import eachPlugin, { each } from \"balises/each\";\n *\n * const html = baseHtml.with(eachPlugin);\n * const items = signal([{ id: 1, name: \"Alice\" }, { id: 2, name: \"Bob\" }]);\n *\n * // Three-arg form: DOM reused when keys match\n * html`<ul>\n * ${each(items, i => i.id, itemSignal => html`<li>${() => itemSignal.value.name}</li>`)}\n * </ul>`.render();\n * ```\n */\n\nimport {\n computed,\n isSignal,\n signal,\n ReadonlySignal,\n type Reactive,\n} from \"./signals/index.js\";\nimport { Signal } from \"./signals/signal.js\";\nimport { Template, type InterpolationPlugin } from \"./template.js\";\n\n/** Marker symbol for each() descriptors */\nconst EACH = Symbol(\"each\");\n\n/** Type guard for primitives */\nconst isPrimitive = (v: unknown): v is string | number | boolean | symbol =>\n typeof v !== \"object\" && typeof v !== \"function\";\n\n/** Each descriptor - returned by each() */\nexport interface EachDescriptor<T> {\n readonly [EACH]: true;\n /** @internal List source (array, signal, or getter) */\n __list__: T[] | Reactive<T[]> | (() => T[]);\n /** @internal Key extraction function */\n __keyFn__: (item: T, index: number) => unknown;\n /** @internal Render function for each item (type varies by form) */\n __renderFn__:\n | ((item: T, index: number) => Template)\n | ((item: ReadonlySignal<T>, index: number) => Template);\n /** @internal Whether this is the three-arg (keyed) form */\n __keyed__: boolean;\n}\n\n/** Cache entry for list items */\ninterface CacheEntry<T> {\n nodes: Node[];\n dispose: () => void;\n item: T;\n itemSignal?: Signal<T>;\n}\n\n/**\n * Render content and insert nodes before marker.\n * Handles Templates and primitives.\n */\nfunction insertContent(\n marker: Comment,\n value: unknown,\n nodes: Node[],\n disposers: (() => void)[],\n): void {\n const parent = marker.parentNode!;\n\n for (const item of Array.isArray(value) ? value : [value]) {\n if (item instanceof Template) {\n const { fragment, dispose } = item.render();\n disposers.push(dispose);\n nodes.push(...fragment.childNodes);\n parent.insertBefore(fragment, marker);\n } else if (item != null && typeof item !== \"boolean\") {\n const node = document.createTextNode(String(item));\n nodes.push(node);\n parent.insertBefore(node, marker);\n }\n }\n}\n\n/**\n * Create a keyed list descriptor.\n *\n * Two-argument form: uses object reference as key (or index for primitives).\n * Render function receives the raw item. DOM is reused only when object reference matches.\n *\n * Three-argument form: uses explicit keyFn.\n * Render function receives a ReadonlySignal<T> wrapping the item.\n * DOM is reused when keys match, and the signal is updated with new item data.\n *\n * @example\n * ```ts\n * // Two-arg: object reference as key, receives raw item\n * each(items, item => html`<li>${item.name}</li>`)\n *\n * // Three-arg: explicit key, receives ReadonlySignal for DOM reuse\n * each(items, item => item.id, itemSignal => html`<li>${() => itemSignal.value.name}</li>`)\n * ```\n */\nexport function each<T>(\n list: T[] | Reactive<T[]> | (() => T[]),\n renderFn: (item: T, index: number) => Template,\n): EachDescriptor<T>;\nexport function each<T>(\n list: T[] | Reactive<T[]> | (() => T[]),\n keyFn: (item: T, index: number) => unknown,\n renderFn: (item: ReadonlySignal<T>, index: number) => Template,\n): EachDescriptor<T>;\nexport function each<T>(\n list: T[] | Reactive<T[]> | (() => T[]),\n keyFnOrRenderFn:\n | ((item: T, index: number) => unknown)\n | ((item: T, index: number) => Template),\n maybeRenderFn?: (item: ReadonlySignal<T>, index: number) => Template,\n): EachDescriptor<T> {\n const hasExplicitKeyFn = maybeRenderFn !== undefined;\n const keyFn = hasExplicitKeyFn\n ? (keyFnOrRenderFn as (item: T, index: number) => unknown)\n : (item: T, index: number) => (isPrimitive(item) ? index : item);\n const renderFn = hasExplicitKeyFn\n ? maybeRenderFn\n : (keyFnOrRenderFn as (item: T, index: number) => Template);\n\n return {\n [EACH]: true,\n __list__: list,\n __keyFn__: keyFn,\n __renderFn__: renderFn,\n __keyed__: hasExplicitKeyFn,\n };\n}\n\n/**\n * Plugin that handles each() descriptors.\n */\nconst eachPlugin: InterpolationPlugin = (value) => {\n if (!(value && typeof value === \"object\" && EACH in value)) return null;\n\n return (marker, disposers) => {\n const desc = value as EachDescriptor<unknown>;\n bindEach(desc, marker, disposers);\n };\n};\n\nexport default eachPlugin;\n\n/**\n * Bind an each() descriptor to a marker position.\n */\nfunction bindEach<T>(\n desc: EachDescriptor<T>,\n marker: Comment,\n disposers: (() => void)[],\n): void {\n const { __list__, __keyFn__, __renderFn__, __keyed__ } = desc;\n const parent = marker.parentNode!;\n\n // Get current array value\n const getList = (): T[] => {\n if (typeof __list__ === \"function\" && !isSignal(__list__)) {\n return (__list__ as () => T[])();\n }\n return isSignal(__list__) ? (__list__.value as T[]) : (__list__ as T[]);\n };\n\n // Wrap in computed for reactivity\n const listComputed = computed(getList);\n\n // Cache: key -> { nodes, dispose, item, itemSignal? }\n const cache = new Map<unknown, CacheEntry<T>>();\n\n // Track previous order for LIS-based reordering\n let prevKeys: unknown[] = [];\n\n const reconcile = () => {\n const items = listComputed.value;\n const seenKeys = new Set<unknown>();\n const newKeys: unknown[] = [];\n const newEntries: CacheEntry<T>[] = [];\n let hasDuplicateWarning = false;\n\n // Process items, create/reuse entries\n for (let i = 0; i < items.length; i++) {\n const item = items[i]!;\n const key = __keyFn__(item, i);\n\n // Warn and skip duplicates (only warn once)\n if (seenKeys.has(key)) {\n if (!hasDuplicateWarning) {\n console.warn(`[each] Duplicate key: ${String(key)}. Skipping.`);\n hasDuplicateWarning = true;\n }\n continue;\n }\n seenKeys.add(key);\n\n const cached = cache.get(key);\n\n if (__keyed__) {\n // Three-arg form: reuse if key exists, update signal\n if (cached) {\n cached.itemSignal!.value = item;\n cached.item = item;\n newKeys.push(key);\n newEntries.push(cached);\n } else {\n // Create new entry with signal\n const itemSignal = signal(item);\n const readonlySignal = new ReadonlySignal(itemSignal);\n const nodes: Node[] = [];\n const itemDisposers: (() => void)[] = [];\n insertContent(\n marker,\n (\n __renderFn__ as (\n item: ReadonlySignal<T>,\n index: number,\n ) => Template\n )(readonlySignal, i),\n nodes,\n itemDisposers,\n );\n const entry: CacheEntry<T> = {\n nodes,\n dispose: () => itemDisposers.forEach((d) => d()),\n item,\n itemSignal,\n };\n cache.set(key, entry);\n newKeys.push(key);\n newEntries.push(entry);\n }\n } else {\n // Two-arg form: reuse only if same object reference\n if (cached && cached.item === item) {\n newKeys.push(key);\n newEntries.push(cached);\n } else {\n // Dispose old entry if key exists but item changed\n if (cached) {\n cached.dispose();\n for (const node of cached.nodes) {\n (node as ChildNode).remove();\n }\n cache.delete(key);\n }\n // Create new entry\n const nodes: Node[] = [];\n const itemDisposers: (() => void)[] = [];\n insertContent(\n marker,\n (__renderFn__ as (item: T, index: number) => Template)(item, i),\n nodes,\n itemDisposers,\n );\n const entry: CacheEntry<T> = {\n nodes,\n dispose: () => itemDisposers.forEach((d) => d()),\n item,\n };\n cache.set(key, entry);\n newKeys.push(key);\n newEntries.push(entry);\n }\n }\n }\n\n // Remove stale entries\n for (const [key, entry] of cache) {\n if (!seenKeys.has(key)) {\n entry.dispose();\n for (const node of entry.nodes) {\n (node as ChildNode).remove();\n }\n cache.delete(key);\n }\n }\n\n // Reorder nodes using LIS optimization\n reorderNodes(newEntries, newKeys, prevKeys, marker, parent);\n prevKeys = newKeys;\n };\n\n // Initial render and subscribe\n reconcile();\n const unsub = listComputed.subscribe(reconcile);\n\n disposers.push(() => {\n unsub();\n listComputed.dispose();\n for (const entry of cache.values()) {\n entry.dispose();\n for (const node of entry.nodes) {\n (node as ChildNode).remove();\n }\n }\n cache.clear();\n });\n}\n\n/**\n * Reorder nodes to match the new order using LIS optimization.\n *\n * Uses Longest Increasing Subsequence to minimize DOM moves.\n * Only nodes that are NOT part of the LIS need to be moved.\n */\nfunction reorderNodes<T>(\n entries: CacheEntry<T>[],\n newKeys: unknown[],\n prevKeys: unknown[],\n marker: Comment,\n parent: ParentNode,\n): void {\n const len = entries.length;\n if (len === 0) return;\n\n // Skip reordering on first render - nodes are already in correct order\n if (prevKeys.length === 0) return;\n\n // Build a map of key -> old index\n const prevIndexMap = new Map<unknown, number>();\n for (let i = 0; i < prevKeys.length; i++) {\n prevIndexMap.set(prevKeys[i], i);\n }\n\n // Build array of old indices for keys that existed before (-1 for new)\n const oldIndices: number[] = [];\n let allNew = true;\n for (let i = 0; i < len; i++) {\n const oldIdx = prevIndexMap.get(newKeys[i]);\n oldIndices.push(oldIdx !== undefined ? ((allNew = false), oldIdx) : -1);\n }\n\n // Skip reordering if ALL items are new - nodes were just created in correct order\n if (allNew) {\n return;\n }\n\n // Find LIS of old indices (only considering items that existed before)\n const lisIndices = longestIncreasingSubsequence(oldIndices);\n const lisSet = new Set(lisIndices);\n\n // Move nodes that are NOT in the LIS\n // Process in reverse order, using insertBefore to place nodes\n let insertionPoint: Node | null = marker;\n\n for (let i = len - 1; i >= 0; i--) {\n const entry = entries[i]!;\n const nodes = entry.nodes;\n\n if (!lisSet.has(i)) {\n // This item needs to be moved\n for (let j = nodes.length - 1; j >= 0; j--) {\n parent.insertBefore(nodes[j]!, insertionPoint);\n insertionPoint = nodes[j]!;\n }\n }\n\n // Update insertion point to the first node of current entry\n if (nodes.length > 0) {\n insertionPoint = nodes[0]!;\n }\n }\n}\n\n/**\n * Find the longest increasing subsequence indices.\n * Returns the indices in the input array that form the LIS.\n *\n * Uses binary search for O(n log n) complexity.\n * Items with value -1 (new items) are never part of the LIS.\n */\nfunction longestIncreasingSubsequence(arr: number[]): number[] {\n const len = arr.length;\n if (len === 0) return [];\n\n // tails[i] = index in arr of smallest tail element for LIS of length i+1\n const tails: number[] = [];\n // predecessor[i] = index in arr of element before arr[i] in the LIS\n const predecessor: number[] = new Array(len).fill(-1);\n\n for (let i = 0; i < len; i++) {\n const val = arr[i]!;\n // Skip new items (-1)\n if (val === -1) continue;\n\n // Binary search for position in tails\n let lo = 0;\n let hi = tails.length;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (arr[tails[mid]!]! < val) {\n lo = mid + 1;\n } else {\n hi = mid;\n }\n }\n\n // lo is the position where val should go\n if (lo > 0) {\n predecessor[i] = tails[lo - 1]!;\n }\n\n if (lo === tails.length) {\n tails.push(i);\n } else {\n tails[lo] = i;\n }\n }\n\n // Reconstruct LIS indices\n const result: number[] = [];\n let k = tails.length > 0 ? tails[tails.length - 1]! : -1;\n while (k !== -1) {\n result.push(k);\n k = predecessor[k]!;\n }\n result.reverse();\n\n return result;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "balises",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "A very simple reactive html templating library",
5
5
  "author": "Julien Elbaz <elbywan@hotmail.com>",
6
6
  "type": "module",
@@ -98,6 +98,6 @@
98
98
  "typescript-eslint": "8.50.1"
99
99
  },
100
100
  "dependencies": {
101
- "balises": "0.7.1"
101
+ "balises": "0.7.2"
102
102
  }
103
103
  }