@zeyos/zx 4.4.0 → 5.2.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 +447 -0
- package/README.md +44 -11
- package/dist/types/components/account-menu/account-menu.d.ts +8 -0
- package/dist/types/components/activity-list/activity-list.d.ts +31 -1
- package/dist/types/components/app-icon/app-icon.d.ts +14 -2
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +293 -17
- package/dist/types/components/avatar/avatar.d.ts +46 -7
- package/dist/types/components/badge/badge.d.ts +21 -1
- package/dist/types/components/button/button.d.ts +15 -0
- package/dist/types/components/calendar/calendar.d.ts +67 -5
- package/dist/types/components/card-view/card-view.d.ts +7 -4
- package/dist/types/components/card-view/record-card.d.ts +11 -0
- package/dist/types/components/chart/chart.d.ts +10 -0
- package/dist/types/components/checklist/checklist.d.ts +9 -0
- package/dist/types/components/code-editor/code-editor.d.ts +6 -17
- package/dist/types/components/context-menu/context-menu.d.ts +6 -2
- package/dist/types/components/data-filter/data-filter.d.ts +4 -4
- package/dist/types/components/datebox/date-range-box.d.ts +5 -1
- package/dist/types/components/datebox/datebox.d.ts +7 -2
- package/dist/types/components/dialog/dialog.d.ts +46 -6
- package/dist/types/components/dropdown/dropdown.d.ts +24 -9
- package/dist/types/components/entity-ref/entity-ref.d.ts +2 -2
- package/dist/types/components/field/field.d.ts +54 -2
- package/dist/types/components/fieldset/external-adapter.d.ts +83 -0
- package/dist/types/components/fieldset/fieldset.d.ts +26 -1
- package/dist/types/components/file-item/file-item.d.ts +34 -6
- package/dist/types/components/file-list/file-list.d.ts +35 -3
- package/dist/types/components/filter/filter.d.ts +127 -3
- package/dist/types/components/filter-panel/filter-panel.d.ts +36 -7
- package/dist/types/components/form/form.d.ts +12 -1
- package/dist/types/components/kanban-view/kanban-card.d.ts +8 -0
- package/dist/types/components/launcher/launcher.d.ts +268 -15
- package/dist/types/components/list-toolbar/list-toolbar.d.ts +16 -7
- package/dist/types/components/menu-button/menu-button.d.ts +39 -5
- package/dist/types/components/message/message.d.ts +132 -17
- package/dist/types/components/modal/modal.d.ts +43 -2
- package/dist/types/components/module-bar/module-bar.d.ts +395 -0
- package/dist/types/components/pagination/pagination.d.ts +17 -3
- package/dist/types/components/select/color.d.ts +12 -0
- package/dist/types/components/select/glyphs.d.ts +124 -0
- package/dist/types/components/select/select.d.ts +17 -3
- package/dist/types/components/sheet/sheet.d.ts +15 -0
- package/dist/types/components/stat-tile/stat-tile.d.ts +16 -5
- package/dist/types/components/tabbox/tabbox.d.ts +9 -0
- package/dist/types/components/table/table.d.ts +311 -18
- package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
- package/dist/types/components/tree/tree.d.ts +13 -0
- package/dist/types/components/view/record-view.d.ts +59 -2
- package/dist/types/components/view-settings/view-settings.d.ts +879 -0
- package/dist/types/components/workspace-switcher/workspace-switcher.d.ts +327 -0
- package/dist/types/core/breakpoint.d.ts +1 -1
- package/dist/types/core/component.d.ts +12 -2
- package/dist/types/core/i18n.d.ts +27 -3
- package/dist/types/core/icons.d.ts +23 -25
- package/dist/types/core/keyboard.d.ts +98 -1
- package/dist/types/core/messages.d.ts +382 -0
- package/dist/types/core/motion.d.ts +59 -0
- package/dist/types/core/overlay-host.d.ts +42 -1
- package/dist/types/core/overlay-stack.d.ts +260 -0
- package/dist/types/index.d.ts +10 -5
- package/dist/types/internal/app-rail.d.ts +38 -0
- package/dist/types/internal/menu-items.d.ts +71 -3
- package/dist/types/zeyos/connect.d.ts +6 -0
- package/dist/types/zeyos/form.d.ts +50 -9
- package/dist/types/zeyos/index.d.ts +2 -1
- package/dist/types/zeyos/launcher.d.ts +9 -0
- package/dist/types/zeyos/module-bar.d.ts +176 -0
- package/dist/types/zeyos/query.d.ts +110 -5
- package/dist/types/zeyos/schema.d.ts +24 -1
- package/dist/types/zeyos/table.d.ts +345 -22
- package/dist/types/zeyos/view.d.ts +26 -2
- package/dist/zx-zeyos.esm.js +6178 -1008
- package/dist/zx-zeyos.esm.min.js +53 -5
- package/dist/zx.css +2861 -827
- package/dist/zx.esm.js +8909 -1480
- package/dist/zx.esm.min.js +55 -7
- package/dist/zx.global.js +8911 -1482
- package/dist/zx.global.min.js +55 -7
- package/dist/zx.min.css +1 -1
- package/docs/api.json +1079 -117
- package/docs/llms.md +956 -154
- package/docs/llms.txt +9 -5
- package/package.json +1 -1
- package/src/components/account-menu/account-menu.css +35 -10
- package/src/components/account-menu/account-menu.js +12 -0
- package/src/components/activity-item/activity-item.css +1 -1
- package/src/components/activity-item/activity-item.js +3 -0
- package/src/components/activity-list/activity-list.css +38 -3
- package/src/components/activity-list/activity-list.js +61 -1
- package/src/components/app-icon/app-icon.css +14 -3
- package/src/components/app-icon/app-icon.js +11 -1
- package/src/components/app-rail/app-rail.css +59 -79
- package/src/components/app-sidebar/app-sidebar.css +304 -168
- package/src/components/app-sidebar/app-sidebar.js +667 -46
- package/src/components/aurora/aurora.css +5 -3
- package/src/components/avatar/avatar.css +32 -7
- package/src/components/avatar/avatar.js +72 -8
- package/src/components/badge/badge.css +54 -2
- package/src/components/badge/badge.js +29 -2
- package/src/components/breadcrumb/breadcrumb.css +1 -1
- package/src/components/button/button.css +67 -9
- package/src/components/button/button.js +38 -0
- package/src/components/calendar/calendar.css +47 -47
- package/src/components/calendar/calendar.js +26 -52
- package/src/components/card/card.css +4 -4
- package/src/components/card-view/card-view.css +14 -12
- package/src/components/card-view/card-view.js +13 -9
- package/src/components/card-view/record-card.js +14 -3
- package/src/components/chart/chart.css +4 -1
- package/src/components/chart/chart.js +12 -0
- package/src/components/check-button/check-button.css +10 -2
- package/src/components/checklist/checklist.css +9 -2
- package/src/components/checklist/checklist.js +11 -5
- package/src/components/checklist/field-adapter.js +3 -1
- package/src/components/code-editor/code-editor.css +7 -6
- package/src/components/code-editor/code-editor.js +19 -1
- package/src/components/code-editor/field-adapter.js +9 -2
- package/src/components/context-menu/context-menu.css +9 -4
- package/src/components/context-menu/context-menu.js +8 -2
- package/src/components/copy/copy-button.css +6 -0
- package/src/components/copy/copy-input.css +9 -4
- package/src/components/data-filter/data-filter.css +5 -4
- package/src/components/data-filter/data-filter.js +2 -2
- package/src/components/date-picker/date-picker.css +6 -6
- package/src/components/date-picker/date-range-picker.css +3 -3
- package/src/components/date-picker/time-picker.css +3 -3
- package/src/components/datebox/date-range-box.css +79 -2
- package/src/components/datebox/date-range-box.js +13 -2
- package/src/components/datebox/datebox.css +79 -2
- package/src/components/datebox/datebox.js +13 -2
- package/src/components/dialog/dialog.css +26 -0
- package/src/components/dialog/dialog.js +99 -17
- package/src/components/dock/dock.css +5 -4
- package/src/components/dropdown/dropdown.css +1 -1
- package/src/components/dropdown/dropdown.js +159 -72
- package/src/components/empty-state/empty-state.css +2 -2
- package/src/components/entity-ref/entity-ref.css +1 -1
- package/src/components/entity-ref/entity-ref.js +1 -1
- package/src/components/field/field.css +28 -9
- package/src/components/field/field.js +101 -29
- package/src/components/field-upload/field-upload.css +1 -1
- package/src/components/fieldset/external-adapter.js +363 -0
- package/src/components/fieldset/fieldset.css +5 -3
- package/src/components/fieldset/fieldset.js +38 -64
- package/src/components/file-item/file-item.js +144 -10
- package/src/components/file-list/file-list.js +144 -21
- package/src/components/filter/filter.css +86 -2
- package/src/components/filter/filter.js +307 -11
- package/src/components/filter-panel/filter-panel.css +1 -1
- package/src/components/filter-panel/filter-panel.js +149 -32
- package/src/components/finder/finder.css +16 -14
- package/src/components/form/form.css +5 -4
- package/src/components/form/form.js +11 -2
- package/src/components/grid/grid.css +1 -1
- package/src/components/groupbox/groupbox.css +1 -1
- package/src/components/kanban-view/kanban-card.js +11 -3
- package/src/components/kanban-view/kanban-view.css +42 -18
- package/src/components/kanban-view/kanban-view.js +9 -6
- package/src/components/launcher/launcher.css +108 -26
- package/src/components/launcher/launcher.js +530 -42
- package/src/components/list-toolbar/list-toolbar.css +13 -8
- package/src/components/list-toolbar/list-toolbar.js +40 -14
- package/src/components/master-panel/master-panel.css +2 -0
- package/src/components/master-panel/master-panel.js +0 -1
- package/src/components/menu-button/menu-button.css +72 -4
- package/src/components/menu-button/menu-button.js +160 -18
- package/src/components/message/message.css +33 -3
- package/src/components/message/message.js +264 -22
- package/src/components/modal/modal.css +1 -1
- package/src/components/modal/modal.js +83 -15
- package/src/components/module-bar/module-bar.css +486 -0
- package/src/components/module-bar/module-bar.js +1074 -0
- package/src/components/multi-value-editor/multi-value-editor.css +1 -1
- package/src/components/navigation-bar/navigation-bar.css +18 -9
- package/src/components/number-field/field-adapter.js +3 -1
- package/src/components/number-field/number-field.css +70 -0
- package/src/components/number-field/number-field.js +2 -0
- package/src/components/pagination/pagination.css +13 -11
- package/src/components/pagination/pagination.js +20 -4
- package/src/components/panel/panel.css +4 -3
- package/src/components/questionnaire/questionnaire.css +4 -4
- package/src/components/rating/field-adapter.js +2 -1
- package/src/components/rating/rating.css +8 -1
- package/src/components/search/search.css +4 -1
- package/src/components/select/color.js +22 -0
- package/src/components/select/field-adapter.js +2 -0
- package/src/components/select/glyphs.js +165 -0
- package/src/components/select/select.css +138 -45
- package/src/components/select/select.js +92 -47
- package/src/components/sheet/sheet.css +12 -9
- package/src/components/sheet/sheet.js +74 -32
- package/src/components/slider/field-adapter.js +6 -1
- package/src/components/slider/slider.css +18 -4
- package/src/components/slider/slider.js +1 -1
- package/src/components/sort-control/sort-control.css +4 -1
- package/src/components/split-view/split-view.css +1 -1
- package/src/components/stat-tile/stat-tile.css +42 -47
- package/src/components/stat-tile/stat-tile.js +17 -6
- package/src/components/stepper/stepper.css +1 -1
- package/src/components/tabbox/tabbox.css +41 -32
- package/src/components/tabbox/tabbox.js +5 -1
- package/src/components/table/table.css +187 -37
- package/src/components/table/table.js +718 -73
- package/src/components/tag-picker/field-adapter.js +2 -1
- package/src/components/tag-picker/tag-picker.css +83 -5
- package/src/components/tag-picker/tag-picker.js +25 -8
- package/src/components/timebox/timebox.css +22 -2
- package/src/components/toggle/toggle.css +12 -1
- package/src/components/toolbar/toolbar.js +2 -2
- package/src/components/tooltip/tooltip.css +4 -0
- package/src/components/tooltip/tooltip.js +44 -19
- package/src/components/tree/tree.css +26 -17
- package/src/components/tree/tree.js +31 -0
- package/src/components/value-list/value-list.css +3 -3
- package/src/components/view/record-view.css +1 -1
- package/src/components/view/record-view.js +178 -18
- package/src/components/view-settings/view-settings.css +285 -0
- package/src/components/view-settings/view-settings.js +1830 -0
- package/src/components/workspace-switcher/workspace-switcher.css +188 -0
- package/src/components/workspace-switcher/workspace-switcher.js +463 -0
- package/src/core/breakpoint.js +1 -1
- package/src/core/component.js +88 -17
- package/src/core/fontawesome.js +5 -1
- package/src/core/i18n.js +47 -4
- package/src/core/icons.js +204 -61
- package/src/core/keyboard.js +206 -11
- package/src/core/messages.js +427 -0
- package/src/core/motion.js +147 -0
- package/src/core/overlay-host.js +77 -3
- package/src/core/overlay-stack.js +487 -0
- package/src/core/position.js +22 -3
- package/src/index.js +10 -5
- package/src/internal/app-rail.js +135 -55
- package/src/internal/menu-items.js +61 -3
- package/src/zeyos/connect.js +6 -2
- package/src/zeyos/form.js +69 -27
- package/src/zeyos/index.js +4 -1
- package/src/zeyos/launcher.js +8 -3
- package/src/zeyos/module-bar.js +181 -0
- package/src/zeyos/query.js +197 -13
- package/src/zeyos/schema.js +144 -27
- package/src/zeyos/table.js +287 -84
- package/src/zeyos/view.js +49 -93
- package/styles/base.css +72 -1
- package/styles/icons.css +11 -1
- package/styles/print.css +1 -1
- package/styles/tokens/dark.css +117 -37
- package/styles/tokens/global.css +121 -27
- package/styles/tokens/semantic.css +140 -43
- package/styles/tokens/themes.css +7 -6
- package/styles/zx.css +3 -1
- package/styles/tokens/density.css +0 -9
|
@@ -68,10 +68,11 @@
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/*
|
|
71
|
-
* WCAG 2.5.5 Target Size: 44 px,
|
|
72
|
-
* a finger smaller, so this floor is deliberately not expressed through
|
|
73
|
-
* (
|
|
74
|
-
* until the tool is hovered, focused, or pressed, so a target this size does not
|
|
71
|
+
* WCAG 2.5.5 Target Size: 44 px, whatever the theme does to spacing. A tighter theme tightens a
|
|
72
|
+
* layout; it does not make a finger smaller, so this floor is deliberately not expressed through
|
|
73
|
+
* --zx-control-height (30 px by default) the way the rest of the controls in this row are. The
|
|
74
|
+
* chrome is flat until the tool is hovered, focused, or pressed, so a target this size does not
|
|
75
|
+
* read as a slab.
|
|
75
76
|
*/
|
|
76
77
|
.zx-list-toolbar__tool {
|
|
77
78
|
position: relative;
|
|
@@ -100,11 +101,12 @@
|
|
|
100
101
|
box-shadow: var(--zx-focus-ring);
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
/* A pressed tool is marked three ways — the state in `aria-pressed`,
|
|
104
|
-
border where the other states have none — so it never rests on colour alone.
|
|
104
|
+
/* A pressed tool is marked three ways — the state in `aria-pressed`, the neutral current fill, and
|
|
105
|
+
a border where the other states have none — so it never rests on colour alone. A toggled filter
|
|
106
|
+
is a mode the list is in, not a pick, so the fill is neutral rather than the accent tint. */
|
|
105
107
|
.zx-list-toolbar__tool[aria-pressed="true"] {
|
|
106
108
|
border-color: var(--zx-color-border-strong);
|
|
107
|
-
background: var(--zx-color-bg-
|
|
109
|
+
background: var(--zx-color-bg-current);
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
.zx-list-toolbar__tool:disabled {
|
|
@@ -150,9 +152,12 @@
|
|
|
150
152
|
box-shadow: var(--zx-focus-ring);
|
|
151
153
|
}
|
|
152
154
|
|
|
155
|
+
/* The current view is where you are: the neutral current fill, the full text colour, a heavier
|
|
156
|
+
label. Hover stays a lighter step of the same fill. */
|
|
153
157
|
.zx-list-toolbar__view[aria-pressed="true"] {
|
|
154
|
-
background: var(--zx-color-bg-
|
|
158
|
+
background: var(--zx-color-bg-current);
|
|
155
159
|
color: var(--zx-color-text);
|
|
160
|
+
font-weight: var(--zx-weight-semibold);
|
|
156
161
|
}
|
|
157
162
|
|
|
158
163
|
@media (prefers-reduced-motion: no-preference) {
|
|
@@ -7,8 +7,10 @@ import { badge } from '../badge/badge.js';
|
|
|
7
7
|
import { Search } from '../search/search.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Built-in view shapes, so `views: ['
|
|
11
|
-
* that wants other words hands descriptors instead.
|
|
10
|
+
* Built-in view shapes, so `views: ['table', 'list', 'cards', 'gallery', 'board']` needs no labels
|
|
11
|
+
* of its own. A host that wants other words hands descriptors instead. Each shape has the glyph
|
|
12
|
+
* the overview tools people already use give it: a ruled grid for the table, lines for the list,
|
|
13
|
+
* tiles for cards, a picture for the gallery, and columns for the board.
|
|
12
14
|
*
|
|
13
15
|
* `auto` deliberately carries no icon: no glyph in the set says "whatever fits", and an icon that
|
|
14
16
|
* has to be explained is worse than the word.
|
|
@@ -16,9 +18,11 @@ import { Search } from '../search/search.js';
|
|
|
16
18
|
*/
|
|
17
19
|
const VIEW_PRESETS = Object.freeze({
|
|
18
20
|
auto: { icon: null, key: 'listToolbar.viewAuto', fallback: 'Auto' },
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
table: { icon: 'table', key: 'listToolbar.viewTable', fallback: 'Table' },
|
|
22
|
+
list: { icon: 'list', key: 'listToolbar.viewList', fallback: 'List' },
|
|
23
|
+
cards: { icon: 'grid', key: 'listToolbar.viewCards', fallback: 'Cards' },
|
|
24
|
+
gallery: { icon: 'image', key: 'listToolbar.viewGallery', fallback: 'Gallery' },
|
|
25
|
+
board: { icon: 'columns', key: 'listToolbar.viewBoard', fallback: 'Board' }
|
|
22
26
|
});
|
|
23
27
|
|
|
24
28
|
/**
|
|
@@ -88,7 +92,7 @@ const VIEW_PRESETS = Object.freeze({
|
|
|
88
92
|
* control, rather than found separately beside it.
|
|
89
93
|
* - **The count line is a live region**, and `setCount(null)` clears it. The previous number
|
|
90
94
|
* describes the previous filters, and a stale count during a load is worse than none.
|
|
91
|
-
* - **Tool buttons meet the 44 px target**
|
|
95
|
+
* - **Tool buttons meet the 44 px target** at any spacing. An icon button sized to its glyph is
|
|
92
96
|
* the control people miss twice before hitting once.
|
|
93
97
|
*
|
|
94
98
|
* `bind()` is optional sugar; the toolbar is fully functional without it.
|
|
@@ -378,8 +382,10 @@ export class ListToolbar extends Component {
|
|
|
378
382
|
}
|
|
379
383
|
|
|
380
384
|
/**
|
|
381
|
-
* Optional sugar for a `TableView` or `
|
|
382
|
-
* mounts the view's field chooser into this bar
|
|
385
|
+
* Optional sugar for a `TableView`, `CardView`, or `KanbanView`: it wires the shape switch to
|
|
386
|
+
* the view and mounts the view's field chooser into this bar — through the view's public
|
|
387
|
+
* `mountFieldControls()` when it has one, which every `RecordView` does. A `TableView` with the
|
|
388
|
+
* default `fieldControls: true` contributes its Table's column chooser.
|
|
383
389
|
*
|
|
384
390
|
* Nothing is imported and nothing is required — every capability is feature-detected, so a view
|
|
385
391
|
* that offers none of them still works and still gets `viewchange`, and the host swaps the view
|
|
@@ -409,10 +415,19 @@ export class ListToolbar extends Component {
|
|
|
409
415
|
* @returns {this}
|
|
410
416
|
*/
|
|
411
417
|
unbind() {
|
|
418
|
+
const bound = this._bound;
|
|
412
419
|
this._bound = null;
|
|
413
420
|
if (this._chooser) {
|
|
414
|
-
|
|
421
|
+
const chooser = this._chooser;
|
|
415
422
|
this._chooser = null;
|
|
423
|
+
const view = bound?.view;
|
|
424
|
+
// The view put it here and knows where it came from; hand it back only while it is still
|
|
425
|
+
// ours, since a destroyed view has already taken it away.
|
|
426
|
+
if (chooser.viaView && view && typeof view.mountFieldControls === 'function') {
|
|
427
|
+
if (this.el.contains(chooser.element)) view.mountFieldControls(null);
|
|
428
|
+
} else {
|
|
429
|
+
restorePosition(chooser, this.el);
|
|
430
|
+
}
|
|
416
431
|
}
|
|
417
432
|
return this;
|
|
418
433
|
}
|
|
@@ -597,21 +612,28 @@ export class ListToolbar extends Component {
|
|
|
597
612
|
if (view.options?.fieldControls === false) return;
|
|
598
613
|
const element = this._findFieldControls(view);
|
|
599
614
|
if (!element || this.el.contains(element)) return;
|
|
615
|
+
if (typeof view.mountFieldControls === 'function') {
|
|
616
|
+
const mounted = view.mountFieldControls(this.refs.tools);
|
|
617
|
+
if (isElement(mounted) && this.el.contains(mounted)) {
|
|
618
|
+
this._chooser = { element: mounted, parent: null, next: null, viaView: true };
|
|
619
|
+
}
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
600
622
|
this._chooser = capturePosition(element);
|
|
601
623
|
this.refs.tools.append(element);
|
|
602
624
|
}
|
|
603
625
|
|
|
604
626
|
/**
|
|
605
|
-
* Finds a view's field chooser without importing the view
|
|
627
|
+
* Finds a view's field chooser without importing the view: its public `getFieldControls()`,
|
|
628
|
+
* else — for an object that only looks like a record view — the shared chooser's class.
|
|
606
629
|
* @param {Record<string, any>} view Bound view.
|
|
607
630
|
* @returns {Element|null}
|
|
608
631
|
*/
|
|
609
632
|
_findFieldControls(view) {
|
|
610
633
|
if (typeof view.getFieldControls === 'function') {
|
|
611
634
|
const element = view.getFieldControls();
|
|
612
|
-
|
|
635
|
+
return isElement(element) ? element : null;
|
|
613
636
|
}
|
|
614
|
-
if (isElement(view._viewFieldControls)) return view._viewFieldControls;
|
|
615
637
|
const root = typeof view.toElement === 'function' ? view.toElement() : null;
|
|
616
638
|
return isElement(root) ? root.querySelector('.zx-record-view__field-controls') : null;
|
|
617
639
|
}
|
|
@@ -661,7 +683,10 @@ export class ListToolbar extends Component {
|
|
|
661
683
|
* @property {((event: MouseEvent) => void)|null} onclick Descriptor callback.
|
|
662
684
|
*/
|
|
663
685
|
|
|
664
|
-
/**
|
|
686
|
+
/**
|
|
687
|
+
* @typedef {{element: Element, parent: Node|null, next: Node|null, viaView?: boolean}} AdoptedElement
|
|
688
|
+
* `viaView` marks a field chooser the bound view moved here itself and takes back on `unbind()`.
|
|
689
|
+
*/
|
|
665
690
|
|
|
666
691
|
/**
|
|
667
692
|
* @typedef {Object} RecordViewCapabilities
|
|
@@ -669,7 +694,8 @@ export class ListToolbar extends Component {
|
|
|
669
694
|
* @property {string|null} setShape Method that changes the view shape, if any.
|
|
670
695
|
* @property {string|null} getShape Method that reads the view shape, if any.
|
|
671
696
|
* @property {boolean} events Whether the object emits component events.
|
|
672
|
-
* @property {boolean} fieldControls Whether the object offers its field chooser through a method
|
|
697
|
+
* @property {boolean} fieldControls Whether the object offers its field chooser through a method
|
|
698
|
+
* (`getFieldControls()`, which every `RecordView` has).
|
|
673
699
|
*/
|
|
674
700
|
|
|
675
701
|
/**
|
|
@@ -89,7 +89,6 @@ export class MasterPanel extends Component {
|
|
|
89
89
|
|
|
90
90
|
const moduleName = normalizeModule(this.options.module);
|
|
91
91
|
root.dataset.module = moduleName;
|
|
92
|
-
root.style.setProperty('--zx-master-panel-accent', `var(--zx-module-${moduleName})`);
|
|
93
92
|
this.setTitle(this.options.title);
|
|
94
93
|
if (this.options.content !== null) this.setContent(this.options.content);
|
|
95
94
|
else content.append(...originalContent);
|
|
@@ -16,12 +16,16 @@
|
|
|
16
16
|
align-items: center;
|
|
17
17
|
gap: var(--zx-space-2);
|
|
18
18
|
inline-size: 100%;
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
/* A menu row sits a little under a control so a list of them reads lighter than a column of
|
|
20
|
+
fields; the radius is the smallest step, inset inside the panel's own. */
|
|
21
|
+
min-block-size: calc(var(--zx-control-height) - 2px);
|
|
22
|
+
border: 0;
|
|
23
|
+
border-radius: var(--zx-radius-sm);
|
|
21
24
|
background: transparent;
|
|
22
|
-
padding
|
|
25
|
+
padding: var(--zx-space-1) var(--zx-space-2);
|
|
23
26
|
color: inherit;
|
|
24
27
|
font: inherit;
|
|
28
|
+
line-height: 1.35;
|
|
25
29
|
text-align: start;
|
|
26
30
|
text-decoration: none;
|
|
27
31
|
}
|
|
@@ -112,8 +116,9 @@
|
|
|
112
116
|
box-shadow: inset 0 0 0 2px var(--zx-color-bg-raised);
|
|
113
117
|
}
|
|
114
118
|
|
|
119
|
+
/* A section label is a caption, never taller than the rows it introduces. */
|
|
115
120
|
.zx-menu-button__heading {
|
|
116
|
-
padding: var(--zx-space-
|
|
121
|
+
padding: var(--zx-space-1) var(--zx-space-2);
|
|
117
122
|
color: var(--zx-color-text-muted);
|
|
118
123
|
font-size: var(--zx-text-xs);
|
|
119
124
|
font-weight: 600;
|
|
@@ -124,3 +129,66 @@
|
|
|
124
129
|
margin: var(--zx-space-1) var(--zx-space-2);
|
|
125
130
|
background: var(--zx-color-overlay-divider);
|
|
126
131
|
}
|
|
132
|
+
|
|
133
|
+
/*
|
|
134
|
+
* A choice: a setting chosen in place — its label, then a small segmented control of options, the
|
|
135
|
+
* checked one lifted out of the track. Neutral, not the accent: it is a state, not an action.
|
|
136
|
+
*/
|
|
137
|
+
.zx-menu-button__choice {
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
justify-content: space-between;
|
|
141
|
+
gap: var(--zx-space-3);
|
|
142
|
+
min-block-size: var(--zx-control-height);
|
|
143
|
+
padding: var(--zx-space-1) var(--zx-space-2);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.zx-menu-button__choice-label {
|
|
147
|
+
color: var(--zx-color-text);
|
|
148
|
+
font-size: var(--zx-text-md);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.zx-menu-button__choice-options {
|
|
152
|
+
display: inline-flex;
|
|
153
|
+
flex: none;
|
|
154
|
+
gap: 2px;
|
|
155
|
+
border-radius: var(--zx-radius-md);
|
|
156
|
+
background: var(--zx-color-bg-hover);
|
|
157
|
+
padding: 2px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.zx-menu-button__choice-option {
|
|
161
|
+
display: grid;
|
|
162
|
+
place-items: center;
|
|
163
|
+
inline-size: var(--zx-space-6);
|
|
164
|
+
block-size: var(--zx-space-6);
|
|
165
|
+
border: 0;
|
|
166
|
+
border-radius: var(--zx-radius-sm);
|
|
167
|
+
background: transparent;
|
|
168
|
+
padding: 0;
|
|
169
|
+
color: var(--zx-color-text-muted);
|
|
170
|
+
cursor: pointer;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.zx-menu-button__choice-option:hover { color: var(--zx-color-text); }
|
|
174
|
+
|
|
175
|
+
.zx-menu-button__choice-option[aria-checked="true"] {
|
|
176
|
+
background: var(--zx-color-bg-surface);
|
|
177
|
+
box-shadow: 0 0 0 1px var(--zx-color-border-subtle);
|
|
178
|
+
color: var(--zx-color-text);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.zx-menu-button__choice-option[aria-disabled="true"] { cursor: not-allowed; opacity: .55; }
|
|
182
|
+
|
|
183
|
+
.zx-menu-button__choice-option:focus-visible {
|
|
184
|
+
outline-color: transparent;
|
|
185
|
+
box-shadow: var(--zx-focus-ring);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@media (forced-colors: active) {
|
|
189
|
+
.zx-menu-button__choice-option[aria-checked="true"] {
|
|
190
|
+
background: Highlight;
|
|
191
|
+
color: HighlightText;
|
|
192
|
+
forced-color-adjust: none;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Component } from '../../core/component.js';
|
|
2
2
|
import { h, resolveElement } from '../../core/dom.js';
|
|
3
|
+
import { uid } from '../../core/util.js';
|
|
3
4
|
import { icon } from '../../core/icons.js';
|
|
4
5
|
import { rovingTabindex, typeahead } from '../../core/keyboard.js';
|
|
5
6
|
import {
|
|
@@ -23,8 +24,19 @@ import { Dropdown } from '../dropdown/dropdown.js';
|
|
|
23
24
|
* @property {(event: CustomEvent<Record<string, never>>) => void} [onclose] Close event listener.
|
|
24
25
|
*/
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {Object} MenuButtonOpenOptions
|
|
29
|
+
* @property {boolean|HTMLElement} [returnFocus=true] Where focus goes when this opening ends with a
|
|
30
|
+
* selection or Escape: `true` is the trigger, an element is that element (the trigger again if
|
|
31
|
+
* it has left the document), and `false` leaves focus alone.
|
|
32
|
+
*/
|
|
33
|
+
|
|
26
34
|
/**
|
|
27
35
|
* APG menu-button trigger backed by an anchored Dropdown.
|
|
36
|
+
*
|
|
37
|
+
* Choosing an item closes the menu and puts focus back on the trigger before the `select` event
|
|
38
|
+
* and the item's `onselect` run, so whatever they open — a dialog, a sheet — records the trigger
|
|
39
|
+
* as the place to return to, and focus they move themselves stays where they put it.
|
|
28
40
|
* @fires MenuButton#select
|
|
29
41
|
* @fires MenuButton#open
|
|
30
42
|
* @fires MenuButton#close
|
|
@@ -54,6 +66,8 @@ export class MenuButton extends Component {
|
|
|
54
66
|
#items = [];
|
|
55
67
|
/** @type {{attributes: Array<[string, string]>, children: Node[]}|null} */
|
|
56
68
|
#original = null;
|
|
69
|
+
/** @type {boolean|HTMLElement} Focus destination for the current opening. */
|
|
70
|
+
#returnFocus = true;
|
|
57
71
|
#created = false;
|
|
58
72
|
#destroyed = false;
|
|
59
73
|
|
|
@@ -87,8 +101,15 @@ export class MenuButton extends Component {
|
|
|
87
101
|
content: null
|
|
88
102
|
});
|
|
89
103
|
this.#panel = this.#dropdown.getPanel();
|
|
104
|
+
// Escape reaches the menu through the overlay stack, which offers it only to the topmost layer;
|
|
105
|
+
// the menu decides where focus goes. Items are reached with the arrow keys, not Tab.
|
|
106
|
+
this.#dropdown._configure({ onEscape: () => this.#dismiss(), tabReach: false });
|
|
90
107
|
this.#panel.classList.add('zx-menu-button__menu');
|
|
91
108
|
this.#panel.setAttribute('role', 'menu');
|
|
109
|
+
// The menu is named by its trigger, as the APG menu-button pattern has it. An id given here to
|
|
110
|
+
// an enhanced trigger goes again on destroy(), with the rest of the attributes it gained.
|
|
111
|
+
if (!this.el.id) this.el.id = uid('zx-menu-button');
|
|
112
|
+
this.#panel.setAttribute('aria-labelledby', this.el.id);
|
|
92
113
|
this.el.setAttribute('aria-haspopup', 'menu');
|
|
93
114
|
if (existing && hasExplicitLabel) this.setLabel(this.options.label);
|
|
94
115
|
|
|
@@ -100,12 +121,15 @@ export class MenuButton extends Component {
|
|
|
100
121
|
);
|
|
101
122
|
|
|
102
123
|
this.#dropdown.on('open', () => this.emit('open'));
|
|
103
|
-
this.#dropdown.on('close', () =>
|
|
124
|
+
this.#dropdown.on('close', () => {
|
|
125
|
+
this.#returnFocus = true;
|
|
126
|
+
this.emit('close');
|
|
127
|
+
});
|
|
104
128
|
this.listen(this.el, 'click', () => {
|
|
105
129
|
if (this.isOpen()) this.close();
|
|
106
130
|
else {
|
|
107
131
|
this.open();
|
|
108
|
-
this.#
|
|
132
|
+
this.#focusFirstItem();
|
|
109
133
|
}
|
|
110
134
|
});
|
|
111
135
|
this.listen(this.el, 'keydown', (event) => this.#onTriggerKeydown(event));
|
|
@@ -114,6 +138,9 @@ export class MenuButton extends Component {
|
|
|
114
138
|
if (item && this.#panel.contains(item)) this.#selectElement(item, event);
|
|
115
139
|
});
|
|
116
140
|
this.listen(this.#panel, 'keydown', (event) => this.#onMenuKeydown(event));
|
|
141
|
+
// A choice row runs across, not down: Left and Right move within it, Up and Down leave it.
|
|
142
|
+
// Capture, so the menu's own roving focus never sees the keys this takes.
|
|
143
|
+
this.listen(this.#panel, 'keydown', (event) => this.#onChoiceKeydown(event), { capture: true });
|
|
117
144
|
}
|
|
118
145
|
|
|
119
146
|
/**
|
|
@@ -137,15 +164,18 @@ export class MenuButton extends Component {
|
|
|
137
164
|
*/
|
|
138
165
|
setItems(items) {
|
|
139
166
|
this.#items = Array.isArray(items) ? items.slice() : [];
|
|
140
|
-
this.#panel.replaceChildren(...renderMenuEntries(this.#items, 'menu-button'));
|
|
167
|
+
this.#panel.replaceChildren(...renderMenuEntries(this.#items, 'menu-button', { choices: true }));
|
|
141
168
|
return this;
|
|
142
169
|
}
|
|
143
170
|
|
|
144
171
|
/**
|
|
145
172
|
* Opens the menu.
|
|
173
|
+
* @param {MenuButtonOpenOptions} [options={}] Where focus returns when this opening ends; ignored
|
|
174
|
+
* while the menu is already open.
|
|
146
175
|
* @returns {this}
|
|
147
176
|
*/
|
|
148
|
-
open() {
|
|
177
|
+
open(options = {}) {
|
|
178
|
+
if (!this.isOpen()) this.#returnFocus = options?.returnFocus ?? true;
|
|
149
179
|
this.#dropdown.open();
|
|
150
180
|
return this;
|
|
151
181
|
}
|
|
@@ -159,6 +189,15 @@ export class MenuButton extends Component {
|
|
|
159
189
|
return this;
|
|
160
190
|
}
|
|
161
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Opens the menu when it is closed, and closes it when it is open. Opening this way moves no
|
|
194
|
+
* focus; `focusFirst()` opens with focus on the first item.
|
|
195
|
+
* @returns {this}
|
|
196
|
+
*/
|
|
197
|
+
toggle() {
|
|
198
|
+
return this.isOpen() ? this.close() : this.open();
|
|
199
|
+
}
|
|
200
|
+
|
|
162
201
|
/**
|
|
163
202
|
* Reports whether the menu is open.
|
|
164
203
|
* @returns {boolean}
|
|
@@ -177,16 +216,24 @@ export class MenuButton extends Component {
|
|
|
177
216
|
return this.#panel;
|
|
178
217
|
}
|
|
179
218
|
|
|
180
|
-
/**
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
219
|
+
/**
|
|
220
|
+
* Opens the menu and focuses its first enabled item.
|
|
221
|
+
* @param {MenuButtonOpenOptions} [options={}] Passed to `open()`.
|
|
222
|
+
* @returns {this}
|
|
223
|
+
*/
|
|
224
|
+
focusFirst(options = {}) {
|
|
225
|
+
this.open(options);
|
|
226
|
+
this.#focusFirstItem();
|
|
184
227
|
return this;
|
|
185
228
|
}
|
|
186
229
|
|
|
187
|
-
/**
|
|
188
|
-
|
|
189
|
-
|
|
230
|
+
/**
|
|
231
|
+
* Opens the menu and focuses its last enabled item.
|
|
232
|
+
* @param {MenuButtonOpenOptions} [options={}] Passed to `open()`.
|
|
233
|
+
* @returns {this}
|
|
234
|
+
*/
|
|
235
|
+
focusLast(options = {}) {
|
|
236
|
+
this.open(options);
|
|
190
237
|
this.#roving.focusLast();
|
|
191
238
|
return this;
|
|
192
239
|
}
|
|
@@ -224,17 +271,11 @@ export class MenuButton extends Component {
|
|
|
224
271
|
event.preventDefault();
|
|
225
272
|
this.open();
|
|
226
273
|
if (event.key === 'ArrowUp') this.#roving.focusLast();
|
|
227
|
-
else this.#
|
|
274
|
+
else this.#focusFirstItem();
|
|
228
275
|
}
|
|
229
276
|
|
|
230
277
|
/** @param {KeyboardEvent} event @returns {void} */
|
|
231
278
|
#onMenuKeydown(event) {
|
|
232
|
-
if (event.key === 'Escape') {
|
|
233
|
-
event.preventDefault();
|
|
234
|
-
this.close();
|
|
235
|
-
this.el.focus();
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
279
|
if (event.key === 'Tab') {
|
|
239
280
|
this.close();
|
|
240
281
|
return;
|
|
@@ -250,17 +291,107 @@ export class MenuButton extends Component {
|
|
|
250
291
|
this.#typeahead(event);
|
|
251
292
|
}
|
|
252
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Escape, as the overlay stack routes it here: closes the menu, and returns focus when the menu
|
|
296
|
+
* held it (or it had already fallen to the body).
|
|
297
|
+
* @returns {void}
|
|
298
|
+
*/
|
|
299
|
+
#dismiss() {
|
|
300
|
+
const active = document.activeElement;
|
|
301
|
+
const reclaim = !active || active === document.body || this.#panel.contains(active);
|
|
302
|
+
const target = this.#returnTarget();
|
|
303
|
+
this.close();
|
|
304
|
+
if (reclaim) target?.focus();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Focuses the first item, or, when that is a choice row, its checked option. @returns {void} */
|
|
308
|
+
#focusFirstItem() {
|
|
309
|
+
this.#roving.focusFirst();
|
|
310
|
+
const first = this.#menuItems()[0];
|
|
311
|
+
const checked = first?.hasAttribute('data-menu-option')
|
|
312
|
+
? first.parentElement?.querySelector('[aria-checked="true"]') : null;
|
|
313
|
+
if (checked instanceof HTMLElement) checked.focus();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** @param {KeyboardEvent} event @returns {void} */
|
|
317
|
+
#onChoiceKeydown(event) {
|
|
318
|
+
const option = /** @type {Element|null} */ (event.target)?.closest?.('[data-menu-option]');
|
|
319
|
+
if (!(option instanceof HTMLElement) || !this.#panel.contains(option)) {
|
|
320
|
+
// Arriving at a choice row from another item lands on its checked option, not its nearest.
|
|
321
|
+
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;
|
|
322
|
+
const items = this.#menuItems();
|
|
323
|
+
const index = items.indexOf(/** @type {HTMLElement} */ (event.target));
|
|
324
|
+
if (index < 0) return;
|
|
325
|
+
const next = items[(index + (event.key === 'ArrowDown' ? 1 : -1) + items.length) % items.length];
|
|
326
|
+
const row = next?.hasAttribute('data-menu-option') ? next.parentElement : null;
|
|
327
|
+
if (!row) return;
|
|
328
|
+
event.preventDefault();
|
|
329
|
+
event.stopPropagation();
|
|
330
|
+
/** @type {HTMLElement} */ (row.querySelector('[aria-checked="true"]') ?? next).focus();
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const row = /** @type {HTMLElement[]} */ ([...(option.parentElement?.children ?? [])]);
|
|
334
|
+
const items = this.#menuItems();
|
|
335
|
+
let target = null;
|
|
336
|
+
if (event.key === 'ArrowLeft') target = row[(row.indexOf(option) - 1 + row.length) % row.length];
|
|
337
|
+
else if (event.key === 'ArrowRight') target = row[(row.indexOf(option) + 1) % row.length];
|
|
338
|
+
else if (event.key === 'ArrowDown') target = items[(items.indexOf(row.at(-1)) + 1) % items.length];
|
|
339
|
+
else if (event.key === 'ArrowUp') target = items[(items.indexOf(row[0]) - 1 + items.length) % items.length];
|
|
340
|
+
else return;
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
event.stopPropagation();
|
|
343
|
+
// Into another choice row, focus lands on its checked option.
|
|
344
|
+
const checked = target?.closest('[role="group"]')?.querySelector('[data-menu-option][aria-checked="true"]');
|
|
345
|
+
const next = event.key === 'ArrowDown' || event.key === 'ArrowUp' ? /** @type {HTMLElement} */ (checked ?? target) : target;
|
|
346
|
+
next?.focus();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** @param {HTMLElement} element @param {Record<string, any>} entry @param {Event|null} event @returns {void} */
|
|
350
|
+
#selectChoice(element, entry, event) {
|
|
351
|
+
const option = entry.options?.[Number(element.getAttribute('data-menu-option'))];
|
|
352
|
+
if (!option || option.disabled) {
|
|
353
|
+
event?.preventDefault();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
// A setting changes in place and the menu stays open, so its effect shows.
|
|
357
|
+
for (const sibling of element.parentElement?.children ?? []) {
|
|
358
|
+
sibling.setAttribute('aria-checked', String(sibling === element));
|
|
359
|
+
}
|
|
360
|
+
element.focus();
|
|
361
|
+
entry.onselect?.(option.value, entry, this);
|
|
362
|
+
// A handler that closed the menu leaves focus on a hidden option: it goes where a close sends it.
|
|
363
|
+
if (!this.isOpen() && (element.contains(document.activeElement) || document.activeElement === document.body)) {
|
|
364
|
+
this.#returnTarget()?.focus();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
253
368
|
/** @param {Element} element @param {Event|null} [event=null] @returns {void} */
|
|
254
369
|
#selectElement(element, event = null) {
|
|
255
370
|
const index = Number(element.getAttribute('data-menu-item'));
|
|
256
371
|
const item = this.#items[index];
|
|
372
|
+
if (item && typeof item === 'object' && item.type === 'choice') {
|
|
373
|
+
this.#selectChoice(/** @type {HTMLElement} */ (element), item, event);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
257
376
|
if (!isMenuItem(item) || item.disabled) {
|
|
258
377
|
event?.preventDefault();
|
|
259
378
|
return;
|
|
260
379
|
}
|
|
380
|
+
/*
|
|
381
|
+
* Focus goes back first, as the APG pattern has it, and only if it is still the menu's to give:
|
|
382
|
+
* inside the panel, or already lost to the body. A dialog opened by a listener below then
|
|
383
|
+
* records the trigger as its opener rather than an item that is about to be hidden, and focus
|
|
384
|
+
* a listener moves on purpose is left where it was put.
|
|
385
|
+
*/
|
|
386
|
+
const target = this.#returnTarget();
|
|
387
|
+
const active = document.activeElement;
|
|
388
|
+
const reclaim = Boolean(target) && (!active || active === document.body || this.#panel.contains(active));
|
|
389
|
+
if (reclaim) target?.focus();
|
|
261
390
|
const selected = this.emit('select', { value: item.value, item });
|
|
262
391
|
if (selected.defaultPrevented) {
|
|
263
392
|
event?.preventDefault();
|
|
393
|
+
// The menu stays open, so focus returns to the item, unless a listener has moved it again.
|
|
394
|
+
if (reclaim && document.activeElement === target && element instanceof HTMLElement) element.focus();
|
|
264
395
|
return;
|
|
265
396
|
}
|
|
266
397
|
try {
|
|
@@ -270,6 +401,17 @@ export class MenuButton extends Component {
|
|
|
270
401
|
}
|
|
271
402
|
}
|
|
272
403
|
|
|
404
|
+
/**
|
|
405
|
+
* The element focus returns to when the current opening ends, or null when it should stay put.
|
|
406
|
+
* @returns {HTMLElement|null}
|
|
407
|
+
*/
|
|
408
|
+
#returnTarget() {
|
|
409
|
+
const target = this.#returnFocus;
|
|
410
|
+
if (target === false) return null;
|
|
411
|
+
if (target instanceof HTMLElement && target.isConnected) return target;
|
|
412
|
+
return /** @type {HTMLElement} */ (this.el);
|
|
413
|
+
}
|
|
414
|
+
|
|
273
415
|
/** @returns {HTMLElement[]} */
|
|
274
416
|
#menuItems() {
|
|
275
417
|
return Array.from(this.#panel.querySelectorAll(MENU_ITEM_SELECTOR));
|
|
@@ -25,12 +25,30 @@
|
|
|
25
25
|
align-content: start;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/* On a phone the toasts rise from the foot of the screen, full width, where they cover content
|
|
29
|
+
rather than the header's controls, and sit where a thumb can reach their action. */
|
|
30
|
+
@media (max-width: 640px) {
|
|
31
|
+
.zx-message-region {
|
|
32
|
+
inset-block-start: auto;
|
|
33
|
+
inset-block-end: var(--zx-space-4);
|
|
34
|
+
inset-inline: var(--zx-space-4);
|
|
35
|
+
inline-size: auto;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.zx-message-region:popover-open {
|
|
39
|
+
align-content: end;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
.zx-message__toast {
|
|
29
44
|
--zx-message-kind: var(--zx-color-info);
|
|
30
45
|
--zx-message-fallback-bg: var(--zx-color-info-bg);
|
|
31
46
|
position: relative;
|
|
32
47
|
display: grid;
|
|
33
|
-
|
|
48
|
+
/* Icon and text, then whatever follows — the action, the close button — in columns of their own. */
|
|
49
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
50
|
+
grid-auto-columns: auto;
|
|
51
|
+
grid-auto-flow: column;
|
|
34
52
|
align-items: start;
|
|
35
53
|
gap: var(--zx-space-3);
|
|
36
54
|
border: 1px solid var(--zx-color-overlay-border);
|
|
@@ -64,8 +82,8 @@
|
|
|
64
82
|
background-clip: padding-box;
|
|
65
83
|
-webkit-backdrop-filter: var(--zx-glass-filter-overlay);
|
|
66
84
|
backdrop-filter: var(--zx-glass-filter-overlay);
|
|
67
|
-
|
|
68
|
-
|
|
85
|
+
/* No bar on the leading edge: the kind shows in the icon and the tinted border. */
|
|
86
|
+
box-shadow: var(--zx-glass-overlay-shadow);
|
|
69
87
|
}
|
|
70
88
|
}
|
|
71
89
|
|
|
@@ -90,6 +108,18 @@
|
|
|
90
108
|
overflow-wrap: anywhere;
|
|
91
109
|
}
|
|
92
110
|
|
|
111
|
+
/* A leading visual — an avatar, say — keeps its own size, centred on the first line of text. */
|
|
112
|
+
.zx-message__toast[data-leading] > .zx-message__icon {
|
|
113
|
+
margin-block: calc(-0.5 * var(--zx-space-1)) 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* The action sits level with the close button: both are pulled up the same step so their centres
|
|
117
|
+
meet the first line of text rather than hanging below it. */
|
|
118
|
+
.zx-message__action {
|
|
119
|
+
margin-block: calc(-1 * var(--zx-space-1)) 0;
|
|
120
|
+
white-space: nowrap;
|
|
121
|
+
}
|
|
122
|
+
|
|
93
123
|
/* Visual chrome comes from the shared .zx-icon-btn utility; only alignment is set here so
|
|
94
124
|
the close control sits flush with the trailing top corner without stretching the toast.
|
|
95
125
|
Trailing, not right: in an RTL document the close control belongs on the left, and a
|