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,87 @@
1
+ // src/modules/FletBox/utils/units.js
2
+ // import { toREM, setBaseFontSize, toPX } from './utils/units.js';
3
+ //
4
+ // // Global configuration (optional)
5
+ // setBaseFontSize(16); // 1rem = 16px (default)
6
+ //
7
+ // // Automatic conversions
8
+ // toREM(16) // "1rem"
9
+ // toREM(24) // "1.5rem"
10
+ // toREM(8) // "0.5rem"
11
+ //
12
+ // // Strings pass through as-is
13
+ // toREM('16px') // "16px"
14
+ // toREM('1rem') // "1rem"
15
+ // toREM('50%') // "50%"
16
+ //
17
+ // // For exact pixels
18
+ // toPX(16) // "16px"
19
+ // toPX(24) // "24px"
20
+ // toPX('1rem') // "1rem"
21
+
22
+ let BASE_FONT_SIZE = 16;
23
+
24
+ /**
25
+ * Sets the base font size for rem conversion
26
+ * @param {number} size - Base size in pixels (default 16)
27
+ * @example
28
+ * setBaseFontSize(16); // 1rem = 16px
29
+ * setBaseFontSize(20); // 1rem = 20px
30
+ */
31
+ export const setBaseFontSize = (size) => {
32
+ BASE_FONT_SIZE = size;
33
+ document.documentElement.style.fontSize = `${size}px`;
34
+ };
35
+
36
+ /**
37
+ * Converts a numeric value to rem, or keeps strings as they are
38
+ * @param {number|string} value - Value to convert
39
+ * @returns {string} Converted value (e.g. "1rem", "16px", "50%")
40
+ * @example
41
+ * toREM(16) // "1rem"
42
+ * toREM(8) // "0.5rem"
43
+ * toREM('16px') // "16px"
44
+ * toREM('1rem') // "1rem"
45
+ * toREM('50%') // "50%"
46
+ */
47
+ export const toREM = (value) => {
48
+ if (value === undefined || value === null) return undefined;
49
+
50
+ // If it is a number, convert to rem
51
+ if (typeof value === "number") {
52
+ return `${value / BASE_FONT_SIZE}rem`;
53
+ }
54
+
55
+ // If it is a string, return it as-is
56
+ if (typeof value === "string") {
57
+ return value;
58
+ }
59
+
60
+ // For other types (objects, booleans, etc.)
61
+ return String(value);
62
+ };
63
+
64
+ /**
65
+ * Converts a value to pixels if it is a number, or keeps strings
66
+ * @param {number|string} value - Value to convert
67
+ * @returns {string} Value in pixels or original string
68
+ * @example
69
+ * toPX(16) // "16px"
70
+ * toPX('1rem') // "1rem"
71
+ * toPX('50%') // "50%"
72
+ */
73
+ export const toPX = (value) => {
74
+ if (value === undefined || value === null) return undefined;
75
+
76
+ if (typeof value === "number") {
77
+ return `${value}px`;
78
+ }
79
+
80
+ return value;
81
+ };
82
+
83
+ /**
84
+ * Gets the current base size
85
+ * @returns {number} Base size in pixels
86
+ */
87
+ export const getBaseFontSize = () => BASE_FONT_SIZE;
@@ -0,0 +1,169 @@
1
+ // utils/visualEffects.js
2
+ /**
3
+ * Visual Effects - Reusable visual effects for widgets
4
+ * @module utils/visualEffects
5
+ */
6
+
7
+ // Cache for injected keyframes
8
+ const injectedKeyframes = new Set();
9
+
10
+ /**
11
+ * Inject CSS keyframes if not already injected
12
+ * @param {string} name - Animation name
13
+ * @param {string} keyframes - CSS keyframes
14
+ */
15
+ export const injectKeyframes = (name, keyframes) => {
16
+ if (injectedKeyframes.has(name)) return;
17
+ const style = document.createElement("style");
18
+ style.textContent = `@keyframes ${name} { ${keyframes} }`;
19
+ document.head.appendChild(style);
20
+ injectedKeyframes.add(name);
21
+ };
22
+
23
+ /**
24
+ * Apply stripe pattern to an element
25
+ * @param {HTMLElement} element - Target element
26
+ * @param {Object} options - Stripe options
27
+ */
28
+ export const applyStripes = (element, options = {}) => {
29
+ const {
30
+ color = "rgba(255, 255, 255, 0.15)",
31
+ size = 10,
32
+ angle = 45,
33
+ animated = false,
34
+ duration = "0.5s",
35
+ } = options;
36
+
37
+ const stripePattern = `repeating-linear-gradient(
38
+ ${angle}deg,
39
+ ${color} 0px,
40
+ ${color} ${size}px,
41
+ transparent ${size}px,
42
+ transparent ${size * 2}px
43
+ )`;
44
+
45
+ element.style.backgroundImage = stripePattern;
46
+ element.style.backgroundSize = `${size * 2}px ${size * 2}px`;
47
+
48
+ if (animated) {
49
+ element.style.animation = `progress-stripes ${duration} linear infinite`;
50
+
51
+ injectKeyframes(
52
+ "progress-stripes",
53
+ `
54
+ 0% { background-position: 0 0; }
55
+ 100% { background-position: ${size * 2}px 0; }
56
+ `,
57
+ );
58
+ }
59
+ };
60
+
61
+ /**
62
+ * Remove stripe pattern from element
63
+ */
64
+ export const removeStripes = (element) => {
65
+ element.style.backgroundImage = "";
66
+ element.style.animation = "";
67
+ };
68
+
69
+ /**
70
+ * Apply shimmer/skeleton effect
71
+ */
72
+ export const applyShimmer = (element, options = {}) => {
73
+ const {
74
+ highlightColor = "rgba(255, 255, 255, 0.3)",
75
+ baseColor = "rgba(0, 0, 0, 0.05)",
76
+ duration = "1.5s",
77
+ } = options;
78
+
79
+ element.style.background = `linear-gradient(
80
+ 110deg,
81
+ ${baseColor} 0%,
82
+ ${baseColor} 35%,
83
+ ${highlightColor} 50%,
84
+ ${baseColor} 65%,
85
+ ${baseColor} 100%
86
+ )`;
87
+ element.style.backgroundSize = "200% 100%";
88
+ element.style.animation = `shimmer ${duration} ease-in-out infinite`;
89
+
90
+ injectKeyframes(
91
+ "shimmer",
92
+ `
93
+ 0% { background-position: 200% 0; }
94
+ 100% { background-position: -200% 0; }
95
+ `,
96
+ );
97
+ };
98
+
99
+ /**
100
+ * Apply glow effect
101
+ */
102
+ export const applyGlow = (element, options = {}) => {
103
+ const {
104
+ color = "rgba(0, 122, 255, 0.5)",
105
+ intensity = 10,
106
+ duration = "2s",
107
+ } = options;
108
+
109
+ element.style.transition = "box-shadow 0.2s ease";
110
+ element.style.animation = `glow-pulse ${duration} ease-in-out infinite`;
111
+
112
+ injectKeyframes(
113
+ "glow-pulse",
114
+ `
115
+ 0% { box-shadow: 0 0 0 ${color}; }
116
+ 50% { box-shadow: 0 0 ${intensity}px ${color}; }
117
+ 100% { box-shadow: 0 0 0 ${color}; }
118
+ `,
119
+ );
120
+ };
121
+
122
+ /**
123
+ * Apply indeterminate loading animation
124
+ */
125
+ export const applyIndeterminate = (element, options = {}) => {
126
+ const { duration = "1.5s", width = "50%" } = options;
127
+
128
+ element.style.position = "relative";
129
+ element.style.width = width;
130
+ element.style.animation = `indeterminate-progress ${duration} ease-in-out infinite`;
131
+
132
+ injectKeyframes(
133
+ "indeterminate-progress",
134
+ `
135
+ 0% { transform: translateX(-100%); }
136
+ 50% { transform: translateX(0%); }
137
+ 100% { transform: translateX(100%); }
138
+ `,
139
+ );
140
+ };
141
+
142
+ /**
143
+ * Apply pulse animation
144
+ */
145
+ export const applyPulse = (element, options = {}) => {
146
+ const { scale = 1.05, duration = "0.3s" } = options;
147
+
148
+ element.style.transition = `transform ${duration} ease`;
149
+ element.style.animation = `pulse ${duration} ease-in-out`;
150
+
151
+ injectKeyframes(
152
+ "pulse",
153
+ `
154
+ 0% { transform: scale(1); }
155
+ 50% { transform: scale(${scale}); }
156
+ 100% { transform: scale(1); }
157
+ `,
158
+ );
159
+ };
160
+
161
+ export default {
162
+ injectKeyframes,
163
+ applyStripes,
164
+ removeStripes,
165
+ applyShimmer,
166
+ applyGlow,
167
+ applyIndeterminate,
168
+ applyPulse,
169
+ };
@@ -0,0 +1,114 @@
1
+ // core/addChildren.js
2
+
3
+ /**
4
+ * Tracks which child nodes were appended via props (`child` / `children`).
5
+ * Using a WeakSet (external to the nodes) works with any Node type, including
6
+ * plain Text nodes, which cannot carry extra properties.
7
+ *
8
+ * @type {WeakSet<Node>}
9
+ */
10
+ const PROP_CHILDREN = new WeakSet();
11
+
12
+ /**
13
+ * Appends one or more children to a parent widget.
14
+ *
15
+ * Accepts a single child or an array of children. Each item may be:
16
+ * - A DOM Node (appended directly).
17
+ * - A string or number (converted to a TextNode).
18
+ * - `null`, `undefined`, or `false` (skipped silently).
19
+ *
20
+ * When `markAsProp` is `true` (the default), every appended node is recorded
21
+ * in `PROP_CHILDREN` so that `removePropChildren` can selectively remove only
22
+ * those nodes later.
23
+ *
24
+ * @param {HTMLElement} parent - The parent widget to append children to.
25
+ * @param {Node|string|number|Array|null|undefined|false} children - Child or
26
+ * array of children to append.
27
+ * @param {Object} [options={}] - Options.
28
+ * @param {boolean} [options.markAsProp=true] - Whether to mark appended nodes
29
+ * as prop-managed so they can be cleaned up by `removePropChildren`.
30
+ * @returns {HTMLElement} The parent widget (for chaining).
31
+ */
32
+ export const addChildren = (parent, children, options = {}) => {
33
+ if (!children && children !== 0) return parent;
34
+
35
+ const { markAsProp = true } = options;
36
+ const items = Array.isArray(children) ? children : [children];
37
+
38
+ items.forEach((child) => {
39
+ if (child === null || child === undefined || child === false) return;
40
+
41
+ let node = child;
42
+ if (typeof child === "string" || typeof child === "number") {
43
+ node = document.createTextNode(String(child));
44
+ }
45
+
46
+ if (markAsProp) {
47
+ PROP_CHILDREN.add(node);
48
+ }
49
+
50
+ parent.appendChild(node);
51
+ });
52
+
53
+ return parent;
54
+ };
55
+
56
+ /**
57
+ * Removes all children that were previously appended via props (i.e. tracked in
58
+ * `PROP_CHILDREN`).
59
+ *
60
+ * Handles two cases:
61
+ * 1. **Normal path** – the node is still a direct child of `parent`. The
62
+ * overridden `removeChild` is used, which also updates `_children`.
63
+ * 2. **Fallback path** – the node was moved to a different parent (e.g. by
64
+ * drag-and-drop). `_children` is updated manually and the widget's `parent`
65
+ * reference is cleared.
66
+ *
67
+ * In both cases the node is removed from `PROP_CHILDREN` to allow future
68
+ * garbage collection.
69
+ *
70
+ * @param {HTMLElement} parent - The parent widget whose prop-children should be
71
+ * removed.
72
+ * @returns {HTMLElement} The parent widget (for chaining).
73
+ */
74
+ export const removePropChildren = (parent) => {
75
+ const toRemove = Array.from(parent.childNodes).filter((child) =>
76
+ PROP_CHILDREN.has(child)
77
+ );
78
+
79
+ toRemove.forEach((child) => {
80
+ if (child.parentNode === parent) {
81
+ // Normal path: removeChild override also cleans up _children.
82
+ parent.removeChild(child);
83
+ } else {
84
+ // Fallback: node was moved; manually clean up _children.
85
+ const idx = parent._children.indexOf(child);
86
+ if (idx !== -1) parent._children.splice(idx, 1);
87
+ if (child._isWidget) child.parent = null;
88
+ }
89
+ PROP_CHILDREN.delete(child);
90
+ });
91
+
92
+ return parent;
93
+ };
94
+
95
+ /**
96
+ * Removes ALL children from a parent widget, regardless of whether they were
97
+ * appended via props or programmatically.
98
+ *
99
+ * Resets `_children` to an empty array and clears every node from
100
+ * `PROP_CHILDREN` as it is removed.
101
+ *
102
+ * @param {HTMLElement} parent - The parent widget to clear.
103
+ * @returns {HTMLElement} The parent widget (for chaining).
104
+ */
105
+ export const clearChildren = (parent) => {
106
+ while (parent.firstChild) {
107
+ PROP_CHILDREN.delete(parent.firstChild);
108
+ parent.removeChild(parent.firstChild);
109
+ }
110
+ parent._children = [];
111
+ return parent;
112
+ };
113
+
114
+ export default { addChildren, removePropChildren, clearChildren };
@@ -0,0 +1,256 @@
1
+ // core/assignProps.js
2
+ import { setStyles } from "./tools.js";
3
+
4
+ /**
5
+ * Extracts the real value from a prop that may be a reactive accessor or a
6
+ * plain reactive object.
7
+ *
8
+ * - If the value is a function flagged with `_isReactive`, calls it to get the
9
+ * current value.
10
+ * - If the value is a reactive object (has `_isReactive === true`), returns
11
+ * `_value` when available, otherwise falls back to `valueOf()`.
12
+ * - Otherwise returns the value unchanged.
13
+ *
14
+ * @param {*} val - The raw prop value (may be reactive or plain).
15
+ * @returns {*} The resolved runtime value.
16
+ */
17
+ const getValue = (val) => {
18
+ if (typeof val === "function" && val._isReactive) {
19
+ return val();
20
+ }
21
+ if (val && typeof val === "object" && val._isReactive === true) {
22
+ return val._value !== undefined ? val._value : val.valueOf();
23
+ }
24
+ return val;
25
+ };
26
+
27
+ /**
28
+ * HTML attributes whose mere presence means "true". Writing them through
29
+ * `setAttribute` with a falsy value is wrong — `setAttribute("readonly", false)`
30
+ * leaves the attribute present (`readonly="false"`) and the field stays
31
+ * read-only. These are written as DOM properties instead, so passing `false`
32
+ * removes the attribute.
33
+ *
34
+ * @type {Set<string>}
35
+ */
36
+ const BOOLEAN_ATTRIBUTES = new Set([
37
+ "disabled",
38
+ "checked",
39
+ "selected",
40
+ "readOnly",
41
+ "required",
42
+ "autofocus",
43
+ "hidden",
44
+ "draggable",
45
+ ]);
46
+
47
+ /**
48
+ * Applies a single "special" prop directly to the widget. Handles cases that
49
+ * are not pure CSS assignments, such as `value` for form elements and `variant`
50
+ * as a dataset attribute.
51
+ *
52
+ * @param {HTMLElement} widget - The target DOM element.
53
+ * @param {string} key - The special prop name (e.g. `'variant'`).
54
+ * @param {*} value - The raw value (resolved via `getValue` internally).
55
+ * @returns {void}
56
+ */
57
+ const applySpecial = (widget, key, value) => {
58
+ const val = getValue(value);
59
+
60
+ switch (key) {
61
+ case "value":
62
+ if (widget.tagName === "INPUT" || widget.tagName === "TEXTAREA") {
63
+ if (widget.value !== val) widget.value = val;
64
+ }
65
+ break;
66
+ case "variant":
67
+ widget.dataset.variant = val;
68
+ break;
69
+ }
70
+ };
71
+
72
+ /**
73
+ * Applies a processed props object to a widget element.
74
+ *
75
+ * Handles five prop categories in order:
76
+ * 1. **style** – merged into the element's inline style via `setStyles`.
77
+ * 2. **textContent** – set as `value` on form elements, `textContent` elsewhere.
78
+ * 3. **events** – previous listeners are removed and the new set is attached.
79
+ * `click` is assigned via `onclick` for reliable override behaviour.
80
+ * 4. **attributes** – applied with `setAttribute`, with special handling for
81
+ * `className` and boolean attributes (`disabled`, `readOnly`, `hidden`, …).
82
+ * 5. **special** – delegated to `applySpecial` (e.g. `value`, `variant`).
83
+ *
84
+ * Any keys not in the known set are handled as a flat-prop fallback: `on*`
85
+ * functions become event listeners, common HTML attributes are set directly, and
86
+ * everything else is tried as an inline style.
87
+ *
88
+ * The `_updating` guard prevents re-entrant calls from triggering reactive
89
+ * side-effects during the assignment.
90
+ *
91
+ * @param {HTMLElement} widget - The target DOM element.
92
+ * @param {Object|null} props - Processed props object from `processProps`, or
93
+ * `null` / `undefined` to no-op.
94
+ * @returns {HTMLElement} The same widget (for chaining).
95
+ */
96
+ export const assignProps = (widget, props) => {
97
+ if (!props) return widget;
98
+
99
+ const wasUpdating = widget._updating;
100
+ widget._updating = true;
101
+
102
+ try {
103
+ // ============================================================
104
+ // 1. STYLES
105
+ // ============================================================
106
+ if (props.style && Object.keys(props.style).length > 0) {
107
+ setStyles(widget, props.style);
108
+ }
109
+
110
+ // ============================================================
111
+ // 2. TEXT
112
+ // ============================================================
113
+ if (props.textContent !== undefined) {
114
+ const textValue = getValue(props.textContent);
115
+ if (widget.tagName === "INPUT" || widget.tagName === "TEXTAREA") {
116
+ if (widget.value !== textValue) widget.value = textValue;
117
+ } else {
118
+ if (widget.textContent !== textValue) widget.textContent = textValue;
119
+ }
120
+ }
121
+
122
+ // ============================================================
123
+ // 3. EVENTS
124
+ // ============================================================
125
+ // Remove all previously registered listeners before adding the new ones.
126
+ if (widget._events && widget._events.length > 0) {
127
+ widget._events.forEach(({ event, handler }) => {
128
+ widget.removeEventListener(event, handler);
129
+ });
130
+ widget._events = [];
131
+ }
132
+
133
+ widget.onclick = null;
134
+
135
+ if (props.events && Object.keys(props.events).length > 0) {
136
+ Object.entries(props.events).forEach(([event, handler]) => {
137
+ if (typeof handler !== "function") return;
138
+ if (event === "click") {
139
+ widget.onclick = handler;
140
+ } else {
141
+ widget.addEventListener(event, handler);
142
+ widget._events.push({ event, handler });
143
+ }
144
+ });
145
+ }
146
+
147
+ // ============================================================
148
+ // 4. ATTRIBUTES
149
+ // ============================================================
150
+ if (props.attributes && Object.keys(props.attributes).length > 0) {
151
+ Object.entries(props.attributes).forEach(([key, value]) => {
152
+ const finalValue = getValue(value);
153
+ try {
154
+ if (key === "className" || key === "class") {
155
+ widget.className = finalValue;
156
+ } else if (BOOLEAN_ATTRIBUTES.has(key)) {
157
+ widget[key] =
158
+ typeof finalValue === "boolean"
159
+ ? finalValue
160
+ : Boolean(finalValue);
161
+ } else {
162
+ widget.setAttribute(key, finalValue);
163
+ }
164
+ } catch (e) {
165
+ // Invalid attribute – silently skip
166
+ }
167
+ });
168
+ }
169
+
170
+ // ============================================================
171
+ // 5. SPECIALS
172
+ // ============================================================
173
+ if (props.special) {
174
+ for (const [key, value] of Object.entries(props.special)) {
175
+ applySpecial(widget, key, value);
176
+ }
177
+ }
178
+
179
+ // ============================================================
180
+ // 6. FALLBACK FOR FLAT PROPS
181
+ // ============================================================
182
+ // Props not belonging to any known category are handled here as a best-
183
+ // effort: event handlers, common HTML attributes, and inline CSS.
184
+ const knownKeys = new Set([
185
+ "style",
186
+ "events",
187
+ "attributes",
188
+ "textContent",
189
+ "special",
190
+ "child",
191
+ "children",
192
+ "ref",
193
+ ]);
194
+
195
+ const extraKeys = Object.keys(props).filter((key) => !knownKeys.has(key));
196
+ if (extraKeys.length > 0) {
197
+ for (const key of extraKeys) {
198
+ const value = props[key];
199
+ if (value === undefined || value === null) continue;
200
+
201
+ if (key.startsWith("on") && typeof value === "function") {
202
+ const eventName = key.slice(2).toLowerCase();
203
+ if (eventName === "click") {
204
+ widget.onclick = value;
205
+ } else {
206
+ widget.addEventListener(eventName, value);
207
+ widget._events.push({ event: eventName, handler: value });
208
+ }
209
+ } else if (
210
+ [
211
+ "className",
212
+ "class",
213
+ "id",
214
+ "type",
215
+ "href",
216
+ "src",
217
+ "alt",
218
+ "title",
219
+ "role",
220
+ ].includes(key)
221
+ ) {
222
+ const finalValue = getValue(value);
223
+ if (key === "className" || key === "class") {
224
+ widget.className = finalValue;
225
+ } else {
226
+ try {
227
+ widget.setAttribute(key, finalValue);
228
+ } catch (e) {}
229
+ }
230
+ } else if (key === "text" || key === "label" || key === "caption") {
231
+ const textValue = getValue(value);
232
+ if (widget.tagName === "INPUT" || widget.tagName === "TEXTAREA") {
233
+ if (widget.value !== textValue) widget.value = textValue;
234
+ } else {
235
+ if (widget.textContent !== textValue) widget.textContent = textValue;
236
+ }
237
+ } else {
238
+ const finalValue = getValue(value);
239
+ const cssValue =
240
+ typeof finalValue === "number" ? finalValue + "px" : finalValue;
241
+ try {
242
+ widget.style[key] = cssValue;
243
+ } catch (e) {}
244
+ }
245
+ }
246
+ }
247
+ } catch (error) {
248
+ console.error("❌ [assignProps] Error:", error);
249
+ } finally {
250
+ widget._updating = wasUpdating;
251
+ }
252
+
253
+ return widget;
254
+ };
255
+
256
+ export default assignProps;
@@ -0,0 +1,28 @@
1
+ // core/createWidget.js
2
+
3
+ /**
4
+ * Creates a bare DOM element for use as a widget.
5
+ *
6
+ * This is the lowest-level factory step. It validates the tag, creates the
7
+ * element, and initialises the `_events` array that all higher-level modules
8
+ * rely on for listener tracking. No props are applied here — that is the
9
+ * responsibility of `assignProps`.
10
+ *
11
+ * @param {string} tag - HTML tag name for the element (e.g. `'div'`, `'button'`).
12
+ * Defaults to `'div'` when the value is not a string.
13
+ * @param {Object} props - Reserved for future use; currently unused.
14
+ * @returns {HTMLElement} The newly created DOM element with `_events` initialised.
15
+ */
16
+ export const createWidget = (tag, props) => {
17
+ // Validateste that tag is a string; fall back to 'div' when it is not.
18
+ let finalTag = tag;
19
+ if (typeof finalTag !== "string") {
20
+ console.warn("⚠️ createWidget: tag is not a string, using 'div'", finalTag);
21
+ finalTag = "div";
22
+ }
23
+
24
+ const widget = document.createElement(finalTag);
25
+ widget._events = [];
26
+
27
+ return widget;
28
+ };