@luna_ui/luna 0.3.4 → 0.3.5
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/cli.mjs +1264 -27
- package/dist/css/index.d.ts +194 -0
- package/dist/css/index.js +721 -0
- package/dist/css/runtime.d.ts +92 -0
- package/dist/css/runtime.js +179 -0
- package/dist/index.js +1 -1
- package/dist/jsx-dev-runtime.js +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/dist/{src-BDdxGwvq.js → src-CHiGeWfy.js} +1 -1
- package/dist/vite-plugin.d.ts +122 -0
- package/dist/vite-plugin.js +1518 -0
- package/package.json +16 -2
- package/src/css/extract.ts +798 -0
- package/src/css/index.ts +10 -0
- package/src/css/inject.ts +205 -0
- package/src/css/inline.ts +182 -0
- package/src/css/minify.ts +70 -0
- package/src/css/optimizer.ts +6 -0
- package/src/css/runtime.ts +344 -0
- package/src/css-optimizer/README.md +353 -0
- package/src/css-optimizer/cooccurrence.ts +100 -0
- package/src/css-optimizer/core.ts +263 -0
- package/src/css-optimizer/extractors.ts +243 -0
- package/src/css-optimizer/hash.ts +54 -0
- package/src/css-optimizer/index.ts +129 -0
- package/src/css-optimizer/merge.ts +109 -0
- package/src/css-optimizer/moonbit-analyzer.ts +210 -0
- package/src/css-optimizer/parser.ts +120 -0
- package/src/css-optimizer/pattern.ts +171 -0
- package/src/css-optimizer/transformers.ts +301 -0
- package/src/css-optimizer/types.ts +128 -0
- package/src/event-utils.ts +227 -0
- package/src/index.ts +890 -0
- package/src/jsx-dev-runtime.ts +2 -0
- package/src/jsx-runtime.ts +398 -0
- package/src/vite-plugin.ts +718 -0
- package/tests/__screenshots__/context.test.ts/Context-API-context-with-reactive-effects-context-value-accessible-in-effect-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-For-component--SolidJS-style--For-updates-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-accepts-children-as-function-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-toggles-visibility-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-attribute-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-style-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-createElementNs--SVG-support--createElementNs-with-dynamic-attribute-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-effect-with-DOM-effect-tracks-signal-changes-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-clear-to-empty-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-empty-array-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-removes-items-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-renders-initial-list-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-updates-when-items-change-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-empty-to-non-empty-transition-in-SVG-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-reordering-in-SVG-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-updates-SVG-elements-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-with-nested-SVG-groups-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-ref-callback--JSX-style--ref-callback-with-nested-elements-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-creates-a-node-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-with-false-condition-creates-placeholder-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-text-nodes-textDyn-creates-reactive-text-node-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-forEach-renders-correctly-without-show--initial-items--1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-forEach-with-context-renders-correctly-without-show-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-nested-components-with-context--forEach--and-show-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-show-and-forEach-inherit-context-from-Owner--fixed--1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-show-and-forEach-work-together--context-uses-default--1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Context---ForEach-integration-forEach-items-can-access-context-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-renders-initial-list-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-updates-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-with-object-items-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-hides-when-condition-is-false-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-renders-when-condition-is-true-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-from-false-to-true-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-reactively-1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--event-listener-pattern--Solid-js-docs-example--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--multiple-cleanups-in-component-body--LIFO-order--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-in-component-body-runs-on-unmount-1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-works-with-For-loop-items--component-body-style--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--timer-cleanup-pattern--Solid-js-style--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Effects-effect-cleanup-runs-before-re-run-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-large-list-update-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-nested-batch-operations-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-rapid-sequential-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-Show-component---visible-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-show-hide-element---visible-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-fragment-with-list-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-nested-fragments-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-simple-fragment-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-conditional-toggle-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-addition-updates-match-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-removal-updates-match-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-text-updates-match-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-className-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-style-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-multiple-dynamic-attributes-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-deeply-nested-conditionals-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-empty-to-populated-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-populated-to-empty-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-cleanup-order-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-with-inner-signal-change-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-is-called-when-effect-re-runs-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-with-resource-simulation-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-multiple-children--no-wrapper--1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-no-children-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-fragment-with-list-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-nested-Fragments-work-correctly-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-complex-reordering-with-additions-and-removals-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-insert-in-middle-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-remove-from-middle-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-reverse-list-order-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-shuffle-list-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-Show-component-renders-when-condition-is-true-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-renders-content-when-initially-true-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-toggles-visibility-dynamically-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Memo-Dependency-Chain-conditional-memo-dependencies-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-basic-signal-get-set-produces-same-values-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-batch-updates-produce-same-final-values-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-peek-reads-value-without-tracking-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-selective-tracking-with-untrack-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-untrack-prevents-dependency-tracking-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--reactivity-accessor-is-reactive-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-empty-string-for-non-failure-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-undefined-for-non-failure-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsFailure-and-stateError-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsPending-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsSuccess-and-stateValue-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateValue-returns-undefined-for-non-success-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-reject-transitions-to-failure-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-resolve-transitions-to-success-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-returns-resource--resolve--and-reject-functions-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-starts-in-pending-state-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-async-resolve-works-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-starts-in-pending-state-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-failure-on-reject-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-success-on-resolve-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-can-wrap-fetch-like-async-operations-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-works-with-setTimeout-simulation-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourceGet-tracks-dependencies-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourcePeek-does-not-track-dependencies-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-resourceRefetch-refetch-resets-to-pending-and-re-runs-fetcher-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-children-to-body-by-default-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-to-selector-mount-target-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/SolidJS-API-compatibility-createEffect-tracks-dependencies-automatically-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-accessor-condition-in-Match-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-multiple-Match-components-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-single-Match-and-fallback-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-components-Switch-updates-DOM-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-multiple-dependencies-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-single-dependency-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-with-defer-option-skips-initial-run-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Arrays-array-updates-work-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-only-triggers-when-accessed-property-changes-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-parent-path-change-notifies-child-accessors-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-nested-property-access-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-property-access-in-effects-1.png +0 -0
- package/tests/context.test.ts +118 -0
- package/tests/css-optimizer-extractors.test.ts +264 -0
- package/tests/css-optimizer-integration.test.ts +566 -0
- package/tests/css-optimizer-transformers.test.ts +301 -0
- package/tests/css-optimizer.test.ts +646 -0
- package/tests/css-runtime.bench.ts +442 -0
- package/tests/css-runtime.test.ts +342 -0
- package/tests/dom.test.ts +872 -0
- package/tests/integration.test.ts +405 -0
- package/tests/issue-5-for-infinite-loop.test.ts +516 -0
- package/tests/jsx-runtime.test.tsx +393 -0
- package/tests/lifecycle.test.ts +833 -0
- package/tests/move-before.bench.ts +304 -0
- package/tests/preact-signals-comparison.test.ts +1608 -0
- package/tests/resource.test.ts +160 -0
- package/tests/router.test.ts +117 -0
- package/tests/show-initial-mount-leak.test.tsx +182 -0
- package/tests/solidjs-api.test.ts +659 -0
- package/tests/static-perf.bench.ts +64 -0
- package/tests/store.test.ts +263 -0
- package/tests/tsx-syntax.test.tsx +404 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Re-export from MoonBit build output (api_js)
|
|
3
|
+
// This file wraps MoonBit APIs to provide SolidJS-compatible interface
|
|
4
|
+
|
|
5
|
+
// Type definitions for SolidJS-compatible API
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Represents a node created by Luna's reactive system.
|
|
9
|
+
* This is an opaque type - the actual structure is managed by MoonBit.
|
|
10
|
+
*/
|
|
11
|
+
export type LunaNode = unknown;
|
|
12
|
+
|
|
13
|
+
export type Accessor<T> = () => T;
|
|
14
|
+
export type Setter<T> = (value: T | ((prev: T) => T)) => void;
|
|
15
|
+
export type Signal<T> = [Accessor<T>, Setter<T>];
|
|
16
|
+
|
|
17
|
+
export interface ForProps<T> {
|
|
18
|
+
each: Accessor<T[]> | T[];
|
|
19
|
+
fallback?: LunaNode;
|
|
20
|
+
children: (item: T, index: Accessor<number>) => LunaNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ShowProps<T> {
|
|
24
|
+
when: T | Accessor<T>;
|
|
25
|
+
fallback?: LunaNode;
|
|
26
|
+
/** Must be a function to ensure proper lifecycle (onCleanup/onMount) support */
|
|
27
|
+
children: (() => LunaNode) | ((item: NonNullable<T>) => LunaNode);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface IndexProps<T> {
|
|
31
|
+
each: Accessor<T[]> | T[];
|
|
32
|
+
fallback?: LunaNode;
|
|
33
|
+
children: (item: Accessor<T>, index: number) => LunaNode;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface Context<T> {
|
|
37
|
+
id: number;
|
|
38
|
+
default_value: () => T;
|
|
39
|
+
providers: unknown[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ProviderProps<T> {
|
|
43
|
+
context: Context<T>;
|
|
44
|
+
value: T;
|
|
45
|
+
/** Must be a function to ensure proper context access and lifecycle (onCleanup/onMount) support */
|
|
46
|
+
children: () => LunaNode;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface MatchProps<T> {
|
|
50
|
+
when: T | Accessor<T>;
|
|
51
|
+
/** Must be a function to ensure proper lifecycle (onCleanup/onMount) support */
|
|
52
|
+
children: (() => LunaNode) | ((item: NonNullable<T>) => LunaNode);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface SwitchProps {
|
|
56
|
+
fallback?: LunaNode;
|
|
57
|
+
children: LunaNode[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface PortalProps {
|
|
61
|
+
mount?: Element | string;
|
|
62
|
+
useShadow?: boolean;
|
|
63
|
+
/** Must be a function to ensure proper lifecycle (onCleanup/onMount) support */
|
|
64
|
+
children: () => LunaNode;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ResourceAccessor<T> {
|
|
68
|
+
(): T | undefined;
|
|
69
|
+
loading: boolean;
|
|
70
|
+
error: string | undefined;
|
|
71
|
+
state: 'pending' | 'ready' | 'errored' | 'unresolved';
|
|
72
|
+
latest: T | undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type SetStoreFunction<T> = (...args: any[]) => void;
|
|
76
|
+
|
|
77
|
+
import {
|
|
78
|
+
// Signal API (internal)
|
|
79
|
+
createSignal as _createSignal,
|
|
80
|
+
get as _get,
|
|
81
|
+
set as _set,
|
|
82
|
+
update as _update,
|
|
83
|
+
peek as _peek,
|
|
84
|
+
subscribe as _subscribe,
|
|
85
|
+
map as _map,
|
|
86
|
+
createMemo as _createMemo,
|
|
87
|
+
combine as _combine,
|
|
88
|
+
effect as _effect,
|
|
89
|
+
renderEffect as _renderEffect,
|
|
90
|
+
batchStart,
|
|
91
|
+
batchEnd,
|
|
92
|
+
runUntracked,
|
|
93
|
+
batch,
|
|
94
|
+
onCleanup,
|
|
95
|
+
createRoot,
|
|
96
|
+
getOwner,
|
|
97
|
+
runWithOwner,
|
|
98
|
+
hasOwner,
|
|
99
|
+
onMount,
|
|
100
|
+
// DOM API
|
|
101
|
+
text,
|
|
102
|
+
textDyn,
|
|
103
|
+
render,
|
|
104
|
+
mount,
|
|
105
|
+
show,
|
|
106
|
+
jsx,
|
|
107
|
+
jsxs,
|
|
108
|
+
Fragment as fragment, // MoonBit's fragment function (Array -> LunaNode)
|
|
109
|
+
createElement,
|
|
110
|
+
createElementNs,
|
|
111
|
+
svgNs,
|
|
112
|
+
mathmlNs,
|
|
113
|
+
events,
|
|
114
|
+
forEach,
|
|
115
|
+
// Timer utilities
|
|
116
|
+
debounced as _debounced,
|
|
117
|
+
// Route definitions
|
|
118
|
+
routePage,
|
|
119
|
+
routePageTitled,
|
|
120
|
+
routePageFull,
|
|
121
|
+
createRouter,
|
|
122
|
+
routerNavigate,
|
|
123
|
+
routerReplace,
|
|
124
|
+
routerGetPath,
|
|
125
|
+
routerGetMatch,
|
|
126
|
+
routerGetBase,
|
|
127
|
+
// Context API
|
|
128
|
+
createContext,
|
|
129
|
+
provide,
|
|
130
|
+
useContext,
|
|
131
|
+
// Resource API
|
|
132
|
+
createResource as _createResource,
|
|
133
|
+
createDeferred as _createDeferred,
|
|
134
|
+
resourceGet,
|
|
135
|
+
resourcePeek,
|
|
136
|
+
resourceRefetch,
|
|
137
|
+
resourceIsPending,
|
|
138
|
+
resourceIsSuccess,
|
|
139
|
+
resourceIsFailure,
|
|
140
|
+
resourceValue,
|
|
141
|
+
resourceError,
|
|
142
|
+
stateIsPending,
|
|
143
|
+
stateIsSuccess,
|
|
144
|
+
stateIsFailure,
|
|
145
|
+
stateValue,
|
|
146
|
+
stateError,
|
|
147
|
+
// Portal API
|
|
148
|
+
portalToBody,
|
|
149
|
+
portalToSelector,
|
|
150
|
+
portalWithShadow,
|
|
151
|
+
portalToElementWithShadow,
|
|
152
|
+
} from "../../../target/js/release/build/platform/js/api/api.js";
|
|
153
|
+
|
|
154
|
+
// ============================================================================
|
|
155
|
+
// SolidJS-compatible Signal API
|
|
156
|
+
// ============================================================================
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Creates a reactive signal (SolidJS-style)
|
|
160
|
+
*/
|
|
161
|
+
export function createSignal<T>(initialValue: T): Signal<T> {
|
|
162
|
+
const signal = _createSignal(initialValue);
|
|
163
|
+
|
|
164
|
+
const getter: Accessor<T> = () => _get(signal);
|
|
165
|
+
|
|
166
|
+
const setter: Setter<T> = (valueOrUpdater) => {
|
|
167
|
+
if (typeof valueOrUpdater === "function") {
|
|
168
|
+
_update(signal, valueOrUpdater);
|
|
169
|
+
} else {
|
|
170
|
+
_set(signal, valueOrUpdater);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return [getter, setter];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Creates a reactive effect (SolidJS-style)
|
|
179
|
+
* Deferred execution via microtask - runs after rendering completes
|
|
180
|
+
*/
|
|
181
|
+
export function createEffect(fn: () => void): () => void {
|
|
182
|
+
return _effect(fn);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Creates a render effect (SolidJS-style)
|
|
187
|
+
* Immediate/synchronous execution - runs during rendering
|
|
188
|
+
*/
|
|
189
|
+
export function createRenderEffect(fn: () => void): () => void {
|
|
190
|
+
return _renderEffect(fn);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Creates a memoized computed value (SolidJS-style)
|
|
195
|
+
*/
|
|
196
|
+
export function createMemo<T>(fn: () => T): Accessor<T> {
|
|
197
|
+
return _createMemo(fn);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Runs a function without tracking dependencies (SolidJS-style alias)
|
|
202
|
+
*/
|
|
203
|
+
export { runUntracked as untrack };
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Explicit dependency tracking helper (SolidJS-style)
|
|
207
|
+
* Wraps a function to explicitly specify which signals to track
|
|
208
|
+
*
|
|
209
|
+
* @template T
|
|
210
|
+
* @template U
|
|
211
|
+
* @param {(() => T) | Array<() => any>} deps - Signal accessor(s) to track
|
|
212
|
+
* @param {(input: T, prevInput?: T, prevValue?: U) => U} fn - Function to run with dependency values
|
|
213
|
+
* @param {{ defer?: boolean }} [options] - Options (defer: don't run on initial)
|
|
214
|
+
* @returns {(prevValue?: U) => U | undefined}
|
|
215
|
+
*/
|
|
216
|
+
export function on(deps, fn, options = {}) {
|
|
217
|
+
const { defer = false } = options;
|
|
218
|
+
const isArray = Array.isArray(deps);
|
|
219
|
+
|
|
220
|
+
let prevInput;
|
|
221
|
+
let prevValue;
|
|
222
|
+
let isFirst = true;
|
|
223
|
+
|
|
224
|
+
return (injectedPrevValue) => {
|
|
225
|
+
// Get current dependency values
|
|
226
|
+
const input = isArray ? deps.map((d) => d()) : deps();
|
|
227
|
+
|
|
228
|
+
// Handle deferred execution
|
|
229
|
+
if (defer && isFirst) {
|
|
230
|
+
isFirst = false;
|
|
231
|
+
prevInput = input;
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Run the function with current and previous values
|
|
236
|
+
const result = fn(input, prevInput, injectedPrevValue ?? prevValue);
|
|
237
|
+
|
|
238
|
+
// Store for next run
|
|
239
|
+
prevInput = input;
|
|
240
|
+
prevValue = result;
|
|
241
|
+
isFirst = false;
|
|
242
|
+
|
|
243
|
+
return result;
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Merge multiple props objects, with later objects taking precedence (SolidJS-style)
|
|
249
|
+
* Event handlers and refs are merged, other props are overwritten
|
|
250
|
+
*
|
|
251
|
+
* @template T
|
|
252
|
+
* @param {...T} sources - Props objects to merge
|
|
253
|
+
* @returns {T}
|
|
254
|
+
*/
|
|
255
|
+
export function mergeProps(...sources) {
|
|
256
|
+
const result = {};
|
|
257
|
+
|
|
258
|
+
for (const source of sources) {
|
|
259
|
+
if (!source) continue;
|
|
260
|
+
|
|
261
|
+
for (const key of Object.keys(source)) {
|
|
262
|
+
const value = source[key];
|
|
263
|
+
|
|
264
|
+
// Merge event handlers (on* props)
|
|
265
|
+
if (key.startsWith("on") && typeof value === "function") {
|
|
266
|
+
const existing = result[key];
|
|
267
|
+
if (typeof existing === "function") {
|
|
268
|
+
result[key] = (...args) => {
|
|
269
|
+
existing(...args);
|
|
270
|
+
value(...args);
|
|
271
|
+
};
|
|
272
|
+
} else {
|
|
273
|
+
result[key] = value;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Merge ref callbacks
|
|
277
|
+
else if (key === "ref" && typeof value === "function") {
|
|
278
|
+
const existing = result[key];
|
|
279
|
+
if (typeof existing === "function") {
|
|
280
|
+
result[key] = (el) => {
|
|
281
|
+
existing(el);
|
|
282
|
+
value(el);
|
|
283
|
+
};
|
|
284
|
+
} else {
|
|
285
|
+
result[key] = value;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Merge class/className
|
|
289
|
+
else if (key === "class" || key === "className") {
|
|
290
|
+
const existing = result[key];
|
|
291
|
+
if (existing) {
|
|
292
|
+
result[key] = `${existing} ${value}`;
|
|
293
|
+
} else {
|
|
294
|
+
result[key] = value;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Merge style objects
|
|
298
|
+
else if (key === "style" && typeof value === "object" && typeof result[key] === "object") {
|
|
299
|
+
result[key] = { ...result[key], ...value };
|
|
300
|
+
}
|
|
301
|
+
// Default: overwrite
|
|
302
|
+
else {
|
|
303
|
+
result[key] = value;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Split props into multiple objects based on key lists (SolidJS-style)
|
|
313
|
+
*
|
|
314
|
+
* @template T
|
|
315
|
+
* @template K
|
|
316
|
+
* @param {T} props - Props object to split
|
|
317
|
+
* @param {...K[]} keys - Arrays of keys to extract
|
|
318
|
+
* @returns {[Pick<T, K>, Omit<T, K>]}
|
|
319
|
+
*/
|
|
320
|
+
export function splitProps(props, ...keys) {
|
|
321
|
+
const result = [];
|
|
322
|
+
const remaining = { ...props };
|
|
323
|
+
|
|
324
|
+
for (const keyList of keys) {
|
|
325
|
+
const extracted = {};
|
|
326
|
+
for (const key of keyList) {
|
|
327
|
+
if (key in remaining) {
|
|
328
|
+
extracted[key] = remaining[key];
|
|
329
|
+
delete remaining[key];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
result.push(extracted);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
result.push(remaining);
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Creates a resource for async data (SolidJS-style)
|
|
341
|
+
*/
|
|
342
|
+
export function createResource<T>(fetcher: (resolve: (v: T) => void, reject: (e: string) => void) => void): [ResourceAccessor<T>, { refetch: () => void }] {
|
|
343
|
+
const resource = _createResource(fetcher);
|
|
344
|
+
|
|
345
|
+
// Use resourceGet for tracking dependencies, stateValue for actual value
|
|
346
|
+
const accessor = () => stateValue(resourceGet(resource));
|
|
347
|
+
Object.defineProperties(accessor, {
|
|
348
|
+
loading: { get: () => resourceIsPending(resource) },
|
|
349
|
+
error: { get: () => resourceError(resource) },
|
|
350
|
+
state: {
|
|
351
|
+
get: () => {
|
|
352
|
+
if (resourceIsPending(resource)) return "pending";
|
|
353
|
+
if (resourceIsSuccess(resource)) return "ready";
|
|
354
|
+
if (resourceIsFailure(resource)) return "errored";
|
|
355
|
+
return "unresolved";
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
latest: { get: () => resourcePeek(resource) },
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
return [accessor, { refetch: () => resourceRefetch(resource) }];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Creates a deferred resource (SolidJS-style)
|
|
366
|
+
*/
|
|
367
|
+
export function createDeferred<T>(): [ResourceAccessor<T>, (value: T) => void, (error: string) => void] {
|
|
368
|
+
const result = _createDeferred();
|
|
369
|
+
const resource = result._0;
|
|
370
|
+
const resolve = result._1;
|
|
371
|
+
const reject = result._2;
|
|
372
|
+
|
|
373
|
+
// Use resourceGet for tracking dependencies, stateValue for actual value
|
|
374
|
+
const accessor = () => stateValue(resourceGet(resource));
|
|
375
|
+
Object.defineProperties(accessor, {
|
|
376
|
+
loading: { get: () => resourceIsPending(resource) },
|
|
377
|
+
error: { get: () => resourceError(resource) },
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
return [accessor, resolve, reject];
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Debounces a signal (returns SolidJS-style signal)
|
|
385
|
+
*/
|
|
386
|
+
export function debounced<T>(signal: Signal<T>, delayMs: number): Signal<T> {
|
|
387
|
+
const [getter] = signal;
|
|
388
|
+
const innerSignal = _createSignal(getter());
|
|
389
|
+
const debouncedInner = _debounced(innerSignal, delayMs);
|
|
390
|
+
return [() => _get(debouncedInner), (v) => _set(innerSignal, v)];
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// ============================================================================
|
|
394
|
+
// SolidJS-compatible Component API
|
|
395
|
+
// ============================================================================
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Resolves child content: if it's a function, calls it with args.
|
|
399
|
+
* If the result is an array, wraps it in a fragment.
|
|
400
|
+
*/
|
|
401
|
+
function resolveChild(value: any, ...args: any[]): any {
|
|
402
|
+
if (typeof value === "function") {
|
|
403
|
+
const result = value(...args);
|
|
404
|
+
return Array.isArray(result) ? fragment(result) : result;
|
|
405
|
+
}
|
|
406
|
+
return Array.isArray(value) ? fragment(value) : value;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* JSX-compatible Fragment component.
|
|
411
|
+
* Wraps children in a LunaNode fragment for use in JSX.
|
|
412
|
+
* Also supports direct array call for backwards compatibility: Fragment([...])
|
|
413
|
+
*/
|
|
414
|
+
export function Fragment(propsOrChildren: { children?: any } | any[]): any {
|
|
415
|
+
// Support direct array call: Fragment([child1, child2])
|
|
416
|
+
if (Array.isArray(propsOrChildren)) {
|
|
417
|
+
return fragment(propsOrChildren);
|
|
418
|
+
}
|
|
419
|
+
// JSX style: Fragment({ children })
|
|
420
|
+
const { children } = propsOrChildren || {};
|
|
421
|
+
if (!children) return fragment([]);
|
|
422
|
+
if (Array.isArray(children)) {
|
|
423
|
+
return fragment(children);
|
|
424
|
+
}
|
|
425
|
+
return fragment([children]);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* For component for list rendering (SolidJS-style)
|
|
430
|
+
*/
|
|
431
|
+
export function For<T>(props: ForProps<T>): any {
|
|
432
|
+
const { each, fallback, children } = props;
|
|
433
|
+
|
|
434
|
+
// If each is not provided or is falsy, show fallback
|
|
435
|
+
if (!each) {
|
|
436
|
+
return fallback ?? null;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// each should be a getter function
|
|
440
|
+
const getter = typeof each === "function" ? each : () => each;
|
|
441
|
+
|
|
442
|
+
return forEach(getter, (item, index) => {
|
|
443
|
+
// Wrap index in a getter for SolidJS compatibility
|
|
444
|
+
return children(item, () => index);
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Show component for conditional rendering (SolidJS-style)
|
|
450
|
+
*/
|
|
451
|
+
export function Show<T>(props: ShowProps<T>): any {
|
|
452
|
+
const { when, children } = props;
|
|
453
|
+
// TODO: fallback support requires MoonBit-side changes
|
|
454
|
+
|
|
455
|
+
// Convert when to a getter if it's not already
|
|
456
|
+
const condition = typeof when === "function" ? when : () => when;
|
|
457
|
+
|
|
458
|
+
return show(
|
|
459
|
+
() => Boolean(condition()),
|
|
460
|
+
() => resolveChild(children, condition())
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Index component for index-based list rendering (SolidJS-style)
|
|
466
|
+
*/
|
|
467
|
+
export function Index<T>(props: IndexProps<T>): any {
|
|
468
|
+
const { each, fallback, children } = props;
|
|
469
|
+
|
|
470
|
+
if (!each) {
|
|
471
|
+
return fallback ?? null;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const getter = typeof each === "function" ? each : () => each;
|
|
475
|
+
const items = getter();
|
|
476
|
+
|
|
477
|
+
if (items.length === 0 && fallback) {
|
|
478
|
+
return fallback;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Use index_each from MoonBit if available, otherwise simulate with forEach
|
|
482
|
+
// For now, we'll use forEach with index-based tracking
|
|
483
|
+
return forEach(getter, (_item, index) => {
|
|
484
|
+
// Provide item as a getter for reactivity at that index
|
|
485
|
+
const itemGetter = () => getter()[index];
|
|
486
|
+
return children(itemGetter, index);
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Provider component for Context (SolidJS-style)
|
|
492
|
+
* Children must be a function: {() => <Child />}
|
|
493
|
+
*/
|
|
494
|
+
export function Provider<T>(props: ProviderProps<T>): any {
|
|
495
|
+
const { context, value, children } = props;
|
|
496
|
+
|
|
497
|
+
return provide(context, value, children);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Switch component for conditional rendering with multiple branches (SolidJS-style)
|
|
502
|
+
* Reactively updates when conditions change.
|
|
503
|
+
*/
|
|
504
|
+
export function Switch(props: SwitchProps): any {
|
|
505
|
+
const { fallback, children } = props;
|
|
506
|
+
|
|
507
|
+
// children should be Match components, each with { when, children }
|
|
508
|
+
// Since we don't have compile-time JSX, children is an array of Match results
|
|
509
|
+
|
|
510
|
+
// Normalize children to a flat array
|
|
511
|
+
let childArray: any[];
|
|
512
|
+
if (!children) {
|
|
513
|
+
childArray = [];
|
|
514
|
+
} else if (!Array.isArray(children)) {
|
|
515
|
+
childArray = [children];
|
|
516
|
+
} else {
|
|
517
|
+
// Flatten nested arrays (JSX can nest them)
|
|
518
|
+
childArray = children.flat();
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Filter to only Match components
|
|
522
|
+
const matches = childArray.filter((child) => child && child.__isMatch);
|
|
523
|
+
|
|
524
|
+
if (matches.length === 0) {
|
|
525
|
+
return fallback ?? null;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Track which Match index is currently active (-1 = none, show fallback)
|
|
529
|
+
const matchIndex = createMemo(() => {
|
|
530
|
+
for (let i = 0; i < matches.length; i++) {
|
|
531
|
+
const match = matches[i];
|
|
532
|
+
if (match.when()) {
|
|
533
|
+
return i;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return -1;
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
// Create a show for each Match (only the active one will render)
|
|
540
|
+
const nodes: any[] = [];
|
|
541
|
+
for (let i = 0; i < matches.length; i++) {
|
|
542
|
+
const match = matches[i];
|
|
543
|
+
const idx = i; // Capture index for closure
|
|
544
|
+
nodes.push(
|
|
545
|
+
show(
|
|
546
|
+
() => matchIndex() === idx,
|
|
547
|
+
match.children // Match.children is already a resolved function
|
|
548
|
+
)
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Add fallback (renders when no match)
|
|
553
|
+
if (fallback) {
|
|
554
|
+
nodes.push(
|
|
555
|
+
show(
|
|
556
|
+
() => matchIndex() === -1,
|
|
557
|
+
() => resolveChild(fallback)
|
|
558
|
+
)
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return fragment(nodes);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Match component for use inside Switch (SolidJS-style)
|
|
567
|
+
*/
|
|
568
|
+
export function Match<T>(props: MatchProps<T>): { __isMatch: true; when: () => boolean; condition: () => T; children: () => any } {
|
|
569
|
+
const { when, children } = props;
|
|
570
|
+
const condition = typeof when === "function" ? when : () => when;
|
|
571
|
+
|
|
572
|
+
return {
|
|
573
|
+
__isMatch: true,
|
|
574
|
+
when: () => Boolean(condition()),
|
|
575
|
+
condition,
|
|
576
|
+
// Wrap children in a function that resolves with condition value
|
|
577
|
+
children: () => resolveChild(children, condition()),
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Portal component for rendering outside the component tree (SolidJS-style)
|
|
583
|
+
* Children must be a function: {() => <Child />}
|
|
584
|
+
*/
|
|
585
|
+
export function Portal(props: PortalProps): any {
|
|
586
|
+
const { mount, useShadow = false, children } = props;
|
|
587
|
+
|
|
588
|
+
// Resolve children (must be a function)
|
|
589
|
+
const resolvedChildren = [children()];
|
|
590
|
+
|
|
591
|
+
// Handle different mount targets
|
|
592
|
+
if (useShadow) {
|
|
593
|
+
if (typeof mount === "string") {
|
|
594
|
+
const target = document.querySelector(mount);
|
|
595
|
+
if (target) {
|
|
596
|
+
return portalToElementWithShadow(target, resolvedChildren);
|
|
597
|
+
}
|
|
598
|
+
} else if (mount) {
|
|
599
|
+
return portalToElementWithShadow(mount, resolvedChildren);
|
|
600
|
+
}
|
|
601
|
+
return portalWithShadow(resolvedChildren);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (typeof mount === "string") {
|
|
605
|
+
return portalToSelector(mount, resolvedChildren);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (mount) {
|
|
609
|
+
// For custom element mount, use selector approach
|
|
610
|
+
return portalToBody(resolvedChildren);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return portalToBody(resolvedChildren);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// ============================================================================
|
|
617
|
+
// Store API (SolidJS-style)
|
|
618
|
+
// ============================================================================
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Creates a reactive store with nested property tracking (SolidJS-style)
|
|
622
|
+
*/
|
|
623
|
+
export function createStore<T extends object>(initialValue: T): [T, SetStoreFunction<T>] {
|
|
624
|
+
// Store signals for each path
|
|
625
|
+
const signals = new Map();
|
|
626
|
+
// Deep clone the initial value to avoid mutation issues
|
|
627
|
+
const store = structuredClone(initialValue);
|
|
628
|
+
|
|
629
|
+
// Get or create a signal for a path
|
|
630
|
+
function getSignal(path) {
|
|
631
|
+
const key = path.join(".");
|
|
632
|
+
if (!signals.has(key)) {
|
|
633
|
+
const value = getValueAtPath(store, path);
|
|
634
|
+
signals.set(key, _createSignal(value));
|
|
635
|
+
}
|
|
636
|
+
return signals.get(key);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Get value at a path in an object
|
|
640
|
+
function getValueAtPath(obj, path) {
|
|
641
|
+
let current = obj;
|
|
642
|
+
for (const key of path) {
|
|
643
|
+
if (current == null) return undefined;
|
|
644
|
+
current = current[key];
|
|
645
|
+
}
|
|
646
|
+
return current;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Set value at a path in an object
|
|
650
|
+
function setValueAtPath(obj, path, value) {
|
|
651
|
+
if (path.length === 0) return;
|
|
652
|
+
let current = obj;
|
|
653
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
654
|
+
const key = path[i];
|
|
655
|
+
if (current[key] == null) {
|
|
656
|
+
// Preserve array vs object based on next key
|
|
657
|
+
const nextKey = path[i + 1];
|
|
658
|
+
current[key] = typeof nextKey === "number" || /^\d+$/.test(nextKey) ? [] : {};
|
|
659
|
+
}
|
|
660
|
+
current = current[key];
|
|
661
|
+
}
|
|
662
|
+
current[path[path.length - 1]] = value;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Notify all signals that might be affected by a path change
|
|
666
|
+
// Uses batch to ensure effects only run once even if multiple signals are updated
|
|
667
|
+
function notifyPath(path) {
|
|
668
|
+
const pathStr = path.join(".");
|
|
669
|
+
|
|
670
|
+
batchStart();
|
|
671
|
+
try {
|
|
672
|
+
for (const [key, signal] of signals.entries()) {
|
|
673
|
+
// Only notify:
|
|
674
|
+
// 1. The exact path that changed
|
|
675
|
+
// 2. Child paths (paths that start with the changed path)
|
|
676
|
+
// Do NOT notify parent paths - they didn't change (object reference is same)
|
|
677
|
+
if (key === pathStr || key.startsWith(pathStr + ".")) {
|
|
678
|
+
const signalPath = key.split(".");
|
|
679
|
+
const newValue = getValueAtPath(store, signalPath);
|
|
680
|
+
_set(signal, newValue);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
} finally {
|
|
684
|
+
batchEnd();
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Create a proxy for reactive access
|
|
689
|
+
function createProxy(target, path = []) {
|
|
690
|
+
if (target === null || typeof target !== "object") {
|
|
691
|
+
return target;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return new Proxy(target, {
|
|
695
|
+
get(obj, prop) {
|
|
696
|
+
if (typeof prop === "symbol") {
|
|
697
|
+
return obj[prop];
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const currentPath = [...path, prop];
|
|
701
|
+
const signal = getSignal(currentPath);
|
|
702
|
+
// Track dependency by reading the signal
|
|
703
|
+
_get(signal);
|
|
704
|
+
|
|
705
|
+
const value = obj[prop];
|
|
706
|
+
if (value !== null && typeof value === "object") {
|
|
707
|
+
return createProxy(value, currentPath);
|
|
708
|
+
}
|
|
709
|
+
return value;
|
|
710
|
+
},
|
|
711
|
+
|
|
712
|
+
set(obj, prop, value) {
|
|
713
|
+
// Direct assignment on proxy - update store and notify
|
|
714
|
+
const currentPath = [...path, prop];
|
|
715
|
+
obj[prop] = value;
|
|
716
|
+
notifyPath(currentPath);
|
|
717
|
+
return true;
|
|
718
|
+
},
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// setState function supporting path-based updates
|
|
723
|
+
function setState(...args) {
|
|
724
|
+
if (args.length === 0) return;
|
|
725
|
+
|
|
726
|
+
// Collect path segments and final value/updater
|
|
727
|
+
const path = [];
|
|
728
|
+
let i = 0;
|
|
729
|
+
|
|
730
|
+
// Collect string path segments
|
|
731
|
+
while (i < args.length - 1 && typeof args[i] === "string") {
|
|
732
|
+
path.push(args[i]);
|
|
733
|
+
i++;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const valueOrUpdater = args[i];
|
|
737
|
+
|
|
738
|
+
// If no path, treat as root update
|
|
739
|
+
if (path.length === 0 && typeof valueOrUpdater === "object" && valueOrUpdater !== null) {
|
|
740
|
+
// Merge at root
|
|
741
|
+
Object.assign(store, valueOrUpdater);
|
|
742
|
+
// Notify all signals
|
|
743
|
+
for (const [key, signal] of signals.entries()) {
|
|
744
|
+
const signalPath = key.split(".");
|
|
745
|
+
const newValue = getValueAtPath(store, signalPath);
|
|
746
|
+
_set(signal, newValue);
|
|
747
|
+
}
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Get current value at path
|
|
752
|
+
const currentValue = getValueAtPath(store, path);
|
|
753
|
+
|
|
754
|
+
// Determine new value
|
|
755
|
+
let newValue;
|
|
756
|
+
if (typeof valueOrUpdater === "function") {
|
|
757
|
+
newValue = valueOrUpdater(currentValue);
|
|
758
|
+
} else if (
|
|
759
|
+
Array.isArray(valueOrUpdater)
|
|
760
|
+
) {
|
|
761
|
+
// Arrays are replaced, not merged
|
|
762
|
+
newValue = valueOrUpdater;
|
|
763
|
+
} else if (
|
|
764
|
+
typeof valueOrUpdater === "object" &&
|
|
765
|
+
valueOrUpdater !== null &&
|
|
766
|
+
typeof currentValue === "object" &&
|
|
767
|
+
currentValue !== null &&
|
|
768
|
+
!Array.isArray(currentValue)
|
|
769
|
+
) {
|
|
770
|
+
// Merge objects (but not arrays)
|
|
771
|
+
newValue = { ...currentValue, ...valueOrUpdater };
|
|
772
|
+
} else {
|
|
773
|
+
newValue = valueOrUpdater;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Set value in store
|
|
777
|
+
setValueAtPath(store, path, newValue);
|
|
778
|
+
|
|
779
|
+
// Notify affected signals
|
|
780
|
+
notifyPath(path);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const proxy = createProxy(store);
|
|
784
|
+
return [proxy, setState];
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Produce helper for immer-style mutations (SolidJS-style)
|
|
789
|
+
* @template T
|
|
790
|
+
* @param {(draft: T) => void} fn - Mutation function
|
|
791
|
+
* @returns {(state: T) => T} - Function that applies mutations to a copy
|
|
792
|
+
*/
|
|
793
|
+
export function produce(fn) {
|
|
794
|
+
return (state) => {
|
|
795
|
+
const draft = structuredClone(state);
|
|
796
|
+
fn(draft);
|
|
797
|
+
return draft;
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Reconcile helper for efficient array/object updates (SolidJS-style)
|
|
803
|
+
* @template T
|
|
804
|
+
* @param {T} value - New value to reconcile
|
|
805
|
+
* @returns {(state: T) => T} - Function that returns the new value
|
|
806
|
+
*/
|
|
807
|
+
export function reconcile(value) {
|
|
808
|
+
return () => value;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Re-export unchanged APIs
|
|
812
|
+
export {
|
|
813
|
+
// Batch control
|
|
814
|
+
batchStart,
|
|
815
|
+
batchEnd,
|
|
816
|
+
batch,
|
|
817
|
+
// Cleanup
|
|
818
|
+
onCleanup,
|
|
819
|
+
// Owner/Root
|
|
820
|
+
createRoot,
|
|
821
|
+
getOwner,
|
|
822
|
+
runWithOwner,
|
|
823
|
+
hasOwner,
|
|
824
|
+
onMount,
|
|
825
|
+
// DOM API
|
|
826
|
+
text,
|
|
827
|
+
textDyn,
|
|
828
|
+
render,
|
|
829
|
+
mount,
|
|
830
|
+
show,
|
|
831
|
+
jsx,
|
|
832
|
+
jsxs,
|
|
833
|
+
fragment, // Low-level MoonBit fragment function
|
|
834
|
+
createElement,
|
|
835
|
+
createElementNs,
|
|
836
|
+
svgNs,
|
|
837
|
+
mathmlNs,
|
|
838
|
+
events,
|
|
839
|
+
forEach,
|
|
840
|
+
// Route definitions
|
|
841
|
+
routePage,
|
|
842
|
+
routePageTitled,
|
|
843
|
+
routePageFull,
|
|
844
|
+
createRouter,
|
|
845
|
+
routerNavigate,
|
|
846
|
+
routerReplace,
|
|
847
|
+
routerGetPath,
|
|
848
|
+
routerGetMatch,
|
|
849
|
+
routerGetBase,
|
|
850
|
+
// Context API
|
|
851
|
+
createContext,
|
|
852
|
+
provide,
|
|
853
|
+
useContext,
|
|
854
|
+
// Resource helpers (for direct access)
|
|
855
|
+
resourceGet,
|
|
856
|
+
resourcePeek,
|
|
857
|
+
resourceRefetch,
|
|
858
|
+
resourceIsPending,
|
|
859
|
+
resourceIsSuccess,
|
|
860
|
+
resourceIsFailure,
|
|
861
|
+
resourceValue,
|
|
862
|
+
resourceError,
|
|
863
|
+
stateIsPending,
|
|
864
|
+
stateIsSuccess,
|
|
865
|
+
stateIsFailure,
|
|
866
|
+
stateValue,
|
|
867
|
+
stateError,
|
|
868
|
+
// Portal API (low-level)
|
|
869
|
+
portalToBody,
|
|
870
|
+
portalToSelector,
|
|
871
|
+
portalWithShadow,
|
|
872
|
+
portalToElementWithShadow,
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
// Legacy API exports (for backwards compatibility during migration)
|
|
876
|
+
export {
|
|
877
|
+
_get as get,
|
|
878
|
+
_set as set,
|
|
879
|
+
_update as update,
|
|
880
|
+
_peek as peek,
|
|
881
|
+
_subscribe as subscribe,
|
|
882
|
+
_map as map,
|
|
883
|
+
_combine as combine,
|
|
884
|
+
_effect as effect,
|
|
885
|
+
_renderEffect as renderEffect,
|
|
886
|
+
runUntracked,
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
// Event utilities (tree-shakeable, also available via "@luna_ui/luna/event-utils")
|
|
890
|
+
export * from "./event-utils";
|