@fictjs/runtime 0.0.2
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/README.md +17 -0
- package/dist/index.cjs +4224 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1572 -0
- package/dist/index.d.ts +1572 -0
- package/dist/index.dev.js +4240 -0
- package/dist/index.dev.js.map +1 -0
- package/dist/index.js +4133 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx-dev-runtime.cjs +44 -0
- package/dist/jsx-dev-runtime.cjs.map +1 -0
- package/dist/jsx-dev-runtime.js +14 -0
- package/dist/jsx-dev-runtime.js.map +1 -0
- package/dist/jsx-runtime.cjs +44 -0
- package/dist/jsx-runtime.cjs.map +1 -0
- package/dist/jsx-runtime.js +14 -0
- package/dist/jsx-runtime.js.map +1 -0
- package/dist/slim.cjs +3384 -0
- package/dist/slim.cjs.map +1 -0
- package/dist/slim.d.cts +475 -0
- package/dist/slim.d.ts +475 -0
- package/dist/slim.js +3335 -0
- package/dist/slim.js.map +1 -0
- package/package.json +68 -0
- package/src/binding.ts +2127 -0
- package/src/constants.ts +456 -0
- package/src/cycle-guard.ts +134 -0
- package/src/devtools.ts +17 -0
- package/src/dom.ts +683 -0
- package/src/effect.ts +83 -0
- package/src/error-boundary.ts +118 -0
- package/src/hooks.ts +72 -0
- package/src/index.ts +184 -0
- package/src/jsx-dev-runtime.ts +2 -0
- package/src/jsx-runtime.ts +2 -0
- package/src/jsx.ts +786 -0
- package/src/lifecycle.ts +273 -0
- package/src/list-helpers.ts +619 -0
- package/src/memo.ts +14 -0
- package/src/node-ops.ts +185 -0
- package/src/props.ts +212 -0
- package/src/reconcile.ts +151 -0
- package/src/ref.ts +25 -0
- package/src/scheduler.ts +12 -0
- package/src/signal.ts +1278 -0
- package/src/slim.ts +68 -0
- package/src/store.ts +210 -0
- package/src/suspense.ts +187 -0
- package/src/transition.ts +128 -0
- package/src/types.ts +172 -0
- package/src/versioned-signal.ts +58 -0
package/src/effect.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCurrentRoot,
|
|
3
|
+
handleError,
|
|
4
|
+
registerRootCleanup,
|
|
5
|
+
runCleanupList,
|
|
6
|
+
withEffectCleanups,
|
|
7
|
+
} from './lifecycle'
|
|
8
|
+
import { effect } from './signal'
|
|
9
|
+
import type { Cleanup } from './types'
|
|
10
|
+
|
|
11
|
+
export type Effect = () => void | Cleanup
|
|
12
|
+
|
|
13
|
+
export function createEffect(fn: Effect): () => void {
|
|
14
|
+
let cleanups: Cleanup[] = []
|
|
15
|
+
const rootForError = getCurrentRoot()
|
|
16
|
+
|
|
17
|
+
const run = () => {
|
|
18
|
+
runCleanupList(cleanups)
|
|
19
|
+
const bucket: Cleanup[] = []
|
|
20
|
+
withEffectCleanups(bucket, () => {
|
|
21
|
+
try {
|
|
22
|
+
const maybeCleanup = fn()
|
|
23
|
+
if (typeof maybeCleanup === 'function') {
|
|
24
|
+
bucket.push(maybeCleanup)
|
|
25
|
+
}
|
|
26
|
+
} catch (err) {
|
|
27
|
+
if (handleError(err, { source: 'effect' }, rootForError)) {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
throw err
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
cleanups = bucket
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const disposeEffect = effect(run)
|
|
37
|
+
const teardown = () => {
|
|
38
|
+
runCleanupList(cleanups)
|
|
39
|
+
disposeEffect()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
registerRootCleanup(teardown)
|
|
43
|
+
|
|
44
|
+
return teardown
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const $effect = createEffect
|
|
48
|
+
|
|
49
|
+
export function createRenderEffect(fn: Effect): () => void {
|
|
50
|
+
let cleanup: Cleanup | undefined
|
|
51
|
+
const rootForError = getCurrentRoot()
|
|
52
|
+
|
|
53
|
+
const run = () => {
|
|
54
|
+
if (cleanup) {
|
|
55
|
+
cleanup()
|
|
56
|
+
cleanup = undefined
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const maybeCleanup = fn()
|
|
60
|
+
if (typeof maybeCleanup === 'function') {
|
|
61
|
+
cleanup = maybeCleanup
|
|
62
|
+
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (handleError(err, { source: 'effect' }, rootForError)) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
throw err
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const disposeEffect = effect(run)
|
|
72
|
+
const teardown = () => {
|
|
73
|
+
if (cleanup) {
|
|
74
|
+
cleanup()
|
|
75
|
+
cleanup = undefined
|
|
76
|
+
}
|
|
77
|
+
disposeEffect()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
registerRootCleanup(teardown)
|
|
81
|
+
|
|
82
|
+
return teardown
|
|
83
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createElement } from './dom'
|
|
2
|
+
import { createEffect } from './effect'
|
|
3
|
+
import {
|
|
4
|
+
createRootContext,
|
|
5
|
+
destroyRoot,
|
|
6
|
+
flushOnMount,
|
|
7
|
+
pushRoot,
|
|
8
|
+
popRoot,
|
|
9
|
+
registerErrorHandler,
|
|
10
|
+
} from './lifecycle'
|
|
11
|
+
import { insertNodesBefore, removeNodes, toNodeArray } from './node-ops'
|
|
12
|
+
import { createSignal } from './signal'
|
|
13
|
+
import type { BaseProps, FictNode } from './types'
|
|
14
|
+
|
|
15
|
+
interface ErrorBoundaryProps extends BaseProps {
|
|
16
|
+
fallback: FictNode | ((err: unknown) => FictNode)
|
|
17
|
+
onError?: (err: unknown) => void
|
|
18
|
+
resetKeys?: unknown | (() => unknown)
|
|
19
|
+
}
|
|
20
|
+
|
|
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
|
+
const currentView = createSignal<FictNode | null>(props.children ?? null)
|
|
27
|
+
|
|
28
|
+
let cleanup: (() => void) | undefined
|
|
29
|
+
let activeNodes: Node[] = []
|
|
30
|
+
let renderingFallback = false
|
|
31
|
+
|
|
32
|
+
const toView = (err: unknown | null): FictNode | null => {
|
|
33
|
+
if (err != null) {
|
|
34
|
+
return typeof props.fallback === 'function'
|
|
35
|
+
? (props.fallback as (e: unknown) => FictNode)(err)
|
|
36
|
+
: props.fallback
|
|
37
|
+
}
|
|
38
|
+
return props.children ?? null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const renderValue = (value: FictNode | null) => {
|
|
42
|
+
if (cleanup) {
|
|
43
|
+
cleanup()
|
|
44
|
+
cleanup = undefined
|
|
45
|
+
}
|
|
46
|
+
if (activeNodes.length) {
|
|
47
|
+
removeNodes(activeNodes)
|
|
48
|
+
activeNodes = []
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (value == null || value === false) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const root = createRootContext()
|
|
56
|
+
const prev = pushRoot(root)
|
|
57
|
+
let nodes: Node[] = []
|
|
58
|
+
try {
|
|
59
|
+
const output = createElement(value)
|
|
60
|
+
nodes = toNodeArray(output)
|
|
61
|
+
const parentNode = marker.parentNode as (ParentNode & Node) | null
|
|
62
|
+
if (parentNode) {
|
|
63
|
+
insertNodesBefore(parentNode, nodes, marker)
|
|
64
|
+
}
|
|
65
|
+
} catch (err) {
|
|
66
|
+
popRoot(prev)
|
|
67
|
+
flushOnMount(root)
|
|
68
|
+
destroyRoot(root)
|
|
69
|
+
// Fall back immediately on render errors, avoid infinite recursion
|
|
70
|
+
if (renderingFallback) {
|
|
71
|
+
throw err
|
|
72
|
+
}
|
|
73
|
+
renderingFallback = true
|
|
74
|
+
try {
|
|
75
|
+
renderValue(toView(err))
|
|
76
|
+
} finally {
|
|
77
|
+
renderingFallback = false
|
|
78
|
+
}
|
|
79
|
+
props.onError?.(err)
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
popRoot(prev)
|
|
83
|
+
flushOnMount(root)
|
|
84
|
+
|
|
85
|
+
cleanup = () => {
|
|
86
|
+
destroyRoot(root)
|
|
87
|
+
removeNodes(nodes)
|
|
88
|
+
}
|
|
89
|
+
activeNodes = nodes
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
createEffect(() => {
|
|
93
|
+
const value = currentView()
|
|
94
|
+
renderValue(value)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
registerErrorHandler(err => {
|
|
98
|
+
renderValue(toView(err))
|
|
99
|
+
props.onError?.(err)
|
|
100
|
+
return true
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
if (props.resetKeys !== undefined) {
|
|
104
|
+
const isGetter =
|
|
105
|
+
typeof props.resetKeys === 'function' && (props.resetKeys as () => unknown).length === 0
|
|
106
|
+
const getter = isGetter ? (props.resetKeys as () => unknown) : undefined
|
|
107
|
+
let prev = isGetter ? getter!() : props.resetKeys
|
|
108
|
+
createEffect(() => {
|
|
109
|
+
const next = getter ? getter() : props.resetKeys
|
|
110
|
+
if (prev !== next) {
|
|
111
|
+
prev = next
|
|
112
|
+
renderValue(toView(null))
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return fragment
|
|
118
|
+
}
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createEffect } from './effect'
|
|
2
|
+
import { createMemo } from './memo'
|
|
3
|
+
import { createSignal, type SignalAccessor, type ComputedAccessor } from './signal'
|
|
4
|
+
|
|
5
|
+
interface HookContext {
|
|
6
|
+
slots: unknown[]
|
|
7
|
+
cursor: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const ctxStack: HookContext[] = []
|
|
11
|
+
|
|
12
|
+
export function __fictUseContext(): HookContext {
|
|
13
|
+
if (ctxStack.length === 0) {
|
|
14
|
+
const ctx: HookContext = { slots: [], cursor: 0 }
|
|
15
|
+
ctxStack.push(ctx)
|
|
16
|
+
return ctx
|
|
17
|
+
}
|
|
18
|
+
const ctx = ctxStack[ctxStack.length - 1]!
|
|
19
|
+
ctx.cursor = 0
|
|
20
|
+
return ctx
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function __fictPushContext(): HookContext {
|
|
24
|
+
const ctx: HookContext = { slots: [], cursor: 0 }
|
|
25
|
+
ctxStack.push(ctx)
|
|
26
|
+
return ctx
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function __fictPopContext(): void {
|
|
30
|
+
ctxStack.pop()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function __fictResetContext(): void {
|
|
34
|
+
ctxStack.length = 0
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function __fictUseSignal<T>(ctx: HookContext, initial: T, slot?: number): SignalAccessor<T> {
|
|
38
|
+
const index = slot ?? ctx.cursor++
|
|
39
|
+
if (!ctx.slots[index]) {
|
|
40
|
+
ctx.slots[index] = createSignal(initial)
|
|
41
|
+
}
|
|
42
|
+
return ctx.slots[index] as SignalAccessor<T>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function __fictUseMemo<T>(
|
|
46
|
+
ctx: HookContext,
|
|
47
|
+
fn: () => T,
|
|
48
|
+
slot?: number,
|
|
49
|
+
): ComputedAccessor<T> {
|
|
50
|
+
const index = slot ?? ctx.cursor++
|
|
51
|
+
if (!ctx.slots[index]) {
|
|
52
|
+
ctx.slots[index] = createMemo(fn)
|
|
53
|
+
}
|
|
54
|
+
return ctx.slots[index] as ComputedAccessor<T>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function __fictUseEffect(ctx: HookContext, fn: () => void, slot?: number): void {
|
|
58
|
+
const index = slot ?? ctx.cursor++
|
|
59
|
+
if (!ctx.slots[index]) {
|
|
60
|
+
ctx.slots[index] = createEffect(fn)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function __fictRender<T>(ctx: HookContext, fn: () => T): T {
|
|
65
|
+
ctxStack.push(ctx)
|
|
66
|
+
ctx.cursor = 0
|
|
67
|
+
try {
|
|
68
|
+
return fn()
|
|
69
|
+
} finally {
|
|
70
|
+
ctxStack.pop()
|
|
71
|
+
}
|
|
72
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Core Reactive Primitives
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
export { createSignal, createSelector, type Signal, $state } from './signal'
|
|
6
|
+
export { createStore, type Store } from './store'
|
|
7
|
+
export { createMemo, type Memo, $memo } from './memo'
|
|
8
|
+
export { createEffect, createRenderEffect, type Effect, $effect } from './effect'
|
|
9
|
+
export {
|
|
10
|
+
__fictUseContext,
|
|
11
|
+
__fictPushContext,
|
|
12
|
+
__fictPopContext,
|
|
13
|
+
__fictUseSignal,
|
|
14
|
+
__fictUseMemo,
|
|
15
|
+
__fictUseEffect,
|
|
16
|
+
__fictRender,
|
|
17
|
+
__fictResetContext,
|
|
18
|
+
} from './hooks'
|
|
19
|
+
export {
|
|
20
|
+
createVersionedSignal,
|
|
21
|
+
type VersionedSignal,
|
|
22
|
+
type VersionedSignalOptions,
|
|
23
|
+
} from './versioned-signal'
|
|
24
|
+
|
|
25
|
+
// Props helpers
|
|
26
|
+
export {
|
|
27
|
+
__fictProp,
|
|
28
|
+
__fictProp as prop,
|
|
29
|
+
__fictPropsRest,
|
|
30
|
+
mergeProps,
|
|
31
|
+
useProp,
|
|
32
|
+
createPropsProxy,
|
|
33
|
+
} from './props'
|
|
34
|
+
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Lifecycle
|
|
37
|
+
// ============================================================================
|
|
38
|
+
|
|
39
|
+
export { onMount, onDestroy, onCleanup, createRoot } from './lifecycle'
|
|
40
|
+
|
|
41
|
+
// Ref utilities
|
|
42
|
+
export { createRef } from './ref'
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Scheduler / Utilities
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
export { batch, untrack } from './scheduler'
|
|
49
|
+
export { setCycleProtectionOptions } from './cycle-guard'
|
|
50
|
+
|
|
51
|
+
// Transition API for priority scheduling
|
|
52
|
+
export { startTransition, useTransition, useDeferredValue } from './scheduler'
|
|
53
|
+
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// JSX Runtime
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
58
|
+
export type { JSX } from './jsx'
|
|
59
|
+
export { Fragment } from './jsx'
|
|
60
|
+
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// DOM Rendering
|
|
63
|
+
// ============================================================================
|
|
64
|
+
|
|
65
|
+
export { createElement, render, template } from './dom'
|
|
66
|
+
export { ErrorBoundary } from './error-boundary'
|
|
67
|
+
export { Suspense, createSuspenseToken } from './suspense'
|
|
68
|
+
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// Reactive DOM Bindings
|
|
71
|
+
// ============================================================================
|
|
72
|
+
|
|
73
|
+
export {
|
|
74
|
+
// Core binding utilities
|
|
75
|
+
createTextBinding,
|
|
76
|
+
createChildBinding,
|
|
77
|
+
createAttributeBinding,
|
|
78
|
+
createStyleBinding,
|
|
79
|
+
createClassBinding,
|
|
80
|
+
// Low-level binding helpers (for direct DOM manipulation)
|
|
81
|
+
bindText,
|
|
82
|
+
bindAttribute,
|
|
83
|
+
bindStyle,
|
|
84
|
+
bindClass,
|
|
85
|
+
bindEvent,
|
|
86
|
+
bindProperty,
|
|
87
|
+
bindRef,
|
|
88
|
+
insert,
|
|
89
|
+
// Event delegation
|
|
90
|
+
delegateEvents,
|
|
91
|
+
clearDelegatedEvents,
|
|
92
|
+
addEventListener,
|
|
93
|
+
// Spread props
|
|
94
|
+
spread,
|
|
95
|
+
assign,
|
|
96
|
+
classList,
|
|
97
|
+
// Reactive detection
|
|
98
|
+
isReactive,
|
|
99
|
+
// Advanced bindings
|
|
100
|
+
createConditional,
|
|
101
|
+
createList,
|
|
102
|
+
createPortal,
|
|
103
|
+
createShow,
|
|
104
|
+
// Utility functions
|
|
105
|
+
unwrap,
|
|
106
|
+
unwrapPrimitive,
|
|
107
|
+
} from './binding'
|
|
108
|
+
|
|
109
|
+
// Constants for DOM handling
|
|
110
|
+
export {
|
|
111
|
+
Properties,
|
|
112
|
+
ChildProperties,
|
|
113
|
+
Aliases,
|
|
114
|
+
getPropAlias,
|
|
115
|
+
BooleanAttributes,
|
|
116
|
+
SVGElements,
|
|
117
|
+
SVGNamespace,
|
|
118
|
+
DelegatedEvents,
|
|
119
|
+
UnitlessStyles,
|
|
120
|
+
} from './constants'
|
|
121
|
+
|
|
122
|
+
// Reconcile algorithm
|
|
123
|
+
export { default as reconcileArrays } from './reconcile'
|
|
124
|
+
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// Types
|
|
127
|
+
// ============================================================================
|
|
128
|
+
|
|
129
|
+
export type {
|
|
130
|
+
MaybeReactive,
|
|
131
|
+
BindingHandle,
|
|
132
|
+
KeyFn,
|
|
133
|
+
CreateElementFn,
|
|
134
|
+
AttributeSetter,
|
|
135
|
+
} from './binding'
|
|
136
|
+
|
|
137
|
+
export type {
|
|
138
|
+
FictNode,
|
|
139
|
+
FictVNode,
|
|
140
|
+
DOMElement,
|
|
141
|
+
Cleanup,
|
|
142
|
+
Component,
|
|
143
|
+
BaseProps,
|
|
144
|
+
PropsWithChildren,
|
|
145
|
+
Ref,
|
|
146
|
+
RefCallback,
|
|
147
|
+
RefObject,
|
|
148
|
+
StyleProp,
|
|
149
|
+
ClassProp,
|
|
150
|
+
EventHandler,
|
|
151
|
+
ErrorInfo,
|
|
152
|
+
SuspenseToken,
|
|
153
|
+
} from './types'
|
|
154
|
+
|
|
155
|
+
// Devtools hook (optional)
|
|
156
|
+
export { getDevtoolsHook, type FictDevtoolsHook } from './devtools'
|
|
157
|
+
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// List Helpers (for compiler-generated code)
|
|
160
|
+
// ============================================================================
|
|
161
|
+
|
|
162
|
+
export {
|
|
163
|
+
// DOM manipulation primitives
|
|
164
|
+
moveNodesBefore,
|
|
165
|
+
removeNodes,
|
|
166
|
+
insertNodesBefore,
|
|
167
|
+
moveMarkerBlock,
|
|
168
|
+
destroyMarkerBlock,
|
|
169
|
+
// Keyed list container
|
|
170
|
+
createKeyedListContainer,
|
|
171
|
+
// Block creation
|
|
172
|
+
createKeyedBlock,
|
|
173
|
+
// High-level list binding (for compiler-generated code)
|
|
174
|
+
createKeyedList,
|
|
175
|
+
// Utilities
|
|
176
|
+
toNodeArray,
|
|
177
|
+
getFirstNodeAfter,
|
|
178
|
+
isNodeBetweenMarkers,
|
|
179
|
+
// Types
|
|
180
|
+
type KeyedBlock,
|
|
181
|
+
type KeyedListContainer,
|
|
182
|
+
type KeyedListBinding,
|
|
183
|
+
type MarkerBlock,
|
|
184
|
+
} from './list-helpers'
|