@barefootjs/client 0.21.2 → 0.21.3
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.
- package/dist/runtime/index.js +20 -7
- package/dist/runtime/query.d.ts +42 -0
- package/dist/runtime/query.d.ts.map +1 -1
- package/dist/runtime/standalone.js +20 -7
- package/package.json +2 -2
- package/src/runtime/insert.ts +19 -8
- package/src/runtime/query.ts +48 -1
package/dist/runtime/index.js
CHANGED
|
@@ -712,6 +712,20 @@ function find(scope, selector, ignoreScope) {
|
|
|
712
712
|
return findInPortals(scopeId, selector);
|
|
713
713
|
return null;
|
|
714
714
|
}
|
|
715
|
+
function findCondTarget(scope, selector) {
|
|
716
|
+
const commentInfo = commentScopeRegistry.get(scope);
|
|
717
|
+
if (!commentInfo) {
|
|
718
|
+
return scope.querySelector(selector);
|
|
719
|
+
}
|
|
720
|
+
for (const candidate of candidatesInScope(scope, selector)) {
|
|
721
|
+
if (candidate.parentElement === commentInfo.commentNode.parentElement)
|
|
722
|
+
return candidate;
|
|
723
|
+
const nearestScope = candidate.closest(`[${BF_SCOPE}]`);
|
|
724
|
+
if (!nearestScope || !isInCommentScopeRange(nearestScope, commentInfo.commentNode))
|
|
725
|
+
return candidate;
|
|
726
|
+
}
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
715
729
|
function findInPortals(scopeId, selector) {
|
|
716
730
|
const portals = document.querySelectorAll(`[${BF_PORTAL_OWNER}="${scopeId}"]`);
|
|
717
731
|
for (const portal of portals) {
|
|
@@ -2531,7 +2545,7 @@ function insert(scope, id, conditionFn, whenTrue, whenFalse, bfId) {
|
|
|
2531
2545
|
}
|
|
2532
2546
|
function autoFocusConditionalElement(region, id) {
|
|
2533
2547
|
requestAnimationFrame(() => {
|
|
2534
|
-
const condEl = region.anchor ? findCondElInRange(region.anchor, id) : region.bindScope
|
|
2548
|
+
const condEl = region.anchor ? findCondElInRange(region.anchor, id) : findCondTarget(region.bindScope, `[${BF_COND}="${id}"]`);
|
|
2535
2549
|
if (condEl) {
|
|
2536
2550
|
const autofocusEl = condEl.matches("[autofocus]") ? condEl : condEl.querySelector("[autofocus]");
|
|
2537
2551
|
if (autofocusEl && typeof autofocusEl.focus === "function") {
|
|
@@ -2572,15 +2586,14 @@ function updateFragmentConditional(region, id, result) {
|
|
|
2572
2586
|
if (region.anchor) {
|
|
2573
2587
|
startComment = findCondStartInRange(region.anchor, id);
|
|
2574
2588
|
} else {
|
|
2575
|
-
const
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
startComment = walker.currentNode;
|
|
2589
|
+
for (const comment of commentsInScope(scope)) {
|
|
2590
|
+
if (comment.nodeValue === startMarker) {
|
|
2591
|
+
startComment = comment;
|
|
2579
2592
|
break;
|
|
2580
2593
|
}
|
|
2581
2594
|
}
|
|
2582
2595
|
}
|
|
2583
|
-
const condEl = region.anchor ? findCondElInRange(region.anchor, id) : scope
|
|
2596
|
+
const condEl = region.anchor ? findCondElInRange(region.anchor, id) : findCondTarget(scope, `[${BF_COND}="${id}"]`);
|
|
2584
2597
|
const endMarker = `bf-cond-end:${id}`;
|
|
2585
2598
|
if (startComment) {
|
|
2586
2599
|
const nodesToRemove = [];
|
|
@@ -2620,7 +2633,7 @@ function updateFragmentConditional(region, id, result) {
|
|
|
2620
2633
|
}
|
|
2621
2634
|
}
|
|
2622
2635
|
function updateElementConditional(region, id, result) {
|
|
2623
|
-
const condEl = region.anchor ? findCondElInRange(region.anchor, id) : region.bindScope
|
|
2636
|
+
const condEl = region.anchor ? findCondElInRange(region.anchor, id) : findCondTarget(region.bindScope, `[${BF_COND}="${id}"]`);
|
|
2624
2637
|
if (!condEl)
|
|
2625
2638
|
return;
|
|
2626
2639
|
const { html, slots } = result;
|
package/dist/runtime/query.d.ts
CHANGED
|
@@ -30,6 +30,32 @@ export declare function findScope(name: string, idx: number, parent: Element | D
|
|
|
30
30
|
* @returns The matching element or null
|
|
31
31
|
*/
|
|
32
32
|
export declare function find(scope: Element | null, selector: string, ignoreScope?: boolean): Element | null;
|
|
33
|
+
/**
|
|
34
|
+
* Find a conditional's own target (a `bf-c="id"` element, for `insert.ts`)
|
|
35
|
+
* within `scope`'s content range.
|
|
36
|
+
*
|
|
37
|
+
* Deliberately narrower than `find()`: for a regular (non-comment) scope,
|
|
38
|
+
* this is a **plain, unfiltered** `scope.querySelector(selector)` — not
|
|
39
|
+
* `find()`'s `belongsToScope`-gated search. `belongsToScope` accepts a
|
|
40
|
+
* candidate only if `candidate.closest('[bf-s]')` is *exactly* `scope` —
|
|
41
|
+
* which can never hold when `scope` itself carries no `bf-s`. That's the
|
|
42
|
+
* common case for `insert()`'s `region.bindScope`: a `mapArray` loop item's
|
|
43
|
+
* cloned template root (e.g. a `.comment-item` `<div>`) is keyed by
|
|
44
|
+
* `data-key` for reconciliation, not given its own `bf-s`, so any `bf-c`
|
|
45
|
+
* conditional inside that item would be rejected outright by
|
|
46
|
+
* `belongsToScope` — not because of any nested-child-scope subtlety, just
|
|
47
|
+
* because `scope` itself isn't `bf-s`-addressable. Using `find()` here
|
|
48
|
+
* previously broke exactly that shape (piconic-ai/barefootjs#2313's first
|
|
49
|
+
* attempt, caught by the `site/ui` e2e suite's `SocialThreadDemo`
|
|
50
|
+
* comment-editing test — a per-comment `editing` conditional inside a
|
|
51
|
+
* `sortedComments().map(...)` loop item with no `bf-s` of its own).
|
|
52
|
+
*
|
|
53
|
+
* A comment-scope proxy (fragment-root component) still gets the
|
|
54
|
+
* comment-range-bounded, nested-fragment-excluding search — the same rule
|
|
55
|
+
* `find()`'s comment branch uses — since that IS what's needed to walk past
|
|
56
|
+
* a fragment-root's own top-level siblings correctly (#2312).
|
|
57
|
+
*/
|
|
58
|
+
export declare function findCondTarget(scope: Element, selector: string): Element | null;
|
|
33
59
|
/**
|
|
34
60
|
* Find an element matching a selector, checking the element itself first,
|
|
35
61
|
* then its descendants. Unlike querySelector() which only searches descendants,
|
|
@@ -98,6 +124,22 @@ export declare function $c(scope: Element | null, ...ids: string[]): (Element |
|
|
|
98
124
|
* walker-owned root fragments.
|
|
99
125
|
*/
|
|
100
126
|
export declare function findCommentChildScope(scope: Element, parentIds: readonly string[], slotId: string): Element | null;
|
|
127
|
+
/**
|
|
128
|
+
* Enumerate comment nodes in a scope's DOM range: the comment-scope
|
|
129
|
+
* sibling range for comment-anchored scopes, the element subtree
|
|
130
|
+
* otherwise. Mirrors candidatesInScope's notion of "where the scope's
|
|
131
|
+
* content lives", but yields comments instead of elements.
|
|
132
|
+
*
|
|
133
|
+
* Exported for `insert.ts`'s branch-swap path (`updateFragmentConditional`),
|
|
134
|
+
* which otherwise walked `scope`'s own descendants with a bare
|
|
135
|
+
* `document.createTreeWalker` — never finding a conditional's
|
|
136
|
+
* `bf-cond-start:`/`bf-cond-end:` markers when they sit as a *sibling* of
|
|
137
|
+
* the scope's comment-scope proxy rather than nested inside it (true for
|
|
138
|
+
* any fragment-root component's own top-level conditional, since the proxy
|
|
139
|
+
* is one specific top-level element and the conditional's markers may be
|
|
140
|
+
* others). See piconic-ai/sora's `ListSidebar` for the real-world repro.
|
|
141
|
+
*/
|
|
142
|
+
export declare function commentsInScope(scope: Element): Generator<Comment>;
|
|
101
143
|
/**
|
|
102
144
|
* Find Text nodes for reactive text expressions marked by comment nodes.
|
|
103
145
|
* Expects marker format: <!--bf:sX-->text<!--/-->
|
|
@@ -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;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;
|
|
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;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAW/E;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;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAiB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,CAkBnE;AAyDD;;;;;;;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"}
|
|
@@ -1120,6 +1120,20 @@ function find(scope, selector, ignoreScope) {
|
|
|
1120
1120
|
return findInPortals(scopeId, selector);
|
|
1121
1121
|
return null;
|
|
1122
1122
|
}
|
|
1123
|
+
function findCondTarget(scope, selector) {
|
|
1124
|
+
const commentInfo = commentScopeRegistry.get(scope);
|
|
1125
|
+
if (!commentInfo) {
|
|
1126
|
+
return scope.querySelector(selector);
|
|
1127
|
+
}
|
|
1128
|
+
for (const candidate of candidatesInScope(scope, selector)) {
|
|
1129
|
+
if (candidate.parentElement === commentInfo.commentNode.parentElement)
|
|
1130
|
+
return candidate;
|
|
1131
|
+
const nearestScope = candidate.closest(`[${BF_SCOPE}]`);
|
|
1132
|
+
if (!nearestScope || !isInCommentScopeRange(nearestScope, commentInfo.commentNode))
|
|
1133
|
+
return candidate;
|
|
1134
|
+
}
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1123
1137
|
function findInPortals(scopeId, selector) {
|
|
1124
1138
|
const portals = document.querySelectorAll(`[${BF_PORTAL_OWNER}="${scopeId}"]`);
|
|
1125
1139
|
for (const portal of portals) {
|
|
@@ -2932,7 +2946,7 @@ function insert(scope, id, conditionFn, whenTrue, whenFalse, bfId) {
|
|
|
2932
2946
|
}
|
|
2933
2947
|
function autoFocusConditionalElement(region, id) {
|
|
2934
2948
|
requestAnimationFrame(() => {
|
|
2935
|
-
const condEl = region.anchor ? findCondElInRange(region.anchor, id) : region.bindScope
|
|
2949
|
+
const condEl = region.anchor ? findCondElInRange(region.anchor, id) : findCondTarget(region.bindScope, `[${BF_COND}="${id}"]`);
|
|
2936
2950
|
if (condEl) {
|
|
2937
2951
|
const autofocusEl = condEl.matches("[autofocus]") ? condEl : condEl.querySelector("[autofocus]");
|
|
2938
2952
|
if (autofocusEl && typeof autofocusEl.focus === "function") {
|
|
@@ -2973,15 +2987,14 @@ function updateFragmentConditional(region, id, result) {
|
|
|
2973
2987
|
if (region.anchor) {
|
|
2974
2988
|
startComment = findCondStartInRange(region.anchor, id);
|
|
2975
2989
|
} else {
|
|
2976
|
-
const
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
startComment = walker.currentNode;
|
|
2990
|
+
for (const comment of commentsInScope(scope)) {
|
|
2991
|
+
if (comment.nodeValue === startMarker) {
|
|
2992
|
+
startComment = comment;
|
|
2980
2993
|
break;
|
|
2981
2994
|
}
|
|
2982
2995
|
}
|
|
2983
2996
|
}
|
|
2984
|
-
const condEl = region.anchor ? findCondElInRange(region.anchor, id) : scope
|
|
2997
|
+
const condEl = region.anchor ? findCondElInRange(region.anchor, id) : findCondTarget(scope, `[${BF_COND}="${id}"]`);
|
|
2985
2998
|
const endMarker = `bf-cond-end:${id}`;
|
|
2986
2999
|
if (startComment) {
|
|
2987
3000
|
const nodesToRemove = [];
|
|
@@ -3021,7 +3034,7 @@ function updateFragmentConditional(region, id, result) {
|
|
|
3021
3034
|
}
|
|
3022
3035
|
}
|
|
3023
3036
|
function updateElementConditional(region, id, result) {
|
|
3024
|
-
const condEl = region.anchor ? findCondElInRange(region.anchor, id) : region.bindScope
|
|
3037
|
+
const condEl = region.anchor ? findCondElInRange(region.anchor, id) : findCondTarget(region.bindScope, `[${BF_COND}="${id}"]`);
|
|
3025
3038
|
if (!condEl)
|
|
3026
3039
|
return;
|
|
3027
3040
|
const { html, slots } = result;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/client",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.3",
|
|
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.
|
|
58
|
+
"@barefootjs/shared": "0.21.3"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@barefootjs/jsx": ">=0.2.0"
|
package/src/runtime/insert.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { createEffect, untrack } from '@barefootjs/client/reactive'
|
|
10
|
-
import { find } from './query.ts'
|
|
10
|
+
import { find, findCondTarget, commentsInScope } from './query.ts'
|
|
11
11
|
import { setParentScopeId, parseHTML } from './component.ts'
|
|
12
12
|
import { commentScopeRegistry, getCommentScopeBoundary } from './scope.ts'
|
|
13
13
|
import { BF_COND, BF_SCOPE, BF_LOOP_ITEM } from '@barefootjs/shared'
|
|
@@ -342,7 +342,7 @@ function autoFocusConditionalElement(region: CondRegion, id: string): void {
|
|
|
342
342
|
requestAnimationFrame(() => {
|
|
343
343
|
const condEl = region.anchor
|
|
344
344
|
? findCondElInRange(region.anchor, id)
|
|
345
|
-
: region.bindScope
|
|
345
|
+
: findCondTarget(region.bindScope, `[${BF_COND}="${id}"]`)
|
|
346
346
|
if (condEl) {
|
|
347
347
|
const autofocusEl = condEl.matches('[autofocus]')
|
|
348
348
|
? condEl
|
|
@@ -406,10 +406,15 @@ function updateFragmentConditional(region: CondRegion, id: string, result: Branc
|
|
|
406
406
|
if (region.anchor) {
|
|
407
407
|
startComment = findCondStartInRange(region.anchor, id)
|
|
408
408
|
} else {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
409
|
+
// commentsInScope is comment-scope-aware: for a fragment-root
|
|
410
|
+
// component's scope (a comment-scope proxy element), it walks the
|
|
411
|
+
// proxy's *sibling* range rather than only `scope`'s own descendants —
|
|
412
|
+
// required when this conditional's markers are themselves a top-level
|
|
413
|
+
// sibling of the proxy, not nested inside it (a bare TreeWalker(scope)
|
|
414
|
+
// never finds them, silently freezing the branch after first render).
|
|
415
|
+
for (const comment of commentsInScope(scope)) {
|
|
416
|
+
if (comment.nodeValue === startMarker) {
|
|
417
|
+
startComment = comment
|
|
413
418
|
break
|
|
414
419
|
}
|
|
415
420
|
}
|
|
@@ -417,7 +422,7 @@ function updateFragmentConditional(region: CondRegion, id: string, result: Branc
|
|
|
417
422
|
|
|
418
423
|
const condEl = region.anchor
|
|
419
424
|
? findCondElInRange(region.anchor, id)
|
|
420
|
-
: scope
|
|
425
|
+
: findCondTarget(scope, `[${BF_COND}="${id}"]`)
|
|
421
426
|
|
|
422
427
|
const endMarker = `bf-cond-end:${id}`
|
|
423
428
|
|
|
@@ -486,9 +491,15 @@ function updateFragmentConditional(region: CondRegion, id: string, result: Branc
|
|
|
486
491
|
* Update element conditional (single element with bf-c)
|
|
487
492
|
*/
|
|
488
493
|
function updateElementConditional(region: CondRegion, id: string, result: BranchTemplateResult): void {
|
|
494
|
+
// findCondTarget, not find(): for a comment-scope proxy it resolves a
|
|
495
|
+
// top-level sibling conditional the same way first hydration does, but
|
|
496
|
+
// for a regular scope it's a plain scope.querySelector(...) — not
|
|
497
|
+
// find()'s belongsToScope-gated search, which requires scope itself to
|
|
498
|
+
// carry bf-s (a mapArray loop item's cloned root usually doesn't; see
|
|
499
|
+
// findCondTarget's doc comment in query.ts).
|
|
489
500
|
const condEl = region.anchor
|
|
490
501
|
? findCondElInRange(region.anchor, id)
|
|
491
|
-
: region.bindScope
|
|
502
|
+
: findCondTarget(region.bindScope, `[${BF_COND}="${id}"]`)
|
|
492
503
|
if (!condEl) return
|
|
493
504
|
|
|
494
505
|
const { html, slots } = result
|
package/src/runtime/query.ts
CHANGED
|
@@ -345,6 +345,44 @@ export function find(
|
|
|
345
345
|
return null
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
/**
|
|
349
|
+
* Find a conditional's own target (a `bf-c="id"` element, for `insert.ts`)
|
|
350
|
+
* within `scope`'s content range.
|
|
351
|
+
*
|
|
352
|
+
* Deliberately narrower than `find()`: for a regular (non-comment) scope,
|
|
353
|
+
* this is a **plain, unfiltered** `scope.querySelector(selector)` — not
|
|
354
|
+
* `find()`'s `belongsToScope`-gated search. `belongsToScope` accepts a
|
|
355
|
+
* candidate only if `candidate.closest('[bf-s]')` is *exactly* `scope` —
|
|
356
|
+
* which can never hold when `scope` itself carries no `bf-s`. That's the
|
|
357
|
+
* common case for `insert()`'s `region.bindScope`: a `mapArray` loop item's
|
|
358
|
+
* cloned template root (e.g. a `.comment-item` `<div>`) is keyed by
|
|
359
|
+
* `data-key` for reconciliation, not given its own `bf-s`, so any `bf-c`
|
|
360
|
+
* conditional inside that item would be rejected outright by
|
|
361
|
+
* `belongsToScope` — not because of any nested-child-scope subtlety, just
|
|
362
|
+
* because `scope` itself isn't `bf-s`-addressable. Using `find()` here
|
|
363
|
+
* previously broke exactly that shape (piconic-ai/barefootjs#2313's first
|
|
364
|
+
* attempt, caught by the `site/ui` e2e suite's `SocialThreadDemo`
|
|
365
|
+
* comment-editing test — a per-comment `editing` conditional inside a
|
|
366
|
+
* `sortedComments().map(...)` loop item with no `bf-s` of its own).
|
|
367
|
+
*
|
|
368
|
+
* A comment-scope proxy (fragment-root component) still gets the
|
|
369
|
+
* comment-range-bounded, nested-fragment-excluding search — the same rule
|
|
370
|
+
* `find()`'s comment branch uses — since that IS what's needed to walk past
|
|
371
|
+
* a fragment-root's own top-level siblings correctly (#2312).
|
|
372
|
+
*/
|
|
373
|
+
export function findCondTarget(scope: Element, selector: string): Element | null {
|
|
374
|
+
const commentInfo = commentScopeRegistry.get(scope)
|
|
375
|
+
if (!commentInfo) {
|
|
376
|
+
return scope.querySelector(selector)
|
|
377
|
+
}
|
|
378
|
+
for (const candidate of candidatesInScope(scope, selector)) {
|
|
379
|
+
if (candidate.parentElement === commentInfo.commentNode.parentElement) return candidate
|
|
380
|
+
const nearestScope = candidate.closest(`[${BF_SCOPE}]`)
|
|
381
|
+
if (!nearestScope || !isInCommentScopeRange(nearestScope, commentInfo.commentNode)) return candidate
|
|
382
|
+
}
|
|
383
|
+
return null
|
|
384
|
+
}
|
|
385
|
+
|
|
348
386
|
/**
|
|
349
387
|
* Search in portals owned by a scope.
|
|
350
388
|
*/
|
|
@@ -609,8 +647,17 @@ export function findCommentChildScope(
|
|
|
609
647
|
* sibling range for comment-anchored scopes, the element subtree
|
|
610
648
|
* otherwise. Mirrors candidatesInScope's notion of "where the scope's
|
|
611
649
|
* content lives", but yields comments instead of elements.
|
|
650
|
+
*
|
|
651
|
+
* Exported for `insert.ts`'s branch-swap path (`updateFragmentConditional`),
|
|
652
|
+
* which otherwise walked `scope`'s own descendants with a bare
|
|
653
|
+
* `document.createTreeWalker` — never finding a conditional's
|
|
654
|
+
* `bf-cond-start:`/`bf-cond-end:` markers when they sit as a *sibling* of
|
|
655
|
+
* the scope's comment-scope proxy rather than nested inside it (true for
|
|
656
|
+
* any fragment-root component's own top-level conditional, since the proxy
|
|
657
|
+
* is one specific top-level element and the conditional's markers may be
|
|
658
|
+
* others). See piconic-ai/sora's `ListSidebar` for the real-world repro.
|
|
612
659
|
*/
|
|
613
|
-
function* commentsInScope(scope: Element): Generator<Comment> {
|
|
660
|
+
export function* commentsInScope(scope: Element): Generator<Comment> {
|
|
614
661
|
const commentInfo = commentScopeRegistry.get(scope)
|
|
615
662
|
|
|
616
663
|
if (commentInfo) {
|