@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.
Files changed (51) hide show
  1. package/README.md +17 -0
  2. package/dist/index.cjs +4224 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +1572 -0
  5. package/dist/index.d.ts +1572 -0
  6. package/dist/index.dev.js +4240 -0
  7. package/dist/index.dev.js.map +1 -0
  8. package/dist/index.js +4133 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/jsx-dev-runtime.cjs +44 -0
  11. package/dist/jsx-dev-runtime.cjs.map +1 -0
  12. package/dist/jsx-dev-runtime.js +14 -0
  13. package/dist/jsx-dev-runtime.js.map +1 -0
  14. package/dist/jsx-runtime.cjs +44 -0
  15. package/dist/jsx-runtime.cjs.map +1 -0
  16. package/dist/jsx-runtime.js +14 -0
  17. package/dist/jsx-runtime.js.map +1 -0
  18. package/dist/slim.cjs +3384 -0
  19. package/dist/slim.cjs.map +1 -0
  20. package/dist/slim.d.cts +475 -0
  21. package/dist/slim.d.ts +475 -0
  22. package/dist/slim.js +3335 -0
  23. package/dist/slim.js.map +1 -0
  24. package/package.json +68 -0
  25. package/src/binding.ts +2127 -0
  26. package/src/constants.ts +456 -0
  27. package/src/cycle-guard.ts +134 -0
  28. package/src/devtools.ts +17 -0
  29. package/src/dom.ts +683 -0
  30. package/src/effect.ts +83 -0
  31. package/src/error-boundary.ts +118 -0
  32. package/src/hooks.ts +72 -0
  33. package/src/index.ts +184 -0
  34. package/src/jsx-dev-runtime.ts +2 -0
  35. package/src/jsx-runtime.ts +2 -0
  36. package/src/jsx.ts +786 -0
  37. package/src/lifecycle.ts +273 -0
  38. package/src/list-helpers.ts +619 -0
  39. package/src/memo.ts +14 -0
  40. package/src/node-ops.ts +185 -0
  41. package/src/props.ts +212 -0
  42. package/src/reconcile.ts +151 -0
  43. package/src/ref.ts +25 -0
  44. package/src/scheduler.ts +12 -0
  45. package/src/signal.ts +1278 -0
  46. package/src/slim.ts +68 -0
  47. package/src/store.ts +210 -0
  48. package/src/suspense.ts +187 -0
  49. package/src/transition.ts +128 -0
  50. package/src/types.ts +172 -0
  51. 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'
@@ -0,0 +1,2 @@
1
+ export { jsx, jsxs, jsxDEV, Fragment } from './jsx'
2
+ export type { JSX } from './jsx'
@@ -0,0 +1,2 @@
1
+ export { jsx, jsxs, jsxDEV, Fragment } from './jsx'
2
+ export type { JSX } from './jsx'