@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/src/dom.ts
CHANGED
|
@@ -25,7 +25,14 @@ import {
|
|
|
25
25
|
type AttributeSetter,
|
|
26
26
|
type BindingHandle,
|
|
27
27
|
} from './binding'
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
Properties,
|
|
30
|
+
ChildProperties,
|
|
31
|
+
Aliases,
|
|
32
|
+
getPropAlias,
|
|
33
|
+
SVGElements,
|
|
34
|
+
SVGNamespace,
|
|
35
|
+
} from './constants'
|
|
29
36
|
import { __fictPushContext, __fictPopContext } from './hooks'
|
|
30
37
|
import { Fragment } from './jsx'
|
|
31
38
|
import {
|
|
@@ -43,6 +50,11 @@ import { createPropsProxy, unwrapProps } from './props'
|
|
|
43
50
|
import { untrack } from './scheduler'
|
|
44
51
|
import type { DOMElement, FictNode, FictVNode, RefObject } from './types'
|
|
45
52
|
|
|
53
|
+
type NamespaceContext = 'svg' | 'mathml' | null
|
|
54
|
+
|
|
55
|
+
const SVG_NS = 'http://www.w3.org/2000/svg'
|
|
56
|
+
const MATHML_NS = 'http://www.w3.org/1998/Math/MathML'
|
|
57
|
+
|
|
46
58
|
// ============================================================================
|
|
47
59
|
// Main Render Function
|
|
48
60
|
// ============================================================================
|
|
@@ -104,6 +116,19 @@ export function render(view: () => FictNode, container: HTMLElement): () => void
|
|
|
104
116
|
* - Reactive values (functions returning any of the above)
|
|
105
117
|
*/
|
|
106
118
|
export function createElement(node: FictNode): DOMElement {
|
|
119
|
+
return createElementWithContext(node, null)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function resolveNamespace(tagName: string, namespace: NamespaceContext): NamespaceContext {
|
|
123
|
+
if (tagName === 'svg') return 'svg'
|
|
124
|
+
if (tagName === 'math') return 'mathml'
|
|
125
|
+
if (namespace === 'mathml') return 'mathml'
|
|
126
|
+
if (namespace === 'svg') return 'svg'
|
|
127
|
+
if (SVGElements.has(tagName)) return 'svg'
|
|
128
|
+
return null
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function createElementWithContext(node: FictNode, namespace: NamespaceContext): DOMElement {
|
|
107
132
|
// Already a DOM node - pass through
|
|
108
133
|
if (node instanceof Node) {
|
|
109
134
|
return node
|
|
@@ -118,7 +143,12 @@ export function createElement(node: FictNode): DOMElement {
|
|
|
118
143
|
if (typeof node === 'object' && node !== null && !(node instanceof Node)) {
|
|
119
144
|
// Handle BindingHandle (createList, createConditional, etc)
|
|
120
145
|
if ('marker' in node) {
|
|
121
|
-
|
|
146
|
+
const handle = node as { marker: unknown; dispose?: () => void }
|
|
147
|
+
// Register dispose cleanup if available
|
|
148
|
+
if (typeof handle.dispose === 'function') {
|
|
149
|
+
registerRootCleanup(handle.dispose)
|
|
150
|
+
}
|
|
151
|
+
return createElement(handle.marker as FictNode)
|
|
122
152
|
}
|
|
123
153
|
|
|
124
154
|
const nodeRecord = node as unknown as Record<PropertyKey, unknown>
|
|
@@ -134,7 +164,7 @@ export function createElement(node: FictNode): DOMElement {
|
|
|
134
164
|
if (Array.isArray(node)) {
|
|
135
165
|
const frag = document.createDocumentFragment()
|
|
136
166
|
for (const child of node) {
|
|
137
|
-
appendChildNode(frag, child)
|
|
167
|
+
appendChildNode(frag, child, namespace)
|
|
138
168
|
}
|
|
139
169
|
return frag
|
|
140
170
|
}
|
|
@@ -186,7 +216,7 @@ export function createElement(node: FictNode): DOMElement {
|
|
|
186
216
|
__fictPushContext()
|
|
187
217
|
const rendered = vnode.type(props)
|
|
188
218
|
__fictPopContext()
|
|
189
|
-
return
|
|
219
|
+
return createElementWithContext(rendered as FictNode, namespace)
|
|
190
220
|
} catch (err) {
|
|
191
221
|
__fictPopContext()
|
|
192
222
|
if (handleSuspend(err as any)) {
|
|
@@ -201,15 +231,26 @@ export function createElement(node: FictNode): DOMElement {
|
|
|
201
231
|
if (vnode.type === Fragment) {
|
|
202
232
|
const frag = document.createDocumentFragment()
|
|
203
233
|
const children = vnode.props?.children as FictNode | FictNode[] | undefined
|
|
204
|
-
appendChildren(frag, children)
|
|
234
|
+
appendChildren(frag, children, namespace)
|
|
205
235
|
return frag
|
|
206
236
|
}
|
|
207
237
|
|
|
208
238
|
// HTML Element
|
|
209
239
|
const tagName = typeof vnode.type === 'string' ? vnode.type : 'div'
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
240
|
+
const resolvedNamespace = resolveNamespace(tagName, namespace)
|
|
241
|
+
const el =
|
|
242
|
+
resolvedNamespace === 'svg'
|
|
243
|
+
? document.createElementNS(SVG_NS, tagName)
|
|
244
|
+
: resolvedNamespace === 'mathml'
|
|
245
|
+
? document.createElementNS(MATHML_NS, tagName)
|
|
246
|
+
: document.createElement(tagName)
|
|
247
|
+
applyProps(el, vnode.props ?? {}, resolvedNamespace === 'svg')
|
|
248
|
+
appendChildren(
|
|
249
|
+
el as unknown as ParentNode & Node,
|
|
250
|
+
vnode.props?.children as FictNode | FictNode[] | undefined,
|
|
251
|
+
tagName === 'foreignObject' ? null : resolvedNamespace,
|
|
252
|
+
)
|
|
253
|
+
return el as DOMElement
|
|
213
254
|
}
|
|
214
255
|
|
|
215
256
|
/**
|
|
@@ -231,7 +272,7 @@ export function template(
|
|
|
231
272
|
|
|
232
273
|
const create = (): Node => {
|
|
233
274
|
const t = isMathML
|
|
234
|
-
? document.createElementNS(
|
|
275
|
+
? document.createElementNS(MATHML_NS, 'template')
|
|
235
276
|
: document.createElement('template')
|
|
236
277
|
t.innerHTML = html
|
|
237
278
|
|
|
@@ -279,7 +320,11 @@ function isBindingHandle(node: unknown): node is BindingHandle {
|
|
|
279
320
|
/**
|
|
280
321
|
* Append a child node to a parent, handling all node types including reactive values.
|
|
281
322
|
*/
|
|
282
|
-
function appendChildNode(
|
|
323
|
+
function appendChildNode(
|
|
324
|
+
parent: ParentNode & Node,
|
|
325
|
+
child: FictNode,
|
|
326
|
+
namespace: NamespaceContext,
|
|
327
|
+
): void {
|
|
283
328
|
// Skip nullish values
|
|
284
329
|
if (child === null || child === undefined || child === false) {
|
|
285
330
|
return
|
|
@@ -287,7 +332,7 @@ function appendChildNode(parent: HTMLElement | DocumentFragment, child: FictNode
|
|
|
287
332
|
|
|
288
333
|
// Handle BindingHandle (recursive)
|
|
289
334
|
if (isBindingHandle(child)) {
|
|
290
|
-
appendChildNode(parent, child.marker)
|
|
335
|
+
appendChildNode(parent, child.marker, namespace)
|
|
291
336
|
// Flush pending nodes now that markers are in the DOM
|
|
292
337
|
child.flush?.()
|
|
293
338
|
return
|
|
@@ -296,14 +341,14 @@ function appendChildNode(parent: HTMLElement | DocumentFragment, child: FictNode
|
|
|
296
341
|
// Handle getter function (recursive)
|
|
297
342
|
if (typeof child === 'function' && (child as () => FictNode).length === 0) {
|
|
298
343
|
const childGetter = child as () => FictNode
|
|
299
|
-
createChildBinding(parent
|
|
344
|
+
createChildBinding(parent, childGetter, node => createElementWithContext(node, namespace))
|
|
300
345
|
return
|
|
301
346
|
}
|
|
302
347
|
|
|
303
348
|
// Static child - create element and append
|
|
304
349
|
if (Array.isArray(child)) {
|
|
305
350
|
for (const item of child) {
|
|
306
|
-
appendChildNode(parent, item)
|
|
351
|
+
appendChildNode(parent, item, namespace)
|
|
307
352
|
}
|
|
308
353
|
return
|
|
309
354
|
}
|
|
@@ -313,14 +358,14 @@ function appendChildNode(parent: HTMLElement | DocumentFragment, child: FictNode
|
|
|
313
358
|
if (typeof child !== 'object' || child === null) {
|
|
314
359
|
domNode = document.createTextNode(String(child ?? ''))
|
|
315
360
|
} else {
|
|
316
|
-
domNode =
|
|
361
|
+
domNode = createElementWithContext(child as any, namespace) as Node
|
|
317
362
|
}
|
|
318
363
|
|
|
319
364
|
// Handle DocumentFragment manually to avoid JSDOM issues
|
|
320
365
|
if (domNode.nodeType === 11) {
|
|
321
366
|
const children = Array.from(domNode.childNodes)
|
|
322
367
|
for (const node of children) {
|
|
323
|
-
appendChildNode(parent, node)
|
|
368
|
+
appendChildNode(parent, node as FictNode, namespace)
|
|
324
369
|
}
|
|
325
370
|
return
|
|
326
371
|
}
|
|
@@ -345,19 +390,20 @@ function appendChildNode(parent: HTMLElement | DocumentFragment, child: FictNode
|
|
|
345
390
|
* Append multiple children, handling arrays and nested structures.
|
|
346
391
|
*/
|
|
347
392
|
function appendChildren(
|
|
348
|
-
parent:
|
|
393
|
+
parent: ParentNode & Node,
|
|
349
394
|
children: FictNode | FictNode[] | undefined,
|
|
395
|
+
namespace: NamespaceContext,
|
|
350
396
|
): void {
|
|
351
397
|
if (children === undefined) return
|
|
352
398
|
|
|
353
399
|
if (Array.isArray(children)) {
|
|
354
400
|
for (const child of children) {
|
|
355
|
-
appendChildren(parent, child)
|
|
401
|
+
appendChildren(parent, child, namespace)
|
|
356
402
|
}
|
|
357
403
|
return
|
|
358
404
|
}
|
|
359
405
|
|
|
360
|
-
appendChildNode(parent, children)
|
|
406
|
+
appendChildNode(parent, children, namespace)
|
|
361
407
|
}
|
|
362
408
|
|
|
363
409
|
// ============================================================================
|
|
@@ -368,10 +414,10 @@ function appendChildren(
|
|
|
368
414
|
* Apply a ref to an element, supporting both callback and object refs.
|
|
369
415
|
* Both types are automatically cleaned up on unmount.
|
|
370
416
|
*/
|
|
371
|
-
function applyRef(el:
|
|
417
|
+
function applyRef(el: Element, value: unknown): void {
|
|
372
418
|
if (typeof value === 'function') {
|
|
373
419
|
// Callback ref
|
|
374
|
-
const refFn = value as (el:
|
|
420
|
+
const refFn = value as (el: Element | null) => void
|
|
375
421
|
refFn(el)
|
|
376
422
|
|
|
377
423
|
// Match React behavior: call ref(null) on unmount
|
|
@@ -382,7 +428,7 @@ function applyRef(el: HTMLElement, value: unknown): void {
|
|
|
382
428
|
}
|
|
383
429
|
} else if (value && typeof value === 'object' && 'current' in value) {
|
|
384
430
|
// Object ref
|
|
385
|
-
const refObj = value as RefObject<
|
|
431
|
+
const refObj = value as RefObject<Element>
|
|
386
432
|
refObj.current = el
|
|
387
433
|
|
|
388
434
|
// Auto-cleanup on unmount
|
|
@@ -402,7 +448,7 @@ function applyRef(el: HTMLElement, value: unknown): void {
|
|
|
402
448
|
* Apply props to an HTML element, setting up reactive bindings as needed.
|
|
403
449
|
* Uses comprehensive property constants for correct attribute/property handling.
|
|
404
450
|
*/
|
|
405
|
-
function applyProps(el:
|
|
451
|
+
function applyProps(el: Element, props: Record<string, unknown>, isSVG = false): void {
|
|
406
452
|
props = unwrapProps(props)
|
|
407
453
|
const tagName = el.tagName
|
|
408
454
|
|
|
@@ -545,10 +591,6 @@ function applyProps(el: HTMLElement, props: Record<string, unknown>, isSVG = fal
|
|
|
545
591
|
const attrName = Aliases[key] || key
|
|
546
592
|
createAttributeBinding(el, attrName, value as MaybeReactive<unknown>, setAttribute)
|
|
547
593
|
}
|
|
548
|
-
|
|
549
|
-
// Handle children
|
|
550
|
-
const children = props.children as FictNode | FictNode[] | undefined
|
|
551
|
-
appendChildren(el, children)
|
|
552
594
|
}
|
|
553
595
|
|
|
554
596
|
/**
|
|
@@ -565,7 +607,7 @@ function toPropertyName(name: string): string {
|
|
|
565
607
|
/**
|
|
566
608
|
* Set an attribute on an element, handling various value types.
|
|
567
609
|
*/
|
|
568
|
-
const setAttribute: AttributeSetter = (el:
|
|
610
|
+
const setAttribute: AttributeSetter = (el: Element, key: string, value: unknown): void => {
|
|
569
611
|
// Remove attribute for nullish/false values
|
|
570
612
|
if (value === undefined || value === null || value === false) {
|
|
571
613
|
el.removeAttribute(key)
|
|
@@ -598,7 +640,7 @@ const setAttribute: AttributeSetter = (el: HTMLElement, key: string, value: unkn
|
|
|
598
640
|
/**
|
|
599
641
|
* Set a property on an element, ensuring nullish values clear sensibly.
|
|
600
642
|
*/
|
|
601
|
-
const setProperty: AttributeSetter = (el:
|
|
643
|
+
const setProperty: AttributeSetter = (el: Element, key: string, value: unknown): void => {
|
|
602
644
|
if (value === undefined || value === null) {
|
|
603
645
|
const fallback = key === 'checked' || key === 'selected' ? false : ''
|
|
604
646
|
;(el as unknown as Record<string, unknown>)[key] = fallback
|
|
@@ -610,7 +652,7 @@ const setProperty: AttributeSetter = (el: HTMLElement, key: string, value: unkno
|
|
|
610
652
|
for (const k in value as Record<string, string>) {
|
|
611
653
|
const v = (value as Record<string, string>)[k]
|
|
612
654
|
if (v !== undefined) {
|
|
613
|
-
;(el.style as unknown as Record<string, string>)[k] = String(v)
|
|
655
|
+
;((el as HTMLElement).style as unknown as Record<string, string>)[k] = String(v)
|
|
614
656
|
}
|
|
615
657
|
}
|
|
616
658
|
return
|
|
@@ -622,14 +664,14 @@ const setProperty: AttributeSetter = (el: HTMLElement, key: string, value: unkno
|
|
|
622
664
|
/**
|
|
623
665
|
* Set innerHTML on an element (used for dangerouslySetInnerHTML)
|
|
624
666
|
*/
|
|
625
|
-
const setInnerHTML: AttributeSetter = (el:
|
|
626
|
-
el.innerHTML = value == null ? '' : String(value)
|
|
667
|
+
const setInnerHTML: AttributeSetter = (el: Element, _key: string, value: unknown): void => {
|
|
668
|
+
;(el as HTMLElement).innerHTML = value == null ? '' : String(value)
|
|
627
669
|
}
|
|
628
670
|
|
|
629
671
|
/**
|
|
630
672
|
* Set a boolean attribute on an element (empty string when true, removed when false)
|
|
631
673
|
*/
|
|
632
|
-
const setBoolAttribute: AttributeSetter = (el:
|
|
674
|
+
const setBoolAttribute: AttributeSetter = (el: Element, key: string, value: unknown): void => {
|
|
633
675
|
if (value) {
|
|
634
676
|
el.setAttribute(key, '')
|
|
635
677
|
} else {
|
|
@@ -640,7 +682,7 @@ const setBoolAttribute: AttributeSetter = (el: HTMLElement, key: string, value:
|
|
|
640
682
|
/**
|
|
641
683
|
* Set an attribute with a namespace (for SVG xlink:href, etc.)
|
|
642
684
|
*/
|
|
643
|
-
function setAttributeNS(el:
|
|
685
|
+
function setAttributeNS(el: Element, namespace: string, name: string, value: unknown): void {
|
|
644
686
|
if (value == null) {
|
|
645
687
|
el.removeAttributeNS(namespace, name)
|
|
646
688
|
} else {
|
package/src/error-boundary.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
createRootContext,
|
|
5
5
|
destroyRoot,
|
|
6
6
|
flushOnMount,
|
|
7
|
+
getCurrentRoot,
|
|
7
8
|
pushRoot,
|
|
8
9
|
popRoot,
|
|
9
10
|
registerErrorHandler,
|
|
@@ -24,6 +25,7 @@ export function ErrorBoundary(props: ErrorBoundaryProps): FictNode {
|
|
|
24
25
|
fragment.appendChild(marker)
|
|
25
26
|
|
|
26
27
|
const currentView = createSignal<FictNode | null>(props.children ?? null)
|
|
28
|
+
const hostRoot = getCurrentRoot()
|
|
27
29
|
|
|
28
30
|
let cleanup: (() => void) | undefined
|
|
29
31
|
let activeNodes: Node[] = []
|
|
@@ -52,7 +54,7 @@ export function ErrorBoundary(props: ErrorBoundaryProps): FictNode {
|
|
|
52
54
|
return
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
const root = createRootContext()
|
|
57
|
+
const root = createRootContext(hostRoot)
|
|
56
58
|
const prev = pushRoot(root)
|
|
57
59
|
let nodes: Node[] = []
|
|
58
60
|
try {
|
package/src/list-helpers.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
createRootContext,
|
|
12
12
|
destroyRoot,
|
|
13
13
|
flushOnMount,
|
|
14
|
+
getCurrentRoot,
|
|
14
15
|
popRoot,
|
|
15
16
|
pushRoot,
|
|
16
17
|
type RootContext,
|
|
@@ -200,7 +201,7 @@ function removeBlockRange(block: MarkerBlock): void {
|
|
|
200
201
|
}
|
|
201
202
|
}
|
|
202
203
|
|
|
203
|
-
function createVersionedSignalAccessor<T>(initialValue: T): Signal<T> {
|
|
204
|
+
export function createVersionedSignalAccessor<T>(initialValue: T): Signal<T> {
|
|
204
205
|
let current = initialValue
|
|
205
206
|
let version = 0
|
|
206
207
|
const track = createSignal(version)
|
|
@@ -243,6 +244,16 @@ export function createKeyedListContainer<T = unknown>(): KeyedListContainer<T> {
|
|
|
243
244
|
container.nextBlocks.clear()
|
|
244
245
|
|
|
245
246
|
// Remove nodes (including markers)
|
|
247
|
+
// Check if markers are still in DOM before using Range
|
|
248
|
+
if (!startMarker.parentNode || !endMarker.parentNode) {
|
|
249
|
+
// Markers already removed, nothing to do
|
|
250
|
+
container.currentNodes = []
|
|
251
|
+
container.nextNodes = []
|
|
252
|
+
container.orderedBlocks.length = 0
|
|
253
|
+
container.nextOrderedBlocks.length = 0
|
|
254
|
+
container.orderedIndexByKey.clear()
|
|
255
|
+
return
|
|
256
|
+
}
|
|
246
257
|
const range = document.createRange()
|
|
247
258
|
range.setStartBefore(startMarker)
|
|
248
259
|
range.setEndAfter(endMarker)
|
|
@@ -292,6 +303,7 @@ export function createKeyedBlock<T>(
|
|
|
292
303
|
index: number,
|
|
293
304
|
render: (item: Signal<T>, index: Signal<number>, key: string | number) => Node[],
|
|
294
305
|
needsIndex = true,
|
|
306
|
+
hostRoot?: RootContext,
|
|
295
307
|
): KeyedBlock<T> {
|
|
296
308
|
// Use versioned signal for all item types; avoid diffing proxy overhead for objects
|
|
297
309
|
const itemSig = createVersionedSignalAccessor(item)
|
|
@@ -303,7 +315,7 @@ export function createKeyedBlock<T>(
|
|
|
303
315
|
index = next as number
|
|
304
316
|
return index
|
|
305
317
|
}) as Signal<number>)
|
|
306
|
-
const root = createRootContext()
|
|
318
|
+
const root = createRootContext(hostRoot)
|
|
307
319
|
const prevRoot = pushRoot(root)
|
|
308
320
|
|
|
309
321
|
// Isolate child effects from the outer effect (e.g., performDiff) by clearing activeSub.
|
|
@@ -399,6 +411,7 @@ function createFineGrainedKeyedList<T>(
|
|
|
399
411
|
needsIndex: boolean,
|
|
400
412
|
): KeyedListBinding {
|
|
401
413
|
const container = createKeyedListContainer<T>()
|
|
414
|
+
const hostRoot = getCurrentRoot()
|
|
402
415
|
const fragment = document.createDocumentFragment()
|
|
403
416
|
fragment.append(container.startMarker, container.endMarker)
|
|
404
417
|
let pendingItems: T[] | null = null
|
|
@@ -491,7 +504,7 @@ function createFineGrainedKeyedList<T>(
|
|
|
491
504
|
}
|
|
492
505
|
|
|
493
506
|
// Create new block
|
|
494
|
-
block = createKeyedBlock(key, item, index, renderItem, needsIndex)
|
|
507
|
+
block = createKeyedBlock(key, item, index, renderItem, needsIndex, hostRoot)
|
|
495
508
|
}
|
|
496
509
|
|
|
497
510
|
const resolvedBlock = block!
|
package/src/ref.ts
CHANGED
package/src/store.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { signal, batch, type SignalAccessor } from './signal'
|
|
|
2
2
|
|
|
3
3
|
const PROXY = Symbol('fict:store-proxy')
|
|
4
4
|
const TARGET = Symbol('fict:store-target')
|
|
5
|
+
const ITERATE_KEY = Symbol('fict:iterate')
|
|
5
6
|
|
|
6
7
|
// ============================================================================
|
|
7
8
|
// Store (Deep Proxy)
|
|
@@ -57,22 +58,43 @@ function wrap<T>(value: T): T {
|
|
|
57
58
|
// Recursively wrap objects
|
|
58
59
|
return wrap(value)
|
|
59
60
|
},
|
|
61
|
+
has(target, prop) {
|
|
62
|
+
const result = Reflect.has(target, prop)
|
|
63
|
+
track(target, prop)
|
|
64
|
+
return result
|
|
65
|
+
},
|
|
66
|
+
ownKeys(target) {
|
|
67
|
+
track(target, ITERATE_KEY)
|
|
68
|
+
return Reflect.ownKeys(target)
|
|
69
|
+
},
|
|
70
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
71
|
+
track(target, prop)
|
|
72
|
+
return Reflect.getOwnPropertyDescriptor(target, prop)
|
|
73
|
+
},
|
|
60
74
|
set(target, prop, value, receiver) {
|
|
61
75
|
if (prop === PROXY || prop === TARGET) return false
|
|
62
76
|
|
|
77
|
+
const hadKey = Object.prototype.hasOwnProperty.call(target, prop)
|
|
63
78
|
const oldValue = Reflect.get(target, prop, receiver)
|
|
64
79
|
if (oldValue === value) return true
|
|
65
80
|
|
|
66
81
|
const result = Reflect.set(target, prop, value, receiver)
|
|
67
82
|
if (result) {
|
|
68
83
|
trigger(target, prop)
|
|
84
|
+
if (!hadKey) {
|
|
85
|
+
trigger(target, ITERATE_KEY)
|
|
86
|
+
}
|
|
69
87
|
}
|
|
70
88
|
return result
|
|
71
89
|
},
|
|
72
90
|
deleteProperty(target, prop) {
|
|
91
|
+
const hadKey = Object.prototype.hasOwnProperty.call(target, prop)
|
|
73
92
|
const result = Reflect.deleteProperty(target, prop)
|
|
74
93
|
if (result) {
|
|
75
94
|
trigger(target, prop)
|
|
95
|
+
if (hadKey) {
|
|
96
|
+
trigger(target, ITERATE_KEY)
|
|
97
|
+
}
|
|
76
98
|
}
|
|
77
99
|
return result
|
|
78
100
|
},
|
|
@@ -99,7 +121,9 @@ function track(target: object, prop: string | symbol) {
|
|
|
99
121
|
|
|
100
122
|
let s = signals.get(prop)
|
|
101
123
|
if (!s) {
|
|
102
|
-
|
|
124
|
+
const initial =
|
|
125
|
+
prop === ITERATE_KEY ? (Reflect.ownKeys(target).length as number) : getLastValue(target, prop)
|
|
126
|
+
s = signal(initial)
|
|
103
127
|
signals.set(prop, s)
|
|
104
128
|
}
|
|
105
129
|
s() // subscribe
|
|
@@ -110,7 +134,11 @@ function trigger(target: object, prop: string | symbol) {
|
|
|
110
134
|
if (signals) {
|
|
111
135
|
const s = signals.get(prop)
|
|
112
136
|
if (s) {
|
|
113
|
-
|
|
137
|
+
if (prop === ITERATE_KEY) {
|
|
138
|
+
s(Reflect.ownKeys(target).length)
|
|
139
|
+
} else {
|
|
140
|
+
s(getLastValue(target, prop)) // notify with new value
|
|
141
|
+
}
|
|
114
142
|
}
|
|
115
143
|
}
|
|
116
144
|
}
|
package/src/types.ts
CHANGED
|
@@ -128,15 +128,15 @@ export interface DOMEventHandlers {
|
|
|
128
128
|
// ============================================================================
|
|
129
129
|
|
|
130
130
|
/** Ref callback type */
|
|
131
|
-
export type RefCallback<T extends
|
|
131
|
+
export type RefCallback<T extends Element = HTMLElement> = (element: T) => void
|
|
132
132
|
|
|
133
133
|
/** Ref object type (for future use with createRef) */
|
|
134
|
-
export interface RefObject<T extends
|
|
134
|
+
export interface RefObject<T extends Element = HTMLElement> {
|
|
135
135
|
current: T | null
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
/** Ref type that can be either callback or object */
|
|
139
|
-
export type Ref<T extends
|
|
139
|
+
export type Ref<T extends Element = HTMLElement> = RefCallback<T> | RefObject<T>
|
|
140
140
|
|
|
141
141
|
// ============================================================================
|
|
142
142
|
// Style Types
|