@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
@@ -0,0 +1,475 @@
1
+ /**
2
+ * Signal accessor - function to get/set signal value
3
+ */
4
+ interface SignalAccessor<T> {
5
+ (): T;
6
+ (value: T): void;
7
+ }
8
+ /**
9
+ * Computed accessor - function to get computed value
10
+ */
11
+ type ComputedAccessor<T> = () => T;
12
+ /**
13
+ * Create a reactive signal
14
+ * @param initialValue - The initial value
15
+ * @returns A signal accessor function
16
+ */
17
+ declare function signal<T>(initialValue: T): SignalAccessor<T>;
18
+ declare const $state: <T>(value: T) => T;
19
+ /**
20
+ * Create a selector signal that efficiently updates only when the selected key matches.
21
+ * Useful for large lists where only one item is selected.
22
+ *
23
+ * @param source - The source signal returning the current key
24
+ * @param equalityFn - Optional equality function
25
+ * @returns A selector function that takes a key and returns a boolean signal accessor
26
+ */
27
+ declare function createSelector<T>(source: () => T, equalityFn?: (a: T, b: T) => boolean): (key: T) => boolean;
28
+
29
+ type Memo<T> = () => T;
30
+ declare function createMemo<T>(fn: () => T): Memo<T>;
31
+
32
+ /** Any DOM node that can be rendered */
33
+ type DOMElement = Node;
34
+ /** Cleanup function type */
35
+ type Cleanup = () => void;
36
+ /** Fict Virtual Node - represents a component or element in the virtual tree */
37
+ interface FictVNode {
38
+ /** Element type: tag name, Fragment symbol, or component function */
39
+ type: string | symbol | ((props: Record<string, unknown>) => FictNode);
40
+ /** Props passed to the element/component */
41
+ props: Record<string, unknown> | null;
42
+ /** Optional key for list rendering optimization */
43
+ key?: string | undefined;
44
+ }
45
+ /**
46
+ * Fict Node - represents any renderable value
47
+ * This type covers all possible values that can appear in JSX
48
+ */
49
+ type FictNode = FictVNode | FictNode[] | Node | string | number | boolean | null | undefined;
50
+ interface ErrorInfo {
51
+ source: 'render' | 'effect' | 'event' | 'renderChild' | 'cleanup';
52
+ componentName?: string;
53
+ eventName?: string;
54
+ }
55
+ interface SuspenseToken {
56
+ then: Promise<unknown>['then'];
57
+ }
58
+
59
+ type Effect = () => void | Cleanup;
60
+ declare function createEffect(fn: Effect): () => void;
61
+ declare function createRenderEffect(fn: Effect): () => void;
62
+
63
+ declare function batch<T>(fn: () => T): T;
64
+ declare function untrack<T>(fn: () => T): T;
65
+
66
+ /**
67
+ * Fict Reactive DOM Binding System
68
+ *
69
+ * This module provides the core mechanisms for reactive DOM updates.
70
+ * It bridges the gap between Fict's reactive system (signals, effects)
71
+ * and the DOM, enabling fine-grained updates without a virtual DOM.
72
+ *
73
+ * Design Philosophy:
74
+ * - Values wrapped in functions `() => T` are treated as reactive
75
+ * - Static values are applied once without tracking
76
+ * - The compiler transforms JSX expressions to use these primitives
77
+ */
78
+
79
+ /** Internal type for createElement function reference */
80
+ type CreateElementFn = (node: FictNode) => Node;
81
+ /** Handle returned by conditional/list bindings for cleanup */
82
+ interface BindingHandle {
83
+ /** Marker node(s) used for positioning */
84
+ marker: Comment | DocumentFragment;
85
+ /** Flush pending content - call after markers are inserted into DOM */
86
+ flush?: () => void;
87
+ /** Dispose function to clean up the binding */
88
+ dispose: Cleanup;
89
+ }
90
+ /**
91
+ * Bind a reactive value to an existing text node.
92
+ * This is a convenience function for binding to existing DOM nodes.
93
+ */
94
+ declare function bindText(textNode: Text, getValue: () => unknown): Cleanup;
95
+ /**
96
+ * Bind a reactive value to an element's attribute.
97
+ */
98
+ declare function bindAttribute(el: HTMLElement, key: string, getValue: () => unknown): Cleanup;
99
+ /**
100
+ * Bind a reactive value to an element's property.
101
+ */
102
+ declare function bindProperty(el: HTMLElement, key: string, getValue: () => unknown): Cleanup;
103
+ /**
104
+ * Bind a reactive style value to an existing element.
105
+ */
106
+ declare function bindStyle(el: HTMLElement, getValue: () => string | Record<string, string | number> | null | undefined): Cleanup;
107
+ /**
108
+ * Bind a reactive class value to an existing element.
109
+ */
110
+ declare function bindClass(el: HTMLElement, getValue: () => string | Record<string, boolean> | null | undefined): Cleanup;
111
+ /**
112
+ * Insert reactive content into a parent element.
113
+ * This is a simpler API than createChildBinding for basic cases.
114
+ *
115
+ * @param parent - The parent element to insert into
116
+ * @param getValue - Function that returns the value to render
117
+ * @param markerOrCreateElement - Optional marker node to insert before, or createElementFn
118
+ * @param createElementFn - Optional function to create DOM elements (when marker is provided)
119
+ */
120
+ declare function insert(parent: HTMLElement | DocumentFragment, getValue: () => FictNode, markerOrCreateElement?: Node | CreateElementFn, createElementFn?: CreateElementFn): Cleanup;
121
+ declare global {
122
+ interface HTMLElement {
123
+ _$host?: HTMLElement;
124
+ [key: `$$${string}`]: EventListener | [EventListener, unknown] | undefined;
125
+ [key: `$$${string}Data`]: unknown;
126
+ }
127
+ interface Document extends Record<string, unknown> {
128
+ }
129
+ }
130
+ /**
131
+ * Initialize event delegation for a set of event names.
132
+ * Events will be handled at the document level and dispatched to the appropriate handlers.
133
+ *
134
+ * @param eventNames - Array of event names to delegate
135
+ * @param doc - The document to attach handlers to (default: window.document)
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * // Called automatically by the compiler for delegated events
140
+ * delegateEvents(['click', 'input', 'keydown'])
141
+ * ```
142
+ */
143
+ declare function delegateEvents(eventNames: string[], doc?: Document): void;
144
+ /**
145
+ * Clear all delegated event handlers from a document.
146
+ *
147
+ * @param doc - The document to clear handlers from (default: window.document)
148
+ */
149
+ declare function clearDelegatedEvents(doc?: Document): void;
150
+ /**
151
+ * Bind an event listener to an element.
152
+ * Uses event delegation for better performance when applicable.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * // Static event
157
+ * bindEvent(button, 'click', handleClick)
158
+ *
159
+ * // Reactive event (compiler output)
160
+ * bindEvent(button, 'click', () => $handler())
161
+ *
162
+ * // With modifiers
163
+ * bindEvent(button, 'click', handler, { capture: true, passive: true, once: true })
164
+ * ```
165
+ */
166
+ declare function bindEvent(el: HTMLElement, eventName: string, handler: EventListenerOrEventListenerObject | null | undefined, options?: boolean | AddEventListenerOptions): Cleanup;
167
+ /**
168
+ * Bind a ref to an element.
169
+ * Supports both callback refs and ref objects.
170
+ *
171
+ * @param el - The element to bind the ref to
172
+ * @param ref - Either a callback function, a ref object, or a reactive getter
173
+ * @returns Cleanup function
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * // Callback ref
178
+ * bindRef(el, (element) => { store.input = element })
179
+ *
180
+ * // Ref object
181
+ * const inputRef = createRef()
182
+ * bindRef(el, inputRef)
183
+ *
184
+ * // Reactive ref (compiler output)
185
+ * bindRef(el, () => props.ref)
186
+ * ```
187
+ */
188
+ declare function bindRef(el: HTMLElement, ref: unknown): Cleanup;
189
+ /**
190
+ * Create a conditional rendering binding.
191
+ * Efficiently renders one of two branches based on a condition.
192
+ *
193
+ * This is an optimized version for `{condition ? <A /> : <B />}` patterns
194
+ * where both branches are known statically.
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * // Compiler output for {show ? <A /> : <B />}
199
+ * createConditional(
200
+ * () => $show(),
201
+ * () => jsx(A, {}),
202
+ * () => jsx(B, {}),
203
+ * createElement
204
+ * )
205
+ * ```
206
+ */
207
+ declare function createConditional(condition: () => boolean, renderTrue: () => FictNode, createElementFn: CreateElementFn, renderFalse?: () => FictNode): BindingHandle;
208
+ /** Key extractor function type */
209
+ type KeyFn<T> = (item: T, index: number) => string | number;
210
+ /**
211
+ * Create a reactive list rendering binding with optional keying.
212
+ */
213
+ declare function createList<T>(items: () => T[], renderItem: (item: T, index: number) => FictNode, createElementFn: CreateElementFn, getKey?: KeyFn<T>): BindingHandle;
214
+
215
+ /**
216
+ * Fict DOM Rendering System
217
+ *
218
+ * This module provides DOM rendering capabilities with reactive bindings.
219
+ * It transforms JSX virtual nodes into actual DOM elements, automatically
220
+ * setting up reactive updates for dynamic values.
221
+ *
222
+ * Key Features:
223
+ * - Reactive text content: `{count}` updates when count changes
224
+ * - Reactive attributes: `disabled={!isValid}` updates reactively
225
+ * - Reactive children: `{show && <Modal />}` handles conditionals
226
+ * - List rendering: `{items.map(...)}` with efficient keyed updates
227
+ */
228
+
229
+ /**
230
+ * Render a Fict view into a container element.
231
+ *
232
+ * @param view - A function that returns the view to render
233
+ * @param container - The DOM container to render into
234
+ * @returns A teardown function to unmount the view
235
+ *
236
+ * @example
237
+ * ```ts
238
+ * const unmount = render(() => <App />, document.getElementById('root')!)
239
+ * // Later: unmount()
240
+ * ```
241
+ */
242
+ declare function render(view: () => FictNode, container: HTMLElement): () => void;
243
+ /**
244
+ * Create a DOM element from a Fict node.
245
+ * This is the main entry point for converting virtual nodes to real DOM.
246
+ *
247
+ * Supports:
248
+ * - Native DOM nodes (passed through)
249
+ * - Null/undefined/false (empty text node)
250
+ * - Arrays (DocumentFragment)
251
+ * - Strings/numbers (text nodes)
252
+ * - Booleans (empty text node)
253
+ * - VNodes (components or HTML elements)
254
+ * - Reactive values (functions returning any of the above)
255
+ */
256
+ declare function createElement(node: FictNode): DOMElement;
257
+ /**
258
+ * Create a template cloning factory from an HTML string.
259
+ * Used by the compiler for efficient DOM generation.
260
+ *
261
+ * @param html - The HTML string to create a template from
262
+ * @param isImportNode - Use importNode for elements like img/iframe
263
+ * @param isSVG - Whether the template is SVG content
264
+ * @param isMathML - Whether the template is MathML content
265
+ */
266
+ declare function template(html: string, isImportNode?: boolean, isSVG?: boolean, isMathML?: boolean): () => Node;
267
+
268
+ declare const Fragment: unique symbol;
269
+
270
+ type LifecycleFn = () => void | Cleanup;
271
+ interface RootContext {
272
+ parent?: RootContext | undefined;
273
+ onMountCallbacks?: LifecycleFn[];
274
+ cleanups: Cleanup[];
275
+ destroyCallbacks: Cleanup[];
276
+ errorHandlers?: ErrorHandler[];
277
+ suspenseHandlers?: SuspenseHandler[];
278
+ }
279
+ type ErrorHandler = (err: unknown, info?: ErrorInfo) => boolean | void;
280
+ type SuspenseHandler = (token: SuspenseToken | PromiseLike<unknown>) => boolean | void;
281
+ declare function onDestroy(fn: LifecycleFn): void;
282
+
283
+ /**
284
+ * Low-level DOM node helpers shared across runtime modules.
285
+ * Keep this file dependency-free to avoid module cycles.
286
+ */
287
+ /**
288
+ * Convert a value to a flat array of DOM nodes.
289
+ * Defensively handles proxies and non-DOM values.
290
+ */
291
+ declare function toNodeArray(node: Node | Node[] | unknown): Node[];
292
+ /**
293
+ * Insert nodes before an anchor node, preserving order.
294
+ * Uses DocumentFragment for batch insertion when inserting multiple nodes.
295
+ */
296
+ declare function insertNodesBefore(parent: ParentNode & Node, nodes: Node[], anchor: Node | null): void;
297
+ /**
298
+ * Remove an array of nodes from the DOM.
299
+ */
300
+ declare function removeNodes(nodes: Node[]): void;
301
+
302
+ /**
303
+ * List Helpers for Compiler-Generated Fine-Grained Updates
304
+ *
305
+ * These helpers are used by the compiler to generate efficient keyed list rendering.
306
+ * They provide low-level primitives for DOM node manipulation without rebuilding.
307
+ */
308
+
309
+ /**
310
+ * A keyed block represents a single item in a list with its associated DOM nodes and state
311
+ */
312
+ interface KeyedBlock<T = unknown> {
313
+ /** Unique key for this block */
314
+ key: string | number;
315
+ /** DOM nodes belonging to this block */
316
+ nodes: Node[];
317
+ /** Root context for lifecycle management */
318
+ root: RootContext;
319
+ /** Signal containing the current item value */
320
+ item: SignalAccessor<T>;
321
+ /** Signal containing the current index */
322
+ index: SignalAccessor<number>;
323
+ /** Last raw item value assigned to this block */
324
+ rawItem: T;
325
+ /** Last raw index value assigned to this block */
326
+ rawIndex: number;
327
+ }
328
+ /**
329
+ * Container for managing keyed list blocks
330
+ */
331
+ interface KeyedListContainer<T = unknown> {
332
+ /** Start marker comment node */
333
+ startMarker: Comment;
334
+ /** End marker comment node */
335
+ endMarker: Comment;
336
+ /** Map of key to block */
337
+ blocks: Map<string | number, KeyedBlock<T>>;
338
+ /** Scratch map reused for the next render */
339
+ nextBlocks: Map<string | number, KeyedBlock<T>>;
340
+ /** Current nodes in DOM order (including markers) */
341
+ currentNodes: Node[];
342
+ /** Next-frame node buffer to avoid reallocations */
343
+ nextNodes: Node[];
344
+ /** Ordered blocks in current DOM order */
345
+ orderedBlocks: KeyedBlock<T>[];
346
+ /** Next-frame ordered block buffer to avoid reallocations */
347
+ nextOrderedBlocks: KeyedBlock<T>[];
348
+ /** Track position of keys in the ordered buffer to handle duplicates */
349
+ orderedIndexByKey: Map<string | number, number>;
350
+ /** Cleanup function */
351
+ dispose: () => void;
352
+ }
353
+ /**
354
+ * Binding handle returned by createKeyedList for compiler-generated code
355
+ */
356
+ interface KeyedListBinding {
357
+ /** Document fragment placeholder inserted by the compiler/runtime */
358
+ marker: DocumentFragment;
359
+ /** Start marker comment node */
360
+ startMarker: Comment;
361
+ /** End marker comment node */
362
+ endMarker: Comment;
363
+ /** Flush pending items - call after markers are inserted into DOM */
364
+ flush?: () => void;
365
+ /** Cleanup function */
366
+ dispose: () => void;
367
+ }
368
+ type FineGrainedRenderItem<T> = (itemSig: SignalAccessor<T>, indexSig: SignalAccessor<number>, key: string | number) => Node[];
369
+ /**
370
+ * A block identified by start/end comment markers.
371
+ */
372
+ interface MarkerBlock {
373
+ start: Comment;
374
+ end: Comment;
375
+ root?: RootContext;
376
+ }
377
+ /**
378
+ * Remove an array of nodes from the DOM
379
+ *
380
+ * @param nodes - Array of nodes to remove
381
+ */
382
+ /**
383
+ * Move an entire marker-delimited block (including markers) before the anchor.
384
+ */
385
+ declare function moveMarkerBlock(parent: Node, block: MarkerBlock, anchor: Node | null): void;
386
+ /**
387
+ * Destroy a marker-delimited block, removing nodes and destroying the associated root.
388
+ */
389
+ declare function destroyMarkerBlock(block: MarkerBlock): void;
390
+ /**
391
+ * Create a container for managing a keyed list.
392
+ * This sets up the marker nodes and provides cleanup.
393
+ *
394
+ * @returns Container object with markers, blocks map, and dispose function
395
+ */
396
+ declare function createKeyedListContainer<T = unknown>(): KeyedListContainer<T>;
397
+ /**
398
+ * Create a new keyed block with the given render function
399
+ *
400
+ * @param key - Unique key for this block
401
+ * @param item - Initial item value
402
+ * @param index - Initial index
403
+ * @param render - Function that creates the DOM nodes and sets up bindings
404
+ * @returns New KeyedBlock
405
+ */
406
+ declare function createKeyedBlock<T>(key: string | number, item: T, index: number, render: (item: SignalAccessor<T>, index: SignalAccessor<number>, key: string | number) => Node[], needsIndex?: boolean): KeyedBlock<T>;
407
+ /**
408
+ * Find the first node after the start marker (for getting current anchor)
409
+ */
410
+ declare function getFirstNodeAfter(marker: Comment): Node | null;
411
+ /**
412
+ * Create a keyed list binding with automatic diffing and DOM updates.
413
+ * This is used by compiler-generated code for efficient list rendering.
414
+ *
415
+ * @param getItems - Function that returns the current array of items
416
+ * @param keyFn - Function to extract unique key from each item
417
+ * @param renderItem - Function that creates DOM nodes for each item
418
+ * @returns Binding handle with markers and dispose function
419
+ */
420
+ declare function createKeyedList<T>(getItems: () => T[], keyFn: (item: T, index: number) => string | number, renderItem: FineGrainedRenderItem<T>, needsIndex?: boolean): KeyedListBinding;
421
+
422
+ interface HookContext {
423
+ slots: unknown[];
424
+ cursor: number;
425
+ }
426
+ declare function __fictUseContext(): HookContext;
427
+ declare function __fictPushContext(): HookContext;
428
+ declare function __fictPopContext(): void;
429
+ declare function __fictResetContext(): void;
430
+ declare function __fictUseSignal<T>(ctx: HookContext, initial: T, slot?: number): SignalAccessor<T>;
431
+ declare function __fictUseMemo<T>(ctx: HookContext, fn: () => T, slot?: number): ComputedAccessor<T>;
432
+ declare function __fictUseEffect(ctx: HookContext, fn: () => void, slot?: number): void;
433
+ declare function __fictRender<T>(ctx: HookContext, fn: () => T): T;
434
+
435
+ /**
436
+ * @internal
437
+ * Marks a zero-arg getter so props proxy can lazily evaluate it.
438
+ * Users normally never call this directly; the compiler injects it.
439
+ */
440
+ declare function __fictProp<T>(getter: () => T): () => T;
441
+ declare function createPropsProxy<T extends Record<string, unknown>>(props: T): T;
442
+ /**
443
+ * Create a rest-like props object while preserving prop getters.
444
+ * Excludes the specified keys from the returned object.
445
+ */
446
+ declare function __fictPropsRest<T extends Record<string, unknown>>(props: T, exclude: (string | number | symbol)[]): Record<string, unknown>;
447
+ /**
448
+ * Merge multiple props-like objects while preserving lazy getters.
449
+ * Later sources override earlier ones.
450
+ *
451
+ * Uses lazy lookup strategy - properties are only accessed when read,
452
+ * avoiding upfront iteration of all keys.
453
+ */
454
+ type MergeSource<T extends Record<string, unknown>> = T | (() => T);
455
+ declare function mergeProps<T extends Record<string, unknown>>(...sources: (MergeSource<T> | null | undefined)[]): Record<string, unknown>;
456
+ type PropGetter<T> = (() => T) & {
457
+ __fictProp: true;
458
+ };
459
+ /**
460
+ * Memoize a prop getter to cache expensive computations.
461
+ * Use when prop expressions involve heavy calculations.
462
+ *
463
+ * @example
464
+ * ```tsx
465
+ * // Without useProp - recomputes on every access
466
+ * <Child data={expensiveComputation(list, filter)} />
467
+ *
468
+ * // With useProp - cached until dependencies change, auto-unwrapped by props proxy
469
+ * const memoizedData = useProp(() => expensiveComputation(list, filter))
470
+ * <Child data={memoizedData} />
471
+ * ```
472
+ */
473
+ declare function useProp<T>(getter: () => T): PropGetter<T>;
474
+
475
+ export { $state, Fragment, __fictPopContext, __fictProp, __fictPropsRest, __fictPushContext, __fictRender, __fictResetContext, __fictUseContext, __fictUseEffect, __fictUseMemo, __fictUseSignal, batch, bindAttribute, bindClass, bindEvent, bindProperty, bindRef, bindStyle, bindText, clearDelegatedEvents, createConditional, createEffect, createElement, createKeyedBlock, createKeyedList, createKeyedListContainer, createList, createMemo, createPropsProxy, createRenderEffect, createSelector, signal as createSignal, delegateEvents, destroyMarkerBlock, getFirstNodeAfter, insert, insertNodesBefore, mergeProps, moveMarkerBlock, onDestroy, __fictProp as prop, removeNodes, render, template, toNodeArray, untrack, useProp };