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.
- package/LICENSE +21 -0
- package/README.md +190 -2
- package/bin/cli.js +394 -0
- package/bin/commands/buildAndroid.js +200 -0
- package/bin/commands/buildIOS.js +237 -0
- package/bin/commands/create.js +406 -0
- package/bin/commands/createBundle.js +375 -0
- package/bin/commands/createComponent.js +115 -0
- package/bin/commands/createScreen.js +161 -0
- package/bin/commands/killServer.js +107 -0
- package/bin/commands/package.js +131 -0
- package/bin/commands/release.js +189 -0
- package/bin/commands/runBundle.js +62 -0
- package/bin/commands/runCloudflare.js +89 -0
- package/bin/commands/runNative.js +109 -0
- package/bin/commands/runServer.js +390 -0
- package/bin/ota/loader.js +361 -0
- package/bin/utils/bundleConfig.js +178 -0
- package/bin/utils/capacitor.js +137 -0
- package/bin/utils/colors.js +215 -0
- package/bin/utils/helpers.js +80 -0
- package/bin/utils/spinner.js +96 -0
- package/bin/utils/templates.js +1824 -0
- package/bin/utils/version.js +108 -0
- package/package.json +58 -4
- package/src/animations/AnimatedBox.js +607 -0
- package/src/animations/AnimatedText.js +105 -0
- package/src/animations/MatrixRain.js +220 -0
- package/src/animations/ParallaxBox.js +369 -0
- package/src/animations/animate.js +332 -0
- package/src/animations/index.js +25 -0
- package/src/app.js +41 -0
- package/src/assets/icon-1024-maskable.png +0 -0
- package/src/assets/icon-192-maskable.png +0 -0
- package/src/assets/icon-192.png +0 -0
- package/src/assets/icon-512-maskable.png +0 -0
- package/src/assets/icon-512.png +0 -0
- package/src/assets/logo.png +0 -0
- package/src/core/App.js +44 -0
- package/src/core/hmr-client.js +142 -0
- package/src/core/index.js +34 -0
- package/src/core/pwa.js +100 -0
- package/src/core/runApp.js +430 -0
- package/src/create-index.sh +237 -0
- package/src/fonts/MaterialIcons-Regular.woff2 +0 -0
- package/src/fonts/icons.css +23 -0
- package/src/index.d.ts +1930 -0
- package/src/index.js +288 -0
- package/src/navigations/AdaptiveScaffold.js +149 -0
- package/src/navigations/AppBar.js +331 -0
- package/src/navigations/BottomNavigation.js +298 -0
- package/src/navigations/CollapsibleSideBar.js +139 -0
- package/src/navigations/Drawer.js +333 -0
- package/src/navigations/DrawerItem.js +260 -0
- package/src/navigations/Router.js +475 -0
- package/src/navigations/Scaffold.js +537 -0
- package/src/navigations/Tabs.js +384 -0
- package/src/navigations/index.js +57 -0
- package/src/services/RamStore.js +213 -0
- package/src/services/Session.js +310 -0
- package/src/services/Storage.js +309 -0
- package/src/services/http.js +350 -0
- package/src/services/index.js +57 -0
- package/src/tools/animation.js +42 -0
- package/src/tools/array.js +95 -0
- package/src/tools/border.js +28 -0
- package/src/tools/clipboard.js +104 -0
- package/src/tools/color.js +329 -0
- package/src/tools/createList.js +215 -0
- package/src/tools/delay.js +55 -0
- package/src/tools/device.js +138 -0
- package/src/tools/dict.js +297 -0
- package/src/tools/dimensions.js +118 -0
- package/src/tools/filter.js +41 -0
- package/src/tools/flex.js +26 -0
- package/src/tools/gradient.js +34 -0
- package/src/tools/grid.js +57 -0
- package/src/tools/index.js +38 -0
- package/src/tools/mapList.js +107 -0
- package/src/tools/margin.js +93 -0
- package/src/tools/memo.js +60 -0
- package/src/tools/os.js +104 -0
- package/src/tools/padding.js +93 -0
- package/src/tools/print.js +69 -0
- package/src/tools/random.js +299 -0
- package/src/tools/ref.js +70 -0
- package/src/tools/rgba.js +4 -0
- package/src/tools/shadow.js +23 -0
- package/src/tools/string.js +85 -0
- package/src/tools/time.js +674 -0
- package/src/tools/transform.js +68 -0
- package/src/tools/transition.js +48 -0
- package/src/tools/useState.js +268 -0
- package/src/tools/uuid.js +42 -0
- package/src/utils/TextInputValidator.js +256 -0
- package/src/utils/composeUpdate.js +65 -0
- package/src/utils/getWidgetProps.js +35 -0
- package/src/utils/index.js +76 -0
- package/src/utils/markdownParser.js +368 -0
- package/src/utils/mediaTime.js +79 -0
- package/src/utils/navigation.js +263 -0
- package/src/utils/scriptInjector.js +73 -0
- package/src/utils/stackPosition.js +31 -0
- package/src/utils/stopWebRefresh.js +77 -0
- package/src/utils/styleInjector.js +38 -0
- package/src/utils/syntaxHighlight.js +339 -0
- package/src/utils/themes.js +347 -0
- package/src/utils/units.js +87 -0
- package/src/utils/visualEffects.js +169 -0
- package/src/widget-factory/addChildren.js +114 -0
- package/src/widget-factory/assignProps.js +256 -0
- package/src/widget-factory/createWidget.js +28 -0
- package/src/widget-factory/effects.js +132 -0
- package/src/widget-factory/index.js +7 -0
- package/src/widget-factory/lifecycle.js +210 -0
- package/src/widget-factory/makeParentable.js +89 -0
- package/src/widget-factory/pixTool.js +40 -0
- package/src/widget-factory/processProps.js +122 -0
- package/src/widget-factory/reactivity.js +98 -0
- package/src/widget-factory/remTool.js +34 -0
- package/src/widget-factory/stackPosition.js +87 -0
- package/src/widget-factory/stringTool.js +26 -0
- package/src/widget-factory/tools.js +249 -0
- package/src/widget-factory/translateProps.js +1049 -0
- package/src/widget-factory/warnings.js +284 -0
- package/src/widget-factory/widgetFactory.js +200 -0
- package/src/widgets/Accordion.js +562 -0
- package/src/widgets/AdMob.js +315 -0
- package/src/widgets/AdSense.js +268 -0
- package/src/widgets/AlertDialog.js +162 -0
- package/src/widgets/Audio.js +311 -0
- package/src/widgets/Avatar.js +210 -0
- package/src/widgets/Badge.js +189 -0
- package/src/widgets/BottomSheet.js +287 -0
- package/src/widgets/Button.js +247 -0
- package/src/widgets/Card.js +33 -0
- package/src/widgets/Carousel.js +301 -0
- package/src/widgets/Chart.js +632 -0
- package/src/widgets/Checkbox.js +106 -0
- package/src/widgets/Chip.js +153 -0
- package/src/widgets/CircularBar.js +352 -0
- package/src/widgets/CircularChart.js +549 -0
- package/src/widgets/CodeViewer.js +236 -0
- package/src/widgets/Column.js +29 -0
- package/src/widgets/Container.js +31 -0
- package/src/widgets/DataTable.js +257 -0
- package/src/widgets/Divider.js +93 -0
- package/src/widgets/DraggBox.js +115 -0
- package/src/widgets/Dropdown.js +505 -0
- package/src/widgets/DroppBox.js +180 -0
- package/src/widgets/FloatingActionButton.js +91 -0
- package/src/widgets/GridView.js +20 -0
- package/src/widgets/Icon.js +33 -0
- package/src/widgets/Image.js +15 -0
- package/src/widgets/Input.js +565 -0
- package/src/widgets/Inspector.js +254 -0
- package/src/widgets/InstallButton.js +110 -0
- package/src/widgets/ListTile.js +224 -0
- package/src/widgets/ListView.js +503 -0
- package/src/widgets/Markdown.js +1236 -0
- package/src/widgets/Modal.js +496 -0
- package/src/widgets/Pagination.js +195 -0
- package/src/widgets/ProgressBar.js +178 -0
- package/src/widgets/QRCode.js +467 -0
- package/src/widgets/Radio.js +132 -0
- package/src/widgets/Rating.js +261 -0
- package/src/widgets/Row.js +29 -0
- package/src/widgets/Skeleton.js +243 -0
- package/src/widgets/Slider.js +527 -0
- package/src/widgets/SnackBar.js +227 -0
- package/src/widgets/Stack.js +29 -0
- package/src/widgets/Stepper.js +296 -0
- package/src/widgets/Switch.js +102 -0
- package/src/widgets/Text.js +98 -0
- package/src/widgets/Tooltip.js +267 -0
- package/src/widgets/TreeView.js +370 -0
- package/src/widgets/Video.js +92 -0
- 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;
|