@barefootjs/client 0.26.3 → 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.
@@ -1758,8 +1758,6 @@ var _parentScopeId = null;
1758
1758
  function setParentScopeId(id) {
1759
1759
  _parentScopeId = id;
1760
1760
  }
1761
- var propsUpdateMap = new WeakMap;
1762
- var propsMap = new WeakMap;
1763
1761
  function createComponent(nameOrDef, props = {}, key, slot) {
1764
1762
  if (props == null)
1765
1763
  props = {};
@@ -1851,24 +1849,8 @@ function createComponent(nameOrDef, props = {}, key, slot) {
1851
1849
  }
1852
1850
  setCurrentScope(prevScope);
1853
1851
  hydratedScopes.add(element);
1854
- propsMap.set(element, props);
1855
- registerPropsUpdate(element, name, props);
1856
1852
  return element;
1857
1853
  }
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
1854
  function renderChild(name, props, key, slotSuffix) {
1873
1855
  const templateFn = getTemplate(name);
1874
1856
  const suffix = slotSuffix ? `_${slotSuffix}` : "";
@@ -1934,6 +1916,11 @@ function escapeText(value) {
1934
1916
  return "";
1935
1917
  return escapeAttr(value);
1936
1918
  }
1919
+ function escapeTextOrNode(value) {
1920
+ if (typeof Node !== "undefined" && value instanceof Node)
1921
+ return value;
1922
+ return escapeText(value);
1923
+ }
1937
1924
  var SVG_NS = "http://www.w3.org/2000/svg";
1938
1925
  function parseHTML(html, parent) {
1939
1926
  const tpl = document.createElement("template");
@@ -1998,7 +1985,6 @@ function createComponentFromDef(def, props, key) {
1998
1985
  }
1999
1986
  def.init(element, props);
2000
1987
  hydratedScopes.add(element);
2001
- propsMap.set(element, props);
2002
1988
  return element;
2003
1989
  }
2004
1990
 
@@ -2056,7 +2042,7 @@ function createPortal(children, container = document.body, options) {
2056
2042
  }
2057
2043
  };
2058
2044
  }
2059
- // src/runtime/reconcile-elements.ts
2045
+ // src/runtime/loop-markers.ts
2060
2046
  function findLoopMarkers(container, markerId) {
2061
2047
  let startMarker = null;
2062
2048
  let endMarker = null;
@@ -2101,14 +2087,6 @@ function getElementsBetweenMarkers(start, end) {
2101
2087
  }
2102
2088
  return elements;
2103
2089
  }
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
2090
  function getLoopChildren(container, markerId) {
2113
2091
  const { startMarker, endMarker } = findLoopMarkers(container, markerId);
2114
2092
  if (startMarker && endMarker) {
@@ -2129,159 +2107,6 @@ function getLoopNodes(container, markerId) {
2129
2107
  }
2130
2108
  return Array.from(container.childNodes);
2131
2109
  }
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
2110
  // src/runtime/qsa-item.ts
2286
2111
  function* itemRootElements(primaryEl) {
2287
2112
  yield primaryEl;
@@ -2792,6 +2617,202 @@ function mapArrayAnchored(accessor, container, getKey, renderItem, markerId, bfI
2792
2617
  }
2793
2618
  }, bfId);
2794
2619
  }
2620
+ // src/runtime/patch-leaf.ts
2621
+ function patchLeaf(el, html) {
2622
+ const tpl = document.createElement("template");
2623
+ tpl.innerHTML = html;
2624
+ const next = tpl.content.firstElementChild;
2625
+ if (!next)
2626
+ return;
2627
+ if (next.tagName !== el.tagName) {
2628
+ console.warn("[barefootjs] flatMap leaf root tag changed under a stable key " + `(<${el.tagName.toLowerCase()}> -> <${next.tagName.toLowerCase()}>); ` + "give each branch its own key so the node is replaced instead of patched.");
2629
+ }
2630
+ for (const name of el.getAttributeNames()) {
2631
+ if (name === "data-key")
2632
+ continue;
2633
+ if (!next.hasAttribute(name))
2634
+ el.removeAttribute(name);
2635
+ }
2636
+ for (const name of next.getAttributeNames()) {
2637
+ if (name === "data-key")
2638
+ continue;
2639
+ const value = next.getAttribute(name);
2640
+ if (value !== null && el.getAttribute(name) !== value)
2641
+ el.setAttribute(name, value);
2642
+ }
2643
+ el.replaceChildren(...Array.from(next.childNodes));
2644
+ }
2645
+ // src/runtime/claim-slots.ts
2646
+ function resolvePath(root, path) {
2647
+ let node = root;
2648
+ for (const index of path) {
2649
+ const child = node.childNodes[index];
2650
+ if (!child)
2651
+ return null;
2652
+ node = child;
2653
+ }
2654
+ return node;
2655
+ }
2656
+ function isSlotComment(node, id) {
2657
+ return node != null && node.nodeType === Node.COMMENT_NODE && node.nodeValue === `bf:${id}`;
2658
+ }
2659
+ function findOwnedMarker(root, id) {
2660
+ const marker = `bf:${id}`;
2661
+ const parentOwned = id.startsWith(BF_PARENT_OWNED_PREFIX);
2662
+ const registryInfo = commentScopeRegistry.get(root);
2663
+ const boundary = registryInfo ? registryInfo.commentNode.parentElement : root;
2664
+ for (const comment of commentsInScope(root)) {
2665
+ if (comment.nodeValue !== marker)
2666
+ continue;
2667
+ if (parentOwned)
2668
+ return comment;
2669
+ let owned = true;
2670
+ for (let el = comment.parentElement;el && el !== boundary; el = el.parentElement) {
2671
+ if (el.hasAttribute(BF_SCOPE)) {
2672
+ owned = false;
2673
+ break;
2674
+ }
2675
+ }
2676
+ if (owned)
2677
+ return comment;
2678
+ }
2679
+ return null;
2680
+ }
2681
+ function findMarkupEnd(start) {
2682
+ let depth = 0;
2683
+ let node = start.nextSibling;
2684
+ while (node) {
2685
+ if (node.nodeType === Node.COMMENT_NODE) {
2686
+ const value = node.nodeValue ?? "";
2687
+ if (value.startsWith("bf:")) {
2688
+ depth++;
2689
+ } else if (value === "/") {
2690
+ if (depth === 0)
2691
+ return node;
2692
+ depth--;
2693
+ }
2694
+ }
2695
+ node = node.nextSibling;
2696
+ }
2697
+ return null;
2698
+ }
2699
+ function resolveAnchor(root, spec) {
2700
+ if (spec.path.length > 0) {
2701
+ const resolved = resolvePath(root, spec.path);
2702
+ if (isSlotComment(resolved, spec.id))
2703
+ return resolved;
2704
+ console.warn(`[barefootjs] claim path for slot ${spec.id} did not resolve to its bf:${spec.id} marker; falling back to a scan`);
2705
+ }
2706
+ const found = findOwnedMarker(root, spec.id);
2707
+ if (!found) {
2708
+ console.warn(`[barefootjs] slot ${spec.id} marker not found; skipping`);
2709
+ }
2710
+ return found;
2711
+ }
2712
+ function claimMarkerlessText(root, spec) {
2713
+ if (spec.path.length === 0) {
2714
+ console.warn(`[barefootjs] markerless slot ${spec.id} has an empty path; skipping`);
2715
+ return null;
2716
+ }
2717
+ const parentPath = spec.path.slice(0, -1);
2718
+ const idx = spec.path[spec.path.length - 1];
2719
+ const parent = resolvePath(root, parentPath);
2720
+ if (!parent) {
2721
+ console.warn(`[barefootjs] markerless claim path for slot ${spec.id} did not resolve to a parent node; skipping`);
2722
+ return null;
2723
+ }
2724
+ const existing = parent.childNodes[idx];
2725
+ if (existing && existing.nodeType === Node.TEXT_NODE) {
2726
+ return { kind: "text", node: existing };
2727
+ }
2728
+ const node = document.createTextNode("");
2729
+ parent.insertBefore(node, existing ?? null);
2730
+ return { kind: "text", node };
2731
+ }
2732
+ function claimOne(root, spec) {
2733
+ if (spec.kind === "text" && spec.markerless) {
2734
+ return claimMarkerlessText(root, spec);
2735
+ }
2736
+ const anchor = resolveAnchor(root, spec);
2737
+ if (!anchor)
2738
+ return null;
2739
+ if (spec.kind === "text") {
2740
+ return { kind: "text", node: textNodeAfterComment(anchor) };
2741
+ }
2742
+ const end = findMarkupEnd(anchor);
2743
+ if (!end) {
2744
+ console.warn(`[barefootjs] slot ${spec.id} has no end marker; skipping`);
2745
+ return null;
2746
+ }
2747
+ return { kind: "markup", start: anchor, end, last: undefined };
2748
+ }
2749
+ function writeText(ref, value) {
2750
+ ref.node.nodeValue = String(value ?? "");
2751
+ }
2752
+ function clearMarkupRange(start, end) {
2753
+ const parent = end.parentNode;
2754
+ if (!parent)
2755
+ return;
2756
+ let node = start.nextSibling;
2757
+ while (node && node !== end) {
2758
+ const next = node.nextSibling;
2759
+ parent.removeChild(node);
2760
+ node = next;
2761
+ }
2762
+ }
2763
+ function writeMarkup(ref, value) {
2764
+ const { start, end } = ref;
2765
+ const parent = end.parentNode;
2766
+ if (!parent)
2767
+ return;
2768
+ if (value != null && value.__isSlot)
2769
+ return;
2770
+ if (typeof Node !== "undefined" && value instanceof Node) {
2771
+ if (value === ref.last)
2772
+ return;
2773
+ clearMarkupRange(start, end);
2774
+ parent.insertBefore(value, end);
2775
+ ref.last = value;
2776
+ return;
2777
+ }
2778
+ const text = String(value ?? "");
2779
+ if (text === ref.last)
2780
+ return;
2781
+ clearMarkupRange(start, end);
2782
+ const tpl = document.createElement("template");
2783
+ tpl.innerHTML = text;
2784
+ parent.insertBefore(tpl.content, end);
2785
+ ref.last = text;
2786
+ }
2787
+ function writeSlot(refs, id, value) {
2788
+ const ref = refs.get(id);
2789
+ if (!ref) {
2790
+ console.warn(`[barefootjs] no claimed slot for id ${id}; write ignored`);
2791
+ return;
2792
+ }
2793
+ if (ref.kind === "text") {
2794
+ writeText(ref, value);
2795
+ } else {
2796
+ writeMarkup(ref, value);
2797
+ }
2798
+ }
2799
+ function claimSlots(root, plan) {
2800
+ const refs = new Map;
2801
+ for (const spec of plan) {
2802
+ const ref = claimOne(root, spec);
2803
+ if (ref)
2804
+ refs.set(spec.id, ref);
2805
+ }
2806
+ return { write: (id, value) => writeSlot(refs, id, value) };
2807
+ }
2808
+ function lazySlots(root, plan) {
2809
+ let claimed = null;
2810
+ return (id, value) => {
2811
+ if (!claimed)
2812
+ claimed = claimSlots(root, plan);
2813
+ claimed.write(id, value);
2814
+ };
2815
+ }
2795
2816
  // src/runtime/style.ts
2796
2817
  function styleToCss(value) {
2797
2818
  if (value == null)
@@ -3233,26 +3254,6 @@ function __bfText(current, value) {
3233
3254
  }
3234
3255
  return textNode;
3235
3256
  }
3236
- // src/runtime/client-marker.ts
3237
- function updateClientMarker(scope, id, value) {
3238
- if (!scope)
3239
- return;
3240
- const marker = `bf-client:${id}`;
3241
- const walker = document.createTreeWalker(scope, NodeFilter.SHOW_COMMENT);
3242
- while (walker.nextNode()) {
3243
- if (walker.currentNode.nodeValue === marker) {
3244
- const comment = walker.currentNode;
3245
- let textNode = comment.nextSibling;
3246
- if (textNode?.nodeType !== Node.TEXT_NODE || !textNode.nodeValue?.startsWith("​")) {
3247
- textNode = document.createTextNode("​" + String(value ?? ""));
3248
- comment.parentNode?.insertBefore(textNode, comment.nextSibling);
3249
- } else {
3250
- textNode.nodeValue = "​" + String(value ?? "");
3251
- }
3252
- return;
3253
- }
3254
- }
3255
- }
3256
3257
  // src/runtime/render.ts
3257
3258
  function render(container, nameOrDef, props = {}) {
3258
3259
  let name;
@@ -3333,7 +3334,6 @@ export {
3333
3334
  useContext,
3334
3335
  upsertChildItem,
3335
3336
  upsertChild,
3336
- updateClientMarker,
3337
3337
  unwrap,
3338
3338
  untrack,
3339
3339
  textNodeAfterComment as tAfter,
@@ -3349,19 +3349,19 @@ export {
3349
3349
  rehydrateAll,
3350
3350
  registerTemplate,
3351
3351
  registerComponent,
3352
- reconcileList,
3353
- reconcileElements,
3354
3352
  queryHref,
3355
3353
  qsaItem,
3356
3354
  qsaChildScopes,
3357
3355
  qsaChildScope,
3358
3356
  qsa,
3359
3357
  provideContext,
3358
+ patchLeaf,
3360
3359
  parseHTML,
3361
3360
  onMount,
3362
3361
  onCleanup,
3363
3362
  mapArrayAnchored,
3364
3363
  mapArray,
3364
+ lazySlots,
3365
3365
  isSSRPortal,
3366
3366
  insert,
3367
3367
  initChild,
@@ -3370,10 +3370,8 @@ export {
3370
3370
  hasTemplate,
3371
3371
  getTemplate,
3372
3372
  getRegisteredDef,
3373
- getPropsUpdateFn,
3374
3373
  getLoopNodes,
3375
3374
  getLoopChildren,
3376
- getComponentProps,
3377
3375
  getComponentInit,
3378
3376
  forwardProps,
3379
3377
  formatDate,
@@ -3381,6 +3379,7 @@ export {
3381
3379
  findSiblingSlot,
3382
3380
  findScope,
3383
3381
  find,
3382
+ escapeTextOrNode,
3384
3383
  escapeText,
3385
3384
  escapeAttr,
3386
3385
  endTurn,
@@ -3399,6 +3398,7 @@ export {
3399
3398
  createContext,
3400
3399
  createComponent,
3401
3400
  cleanupPortalPlaceholder,
3401
+ claimSlots,
3402
3402
  beginTurn,
3403
3403
  batch,
3404
3404
  applyRestAttrs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/client",
3
- "version": "0.26.3",
3
+ "version": "0.27.0",
4
4
  "description": "BarefootJS client package: reactive primitives (SSR-safe) plus browser runtime under the `/runtime` subpath (compiler target)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -55,7 +55,7 @@
55
55
  "directory": "packages/client"
56
56
  },
57
57
  "dependencies": {
58
- "@barefootjs/shared": "0.26.3"
58
+ "@barefootjs/shared": "0.27.0"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@barefootjs/jsx": ">=0.2.0"
package/src/reactive.ts CHANGED
@@ -319,7 +319,8 @@ export function createSignal<T>(initialValue: T, __bfId?: string): Signal<T> {
319
319
  export function createEffect(fn: EffectFn, __bfId?: string, __bfKind: SubscriberKind = 'effect'): void {
320
320
  // Note: Nested effects are now allowed. runEffect() properly saves/restores
321
321
  // prevEffect, so nested effects correctly track their own dependencies.
322
- // This enables synchronous component initialization in reconcileList.
322
+ // This enables synchronous component initialization inside loop reconcilers
323
+ // (mapArray/mapArrayAnchored).
323
324
 
324
325
  const effect: EffectContext = {
325
326
  fn,