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,384 @@
1
+ // navigations/Tabs.js
2
+ import { WidgetFactory } from "../widget-factory/index.js";
3
+ import { colors } from "../utils/themes.js";
4
+ import { Container } from "../widgets/Container.js";
5
+ import { Row } from "../widgets/Row.js";
6
+ import { Text } from "../widgets/Text.js";
7
+ import { Icon } from "../widgets/Icon.js";
8
+
9
+ /**
10
+ * @typedef {Object} TabDefinition
11
+ * @property {string} label - Display text for the tab.
12
+ * @property {string} [title] - Alias for label.
13
+ * @property {string} [icon] - Material Icon name shown alongside the label.
14
+ * @property {number|string} [badge] - Badge value shown in a small chip on the tab.
15
+ */
16
+
17
+ /**
18
+ * @typedef {Object} TabsProps
19
+ * @property {Array<string|TabDefinition>} [tabs=[]] - Tab definitions. A plain string is treated as the label.
20
+ * @property {HTMLElement[]} [children=[]] - Content panels, one per tab (matched by index).
21
+ * @property {number} [activeIndex=0] - Initially active tab index.
22
+ * @property {Function} [onChange] - Called with the new index whenever the active tab changes.
23
+ * @property {"underline"|"filled"|"pills"|"slider"} [variant="underline"] - Visual style of the tab bar.
24
+ * @property {"small"|"medium"|"large"} [size="medium"] - Size preset controlling padding, font size, and height.
25
+ * @property {string} [color] - Accent color (indicator, active icon).
26
+ * @property {string} [textColor] - Label color for inactive tabs.
27
+ * @property {string} [activeTextColor] - Label color for the active tab.
28
+ * @property {string} [bgColor] - Background fill of the tab bar (slider/filled variants).
29
+ * @property {string} [buttonColor] - Slider thumb color (slider variant).
30
+ * @property {"left"|"center"|"right"} [alignment="left"] - Alignment hint (not yet fully applied).
31
+ * @property {boolean} [fullWidth=true] - Whether tabs stretch to fill the available width.
32
+ * @property {boolean} [showDivider=true] - Whether to show a bottom border on the underline variant.
33
+ * @property {string} [dividerColor] - Color of the underline divider.
34
+ * @property {boolean} [showIcon=false] - Whether to render the tab's icon.
35
+ * @property {"left"|"right"} [iconPosition="left"] - Position of the icon relative to the label.
36
+ * @property {number} [iconSize=18] - Icon size in pixels.
37
+ * @property {Array<number|string>} [badges=[]] - Badge values indexed by tab position; overrides tab-level badge.
38
+ */
39
+
40
+ /**
41
+ * Tabs renders a horizontal tab bar with associated content panels.
42
+ *
43
+ * Four visual variants are supported:
44
+ * - `underline` — a moving underline indicator below the active tab.
45
+ * - `filled` — tabs on a solid background, active tab visually distinct.
46
+ * - `pills` — rounded pill-shaped tab buttons.
47
+ * - `slider` — an animated filled thumb that slides under the active tab.
48
+ *
49
+ * The active tab tracks a `currentIndex` state internally. Keyboard
50
+ * navigation (Enter/Space) and ARIA attributes are included for accessibility.
51
+ *
52
+ * A ResizeObserver and a window resize listener keep the slider thumb
53
+ * position correct when the tab bar dimensions change.
54
+ *
55
+ * @param {TabsProps} props
56
+ * @returns {HTMLElement} The tabs container element, augmented with:
57
+ * - `activeIndex` (getter/setter) — read or set the active tab index.
58
+ * - `setActiveTab(index: number)` — alias for the setter.
59
+ */
60
+ export const Tabs = (props) => {
61
+ const {
62
+ tabs = [],
63
+ children = [],
64
+ activeIndex = 0,
65
+ onChange,
66
+ variant = "underline",
67
+ size = "medium",
68
+ color = colors.primary,
69
+ textColor = colors.text,
70
+ activeTextColor = colors.white,
71
+ bgColor = colors.gray100,
72
+ buttonColor = colors.secondary,
73
+ alignment = "left",
74
+ fullWidth = true,
75
+ showDivider = true,
76
+ dividerColor = colors.border,
77
+ showIcon = false,
78
+ iconPosition = "left",
79
+ iconSize = 18,
80
+ badges = [],
81
+ ...rest
82
+ } = props;
83
+
84
+ let currentIndex = activeIndex;
85
+ let sliderIndicator = null;
86
+ let tabsWrapperRef = null;
87
+ let tabButtonsRef = [];
88
+
89
+ // Size preset lookup — controls padding, font size, icon size, content
90
+ // padding, and minimum height in a single prop for consistent scaling.
91
+ const sizes = {
92
+ small: { p: "6px 12px", f: 12, g: 4, i: 14, cp: 12, h: 32 },
93
+ medium: { p: "8px 16px", f: 14, g: 8, i: 18, cp: 16, h: 40 },
94
+ large: { p: "12px 20px", f: 16, g: 10, i: 22, cp: 20, h: 48 },
95
+ };
96
+ const sz = sizes[size] || sizes.medium;
97
+
98
+ /** Extract display label from a tab definition or plain string. */
99
+ const getLabel = (t) =>
100
+ typeof t === "object" ? t.label || t.title : String(t);
101
+
102
+ /** Extract icon name from a tab definition (returns null for plain strings). */
103
+ const getIcon = (t) => (typeof t === "object" ? t.icon || null : null);
104
+
105
+ /**
106
+ * Resolve the badge value for a given tab index.
107
+ * The `badges` prop array takes priority over the tab-level `badge` field.
108
+ */
109
+ const getBadge = (i) => badges[i] || tabs[i]?.badge || null;
110
+
111
+ const container = Container({
112
+ width: "100%",
113
+ display: "flex",
114
+ flexDirection: "column",
115
+ ...rest,
116
+ });
117
+
118
+ const tabBar = Container({
119
+ position: "relative",
120
+ backgroundColor:
121
+ variant === "slider"
122
+ ? bgColor
123
+ : variant === "filled"
124
+ ? bgColor
125
+ : "transparent",
126
+ borderBottom:
127
+ variant === "underline" && showDivider
128
+ ? `1px solid ${dividerColor}`
129
+ : "none",
130
+ borderRadius: variant === "slider" ? 24 : variant === "pills" ? 20 : 0,
131
+ padding: variant === "slider" ? "4px" : 0,
132
+ flexShrink: 0,
133
+ });
134
+
135
+ const tabsWrapper = Row({
136
+ alignItems: "center",
137
+ justifyContent: fullWidth ? "space-between" : "flex-start",
138
+ gap: variant === "pills" ? 4 : 0,
139
+ position: "relative",
140
+ flexWrap: "nowrap",
141
+ width: fullWidth ? "100%" : "auto",
142
+ backgroundColor: variant === "slider" ? bgColor : "transparent",
143
+ borderRadius: variant === "slider" ? 24 : 0,
144
+ padding: variant === "slider" ? "4px" : 0,
145
+ });
146
+ tabsWrapperRef = tabsWrapper;
147
+
148
+ const tabButtons = [];
149
+
150
+ tabs.forEach((tab, idx) => {
151
+ const label = getLabel(tab);
152
+ const icon = getIcon(tab);
153
+ const badge = getBadge(idx);
154
+ const isActive = currentIndex === idx;
155
+
156
+ // Build the row of content inside each tab button.
157
+ const content = [];
158
+ if (showIcon && icon && iconPosition === "left") {
159
+ content.push(
160
+ Icon({
161
+ name: icon,
162
+ size: iconSize || sz.i,
163
+ color: isActive ? activeTextColor : textColor,
164
+ }),
165
+ );
166
+ }
167
+ if (label) {
168
+ content.push(
169
+ Text({
170
+ text: label,
171
+ size: sz.f,
172
+ color: isActive ? activeTextColor : textColor,
173
+ weight: isActive ? "500" : "400",
174
+ }),
175
+ );
176
+ }
177
+ if (badge) {
178
+ content.push(
179
+ Container({
180
+ backgroundColor: colors.danger,
181
+ borderRadius: 10,
182
+ padding: "2px 6px",
183
+ marginLeft: 4,
184
+ child: Text({ text: String(badge), size: 10, color: "#fff" }),
185
+ }),
186
+ );
187
+ }
188
+
189
+ const btn = Container({
190
+ borderRadius: variant === "slider" ? 20 : variant === "pills" ? 20 : 0,
191
+ backgroundColor: "transparent",
192
+ color: isActive ? activeTextColor : textColor,
193
+ display: "flex",
194
+ alignItems: "center",
195
+ justifyContent: "center",
196
+ gap: sz.g,
197
+ padding: sz.p,
198
+ cursor: "pointer",
199
+ transition: "all 0.2s",
200
+ flex: fullWidth ? 1 : "0 0 auto",
201
+ whiteSpace: "nowrap",
202
+ // Subtract the slider's internal padding so buttons don't overflow it.
203
+ minHeight: sz.h - (variant === "slider" ? 8 : 0),
204
+ zIndex: 2,
205
+ // Accessibility: expose as a tab role with aria-selected.
206
+ role: "tab",
207
+ tabIndex: 0,
208
+ "aria-selected": isActive.toString(),
209
+ onKeyDown: (e) => {
210
+ if (e.key === "Enter" || e.key === " ") {
211
+ e.preventDefault();
212
+ if (currentIndex !== idx) {
213
+ currentIndex = idx;
214
+ updateActiveTab(idx);
215
+ onChange?.(idx);
216
+ }
217
+ }
218
+ },
219
+ child: Row({
220
+ alignItems: "center",
221
+ justifyContent: "center",
222
+ gap: 4,
223
+ children: content,
224
+ }),
225
+ });
226
+
227
+ btn.onclick = () => {
228
+ if (currentIndex !== idx) {
229
+ currentIndex = idx;
230
+ updateActiveTab(idx);
231
+ onChange?.(idx);
232
+ }
233
+ };
234
+
235
+ tabButtons.push(btn);
236
+ tabsWrapper.appendChild(btn);
237
+ });
238
+ tabButtonsRef = tabButtons;
239
+
240
+ // The slider variant uses an absolutely positioned "thumb" div that
241
+ // animates to the active tab's position via CSS transform.
242
+ if (variant === "slider") {
243
+ sliderIndicator = WidgetFactory({
244
+ tag: "div",
245
+ position: "absolute",
246
+ top: "4px",
247
+ left: "4px",
248
+ height: "calc(100% - 8px)",
249
+ backgroundColor: buttonColor,
250
+ borderRadius: 20,
251
+ transition: "transform 0.3s ease, width 0.3s ease",
252
+ zIndex: 1,
253
+ });
254
+ tabsWrapper.appendChild(sliderIndicator);
255
+ }
256
+
257
+ tabBar.appendChild(tabsWrapper);
258
+ container.appendChild(tabBar);
259
+
260
+ const contentContainer = Container({
261
+ display: "flex",
262
+ padding: `${sz.cp}px 0`,
263
+ width: "100%",
264
+ overflow: "auto",
265
+ });
266
+ container.appendChild(contentContainer);
267
+
268
+ /**
269
+ * Swap the content panel to the one matching `currentIndex`.
270
+ */
271
+ const updateContent = () => {
272
+ while (contentContainer.firstChild)
273
+ contentContainer.removeChild(contentContainer.firstChild);
274
+ const active = children[currentIndex];
275
+ if (active instanceof HTMLElement) contentContainer.appendChild(active);
276
+ };
277
+
278
+ /**
279
+ * Reposition the slider thumb to sit under the active tab button.
280
+ * Reads live DOM rectangles so it stays accurate after layout changes.
281
+ */
282
+ const updateSliderPosition = () => {
283
+ if (!sliderIndicator || !tabButtonsRef[currentIndex]) return;
284
+ const btn = tabButtonsRef[currentIndex];
285
+ const wrapperRect = tabsWrapperRef.getBoundingClientRect();
286
+ const btnRect = btn.getBoundingClientRect();
287
+ const left = btnRect.left - wrapperRect.left;
288
+ sliderIndicator.style.width = `${btnRect.width}px`;
289
+ sliderIndicator.style.transform = `translateX(${left}px)`;
290
+ };
291
+
292
+ /**
293
+ * Apply active/inactive styles to all tab buttons and update the content
294
+ * panel and slider position. Called on every tab change.
295
+ *
296
+ * @param {number} idx - The newly active tab index.
297
+ */
298
+ const updateActiveTab = (idx) => {
299
+ tabButtonsRef.forEach((btn, i) => {
300
+ const isActive = i === idx;
301
+
302
+ // Keep aria-selected in sync with the visual state.
303
+ btn.setAttribute("aria-selected", isActive.toString());
304
+
305
+ const textSpan = btn.querySelector("span:not(.material-icons)");
306
+ if (textSpan) {
307
+ textSpan.style.color = isActive ? activeTextColor : textColor;
308
+ textSpan.style.fontWeight = isActive ? "500" : "400";
309
+ }
310
+ const iconEl = btn.querySelector(".material-icons");
311
+ if (iconEl) {
312
+ iconEl.style.color = isActive ? activeTextColor : textColor;
313
+ }
314
+ btn.style.color = isActive ? activeTextColor : textColor;
315
+ });
316
+
317
+ updateSliderPosition();
318
+ updateContent();
319
+ };
320
+
321
+ // Defer the initial slider position calculation by one frame so the DOM
322
+ // has been laid out and getBoundingClientRect() returns real values.
323
+ let initialPositionTimer = setTimeout(() => {
324
+ initialPositionTimer = null;
325
+ updateSliderPosition();
326
+ }, 16);
327
+
328
+ // Debounced window resize handler — recomputes slider position after the
329
+ // layout has stabilised following the resize event.
330
+ let resizeTimeout = null;
331
+ let resizeObserver = null;
332
+ const handleResize = () => {
333
+ clearTimeout(resizeTimeout);
334
+ resizeTimeout = setTimeout(() => {
335
+ if (sliderIndicator && tabButtonsRef[currentIndex]) {
336
+ updateSliderPosition();
337
+ }
338
+ }, 100);
339
+ };
340
+ window.addEventListener("resize", handleResize);
341
+
342
+ // Also observe the tab bar element itself so the thumb repositions when
343
+ // the container resizes independently of the window (e.g. sidebar toggle).
344
+ if (typeof ResizeObserver !== "undefined") {
345
+ resizeObserver = new ResizeObserver(() => updateSliderPosition());
346
+ if (tabsWrapperRef) resizeObserver.observe(tabsWrapperRef);
347
+ }
348
+
349
+ // Cleanup: cancel timers and remove listeners when the component is unmounted.
350
+ const originalCleanup = container._cleanup;
351
+ container._cleanup = () => {
352
+ if (initialPositionTimer) clearTimeout(initialPositionTimer);
353
+ if (resizeTimeout) clearTimeout(resizeTimeout);
354
+ window.removeEventListener("resize", handleResize);
355
+ if (resizeObserver) resizeObserver.disconnect();
356
+ if (originalCleanup) originalCleanup();
357
+ };
358
+
359
+ // ========== PUBLIC API ==========
360
+ Object.defineProperty(container, "activeIndex", {
361
+ get: () => currentIndex,
362
+ set: (idx) => {
363
+ if (idx >= 0 && idx < tabs.length && currentIndex !== idx) {
364
+ currentIndex = idx;
365
+ updateActiveTab(idx);
366
+ onChange?.(idx);
367
+ }
368
+ },
369
+ });
370
+
371
+ /**
372
+ * Programmatically activate a tab by index.
373
+ * Equivalent to setting `container.activeIndex = idx`.
374
+ *
375
+ * @param {number} idx - Zero-based tab index.
376
+ */
377
+ container.setActiveTab = (idx) => {
378
+ container.activeIndex = idx;
379
+ };
380
+
381
+ return container;
382
+ };
383
+
384
+ export default Tabs;
@@ -0,0 +1,57 @@
1
+ // src/navigations/index.js
2
+ /**
3
+ * Navigation module public API.
4
+ *
5
+ * Re-exports all navigation components and router utilities so consumers
6
+ * can import from a single entry point:
7
+ *
8
+ * @example
9
+ * import { Scaffold, AppBar, Tabs, goTo } from "../navigations/index.js";
10
+ */
11
+
12
+ export { Scaffold } from "./Scaffold.js";
13
+ export { AdaptiveScaffold } from "./AdaptiveScaffold.js";
14
+ export { AppBar } from "./AppBar.js";
15
+ export { CollapsibleSideBar } from "./CollapsibleSideBar.js";
16
+ export {
17
+ Drawer,
18
+ openDrawer,
19
+ closeDrawer,
20
+ toggleDrawer,
21
+ destroyDrawer,
22
+ } from "./Drawer.js";
23
+ export { BottomNavigation } from "./BottomNavigation.js";
24
+ export { Tabs } from "./Tabs.js";
25
+ export { DrawerItem } from "./DrawerItem.js";
26
+ export {
27
+ /** Initialize the router with a route map and an optional starting path. */
28
+ initRouter,
29
+ /** Navigate to a path, pushing a new history entry. */
30
+ goTo,
31
+ /** Navigate back in the router's internal history stack. */
32
+ goBack,
33
+ /** Navigate forward using the browser's built-in history. */
34
+ goForward,
35
+ /** Navigate to a path by replacing the current history entry. */
36
+ replace,
37
+ /** Returns the currently active path. */
38
+ getCurrentPath,
39
+ /** Returns the component/config for the current route. */
40
+ getCurrentRoute,
41
+ /** Returns the full route state (route, params, query, path). */
42
+ getCurrentRouteConfig,
43
+ /** Look up the component/config for any path. */
44
+ getRoute,
45
+ /** Check whether a path matches the current route. */
46
+ isActive,
47
+ /** Subscribe to route change events; returns an unsubscribe function. */
48
+ subscribe,
49
+ /** Returns a copy of the current dynamic path params. */
50
+ useParams,
51
+ /** Returns a copy of the current query string params. */
52
+ useQueryParams,
53
+ /** Build a URL from a pattern, params, and query string. */
54
+ buildUrl,
55
+ /** Tear down the router and clear all state and listeners. */
56
+ clearRouter,
57
+ } from "./Router.js";
@@ -0,0 +1,213 @@
1
+ // src/components/flet-box/services/RamStore.js
2
+
3
+ /**
4
+ * RamStore - In-memory storage (live memory) with reactivity
5
+ *
6
+ * @description
7
+ * Same API as Storage.js and Session.js, but keeps data in RAM.
8
+ * Data is lost when the page is reloaded.
9
+ * Includes subscribe() for reactivity between widgets.
10
+ */
11
+
12
+ // ========== INTERNAL STATE (in RAM) ==========
13
+ const ramStore = new Map();
14
+
15
+ // ========== SUBSCRIBERS (for reactivity) ==========
16
+ let subscribers = [];
17
+
18
+ // ========== INTERNAL FUNCTION TO NOTIFY ==========
19
+ const notifySubscribers = (key, newValue, oldValue) => {
20
+ subscribers.forEach((callback) => {
21
+ try {
22
+ callback(key, newValue, oldValue);
23
+ } catch (e) {
24
+ console.error("[RamStore] Subscriber error:", e);
25
+ }
26
+ });
27
+ };
28
+
29
+ // ========== CREATE / UPDATE ==========
30
+
31
+ /**
32
+ * Saves or updates a value in RAM
33
+ * @param {string} key - Data key
34
+ * @param {any} value - Value to save
35
+ * @returns {boolean}
36
+ */
37
+ export const saveRam = (key, value) => {
38
+ try {
39
+ const oldValue = ramStore.get(key);
40
+ const result = ramStore.set(key, value);
41
+ notifySubscribers(key, value, oldValue);
42
+ // console.log(`[RamStore] saved: ${key} =`, value);
43
+ return true;
44
+ } catch (error) {
45
+ console.error("Error saving to RAM:", error);
46
+ return false;
47
+ }
48
+ };
49
+
50
+ // ========== READ ==========
51
+
52
+ /**
53
+ * Gets a value from RAM
54
+ * @param {string} key - Data key
55
+ * @returns {any}
56
+ */
57
+ export const getRam = (key) => {
58
+ try {
59
+ return ramStore.has(key) ? ramStore.get(key) : null;
60
+ } catch (error) {
61
+ console.error("Error reading from RAM:", error);
62
+ return null;
63
+ }
64
+ };
65
+
66
+ /**
67
+ * Gets all data from RAM
68
+ * @returns {Object}
69
+ */
70
+ export const getAllRam = () => {
71
+ try {
72
+ const result = {};
73
+ for (const [key, value] of ramStore.entries()) {
74
+ result[key] = value;
75
+ }
76
+ return result;
77
+ } catch (error) {
78
+ console.error("Error getting all RAM data:", error);
79
+ return {};
80
+ }
81
+ };
82
+
83
+ /**
84
+ * Gets all keys from RAM
85
+ * @returns {string[]}
86
+ */
87
+ export const getAllRamKeys = () => {
88
+ try {
89
+ return Array.from(ramStore.keys());
90
+ } catch (error) {
91
+ console.error("Error getting RAM keys:", error);
92
+ return [];
93
+ }
94
+ };
95
+
96
+ /**
97
+ * Checks whether a key exists in RAM
98
+ * @param {string} key
99
+ * @returns {boolean}
100
+ */
101
+ export const hasRam = (key) => {
102
+ return ramStore.has(key);
103
+ };
104
+
105
+ // ========== UPDATE ==========
106
+
107
+ /**
108
+ * Updates an existing value (only if it exists)
109
+ * @param {string} key
110
+ * @param {any} newValue
111
+ * @returns {boolean}
112
+ */
113
+ export const updateRam = (key, newValue) => {
114
+ try {
115
+ if (!ramStore.has(key)) return false;
116
+ const oldValue = ramStore.get(key);
117
+ ramStore.set(key, newValue);
118
+ notifySubscribers(key, newValue, oldValue);
119
+ return true;
120
+ } catch (error) {
121
+ console.error("Error updating RAM:", error);
122
+ return false;
123
+ }
124
+ };
125
+
126
+ // ========== DELETE ==========
127
+
128
+ /**
129
+ * Deletes a value from RAM
130
+ * @param {string} key
131
+ * @returns {boolean}
132
+ */
133
+ export const deleteRam = (key) => {
134
+ try {
135
+ const oldValue = ramStore.get(key);
136
+ const deleted = ramStore.delete(key);
137
+ if (deleted) {
138
+ notifySubscribers(key, null, oldValue);
139
+ }
140
+ return deleted;
141
+ } catch (error) {
142
+ console.error("Error deleting from RAM:", error);
143
+ return false;
144
+ }
145
+ };
146
+
147
+ /**
148
+ * Deletes ALL data from RAM
149
+ * @returns {boolean}
150
+ */
151
+ export const clearAllRam = () => {
152
+ try {
153
+ ramStore.clear();
154
+ notifySubscribers(null, null, null);
155
+ return true;
156
+ } catch (error) {
157
+ console.error("Error clearing RAM:", error);
158
+ return false;
159
+ }
160
+ };
161
+
162
+ // ========== SUBSCRIBE (REACTIVITY) ==========
163
+
164
+ /**
165
+ * Subscribes a callback to RAM changes
166
+ * @param {Function} callback - (key, newValue, oldValue) => void
167
+ * @returns {Function} - unsubscribe function
168
+ */
169
+ export const subscribeRam = (callback) => {
170
+ subscribers.push(callback);
171
+ // console.log(`[RamStore] Subscriber added. Total: ${subscribers.length}`);
172
+
173
+ // Return a function to unsubscribe
174
+ return () => {
175
+ subscribers = subscribers.filter((s) => s !== callback);
176
+ console.log(`[RamStore] Subscriber removed. Total: ${subscribers.length}`);
177
+ };
178
+ };
179
+
180
+ // ========== UTILS ==========
181
+
182
+ /**
183
+ * Gets the number of items in RAM
184
+ * @returns {number}
185
+ */
186
+ export const getRamItemCount = () => {
187
+ return ramStore.size;
188
+ };
189
+
190
+ /**
191
+ * Checks whether RamStore is available
192
+ * @returns {boolean}
193
+ */
194
+ export const isRamAvailable = () => {
195
+ return true;
196
+ };
197
+
198
+ // ========== EXPORT DEFAULT ==========
199
+ const RamStore = {
200
+ saveRam,
201
+ getRam,
202
+ getAllRam,
203
+ getAllRamKeys,
204
+ hasRam,
205
+ updateRam,
206
+ deleteRam,
207
+ clearAllRam,
208
+ subscribeRam,
209
+ getRamItemCount,
210
+ isRamAvailable,
211
+ };
212
+
213
+ export default RamStore;