@fictjs/runtime 0.10.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 +3 -3
- package/dist/advanced.d.ts +3 -3
- package/dist/advanced.js +4 -4
- package/dist/{binding-DqxS9ZQf.d.ts → binding-DcnhUSQK.d.ts} +1 -1
- package/dist/{binding-DUEukRxl.d.cts → binding-FRyTeLDn.d.cts} +1 -1
- package/dist/{chunk-DKA2I6ET.js → chunk-2UR2UWE2.js} +3 -3
- package/dist/{chunk-SZLJCQFZ.cjs → chunk-44EQF3AR.cjs} +63 -52
- package/dist/chunk-44EQF3AR.cjs.map +1 -0
- package/dist/{chunk-I4GKKAAY.cjs → chunk-4QGEN5SJ.cjs} +295 -262
- package/dist/chunk-4QGEN5SJ.cjs.map +1 -0
- package/dist/{chunk-V7BC64W2.cjs → chunk-C5IE4WUG.cjs} +8 -8
- package/dist/{chunk-V7BC64W2.cjs.map → chunk-C5IE4WUG.cjs.map} +1 -1
- package/dist/{chunk-F4RVNXOL.js → chunk-DIK33H5U.js} +8 -2
- package/dist/chunk-DIK33H5U.js.map +1 -0
- package/dist/{chunk-2JRPPCG7.js → chunk-FESAXMHT.js} +7 -6
- package/dist/{chunk-2JRPPCG7.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-EQ5E4WOV.cjs → chunk-QNMYVXRL.cjs} +44 -38
- package/dist/chunk-QNMYVXRL.cjs.map +1 -0
- package/dist/{chunk-P4TZLFV6.js → chunk-S63VBIWN.js} +27 -16
- package/dist/chunk-S63VBIWN.js.map +1 -0
- package/dist/{chunk-R6FINS25.js → chunk-WIHNVN6L.js} +106 -73
- package/dist/chunk-WIHNVN6L.js.map +1 -0
- package/dist/{devtools-CMxlJUTx.d.cts → devtools-BtIkN77t.d.cts} +1 -1
- package/dist/{devtools-C4Hgfa-S.d.ts → devtools-D2z4llpA.d.ts} +1 -1
- package/dist/index.cjs +60 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.dev.js +72 -51
- 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 +1 -1
- package/dist/internal-list.d.ts +1 -1
- package/dist/internal-list.js +3 -3
- package/dist/internal.cjs +5 -5
- package/dist/internal.d.cts +4 -4
- package/dist/internal.d.ts +4 -4
- package/dist/internal.js +4 -4
- package/dist/{list-BBzsJhrm.d.ts → list-BKM6YOPq.d.ts} +1 -1
- package/dist/{list-_NJCcjl1.d.cts → list-Bi8dDF8Q.d.cts} +1 -1
- package/dist/loader.cjs +28 -26
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.js +11 -9
- package/dist/loader.js.map +1 -1
- package/dist/{props--zJ4ebbT.d.cts → props-9chMyBGb.d.cts} +1 -1
- package/dist/{props-BAGR7j-j.d.ts → props-D1nj2p_3.d.ts} +1 -1
- package/dist/{scope-CuImnvh1.d.ts → scope-BSkhJr0a.d.ts} +1 -1
- package/dist/{scope-Dq5hOu7c.d.cts → scope-Bn3sxem5.d.cts} +1 -1
- package/package.json +1 -1
- package/src/binding.ts +59 -29
- package/src/context.ts +4 -3
- package/src/dom.ts +65 -39
- package/src/error-boundary.ts +5 -5
- package/src/lifecycle.ts +8 -1
- package/src/list-helpers.ts +30 -13
- package/src/loader.ts +10 -8
- package/src/node-ops.ts +8 -5
- package/src/suspense.ts +5 -4
- package/dist/chunk-EQ5E4WOV.cjs.map +0 -1
- package/dist/chunk-F4RVNXOL.js.map +0 -1
- package/dist/chunk-I4GKKAAY.cjs.map +0 -1
- package/dist/chunk-K3DH5SD5.cjs +0 -111
- package/dist/chunk-K3DH5SD5.cjs.map +0 -1
- package/dist/chunk-P4TZLFV6.js.map +0 -1
- package/dist/chunk-R6FINS25.js.map +0 -1
- package/dist/chunk-SZLJCQFZ.cjs.map +0 -1
- /package/dist/{chunk-DKA2I6ET.js.map → chunk-2UR2UWE2.js.map} +0 -0
package/src/dom.ts
CHANGED
|
@@ -123,6 +123,7 @@ function annotateComponentElements(
|
|
|
123
123
|
*/
|
|
124
124
|
export function render(view: () => FictNode, container: HTMLElement): () => void {
|
|
125
125
|
const root = createRootContext()
|
|
126
|
+
root.ownerDocument = container.ownerDocument ?? document
|
|
126
127
|
const prev = pushRoot(root)
|
|
127
128
|
let dom: DOMElement = undefined as unknown as DOMElement
|
|
128
129
|
try {
|
|
@@ -166,6 +167,7 @@ export function render(view: () => FictNode, container: HTMLElement): () => void
|
|
|
166
167
|
*/
|
|
167
168
|
export function hydrateComponent(view: () => FictNode, container: HTMLElement): () => void {
|
|
168
169
|
const root = createRootContext()
|
|
170
|
+
root.ownerDocument = container.ownerDocument ?? document
|
|
169
171
|
const prev = pushRoot(root)
|
|
170
172
|
|
|
171
173
|
// Enable hydration flags for bindings that check __fictIsHydrating()
|
|
@@ -209,7 +211,7 @@ export function hydrateComponent(view: () => FictNode, container: HTMLElement):
|
|
|
209
211
|
* - Reactive values (functions returning any of the above)
|
|
210
212
|
*/
|
|
211
213
|
export function createElement(node: FictNode): DOMElement {
|
|
212
|
-
return createElementWithContext(node, null)
|
|
214
|
+
return createElementWithContext(node, null, resolveOwnerDocument())
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
function resolveNamespace(tagName: string, namespace: NamespaceContext): NamespaceContext {
|
|
@@ -221,7 +223,15 @@ function resolveNamespace(tagName: string, namespace: NamespaceContext): Namespa
|
|
|
221
223
|
return null
|
|
222
224
|
}
|
|
223
225
|
|
|
224
|
-
function
|
|
226
|
+
function resolveOwnerDocument(ownerDocument?: Document): Document {
|
|
227
|
+
return ownerDocument ?? getCurrentRoot()?.ownerDocument ?? document
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function createElementWithContext(
|
|
231
|
+
node: FictNode,
|
|
232
|
+
namespace: NamespaceContext,
|
|
233
|
+
ownerDocument: Document,
|
|
234
|
+
): DOMElement {
|
|
225
235
|
// Already a DOM node - pass through
|
|
226
236
|
if (node instanceof Node) {
|
|
227
237
|
return node
|
|
@@ -229,22 +239,22 @@ function createElementWithContext(node: FictNode, namespace: NamespaceContext):
|
|
|
229
239
|
|
|
230
240
|
// Null/undefined/false - empty placeholder
|
|
231
241
|
if (node === null || node === undefined || node === false) {
|
|
232
|
-
return
|
|
242
|
+
return ownerDocument.createTextNode('')
|
|
233
243
|
}
|
|
234
244
|
|
|
235
245
|
// Reactive getter function - resolve to actual node
|
|
236
246
|
if (isReactive(node)) {
|
|
237
247
|
const resolved = (node as () => FictNode)()
|
|
238
248
|
if (resolved === node) {
|
|
239
|
-
return
|
|
249
|
+
return ownerDocument.createTextNode('')
|
|
240
250
|
}
|
|
241
|
-
return createElementWithContext(resolved, namespace)
|
|
251
|
+
return createElementWithContext(resolved, namespace, ownerDocument)
|
|
242
252
|
}
|
|
243
253
|
|
|
244
254
|
// Non-reactive function values are not valid DOM nodes.
|
|
245
255
|
// Keep callback values inert instead of stringifying function source.
|
|
246
256
|
if (typeof node === 'function') {
|
|
247
|
-
return
|
|
257
|
+
return ownerDocument.createTextNode('')
|
|
248
258
|
}
|
|
249
259
|
|
|
250
260
|
if (typeof node === 'object' && node !== null && !(node instanceof Node)) {
|
|
@@ -265,26 +275,26 @@ function createElementWithContext(node: FictNode, namespace: NamespaceContext):
|
|
|
265
275
|
.catch(() => undefined)
|
|
266
276
|
}
|
|
267
277
|
}
|
|
268
|
-
return
|
|
278
|
+
return createElementWithContext(handle.marker as FictNode, namespace, ownerDocument)
|
|
269
279
|
}
|
|
270
280
|
}
|
|
271
281
|
|
|
272
282
|
// Array - create fragment
|
|
273
283
|
if (Array.isArray(node)) {
|
|
274
|
-
const frag =
|
|
284
|
+
const frag = ownerDocument.createDocumentFragment()
|
|
275
285
|
for (const child of node) {
|
|
276
|
-
appendChildNode(frag, child, namespace)
|
|
286
|
+
appendChildNode(frag, child, namespace, ownerDocument)
|
|
277
287
|
}
|
|
278
288
|
return frag
|
|
279
289
|
}
|
|
280
290
|
|
|
281
291
|
// Primitive values - text node
|
|
282
292
|
if (typeof node === 'string' || typeof node === 'number') {
|
|
283
|
-
return
|
|
293
|
+
return ownerDocument.createTextNode(String(node))
|
|
284
294
|
}
|
|
285
295
|
|
|
286
296
|
if (typeof node === 'boolean') {
|
|
287
|
-
return
|
|
297
|
+
return ownerDocument.createTextNode('')
|
|
288
298
|
}
|
|
289
299
|
|
|
290
300
|
// VNode
|
|
@@ -355,13 +365,13 @@ function createElementWithContext(node: FictNode, namespace: NamespaceContext):
|
|
|
355
365
|
onCleanup(() => hook.componentUnmount?.(componentId))
|
|
356
366
|
}
|
|
357
367
|
if (__fictIsResumable() && !__fictIsHydrating()) {
|
|
358
|
-
const content = createElementWithContext(rendered as FictNode, namespace)
|
|
368
|
+
const content = createElementWithContext(rendered as FictNode, namespace, ownerDocument)
|
|
359
369
|
const host =
|
|
360
370
|
namespace === 'svg'
|
|
361
|
-
?
|
|
371
|
+
? ownerDocument.createElementNS(SVG_NS, 'fict-host')
|
|
362
372
|
: namespace === 'mathml'
|
|
363
|
-
?
|
|
364
|
-
:
|
|
373
|
+
? ownerDocument.createElementNS(MATHML_NS, 'fict-host')
|
|
374
|
+
: ownerDocument.createElement('fict-host')
|
|
365
375
|
host.setAttribute('data-fict-host', '')
|
|
366
376
|
if (namespace === null && (host as HTMLElement).style) {
|
|
367
377
|
;(host as HTMLElement).style.display = 'contents'
|
|
@@ -384,7 +394,7 @@ function createElementWithContext(node: FictNode, namespace: NamespaceContext):
|
|
|
384
394
|
}
|
|
385
395
|
return host as DOMElement
|
|
386
396
|
}
|
|
387
|
-
const componentRoot = createElementWithContext(rendered as FictNode, namespace)
|
|
397
|
+
const componentRoot = createElementWithContext(rendered as FictNode, namespace, ownerDocument)
|
|
388
398
|
if (hook && componentId !== undefined) {
|
|
389
399
|
mountElements = collectComponentMountElements(componentRoot)
|
|
390
400
|
annotateComponentElements(mountElements, componentId, componentName)
|
|
@@ -392,7 +402,7 @@ function createElementWithContext(node: FictNode, namespace: NamespaceContext):
|
|
|
392
402
|
return componentRoot
|
|
393
403
|
} catch (err) {
|
|
394
404
|
if (handleSuspend(err as any)) {
|
|
395
|
-
return
|
|
405
|
+
return ownerDocument.createComment('fict:suspend')
|
|
396
406
|
}
|
|
397
407
|
handleError(err, { source: 'render', componentName: vnode.type.name })
|
|
398
408
|
throw err
|
|
@@ -403,9 +413,9 @@ function createElementWithContext(node: FictNode, namespace: NamespaceContext):
|
|
|
403
413
|
|
|
404
414
|
// Fragment
|
|
405
415
|
if (vnode.type === Fragment) {
|
|
406
|
-
const frag =
|
|
416
|
+
const frag = ownerDocument.createDocumentFragment()
|
|
407
417
|
const children = vnode.props?.children as FictNode | FictNode[] | undefined
|
|
408
|
-
appendChildren(frag, children, namespace)
|
|
418
|
+
appendChildren(frag, children, namespace, ownerDocument)
|
|
409
419
|
return frag
|
|
410
420
|
}
|
|
411
421
|
|
|
@@ -414,15 +424,16 @@ function createElementWithContext(node: FictNode, namespace: NamespaceContext):
|
|
|
414
424
|
const resolvedNamespace = resolveNamespace(tagName, namespace)
|
|
415
425
|
const el =
|
|
416
426
|
resolvedNamespace === 'svg'
|
|
417
|
-
?
|
|
427
|
+
? ownerDocument.createElementNS(SVG_NS, tagName)
|
|
418
428
|
: resolvedNamespace === 'mathml'
|
|
419
|
-
?
|
|
420
|
-
:
|
|
429
|
+
? ownerDocument.createElementNS(MATHML_NS, tagName)
|
|
430
|
+
: ownerDocument.createElement(tagName)
|
|
421
431
|
applyProps(el, vnode.props ?? {}, resolvedNamespace === 'svg')
|
|
422
432
|
appendChildren(
|
|
423
433
|
el as unknown as ParentNode & Node,
|
|
424
434
|
vnode.props?.children as FictNode | FictNode[] | undefined,
|
|
425
435
|
tagName === 'foreignObject' ? null : resolvedNamespace,
|
|
436
|
+
ownerDocument,
|
|
426
437
|
)
|
|
427
438
|
return el as DOMElement
|
|
428
439
|
}
|
|
@@ -442,10 +453,10 @@ export function template(
|
|
|
442
453
|
isSVG?: boolean,
|
|
443
454
|
isMathML?: boolean,
|
|
444
455
|
): () => Node {
|
|
445
|
-
|
|
456
|
+
const nodeByDocument = new WeakMap<Document, Node>()
|
|
446
457
|
|
|
447
|
-
const create = (): Node => {
|
|
448
|
-
const t =
|
|
458
|
+
const create = (ownerDocument: Document): Node => {
|
|
459
|
+
const t = ownerDocument.createElement('template')
|
|
449
460
|
|
|
450
461
|
if (isSVG) {
|
|
451
462
|
// fix: Wrap HTML in <svg> to parse content in SVG namespace
|
|
@@ -463,7 +474,7 @@ export function template(
|
|
|
463
474
|
return wrapper.firstChild!
|
|
464
475
|
}
|
|
465
476
|
// Preserve all root nodes by returning a fragment
|
|
466
|
-
const fragment =
|
|
477
|
+
const fragment = ownerDocument.createDocumentFragment()
|
|
467
478
|
fragment.append(...Array.from(wrapper.childNodes))
|
|
468
479
|
return fragment
|
|
469
480
|
}
|
|
@@ -483,7 +494,7 @@ export function template(
|
|
|
483
494
|
return wrapper.firstChild!
|
|
484
495
|
}
|
|
485
496
|
// Preserve all root nodes by returning a fragment
|
|
486
|
-
const fragment =
|
|
497
|
+
const fragment = ownerDocument.createDocumentFragment()
|
|
487
498
|
fragment.append(...Array.from(wrapper.childNodes))
|
|
488
499
|
return fragment
|
|
489
500
|
}
|
|
@@ -504,17 +515,26 @@ export function template(
|
|
|
504
515
|
return content
|
|
505
516
|
}
|
|
506
517
|
|
|
518
|
+
const getBase = (ownerDocument: Document): Node => {
|
|
519
|
+
const cached = nodeByDocument.get(ownerDocument)
|
|
520
|
+
if (cached) return cached
|
|
521
|
+
const created = create(ownerDocument)
|
|
522
|
+
nodeByDocument.set(ownerDocument, created)
|
|
523
|
+
return created
|
|
524
|
+
}
|
|
525
|
+
|
|
507
526
|
// Create the cloning function
|
|
508
527
|
const fn = isImportNode
|
|
509
528
|
? () =>
|
|
510
529
|
untrack(() => {
|
|
511
|
-
const
|
|
530
|
+
const ownerDocument = resolveOwnerDocument()
|
|
531
|
+
const base = getBase(ownerDocument)
|
|
512
532
|
return isHydratingActive()
|
|
513
|
-
? claimNodes(base, () =>
|
|
514
|
-
:
|
|
533
|
+
? claimNodes(base, () => ownerDocument.importNode(base, true))
|
|
534
|
+
: ownerDocument.importNode(base, true)
|
|
515
535
|
})
|
|
516
536
|
: () => {
|
|
517
|
-
const base =
|
|
537
|
+
const base = getBase(resolveOwnerDocument())
|
|
518
538
|
return isHydratingActive()
|
|
519
539
|
? claimNodes(base, () => base.cloneNode(true))
|
|
520
540
|
: base.cloneNode(true)
|
|
@@ -553,7 +573,10 @@ function appendChildNode(
|
|
|
553
573
|
parent: ParentNode & Node,
|
|
554
574
|
child: FictNode,
|
|
555
575
|
namespace: NamespaceContext,
|
|
576
|
+
ownerDocument: Document,
|
|
556
577
|
): void {
|
|
578
|
+
const parentOwnerDocument = parent.ownerDocument ?? ownerDocument
|
|
579
|
+
|
|
557
580
|
// Skip nullish values
|
|
558
581
|
if (child === null || child === undefined || child === false) {
|
|
559
582
|
return
|
|
@@ -561,7 +584,7 @@ function appendChildNode(
|
|
|
561
584
|
|
|
562
585
|
// Handle BindingHandle (recursive)
|
|
563
586
|
if (isBindingHandle(child)) {
|
|
564
|
-
appendChildNode(parent, child.marker, namespace)
|
|
587
|
+
appendChildNode(parent, child.marker, namespace, parentOwnerDocument)
|
|
565
588
|
// Flush pending nodes now that markers are in the DOM
|
|
566
589
|
child.flush?.()
|
|
567
590
|
return
|
|
@@ -573,7 +596,9 @@ function appendChildNode(
|
|
|
573
596
|
if (typeof child === 'function') {
|
|
574
597
|
const childGetter = child as () => FictNode
|
|
575
598
|
if (isReactive(childGetter)) {
|
|
576
|
-
createChildBinding(parent, childGetter, node =>
|
|
599
|
+
createChildBinding(parent, childGetter, node =>
|
|
600
|
+
createElementWithContext(node, namespace, parentOwnerDocument),
|
|
601
|
+
)
|
|
577
602
|
return
|
|
578
603
|
}
|
|
579
604
|
return
|
|
@@ -582,7 +607,7 @@ function appendChildNode(
|
|
|
582
607
|
// Static child - create element and append
|
|
583
608
|
if (Array.isArray(child)) {
|
|
584
609
|
for (const item of child) {
|
|
585
|
-
appendChildNode(parent, item, namespace)
|
|
610
|
+
appendChildNode(parent, item, namespace, parentOwnerDocument)
|
|
586
611
|
}
|
|
587
612
|
return
|
|
588
613
|
}
|
|
@@ -590,16 +615,16 @@ function appendChildNode(
|
|
|
590
615
|
// Cast to Node for remaining logic
|
|
591
616
|
let domNode: Node
|
|
592
617
|
if (typeof child !== 'object' || child === null) {
|
|
593
|
-
domNode =
|
|
618
|
+
domNode = parentOwnerDocument.createTextNode(String(child ?? ''))
|
|
594
619
|
} else {
|
|
595
|
-
domNode = createElementWithContext(child as any, namespace) as Node
|
|
620
|
+
domNode = createElementWithContext(child as any, namespace, parentOwnerDocument) as Node
|
|
596
621
|
}
|
|
597
622
|
|
|
598
623
|
// Handle DocumentFragment manually to avoid JSDOM issues
|
|
599
624
|
if (domNode.nodeType === 11) {
|
|
600
625
|
const children = Array.from(domNode.childNodes)
|
|
601
626
|
for (const node of children) {
|
|
602
|
-
appendChildNode(parent, node as FictNode, namespace)
|
|
627
|
+
appendChildNode(parent, node as FictNode, namespace, parentOwnerDocument)
|
|
603
628
|
}
|
|
604
629
|
return
|
|
605
630
|
}
|
|
@@ -627,17 +652,18 @@ function appendChildren(
|
|
|
627
652
|
parent: ParentNode & Node,
|
|
628
653
|
children: FictNode | FictNode[] | undefined,
|
|
629
654
|
namespace: NamespaceContext,
|
|
655
|
+
ownerDocument: Document,
|
|
630
656
|
): void {
|
|
631
657
|
if (children === undefined) return
|
|
632
658
|
|
|
633
659
|
if (Array.isArray(children)) {
|
|
634
660
|
for (const child of children) {
|
|
635
|
-
appendChildren(parent, child, namespace)
|
|
661
|
+
appendChildren(parent, child, namespace, ownerDocument)
|
|
636
662
|
}
|
|
637
663
|
return
|
|
638
664
|
}
|
|
639
665
|
|
|
640
|
-
appendChildNode(parent, children, namespace)
|
|
666
|
+
appendChildNode(parent, children, namespace, ownerDocument)
|
|
641
667
|
}
|
|
642
668
|
|
|
643
669
|
// ============================================================================
|
package/src/error-boundary.ts
CHANGED
|
@@ -19,11 +19,11 @@ interface ErrorBoundaryProps extends BaseProps {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function ErrorBoundary(props: ErrorBoundaryProps): FictNode {
|
|
22
|
-
const fragment = document.createDocumentFragment()
|
|
23
|
-
const marker = document.createComment('fict:error-boundary')
|
|
24
|
-
fragment.appendChild(marker)
|
|
25
|
-
|
|
26
22
|
const hostRoot = getCurrentRoot()
|
|
23
|
+
const markerOwnerDocument = hostRoot?.ownerDocument ?? document
|
|
24
|
+
const fragment = markerOwnerDocument.createDocumentFragment()
|
|
25
|
+
const marker = markerOwnerDocument.createComment('fict:error-boundary')
|
|
26
|
+
fragment.appendChild(marker)
|
|
27
27
|
|
|
28
28
|
let cleanup: (() => void) | undefined
|
|
29
29
|
let activeNodes: Node[] = []
|
|
@@ -58,7 +58,7 @@ export function ErrorBoundary(props: ErrorBoundaryProps): FictNode {
|
|
|
58
58
|
let nodes: Node[] = []
|
|
59
59
|
try {
|
|
60
60
|
const output = createElement(value)
|
|
61
|
-
nodes = toNodeArray(output)
|
|
61
|
+
nodes = toNodeArray(output, markerOwnerDocument)
|
|
62
62
|
const parentNode = marker.parentNode as (ParentNode & Node) | null
|
|
63
63
|
if (parentNode) {
|
|
64
64
|
insertNodesBefore(parentNode, nodes, marker)
|
package/src/lifecycle.ts
CHANGED
|
@@ -11,6 +11,7 @@ type LifecycleFn = () => void | Cleanup
|
|
|
11
11
|
|
|
12
12
|
export interface RootContext {
|
|
13
13
|
parent?: RootContext | undefined
|
|
14
|
+
ownerDocument?: Document | undefined
|
|
14
15
|
onMountCallbacks?: LifecycleFn[]
|
|
15
16
|
cleanups: Cleanup[]
|
|
16
17
|
destroyCallbacks: Cleanup[]
|
|
@@ -60,7 +61,13 @@ function setRootSuspendDevtools(root: RootContext, suspended: boolean): void {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
export function createRootContext(parent?: RootContext): RootContext {
|
|
63
|
-
const root = {
|
|
64
|
+
const root = {
|
|
65
|
+
parent,
|
|
66
|
+
ownerDocument: parent?.ownerDocument,
|
|
67
|
+
cleanups: [],
|
|
68
|
+
destroyCallbacks: [],
|
|
69
|
+
suspended: false,
|
|
70
|
+
}
|
|
64
71
|
registerRootDevtools(root)
|
|
65
72
|
return root
|
|
66
73
|
}
|
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
|
|
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/suspense.ts
CHANGED
|
@@ -55,6 +55,7 @@ export function Suspense(props: SuspenseProps): FictNode {
|
|
|
55
55
|
let resolvedOnce = false
|
|
56
56
|
let epoch = 0
|
|
57
57
|
const hostRoot = getCurrentRoot()
|
|
58
|
+
const markerOwnerDocument = hostRoot?.ownerDocument ?? document
|
|
58
59
|
|
|
59
60
|
const toFallback = (err?: unknown) =>
|
|
60
61
|
typeof props.fallback === 'function'
|
|
@@ -85,7 +86,7 @@ export function Suspense(props: SuspenseProps): FictNode {
|
|
|
85
86
|
boundaryPushed = true
|
|
86
87
|
}
|
|
87
88
|
const output = createElement(view)
|
|
88
|
-
nodes = toNodeArray(output)
|
|
89
|
+
nodes = toNodeArray(output, markerOwnerDocument)
|
|
89
90
|
// Suspended view: child threw a suspense token and was handled upstream.
|
|
90
91
|
// Avoid replacing existing fallback content; tear down this attempt.
|
|
91
92
|
const suspendedAttempt =
|
|
@@ -123,9 +124,9 @@ export function Suspense(props: SuspenseProps): FictNode {
|
|
|
123
124
|
activeNodes = nodes
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
const fragment =
|
|
127
|
-
const startMarker =
|
|
128
|
-
const endMarker =
|
|
127
|
+
const fragment = markerOwnerDocument.createDocumentFragment()
|
|
128
|
+
const startMarker = markerOwnerDocument.createComment('fict:suspense-start')
|
|
129
|
+
const endMarker = markerOwnerDocument.createComment('fict:suspense-end')
|
|
129
130
|
fragment.appendChild(startMarker)
|
|
130
131
|
fragment.appendChild(endMarker)
|
|
131
132
|
let cleanup: (() => void) | undefined
|