@luna_ui/luna 0.7.3 → 0.17.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 (83) 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-vO066aMd.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 +50 -0
  22. package/dist/signals.js +1 -0
  23. package/dist/vite-plugin.d.ts +7708 -2
  24. package/dist/vite-plugin.js +7 -6
  25. package/package.json +34 -11
  26. package/dist/event-utils-C_M2XBNj.js +0 -1
  27. package/dist/src-BbjOW18q.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/vite-plugin.ts +0 -718
  58. package/tests/__screenshots__/apg.test.ts/APG-Components---Accessibility-Tests-Button-Pattern-disabled-button-has-aria-disabled-1.png +0 -0
  59. package/tests/apg.test.ts +0 -466
  60. package/tests/context.test.ts +0 -118
  61. package/tests/css-optimizer-extractors.test.ts +0 -264
  62. package/tests/css-optimizer-integration.test.ts +0 -566
  63. package/tests/css-optimizer-transformers.test.ts +0 -301
  64. package/tests/css-optimizer.test.ts +0 -646
  65. package/tests/css-runtime.bench.ts +0 -442
  66. package/tests/css-runtime.test.ts +0 -342
  67. package/tests/debounced.test.ts +0 -165
  68. package/tests/dom.test.ts +0 -873
  69. package/tests/integration.test.ts +0 -405
  70. package/tests/issue-11-show-null-to-truthy.test.ts +0 -176
  71. package/tests/issue-5-for-infinite-loop.test.ts +0 -516
  72. package/tests/jsx-runtime.test.tsx +0 -393
  73. package/tests/lifecycle.test.ts +0 -833
  74. package/tests/move-before.bench.ts +0 -304
  75. package/tests/preact-signals-comparison.test.ts +0 -1608
  76. package/tests/resource.test.ts +0 -160
  77. package/tests/router.test.ts +0 -117
  78. package/tests/show-initial-mount-leak.test.tsx +0 -182
  79. package/tests/solidjs-api.test.ts +0 -660
  80. package/tests/static-perf.bench.ts +0 -64
  81. package/tests/store.test.ts +0 -263
  82. package/tests/tsx-syntax.test.tsx +0 -404
  83. /package/dist/{event-utils-Cd5f3Njd.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
- });