@barefootjs/client 0.26.4 → 0.27.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.
@@ -1352,8 +1352,6 @@ var _parentScopeId = null;
1352
1352
  function setParentScopeId(id) {
1353
1353
  _parentScopeId = id;
1354
1354
  }
1355
- var propsUpdateMap = new WeakMap;
1356
- var propsMap = new WeakMap;
1357
1355
  function createComponent(nameOrDef, props = {}, key, slot) {
1358
1356
  if (props == null)
1359
1357
  props = {};
@@ -1445,24 +1443,8 @@ function createComponent(nameOrDef, props = {}, key, slot) {
1445
1443
  }
1446
1444
  setCurrentScope(prevScope);
1447
1445
  hydratedScopes.add(element);
1448
- propsMap.set(element, props);
1449
- registerPropsUpdate(element, name, props);
1450
1446
  return element;
1451
1447
  }
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
1448
  function renderChild(name, props, key, slotSuffix) {
1467
1449
  const templateFn = getTemplate(name);
1468
1450
  const suffix = slotSuffix ? `_${slotSuffix}` : "";
@@ -1528,6 +1510,11 @@ function escapeText(value) {
1528
1510
  return "";
1529
1511
  return escapeAttr(value);
1530
1512
  }
1513
+ function escapeTextOrNode(value) {
1514
+ if (typeof Node !== "undefined" && value instanceof Node)
1515
+ return value;
1516
+ return escapeText(value);
1517
+ }
1531
1518
  var SVG_NS = "http://www.w3.org/2000/svg";
1532
1519
  function parseHTML(html, parent) {
1533
1520
  const tpl = document.createElement("template");
@@ -1592,7 +1579,6 @@ function createComponentFromDef(def, props, key) {
1592
1579
  }
1593
1580
  def.init(element, props);
1594
1581
  hydratedScopes.add(element);
1595
- propsMap.set(element, props);
1596
1582
  return element;
1597
1583
  }
1598
1584
 
@@ -1650,7 +1636,7 @@ function createPortal(children, container = document.body, options) {
1650
1636
  }
1651
1637
  };
1652
1638
  }
1653
- // src/runtime/reconcile-elements.ts
1639
+ // src/runtime/loop-markers.ts
1654
1640
  function findLoopMarkers(container, markerId) {
1655
1641
  let startMarker = null;
1656
1642
  let endMarker = null;
@@ -1695,14 +1681,6 @@ function getElementsBetweenMarkers(start, end) {
1695
1681
  }
1696
1682
  return elements;
1697
1683
  }
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
1684
  function getLoopChildren(container, markerId) {
1707
1685
  const { startMarker, endMarker } = findLoopMarkers(container, markerId);
1708
1686
  if (startMarker && endMarker) {
@@ -1723,159 +1701,6 @@ function getLoopNodes(container, markerId) {
1723
1701
  }
1724
1702
  return Array.from(container.childNodes);
1725
1703
  }
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
1704
  // src/runtime/qsa-item.ts
1880
1705
  function* itemRootElements(primaryEl) {
1881
1706
  yield primaryEl;
@@ -2412,35 +2237,44 @@ function patchLeaf(el, html) {
2412
2237
  }
2413
2238
  el.replaceChildren(...Array.from(next.childNodes));
2414
2239
  }
2415
- // src/runtime/patch-slot-range.ts
2416
- function patchSlotRange(scope, id, html) {
2240
+ // src/runtime/claim-slots.ts
2241
+ function resolvePath(root, path) {
2242
+ let node = root;
2243
+ for (const index of path) {
2244
+ const child = node.childNodes[index];
2245
+ if (!child)
2246
+ return null;
2247
+ node = child;
2248
+ }
2249
+ return node;
2250
+ }
2251
+ function isSlotComment(node, id) {
2252
+ return node != null && node.nodeType === Node.COMMENT_NODE && node.nodeValue === `bf:${id}`;
2253
+ }
2254
+ function findOwnedMarker(root, id) {
2417
2255
  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;
2256
+ const parentOwned = id.startsWith(BF_PARENT_OWNED_PREFIX);
2257
+ const registryInfo = commentScopeRegistry.get(root);
2258
+ const boundary = registryInfo ? registryInfo.commentNode.parentElement : root;
2259
+ for (const comment of commentsInScope(root)) {
2422
2260
  if (comment.nodeValue !== marker)
2423
2261
  continue;
2262
+ if (parentOwned)
2263
+ return comment;
2424
2264
  let owned = true;
2425
- for (let el = comment.parentElement;el && el !== scope; el = el.parentElement) {
2265
+ for (let el = comment.parentElement;el && el !== boundary; el = el.parentElement) {
2426
2266
  if (el.hasAttribute(BF_SCOPE)) {
2427
2267
  owned = false;
2428
2268
  break;
2429
2269
  }
2430
2270
  }
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;
2271
+ if (owned)
2272
+ return comment;
2440
2273
  }
2274
+ return null;
2275
+ }
2276
+ function findMarkupEnd(start) {
2441
2277
  let depth = 0;
2442
- let end = null;
2443
- const toRemove = [];
2444
2278
  let node = start.nextSibling;
2445
2279
  while (node) {
2446
2280
  if (node.nodeType === Node.COMMENT_NODE) {
@@ -2448,25 +2282,131 @@ function patchSlotRange(scope, id, html) {
2448
2282
  if (value.startsWith("bf:")) {
2449
2283
  depth++;
2450
2284
  } else if (value === "/") {
2451
- if (depth === 0) {
2452
- end = node;
2453
- break;
2454
- }
2285
+ if (depth === 0)
2286
+ return node;
2455
2287
  depth--;
2456
2288
  }
2457
2289
  }
2458
- toRemove.push(node);
2459
2290
  node = node.nextSibling;
2460
2291
  }
2292
+ return null;
2293
+ }
2294
+ function resolveAnchor(root, spec) {
2295
+ if (spec.path.length > 0) {
2296
+ const resolved = resolvePath(root, spec.path);
2297
+ if (isSlotComment(resolved, spec.id))
2298
+ return resolved;
2299
+ console.warn(`[barefootjs] claim path for slot ${spec.id} did not resolve to its bf:${spec.id} marker; falling back to a scan`);
2300
+ }
2301
+ const found = findOwnedMarker(root, spec.id);
2302
+ if (!found) {
2303
+ console.warn(`[barefootjs] slot ${spec.id} marker not found; skipping`);
2304
+ }
2305
+ return found;
2306
+ }
2307
+ function claimMarkerlessText(root, spec) {
2308
+ if (spec.path.length === 0) {
2309
+ console.warn(`[barefootjs] markerless slot ${spec.id} has an empty path; skipping`);
2310
+ return null;
2311
+ }
2312
+ const parentPath = spec.path.slice(0, -1);
2313
+ const idx = spec.path[spec.path.length - 1];
2314
+ const parent = resolvePath(root, parentPath);
2315
+ if (!parent) {
2316
+ console.warn(`[barefootjs] markerless claim path for slot ${spec.id} did not resolve to a parent node; skipping`);
2317
+ return null;
2318
+ }
2319
+ const existing = parent.childNodes[idx];
2320
+ if (existing && existing.nodeType === Node.TEXT_NODE) {
2321
+ return { kind: "text", node: existing };
2322
+ }
2323
+ const node = document.createTextNode("");
2324
+ parent.insertBefore(node, existing ?? null);
2325
+ return { kind: "text", node };
2326
+ }
2327
+ function claimOne(root, spec) {
2328
+ if (spec.kind === "text" && spec.markerless) {
2329
+ return claimMarkerlessText(root, spec);
2330
+ }
2331
+ const anchor = resolveAnchor(root, spec);
2332
+ if (!anchor)
2333
+ return null;
2334
+ if (spec.kind === "text") {
2335
+ return { kind: "text", node: textNodeAfterComment(anchor) };
2336
+ }
2337
+ const end = findMarkupEnd(anchor);
2461
2338
  if (!end) {
2462
- console.warn(`[barefootjs] preamble region bf:${id} has no end marker; skipping patch`);
2339
+ console.warn(`[barefootjs] slot ${spec.id} has no end marker; skipping`);
2340
+ return null;
2341
+ }
2342
+ return { kind: "markup", start: anchor, end, last: undefined };
2343
+ }
2344
+ function writeText(ref, value) {
2345
+ ref.node.nodeValue = String(value ?? "");
2346
+ }
2347
+ function clearMarkupRange(start, end) {
2348
+ const parent = end.parentNode;
2349
+ if (!parent)
2350
+ return;
2351
+ let node = start.nextSibling;
2352
+ while (node && node !== end) {
2353
+ const next = node.nextSibling;
2354
+ parent.removeChild(node);
2355
+ node = next;
2356
+ }
2357
+ }
2358
+ function writeMarkup(ref, value) {
2359
+ const { start, end } = ref;
2360
+ const parent = end.parentNode;
2361
+ if (!parent)
2362
+ return;
2363
+ if (value != null && value.__isSlot)
2364
+ return;
2365
+ if (typeof Node !== "undefined" && value instanceof Node) {
2366
+ if (value === ref.last)
2367
+ return;
2368
+ clearMarkupRange(start, end);
2369
+ parent.insertBefore(value, end);
2370
+ ref.last = value;
2463
2371
  return;
2464
2372
  }
2465
- for (const n of toRemove)
2466
- parent.removeChild(n);
2373
+ const text = String(value ?? "");
2374
+ if (text === ref.last)
2375
+ return;
2376
+ clearMarkupRange(start, end);
2467
2377
  const tpl = document.createElement("template");
2468
- tpl.innerHTML = html;
2378
+ tpl.innerHTML = text;
2469
2379
  parent.insertBefore(tpl.content, end);
2380
+ ref.last = text;
2381
+ }
2382
+ function writeSlot(refs, id, value) {
2383
+ const ref = refs.get(id);
2384
+ if (!ref) {
2385
+ console.warn(`[barefootjs] no claimed slot for id ${id}; write ignored`);
2386
+ return;
2387
+ }
2388
+ if (ref.kind === "text") {
2389
+ writeText(ref, value);
2390
+ } else {
2391
+ writeMarkup(ref, value);
2392
+ }
2393
+ }
2394
+ function claimSlots(root, plan) {
2395
+ const refs = new Map;
2396
+ for (const spec of plan) {
2397
+ const ref = claimOne(root, spec);
2398
+ if (ref)
2399
+ refs.set(spec.id, ref);
2400
+ }
2401
+ return { write: (id, value) => writeSlot(refs, id, value) };
2402
+ }
2403
+ function lazySlots(root, plan) {
2404
+ let claimed = null;
2405
+ return (id, value) => {
2406
+ if (!claimed)
2407
+ claimed = claimSlots(root, plan);
2408
+ claimed.write(id, value);
2409
+ };
2470
2410
  }
2471
2411
  // src/runtime/apply-rest-attrs.ts
2472
2412
  import { createEffect as createEffect2 } from "@barefootjs/client/reactive";
@@ -2914,26 +2854,6 @@ function __bfText(current, value) {
2914
2854
  }
2915
2855
  return textNode;
2916
2856
  }
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
2857
  // src/runtime/render.ts
2938
2858
  function render(container, nameOrDef, props = {}) {
2939
2859
  let name;
@@ -3014,7 +2934,6 @@ export {
3014
2934
  useContext,
3015
2935
  upsertChildItem,
3016
2936
  upsertChild,
3017
- updateClientMarker,
3018
2937
  unwrap,
3019
2938
  untrack3 as untrack,
3020
2939
  textNodeAfterComment as tAfter,
@@ -3030,21 +2949,19 @@ export {
3030
2949
  rehydrateAll,
3031
2950
  registerTemplate,
3032
2951
  registerComponent,
3033
- reconcileList,
3034
- reconcileElements,
3035
2952
  queryHref,
3036
2953
  qsaItem,
3037
2954
  qsaChildScopes,
3038
2955
  qsaChildScope,
3039
2956
  qsa,
3040
2957
  provideContext,
3041
- patchSlotRange,
3042
2958
  patchLeaf,
3043
2959
  parseHTML,
3044
2960
  onMount,
3045
2961
  onCleanup,
3046
2962
  mapArrayAnchored,
3047
2963
  mapArray,
2964
+ lazySlots,
3048
2965
  isSSRPortal,
3049
2966
  insert,
3050
2967
  initChild,
@@ -3053,10 +2970,8 @@ export {
3053
2970
  hasTemplate,
3054
2971
  getTemplate,
3055
2972
  getRegisteredDef,
3056
- getPropsUpdateFn,
3057
2973
  getLoopNodes,
3058
2974
  getLoopChildren,
3059
- getComponentProps,
3060
2975
  getComponentInit,
3061
2976
  forwardProps,
3062
2977
  formatDate,
@@ -3064,6 +2979,7 @@ export {
3064
2979
  findSiblingSlot,
3065
2980
  findScope,
3066
2981
  find,
2982
+ escapeTextOrNode,
3067
2983
  escapeText,
3068
2984
  escapeAttr,
3069
2985
  endTurn,
@@ -3082,6 +2998,7 @@ export {
3082
2998
  createContext,
3083
2999
  createComponent,
3084
3000
  cleanupPortalPlaceholder,
3001
+ claimSlots,
3085
3002
  beginTurn,
3086
3003
  batch,
3087
3004
  applyRestAttrs,
@@ -0,0 +1,26 @@
1
+ /**
2
+ * BarefootJS - Loop Boundary Marker Lookup
3
+ *
4
+ * `<!--bf-loop:<id>-->` / `<!--bf-/loop:<id>-->` comment markers delimit a
5
+ * loop's rendered range inside its container so `mapArray`/`mapArrayAnchored`
6
+ * (`./map-array.ts`) can reconcile only that range without disturbing
7
+ * non-loop siblings. These lookups used to also back the now-removed
8
+ * `reconcileElements`/`reconcileList` element-reconciler (slot unification
9
+ * A4); they remain as the compiler-facing marker API.
10
+ */
11
+ /**
12
+ * Get loop children from a container, respecting bf-loop boundary markers.
13
+ * When markers are present, returns only elements between them.
14
+ * When absent, returns all children (backward compatible).
15
+ * Exported for use by compiler-generated hydration code.
16
+ */
17
+ export declare function getLoopChildren(container: HTMLElement, markerId?: string): HTMLElement[];
18
+ /**
19
+ * Like {@link getLoopChildren}, but returns every node between the loop
20
+ * boundary markers — Comments (per-item `<!--bf-loop-i-->` markers) and
21
+ * text included. The branch-clearing path needs to remove the per-item
22
+ * marker comments alongside elements; otherwise stale markers would
23
+ * accumulate when a branch swap forces mapArray to start over (#1212).
24
+ */
25
+ export declare function getLoopNodes(container: HTMLElement, markerId?: string): Node[];
26
+ //# sourceMappingURL=loop-markers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop-markers.d.ts","sourceRoot":"","sources":["../../src/runtime/loop-markers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAyDH;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,EAAE,CAMxF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,CAY9E"}