@barefootjs/client 0.26.4 → 0.28.0

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.
Files changed (44) hide show
  1. package/dist/reactive.d.ts.map +1 -1
  2. package/dist/runtime/claim-slots.d.ts +222 -0
  3. package/dist/runtime/claim-slots.d.ts.map +1 -0
  4. package/dist/runtime/component.d.ts +40 -18
  5. package/dist/runtime/component.d.ts.map +1 -1
  6. package/dist/runtime/dynamic-text.d.ts +24 -1
  7. package/dist/runtime/dynamic-text.d.ts.map +1 -1
  8. package/dist/runtime/index.d.ts +4 -5
  9. package/dist/runtime/index.d.ts.map +1 -1
  10. package/dist/runtime/index.js +467 -259
  11. package/dist/runtime/loop-markers.d.ts +26 -0
  12. package/dist/runtime/loop-markers.d.ts.map +1 -0
  13. package/dist/runtime/map-array-lazy.d.ts +164 -0
  14. package/dist/runtime/map-array-lazy.d.ts.map +1 -0
  15. package/dist/runtime/map-array.d.ts +35 -0
  16. package/dist/runtime/map-array.d.ts.map +1 -1
  17. package/dist/runtime/qsa-item.d.ts +7 -0
  18. package/dist/runtime/qsa-item.d.ts.map +1 -1
  19. package/dist/runtime/registry.d.ts.map +1 -1
  20. package/dist/runtime/standalone.js +455 -248
  21. package/package.json +2 -2
  22. package/src/reactive.ts +2 -1
  23. package/src/runtime/claim-slots.ts +647 -0
  24. package/src/runtime/component.ts +153 -70
  25. package/src/runtime/dynamic-text.ts +24 -1
  26. package/src/runtime/index.ts +20 -7
  27. package/src/runtime/insert.ts +1 -1
  28. package/src/runtime/loop-markers.ts +100 -0
  29. package/src/runtime/map-array-lazy.ts +470 -0
  30. package/src/runtime/map-array.ts +68 -11
  31. package/src/runtime/qsa-item.ts +9 -3
  32. package/src/runtime/registry.ts +5 -3
  33. package/dist/runtime/client-marker.d.ts +0 -21
  34. package/dist/runtime/client-marker.d.ts.map +0 -1
  35. package/dist/runtime/list.d.ts +0 -21
  36. package/dist/runtime/list.d.ts.map +0 -1
  37. package/dist/runtime/patch-slot-range.d.ts +0 -47
  38. package/dist/runtime/patch-slot-range.d.ts.map +0 -1
  39. package/dist/runtime/reconcile-elements.d.ts +0 -44
  40. package/dist/runtime/reconcile-elements.d.ts.map +0 -1
  41. package/src/runtime/client-marker.ts +0 -46
  42. package/src/runtime/list.ts +0 -47
  43. package/src/runtime/patch-slot-range.ts +0 -105
  44. package/src/runtime/reconcile-elements.ts +0 -391
@@ -1550,9 +1550,7 @@ function upsertChild(parent, name, slotId, props, key, anchorScope) {
1550
1550
  const ph = parent.getAttribute(BF_PLACEHOLDER) === phId ? parent : parent.querySelector(`[${BF_PLACEHOLDER}="${phId}"]`);
1551
1551
  if (ph) {
1552
1552
  const slot = slotId ? buildSlotInfo(parent, slotId, anchorScope) : undefined;
1553
- const comp = createComponent(name, props, key, slot);
1554
- ph.replaceWith(comp);
1555
- return comp;
1553
+ return createComponent(name, props, key, slot, ph);
1556
1554
  }
1557
1555
  return null;
1558
1556
  }
@@ -1758,13 +1756,30 @@ var _parentScopeId = null;
1758
1756
  function setParentScopeId(id) {
1759
1757
  _parentScopeId = id;
1760
1758
  }
1761
- var propsUpdateMap = new WeakMap;
1762
- var propsMap = new WeakMap;
1763
- function createComponent(nameOrDef, props = {}, key, slot) {
1759
+ var _rowMountPoint = null;
1760
+ function setRowMountPoint(p) {
1761
+ const prev = _rowMountPoint;
1762
+ _rowMountPoint = p;
1763
+ return prev;
1764
+ }
1765
+ function takeRowMountPoint() {
1766
+ const p = _rowMountPoint;
1767
+ _rowMountPoint = null;
1768
+ return p;
1769
+ }
1770
+ function createComponent(nameOrDef, props = {}, key, slot, mountAt) {
1771
+ const element = materializeComponent(nameOrDef, props, key, slot, mountAt);
1772
+ if (mountAt && mountAt.parentNode && element !== mountAt) {
1773
+ mountAt.replaceWith(element);
1774
+ }
1775
+ return element;
1776
+ }
1777
+ function materializeComponent(nameOrDef, props = {}, key, slot, mountAt) {
1764
1778
  if (props == null)
1765
1779
  props = {};
1780
+ const rowMount = mountAt ? null : takeRowMountPoint();
1766
1781
  if (typeof nameOrDef !== "string") {
1767
- return createComponentFromDef(nameOrDef, props, key);
1782
+ return createComponentFromDef(nameOrDef, props, key, mountAt, rowMount);
1768
1783
  }
1769
1784
  const name = nameOrDef;
1770
1785
  const templateFn = getTemplate(name);
@@ -1824,8 +1839,13 @@ function createComponent(nameOrDef, props = {}, key, slot) {
1824
1839
  if (key !== undefined) {
1825
1840
  element.setAttribute(BF_KEY, String(key));
1826
1841
  }
1827
- const prevScope = setCurrentScope(element);
1828
1842
  const rootIsDeferredPlaceholder = element.hasAttribute(BF_PLACEHOLDER);
1843
+ if (mountAt && !rootIsDeferredPlaceholder) {
1844
+ mountAt.replaceWith(element);
1845
+ } else if (rowMount && !rootIsDeferredPlaceholder) {
1846
+ rowMount.container.insertBefore(element, rowMount.anchor);
1847
+ }
1848
+ const prevScope = setCurrentScope(element);
1829
1849
  let placeholderWrapper = null;
1830
1850
  if (rootIsDeferredPlaceholder) {
1831
1851
  placeholderWrapper = parseHTML("<div></div>").firstChild;
@@ -1851,24 +1871,8 @@ function createComponent(nameOrDef, props = {}, key, slot) {
1851
1871
  }
1852
1872
  setCurrentScope(prevScope);
1853
1873
  hydratedScopes.add(element);
1854
- propsMap.set(element, props);
1855
- registerPropsUpdate(element, name, props);
1856
1874
  return element;
1857
1875
  }
1858
- function getComponentProps(element) {
1859
- return propsMap.get(element);
1860
- }
1861
- function registerPropsUpdate(element, name, _initialProps) {
1862
- propsUpdateMap.set(element, (newProps) => {
1863
- const init = getComponentInit(name);
1864
- if (init) {
1865
- init(element, newProps);
1866
- }
1867
- });
1868
- }
1869
- function getPropsUpdateFn(element) {
1870
- return propsUpdateMap.get(element);
1871
- }
1872
1876
  function renderChild(name, props, key, slotSuffix) {
1873
1877
  const templateFn = getTemplate(name);
1874
1878
  const suffix = slotSuffix ? `_${slotSuffix}` : "";
@@ -1934,6 +1938,11 @@ function escapeText(value) {
1934
1938
  return "";
1935
1939
  return escapeAttr(value);
1936
1940
  }
1941
+ function escapeTextOrNode(value) {
1942
+ if (typeof Node !== "undefined" && value instanceof Node)
1943
+ return value;
1944
+ return escapeText(value);
1945
+ }
1937
1946
  var SVG_NS = "http://www.w3.org/2000/svg";
1938
1947
  function parseHTML(html, parent) {
1939
1948
  const tpl = document.createElement("template");
@@ -1977,7 +1986,7 @@ function insertGetterChildren(element, children) {
1977
1986
  element.appendChild(document.createTextNode(String(children)));
1978
1987
  }
1979
1988
  }
1980
- function createComponentFromDef(def, props, key) {
1989
+ function createComponentFromDef(def, props, key, mountAt, rowMount) {
1981
1990
  if (!def.template) {
1982
1991
  throw new Error("[BarefootJS] createComponent with ComponentDef requires a template function");
1983
1992
  }
@@ -1996,9 +2005,13 @@ function createComponentFromDef(def, props, key) {
1996
2005
  if (key !== undefined) {
1997
2006
  element.setAttribute(BF_KEY, String(key));
1998
2007
  }
2008
+ if (mountAt) {
2009
+ mountAt.replaceWith(element);
2010
+ } else if (rowMount) {
2011
+ rowMount.container.insertBefore(element, rowMount.anchor);
2012
+ }
1999
2013
  def.init(element, props);
2000
2014
  hydratedScopes.add(element);
2001
- propsMap.set(element, props);
2002
2015
  return element;
2003
2016
  }
2004
2017
 
@@ -2056,7 +2069,7 @@ function createPortal(children, container = document.body, options) {
2056
2069
  }
2057
2070
  };
2058
2071
  }
2059
- // src/runtime/reconcile-elements.ts
2072
+ // src/runtime/loop-markers.ts
2060
2073
  function findLoopMarkers(container, markerId) {
2061
2074
  let startMarker = null;
2062
2075
  let endMarker = null;
@@ -2101,14 +2114,6 @@ function getElementsBetweenMarkers(start, end) {
2101
2114
  }
2102
2115
  return elements;
2103
2116
  }
2104
- function removeElementsBetweenMarkers(start, end) {
2105
- let node = start.nextSibling;
2106
- while (node && node !== end) {
2107
- const next = node.nextSibling;
2108
- node.parentNode?.removeChild(node);
2109
- node = next;
2110
- }
2111
- }
2112
2117
  function getLoopChildren(container, markerId) {
2113
2118
  const { startMarker, endMarker } = findLoopMarkers(container, markerId);
2114
2119
  if (startMarker && endMarker) {
@@ -2129,159 +2134,6 @@ function getLoopNodes(container, markerId) {
2129
2134
  }
2130
2135
  return Array.from(container.childNodes);
2131
2136
  }
2132
- function reconcileElements(container, items, getKey, renderItem, firstElement, markerId) {
2133
- if (!container || !items)
2134
- return;
2135
- const { startMarker, endMarker } = findLoopMarkers(container, markerId);
2136
- const existingByKey = new Map;
2137
- let hasKeyedChildren = false;
2138
- const loopChildren = startMarker ? getElementsBetweenMarkers(startMarker, endMarker) : Array.from(container.children);
2139
- for (const child of loopChildren) {
2140
- const el = child;
2141
- const key = el.dataset?.key;
2142
- if (key !== undefined) {
2143
- existingByKey.set(key, el);
2144
- hasKeyedChildren = true;
2145
- }
2146
- }
2147
- if (!hasKeyedChildren) {
2148
- if (items.length === 0) {
2149
- if (startMarker) {
2150
- removeElementsBetweenMarkers(startMarker, endMarker);
2151
- } else {
2152
- container.innerHTML = "";
2153
- }
2154
- return;
2155
- }
2156
- const fragment = document.createDocumentFragment();
2157
- for (let i = 0;i < items.length; i++) {
2158
- const el = i === 0 && firstElement ? firstElement : renderItem(items[i], i);
2159
- const key = getKey ? getKey(items[i], i) : String(i);
2160
- if (!el.dataset.key)
2161
- el.setAttribute(BF_KEY, key);
2162
- fragment.appendChild(el);
2163
- }
2164
- if (startMarker) {
2165
- removeElementsBetweenMarkers(startMarker, endMarker);
2166
- endMarker.parentNode.insertBefore(fragment, endMarker);
2167
- } else {
2168
- container.innerHTML = "";
2169
- container.appendChild(fragment);
2170
- }
2171
- return;
2172
- }
2173
- let insertAnchor = endMarker ?? null;
2174
- if (!startMarker) {
2175
- let foundKeyed = false;
2176
- for (const child of Array.from(container.childNodes)) {
2177
- if (child.nodeType === Node.ELEMENT_NODE && child.dataset.key !== undefined) {
2178
- foundKeyed = true;
2179
- } else if (foundKeyed) {
2180
- insertAnchor = child;
2181
- break;
2182
- }
2183
- }
2184
- }
2185
- let focusedKey = null;
2186
- const activeEl = document.activeElement;
2187
- if (activeEl && activeEl !== document.body) {
2188
- const tag = activeEl.tagName;
2189
- if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || activeEl.isContentEditable) {
2190
- for (const [key, el] of existingByKey) {
2191
- if (el.contains(activeEl)) {
2192
- focusedKey = key;
2193
- break;
2194
- }
2195
- }
2196
- }
2197
- }
2198
- const desiredElements = [];
2199
- const toRemove = [];
2200
- let focusTarget = null;
2201
- for (let i = 0;i < items.length; i++) {
2202
- const item = items[i];
2203
- const key = getKey ? getKey(item, i) : String(i);
2204
- const createEl = () => i === 0 && firstElement ? firstElement : renderItem(item, i);
2205
- const existing = existingByKey.get(key);
2206
- if (existing) {
2207
- existingByKey.delete(key);
2208
- if (existing.getAttribute(BF_SCOPE) && !hydratedScopes.has(existing)) {
2209
- const newEl = createEl();
2210
- if (!newEl.dataset.key)
2211
- newEl.setAttribute(BF_KEY, key);
2212
- desiredElements.push(newEl);
2213
- toRemove.push(existing);
2214
- } else if (focusedKey === key) {
2215
- const newEl = createEl();
2216
- if (!newEl.dataset.key)
2217
- newEl.setAttribute(BF_KEY, key);
2218
- focusTarget = prepareInputTransfer(existing, newEl);
2219
- desiredElements.push(newEl);
2220
- toRemove.push(existing);
2221
- } else {
2222
- const newEl = createEl();
2223
- if (!newEl.dataset.key)
2224
- newEl.setAttribute(BF_KEY, key);
2225
- desiredElements.push(newEl);
2226
- toRemove.push(existing);
2227
- }
2228
- } else {
2229
- const el = createEl();
2230
- if (!el.dataset.key)
2231
- el.setAttribute(BF_KEY, key);
2232
- desiredElements.push(el);
2233
- }
2234
- }
2235
- for (const el of existingByKey.values()) {
2236
- toRemove.push(el);
2237
- }
2238
- for (const el of toRemove) {
2239
- if (el.parentNode)
2240
- el.remove();
2241
- }
2242
- for (const el of desiredElements) {
2243
- container.insertBefore(el, insertAnchor);
2244
- }
2245
- if (focusTarget) {
2246
- focusTarget.target.focus();
2247
- if (typeof focusTarget.selectionStart === "number") {
2248
- focusTarget.target.selectionStart = focusTarget.selectionStart;
2249
- focusTarget.target.selectionEnd = focusTarget.selectionEnd;
2250
- }
2251
- }
2252
- }
2253
- function prepareInputTransfer(oldEl, newEl) {
2254
- const focused = oldEl.contains(document.activeElement) ? document.activeElement : null;
2255
- if (!focused)
2256
- return null;
2257
- const tag = focused.tagName;
2258
- const oldInputs = Array.from(oldEl.querySelectorAll(tag));
2259
- const idx = oldInputs.indexOf(focused);
2260
- if (idx < 0)
2261
- return null;
2262
- const newInputs = Array.from(newEl.querySelectorAll(tag));
2263
- const target = newInputs[idx];
2264
- if (!target)
2265
- return null;
2266
- target.value = focused.value;
2267
- return {
2268
- target,
2269
- selectionStart: focused.selectionStart,
2270
- selectionEnd: focused.selectionEnd
2271
- };
2272
- }
2273
-
2274
- // src/runtime/list.ts
2275
- function reconcileList(container, items, getKey, renderItem) {
2276
- if (!container || !items)
2277
- return;
2278
- if (items.length === 0) {
2279
- container.innerHTML = "";
2280
- return;
2281
- }
2282
- const firstElement = renderItem(items[0], 0);
2283
- reconcileElements(container, items, getKey, renderItem, firstElement);
2284
- }
2285
2137
  // src/runtime/qsa-item.ts
2286
2138
  function* itemRootElements(primaryEl) {
2287
2139
  yield primaryEl;
@@ -2338,9 +2190,7 @@ function upsertChildItem(primaryEl, name, slotId, props, key, anchorScope) {
2338
2190
  const ph = qsaItem(primaryEl, `[data-bf-ph="${phId}"]`);
2339
2191
  if (ph) {
2340
2192
  const slot = slotId ? buildSlotInfo(primaryEl, slotId, anchorScope) : undefined;
2341
- const comp = createComponent(name, props, key, slot);
2342
- ph.replaceWith(comp);
2343
- return comp;
2193
+ return createComponent(name, props, key, slot, ph);
2344
2194
  }
2345
2195
  return null;
2346
2196
  }
@@ -2452,7 +2302,7 @@ function removeScope(scope) {
2452
2302
  ex.remove();
2453
2303
  }
2454
2304
  }
2455
- function createItemScope(item, index, renderItem, existingPrimary, existingExtras, existingStart) {
2305
+ function createItemScope(item, index, renderItem, existingPrimary, existingExtras, existingStart, rowMount) {
2456
2306
  let primaryEl;
2457
2307
  let dispose;
2458
2308
  let setItem;
@@ -2462,7 +2312,14 @@ function createItemScope(item, index, renderItem, existingPrimary, existingExtra
2462
2312
  dispose = d;
2463
2313
  const [itemAccessor, itemSetter] = createSignal(item);
2464
2314
  setItem = itemSetter;
2465
- primaryEl = renderItem(itemAccessor, index, existingPrimary);
2315
+ const ownsRowMount = !existingPrimary && !!rowMount;
2316
+ const prevRowMount = ownsRowMount ? setRowMountPoint(rowMount) : null;
2317
+ try {
2318
+ primaryEl = renderItem(itemAccessor, index, existingPrimary);
2319
+ } finally {
2320
+ if (ownsRowMount)
2321
+ setRowMountPoint(prevRowMount);
2322
+ }
2466
2323
  if (existingPrimary) {
2467
2324
  extras = existingExtras ?? [];
2468
2325
  startMarker = existingStart ?? null;
@@ -2476,6 +2333,9 @@ function createItemScope(item, index, renderItem, existingPrimary, existingExtra
2476
2333
  }
2477
2334
  return;
2478
2335
  });
2336
+ if (rowMount && !existingPrimary && extras.length > 0 && primaryEl.parentNode) {
2337
+ primaryEl.remove();
2338
+ }
2479
2339
  return { startMarker, primaryEl, extras, dispose, setItem };
2480
2340
  }
2481
2341
  function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
@@ -2517,7 +2377,7 @@ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2517
2377
  for (let i2 = existingRanges.length;i2 < items.length; i2++) {
2518
2378
  const item = items[i2];
2519
2379
  const key = getKey ? getKey(item, i2) : String(i2);
2520
- const scope = createItemScope(item, i2, renderItem);
2380
+ const scope = createItemScope(item, i2, renderItem, undefined, undefined, undefined, { container, anchor });
2521
2381
  if (!scope.primaryEl.dataset.key)
2522
2382
  scope.primaryEl.setAttribute(BF_KEY, key);
2523
2383
  scopes.set(key, scope);
@@ -2596,7 +2456,7 @@ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2596
2456
  existing.setItem(item);
2597
2457
  desiredOrder.push(existing);
2598
2458
  } else {
2599
- const scope = createItemScope(item, i2, renderItem);
2459
+ const scope = createItemScope(item, i2, renderItem, undefined, undefined, undefined, { container, anchor });
2600
2460
  if (!scope.primaryEl.dataset.key)
2601
2461
  scope.primaryEl.setAttribute(BF_KEY, key);
2602
2462
  scopes.set(key, scope);
@@ -2792,6 +2652,197 @@ function mapArrayAnchored(accessor, container, getKey, renderItem, markerId, bfI
2792
2652
  }
2793
2653
  }, bfId);
2794
2654
  }
2655
+ // src/runtime/map-array-lazy.ts
2656
+ function mapArrayLazy(accessor, container, getKey, plan, markerId, bfId) {
2657
+ if (!container)
2658
+ return;
2659
+ const entries = new Map;
2660
+ let entryList = [];
2661
+ let hydrated = false;
2662
+ const needsResubscribe = plan.applyOuter !== undefined;
2663
+ const [generation, bumpGeneration] = needsResubscribe ? createSignal(0) : [null, null];
2664
+ let stranded = false;
2665
+ const markStranded = () => {
2666
+ if (needsResubscribe)
2667
+ stranded = true;
2668
+ };
2669
+ const flushStranded = () => {
2670
+ if (stranded && bumpGeneration) {
2671
+ stranded = false;
2672
+ bumpGeneration((n) => n + 1);
2673
+ }
2674
+ };
2675
+ let cachedStart = null;
2676
+ let cachedEnd = null;
2677
+ const resolveMarkers = () => {
2678
+ if (cachedStart && cachedEnd && cachedStart.isConnected && cachedEnd.isConnected) {
2679
+ return { start: cachedStart, end: cachedEnd };
2680
+ }
2681
+ const found = findLoopMarkers2(container, markerId);
2682
+ cachedStart = found.start;
2683
+ cachedEnd = found.end;
2684
+ return found;
2685
+ };
2686
+ const createEntry = (item, index, key) => {
2687
+ const entry = {
2688
+ key,
2689
+ primaryEl: undefined,
2690
+ item,
2691
+ refs: null,
2692
+ last: null
2693
+ };
2694
+ entry.primaryEl = untrack(() => plan.createRow(entry, index));
2695
+ if (!entry.primaryEl.dataset.key)
2696
+ entry.primaryEl.setAttribute(BF_KEY, key);
2697
+ markStranded();
2698
+ return entry;
2699
+ };
2700
+ createEffect(() => {
2701
+ const items = accessor();
2702
+ if (!items)
2703
+ return;
2704
+ const { start: startMarker, end: endMarker } = resolveMarkers();
2705
+ const anchor = endMarker ?? null;
2706
+ if (!hydrated) {
2707
+ hydrated = true;
2708
+ const doms = [];
2709
+ for (let node = startMarker ? startMarker.nextSibling : container.firstChild;node && node !== anchor; node = node.nextSibling) {
2710
+ if (node.nodeType === Node.ELEMENT_NODE)
2711
+ doms.push(node);
2712
+ }
2713
+ if (doms.length > 0 && entries.size === 0) {
2714
+ const list = [];
2715
+ const shared = Math.min(doms.length, items.length);
2716
+ for (let i2 = 0;i2 < shared; i2++) {
2717
+ const el = doms[i2];
2718
+ const ssrKey = el.getAttribute(BF_KEY);
2719
+ const key = ssrKey !== null ? ssrKey : getKey ? getKey(items[i2], i2) : String(i2);
2720
+ const entry = { key, primaryEl: el, item: items[i2], refs: null, last: null };
2721
+ entries.set(key, entry);
2722
+ list.push(entry);
2723
+ }
2724
+ for (let i2 = doms.length;i2 < items.length; i2++) {
2725
+ const item = items[i2];
2726
+ const key = getKey ? getKey(item, i2) : String(i2);
2727
+ const entry = createEntry(item, i2, key);
2728
+ entries.set(key, entry);
2729
+ list.push(entry);
2730
+ container.insertBefore(entry.primaryEl, anchor);
2731
+ }
2732
+ for (let i2 = items.length;i2 < doms.length; i2++)
2733
+ doms[i2].remove();
2734
+ entryList = list;
2735
+ flushStranded();
2736
+ return;
2737
+ }
2738
+ }
2739
+ if (items.length === 0) {
2740
+ if (entries.size > 0) {
2741
+ if (startMarker && endMarker) {
2742
+ const range = document.createRange();
2743
+ range.setStartAfter(startMarker);
2744
+ range.setEndBefore(endMarker);
2745
+ range.deleteContents();
2746
+ } else {
2747
+ let actualNodeCount = 0;
2748
+ for (let node = container.firstChild;node; node = node.nextSibling)
2749
+ actualNodeCount++;
2750
+ if (actualNodeCount === entries.size) {
2751
+ container.textContent = "";
2752
+ } else {
2753
+ for (const entry of entries.values())
2754
+ entry.primaryEl.remove();
2755
+ }
2756
+ }
2757
+ entries.clear();
2758
+ entryList = [];
2759
+ }
2760
+ return;
2761
+ }
2762
+ const newKeys = new Set;
2763
+ const warnedKeys = new Set;
2764
+ const desiredOrder = [];
2765
+ for (let i2 = 0;i2 < items.length; i2++) {
2766
+ const item = items[i2];
2767
+ const key = getKey ? getKey(item, i2) : String(i2);
2768
+ if (newKeys.has(key) && !warnedKeys.has(key)) {
2769
+ warnedKeys.add(key);
2770
+ console.warn(`[BarefootJS] mapArrayLazy: duplicate key "${key}" — items with this key collapse to a single DOM row, ` + `so only the last one renders. Use a per-item identifier (e.g. \`key={item.id}\`) for correct reconciliation.`);
2771
+ }
2772
+ newKeys.add(key);
2773
+ const existing = entries.get(key);
2774
+ if (existing) {
2775
+ if (!Object.is(existing.item, item)) {
2776
+ const prevItem = existing.item;
2777
+ existing.item = item;
2778
+ untrack(() => plan.applyItem(existing, prevItem));
2779
+ markStranded();
2780
+ }
2781
+ desiredOrder.push(existing);
2782
+ } else {
2783
+ const entry = createEntry(item, i2, key);
2784
+ entries.set(key, entry);
2785
+ desiredOrder.push(entry);
2786
+ }
2787
+ }
2788
+ for (const [key, entry] of entries) {
2789
+ if (!newKeys.has(key)) {
2790
+ if (entry.primaryEl.parentNode)
2791
+ entry.primaryEl.remove();
2792
+ entries.delete(key);
2793
+ }
2794
+ }
2795
+ const primaryElToDesiredIndex = new Map;
2796
+ for (let i2 = 0;i2 < desiredOrder.length; i2++) {
2797
+ primaryElToDesiredIndex.set(desiredOrder[i2].primaryEl, i2);
2798
+ }
2799
+ const domOrderIndices = [];
2800
+ for (let node = startMarker ? startMarker.nextSibling : container.firstChild;node && node !== anchor; node = node.nextSibling) {
2801
+ if (node.nodeType !== Node.ELEMENT_NODE)
2802
+ continue;
2803
+ const idx = primaryElToDesiredIndex.get(node);
2804
+ if (idx !== undefined)
2805
+ domOrderIndices.push(idx);
2806
+ }
2807
+ const stationary = new Array(desiredOrder.length).fill(false);
2808
+ for (const pos of longestIncreasingSubsequenceIndices(domOrderIndices)) {
2809
+ stationary[domOrderIndices[pos]] = true;
2810
+ }
2811
+ let i = 0;
2812
+ while (i < desiredOrder.length) {
2813
+ if (stationary[i]) {
2814
+ i++;
2815
+ continue;
2816
+ }
2817
+ let j = i;
2818
+ while (j < desiredOrder.length && !stationary[j])
2819
+ j++;
2820
+ const before = j < desiredOrder.length ? desiredOrder[j].primaryEl : anchor;
2821
+ if (j - i === 1) {
2822
+ container.insertBefore(desiredOrder[i].primaryEl, before);
2823
+ } else {
2824
+ const runFragment = document.createDocumentFragment();
2825
+ for (let k = i;k < j; k++)
2826
+ runFragment.appendChild(desiredOrder[k].primaryEl);
2827
+ container.insertBefore(runFragment, before);
2828
+ }
2829
+ i = j;
2830
+ }
2831
+ entryList = desiredOrder;
2832
+ flushStranded();
2833
+ }, bfId);
2834
+ if (plan.applyOuter) {
2835
+ const applyOuter = plan.applyOuter.bind(plan);
2836
+ let seed = true;
2837
+ createEffect(() => {
2838
+ if (generation)
2839
+ generation();
2840
+ const isSeed = seed;
2841
+ seed = false;
2842
+ applyOuter(entryList, isSeed);
2843
+ });
2844
+ }
2845
+ }
2795
2846
  // src/runtime/patch-leaf.ts
2796
2847
  function patchLeaf(el, html) {
2797
2848
  const tpl = document.createElement("template");
@@ -2817,35 +2868,44 @@ function patchLeaf(el, html) {
2817
2868
  }
2818
2869
  el.replaceChildren(...Array.from(next.childNodes));
2819
2870
  }
2820
- // src/runtime/patch-slot-range.ts
2821
- function patchSlotRange(scope, id, html) {
2871
+ // src/runtime/claim-slots.ts
2872
+ function resolvePath(root, path) {
2873
+ let node = root;
2874
+ for (const index of path) {
2875
+ const child = node.childNodes[index];
2876
+ if (!child)
2877
+ return null;
2878
+ node = child;
2879
+ }
2880
+ return node;
2881
+ }
2882
+ function isSlotComment(node, id) {
2883
+ return node != null && node.nodeType === Node.COMMENT_NODE && node.nodeValue === `bf:${id}`;
2884
+ }
2885
+ function findOwnedMarker(root, id) {
2822
2886
  const marker = `bf:${id}`;
2823
- let start = null;
2824
- const walker = document.createTreeWalker(scope, NodeFilter.SHOW_COMMENT);
2825
- while (walker.nextNode()) {
2826
- const comment = walker.currentNode;
2887
+ const parentOwned = id.startsWith(BF_PARENT_OWNED_PREFIX);
2888
+ const registryInfo = commentScopeRegistry.get(root);
2889
+ const boundary = registryInfo ? registryInfo.commentNode.parentElement : root;
2890
+ for (const comment of commentsInScope(root)) {
2827
2891
  if (comment.nodeValue !== marker)
2828
2892
  continue;
2893
+ if (parentOwned)
2894
+ return comment;
2829
2895
  let owned = true;
2830
- for (let el = comment.parentElement;el && el !== scope; el = el.parentElement) {
2896
+ for (let el = comment.parentElement;el && el !== boundary; el = el.parentElement) {
2831
2897
  if (el.hasAttribute(BF_SCOPE)) {
2832
2898
  owned = false;
2833
2899
  break;
2834
2900
  }
2835
2901
  }
2836
- if (owned) {
2837
- start = comment;
2838
- break;
2839
- }
2840
- }
2841
- const parent = start?.parentNode;
2842
- if (!start || !parent) {
2843
- console.warn(`[barefootjs] preamble region marker bf:${id} not found in row; skipping patch`);
2844
- return;
2902
+ if (owned)
2903
+ return comment;
2845
2904
  }
2905
+ return null;
2906
+ }
2907
+ function findMarkupEnd(start) {
2846
2908
  let depth = 0;
2847
- let end = null;
2848
- const toRemove = [];
2849
2909
  let node = start.nextSibling;
2850
2910
  while (node) {
2851
2911
  if (node.nodeType === Node.COMMENT_NODE) {
@@ -2853,25 +2913,192 @@ function patchSlotRange(scope, id, html) {
2853
2913
  if (value.startsWith("bf:")) {
2854
2914
  depth++;
2855
2915
  } else if (value === "/") {
2856
- if (depth === 0) {
2857
- end = node;
2858
- break;
2859
- }
2916
+ if (depth === 0)
2917
+ return node;
2860
2918
  depth--;
2861
2919
  }
2862
2920
  }
2863
- toRemove.push(node);
2864
2921
  node = node.nextSibling;
2865
2922
  }
2923
+ return null;
2924
+ }
2925
+ function resolveAnchor(root, spec) {
2926
+ if (spec.path.length > 0) {
2927
+ const resolved = resolvePath(root, spec.path);
2928
+ if (isSlotComment(resolved, spec.id))
2929
+ return resolved;
2930
+ console.warn(`[barefootjs] claim path for slot ${spec.id} did not resolve to its bf:${spec.id} marker; falling back to a scan`);
2931
+ }
2932
+ const found = findOwnedMarker(root, spec.id);
2933
+ if (!found) {
2934
+ console.warn(`[barefootjs] slot ${spec.id} marker not found; skipping`);
2935
+ }
2936
+ return found;
2937
+ }
2938
+ function claimMarkerlessText(root, spec) {
2939
+ if (spec.path.length === 0) {
2940
+ console.warn(`[barefootjs] markerless slot ${spec.id} has an empty path; skipping`);
2941
+ return null;
2942
+ }
2943
+ const parentPath = spec.path.slice(0, -1);
2944
+ const idx = spec.path[spec.path.length - 1];
2945
+ const parent = resolvePath(root, parentPath);
2946
+ if (!parent) {
2947
+ console.warn(`[barefootjs] markerless claim path for slot ${spec.id} did not resolve to a parent node; skipping`);
2948
+ return null;
2949
+ }
2950
+ const existing = parent.childNodes[idx];
2951
+ if (existing && existing.nodeType === Node.TEXT_NODE) {
2952
+ return { kind: "text", ref: existing };
2953
+ }
2954
+ const node = document.createTextNode("");
2955
+ parent.insertBefore(node, existing ?? null);
2956
+ return { kind: "text", ref: node };
2957
+ }
2958
+ function claimOne(root, spec) {
2959
+ if (spec.kind === "text" && spec.markerless) {
2960
+ return claimMarkerlessText(root, spec);
2961
+ }
2962
+ const anchor = resolveAnchor(root, spec);
2963
+ if (!anchor)
2964
+ return null;
2965
+ if (spec.kind === "text") {
2966
+ const next = anchor.nextSibling;
2967
+ return { kind: "text", ref: next?.nodeType === Node.TEXT_NODE ? next : anchor };
2968
+ }
2969
+ const end = findMarkupEnd(anchor);
2970
+ if (!end) {
2971
+ console.warn(`[barefootjs] slot ${spec.id} has no end marker; skipping`);
2972
+ return null;
2973
+ }
2974
+ return { kind: "markup", start: anchor, end, last: undefined };
2975
+ }
2976
+ function materializeText(slot) {
2977
+ const anchor = slot.ref;
2978
+ const parent = anchor.parentNode;
2979
+ if (!parent)
2980
+ return null;
2981
+ const node = document.createTextNode("");
2982
+ parent.insertBefore(node, anchor.nextSibling);
2983
+ slot.ref = node;
2984
+ return node;
2985
+ }
2986
+ function readText(slot) {
2987
+ return slot.ref.nodeType === Node.TEXT_NODE ? slot.ref.nodeValue ?? "" : "";
2988
+ }
2989
+ function writeText(slot, value) {
2990
+ const node = slot.ref.nodeType === Node.TEXT_NODE ? slot.ref : materializeText(slot);
2991
+ if (!node)
2992
+ return;
2993
+ node.nodeValue = String(value ?? "");
2994
+ }
2995
+ function textOrNode(value) {
2996
+ if (typeof Node !== "undefined" && value instanceof Node)
2997
+ return value;
2998
+ return String(value);
2999
+ }
3000
+ function promoteTextToMarkup(slot, id) {
3001
+ const anchor = slot.ref.nodeType === Node.COMMENT_NODE ? slot.ref : isSlotComment(slot.ref.previousSibling, id) ? slot.ref.previousSibling : null;
3002
+ if (!anchor) {
3003
+ console.warn(`[barefootjs] slot ${id} was claimed as text and received a Node, but has no anchor marker to promote from; write ignored`);
3004
+ return null;
3005
+ }
3006
+ const end = findMarkupEnd(anchor);
2866
3007
  if (!end) {
2867
- console.warn(`[barefootjs] preamble region bf:${id} has no end marker; skipping patch`);
3008
+ console.warn(`[barefootjs] slot ${id} was claimed as text and received a Node, but has no end marker; write ignored`);
3009
+ return null;
3010
+ }
3011
+ return { kind: "markup", start: anchor, end, last: undefined };
3012
+ }
3013
+ function clearMarkupRange(start, end) {
3014
+ const parent = end.parentNode;
3015
+ if (!parent)
2868
3016
  return;
3017
+ let node = start.nextSibling;
3018
+ while (node && node !== end) {
3019
+ const next = node.nextSibling;
3020
+ parent.removeChild(node);
3021
+ node = next;
2869
3022
  }
2870
- for (const n of toRemove)
2871
- parent.removeChild(n);
3023
+ }
3024
+ function writeMarkup(ref, value) {
3025
+ const { start, end } = ref;
3026
+ const parent = end.parentNode;
3027
+ if (!parent)
3028
+ return;
3029
+ if (value != null && value.__isSlot)
3030
+ return;
3031
+ if (typeof Node !== "undefined" && value instanceof Node) {
3032
+ if (value === ref.last)
3033
+ return;
3034
+ clearMarkupRange(start, end);
3035
+ parent.insertBefore(value, end);
3036
+ ref.last = value;
3037
+ return;
3038
+ }
3039
+ const text = String(value ?? "");
3040
+ if (text === ref.last)
3041
+ return;
3042
+ clearMarkupRange(start, end);
2872
3043
  const tpl = document.createElement("template");
2873
- tpl.innerHTML = html;
3044
+ tpl.innerHTML = text;
2874
3045
  parent.insertBefore(tpl.content, end);
3046
+ ref.last = text;
3047
+ }
3048
+ function writeSlot(refs, id, value) {
3049
+ const ref = refs.get(id);
3050
+ if (!ref) {
3051
+ console.warn(`[barefootjs] no claimed slot for id ${id}; write ignored`);
3052
+ return;
3053
+ }
3054
+ if (ref.kind === "text") {
3055
+ if (typeof Node !== "undefined" && value instanceof Node) {
3056
+ const promoted = promoteTextToMarkup(ref, id);
3057
+ if (!promoted)
3058
+ return;
3059
+ refs.set(id, promoted);
3060
+ writeMarkup(promoted, value);
3061
+ return;
3062
+ }
3063
+ writeText(ref, value);
3064
+ } else {
3065
+ writeMarkup(ref, value);
3066
+ }
3067
+ }
3068
+ function readSlot(refs, id) {
3069
+ const ref = refs.get(id);
3070
+ if (!ref || ref.kind !== "text")
3071
+ return null;
3072
+ return readText(ref);
3073
+ }
3074
+ function claimSlots(root, plan) {
3075
+ const refs = claimRefs(root, plan);
3076
+ return { write: (id, value) => writeSlot(refs, id, value) };
3077
+ }
3078
+ function claimRefs(root, plan) {
3079
+ const refs = new Map;
3080
+ for (const spec of plan) {
3081
+ const ref = claimOne(root, spec);
3082
+ if (ref)
3083
+ refs.set(spec.id, ref);
3084
+ }
3085
+ return refs;
3086
+ }
3087
+ function lazySlots(root, plan) {
3088
+ let claimed = null;
3089
+ return (id, value) => {
3090
+ if (!claimed)
3091
+ claimed = claimSlots(root, plan);
3092
+ claimed.write(id, value);
3093
+ };
3094
+ }
3095
+ function lazyClaimSlots(root, plan) {
3096
+ let refs = null;
3097
+ const ensure = () => refs ??= claimRefs(root, plan);
3098
+ return {
3099
+ write: (id, value) => writeSlot(ensure(), id, value),
3100
+ read: (id) => readSlot(ensure(), id)
3101
+ };
2875
3102
  }
2876
3103
  // src/runtime/style.ts
2877
3104
  function styleToCss(value) {
@@ -3314,26 +3541,6 @@ function __bfText(current, value) {
3314
3541
  }
3315
3542
  return textNode;
3316
3543
  }
3317
- // src/runtime/client-marker.ts
3318
- function updateClientMarker(scope, id, value) {
3319
- if (!scope)
3320
- return;
3321
- const marker = `bf-client:${id}`;
3322
- const walker = document.createTreeWalker(scope, NodeFilter.SHOW_COMMENT);
3323
- while (walker.nextNode()) {
3324
- if (walker.currentNode.nodeValue === marker) {
3325
- const comment = walker.currentNode;
3326
- let textNode = comment.nextSibling;
3327
- if (textNode?.nodeType !== Node.TEXT_NODE || !textNode.nodeValue?.startsWith("​")) {
3328
- textNode = document.createTextNode("​" + String(value ?? ""));
3329
- comment.parentNode?.insertBefore(textNode, comment.nextSibling);
3330
- } else {
3331
- textNode.nodeValue = "​" + String(value ?? "");
3332
- }
3333
- return;
3334
- }
3335
- }
3336
- }
3337
3544
  // src/runtime/render.ts
3338
3545
  function render(container, nameOrDef, props = {}) {
3339
3546
  let name;
@@ -3414,9 +3621,9 @@ export {
3414
3621
  useContext,
3415
3622
  upsertChildItem,
3416
3623
  upsertChild,
3417
- updateClientMarker,
3418
3624
  unwrap,
3419
3625
  untrack,
3626
+ textOrNode,
3420
3627
  textNodeAfterComment as tAfter,
3421
3628
  styleToCss,
3422
3629
  spreadAttrs,
@@ -3430,21 +3637,21 @@ export {
3430
3637
  rehydrateAll,
3431
3638
  registerTemplate,
3432
3639
  registerComponent,
3433
- reconcileList,
3434
- reconcileElements,
3435
3640
  queryHref,
3436
3641
  qsaItem,
3437
3642
  qsaChildScopes,
3438
3643
  qsaChildScope,
3439
3644
  qsa,
3440
3645
  provideContext,
3441
- patchSlotRange,
3442
3646
  patchLeaf,
3443
3647
  parseHTML,
3444
3648
  onMount,
3445
3649
  onCleanup,
3650
+ mapArrayLazy,
3446
3651
  mapArrayAnchored,
3447
3652
  mapArray,
3653
+ lazySlots,
3654
+ lazyClaimSlots,
3448
3655
  isSSRPortal,
3449
3656
  insert,
3450
3657
  initChild,
@@ -3453,10 +3660,8 @@ export {
3453
3660
  hasTemplate,
3454
3661
  getTemplate,
3455
3662
  getRegisteredDef,
3456
- getPropsUpdateFn,
3457
3663
  getLoopNodes,
3458
3664
  getLoopChildren,
3459
- getComponentProps,
3460
3665
  getComponentInit,
3461
3666
  forwardProps,
3462
3667
  formatDate,
@@ -3464,6 +3669,7 @@ export {
3464
3669
  findSiblingSlot,
3465
3670
  findScope,
3466
3671
  find,
3672
+ escapeTextOrNode,
3467
3673
  escapeText,
3468
3674
  escapeAttr,
3469
3675
  endTurn,
@@ -3482,6 +3688,7 @@ export {
3482
3688
  createContext,
3483
3689
  createComponent,
3484
3690
  cleanupPortalPlaceholder,
3691
+ claimSlots,
3485
3692
  beginTurn,
3486
3693
  batch,
3487
3694
  applyRestAttrs,