@fictjs/runtime 0.9.0 → 0.11.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.
- package/dist/advanced.cjs +9 -9
- package/dist/advanced.d.cts +4 -4
- package/dist/advanced.d.ts +4 -4
- package/dist/advanced.js +4 -4
- package/dist/{binding-BWchH3Kp.d.cts → binding-DcnhUSQK.d.ts} +5 -3
- package/dist/{binding-BWchH3Kp.d.ts → binding-FRyTeLDn.d.cts} +5 -3
- package/dist/{chunk-FVX77557.js → chunk-2UR2UWE2.js} +3 -3
- package/dist/{chunk-LBE6DC3V.cjs → chunk-44EQF3AR.cjs} +63 -52
- package/dist/chunk-44EQF3AR.cjs.map +1 -0
- package/dist/{chunk-OAM7HABA.cjs → chunk-4QGEN5SJ.cjs} +340 -263
- package/dist/chunk-4QGEN5SJ.cjs.map +1 -0
- package/dist/{chunk-PD6IQY2Y.cjs → chunk-C5IE4WUG.cjs} +8 -8
- package/dist/{chunk-PD6IQY2Y.cjs.map → chunk-C5IE4WUG.cjs.map} +1 -1
- package/dist/{chunk-DXG3TARY.js → chunk-DIK33H5U.js} +202 -30
- package/dist/chunk-DIK33H5U.js.map +1 -0
- package/dist/{chunk-JVYH76ZX.js → chunk-FESAXMHT.js} +7 -6
- package/dist/{chunk-JVYH76ZX.js.map → chunk-FESAXMHT.js.map} +1 -1
- package/dist/chunk-FHQZCAAK.cjs +112 -0
- package/dist/chunk-FHQZCAAK.cjs.map +1 -0
- package/dist/{chunk-UBFDB6OL.cjs → chunk-QNMYVXRL.cjs} +222 -50
- package/dist/chunk-QNMYVXRL.cjs.map +1 -0
- package/dist/{chunk-N6ODUM2Y.js → chunk-S63VBIWN.js} +27 -16
- package/dist/chunk-S63VBIWN.js.map +1 -0
- package/dist/{chunk-T2LNV5Q5.js → chunk-WIHNVN6L.js} +153 -76
- package/dist/chunk-WIHNVN6L.js.map +1 -0
- package/dist/{devtools-BDp76luf.d.ts → devtools-BtIkN77t.d.cts} +14 -2
- package/dist/{devtools-5AipK9CX.d.cts → devtools-D2z4llpA.d.ts} +14 -2
- package/dist/index.cjs +60 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.dev.js +300 -74
- package/dist/index.dev.js.map +1 -1
- package/dist/index.js +13 -11
- package/dist/index.js.map +1 -1
- package/dist/internal-list.cjs +4 -4
- package/dist/internal-list.d.cts +2 -2
- package/dist/internal-list.d.ts +2 -2
- package/dist/internal-list.js +3 -3
- package/dist/internal.cjs +5 -5
- package/dist/internal.d.cts +6 -6
- package/dist/internal.d.ts +6 -6
- package/dist/internal.js +4 -4
- package/dist/jsx-dev-runtime.d.cts +671 -0
- package/dist/jsx-dev-runtime.d.ts +671 -0
- package/dist/jsx-runtime.d.cts +671 -0
- package/dist/jsx-runtime.d.ts +671 -0
- package/dist/{list-DL5DOFcO.d.ts → list-BKM6YOPq.d.ts} +2 -2
- package/dist/{list-hP7hQ9Vk.d.cts → list-Bi8dDF8Q.d.cts} +2 -2
- package/dist/loader.cjs +34 -28
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.d.cts +2 -2
- package/dist/loader.d.ts +2 -2
- package/dist/loader.js +17 -11
- package/dist/loader.js.map +1 -1
- package/dist/{props-BpZz0AOq.d.cts → props-9chMyBGb.d.cts} +2 -2
- package/dist/{props-CjLH0JE-.d.ts → props-D1nj2p_3.d.ts} +2 -2
- package/dist/{resume-BJ4oHLi_.d.cts → resume-C5IKAIdh.d.ts} +2 -2
- package/dist/{resume-CuyJWXP_.d.ts → resume-DPZxmA95.d.cts} +2 -2
- package/dist/{scope-jPt5DHRT.d.ts → scope-BSkhJr0a.d.ts} +1 -1
- package/dist/{scope-BJCtq8hJ.d.cts → scope-Bn3sxem5.d.cts} +1 -1
- package/dist/{signal-C4ISF17w.d.cts → signal-Z4KkDk9h.d.cts} +12 -1
- package/dist/{signal-C4ISF17w.d.ts → signal-Z4KkDk9h.d.ts} +12 -1
- package/package.json +2 -2
- package/src/binding.ts +59 -29
- package/src/context.ts +4 -3
- package/src/devtools.ts +19 -2
- package/src/dom.ts +122 -42
- package/src/effect.ts +5 -5
- package/src/error-boundary.ts +5 -5
- package/src/hooks.ts +13 -5
- package/src/lifecycle.ts +48 -3
- package/src/list-helpers.ts +30 -13
- package/src/loader.ts +20 -12
- package/src/node-ops.ts +8 -5
- package/src/signal.ts +191 -18
- package/src/suspense.ts +5 -4
- package/src/transition.ts +9 -3
- package/dist/chunk-DXG3TARY.js.map +0 -1
- package/dist/chunk-LBE6DC3V.cjs.map +0 -1
- package/dist/chunk-N6ODUM2Y.js.map +0 -1
- package/dist/chunk-OAM7HABA.cjs.map +0 -1
- package/dist/chunk-PG4QX2I2.cjs +0 -111
- package/dist/chunk-PG4QX2I2.cjs.map +0 -1
- package/dist/chunk-T2LNV5Q5.js.map +0 -1
- package/dist/chunk-UBFDB6OL.cjs.map +0 -1
- /package/dist/{chunk-FVX77557.js.map → chunk-2UR2UWE2.js.map} +0 -0
package/src/list-helpers.ts
CHANGED
|
@@ -203,9 +203,12 @@ export function createVersionedSignalAccessor<T>(initialValue: T): Signal<T> {
|
|
|
203
203
|
function createKeyedListContainer<T = unknown>(
|
|
204
204
|
startOverride?: Comment,
|
|
205
205
|
endOverride?: Comment,
|
|
206
|
+
defaultOwnerDocument?: Document,
|
|
206
207
|
): KeyedListContainer<T> {
|
|
207
|
-
const
|
|
208
|
-
|
|
208
|
+
const markerOwnerDocument =
|
|
209
|
+
startOverride?.ownerDocument ?? endOverride?.ownerDocument ?? defaultOwnerDocument ?? document
|
|
210
|
+
const startMarker = startOverride ?? markerOwnerDocument.createComment('fict:list:start')
|
|
211
|
+
const endMarker = endOverride ?? markerOwnerDocument.createComment('fict:list:end')
|
|
209
212
|
|
|
210
213
|
const dispose = () => {
|
|
211
214
|
// Clean up all blocks
|
|
@@ -228,7 +231,9 @@ function createKeyedListContainer<T = unknown>(
|
|
|
228
231
|
container.orderedIndexByKey.clear()
|
|
229
232
|
return
|
|
230
233
|
}
|
|
231
|
-
const
|
|
234
|
+
const rangeOwnerDocument =
|
|
235
|
+
startMarker.ownerDocument ?? endMarker.ownerDocument ?? markerOwnerDocument
|
|
236
|
+
const range = rangeOwnerDocument.createRange()
|
|
232
237
|
range.setStartBefore(startMarker)
|
|
233
238
|
range.setEndAfter(endMarker)
|
|
234
239
|
range.deleteContents()
|
|
@@ -284,12 +289,13 @@ function createKeyedBlock<T>(
|
|
|
284
289
|
|
|
285
290
|
const indexSig = needsIndex
|
|
286
291
|
? createSignal<number>(index)
|
|
287
|
-
: ((
|
|
292
|
+
: (function indexSignal(next?: number) {
|
|
288
293
|
if (arguments.length === 0) return index
|
|
289
294
|
index = next as number
|
|
290
295
|
return index
|
|
291
|
-
}
|
|
296
|
+
} as Signal<number>)
|
|
292
297
|
const root = createRootContext(hostRoot)
|
|
298
|
+
const nodeOwnerDocument = root.ownerDocument ?? hostRoot?.ownerDocument ?? document
|
|
293
299
|
const prevRoot = pushRoot(root)
|
|
294
300
|
// maintaining proper cleanup chain. The scope will be disposed when
|
|
295
301
|
// the root is destroyed, ensuring nested effects are properly cleaned up.
|
|
@@ -310,10 +316,10 @@ function createKeyedBlock<T>(
|
|
|
310
316
|
rendered instanceof Node ||
|
|
311
317
|
(Array.isArray(rendered) && rendered.every(n => n instanceof Node))
|
|
312
318
|
) {
|
|
313
|
-
nodes = toNodeArray(rendered)
|
|
319
|
+
nodes = toNodeArray(rendered, nodeOwnerDocument)
|
|
314
320
|
} else {
|
|
315
321
|
const element = createElement(rendered as unknown as FictNode)
|
|
316
|
-
nodes = toNodeArray(element)
|
|
322
|
+
nodes = toNodeArray(element, nodeOwnerDocument)
|
|
317
323
|
}
|
|
318
324
|
})
|
|
319
325
|
|
|
@@ -500,10 +506,18 @@ function createFineGrainedKeyedList<T>(
|
|
|
500
506
|
startOverride?: Comment,
|
|
501
507
|
endOverride?: Comment,
|
|
502
508
|
): KeyedListBinding {
|
|
503
|
-
const container = createKeyedListContainer<T>(startOverride, endOverride)
|
|
504
509
|
const hostRoot = getCurrentRoot()
|
|
510
|
+
const container = createKeyedListContainer<T>(
|
|
511
|
+
startOverride,
|
|
512
|
+
endOverride,
|
|
513
|
+
hostRoot?.ownerDocument ?? document,
|
|
514
|
+
)
|
|
515
|
+
const markerOwnerDocument =
|
|
516
|
+
container.startMarker.ownerDocument ?? hostRoot?.ownerDocument ?? document
|
|
505
517
|
const useProvided = !!(startOverride && endOverride)
|
|
506
|
-
const fragment = useProvided
|
|
518
|
+
const fragment = useProvided
|
|
519
|
+
? container.startMarker
|
|
520
|
+
: markerOwnerDocument.createDocumentFragment()
|
|
507
521
|
if (!useProvided) {
|
|
508
522
|
;(fragment as DocumentFragment).append(container.startMarker, container.endMarker)
|
|
509
523
|
}
|
|
@@ -580,7 +594,7 @@ function createFineGrainedKeyedList<T>(
|
|
|
580
594
|
withHydrationRange(
|
|
581
595
|
container.startMarker.nextSibling,
|
|
582
596
|
container.endMarker,
|
|
583
|
-
parent.ownerDocument ??
|
|
597
|
+
parent.ownerDocument ?? markerOwnerDocument,
|
|
584
598
|
() => {
|
|
585
599
|
for (let index = 0; index < newItems.length; index++) {
|
|
586
600
|
const item = newItems[index]!
|
|
@@ -632,7 +646,7 @@ function createFineGrainedKeyedList<T>(
|
|
|
632
646
|
destroyRoot(block.root)
|
|
633
647
|
}
|
|
634
648
|
// Use Range.deleteContents for efficient bulk DOM removal
|
|
635
|
-
const range =
|
|
649
|
+
const range = (parent.ownerDocument ?? markerOwnerDocument).createRange()
|
|
636
650
|
range.setStartAfter(container.startMarker)
|
|
637
651
|
range.setEndBefore(container.endMarker)
|
|
638
652
|
range.deleteContents()
|
|
@@ -934,7 +948,7 @@ function createFineGrainedKeyedList<T>(
|
|
|
934
948
|
|
|
935
949
|
const waitForConnection = () => {
|
|
936
950
|
if (connectObserver || typeof MutationObserver === 'undefined') return
|
|
937
|
-
const root = container.startMarker.getRootNode?.() ??
|
|
951
|
+
const root = container.startMarker.getRootNode?.() ?? markerOwnerDocument
|
|
938
952
|
const shadowRoot =
|
|
939
953
|
root && root.nodeType === 11 && isShadowRoot(root as Node) ? (root as ShadowRoot) : null
|
|
940
954
|
connectObserver = new MutationObserver(() => {
|
|
@@ -946,7 +960,10 @@ function createFineGrainedKeyedList<T>(
|
|
|
946
960
|
}
|
|
947
961
|
}
|
|
948
962
|
})
|
|
949
|
-
connectObserver.observe(
|
|
963
|
+
connectObserver.observe(markerOwnerDocument, { childList: true, subtree: true })
|
|
964
|
+
if (root && root.nodeType === 11) {
|
|
965
|
+
connectObserver.observe(root as Node, { childList: true, subtree: true })
|
|
966
|
+
}
|
|
950
967
|
if (shadowRoot) {
|
|
951
968
|
connectObserver.observe(shadowRoot, { childList: true, subtree: true })
|
|
952
969
|
}
|
package/src/loader.ts
CHANGED
|
@@ -447,19 +447,20 @@ function setupHoverPrefetch(doc: Document, delay: number): () => void {
|
|
|
447
447
|
}
|
|
448
448
|
|
|
449
449
|
function prefetchElementQrls(el: Element): void {
|
|
450
|
+
const ownerDocument = el.ownerDocument ?? (typeof document !== 'undefined' ? document : undefined)
|
|
450
451
|
// Prefetch event handler QRLs
|
|
451
452
|
const eventAttrs = ['on:click', 'on:input', 'on:change', 'on:submit', 'on:keydown', 'on:keyup']
|
|
452
453
|
for (const attr of eventAttrs) {
|
|
453
454
|
const qrl = el.getAttribute(attr)
|
|
454
455
|
if (qrl) {
|
|
455
|
-
prefetchQrl(qrl)
|
|
456
|
+
prefetchQrl(qrl, ownerDocument)
|
|
456
457
|
}
|
|
457
458
|
}
|
|
458
459
|
|
|
459
460
|
// Prefetch resume handler QRL
|
|
460
461
|
const resumeQrl = el.getAttribute('data-fict-h')
|
|
461
462
|
if (resumeQrl) {
|
|
462
|
-
prefetchQrl(resumeQrl)
|
|
463
|
+
prefetchQrl(resumeQrl, ownerDocument)
|
|
463
464
|
}
|
|
464
465
|
|
|
465
466
|
// Also check children for nested QRLs
|
|
@@ -470,17 +471,17 @@ function prefetchElementQrls(el: Element): void {
|
|
|
470
471
|
for (const attr of eventAttrs) {
|
|
471
472
|
const qrl = child.getAttribute(attr)
|
|
472
473
|
if (qrl) {
|
|
473
|
-
prefetchQrl(qrl)
|
|
474
|
+
prefetchQrl(qrl, ownerDocument)
|
|
474
475
|
}
|
|
475
476
|
}
|
|
476
477
|
const childResumeQrl = child.getAttribute('data-fict-h')
|
|
477
478
|
if (childResumeQrl) {
|
|
478
|
-
prefetchQrl(childResumeQrl)
|
|
479
|
+
prefetchQrl(childResumeQrl, ownerDocument)
|
|
479
480
|
}
|
|
480
481
|
})
|
|
481
482
|
}
|
|
482
483
|
|
|
483
|
-
function prefetchQrl(qrl: string): void {
|
|
484
|
+
function prefetchQrl(qrl: string, ownerDocument?: Document): void {
|
|
484
485
|
const { url } = parseQrl(qrl)
|
|
485
486
|
if (!url || prefetchedUrls.has(url)) return
|
|
486
487
|
|
|
@@ -490,12 +491,13 @@ function prefetchQrl(qrl: string): void {
|
|
|
490
491
|
const resolvedUrl = resolveModuleUrl(url)
|
|
491
492
|
|
|
492
493
|
// Use modulepreload link for best browser support
|
|
493
|
-
|
|
494
|
-
|
|
494
|
+
const doc = ownerDocument ?? (typeof document !== 'undefined' ? document : undefined)
|
|
495
|
+
if (doc) {
|
|
496
|
+
const link = doc.createElement('link')
|
|
495
497
|
link.rel = 'modulepreload'
|
|
496
498
|
link.href = resolvedUrl
|
|
497
499
|
link.crossOrigin = 'anonymous'
|
|
498
|
-
|
|
500
|
+
doc.head?.appendChild(link)
|
|
499
501
|
}
|
|
500
502
|
}
|
|
501
503
|
|
|
@@ -507,9 +509,15 @@ function prefetchQrl(qrl: string): void {
|
|
|
507
509
|
function handleResumableEvent(event: Event): void {
|
|
508
510
|
const promise = handleResumableEventAsync(event)
|
|
509
511
|
pendingHandlers.add(promise)
|
|
510
|
-
promise
|
|
511
|
-
|
|
512
|
-
|
|
512
|
+
void promise
|
|
513
|
+
.catch(error => {
|
|
514
|
+
if (typeof console !== 'undefined' && typeof console.error === 'function') {
|
|
515
|
+
console.error('[fict/loader] Failed to handle resumable event.', error)
|
|
516
|
+
}
|
|
517
|
+
})
|
|
518
|
+
.finally(() => {
|
|
519
|
+
pendingHandlers.delete(promise)
|
|
520
|
+
})
|
|
513
521
|
}
|
|
514
522
|
|
|
515
523
|
async function handleResumableEventAsync(event: Event): Promise<void> {
|
|
@@ -535,7 +543,7 @@ async function handleResumableEventAsync(event: Event): Promise<void> {
|
|
|
535
543
|
expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,
|
|
536
544
|
scopeId,
|
|
537
545
|
})
|
|
538
|
-
|
|
546
|
+
continue
|
|
539
547
|
}
|
|
540
548
|
__fictEnsureScope(scopeId, host, snapshot)
|
|
541
549
|
|
package/src/node-ops.ts
CHANGED
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* Convert a value to a flat array of DOM nodes.
|
|
8
8
|
* Defensively handles proxies and non-DOM values.
|
|
9
9
|
*/
|
|
10
|
-
export function toNodeArray(
|
|
10
|
+
export function toNodeArray(
|
|
11
|
+
node: Node | Node[] | unknown,
|
|
12
|
+
ownerDocument: Document = document,
|
|
13
|
+
): Node[] {
|
|
11
14
|
try {
|
|
12
15
|
if (Array.isArray(node)) {
|
|
13
16
|
// Preserve original array reference when it's already a flat Node array
|
|
@@ -29,7 +32,7 @@ export function toNodeArray(node: Node | Node[] | unknown): Node[] {
|
|
|
29
32
|
}
|
|
30
33
|
const result: Node[] = []
|
|
31
34
|
for (const item of node) {
|
|
32
|
-
result.push(...toNodeArray(item))
|
|
35
|
+
result.push(...toNodeArray(item, ownerDocument))
|
|
33
36
|
}
|
|
34
37
|
return result
|
|
35
38
|
}
|
|
@@ -62,7 +65,7 @@ export function toNodeArray(node: Node | Node[] | unknown): Node[] {
|
|
|
62
65
|
try {
|
|
63
66
|
// Duck-type BindingHandle-like values
|
|
64
67
|
if (typeof node === 'object' && node !== null && 'marker' in node) {
|
|
65
|
-
return toNodeArray((node as { marker: unknown }).marker)
|
|
68
|
+
return toNodeArray((node as { marker: unknown }).marker, ownerDocument)
|
|
66
69
|
}
|
|
67
70
|
} catch {
|
|
68
71
|
// Ignore property check error
|
|
@@ -70,9 +73,9 @@ export function toNodeArray(node: Node | Node[] | unknown): Node[] {
|
|
|
70
73
|
|
|
71
74
|
// Primitive fallback
|
|
72
75
|
try {
|
|
73
|
-
return [
|
|
76
|
+
return [ownerDocument.createTextNode(String(node))]
|
|
74
77
|
} catch {
|
|
75
|
-
return [
|
|
78
|
+
return [ownerDocument.createTextNode('')]
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
81
|
|
package/src/signal.ts
CHANGED
|
@@ -93,6 +93,18 @@ export interface MemoOptions<T> {
|
|
|
93
93
|
name?: string
|
|
94
94
|
/** Source location */
|
|
95
95
|
devToolsSource?: string
|
|
96
|
+
/** Internal memo created by compiler runtime plumbing (hidden from DevTools) */
|
|
97
|
+
internal?: boolean
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Options for creating an effect
|
|
102
|
+
*/
|
|
103
|
+
export interface EffectOptions {
|
|
104
|
+
/** Debug name */
|
|
105
|
+
name?: string
|
|
106
|
+
/** Source location */
|
|
107
|
+
devToolsSource?: string
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
/**
|
|
@@ -145,6 +157,8 @@ export interface ComputedNode<T = unknown> extends BaseNode {
|
|
|
145
157
|
name?: string
|
|
146
158
|
/** Source location */
|
|
147
159
|
devToolsSource?: string
|
|
160
|
+
/** Hide this computed from DevTools (used by compiler-internal memos) */
|
|
161
|
+
devToolsInternal?: boolean
|
|
148
162
|
}
|
|
149
163
|
|
|
150
164
|
/**
|
|
@@ -161,6 +175,10 @@ export interface EffectNode extends BaseNode {
|
|
|
161
175
|
runCleanup?: () => void
|
|
162
176
|
/** Root context for error/suspense handling */
|
|
163
177
|
root?: RootContext
|
|
178
|
+
/** Debug name */
|
|
179
|
+
name?: string
|
|
180
|
+
/** Source location */
|
|
181
|
+
devToolsSource?: string
|
|
164
182
|
/** Devtools ID */
|
|
165
183
|
__id?: number | undefined
|
|
166
184
|
}
|
|
@@ -788,6 +806,16 @@ function purgeDeps(sub: ReactiveNode): void {
|
|
|
788
806
|
* @param node - The node to dispose
|
|
789
807
|
*/
|
|
790
808
|
function disposeNode(node: ReactiveNode): void {
|
|
809
|
+
if (isDev) {
|
|
810
|
+
if ('fn' in node && typeof node.fn === 'function') {
|
|
811
|
+
disposeEffectDevtools(node as EffectNode)
|
|
812
|
+
} else if ('getter' in node && typeof node.getter === 'function') {
|
|
813
|
+
disposeComputedDevtools(node as ComputedNode)
|
|
814
|
+
} else if ('currentValue' in node) {
|
|
815
|
+
disposeSignalDevtools(node as SignalNode)
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
791
819
|
node.depsTail = undefined
|
|
792
820
|
node.flags = 0
|
|
793
821
|
purgeDeps(node)
|
|
@@ -858,6 +886,7 @@ function runEffect(e: EffectNode): void {
|
|
|
858
886
|
const flags = e.flags
|
|
859
887
|
const runCleanup = () => {
|
|
860
888
|
if (!e.runCleanup) return
|
|
889
|
+
if (isDev) effectCleanupDevtools(e)
|
|
861
890
|
inCleanup = true
|
|
862
891
|
activeCleanupFlushId = currentFlushId
|
|
863
892
|
try {
|
|
@@ -871,7 +900,6 @@ function runEffect(e: EffectNode): void {
|
|
|
871
900
|
// Run cleanup before re-run; values are still the previous commit.
|
|
872
901
|
runCleanup()
|
|
873
902
|
++cycle
|
|
874
|
-
if (isDev) effectRunDevtools(e)
|
|
875
903
|
e.depsTail = undefined
|
|
876
904
|
e.flags = WatchingRunning
|
|
877
905
|
const prevSub = activeSub
|
|
@@ -913,7 +941,6 @@ function runEffect(e: EffectNode): void {
|
|
|
913
941
|
// Cleanup reads should observe previous values for this flush.
|
|
914
942
|
runCleanup()
|
|
915
943
|
++cycle
|
|
916
|
-
if (isDev) effectRunDevtools(e)
|
|
917
944
|
e.depsTail = undefined
|
|
918
945
|
e.flags = WatchingRunning
|
|
919
946
|
const prevSub = activeSub
|
|
@@ -956,20 +983,31 @@ export function scheduleFlush(): void {
|
|
|
956
983
|
*/
|
|
957
984
|
function flush(): void {
|
|
958
985
|
beginFlushGuard()
|
|
986
|
+
let flushReported = false
|
|
987
|
+
const finishFlush = () => {
|
|
988
|
+
if (flushReported && isDev) {
|
|
989
|
+
flushEndDevtools()
|
|
990
|
+
}
|
|
991
|
+
endFlushGuard()
|
|
992
|
+
}
|
|
959
993
|
if (batchDepth > 0) {
|
|
960
994
|
// If batching is active, defer until the batch completes
|
|
961
995
|
scheduleFlush()
|
|
962
|
-
|
|
996
|
+
finishFlush()
|
|
963
997
|
return
|
|
964
998
|
}
|
|
965
999
|
const hasWork = highPriorityQueue.length > 0 || lowPriorityQueue.length > 0
|
|
966
1000
|
if (!hasWork) {
|
|
967
1001
|
flushScheduled = false
|
|
968
|
-
|
|
1002
|
+
finishFlush()
|
|
969
1003
|
return
|
|
970
1004
|
}
|
|
971
1005
|
currentFlushId++
|
|
972
1006
|
flushScheduled = false
|
|
1007
|
+
if (isDev) {
|
|
1008
|
+
flushStartDevtools()
|
|
1009
|
+
flushReported = true
|
|
1010
|
+
}
|
|
973
1011
|
|
|
974
1012
|
// 1. Process all high-priority effects first
|
|
975
1013
|
let highIndex = 0
|
|
@@ -993,7 +1031,7 @@ function flush(): void {
|
|
|
993
1031
|
highPriorityQueue.length = 0
|
|
994
1032
|
lowPriorityQueue.length = 0
|
|
995
1033
|
flushScheduled = false
|
|
996
|
-
|
|
1034
|
+
finishFlush()
|
|
997
1035
|
return
|
|
998
1036
|
}
|
|
999
1037
|
highIndex++
|
|
@@ -1011,7 +1049,7 @@ function flush(): void {
|
|
|
1011
1049
|
lowPriorityQueue.length -= lowIndex
|
|
1012
1050
|
}
|
|
1013
1051
|
scheduleFlush()
|
|
1014
|
-
|
|
1052
|
+
finishFlush()
|
|
1015
1053
|
return
|
|
1016
1054
|
}
|
|
1017
1055
|
const e = lowPriorityQueue[lowIndex]!
|
|
@@ -1033,7 +1071,7 @@ function flush(): void {
|
|
|
1033
1071
|
highPriorityQueue.length = 0
|
|
1034
1072
|
lowPriorityQueue.length = 0
|
|
1035
1073
|
flushScheduled = false
|
|
1036
|
-
|
|
1074
|
+
finishFlush()
|
|
1037
1075
|
return
|
|
1038
1076
|
}
|
|
1039
1077
|
lowIndex++
|
|
@@ -1041,7 +1079,7 @@ function flush(): void {
|
|
|
1041
1079
|
}
|
|
1042
1080
|
lowPriorityQueue.length = 0
|
|
1043
1081
|
|
|
1044
|
-
|
|
1082
|
+
finishFlush()
|
|
1045
1083
|
}
|
|
1046
1084
|
// ============================================================================
|
|
1047
1085
|
// Signal - Inline optimized version
|
|
@@ -1138,6 +1176,7 @@ export function computed<T>(
|
|
|
1138
1176
|
if (options?.equals !== undefined) c.equals = options.equals
|
|
1139
1177
|
if (options?.name !== undefined) c.name = options.name
|
|
1140
1178
|
if (options?.devToolsSource !== undefined) c.devToolsSource = options.devToolsSource
|
|
1179
|
+
if (options?.internal === true) c.devToolsInternal = true
|
|
1141
1180
|
if (isDev) registerComputedDevtools(c)
|
|
1142
1181
|
const bound = (computedOper as (this: ComputedNode<T>) => T).bind(
|
|
1143
1182
|
c as any,
|
|
@@ -1201,9 +1240,10 @@ function computedOper<T>(this: ComputedNode<T>): T {
|
|
|
1201
1240
|
/**
|
|
1202
1241
|
* Create a reactive effect
|
|
1203
1242
|
* @param fn - The effect function
|
|
1243
|
+
* @param options - Effect options
|
|
1204
1244
|
* @returns An effect disposer function
|
|
1205
1245
|
*/
|
|
1206
|
-
export function effect(fn: () => void): EffectDisposer {
|
|
1246
|
+
export function effect(fn: () => void, options?: EffectOptions): EffectDisposer {
|
|
1207
1247
|
const e: EffectNode = {
|
|
1208
1248
|
fn,
|
|
1209
1249
|
subs: undefined,
|
|
@@ -1211,6 +1251,8 @@ export function effect(fn: () => void): EffectDisposer {
|
|
|
1211
1251
|
deps: undefined,
|
|
1212
1252
|
depsTail: undefined,
|
|
1213
1253
|
flags: WatchingRunning,
|
|
1254
|
+
...(options?.name !== undefined ? { name: options.name } : {}),
|
|
1255
|
+
...(options?.devToolsSource !== undefined ? { devToolsSource: options.devToolsSource } : {}),
|
|
1214
1256
|
__id: undefined as number | undefined,
|
|
1215
1257
|
}
|
|
1216
1258
|
const root = getCurrentRoot()
|
|
@@ -1219,6 +1261,7 @@ export function effect(fn: () => void): EffectDisposer {
|
|
|
1219
1261
|
}
|
|
1220
1262
|
|
|
1221
1263
|
if (isDev) registerEffectDevtools(e)
|
|
1264
|
+
e.fn = wrapEffectFnWithDevtoolsTiming(e, fn)
|
|
1222
1265
|
|
|
1223
1266
|
const prevSub = activeSub
|
|
1224
1267
|
if (prevSub !== undefined) link(e, prevSub, 0)
|
|
@@ -1227,8 +1270,7 @@ export function effect(fn: () => void): EffectDisposer {
|
|
|
1227
1270
|
let didThrow = false
|
|
1228
1271
|
let thrown: unknown
|
|
1229
1272
|
try {
|
|
1230
|
-
|
|
1231
|
-
fn()
|
|
1273
|
+
e.fn()
|
|
1232
1274
|
} catch (err) {
|
|
1233
1275
|
didThrow = true
|
|
1234
1276
|
thrown = err
|
|
@@ -1261,6 +1303,7 @@ export function effectWithCleanup(
|
|
|
1261
1303
|
fn: () => void,
|
|
1262
1304
|
cleanupRunner: () => void,
|
|
1263
1305
|
root?: RootContext,
|
|
1306
|
+
options?: EffectOptions,
|
|
1264
1307
|
): EffectDisposer {
|
|
1265
1308
|
const e: EffectNode = {
|
|
1266
1309
|
fn,
|
|
@@ -1270,6 +1313,8 @@ export function effectWithCleanup(
|
|
|
1270
1313
|
depsTail: undefined,
|
|
1271
1314
|
flags: WatchingRunning,
|
|
1272
1315
|
runCleanup: cleanupRunner,
|
|
1316
|
+
...(options?.name !== undefined ? { name: options.name } : {}),
|
|
1317
|
+
...(options?.devToolsSource !== undefined ? { devToolsSource: options.devToolsSource } : {}),
|
|
1273
1318
|
__id: undefined as number | undefined,
|
|
1274
1319
|
}
|
|
1275
1320
|
const resolvedRoot = root ?? getCurrentRoot()
|
|
@@ -1278,6 +1323,7 @@ export function effectWithCleanup(
|
|
|
1278
1323
|
}
|
|
1279
1324
|
|
|
1280
1325
|
if (isDev) registerEffectDevtools(e)
|
|
1326
|
+
e.fn = wrapEffectFnWithDevtoolsTiming(e, fn)
|
|
1281
1327
|
|
|
1282
1328
|
const prevSub = activeSub
|
|
1283
1329
|
if (prevSub !== undefined) link(e, prevSub, 0)
|
|
@@ -1286,8 +1332,7 @@ export function effectWithCleanup(
|
|
|
1286
1332
|
let didThrow = false
|
|
1287
1333
|
let thrown: unknown
|
|
1288
1334
|
try {
|
|
1289
|
-
|
|
1290
|
-
fn()
|
|
1335
|
+
e.fn()
|
|
1291
1336
|
} catch (err) {
|
|
1292
1337
|
didThrow = true
|
|
1293
1338
|
thrown = err
|
|
@@ -1385,13 +1430,24 @@ export function trigger(fn: () => void): void {
|
|
|
1385
1430
|
* Start a batch of updates
|
|
1386
1431
|
*/
|
|
1387
1432
|
export function startBatch(): void {
|
|
1433
|
+
const enteringOuterBatch = batchDepth === 0
|
|
1388
1434
|
++batchDepth
|
|
1435
|
+
if (enteringOuterBatch && isDev) {
|
|
1436
|
+
batchStartDevtools()
|
|
1437
|
+
}
|
|
1389
1438
|
}
|
|
1390
1439
|
/**
|
|
1391
1440
|
* End a batch of updates and flush effects
|
|
1392
1441
|
*/
|
|
1393
1442
|
export function endBatch(): void {
|
|
1394
|
-
if (
|
|
1443
|
+
if (batchDepth === 0) return
|
|
1444
|
+
--batchDepth
|
|
1445
|
+
if (batchDepth === 0) {
|
|
1446
|
+
if (isDev) {
|
|
1447
|
+
batchEndDevtools()
|
|
1448
|
+
}
|
|
1449
|
+
flush()
|
|
1450
|
+
}
|
|
1395
1451
|
}
|
|
1396
1452
|
/**
|
|
1397
1453
|
* Execute a function in a batch
|
|
@@ -1399,7 +1455,11 @@ export function endBatch(): void {
|
|
|
1399
1455
|
* @returns The return value of the function
|
|
1400
1456
|
*/
|
|
1401
1457
|
export function batch<T>(fn: () => T): T {
|
|
1458
|
+
const enteringOuterBatch = batchDepth === 0
|
|
1402
1459
|
++batchDepth
|
|
1460
|
+
if (enteringOuterBatch && isDev) {
|
|
1461
|
+
batchStartDevtools()
|
|
1462
|
+
}
|
|
1403
1463
|
let result!: T
|
|
1404
1464
|
let error: unknown
|
|
1405
1465
|
try {
|
|
@@ -1409,6 +1469,9 @@ export function batch<T>(fn: () => T): T {
|
|
|
1409
1469
|
} finally {
|
|
1410
1470
|
--batchDepth
|
|
1411
1471
|
if (batchDepth === 0) {
|
|
1472
|
+
if (isDev) {
|
|
1473
|
+
batchEndDevtools()
|
|
1474
|
+
}
|
|
1412
1475
|
try {
|
|
1413
1476
|
flush()
|
|
1414
1477
|
} catch (flushErr) {
|
|
@@ -1463,6 +1526,7 @@ export function __resetReactiveState(): void {
|
|
|
1463
1526
|
cycle = 0
|
|
1464
1527
|
currentFlushId = 0
|
|
1465
1528
|
activeCleanupFlushId = 0
|
|
1529
|
+
clearDevtoolsSignalSetters()
|
|
1466
1530
|
}
|
|
1467
1531
|
/**
|
|
1468
1532
|
* Execute a function without tracking dependencies
|
|
@@ -1588,12 +1652,25 @@ interface DevtoolsIdentifiable {
|
|
|
1588
1652
|
|
|
1589
1653
|
let registerSignalDevtools: <T>(node: SignalNode<T>) => number | undefined = () => undefined
|
|
1590
1654
|
let updateSignalDevtools: <T>(node: SignalNode<T>, value: unknown) => void = () => {}
|
|
1655
|
+
let disposeSignalDevtools: <T>(node: SignalNode<T>) => void = () => {}
|
|
1591
1656
|
let registerComputedDevtools: <T>(node: ComputedNode<T>) => number | undefined = () => undefined
|
|
1592
1657
|
let updateComputedDevtools: <T>(node: ComputedNode<T>, value: unknown) => void = () => {}
|
|
1658
|
+
let disposeComputedDevtools: <T>(node: ComputedNode<T>) => void = () => {}
|
|
1593
1659
|
let registerEffectDevtools: (node: EffectNode) => number | undefined = () => undefined
|
|
1594
|
-
let effectRunDevtools: (node: EffectNode) => void = () => {}
|
|
1660
|
+
let effectRunDevtools: (node: EffectNode, duration?: number) => void = () => {}
|
|
1661
|
+
let wrapEffectFnWithDevtoolsTiming: (node: EffectNode, fn: () => void) => () => void = (
|
|
1662
|
+
_node,
|
|
1663
|
+
fn,
|
|
1664
|
+
) => fn
|
|
1665
|
+
let effectCleanupDevtools: (node: EffectNode) => void = () => {}
|
|
1666
|
+
let disposeEffectDevtools: (node: EffectNode) => void = () => {}
|
|
1595
1667
|
let trackDependencyDevtools: (dep: ReactiveNode, sub: ReactiveNode) => void = () => {}
|
|
1596
1668
|
let untrackDependencyDevtools: (dep: ReactiveNode, sub: ReactiveNode) => void = () => {}
|
|
1669
|
+
let batchStartDevtools: () => void = () => {}
|
|
1670
|
+
let batchEndDevtools: () => void = () => {}
|
|
1671
|
+
let flushStartDevtools: () => void = () => {}
|
|
1672
|
+
let flushEndDevtools: () => void = () => {}
|
|
1673
|
+
let clearDevtoolsSignalSetters: () => void = () => {}
|
|
1597
1674
|
|
|
1598
1675
|
// Keep this as a direct conditional expression (instead of `if (isDev)`) so
|
|
1599
1676
|
// bundlers can eliminate the entire devtools setup block when `__DEV__` is
|
|
@@ -1606,6 +1683,25 @@ if (
|
|
|
1606
1683
|
// Unified ID counter for all reactive nodes (signal/computed/effect)
|
|
1607
1684
|
// to prevent ID collisions when storing in single devtools maps
|
|
1608
1685
|
let nextDevtoolsId = 0
|
|
1686
|
+
const getSignalSetterMap = () => {
|
|
1687
|
+
if (typeof globalThis === 'undefined') return undefined
|
|
1688
|
+
const global = globalThis as typeof globalThis & {
|
|
1689
|
+
__FICT_DEVTOOLS_SIGNALS__?: Map<number, (value: unknown) => void>
|
|
1690
|
+
}
|
|
1691
|
+
if (!global.__FICT_DEVTOOLS_SIGNALS__) {
|
|
1692
|
+
global.__FICT_DEVTOOLS_SIGNALS__ = new Map<number, (value: unknown) => void>()
|
|
1693
|
+
}
|
|
1694
|
+
return global.__FICT_DEVTOOLS_SIGNALS__
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
const getExistingSignalSetterMap = () => {
|
|
1698
|
+
if (typeof globalThis === 'undefined') return undefined
|
|
1699
|
+
return (
|
|
1700
|
+
globalThis as typeof globalThis & {
|
|
1701
|
+
__FICT_DEVTOOLS_SIGNALS__?: Map<number, (value: unknown) => void>
|
|
1702
|
+
}
|
|
1703
|
+
).__FICT_DEVTOOLS_SIGNALS__
|
|
1704
|
+
}
|
|
1609
1705
|
|
|
1610
1706
|
registerSignalDevtools = node => {
|
|
1611
1707
|
const hook = getDevtoolsHook()
|
|
@@ -1618,6 +1714,9 @@ if (
|
|
|
1618
1714
|
if (ownerId !== undefined) (options as any).ownerId = ownerId
|
|
1619
1715
|
hook.registerSignal(id, node.currentValue, options)
|
|
1620
1716
|
;(node as SignalNode & DevtoolsIdentifiable).__id = id
|
|
1717
|
+
getSignalSetterMap()?.set(id, value => {
|
|
1718
|
+
signalOper.call(node as SignalNode<unknown>, value)
|
|
1719
|
+
})
|
|
1621
1720
|
return id
|
|
1622
1721
|
}
|
|
1623
1722
|
|
|
@@ -1628,9 +1727,20 @@ if (
|
|
|
1628
1727
|
if (id) hook.updateSignal(id, value)
|
|
1629
1728
|
}
|
|
1630
1729
|
|
|
1730
|
+
disposeSignalDevtools = node => {
|
|
1731
|
+
const identifiable = node as SignalNode & DevtoolsIdentifiable
|
|
1732
|
+
const id = identifiable.__id
|
|
1733
|
+
if (!id) return
|
|
1734
|
+
const hook = getDevtoolsHook()
|
|
1735
|
+
hook?.disposeSignal?.(id)
|
|
1736
|
+
getExistingSignalSetterMap()?.delete(id)
|
|
1737
|
+
delete identifiable.__id
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1631
1740
|
registerComputedDevtools = node => {
|
|
1632
1741
|
const hook = getDevtoolsHook()
|
|
1633
1742
|
if (!hook) return undefined
|
|
1743
|
+
if (node.devToolsInternal) return undefined
|
|
1634
1744
|
const id = ++nextDevtoolsId
|
|
1635
1745
|
const options: { name?: string; source?: string } = {}
|
|
1636
1746
|
if (node.name !== undefined) options.name = node.name
|
|
@@ -1650,21 +1760,60 @@ if (
|
|
|
1650
1760
|
if (id) hook.updateComputed(id, value)
|
|
1651
1761
|
}
|
|
1652
1762
|
|
|
1763
|
+
disposeComputedDevtools = node => {
|
|
1764
|
+
const identifiable = node as ComputedNode & DevtoolsIdentifiable
|
|
1765
|
+
const id = identifiable.__id
|
|
1766
|
+
if (!id) return
|
|
1767
|
+
const hook = getDevtoolsHook()
|
|
1768
|
+
hook?.disposeComputed?.(id)
|
|
1769
|
+
delete identifiable.__id
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1653
1772
|
registerEffectDevtools = node => {
|
|
1654
1773
|
const hook = getDevtoolsHook()
|
|
1655
1774
|
if (!hook) return undefined
|
|
1656
1775
|
const id = ++nextDevtoolsId
|
|
1776
|
+
const options: { ownerId?: number; source?: string } = {}
|
|
1657
1777
|
const ownerId = __fictGetCurrentComponentId()
|
|
1658
|
-
|
|
1778
|
+
if (ownerId !== undefined) options.ownerId = ownerId
|
|
1779
|
+
if (node.devToolsSource !== undefined) options.source = node.devToolsSource
|
|
1780
|
+
hook.registerEffect(id, Object.keys(options).length > 0 ? options : undefined)
|
|
1659
1781
|
;(node as EffectNode & DevtoolsIdentifiable).__id = id
|
|
1660
1782
|
return id
|
|
1661
1783
|
}
|
|
1662
1784
|
|
|
1663
|
-
effectRunDevtools = node => {
|
|
1785
|
+
effectRunDevtools = (node, duration) => {
|
|
1786
|
+
const hook = getDevtoolsHook()
|
|
1787
|
+
if (!hook) return
|
|
1788
|
+
const id = (node as EffectNode & DevtoolsIdentifiable).__id
|
|
1789
|
+
if (id) hook.effectRun(id, duration)
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
wrapEffectFnWithDevtoolsTiming = (node, fn) => {
|
|
1793
|
+
return () => {
|
|
1794
|
+
const startedAt = performance.now()
|
|
1795
|
+
try {
|
|
1796
|
+
fn()
|
|
1797
|
+
} finally {
|
|
1798
|
+
effectRunDevtools(node, performance.now() - startedAt)
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
effectCleanupDevtools = node => {
|
|
1664
1804
|
const hook = getDevtoolsHook()
|
|
1665
1805
|
if (!hook) return
|
|
1666
1806
|
const id = (node as EffectNode & DevtoolsIdentifiable).__id
|
|
1667
|
-
if (id) hook.
|
|
1807
|
+
if (id) hook.effectCleanup?.(id)
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
disposeEffectDevtools = node => {
|
|
1811
|
+
const identifiable = node as EffectNode & DevtoolsIdentifiable
|
|
1812
|
+
const id = identifiable.__id
|
|
1813
|
+
if (!id) return
|
|
1814
|
+
const hook = getDevtoolsHook()
|
|
1815
|
+
hook?.disposeEffect?.(id)
|
|
1816
|
+
delete identifiable.__id
|
|
1668
1817
|
}
|
|
1669
1818
|
|
|
1670
1819
|
trackDependencyDevtools = (dep, sub) => {
|
|
@@ -1682,6 +1831,30 @@ if (
|
|
|
1682
1831
|
const subId = (sub as ReactiveNode & DevtoolsIdentifiable).__id
|
|
1683
1832
|
if (depId && subId) hook.untrackDependency(subId, depId)
|
|
1684
1833
|
}
|
|
1834
|
+
|
|
1835
|
+
batchStartDevtools = () => {
|
|
1836
|
+
const hook = getDevtoolsHook()
|
|
1837
|
+
hook?.batchStart?.()
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
batchEndDevtools = () => {
|
|
1841
|
+
const hook = getDevtoolsHook()
|
|
1842
|
+
hook?.batchEnd?.()
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
flushStartDevtools = () => {
|
|
1846
|
+
const hook = getDevtoolsHook()
|
|
1847
|
+
hook?.flushStart?.()
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
flushEndDevtools = () => {
|
|
1851
|
+
const hook = getDevtoolsHook()
|
|
1852
|
+
hook?.flushEnd?.()
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
clearDevtoolsSignalSetters = () => {
|
|
1856
|
+
getExistingSignalSetterMap()?.clear()
|
|
1857
|
+
}
|
|
1685
1858
|
}
|
|
1686
1859
|
|
|
1687
1860
|
// ============================================================================
|