flet-box 1.0.6 → 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/README.md +28 -1
- package/bin/cli.js +90 -11
- package/bin/commands/auditFix.js +682 -0
- package/bin/commands/checkApi.js +20 -6
- package/bin/commands/checkUi.js +1369 -0
- package/bin/commands/create.js +86 -12
- package/bin/commands/createBundle.js +75 -1
- package/bin/commands/images.js +349 -0
- package/bin/commands/pruneAssets.js +170 -0
- package/bin/commands/release.js +34 -1
- package/bin/commands/runCloudflare.js +47 -9
- package/bin/commands/runServer.js +91 -25
- package/bin/commands/toPng.js +346 -0
- package/bin/commands/unpack.js +1173 -0
- package/bin/ota/loader.js +90 -0
- package/bin/utils/appSources.js +101 -0
- package/bin/utils/bundleConfig.js +166 -0
- package/bin/utils/dtsProps.js +147 -0
- package/bin/utils/imageTools.js +271 -0
- package/bin/utils/images/bmp.js +99 -0
- package/bin/utils/images/gif.js +244 -0
- package/bin/utils/images/index.js +163 -0
- package/bin/utils/images/jpeg.js +643 -0
- package/bin/utils/images/png.js +486 -0
- package/bin/utils/images/unsupported.js +29 -0
- package/bin/utils/neutralise.js +109 -6
- package/bin/utils/pwaShell.js +137 -0
- package/bin/utils/templates.js +1653 -471
- package/docs/README.md +2 -0
- package/docs/cli/README.md +144 -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 +105 -9
- 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 +4 -0
- package/docs/widget/AdMob.md +5 -1
- package/docs/widget/AdSense.md +4 -0
- package/docs/widget/AnimatedBox.md +7 -3
- package/docs/widget/AnimatedText.md +4 -0
- package/docs/widget/Audio.md +5 -1
- package/docs/widget/Avatar.md +15 -3
- package/docs/widget/Badge.md +4 -0
- package/docs/widget/Button.md +4 -0
- package/docs/widget/COMMON_PROPS.md +52 -0
- package/docs/widget/Card.md +4 -0
- package/docs/widget/Carousel.md +4 -0
- package/docs/widget/Chart.md +4 -0
- package/docs/widget/Checkbox.md +4 -0
- package/docs/widget/Chip.md +4 -0
- package/docs/widget/CircularBar.md +4 -0
- package/docs/widget/CircularChart.md +4 -0
- package/docs/widget/CodeViewer.md +4 -0
- package/docs/widget/Column.md +4 -0
- package/docs/widget/Container.md +4 -0
- package/docs/widget/DataTable.md +4 -0
- package/docs/widget/Divider.md +4 -0
- package/docs/widget/DraggBox.md +6 -2
- package/docs/widget/Dropdown.md +6 -0
- package/docs/widget/DroppBox.md +5 -1
- package/docs/widget/FloatingActionButton.md +4 -0
- package/docs/widget/GridView.md +4 -0
- package/docs/widget/Icon.md +4 -0
- package/docs/widget/Image.md +14 -3
- package/docs/widget/Input.md +4 -0
- package/docs/widget/Inspector.md +2 -2
- package/docs/widget/InstallButton.md +4 -0
- package/docs/widget/ListTile.md +4 -0
- package/docs/widget/ListView.md +4 -0
- package/docs/widget/Maps.md +191 -0
- package/docs/widget/Markdown.md +4 -0
- package/docs/widget/MatrixRain.md +4 -0
- package/docs/widget/Pagination.md +4 -0
- package/docs/widget/ParallaxBox.md +4 -0
- package/docs/widget/ProgressBar.md +4 -0
- package/docs/widget/QRCode.md +4 -0
- package/docs/widget/README.md +12 -10
- package/docs/widget/Radio.md +5 -1
- package/docs/widget/Rating.md +4 -0
- package/docs/widget/Row.md +4 -0
- package/docs/widget/Skeleton.md +4 -0
- package/docs/widget/Slider.md +4 -0
- package/docs/widget/Stack.md +4 -0
- package/docs/widget/Stepper.md +4 -0
- package/docs/widget/Switch.md +4 -0
- package/docs/widget/Tabs.md +5 -5
- package/docs/widget/Text.md +4 -0
- package/docs/widget/Tooltip.md +14 -13
- package/docs/widget/TreeView.md +4 -0
- package/docs/widget/Video.md +6 -2
- package/package.json +2 -2
- package/src/animations/AnimatedBox.js +4 -1
- package/src/animations/MatrixRain.js +3 -3
- package/src/core/hmr-client.js +129 -34
- package/src/index.d.ts +195 -7
- package/src/index.js +2 -0
- package/src/navigations/AppBar.js +22 -16
- package/src/navigations/BottomNavigation.js +8 -13
- package/src/navigations/CollapsibleSideBar.js +30 -8
- package/src/navigations/Drawer.js +25 -15
- 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 +60 -1
- package/src/widgets/Accordion.js +20 -23
- package/src/widgets/AdMob.js +7 -2
- package/src/widgets/Avatar.js +236 -133
- 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/Container.js +13 -0
- package/src/widgets/DataTable.js +17 -11
- package/src/widgets/DraggBox.js +2 -1
- package/src/widgets/Dropdown.js +59 -14
- package/src/widgets/DroppBox.js +4 -2
- package/src/widgets/FloatingActionButton.js +13 -7
- package/src/widgets/Icon.js +3 -1
- package/src/widgets/Image.js +102 -3
- 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/Maps.js +1495 -0
- 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 +24 -12
- package/src/widgets/TreeView.js +27 -15
- package/src/widgets/index.js +1 -0
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { WidgetFactory } from "../widget-factory/index.js";
|
|
6
6
|
import { colors } from "../utils/themes.js";
|
|
7
|
+
import { readableInk } from "../utils/readableInk.js";
|
|
8
|
+
|
|
9
|
+
import { toREM } from "../utils/units.js";
|
|
7
10
|
|
|
8
11
|
export const CircularChart = (props) => {
|
|
9
12
|
let {
|
|
@@ -21,7 +24,10 @@ export const CircularChart = (props) => {
|
|
|
21
24
|
centerContent = null,
|
|
22
25
|
showLabels = false,
|
|
23
26
|
labelSize = 12,
|
|
24
|
-
|
|
27
|
+
// Unset by default: every slice has its own fill, and the theme's ink is
|
|
28
|
+
// the same colour as the primary slice in both palettes, so the label has
|
|
29
|
+
// to be chosen against the slice it sits on (see `labelInk`).
|
|
30
|
+
labelColor = null,
|
|
25
31
|
onClick = null,
|
|
26
32
|
onHover = null,
|
|
27
33
|
defaultColors = [
|
|
@@ -42,7 +48,7 @@ export const CircularChart = (props) => {
|
|
|
42
48
|
borderColor = null,
|
|
43
49
|
borderWidth = 0,
|
|
44
50
|
sliceBorderWidth = 0,
|
|
45
|
-
sliceBorderColor =
|
|
51
|
+
sliceBorderColor = colors.card,
|
|
46
52
|
...rest
|
|
47
53
|
} = props;
|
|
48
54
|
|
|
@@ -117,10 +123,15 @@ export const CircularChart = (props) => {
|
|
|
117
123
|
const updateCanvasSize = () => {
|
|
118
124
|
if (!canvas) return;
|
|
119
125
|
if (isFixedSize) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
// The box is routed through rem, so `size: 160` is 240px wide for a reader
|
|
127
|
+
// on a 24px base font. The buffer has to follow the rendered box or the
|
|
128
|
+
// chart grows and turns soft; the numbers are the fallback for a node
|
|
129
|
+
// that has no layout yet.
|
|
130
|
+
const { width, height } = container.getBoundingClientRect();
|
|
131
|
+
canvas.width = width > 0 ? width : canvasWidth;
|
|
132
|
+
canvas.height = height > 0 ? height : canvasHeight;
|
|
133
|
+
currentWidth = canvas.width;
|
|
134
|
+
currentHeight = canvas.height;
|
|
124
135
|
} else {
|
|
125
136
|
const rect = container.getBoundingClientRect();
|
|
126
137
|
let w = rect.width;
|
|
@@ -192,6 +203,13 @@ export const CircularChart = (props) => {
|
|
|
192
203
|
return defaultColors[index % defaultColors.length];
|
|
193
204
|
};
|
|
194
205
|
|
|
206
|
+
const labelInk = (fill) => {
|
|
207
|
+
if (labelColor) return labelColor;
|
|
208
|
+
if (fill === colors.primary) return colors.onPrimary;
|
|
209
|
+
if (fill === colors.secondary) return colors.onSecondary;
|
|
210
|
+
return readableInk(fill, colors.text);
|
|
211
|
+
};
|
|
212
|
+
|
|
195
213
|
// Draw each segment with rounded ends (each one with its own color)
|
|
196
214
|
const drawRoundedSegment = (slice, index) => {
|
|
197
215
|
const r = getRadiusVal() / 2;
|
|
@@ -269,7 +287,7 @@ export const CircularChart = (props) => {
|
|
|
269
287
|
const x = cx + labelRadius * Math.cos(midAngle);
|
|
270
288
|
const y = cy + labelRadius * Math.sin(midAngle);
|
|
271
289
|
ctx.font = `${labelSize}px system-ui`;
|
|
272
|
-
ctx.fillStyle =
|
|
290
|
+
ctx.fillStyle = labelInk(color);
|
|
273
291
|
ctx.shadowBlur = 0;
|
|
274
292
|
const percent = Math.round(
|
|
275
293
|
(slice.value / (slice.end - slice.start)) * 100,
|
|
@@ -463,7 +481,7 @@ export const CircularChart = (props) => {
|
|
|
463
481
|
position: "relative",
|
|
464
482
|
overflow: "hidden",
|
|
465
483
|
borderRadius:
|
|
466
|
-
|
|
484
|
+
toREM(borderRadius),
|
|
467
485
|
border:
|
|
468
486
|
borderWidth > 0 && borderColor
|
|
469
487
|
? `${borderWidth}px solid ${borderColor}`
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
import { WidgetFactory } from "../widget-factory/index.js";
|
|
6
6
|
import { colors } from "../utils/themes.js";
|
|
7
7
|
import { composeUpdate } from "../utils/composeUpdate.js";
|
|
8
|
-
import { generateHighlightedHtml } from "../utils/syntaxHighlight.js";
|
|
8
|
+
import { generateHighlightedHtml, syntaxPaletteFor } from "../utils/syntaxHighlight.js";
|
|
9
|
+
import { clipboard } from "../tools/clipboard.js";
|
|
10
|
+
|
|
11
|
+
import { toREM } from "../utils/units.js";
|
|
9
12
|
|
|
10
13
|
export const CodeViewer = (props) => {
|
|
11
14
|
const {
|
|
@@ -20,22 +23,32 @@ code = "",
|
|
|
20
23
|
showLineNumbers = false,
|
|
21
24
|
startingLineNumber = 1,
|
|
22
25
|
lineNumberWidth = 40,
|
|
23
|
-
lineNumberColor
|
|
26
|
+
lineNumberColor,
|
|
24
27
|
...rest
|
|
25
28
|
} = props;
|
|
26
29
|
|
|
30
|
+
// The token map follows the block's own surface: the light theme's #F4F4F5
|
|
31
|
+
// pane needs the light map, and a caller that paints a dark pane on the light
|
|
32
|
+
// theme still gets spans that read.
|
|
33
|
+
const syntaxPalette = syntaxPaletteFor(backgroundColor);
|
|
34
|
+
// The gutter is not a token: it takes the muted ink of the pane's palette, so
|
|
35
|
+
// a custom pane does not get the page theme's grey on top of it.
|
|
36
|
+
const gutterInk = lineNumberColor || syntaxPalette.muted;
|
|
37
|
+
|
|
27
38
|
let currentCode =
|
|
28
39
|
typeof code === "string" ? code : JSON.stringify(code, null, 2);
|
|
29
40
|
let currentTitle = title;
|
|
30
41
|
let headerRef = null;
|
|
42
|
+
let headerTitleRef = null;
|
|
31
43
|
let scrollWrapperRef = null;
|
|
32
44
|
let lineNumbersColRef = null;
|
|
33
45
|
let codeColRef = null;
|
|
34
46
|
let codeWrapperRef = null;
|
|
47
|
+
let copyResetTimer = null;
|
|
35
48
|
|
|
36
49
|
const refreshContent = () => {
|
|
37
50
|
const lines = currentCode.split("\n");
|
|
38
|
-
const highlightedLines = lines.map((line) => generateHighlightedHtml(line));
|
|
51
|
+
const highlightedLines = lines.map((line) => generateHighlightedHtml(line, syntaxPalette));
|
|
39
52
|
|
|
40
53
|
if (showLineNumbers) {
|
|
41
54
|
// Update line numbers
|
|
@@ -44,7 +57,7 @@ code = "",
|
|
|
44
57
|
for (let i = 0; i < lines.length; i++) {
|
|
45
58
|
const numberDiv = WidgetFactory({
|
|
46
59
|
tag: "div",
|
|
47
|
-
padding: `0 ${padding / 2}
|
|
60
|
+
padding: `0 ${toREM(padding / 2)} 0 ${toREM(padding)}`,
|
|
48
61
|
whiteSpace: "pre",
|
|
49
62
|
textContent: String(startingLineNumber + i),
|
|
50
63
|
});
|
|
@@ -79,28 +92,86 @@ code = "",
|
|
|
79
92
|
width: "100%",
|
|
80
93
|
display: "flex",
|
|
81
94
|
flexDirection: "column",
|
|
95
|
+
position: "relative",
|
|
82
96
|
...rest,
|
|
83
97
|
});
|
|
84
98
|
|
|
99
|
+
// The copy button carries its own solid plate, so it reads on the primary
|
|
100
|
+
// header and on any caller-painted pane, and it never relies on the page ink.
|
|
101
|
+
const copyButtonRef = WidgetFactory({
|
|
102
|
+
tag: "button",
|
|
103
|
+
type: "button",
|
|
104
|
+
textContent: "Copy",
|
|
105
|
+
backgroundColor: colors.card,
|
|
106
|
+
color: colors.text,
|
|
107
|
+
border: `1px solid ${colors.border}`,
|
|
108
|
+
borderRadius: 6,
|
|
109
|
+
padding: "2px 8px",
|
|
110
|
+
fontFamily: "monospace",
|
|
111
|
+
fontSize: 11,
|
|
112
|
+
lineHeight: "1.6",
|
|
113
|
+
flexShrink: 0,
|
|
114
|
+
cursor: "pointer",
|
|
115
|
+
onclick: () => {
|
|
116
|
+
clipboard.copy(currentCode);
|
|
117
|
+
copyButtonRef.textContent = "Copied";
|
|
118
|
+
clearTimeout(copyResetTimer);
|
|
119
|
+
copyResetTimer = setTimeout(() => {
|
|
120
|
+
copyButtonRef.textContent = "Copy";
|
|
121
|
+
}, 1500);
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Inside the header when there is one; floating over the block otherwise.
|
|
126
|
+
const attachCopyButton = () => {
|
|
127
|
+
if (headerRef) {
|
|
128
|
+
copyButtonRef.style.position = "";
|
|
129
|
+
copyButtonRef.style.top = "";
|
|
130
|
+
copyButtonRef.style.right = "";
|
|
131
|
+
copyButtonRef.style.zIndex = "";
|
|
132
|
+
headerRef.appendChild(copyButtonRef);
|
|
133
|
+
} else {
|
|
134
|
+
copyButtonRef.style.position = "absolute";
|
|
135
|
+
copyButtonRef.style.top = toREM(8);
|
|
136
|
+
copyButtonRef.style.right = toREM(8);
|
|
137
|
+
copyButtonRef.style.zIndex = "3";
|
|
138
|
+
container.appendChild(copyButtonRef);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
85
142
|
// Header. Built through a helper so a later `title` update can create one on a
|
|
86
143
|
// widget that was constructed without it.
|
|
87
144
|
const ensureHeader = (text) => {
|
|
88
145
|
if (!showHeader || !text) return null;
|
|
89
146
|
if (headerRef) {
|
|
90
|
-
|
|
147
|
+
headerTitleRef.textContent = text;
|
|
91
148
|
return headerRef;
|
|
92
149
|
}
|
|
93
150
|
headerRef = WidgetFactory({
|
|
94
151
|
tag: "div",
|
|
152
|
+
display: "flex",
|
|
153
|
+
alignItems: "center",
|
|
154
|
+
justifyContent: "space-between",
|
|
155
|
+
gap: 8,
|
|
95
156
|
padding: "8px 12px",
|
|
96
157
|
backgroundColor: colors.primary,
|
|
97
|
-
borderRadius: `${borderRadius}
|
|
98
|
-
|
|
158
|
+
borderRadius: `${toREM(borderRadius)} ${toREM(borderRadius)} 0 0`,
|
|
159
|
+
// The header is a primary plate; in dark mode that plate is near-white,
|
|
160
|
+
// so a literal white title disappeared into it.
|
|
161
|
+
color: colors.onPrimary,
|
|
99
162
|
fontSize: "14px",
|
|
100
163
|
fontWeight: "bold",
|
|
164
|
+
});
|
|
165
|
+
headerTitleRef = WidgetFactory({
|
|
166
|
+
tag: "span",
|
|
101
167
|
textContent: text,
|
|
168
|
+
overflow: "hidden",
|
|
169
|
+
textOverflow: "ellipsis",
|
|
170
|
+
whiteSpace: "nowrap",
|
|
102
171
|
});
|
|
172
|
+
headerRef.appendChild(headerTitleRef);
|
|
103
173
|
container.insertBefore(headerRef, container.firstChild);
|
|
174
|
+
attachCopyButton();
|
|
104
175
|
return headerRef;
|
|
105
176
|
};
|
|
106
177
|
ensureHeader(currentTitle);
|
|
@@ -110,10 +181,10 @@ code = "",
|
|
|
110
181
|
tag: "div",
|
|
111
182
|
backgroundColor,
|
|
112
183
|
borderRadius: currentTitle
|
|
113
|
-
? `0 0 ${borderRadius}
|
|
114
|
-
:
|
|
184
|
+
? `0 0 ${toREM(borderRadius)} ${toREM(borderRadius)}`
|
|
185
|
+
: toREM(borderRadius),
|
|
115
186
|
overflow: "auto",
|
|
116
|
-
maxHeight:
|
|
187
|
+
maxHeight: toREM(maxHeight),
|
|
117
188
|
overflowX: "auto",
|
|
118
189
|
overflowY: "auto",
|
|
119
190
|
});
|
|
@@ -132,22 +203,22 @@ code = "",
|
|
|
132
203
|
tag: "div",
|
|
133
204
|
backgroundColor,
|
|
134
205
|
borderRight: `1px solid ${colors.border}`,
|
|
135
|
-
padding: `${padding}
|
|
206
|
+
padding: `${toREM(padding)} 0`,
|
|
136
207
|
fontFamily: "monospace",
|
|
137
|
-
fontSize:
|
|
208
|
+
fontSize: toREM(fontSize),
|
|
138
209
|
lineHeight: "1.5",
|
|
139
210
|
textAlign: "right",
|
|
140
|
-
color:
|
|
211
|
+
color: gutterInk,
|
|
141
212
|
userSelect: "none",
|
|
142
|
-
width:
|
|
213
|
+
width: toREM(lineNumberWidth),
|
|
143
214
|
flexShrink: 0,
|
|
144
215
|
});
|
|
145
216
|
|
|
146
217
|
codeColRef = WidgetFactory({
|
|
147
218
|
tag: "div",
|
|
148
|
-
padding:
|
|
219
|
+
padding: toREM(padding),
|
|
149
220
|
fontFamily: "monospace",
|
|
150
|
-
fontSize:
|
|
221
|
+
fontSize: toREM(fontSize),
|
|
151
222
|
lineHeight: "1.5",
|
|
152
223
|
whiteSpace: "pre",
|
|
153
224
|
flex: 1,
|
|
@@ -170,9 +241,9 @@ code = "",
|
|
|
170
241
|
} else {
|
|
171
242
|
codeWrapperRef = WidgetFactory({
|
|
172
243
|
tag: "div",
|
|
173
|
-
padding:
|
|
244
|
+
padding: toREM(padding),
|
|
174
245
|
fontFamily: "monospace",
|
|
175
|
-
fontSize:
|
|
246
|
+
fontSize: toREM(fontSize),
|
|
176
247
|
whiteSpace: "pre",
|
|
177
248
|
lineHeight: "1.5",
|
|
178
249
|
});
|
|
@@ -180,16 +251,18 @@ code = "",
|
|
|
180
251
|
}
|
|
181
252
|
|
|
182
253
|
container.appendChild(scrollWrapperRef);
|
|
254
|
+
if (!headerRef) attachCopyButton();
|
|
255
|
+
container.onUnmount(() => clearTimeout(copyResetTimer));
|
|
183
256
|
|
|
184
257
|
// Fill initial content
|
|
185
258
|
const lines = currentCode.split("\n");
|
|
186
|
-
const highlightedLines = lines.map((line) => generateHighlightedHtml(line));
|
|
259
|
+
const highlightedLines = lines.map((line) => generateHighlightedHtml(line, syntaxPalette));
|
|
187
260
|
|
|
188
261
|
if (showLineNumbers) {
|
|
189
262
|
for (let i = 0; i < lines.length; i++) {
|
|
190
263
|
const numberDiv = WidgetFactory({
|
|
191
264
|
tag: "div",
|
|
192
|
-
padding: `0 ${padding / 2}
|
|
265
|
+
padding: `0 ${toREM(padding / 2)} 0 ${toREM(padding)}`,
|
|
193
266
|
whiteSpace: "pre",
|
|
194
267
|
textContent: String(startingLineNumber + i),
|
|
195
268
|
});
|
package/src/widgets/Container.js
CHANGED
|
@@ -13,9 +13,21 @@ export const Container = (props = {}) => {
|
|
|
13
13
|
overflow = "auto",
|
|
14
14
|
boxSizing = "border-box",
|
|
15
15
|
bgColor = colors.surface,
|
|
16
|
+
flex,
|
|
17
|
+
flexGrow,
|
|
18
|
+
flexShrink,
|
|
16
19
|
...rest
|
|
17
20
|
} = props;
|
|
18
21
|
|
|
22
|
+
// Container paints `overflow: auto`, and a flex item whose overflow is not
|
|
23
|
+
// `visible` has an automatic minimum size of zero — so by default it shrank
|
|
24
|
+
// to nothing inside a column that overflows. Opting into the flex shorthand
|
|
25
|
+
// still wins, which is how a stretchable body keeps working.
|
|
26
|
+
const flexDefaults =
|
|
27
|
+
flex === undefined && flexGrow === undefined && flexShrink === undefined
|
|
28
|
+
? { flexShrink: 0 }
|
|
29
|
+
: { flex, flexGrow, flexShrink };
|
|
30
|
+
|
|
19
31
|
return WidgetFactory({
|
|
20
32
|
tag: "div",
|
|
21
33
|
widgetName: "Container",
|
|
@@ -24,6 +36,7 @@ export const Container = (props = {}) => {
|
|
|
24
36
|
overflow: overflow,
|
|
25
37
|
boxSizing: boxSizing,
|
|
26
38
|
backgroundColor: bgColor,
|
|
39
|
+
...flexDefaults,
|
|
27
40
|
...rest,
|
|
28
41
|
});
|
|
29
42
|
};
|
package/src/widgets/DataTable.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 DataTable = (props) => {
|
|
9
11
|
const {
|
|
10
12
|
columns,
|
|
@@ -22,7 +24,7 @@ export const DataTable = (props) => {
|
|
|
22
24
|
|
|
23
25
|
rowBgColor = "transparent",
|
|
24
26
|
rowTextColor = colors.text,
|
|
25
|
-
rowFontSize =
|
|
27
|
+
rowFontSize = 14,
|
|
26
28
|
|
|
27
29
|
stripedRowBgColor = colors.gray50,
|
|
28
30
|
|
|
@@ -31,8 +33,12 @@ export const DataTable = (props) => {
|
|
|
31
33
|
borderColor = colors.border,
|
|
32
34
|
borderWidth = 1,
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
// A padding the caller can hand as `8`, `"8px 12px"` or `"1rem"`: every
|
|
37
|
+
// write below goes through toREM, which scales the first two and leaves the
|
|
38
|
+
// third alone. Written straight to `style`, a bare number is an invalid CSS
|
|
39
|
+
// value the browser drops, and a string is a frozen pixel.
|
|
40
|
+
cellPadding = "8px 12px",
|
|
41
|
+
headerCellPadding = 12,
|
|
36
42
|
|
|
37
43
|
// ========== OTHER PROPS ==========
|
|
38
44
|
...rest
|
|
@@ -61,7 +67,7 @@ export const DataTable = (props) => {
|
|
|
61
67
|
for (const col of columns) {
|
|
62
68
|
const th = document.createElement("th");
|
|
63
69
|
th.textContent = typeof col === "string" ? col : col.label;
|
|
64
|
-
th.style.padding = headerCellPadding;
|
|
70
|
+
th.style.padding = toREM(headerCellPadding);
|
|
65
71
|
th.style.textAlign = "left";
|
|
66
72
|
th.style.borderBottom = `2px solid ${borderColor}`;
|
|
67
73
|
th.style.backgroundColor = headerBgColor;
|
|
@@ -69,7 +75,7 @@ export const DataTable = (props) => {
|
|
|
69
75
|
th.style.fontWeight = headerFontWeight;
|
|
70
76
|
th.style.fontSize =
|
|
71
77
|
typeof headerFontSize === "number"
|
|
72
|
-
?
|
|
78
|
+
? toREM(headerFontSize)
|
|
73
79
|
: headerFontSize;
|
|
74
80
|
|
|
75
81
|
// Custom alignment per column
|
|
@@ -122,11 +128,11 @@ export const DataTable = (props) => {
|
|
|
122
128
|
const value = row[key];
|
|
123
129
|
|
|
124
130
|
td.textContent = value !== undefined ? String(value) : "";
|
|
125
|
-
td.style.padding = cellPadding;
|
|
131
|
+
td.style.padding = toREM(cellPadding);
|
|
126
132
|
td.style.borderBottom = `${borderWidth}px solid ${borderColor}`;
|
|
127
133
|
td.style.color = rowTextColor;
|
|
128
134
|
td.style.fontSize =
|
|
129
|
-
|
|
135
|
+
toREM(rowFontSize);
|
|
130
136
|
|
|
131
137
|
// Custom alignment per column
|
|
132
138
|
if (typeof col !== "string" && col.align) {
|
|
@@ -195,11 +201,11 @@ export const DataTable = (props) => {
|
|
|
195
201
|
const value = row[key];
|
|
196
202
|
|
|
197
203
|
td.textContent = value !== undefined ? String(value) : "";
|
|
198
|
-
td.style.padding = cellPadding;
|
|
204
|
+
td.style.padding = toREM(cellPadding);
|
|
199
205
|
td.style.borderBottom = `${borderWidth}px solid ${borderColor}`;
|
|
200
206
|
td.style.color = rowTextColor;
|
|
201
207
|
td.style.fontSize =
|
|
202
|
-
|
|
208
|
+
toREM(rowFontSize);
|
|
203
209
|
|
|
204
210
|
if (typeof col !== "string" && col.align) {
|
|
205
211
|
td.style.textAlign = col.align;
|
|
@@ -236,7 +242,7 @@ export const DataTable = (props) => {
|
|
|
236
242
|
for (const col of newColumns) {
|
|
237
243
|
const th = document.createElement("th");
|
|
238
244
|
th.textContent = typeof col === "string" ? col : col.label;
|
|
239
|
-
th.style.padding = headerCellPadding;
|
|
245
|
+
th.style.padding = toREM(headerCellPadding);
|
|
240
246
|
th.style.textAlign = "left";
|
|
241
247
|
th.style.borderBottom = `2px solid ${borderColor}`;
|
|
242
248
|
th.style.backgroundColor = headerBgColor;
|
|
@@ -244,7 +250,7 @@ export const DataTable = (props) => {
|
|
|
244
250
|
th.style.fontWeight = headerFontWeight;
|
|
245
251
|
th.style.fontSize =
|
|
246
252
|
typeof headerFontSize === "number"
|
|
247
|
-
?
|
|
253
|
+
? toREM(headerFontSize)
|
|
248
254
|
: headerFontSize;
|
|
249
255
|
|
|
250
256
|
if (typeof col !== "string" && col.align) {
|
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
|
@@ -5,11 +5,15 @@
|
|
|
5
5
|
import { WidgetFactory } from "../widget-factory/index.js";
|
|
6
6
|
import { colors } from "../utils/themes.js";
|
|
7
7
|
import { composeUpdate } from "../utils/composeUpdate.js";
|
|
8
|
+
import { isReactive, reactiveKeyOf, resolveReactive } from "../widget-factory/reactiveValue.js";
|
|
9
|
+
import { registerWidget } from "../tools/useState.js";
|
|
8
10
|
import { Container } from "./Container.js";
|
|
9
11
|
import { Row } from "./Row.js";
|
|
10
12
|
import { Column } from "./Column.js";
|
|
11
13
|
import { Text } from "./Text.js";
|
|
12
14
|
import { Icon } from "./Icon.js";
|
|
15
|
+
import { toREM } from "../utils/units.js";
|
|
16
|
+
import { TAP_MINIMUM } from "../tools/index.js";
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
19
|
* Props owned by the Dropdown's closure state. Routed to the widget's `update`
|
|
@@ -22,7 +26,32 @@ const DROPDOWN_PROP_KEYS = [
|
|
|
22
26
|
"width", "portal",
|
|
23
27
|
];
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The owned keys `update()` can actually apply, so they are also the ones worth
|
|
31
|
+
* subscribing. A callback is never bound: `onChange` has no branch in `update()`.
|
|
32
|
+
*/
|
|
33
|
+
const BINDABLE_KEYS = [
|
|
34
|
+
"options", "value", "placeholder", "disabled", "label", "error", "variant",
|
|
35
|
+
"size", "borderRadius", "color", "bgColor", "textColor", "optionHoverColor",
|
|
36
|
+
"clearable",
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export const Dropdown = (inputProps = {}) => {
|
|
40
|
+
// The factory never receives these keys, so it can neither resolve a `useState`
|
|
41
|
+
// value for them (a live reactive is a *function*, and `findSelected()` would
|
|
42
|
+
// compare it against the option list and never match) nor subscribe this widget
|
|
43
|
+
// to the key. Both happen here instead: read once, keep the keys, bind after
|
|
44
|
+
// `composeUpdate` has routed a later patch into `update()`.
|
|
45
|
+
const boundKeys = new Map();
|
|
46
|
+
const props = { ...inputProps };
|
|
47
|
+
for (const key of BINDABLE_KEYS) {
|
|
48
|
+
const value = inputProps[key];
|
|
49
|
+
if (!isReactive(value)) continue;
|
|
50
|
+
props[key] = resolveReactive(value);
|
|
51
|
+
const stateKey = reactiveKeyOf(value);
|
|
52
|
+
if (stateKey) boundKeys.set(key, stateKey);
|
|
53
|
+
}
|
|
54
|
+
|
|
26
55
|
const {
|
|
27
56
|
options = [],
|
|
28
57
|
value = null,
|
|
@@ -70,10 +99,12 @@ export const Dropdown = (props) => {
|
|
|
70
99
|
let resizeListener = null;
|
|
71
100
|
let scrollListener = null;
|
|
72
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. */
|
|
73
104
|
const sizes = {
|
|
74
|
-
small: { padding:
|
|
75
|
-
medium: { padding:
|
|
76
|
-
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 },
|
|
77
108
|
};
|
|
78
109
|
let currentSize = sizes[size] || sizes.medium;
|
|
79
110
|
|
|
@@ -170,14 +201,16 @@ export const Dropdown = (props) => {
|
|
|
170
201
|
alignItems: "center",
|
|
171
202
|
justifyContent: "space-between",
|
|
172
203
|
width: "100%",
|
|
173
|
-
|
|
204
|
+
paddingVertical: currentSize.padding[0],
|
|
205
|
+
paddingHorizontal: currentSize.padding[1],
|
|
206
|
+
minHeight: currentSize.minHeight,
|
|
174
207
|
backgroundColor:
|
|
175
208
|
currentVariant === "filled" ? `${currentBgColor}CC` : "transparent",
|
|
176
209
|
border:
|
|
177
210
|
currentVariant === "outlined"
|
|
178
211
|
? `1px solid ${currentError ? colors.danger : colors.border}`
|
|
179
212
|
: "none",
|
|
180
|
-
borderRadius:
|
|
213
|
+
borderRadius: currentBorderRadius,
|
|
181
214
|
cursor: currentDisabled ? "not-allowed" : "pointer",
|
|
182
215
|
opacity: currentDisabled ? 0.5 : 1,
|
|
183
216
|
boxSizing: "border-box",
|
|
@@ -209,11 +242,15 @@ export const Dropdown = (props) => {
|
|
|
209
242
|
],
|
|
210
243
|
});
|
|
211
244
|
|
|
212
|
-
//
|
|
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.
|
|
213
249
|
const container = Container({
|
|
214
250
|
widgetName: "Dropdown",
|
|
215
251
|
position: "relative",
|
|
216
252
|
width: width,
|
|
253
|
+
bgColor: "transparent",
|
|
217
254
|
...rest,
|
|
218
255
|
});
|
|
219
256
|
|
|
@@ -221,9 +258,9 @@ export const Dropdown = (props) => {
|
|
|
221
258
|
const labelEl = WidgetFactory({
|
|
222
259
|
tag: "label",
|
|
223
260
|
textContent: currentLabel,
|
|
224
|
-
fontSize:
|
|
261
|
+
fontSize: 12,
|
|
225
262
|
color: currentError ? colors.danger : colors.textSecondary,
|
|
226
|
-
marginBottom:
|
|
263
|
+
marginBottom: 4,
|
|
227
264
|
display: "block",
|
|
228
265
|
});
|
|
229
266
|
container.appendChild(labelEl);
|
|
@@ -291,7 +328,7 @@ export const Dropdown = (props) => {
|
|
|
291
328
|
zIndex: 10000,
|
|
292
329
|
border: `1px solid ${colors.border}`,
|
|
293
330
|
backgroundColor: currentBgColor,
|
|
294
|
-
borderRadius:
|
|
331
|
+
borderRadius: currentBorderRadius, // same borderRadius
|
|
295
332
|
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
|
296
333
|
maxHeight: "250px",
|
|
297
334
|
overflowY: "auto",
|
|
@@ -321,12 +358,14 @@ export const Dropdown = (props) => {
|
|
|
321
358
|
);
|
|
322
359
|
|
|
323
360
|
const optionItem = Row({
|
|
324
|
-
|
|
361
|
+
paddingVertical: 8,
|
|
362
|
+
paddingHorizontal: 12,
|
|
363
|
+
minHeight: TAP_MINIMUM,
|
|
325
364
|
cursor: "pointer",
|
|
326
365
|
backgroundColor: isSelected ? currentColor + "15" : "transparent",
|
|
327
366
|
alignItems: "center",
|
|
328
367
|
justifyContent: "space-between",
|
|
329
|
-
gap:
|
|
368
|
+
gap: 8,
|
|
330
369
|
children: [
|
|
331
370
|
Row({
|
|
332
371
|
alignItems: "center",
|
|
@@ -469,8 +508,9 @@ export const Dropdown = (props) => {
|
|
|
469
508
|
}
|
|
470
509
|
if (needsDisplayUpdate) {
|
|
471
510
|
updateDisplay();
|
|
472
|
-
selector.style.padding = currentSize.padding
|
|
473
|
-
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);
|
|
474
514
|
if (currentVariant === "outlined")
|
|
475
515
|
selector.style.border = `1px solid ${currentError ? colors.danger : colors.border}`;
|
|
476
516
|
else selector.style.border = "none";
|
|
@@ -499,6 +539,11 @@ export const Dropdown = (props) => {
|
|
|
499
539
|
container.close = closeMenu;
|
|
500
540
|
composeUpdate(container, DROPDOWN_PROP_KEYS, update);
|
|
501
541
|
|
|
542
|
+
// Bind after composing: `registerWidget` pushes the current store value through
|
|
543
|
+
// `update`, which must be the composed one that routes owned keys into this
|
|
544
|
+
// widget's closure. It also releases the subscription on unmount by itself.
|
|
545
|
+
for (const [propName, stateKey] of boundKeys) registerWidget(stateKey, container, propName);
|
|
546
|
+
|
|
502
547
|
container.onUnmount(() => {
|
|
503
548
|
closeMenu();
|
|
504
549
|
});
|
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],
|