zabi-components 8.1.0 → 9.0.0
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/CHANGELOG.md +1011 -0
- package/README.md +1 -1
- package/THEME.md +18 -5
- package/THEMING.md +308 -74
- package/dist/atoms/ActionPanel.svelte +1 -1
- package/dist/atoms/Avatar.svelte +19 -3
- package/dist/atoms/Avatar.svelte.d.ts +12 -0
- package/dist/atoms/Badge.svelte +37 -8
- package/dist/atoms/Badge.svelte.d.ts +6 -0
- package/dist/atoms/Block.svelte +74 -0
- package/dist/atoms/Block.svelte.d.ts +29 -0
- package/dist/atoms/Button.svelte +52 -15
- package/dist/atoms/Button.svelte.d.ts +3 -2
- package/dist/atoms/Card.svelte +181 -25
- package/dist/atoms/Card.svelte.d.ts +55 -3
- package/dist/atoms/Checkbox.svelte +33 -0
- package/dist/atoms/Checkbox.svelte.d.ts +27 -0
- package/dist/atoms/Chip.svelte +351 -0
- package/dist/atoms/Chip.svelte.d.ts +57 -0
- package/dist/atoms/CodeField.svelte +385 -0
- package/dist/atoms/CodeField.svelte.d.ts +61 -0
- package/dist/atoms/ColorPicker.svelte +1 -0
- package/dist/atoms/DateField.svelte +41 -3
- package/dist/atoms/DateField.svelte.d.ts +31 -3
- package/dist/atoms/FloatingActionButton.svelte +5 -3
- package/dist/atoms/FloatingActionButton.svelte.d.ts +1 -1
- package/dist/atoms/Heading.svelte +27 -1
- package/dist/atoms/Heading.svelte.d.ts +7 -0
- package/dist/atoms/IconButton.svelte +81 -9
- package/dist/atoms/IconButton.svelte.d.ts +13 -0
- package/dist/atoms/Input.svelte +10 -5
- package/dist/atoms/Input.svelte.d.ts +3 -2
- package/dist/atoms/List.svelte +3 -2
- package/dist/atoms/ListItem.svelte +31 -7
- package/dist/atoms/ListItem.svelte.d.ts +6 -0
- package/dist/atoms/PickerField.svelte +239 -0
- package/dist/atoms/PickerField.svelte.d.ts +67 -0
- package/dist/atoms/Progress.svelte +78 -17
- package/dist/atoms/Progress.svelte.d.ts +11 -1
- package/dist/atoms/Radio.svelte +32 -0
- package/dist/atoms/Radio.svelte.d.ts +26 -0
- package/dist/atoms/Rating.svelte +132 -6
- package/dist/atoms/Rating.svelte.d.ts +24 -3
- package/dist/atoms/Select.svelte +24 -26
- package/dist/atoms/Select.svelte.d.ts +12 -0
- package/dist/atoms/SelectionControl.svelte +99 -6
- package/dist/atoms/SelectionControl.svelte.d.ts +25 -0
- package/dist/atoms/Skeleton.svelte +114 -18
- package/dist/atoms/Skeleton.svelte.d.ts +30 -3
- package/dist/atoms/SkeletonGroup.svelte +43 -0
- package/dist/atoms/SkeletonGroup.svelte.d.ts +20 -0
- package/dist/atoms/Slider.svelte +35 -0
- package/dist/atoms/Sparkline.svelte +109 -0
- package/dist/atoms/Sparkline.svelte.d.ts +27 -0
- package/dist/atoms/SpeechBubble.svelte +187 -0
- package/dist/atoms/SpeechBubble.svelte.d.ts +33 -0
- package/dist/atoms/Stat.svelte +72 -0
- package/dist/atoms/Stat.svelte.d.ts +19 -0
- package/dist/atoms/TemporalField.svelte +342 -10
- package/dist/atoms/TemporalField.svelte.d.ts +20 -4
- package/dist/atoms/Textarea.svelte +100 -5
- package/dist/atoms/Textarea.svelte.d.ts +21 -0
- package/dist/atoms/TimeColumn.svelte +150 -0
- package/dist/atoms/TimeColumn.svelte.d.ts +25 -0
- package/dist/atoms/TimeField.svelte +46 -3
- package/dist/atoms/TimeField.svelte.d.ts +34 -3
- package/dist/atoms/Toast.svelte +6 -5
- package/dist/atoms/Toggle.svelte +61 -10
- package/dist/atoms/Toggle.svelte.d.ts +15 -0
- package/dist/atoms/Tooltip.svelte +20 -1
- package/dist/atoms/Tooltip.svelte.d.ts +7 -0
- package/dist/atoms/index.d.ts +21 -0
- package/dist/atoms/index.js +17 -0
- package/dist/atoms/selection-control.styles.d.ts +15 -4
- package/dist/atoms/selection-control.styles.js +16 -5
- package/dist/components/atoms/index.d.ts +21 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/atoms/selection-control.styles.d.ts +15 -4
- package/dist/components/atoms/selection-control.styles.d.ts.map +1 -1
- package/dist/components/index.d.ts +27 -3
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/map.d.ts +11 -0
- package/dist/components/map.d.ts.map +1 -0
- package/dist/components/molecules/index.d.ts +7 -2
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/toast-store.d.ts +30 -0
- package/dist/components/molecules/toast-store.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts +6 -1
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/types/variants.d.ts +12 -3
- package/dist/components/types/variants.d.ts.map +1 -1
- package/dist/components/util/app-shell.d.ts +55 -4
- package/dist/components/util/app-shell.d.ts.map +1 -1
- package/dist/components/util/avatar.d.ts +3 -0
- package/dist/components/util/avatar.d.ts.map +1 -1
- package/dist/components/util/bottom-tab-bar.d.ts +6 -0
- package/dist/components/util/bottom-tab-bar.d.ts.map +1 -1
- package/dist/components/util/chart.d.ts +140 -0
- package/dist/components/util/chart.d.ts.map +1 -0
- package/dist/components/util/chip.d.ts +66 -0
- package/dist/components/util/chip.d.ts.map +1 -0
- package/dist/components/util/cn.d.ts.map +1 -1
- package/dist/components/util/code-field.d.ts +63 -0
- package/dist/components/util/code-field.d.ts.map +1 -0
- package/dist/components/util/collapsible.d.ts +6 -0
- package/dist/components/util/collapsible.d.ts.map +1 -1
- package/dist/components/util/display-voice.d.ts +10 -0
- package/dist/components/util/display-voice.d.ts.map +1 -0
- package/dist/components/util/field-control.d.ts +30 -0
- package/dist/components/util/field-control.d.ts.map +1 -0
- package/dist/components/util/field.d.ts +6 -0
- package/dist/components/util/field.d.ts.map +1 -1
- package/dist/components/util/focus-utils.d.ts +2 -0
- package/dist/components/util/focus-utils.d.ts.map +1 -1
- package/dist/components/util/map-view.d.ts +144 -0
- package/dist/components/util/map-view.d.ts.map +1 -0
- package/dist/components/util/photo.d.ts +19 -0
- package/dist/components/util/photo.d.ts.map +1 -1
- package/dist/components/util/progress.d.ts +19 -0
- package/dist/components/util/progress.d.ts.map +1 -0
- package/dist/components/util/scroll-edge.d.ts +33 -0
- package/dist/components/util/scroll-edge.d.ts.map +1 -0
- package/dist/components/util/sheet-drag.d.ts +25 -1
- package/dist/components/util/sheet-drag.d.ts.map +1 -1
- package/dist/components/util/skeleton.d.ts +15 -0
- package/dist/components/util/skeleton.d.ts.map +1 -0
- package/dist/components/util/sliding-indicator.d.ts +25 -0
- package/dist/components/util/sliding-indicator.d.ts.map +1 -0
- package/dist/components/util/temporal-field.d.ts +89 -0
- package/dist/components/util/temporal-field.d.ts.map +1 -0
- package/dist/components/util/toaster.d.ts +40 -0
- package/dist/components/util/toaster.d.ts.map +1 -1
- package/dist/components/util/tone-surface.d.ts +34 -0
- package/dist/components/util/tone-surface.d.ts.map +1 -0
- package/dist/components/util/top-navbar.d.ts +26 -2
- package/dist/components/util/top-navbar.d.ts.map +1 -1
- package/dist/components/util/zabi-strings.d.ts +9 -0
- package/dist/components/util/zabi-strings.d.ts.map +1 -1
- package/dist/create-theme/cli.js +6 -2
- package/dist/create-theme/index.d.ts +17 -3
- package/dist/create-theme/index.js +252 -35
- package/dist/create-theme/lib/resolve.js +97 -0
- package/dist/create-theme/theme-data.js +1618 -90
- package/dist/index.d.ts +27 -3
- package/dist/index.js +18 -0
- package/dist/map.d.ts +10 -0
- package/dist/map.js +9 -0
- package/dist/molecules/Alert.svelte +31 -12
- package/dist/molecules/Alert.svelte.d.ts +12 -1
- package/dist/molecules/AppBar.svelte +287 -28
- package/dist/molecules/AppBar.svelte.d.ts +62 -0
- package/dist/molecules/AvatarGroup.svelte +1 -1
- package/dist/molecules/BarChart.svelte +381 -0
- package/dist/molecules/BarChart.svelte.d.ts +45 -0
- package/dist/molecules/BottomSheet.svelte +88 -20
- package/dist/molecules/BottomSheet.svelte.d.ts +17 -1
- package/dist/molecules/BottomTabBar.svelte +134 -28
- package/dist/molecules/BottomTabBar.svelte.d.ts +36 -2
- package/dist/molecules/Calendar.svelte.d.ts +1 -1
- package/dist/molecules/ChipGroup.svelte +325 -0
- package/dist/molecules/ChipGroup.svelte.d.ts +41 -0
- package/dist/molecules/Collapsible.svelte +158 -54
- package/dist/molecules/Collapsible.svelte.d.ts +23 -6
- package/dist/molecules/CollapsibleGroup.svelte +16 -6
- package/dist/molecules/ConfirmDialog.svelte +5 -1
- package/dist/molecules/ConfirmDialog.svelte.d.ts +3 -0
- package/dist/molecules/Drawer.svelte +33 -12
- package/dist/molecules/Dropdown.svelte +9 -1
- package/dist/molecules/Dropdown.svelte.d.ts +2 -0
- package/dist/molecules/DropdownItem.svelte +1 -1
- package/dist/molecules/ImageUpload.svelte +230 -120
- package/dist/molecules/ImageUpload.svelte.d.ts +38 -3
- package/dist/molecules/LineChart.svelte +459 -0
- package/dist/molecules/LineChart.svelte.d.ts +53 -0
- package/dist/molecules/MapView.svelte +565 -0
- package/dist/molecules/MapView.svelte.d.ts +88 -0
- package/dist/molecules/Modal.svelte +11 -6
- package/dist/molecules/PhotoGrid.svelte +100 -10
- package/dist/molecules/PhotoGrid.svelte.d.ts +11 -0
- package/dist/molecules/PhotoViewer.svelte +121 -24
- package/dist/molecules/PhotoViewer.svelte.d.ts +18 -0
- package/dist/molecules/PieChart.svelte +421 -0
- package/dist/molecules/PieChart.svelte.d.ts +51 -0
- package/dist/molecules/RadioGroup.svelte +20 -4
- package/dist/molecules/RadioGroup.svelte.d.ts +5 -0
- package/dist/molecules/SegmentedControl.svelte +136 -3
- package/dist/molecules/SegmentedControl.svelte.d.ts +6 -0
- package/dist/molecules/SidebarFooter.svelte +1 -1
- package/dist/molecules/SidebarNavSection.svelte +1 -1
- package/dist/molecules/SortableList.svelte +1 -0
- package/dist/molecules/Stepper.svelte +21 -7
- package/dist/molecules/StickyActionBar.svelte +67 -8
- package/dist/molecules/StickyActionBar.svelte.d.ts +14 -1
- package/dist/molecules/Tabs.svelte +79 -2
- package/dist/molecules/Toaster.svelte +171 -7
- package/dist/molecules/Toaster.svelte.d.ts +60 -2
- package/dist/molecules/ToasterToast.svelte +292 -60
- package/dist/molecules/ToasterToast.svelte.d.ts +6 -0
- package/dist/molecules/UnsavedChangesBar.svelte +13 -2
- package/dist/molecules/index.d.ts +7 -2
- package/dist/molecules/index.js +4 -0
- package/dist/molecules/toast-store.d.ts +30 -0
- package/dist/molecules/toast-store.js +4 -1
- package/dist/organisms/AppNavigation.svelte +440 -0
- package/dist/organisms/AppNavigation.svelte.d.ts +72 -0
- package/dist/organisms/AppShell.svelte +539 -39
- package/dist/organisms/AppShell.svelte.d.ts +102 -6
- package/dist/organisms/AppShellHeaderPart.svelte +163 -0
- package/dist/organisms/AppShellHeaderPart.svelte.d.ts +57 -0
- package/dist/organisms/SidebarNavigation.svelte +23 -36
- package/dist/organisms/SidebarPanel.svelte +10 -14
- package/dist/organisms/SidebarPanel.svelte.d.ts +1 -0
- package/dist/organisms/SidebarShell.svelte +2 -3
- package/dist/organisms/TopNavbar.svelte +117 -28
- package/dist/organisms/TopNavbar.svelte.d.ts +22 -1
- package/dist/organisms/TopNavbarSheetContent.svelte +145 -0
- package/dist/organisms/TopNavbarSheetContent.svelte.d.ts +24 -0
- package/dist/organisms/index.d.ts +6 -1
- package/dist/organisms/index.js +4 -0
- package/dist/types/components.d.ts +17 -1
- package/dist/types/components.d.ts.map +1 -1
- package/dist/types/index.d.ts +497 -10
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/variants.ts +14 -3
- package/dist/util/app-shell.d.ts +55 -4
- package/dist/util/app-shell.js +32 -7
- package/dist/util/avatar.d.ts +3 -0
- package/dist/util/bottom-tab-bar.d.ts +6 -0
- package/dist/util/chart.d.ts +139 -0
- package/dist/util/chart.js +305 -0
- package/dist/util/chip.d.ts +65 -0
- package/dist/util/chip.js +58 -0
- package/dist/util/cn.js +28 -2
- package/dist/util/code-field.d.ts +62 -0
- package/dist/util/code-field.js +112 -0
- package/dist/util/collapsible.d.ts +6 -0
- package/dist/util/display-voice.d.ts +9 -0
- package/dist/util/display-voice.js +9 -0
- package/dist/util/field-control.d.ts +29 -0
- package/dist/util/field-control.js +32 -0
- package/dist/util/field.d.ts +6 -0
- package/dist/util/focus-utils.d.ts +2 -0
- package/dist/util/focus-utils.js +4 -0
- package/dist/util/map-view.d.ts +143 -0
- package/dist/util/map-view.js +121 -0
- package/dist/util/photo.d.ts +19 -0
- package/dist/util/photo.js +3 -0
- package/dist/util/progress.d.ts +18 -0
- package/dist/util/progress.js +17 -0
- package/dist/util/scroll-edge.d.ts +32 -0
- package/dist/util/scroll-edge.js +87 -0
- package/dist/util/sheet-drag.d.ts +25 -1
- package/dist/util/sheet-drag.js +71 -1
- package/dist/util/skeleton.d.ts +14 -0
- package/dist/util/skeleton.js +22 -0
- package/dist/util/sliding-indicator.d.ts +24 -0
- package/dist/util/sliding-indicator.js +44 -0
- package/dist/util/temporal-field.d.ts +88 -0
- package/dist/util/temporal-field.js +136 -0
- package/dist/util/toaster.d.ts +40 -0
- package/dist/util/toaster.js +55 -0
- package/dist/util/tone-surface.d.ts +33 -0
- package/dist/util/tone-surface.js +35 -0
- package/dist/util/top-navbar.d.ts +26 -2
- package/dist/util/top-navbar.js +23 -0
- package/dist/util/zabi-strings.d.ts +9 -0
- package/dist/zabi-components-colors.css +9 -3
- package/dist/zabi-components-theme-dark-only.css +474 -164
- package/dist/zabi-components-theme-dark.css +474 -164
- package/dist/zabi-components-theme-only.css +933 -99
- package/dist/zabi-components-theme.css +933 -99
- package/dist/zabi-components.css +2275 -321
- package/package.json +11 -1
package/dist/util/sheet-drag.js
CHANGED
|
@@ -8,9 +8,63 @@
|
|
|
8
8
|
*/
|
|
9
9
|
/** Movement under this many px is a press, not a drag. */
|
|
10
10
|
export const DRAG_SLOP = 6;
|
|
11
|
+
/**
|
|
12
|
+
* Movement, on either axis, after which a touch in the content has an axis:
|
|
13
|
+
* more across than down is sideways (a carousel, a row that scrolls, a
|
|
14
|
+
* swipeable item), and stays so until the finger lifts.
|
|
15
|
+
*/
|
|
16
|
+
export const AXIS_SLOP = 8;
|
|
11
17
|
/** Controls in the handle zone that keep their own press, except the grip itself. */
|
|
12
18
|
const INTERACTIVE = "button, a[href], input, select, textarea, [data-sheet-no-drag]";
|
|
13
19
|
const GRIP = "[data-sheet-grip]";
|
|
20
|
+
/**
|
|
21
|
+
* Content that is dragged by a finger on its own and so is never the sheet's:
|
|
22
|
+
* anything carrying `data-sheet-no-drag`, and a range input. Read on a touch
|
|
23
|
+
* that starts in the content.
|
|
24
|
+
*/
|
|
25
|
+
const OWN_DRAG = "[data-sheet-no-drag], input[type='range']";
|
|
26
|
+
/**
|
|
27
|
+
* Whether a computed `touch-action` hands vertical gestures to the element's
|
|
28
|
+
* own code: `none`, or panning on the x axis only. `auto`, `manipulation`,
|
|
29
|
+
* `pan-y` and the like let the browser pan vertically, which is the sheet's
|
|
30
|
+
* to take.
|
|
31
|
+
*/
|
|
32
|
+
function ownsVerticalGesture(touchAction) {
|
|
33
|
+
const tokens = touchAction.trim().split(/\s+/);
|
|
34
|
+
if (tokens.includes("none"))
|
|
35
|
+
return true;
|
|
36
|
+
const sideways = tokens.some((token) => token === "pan-x" || token === "pan-left" || token === "pan-right");
|
|
37
|
+
const vertical = tokens.some((token) => token === "pan-y" || token === "pan-up" || token === "pan-down");
|
|
38
|
+
return sideways && !vertical;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Whether a touch that begins on `target` belongs to the content and not to the
|
|
42
|
+
* sheet: it started on something that is dragged itself (`OWN_DRAG`), or it
|
|
43
|
+
* has, between it and the sheet's `scroller`, a box that scrolls on the y axis
|
|
44
|
+
* or one whose `touch-action` leaves vertical panning to the page's own code
|
|
45
|
+
* (`ownsVerticalGesture`: a map, a canvas, a drawing pad, a slider).
|
|
46
|
+
* A box that can scroll keeps the gesture whether it is at its top or not: a
|
|
47
|
+
* drag that sometimes scrolls a list and sometimes closes the sheet cannot be
|
|
48
|
+
* predicted, and the list does not hand over what it cannot use
|
|
49
|
+
* (`overscroll-contain`). The scroller itself is not asked here; it is the
|
|
50
|
+
* sheet's own content, and hands a pull down at its top to the sheet.
|
|
51
|
+
*/
|
|
52
|
+
export function contentTakesTouch(target, scroller) {
|
|
53
|
+
if (!(target instanceof Element))
|
|
54
|
+
return false;
|
|
55
|
+
if (target.closest(OWN_DRAG))
|
|
56
|
+
return true;
|
|
57
|
+
for (let node = target; node && node !== scroller; node = node.parentElement) {
|
|
58
|
+
const style = getComputedStyle(node);
|
|
59
|
+
if (ownsVerticalGesture(style.getPropertyValue("touch-action")))
|
|
60
|
+
return true;
|
|
61
|
+
const overflow = style.overflowY;
|
|
62
|
+
if ((overflow === "auto" || overflow === "scroll") && node.scrollHeight > node.clientHeight) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
14
68
|
/** The clock of this page, for when an event's own time cannot be used. */
|
|
15
69
|
const now = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
|
|
16
70
|
/**
|
|
@@ -159,8 +213,13 @@ export function attachSheetDrag({ handleZone, scroller }, callbacks) {
|
|
|
159
213
|
// is not passive can take it.
|
|
160
214
|
let touchId = null;
|
|
161
215
|
let touchStart = 0;
|
|
216
|
+
let touchStartX = 0;
|
|
217
|
+
/** Decided once per gesture, after `AXIS_SLOP`: null until then. */
|
|
218
|
+
let touchAxis = null;
|
|
162
219
|
let touchDragging = false;
|
|
163
220
|
let startedAtTop = false;
|
|
221
|
+
/** The touch began on something inside the content that keeps it. */
|
|
222
|
+
let contentOwns = false;
|
|
164
223
|
const touchOf = (event, list) => Array.from(list).find((touch) => touch.identifier === touchId);
|
|
165
224
|
function onTouchStart(event) {
|
|
166
225
|
if (touchId !== null || pointerId !== null || event.touches.length !== 1)
|
|
@@ -168,8 +227,11 @@ export function attachSheetDrag({ handleZone, scroller }, callbacks) {
|
|
|
168
227
|
const touch = event.touches[0];
|
|
169
228
|
touchId = touch.identifier;
|
|
170
229
|
touchStart = touch.clientY;
|
|
230
|
+
touchStartX = touch.clientX;
|
|
231
|
+
touchAxis = null;
|
|
171
232
|
touchDragging = false;
|
|
172
233
|
startedAtTop = (scroller?.scrollTop ?? 0) <= 0;
|
|
234
|
+
contentOwns = !!scroller && contentTakesTouch(event.target, scroller);
|
|
173
235
|
}
|
|
174
236
|
function onTouchMove(event) {
|
|
175
237
|
const touch = touchOf(event, event.touches);
|
|
@@ -178,8 +240,16 @@ export function attachSheetDrag({ handleZone, scroller }, callbacks) {
|
|
|
178
240
|
const distance = touch.clientY - touchStart;
|
|
179
241
|
if (!touchDragging) {
|
|
180
242
|
// Content that has scrolled, or a swipe upwards: the content's.
|
|
181
|
-
if (!startedAtTop || (scroller?.scrollTop ?? 0) > 0)
|
|
243
|
+
if (contentOwns || touchAxis === "x" || !startedAtTop || (scroller?.scrollTop ?? 0) > 0)
|
|
182
244
|
return;
|
|
245
|
+
if (touchAxis === null) {
|
|
246
|
+
const across = Math.abs(touch.clientX - touchStartX);
|
|
247
|
+
if (Math.max(across, Math.abs(distance)) < AXIS_SLOP)
|
|
248
|
+
return;
|
|
249
|
+
touchAxis = across > Math.abs(distance) ? "x" : "y";
|
|
250
|
+
if (touchAxis === "x")
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
183
253
|
if (distance < DRAG_SLOP || !allowed())
|
|
184
254
|
return;
|
|
185
255
|
touchDragging = true;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Every built-in string. Pass a partial object to `strings` to translate. */
|
|
2
|
+
export interface SkeletonStrings {
|
|
3
|
+
/** The name of the loading status, for a lone `Skeleton` and for a `SkeletonGroup`. */
|
|
4
|
+
label: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const DEFAULT_SKELETON_STRINGS: SkeletonStrings;
|
|
7
|
+
/** The fewest and the most lines a text `Skeleton` draws with `lines`. */
|
|
8
|
+
export declare const SKELETON_LINES_MAX = 20;
|
|
9
|
+
/** `lines` as a whole number from 1 to 20, or 0 for "not a paragraph" (unset, below 1, not a number). */
|
|
10
|
+
export declare function skeletonLineCount(lines: number | undefined): number;
|
|
11
|
+
/** For `SkeletonGroup`: marks everything below it as pieces of one status. Call while the component initialises. */
|
|
12
|
+
export declare function provideSkeletonGroup(): void;
|
|
13
|
+
/** For `Skeleton`: whether it sits under a `SkeletonGroup`. Call while the component initialises. */
|
|
14
|
+
export declare function inSkeletonGroup(): boolean;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Types and helpers shared by `Skeleton` and `SkeletonGroup`. Kept out of the components so they can be tested without a DOM. */
|
|
2
|
+
import { getContext, setContext } from "svelte";
|
|
3
|
+
export const DEFAULT_SKELETON_STRINGS = {
|
|
4
|
+
label: "Loading…",
|
|
5
|
+
};
|
|
6
|
+
/** The fewest and the most lines a text `Skeleton` draws with `lines`. */
|
|
7
|
+
export const SKELETON_LINES_MAX = 20;
|
|
8
|
+
/** `lines` as a whole number from 1 to 20, or 0 for "not a paragraph" (unset, below 1, not a number). */
|
|
9
|
+
export function skeletonLineCount(lines) {
|
|
10
|
+
if (lines === undefined || !Number.isFinite(lines) || lines < 1)
|
|
11
|
+
return 0;
|
|
12
|
+
return Math.min(SKELETON_LINES_MAX, Math.floor(lines));
|
|
13
|
+
}
|
|
14
|
+
const GROUP_KEY = Symbol("zabi-skeleton-group");
|
|
15
|
+
/** For `SkeletonGroup`: marks everything below it as pieces of one status. Call while the component initialises. */
|
|
16
|
+
export function provideSkeletonGroup() {
|
|
17
|
+
setContext(GROUP_KEY, true);
|
|
18
|
+
}
|
|
19
|
+
/** For `Skeleton`: whether it sits under a `SkeletonGroup`. Call while the component initialises. */
|
|
20
|
+
export function inSkeletonGroup() {
|
|
21
|
+
return getContext(GROUP_KEY) === true;
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Measuring for the sliding indicator of Tabs and SegmentedControl. The
|
|
3
|
+
* component paints its selected item itself until a box has been measured, so
|
|
4
|
+
* a server render, a page that has not hydrated and an engine that cannot
|
|
5
|
+
* measure all keep the static look.
|
|
6
|
+
*/
|
|
7
|
+
export interface IndicatorBox {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The `item`'s box in the coordinates an absolutely positioned child of
|
|
15
|
+
* `container` uses: from the container's padding box, plus what the container
|
|
16
|
+
* has scrolled (so an indicator inside a scrolling list stays on its item).
|
|
17
|
+
* Measured as physical boxes, so a right-to-left layout needs nothing extra.
|
|
18
|
+
* Null where nothing can be measured (no layout, or the item is not shown).
|
|
19
|
+
*/
|
|
20
|
+
export declare function measureIndicator(container: HTMLElement, item: HTMLElement): IndicatorBox | null;
|
|
21
|
+
/** Calls `onChange` when any of the `targets` changes size; a no-op without ResizeObserver. */
|
|
22
|
+
export declare function observeSizes(targets: Array<Element | null | undefined>, onChange: () => void): () => void;
|
|
23
|
+
/** Runs `callback` after the next frame; the timer stands in where there are no frames. */
|
|
24
|
+
export declare function afterFrame(callback: () => void): () => void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Measuring for the sliding indicator of Tabs and SegmentedControl. The
|
|
3
|
+
* component paints its selected item itself until a box has been measured, so
|
|
4
|
+
* a server render, a page that has not hydrated and an engine that cannot
|
|
5
|
+
* measure all keep the static look.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* The `item`'s box in the coordinates an absolutely positioned child of
|
|
9
|
+
* `container` uses: from the container's padding box, plus what the container
|
|
10
|
+
* has scrolled (so an indicator inside a scrolling list stays on its item).
|
|
11
|
+
* Measured as physical boxes, so a right-to-left layout needs nothing extra.
|
|
12
|
+
* Null where nothing can be measured (no layout, or the item is not shown).
|
|
13
|
+
*/
|
|
14
|
+
export function measureIndicator(container, item) {
|
|
15
|
+
const outer = container.getBoundingClientRect();
|
|
16
|
+
const inner = item.getBoundingClientRect();
|
|
17
|
+
if (inner.width === 0 && inner.height === 0)
|
|
18
|
+
return null;
|
|
19
|
+
return {
|
|
20
|
+
x: inner.left - outer.left - container.clientLeft + container.scrollLeft,
|
|
21
|
+
y: inner.top - outer.top - container.clientTop + container.scrollTop,
|
|
22
|
+
width: inner.width,
|
|
23
|
+
height: inner.height,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Calls `onChange` when any of the `targets` changes size; a no-op without ResizeObserver. */
|
|
27
|
+
export function observeSizes(targets, onChange) {
|
|
28
|
+
if (typeof ResizeObserver === "undefined")
|
|
29
|
+
return () => { };
|
|
30
|
+
const observer = new ResizeObserver(onChange);
|
|
31
|
+
for (const target of targets)
|
|
32
|
+
if (target)
|
|
33
|
+
observer.observe(target);
|
|
34
|
+
return () => observer.disconnect();
|
|
35
|
+
}
|
|
36
|
+
/** Runs `callback` after the next frame; the timer stands in where there are no frames. */
|
|
37
|
+
export function afterFrame(callback) {
|
|
38
|
+
if (typeof requestAnimationFrame === "function") {
|
|
39
|
+
const id = requestAnimationFrame(callback);
|
|
40
|
+
return () => cancelAnimationFrame(id);
|
|
41
|
+
}
|
|
42
|
+
const id = setTimeout(callback, 16);
|
|
43
|
+
return () => clearTimeout(id);
|
|
44
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What DateField and TimeField share once they are given a `locale`: the
|
|
3
|
+
* words, and the pure parts of the time picker (which hours and minutes
|
|
4
|
+
* there are, which are outside `min` and `max`).
|
|
5
|
+
*/
|
|
6
|
+
/** The words DateField says when it has a `locale`. Replace any of them to translate. */
|
|
7
|
+
export interface DateFieldStrings {
|
|
8
|
+
/** The sheet's action that empties the field. Shown while the field has a value and is not `required`. */
|
|
9
|
+
clear: string;
|
|
10
|
+
/** The sheet's name when the field has no label. */
|
|
11
|
+
chooseDate: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const DATE_FIELD_STRINGS: DateFieldStrings;
|
|
14
|
+
/** The words TimeField says when it has a `locale`. Replace any of them to translate. */
|
|
15
|
+
export interface TimeFieldStrings {
|
|
16
|
+
/** The sheet's action that empties the field. Shown while the field has a value and is not `required`. */
|
|
17
|
+
clear: string;
|
|
18
|
+
/** The sheet's action that sets the chosen time and closes it. */
|
|
19
|
+
done: string;
|
|
20
|
+
/** Accessible name of the column of hours. */
|
|
21
|
+
hours: string;
|
|
22
|
+
/** Accessible name of the column of minutes. */
|
|
23
|
+
minutes: string;
|
|
24
|
+
/** The sheet's words beside Done for the time Done will set: "Chosen time 18:00". */
|
|
25
|
+
chosenTime: string;
|
|
26
|
+
/** The sheet's name when the field has no label. */
|
|
27
|
+
chooseTime: string;
|
|
28
|
+
}
|
|
29
|
+
export declare const TIME_FIELD_STRINGS: TimeFieldStrings;
|
|
30
|
+
/** Whether a `locale` was given: a tag, or a list with at least one. */
|
|
31
|
+
export declare function hasLocale(locale: string | string[] | undefined | null): boolean;
|
|
32
|
+
/** The minutes between choices when the app says nothing: five. */
|
|
33
|
+
export declare const DEFAULT_MINUTE_STEP = 5;
|
|
34
|
+
/**
|
|
35
|
+
* Whether the library's time picker can serve this `step`. It has hours and
|
|
36
|
+
* minutes only, so a step in seconds (under 60) or `"any"` is left to the
|
|
37
|
+
* platform's own picker.
|
|
38
|
+
*/
|
|
39
|
+
export declare function stepFitsLibraryPicker(step: number | "any" | undefined): boolean;
|
|
40
|
+
/** `step` (in seconds) as whole minutes between choices; five without one. */
|
|
41
|
+
export declare function minuteStep(step: number | "any" | undefined): number;
|
|
42
|
+
/** `"19:30"` (or `"19:30:15"`) as [19, 30]; null for anything else. */
|
|
43
|
+
export declare function parseHourMinute(value: string | null | undefined): [number, number] | null;
|
|
44
|
+
/**
|
|
45
|
+
* The time an empty field's columns stand at: `openAt`, else `now` (an
|
|
46
|
+
* hour and a minute), kept within `min` and `max` and on the minute step.
|
|
47
|
+
* Only a place to scroll to; nothing is chosen. Two-digit strings, as the
|
|
48
|
+
* columns' option values.
|
|
49
|
+
*/
|
|
50
|
+
export declare function openingTime(options: {
|
|
51
|
+
openAt?: string;
|
|
52
|
+
now: [number, number];
|
|
53
|
+
stepMinutes: number;
|
|
54
|
+
min?: string;
|
|
55
|
+
max?: string;
|
|
56
|
+
}): [string, string];
|
|
57
|
+
/**
|
|
58
|
+
* Whether a time is within `min` and `max`, either of which may be missing.
|
|
59
|
+
* Like the native input, a `min` later than `max` is a range over midnight:
|
|
60
|
+
* 22:00 to 06:00 allows 23:30 and 05:00.
|
|
61
|
+
*/
|
|
62
|
+
export declare function timeInRange(hour: number, minute: number, min?: string, max?: string): boolean;
|
|
63
|
+
export interface TimeChoice {
|
|
64
|
+
/** Two digits: `"07"`. */
|
|
65
|
+
value: string;
|
|
66
|
+
label: string;
|
|
67
|
+
disabled: boolean;
|
|
68
|
+
}
|
|
69
|
+
/** The hour as the locale writes it: "19" in Swedish, "7 PM" in American English. */
|
|
70
|
+
export declare function hourLabel(hour: number, locale: string | string[] | undefined): string;
|
|
71
|
+
/** The minute as two digits, in the locale's own numerals. */
|
|
72
|
+
export declare function minuteLabel(minute: number, locale: string | string[] | undefined): string;
|
|
73
|
+
/** The 24 hours. An hour is disabled when no time in it is within `min` and `max`. */
|
|
74
|
+
export declare function hourChoices(locale: string | string[] | undefined, min?: string, max?: string): TimeChoice[];
|
|
75
|
+
/**
|
|
76
|
+
* The minutes of one hour: every `stepMinutes`, plus the minute of `current`
|
|
77
|
+
* when it is not on the step, so a value such as 19:07 can be kept. A minute
|
|
78
|
+
* is disabled when the time is outside `min` and `max`; with no hour chosen
|
|
79
|
+
* none is.
|
|
80
|
+
*/
|
|
81
|
+
export declare function minuteChoices(options: {
|
|
82
|
+
locale: string | string[] | undefined;
|
|
83
|
+
stepMinutes: number;
|
|
84
|
+
hour: string;
|
|
85
|
+
current?: string;
|
|
86
|
+
min?: string;
|
|
87
|
+
max?: string;
|
|
88
|
+
}): TimeChoice[];
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What DateField and TimeField share once they are given a `locale`: the
|
|
3
|
+
* words, and the pure parts of the time picker (which hours and minutes
|
|
4
|
+
* there are, which are outside `min` and `max`).
|
|
5
|
+
*/
|
|
6
|
+
import { formatTime } from "./date.js";
|
|
7
|
+
export const DATE_FIELD_STRINGS = {
|
|
8
|
+
clear: "Clear",
|
|
9
|
+
chooseDate: "Choose date",
|
|
10
|
+
};
|
|
11
|
+
export const TIME_FIELD_STRINGS = {
|
|
12
|
+
clear: "Clear",
|
|
13
|
+
done: "Done",
|
|
14
|
+
hours: "Hours",
|
|
15
|
+
minutes: "Minutes",
|
|
16
|
+
chosenTime: "Chosen time",
|
|
17
|
+
chooseTime: "Choose time",
|
|
18
|
+
};
|
|
19
|
+
/** Whether a `locale` was given: a tag, or a list with at least one. */
|
|
20
|
+
export function hasLocale(locale) {
|
|
21
|
+
return Array.isArray(locale) ? locale.length > 0 : !!locale;
|
|
22
|
+
}
|
|
23
|
+
/** The minutes between choices when the app says nothing: five. */
|
|
24
|
+
export const DEFAULT_MINUTE_STEP = 5;
|
|
25
|
+
/**
|
|
26
|
+
* Whether the library's time picker can serve this `step`. It has hours and
|
|
27
|
+
* minutes only, so a step in seconds (under 60) or `"any"` is left to the
|
|
28
|
+
* platform's own picker.
|
|
29
|
+
*/
|
|
30
|
+
export function stepFitsLibraryPicker(step) {
|
|
31
|
+
if (step === undefined)
|
|
32
|
+
return true;
|
|
33
|
+
return typeof step === "number" && Number.isFinite(step) && step >= 60;
|
|
34
|
+
}
|
|
35
|
+
/** `step` (in seconds) as whole minutes between choices; five without one. */
|
|
36
|
+
export function minuteStep(step) {
|
|
37
|
+
if (typeof step === "number" && Number.isFinite(step) && step >= 60)
|
|
38
|
+
return Math.round(step / 60);
|
|
39
|
+
return DEFAULT_MINUTE_STEP;
|
|
40
|
+
}
|
|
41
|
+
const pad2 = (value) => String(value).padStart(2, "0");
|
|
42
|
+
/** `"19:30"` (or `"19:30:15"`) as [19, 30]; null for anything else. */
|
|
43
|
+
export function parseHourMinute(value) {
|
|
44
|
+
const match = /^(\d{2}):(\d{2})(?::\d{2}(?:\.\d+)?)?$/.exec(value ?? "");
|
|
45
|
+
if (!match)
|
|
46
|
+
return null;
|
|
47
|
+
const hour = Number(match[1]);
|
|
48
|
+
const minute = Number(match[2]);
|
|
49
|
+
return hour > 23 || minute > 59 ? null : [hour, minute];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The time an empty field's columns stand at: `openAt`, else `now` (an
|
|
53
|
+
* hour and a minute), kept within `min` and `max` and on the minute step.
|
|
54
|
+
* Only a place to scroll to; nothing is chosen. Two-digit strings, as the
|
|
55
|
+
* columns' option values.
|
|
56
|
+
*/
|
|
57
|
+
export function openingTime(options) {
|
|
58
|
+
const { openAt, now, stepMinutes, min, max } = options;
|
|
59
|
+
const [hour, minute] = parseHourMinute(openAt) ?? now;
|
|
60
|
+
let at = hour * 60 + minute;
|
|
61
|
+
const lower = parseHourMinute(min);
|
|
62
|
+
const upper = parseHourMinute(max);
|
|
63
|
+
const from = lower ? lower[0] * 60 + lower[1] : null;
|
|
64
|
+
const to = upper ? upper[0] * 60 + upper[1] : null;
|
|
65
|
+
const inRange = (value) => timeInRange(Math.floor(value / 60) % 24, value % 60, min, max);
|
|
66
|
+
if (!inRange(at)) {
|
|
67
|
+
const overMidnight = from !== null && to !== null && from > to;
|
|
68
|
+
at = from !== null && (overMidnight || at < from) ? from : (to ?? at);
|
|
69
|
+
}
|
|
70
|
+
const floored = Math.floor(at / stepMinutes) * stepMinutes;
|
|
71
|
+
const stepped = inRange(floored) ? floored : Math.min(Math.ceil(at / stepMinutes) * stepMinutes, 23 * 60 + 59);
|
|
72
|
+
return [pad2(Math.floor(stepped / 60)), pad2(stepped % 60)];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Whether a time is within `min` and `max`, either of which may be missing.
|
|
76
|
+
* Like the native input, a `min` later than `max` is a range over midnight:
|
|
77
|
+
* 22:00 to 06:00 allows 23:30 and 05:00.
|
|
78
|
+
*/
|
|
79
|
+
export function timeInRange(hour, minute, min, max) {
|
|
80
|
+
const at = hour * 60 + minute;
|
|
81
|
+
const lower = parseHourMinute(min);
|
|
82
|
+
const upper = parseHourMinute(max);
|
|
83
|
+
const from = lower ? lower[0] * 60 + lower[1] : null;
|
|
84
|
+
const to = upper ? upper[0] * 60 + upper[1] : null;
|
|
85
|
+
if (from !== null && to !== null && from > to)
|
|
86
|
+
return at >= from || at <= to;
|
|
87
|
+
if (from !== null && at < from)
|
|
88
|
+
return false;
|
|
89
|
+
if (to !== null && at > to)
|
|
90
|
+
return false;
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
/** The hour as the locale writes it: "19" in Swedish, "7 PM" in American English. */
|
|
94
|
+
export function hourLabel(hour, locale) {
|
|
95
|
+
return formatTime(`${pad2(hour)}:00`, locale, { hour: "numeric" }) || pad2(hour);
|
|
96
|
+
}
|
|
97
|
+
/** The minute as two digits, in the locale's own numerals. */
|
|
98
|
+
export function minuteLabel(minute, locale) {
|
|
99
|
+
try {
|
|
100
|
+
return new Intl.NumberFormat(locale || undefined, { minimumIntegerDigits: 2, useGrouping: false }).format(minute);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return pad2(minute);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** The 24 hours. An hour is disabled when no time in it is within `min` and `max`. */
|
|
107
|
+
export function hourChoices(locale, min, max) {
|
|
108
|
+
return Array.from({ length: 24 }, (_, hour) => ({
|
|
109
|
+
value: pad2(hour),
|
|
110
|
+
label: hourLabel(hour, locale),
|
|
111
|
+
disabled: !Array.from({ length: 60 }, (_, minute) => minute).some((minute) => timeInRange(hour, minute, min, max)),
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The minutes of one hour: every `stepMinutes`, plus the minute of `current`
|
|
116
|
+
* when it is not on the step, so a value such as 19:07 can be kept. A minute
|
|
117
|
+
* is disabled when the time is outside `min` and `max`; with no hour chosen
|
|
118
|
+
* none is.
|
|
119
|
+
*/
|
|
120
|
+
export function minuteChoices(options) {
|
|
121
|
+
const { locale, stepMinutes, hour, current, min, max } = options;
|
|
122
|
+
const minutes = new Set();
|
|
123
|
+
for (let minute = 0; minute < 60; minute += stepMinutes)
|
|
124
|
+
minutes.add(minute);
|
|
125
|
+
const now = parseHourMinute(current);
|
|
126
|
+
if (now)
|
|
127
|
+
minutes.add(now[1]);
|
|
128
|
+
const chosenHour = hour === "" ? null : Number(hour);
|
|
129
|
+
return [...minutes]
|
|
130
|
+
.sort((a, b) => a - b)
|
|
131
|
+
.map((minute) => ({
|
|
132
|
+
value: pad2(minute),
|
|
133
|
+
label: minuteLabel(minute, locale),
|
|
134
|
+
disabled: chosenHour !== null && !timeInRange(chosenHour, minute, min, max),
|
|
135
|
+
}));
|
|
136
|
+
}
|
package/dist/util/toaster.d.ts
CHANGED
|
@@ -90,3 +90,43 @@ export interface ToastPauseChange {
|
|
|
90
90
|
/** True while a pointer is over the toast, a finger is on it, or focus is inside it. */
|
|
91
91
|
paused: boolean;
|
|
92
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* How a `Toaster` presents its toasts. `"stack"` is the stack in the corner
|
|
95
|
+
* (and across the width on a phone); `"snackbar"` is the compact form for a
|
|
96
|
+
* phone, one at a time, docked above whatever is at the bottom; `"auto"` is
|
|
97
|
+
* the snackbar below 640px and the stack from there up.
|
|
98
|
+
*/
|
|
99
|
+
export type ToasterMode = "stack" | "snackbar" | "auto";
|
|
100
|
+
/** The words of a toast on screen: its first line, and the line under it if there is one. */
|
|
101
|
+
export declare function toastTexts(toast: {
|
|
102
|
+
title?: string;
|
|
103
|
+
message: string;
|
|
104
|
+
type: string;
|
|
105
|
+
}, strings: Pick<ToasterStrings, "successTitle" | "errorTitle" | "warningTitle" | "infoTitle">): {
|
|
106
|
+
title: string;
|
|
107
|
+
message: string;
|
|
108
|
+
headerTitle: string;
|
|
109
|
+
secondLine: string;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* True when the toast has to stay until a person has dealt with it, which
|
|
113
|
+
* makes it the one a `max` never drops: an error, one with an action, one
|
|
114
|
+
* with no end (`"persistent"`, or text too long to read in `long`).
|
|
115
|
+
*/
|
|
116
|
+
export declare function toastStays(toast: {
|
|
117
|
+
title?: string;
|
|
118
|
+
message: string;
|
|
119
|
+
type: string;
|
|
120
|
+
duration?: ToastDuration;
|
|
121
|
+
action?: unknown;
|
|
122
|
+
}, defaultDuration?: ToastDuration): boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Ids of toasts that a `max` is dropping to make room for a newer one. Such a
|
|
125
|
+
* toast leaves at once, without the exit motion: a newer toast is coming in
|
|
126
|
+
* where it is, and the two would otherwise lie on each other for a moment.
|
|
127
|
+
*/
|
|
128
|
+
export declare const toastsReplaced: Set<string>;
|
|
129
|
+
/** The Material standard curve (`--ease-standard`), as an easing function for a script-driven slide. */
|
|
130
|
+
export declare function easeStandard(t: number): number;
|
|
131
|
+
/** A toast dragged further than this share of its width is dismissed, flick or not. */
|
|
132
|
+
export declare const TOAST_SWIPE_DISMISS_FRACTION = 0.4;
|
package/dist/util/toaster.js
CHANGED
|
@@ -73,3 +73,58 @@ export function resolveToastDuration(toast, defaultDuration) {
|
|
|
73
73
|
* As long as `short`: holding a short toast starts it again, in effect.
|
|
74
74
|
*/
|
|
75
75
|
export const TOAST_SECONDS_AFTER_HOLD = 3;
|
|
76
|
+
/** The words of a toast on screen: its first line, and the line under it if there is one. */
|
|
77
|
+
export function toastTexts(toast, strings) {
|
|
78
|
+
const title = toast.title?.trim() ?? "";
|
|
79
|
+
const message = toast.message.trim();
|
|
80
|
+
const fallback = toast.type === "success"
|
|
81
|
+
? strings.successTitle
|
|
82
|
+
: toast.type === "error"
|
|
83
|
+
? strings.errorTitle
|
|
84
|
+
: toast.type === "warning"
|
|
85
|
+
? strings.warningTitle
|
|
86
|
+
: strings.infoTitle;
|
|
87
|
+
return {
|
|
88
|
+
title,
|
|
89
|
+
message,
|
|
90
|
+
// The title, or the message when there is no title.
|
|
91
|
+
headerTitle: title || message || fallback,
|
|
92
|
+
// The message, under a title that says something else.
|
|
93
|
+
secondLine: title && message && message !== title ? message : "",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* True when the toast has to stay until a person has dealt with it, which
|
|
98
|
+
* makes it the one a `max` never drops: an error, one with an action, one
|
|
99
|
+
* with no end (`"persistent"`, or text too long to read in `long`).
|
|
100
|
+
*/
|
|
101
|
+
export function toastStays(toast, defaultDuration) {
|
|
102
|
+
const title = toast.title?.trim() ?? "";
|
|
103
|
+
const message = toast.message.trim();
|
|
104
|
+
const textLength = (title || message).length + (title && message && message !== title ? message.length : 0);
|
|
105
|
+
return (resolveToastDuration({ duration: toast.duration, type: toast.type, hasAction: !!toast.action, textLength }, defaultDuration) === 0);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Ids of toasts that a `max` is dropping to make room for a newer one. Such a
|
|
109
|
+
* toast leaves at once, without the exit motion: a newer toast is coming in
|
|
110
|
+
* where it is, and the two would otherwise lie on each other for a moment.
|
|
111
|
+
*/
|
|
112
|
+
export const toastsReplaced = new Set();
|
|
113
|
+
/** The Material standard curve (`--ease-standard`), as an easing function for a script-driven slide. */
|
|
114
|
+
export function easeStandard(t) {
|
|
115
|
+
const [x1, y1, x2, y2] = [0.2, 0, 0, 1];
|
|
116
|
+
const at = (a, b, s) => 3 * a * (1 - s) ** 2 * s + 3 * b * (1 - s) * s ** 2 + s ** 3;
|
|
117
|
+
let lo = 0;
|
|
118
|
+
let hi = 1;
|
|
119
|
+
let s = t;
|
|
120
|
+
for (let i = 0; i < 20; i++) {
|
|
121
|
+
s = (lo + hi) / 2;
|
|
122
|
+
if (at(x1, x2, s) < t)
|
|
123
|
+
lo = s;
|
|
124
|
+
else
|
|
125
|
+
hi = s;
|
|
126
|
+
}
|
|
127
|
+
return at(y1, y2, s);
|
|
128
|
+
}
|
|
129
|
+
/** A toast dragged further than this share of its width is dismissed, flick or not. */
|
|
130
|
+
export const TOAST_SWIPE_DISMISS_FRACTION = 0.4;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { SurfaceTone } from "../types/variants.js";
|
|
2
|
+
export type ToneSurfaceOptions = {
|
|
3
|
+
tone?: SurfaceTone;
|
|
4
|
+
/** Any CSS colour for a fill the app chooses, such as `var(--group-colour)`. Wins over `tone`. */
|
|
5
|
+
fill?: string;
|
|
6
|
+
/** The label colour on `fill`, any CSS colour. Without it the label is the block's own text colour. */
|
|
7
|
+
onFill?: string;
|
|
8
|
+
};
|
|
9
|
+
export type ToneSurface = {
|
|
10
|
+
/** Utility classes for the fill and its on-colour scope; empty for `neutral`. */
|
|
11
|
+
classes: string;
|
|
12
|
+
/** Inline style for a custom fill, or undefined. */
|
|
13
|
+
style: string | undefined;
|
|
14
|
+
/** True when the surface is filled by tone or by `fill`, so the fill is its edge. */
|
|
15
|
+
filled: boolean;
|
|
16
|
+
/** True when the fill is the brand, the accent or a custom colour (the on-colour scope applies). */
|
|
17
|
+
scoped: boolean;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The classes and the inline style for a filled surface, shared by Card and Block.
|
|
21
|
+
*
|
|
22
|
+
* - `neutral` (the default): nothing, the component paints what it always did.
|
|
23
|
+
* - `tint`: the brand tint as a content surface. Text, links and rings are
|
|
24
|
+
* guarded on it, so it needs no on-colour scope.
|
|
25
|
+
* - `brand` / `accent`: the fill and its `on-brand` / `on-accent` scope.
|
|
26
|
+
* - `fill`: the app's own colour. Wins over `tone`. The scope is `on-fill`,
|
|
27
|
+
* with `onFill` as the label colour.
|
|
28
|
+
*
|
|
29
|
+
* The library cannot check the contrast of a `fill` and `onFill` pair: it is
|
|
30
|
+
* a colour the app picks at run time, so the app must hold the label to 4.5:1
|
|
31
|
+
* on the fill (3:1 for edges and focus rings).
|
|
32
|
+
*/
|
|
33
|
+
export declare function toneSurface({ tone, fill, onFill }: ToneSurfaceOptions): ToneSurface;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The classes and the inline style for a filled surface, shared by Card and Block.
|
|
3
|
+
*
|
|
4
|
+
* - `neutral` (the default): nothing, the component paints what it always did.
|
|
5
|
+
* - `tint`: the brand tint as a content surface. Text, links and rings are
|
|
6
|
+
* guarded on it, so it needs no on-colour scope.
|
|
7
|
+
* - `brand` / `accent`: the fill and its `on-brand` / `on-accent` scope.
|
|
8
|
+
* - `fill`: the app's own colour. Wins over `tone`. The scope is `on-fill`,
|
|
9
|
+
* with `onFill` as the label colour.
|
|
10
|
+
*
|
|
11
|
+
* The library cannot check the contrast of a `fill` and `onFill` pair: it is
|
|
12
|
+
* a colour the app picks at run time, so the app must hold the label to 4.5:1
|
|
13
|
+
* on the fill (3:1 for edges and focus rings).
|
|
14
|
+
*/
|
|
15
|
+
export function toneSurface({ tone = "neutral", fill, onFill }) {
|
|
16
|
+
if (fill) {
|
|
17
|
+
const parts = [`background-color: ${fill}`];
|
|
18
|
+
// --zabi-fill is the colour a solid control inside is lettered in when
|
|
19
|
+
// the scope inverts it; see `--zabi-inv-primary-text` in app.css.
|
|
20
|
+
parts.push(`--zabi-fill: ${fill}`);
|
|
21
|
+
if (onFill)
|
|
22
|
+
parts.push(`--zabi-on-fill: ${onFill}`);
|
|
23
|
+
return { classes: "on-fill", style: parts.join("; "), filled: true, scoped: true };
|
|
24
|
+
}
|
|
25
|
+
switch (tone) {
|
|
26
|
+
case "tint":
|
|
27
|
+
return { classes: "bg-card-tint", style: undefined, filled: true, scoped: false };
|
|
28
|
+
case "brand":
|
|
29
|
+
return { classes: "bg-action-primary on-brand", style: undefined, filled: true, scoped: true };
|
|
30
|
+
case "accent":
|
|
31
|
+
return { classes: "bg-accent on-accent", style: undefined, filled: true, scoped: true };
|
|
32
|
+
default:
|
|
33
|
+
return { classes: "", style: undefined, filled: false, scoped: false };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -2,9 +2,33 @@
|
|
|
2
2
|
export interface TopNavbarStrings {
|
|
3
3
|
/** Accessible name of the phone menu's button while the menu is closed. */
|
|
4
4
|
openMenu: string;
|
|
5
|
-
/** The same button, while the menu is open. */
|
|
5
|
+
/** The same button, while the inline menu is open (the sheet's own button closes it). */
|
|
6
6
|
closeMenu: string;
|
|
7
|
+
/** The phone menu's sheet: its heading and its accessible name, and the name of the links' `nav` when the bar has no `ariaLabel`. */
|
|
8
|
+
menuTitle?: string;
|
|
9
|
+
/** The heading over the theme choice in the phone menu's sheet. */
|
|
10
|
+
themeHeading?: string;
|
|
7
11
|
/** Read after the label of a link that opens in a new tab. */
|
|
8
12
|
opensInNewTab: string;
|
|
9
13
|
}
|
|
10
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Every word, including the optional ones: `menuTitle` and `themeHeading` are
|
|
16
|
+
* optional in `TopNavbarStrings` so that code written before they existed, with
|
|
17
|
+
* a complete object, still type-checks.
|
|
18
|
+
*/
|
|
19
|
+
export declare const DEFAULT_TOP_NAVBAR_STRINGS: Required<TopNavbarStrings>;
|
|
20
|
+
/**
|
|
21
|
+
* Where a piece of the bar's `actions` is drawn: in the `bar`, or in the
|
|
22
|
+
* `menu` sheet that opens below `collapseAt`, where it has the full width.
|
|
23
|
+
*/
|
|
24
|
+
export type TopNavbarPlace = "bar" | "menu";
|
|
25
|
+
/** Called by the sheet's content: everything rendered inside it reads `"menu"`. */
|
|
26
|
+
export declare function markTopNavbarPlace(place: TopNavbarPlace): void;
|
|
27
|
+
/**
|
|
28
|
+
* Where the component calling this is drawn. Call it while the component
|
|
29
|
+
* initialises. A control in a TopNavbar's `actions` snippet can then draw
|
|
30
|
+
* itself large in the sheet and compact in the bar, with no argument to the
|
|
31
|
+
* snippet: `{#snippet actions()}<MyPicker />{/snippet}` keeps working. Outside
|
|
32
|
+
* a TopNavbar, in the bar and in `menu="inline"` it is `"bar"`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getTopNavbarPlace(): TopNavbarPlace;
|