@zeyos/zx 4.3.4 → 5.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 +438 -0
- package/README.md +49 -10
- package/dist/types/components/activity-item/activity-item.d.ts +351 -0
- package/dist/types/components/activity-list/activity-list.d.ts +217 -0
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +33 -3
- 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 +23 -0
- package/dist/types/components/calendar/calendar.d.ts +19 -0
- package/dist/types/components/card/card.d.ts +7 -0
- package/dist/types/components/card-view/card-view.d.ts +11 -3
- package/dist/types/components/card-view/record-card.d.ts +7 -1
- 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 +149 -0
- package/dist/types/components/code-editor/field-adapter.d.ts +5 -0
- package/dist/types/components/context-menu/context-menu.d.ts +18 -2
- package/dist/types/components/date-picker/date-picker.d.ts +8 -0
- package/dist/types/components/date-picker/time-picker.d.ts +7 -0
- 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 +75 -6
- package/dist/types/components/dropdown/dropdown.d.ts +2 -2
- package/dist/types/components/entity-ref/entity-ref.d.ts +331 -0
- package/dist/types/components/field/field.d.ts +24 -2
- package/dist/types/components/fieldset/fieldset.d.ts +19 -1
- package/dist/types/components/file-item/file-item.d.ts +262 -0
- package/dist/types/components/file-list/file-list.d.ts +171 -0
- package/dist/types/components/filter/filter.d.ts +91 -36
- package/dist/types/components/filter-panel/filter-panel.d.ts +449 -0
- package/dist/types/components/form/form.d.ts +12 -1
- package/dist/types/components/kanban-view/kanban-card.d.ts +146 -0
- package/dist/types/components/kanban-view/kanban-view.d.ts +170 -4
- package/dist/types/components/launcher/launcher.d.ts +278 -26
- package/dist/types/components/list-toolbar/list-toolbar.d.ts +599 -0
- package/dist/types/components/menu-button/menu-button.d.ts +37 -42
- package/dist/types/components/message/message.d.ts +126 -17
- package/dist/types/components/modal/modal.d.ts +61 -2
- package/dist/types/components/navigation-bar/navigation-bar.d.ts +105 -21
- package/dist/types/components/panel/panel.d.ts +7 -0
- package/dist/types/components/search/search.d.ts +7 -0
- 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 +15 -3
- package/dist/types/components/sort-control/sort-control.d.ts +105 -0
- package/dist/types/components/stat-tile/stat-tile.d.ts +288 -0
- package/dist/types/components/tabbox/tabbox.d.ts +16 -0
- package/dist/types/components/table/table.d.ts +190 -1
- package/dist/types/components/table-view/table-view.d.ts +11 -5
- package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
- package/dist/types/components/timebox/timebox.d.ts +7 -0
- package/dist/types/components/tree/tree.d.ts +13 -0
- package/dist/types/components/value-list/value-list.d.ts +8 -0
- package/dist/types/components/view/record-view.d.ts +96 -5
- package/dist/types/core/breakpoint.d.ts +1 -1
- package/dist/types/core/icons.d.ts +37 -22
- package/dist/types/core/motion.d.ts +59 -0
- package/dist/types/core/overlay-host.d.ts +15 -1
- package/dist/types/index.d.ts +16 -3
- package/dist/types/internal/app-rail.d.ts +10 -0
- package/dist/types/internal/menu-items.d.ts +134 -0
- 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 +1 -1
- package/dist/types/zeyos/query.d.ts +79 -0
- package/dist/types/zeyos/schema.d.ts +24 -1
- package/dist/types/zeyos/table.d.ts +100 -14
- package/dist/types/zeyos/view.d.ts +10 -0
- package/dist/zx-zeyos.esm.js +2206 -489
- package/dist/zx-zeyos.esm.min.js +56 -1
- package/dist/zx.css +3322 -724
- package/dist/zx.esm.js +12244 -5809
- package/dist/zx.esm.min.js +58 -3
- package/dist/zx.global.js +12246 -5811
- package/dist/zx.global.min.js +58 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +1683 -146
- package/docs/llms.md +777 -110
- package/docs/llms.txt +14 -8
- package/package.json +1 -1
- package/src/components/account-menu/account-menu.css +35 -10
- package/src/components/activity-item/activity-item.css +137 -0
- package/src/components/activity-item/activity-item.js +602 -0
- package/src/components/activity-list/activity-list.css +99 -0
- package/src/components/activity-list/activity-list.js +409 -0
- package/src/components/app-icon/app-icon.css +1 -1
- package/src/components/app-rail/app-rail.css +191 -22
- package/src/components/app-sidebar/app-sidebar.css +349 -24
- package/src/components/app-sidebar/app-sidebar.js +86 -11
- 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/button/button.css +74 -8
- package/src/components/button/button.js +44 -1
- package/src/components/calendar/calendar.css +46 -46
- package/src/components/calendar/calendar.js +33 -3
- package/src/components/card/card.css +2 -2
- package/src/components/card/card.js +12 -1
- package/src/components/card-view/card-view.css +10 -7
- package/src/components/card-view/card-view.js +25 -9
- package/src/components/card-view/record-card.js +7 -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 +68 -0
- package/src/components/code-editor/code-editor.js +306 -0
- package/src/components/code-editor/field-adapter.js +34 -0
- package/src/components/context-menu/context-menu.css +87 -5
- package/src/components/context-menu/context-menu.js +22 -30
- package/src/components/copy/copy-button.css +6 -0
- package/src/components/copy/copy-input.css +8 -3
- package/src/components/data-filter/data-filter.css +5 -4
- package/src/components/date-picker/date-picker.css +6 -6
- package/src/components/date-picker/date-picker.js +22 -8
- package/src/components/date-picker/date-range-picker.css +3 -3
- package/src/components/date-picker/month-picker.js +3 -3
- package/src/components/date-picker/time-picker.css +1 -1
- package/src/components/date-picker/time-picker.js +20 -8
- 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 +174 -70
- package/src/components/dropdown/dropdown.css +1 -1
- package/src/components/dropdown/dropdown.js +7 -5
- package/src/components/empty-state/empty-state.css +2 -2
- package/src/components/entity-ref/entity-ref.css +182 -0
- package/src/components/entity-ref/entity-ref.js +496 -0
- package/src/components/field/field.css +28 -9
- package/src/components/field/field.js +68 -26
- package/src/components/field-upload/field-upload.css +1 -1
- package/src/components/fieldset/fieldset.css +5 -3
- package/src/components/fieldset/fieldset.js +28 -5
- package/src/components/file-item/file-item.css +88 -0
- package/src/components/file-item/file-item.js +505 -0
- package/src/components/file-list/file-list.css +51 -0
- package/src/components/file-list/file-list.js +320 -0
- package/src/components/filter/filter.css +27 -1
- package/src/components/filter/filter.js +205 -47
- package/src/components/filter-panel/filter-panel.css +104 -0
- package/src/components/filter-panel/filter-panel.js +964 -0
- package/src/components/finder/finder.css +15 -13
- 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 +247 -0
- package/src/components/kanban-view/kanban-view.css +226 -8
- package/src/components/kanban-view/kanban-view.js +255 -12
- package/src/components/launcher/launcher.css +107 -25
- package/src/components/launcher/launcher.js +557 -63
- package/src/components/list-toolbar/list-toolbar.css +194 -0
- package/src/components/list-toolbar/list-toolbar.js +851 -0
- package/src/components/master-panel/master-panel.css +2 -0
- package/src/components/menu-button/menu-button.css +87 -5
- package/src/components/menu-button/menu-button.js +86 -46
- package/src/components/message/message.css +38 -5
- package/src/components/message/message.js +230 -17
- package/src/components/modal/modal.css +15 -0
- package/src/components/modal/modal.js +159 -3
- package/src/components/navigation-bar/navigation-bar.css +95 -11
- package/src/components/navigation-bar/navigation-bar.js +171 -4
- package/src/components/number-field/field-adapter.js +1 -0
- 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 +8 -6
- package/src/components/panel/panel.css +1 -1
- package/src/components/panel/panel.js +13 -2
- package/src/components/rating/rating.css +7 -0
- package/src/components/search/search.js +17 -5
- package/src/components/select/color.js +22 -0
- package/src/components/select/field-adapter.js +6 -1
- package/src/components/select/glyphs.js +165 -0
- package/src/components/select/select.css +138 -45
- package/src/components/select/select.js +86 -47
- package/src/components/sheet/sheet.css +11 -8
- package/src/components/slider/slider.css +16 -2
- package/src/components/slider/slider.js +1 -1
- package/src/components/sort-control/sort-control.css +20 -0
- package/src/components/sort-control/sort-control.js +140 -0
- package/src/components/stat-tile/stat-tile.css +208 -0
- package/src/components/stat-tile/stat-tile.js +601 -0
- package/src/components/tabbox/tabbox.css +32 -27
- package/src/components/tabbox/tabbox.js +17 -2
- package/src/components/table/table.css +155 -31
- package/src/components/table/table.js +386 -33
- package/src/components/table-view/table-view.css +8 -2
- package/src/components/table-view/table-view.js +21 -6
- package/src/components/tag-picker/tag-picker.css +86 -6
- package/src/components/tag-picker/tag-picker.js +25 -8
- package/src/components/timebox/timebox.css +22 -2
- package/src/components/timebox/timebox.js +18 -5
- 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 +10 -3
- package/src/components/tree/tree.css +25 -16
- package/src/components/tree/tree.js +31 -0
- package/src/components/value-list/value-list.css +3 -3
- package/src/components/value-list/value-list.js +22 -6
- package/src/components/view/record-view.css +5 -0
- package/src/components/view/record-view.js +100 -7
- package/src/core/breakpoint.js +1 -1
- package/src/core/icons.js +259 -57
- package/src/core/motion.js +147 -0
- package/src/core/overlay-host.js +21 -2
- package/src/index.js +30 -3
- package/src/internal/app-rail.js +18 -1
- package/src/internal/menu-items.js +142 -0
- package/src/zeyos/connect.js +6 -2
- package/src/zeyos/form.js +67 -27
- package/src/zeyos/index.js +1 -1
- package/src/zeyos/query.js +153 -0
- package/src/zeyos/schema.js +144 -27
- package/src/zeyos/table.js +68 -5
- package/src/zeyos/view.js +6 -0
- package/styles/base.css +56 -0
- package/styles/icons.css +11 -1
- package/styles/print.css +192 -0
- package/styles/tokens/dark.css +113 -37
- package/styles/tokens/global.css +121 -27
- package/styles/tokens/semantic.css +100 -31
- package/styles/tokens/themes.css +7 -6
- package/styles/zx.css +11 -1
- package/styles/tokens/density.css +0 -9
|
@@ -11,15 +11,23 @@
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
.zx-menu-button__item {
|
|
14
|
-
display:
|
|
14
|
+
display: grid;
|
|
15
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
15
16
|
align-items: center;
|
|
16
17
|
gap: var(--zx-space-2);
|
|
17
18
|
inline-size: 100%;
|
|
18
|
-
|
|
19
|
-
|
|
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);
|
|
20
24
|
background: transparent;
|
|
21
|
-
padding
|
|
25
|
+
padding: var(--zx-space-1) var(--zx-space-2);
|
|
26
|
+
color: inherit;
|
|
27
|
+
font: inherit;
|
|
28
|
+
line-height: 1.35;
|
|
22
29
|
text-align: start;
|
|
30
|
+
text-decoration: none;
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
.zx-menu-button__item:hover,
|
|
@@ -37,11 +45,85 @@
|
|
|
37
45
|
opacity: .65;
|
|
38
46
|
}
|
|
39
47
|
|
|
40
|
-
.zx-menu-
|
|
48
|
+
.zx-menu-button__leading,
|
|
49
|
+
.zx-menu-button__meta {
|
|
41
50
|
display: inline-flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
gap: var(--zx-space-2);
|
|
42
53
|
flex: none;
|
|
43
54
|
}
|
|
44
55
|
|
|
56
|
+
.zx-menu-button__meta {
|
|
57
|
+
justify-content: flex-end;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.zx-menu-button__leading {
|
|
61
|
+
justify-content: center;
|
|
62
|
+
inline-size: var(--zx-space-4);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.zx-menu-button__body {
|
|
66
|
+
display: flex;
|
|
67
|
+
min-inline-size: 0;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.zx-menu-button__label,
|
|
72
|
+
.zx-menu-button__description {
|
|
73
|
+
overflow: hidden;
|
|
74
|
+
text-overflow: ellipsis;
|
|
75
|
+
white-space: nowrap;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.zx-menu-button__description,
|
|
79
|
+
.zx-menu-button__shortcut,
|
|
80
|
+
.zx-menu-button__adornment {
|
|
81
|
+
color: var(--zx-color-text-muted);
|
|
82
|
+
font-size: var(--zx-text-xs);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.zx-menu-button__badge {
|
|
86
|
+
min-inline-size: var(--zx-space-4);
|
|
87
|
+
border-radius: var(--zx-radius-full);
|
|
88
|
+
background: var(--zx-color-bg-muted);
|
|
89
|
+
padding-inline: var(--zx-space-1);
|
|
90
|
+
color: var(--zx-color-text-muted);
|
|
91
|
+
font-size: var(--zx-text-xs);
|
|
92
|
+
text-align: center;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.zx-menu-button__shortcut {
|
|
96
|
+
border: 0;
|
|
97
|
+
background: transparent;
|
|
98
|
+
padding: 0;
|
|
99
|
+
font-family: var(--zx-font-mono);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.zx-menu-button__check {
|
|
103
|
+
inline-size: var(--zx-space-3);
|
|
104
|
+
block-size: var(--zx-space-3);
|
|
105
|
+
border: 1px solid var(--zx-color-border-strong);
|
|
106
|
+
border-radius: var(--zx-radius-sm);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.zx-menu-button__check[data-check-kind="radio"] {
|
|
110
|
+
border-radius: var(--zx-radius-full);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.zx-menu-button__item[aria-checked="true"] .zx-menu-button__check {
|
|
114
|
+
border-color: var(--zx-color-accent);
|
|
115
|
+
background: var(--zx-color-accent);
|
|
116
|
+
box-shadow: inset 0 0 0 2px var(--zx-color-bg-raised);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* A section label is a caption, never taller than the rows it introduces. */
|
|
120
|
+
.zx-menu-button__heading {
|
|
121
|
+
padding: var(--zx-space-1) var(--zx-space-2);
|
|
122
|
+
color: var(--zx-color-text-muted);
|
|
123
|
+
font-size: var(--zx-text-xs);
|
|
124
|
+
font-weight: 600;
|
|
125
|
+
}
|
|
126
|
+
|
|
45
127
|
.zx-menu-button__separator {
|
|
46
128
|
block-size: 1px;
|
|
47
129
|
margin: var(--zx-space-1) var(--zx-space-2);
|
|
@@ -1,21 +1,17 @@
|
|
|
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';
|
|
6
|
+
import {
|
|
7
|
+
isMenuItem,
|
|
8
|
+
MENU_ITEM_SELECTOR,
|
|
9
|
+
renderMenuEntries
|
|
10
|
+
} from '../../internal/menu-items.js';
|
|
5
11
|
import { Dropdown } from '../dropdown/dropdown.js';
|
|
6
12
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @typedef {Object} MenuItem
|
|
11
|
-
* @property {string} label Item label.
|
|
12
|
-
* @property {string} [icon] Kernel icon name.
|
|
13
|
-
* @property {unknown} [value] Value emitted on selection.
|
|
14
|
-
* @property {boolean} [disabled=false] Whether selection is disabled.
|
|
15
|
-
* @property {boolean} [danger=false] Whether the action is dangerous.
|
|
16
|
-
* @property {(value: unknown, item: MenuItem, menuButton: MenuButton) => void} [onselect] Selection callback.
|
|
17
|
-
*/
|
|
18
|
-
/** @typedef {MenuItem|'-'} MenuButtonItem */
|
|
13
|
+
/** @typedef {import('../../internal/menu-items.js').MenuItem} MenuItem */
|
|
14
|
+
/** @typedef {import('../../internal/menu-items.js').MenuEntry} MenuButtonItem */
|
|
19
15
|
/**
|
|
20
16
|
* @typedef {Object} MenuButtonOptions
|
|
21
17
|
* @property {string} [label=''] Trigger label.
|
|
@@ -28,8 +24,19 @@ const MENU_ITEM_SELECTOR = '[role="menuitem"]';
|
|
|
28
24
|
* @property {(event: CustomEvent<Record<string, never>>) => void} [onclose] Close event listener.
|
|
29
25
|
*/
|
|
30
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
|
+
|
|
31
34
|
/**
|
|
32
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.
|
|
33
40
|
* @fires MenuButton#select
|
|
34
41
|
* @fires MenuButton#open
|
|
35
42
|
* @fires MenuButton#close
|
|
@@ -59,6 +66,8 @@ export class MenuButton extends Component {
|
|
|
59
66
|
#items = [];
|
|
60
67
|
/** @type {{attributes: Array<[string, string]>, children: Node[]}|null} */
|
|
61
68
|
#original = null;
|
|
69
|
+
/** @type {boolean|HTMLElement} Focus destination for the current opening. */
|
|
70
|
+
#returnFocus = true;
|
|
62
71
|
#created = false;
|
|
63
72
|
#destroyed = false;
|
|
64
73
|
|
|
@@ -94,6 +103,10 @@ export class MenuButton extends Component {
|
|
|
94
103
|
this.#panel = this.#dropdown.getPanel();
|
|
95
104
|
this.#panel.classList.add('zx-menu-button__menu');
|
|
96
105
|
this.#panel.setAttribute('role', 'menu');
|
|
106
|
+
// The menu is named by its trigger, as the APG menu-button pattern has it. An id given here to
|
|
107
|
+
// an enhanced trigger goes again on destroy(), with the rest of the attributes it gained.
|
|
108
|
+
if (!this.el.id) this.el.id = uid('zx-menu-button');
|
|
109
|
+
this.#panel.setAttribute('aria-labelledby', this.el.id);
|
|
97
110
|
this.el.setAttribute('aria-haspopup', 'menu');
|
|
98
111
|
if (existing && hasExplicitLabel) this.setLabel(this.options.label);
|
|
99
112
|
|
|
@@ -105,7 +118,10 @@ export class MenuButton extends Component {
|
|
|
105
118
|
);
|
|
106
119
|
|
|
107
120
|
this.#dropdown.on('open', () => this.emit('open'));
|
|
108
|
-
this.#dropdown.on('close', () =>
|
|
121
|
+
this.#dropdown.on('close', () => {
|
|
122
|
+
this.#returnFocus = true;
|
|
123
|
+
this.emit('close');
|
|
124
|
+
});
|
|
109
125
|
this.listen(this.el, 'click', () => {
|
|
110
126
|
if (this.isOpen()) this.close();
|
|
111
127
|
else {
|
|
@@ -116,7 +132,7 @@ export class MenuButton extends Component {
|
|
|
116
132
|
this.listen(this.el, 'keydown', (event) => this.#onTriggerKeydown(event));
|
|
117
133
|
this.listen(this.#panel, 'click', (event) => {
|
|
118
134
|
const item = event.target.closest?.(MENU_ITEM_SELECTOR);
|
|
119
|
-
if (item && this.#panel.contains(item)) this.#selectElement(item);
|
|
135
|
+
if (item && this.#panel.contains(item)) this.#selectElement(item, event);
|
|
120
136
|
});
|
|
121
137
|
this.listen(this.#panel, 'keydown', (event) => this.#onMenuKeydown(event));
|
|
122
138
|
}
|
|
@@ -142,33 +158,18 @@ export class MenuButton extends Component {
|
|
|
142
158
|
*/
|
|
143
159
|
setItems(items) {
|
|
144
160
|
this.#items = Array.isArray(items) ? items.slice() : [];
|
|
145
|
-
this.#panel.replaceChildren();
|
|
146
|
-
this.#items.forEach((item, index) => {
|
|
147
|
-
if (item === '-') {
|
|
148
|
-
this.#panel.append(h('div', { class: 'zx-menu-button__separator', role: 'separator' }));
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
const children = [];
|
|
152
|
-
if (item.icon) children.push(h('span', { class: 'zx-menu-button__icon' }, icon(item.icon)));
|
|
153
|
-
children.push(h('span', { class: 'zx-menu-button__label' }, item.label));
|
|
154
|
-
this.#panel.append(h('button', {
|
|
155
|
-
class: 'zx-menu-button__item',
|
|
156
|
-
type: 'button',
|
|
157
|
-
role: 'menuitem',
|
|
158
|
-
tabindex: '-1',
|
|
159
|
-
'data-menu-item': String(index),
|
|
160
|
-
'data-danger': item.danger ? 'true' : null,
|
|
161
|
-
ariaDisabled: item.disabled ? 'true' : null
|
|
162
|
-
}, children));
|
|
163
|
-
});
|
|
161
|
+
this.#panel.replaceChildren(...renderMenuEntries(this.#items, 'menu-button'));
|
|
164
162
|
return this;
|
|
165
163
|
}
|
|
166
164
|
|
|
167
165
|
/**
|
|
168
166
|
* Opens the menu.
|
|
167
|
+
* @param {MenuButtonOpenOptions} [options={}] Where focus returns when this opening ends; ignored
|
|
168
|
+
* while the menu is already open.
|
|
169
169
|
* @returns {this}
|
|
170
170
|
*/
|
|
171
|
-
open() {
|
|
171
|
+
open(options = {}) {
|
|
172
|
+
if (!this.isOpen()) this.#returnFocus = options?.returnFocus ?? true;
|
|
172
173
|
this.#dropdown.open();
|
|
173
174
|
return this;
|
|
174
175
|
}
|
|
@@ -200,16 +201,24 @@ export class MenuButton extends Component {
|
|
|
200
201
|
return this.#panel;
|
|
201
202
|
}
|
|
202
203
|
|
|
203
|
-
/**
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Opens the menu and focuses its first enabled item.
|
|
206
|
+
* @param {MenuButtonOpenOptions} [options={}] Passed to `open()`.
|
|
207
|
+
* @returns {this}
|
|
208
|
+
*/
|
|
209
|
+
focusFirst(options = {}) {
|
|
210
|
+
this.open(options);
|
|
206
211
|
this.#roving.focusFirst();
|
|
207
212
|
return this;
|
|
208
213
|
}
|
|
209
214
|
|
|
210
|
-
/**
|
|
211
|
-
|
|
212
|
-
|
|
215
|
+
/**
|
|
216
|
+
* Opens the menu and focuses its last enabled item.
|
|
217
|
+
* @param {MenuButtonOpenOptions} [options={}] Passed to `open()`.
|
|
218
|
+
* @returns {this}
|
|
219
|
+
*/
|
|
220
|
+
focusLast(options = {}) {
|
|
221
|
+
this.open(options);
|
|
213
222
|
this.#roving.focusLast();
|
|
214
223
|
return this;
|
|
215
224
|
}
|
|
@@ -254,8 +263,9 @@ export class MenuButton extends Component {
|
|
|
254
263
|
#onMenuKeydown(event) {
|
|
255
264
|
if (event.key === 'Escape') {
|
|
256
265
|
event.preventDefault();
|
|
266
|
+
const target = this.#returnTarget();
|
|
257
267
|
this.close();
|
|
258
|
-
|
|
268
|
+
target?.focus();
|
|
259
269
|
return;
|
|
260
270
|
}
|
|
261
271
|
if (event.key === 'Tab') {
|
|
@@ -265,20 +275,39 @@ export class MenuButton extends Component {
|
|
|
265
275
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
266
276
|
const item = event.target.closest?.(MENU_ITEM_SELECTOR);
|
|
267
277
|
if (!item || !this.#panel.contains(item)) return;
|
|
278
|
+
if (event.key === 'Enter' && item.tagName === 'A') return;
|
|
268
279
|
event.preventDefault();
|
|
269
|
-
|
|
280
|
+
/** @type {HTMLElement} */ (item).click();
|
|
270
281
|
return;
|
|
271
282
|
}
|
|
272
283
|
this.#typeahead(event);
|
|
273
284
|
}
|
|
274
285
|
|
|
275
|
-
/** @param {Element} element @returns {void} */
|
|
276
|
-
#selectElement(element) {
|
|
286
|
+
/** @param {Element} element @param {Event|null} [event=null] @returns {void} */
|
|
287
|
+
#selectElement(element, event = null) {
|
|
277
288
|
const index = Number(element.getAttribute('data-menu-item'));
|
|
278
289
|
const item = this.#items[index];
|
|
279
|
-
if (!item || item
|
|
290
|
+
if (!isMenuItem(item) || item.disabled) {
|
|
291
|
+
event?.preventDefault();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
/*
|
|
295
|
+
* Focus goes back first, as the APG pattern has it, and only if it is still the menu's to give:
|
|
296
|
+
* inside the panel, or already lost to the body. A dialog opened by a listener below then
|
|
297
|
+
* records the trigger as its opener rather than an item that is about to be hidden, and focus
|
|
298
|
+
* a listener moves on purpose is left where it was put.
|
|
299
|
+
*/
|
|
300
|
+
const target = this.#returnTarget();
|
|
301
|
+
const active = document.activeElement;
|
|
302
|
+
const reclaim = Boolean(target) && (!active || active === document.body || this.#panel.contains(active));
|
|
303
|
+
if (reclaim) target?.focus();
|
|
280
304
|
const selected = this.emit('select', { value: item.value, item });
|
|
281
|
-
if (selected.defaultPrevented)
|
|
305
|
+
if (selected.defaultPrevented) {
|
|
306
|
+
event?.preventDefault();
|
|
307
|
+
// The menu stays open, so focus returns to the item, unless a listener has moved it again.
|
|
308
|
+
if (reclaim && document.activeElement === target && element instanceof HTMLElement) element.focus();
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
282
311
|
try {
|
|
283
312
|
item.onselect?.(item.value, item, this);
|
|
284
313
|
} finally {
|
|
@@ -286,6 +315,17 @@ export class MenuButton extends Component {
|
|
|
286
315
|
}
|
|
287
316
|
}
|
|
288
317
|
|
|
318
|
+
/**
|
|
319
|
+
* The element focus returns to when the current opening ends, or null when it should stay put.
|
|
320
|
+
* @returns {HTMLElement|null}
|
|
321
|
+
*/
|
|
322
|
+
#returnTarget() {
|
|
323
|
+
const target = this.#returnFocus;
|
|
324
|
+
if (target === false) return null;
|
|
325
|
+
if (target instanceof HTMLElement && target.isConnected) return target;
|
|
326
|
+
return /** @type {HTMLElement} */ (this.el);
|
|
327
|
+
}
|
|
328
|
+
|
|
289
329
|
/** @returns {HTMLElement[]} */
|
|
290
330
|
#menuItems() {
|
|
291
331
|
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,10 +108,25 @@
|
|
|
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
|
-
the close control sits flush with the top
|
|
124
|
+
the close control sits flush with the trailing top corner without stretching the toast.
|
|
125
|
+
Trailing, not right: in an RTL document the close control belongs on the left, and a
|
|
126
|
+
four-value `margin` shorthand is the one place a logical property cannot be spelled. */
|
|
95
127
|
.zx-message__close {
|
|
96
|
-
margin: calc(-1 * var(--zx-space-1))
|
|
128
|
+
margin-block: calc(-1 * var(--zx-space-1)) 0;
|
|
129
|
+
margin-inline: 0 calc(-1 * var(--zx-space-1));
|
|
97
130
|
}
|
|
98
131
|
|
|
99
132
|
.zx-message__progress {
|