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,115 @@
1
+ /**
2
+ * @file DraggBox.js
3
+ * @description Draggable wrapper that exposes drag events for drag-and-drop.
4
+ */
5
+ import { WidgetFactory } from "../widget-factory/index.js";
6
+ import { colors } from "../utils/themes.js";
7
+
8
+ export const DraggBox = (props) => {
9
+ const {
10
+ child,
11
+ data,
12
+ group = "default",
13
+ disabled = false,
14
+ onDragStart,
15
+ onDragEnd,
16
+ dragImage,
17
+ cloneOnDrag = true,
18
+ opacity = 0.5,
19
+
20
+ // Color props for consistency
21
+ dragOverlayColor = `${colors.primary}20`,
22
+ dragBorderColor = colors.primary,
23
+
24
+ ...rest
25
+ } = props;
26
+
27
+ if (!child) return null;
28
+
29
+ const dragId = `drag-${Date.now()}-${Math.random()}`;
30
+ let dragClone = null;
31
+
32
+ const wrapper = WidgetFactory({
33
+ tag: "div",
34
+ widgetName: "DraggBox",
35
+ display: "inline-block",
36
+ cursor: disabled ? "default" : "grab",
37
+ userSelect: "none",
38
+ draggable: !disabled,
39
+ child: child,
40
+ ...rest,
41
+ });
42
+
43
+ const createDragClone = () => {
44
+ const clone = wrapper.cloneNode(true);
45
+ clone.style.position = "fixed";
46
+ clone.style.top = "-1000px";
47
+ clone.style.left = "-1000px";
48
+ clone.style.width = `${wrapper.offsetWidth}px`;
49
+ clone.style.opacity = "0.8";
50
+ clone.style.transform = "rotate(5deg) scale(1.05)";
51
+ clone.style.transition = "none";
52
+ clone.style.pointerEvents = "none";
53
+ clone.style.zIndex = "99999";
54
+ clone.style.backgroundColor = dragOverlayColor;
55
+ clone.style.border = `2px dashed ${dragBorderColor}`;
56
+ clone.style.borderRadius = "8px";
57
+ document.body.appendChild(clone);
58
+ return clone;
59
+ };
60
+
61
+ wrapper.addEventListener("dragstart", (e) => {
62
+ if (disabled) {
63
+ e.preventDefault();
64
+ return false;
65
+ }
66
+
67
+ window.__dragData = {
68
+ data: data,
69
+ group: group,
70
+ dragId: dragId,
71
+ };
72
+
73
+ e.dataTransfer.setData("text/plain", dragId);
74
+ e.dataTransfer.effectAllowed = "copy";
75
+
76
+ if (cloneOnDrag && !dragImage) {
77
+ dragClone = createDragClone();
78
+ e.dataTransfer.setDragImage(dragClone, 20, 20);
79
+ } else if (dragImage) {
80
+ e.dataTransfer.setDragImage(dragImage, 20, 20);
81
+ } else {
82
+ const emptyImg = new Image();
83
+ emptyImg.src =
84
+ "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
85
+ e.dataTransfer.setDragImage(emptyImg, 0, 0);
86
+ }
87
+
88
+ wrapper.style.opacity = String(opacity);
89
+ wrapper.style.cursor = "grabbing";
90
+
91
+ if (onDragStart) onDragStart(e, data);
92
+ });
93
+
94
+ wrapper.addEventListener("dragend", (e) => {
95
+ wrapper.style.opacity = "";
96
+ wrapper.style.cursor = "grab";
97
+
98
+ if (dragClone && dragClone.parentNode) {
99
+ dragClone.parentNode.removeChild(dragClone);
100
+ dragClone = null;
101
+ }
102
+
103
+ setTimeout(() => {
104
+ if (window.__dragData?.dragId === dragId) {
105
+ window.__dragData = null;
106
+ }
107
+ }, 100);
108
+
109
+ if (onDragEnd) onDragEnd(e, data);
110
+ });
111
+
112
+ return wrapper;
113
+ };
114
+
115
+ export default DraggBox;
@@ -0,0 +1,505 @@
1
+ /**
2
+ * @file Dropdown.js
3
+ * @description Select dropdown with portal menu, unified textColor/borderRadius/bgColor.
4
+ */
5
+ import { WidgetFactory } from "../widget-factory/index.js";
6
+ import { colors } from "../utils/themes.js";
7
+ import { composeUpdate } from "../utils/composeUpdate.js";
8
+ import { Container } from "./Container.js";
9
+ import { Row } from "./Row.js";
10
+ import { Column } from "./Column.js";
11
+ import { Text } from "./Text.js";
12
+ import { Icon } from "./Icon.js";
13
+
14
+ /**
15
+ * Props owned by the Dropdown's closure state. Routed to the widget's `update`
16
+ * and never to the factory's (see `composeUpdate`).
17
+ */
18
+ const DROPDOWN_PROP_KEYS = [
19
+ "options", "value", "onChange", "onPress", "placeholder", "disabled",
20
+ "label", "error", "variant", "size", "borderRadius",
21
+ "color", "bgColor", "textColor", "optionHoverColor", "clearable",
22
+ "width", "portal",
23
+ ];
24
+
25
+ export const Dropdown = (props) => {
26
+ const {
27
+ options = [],
28
+ value = null,
29
+ onChange,
30
+ /** @deprecated Use onPress instead */
31
+ onPress: onPressProp,
32
+ placeholder = "Select...",
33
+ disabled = false,
34
+ label,
35
+ error = false,
36
+ variant = "outlined",
37
+ size = "medium",
38
+ borderRadius = 8,
39
+ color = colors.primary,
40
+ bgColor = colors.surface,
41
+ textColor = colors.text, // ← NEW: general text color
42
+ optionHoverColor = colors.border, // ← hover background on option
43
+ clearable = false,
44
+ width = "100%",
45
+ portal = true,
46
+ ...rest
47
+ } = props;
48
+
49
+ // onPress is the canonical callback name; onChange is kept as a deprecated alias
50
+ const effectiveOnChange = onPressProp || onChange;
51
+
52
+ // Internal state
53
+ let isOpen = false;
54
+ let selectedValue = value;
55
+ let currentOptions = [...options];
56
+ let currentDisabled = disabled;
57
+ let currentError = error;
58
+ let currentVariant = variant;
59
+ let currentPlaceholder = placeholder;
60
+ let currentLabel = label;
61
+ let currentClearable = clearable;
62
+ let currentColor = color;
63
+ let currentBgColor = bgColor;
64
+ let currentTextColor = textColor; // ← store textColor
65
+ let currentBorderRadius = borderRadius;
66
+ let currentOptionHoverColor = optionHoverColor;
67
+
68
+ let menuElement = null;
69
+ let outsideClickListener = null;
70
+ let resizeListener = null;
71
+ let scrollListener = null;
72
+
73
+ const sizes = {
74
+ small: { padding: "6px 12px", fontSize: 12 },
75
+ medium: { padding: "8px 14px", fontSize: 14 },
76
+ large: { padding: "12px 16px", fontSize: 16 },
77
+ };
78
+ let currentSize = sizes[size] || sizes.medium;
79
+
80
+ const getLabel = (opt) =>
81
+ typeof opt === "object" ? opt.label || String(opt.value) : String(opt);
82
+ const getValue = (opt) => (typeof opt === "object" ? opt.value : opt);
83
+ const getIcon = (opt) => (typeof opt === "object" ? opt.icon || null : null);
84
+ const findSelected = () =>
85
+ currentOptions.find((opt) => getValue(opt) === selectedValue);
86
+
87
+ // Update selector display
88
+ const updateDisplay = () => {
89
+ if (!selector) return;
90
+ const selected = findSelected();
91
+ const displayText = selected ? getLabel(selected) : "";
92
+ const displayIcon = selected ? getIcon(selected) : null;
93
+
94
+ const leftRow = selector.children[0];
95
+ if (!leftRow) return;
96
+ while (leftRow.firstChild) leftRow.removeChild(leftRow.firstChild);
97
+
98
+ if (displayIcon) leftRow.appendChild(Text({ text: displayIcon, size: 16 }));
99
+ leftRow.appendChild(
100
+ Text({
101
+ text: displayText || currentPlaceholder,
102
+ size: currentSize.fontSize,
103
+ color: displayText ? currentTextColor : colors.textSecondary,
104
+ flex: 1,
105
+ }),
106
+ );
107
+
108
+ if (selector.style) {
109
+ if (currentVariant === "outlined") {
110
+ selector.style.border = `1px solid ${currentError ? colors.danger : colors.border}`;
111
+ }
112
+ selector.style.opacity = currentDisabled ? "0.5" : "1";
113
+ selector.style.cursor = currentDisabled ? "not-allowed" : "pointer";
114
+ }
115
+ };
116
+
117
+ // Build selector
118
+ const selectedOption = findSelected();
119
+ const displayText = selectedOption ? getLabel(selectedOption) : "";
120
+ const displayIcon = selectedOption ? getIcon(selectedOption) : null;
121
+
122
+ const leftContent = [];
123
+ if (displayIcon) {
124
+ leftContent.push(
125
+ Text({ text: displayIcon, size: 16, flexShrink: 0 }),
126
+ );
127
+ }
128
+ leftContent.push(
129
+ Text({
130
+ text: displayText || currentPlaceholder,
131
+ size: currentSize.fontSize,
132
+ color: displayText ? currentTextColor : colors.textSecondary,
133
+ // minWidth 0 + overflow hidden: un nombre de entidad largo se recorta con
134
+ // puntos suspensivos en vez de empujar la flecha fuera del selector.
135
+ flex: 1,
136
+ minWidth: 0,
137
+ overflow: "hidden",
138
+ textOverflow: "ellipsis",
139
+ whiteSpace: "nowrap",
140
+ }),
141
+ );
142
+
143
+ const rightContent = [];
144
+ if (currentClearable && selectedValue && !currentDisabled) {
145
+ const clearIcon = Icon({
146
+ name: "close",
147
+ size: 16,
148
+ cursor: "pointer",
149
+ onclick: (e) => {
150
+ e.stopPropagation();
151
+ if (!currentDisabled) {
152
+ selectedValue = null;
153
+ if (effectiveOnChange) effectiveOnChange(null);
154
+ updateDisplay();
155
+ closeMenu();
156
+ }
157
+ },
158
+ });
159
+ rightContent.push(clearIcon);
160
+ }
161
+
162
+ const arrowIcon = Icon({
163
+ name: "expand_more",
164
+ size: 20,
165
+ transition: "transform 0.2s",
166
+ });
167
+ rightContent.push(arrowIcon);
168
+
169
+ const selector = Row({
170
+ alignItems: "center",
171
+ justifyContent: "space-between",
172
+ width: "100%",
173
+ padding: currentSize.padding,
174
+ backgroundColor:
175
+ currentVariant === "filled" ? `${currentBgColor}CC` : "transparent",
176
+ border:
177
+ currentVariant === "outlined"
178
+ ? `1px solid ${currentError ? colors.danger : colors.border}`
179
+ : "none",
180
+ borderRadius: `${currentBorderRadius}px`,
181
+ cursor: currentDisabled ? "not-allowed" : "pointer",
182
+ opacity: currentDisabled ? 0.5 : 1,
183
+ boxSizing: "border-box",
184
+ color: currentTextColor, // inherit text color
185
+ children: [
186
+ Row({
187
+ alignItems: "center",
188
+ gap: 8,
189
+ // minWidth: 0 para que un texto largo ENCOJA y no empuje al icono: sin
190
+ // él, el span no puede bajar de su ancho de contenido y el
191
+ // expand_more sale del selector.
192
+ flex: 1,
193
+ minWidth: 0,
194
+ children: leftContent,
195
+ }),
196
+ // El Row de la derecha TIENE que medir lo que su contenido. Row() trae
197
+ // width "100%" por defecto, y con eso este bloque se apropiaba de casi
198
+ // todo el ancho del selector: al no llevar justifyContent, el glifo
199
+ // quedaba anclado a su borde izquierdo, pegado al texto, en vez de
200
+ // apartado a la derecha. flexShrink 0 evita además que se apriete cuando
201
+ // el texto es largo.
202
+ Row({
203
+ alignItems: "center",
204
+ gap: 4,
205
+ width: "auto",
206
+ flexShrink: 0,
207
+ children: rightContent,
208
+ }),
209
+ ],
210
+ });
211
+
212
+ // Main container
213
+ const container = Container({
214
+ widgetName: "Dropdown",
215
+ position: "relative",
216
+ width: width,
217
+ ...rest,
218
+ });
219
+
220
+ if (currentLabel) {
221
+ const labelEl = WidgetFactory({
222
+ tag: "label",
223
+ textContent: currentLabel,
224
+ fontSize: "12px",
225
+ color: currentError ? colors.danger : colors.textSecondary,
226
+ marginBottom: "4px",
227
+ display: "block",
228
+ });
229
+ container.appendChild(labelEl);
230
+ }
231
+ container.appendChild(selector);
232
+
233
+ // Menu functions (portal)
234
+ const closeMenu = () => {
235
+ if (!isOpen) return;
236
+ if (menuElement && menuElement.parentNode) {
237
+ menuElement.parentNode.removeChild(menuElement);
238
+ }
239
+ menuElement = null;
240
+ arrowIcon.style.transform = "rotate(0deg)";
241
+ isOpen = false;
242
+
243
+ if (outsideClickListener)
244
+ document.removeEventListener("click", outsideClickListener);
245
+ if (resizeListener) window.removeEventListener("resize", resizeListener);
246
+ if (scrollListener) window.removeEventListener("scroll", scrollListener);
247
+ outsideClickListener = resizeListener = scrollListener = null;
248
+ };
249
+
250
+ const updateMenuPosition = () => {
251
+ if (!menuElement) return;
252
+ const selectorRect = selector.getBoundingClientRect();
253
+ const menuHeight = menuElement.offsetHeight;
254
+ const viewportHeight = window.innerHeight;
255
+
256
+ let left = selectorRect.left;
257
+ if (left + selectorRect.width > window.innerWidth) {
258
+ left = window.innerWidth - selectorRect.width;
259
+ }
260
+ left = Math.max(8, left);
261
+
262
+ let top = selectorRect.bottom + 4;
263
+ let bottom = "auto";
264
+ if (top + menuHeight > viewportHeight) {
265
+ top = "auto";
266
+ bottom = viewportHeight - selectorRect.top + 4;
267
+ }
268
+
269
+ menuElement.style.position = "fixed";
270
+ menuElement.style.left = `${left}px`;
271
+ menuElement.style.width = `${selectorRect.width}px`;
272
+ if (top !== "auto") {
273
+ menuElement.style.top = `${top}px`;
274
+ menuElement.style.bottom = "auto";
275
+ } else {
276
+ menuElement.style.top = "auto";
277
+ menuElement.style.bottom = `${bottom}px`;
278
+ }
279
+ };
280
+
281
+ const openMenu = () => {
282
+ if (currentDisabled || isOpen) return;
283
+ closeMenu();
284
+ isOpen = true;
285
+
286
+ menuElement = Column({
287
+ zIndex: 10000,
288
+ border: `1px solid ${colors.border}`,
289
+ backgroundColor: currentBgColor,
290
+ borderRadius: `${currentBorderRadius}px`, // same borderRadius
291
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
292
+ maxHeight: "250px",
293
+ overflowY: "auto",
294
+ margin: 0,
295
+ padding: 0,
296
+ color: currentTextColor, // inherit textColor
297
+ });
298
+
299
+ currentOptions.forEach((opt) => {
300
+ const optValue = getValue(opt);
301
+ const optLabel = getLabel(opt);
302
+ const optIcon = getIcon(opt);
303
+ const isSelected = optValue === selectedValue;
304
+
305
+ const leftOption = [];
306
+ if (optIcon)
307
+ leftOption.push(
308
+ Text({ text: optIcon, size: 16, color: currentTextColor }),
309
+ );
310
+ leftOption.push(
311
+ Text({
312
+ text: optLabel,
313
+ size: currentSize.fontSize,
314
+ flex: 1,
315
+ color: currentTextColor,
316
+ }),
317
+ );
318
+
319
+ const optionItem = Row({
320
+ padding: "10px 16px",
321
+ cursor: "pointer",
322
+ backgroundColor: isSelected ? currentColor + "15" : "transparent",
323
+ alignItems: "center",
324
+ justifyContent: "space-between",
325
+ gap: "8px",
326
+ children: [
327
+ Row({
328
+ alignItems: "center",
329
+ gap: 8,
330
+ flex: 1,
331
+ children: leftOption,
332
+ }),
333
+ isSelected
334
+ ? Icon({ name: "check", size: 16, color: currentColor })
335
+ : null,
336
+ ].filter(Boolean),
337
+ });
338
+
339
+ optionItem.onmouseenter = () => {
340
+ if (!isSelected)
341
+ optionItem.style.backgroundColor = currentOptionHoverColor;
342
+ };
343
+ optionItem.onmouseleave = () => {
344
+ optionItem.style.backgroundColor = isSelected
345
+ ? currentColor + "15"
346
+ : "transparent";
347
+ };
348
+ optionItem.onclick = (e) => {
349
+ e.stopPropagation();
350
+ if (!currentDisabled) {
351
+ selectedValue = optValue;
352
+ if (effectiveOnChange) effectiveOnChange(optValue);
353
+ updateDisplay();
354
+ closeMenu();
355
+ }
356
+ };
357
+ menuElement.appendChild(optionItem);
358
+ });
359
+
360
+ if (portal) {
361
+ document.body.appendChild(menuElement);
362
+ updateMenuPosition();
363
+ resizeListener = () => updateMenuPosition();
364
+ scrollListener = () => updateMenuPosition();
365
+ window.addEventListener("resize", resizeListener);
366
+ window.addEventListener("scroll", scrollListener, true);
367
+ } else {
368
+ container.appendChild(menuElement);
369
+ menuElement.style.position = "absolute";
370
+ menuElement.style.left = "0";
371
+ menuElement.style.top = "100%";
372
+ menuElement.style.width = "100%";
373
+ }
374
+
375
+ arrowIcon.style.transform = "rotate(180deg)";
376
+
377
+ outsideClickListener = (e) => {
378
+ if (
379
+ !container.contains(e.target) &&
380
+ (!menuElement || !menuElement.contains(e.target))
381
+ ) {
382
+ closeMenu();
383
+ }
384
+ };
385
+ document.addEventListener("click", outsideClickListener);
386
+ };
387
+
388
+ selector.onclick = (e) => {
389
+ e.stopPropagation();
390
+ if (currentDisabled) return;
391
+ isOpen ? closeMenu() : openMenu();
392
+ };
393
+
394
+ // Public API and reactivity
395
+ const update = (newProps) => {
396
+ let needsDisplayUpdate = false;
397
+ if (newProps.options !== undefined) {
398
+ currentOptions = [...newProps.options];
399
+ needsDisplayUpdate = true;
400
+ }
401
+ if (newProps.value !== undefined && newProps.value !== selectedValue) {
402
+ selectedValue = newProps.value;
403
+ needsDisplayUpdate = true;
404
+ if (effectiveOnChange) effectiveOnChange(selectedValue);
405
+ }
406
+ if (newProps.disabled !== undefined) {
407
+ currentDisabled = newProps.disabled;
408
+ needsDisplayUpdate = true;
409
+ if (currentDisabled && isOpen) closeMenu();
410
+ }
411
+ if (newProps.error !== undefined) {
412
+ currentError = newProps.error;
413
+ needsDisplayUpdate = true;
414
+ }
415
+ if (newProps.variant !== undefined) {
416
+ currentVariant = newProps.variant;
417
+ needsDisplayUpdate = true;
418
+ }
419
+ if (newProps.placeholder !== undefined) {
420
+ currentPlaceholder = newProps.placeholder;
421
+ needsDisplayUpdate = true;
422
+ }
423
+ if (newProps.label !== undefined) {
424
+ currentLabel = newProps.label;
425
+ const existingLabel = container.querySelector("label");
426
+ if (existingLabel) existingLabel.textContent = currentLabel;
427
+ else if (currentLabel) {
428
+ const labelEl = WidgetFactory({
429
+ tag: "label",
430
+ textContent: currentLabel,
431
+ fontSize: "12px",
432
+ color: currentError ? colors.danger : colors.textSecondary,
433
+ marginBottom: "4px",
434
+ display: "block",
435
+ });
436
+ container.insertBefore(labelEl, container.firstChild);
437
+ }
438
+ }
439
+ if (newProps.clearable !== undefined) {
440
+ currentClearable = newProps.clearable;
441
+ needsDisplayUpdate = true;
442
+ }
443
+ if (newProps.color !== undefined) {
444
+ currentColor = newProps.color;
445
+ needsDisplayUpdate = true;
446
+ }
447
+ if (newProps.bgColor !== undefined) {
448
+ currentBgColor = newProps.bgColor;
449
+ needsDisplayUpdate = true;
450
+ }
451
+ if (newProps.textColor !== undefined) {
452
+ currentTextColor = newProps.textColor;
453
+ needsDisplayUpdate = true;
454
+ }
455
+ if (newProps.borderRadius !== undefined) {
456
+ currentBorderRadius = newProps.borderRadius;
457
+ needsDisplayUpdate = true;
458
+ }
459
+ if (newProps.optionHoverColor !== undefined) {
460
+ currentOptionHoverColor = newProps.optionHoverColor;
461
+ }
462
+ if (newProps.size !== undefined && sizes[newProps.size]) {
463
+ currentSize = sizes[newProps.size];
464
+ needsDisplayUpdate = true;
465
+ }
466
+ if (needsDisplayUpdate) {
467
+ updateDisplay();
468
+ selector.style.padding = currentSize.padding;
469
+ selector.style.borderRadius = `${currentBorderRadius}px`;
470
+ if (currentVariant === "outlined")
471
+ selector.style.border = `1px solid ${currentError ? colors.danger : colors.border}`;
472
+ else selector.style.border = "none";
473
+ selector.style.backgroundColor =
474
+ currentVariant === "filled" ? `${currentBgColor}CC` : "transparent";
475
+ }
476
+ };
477
+
478
+ Object.defineProperty(container, "value", {
479
+ get: () => selectedValue,
480
+ set: (v) => update({ value: v }),
481
+ });
482
+ Object.defineProperty(container, "options", {
483
+ get: () => currentOptions,
484
+ set: (opts) => update({ options: opts }),
485
+ });
486
+ Object.defineProperty(container, "disabled", {
487
+ get: () => currentDisabled,
488
+ set: (d) => update({ disabled: d }),
489
+ });
490
+ Object.defineProperty(container, "error", {
491
+ get: () => currentError,
492
+ set: (e) => update({ error: e }),
493
+ });
494
+ container.open = openMenu;
495
+ container.close = closeMenu;
496
+ composeUpdate(container, DROPDOWN_PROP_KEYS, update);
497
+
498
+ container.onUnmount(() => {
499
+ closeMenu();
500
+ });
501
+
502
+ return container;
503
+ };
504
+
505
+ export default Dropdown;