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,254 @@
|
|
|
1
|
+
// utils/Inspector.js
|
|
2
|
+
import { getWidgetProps } from "../utils/getWidgetProps.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Converts a JavaScript value to its source-code string representation.
|
|
6
|
+
* Handles primitives, arrays, plain objects, and nested widget elements
|
|
7
|
+
* by recursively calling `Inspector` for any DOM node that carries `_widgetName`.
|
|
8
|
+
*
|
|
9
|
+
* @param {*} value - The value to stringify.
|
|
10
|
+
* @param {number} [indent=0] - Current indentation depth (used for pretty-printing nested structures).
|
|
11
|
+
* @returns {string} A human-readable code representation of the value.
|
|
12
|
+
*/
|
|
13
|
+
const stringifyValue = (value, indent = 0) => {
|
|
14
|
+
const spaces = " ".repeat(indent);
|
|
15
|
+
|
|
16
|
+
if (value === null) return "null";
|
|
17
|
+
if (value === undefined) return "undefined";
|
|
18
|
+
|
|
19
|
+
if (typeof value === "function") {
|
|
20
|
+
return "() => {}";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof value === "string") {
|
|
24
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
25
|
+
return `'${escaped}'`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof value === "number") return String(value);
|
|
29
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
30
|
+
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
if (value.length === 0) return "[]";
|
|
33
|
+
const items = value
|
|
34
|
+
.map((item) => stringifyValue(item, indent + 1))
|
|
35
|
+
.join(",\n" + spaces + " ");
|
|
36
|
+
return `[\n${spaces} ${items}\n${spaces}]`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (typeof value === "object" && value !== null) {
|
|
40
|
+
// If the object is a widget DOM node, delegate to Inspector for full reconstruction
|
|
41
|
+
if (value.nodeType === 1 && value._widgetName) {
|
|
42
|
+
return Inspector(value, indent);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const entries = Object.entries(value);
|
|
46
|
+
if (entries.length === 0) return "{}";
|
|
47
|
+
|
|
48
|
+
const props = entries
|
|
49
|
+
.map(([k, v]) => {
|
|
50
|
+
if (typeof v === "number" || typeof v === "boolean") {
|
|
51
|
+
return `${k}: ${v}`;
|
|
52
|
+
}
|
|
53
|
+
if (typeof v === "string") {
|
|
54
|
+
return `${k}: '${v.replace(/'/g, "\\'")}'`;
|
|
55
|
+
}
|
|
56
|
+
if (v && v.nodeType === 1) {
|
|
57
|
+
return `${k}: ${Inspector(v, indent + 1)}`;
|
|
58
|
+
}
|
|
59
|
+
return `${k}: ${stringifyValue(v, indent + 1)}`;
|
|
60
|
+
})
|
|
61
|
+
.join(",\n" + spaces + " ");
|
|
62
|
+
|
|
63
|
+
return `{\n${spaces} ${props}\n${spaces}}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return String(value);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolves the display name of a widget element.
|
|
71
|
+
*
|
|
72
|
+
* Names come from the underlying HTML tag (documented behavior): `span` →
|
|
73
|
+
* `Span()`, `div` → `Div()`, `button` → `Button()`, … `_widgetName` is used
|
|
74
|
+
* only as a fallback for tags without a mapping so the output reads like real
|
|
75
|
+
* DOM.
|
|
76
|
+
*
|
|
77
|
+
* @param {HTMLElement} widget - The DOM node whose name to resolve.
|
|
78
|
+
* @returns {string} The widget name (e.g. "Button", "Span", "Div").
|
|
79
|
+
*/
|
|
80
|
+
const TAG_NAMES = {
|
|
81
|
+
SPAN: "Span",
|
|
82
|
+
DIV: "Div",
|
|
83
|
+
BUTTON: "Button",
|
|
84
|
+
INPUT: "Input",
|
|
85
|
+
TEXTAREA: "Textarea",
|
|
86
|
+
IMG: "Image",
|
|
87
|
+
CANVAS: "Canvas",
|
|
88
|
+
VIDEO: "Video",
|
|
89
|
+
AUDIO: "Audio",
|
|
90
|
+
SELECT: "Select",
|
|
91
|
+
TABLE: "Table",
|
|
92
|
+
TH: "Th",
|
|
93
|
+
TD: "Td",
|
|
94
|
+
TR: "Tr",
|
|
95
|
+
UL: "Ul",
|
|
96
|
+
OL: "Ol",
|
|
97
|
+
LI: "Li",
|
|
98
|
+
A: "A",
|
|
99
|
+
CODE: "Code",
|
|
100
|
+
PRE: "Pre",
|
|
101
|
+
HEADER: "Header",
|
|
102
|
+
FOOTER: "Footer",
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const getWidgetName = (widget) => {
|
|
106
|
+
const tag = (widget.tagName || "").toUpperCase();
|
|
107
|
+
if (TAG_NAMES[tag]) return TAG_NAMES[tag];
|
|
108
|
+
if (widget._widgetName) return widget._widgetName;
|
|
109
|
+
return widget.tagName?.toLowerCase() || "unknown";
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Collects all direct child elements of a widget, merging both the real DOM
|
|
114
|
+
* children and any virtual children stored in `_children` (set by WidgetFactory).
|
|
115
|
+
* Deduplication prevents the same node from appearing twice.
|
|
116
|
+
*
|
|
117
|
+
* @param {HTMLElement} widget - The widget whose children to collect.
|
|
118
|
+
* @returns {HTMLElement[]} Deduplicated array of child element nodes.
|
|
119
|
+
*/
|
|
120
|
+
const collectChildren = (widget) => {
|
|
121
|
+
const children = [];
|
|
122
|
+
|
|
123
|
+
for (const child of widget.children) {
|
|
124
|
+
if (child.nodeType === 1) {
|
|
125
|
+
children.push(child);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// _children may hold widget references that aren't yet in the DOM
|
|
130
|
+
if (widget._children && Array.isArray(widget._children)) {
|
|
131
|
+
for (const child of widget._children) {
|
|
132
|
+
if (child && child.nodeType === 1 && !children.includes(child)) {
|
|
133
|
+
children.push(child);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return children;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Converts a widget element back into its equivalent source-code representation,
|
|
143
|
+
* recursively reconstructing its full prop tree and children.
|
|
144
|
+
*
|
|
145
|
+
* This is the core utility for the widget inspector — it lets you paste the output
|
|
146
|
+
* directly into your code to reproduce the exact widget configuration.
|
|
147
|
+
*
|
|
148
|
+
* @param {HTMLElement} widget - The root widget to inspect.
|
|
149
|
+
* @param {number} [indent=0] - Starting indentation depth for pretty-printing.
|
|
150
|
+
* @returns {string} Source code string such as `Row({ gap: 8, children: [...] })`.
|
|
151
|
+
*/
|
|
152
|
+
export const Inspector = (widget, indent = 0) => {
|
|
153
|
+
if (!widget || widget.nodeType !== 1) {
|
|
154
|
+
return String(widget);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const name = getWidgetName(widget);
|
|
158
|
+
const props = getWidgetProps(widget);
|
|
159
|
+
const spaces = " ".repeat(indent);
|
|
160
|
+
const nextSpaces = " ".repeat(indent + 1);
|
|
161
|
+
|
|
162
|
+
// Separate structural props from the rest so we can render them last
|
|
163
|
+
const { child, children, ...otherProps } = props;
|
|
164
|
+
|
|
165
|
+
const propsList = Object.entries(otherProps)
|
|
166
|
+
.map(([key, value]) => {
|
|
167
|
+
const formattedValue = stringifyValue(value, indent + 1);
|
|
168
|
+
return `${key}: ${formattedValue}`;
|
|
169
|
+
})
|
|
170
|
+
.join(",\n" + nextSpaces);
|
|
171
|
+
|
|
172
|
+
// Reconstruct a single `child` prop
|
|
173
|
+
let childStr = "";
|
|
174
|
+
if (child && child.nodeType === 1) {
|
|
175
|
+
childStr = `\n${nextSpaces}child: ${Inspector(child, indent + 1)}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Reconstruct a `children` array prop
|
|
179
|
+
let childrenStr = "";
|
|
180
|
+
if (children && Array.isArray(children) && children.length > 0) {
|
|
181
|
+
const childrenItems = children
|
|
182
|
+
.filter((c) => c && c.nodeType === 1)
|
|
183
|
+
.map((c) => Inspector(c, indent + 1))
|
|
184
|
+
.join(",\n" + nextSpaces + " ");
|
|
185
|
+
|
|
186
|
+
if (childrenItems) {
|
|
187
|
+
childrenStr = `\n${nextSpaces}children: [\n${nextSpaces} ${childrenItems}\n${nextSpaces}]`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Fall back to actual DOM children when no explicit child/children props exist
|
|
192
|
+
const domChildren = collectChildren(widget);
|
|
193
|
+
let domChildrenStr = "";
|
|
194
|
+
if (
|
|
195
|
+
domChildren.length > 0 &&
|
|
196
|
+
!child &&
|
|
197
|
+
(!children || children.length === 0)
|
|
198
|
+
) {
|
|
199
|
+
const domChildrenItems = domChildren
|
|
200
|
+
.map((c) => Inspector(c, indent + 1))
|
|
201
|
+
.join(",\n" + nextSpaces + " ");
|
|
202
|
+
|
|
203
|
+
if (domChildrenItems) {
|
|
204
|
+
domChildrenStr = `\n${nextSpaces}children: [\n${nextSpaces} ${domChildrenItems}\n${nextSpaces}]`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const allContent = [
|
|
209
|
+
propsList && `${nextSpaces}${propsList}`,
|
|
210
|
+
childStr,
|
|
211
|
+
childrenStr,
|
|
212
|
+
domChildrenStr,
|
|
213
|
+
]
|
|
214
|
+
.filter(Boolean)
|
|
215
|
+
.join(",\n");
|
|
216
|
+
|
|
217
|
+
if (allContent) {
|
|
218
|
+
return `${name}({\n${allContent}\n${spaces}})`;
|
|
219
|
+
}
|
|
220
|
+
return `${name}()`;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Logs the widget's source-code representation to the console with green
|
|
225
|
+
* monospace styling — handy for quick visual inspection during development.
|
|
226
|
+
*
|
|
227
|
+
* @param {HTMLElement} widget - The widget to print.
|
|
228
|
+
*/
|
|
229
|
+
export const printWidgetCode = (widget) => {
|
|
230
|
+
console.log(
|
|
231
|
+
"%c" + Inspector(widget),
|
|
232
|
+
"color: #4ade80; font-family: monospace;",
|
|
233
|
+
);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Performs a full diagnostic inspection of a widget, logging its name,
|
|
238
|
+
* resolved props, reconstructed source code, and child count as a grouped
|
|
239
|
+
* console entry. Returns the widget unchanged so it can be used inline
|
|
240
|
+
* (e.g. `inspectWidget(myWidget).style.color = "red"`).
|
|
241
|
+
*
|
|
242
|
+
* @param {HTMLElement} widget - The widget to inspect.
|
|
243
|
+
* @returns {HTMLElement} The same widget, unmodified.
|
|
244
|
+
*/
|
|
245
|
+
export const inspectWidget = (widget) => {
|
|
246
|
+
console.group(`🔍 ${getWidgetName(widget)}`);
|
|
247
|
+
console.log("📦 Props:", getWidgetProps(widget));
|
|
248
|
+
console.log("📝 Code:\n", Inspector(widget));
|
|
249
|
+
console.log("👪 Children:", collectChildren(widget).length);
|
|
250
|
+
console.groupEnd();
|
|
251
|
+
return widget;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
export default { Inspector, printWidgetCode, inspectWidget };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file InstallButton.js
|
|
3
|
+
* @description PWA install prompt button; hidden unless install is available.
|
|
4
|
+
*/
|
|
5
|
+
import { Button } from "./Button.js";
|
|
6
|
+
import { colors } from "../utils/themes.js";
|
|
7
|
+
|
|
8
|
+
export const InstallButton = (props) => {
|
|
9
|
+
const {
|
|
10
|
+
text = "📲 Install",
|
|
11
|
+
variant = "filled",
|
|
12
|
+
size = "medium",
|
|
13
|
+
borderRadius = 28,
|
|
14
|
+
padding = "12px 24px",
|
|
15
|
+
bottom = 20,
|
|
16
|
+
backgroundColor,
|
|
17
|
+
color,
|
|
18
|
+
onInstalled,
|
|
19
|
+
onClick,
|
|
20
|
+
...rest
|
|
21
|
+
} = props;
|
|
22
|
+
|
|
23
|
+
let deferredPrompt = null;
|
|
24
|
+
let button = null;
|
|
25
|
+
let isInstalled = false;
|
|
26
|
+
|
|
27
|
+
const handleClick = async () => {
|
|
28
|
+
if (deferredPrompt) {
|
|
29
|
+
deferredPrompt.prompt();
|
|
30
|
+
const { outcome } = await deferredPrompt.userChoice;
|
|
31
|
+
if (outcome === "accepted") {
|
|
32
|
+
if (onInstalled) onInstalled();
|
|
33
|
+
hideButton();
|
|
34
|
+
}
|
|
35
|
+
deferredPrompt = null;
|
|
36
|
+
}
|
|
37
|
+
if (onClick) onClick();
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const showButton = () => {
|
|
41
|
+
if (button && !isInstalled) {
|
|
42
|
+
button.style.opacity = "1";
|
|
43
|
+
button.style.visibility = "visible";
|
|
44
|
+
button.style.display = "flex";
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const hideButton = () => {
|
|
49
|
+
if (button) {
|
|
50
|
+
button.style.opacity = "0";
|
|
51
|
+
button.style.visibility = "hidden";
|
|
52
|
+
button.style.display = "none";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Createste button using the flet-box Button component
|
|
57
|
+
button = Button({
|
|
58
|
+
widgetName: "InstallButton",
|
|
59
|
+
text: text,
|
|
60
|
+
variant: variant,
|
|
61
|
+
size: size,
|
|
62
|
+
bgColor: backgroundColor,
|
|
63
|
+
color: color,
|
|
64
|
+
borderRadius: borderRadius,
|
|
65
|
+
padding: padding,
|
|
66
|
+
position: "fixed",
|
|
67
|
+
bottom: `${bottom}px`,
|
|
68
|
+
left: "50%",
|
|
69
|
+
transform: "translateX(-50%)",
|
|
70
|
+
zIndex: 10000,
|
|
71
|
+
opacity: "0",
|
|
72
|
+
visibility: "hidden",
|
|
73
|
+
display: "none",
|
|
74
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.2)",
|
|
75
|
+
onPress: handleClick,
|
|
76
|
+
...rest,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Listen to events
|
|
80
|
+
const handleBeforeInstallPrompt = (e) => {
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
deferredPrompt = e;
|
|
83
|
+
showButton();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleAppInstalled = () => {
|
|
87
|
+
isInstalled = true;
|
|
88
|
+
hideButton();
|
|
89
|
+
if (onInstalled) onInstalled();
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
window.addEventListener("beforeinstallprompt", handleBeforeInstallPrompt);
|
|
93
|
+
window.addEventListener("appinstalled", handleAppInstalled);
|
|
94
|
+
|
|
95
|
+
// Cleanup
|
|
96
|
+
const cleanup = () => {
|
|
97
|
+
window.removeEventListener(
|
|
98
|
+
"beforeinstallprompt",
|
|
99
|
+
handleBeforeInstallPrompt,
|
|
100
|
+
);
|
|
101
|
+
window.removeEventListener("appinstalled", handleAppInstalled);
|
|
102
|
+
if (button && button.remove) button.remove();
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
button.onUnmount(cleanup);
|
|
106
|
+
|
|
107
|
+
return button;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export default InstallButton;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file ListTile.js
|
|
3
|
+
* @description List row with leading/trailing content and optional divider.
|
|
4
|
+
*/
|
|
5
|
+
import { WidgetFactory } from "../widget-factory/index.js";
|
|
6
|
+
import { Row } from "./Row.js";
|
|
7
|
+
import { Column } from "./Column.js";
|
|
8
|
+
import { Text } from "./Text.js";
|
|
9
|
+
import { colors, subscribeTheme } from "../utils/themes.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} ListTileProps
|
|
13
|
+
* @property {HTMLElement} [leftItem] - Widget rendered on the leading edge (e.g. an avatar or icon).
|
|
14
|
+
* @property {HTMLElement} [rightItem] - Widget rendered on the trailing edge (e.g. a chevron or switch).
|
|
15
|
+
* @property {string} [title] - Primary text line.
|
|
16
|
+
* @property {string} [subtitle] - Secondary text line rendered below the title at reduced size.
|
|
17
|
+
* @property {string} [description] - Tertiary text line rendered below the subtitle, typically muted.
|
|
18
|
+
* @property {Function} [onPress] - Click handler. When provided the tile becomes interactive.
|
|
19
|
+
* @property {boolean} [selected=false] - Highlights the tile with `selectedBgColor` and bolds the title.
|
|
20
|
+
* @property {boolean} [disabled=false] - Reduces opacity and removes interactivity.
|
|
21
|
+
* @property {boolean} [divider=false] - When true, appends a thin horizontal rule below the tile.
|
|
22
|
+
* @property {number} [paddingHorizontal=16] - Horizontal padding in pixels.
|
|
23
|
+
* @property {number} [paddingVertical=12] - Vertical padding in pixels.
|
|
24
|
+
* @property {number} [gap=12] - Gap between the left item, text column, and right item.
|
|
25
|
+
* @property {number} [elevation=0] - Drop-shadow depth. 0 means no shadow.
|
|
26
|
+
* @property {number} [borderRadius=0] - Corner radius of the tile in pixels.
|
|
27
|
+
* @property {string} [bgColor] - Default background color.
|
|
28
|
+
* @property {string} [selectedBgColor] - Background color when `selected` is true.
|
|
29
|
+
* @property {string} [hoverColor] - Background color on mouse hover (only when interactive).
|
|
30
|
+
* @property {Object} [titleProps={}] - Extra props forwarded to the `Text` widget for the title.
|
|
31
|
+
* @property {Object} [subtitleProps={}] - Extra props forwarded to the `Text` widget for the subtitle.
|
|
32
|
+
* @property {Object} [descriptionProps={}] - Extra props forwarded to the `Text` widget for the description.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A single-row list item that follows the Material Design ListTile pattern.
|
|
37
|
+
*
|
|
38
|
+
* Composes a leading widget, a text column (title / subtitle / description),
|
|
39
|
+
* and a trailing widget into a horizontally-aligned row. Supports selection
|
|
40
|
+
* highlighting, hover effects, elevation shadows, and an optional divider.
|
|
41
|
+
* Automatically unsubscribes from the theme system when the tile is unmounted.
|
|
42
|
+
*
|
|
43
|
+
* @param {ListTileProps} props
|
|
44
|
+
* @returns {HTMLElement} The tile element (or a wrapper `<div>` when `divider` is true).
|
|
45
|
+
*/
|
|
46
|
+
export const ListTile = (props) => {
|
|
47
|
+
const {
|
|
48
|
+
leftItem,
|
|
49
|
+
rightItem,
|
|
50
|
+
title,
|
|
51
|
+
subtitle,
|
|
52
|
+
description,
|
|
53
|
+
onPress,
|
|
54
|
+
selected = false,
|
|
55
|
+
disabled = false,
|
|
56
|
+
divider = false,
|
|
57
|
+
paddingHorizontal = 16,
|
|
58
|
+
paddingVertical = 12,
|
|
59
|
+
gap = 12,
|
|
60
|
+
elevation = 0,
|
|
61
|
+
borderRadius = 0,
|
|
62
|
+
bgColor = colors.surface,
|
|
63
|
+
selectedBgColor = `${colors.primary}20`,
|
|
64
|
+
hoverColor = colors.border,
|
|
65
|
+
titleProps = {},
|
|
66
|
+
subtitleProps = {},
|
|
67
|
+
descriptionProps = {},
|
|
68
|
+
...rest
|
|
69
|
+
} = props;
|
|
70
|
+
|
|
71
|
+
// Only add pointer cursor and hover/click behavior when an onPress handler exists
|
|
72
|
+
// and the tile is not disabled.
|
|
73
|
+
const isInteractive = onPress && !disabled;
|
|
74
|
+
|
|
75
|
+
// Build text column — only include lines that were actually provided
|
|
76
|
+
const textChildren = [];
|
|
77
|
+
|
|
78
|
+
if (title) {
|
|
79
|
+
textChildren.push(
|
|
80
|
+
Text({
|
|
81
|
+
text: title,
|
|
82
|
+
size: 16,
|
|
83
|
+
weight: selected ? "600" : "500",
|
|
84
|
+
color: selected ? colors.primary : colors.text,
|
|
85
|
+
...titleProps,
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (subtitle) {
|
|
91
|
+
textChildren.push(
|
|
92
|
+
Text({
|
|
93
|
+
text: subtitle,
|
|
94
|
+
size: 13,
|
|
95
|
+
color: colors.textSecondary,
|
|
96
|
+
marginTop: 2,
|
|
97
|
+
...subtitleProps,
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (description) {
|
|
103
|
+
textChildren.push(
|
|
104
|
+
Text({
|
|
105
|
+
text: description,
|
|
106
|
+
size: 12,
|
|
107
|
+
color: colors.textDisabled,
|
|
108
|
+
marginTop: 2,
|
|
109
|
+
...descriptionProps,
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Wrap text lines in a Column only when at least one line was provided
|
|
115
|
+
const centerColumn =
|
|
116
|
+
textChildren.length > 0
|
|
117
|
+
? Column({
|
|
118
|
+
gap: 2,
|
|
119
|
+
alignItems: "flex-start",
|
|
120
|
+
flex: 1,
|
|
121
|
+
minWidth: 0, // allows the column to shrink below its content size in a flex row
|
|
122
|
+
children: textChildren,
|
|
123
|
+
})
|
|
124
|
+
: null;
|
|
125
|
+
|
|
126
|
+
// Assemble the main content row: [leftItem] [textColumn] [rightItem]
|
|
127
|
+
const rowChildren = [];
|
|
128
|
+
if (leftItem) rowChildren.push(leftItem);
|
|
129
|
+
if (centerColumn) rowChildren.push(centerColumn);
|
|
130
|
+
if (rightItem) rowChildren.push(rightItem);
|
|
131
|
+
|
|
132
|
+
const contentRow = Row({
|
|
133
|
+
alignItems: "center",
|
|
134
|
+
gap: gap,
|
|
135
|
+
padding: `${paddingVertical}px ${paddingHorizontal}px`,
|
|
136
|
+
width: "100%",
|
|
137
|
+
boxSizing: "border-box",
|
|
138
|
+
children: rowChildren,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Main tile element
|
|
142
|
+
const tile = WidgetFactory({
|
|
143
|
+
tag: "div",
|
|
144
|
+
widgetName: "ListTile",
|
|
145
|
+
backgroundColor: selected ? selectedBgColor : bgColor,
|
|
146
|
+
borderRadius: borderRadius,
|
|
147
|
+
cursor: isInteractive ? "pointer" : "default",
|
|
148
|
+
opacity: disabled ? 0.6 : 1,
|
|
149
|
+
boxShadow:
|
|
150
|
+
elevation > 0
|
|
151
|
+
? `0 ${elevation}px ${elevation * 2}px rgba(0,0,0,0.1)`
|
|
152
|
+
: "none",
|
|
153
|
+
transition: "all 0.2s ease",
|
|
154
|
+
width: "100%",
|
|
155
|
+
child: contentRow,
|
|
156
|
+
onclick: isInteractive ? onPress : null,
|
|
157
|
+
...rest,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Hover effects — only when the tile can be interacted with
|
|
161
|
+
if (isInteractive) {
|
|
162
|
+
tile.addEventListener("mouseenter", () => {
|
|
163
|
+
if (!selected) tile.style.backgroundColor = hoverColor;
|
|
164
|
+
if (elevation > 0) {
|
|
165
|
+
tile.style.transform = "translateY(-2px)";
|
|
166
|
+
tile.style.boxShadow = `0 ${elevation + 2}px ${(elevation + 2) * 2}px rgba(0,0,0,0.15)`;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
tile.addEventListener("mouseleave", () => {
|
|
170
|
+
if (!selected) tile.style.backgroundColor = bgColor;
|
|
171
|
+
if (elevation > 0) {
|
|
172
|
+
tile.style.transform = "translateY(0)";
|
|
173
|
+
tile.style.boxShadow = `0 ${elevation}px ${elevation * 2}px rgba(0,0,0,0.1)`;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Theme subscription — keep the background in sync with the active theme.
|
|
179
|
+
// Only subscribe when the caller has not overridden bgColor/selectedBgColor,
|
|
180
|
+
// to avoid fighting with explicit colors.
|
|
181
|
+
let unsubscribeTheme = null;
|
|
182
|
+
if (!props.bgColor && !props.selectedBgColor) {
|
|
183
|
+
unsubscribeTheme = subscribeTheme(() => {
|
|
184
|
+
if (!selected) {
|
|
185
|
+
tile.style.backgroundColor = colors.surface;
|
|
186
|
+
} else {
|
|
187
|
+
tile.style.backgroundColor = `${colors.primary}20`;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Cleanup — unsubscribe from the theme system when the tile is unmounted
|
|
193
|
+
tile.onUnmount(() => {
|
|
194
|
+
if (unsubscribeTheme) unsubscribeTheme();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// When a divider is requested, wrap the tile in a column and append a separator line
|
|
198
|
+
if (divider) {
|
|
199
|
+
const dividerLine = WidgetFactory({
|
|
200
|
+
height: 1,
|
|
201
|
+
backgroundColor: colors.border,
|
|
202
|
+
marginLeft: `${paddingHorizontal}px`,
|
|
203
|
+
width: `calc(100% - ${paddingHorizontal}px)`,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const container = WidgetFactory({
|
|
207
|
+
width: "100%",
|
|
208
|
+
display: "flex",
|
|
209
|
+
flexDirection: "column",
|
|
210
|
+
children: [tile, dividerLine],
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Propagate cleanup to the outer container as well
|
|
214
|
+
container.onUnmount(() => {
|
|
215
|
+
if (unsubscribeTheme) unsubscribeTheme();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
return container;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return tile;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export default ListTile;
|