@fictjs/runtime 0.0.7 → 0.0.9
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/index.cjs +2287 -2329
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -26
- package/dist/index.d.ts +29 -26
- package/dist/index.dev.js +2295 -2336
- package/dist/index.dev.js.map +1 -1
- package/dist/index.js +2287 -2329
- package/dist/index.js.map +1 -1
- package/dist/slim.cjs +1858 -1850
- package/dist/slim.cjs.map +1 -1
- package/dist/slim.d.cts +12 -11
- package/dist/slim.d.ts +12 -11
- package/dist/slim.js +1858 -1850
- package/dist/slim.js.map +1 -1
- package/package.json +1 -1
- package/src/binding.ts +78 -83
- package/src/dom.ts +75 -33
- package/src/error-boundary.ts +3 -1
- package/src/list-helpers.ts +16 -3
- package/src/ref.ts +1 -1
- package/src/store.ts +30 -2
- package/src/types.ts +3 -3
package/package.json
CHANGED
package/src/binding.ts
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
} from './lifecycle'
|
|
39
39
|
import { toNodeArray, removeNodes, insertNodesBefore } from './node-ops'
|
|
40
40
|
import { computed, createSignal, untrack, type Signal } from './signal'
|
|
41
|
+
import { createVersionedSignalAccessor } from './list-helpers'
|
|
41
42
|
import type { Cleanup, FictNode } from './types'
|
|
42
43
|
|
|
43
44
|
// ============================================================================
|
|
@@ -66,10 +67,8 @@ interface ManagedBlock<T = unknown> {
|
|
|
66
67
|
root: RootContext
|
|
67
68
|
value: Signal<T>
|
|
68
69
|
index: Signal<number>
|
|
69
|
-
version: Signal<number>
|
|
70
70
|
start: Comment
|
|
71
71
|
end: Comment
|
|
72
|
-
valueProxy: T
|
|
73
72
|
renderCurrent: () => FictNode
|
|
74
73
|
}
|
|
75
74
|
|
|
@@ -165,7 +164,7 @@ export function unwrapPrimitive<T>(value: T): T {
|
|
|
165
164
|
return value
|
|
166
165
|
}
|
|
167
166
|
|
|
168
|
-
function
|
|
167
|
+
function _createValueProxy<T>(read: () => T): T {
|
|
169
168
|
const getPrimitivePrototype = (value: unknown): Record<PropertyKey, unknown> | undefined => {
|
|
170
169
|
switch (typeof value) {
|
|
171
170
|
case 'string':
|
|
@@ -339,7 +338,7 @@ function formatTextValue(value: unknown): string {
|
|
|
339
338
|
// ============================================================================
|
|
340
339
|
|
|
341
340
|
/** Attribute setter function type */
|
|
342
|
-
export type AttributeSetter = (el:
|
|
341
|
+
export type AttributeSetter = (el: Element, key: string, value: unknown) => void
|
|
343
342
|
|
|
344
343
|
/**
|
|
345
344
|
* Create a reactive attribute binding on an element.
|
|
@@ -354,7 +353,7 @@ export type AttributeSetter = (el: HTMLElement, key: string, value: unknown) =>
|
|
|
354
353
|
* ```
|
|
355
354
|
*/
|
|
356
355
|
export function createAttributeBinding(
|
|
357
|
-
el:
|
|
356
|
+
el: Element,
|
|
358
357
|
key: string,
|
|
359
358
|
value: MaybeReactive<unknown>,
|
|
360
359
|
setter: AttributeSetter,
|
|
@@ -373,7 +372,7 @@ export function createAttributeBinding(
|
|
|
373
372
|
/**
|
|
374
373
|
* Bind a reactive value to an element's attribute.
|
|
375
374
|
*/
|
|
376
|
-
export function bindAttribute(el:
|
|
375
|
+
export function bindAttribute(el: Element, key: string, getValue: () => unknown): Cleanup {
|
|
377
376
|
let prevValue: unknown = undefined
|
|
378
377
|
return createRenderEffect(() => {
|
|
379
378
|
const value = getValue()
|
|
@@ -393,7 +392,7 @@ export function bindAttribute(el: HTMLElement, key: string, getValue: () => unkn
|
|
|
393
392
|
/**
|
|
394
393
|
* Bind a reactive value to an element's property.
|
|
395
394
|
*/
|
|
396
|
-
export function bindProperty(el:
|
|
395
|
+
export function bindProperty(el: Element, key: string, getValue: () => unknown): Cleanup {
|
|
397
396
|
// Keep behavior aligned with the legacy createElement+applyProps path in `dom.ts`,
|
|
398
397
|
// where certain keys must behave like DOM properties and nullish clears should
|
|
399
398
|
// reset to sensible defaults (e.g. value -> '', checked -> false).
|
|
@@ -430,18 +429,19 @@ export function bindProperty(el: HTMLElement, key: string, getValue: () => unkno
|
|
|
430
429
|
* Apply styles to an element, supporting reactive style objects/strings.
|
|
431
430
|
*/
|
|
432
431
|
export function createStyleBinding(
|
|
433
|
-
el:
|
|
432
|
+
el: Element,
|
|
434
433
|
value: MaybeReactive<string | Record<string, string | number> | null | undefined>,
|
|
435
434
|
): void {
|
|
435
|
+
const target = el as Element & { style: CSSStyleDeclaration }
|
|
436
436
|
if (isReactive(value)) {
|
|
437
437
|
let prev: unknown
|
|
438
438
|
createRenderEffect(() => {
|
|
439
439
|
const next = (value as () => unknown)()
|
|
440
|
-
applyStyle(
|
|
440
|
+
applyStyle(target, next, prev)
|
|
441
441
|
prev = next
|
|
442
442
|
})
|
|
443
443
|
} else {
|
|
444
|
-
applyStyle(
|
|
444
|
+
applyStyle(target, value, undefined)
|
|
445
445
|
}
|
|
446
446
|
}
|
|
447
447
|
|
|
@@ -449,13 +449,14 @@ export function createStyleBinding(
|
|
|
449
449
|
* Bind a reactive style value to an existing element.
|
|
450
450
|
*/
|
|
451
451
|
export function bindStyle(
|
|
452
|
-
el:
|
|
452
|
+
el: Element,
|
|
453
453
|
getValue: () => string | Record<string, string | number> | null | undefined,
|
|
454
454
|
): Cleanup {
|
|
455
|
+
const target = el as Element & { style: CSSStyleDeclaration }
|
|
455
456
|
let prev: unknown
|
|
456
457
|
return createRenderEffect(() => {
|
|
457
458
|
const next = getValue()
|
|
458
|
-
applyStyle(
|
|
459
|
+
applyStyle(target, next, prev)
|
|
459
460
|
prev = next
|
|
460
461
|
})
|
|
461
462
|
}
|
|
@@ -463,7 +464,11 @@ export function bindStyle(
|
|
|
463
464
|
/**
|
|
464
465
|
* Apply a style value to an element
|
|
465
466
|
*/
|
|
466
|
-
function applyStyle(
|
|
467
|
+
function applyStyle(
|
|
468
|
+
el: Element & { style: CSSStyleDeclaration },
|
|
469
|
+
value: unknown,
|
|
470
|
+
prev: unknown,
|
|
471
|
+
): void {
|
|
467
472
|
if (typeof value === 'string') {
|
|
468
473
|
el.style.cssText = value
|
|
469
474
|
} else if (value && typeof value === 'object') {
|
|
@@ -525,7 +530,7 @@ function isUnitlessStyleProperty(prop: string): boolean {
|
|
|
525
530
|
* Apply class to an element, supporting reactive class values.
|
|
526
531
|
*/
|
|
527
532
|
export function createClassBinding(
|
|
528
|
-
el:
|
|
533
|
+
el: Element,
|
|
529
534
|
value: MaybeReactive<string | Record<string, boolean> | null | undefined>,
|
|
530
535
|
): void {
|
|
531
536
|
if (isReactive(value)) {
|
|
@@ -543,7 +548,7 @@ export function createClassBinding(
|
|
|
543
548
|
* Bind a reactive class value to an existing element.
|
|
544
549
|
*/
|
|
545
550
|
export function bindClass(
|
|
546
|
-
el:
|
|
551
|
+
el: Element,
|
|
547
552
|
getValue: () => string | Record<string, boolean> | null | undefined,
|
|
548
553
|
): Cleanup {
|
|
549
554
|
let prev: Record<string, boolean> = {}
|
|
@@ -556,7 +561,7 @@ export function bindClass(
|
|
|
556
561
|
/**
|
|
557
562
|
* Toggle a class key (supports space-separated class names)
|
|
558
563
|
*/
|
|
559
|
-
function toggleClassKey(node:
|
|
564
|
+
function toggleClassKey(node: Element, key: string, value: boolean): void {
|
|
560
565
|
const classNames = key.trim().split(/\s+/)
|
|
561
566
|
for (let i = 0, len = classNames.length; i < len; i++) {
|
|
562
567
|
node.classList.toggle(classNames[i]!, value)
|
|
@@ -567,7 +572,7 @@ function toggleClassKey(node: HTMLElement, key: string, value: boolean): void {
|
|
|
567
572
|
* Apply a class value to an element using classList.toggle for efficient updates.
|
|
568
573
|
* Returns the new prev state for tracking.
|
|
569
574
|
*/
|
|
570
|
-
function applyClass(el:
|
|
575
|
+
function applyClass(el: Element, value: unknown, prev: unknown): Record<string, boolean> {
|
|
571
576
|
const prevState = (prev && typeof prev === 'object' ? prev : {}) as Record<string, boolean>
|
|
572
577
|
|
|
573
578
|
// Handle string value - full replacement
|
|
@@ -620,7 +625,7 @@ function applyClass(el: HTMLElement, value: unknown, prev: unknown): Record<stri
|
|
|
620
625
|
* Exported classList function for direct use (compatible with dom-expressions)
|
|
621
626
|
*/
|
|
622
627
|
export function classList(
|
|
623
|
-
node:
|
|
628
|
+
node: Element,
|
|
624
629
|
value: Record<string, boolean> | null | undefined,
|
|
625
630
|
prev: Record<string, boolean> = {},
|
|
626
631
|
): Record<string, boolean> {
|
|
@@ -631,19 +636,6 @@ export function classList(
|
|
|
631
636
|
// Child/Insert Binding (Dynamic Children)
|
|
632
637
|
// ============================================================================
|
|
633
638
|
|
|
634
|
-
/** Managed child node with its dispose function */
|
|
635
|
-
interface ManagedBlock<T = unknown> {
|
|
636
|
-
nodes: Node[]
|
|
637
|
-
root: RootContext
|
|
638
|
-
value: Signal<T>
|
|
639
|
-
index: Signal<number>
|
|
640
|
-
version: Signal<number>
|
|
641
|
-
start: Comment
|
|
642
|
-
end: Comment
|
|
643
|
-
valueProxy: T
|
|
644
|
-
renderCurrent: () => FictNode
|
|
645
|
-
}
|
|
646
|
-
|
|
647
639
|
/**
|
|
648
640
|
* Insert reactive content into a parent element.
|
|
649
641
|
* This is a simpler API than createChildBinding for basic cases.
|
|
@@ -654,11 +646,12 @@ interface ManagedBlock<T = unknown> {
|
|
|
654
646
|
* @param createElementFn - Optional function to create DOM elements (when marker is provided)
|
|
655
647
|
*/
|
|
656
648
|
export function insert(
|
|
657
|
-
parent:
|
|
649
|
+
parent: ParentNode & Node,
|
|
658
650
|
getValue: () => FictNode,
|
|
659
651
|
markerOrCreateElement?: Node | CreateElementFn,
|
|
660
652
|
createElementFn?: CreateElementFn,
|
|
661
653
|
): Cleanup {
|
|
654
|
+
const hostRoot = getCurrentRoot()
|
|
662
655
|
let marker: Node
|
|
663
656
|
let ownsMarker = false
|
|
664
657
|
let createFn: CreateElementFn | undefined = createElementFn
|
|
@@ -736,7 +729,7 @@ export function insert(
|
|
|
736
729
|
}
|
|
737
730
|
clearCurrentNodes()
|
|
738
731
|
|
|
739
|
-
const root = createRootContext()
|
|
732
|
+
const root = createRootContext(hostRoot)
|
|
740
733
|
const prev = pushRoot(root)
|
|
741
734
|
let nodes: Node[] = []
|
|
742
735
|
try {
|
|
@@ -748,7 +741,15 @@ export function insert(
|
|
|
748
741
|
if (value.every(v => v instanceof Node)) {
|
|
749
742
|
newNode = value as Node[]
|
|
750
743
|
} else {
|
|
751
|
-
|
|
744
|
+
if (createFn) {
|
|
745
|
+
const mapped: Node[] = []
|
|
746
|
+
for (const item of value) {
|
|
747
|
+
mapped.push(...toNodeArray(createFn(item as any)))
|
|
748
|
+
}
|
|
749
|
+
newNode = mapped
|
|
750
|
+
} else {
|
|
751
|
+
newNode = document.createTextNode(String(value))
|
|
752
|
+
}
|
|
752
753
|
}
|
|
753
754
|
} else {
|
|
754
755
|
newNode = createFn ? createFn(value) : document.createTextNode(String(value))
|
|
@@ -794,15 +795,16 @@ export function insert(
|
|
|
794
795
|
* ```
|
|
795
796
|
*/
|
|
796
797
|
export function createChildBinding(
|
|
797
|
-
parent:
|
|
798
|
+
parent: ParentNode & Node,
|
|
798
799
|
getValue: () => FictNode,
|
|
799
800
|
createElementFn: CreateElementFn,
|
|
800
801
|
): BindingHandle {
|
|
801
802
|
const marker = document.createComment('fict:child')
|
|
802
803
|
parent.appendChild(marker)
|
|
804
|
+
const hostRoot = getCurrentRoot()
|
|
803
805
|
|
|
804
806
|
const dispose = createRenderEffect(() => {
|
|
805
|
-
const root = createRootContext()
|
|
807
|
+
const root = createRootContext(hostRoot)
|
|
806
808
|
const prev = pushRoot(root)
|
|
807
809
|
let nodes: Node[] = []
|
|
808
810
|
let handledError = false
|
|
@@ -857,10 +859,10 @@ export function createChildBinding(
|
|
|
857
859
|
// Event Delegation System
|
|
858
860
|
// ============================================================================
|
|
859
861
|
|
|
860
|
-
// Extend
|
|
862
|
+
// Extend Element/Document type to support event delegation
|
|
861
863
|
declare global {
|
|
862
|
-
interface
|
|
863
|
-
_$host?:
|
|
864
|
+
interface Element {
|
|
865
|
+
_$host?: Element
|
|
864
866
|
[key: `$$${string}`]: EventListener | [EventListener, unknown] | undefined
|
|
865
867
|
[key: `$$${string}Data`]: unknown
|
|
866
868
|
}
|
|
@@ -912,7 +914,7 @@ export function clearDelegatedEvents(doc: Document = window.document): void {
|
|
|
912
914
|
* Walks up the DOM tree to find and call handlers stored as $$eventName properties.
|
|
913
915
|
*/
|
|
914
916
|
function globalEventHandler(e: Event): void {
|
|
915
|
-
let node = e.target as
|
|
917
|
+
let node = e.target as Element | null
|
|
916
918
|
const key = `$$${e.type}` as const
|
|
917
919
|
const dataKey = `${key}Data` as `$$${string}Data`
|
|
918
920
|
const oriTarget = e.target
|
|
@@ -958,7 +960,7 @@ function globalEventHandler(e: Event): void {
|
|
|
958
960
|
if (
|
|
959
961
|
shadowHost &&
|
|
960
962
|
typeof shadowHost !== 'string' &&
|
|
961
|
-
!(shadowHost as
|
|
963
|
+
!(shadowHost as Element)._$host &&
|
|
962
964
|
node.contains(e.target as Node)
|
|
963
965
|
) {
|
|
964
966
|
retarget(shadowHost as EventTarget)
|
|
@@ -971,7 +973,7 @@ function globalEventHandler(e: Event): void {
|
|
|
971
973
|
while (handleNode() && node) {
|
|
972
974
|
node = (node._$host ||
|
|
973
975
|
node.parentNode ||
|
|
974
|
-
(node as unknown as ShadowRoot).host) as
|
|
976
|
+
(node as unknown as ShadowRoot).host) as Element | null
|
|
975
977
|
}
|
|
976
978
|
}
|
|
977
979
|
|
|
@@ -988,7 +990,7 @@ function globalEventHandler(e: Event): void {
|
|
|
988
990
|
const path = e.composedPath()
|
|
989
991
|
retarget(path[0] as EventTarget)
|
|
990
992
|
for (let i = 0; i < path.length - 2; i++) {
|
|
991
|
-
node = path[i] as
|
|
993
|
+
node = path[i] as Element
|
|
992
994
|
if (!handleNode()) break
|
|
993
995
|
// Handle portal event bubbling
|
|
994
996
|
if (node._$host) {
|
|
@@ -1020,7 +1022,7 @@ function globalEventHandler(e: Event): void {
|
|
|
1020
1022
|
* @param delegate - Whether to use delegation (auto-detected based on event name)
|
|
1021
1023
|
*/
|
|
1022
1024
|
export function addEventListener(
|
|
1023
|
-
node:
|
|
1025
|
+
node: Element,
|
|
1024
1026
|
name: string,
|
|
1025
1027
|
handler: EventListener | [EventListener, unknown] | null | undefined,
|
|
1026
1028
|
delegate?: boolean,
|
|
@@ -1066,7 +1068,7 @@ export function addEventListener(
|
|
|
1066
1068
|
* ```
|
|
1067
1069
|
*/
|
|
1068
1070
|
export function bindEvent(
|
|
1069
|
-
el:
|
|
1071
|
+
el: Element,
|
|
1070
1072
|
eventName: string,
|
|
1071
1073
|
handler: EventListenerOrEventListenerObject | null | undefined,
|
|
1072
1074
|
options?: boolean | AddEventListenerOptions,
|
|
@@ -1153,7 +1155,7 @@ export function bindEvent(
|
|
|
1153
1155
|
* bindRef(el, () => props.ref)
|
|
1154
1156
|
* ```
|
|
1155
1157
|
*/
|
|
1156
|
-
export function bindRef(el:
|
|
1158
|
+
export function bindRef(el: Element, ref: unknown): Cleanup {
|
|
1157
1159
|
if (ref == null) return () => {}
|
|
1158
1160
|
|
|
1159
1161
|
// Handle reactive refs (getters)
|
|
@@ -1164,10 +1166,10 @@ export function bindRef(el: HTMLElement, ref: unknown): Cleanup {
|
|
|
1164
1166
|
|
|
1165
1167
|
if (typeof refValue === 'function') {
|
|
1166
1168
|
// Callback ref: call with element
|
|
1167
|
-
;(refValue as (el:
|
|
1169
|
+
;(refValue as (el: Element) => void)(el)
|
|
1168
1170
|
} else if (typeof refValue === 'object' && 'current' in refValue) {
|
|
1169
1171
|
// Ref object: set current property
|
|
1170
|
-
;(refValue as { current:
|
|
1172
|
+
;(refValue as { current: Element | null }).current = el
|
|
1171
1173
|
}
|
|
1172
1174
|
}
|
|
1173
1175
|
|
|
@@ -1187,7 +1189,7 @@ export function bindRef(el: HTMLElement, ref: unknown): Cleanup {
|
|
|
1187
1189
|
const nullifyCleanup = () => {
|
|
1188
1190
|
const currentRef = getRef()
|
|
1189
1191
|
if (currentRef && typeof currentRef === 'object' && 'current' in currentRef) {
|
|
1190
|
-
;(currentRef as { current:
|
|
1192
|
+
;(currentRef as { current: Element | null }).current = null
|
|
1191
1193
|
}
|
|
1192
1194
|
}
|
|
1193
1195
|
registerRootCleanup(nullifyCleanup)
|
|
@@ -1202,7 +1204,7 @@ export function bindRef(el: HTMLElement, ref: unknown): Cleanup {
|
|
|
1202
1204
|
const cleanup = () => {
|
|
1203
1205
|
const refValue = getRef()
|
|
1204
1206
|
if (refValue && typeof refValue === 'object' && 'current' in refValue) {
|
|
1205
|
-
;(refValue as { current:
|
|
1207
|
+
;(refValue as { current: Element | null }).current = null
|
|
1206
1208
|
}
|
|
1207
1209
|
}
|
|
1208
1210
|
registerRootCleanup(cleanup)
|
|
@@ -1231,7 +1233,7 @@ export function bindRef(el: HTMLElement, ref: unknown): Cleanup {
|
|
|
1231
1233
|
* ```
|
|
1232
1234
|
*/
|
|
1233
1235
|
export function spread(
|
|
1234
|
-
node:
|
|
1236
|
+
node: Element,
|
|
1235
1237
|
props: Record<string, unknown> = {},
|
|
1236
1238
|
isSVG = false,
|
|
1237
1239
|
skipChildren = false,
|
|
@@ -1248,7 +1250,7 @@ export function spread(
|
|
|
1248
1250
|
// Handle ref
|
|
1249
1251
|
createRenderEffect(() => {
|
|
1250
1252
|
if (typeof props.ref === 'function') {
|
|
1251
|
-
;(props.ref as (el:
|
|
1253
|
+
;(props.ref as (el: Element) => void)(node)
|
|
1252
1254
|
}
|
|
1253
1255
|
})
|
|
1254
1256
|
|
|
@@ -1272,7 +1274,7 @@ export function spread(
|
|
|
1272
1274
|
* @param skipRef - Whether to skip ref handling
|
|
1273
1275
|
*/
|
|
1274
1276
|
export function assign(
|
|
1275
|
-
node:
|
|
1277
|
+
node: Element,
|
|
1276
1278
|
props: Record<string, unknown>,
|
|
1277
1279
|
isSVG = false,
|
|
1278
1280
|
skipChildren = false,
|
|
@@ -1307,7 +1309,7 @@ export function assign(
|
|
|
1307
1309
|
* Assign a single prop to a node.
|
|
1308
1310
|
*/
|
|
1309
1311
|
function assignProp(
|
|
1310
|
-
node:
|
|
1312
|
+
node: Element,
|
|
1311
1313
|
prop: string,
|
|
1312
1314
|
value: unknown,
|
|
1313
1315
|
prev: unknown,
|
|
@@ -1317,7 +1319,7 @@ function assignProp(
|
|
|
1317
1319
|
): unknown {
|
|
1318
1320
|
// Style handling
|
|
1319
1321
|
if (prop === 'style') {
|
|
1320
|
-
applyStyle(node, value, prev)
|
|
1322
|
+
applyStyle(node as Element & { style: CSSStyleDeclaration }, value, prev)
|
|
1321
1323
|
return value
|
|
1322
1324
|
}
|
|
1323
1325
|
|
|
@@ -1332,7 +1334,7 @@ function assignProp(
|
|
|
1332
1334
|
// Ref handling
|
|
1333
1335
|
if (prop === 'ref') {
|
|
1334
1336
|
if (!skipRef && typeof value === 'function') {
|
|
1335
|
-
;(value as (el:
|
|
1337
|
+
;(value as (el: Element) => void)(node)
|
|
1336
1338
|
}
|
|
1337
1339
|
return value
|
|
1338
1340
|
}
|
|
@@ -1472,6 +1474,7 @@ export function createConditional(
|
|
|
1472
1474
|
const endMarker = document.createComment('fict:cond:end')
|
|
1473
1475
|
const fragment = document.createDocumentFragment()
|
|
1474
1476
|
fragment.append(startMarker, endMarker)
|
|
1477
|
+
const hostRoot = getCurrentRoot()
|
|
1475
1478
|
|
|
1476
1479
|
let currentNodes: Node[] = []
|
|
1477
1480
|
let currentRoot: RootContext | null = null
|
|
@@ -1513,7 +1516,7 @@ export function createConditional(
|
|
|
1513
1516
|
return
|
|
1514
1517
|
}
|
|
1515
1518
|
|
|
1516
|
-
const root = createRootContext()
|
|
1519
|
+
const root = createRootContext(hostRoot)
|
|
1517
1520
|
const prev = pushRoot(root)
|
|
1518
1521
|
let handledError = false
|
|
1519
1522
|
try {
|
|
@@ -1583,10 +1586,11 @@ export type KeyFn<T> = (item: T, index: number) => string | number
|
|
|
1583
1586
|
|
|
1584
1587
|
/**
|
|
1585
1588
|
* Create a reactive list rendering binding with optional keying.
|
|
1589
|
+
* The render callback receives signal accessors for the item and index.
|
|
1586
1590
|
*/
|
|
1587
1591
|
export function createList<T>(
|
|
1588
1592
|
items: () => T[],
|
|
1589
|
-
renderItem: (item: T
|
|
1593
|
+
renderItem: (item: Signal<T>, index: Signal<number>) => FictNode,
|
|
1590
1594
|
createElementFn: CreateElementFn,
|
|
1591
1595
|
getKey?: KeyFn<T>,
|
|
1592
1596
|
): BindingHandle {
|
|
@@ -1594,6 +1598,7 @@ export function createList<T>(
|
|
|
1594
1598
|
const endMarker = document.createComment('fict:list:end')
|
|
1595
1599
|
const fragment = document.createDocumentFragment()
|
|
1596
1600
|
fragment.append(startMarker, endMarker)
|
|
1601
|
+
const hostRoot = getCurrentRoot()
|
|
1597
1602
|
|
|
1598
1603
|
const nodeMap = new Map<string | number, ManagedBlock<T>>()
|
|
1599
1604
|
let pendingItems: T[] | null = null
|
|
@@ -1621,21 +1626,17 @@ export function createList<T>(
|
|
|
1621
1626
|
if (!getKey && previousValue !== item) {
|
|
1622
1627
|
destroyRoot(existing.root)
|
|
1623
1628
|
removeBlockNodes(existing)
|
|
1624
|
-
block = mountBlock(item, i, renderItem, parent, endMarker, createElementFn)
|
|
1629
|
+
block = mountBlock(item, i, renderItem, parent, endMarker, createElementFn, hostRoot)
|
|
1625
1630
|
} else {
|
|
1626
1631
|
const previousIndex = existing.index()
|
|
1627
1632
|
existing.value(item)
|
|
1628
1633
|
existing.index(i)
|
|
1629
1634
|
|
|
1630
|
-
if (previousValue === item) {
|
|
1631
|
-
bumpBlockVersion(existing)
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
1635
|
const needsRerender = getKey ? true : previousValue !== item || previousIndex !== i
|
|
1635
1636
|
block = needsRerender ? rerenderBlock(existing, createElementFn) : existing
|
|
1636
1637
|
}
|
|
1637
1638
|
} else {
|
|
1638
|
-
block = mountBlock(item, i, renderItem, parent, endMarker, createElementFn)
|
|
1639
|
+
block = mountBlock(item, i, renderItem, parent, endMarker, createElementFn, hostRoot)
|
|
1639
1640
|
}
|
|
1640
1641
|
|
|
1641
1642
|
newNodeMap.set(key, block)
|
|
@@ -1699,7 +1700,11 @@ export function createList<T>(
|
|
|
1699
1700
|
* createShow(container, () => $visible())
|
|
1700
1701
|
* ```
|
|
1701
1702
|
*/
|
|
1702
|
-
export function createShow(
|
|
1703
|
+
export function createShow(
|
|
1704
|
+
el: Element & { style: CSSStyleDeclaration },
|
|
1705
|
+
condition: () => boolean,
|
|
1706
|
+
displayValue?: string,
|
|
1707
|
+
): void {
|
|
1703
1708
|
const originalDisplay = displayValue ?? el.style.display
|
|
1704
1709
|
createRenderEffect(() => {
|
|
1705
1710
|
el.style.display = condition() ? originalDisplay : 'none'
|
|
@@ -1723,7 +1728,7 @@ export function createShow(el: HTMLElement, condition: () => boolean, displayVal
|
|
|
1723
1728
|
* ```
|
|
1724
1729
|
*/
|
|
1725
1730
|
export function createPortal(
|
|
1726
|
-
container:
|
|
1731
|
+
container: ParentNode & Node,
|
|
1727
1732
|
render: () => FictNode,
|
|
1728
1733
|
createElementFn: CreateElementFn,
|
|
1729
1734
|
): BindingHandle {
|
|
@@ -1749,7 +1754,7 @@ export function createPortal(
|
|
|
1749
1754
|
}
|
|
1750
1755
|
|
|
1751
1756
|
// Create new content
|
|
1752
|
-
const root = createRootContext()
|
|
1757
|
+
const root = createRootContext(parentRoot)
|
|
1753
1758
|
const prev = pushRoot(root)
|
|
1754
1759
|
let handledError = false
|
|
1755
1760
|
try {
|
|
@@ -1820,22 +1825,18 @@ export function createPortal(
|
|
|
1820
1825
|
function mountBlock<T>(
|
|
1821
1826
|
initialValue: T,
|
|
1822
1827
|
initialIndex: number,
|
|
1823
|
-
renderItem: (item: T
|
|
1828
|
+
renderItem: (item: Signal<T>, index: Signal<number>) => FictNode,
|
|
1824
1829
|
parent: ParentNode & Node,
|
|
1825
1830
|
anchor: Node,
|
|
1826
1831
|
createElementFn: CreateElementFn,
|
|
1832
|
+
hostRoot?: RootContext | undefined,
|
|
1827
1833
|
): ManagedBlock<T> {
|
|
1828
1834
|
const start = document.createComment('fict:block:start')
|
|
1829
1835
|
const end = document.createComment('fict:block:end')
|
|
1830
|
-
const valueSig =
|
|
1836
|
+
const valueSig = createVersionedSignalAccessor<T>(initialValue)
|
|
1831
1837
|
const indexSig = createSignal<number>(initialIndex)
|
|
1832
|
-
const
|
|
1833
|
-
const
|
|
1834
|
-
versionSig()
|
|
1835
|
-
return valueSig()
|
|
1836
|
-
}) as T
|
|
1837
|
-
const renderCurrent = () => renderItem(valueProxy, indexSig())
|
|
1838
|
-
const root = createRootContext()
|
|
1838
|
+
const renderCurrent = () => renderItem(valueSig, indexSig)
|
|
1839
|
+
const root = createRootContext(hostRoot)
|
|
1839
1840
|
const prev = pushRoot(root)
|
|
1840
1841
|
const nodes: Node[] = [start]
|
|
1841
1842
|
let handledError = false
|
|
@@ -1873,10 +1874,8 @@ function mountBlock<T>(
|
|
|
1873
1874
|
root,
|
|
1874
1875
|
value: valueSig,
|
|
1875
1876
|
index: indexSig,
|
|
1876
|
-
version: versionSig,
|
|
1877
1877
|
start,
|
|
1878
1878
|
end,
|
|
1879
|
-
valueProxy,
|
|
1880
1879
|
renderCurrent,
|
|
1881
1880
|
}
|
|
1882
1881
|
}
|
|
@@ -1981,7 +1980,7 @@ function patchElement(el: Element, output: FictNode): boolean {
|
|
|
1981
1980
|
if (key === 'class' || key === 'className') {
|
|
1982
1981
|
el.setAttribute('class', value === false || value === null ? '' : String(value))
|
|
1983
1982
|
} else if (key === 'style' && typeof value === 'string') {
|
|
1984
|
-
;(el as
|
|
1983
|
+
;(el as Element & { style: CSSStyleDeclaration }).style.cssText = value
|
|
1985
1984
|
} else if (value === false || value === null || value === undefined) {
|
|
1986
1985
|
el.removeAttribute(key)
|
|
1987
1986
|
} else if (value === true) {
|
|
@@ -2138,8 +2137,4 @@ function removeBlockNodes<T>(block: ManagedBlock<T>): void {
|
|
|
2138
2137
|
}
|
|
2139
2138
|
}
|
|
2140
2139
|
|
|
2141
|
-
function bumpBlockVersion<T>(block: ManagedBlock<T>): void {
|
|
2142
|
-
block.version(block.version() + 1)
|
|
2143
|
-
}
|
|
2144
|
-
|
|
2145
2140
|
// DOM utility functions are imported from './node-ops' to avoid duplication
|