@brust/runtime-dom 2.0.0-alpha.22 → 2.0.0-alpha.24
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/package.json +1 -1
- package/src/directives/for.ts +6 -3
- package/src/directives/if.ts +43 -8
- package/src/instance.ts +17 -3
package/package.json
CHANGED
package/src/directives/for.ts
CHANGED
|
@@ -33,6 +33,9 @@ BINDERS.push({
|
|
|
33
33
|
if (!adopt.includes(el)) el.remove()
|
|
34
34
|
let firstRun = true
|
|
35
35
|
|
|
36
|
+
// F415: a removed row's effects and cleanups go with it (they were bound under `inst.capture`).
|
|
37
|
+
const drops = new Map<Element, () => void>()
|
|
38
|
+
const drop = (n: Element) => { drops.get(n)?.(); drops.delete(n); n.remove() }
|
|
36
39
|
let rows = new Map<unknown, Element[]>() // key -> elements (array to tolerate duplicates)
|
|
37
40
|
let order: Element[] = []
|
|
38
41
|
|
|
@@ -71,12 +74,12 @@ BINDERS.push({
|
|
|
71
74
|
const rowScope: RowScope = { ...scope, [itemName]: item, ...(indexName ? { [indexName]: i } : {}), __own: own, __tick: computed(() => (outer ? outer() : 0) + own()) }
|
|
72
75
|
;(node as unknown as { __scope: RowScope }).__scope = rowScope
|
|
73
76
|
// a row that is itself a nested host is bound by its own instance
|
|
74
|
-
if (!node.hasAttribute('x-data')) { bindElement(inst,
|
|
77
|
+
if (!node.hasAttribute('x-data')) { const n = node; drops.set(n, inst.capture(() => { bindElement(inst, n, rowScope); walkChildren(inst, n, rowScope) })) }
|
|
75
78
|
}
|
|
76
79
|
;(next.get(key) ?? next.set(key, []).get(key)!).push(node)
|
|
77
80
|
nextOrder.push(node)
|
|
78
81
|
})
|
|
79
|
-
for (const [, pool] of rows) for (const stale of pool) stale
|
|
82
|
+
for (const [, pool] of rows) for (const stale of pool) drop(stale)
|
|
80
83
|
for (const left of adopt.splice(0)) left.remove()
|
|
81
84
|
firstRun = false
|
|
82
85
|
// place in order after the anchor
|
|
@@ -85,6 +88,6 @@ BINDERS.push({
|
|
|
85
88
|
rows = next; order = nextOrder
|
|
86
89
|
for (const n of created) hooks.mountTree(n) // nested hosts in new rows mount now, not on the observer's next tick
|
|
87
90
|
})
|
|
88
|
-
inst.onCleanup(() => { for (const n of order) n
|
|
91
|
+
inst.onCleanup(() => { for (const n of order) drop(n) })
|
|
89
92
|
},
|
|
90
93
|
})
|
package/src/directives/if.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { BINDERS, bindElement, walkChildren, parsed, read, hooks } from '../core-api'
|
|
2
|
+
/** The callback refs of the branch being inserted: attached once its whole subtree is bound and mounted (React attaches
|
|
3
|
+
* refs in the commit, after the DOM is placed and before layout effects). An inner branch inserted with it hands its
|
|
4
|
+
* refs over, so the outermost attaches them all in DOM post-order (children first, as React's commit does). */
|
|
5
|
+
type Attach = [el: Element, on: () => void]
|
|
6
|
+
let attach: Attach[] | null = null
|
|
7
|
+
/** `a` first when `b` contains it (8) or follows it (4) without being inside it (16). */
|
|
8
|
+
const postOrder = ([a]: Attach, [b]: Attach) => { const p = a.compareDocumentPosition(b); return p & 8 || (p & 20) === 4 ? -1 : 1 }
|
|
2
9
|
BINDERS.push({
|
|
3
10
|
match: (a) => a === 'x-if',
|
|
4
11
|
bind: ({ inst, el, attr, raw, scope }) => {
|
|
@@ -8,18 +15,46 @@ BINDERS.push({
|
|
|
8
15
|
template.removeAttribute('x-if'); template.removeAttribute('hidden')
|
|
9
16
|
el.replaceWith(anchor) // the server-rendered element becomes the first clone below
|
|
10
17
|
let current: Element | null = null
|
|
18
|
+
// F415/F416: what the branch bound (its effects, its x-ref) is let go before the element leaves, as React detaches
|
|
19
|
+
// a removed host's refs in the same commit.
|
|
20
|
+
let drop = () => {}
|
|
21
|
+
const remove = () => { drop(); drop = () => {}; current?.remove(); current = null } // the observer disposes nested hosts
|
|
11
22
|
inst.effect(() => {
|
|
12
23
|
const r = read(inst, p, scope, attr); if (!r.ok) return
|
|
13
24
|
const want = Boolean(r.value)
|
|
14
25
|
if (want && !current) {
|
|
15
|
-
current = template.cloneNode(true) as Element
|
|
16
|
-
anchor.after(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
const node = (current = template.cloneNode(true) as Element)
|
|
27
|
+
anchor.after(node)
|
|
28
|
+
const outer = attach; const mine: Attach[] = (attach = [])
|
|
29
|
+
try {
|
|
30
|
+
// nested hosts mount inside the capture too: an `x-cref` on one registers its detach on this branch
|
|
31
|
+
drop = inst.capture(() => { bindElement(inst, node, scope); walkChildren(inst, node, scope); hooks.mountTree(node) })
|
|
32
|
+
} finally { attach = outer }
|
|
33
|
+
if (outer) outer.push(...mine)
|
|
34
|
+
else for (const [, on] of mine.sort(postOrder)) on()
|
|
35
|
+
} else if (!want && current) remove()
|
|
22
36
|
})
|
|
23
|
-
inst.onCleanup(
|
|
37
|
+
inst.onCleanup(remove)
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
// F288 phase B (G5): `x-cref="cell"`, a callback ref inside a branch. The cell holds the callback (or a ref object); it
|
|
41
|
+
// gets the node when the branch is inserted and `null` (or the callback's returned cleanup) when it is removed. On a
|
|
42
|
+
// nested host the host's own instance meets it while it mounts, inside the branch's capture. (A host whose behavior
|
|
43
|
+
// chunk is not loaded yet binds later through whenBehavior, outside the capture: it attaches at once and detaches only
|
|
44
|
+
// with the owner, not the branch: reachable only if an inlined child's root can have its behavior in another chunk.)
|
|
45
|
+
BINDERS.push({
|
|
46
|
+
match: (a) => a === 'x-cref',
|
|
47
|
+
bind: ({ inst, el, raw }) => {
|
|
48
|
+
// on a nested host (an inlined native child's root) the cell is the caller's, as `x-pref`'s is
|
|
49
|
+
const owner = el === inst.host ? inst.parent : inst
|
|
50
|
+
if (!owner) return
|
|
51
|
+
const r = owner.ref(raw.trim()).current as unknown
|
|
52
|
+
let off: (() => void) | void
|
|
53
|
+
owner.onCleanup(() => { off?.(); off = undefined })
|
|
54
|
+
const on = () => {
|
|
55
|
+
if (typeof r === 'function') { const c = r(el); off = typeof c === 'function' ? c : () => r(null) }
|
|
56
|
+
else if (r) { (r as { current: unknown }).current = el; off = () => { (r as { current: unknown }).current = null } }
|
|
57
|
+
}
|
|
58
|
+
attach ? attach.push([el, on]) : on()
|
|
24
59
|
},
|
|
25
60
|
})
|
package/src/instance.ts
CHANGED
|
@@ -18,6 +18,8 @@ export class Instance {
|
|
|
18
18
|
children = new Set<Instance>()
|
|
19
19
|
private disposers: Dispose[] = []
|
|
20
20
|
private cleanups: Array<() => void> = []
|
|
21
|
+
/** F415: while an `x-if` branch or an `x-for` row binds, what it registers is owned by that part (`capture`). */
|
|
22
|
+
private sink: Array<() => void> | null = null
|
|
21
23
|
disposed = false
|
|
22
24
|
/** false until the initial bind pass finishes; the first-paint tripwire only applies before then. */
|
|
23
25
|
booted = false
|
|
@@ -36,7 +38,7 @@ export class Instance {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
effect(fn: () => void | (() => void), tier = 0): Dispose {
|
|
39
|
-
const d = coreEffect(fn, tier); this.disposers.push(d); return d
|
|
41
|
+
const d = coreEffect(fn, tier); (this.sink ?? this.disposers).push(d); return d
|
|
40
42
|
}
|
|
41
43
|
/** `ctx.effect`: deferred until `start()` while the factory runs, immediate afterwards. A compiled effect re-runs after
|
|
42
44
|
* the bindings: layout (`layout` true) before passive (F288, ruling 94404307 amended). */
|
|
@@ -64,7 +66,15 @@ export class Instance {
|
|
|
64
66
|
}
|
|
65
67
|
return true
|
|
66
68
|
}
|
|
67
|
-
onCleanup(fn: () => void): void { this.cleanups.push(fn) }
|
|
69
|
+
onCleanup(fn: () => void): void { (this.sink ?? this.cleanups).push(fn) }
|
|
70
|
+
/** F415/F416: runs `fn` with the effects and cleanups it registers owned by the returned disposer, which an `x-if`
|
|
71
|
+
* branch or an `x-for` row runs when it is removed: its effects stop and its cleanups run (an `x-ref` lets go of the
|
|
72
|
+
* node), in the order they were registered. */
|
|
73
|
+
capture(fn: () => void): () => void {
|
|
74
|
+
const prev = this.sink; const own: Array<() => void> = (this.sink = [])
|
|
75
|
+
try { fn() } finally { this.sink = prev }
|
|
76
|
+
return () => runCleanups(own)
|
|
77
|
+
}
|
|
68
78
|
ref<E extends Element = HTMLElement>(name: string, init?: unknown): { current: E | null } {
|
|
69
79
|
let r = this.refs.get(name)
|
|
70
80
|
// `useRef(init)`: the cell starts at `init`; an `x-ref` element replaces it when it binds (React attaches the same way).
|
|
@@ -87,10 +97,14 @@ export class Instance {
|
|
|
87
97
|
for (const c of Array.from(this.children)) c.dispose()
|
|
88
98
|
this.parent?.children.delete(this)
|
|
89
99
|
for (const d of this.disposers.splice(0)) d()
|
|
90
|
-
|
|
100
|
+
runCleanups(this.cleanups)
|
|
91
101
|
}
|
|
92
102
|
}
|
|
93
103
|
|
|
104
|
+
function runCleanups(list: Array<() => void>): void {
|
|
105
|
+
for (const c of list.splice(0)) { try { c() } catch (e) { console.error('[brust] cleanup threw', e) } }
|
|
106
|
+
}
|
|
107
|
+
|
|
94
108
|
export const instances = new WeakMap<Element, Instance>()
|
|
95
109
|
export function instanceOf(el: Element): Instance | undefined { return instances.get(el) }
|
|
96
110
|
/** Instance of the nearest ancestor HOST. A host that has not mounted yet (chunk loading) yields null: wait, never skip to a farther ancestor. */
|