@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
@@ -1,14 +1,14 @@
1
1
  // src/runtime/index.ts
2
2
  import {
3
- createSignal as createSignal2,
4
- createEffect as createEffect4,
3
+ createSignal as createSignal3,
4
+ createEffect as createEffect5,
5
5
  createDisposableEffect,
6
6
  createMemo,
7
7
  createSelector,
8
8
  createRoot as createRoot3,
9
9
  onCleanup,
10
10
  onMount,
11
- untrack as untrack3,
11
+ untrack as untrack4,
12
12
  batch,
13
13
  setProfilerSink,
14
14
  beginTurn,
@@ -1142,9 +1142,7 @@ function upsertChild(parent, name, slotId, props, key, anchorScope) {
1142
1142
  const ph = parent.getAttribute(BF_PLACEHOLDER) === phId ? parent : parent.querySelector(`[${BF_PLACEHOLDER}="${phId}"]`);
1143
1143
  if (ph) {
1144
1144
  const slot = slotId ? buildSlotInfo(parent, slotId, anchorScope) : undefined;
1145
- const comp = createComponent(name, props, key, slot);
1146
- ph.replaceWith(comp);
1147
- return comp;
1145
+ return createComponent(name, props, key, slot, ph);
1148
1146
  }
1149
1147
  return null;
1150
1148
  }
@@ -1352,13 +1350,30 @@ var _parentScopeId = null;
1352
1350
  function setParentScopeId(id) {
1353
1351
  _parentScopeId = id;
1354
1352
  }
1355
- var propsUpdateMap = new WeakMap;
1356
- var propsMap = new WeakMap;
1357
- function createComponent(nameOrDef, props = {}, key, slot) {
1353
+ var _rowMountPoint = null;
1354
+ function setRowMountPoint(p) {
1355
+ const prev = _rowMountPoint;
1356
+ _rowMountPoint = p;
1357
+ return prev;
1358
+ }
1359
+ function takeRowMountPoint() {
1360
+ const p = _rowMountPoint;
1361
+ _rowMountPoint = null;
1362
+ return p;
1363
+ }
1364
+ function createComponent(nameOrDef, props = {}, key, slot, mountAt) {
1365
+ const element = materializeComponent(nameOrDef, props, key, slot, mountAt);
1366
+ if (mountAt && mountAt.parentNode && element !== mountAt) {
1367
+ mountAt.replaceWith(element);
1368
+ }
1369
+ return element;
1370
+ }
1371
+ function materializeComponent(nameOrDef, props = {}, key, slot, mountAt) {
1358
1372
  if (props == null)
1359
1373
  props = {};
1374
+ const rowMount = mountAt ? null : takeRowMountPoint();
1360
1375
  if (typeof nameOrDef !== "string") {
1361
- return createComponentFromDef(nameOrDef, props, key);
1376
+ return createComponentFromDef(nameOrDef, props, key, mountAt, rowMount);
1362
1377
  }
1363
1378
  const name = nameOrDef;
1364
1379
  const templateFn = getTemplate(name);
@@ -1418,8 +1433,13 @@ function createComponent(nameOrDef, props = {}, key, slot) {
1418
1433
  if (key !== undefined) {
1419
1434
  element.setAttribute(BF_KEY, String(key));
1420
1435
  }
1421
- const prevScope = setCurrentScope(element);
1422
1436
  const rootIsDeferredPlaceholder = element.hasAttribute(BF_PLACEHOLDER);
1437
+ if (mountAt && !rootIsDeferredPlaceholder) {
1438
+ mountAt.replaceWith(element);
1439
+ } else if (rowMount && !rootIsDeferredPlaceholder) {
1440
+ rowMount.container.insertBefore(element, rowMount.anchor);
1441
+ }
1442
+ const prevScope = setCurrentScope(element);
1423
1443
  let placeholderWrapper = null;
1424
1444
  if (rootIsDeferredPlaceholder) {
1425
1445
  placeholderWrapper = parseHTML("<div></div>").firstChild;
@@ -1445,24 +1465,8 @@ function createComponent(nameOrDef, props = {}, key, slot) {
1445
1465
  }
1446
1466
  setCurrentScope(prevScope);
1447
1467
  hydratedScopes.add(element);
1448
- propsMap.set(element, props);
1449
- registerPropsUpdate(element, name, props);
1450
1468
  return element;
1451
1469
  }
1452
- function getComponentProps(element) {
1453
- return propsMap.get(element);
1454
- }
1455
- function registerPropsUpdate(element, name, _initialProps) {
1456
- propsUpdateMap.set(element, (newProps) => {
1457
- const init = getComponentInit(name);
1458
- if (init) {
1459
- init(element, newProps);
1460
- }
1461
- });
1462
- }
1463
- function getPropsUpdateFn(element) {
1464
- return propsUpdateMap.get(element);
1465
- }
1466
1470
  function renderChild(name, props, key, slotSuffix) {
1467
1471
  const templateFn = getTemplate(name);
1468
1472
  const suffix = slotSuffix ? `_${slotSuffix}` : "";
@@ -1528,6 +1532,11 @@ function escapeText(value) {
1528
1532
  return "";
1529
1533
  return escapeAttr(value);
1530
1534
  }
1535
+ function escapeTextOrNode(value) {
1536
+ if (typeof Node !== "undefined" && value instanceof Node)
1537
+ return value;
1538
+ return escapeText(value);
1539
+ }
1531
1540
  var SVG_NS = "http://www.w3.org/2000/svg";
1532
1541
  function parseHTML(html, parent) {
1533
1542
  const tpl = document.createElement("template");
@@ -1571,7 +1580,7 @@ function insertGetterChildren(element, children) {
1571
1580
  element.appendChild(document.createTextNode(String(children)));
1572
1581
  }
1573
1582
  }
1574
- function createComponentFromDef(def, props, key) {
1583
+ function createComponentFromDef(def, props, key, mountAt, rowMount) {
1575
1584
  if (!def.template) {
1576
1585
  throw new Error("[BarefootJS] createComponent with ComponentDef requires a template function");
1577
1586
  }
@@ -1590,9 +1599,13 @@ function createComponentFromDef(def, props, key) {
1590
1599
  if (key !== undefined) {
1591
1600
  element.setAttribute(BF_KEY, String(key));
1592
1601
  }
1602
+ if (mountAt) {
1603
+ mountAt.replaceWith(element);
1604
+ } else if (rowMount) {
1605
+ rowMount.container.insertBefore(element, rowMount.anchor);
1606
+ }
1593
1607
  def.init(element, props);
1594
1608
  hydratedScopes.add(element);
1595
- propsMap.set(element, props);
1596
1609
  return element;
1597
1610
  }
1598
1611
 
@@ -1650,7 +1663,7 @@ function createPortal(children, container = document.body, options) {
1650
1663
  }
1651
1664
  };
1652
1665
  }
1653
- // src/runtime/reconcile-elements.ts
1666
+ // src/runtime/loop-markers.ts
1654
1667
  function findLoopMarkers(container, markerId) {
1655
1668
  let startMarker = null;
1656
1669
  let endMarker = null;
@@ -1695,14 +1708,6 @@ function getElementsBetweenMarkers(start, end) {
1695
1708
  }
1696
1709
  return elements;
1697
1710
  }
1698
- function removeElementsBetweenMarkers(start, end) {
1699
- let node = start.nextSibling;
1700
- while (node && node !== end) {
1701
- const next = node.nextSibling;
1702
- node.parentNode?.removeChild(node);
1703
- node = next;
1704
- }
1705
- }
1706
1711
  function getLoopChildren(container, markerId) {
1707
1712
  const { startMarker, endMarker } = findLoopMarkers(container, markerId);
1708
1713
  if (startMarker && endMarker) {
@@ -1723,159 +1728,6 @@ function getLoopNodes(container, markerId) {
1723
1728
  }
1724
1729
  return Array.from(container.childNodes);
1725
1730
  }
1726
- function reconcileElements(container, items, getKey, renderItem, firstElement, markerId) {
1727
- if (!container || !items)
1728
- return;
1729
- const { startMarker, endMarker } = findLoopMarkers(container, markerId);
1730
- const existingByKey = new Map;
1731
- let hasKeyedChildren = false;
1732
- const loopChildren = startMarker ? getElementsBetweenMarkers(startMarker, endMarker) : Array.from(container.children);
1733
- for (const child of loopChildren) {
1734
- const el = child;
1735
- const key = el.dataset?.key;
1736
- if (key !== undefined) {
1737
- existingByKey.set(key, el);
1738
- hasKeyedChildren = true;
1739
- }
1740
- }
1741
- if (!hasKeyedChildren) {
1742
- if (items.length === 0) {
1743
- if (startMarker) {
1744
- removeElementsBetweenMarkers(startMarker, endMarker);
1745
- } else {
1746
- container.innerHTML = "";
1747
- }
1748
- return;
1749
- }
1750
- const fragment = document.createDocumentFragment();
1751
- for (let i = 0;i < items.length; i++) {
1752
- const el = i === 0 && firstElement ? firstElement : renderItem(items[i], i);
1753
- const key = getKey ? getKey(items[i], i) : String(i);
1754
- if (!el.dataset.key)
1755
- el.setAttribute(BF_KEY, key);
1756
- fragment.appendChild(el);
1757
- }
1758
- if (startMarker) {
1759
- removeElementsBetweenMarkers(startMarker, endMarker);
1760
- endMarker.parentNode.insertBefore(fragment, endMarker);
1761
- } else {
1762
- container.innerHTML = "";
1763
- container.appendChild(fragment);
1764
- }
1765
- return;
1766
- }
1767
- let insertAnchor = endMarker ?? null;
1768
- if (!startMarker) {
1769
- let foundKeyed = false;
1770
- for (const child of Array.from(container.childNodes)) {
1771
- if (child.nodeType === Node.ELEMENT_NODE && child.dataset.key !== undefined) {
1772
- foundKeyed = true;
1773
- } else if (foundKeyed) {
1774
- insertAnchor = child;
1775
- break;
1776
- }
1777
- }
1778
- }
1779
- let focusedKey = null;
1780
- const activeEl = document.activeElement;
1781
- if (activeEl && activeEl !== document.body) {
1782
- const tag = activeEl.tagName;
1783
- if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || activeEl.isContentEditable) {
1784
- for (const [key, el] of existingByKey) {
1785
- if (el.contains(activeEl)) {
1786
- focusedKey = key;
1787
- break;
1788
- }
1789
- }
1790
- }
1791
- }
1792
- const desiredElements = [];
1793
- const toRemove = [];
1794
- let focusTarget = null;
1795
- for (let i = 0;i < items.length; i++) {
1796
- const item = items[i];
1797
- const key = getKey ? getKey(item, i) : String(i);
1798
- const createEl = () => i === 0 && firstElement ? firstElement : renderItem(item, i);
1799
- const existing = existingByKey.get(key);
1800
- if (existing) {
1801
- existingByKey.delete(key);
1802
- if (existing.getAttribute(BF_SCOPE) && !hydratedScopes.has(existing)) {
1803
- const newEl = createEl();
1804
- if (!newEl.dataset.key)
1805
- newEl.setAttribute(BF_KEY, key);
1806
- desiredElements.push(newEl);
1807
- toRemove.push(existing);
1808
- } else if (focusedKey === key) {
1809
- const newEl = createEl();
1810
- if (!newEl.dataset.key)
1811
- newEl.setAttribute(BF_KEY, key);
1812
- focusTarget = prepareInputTransfer(existing, newEl);
1813
- desiredElements.push(newEl);
1814
- toRemove.push(existing);
1815
- } else {
1816
- const newEl = createEl();
1817
- if (!newEl.dataset.key)
1818
- newEl.setAttribute(BF_KEY, key);
1819
- desiredElements.push(newEl);
1820
- toRemove.push(existing);
1821
- }
1822
- } else {
1823
- const el = createEl();
1824
- if (!el.dataset.key)
1825
- el.setAttribute(BF_KEY, key);
1826
- desiredElements.push(el);
1827
- }
1828
- }
1829
- for (const el of existingByKey.values()) {
1830
- toRemove.push(el);
1831
- }
1832
- for (const el of toRemove) {
1833
- if (el.parentNode)
1834
- el.remove();
1835
- }
1836
- for (const el of desiredElements) {
1837
- container.insertBefore(el, insertAnchor);
1838
- }
1839
- if (focusTarget) {
1840
- focusTarget.target.focus();
1841
- if (typeof focusTarget.selectionStart === "number") {
1842
- focusTarget.target.selectionStart = focusTarget.selectionStart;
1843
- focusTarget.target.selectionEnd = focusTarget.selectionEnd;
1844
- }
1845
- }
1846
- }
1847
- function prepareInputTransfer(oldEl, newEl) {
1848
- const focused = oldEl.contains(document.activeElement) ? document.activeElement : null;
1849
- if (!focused)
1850
- return null;
1851
- const tag = focused.tagName;
1852
- const oldInputs = Array.from(oldEl.querySelectorAll(tag));
1853
- const idx = oldInputs.indexOf(focused);
1854
- if (idx < 0)
1855
- return null;
1856
- const newInputs = Array.from(newEl.querySelectorAll(tag));
1857
- const target = newInputs[idx];
1858
- if (!target)
1859
- return null;
1860
- target.value = focused.value;
1861
- return {
1862
- target,
1863
- selectionStart: focused.selectionStart,
1864
- selectionEnd: focused.selectionEnd
1865
- };
1866
- }
1867
-
1868
- // src/runtime/list.ts
1869
- function reconcileList(container, items, getKey, renderItem) {
1870
- if (!container || !items)
1871
- return;
1872
- if (items.length === 0) {
1873
- container.innerHTML = "";
1874
- return;
1875
- }
1876
- const firstElement = renderItem(items[0], 0);
1877
- reconcileElements(container, items, getKey, renderItem, firstElement);
1878
- }
1879
1731
  // src/runtime/qsa-item.ts
1880
1732
  function* itemRootElements(primaryEl) {
1881
1733
  yield primaryEl;
@@ -1932,9 +1784,7 @@ function upsertChildItem(primaryEl, name, slotId, props, key, anchorScope) {
1932
1784
  const ph = qsaItem(primaryEl, `[data-bf-ph="${phId}"]`);
1933
1785
  if (ph) {
1934
1786
  const slot = slotId ? buildSlotInfo(primaryEl, slotId, anchorScope) : undefined;
1935
- const comp = createComponent(name, props, key, slot);
1936
- ph.replaceWith(comp);
1937
- return comp;
1787
+ return createComponent(name, props, key, slot, ph);
1938
1788
  }
1939
1789
  return null;
1940
1790
  }
@@ -2047,7 +1897,7 @@ function removeScope(scope) {
2047
1897
  ex.remove();
2048
1898
  }
2049
1899
  }
2050
- function createItemScope(item, index, renderItem, existingPrimary, existingExtras, existingStart) {
1900
+ function createItemScope(item, index, renderItem, existingPrimary, existingExtras, existingStart, rowMount) {
2051
1901
  let primaryEl;
2052
1902
  let dispose;
2053
1903
  let setItem;
@@ -2057,7 +1907,14 @@ function createItemScope(item, index, renderItem, existingPrimary, existingExtra
2057
1907
  dispose = d;
2058
1908
  const [itemAccessor, itemSetter] = createSignal(item);
2059
1909
  setItem = itemSetter;
2060
- primaryEl = renderItem(itemAccessor, index, existingPrimary);
1910
+ const ownsRowMount = !existingPrimary && !!rowMount;
1911
+ const prevRowMount = ownsRowMount ? setRowMountPoint(rowMount) : null;
1912
+ try {
1913
+ primaryEl = renderItem(itemAccessor, index, existingPrimary);
1914
+ } finally {
1915
+ if (ownsRowMount)
1916
+ setRowMountPoint(prevRowMount);
1917
+ }
2061
1918
  if (existingPrimary) {
2062
1919
  extras = existingExtras ?? [];
2063
1920
  startMarker = existingStart ?? null;
@@ -2071,6 +1928,9 @@ function createItemScope(item, index, renderItem, existingPrimary, existingExtra
2071
1928
  }
2072
1929
  return;
2073
1930
  });
1931
+ if (rowMount && !existingPrimary && extras.length > 0 && primaryEl.parentNode) {
1932
+ primaryEl.remove();
1933
+ }
2074
1934
  return { startMarker, primaryEl, extras, dispose, setItem };
2075
1935
  }
2076
1936
  function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
@@ -2112,7 +1972,7 @@ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2112
1972
  for (let i2 = existingRanges.length;i2 < items.length; i2++) {
2113
1973
  const item = items[i2];
2114
1974
  const key = getKey ? getKey(item, i2) : String(i2);
2115
- const scope = createItemScope(item, i2, renderItem);
1975
+ const scope = createItemScope(item, i2, renderItem, undefined, undefined, undefined, { container, anchor });
2116
1976
  if (!scope.primaryEl.dataset.key)
2117
1977
  scope.primaryEl.setAttribute(BF_KEY, key);
2118
1978
  scopes.set(key, scope);
@@ -2191,7 +2051,7 @@ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2191
2051
  existing.setItem(item);
2192
2052
  desiredOrder.push(existing);
2193
2053
  } else {
2194
- const scope = createItemScope(item, i2, renderItem);
2054
+ const scope = createItemScope(item, i2, renderItem, undefined, undefined, undefined, { container, anchor });
2195
2055
  if (!scope.primaryEl.dataset.key)
2196
2056
  scope.primaryEl.setAttribute(BF_KEY, key);
2197
2057
  scopes.set(key, scope);
@@ -2387,6 +2247,198 @@ function mapArrayAnchored(accessor, container, getKey, renderItem, markerId, bfI
2387
2247
  }
2388
2248
  }, bfId);
2389
2249
  }
2250
+ // src/runtime/map-array-lazy.ts
2251
+ import { createEffect as createEffect2, createSignal as createSignal2, untrack as untrack2 } from "@barefootjs/client/reactive";
2252
+ function mapArrayLazy(accessor, container, getKey, plan, markerId, bfId) {
2253
+ if (!container)
2254
+ return;
2255
+ const entries = new Map;
2256
+ let entryList = [];
2257
+ let hydrated = false;
2258
+ const needsResubscribe = plan.applyOuter !== undefined;
2259
+ const [generation, bumpGeneration] = needsResubscribe ? createSignal2(0) : [null, null];
2260
+ let stranded = false;
2261
+ const markStranded = () => {
2262
+ if (needsResubscribe)
2263
+ stranded = true;
2264
+ };
2265
+ const flushStranded = () => {
2266
+ if (stranded && bumpGeneration) {
2267
+ stranded = false;
2268
+ bumpGeneration((n) => n + 1);
2269
+ }
2270
+ };
2271
+ let cachedStart = null;
2272
+ let cachedEnd = null;
2273
+ const resolveMarkers = () => {
2274
+ if (cachedStart && cachedEnd && cachedStart.isConnected && cachedEnd.isConnected) {
2275
+ return { start: cachedStart, end: cachedEnd };
2276
+ }
2277
+ const found = findLoopMarkers2(container, markerId);
2278
+ cachedStart = found.start;
2279
+ cachedEnd = found.end;
2280
+ return found;
2281
+ };
2282
+ const createEntry = (item, index, key) => {
2283
+ const entry = {
2284
+ key,
2285
+ primaryEl: undefined,
2286
+ item,
2287
+ refs: null,
2288
+ last: null
2289
+ };
2290
+ entry.primaryEl = untrack2(() => plan.createRow(entry, index));
2291
+ if (!entry.primaryEl.dataset.key)
2292
+ entry.primaryEl.setAttribute(BF_KEY, key);
2293
+ markStranded();
2294
+ return entry;
2295
+ };
2296
+ createEffect2(() => {
2297
+ const items = accessor();
2298
+ if (!items)
2299
+ return;
2300
+ const { start: startMarker, end: endMarker } = resolveMarkers();
2301
+ const anchor = endMarker ?? null;
2302
+ if (!hydrated) {
2303
+ hydrated = true;
2304
+ const doms = [];
2305
+ for (let node = startMarker ? startMarker.nextSibling : container.firstChild;node && node !== anchor; node = node.nextSibling) {
2306
+ if (node.nodeType === Node.ELEMENT_NODE)
2307
+ doms.push(node);
2308
+ }
2309
+ if (doms.length > 0 && entries.size === 0) {
2310
+ const list = [];
2311
+ const shared = Math.min(doms.length, items.length);
2312
+ for (let i2 = 0;i2 < shared; i2++) {
2313
+ const el = doms[i2];
2314
+ const ssrKey = el.getAttribute(BF_KEY);
2315
+ const key = ssrKey !== null ? ssrKey : getKey ? getKey(items[i2], i2) : String(i2);
2316
+ const entry = { key, primaryEl: el, item: items[i2], refs: null, last: null };
2317
+ entries.set(key, entry);
2318
+ list.push(entry);
2319
+ }
2320
+ for (let i2 = doms.length;i2 < items.length; i2++) {
2321
+ const item = items[i2];
2322
+ const key = getKey ? getKey(item, i2) : String(i2);
2323
+ const entry = createEntry(item, i2, key);
2324
+ entries.set(key, entry);
2325
+ list.push(entry);
2326
+ container.insertBefore(entry.primaryEl, anchor);
2327
+ }
2328
+ for (let i2 = items.length;i2 < doms.length; i2++)
2329
+ doms[i2].remove();
2330
+ entryList = list;
2331
+ flushStranded();
2332
+ return;
2333
+ }
2334
+ }
2335
+ if (items.length === 0) {
2336
+ if (entries.size > 0) {
2337
+ if (startMarker && endMarker) {
2338
+ const range = document.createRange();
2339
+ range.setStartAfter(startMarker);
2340
+ range.setEndBefore(endMarker);
2341
+ range.deleteContents();
2342
+ } else {
2343
+ let actualNodeCount = 0;
2344
+ for (let node = container.firstChild;node; node = node.nextSibling)
2345
+ actualNodeCount++;
2346
+ if (actualNodeCount === entries.size) {
2347
+ container.textContent = "";
2348
+ } else {
2349
+ for (const entry of entries.values())
2350
+ entry.primaryEl.remove();
2351
+ }
2352
+ }
2353
+ entries.clear();
2354
+ entryList = [];
2355
+ }
2356
+ return;
2357
+ }
2358
+ const newKeys = new Set;
2359
+ const warnedKeys = new Set;
2360
+ const desiredOrder = [];
2361
+ for (let i2 = 0;i2 < items.length; i2++) {
2362
+ const item = items[i2];
2363
+ const key = getKey ? getKey(item, i2) : String(i2);
2364
+ if (newKeys.has(key) && !warnedKeys.has(key)) {
2365
+ warnedKeys.add(key);
2366
+ 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.`);
2367
+ }
2368
+ newKeys.add(key);
2369
+ const existing = entries.get(key);
2370
+ if (existing) {
2371
+ if (!Object.is(existing.item, item)) {
2372
+ const prevItem = existing.item;
2373
+ existing.item = item;
2374
+ untrack2(() => plan.applyItem(existing, prevItem));
2375
+ markStranded();
2376
+ }
2377
+ desiredOrder.push(existing);
2378
+ } else {
2379
+ const entry = createEntry(item, i2, key);
2380
+ entries.set(key, entry);
2381
+ desiredOrder.push(entry);
2382
+ }
2383
+ }
2384
+ for (const [key, entry] of entries) {
2385
+ if (!newKeys.has(key)) {
2386
+ if (entry.primaryEl.parentNode)
2387
+ entry.primaryEl.remove();
2388
+ entries.delete(key);
2389
+ }
2390
+ }
2391
+ const primaryElToDesiredIndex = new Map;
2392
+ for (let i2 = 0;i2 < desiredOrder.length; i2++) {
2393
+ primaryElToDesiredIndex.set(desiredOrder[i2].primaryEl, i2);
2394
+ }
2395
+ const domOrderIndices = [];
2396
+ for (let node = startMarker ? startMarker.nextSibling : container.firstChild;node && node !== anchor; node = node.nextSibling) {
2397
+ if (node.nodeType !== Node.ELEMENT_NODE)
2398
+ continue;
2399
+ const idx = primaryElToDesiredIndex.get(node);
2400
+ if (idx !== undefined)
2401
+ domOrderIndices.push(idx);
2402
+ }
2403
+ const stationary = new Array(desiredOrder.length).fill(false);
2404
+ for (const pos of longestIncreasingSubsequenceIndices(domOrderIndices)) {
2405
+ stationary[domOrderIndices[pos]] = true;
2406
+ }
2407
+ let i = 0;
2408
+ while (i < desiredOrder.length) {
2409
+ if (stationary[i]) {
2410
+ i++;
2411
+ continue;
2412
+ }
2413
+ let j = i;
2414
+ while (j < desiredOrder.length && !stationary[j])
2415
+ j++;
2416
+ const before = j < desiredOrder.length ? desiredOrder[j].primaryEl : anchor;
2417
+ if (j - i === 1) {
2418
+ container.insertBefore(desiredOrder[i].primaryEl, before);
2419
+ } else {
2420
+ const runFragment = document.createDocumentFragment();
2421
+ for (let k = i;k < j; k++)
2422
+ runFragment.appendChild(desiredOrder[k].primaryEl);
2423
+ container.insertBefore(runFragment, before);
2424
+ }
2425
+ i = j;
2426
+ }
2427
+ entryList = desiredOrder;
2428
+ flushStranded();
2429
+ }, bfId);
2430
+ if (plan.applyOuter) {
2431
+ const applyOuter = plan.applyOuter.bind(plan);
2432
+ let seed = true;
2433
+ createEffect2(() => {
2434
+ if (generation)
2435
+ generation();
2436
+ const isSeed = seed;
2437
+ seed = false;
2438
+ applyOuter(entryList, isSeed);
2439
+ });
2440
+ }
2441
+ }
2390
2442
  // src/runtime/patch-leaf.ts
2391
2443
  function patchLeaf(el, html) {
2392
2444
  const tpl = document.createElement("template");
@@ -2412,35 +2464,44 @@ function patchLeaf(el, html) {
2412
2464
  }
2413
2465
  el.replaceChildren(...Array.from(next.childNodes));
2414
2466
  }
2415
- // src/runtime/patch-slot-range.ts
2416
- function patchSlotRange(scope, id, html) {
2467
+ // src/runtime/claim-slots.ts
2468
+ function resolvePath(root, path) {
2469
+ let node = root;
2470
+ for (const index of path) {
2471
+ const child = node.childNodes[index];
2472
+ if (!child)
2473
+ return null;
2474
+ node = child;
2475
+ }
2476
+ return node;
2477
+ }
2478
+ function isSlotComment(node, id) {
2479
+ return node != null && node.nodeType === Node.COMMENT_NODE && node.nodeValue === `bf:${id}`;
2480
+ }
2481
+ function findOwnedMarker(root, id) {
2417
2482
  const marker = `bf:${id}`;
2418
- let start = null;
2419
- const walker = document.createTreeWalker(scope, NodeFilter.SHOW_COMMENT);
2420
- while (walker.nextNode()) {
2421
- const comment = walker.currentNode;
2483
+ const parentOwned = id.startsWith(BF_PARENT_OWNED_PREFIX);
2484
+ const registryInfo = commentScopeRegistry.get(root);
2485
+ const boundary = registryInfo ? registryInfo.commentNode.parentElement : root;
2486
+ for (const comment of commentsInScope(root)) {
2422
2487
  if (comment.nodeValue !== marker)
2423
2488
  continue;
2489
+ if (parentOwned)
2490
+ return comment;
2424
2491
  let owned = true;
2425
- for (let el = comment.parentElement;el && el !== scope; el = el.parentElement) {
2492
+ for (let el = comment.parentElement;el && el !== boundary; el = el.parentElement) {
2426
2493
  if (el.hasAttribute(BF_SCOPE)) {
2427
2494
  owned = false;
2428
2495
  break;
2429
2496
  }
2430
2497
  }
2431
- if (owned) {
2432
- start = comment;
2433
- break;
2434
- }
2435
- }
2436
- const parent = start?.parentNode;
2437
- if (!start || !parent) {
2438
- console.warn(`[barefootjs] preamble region marker bf:${id} not found in row; skipping patch`);
2439
- return;
2498
+ if (owned)
2499
+ return comment;
2440
2500
  }
2501
+ return null;
2502
+ }
2503
+ function findMarkupEnd(start) {
2441
2504
  let depth = 0;
2442
- let end = null;
2443
- const toRemove = [];
2444
2505
  let node = start.nextSibling;
2445
2506
  while (node) {
2446
2507
  if (node.nodeType === Node.COMMENT_NODE) {
@@ -2448,28 +2509,195 @@ function patchSlotRange(scope, id, html) {
2448
2509
  if (value.startsWith("bf:")) {
2449
2510
  depth++;
2450
2511
  } else if (value === "/") {
2451
- if (depth === 0) {
2452
- end = node;
2453
- break;
2454
- }
2512
+ if (depth === 0)
2513
+ return node;
2455
2514
  depth--;
2456
2515
  }
2457
2516
  }
2458
- toRemove.push(node);
2459
2517
  node = node.nextSibling;
2460
2518
  }
2519
+ return null;
2520
+ }
2521
+ function resolveAnchor(root, spec) {
2522
+ if (spec.path.length > 0) {
2523
+ const resolved = resolvePath(root, spec.path);
2524
+ if (isSlotComment(resolved, spec.id))
2525
+ return resolved;
2526
+ console.warn(`[barefootjs] claim path for slot ${spec.id} did not resolve to its bf:${spec.id} marker; falling back to a scan`);
2527
+ }
2528
+ const found = findOwnedMarker(root, spec.id);
2529
+ if (!found) {
2530
+ console.warn(`[barefootjs] slot ${spec.id} marker not found; skipping`);
2531
+ }
2532
+ return found;
2533
+ }
2534
+ function claimMarkerlessText(root, spec) {
2535
+ if (spec.path.length === 0) {
2536
+ console.warn(`[barefootjs] markerless slot ${spec.id} has an empty path; skipping`);
2537
+ return null;
2538
+ }
2539
+ const parentPath = spec.path.slice(0, -1);
2540
+ const idx = spec.path[spec.path.length - 1];
2541
+ const parent = resolvePath(root, parentPath);
2542
+ if (!parent) {
2543
+ console.warn(`[barefootjs] markerless claim path for slot ${spec.id} did not resolve to a parent node; skipping`);
2544
+ return null;
2545
+ }
2546
+ const existing = parent.childNodes[idx];
2547
+ if (existing && existing.nodeType === Node.TEXT_NODE) {
2548
+ return { kind: "text", ref: existing };
2549
+ }
2550
+ const node = document.createTextNode("");
2551
+ parent.insertBefore(node, existing ?? null);
2552
+ return { kind: "text", ref: node };
2553
+ }
2554
+ function claimOne(root, spec) {
2555
+ if (spec.kind === "text" && spec.markerless) {
2556
+ return claimMarkerlessText(root, spec);
2557
+ }
2558
+ const anchor = resolveAnchor(root, spec);
2559
+ if (!anchor)
2560
+ return null;
2561
+ if (spec.kind === "text") {
2562
+ const next = anchor.nextSibling;
2563
+ return { kind: "text", ref: next?.nodeType === Node.TEXT_NODE ? next : anchor };
2564
+ }
2565
+ const end = findMarkupEnd(anchor);
2566
+ if (!end) {
2567
+ console.warn(`[barefootjs] slot ${spec.id} has no end marker; skipping`);
2568
+ return null;
2569
+ }
2570
+ return { kind: "markup", start: anchor, end, last: undefined };
2571
+ }
2572
+ function materializeText(slot) {
2573
+ const anchor = slot.ref;
2574
+ const parent = anchor.parentNode;
2575
+ if (!parent)
2576
+ return null;
2577
+ const node = document.createTextNode("");
2578
+ parent.insertBefore(node, anchor.nextSibling);
2579
+ slot.ref = node;
2580
+ return node;
2581
+ }
2582
+ function readText(slot) {
2583
+ return slot.ref.nodeType === Node.TEXT_NODE ? slot.ref.nodeValue ?? "" : "";
2584
+ }
2585
+ function writeText(slot, value) {
2586
+ const node = slot.ref.nodeType === Node.TEXT_NODE ? slot.ref : materializeText(slot);
2587
+ if (!node)
2588
+ return;
2589
+ node.nodeValue = String(value ?? "");
2590
+ }
2591
+ function textOrNode(value) {
2592
+ if (typeof Node !== "undefined" && value instanceof Node)
2593
+ return value;
2594
+ return String(value);
2595
+ }
2596
+ function promoteTextToMarkup(slot, id) {
2597
+ const anchor = slot.ref.nodeType === Node.COMMENT_NODE ? slot.ref : isSlotComment(slot.ref.previousSibling, id) ? slot.ref.previousSibling : null;
2598
+ if (!anchor) {
2599
+ console.warn(`[barefootjs] slot ${id} was claimed as text and received a Node, but has no anchor marker to promote from; write ignored`);
2600
+ return null;
2601
+ }
2602
+ const end = findMarkupEnd(anchor);
2461
2603
  if (!end) {
2462
- console.warn(`[barefootjs] preamble region bf:${id} has no end marker; skipping patch`);
2604
+ console.warn(`[barefootjs] slot ${id} was claimed as text and received a Node, but has no end marker; write ignored`);
2605
+ return null;
2606
+ }
2607
+ return { kind: "markup", start: anchor, end, last: undefined };
2608
+ }
2609
+ function clearMarkupRange(start, end) {
2610
+ const parent = end.parentNode;
2611
+ if (!parent)
2463
2612
  return;
2613
+ let node = start.nextSibling;
2614
+ while (node && node !== end) {
2615
+ const next = node.nextSibling;
2616
+ parent.removeChild(node);
2617
+ node = next;
2464
2618
  }
2465
- for (const n of toRemove)
2466
- parent.removeChild(n);
2619
+ }
2620
+ function writeMarkup(ref, value) {
2621
+ const { start, end } = ref;
2622
+ const parent = end.parentNode;
2623
+ if (!parent)
2624
+ return;
2625
+ if (value != null && value.__isSlot)
2626
+ return;
2627
+ if (typeof Node !== "undefined" && value instanceof Node) {
2628
+ if (value === ref.last)
2629
+ return;
2630
+ clearMarkupRange(start, end);
2631
+ parent.insertBefore(value, end);
2632
+ ref.last = value;
2633
+ return;
2634
+ }
2635
+ const text = String(value ?? "");
2636
+ if (text === ref.last)
2637
+ return;
2638
+ clearMarkupRange(start, end);
2467
2639
  const tpl = document.createElement("template");
2468
- tpl.innerHTML = html;
2640
+ tpl.innerHTML = text;
2469
2641
  parent.insertBefore(tpl.content, end);
2642
+ ref.last = text;
2643
+ }
2644
+ function writeSlot(refs, id, value) {
2645
+ const ref = refs.get(id);
2646
+ if (!ref) {
2647
+ console.warn(`[barefootjs] no claimed slot for id ${id}; write ignored`);
2648
+ return;
2649
+ }
2650
+ if (ref.kind === "text") {
2651
+ if (typeof Node !== "undefined" && value instanceof Node) {
2652
+ const promoted = promoteTextToMarkup(ref, id);
2653
+ if (!promoted)
2654
+ return;
2655
+ refs.set(id, promoted);
2656
+ writeMarkup(promoted, value);
2657
+ return;
2658
+ }
2659
+ writeText(ref, value);
2660
+ } else {
2661
+ writeMarkup(ref, value);
2662
+ }
2663
+ }
2664
+ function readSlot(refs, id) {
2665
+ const ref = refs.get(id);
2666
+ if (!ref || ref.kind !== "text")
2667
+ return null;
2668
+ return readText(ref);
2669
+ }
2670
+ function claimSlots(root, plan) {
2671
+ const refs = claimRefs(root, plan);
2672
+ return { write: (id, value) => writeSlot(refs, id, value) };
2673
+ }
2674
+ function claimRefs(root, plan) {
2675
+ const refs = new Map;
2676
+ for (const spec of plan) {
2677
+ const ref = claimOne(root, spec);
2678
+ if (ref)
2679
+ refs.set(spec.id, ref);
2680
+ }
2681
+ return refs;
2682
+ }
2683
+ function lazySlots(root, plan) {
2684
+ let claimed = null;
2685
+ return (id, value) => {
2686
+ if (!claimed)
2687
+ claimed = claimSlots(root, plan);
2688
+ claimed.write(id, value);
2689
+ };
2690
+ }
2691
+ function lazyClaimSlots(root, plan) {
2692
+ let refs = null;
2693
+ const ensure = () => refs ??= claimRefs(root, plan);
2694
+ return {
2695
+ write: (id, value) => writeSlot(ensure(), id, value),
2696
+ read: (id) => readSlot(ensure(), id)
2697
+ };
2470
2698
  }
2471
2699
  // src/runtime/apply-rest-attrs.ts
2472
- import { createEffect as createEffect2 } from "@barefootjs/client/reactive";
2700
+ import { createEffect as createEffect3 } from "@barefootjs/client/reactive";
2473
2701
 
2474
2702
  // src/runtime/style.ts
2475
2703
  function styleToCss(value) {
@@ -2514,7 +2742,7 @@ function applyRestAttrs(el, source, excludeKeys) {
2514
2742
  }
2515
2743
  }
2516
2744
  const attrEntries = classified.filter(({ c }) => c.kind !== "ref" && c.kind !== "event" && c.kind !== "skip");
2517
- createEffect2(() => {
2745
+ createEffect3(() => {
2518
2746
  for (const { key, c } of attrEntries) {
2519
2747
  const value = source[key];
2520
2748
  if (c.kind === "innerHTML") {
@@ -2590,7 +2818,7 @@ function date(recv, op) {
2590
2818
  return d[op]();
2591
2819
  }
2592
2820
  // src/runtime/insert.ts
2593
- import { createEffect as createEffect3, untrack as untrack2 } from "@barefootjs/client/reactive";
2821
+ import { createEffect as createEffect4, untrack as untrack3 } from "@barefootjs/client/reactive";
2594
2822
  function makeRegion(scope) {
2595
2823
  if (scope.nodeType === Node.COMMENT_NODE) {
2596
2824
  const anchor = scope;
@@ -2645,7 +2873,7 @@ function normalizeTemplate(value) {
2645
2873
  return typeof value === "string" ? { html: value, slots: EMPTY_SLOTS } : value;
2646
2874
  }
2647
2875
  function evalBranchTemplate(branch) {
2648
- return untrack2(() => normalizeTemplate(branch.template()));
2876
+ return untrack3(() => normalizeTemplate(branch.template()));
2649
2877
  }
2650
2878
  function insert(scope, id, conditionFn, whenTrue, whenFalse, bfId) {
2651
2879
  if (!scope)
@@ -2671,7 +2899,7 @@ function insert(scope, id, conditionFn, whenTrue, whenFalse, bfId) {
2671
2899
  }
2672
2900
  let prevCond;
2673
2901
  let branchCleanup = null;
2674
- createEffect3(() => {
2902
+ createEffect4(() => {
2675
2903
  let currCond;
2676
2904
  try {
2677
2905
  currCond = Boolean(conditionFn());
@@ -2914,26 +3142,6 @@ function __bfText(current, value) {
2914
3142
  }
2915
3143
  return textNode;
2916
3144
  }
2917
- // src/runtime/client-marker.ts
2918
- function updateClientMarker(scope, id, value) {
2919
- if (!scope)
2920
- return;
2921
- const marker = `bf-client:${id}`;
2922
- const walker = document.createTreeWalker(scope, NodeFilter.SHOW_COMMENT);
2923
- while (walker.nextNode()) {
2924
- if (walker.currentNode.nodeValue === marker) {
2925
- const comment = walker.currentNode;
2926
- let textNode = comment.nextSibling;
2927
- if (textNode?.nodeType !== Node.TEXT_NODE || !textNode.nodeValue?.startsWith("​")) {
2928
- textNode = document.createTextNode("​" + String(value ?? ""));
2929
- comment.parentNode?.insertBefore(textNode, comment.nextSibling);
2930
- } else {
2931
- textNode.nodeValue = "​" + String(value ?? "");
2932
- }
2933
- return;
2934
- }
2935
- }
2936
- }
2937
3145
  // src/runtime/render.ts
2938
3146
  function render(container, nameOrDef, props = {}) {
2939
3147
  let name;
@@ -3014,9 +3222,9 @@ export {
3014
3222
  useContext,
3015
3223
  upsertChildItem,
3016
3224
  upsertChild,
3017
- updateClientMarker,
3018
3225
  unwrap,
3019
- untrack3 as untrack,
3226
+ untrack4 as untrack,
3227
+ textOrNode,
3020
3228
  textNodeAfterComment as tAfter,
3021
3229
  styleToCss,
3022
3230
  spreadAttrs,
@@ -3030,21 +3238,21 @@ export {
3030
3238
  rehydrateAll,
3031
3239
  registerTemplate,
3032
3240
  registerComponent,
3033
- reconcileList,
3034
- reconcileElements,
3035
3241
  queryHref,
3036
3242
  qsaItem,
3037
3243
  qsaChildScopes,
3038
3244
  qsaChildScope,
3039
3245
  qsa,
3040
3246
  provideContext,
3041
- patchSlotRange,
3042
3247
  patchLeaf,
3043
3248
  parseHTML,
3044
3249
  onMount,
3045
3250
  onCleanup,
3251
+ mapArrayLazy,
3046
3252
  mapArrayAnchored,
3047
3253
  mapArray,
3254
+ lazySlots,
3255
+ lazyClaimSlots,
3048
3256
  isSSRPortal,
3049
3257
  insert,
3050
3258
  initChild,
@@ -3053,10 +3261,8 @@ export {
3053
3261
  hasTemplate,
3054
3262
  getTemplate,
3055
3263
  getRegisteredDef,
3056
- getPropsUpdateFn,
3057
3264
  getLoopNodes,
3058
3265
  getLoopChildren,
3059
- getComponentProps,
3060
3266
  getComponentInit,
3061
3267
  forwardProps,
3062
3268
  formatDate,
@@ -3064,24 +3270,26 @@ export {
3064
3270
  findSiblingSlot,
3065
3271
  findScope,
3066
3272
  find,
3273
+ escapeTextOrNode,
3067
3274
  escapeText,
3068
3275
  escapeAttr,
3069
3276
  endTurn,
3070
3277
  disposeScope,
3071
3278
  date,
3072
3279
  cssEscape,
3073
- createSignal2 as createSignal,
3280
+ createSignal3 as createSignal,
3074
3281
  createSelector,
3075
3282
  createSearchParams,
3076
3283
  createRoot3 as createRoot,
3077
3284
  createRecordingSink,
3078
3285
  createPortal,
3079
3286
  createMemo,
3080
- createEffect4 as createEffect,
3287
+ createEffect5 as createEffect,
3081
3288
  createDisposableEffect,
3082
3289
  createContext,
3083
3290
  createComponent,
3084
3291
  cleanupPortalPlaceholder,
3292
+ claimSlots,
3085
3293
  beginTurn,
3086
3294
  batch,
3087
3295
  applyRestAttrs,