@fictjs/runtime 0.0.12 → 0.0.14
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 +79 -0
- package/dist/advanced.cjs.map +1 -0
- package/dist/advanced.d.cts +50 -0
- package/dist/advanced.d.ts +50 -0
- package/dist/advanced.js +79 -0
- package/dist/advanced.js.map +1 -0
- package/dist/chunk-624QY53A.cjs +45 -0
- package/dist/chunk-624QY53A.cjs.map +1 -0
- package/dist/chunk-F3AIYQB7.js +45 -0
- package/dist/chunk-F3AIYQB7.js.map +1 -0
- package/dist/chunk-GJTYOFMO.cjs +109 -0
- package/dist/chunk-GJTYOFMO.cjs.map +1 -0
- package/dist/chunk-IUZXKAAY.js +109 -0
- package/dist/chunk-IUZXKAAY.js.map +1 -0
- package/dist/{slim.cjs → chunk-PMF6MWEV.cjs} +2557 -3110
- package/dist/chunk-PMF6MWEV.cjs.map +1 -0
- package/dist/{slim.js → chunk-RY4WDS6R.js} +2596 -3097
- package/dist/chunk-RY4WDS6R.js.map +1 -0
- package/dist/context-B7UYnfzM.d.ts +153 -0
- package/dist/context-UXySaqI_.d.cts +153 -0
- package/dist/effect-Auji1rz9.d.cts +350 -0
- package/dist/effect-Auji1rz9.d.ts +350 -0
- package/dist/index.cjs +108 -4441
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -1492
- package/dist/index.d.ts +5 -1492
- package/dist/index.dev.js +1020 -2788
- package/dist/index.dev.js.map +1 -1
- package/dist/index.js +63 -4301
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +901 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.cts +158 -0
- package/dist/internal.d.ts +158 -0
- package/dist/internal.js +901 -0
- package/dist/internal.js.map +1 -0
- package/dist/{jsx-dev-runtime.d.ts → props-CrOMYbLv.d.cts} +107 -18
- package/dist/{jsx-dev-runtime.d.cts → props-ES0Ag_Wd.d.ts} +107 -18
- package/dist/scope-DKYzWfTn.d.cts +55 -0
- package/dist/scope-S6eAzBJZ.d.ts +55 -0
- package/package.json +10 -5
- package/src/advanced.ts +101 -0
- package/src/binding.ts +25 -422
- package/src/constants.ts +345 -344
- package/src/context.ts +300 -0
- package/src/cycle-guard.ts +124 -97
- package/src/delegated-events.ts +24 -0
- package/src/dom.ts +19 -25
- package/src/effect.ts +4 -0
- package/src/hooks.ts +9 -1
- package/src/index.ts +41 -130
- package/src/internal.ts +130 -0
- package/src/lifecycle.ts +13 -2
- package/src/list-helpers.ts +6 -65
- package/src/props.ts +48 -46
- package/src/signal.ts +59 -39
- package/src/store.ts +47 -7
- package/src/versioned-signal.ts +3 -3
- package/dist/jsx-runtime.d.cts +0 -671
- package/dist/jsx-runtime.d.ts +0 -671
- package/dist/slim.cjs.map +0 -1
- package/dist/slim.d.cts +0 -504
- package/dist/slim.d.ts +0 -504
- package/dist/slim.js.map +0 -1
- package/src/slim.ts +0 -69
package/src/hooks.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { createEffect } from './effect'
|
|
|
2
2
|
import { createMemo } from './memo'
|
|
3
3
|
import { createSignal, type SignalAccessor, type ComputedAccessor } from './signal'
|
|
4
4
|
|
|
5
|
+
const isDev =
|
|
6
|
+
typeof __DEV__ !== 'undefined'
|
|
7
|
+
? __DEV__
|
|
8
|
+
: typeof process === 'undefined' || process.env?.NODE_ENV !== 'production'
|
|
9
|
+
|
|
5
10
|
interface HookContext {
|
|
6
11
|
slots: unknown[]
|
|
7
12
|
cursor: number
|
|
@@ -12,7 +17,10 @@ const ctxStack: HookContext[] = []
|
|
|
12
17
|
|
|
13
18
|
function assertRenderContext(ctx: HookContext, hookName: string): void {
|
|
14
19
|
if (!ctx.rendering) {
|
|
15
|
-
|
|
20
|
+
const message = isDev
|
|
21
|
+
? `${hookName} can only be used during render execution`
|
|
22
|
+
: 'FICT:E_HOOK_RENDER'
|
|
23
|
+
throw new Error(message)
|
|
16
24
|
}
|
|
17
25
|
}
|
|
18
26
|
|
package/src/index.ts
CHANGED
|
@@ -1,38 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Fict Runtime - Public API
|
|
3
|
+
*
|
|
4
|
+
* This module exports the public API for the Fict reactive UI framework.
|
|
5
|
+
*
|
|
6
|
+
* ## Recommended Import Pattern (v1.0+)
|
|
7
|
+
*
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Core public API (most users need only this)
|
|
10
|
+
* import { createEffect, render } from '@fictjs/runtime'
|
|
11
|
+
*
|
|
12
|
+
* // Advanced APIs (power users, escape hatches)
|
|
13
|
+
* import { createSignal, createContext, createScope } from '@fictjs/runtime/advanced'
|
|
14
|
+
*
|
|
15
|
+
* // Internal APIs (compiler/library authors only)
|
|
16
|
+
* import { bindText, __fictUseSignal } from '@fictjs/runtime/internal'
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
* @packageDocumentation
|
|
21
|
+
*/
|
|
22
|
+
|
|
1
23
|
// ============================================================================
|
|
2
24
|
// Core Reactive Primitives
|
|
3
25
|
// ============================================================================
|
|
4
26
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export {
|
|
12
|
-
|
|
13
|
-
__fictPushContext,
|
|
14
|
-
__fictPopContext,
|
|
15
|
-
__fictUseSignal,
|
|
16
|
-
__fictUseMemo,
|
|
17
|
-
__fictUseEffect,
|
|
18
|
-
__fictRender,
|
|
19
|
-
__fictResetContext,
|
|
20
|
-
} from './hooks'
|
|
21
|
-
export {
|
|
22
|
-
createVersionedSignal,
|
|
23
|
-
type VersionedSignal,
|
|
24
|
-
type VersionedSignalOptions,
|
|
25
|
-
} from './versioned-signal'
|
|
26
|
-
|
|
27
|
-
// Props helpers
|
|
28
|
-
export {
|
|
29
|
-
__fictProp,
|
|
30
|
-
__fictProp as prop,
|
|
31
|
-
__fictPropsRest,
|
|
32
|
-
mergeProps,
|
|
33
|
-
useProp,
|
|
34
|
-
createPropsProxy,
|
|
35
|
-
} from './props'
|
|
27
|
+
/**
|
|
28
|
+
* Note: createSignal is exported from ./advanced as an escape hatch.
|
|
29
|
+
* For most use cases, prefer:
|
|
30
|
+
* - $state: For component-local state (compiler-transformed, safe scoping)
|
|
31
|
+
* - $store: For cross-component shared state with deep reactivity
|
|
32
|
+
*/
|
|
33
|
+
export { createMemo, type Memo } from './memo'
|
|
34
|
+
export { createEffect, type Effect } from './effect'
|
|
36
35
|
|
|
37
36
|
// ============================================================================
|
|
38
37
|
// Lifecycle
|
|
@@ -40,7 +39,10 @@ export {
|
|
|
40
39
|
|
|
41
40
|
export { onMount, onDestroy, onCleanup, createRoot } from './lifecycle'
|
|
42
41
|
|
|
43
|
-
//
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// Ref
|
|
44
|
+
// ============================================================================
|
|
45
|
+
|
|
44
46
|
export { createRef } from './ref'
|
|
45
47
|
|
|
46
48
|
// ============================================================================
|
|
@@ -48,9 +50,6 @@ export { createRef } from './ref'
|
|
|
48
50
|
// ============================================================================
|
|
49
51
|
|
|
50
52
|
export { batch, untrack } from './scheduler'
|
|
51
|
-
export { setCycleProtectionOptions } from './cycle-guard'
|
|
52
|
-
|
|
53
|
-
// Transition API for priority scheduling
|
|
54
53
|
export { startTransition, useTransition, useDeferredValue } from './scheduler'
|
|
55
54
|
|
|
56
55
|
// ============================================================================
|
|
@@ -64,79 +63,22 @@ export { Fragment } from './jsx'
|
|
|
64
63
|
// DOM Rendering
|
|
65
64
|
// ============================================================================
|
|
66
65
|
|
|
67
|
-
export { createElement, render
|
|
66
|
+
export { createElement, render } from './dom'
|
|
67
|
+
export { createPortal } from './binding'
|
|
68
68
|
export { ErrorBoundary } from './error-boundary'
|
|
69
69
|
export { Suspense, createSuspenseToken } from './suspense'
|
|
70
|
+
export { createContext, useContext, hasContext, type Context, type ProviderProps } from './context'
|
|
70
71
|
|
|
71
72
|
// ============================================================================
|
|
72
|
-
//
|
|
73
|
-
// ============================================================================
|
|
74
|
-
|
|
75
|
-
export {
|
|
76
|
-
// Core binding utilities
|
|
77
|
-
createTextBinding,
|
|
78
|
-
createChildBinding,
|
|
79
|
-
createAttributeBinding,
|
|
80
|
-
createStyleBinding,
|
|
81
|
-
createClassBinding,
|
|
82
|
-
// Low-level binding helpers (for direct DOM manipulation)
|
|
83
|
-
bindText,
|
|
84
|
-
bindAttribute,
|
|
85
|
-
bindStyle,
|
|
86
|
-
bindClass,
|
|
87
|
-
bindEvent,
|
|
88
|
-
callEventHandler,
|
|
89
|
-
bindProperty,
|
|
90
|
-
bindRef,
|
|
91
|
-
insert,
|
|
92
|
-
// Event delegation
|
|
93
|
-
delegateEvents,
|
|
94
|
-
clearDelegatedEvents,
|
|
95
|
-
addEventListener,
|
|
96
|
-
// Spread props
|
|
97
|
-
spread,
|
|
98
|
-
assign,
|
|
99
|
-
classList,
|
|
100
|
-
// Reactive detection
|
|
101
|
-
isReactive,
|
|
102
|
-
// Advanced bindings
|
|
103
|
-
createConditional,
|
|
104
|
-
createList,
|
|
105
|
-
createPortal,
|
|
106
|
-
createShow,
|
|
107
|
-
// Utility functions
|
|
108
|
-
unwrap,
|
|
109
|
-
unwrapPrimitive,
|
|
110
|
-
} from './binding'
|
|
111
|
-
|
|
112
|
-
// Constants for DOM handling
|
|
113
|
-
export {
|
|
114
|
-
Properties,
|
|
115
|
-
ChildProperties,
|
|
116
|
-
Aliases,
|
|
117
|
-
getPropAlias,
|
|
118
|
-
BooleanAttributes,
|
|
119
|
-
SVGElements,
|
|
120
|
-
SVGNamespace,
|
|
121
|
-
DelegatedEvents,
|
|
122
|
-
UnitlessStyles,
|
|
123
|
-
} from './constants'
|
|
124
|
-
|
|
125
|
-
// Reconcile algorithm
|
|
126
|
-
export { default as reconcileArrays } from './reconcile'
|
|
73
|
+
// Props Utilities (Public)
|
|
74
|
+
// ============================================================================
|
|
75
|
+
|
|
76
|
+
export { prop, mergeProps } from './props'
|
|
127
77
|
|
|
128
78
|
// ============================================================================
|
|
129
79
|
// Types
|
|
130
80
|
// ============================================================================
|
|
131
81
|
|
|
132
|
-
export type {
|
|
133
|
-
MaybeReactive,
|
|
134
|
-
BindingHandle,
|
|
135
|
-
KeyFn,
|
|
136
|
-
CreateElementFn,
|
|
137
|
-
AttributeSetter,
|
|
138
|
-
} from './binding'
|
|
139
|
-
|
|
140
82
|
export type {
|
|
141
83
|
FictNode,
|
|
142
84
|
FictVNode,
|
|
@@ -154,34 +96,3 @@ export type {
|
|
|
154
96
|
ErrorInfo,
|
|
155
97
|
SuspenseToken,
|
|
156
98
|
} from './types'
|
|
157
|
-
|
|
158
|
-
// Devtools hook (optional)
|
|
159
|
-
export { getDevtoolsHook, type FictDevtoolsHook } from './devtools'
|
|
160
|
-
|
|
161
|
-
// ============================================================================
|
|
162
|
-
// List Helpers (for compiler-generated code)
|
|
163
|
-
// ============================================================================
|
|
164
|
-
|
|
165
|
-
export {
|
|
166
|
-
// DOM manipulation primitives
|
|
167
|
-
moveNodesBefore,
|
|
168
|
-
removeNodes,
|
|
169
|
-
insertNodesBefore,
|
|
170
|
-
moveMarkerBlock,
|
|
171
|
-
destroyMarkerBlock,
|
|
172
|
-
// Keyed list container
|
|
173
|
-
createKeyedListContainer,
|
|
174
|
-
// Block creation
|
|
175
|
-
createKeyedBlock,
|
|
176
|
-
// High-level list binding (for compiler-generated code)
|
|
177
|
-
createKeyedList,
|
|
178
|
-
// Utilities
|
|
179
|
-
toNodeArray,
|
|
180
|
-
getFirstNodeAfter,
|
|
181
|
-
isNodeBetweenMarkers,
|
|
182
|
-
// Types
|
|
183
|
-
type KeyedBlock,
|
|
184
|
-
type KeyedListContainer,
|
|
185
|
-
type KeyedListBinding,
|
|
186
|
-
type MarkerBlock,
|
|
187
|
-
} from './list-helpers'
|
package/src/internal.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Internal APIs for Fict Compiler
|
|
3
|
+
*
|
|
4
|
+
* This module exports internal APIs used by compiler-generated code.
|
|
5
|
+
* These APIs are NOT part of the public API and should NOT be used directly.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Core Primitives (also exported from main, but needed by compiler)
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
export { createSignal, createSelector } from './signal'
|
|
16
|
+
export { createStore, type Store } from './store'
|
|
17
|
+
export { createMemo } from './memo'
|
|
18
|
+
export { createEffect } from './effect'
|
|
19
|
+
export { Fragment } from './jsx'
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Hook Context Management (Compiler-generated code)
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
__fictUseContext,
|
|
27
|
+
__fictPushContext,
|
|
28
|
+
__fictPopContext,
|
|
29
|
+
__fictUseSignal,
|
|
30
|
+
__fictUseMemo,
|
|
31
|
+
__fictUseEffect,
|
|
32
|
+
__fictRender,
|
|
33
|
+
__fictResetContext,
|
|
34
|
+
} from './hooks'
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Props Helpers (Compiler-generated code)
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
export { __fictProp, __fictPropsRest, createPropsProxy, mergeProps, prop } from './props'
|
|
41
|
+
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// DOM Bindings (Compiler-generated code)
|
|
44
|
+
// ============================================================================
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
bindText,
|
|
48
|
+
bindAttribute,
|
|
49
|
+
bindStyle,
|
|
50
|
+
bindClass,
|
|
51
|
+
bindEvent,
|
|
52
|
+
callEventHandler,
|
|
53
|
+
bindProperty,
|
|
54
|
+
bindRef,
|
|
55
|
+
insert,
|
|
56
|
+
createConditional,
|
|
57
|
+
createPortal,
|
|
58
|
+
spread,
|
|
59
|
+
assign,
|
|
60
|
+
classList,
|
|
61
|
+
isReactive,
|
|
62
|
+
unwrap,
|
|
63
|
+
} from './binding'
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Event Delegation (Compiler-generated code)
|
|
67
|
+
// ============================================================================
|
|
68
|
+
|
|
69
|
+
export { delegateEvents, clearDelegatedEvents, addEventListener } from './binding'
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// List Helpers (Compiler-generated code)
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
moveNodesBefore,
|
|
77
|
+
removeNodes,
|
|
78
|
+
insertNodesBefore,
|
|
79
|
+
createKeyedList,
|
|
80
|
+
toNodeArray,
|
|
81
|
+
isNodeBetweenMarkers,
|
|
82
|
+
type KeyedListBinding,
|
|
83
|
+
} from './list-helpers'
|
|
84
|
+
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// DOM Creation (Compiler-generated code)
|
|
87
|
+
// ============================================================================
|
|
88
|
+
|
|
89
|
+
export { createElement, template } from './dom'
|
|
90
|
+
export { createRenderEffect } from './effect'
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// Lifecycle (Compiler-generated code)
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
96
|
+
export { onDestroy } from './lifecycle'
|
|
97
|
+
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// Scope (Compiler-generated code)
|
|
100
|
+
// ============================================================================
|
|
101
|
+
|
|
102
|
+
export { runInScope } from './scope'
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// Constants (Compiler/Runtime shared)
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
Properties,
|
|
110
|
+
ChildProperties,
|
|
111
|
+
Aliases,
|
|
112
|
+
getPropAlias,
|
|
113
|
+
BooleanAttributes,
|
|
114
|
+
SVGElements,
|
|
115
|
+
SVGNamespace,
|
|
116
|
+
DelegatedEvents,
|
|
117
|
+
UnitlessStyles,
|
|
118
|
+
} from './constants'
|
|
119
|
+
|
|
120
|
+
// ============================================================================
|
|
121
|
+
// Reconciliation (Internal)
|
|
122
|
+
// ============================================================================
|
|
123
|
+
|
|
124
|
+
export { default as reconcileArrays } from './reconcile'
|
|
125
|
+
|
|
126
|
+
// ============================================================================
|
|
127
|
+
// Types (Internal)
|
|
128
|
+
// ============================================================================
|
|
129
|
+
|
|
130
|
+
export type { MaybeReactive, BindingHandle, CreateElementFn, AttributeSetter } from './binding'
|
package/src/lifecycle.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { enterRootGuard, exitRootGuard } from './cycle-guard'
|
|
2
2
|
import type { Cleanup, ErrorInfo, SuspenseToken } from './types'
|
|
3
3
|
|
|
4
|
+
const isDev =
|
|
5
|
+
typeof __DEV__ !== 'undefined'
|
|
6
|
+
? __DEV__
|
|
7
|
+
: typeof process === 'undefined' || process.env?.NODE_ENV !== 'production'
|
|
8
|
+
|
|
4
9
|
type LifecycleFn = () => void | Cleanup
|
|
5
10
|
|
|
6
11
|
export interface RootContext {
|
|
@@ -169,7 +174,10 @@ function runLifecycle(fn: LifecycleFn): void {
|
|
|
169
174
|
|
|
170
175
|
export function registerErrorHandler(fn: ErrorHandler): void {
|
|
171
176
|
if (!currentRoot) {
|
|
172
|
-
|
|
177
|
+
const message = isDev
|
|
178
|
+
? 'registerErrorHandler must be called within a root'
|
|
179
|
+
: 'FICT:E_ROOT_HANDLER'
|
|
180
|
+
throw new Error(message)
|
|
173
181
|
}
|
|
174
182
|
if (!currentRoot.errorHandlers) {
|
|
175
183
|
currentRoot.errorHandlers = []
|
|
@@ -185,7 +193,10 @@ export function registerErrorHandler(fn: ErrorHandler): void {
|
|
|
185
193
|
|
|
186
194
|
export function registerSuspenseHandler(fn: SuspenseHandler): void {
|
|
187
195
|
if (!currentRoot) {
|
|
188
|
-
|
|
196
|
+
const message = isDev
|
|
197
|
+
? 'registerSuspenseHandler must be called within a root'
|
|
198
|
+
: 'FICT:E_ROOT_SUSPENSE'
|
|
199
|
+
throw new Error(message)
|
|
189
200
|
}
|
|
190
201
|
if (!currentRoot.suspenseHandlers) {
|
|
191
202
|
currentRoot.suspenseHandlers = []
|
package/src/list-helpers.ts
CHANGED
|
@@ -37,7 +37,7 @@ const isDev =
|
|
|
37
37
|
/**
|
|
38
38
|
* A keyed block represents a single item in a list with its associated DOM nodes and state
|
|
39
39
|
*/
|
|
40
|
-
|
|
40
|
+
interface KeyedBlock<T = unknown> {
|
|
41
41
|
/** Unique key for this block */
|
|
42
42
|
key: string | number
|
|
43
43
|
/** DOM nodes belonging to this block */
|
|
@@ -57,7 +57,7 @@ export interface KeyedBlock<T = unknown> {
|
|
|
57
57
|
/**
|
|
58
58
|
* Container for managing keyed list blocks
|
|
59
59
|
*/
|
|
60
|
-
|
|
60
|
+
interface KeyedListContainer<T = unknown> {
|
|
61
61
|
/** Start marker comment node */
|
|
62
62
|
startMarker: Comment
|
|
63
63
|
/** End marker comment node */
|
|
@@ -102,15 +102,6 @@ type FineGrainedRenderItem<T> = (
|
|
|
102
102
|
key: string | number,
|
|
103
103
|
) => Node[]
|
|
104
104
|
|
|
105
|
-
/**
|
|
106
|
-
* A block identified by start/end comment markers.
|
|
107
|
-
*/
|
|
108
|
-
export interface MarkerBlock {
|
|
109
|
-
start: Comment
|
|
110
|
-
end: Comment
|
|
111
|
-
root?: RootContext
|
|
112
|
-
}
|
|
113
|
-
|
|
114
105
|
// ============================================================================
|
|
115
106
|
// DOM Manipulation Primitives
|
|
116
107
|
// ============================================================================
|
|
@@ -129,7 +120,8 @@ export function moveNodesBefore(parent: Node, nodes: Node[], anchor: Node | null
|
|
|
129
120
|
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
130
121
|
const node = nodes[i]!
|
|
131
122
|
if (!node || !(node instanceof Node)) {
|
|
132
|
-
|
|
123
|
+
const message = isDev ? 'Invalid node in moveNodesBefore' : 'FICT:E_NODE'
|
|
124
|
+
throw new Error(message)
|
|
133
125
|
}
|
|
134
126
|
// Only move if not already in correct position
|
|
135
127
|
if (node.nextSibling !== anchor) {
|
|
@@ -162,50 +154,6 @@ export function moveNodesBefore(parent: Node, nodes: Node[], anchor: Node | null
|
|
|
162
154
|
*
|
|
163
155
|
* @param nodes - Array of nodes to remove
|
|
164
156
|
*/
|
|
165
|
-
/**
|
|
166
|
-
* Move an entire marker-delimited block (including markers) before the anchor.
|
|
167
|
-
*/
|
|
168
|
-
export function moveMarkerBlock(parent: Node, block: MarkerBlock, anchor: Node | null): void {
|
|
169
|
-
const nodes = collectBlockNodes(block)
|
|
170
|
-
if (nodes.length === 0) return
|
|
171
|
-
moveNodesBefore(parent, nodes, anchor)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Destroy a marker-delimited block, removing nodes and destroying the associated root.
|
|
176
|
-
*/
|
|
177
|
-
export function destroyMarkerBlock(block: MarkerBlock): void {
|
|
178
|
-
if (block.root) {
|
|
179
|
-
destroyRoot(block.root)
|
|
180
|
-
}
|
|
181
|
-
removeBlockRange(block)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function collectBlockNodes(block: MarkerBlock): Node[] {
|
|
185
|
-
const nodes: Node[] = []
|
|
186
|
-
let cursor: Node | null = block.start
|
|
187
|
-
while (cursor) {
|
|
188
|
-
nodes.push(cursor)
|
|
189
|
-
if (cursor === block.end) {
|
|
190
|
-
break
|
|
191
|
-
}
|
|
192
|
-
cursor = cursor.nextSibling
|
|
193
|
-
}
|
|
194
|
-
return nodes
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function removeBlockRange(block: MarkerBlock): void {
|
|
198
|
-
let cursor: Node | null = block.start
|
|
199
|
-
while (cursor) {
|
|
200
|
-
const next: Node | null = cursor.nextSibling
|
|
201
|
-
cursor.parentNode?.removeChild(cursor)
|
|
202
|
-
if (cursor === block.end) {
|
|
203
|
-
break
|
|
204
|
-
}
|
|
205
|
-
cursor = next
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
157
|
// Number.MAX_SAFE_INTEGER is 2^53 - 1, but we reset earlier to avoid any precision issues
|
|
210
158
|
const MAX_SAFE_VERSION = 0x1fffffffffffff // 2^53 - 1
|
|
211
159
|
|
|
@@ -238,7 +186,7 @@ export function createVersionedSignalAccessor<T>(initialValue: T): Signal<T> {
|
|
|
238
186
|
*
|
|
239
187
|
* @returns Container object with markers, blocks map, and dispose function
|
|
240
188
|
*/
|
|
241
|
-
|
|
189
|
+
function createKeyedListContainer<T = unknown>(): KeyedListContainer<T> {
|
|
242
190
|
const startMarker = document.createComment('fict:list:start')
|
|
243
191
|
const endMarker = document.createComment('fict:list:end')
|
|
244
192
|
|
|
@@ -306,7 +254,7 @@ export function createKeyedListContainer<T = unknown>(): KeyedListContainer<T> {
|
|
|
306
254
|
* @param render - Function that creates the DOM nodes and sets up bindings
|
|
307
255
|
* @returns New KeyedBlock
|
|
308
256
|
*/
|
|
309
|
-
|
|
257
|
+
function createKeyedBlock<T>(
|
|
310
258
|
key: string | number,
|
|
311
259
|
item: T,
|
|
312
260
|
index: number,
|
|
@@ -377,13 +325,6 @@ export function createKeyedBlock<T>(
|
|
|
377
325
|
// Utilities
|
|
378
326
|
// ============================================================================
|
|
379
327
|
|
|
380
|
-
/**
|
|
381
|
-
* Find the first node after the start marker (for getting current anchor)
|
|
382
|
-
*/
|
|
383
|
-
export function getFirstNodeAfter(marker: Comment): Node | null {
|
|
384
|
-
return marker.nextSibling
|
|
385
|
-
}
|
|
386
|
-
|
|
387
328
|
/**
|
|
388
329
|
* Check if a node is between two markers
|
|
389
330
|
*/
|
package/src/props.ts
CHANGED
|
@@ -122,38 +122,46 @@ export function mergeProps<T extends Record<string, unknown>>(
|
|
|
122
122
|
return unwrapProps(value as T)
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
const hasProp = (prop: string | symbol) => {
|
|
126
|
+
for (const src of validSources) {
|
|
127
|
+
const raw = resolveSource(src)
|
|
128
|
+
if (raw && prop in raw) {
|
|
129
|
+
return true
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return false
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const readProp = (prop: string | symbol) => {
|
|
136
|
+
// Only return undefined if no source has this Symbol property
|
|
137
|
+
// Search sources in reverse order (last wins)
|
|
138
|
+
for (let i = validSources.length - 1; i >= 0; i--) {
|
|
139
|
+
const src = validSources[i]!
|
|
140
|
+
const raw = resolveSource(src)
|
|
141
|
+
if (!raw || !(prop in raw)) continue
|
|
142
|
+
|
|
143
|
+
const value = (raw as Record<string | symbol, unknown>)[prop]
|
|
144
|
+
// Preserve prop getters - let child component's createPropsProxy unwrap lazily
|
|
145
|
+
// Note: For Symbol properties, we still wrap in getter if source is dynamic
|
|
146
|
+
if (typeof src === 'function' && !isPropGetter(value)) {
|
|
147
|
+
return __fictProp(() => {
|
|
148
|
+
const latest = resolveSource(src)
|
|
149
|
+
if (!latest || !(prop in latest)) return undefined
|
|
150
|
+
return (latest as Record<string | symbol, unknown>)[prop]
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
return value
|
|
154
|
+
}
|
|
155
|
+
return undefined
|
|
156
|
+
}
|
|
157
|
+
|
|
125
158
|
return new Proxy({} as Record<string, unknown>, {
|
|
126
159
|
get(_, prop) {
|
|
127
|
-
|
|
128
|
-
// Search sources in reverse order (last wins)
|
|
129
|
-
for (let i = validSources.length - 1; i >= 0; i--) {
|
|
130
|
-
const src = validSources[i]!
|
|
131
|
-
const raw = resolveSource(src)
|
|
132
|
-
if (!raw || !(prop in raw)) continue
|
|
133
|
-
|
|
134
|
-
const value = (raw as Record<string | symbol, unknown>)[prop]
|
|
135
|
-
// Preserve prop getters - let child component's createPropsProxy unwrap lazily
|
|
136
|
-
// Note: For Symbol properties, we still wrap in getter if source is dynamic
|
|
137
|
-
if (typeof src === 'function' && !isPropGetter(value)) {
|
|
138
|
-
return __fictProp(() => {
|
|
139
|
-
const latest = resolveSource(src)
|
|
140
|
-
if (!latest || !(prop in latest)) return undefined
|
|
141
|
-
return (latest as Record<string | symbol, unknown>)[prop]
|
|
142
|
-
})
|
|
143
|
-
}
|
|
144
|
-
return value
|
|
145
|
-
}
|
|
146
|
-
return undefined
|
|
160
|
+
return readProp(prop)
|
|
147
161
|
},
|
|
148
162
|
|
|
149
163
|
has(_, prop) {
|
|
150
|
-
|
|
151
|
-
const raw = resolveSource(src)
|
|
152
|
-
if (raw && prop in raw) {
|
|
153
|
-
return true
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return false
|
|
164
|
+
return hasProp(prop)
|
|
157
165
|
},
|
|
158
166
|
|
|
159
167
|
ownKeys() {
|
|
@@ -170,21 +178,12 @@ export function mergeProps<T extends Record<string, unknown>>(
|
|
|
170
178
|
},
|
|
171
179
|
|
|
172
180
|
getOwnPropertyDescriptor(_, prop) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
configurable: true,
|
|
179
|
-
get: () => {
|
|
180
|
-
const value = (raw as Record<string | symbol, unknown>)[prop]
|
|
181
|
-
// Preserve prop getters - let child component's createPropsProxy unwrap lazily
|
|
182
|
-
return value
|
|
183
|
-
},
|
|
184
|
-
}
|
|
185
|
-
}
|
|
181
|
+
if (!hasProp(prop)) return undefined
|
|
182
|
+
return {
|
|
183
|
+
enumerable: true,
|
|
184
|
+
configurable: true,
|
|
185
|
+
get: () => readProp(prop),
|
|
186
186
|
}
|
|
187
|
-
return undefined
|
|
188
187
|
},
|
|
189
188
|
})
|
|
190
189
|
}
|
|
@@ -192,19 +191,22 @@ export function mergeProps<T extends Record<string, unknown>>(
|
|
|
192
191
|
export type PropGetter<T> = (() => T) & { __fictProp: true }
|
|
193
192
|
/**
|
|
194
193
|
* Memoize a prop getter to cache expensive computations.
|
|
195
|
-
* Use when prop expressions involve heavy calculations.
|
|
194
|
+
* Use when prop expressions involve heavy calculations or you need lazy, reactive props.
|
|
196
195
|
*
|
|
197
196
|
* @example
|
|
198
197
|
* ```tsx
|
|
199
|
-
* // Without
|
|
198
|
+
* // Without prop - recomputes on every access
|
|
200
199
|
* <Child data={expensiveComputation(list, filter)} />
|
|
201
200
|
*
|
|
202
|
-
* // With
|
|
203
|
-
* const memoizedData =
|
|
201
|
+
* // With prop - cached until dependencies change, auto-unwrapped by props proxy
|
|
202
|
+
* const memoizedData = prop(() => expensiveComputation(list, filter))
|
|
204
203
|
* <Child data={memoizedData} />
|
|
205
204
|
* ```
|
|
206
205
|
*/
|
|
207
|
-
export function
|
|
206
|
+
export function prop<T>(getter: () => T): PropGetter<T> {
|
|
207
|
+
if (isPropGetter(getter)) {
|
|
208
|
+
return getter as PropGetter<T>
|
|
209
|
+
}
|
|
208
210
|
// Wrap in prop so component props proxy auto-unwraps when passed down.
|
|
209
211
|
return __fictProp(createMemo(getter)) as PropGetter<T>
|
|
210
212
|
}
|