@barefootjs/client 0.21.0 → 0.21.2

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.
@@ -640,7 +640,41 @@ function belongsToScope(element, scope) {
640
640
  if (element.getAttribute(BF_SCOPE))
641
641
  return false;
642
642
  const nearestScope = element.closest(`[${BF_SCOPE}]`);
643
- return nearestScope === scope;
643
+ if (nearestScope !== scope)
644
+ return false;
645
+ return !isInsideNestedCommentScope(element, scope);
646
+ }
647
+ function isInsideNestedCommentScope(element, scope) {
648
+ let current = element;
649
+ while (current !== scope) {
650
+ const parent = current.parentNode;
651
+ if (!parent)
652
+ return false;
653
+ if (isTopLevelCommentScopeNode(current))
654
+ return true;
655
+ current = parent;
656
+ }
657
+ return false;
658
+ }
659
+ function isTopLevelCommentScopeNode(node) {
660
+ const parent = node.parentNode;
661
+ if (!parent)
662
+ return false;
663
+ let depth = 0;
664
+ let sib = parent.firstChild;
665
+ while (sib) {
666
+ if (sib === node)
667
+ return depth > 0;
668
+ if (sib.nodeType === Node.COMMENT_NODE) {
669
+ const value = sib.nodeValue ?? "";
670
+ if (value.startsWith(BF_SCOPE_COMMENT_PREFIX))
671
+ depth++;
672
+ else if (value.startsWith(BF_SCOPE_COMMENT_END_PREFIX) && depth > 0)
673
+ depth--;
674
+ }
675
+ sib = sib.nextSibling;
676
+ }
677
+ return false;
644
678
  }
645
679
  function isInCommentScopeRange(element, commentNode) {
646
680
  const boundary = getCommentScopeBoundary(commentNode);
@@ -665,7 +699,8 @@ function find(scope, selector, ignoreScope) {
665
699
  if (commentInfo) {
666
700
  if (candidate.parentElement === commentInfo.commentNode.parentElement)
667
701
  return candidate;
668
- if (!candidate.closest(`[${BF_SCOPE}]`))
702
+ const nearestScope = candidate.closest(`[${BF_SCOPE}]`);
703
+ if (!nearestScope || !isInCommentScopeRange(nearestScope, commentInfo.commentNode))
669
704
  return candidate;
670
705
  } else {
671
706
  if (belongsToScope(candidate, scope))
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/runtime/query.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,sFAAsF;AACtF,eAAO,MAAM,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAGJ,CAAA;AAqCnC;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,EACjC,OAAO,CAAC,EAAE,OAAO,GAChB,OAAO,GAAG,IAAI,CAoDhB;AA6HD;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAClB,KAAK,EAAE,OAAO,GAAG,IAAI,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,OAAO,GACpB,OAAO,GAAG,IAAI,CA2BhB;AAuBD;;;;;;;GAOG;AACH,wBAAgB,GAAG,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CA0BxE;AA+CD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAU9E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CAO1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAO7E;AAED;;;;;GAKG;AACH,wBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAE9E;AAkDD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,GAAG,IAAI,CAehB;AAmFD;;;;;;;GAOG;AACH,wBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAkC3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAS3D;AAED,OAAO,EAAE,oBAAoB,IAAI,MAAM,EAAE,CAAA"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/runtime/query.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,sFAAsF;AACtF,eAAO,MAAM,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAGJ,CAAA;AAqCnC;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,EACjC,OAAO,CAAC,EAAE,OAAO,GAChB,OAAO,GAAG,IAAI,CAoDhB;AAiLD;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAClB,KAAK,EAAE,OAAO,GAAG,IAAI,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,OAAO,GACpB,OAAO,GAAG,IAAI,CAiChB;AAuBD;;;;;;;GAOG;AACH,wBAAgB,GAAG,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CA0BxE;AA+CD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAU9E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CAO1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAO7E;AAED;;;;;GAKG;AACH,wBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAE9E;AAkDD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,GAAG,IAAI,CAehB;AAmFD;;;;;;;GAOG;AACH,wBAAgB,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAkC3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAS3D;AAED,OAAO,EAAE,oBAAoB,IAAI,MAAM,EAAE,CAAA"}
@@ -1048,7 +1048,41 @@ function belongsToScope(element, scope) {
1048
1048
  if (element.getAttribute(BF_SCOPE))
1049
1049
  return false;
1050
1050
  const nearestScope = element.closest(`[${BF_SCOPE}]`);
1051
- return nearestScope === scope;
1051
+ if (nearestScope !== scope)
1052
+ return false;
1053
+ return !isInsideNestedCommentScope(element, scope);
1054
+ }
1055
+ function isInsideNestedCommentScope(element, scope) {
1056
+ let current = element;
1057
+ while (current !== scope) {
1058
+ const parent = current.parentNode;
1059
+ if (!parent)
1060
+ return false;
1061
+ if (isTopLevelCommentScopeNode(current))
1062
+ return true;
1063
+ current = parent;
1064
+ }
1065
+ return false;
1066
+ }
1067
+ function isTopLevelCommentScopeNode(node) {
1068
+ const parent = node.parentNode;
1069
+ if (!parent)
1070
+ return false;
1071
+ let depth = 0;
1072
+ let sib = parent.firstChild;
1073
+ while (sib) {
1074
+ if (sib === node)
1075
+ return depth > 0;
1076
+ if (sib.nodeType === Node.COMMENT_NODE) {
1077
+ const value = sib.nodeValue ?? "";
1078
+ if (value.startsWith(BF_SCOPE_COMMENT_PREFIX))
1079
+ depth++;
1080
+ else if (value.startsWith(BF_SCOPE_COMMENT_END_PREFIX) && depth > 0)
1081
+ depth--;
1082
+ }
1083
+ sib = sib.nextSibling;
1084
+ }
1085
+ return false;
1052
1086
  }
1053
1087
  function isInCommentScopeRange(element, commentNode) {
1054
1088
  const boundary = getCommentScopeBoundary(commentNode);
@@ -1073,7 +1107,8 @@ function find(scope, selector, ignoreScope) {
1073
1107
  if (commentInfo) {
1074
1108
  if (candidate.parentElement === commentInfo.commentNode.parentElement)
1075
1109
  return candidate;
1076
- if (!candidate.closest(`[${BF_SCOPE}]`))
1110
+ const nearestScope = candidate.closest(`[${BF_SCOPE}]`);
1111
+ if (!nearestScope || !isInCommentScopeRange(nearestScope, commentInfo.commentNode))
1077
1112
  return candidate;
1078
1113
  } else {
1079
1114
  if (belongsToScope(candidate, scope))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/client",
3
- "version": "0.21.0",
3
+ "version": "0.21.2",
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.21.0"
58
+ "@barefootjs/shared": "0.21.2"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@barefootjs/jsx": ">=0.2.0"
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { commentScopeRegistry, getCommentScopeBoundary } from './scope.ts'
10
10
  import { hydratedScopes } from './hydration-state.ts'
11
- import { BF_SCOPE, BF_SLOT, BF_PORTAL_OWNER, BF_PARENT_OWNED_PREFIX, BF_SCOPE_COMMENT_PREFIX } from '@barefootjs/shared'
11
+ import { BF_SCOPE, BF_SLOT, BF_PORTAL_OWNER, BF_PARENT_OWNED_PREFIX, BF_SCOPE_COMMENT_PREFIX, BF_SCOPE_COMMENT_END_PREFIX } from '@barefootjs/shared'
12
12
 
13
13
  /** CSS attribute-value escape with a fallback for environments lacking CSS.escape. */
14
14
  export const cssEscape: (s: string) => string =
@@ -222,7 +222,59 @@ function belongsToScope(element: Element, scope: Element): boolean {
222
222
 
223
223
  // Check if nearest scope matches
224
224
  const nearestScope = element.closest(`[${BF_SCOPE}]`)
225
- return nearestScope === scope
225
+ if (nearestScope !== scope) return false
226
+
227
+ // A fragment-rooted child mounted between `element` and `scope` has no
228
+ // `bf-s` element of its own (#2302) — `closest()` walks straight past it
229
+ // to `scope`, so a slot search on `scope` can wrongly claim a descendant
230
+ // that actually belongs to that nested child's comment scope. Reject it.
231
+ return !isInsideNestedCommentScope(element, scope)
232
+ }
233
+
234
+ /**
235
+ * True if some ancestor of `element` (strictly below `scope`) is a
236
+ * top-level node of a fragment-rooted child's `<!--bf-scope:...-->` range —
237
+ * i.e. `element` structurally belongs to a nested child component even
238
+ * though that child has no `bf-s`-attributed element for `.closest()` to
239
+ * stop at, and regardless of whether `commentScopeRegistry` has registered
240
+ * it yet (a parent's own slot lookups can run before its child-scope
241
+ * lookups register the child, #2302).
242
+ */
243
+ function isInsideNestedCommentScope(element: Element, scope: Element): boolean {
244
+ let current: Node = element
245
+ while (current !== scope) {
246
+ const parent: Node | null = current.parentNode
247
+ if (!parent) return false
248
+ if (isTopLevelCommentScopeNode(current)) return true
249
+ current = parent
250
+ }
251
+ return false
252
+ }
253
+
254
+ /**
255
+ * True if `node` falls inside a `<!--bf-scope:...-->` … `<!--bf-/scope:...-->`
256
+ * range among its own siblings — i.e. `node` is (or is inside) a top-level
257
+ * element of a fragment-rooted child mounted as `node`'s sibling. Single
258
+ * left-to-right pass over the sibling list with a nesting depth counter, so
259
+ * a nested child's own begin/end pair doesn't prematurely close an outer
260
+ * one. Missing end markers (older SSR output) leave depth un-decremented,
261
+ * matching `getCommentScopeBoundary`'s legacy end-of-parent fallback.
262
+ */
263
+ function isTopLevelCommentScopeNode(node: Node): boolean {
264
+ const parent = node.parentNode
265
+ if (!parent) return false
266
+ let depth = 0
267
+ let sib: Node | null = parent.firstChild
268
+ while (sib) {
269
+ if (sib === node) return depth > 0
270
+ if (sib.nodeType === Node.COMMENT_NODE) {
271
+ const value = (sib as Comment).nodeValue ?? ''
272
+ if (value.startsWith(BF_SCOPE_COMMENT_PREFIX)) depth++
273
+ else if (value.startsWith(BF_SCOPE_COMMENT_END_PREFIX) && depth > 0) depth--
274
+ }
275
+ sib = sib.nextSibling
276
+ }
277
+ return false
226
278
  }
227
279
 
228
280
  /**
@@ -271,10 +323,16 @@ export function find(
271
323
  if (ignoreScope) return candidate
272
324
  if (commentInfo) {
273
325
  // Comment scope: top-level siblings in the comment range are always
274
- // accepted (even if they have bf-s, like proxy elements). Descendants
275
- // are accepted only if not inside a nested bf-s scope.
326
+ // accepted (even if they have bf-s, like proxy elements). A descendant
327
+ // nested inside one of those siblings is accepted unless a *nested*
328
+ // child scope sits between it and this one — i.e. its nearest bf-s
329
+ // ancestor falls within this comment's own sibling range. An ancestor
330
+ // bf-s element outside that range doesn't disqualify the candidate:
331
+ // a fragment-root child mounted inside a normal parent island always
332
+ // has one, and `.closest()` alone can't tell the two apart (#2302).
276
333
  if (candidate.parentElement === commentInfo.commentNode.parentElement) return candidate
277
- if (!candidate.closest(`[${BF_SCOPE}]`)) return candidate
334
+ const nearestScope = candidate.closest(`[${BF_SCOPE}]`)
335
+ if (!nearestScope || !isInCommentScopeRange(nearestScope, commentInfo.commentNode)) return candidate
278
336
  } else {
279
337
  if (belongsToScope(candidate, scope)) return candidate
280
338
  }