@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
@@ -1,405 +0,0 @@
1
- /**
2
- * Integration tests for nested components, ForEach, and provide
3
- * These tests document current Luna API behavior and highlight differences from SolidJS
4
- */
5
- import { describe, test, expect, beforeEach } from "vitest";
6
- import {
7
- // Signal API (SolidJS-style)
8
- createSignal,
9
- createEffect,
10
- createRoot,
11
- // Context API
12
- createContext,
13
- provide,
14
- useContext,
15
- // DOM API
16
- createElement,
17
- text,
18
- render,
19
- mount,
20
- forEach,
21
- show,
22
- } from "../src/index";
23
-
24
- // MoonBit AttrValue constructors
25
- const AttrValue = {
26
- Static: (value: string) => ({ $tag: 0, _0: value }),
27
- Dynamic: (getter: () => string) => ({ $tag: 1, _0: getter }),
28
- };
29
-
30
- function attr(name: string, value: unknown) {
31
- return { _0: name, _1: value };
32
- }
33
-
34
- describe("Integration: Nested Components with Context", () => {
35
- let container: HTMLElement;
36
-
37
- beforeEach(() => {
38
- container = document.createElement("div");
39
- document.body.appendChild(container);
40
- });
41
-
42
- describe("Context across component boundaries", () => {
43
- test("context flows through nested function components", () => {
44
- const themeCtx = createContext("light");
45
- const values: string[] = [];
46
-
47
- // Simulate nested component structure
48
- const GrandChild = () => {
49
- values.push(`grandchild: ${useContext(themeCtx)}`);
50
- return text(useContext(themeCtx));
51
- };
52
-
53
- const Child = () => {
54
- values.push(`child: ${useContext(themeCtx)}`);
55
- return createElement("span", [], [GrandChild()]);
56
- };
57
-
58
- const Parent = () => {
59
- values.push(`parent: ${useContext(themeCtx)}`);
60
- return createElement("div", [], [Child()]);
61
- };
62
-
63
- // Provide dark theme at root
64
- provide(themeCtx, "dark", () => {
65
- render(container, Parent());
66
- });
67
-
68
- expect(values).toEqual([
69
- "parent: dark",
70
- "child: dark",
71
- "grandchild: dark",
72
- ]);
73
- expect(container.textContent).toBe("dark");
74
- });
75
-
76
- test("nested provide overrides parent context", () => {
77
- const themeCtx = createContext("light");
78
- const values: string[] = [];
79
-
80
- const Inner = () => {
81
- values.push(useContext(themeCtx));
82
- return text(useContext(themeCtx));
83
- };
84
-
85
- const Middle = () => {
86
- // Override context for children
87
- return provide(themeCtx, "blue", () => {
88
- values.push(`middle sees: ${useContext(themeCtx)}`);
89
- return createElement("div", [], [Inner()]);
90
- });
91
- };
92
-
93
- const Outer = () => {
94
- values.push(`outer sees: ${useContext(themeCtx)}`);
95
- return createElement("div", [], [Middle()]);
96
- };
97
-
98
- provide(themeCtx, "dark", () => {
99
- render(container, Outer());
100
- });
101
-
102
- expect(values).toEqual([
103
- "outer sees: dark",
104
- "middle sees: blue",
105
- "blue",
106
- ]);
107
- });
108
-
109
- test("multiple contexts work independently", () => {
110
- const themeCtx = createContext("light");
111
- const langCtx = createContext("en");
112
- const sizeCtx = createContext("medium");
113
-
114
- let capturedTheme: string;
115
- let capturedLang: string;
116
- let capturedSize: string;
117
-
118
- const DeepComponent = () => {
119
- capturedTheme = useContext(themeCtx);
120
- capturedLang = useContext(langCtx);
121
- capturedSize = useContext(sizeCtx);
122
- return text("deep");
123
- };
124
-
125
- provide(themeCtx, "dark", () => {
126
- provide(langCtx, "ja", () => {
127
- // sizeCtx not provided, should use default
128
- render(container, DeepComponent());
129
- });
130
- });
131
-
132
- expect(capturedTheme!).toBe("dark");
133
- expect(capturedLang!).toBe("ja");
134
- expect(capturedSize!).toBe("medium"); // default
135
- });
136
- });
137
-
138
- describe("ForEach with reactive updates", () => {
139
- test("forEach renders initial list", () => {
140
- const [items] = createSignal(["a", "b", "c"]);
141
-
142
- const List = () =>
143
- createElement(
144
- "ul",
145
- [],
146
- [
147
- forEach(items, (item, index) =>
148
- createElement(
149
- "li",
150
- [attr("data-index", AttrValue.Static(String(index)))],
151
- [text(item)]
152
- )
153
- ),
154
- ]
155
- );
156
-
157
- render(container, List());
158
-
159
- const lis = container.querySelectorAll("li");
160
- expect(lis.length).toBe(3);
161
- expect(lis[0].textContent).toBe("a");
162
- expect(lis[1].textContent).toBe("b");
163
- expect(lis[2].textContent).toBe("c");
164
- });
165
-
166
- test("forEach updates when signal changes", () => {
167
- const [items, setItems] = createSignal(["x", "y"]);
168
-
169
- createRoot((dispose) => {
170
- const List = () =>
171
- createElement("ul", [], [
172
- forEach(items, (item) => createElement("li", [], [text(item)])),
173
- ]);
174
-
175
- render(container, List());
176
-
177
- expect(container.querySelectorAll("li").length).toBe(2);
178
-
179
- // Update the list
180
- setItems(["x", "y", "z"]);
181
-
182
- expect(container.querySelectorAll("li").length).toBe(3);
183
- expect(container.querySelectorAll("li")[2].textContent).toBe("z");
184
-
185
- dispose();
186
- });
187
- });
188
-
189
- test("forEach with object items", () => {
190
- interface User {
191
- id: number;
192
- name: string;
193
- }
194
-
195
- const [users] = createSignal<User[]>([
196
- { id: 1, name: "Alice" },
197
- { id: 2, name: "Bob" },
198
- ]);
199
-
200
- createRoot((dispose) => {
201
- const UserList = () =>
202
- createElement("div", [], [
203
- forEach(users, (user) =>
204
- createElement(
205
- "div",
206
- [attr("data-id", AttrValue.Static(String(user.id)))],
207
- [text(user.name)]
208
- )
209
- ),
210
- ]);
211
-
212
- render(container, UserList());
213
-
214
- const divs = container.querySelectorAll("[data-id]");
215
- expect(divs.length).toBe(2);
216
- expect(divs[0].textContent).toBe("Alice");
217
- expect(divs[1].textContent).toBe("Bob");
218
-
219
- dispose();
220
- });
221
- });
222
- });
223
-
224
- describe("Context + ForEach integration", () => {
225
- test("forEach items can access context", () => {
226
- const prefixCtx = createContext("");
227
- const [items] = createSignal(["one", "two", "three"]);
228
- const renderedTexts: string[] = [];
229
-
230
- const ItemComponent = (item: string) => {
231
- const prefix = useContext(prefixCtx);
232
- const fullText = `${prefix}${item}`;
233
- renderedTexts.push(fullText);
234
- return createElement("span", [], [text(fullText)]);
235
- };
236
-
237
- provide(prefixCtx, "item-", () => {
238
- const List = () =>
239
- createElement("div", [], [
240
- forEach(items, (item) => ItemComponent(item)),
241
- ]);
242
-
243
- render(container, List());
244
- });
245
-
246
- expect(renderedTexts).toEqual(["item-one", "item-two", "item-three"]);
247
- });
248
- });
249
-
250
- describe("Show (conditional rendering)", () => {
251
- // NOTE: show() has a known limitation - it doesn't render initially when true
252
- // because the effect runs before the placeholder is in the DOM.
253
- // This is a design difference from SolidJS's <Show> component.
254
-
255
- test("show hides when condition is false", () => {
256
- const [visible] = createSignal(false);
257
-
258
- const node = show(visible, () =>
259
- createElement("span", [], [text("visible")])
260
- );
261
-
262
- mount(container, node);
263
-
264
- // When false, show renders a placeholder (comment node)
265
- expect(container.querySelector("span")).toBeNull();
266
- });
267
-
268
- test("show toggles from false to true", () => {
269
- // Start with false, then toggle to true
270
- const [visible, setVisible] = createSignal(false);
271
-
272
- const node = show(visible, () =>
273
- createElement("span", [], [text("content")])
274
- );
275
-
276
- mount(container, node);
277
-
278
- expect(container.querySelector("span")).toBeNull();
279
-
280
- // Toggle to true - this should work because placeholder is now in DOM
281
- setVisible(true);
282
- expect(container.querySelector("span")).not.toBeNull();
283
-
284
- // Toggle back to false
285
- setVisible(false);
286
- expect(container.querySelector("span")).toBeNull();
287
- });
288
-
289
- test("show renders when initial condition is true", () => {
290
- const [visible] = createSignal(true);
291
-
292
- const node = show(visible, () =>
293
- createElement("span", [], [text("visible")])
294
- );
295
-
296
- mount(container, node);
297
-
298
- expect(container.querySelector("span")).not.toBeNull();
299
- });
300
- });
301
-
302
- describe("Complex nested scenario", () => {
303
- /**
304
- * Luna's provide() is now Owner-based (component-tree-scoped).
305
- *
306
- * Context values are associated with Owners (reactive scopes).
307
- * When show() or forEach() render later, they inherit context from
308
- * the Owner that was active when they were created.
309
- *
310
- * This matches SolidJS's <Provider> behavior.
311
- */
312
-
313
- test("show and forEach inherit context from Owner (fixed)", () => {
314
- const themeCtx = createContext("light"); // default is "light"
315
- const [showList, setShowList] = createSignal(false);
316
- const [items, setItems] = createSignal(["A", "B"]);
317
-
318
- const ListItem = (item: string) => {
319
- const theme = useContext(themeCtx);
320
- return createElement(
321
- "li",
322
- [attr("class", AttrValue.Static(`item-${theme}`))],
323
- [text(item)]
324
- );
325
- };
326
-
327
- // Context is Owner-based: show/forEach must be created inside provide scope
328
- // to capture the Owner with the context value
329
- provide(themeCtx, "dark", () => {
330
- const listNode = show(showList, () =>
331
- createElement("ul", [], [forEach(items, (item) => ListItem(item))])
332
- );
333
- mount(container, listNode);
334
- });
335
-
336
- // Initially hidden
337
- expect(container.querySelector("ul")).toBeNull();
338
-
339
- // Toggle to visible - context should be "dark" (inherited from Owner)
340
- setShowList(true);
341
- let lis = container.querySelectorAll("li");
342
- expect(lis.length).toBe(2);
343
- // Context is "dark" because Owner-based context persists
344
- expect(lis[0].className).toBe("item-dark");
345
-
346
- // Toggle visibility
347
- setShowList(false);
348
- expect(container.querySelector("ul")).toBeNull();
349
-
350
- setShowList(true);
351
- lis = container.querySelectorAll("li");
352
- expect(lis.length).toBe(2);
353
- // Still "dark" after re-render
354
- expect(lis[0].className).toBe("item-dark");
355
-
356
- // Update items - new items should also get "dark" context
357
- setItems(["A", "B", "C"]);
358
- lis = container.querySelectorAll("li");
359
- expect(lis.length).toBe(3);
360
- expect(lis[2].className).toBe("item-dark");
361
- });
362
-
363
- test("context works with immediate rendering (non-deferred)", () => {
364
- const themeCtx = createContext("light");
365
-
366
- // Context works when rendering is synchronous
367
- provide(themeCtx, "dark", () => {
368
- const theme = useContext(themeCtx);
369
- const node = createElement(
370
- "div",
371
- [attr("class", AttrValue.Static(`theme-${theme}`))],
372
- [text(theme)]
373
- );
374
- mount(container, node);
375
- });
376
-
377
- const div = container.querySelector("div");
378
- expect(div?.className).toBe("theme-dark");
379
- expect(div?.textContent).toBe("dark");
380
- });
381
-
382
- test("forEach renders correctly without show (initial items)", () => {
383
- // This test shows that forEach initial render works,
384
- // but context is still function-scoped
385
- const [items, setItems] = createSignal(["A", "B"]);
386
-
387
- const listNode = createElement("ul", [], [
388
- forEach(items, (item) => createElement("li", [], [text(item)])),
389
- ]);
390
-
391
- mount(container, listNode);
392
-
393
- let lis = container.querySelectorAll("li");
394
- expect(lis.length).toBe(2);
395
- expect(lis[0].textContent).toBe("A");
396
- expect(lis[1].textContent).toBe("B");
397
-
398
- // Update items
399
- setItems(["X", "Y", "Z"]);
400
- lis = container.querySelectorAll("li");
401
- expect(lis.length).toBe(3);
402
- expect(lis[2].textContent).toBe("Z");
403
- });
404
- });
405
- });
@@ -1,176 +0,0 @@
1
- /**
2
- * Issue #11: Show component does not re-render when signal changes from null to truthy value
3
- *
4
- * This test reproduces the exact scenario described in the issue.
5
- * Now using SolidJS-compatible accessor pattern: children receives an accessor function.
6
- */
7
-
8
- import { describe, test, expect, beforeEach, afterEach } from "vitest";
9
- import {
10
- createSignal,
11
- createRoot,
12
- mount,
13
- render,
14
- Show,
15
- show,
16
- createElement,
17
- text,
18
- } from "../src/index";
19
-
20
- describe("Issue #11: Show with null to truthy transition", () => {
21
- let container: HTMLElement;
22
-
23
- beforeEach(() => {
24
- container = document.createElement("div");
25
- document.body.appendChild(container);
26
- });
27
-
28
- afterEach(() => {
29
- container.remove();
30
- });
31
-
32
- test("Show updates when signal changes from null to truthy (pattern 1: direct signal)", () => {
33
- const [data, setData] = createSignal<string | null>(null);
34
-
35
- const node = Show({
36
- when: data,
37
- // SolidJS-style: children receives accessor, call with ()
38
- children: (value: () => string) => createElement("p", [], [text(`Data: ${value()}`)]),
39
- });
40
-
41
- mount(container, node);
42
-
43
- // Initial state: data is null, nothing should be visible
44
- expect(container.querySelector("p")).toBeNull();
45
-
46
- // Update signal to truthy value
47
- setData("loaded data");
48
-
49
- // After update: content should be visible
50
- const p = container.querySelector("p");
51
- expect(p).not.toBeNull();
52
- expect(p?.textContent).toBe("Data: loaded data");
53
- });
54
-
55
- test("Show updates when signal changes from null to truthy (pattern 2: getter function)", () => {
56
- const [data, setData] = createSignal<string | null>(null);
57
-
58
- const node = Show({
59
- when: () => data(),
60
- // SolidJS-style: children receives accessor, call with ()
61
- children: (value: () => string) => createElement("p", [], [text(`Data: ${value()}`)]),
62
- });
63
-
64
- mount(container, node);
65
-
66
- // Initial state: data is null, nothing should be visible
67
- expect(container.querySelector("p")).toBeNull();
68
-
69
- // Update signal to truthy value
70
- setData("loaded data");
71
-
72
- // After update: content should be visible
73
- const p = container.querySelector("p");
74
- expect(p).not.toBeNull();
75
- expect(p?.textContent).toBe("Data: loaded data");
76
- });
77
-
78
- test("Show updates with async delay (simulating real use case)", async () => {
79
- const [data, setData] = createSignal<string | null>(null);
80
-
81
- const node = Show({
82
- when: data,
83
- // SolidJS-style: children receives accessor, call with ()
84
- children: (value: () => string) => createElement("p", [], [text(`Data: ${value()}`)]),
85
- });
86
-
87
- mount(container, node);
88
-
89
- // Initial state
90
- expect(container.querySelector("p")).toBeNull();
91
-
92
- // Simulate async data loading
93
- await new Promise<void>((resolve) => {
94
- setTimeout(() => {
95
- setData("loaded data");
96
- resolve();
97
- }, 50);
98
- });
99
-
100
- // After async update
101
- const p = container.querySelector("p");
102
- expect(p).not.toBeNull();
103
- expect(p?.textContent).toBe("Data: loaded data");
104
- });
105
-
106
- test("Low-level show() works with null to truthy", () => {
107
- const [data, setData] = createSignal<string | null>(null);
108
-
109
- const node = show(
110
- () => data() !== null,
111
- () => createElement("p", [], [text(`Data: ${data()}`)]),
112
- );
113
-
114
- mount(container, node);
115
-
116
- // Initial state
117
- expect(container.querySelector("p")).toBeNull();
118
-
119
- // Update
120
- setData("loaded data");
121
-
122
- // After update
123
- const p = container.querySelector("p");
124
- expect(p).not.toBeNull();
125
- expect(p?.textContent).toBe("Data: loaded data");
126
- });
127
-
128
- test("Show inside createRoot works correctly", () => {
129
- const [data, setData] = createSignal<string | null>(null);
130
- let rootNode: unknown;
131
-
132
- createRoot((dispose) => {
133
- rootNode = Show({
134
- when: data,
135
- // SolidJS-style: children receives accessor, call with ()
136
- children: (value: () => string) => createElement("p", [], [text(`Data: ${value()}`)]),
137
- });
138
- mount(container, rootNode as any);
139
- return dispose;
140
- });
141
-
142
- // Initial state
143
- expect(container.querySelector("p")).toBeNull();
144
-
145
- // Update
146
- setData("loaded data");
147
-
148
- // After update
149
- const p = container.querySelector("p");
150
- expect(p).not.toBeNull();
151
- expect(p?.textContent).toBe("Data: loaded data");
152
- });
153
-
154
- test("Show with render (not mount) works correctly", () => {
155
- const [data, setData] = createSignal<string | null>(null);
156
-
157
- const node = Show({
158
- when: data,
159
- // SolidJS-style: children receives accessor, call with ()
160
- children: (value: () => string) => createElement("p", [], [text(`Data: ${value()}`)]),
161
- });
162
-
163
- render(container, node);
164
-
165
- // Initial state
166
- expect(container.querySelector("p")).toBeNull();
167
-
168
- // Update
169
- setData("loaded data");
170
-
171
- // After update
172
- const p = container.querySelector("p");
173
- expect(p).not.toBeNull();
174
- expect(p?.textContent).toBe("Data: loaded data");
175
- });
176
- });