flet-box 1.0.7 → 1.0.8
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/bin/cli.js +13 -0
- package/bin/commands/checkApi.js +13 -1
- package/bin/commands/checkUi.js +1369 -0
- package/bin/commands/create.js +49 -5
- package/bin/commands/createBundle.js +21 -0
- package/bin/commands/runServer.js +11 -2
- package/bin/utils/pwaShell.js +137 -0
- package/bin/utils/templates.js +1373 -434
- package/docs/README.md +2 -0
- package/docs/cli/README.md +62 -1
- package/docs/guides/README.md +2 -0
- package/docs/guides/clean-ui.md +211 -0
- package/docs/guides/minification-and-protection.md +1 -1
- package/docs/guides/online-updates.md +48 -0
- package/docs/tools/README.md +1 -1
- package/docs/tools/animation.md +1 -1
- package/docs/tools/markdown.md +3 -3
- package/docs/tools/theme.md +41 -1
- package/docs/widget/Accordion.md +3 -0
- package/docs/widget/AdMob.md +4 -1
- package/docs/widget/AdSense.md +3 -0
- package/docs/widget/AnimatedBox.md +6 -3
- package/docs/widget/AnimatedText.md +3 -0
- package/docs/widget/Audio.md +3 -0
- package/docs/widget/Avatar.md +3 -0
- package/docs/widget/Badge.md +3 -0
- package/docs/widget/Button.md +3 -0
- package/docs/widget/COMMON_PROPS.md +52 -0
- package/docs/widget/Card.md +3 -0
- package/docs/widget/Carousel.md +3 -0
- package/docs/widget/Chart.md +3 -0
- package/docs/widget/Checkbox.md +3 -0
- package/docs/widget/Chip.md +3 -0
- package/docs/widget/CircularBar.md +3 -0
- package/docs/widget/CircularChart.md +3 -0
- package/docs/widget/CodeViewer.md +3 -0
- package/docs/widget/Column.md +3 -0
- package/docs/widget/Container.md +3 -0
- package/docs/widget/DataTable.md +3 -0
- package/docs/widget/Divider.md +3 -0
- package/docs/widget/DraggBox.md +3 -0
- package/docs/widget/Dropdown.md +3 -0
- package/docs/widget/DroppBox.md +3 -0
- package/docs/widget/FloatingActionButton.md +3 -0
- package/docs/widget/GridView.md +3 -0
- package/docs/widget/Icon.md +3 -0
- package/docs/widget/Image.md +3 -0
- package/docs/widget/Input.md +3 -0
- package/docs/widget/Inspector.md +2 -2
- package/docs/widget/InstallButton.md +3 -0
- package/docs/widget/ListTile.md +3 -0
- package/docs/widget/ListView.md +3 -0
- package/docs/widget/Maps.md +3 -0
- package/docs/widget/Markdown.md +3 -0
- package/docs/widget/MatrixRain.md +3 -0
- package/docs/widget/Pagination.md +3 -0
- package/docs/widget/ParallaxBox.md +3 -0
- package/docs/widget/ProgressBar.md +3 -0
- package/docs/widget/QRCode.md +3 -0
- package/docs/widget/Radio.md +4 -1
- package/docs/widget/Rating.md +3 -0
- package/docs/widget/Row.md +3 -0
- package/docs/widget/Skeleton.md +3 -0
- package/docs/widget/Slider.md +3 -0
- package/docs/widget/Stack.md +3 -0
- package/docs/widget/Stepper.md +3 -0
- package/docs/widget/Switch.md +3 -0
- package/docs/widget/Tabs.md +5 -5
- package/docs/widget/Text.md +3 -0
- package/docs/widget/Tooltip.md +3 -0
- package/docs/widget/TreeView.md +3 -0
- package/docs/widget/Video.md +3 -0
- package/package.json +2 -2
- package/src/animations/AnimatedBox.js +4 -1
- package/src/animations/MatrixRain.js +3 -3
- package/src/index.d.ts +64 -8
- package/src/index.js +1 -0
- package/src/navigations/AppBar.js +7 -12
- package/src/navigations/BottomNavigation.js +8 -13
- package/src/navigations/CollapsibleSideBar.js +30 -8
- package/src/navigations/Drawer.js +16 -14
- package/src/navigations/DrawerItem.js +3 -1
- package/src/navigations/Scaffold.js +26 -12
- package/src/navigations/Tabs.js +104 -59
- package/src/tools/color.js +54 -0
- package/src/tools/index.js +9 -1
- package/src/tools/scale.js +41 -0
- package/src/tools/shadow.js +20 -0
- package/src/utils/index.js +1 -0
- package/src/utils/readableInk.js +19 -0
- package/src/utils/syntaxHighlight.js +78 -10
- package/src/utils/themes.js +217 -54
- package/src/utils/units.js +33 -11
- package/src/widget-factory/effects.js +56 -5
- package/src/widget-factory/processProps.js +10 -1
- package/src/widget-factory/remTool.js +15 -29
- package/src/widget-factory/stackPosition.js +18 -3
- package/src/widget-factory/translateProps.js +28 -0
- package/src/widgets/Accordion.js +20 -23
- package/src/widgets/AdMob.js +7 -2
- package/src/widgets/Avatar.js +18 -12
- package/src/widgets/Badge.js +3 -2
- package/src/widgets/BottomSheet.js +14 -13
- package/src/widgets/Button.js +69 -30
- package/src/widgets/Card.js +6 -2
- package/src/widgets/Carousel.js +12 -10
- package/src/widgets/Chart.js +28 -5
- package/src/widgets/Checkbox.js +76 -18
- package/src/widgets/Chip.js +31 -10
- package/src/widgets/CircularBar.js +7 -7
- package/src/widgets/CircularChart.js +26 -8
- package/src/widgets/CodeViewer.js +93 -20
- package/src/widgets/DataTable.js +17 -11
- package/src/widgets/DraggBox.js +2 -1
- package/src/widgets/Dropdown.js +26 -13
- package/src/widgets/DroppBox.js +4 -2
- package/src/widgets/FloatingActionButton.js +13 -7
- package/src/widgets/Icon.js +3 -1
- package/src/widgets/Input.js +30 -23
- package/src/widgets/Inspector.js +54 -1
- package/src/widgets/InstallButton.js +6 -4
- package/src/widgets/ListTile.js +4 -3
- package/src/widgets/ListView.js +30 -12
- package/src/widgets/Markdown.js +37 -26
- package/src/widgets/Modal.js +14 -11
- package/src/widgets/Pagination.js +16 -11
- package/src/widgets/ProgressBar.js +6 -4
- package/src/widgets/Radio.js +73 -15
- package/src/widgets/Rating.js +24 -6
- package/src/widgets/Skeleton.js +31 -31
- package/src/widgets/Slider.js +29 -18
- package/src/widgets/SnackBar.js +35 -39
- package/src/widgets/Stepper.js +13 -9
- package/src/widgets/Switch.js +59 -18
- package/src/widgets/Tooltip.js +12 -10
- package/src/widgets/TreeView.js +27 -15
package/src/widgets/DraggBox.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { WidgetFactory } from "../widget-factory/index.js";
|
|
6
6
|
import { colors } from "../utils/themes.js";
|
|
7
|
+
import { toREM } from "../utils/units.js";
|
|
7
8
|
|
|
8
9
|
export const DraggBox = (props) => {
|
|
9
10
|
const {
|
|
@@ -53,7 +54,7 @@ export const DraggBox = (props) => {
|
|
|
53
54
|
clone.style.zIndex = "99999";
|
|
54
55
|
clone.style.backgroundColor = dragOverlayColor;
|
|
55
56
|
clone.style.border = `2px dashed ${dragBorderColor}`;
|
|
56
|
-
clone.style.borderRadius =
|
|
57
|
+
clone.style.borderRadius = toREM(8);
|
|
57
58
|
document.body.appendChild(clone);
|
|
58
59
|
return clone;
|
|
59
60
|
};
|
package/src/widgets/Dropdown.js
CHANGED
|
@@ -12,6 +12,8 @@ import { Row } from "./Row.js";
|
|
|
12
12
|
import { Column } from "./Column.js";
|
|
13
13
|
import { Text } from "./Text.js";
|
|
14
14
|
import { Icon } from "./Icon.js";
|
|
15
|
+
import { toREM } from "../utils/units.js";
|
|
16
|
+
import { TAP_MINIMUM } from "../tools/index.js";
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Props owned by the Dropdown's closure state. Routed to the widget's `update`
|
|
@@ -97,10 +99,12 @@ export const Dropdown = (inputProps = {}) => {
|
|
|
97
99
|
let resizeListener = null;
|
|
98
100
|
let scrollListener = null;
|
|
99
101
|
|
|
102
|
+
/** [vertical, horizontal] in framework units — see `Input`: a `"8px 14px"`
|
|
103
|
+
* string is raw CSS and stops the field growing with the reader's font. */
|
|
100
104
|
const sizes = {
|
|
101
|
-
small: { padding:
|
|
102
|
-
medium: { padding:
|
|
103
|
-
large: { padding:
|
|
105
|
+
small: { padding: [4, 12], fontSize: 12, minHeight: 32 },
|
|
106
|
+
medium: { padding: [8, 12], fontSize: 14, minHeight: TAP_MINIMUM },
|
|
107
|
+
large: { padding: [12, 16], fontSize: 16, minHeight: 48 },
|
|
104
108
|
};
|
|
105
109
|
let currentSize = sizes[size] || sizes.medium;
|
|
106
110
|
|
|
@@ -197,14 +201,16 @@ export const Dropdown = (inputProps = {}) => {
|
|
|
197
201
|
alignItems: "center",
|
|
198
202
|
justifyContent: "space-between",
|
|
199
203
|
width: "100%",
|
|
200
|
-
|
|
204
|
+
paddingVertical: currentSize.padding[0],
|
|
205
|
+
paddingHorizontal: currentSize.padding[1],
|
|
206
|
+
minHeight: currentSize.minHeight,
|
|
201
207
|
backgroundColor:
|
|
202
208
|
currentVariant === "filled" ? `${currentBgColor}CC` : "transparent",
|
|
203
209
|
border:
|
|
204
210
|
currentVariant === "outlined"
|
|
205
211
|
? `1px solid ${currentError ? colors.danger : colors.border}`
|
|
206
212
|
: "none",
|
|
207
|
-
borderRadius:
|
|
213
|
+
borderRadius: currentBorderRadius,
|
|
208
214
|
cursor: currentDisabled ? "not-allowed" : "pointer",
|
|
209
215
|
opacity: currentDisabled ? 0.5 : 1,
|
|
210
216
|
boxSizing: "border-box",
|
|
@@ -236,11 +242,15 @@ export const Dropdown = (inputProps = {}) => {
|
|
|
236
242
|
],
|
|
237
243
|
});
|
|
238
244
|
|
|
239
|
-
//
|
|
245
|
+
// The root must not paint a surface: only the selector does, and an
|
|
246
|
+
// `outlined` selector is deliberately transparent. Container's default
|
|
247
|
+
// background otherwise showed through behind the label as a full-width
|
|
248
|
+
// block, and painted the outlined field as if it were filled.
|
|
240
249
|
const container = Container({
|
|
241
250
|
widgetName: "Dropdown",
|
|
242
251
|
position: "relative",
|
|
243
252
|
width: width,
|
|
253
|
+
bgColor: "transparent",
|
|
244
254
|
...rest,
|
|
245
255
|
});
|
|
246
256
|
|
|
@@ -248,9 +258,9 @@ export const Dropdown = (inputProps = {}) => {
|
|
|
248
258
|
const labelEl = WidgetFactory({
|
|
249
259
|
tag: "label",
|
|
250
260
|
textContent: currentLabel,
|
|
251
|
-
fontSize:
|
|
261
|
+
fontSize: 12,
|
|
252
262
|
color: currentError ? colors.danger : colors.textSecondary,
|
|
253
|
-
marginBottom:
|
|
263
|
+
marginBottom: 4,
|
|
254
264
|
display: "block",
|
|
255
265
|
});
|
|
256
266
|
container.appendChild(labelEl);
|
|
@@ -318,7 +328,7 @@ export const Dropdown = (inputProps = {}) => {
|
|
|
318
328
|
zIndex: 10000,
|
|
319
329
|
border: `1px solid ${colors.border}`,
|
|
320
330
|
backgroundColor: currentBgColor,
|
|
321
|
-
borderRadius:
|
|
331
|
+
borderRadius: currentBorderRadius, // same borderRadius
|
|
322
332
|
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
|
323
333
|
maxHeight: "250px",
|
|
324
334
|
overflowY: "auto",
|
|
@@ -348,12 +358,14 @@ export const Dropdown = (inputProps = {}) => {
|
|
|
348
358
|
);
|
|
349
359
|
|
|
350
360
|
const optionItem = Row({
|
|
351
|
-
|
|
361
|
+
paddingVertical: 8,
|
|
362
|
+
paddingHorizontal: 12,
|
|
363
|
+
minHeight: TAP_MINIMUM,
|
|
352
364
|
cursor: "pointer",
|
|
353
365
|
backgroundColor: isSelected ? currentColor + "15" : "transparent",
|
|
354
366
|
alignItems: "center",
|
|
355
367
|
justifyContent: "space-between",
|
|
356
|
-
gap:
|
|
368
|
+
gap: 8,
|
|
357
369
|
children: [
|
|
358
370
|
Row({
|
|
359
371
|
alignItems: "center",
|
|
@@ -496,8 +508,9 @@ export const Dropdown = (inputProps = {}) => {
|
|
|
496
508
|
}
|
|
497
509
|
if (needsDisplayUpdate) {
|
|
498
510
|
updateDisplay();
|
|
499
|
-
selector.style.padding = currentSize.padding
|
|
500
|
-
selector.style.
|
|
511
|
+
selector.style.padding = `${toREM(currentSize.padding[0])} ${toREM(currentSize.padding[1])}`;
|
|
512
|
+
selector.style.minHeight = toREM(currentSize.minHeight);
|
|
513
|
+
selector.style.borderRadius = toREM(currentBorderRadius);
|
|
501
514
|
if (currentVariant === "outlined")
|
|
502
515
|
selector.style.border = `1px solid ${currentError ? colors.danger : colors.border}`;
|
|
503
516
|
else selector.style.border = "none";
|
package/src/widgets/DroppBox.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import { WidgetFactory } from "../widget-factory/index.js";
|
|
6
6
|
import { colors } from "../utils/themes.js";
|
|
7
7
|
|
|
8
|
+
import { toREM } from "../utils/units.js";
|
|
9
|
+
|
|
8
10
|
export const DroppBox = (props) => {
|
|
9
11
|
const {
|
|
10
12
|
child,
|
|
@@ -58,12 +60,12 @@ export const DroppBox = (props) => {
|
|
|
58
60
|
transition: `all ${transitionDuration} ${transitionTiming}`,
|
|
59
61
|
backgroundColor: bgColor,
|
|
60
62
|
borderRadius:
|
|
61
|
-
|
|
63
|
+
toREM(borderRadius),
|
|
62
64
|
borderWidth: `${borderWidth}px`,
|
|
63
65
|
borderStyle: borderStyle,
|
|
64
66
|
borderColor: borderColor,
|
|
65
67
|
boxShadow: shadow,
|
|
66
|
-
padding:
|
|
68
|
+
padding: toREM(padding),
|
|
67
69
|
...rest,
|
|
68
70
|
});
|
|
69
71
|
|
|
@@ -7,6 +7,8 @@ import { Row } from "./Row.js";
|
|
|
7
7
|
import { Text } from "./Text.js";
|
|
8
8
|
import { Icon } from "./Icon.js";
|
|
9
9
|
import { colors } from "../utils/themes.js";
|
|
10
|
+
import { toREM } from "../utils/units.js";
|
|
11
|
+
import { SHADOW_LEVELS, PILL } from "../tools/index.js";
|
|
10
12
|
|
|
11
13
|
export const FloatingActionButton = (props) => {
|
|
12
14
|
const {
|
|
@@ -14,9 +16,11 @@ export const FloatingActionButton = (props) => {
|
|
|
14
16
|
label,
|
|
15
17
|
onPress,
|
|
16
18
|
backgroundColor = colors.primary,
|
|
17
|
-
foregroundColor =
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
foregroundColor = colors.onPrimary,
|
|
20
|
+
// Depth is the 0-5 scale, like every other widget; 6 was a number the scale
|
|
21
|
+
// does not have, which only worked because this widget built its own string.
|
|
22
|
+
elevation = 4,
|
|
23
|
+
borderRadius = PILL,
|
|
20
24
|
padding = 12,
|
|
21
25
|
mini = false,
|
|
22
26
|
extended = false,
|
|
@@ -24,7 +28,9 @@ export const FloatingActionButton = (props) => {
|
|
|
24
28
|
...rest
|
|
25
29
|
} = props;
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
// 48 and 40 are both scale steps, and both clear the Fitts floor: the mini FAB
|
|
32
|
+
// sits exactly on it, which is as small as a floating target may get.
|
|
33
|
+
const size = mini ? 40 : 48;
|
|
28
34
|
const iconSize = mini ? 20 : 24;
|
|
29
35
|
|
|
30
36
|
const children = [
|
|
@@ -58,14 +64,14 @@ export const FloatingActionButton = (props) => {
|
|
|
58
64
|
gap: 8,
|
|
59
65
|
width: extended ? "auto" : size,
|
|
60
66
|
height: size,
|
|
61
|
-
borderRadius: extended ?
|
|
67
|
+
borderRadius: extended ? borderRadius : "50%",
|
|
62
68
|
backgroundColor: backgroundColor,
|
|
63
69
|
color: foregroundColor,
|
|
64
70
|
cursor: disabled ? "not-allowed" : "pointer",
|
|
65
71
|
opacity: disabled ? 0.6 : 1,
|
|
66
|
-
boxShadow:
|
|
72
|
+
boxShadow: SHADOW_LEVELS[elevation] ?? SHADOW_LEVELS[4],
|
|
67
73
|
transition: "all 0.2s ease",
|
|
68
|
-
padding: extended ?
|
|
74
|
+
padding: extended ? `0 ${toREM(16)}` : 0,
|
|
69
75
|
child: extended
|
|
70
76
|
? Row({ alignItems: "center", gap: 8, children })
|
|
71
77
|
: children[0],
|
package/src/widgets/Icon.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import { WidgetFactory } from "../widget-factory/index.js";
|
|
6
6
|
import { colors } from "../utils/themes.js";
|
|
7
7
|
|
|
8
|
+
import { toREM } from "../utils/units.js";
|
|
9
|
+
|
|
8
10
|
export const Icon = (props) => {
|
|
9
11
|
const {
|
|
10
12
|
icon,
|
|
@@ -20,7 +22,7 @@ export const Icon = (props) => {
|
|
|
20
22
|
widgetName: "Icon",
|
|
21
23
|
className: "material-icons",
|
|
22
24
|
text: iconName, // ✅ CHANGED: text instead of textContent
|
|
23
|
-
fontSize:
|
|
25
|
+
fontSize: toREM(size),
|
|
24
26
|
color: color,
|
|
25
27
|
display: "inline-flex",
|
|
26
28
|
alignItems: "center",
|
package/src/widgets/Input.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { WidgetFactory } from "../widget-factory/index.js";
|
|
6
6
|
import { colors } from "../utils/themes.js";
|
|
7
|
+
import { toREM } from "../utils/units.js";
|
|
8
|
+
import { TAP_MINIMUM } from "../tools/index.js";
|
|
7
9
|
import { Icon } from "./Icon.js";
|
|
8
10
|
import { TextInputValidator } from "../utils/TextInputValidator.js";
|
|
9
11
|
import { composeUpdate } from "../utils/composeUpdate.js";
|
|
@@ -76,7 +78,7 @@ export const Input = (props) => {
|
|
|
76
78
|
size = "medium",
|
|
77
79
|
variant = "outlined",
|
|
78
80
|
fullWidth = false,
|
|
79
|
-
borderRadius =
|
|
81
|
+
borderRadius = 16,
|
|
80
82
|
|
|
81
83
|
iconLeft,
|
|
82
84
|
iconRight,
|
|
@@ -131,11 +133,17 @@ export const Input = (props) => {
|
|
|
131
133
|
const isFocused = () => document.activeElement === inputElement;
|
|
132
134
|
|
|
133
135
|
// ========== SIZES ==========
|
|
134
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Framework units, not CSS strings: this widget builds a real `<input>` and
|
|
138
|
+
* writes `style.*` by hand, so every length here goes through `toREM` at the
|
|
139
|
+
* point of use. Written as `"10px 14px"` the field froze at 16px base and the
|
|
140
|
+
* reader who raises their font got small text inside a control that should have
|
|
141
|
+
* grown with it. `minHeight` is the Fitts floor — a text field is a target.
|
|
142
|
+
*/
|
|
135
143
|
const sizes = {
|
|
136
|
-
small: { padding:
|
|
137
|
-
medium: { padding:
|
|
138
|
-
large: { padding:
|
|
144
|
+
small: { padding: [4, 12], fontSize: 12, iconSize: 16, minHeight: 32 },
|
|
145
|
+
medium: { padding: [8, 12], fontSize: 14, iconSize: 20, minHeight: TAP_MINIMUM },
|
|
146
|
+
large: { padding: [12, 16], fontSize: 16, iconSize: 24, minHeight: 48 },
|
|
139
147
|
};
|
|
140
148
|
const sz = sizes[size] || sizes.medium;
|
|
141
149
|
|
|
@@ -350,7 +358,7 @@ export const Input = (props) => {
|
|
|
350
358
|
fontSize: 12,
|
|
351
359
|
fontWeight: 500,
|
|
352
360
|
color: error ? colors.danger : colors.textSecondary,
|
|
353
|
-
marginBottom:
|
|
361
|
+
marginBottom: 4,
|
|
354
362
|
cursor: "text",
|
|
355
363
|
});
|
|
356
364
|
// Clicking the label focuses the native input — mirrors native <label for> behavior
|
|
@@ -387,8 +395,7 @@ export const Input = (props) => {
|
|
|
387
395
|
|
|
388
396
|
if (variant === "filled") {
|
|
389
397
|
wrapperStyles.backgroundColor = colors.gray100;
|
|
390
|
-
wrapperStyles.borderRadius =
|
|
391
|
-
typeof borderRadius === "number" ? `${borderRadius}px` : borderRadius;
|
|
398
|
+
wrapperStyles.borderRadius = toREM(borderRadius);
|
|
392
399
|
wrapperStyles.border = "none";
|
|
393
400
|
} else if (variant === "underlined") {
|
|
394
401
|
wrapperStyles.backgroundColor = "transparent";
|
|
@@ -397,8 +404,7 @@ export const Input = (props) => {
|
|
|
397
404
|
} else {
|
|
398
405
|
// Default: outlined
|
|
399
406
|
wrapperStyles.backgroundColor = "transparent";
|
|
400
|
-
wrapperStyles.borderRadius =
|
|
401
|
-
typeof borderRadius === "number" ? `${borderRadius}px` : borderRadius;
|
|
407
|
+
wrapperStyles.borderRadius = toREM(borderRadius);
|
|
402
408
|
wrapperStyles.border = `1px solid ${error ? colors.danger : colors.border}`;
|
|
403
409
|
}
|
|
404
410
|
|
|
@@ -414,7 +420,7 @@ export const Input = (props) => {
|
|
|
414
420
|
name: iconLeft,
|
|
415
421
|
size: iconSize || sz.iconSize,
|
|
416
422
|
color: iconColor,
|
|
417
|
-
marginLeft:
|
|
423
|
+
marginLeft: 12,
|
|
418
424
|
flexShrink: 0,
|
|
419
425
|
pointerEvents: "auto",
|
|
420
426
|
});
|
|
@@ -436,8 +442,9 @@ export const Input = (props) => {
|
|
|
436
442
|
inputElement.value = currentValue;
|
|
437
443
|
|
|
438
444
|
inputElement.style.flex = "1";
|
|
439
|
-
inputElement.style.padding = sz.padding
|
|
440
|
-
inputElement.style.fontSize = sz.fontSize;
|
|
445
|
+
inputElement.style.padding = `${toREM(sz.padding[0])} ${toREM(sz.padding[1])}`;
|
|
446
|
+
inputElement.style.fontSize = toREM(sz.fontSize);
|
|
447
|
+
inputElement.style.minHeight = toREM(sz.minHeight);
|
|
441
448
|
inputElement.style.border = "none";
|
|
442
449
|
inputElement.style.outline = "none";
|
|
443
450
|
inputElement.style.backgroundColor = "transparent";
|
|
@@ -505,15 +512,15 @@ export const Input = (props) => {
|
|
|
505
512
|
btn.type = "button";
|
|
506
513
|
btn.textContent = glyph;
|
|
507
514
|
btn.setAttribute("aria-label", label);
|
|
508
|
-
btn.style.width =
|
|
509
|
-
btn.style.height =
|
|
515
|
+
btn.style.width = toREM(32);
|
|
516
|
+
btn.style.height = toREM(32);
|
|
510
517
|
btn.style.flexShrink = "0";
|
|
511
|
-
btn.style.marginRight =
|
|
518
|
+
btn.style.marginRight = toREM(4);
|
|
512
519
|
btn.style.padding = "0";
|
|
513
520
|
btn.style.border = "none";
|
|
514
521
|
btn.style.background = "transparent";
|
|
515
522
|
btn.style.color = colors.textSecondary;
|
|
516
|
-
btn.style.fontSize =
|
|
523
|
+
btn.style.fontSize = toREM(16);
|
|
517
524
|
btn.style.lineHeight = "1";
|
|
518
525
|
btn.style.cursor = disabled ? "not-allowed" : "pointer";
|
|
519
526
|
btn.style.display = "flex";
|
|
@@ -564,12 +571,12 @@ export const Input = (props) => {
|
|
|
564
571
|
if (showValidationIcon && validation !== "none") {
|
|
565
572
|
validationIcon = document.createElement("span");
|
|
566
573
|
validationIcon.textContent = "";
|
|
567
|
-
validationIcon.style.width =
|
|
568
|
-
validationIcon.style.fontSize =
|
|
574
|
+
validationIcon.style.width = toREM(24);
|
|
575
|
+
validationIcon.style.fontSize = toREM(16);
|
|
569
576
|
validationIcon.style.display = "none";
|
|
570
577
|
validationIcon.style.alignItems = "center";
|
|
571
578
|
validationIcon.style.justifyContent = "center";
|
|
572
|
-
validationIcon.style.marginRight =
|
|
579
|
+
validationIcon.style.marginRight = toREM(8);
|
|
573
580
|
validationIcon.style.flexShrink = "0";
|
|
574
581
|
inputWrapper.appendChild(validationIcon);
|
|
575
582
|
}
|
|
@@ -580,7 +587,7 @@ export const Input = (props) => {
|
|
|
580
587
|
name: iconRight,
|
|
581
588
|
size: iconSize || sz.iconSize,
|
|
582
589
|
color: iconColor,
|
|
583
|
-
marginRight:
|
|
590
|
+
marginRight: 12,
|
|
584
591
|
flexShrink: 0,
|
|
585
592
|
pointerEvents: "auto",
|
|
586
593
|
});
|
|
@@ -593,9 +600,9 @@ export const Input = (props) => {
|
|
|
593
600
|
// --- Error message element rendered below the input ---
|
|
594
601
|
errorMessageElement = document.createElement("span");
|
|
595
602
|
errorMessageElement.textContent = "";
|
|
596
|
-
errorMessageElement.style.fontSize =
|
|
603
|
+
errorMessageElement.style.fontSize = toREM(12);
|
|
597
604
|
errorMessageElement.style.color = colors.danger;
|
|
598
|
-
errorMessageElement.style.marginTop =
|
|
605
|
+
errorMessageElement.style.marginTop = toREM(4);
|
|
599
606
|
errorMessageElement.style.display = "none";
|
|
600
607
|
container.appendChild(errorMessageElement);
|
|
601
608
|
|
package/src/widgets/Inspector.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// utils/Inspector.js
|
|
2
2
|
import { getWidgetProps } from "../utils/getWidgetProps.js";
|
|
3
|
+
import { WidgetFactory } from "../widget-factory/index.js";
|
|
4
|
+
import { colors } from "../utils/themes.js";
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Converts a JavaScript value to its source-code string representation.
|
|
@@ -138,6 +140,44 @@ const collectChildren = (widget) => {
|
|
|
138
140
|
return children;
|
|
139
141
|
};
|
|
140
142
|
|
|
143
|
+
/**
|
|
144
|
+
* The widget form: a monospace block holding the reconstructed call.
|
|
145
|
+
*
|
|
146
|
+
* `Inspector` itself is a string builder, which is what the console helpers
|
|
147
|
+
* need. A screen that wants to *show* a tree asks for this one, by passing the
|
|
148
|
+
* props object the documentation describes.
|
|
149
|
+
*
|
|
150
|
+
* @param {{ widget?: HTMLElement, indent?: number }} props
|
|
151
|
+
* @returns {HTMLElement}
|
|
152
|
+
*/
|
|
153
|
+
const InspectorView = ({ widget, indent = 0, ...rest }) => {
|
|
154
|
+
const dump = widget
|
|
155
|
+
? Inspector(widget, indent)
|
|
156
|
+
: 'Inspector({ widget }) — nothing to inspect.';
|
|
157
|
+
|
|
158
|
+
const container = WidgetFactory({
|
|
159
|
+
widgetName: "Inspector",
|
|
160
|
+
tag: "pre",
|
|
161
|
+
text: dump,
|
|
162
|
+
fontFamily:
|
|
163
|
+
'ui-monospace, SFMono-Regular, Menlo, Consolas, "Courier New", monospace',
|
|
164
|
+
fontSize: 12,
|
|
165
|
+
color: colors.text,
|
|
166
|
+
backgroundColor: colors.card,
|
|
167
|
+
padding: 16,
|
|
168
|
+
borderRadius: 8,
|
|
169
|
+
overflowX: "auto",
|
|
170
|
+
width: "100%",
|
|
171
|
+
...rest,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// A <pre> that wraps is a dump nobody can read; the horizontal scroll above
|
|
175
|
+
// is the deal.
|
|
176
|
+
container.style.whiteSpace = "pre";
|
|
177
|
+
|
|
178
|
+
return container;
|
|
179
|
+
};
|
|
180
|
+
|
|
141
181
|
/**
|
|
142
182
|
* Converts a widget element back into its equivalent source-code representation,
|
|
143
183
|
* recursively reconstructing its full prop tree and children.
|
|
@@ -149,7 +189,20 @@ const collectChildren = (widget) => {
|
|
|
149
189
|
* @param {number} [indent=0] - Starting indentation depth for pretty-printing.
|
|
150
190
|
* @returns {string} Source code string such as `Row({ gap: 8, children: [...] })`.
|
|
151
191
|
*/
|
|
152
|
-
export const Inspector = (
|
|
192
|
+
export const Inspector = (target, indent = 0) => {
|
|
193
|
+
// Two calls, one function. `Inspector({ widget, indent })` is the documented
|
|
194
|
+
// widget: it paints the dump so a settings screen can show a tree. Called
|
|
195
|
+
// with a node it stays the string builder `printWidgetCode` uses.
|
|
196
|
+
if (
|
|
197
|
+
target &&
|
|
198
|
+
typeof target === "object" &&
|
|
199
|
+
target.nodeType !== 1 &&
|
|
200
|
+
"widget" in target
|
|
201
|
+
) {
|
|
202
|
+
return InspectorView(target);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const widget = target;
|
|
153
206
|
if (!widget || widget.nodeType !== 1) {
|
|
154
207
|
return String(widget);
|
|
155
208
|
}
|
|
@@ -10,9 +10,11 @@ export const InstallButton = (props) => {
|
|
|
10
10
|
text = "📲 Install",
|
|
11
11
|
variant = "filled",
|
|
12
12
|
size = "medium",
|
|
13
|
-
borderRadius =
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
borderRadius = 16,
|
|
14
|
+
// A number is the framework's own form: Button doubles it horizontally
|
|
15
|
+
// (12 vertical, 24 horizontal) and the factory emits it as `rem`.
|
|
16
|
+
padding = 12,
|
|
17
|
+
bottom = 24,
|
|
16
18
|
backgroundColor,
|
|
17
19
|
color,
|
|
18
20
|
onInstalled,
|
|
@@ -64,7 +66,7 @@ export const InstallButton = (props) => {
|
|
|
64
66
|
borderRadius: borderRadius,
|
|
65
67
|
padding: padding,
|
|
66
68
|
position: "fixed",
|
|
67
|
-
bottom
|
|
69
|
+
bottom,
|
|
68
70
|
left: "50%",
|
|
69
71
|
transform: "translateX(-50%)",
|
|
70
72
|
zIndex: 10000,
|
package/src/widgets/ListTile.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Row } from "./Row.js";
|
|
|
7
7
|
import { Column } from "./Column.js";
|
|
8
8
|
import { Text } from "./Text.js";
|
|
9
9
|
import { colors, subscribeTheme } from "../utils/themes.js";
|
|
10
|
+
import { toREM } from "../utils/units.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* @typedef {Object} ListTileProps
|
|
@@ -132,7 +133,7 @@ export const ListTile = (props) => {
|
|
|
132
133
|
const contentRow = Row({
|
|
133
134
|
alignItems: "center",
|
|
134
135
|
gap: gap,
|
|
135
|
-
padding: `${paddingVertical}
|
|
136
|
+
padding: `${toREM(paddingVertical)} ${toREM(paddingHorizontal)}`,
|
|
136
137
|
width: "100%",
|
|
137
138
|
boxSizing: "border-box",
|
|
138
139
|
children: rowChildren,
|
|
@@ -199,8 +200,8 @@ export const ListTile = (props) => {
|
|
|
199
200
|
const dividerLine = WidgetFactory({
|
|
200
201
|
height: 1,
|
|
201
202
|
backgroundColor: colors.border,
|
|
202
|
-
marginLeft:
|
|
203
|
-
width: `calc(100% - ${paddingHorizontal}
|
|
203
|
+
marginLeft: toREM(paddingHorizontal),
|
|
204
|
+
width: `calc(100% - ${toREM(paddingHorizontal)})`,
|
|
204
205
|
});
|
|
205
206
|
|
|
206
207
|
const container = WidgetFactory({
|
package/src/widgets/ListView.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { WidgetFactory } from "../widget-factory/index.js";
|
|
6
6
|
import { injectKeyframes } from "../utils/styleInjector.js";
|
|
7
|
+
import { toREM, getBaseFontSize } from "../utils/units.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @typedef {Object} ListViewProps
|
|
@@ -11,8 +12,8 @@ import { injectKeyframes } from "../utils/styleInjector.js";
|
|
|
11
12
|
* @property {Function} renderItem - Called with `(item, index)` and must return an `HTMLElement`.
|
|
12
13
|
* @property {number|string} [height=400] - Height of the scroll container. Overridden when `expand` is true.
|
|
13
14
|
* @property {number|string} [width="100%"] - Width of the widget.
|
|
14
|
-
* @property {number} [itemSize=60] - Fixed height of each row in
|
|
15
|
-
* @property {number} [gap=0] - Vertical gap between rows in
|
|
15
|
+
* @property {number} [itemSize=60] - Fixed height of each row in framework units, i.e. it grows with the base font (required for virtualization math).
|
|
16
|
+
* @property {number} [gap=0] - Vertical gap between rows in framework units.
|
|
16
17
|
* @property {number} [bufferSize=5] - Number of extra rows to render above and below the visible viewport.
|
|
17
18
|
* Larger values reduce the chance of blank flashes during fast scrolling at the cost of more DOM nodes.
|
|
18
19
|
* @property {boolean} [showsScrollIndicator=true] - Whether to show the native scrollbar.
|
|
@@ -140,6 +141,23 @@ export const ListView = (props) => {
|
|
|
140
141
|
|
|
141
142
|
// ---- Helper functions ----
|
|
142
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Rendered pixels per framework unit, read from the live root font size.
|
|
146
|
+
*
|
|
147
|
+
* `itemSize` and `gap` are framework units, so the row they reserve grows with
|
|
148
|
+
* the reader's font like every other box. The virtualisation math compares
|
|
149
|
+
* against `scrollTop` and `clientHeight`, which are always CSS pixels, so the
|
|
150
|
+
* two spaces have to be joined here: a 60-unit row is 90px of scroll space for
|
|
151
|
+
* a reader on a 24px base, and rows written straight from the unit overlap.
|
|
152
|
+
*/
|
|
153
|
+
const unitPx = () => {
|
|
154
|
+
if (typeof document === "undefined") return 1;
|
|
155
|
+
const root = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
156
|
+
return root > 0 ? root / getBaseFontSize() : 1;
|
|
157
|
+
};
|
|
158
|
+
const slotHeight = () => (itemSize + gap) * unitPx();
|
|
159
|
+
const gapHeight = () => gap * unitPx();
|
|
160
|
+
|
|
143
161
|
/** @returns {number} Total number of rows (accounting for grid columns). */
|
|
144
162
|
const getTotalRows = () => Math.ceil(_data.length / cols);
|
|
145
163
|
|
|
@@ -159,7 +177,7 @@ export const ListView = (props) => {
|
|
|
159
177
|
*/
|
|
160
178
|
const getItemTop = (index) => {
|
|
161
179
|
const row = Math.floor(index / cols);
|
|
162
|
-
return row * (
|
|
180
|
+
return row * slotHeight();
|
|
163
181
|
};
|
|
164
182
|
|
|
165
183
|
/**
|
|
@@ -171,7 +189,7 @@ export const ListView = (props) => {
|
|
|
171
189
|
const getTotalHeight = () => {
|
|
172
190
|
const rows = getTotalRows();
|
|
173
191
|
if (rows === 0) return 0;
|
|
174
|
-
return Math.max(0, rows * (
|
|
192
|
+
return Math.max(0, rows * slotHeight() - gapHeight());
|
|
175
193
|
};
|
|
176
194
|
|
|
177
195
|
/** Applies the correct logical height to the inner container. */
|
|
@@ -190,7 +208,7 @@ export const ListView = (props) => {
|
|
|
190
208
|
if (!scrollContainer) return;
|
|
191
209
|
|
|
192
210
|
const viewportH = scrollContainer.clientHeight;
|
|
193
|
-
const itemTotalH =
|
|
211
|
+
const itemTotalH = slotHeight();
|
|
194
212
|
// Convert the scroll position into ITEM space: rows start below the header, so
|
|
195
213
|
// the first rows are still on screen while `scrollTop` is inside the header.
|
|
196
214
|
const itemsTop = Math.max(0, scrollContainer.scrollTop - getHeaderHeight());
|
|
@@ -228,7 +246,7 @@ export const ListView = (props) => {
|
|
|
228
246
|
console.error("Error in renderItem:", err);
|
|
229
247
|
itemEl = document.createElement("div");
|
|
230
248
|
itemEl.textContent = "Error";
|
|
231
|
-
itemEl.style.padding =
|
|
249
|
+
itemEl.style.padding = toREM(8);
|
|
232
250
|
}
|
|
233
251
|
}
|
|
234
252
|
if (!itemEl) continue;
|
|
@@ -240,12 +258,12 @@ export const ListView = (props) => {
|
|
|
240
258
|
const col = i % cols;
|
|
241
259
|
wrapper.style.left = `${(col / cols) * 100}%`;
|
|
242
260
|
wrapper.style.width = `${100 / cols}%`;
|
|
243
|
-
wrapper.style.padding = `0 ${gap / 2}
|
|
261
|
+
wrapper.style.padding = `0 ${toREM(gap / 2)}`;
|
|
244
262
|
wrapper.style.boxSizing = "border-box";
|
|
245
263
|
} else {
|
|
246
264
|
wrapper.style.left = "0";
|
|
247
265
|
wrapper.style.right = "0";
|
|
248
|
-
wrapper.style.padding = `0 ${gap}
|
|
266
|
+
wrapper.style.padding = `0 ${toREM(gap)}`;
|
|
249
267
|
}
|
|
250
268
|
wrapper.appendChild(itemEl);
|
|
251
269
|
fragment.appendChild(wrapper);
|
|
@@ -367,8 +385,8 @@ export const ListView = (props) => {
|
|
|
367
385
|
refreshIndicator.style.transition = "height 0.2s";
|
|
368
386
|
|
|
369
387
|
const spinner = document.createElement("div");
|
|
370
|
-
spinner.style.width =
|
|
371
|
-
spinner.style.height =
|
|
388
|
+
spinner.style.width = toREM(24);
|
|
389
|
+
spinner.style.height = toREM(24);
|
|
372
390
|
spinner.style.border = "2px solid #e0e0e0";
|
|
373
391
|
spinner.style.borderTop = "2px solid #007aff";
|
|
374
392
|
spinner.style.borderRadius = "50%";
|
|
@@ -401,7 +419,7 @@ export const ListView = (props) => {
|
|
|
401
419
|
if (h >= 60) {
|
|
402
420
|
// Threshold met — trigger the refresh
|
|
403
421
|
_refreshing = true;
|
|
404
|
-
refreshIndicator.style.height =
|
|
422
|
+
refreshIndicator.style.height = toREM(60);
|
|
405
423
|
onRefresh(() => {
|
|
406
424
|
_refreshing = false;
|
|
407
425
|
refreshIndicator.style.height = "0";
|
|
@@ -498,7 +516,7 @@ export const ListView = (props) => {
|
|
|
498
516
|
set: (val) => {
|
|
499
517
|
_refreshing = val;
|
|
500
518
|
if (refreshIndicator)
|
|
501
|
-
refreshIndicator.style.height = _refreshing ?
|
|
519
|
+
refreshIndicator.style.height = _refreshing ? toREM(60) : "0";
|
|
502
520
|
},
|
|
503
521
|
enumerable: true,
|
|
504
522
|
});
|