@luna_ui/luna 0.11.0 → 0.20.0

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