@luna_ui/luna 0.3.4 → 0.4.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 (182) hide show
  1. package/dist/cli.mjs +1264 -27
  2. package/dist/css/index.d.ts +194 -0
  3. package/dist/css/index.js +721 -0
  4. package/dist/css/runtime.d.ts +92 -0
  5. package/dist/css/runtime.js +179 -0
  6. package/dist/{index-CyEkcO3_.d.ts → index-CDWzWF-h.d.ts} +2 -2
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/jsx-dev-runtime.js +1 -1
  10. package/dist/jsx-runtime.d.ts +1 -1
  11. package/dist/jsx-runtime.js +1 -1
  12. package/dist/src-DEjrAhrg.js +1 -0
  13. package/dist/vite-plugin.d.ts +122 -0
  14. package/dist/vite-plugin.js +1518 -0
  15. package/package.json +16 -2
  16. package/src/css/extract.ts +798 -0
  17. package/src/css/index.ts +10 -0
  18. package/src/css/inject.ts +205 -0
  19. package/src/css/inline.ts +182 -0
  20. package/src/css/minify.ts +70 -0
  21. package/src/css/optimizer.ts +6 -0
  22. package/src/css/runtime.ts +344 -0
  23. package/src/css-optimizer/README.md +353 -0
  24. package/src/css-optimizer/cooccurrence.ts +100 -0
  25. package/src/css-optimizer/core.ts +263 -0
  26. package/src/css-optimizer/extractors.ts +243 -0
  27. package/src/css-optimizer/hash.ts +54 -0
  28. package/src/css-optimizer/index.ts +129 -0
  29. package/src/css-optimizer/merge.ts +109 -0
  30. package/src/css-optimizer/moonbit-analyzer.ts +210 -0
  31. package/src/css-optimizer/parser.ts +120 -0
  32. package/src/css-optimizer/pattern.ts +171 -0
  33. package/src/css-optimizer/transformers.ts +301 -0
  34. package/src/css-optimizer/types.ts +128 -0
  35. package/src/event-utils.ts +227 -0
  36. package/src/hydration/createHydrator.ts +62 -0
  37. package/src/hydration/delegate.ts +62 -0
  38. package/src/hydration/drag.ts +214 -0
  39. package/src/hydration/index.ts +12 -0
  40. package/src/hydration/keyboard.ts +64 -0
  41. package/src/hydration/toggle.ts +101 -0
  42. package/src/index.ts +890 -0
  43. package/src/jsx-dev-runtime.ts +2 -0
  44. package/src/jsx-runtime.ts +398 -0
  45. package/src/vite-plugin.ts +718 -0
  46. package/tests/__screenshots__/context.test.ts/Context-API-context-with-reactive-effects-context-value-accessible-in-effect-1.png +0 -0
  47. package/tests/__screenshots__/dom.test.ts/DOM-API-For-component--SolidJS-style--For-updates-when-signal-changes-1.png +0 -0
  48. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-accepts-children-as-function-1.png +0 -0
  49. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-toggles-visibility-1.png +0 -0
  50. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-attribute-1.png +0 -0
  51. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-style-1.png +0 -0
  52. package/tests/__screenshots__/dom.test.ts/DOM-API-createElementNs--SVG-support--createElementNs-with-dynamic-attribute-1.png +0 -0
  53. package/tests/__screenshots__/dom.test.ts/DOM-API-effect-with-DOM-effect-tracks-signal-changes-1.png +0 -0
  54. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-clear-to-empty-1.png +0 -0
  55. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-empty-array-1.png +0 -0
  56. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-removes-items-1.png +0 -0
  57. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-renders-initial-list-1.png +0 -0
  58. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-updates-when-items-change-1.png +0 -0
  59. 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
  60. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-reordering-in-SVG-1.png +0 -0
  61. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-updates-SVG-elements-when-signal-changes-1.png +0 -0
  62. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-with-nested-SVG-groups-1.png +0 -0
  63. package/tests/__screenshots__/dom.test.ts/DOM-API-ref-callback--JSX-style--ref-callback-with-nested-elements-1.png +0 -0
  64. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-creates-a-node-1.png +0 -0
  65. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-with-false-condition-creates-placeholder-1.png +0 -0
  66. package/tests/__screenshots__/dom.test.ts/DOM-API-text-nodes-textDyn-creates-reactive-text-node-1.png +0 -0
  67. 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
  68. 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
  69. 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
  70. 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
  71. 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
  72. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Context---ForEach-integration-forEach-items-can-access-context-1.png +0 -0
  73. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-renders-initial-list-1.png +0 -0
  74. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-updates-when-signal-changes-1.png +0 -0
  75. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-with-object-items-1.png +0 -0
  76. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-hides-when-condition-is-false-1.png +0 -0
  77. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-renders-when-condition-is-true-1.png +0 -0
  78. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-from-false-to-true-1.png +0 -0
  79. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-reactively-1.png +0 -0
  80. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--event-listener-pattern--Solid-js-docs-example--1.png +0 -0
  81. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--multiple-cleanups-in-component-body--LIFO-order--1.png +0 -0
  82. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-in-component-body-runs-on-unmount-1.png +0 -0
  83. 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
  84. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--timer-cleanup-pattern--Solid-js-style--1.png +0 -0
  85. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Effects-effect-cleanup-runs-before-re-run-1.png +0 -0
  86. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-large-list-update-1.png +0 -0
  87. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-nested-batch-operations-1.png +0 -0
  88. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-rapid-sequential-updates-1.png +0 -0
  89. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-Show-component---visible-1.png +0 -0
  90. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-show-hide-element---visible-1.png +0 -0
  91. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-fragment-with-list-1.png +0 -0
  92. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-nested-fragments-1.png +0 -0
  93. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-simple-fragment-1.png +0 -0
  94. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-conditional-toggle-updates-1.png +0 -0
  95. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-addition-updates-match-1.png +0 -0
  96. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-removal-updates-match-1.png +0 -0
  97. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-text-updates-match-1.png +0 -0
  98. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-className-updates-1.png +0 -0
  99. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-style-updates-1.png +0 -0
  100. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-multiple-dynamic-attributes-1.png +0 -0
  101. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-deeply-nested-conditionals-1.png +0 -0
  102. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-empty-to-populated-1.png +0 -0
  103. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-populated-to-empty-1.png +0 -0
  104. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-cleanup-order-1.png +0 -0
  105. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-with-inner-signal-change-1.png +0 -0
  106. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-is-called-when-effect-re-runs-1.png +0 -0
  107. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-with-resource-simulation-1.png +0 -0
  108. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-multiple-children--no-wrapper--1.png +0 -0
  109. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-no-children-1.png +0 -0
  110. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-fragment-with-list-1.png +0 -0
  111. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-nested-Fragments-work-correctly-1.png +0 -0
  112. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-complex-reordering-with-additions-and-removals-1.png +0 -0
  113. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-insert-in-middle-1.png +0 -0
  114. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-remove-from-middle-1.png +0 -0
  115. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-reverse-list-order-1.png +0 -0
  116. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-shuffle-list-1.png +0 -0
  117. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-Show-component-renders-when-condition-is-true-1.png +0 -0
  118. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-renders-content-when-initially-true-1.png +0 -0
  119. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-toggles-visibility-dynamically-1.png +0 -0
  120. package/tests/__screenshots__/preact-signals-comparison.test.ts/Memo-Dependency-Chain-conditional-memo-dependencies-1.png +0 -0
  121. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-basic-signal-get-set-produces-same-values-1.png +0 -0
  122. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-batch-updates-produce-same-final-values-1.png +0 -0
  123. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-peek-reads-value-without-tracking-1.png +0 -0
  124. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-selective-tracking-with-untrack-1.png +0 -0
  125. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-untrack-prevents-dependency-tracking-1.png +0 -0
  126. package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--reactivity-accessor-is-reactive-1.png +0 -0
  127. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-empty-string-for-non-failure-1.png +0 -0
  128. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-undefined-for-non-failure-1.png +0 -0
  129. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsFailure-and-stateError-1.png +0 -0
  130. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsPending-1.png +0 -0
  131. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsSuccess-and-stateValue-1.png +0 -0
  132. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateValue-returns-undefined-for-non-success-1.png +0 -0
  133. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-reject-transitions-to-failure-1.png +0 -0
  134. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-resolve-transitions-to-success-1.png +0 -0
  135. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-returns-resource--resolve--and-reject-functions-1.png +0 -0
  136. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-starts-in-pending-state-1.png +0 -0
  137. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-async-resolve-works-1.png +0 -0
  138. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-starts-in-pending-state-1.png +0 -0
  139. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-failure-on-reject-1.png +0 -0
  140. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-success-on-resolve-1.png +0 -0
  141. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-can-wrap-fetch-like-async-operations-1.png +0 -0
  142. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-works-with-setTimeout-simulation-1.png +0 -0
  143. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourceGet-tracks-dependencies-1.png +0 -0
  144. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourcePeek-does-not-track-dependencies-1.png +0 -0
  145. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceRefetch-refetch-resets-to-pending-and-re-runs-fetcher-1.png +0 -0
  146. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-children-to-body-by-default-1.png +0 -0
  147. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-to-selector-mount-target-1.png +0 -0
  148. package/tests/__screenshots__/solidjs-api.test.ts/SolidJS-API-compatibility-createEffect-tracks-dependencies-automatically-1.png +0 -0
  149. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-accessor-condition-in-Match-1.png +0 -0
  150. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-multiple-Match-components-1.png +0 -0
  151. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-single-Match-and-fallback-1.png +0 -0
  152. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-components-Switch-updates-DOM-when-signal-changes-1.png +0 -0
  153. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-multiple-dependencies-1.png +0 -0
  154. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-single-dependency-1.png +0 -0
  155. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-with-defer-option-skips-initial-run-1.png +0 -0
  156. package/tests/__screenshots__/store.test.ts/createStore-Arrays-array-updates-work-1.png +0 -0
  157. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-only-triggers-when-accessed-property-changes-1.png +0 -0
  158. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-parent-path-change-notifies-child-accessors-1.png +0 -0
  159. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-nested-property-access-1.png +0 -0
  160. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-property-access-in-effects-1.png +0 -0
  161. package/tests/context.test.ts +118 -0
  162. package/tests/css-optimizer-extractors.test.ts +264 -0
  163. package/tests/css-optimizer-integration.test.ts +566 -0
  164. package/tests/css-optimizer-transformers.test.ts +301 -0
  165. package/tests/css-optimizer.test.ts +646 -0
  166. package/tests/css-runtime.bench.ts +442 -0
  167. package/tests/css-runtime.test.ts +342 -0
  168. package/tests/dom.test.ts +872 -0
  169. package/tests/integration.test.ts +405 -0
  170. package/tests/issue-5-for-infinite-loop.test.ts +516 -0
  171. package/tests/jsx-runtime.test.tsx +393 -0
  172. package/tests/lifecycle.test.ts +833 -0
  173. package/tests/move-before.bench.ts +304 -0
  174. package/tests/preact-signals-comparison.test.ts +1608 -0
  175. package/tests/resource.test.ts +160 -0
  176. package/tests/router.test.ts +117 -0
  177. package/tests/show-initial-mount-leak.test.tsx +182 -0
  178. package/tests/solidjs-api.test.ts +659 -0
  179. package/tests/static-perf.bench.ts +64 -0
  180. package/tests/store.test.ts +263 -0
  181. package/tests/tsx-syntax.test.tsx +404 -0
  182. package/dist/src-BDdxGwvq.js +0 -1
@@ -0,0 +1,516 @@
1
+ /**
2
+ * Issue #5: For component causes infinite loop inside nested Show components
3
+ * https://github.com/mizchi/luna.mbt/issues/5
4
+ *
5
+ * This test reproduces the bug where For inside nested Show components
6
+ * causes "Maximum call stack size exceeded" when a signal is updated
7
+ * in a ref callback.
8
+ */
9
+ import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
10
+ import {
11
+ text,
12
+ createElement,
13
+ render,
14
+ mount,
15
+ For,
16
+ Show,
17
+ createSignal,
18
+ createMemo,
19
+ createRenderEffect,
20
+ onMount,
21
+ createRoot,
22
+ } from "../src/index";
23
+
24
+ function attr(name: string, value: unknown) {
25
+ return { _0: name, _1: value };
26
+ }
27
+
28
+ const AttrValue = {
29
+ Static: (value: string) => ({ $tag: 0, _0: value }),
30
+ Dynamic: (getter: () => string) => ({ $tag: 1, _0: getter }),
31
+ Handler: (handler: (e: unknown) => void) => ({ $tag: 2, _0: handler }),
32
+ };
33
+
34
+ describe("Issue #5: For infinite loop in nested Show", () => {
35
+ let container: HTMLElement;
36
+
37
+ beforeEach(() => {
38
+ container = document.createElement("div");
39
+ document.body.appendChild(container);
40
+ });
41
+
42
+ afterEach(() => {
43
+ container.remove();
44
+ });
45
+
46
+ /**
47
+ * EXACT reproduction from GitHub issue comment:
48
+ * https://github.com/mizchi/luna.mbt/issues/5#issuecomment-3696194021
49
+ *
50
+ * This is the "CASE 2: Infinite Loop" scenario:
51
+ * - Double nested Show
52
+ * - Signal updated synchronously in ref callback
53
+ * - Outer Show starts hidden (isReady = false)
54
+ * - onMount sets isReady = true
55
+ */
56
+ test("EXACT REPRODUCTION: Double nested Show with ref callback signal update", async () => {
57
+ const [lineCount, setLineCount] = createSignal(1);
58
+ const [isReady, setIsReady] = createSignal(false);
59
+
60
+ const lines = createMemo(() =>
61
+ Array.from({ length: lineCount() }, (_, i) => i + 1)
62
+ );
63
+
64
+ // This ref callback updates lineCount signal - should NOT cause infinite loop
65
+ const setupInput = (el: HTMLTextAreaElement) => {
66
+ const count = el.value.split("\n").length;
67
+ setLineCount(count);
68
+ };
69
+
70
+ const node = Show({
71
+ when: isReady,
72
+ children: () =>
73
+ Show({
74
+ when: () => true,
75
+ children: () =>
76
+ createElement("div", [], [
77
+ For({
78
+ each: lines,
79
+ children: (num: number) =>
80
+ createElement("div", [attr("class", AttrValue.Static("line"))], [text(`${num}`)]),
81
+ }),
82
+ createElement(
83
+ "textarea",
84
+ [
85
+ attr("value", AttrValue.Static("line1\nline2\nline3")),
86
+ attr("__ref", AttrValue.Handler(setupInput)),
87
+ ],
88
+ []
89
+ ),
90
+ ]),
91
+ }),
92
+ });
93
+
94
+ mount(container, node);
95
+
96
+ // Initially hidden
97
+ expect(container.querySelectorAll(".line").length).toBe(0);
98
+
99
+ // Simulate onMount behavior - set isReady to true
100
+ // This is where the infinite loop would occur
101
+ setIsReady(true);
102
+
103
+ // Wait for any async effects
104
+ await new Promise(resolve => setTimeout(resolve, 10));
105
+
106
+ // Should render correctly without infinite loop
107
+ expect(container.querySelectorAll(".line").length).toBe(3);
108
+ });
109
+
110
+ /**
111
+ * EXACT reproduction with onMount - matches the issue exactly
112
+ */
113
+ test("EXACT REPRODUCTION WITH ONMOUNT: Double nested Show with ref callback signal update", async () => {
114
+ const [lineCount, setLineCount] = createSignal(1);
115
+ const [isReady, setIsReady] = createSignal(false);
116
+
117
+ const lines = createMemo(() =>
118
+ Array.from({ length: lineCount() }, (_, i) => i + 1)
119
+ );
120
+
121
+ const setupInput = (el: HTMLTextAreaElement) => {
122
+ const count = el.value.split("\n").length;
123
+ setLineCount(count);
124
+ };
125
+
126
+ // Use createRoot to establish owner context for onMount
127
+ createRoot(() => {
128
+ // This simulates onMount(() => setIsReady(true)) from the issue
129
+ onMount(() => {
130
+ setIsReady(true);
131
+ });
132
+
133
+ const node = Show({
134
+ when: isReady,
135
+ children: () =>
136
+ Show({
137
+ when: () => true,
138
+ children: () =>
139
+ createElement("div", [], [
140
+ For({
141
+ each: lines,
142
+ children: (num: number) =>
143
+ createElement("div", [attr("class", AttrValue.Static("line2"))], [text(`${num}`)]),
144
+ }),
145
+ createElement(
146
+ "textarea",
147
+ [
148
+ attr("value", AttrValue.Static("line1\nline2\nline3")),
149
+ attr("__ref", AttrValue.Handler(setupInput)),
150
+ ],
151
+ []
152
+ ),
153
+ ]),
154
+ }),
155
+ });
156
+
157
+ mount(container, node);
158
+ });
159
+
160
+ // Wait for onMount to fire and effects to settle
161
+ await new Promise(resolve => setTimeout(resolve, 50));
162
+
163
+ // Should render correctly without infinite loop
164
+ expect(container.querySelectorAll(".line2").length).toBe(3);
165
+ });
166
+
167
+ /**
168
+ * Test with logging to trace execution flow
169
+ */
170
+ test("DEBUG: Trace execution flow in nested Show + For", async () => {
171
+ const log: string[] = [];
172
+ const refCallCount = { value: 0 };
173
+ const memoCallCount = { value: 0 };
174
+
175
+ const [lineCount, setLineCount] = createSignal(1);
176
+ const [isReady, setIsReady] = createSignal(false);
177
+
178
+ const lines = createMemo(() => {
179
+ memoCallCount.value++;
180
+ const count = lineCount();
181
+ log.push(`memo: lineCount=${count}, call #${memoCallCount.value}`);
182
+ if (memoCallCount.value > 20) {
183
+ throw new Error("Infinite loop detected in memo");
184
+ }
185
+ return Array.from({ length: count }, (_, i) => i + 1);
186
+ });
187
+
188
+ const setupInput = (el: HTMLTextAreaElement) => {
189
+ refCallCount.value++;
190
+ const count = el.value.split("\n").length;
191
+ log.push(`ref callback: value has ${count} lines, call #${refCallCount.value}`);
192
+ if (refCallCount.value > 20) {
193
+ throw new Error("Infinite loop detected in ref callback");
194
+ }
195
+ setLineCount(count);
196
+ };
197
+
198
+ createRoot(() => {
199
+ onMount(() => {
200
+ log.push("onMount: setIsReady(true)");
201
+ setIsReady(true);
202
+ });
203
+
204
+ const node = Show({
205
+ when: isReady,
206
+ children: () => {
207
+ log.push("outer Show children called");
208
+ return Show({
209
+ when: () => true,
210
+ children: () => {
211
+ log.push("inner Show children called");
212
+ return createElement("div", [], [
213
+ For({
214
+ each: lines,
215
+ children: (num: number) => {
216
+ log.push(`For render item: ${num}`);
217
+ return createElement("div", [attr("class", AttrValue.Static("line3"))], [text(`${num}`)]);
218
+ },
219
+ }),
220
+ createElement(
221
+ "textarea",
222
+ [
223
+ attr("value", AttrValue.Static("line1\nline2\nline3")),
224
+ attr("__ref", AttrValue.Handler(setupInput)),
225
+ ],
226
+ []
227
+ ),
228
+ ]);
229
+ },
230
+ });
231
+ },
232
+ });
233
+
234
+ mount(container, node);
235
+ log.push("mount completed");
236
+ });
237
+
238
+ await new Promise(resolve => setTimeout(resolve, 100));
239
+
240
+ // Log the execution trace for debugging
241
+ console.log("Execution trace:", log);
242
+
243
+ // Verify no infinite loop
244
+ expect(refCallCount.value).toBeLessThan(5);
245
+ expect(memoCallCount.value).toBeLessThan(10);
246
+ expect(container.querySelectorAll(".line3").length).toBe(3);
247
+ });
248
+
249
+ test("WORKING: simple For usage", () => {
250
+ const [count] = createSignal(3);
251
+ const items = createMemo(() =>
252
+ Array.from({ length: count() }, (_, i) => i + 1)
253
+ );
254
+
255
+ const node = For({
256
+ each: items,
257
+ children: (item: number) => createElement("div", [], [text(`Item ${item}`)]),
258
+ });
259
+
260
+ mount(container, node);
261
+ expect(container.querySelectorAll("div").length).toBe(3);
262
+ expect(container.textContent).toBe("Item 1Item 2Item 3");
263
+ });
264
+
265
+ test("WORKING: For inside single Show", () => {
266
+ const [isInit] = createSignal(true);
267
+ const [count] = createSignal(3);
268
+ const items = createMemo(() =>
269
+ Array.from({ length: count() }, (_, i) => i + 1)
270
+ );
271
+
272
+ const node = Show({
273
+ when: isInit,
274
+ children: () =>
275
+ For({
276
+ each: items,
277
+ children: (item: number) =>
278
+ createElement("div", [], [text(`Item ${item}`)]),
279
+ }),
280
+ });
281
+
282
+ mount(container, node);
283
+ expect(container.querySelectorAll("div").length).toBe(3);
284
+ });
285
+
286
+ test("WORKING: For inside nested Show (no ref callback)", () => {
287
+ const [isInit] = createSignal(true);
288
+ const [count] = createSignal(3);
289
+ const items = createMemo(() =>
290
+ Array.from({ length: count() }, (_, i) => i + 1)
291
+ );
292
+
293
+ const node = Show({
294
+ when: isInit,
295
+ children: () =>
296
+ Show({
297
+ when: () => true,
298
+ children: () =>
299
+ For({
300
+ each: items,
301
+ children: (item: number) =>
302
+ createElement("div", [], [text(`Item ${item}`)]),
303
+ }),
304
+ }),
305
+ });
306
+
307
+ mount(container, node);
308
+ expect(container.querySelectorAll("div").length).toBe(3);
309
+ });
310
+
311
+ test("VERIFY: For with ref callback signal update inside nested Show", () => {
312
+ // Based on issue #5 - test if ref callback can update signal without infinite loop
313
+
314
+ const [lineCount, setLineCount] = createSignal(1);
315
+ const lineNumbers = createMemo(() =>
316
+ Array.from({ length: lineCount() }, (_, i) => i + 1)
317
+ );
318
+
319
+ const setupEditor = (el: HTMLTextAreaElement) => {
320
+ const lines = el.value.split("\n").length;
321
+ setLineCount(lines); // Signal update in ref callback
322
+ };
323
+
324
+ const node = Show({
325
+ when: () => true,
326
+ children: () =>
327
+ Show({
328
+ when: () => true,
329
+ children: () =>
330
+ createElement("div", [], [
331
+ createElement("div", [attr("class", AttrValue.Static("line-numbers"))], [
332
+ For({
333
+ each: lineNumbers,
334
+ children: (num: number) =>
335
+ createElement("div", [], [text(`${num}`)]),
336
+ }),
337
+ ]),
338
+ createElement(
339
+ "textarea",
340
+ [
341
+ attr("value", AttrValue.Static("line1\nline2\nline3")),
342
+ attr("__ref", AttrValue.Handler(setupEditor)),
343
+ ],
344
+ []
345
+ ),
346
+ ]),
347
+ }),
348
+ });
349
+
350
+ // This should not throw
351
+ mount(container, node);
352
+
353
+ // Verify correct rendering
354
+ expect(container.querySelectorAll(".line-numbers div").length).toBe(3);
355
+ });
356
+
357
+ test("VERIFY: Deferred Show with For and signal update in ref", async () => {
358
+ // More realistic reproduction: Show with setTimeout-deferred condition
359
+ const [isInit, setIsInit] = createSignal(false);
360
+ const [lineCount, setLineCount] = createSignal(1);
361
+ const lineNumbers = createMemo(() =>
362
+ Array.from({ length: lineCount() }, (_, i) => i + 1)
363
+ );
364
+
365
+ const setupEditor = (el: HTMLTextAreaElement) => {
366
+ const lines = el.value.split("\n").length;
367
+ setLineCount(lines);
368
+ };
369
+
370
+ const node = Show({
371
+ when: isInit,
372
+ children: () =>
373
+ Show({
374
+ when: () => true,
375
+ children: () =>
376
+ createElement("div", [], [
377
+ createElement("div", [attr("class", AttrValue.Static("line-numbers"))], [
378
+ For({
379
+ each: lineNumbers,
380
+ children: (num: number) =>
381
+ createElement("div", [], [text(`${num}`)]),
382
+ }),
383
+ ]),
384
+ createElement(
385
+ "textarea",
386
+ [
387
+ attr("value", AttrValue.Static("line1\nline2\nline3")),
388
+ attr("__ref", AttrValue.Handler(setupEditor)),
389
+ ],
390
+ []
391
+ ),
392
+ ]),
393
+ }),
394
+ });
395
+
396
+ mount(container, node);
397
+
398
+ // Initially hidden
399
+ expect(container.querySelectorAll(".line-numbers div").length).toBe(0);
400
+
401
+ // Trigger show after delay (simulating setTimeout)
402
+ await new Promise(resolve => setTimeout(resolve, 10));
403
+ setIsInit(true);
404
+
405
+ // Wait for effects to process
406
+ await new Promise(resolve => setTimeout(resolve, 10));
407
+
408
+ // Should render correctly without infinite loop
409
+ expect(container.querySelectorAll(".line-numbers div").length).toBe(3);
410
+ });
411
+
412
+ test("BUG: Simplified reproduction - For + createRenderEffect with signal update", () => {
413
+ // Simplified version that demonstrates the core issue
414
+ const renderCount = { value: 0 };
415
+ const [count, setCount] = createSignal(1);
416
+ const items = createMemo(() => {
417
+ renderCount.value++;
418
+ if (renderCount.value > 100) {
419
+ throw new Error("Infinite loop detected: render count exceeded 100");
420
+ }
421
+ return Array.from({ length: count() }, (_, i) => i + 1);
422
+ });
423
+
424
+ const node = Show({
425
+ when: () => true,
426
+ children: () =>
427
+ Show({
428
+ when: () => true,
429
+ children: () =>
430
+ createElement("div", [], [
431
+ For({
432
+ each: items,
433
+ children: (item: number) =>
434
+ createElement("div", [], [text(`${item}`)]),
435
+ }),
436
+ ]),
437
+ }),
438
+ });
439
+
440
+ mount(container, node);
441
+
442
+ // Initial render should not trigger infinite loop
443
+ expect(renderCount.value).toBeLessThan(10);
444
+
445
+ // Signal update should not trigger infinite loop
446
+ renderCount.value = 0;
447
+ setCount(3);
448
+ expect(renderCount.value).toBeLessThan(10);
449
+ });
450
+
451
+ test("BUG: Effect firing multiple times inside Show", () => {
452
+ const effectRunCount = { value: 0 };
453
+ const [visible, setVisible] = createSignal(true);
454
+
455
+ const node = Show({
456
+ when: visible,
457
+ children: () => {
458
+ // Track how many times this child function is called
459
+ createRenderEffect(() => {
460
+ effectRunCount.value++;
461
+ });
462
+ return createElement("div", [], [text("content")]);
463
+ },
464
+ });
465
+
466
+ mount(container, node);
467
+
468
+ // Effect should only run once on mount
469
+ // If it runs multiple times (2-3x), this indicates the bug reported in issue #5
470
+ expect(effectRunCount.value).toBe(1);
471
+ });
472
+
473
+ test("BUG: Show instantiates nodes multiple times", () => {
474
+ const instanceCount = { value: 0 };
475
+ const [visible] = createSignal(true);
476
+
477
+ const node = Show({
478
+ when: visible,
479
+ children: () => {
480
+ instanceCount.value++;
481
+ return createElement("div", [], [text("content")]);
482
+ },
483
+ });
484
+
485
+ mount(container, node);
486
+
487
+ // Child function should only be called once
488
+ // Multiple calls indicate the root cause of the infinite loop issue
489
+ expect(instanceCount.value).toBe(1);
490
+ });
491
+
492
+ test("BUG: Nested Show instantiates nodes multiple times", () => {
493
+ const outerCount = { value: 0 };
494
+ const innerCount = { value: 0 };
495
+
496
+ const node = Show({
497
+ when: () => true,
498
+ children: () => {
499
+ outerCount.value++;
500
+ return Show({
501
+ when: () => true,
502
+ children: () => {
503
+ innerCount.value++;
504
+ return createElement("div", [], [text("nested")]);
505
+ },
506
+ });
507
+ },
508
+ });
509
+
510
+ mount(container, node);
511
+
512
+ // Each child function should only be called once
513
+ expect(outerCount.value).toBe(1);
514
+ expect(innerCount.value).toBe(1);
515
+ });
516
+ });