@barefootjs/client 0.27.0 → 0.28.1

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.
@@ -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,11 +1756,30 @@ var _parentScopeId = null;
1758
1756
  function setParentScopeId(id) {
1759
1757
  _parentScopeId = id;
1760
1758
  }
1761
- 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) {
1762
1778
  if (props == null)
1763
1779
  props = {};
1780
+ const rowMount = mountAt ? null : takeRowMountPoint();
1764
1781
  if (typeof nameOrDef !== "string") {
1765
- return createComponentFromDef(nameOrDef, props, key);
1782
+ return createComponentFromDef(nameOrDef, props, key, mountAt, rowMount);
1766
1783
  }
1767
1784
  const name = nameOrDef;
1768
1785
  const templateFn = getTemplate(name);
@@ -1822,8 +1839,13 @@ function createComponent(nameOrDef, props = {}, key, slot) {
1822
1839
  if (key !== undefined) {
1823
1840
  element.setAttribute(BF_KEY, String(key));
1824
1841
  }
1825
- const prevScope = setCurrentScope(element);
1826
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);
1827
1849
  let placeholderWrapper = null;
1828
1850
  if (rootIsDeferredPlaceholder) {
1829
1851
  placeholderWrapper = parseHTML("<div></div>").firstChild;
@@ -1964,7 +1986,7 @@ function insertGetterChildren(element, children) {
1964
1986
  element.appendChild(document.createTextNode(String(children)));
1965
1987
  }
1966
1988
  }
1967
- function createComponentFromDef(def, props, key) {
1989
+ function createComponentFromDef(def, props, key, mountAt, rowMount) {
1968
1990
  if (!def.template) {
1969
1991
  throw new Error("[BarefootJS] createComponent with ComponentDef requires a template function");
1970
1992
  }
@@ -1983,6 +2005,11 @@ function createComponentFromDef(def, props, key) {
1983
2005
  if (key !== undefined) {
1984
2006
  element.setAttribute(BF_KEY, String(key));
1985
2007
  }
2008
+ if (mountAt) {
2009
+ mountAt.replaceWith(element);
2010
+ } else if (rowMount) {
2011
+ rowMount.container.insertBefore(element, rowMount.anchor);
2012
+ }
1986
2013
  def.init(element, props);
1987
2014
  hydratedScopes.add(element);
1988
2015
  return element;
@@ -2163,9 +2190,7 @@ function upsertChildItem(primaryEl, name, slotId, props, key, anchorScope) {
2163
2190
  const ph = qsaItem(primaryEl, `[data-bf-ph="${phId}"]`);
2164
2191
  if (ph) {
2165
2192
  const slot = slotId ? buildSlotInfo(primaryEl, slotId, anchorScope) : undefined;
2166
- const comp = createComponent(name, props, key, slot);
2167
- ph.replaceWith(comp);
2168
- return comp;
2193
+ return createComponent(name, props, key, slot, ph);
2169
2194
  }
2170
2195
  return null;
2171
2196
  }
@@ -2277,7 +2302,7 @@ function removeScope(scope) {
2277
2302
  ex.remove();
2278
2303
  }
2279
2304
  }
2280
- function createItemScope(item, index, renderItem, existingPrimary, existingExtras, existingStart) {
2305
+ function createItemScope(item, index, renderItem, existingPrimary, existingExtras, existingStart, rowMount) {
2281
2306
  let primaryEl;
2282
2307
  let dispose;
2283
2308
  let setItem;
@@ -2287,7 +2312,14 @@ function createItemScope(item, index, renderItem, existingPrimary, existingExtra
2287
2312
  dispose = d;
2288
2313
  const [itemAccessor, itemSetter] = createSignal(item);
2289
2314
  setItem = itemSetter;
2290
- 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
+ }
2291
2323
  if (existingPrimary) {
2292
2324
  extras = existingExtras ?? [];
2293
2325
  startMarker = existingStart ?? null;
@@ -2301,6 +2333,9 @@ function createItemScope(item, index, renderItem, existingPrimary, existingExtra
2301
2333
  }
2302
2334
  return;
2303
2335
  });
2336
+ if (rowMount && !existingPrimary && extras.length > 0 && primaryEl.parentNode) {
2337
+ primaryEl.remove();
2338
+ }
2304
2339
  return { startMarker, primaryEl, extras, dispose, setItem };
2305
2340
  }
2306
2341
  function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
@@ -2342,7 +2377,7 @@ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2342
2377
  for (let i2 = existingRanges.length;i2 < items.length; i2++) {
2343
2378
  const item = items[i2];
2344
2379
  const key = getKey ? getKey(item, i2) : String(i2);
2345
- const scope = createItemScope(item, i2, renderItem);
2380
+ const scope = createItemScope(item, i2, renderItem, undefined, undefined, undefined, { container, anchor });
2346
2381
  if (!scope.primaryEl.dataset.key)
2347
2382
  scope.primaryEl.setAttribute(BF_KEY, key);
2348
2383
  scopes.set(key, scope);
@@ -2421,7 +2456,7 @@ function mapArray(accessor, container, getKey, renderItem, markerId, bfId) {
2421
2456
  existing.setItem(item);
2422
2457
  desiredOrder.push(existing);
2423
2458
  } else {
2424
- const scope = createItemScope(item, i2, renderItem);
2459
+ const scope = createItemScope(item, i2, renderItem, undefined, undefined, undefined, { container, anchor });
2425
2460
  if (!scope.primaryEl.dataset.key)
2426
2461
  scope.primaryEl.setAttribute(BF_KEY, key);
2427
2462
  scopes.set(key, scope);
@@ -2617,6 +2652,197 @@ function mapArrayAnchored(accessor, container, getKey, renderItem, markerId, bfI
2617
2652
  }
2618
2653
  }, bfId);
2619
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
+ }
2620
2846
  // src/runtime/patch-leaf.ts
2621
2847
  function patchLeaf(el, html) {
2622
2848
  const tpl = document.createElement("template");
@@ -2723,11 +2949,11 @@ function claimMarkerlessText(root, spec) {
2723
2949
  }
2724
2950
  const existing = parent.childNodes[idx];
2725
2951
  if (existing && existing.nodeType === Node.TEXT_NODE) {
2726
- return { kind: "text", node: existing };
2952
+ return { kind: "text", ref: existing };
2727
2953
  }
2728
2954
  const node = document.createTextNode("");
2729
2955
  parent.insertBefore(node, existing ?? null);
2730
- return { kind: "text", node };
2956
+ return { kind: "text", ref: node };
2731
2957
  }
2732
2958
  function claimOne(root, spec) {
2733
2959
  if (spec.kind === "text" && spec.markerless) {
@@ -2737,7 +2963,8 @@ function claimOne(root, spec) {
2737
2963
  if (!anchor)
2738
2964
  return null;
2739
2965
  if (spec.kind === "text") {
2740
- return { kind: "text", node: textNodeAfterComment(anchor) };
2966
+ const next = anchor.nextSibling;
2967
+ return { kind: "text", ref: next?.nodeType === Node.TEXT_NODE ? next : anchor };
2741
2968
  }
2742
2969
  const end = findMarkupEnd(anchor);
2743
2970
  if (!end) {
@@ -2746,8 +2973,42 @@ function claimOne(root, spec) {
2746
2973
  }
2747
2974
  return { kind: "markup", start: anchor, end, last: undefined };
2748
2975
  }
2749
- function writeText(ref, value) {
2750
- ref.node.nodeValue = String(value ?? "");
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);
3007
+ if (!end) {
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 };
2751
3012
  }
2752
3013
  function clearMarkupRange(start, end) {
2753
3014
  const parent = end.parentNode;
@@ -2791,19 +3052,37 @@ function writeSlot(refs, id, value) {
2791
3052
  return;
2792
3053
  }
2793
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
+ }
2794
3063
  writeText(ref, value);
2795
3064
  } else {
2796
3065
  writeMarkup(ref, value);
2797
3066
  }
2798
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
+ }
2799
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) {
2800
3079
  const refs = new Map;
2801
3080
  for (const spec of plan) {
2802
3081
  const ref = claimOne(root, spec);
2803
3082
  if (ref)
2804
3083
  refs.set(spec.id, ref);
2805
3084
  }
2806
- return { write: (id, value) => writeSlot(refs, id, value) };
3085
+ return refs;
2807
3086
  }
2808
3087
  function lazySlots(root, plan) {
2809
3088
  let claimed = null;
@@ -2813,6 +3092,14 @@ function lazySlots(root, plan) {
2813
3092
  claimed.write(id, value);
2814
3093
  };
2815
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
+ };
3102
+ }
2816
3103
  // src/runtime/style.ts
2817
3104
  function styleToCss(value) {
2818
3105
  if (value == null)
@@ -3336,6 +3623,7 @@ export {
3336
3623
  upsertChild,
3337
3624
  unwrap,
3338
3625
  untrack,
3626
+ textOrNode,
3339
3627
  textNodeAfterComment as tAfter,
3340
3628
  styleToCss,
3341
3629
  spreadAttrs,
@@ -3359,9 +3647,11 @@ export {
3359
3647
  parseHTML,
3360
3648
  onMount,
3361
3649
  onCleanup,
3650
+ mapArrayLazy,
3362
3651
  mapArrayAnchored,
3363
3652
  mapArray,
3364
3653
  lazySlots,
3654
+ lazyClaimSlots,
3365
3655
  isSSRPortal,
3366
3656
  insert,
3367
3657
  initChild,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/client",
3
- "version": "0.27.0",
3
+ "version": "0.28.1",
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.27.0"
58
+ "@barefootjs/shared": "0.28.1"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@barefootjs/jsx": ">=0.2.0"