flet-box 0.0.0-stage → 1.0.2

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 (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -2
  3. package/bin/cli.js +394 -0
  4. package/bin/commands/buildAndroid.js +200 -0
  5. package/bin/commands/buildIOS.js +237 -0
  6. package/bin/commands/create.js +406 -0
  7. package/bin/commands/createBundle.js +375 -0
  8. package/bin/commands/createComponent.js +115 -0
  9. package/bin/commands/createScreen.js +161 -0
  10. package/bin/commands/killServer.js +107 -0
  11. package/bin/commands/package.js +131 -0
  12. package/bin/commands/release.js +189 -0
  13. package/bin/commands/runBundle.js +62 -0
  14. package/bin/commands/runCloudflare.js +89 -0
  15. package/bin/commands/runNative.js +109 -0
  16. package/bin/commands/runServer.js +390 -0
  17. package/bin/ota/loader.js +361 -0
  18. package/bin/utils/bundleConfig.js +178 -0
  19. package/bin/utils/capacitor.js +137 -0
  20. package/bin/utils/colors.js +215 -0
  21. package/bin/utils/helpers.js +80 -0
  22. package/bin/utils/spinner.js +96 -0
  23. package/bin/utils/templates.js +1824 -0
  24. package/bin/utils/version.js +108 -0
  25. package/package.json +58 -4
  26. package/src/animations/AnimatedBox.js +607 -0
  27. package/src/animations/AnimatedText.js +105 -0
  28. package/src/animations/MatrixRain.js +220 -0
  29. package/src/animations/ParallaxBox.js +369 -0
  30. package/src/animations/animate.js +332 -0
  31. package/src/animations/index.js +25 -0
  32. package/src/app.js +41 -0
  33. package/src/assets/icon-1024-maskable.png +0 -0
  34. package/src/assets/icon-192-maskable.png +0 -0
  35. package/src/assets/icon-192.png +0 -0
  36. package/src/assets/icon-512-maskable.png +0 -0
  37. package/src/assets/icon-512.png +0 -0
  38. package/src/assets/logo.png +0 -0
  39. package/src/core/App.js +44 -0
  40. package/src/core/hmr-client.js +142 -0
  41. package/src/core/index.js +34 -0
  42. package/src/core/pwa.js +100 -0
  43. package/src/core/runApp.js +430 -0
  44. package/src/create-index.sh +237 -0
  45. package/src/fonts/MaterialIcons-Regular.woff2 +0 -0
  46. package/src/fonts/icons.css +23 -0
  47. package/src/index.d.ts +1930 -0
  48. package/src/index.js +288 -0
  49. package/src/navigations/AdaptiveScaffold.js +149 -0
  50. package/src/navigations/AppBar.js +331 -0
  51. package/src/navigations/BottomNavigation.js +298 -0
  52. package/src/navigations/CollapsibleSideBar.js +139 -0
  53. package/src/navigations/Drawer.js +333 -0
  54. package/src/navigations/DrawerItem.js +260 -0
  55. package/src/navigations/Router.js +475 -0
  56. package/src/navigations/Scaffold.js +537 -0
  57. package/src/navigations/Tabs.js +384 -0
  58. package/src/navigations/index.js +57 -0
  59. package/src/services/RamStore.js +213 -0
  60. package/src/services/Session.js +310 -0
  61. package/src/services/Storage.js +309 -0
  62. package/src/services/http.js +350 -0
  63. package/src/services/index.js +57 -0
  64. package/src/tools/animation.js +42 -0
  65. package/src/tools/array.js +95 -0
  66. package/src/tools/border.js +28 -0
  67. package/src/tools/clipboard.js +104 -0
  68. package/src/tools/color.js +329 -0
  69. package/src/tools/createList.js +215 -0
  70. package/src/tools/delay.js +55 -0
  71. package/src/tools/device.js +138 -0
  72. package/src/tools/dict.js +297 -0
  73. package/src/tools/dimensions.js +118 -0
  74. package/src/tools/filter.js +41 -0
  75. package/src/tools/flex.js +26 -0
  76. package/src/tools/gradient.js +34 -0
  77. package/src/tools/grid.js +57 -0
  78. package/src/tools/index.js +38 -0
  79. package/src/tools/mapList.js +107 -0
  80. package/src/tools/margin.js +93 -0
  81. package/src/tools/memo.js +60 -0
  82. package/src/tools/os.js +104 -0
  83. package/src/tools/padding.js +93 -0
  84. package/src/tools/print.js +69 -0
  85. package/src/tools/random.js +299 -0
  86. package/src/tools/ref.js +70 -0
  87. package/src/tools/rgba.js +4 -0
  88. package/src/tools/shadow.js +23 -0
  89. package/src/tools/string.js +85 -0
  90. package/src/tools/time.js +674 -0
  91. package/src/tools/transform.js +68 -0
  92. package/src/tools/transition.js +48 -0
  93. package/src/tools/useState.js +268 -0
  94. package/src/tools/uuid.js +42 -0
  95. package/src/utils/TextInputValidator.js +256 -0
  96. package/src/utils/composeUpdate.js +65 -0
  97. package/src/utils/getWidgetProps.js +35 -0
  98. package/src/utils/index.js +76 -0
  99. package/src/utils/markdownParser.js +368 -0
  100. package/src/utils/mediaTime.js +79 -0
  101. package/src/utils/navigation.js +263 -0
  102. package/src/utils/scriptInjector.js +73 -0
  103. package/src/utils/stackPosition.js +31 -0
  104. package/src/utils/stopWebRefresh.js +77 -0
  105. package/src/utils/styleInjector.js +38 -0
  106. package/src/utils/syntaxHighlight.js +339 -0
  107. package/src/utils/themes.js +347 -0
  108. package/src/utils/units.js +87 -0
  109. package/src/utils/visualEffects.js +169 -0
  110. package/src/widget-factory/addChildren.js +114 -0
  111. package/src/widget-factory/assignProps.js +256 -0
  112. package/src/widget-factory/createWidget.js +28 -0
  113. package/src/widget-factory/effects.js +132 -0
  114. package/src/widget-factory/index.js +7 -0
  115. package/src/widget-factory/lifecycle.js +210 -0
  116. package/src/widget-factory/makeParentable.js +89 -0
  117. package/src/widget-factory/pixTool.js +40 -0
  118. package/src/widget-factory/processProps.js +122 -0
  119. package/src/widget-factory/reactivity.js +98 -0
  120. package/src/widget-factory/remTool.js +34 -0
  121. package/src/widget-factory/stackPosition.js +87 -0
  122. package/src/widget-factory/stringTool.js +26 -0
  123. package/src/widget-factory/tools.js +249 -0
  124. package/src/widget-factory/translateProps.js +1049 -0
  125. package/src/widget-factory/warnings.js +284 -0
  126. package/src/widget-factory/widgetFactory.js +200 -0
  127. package/src/widgets/Accordion.js +562 -0
  128. package/src/widgets/AdMob.js +315 -0
  129. package/src/widgets/AdSense.js +268 -0
  130. package/src/widgets/AlertDialog.js +162 -0
  131. package/src/widgets/Audio.js +311 -0
  132. package/src/widgets/Avatar.js +210 -0
  133. package/src/widgets/Badge.js +189 -0
  134. package/src/widgets/BottomSheet.js +287 -0
  135. package/src/widgets/Button.js +247 -0
  136. package/src/widgets/Card.js +33 -0
  137. package/src/widgets/Carousel.js +301 -0
  138. package/src/widgets/Chart.js +632 -0
  139. package/src/widgets/Checkbox.js +106 -0
  140. package/src/widgets/Chip.js +153 -0
  141. package/src/widgets/CircularBar.js +352 -0
  142. package/src/widgets/CircularChart.js +549 -0
  143. package/src/widgets/CodeViewer.js +236 -0
  144. package/src/widgets/Column.js +29 -0
  145. package/src/widgets/Container.js +31 -0
  146. package/src/widgets/DataTable.js +257 -0
  147. package/src/widgets/Divider.js +93 -0
  148. package/src/widgets/DraggBox.js +115 -0
  149. package/src/widgets/Dropdown.js +505 -0
  150. package/src/widgets/DroppBox.js +180 -0
  151. package/src/widgets/FloatingActionButton.js +91 -0
  152. package/src/widgets/GridView.js +20 -0
  153. package/src/widgets/Icon.js +33 -0
  154. package/src/widgets/Image.js +15 -0
  155. package/src/widgets/Input.js +565 -0
  156. package/src/widgets/Inspector.js +254 -0
  157. package/src/widgets/InstallButton.js +110 -0
  158. package/src/widgets/ListTile.js +224 -0
  159. package/src/widgets/ListView.js +503 -0
  160. package/src/widgets/Markdown.js +1236 -0
  161. package/src/widgets/Modal.js +496 -0
  162. package/src/widgets/Pagination.js +195 -0
  163. package/src/widgets/ProgressBar.js +178 -0
  164. package/src/widgets/QRCode.js +467 -0
  165. package/src/widgets/Radio.js +132 -0
  166. package/src/widgets/Rating.js +261 -0
  167. package/src/widgets/Row.js +29 -0
  168. package/src/widgets/Skeleton.js +243 -0
  169. package/src/widgets/Slider.js +527 -0
  170. package/src/widgets/SnackBar.js +227 -0
  171. package/src/widgets/Stack.js +29 -0
  172. package/src/widgets/Stepper.js +296 -0
  173. package/src/widgets/Switch.js +102 -0
  174. package/src/widgets/Text.js +98 -0
  175. package/src/widgets/Tooltip.js +267 -0
  176. package/src/widgets/TreeView.js +370 -0
  177. package/src/widgets/Video.js +92 -0
  178. package/src/widgets/index.js +52 -0
@@ -0,0 +1,132 @@
1
+ // core/effects.js
2
+
3
+ /**
4
+ * Applies interactive visual effects to a widget that has a click handler.
5
+ *
6
+ * On every call, previously registered effect listeners are removed before new
7
+ * ones are attached, so calling `applyEffects` again after an `update` is safe
8
+ * and idempotent.
9
+ *
10
+ * **Base effects (always applied):**
11
+ * - `-webkit-tap-highlight-color` set to `transparent` to suppress the default
12
+ * mobile tap flash.
13
+ * - `outline` removed for a cleaner focused appearance.
14
+ *
15
+ * **Press / hover effects (only when a click handler is present and
16
+ * `disableTransform !== true`):**
17
+ * - On press start (`mousedown` / `touchstart`): scales the widget to `0.98`
18
+ * and removes the box shadow.
19
+ * - On press end (`mouseup` / `touchend` / `mouseleave` / `touchcancel`):
20
+ * restores the original scale and shadow.
21
+ * - On hover enter (`mouseenter`): translates the widget up by `0.5px` and
22
+ * enhances the box shadow when one was present.
23
+ * - On hover leave (`mouseleave`): restores the original translate and shadow.
24
+ * - Sets `cursor: pointer`.
25
+ *
26
+ * The `scale` and `translate` CSS properties are modified independently to
27
+ * avoid overwriting any `transform` set by `stackPosition` or other consumers.
28
+ *
29
+ * @param {HTMLElement} widget - The widget to apply effects to. Must have
30
+ * `_originalProps` and `_effectListeners` properties (set up by
31
+ * `WidgetFactory`).
32
+ * @returns {HTMLElement} The same widget (for chaining).
33
+ */
34
+ export const applyEffects = (widget) => {
35
+ // Remove previously registered effect listeners before re-attaching.
36
+ if (widget._effectListeners) {
37
+ widget._effectListeners.forEach(({ event, handler }) => {
38
+ widget.removeEventListener(event, handler);
39
+ });
40
+ widget._effectListeners = [];
41
+ }
42
+
43
+ widget.style.webkitTapHighlightColor = "transparent";
44
+ widget.style.outline = "none";
45
+
46
+ const hasClick = widget.onclick && typeof widget.onclick === "function";
47
+ if (!hasClick) return widget;
48
+
49
+ // Store the current shadow so it can be restored after press / hover effects.
50
+ widget._originalShadow = widget.style.boxShadow || "";
51
+
52
+ const disableTransform = widget._originalProps?.disableTransform === true;
53
+ const listeners = [];
54
+
55
+ if (!disableTransform) {
56
+ let isPressed = false;
57
+
58
+ /**
59
+ * Handles press-start events (mousedown / touchstart).
60
+ * Scales the widget down and removes its shadow.
61
+ */
62
+ const onStart = () => {
63
+ if (isPressed) return;
64
+ isPressed = true;
65
+ // Use the independent `scale` property to avoid conflicting with stackPosition.
66
+ widget.style.scale = "0.98";
67
+ widget.style.boxShadow = "none";
68
+ };
69
+
70
+ /**
71
+ * Handles press-end events (mouseup / touchend / mouseleave / touchcancel).
72
+ * Restores scale and shadow without touching `transform`.
73
+ */
74
+ const onEnd = () => {
75
+ if (!isPressed) return;
76
+ isPressed = false;
77
+ // Restore scale without touching transform.
78
+ widget.style.scale = "";
79
+ widget.style.boxShadow = widget._originalShadow || "";
80
+ };
81
+
82
+ const pressStart = ["mousedown", "touchstart"];
83
+ const pressEnd = ["mouseup", "touchend", "mouseleave", "touchcancel"];
84
+
85
+ pressStart.forEach((ev) => {
86
+ widget.addEventListener(ev, onStart, { passive: true });
87
+ listeners.push({ event: ev, handler: onStart });
88
+ });
89
+ pressEnd.forEach((ev) => {
90
+ widget.addEventListener(ev, onEnd, { passive: true });
91
+ listeners.push({ event: ev, handler: onEnd });
92
+ });
93
+
94
+ /**
95
+ * Handles hover-enter (`mouseenter`).
96
+ * Lifts the widget slightly and enhances its shadow when one is present.
97
+ * No-ops while the widget is pressed to avoid conflicting with `onStart`.
98
+ */
99
+ const onHoverEnter = () => {
100
+ if (isPressed) return;
101
+ // Use the independent `translate` property.
102
+ widget.style.translate = "0 -0.5px";
103
+ if (widget._originalShadow && widget._originalShadow !== "none") {
104
+ widget.style.boxShadow = "0 8px 16px rgba(0,0,0,0.2)";
105
+ }
106
+ };
107
+
108
+ /**
109
+ * Handles hover-leave (`mouseleave`).
110
+ * Restores translate and shadow without touching `transform`.
111
+ * No-ops while the widget is pressed.
112
+ */
113
+ const onHoverLeave = () => {
114
+ if (isPressed) return;
115
+ // Restore translate without touching transform.
116
+ widget.style.translate = "";
117
+ widget.style.boxShadow = widget._originalShadow || "";
118
+ };
119
+
120
+ widget.addEventListener("mouseenter", onHoverEnter, { passive: true });
121
+ widget.addEventListener("mouseleave", onHoverLeave, { passive: true });
122
+ listeners.push({ event: "mouseenter", handler: onHoverEnter });
123
+ listeners.push({ event: "mouseleave", handler: onHoverLeave });
124
+
125
+ widget.style.cursor = "pointer";
126
+ }
127
+
128
+ widget._effectListeners = listeners;
129
+ return widget;
130
+ };
131
+
132
+ export default applyEffects;
@@ -0,0 +1,7 @@
1
+ // core/index.js
2
+ // ============================================================
3
+ // 📦 PUBLIC EXPORTS FOR THE WIDGET FACTORY MODULE
4
+ // ============================================================
5
+ // Re-exports the top-level WidgetFactory so consumers can import from the
6
+ // module root without knowing the internal file structure.
7
+ export { WidgetFactory } from "./widgetFactory.js";
@@ -0,0 +1,210 @@
1
+ /**
2
+ * @file lifecycle.js
3
+ * @description
4
+ * Lifecycle management for widgets via a shared MutationObserver.
5
+ *
6
+ * A single global observer watches `document.body` for DOM mutations and
7
+ * triggers mount / unmount callbacks on all registered widgets. Widgets are
8
+ * automatically pruned from the registry when they are permanently removed from
9
+ * the DOM, preventing memory leaks.
10
+ *
11
+ * Exported API:
12
+ * - `addLifecycle(widget)` – attaches lifecycle hooks to a widget.
13
+ * - `reregister(widget)` – re-adds a previously pruned (never-mounted) widget.
14
+ * - `_debugRegistrySize()` – internal diagnostic helper (not part of public API).
15
+ */
16
+
17
+ const registeredWidgets = new Set();
18
+ let globalObserver = null;
19
+
20
+ /**
21
+ * Internal diagnostic hook (not part of the public API).
22
+ * Allows tests to assert that the registry is pruned correctly and has no leaks.
23
+ *
24
+ * @returns {number} Current number of widgets in the lifecycle registry.
25
+ */
26
+ export const _debugRegistrySize = () => registeredWidgets.size;
27
+
28
+ /**
29
+ * Disconnects the global MutationObserver when there are no more widgets to
30
+ * observe. Called after every registry deletion to avoid idle overhead.
31
+ *
32
+ * @returns {void}
33
+ */
34
+ const stopObserverIfIdle = () => {
35
+ if (registeredWidgets.size === 0 && globalObserver) {
36
+ globalObserver.disconnect();
37
+ globalObserver = null;
38
+ }
39
+ };
40
+
41
+ /**
42
+ * Iterates over all registered widgets and fires mount or unmount callbacks
43
+ * based on each widget's current connection state.
44
+ *
45
+ * Pruning strategy:
46
+ * - A widget that is disconnected after being mounted is unmarked and its
47
+ * unmount callbacks are fired. A `setTimeout(0)` defers the actual registry
48
+ * deletion so synchronous reparenting (e.g. drag-and-drop) does not
49
+ * accidentally unregister a widget that is immediately re-attached.
50
+ * - A widget that was created but never mounted and is still disconnected
51
+ * (`_orphanScheduled`) is removed from the registry after one tick without
52
+ * clearing its handlers, allowing a late `appendChild` to re-mount it via
53
+ * `reregister`.
54
+ *
55
+ * @returns {void}
56
+ */
57
+ const checkAllStates = () => {
58
+ registeredWidgets.forEach((widget) => {
59
+ const isConnected = document.body.contains(widget);
60
+ if (isConnected && !widget._mounted) {
61
+ widget._mounted = true;
62
+ widget._orphanScheduled = false;
63
+ widget._mountFns.forEach((fn) => fn(widget));
64
+ } else if (!isConnected && widget._mounted) {
65
+ widget._mounted = false;
66
+ // Fired but NOT drained: a quick reparent (drag & drop) re-attaches the
67
+ // widget before the prune timeout, and the disposers must still be there
68
+ // for the next disconnect. Because `_cleanup` drains the same array, a
69
+ // disposer can run from both paths — they must be idempotent.
70
+ widget._unmountFns.forEach((fn) => {
71
+ try {
72
+ fn(widget);
73
+ } catch (_) {}
74
+ });
75
+
76
+ // Prune: if the widget is still disconnected after this tick, its lifecycle
77
+ // is complete. Remove it from the registry to avoid or(n) sweeps and leaks.
78
+ // The setTimeout lets synchronous reparenting (drag & drop) complete first.
79
+ setTimeout(() => {
80
+ if (!document.body.contains(widget)) {
81
+ registeredWidgets.delete(widget);
82
+ widget._mountFns = [];
83
+ widget._unmountFns = [];
84
+ stopObserverIfIdle();
85
+ }
86
+ }, 0);
87
+ } else if (!isConnected && !widget._mounted && !widget._orphanScheduled) {
88
+ // Orphan prune: widget was created but never mounted and is still
89
+ // disconnected. Remove it from the registry without clearing handlers so
90
+ // that a late appendChild → reregister can still mount it.
91
+ widget._orphanScheduled = true;
92
+ setTimeout(() => {
93
+ widget._orphanScheduled = false;
94
+ if (!widget._mounted && !document.body.contains(widget)) {
95
+ registeredWidgets.delete(widget);
96
+ stopObserverIfIdle();
97
+ }
98
+ }, 0);
99
+ }
100
+ });
101
+ };
102
+
103
+ /**
104
+ * Starts the global MutationObserver if it is not already running.
105
+ * Observes `document.body` for child-list mutations at any depth.
106
+ *
107
+ * @returns {void}
108
+ */
109
+ const ensureObserver = () => {
110
+ if (!globalObserver && typeof MutationObserver !== "undefined" && document.body) {
111
+ globalObserver = new MutationObserver(checkAllStates);
112
+ globalObserver.observe(document.body, { childList: true, subtree: true });
113
+ }
114
+ };
115
+
116
+ /**
117
+ * Attaches lifecycle hooks to a widget and registers it with the global
118
+ * MutationObserver.
119
+ *
120
+ * After this call the widget exposes:
121
+ * - `onMount(fn)` – registers a callback fired when the widget is inserted into
122
+ * the live DOM.
123
+ * - `onUnmount(fn)` – registers a callback fired when the widget is removed from
124
+ * the live DOM.
125
+ * - `triggerMount()` – synchronously fires mount callbacks if the widget is
126
+ * already connected (useful when called right after `appendChild`). The
127
+ * observer will not duplicate the call because `_mounted` is set to `true`.
128
+ * - `_cleanup()` – removes the widget from the registry and disconnects the
129
+ * observer if the registry becomes empty.
130
+ *
131
+ * @param {HTMLElement} widget - The widget to attach lifecycle hooks to.
132
+ * @returns {HTMLElement} The same widget (for chaining).
133
+ */
134
+ export const addLifecycle = (widget) => {
135
+ widget._mountFns = [];
136
+ widget._unmountFns = [];
137
+ widget._mounted = false;
138
+
139
+ widget.onMount = (fn) => typeof fn === "function" && widget._mountFns.push(fn);
140
+ widget.onUnmount = (fn) => typeof fn === "function" && widget._unmountFns.push(fn);
141
+
142
+ /**
143
+ * Synchronously fires mount callbacks when the widget is already in the DOM.
144
+ * `runApp` / `insertBy` call this immediately after appending the widget so
145
+ * consumers get the mount event without waiting for the observer's next tick.
146
+ * The observer does not duplicate the call because `_mounted` is already `true`.
147
+ *
148
+ * @returns {HTMLElement} The widget itself (for chaining).
149
+ */
150
+ widget.triggerMount = () => {
151
+ if (!widget._mounted && document.body.contains(widget)) {
152
+ widget._mounted = true;
153
+ widget._mountFns.forEach((fn) => fn(widget));
154
+ }
155
+ return widget;
156
+ };
157
+
158
+ ensureObserver();
159
+ registeredWidgets.add(widget);
160
+ queueMicrotask(checkAllStates);
161
+
162
+ /**
163
+ * Removes the widget from the lifecycle registry, stops the observer if the
164
+ * registry becomes empty, and releases the widget's resources.
165
+ *
166
+ * Any pending `onUnmount` callbacks are drained (the array is swapped out
167
+ * *before* running them, so the guard also covers a concurrent global-observer
168
+ * fire) and executed exactly once. This makes `onUnmount` the single hook that
169
+ * runs on every teardown path: DOM disconnect (global observer), tree rebuild
170
+ * (`runApp.teardownTree`) and direct `_cleanup()` calls (Scaffold observer).
171
+ *
172
+ * Disposers registered through `onUnmount` MUST be idempotent — they can be
173
+ * triggered by either path, though never twice in practice thanks to the drain.
174
+ *
175
+ * @returns {void}
176
+ */
177
+ widget._cleanup = () => {
178
+ const unmountFns = widget._unmountFns;
179
+ widget._unmountFns = [];
180
+ registeredWidgets.delete(widget);
181
+ stopObserverIfIdle();
182
+ widget._mountFns = [];
183
+ unmountFns.forEach((fn) => {
184
+ try {
185
+ fn(widget);
186
+ } catch (_) {}
187
+ });
188
+ };
189
+
190
+ return widget;
191
+ };
192
+
193
+ /**
194
+ * Re-registers a previously pruned (never-mounted) widget into the lifecycle
195
+ * registry. Intended to be called when a widget that was built before being
196
+ * appended to the DOM is finally attached via `appendChild`.
197
+ *
198
+ * No-ops when the widget is already mounted or already in the registry.
199
+ *
200
+ * @param {HTMLElement} widget - The widget to re-register.
201
+ * @returns {void}
202
+ */
203
+ export const reregister = (widget) => {
204
+ if (!widget || widget._mounted) return;
205
+ if (registeredWidgets.has(widget)) return;
206
+ widget._orphanScheduled = false;
207
+ ensureObserver();
208
+ registeredWidgets.add(widget);
209
+ queueMicrotask(checkAllStates);
210
+ };
@@ -0,0 +1,89 @@
1
+ // core/makeParentable.js
2
+ import { reregister } from "./lifecycle.js";
3
+
4
+ /**
5
+ * Extends a widget with parent–child relationship tracking.
6
+ *
7
+ * After this call the widget gains:
8
+ * - `_children` – live array of direct child nodes (both widget and non-widget).
9
+ * - `_isWidget` – branding flag used by other modules to identify flet-box widgets.
10
+ * - `parent` – a managed accessor backed by a private variable. Readable from
11
+ * anywhere; written automatically by the overridden `appendChild` and
12
+ * `removeChild`.
13
+ * - `appendChild(child)` – overrides the native method to track `_children`,
14
+ * set `child.parent`, and re-register orphaned child widgets with the
15
+ * lifecycle system.
16
+ * - `removeChild(child)` – overrides the native method to splice `_children`
17
+ * and clear `child.parent`.
18
+ * - `getChildren()` – returns a shallow copy of `_children`.
19
+ *
20
+ * @param {HTMLElement} widget - The DOM element to extend with parent tracking.
21
+ * @returns {HTMLElement} The same widget (for chaining).
22
+ */
23
+ export const makeParentable = (widget) => {
24
+ let _parent = null;
25
+ widget._children = [];
26
+ widget._isWidget = true; // Branding flag to identify flet-box widgets
27
+
28
+ Object.defineProperty(widget, "parent", {
29
+ get: () => _parent,
30
+ set: (p) => { _parent = p; },
31
+ enumerable: true,
32
+ configurable: true,
33
+ });
34
+
35
+ const originalAppend = widget.appendChild;
36
+
37
+ /**
38
+ * Overrides the native `appendChild` to:
39
+ * 1. Set `child.parent` when the child is a widget.
40
+ * 2. Re-register the child with the lifecycle observer if it was previously
41
+ * pruned as an orphan (built before being attached to the DOM).
42
+ * 3. Track the child in `_children` (avoids duplicates).
43
+ *
44
+ * @param {Node} child - The node to append.
45
+ * @returns {Node} The appended child (mirrors native behaviour).
46
+ */
47
+ widget.appendChild = function (child) {
48
+ if (child && child._isWidget === true) {
49
+ child.parent = this;
50
+ reregister(child);
51
+ }
52
+ if (!this._children.includes(child)) {
53
+ this._children.push(child);
54
+ }
55
+ return originalAppend.call(this, child);
56
+ };
57
+
58
+ /**
59
+ * Returns a shallow copy of the widget's direct children array.
60
+ *
61
+ * @returns {Array<Node>} Snapshot of `_children`.
62
+ */
63
+ widget.getChildren = () => [...widget._children];
64
+
65
+ const originalRemoveChild = widget.removeChild;
66
+
67
+ /**
68
+ * Overrides the native `removeChild` to:
69
+ * 1. Splice the child out of `_children`.
70
+ * 2. Clear `child.parent` when the child is a widget.
71
+ *
72
+ * @param {Node} child - The node to remove.
73
+ * @returns {Node} The removed child (mirrors native behaviour).
74
+ */
75
+ widget.removeChild = function (child) {
76
+ const index = this._children.indexOf(child);
77
+ if (index !== -1) {
78
+ this._children.splice(index, 1);
79
+ if (child && child._isWidget === true) {
80
+ child.parent = null;
81
+ }
82
+ }
83
+ return originalRemoveChild.call(this, child);
84
+ };
85
+
86
+ return widget;
87
+ };
88
+
89
+ export default makeParentable;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * PX TOOL - Converts numbers to PX
3
+ */
4
+
5
+ /**
6
+ * Converts a number to PX
7
+ * @param {number} value - Number to convert
8
+ * @returns {string} - Value in PX
9
+ *
10
+ * @example
11
+ * toPX(16) → '16px'
12
+ * toPX(24) → '24px'
13
+ */
14
+ export const toPX = (value) => {
15
+ if (typeof value !== "number") return value;
16
+ return `${value}px`;
17
+ };
18
+
19
+ /**
20
+ * Applies PX to a styles object
21
+ * @param {Object} styles - Styles with numbers
22
+ * @returns {Object} - Styles with PX
23
+ *
24
+ * @example
25
+ * applyPX({ padding: 16, fontSize: 24, color: 'red' })
26
+ * // → { padding: '16px', fontSize: '24px', color: 'red' }
27
+ */
28
+ export const applyPX = (styles) => {
29
+ const result = {};
30
+
31
+ for (const [key, value] of Object.entries(styles)) {
32
+ if (typeof value === "number") {
33
+ result[key] = toPX(value);
34
+ } else {
35
+ result[key] = value;
36
+ }
37
+ }
38
+
39
+ return result;
40
+ };
@@ -0,0 +1,122 @@
1
+ // core/processProps.js
2
+ import { getPropDefinition, ATTRIBUTE_DOM_SET } from "./translateProps.js";
3
+ import { toREM } from "./remTool.js"; // Centralized REM conversion utility
4
+
5
+ /**
6
+ * Processes raw props and classifies them into:
7
+ * - style : CSS properties
8
+ * - events : DOM event listeners
9
+ * - attributes : HTML attributes
10
+ * - textContent : text content
11
+ * - special : special props (like value for inputs) that need custom handling
12
+ *
13
+ * @param {Object} props - Raw props object
14
+ * @param {string} tag - HTML tag of the widget (e.g., 'div', 'span', 'input')
15
+ * @param {string} [widgetName] - Real FletBox widget name (e.g., 'QRCode'),
16
+ * used only to produce readable warnings. Falls back to the capitalized tag.
17
+ * @returns {{ style: Object, events: Object, attributes: Object, textContent: *, special: Object }}
18
+ */
19
+ export const processProps = (props, tag = "div", widgetName = null) => {
20
+ const style = {};
21
+ const events = {};
22
+ const attributes = {};
23
+ const special = {};
24
+ let textContent = null;
25
+
26
+ // Derive widget name from tag (e.g., 'div' → 'Div', 'span' → 'Span')
27
+ const name = widgetName || tag.charAt(0).toUpperCase() + tag.slice(1);
28
+
29
+ for (const [key, value] of Object.entries(props)) {
30
+ if (value === undefined || value === null) continue;
31
+
32
+ // `style` is deliberately NOT part of the API: FletBox uses flat, aliased
33
+ // CSS props. Fail loudly instead of silently ignoring the object.
34
+ if (key === "style") {
35
+ console.warn(
36
+ `⚠️ [${name}] The \`style\` prop is not supported. Pass CSS props flat ` +
37
+ `(e.g. backgroundColor, padding, flexDirection).`,
38
+ );
39
+ continue;
40
+ }
41
+
42
+ // Pass widget name context for better warnings
43
+ const def = getPropDefinition(key, { widgetName: name });
44
+
45
+ if (def) {
46
+ switch (def.type) {
47
+ case "style": {
48
+ // Shorthands may fan out to several CSS props (e.g. `px` →
49
+ // paddingLeft + paddingRight). Everything else writes to its domProp.
50
+ const cssValue = applyUnit(def, value);
51
+ const targets = def.targets || [def.domProp];
52
+ for (const target of targets) style[target] = cssValue;
53
+ break;
54
+ }
55
+ case "attribute":
56
+ attributes[def.domProp] = value;
57
+ break;
58
+ case "event":
59
+ events[def.domProp] = value;
60
+ break;
61
+ case "text":
62
+ // 'value' on input-like elements must be handled as a special prop,
63
+ // not as text content, to avoid conflicting with the DOM's own value.
64
+ if (
65
+ key === "value" &&
66
+ ["input", "textarea", "select"].includes(tag)
67
+ ) {
68
+ special[def.domProp] = value;
69
+ } else {
70
+ textContent = value;
71
+ }
72
+ break;
73
+ case "special":
74
+ // Store special props so assignProps can handle them with custom logic.
75
+ special[def.domProp] = value;
76
+ break;
77
+ default:
78
+ console.warn(`⚠️ Unknown type "${def.type}" for prop "${key}"`);
79
+ }
80
+ } else {
81
+ // Unknown prop → intelligent fallback
82
+ if (key.startsWith("data-") || key.startsWith("aria-")) {
83
+ attributes[key] = value;
84
+ } else if (key.startsWith("on") && typeof value === "function") {
85
+ const eventName = key.slice(2).toLowerCase();
86
+ events[eventName] = value;
87
+ } else if (ATTRIBUTE_DOM_SET.has(key)) {
88
+ attributes[key] = value;
89
+ } else {
90
+ style[key] = value;
91
+ }
92
+ }
93
+ }
94
+
95
+ return { style, events, attributes, textContent, special };
96
+ };
97
+
98
+ /**
99
+ * Applies the correct CSS unit to a numeric value based on the prop definition.
100
+ *
101
+ * - `true` / `false` are coerced to `1` / `0` (unitless).
102
+ * - Non-numeric values pass through unchanged.
103
+ * - `unit: "none"` returns the raw number without a suffix.
104
+ * - `unit: "rem"` delegates to the centralized `toREM` converter.
105
+ * - All other units default to `"px"`.
106
+ *
107
+ * @param {Object} def - Prop definition from the prop database (see `translateProps.js`).
108
+ * @param {*} value - The value to convert.
109
+ * @returns {string|number} Value with the correct CSS unit applied.
110
+ */
111
+ function applyUnit(def, value) {
112
+ if (value === true) return 1;
113
+ if (value === false) return 0;
114
+ if (typeof value !== "number") return value;
115
+ const unit = def.unit || "px";
116
+ if (unit === "none") return value;
117
+
118
+ // Use the dynamic REM converter instead of a hard-coded division.
119
+ if (unit === "rem") return toREM(value);
120
+
121
+ return `${value}px`;
122
+ }