@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
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
max-block-size: calc(100vh - (2 * var(--zx-space-3)));
|
|
4
4
|
overflow: auto;
|
|
5
5
|
border: 1px solid var(--zx-color-overlay-border);
|
|
6
|
-
border-radius: var(--zx-radius-
|
|
6
|
+
border-radius: var(--zx-radius-lg);
|
|
7
7
|
background-color: var(--zx-color-overlay-surface);
|
|
8
8
|
background-image: var(--zx-overlay-surface-image);
|
|
9
9
|
background-clip: padding-box;
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { Component } from '../../core/component.js';
|
|
2
2
|
import { h, resolveElement } from '../../core/dom.js';
|
|
3
|
-
import { overlayHost } from '../../core/overlay-host.js';
|
|
3
|
+
import { connectedOverlayHost, openDialogOf, overlayHost, overlayScope } from '../../core/overlay-host.js';
|
|
4
|
+
import { pushOverlay } from '../../core/overlay-stack.js';
|
|
4
5
|
import { position } from '../../core/position.js';
|
|
5
6
|
import { uid } from '../../core/util.js';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
const
|
|
8
|
+
/** What Tab may land on inside a panel: the controls a reader reaches in sequence. */
|
|
9
|
+
const TABBABLE_SELECTOR = [
|
|
10
|
+
'a[href]', 'button:not([disabled])', 'input:not([disabled]):not([type="hidden"])',
|
|
11
|
+
'select:not([disabled])', 'textarea:not([disabled])', 'iframe', '[contenteditable="true"]',
|
|
12
|
+
'[tabindex]'
|
|
13
|
+
].map((selector) => `${selector}:not([tabindex^="-"])`).join(',');
|
|
9
14
|
|
|
10
15
|
/**
|
|
11
16
|
* @typedef {Object} DropdownOptions
|
|
@@ -15,13 +20,23 @@ const HANDLE_ESCAPE_DISMISS = Symbol('zxDropdownEscapeDismiss');
|
|
|
15
20
|
* @property {boolean} [matchWidth=false] Whether the panel matches the anchor width.
|
|
16
21
|
* @property {'click'|'manual'} [openOn='click'] Trigger behavior.
|
|
17
22
|
* @property {boolean} [closeOnSelect=false] Whether a click within the panel closes it.
|
|
18
|
-
* @property {Element|string|null} [scope=null] Element whose nearest Zx theme scope owns the panel; defaults to the anchor.
|
|
23
|
+
* @property {Element|string|null} [scope=null] Element whose nearest Zx theme scope owns the panel; defaults to the anchor. Resolved on every open, so an anchor connected after construction still gets its scope.
|
|
19
24
|
* @property {(event: CustomEvent<Record<string, never>>) => void} [onopen] Open event listener.
|
|
20
25
|
* @property {(event: CustomEvent<Record<string, never>>) => void} [onclose] Close event listener.
|
|
21
26
|
*/
|
|
22
27
|
|
|
23
28
|
/**
|
|
24
29
|
* Generic anchored floating panel using the browser popover top layer.
|
|
30
|
+
*
|
|
31
|
+
* Open, the panel is one entry on the overlay stack (`core/overlay-stack.js`): Escape closes it
|
|
32
|
+
* only while it is the topmost layer that takes Escape, a press outside it and every overlay opened
|
|
33
|
+
* from it closes it, and it closes with the overlay its anchor sits in. Inside an open dialog the
|
|
34
|
+
* panel is hosted by that dialog, so `showModal()` does not make it inert.
|
|
35
|
+
*
|
|
36
|
+
* The panel follows its trigger in the Tab order: Tab on the anchor of an open panel moves into
|
|
37
|
+
* the panel's first focusable control (or onto the panel itself when it carries a `tabindex`),
|
|
38
|
+
* Tab past the panel's last control continues after the anchor, and Shift+Tab from the first goes
|
|
39
|
+
* back to the anchor. Closing while focus is inside returns it to the anchor.
|
|
25
40
|
* @fires Dropdown#open
|
|
26
41
|
* @fires Dropdown#close
|
|
27
42
|
* @extends {Component<DropdownOptions>}
|
|
@@ -46,6 +61,12 @@ export class Dropdown extends Component {
|
|
|
46
61
|
#position = null;
|
|
47
62
|
/** @type {Map<string, string|null>} */
|
|
48
63
|
#anchorAttributes = new Map();
|
|
64
|
+
/** @type {import('../../core/overlay-stack.js').OverlayHandle|null} */
|
|
65
|
+
#overlay = null;
|
|
66
|
+
/** @type {((event: KeyboardEvent) => void)|null} Escape behaviour a composing component supplies. */
|
|
67
|
+
#escapeHandler = null;
|
|
68
|
+
/** Whether the panel joins the Tab order after its anchor; menus and rail flyouts opt out. */
|
|
69
|
+
#tabReach = true;
|
|
49
70
|
#destroyed = false;
|
|
50
71
|
|
|
51
72
|
/**
|
|
@@ -58,7 +79,10 @@ export class Dropdown extends Component {
|
|
|
58
79
|
if (!resolved) throw new TypeError('Dropdown anchor could not be resolved');
|
|
59
80
|
super(null, options);
|
|
60
81
|
this.#anchor = resolved;
|
|
61
|
-
|
|
82
|
+
// An anchor that is not in the document yet has no theme scope to read; the panel then waits
|
|
83
|
+
// detached and finds its host on the first open, instead of settling on <body>. A closed panel
|
|
84
|
+
// rests in the theme scope even when the anchor is in a dialog; it moves in only while open.
|
|
85
|
+
connectedOverlayHost(this.options.scope ?? resolved, { dialog: false })?.append(this.el);
|
|
62
86
|
this.#rememberAnchorAttribute('aria-expanded');
|
|
63
87
|
this.#rememberAnchorAttribute('aria-controls');
|
|
64
88
|
this.#anchor.setAttribute('aria-expanded', 'false');
|
|
@@ -71,6 +95,8 @@ export class Dropdown extends Component {
|
|
|
71
95
|
this.listen(this.el, 'click', () => {
|
|
72
96
|
if (this.options.closeOnSelect) this.close();
|
|
73
97
|
});
|
|
98
|
+
this.listen(this.#anchor, 'keydown', (event) => this.#onAnchorKeydown(/** @type {KeyboardEvent} */ (event)));
|
|
99
|
+
this.listen(this.el, 'keydown', (event) => this.#onPanelKeydown(/** @type {KeyboardEvent} */ (event)));
|
|
74
100
|
}
|
|
75
101
|
|
|
76
102
|
/**
|
|
@@ -93,7 +119,7 @@ export class Dropdown extends Component {
|
|
|
93
119
|
*/
|
|
94
120
|
open() {
|
|
95
121
|
if (this.isOpen() || this.#destroyed) return this;
|
|
96
|
-
const host =
|
|
122
|
+
const host = this.#host();
|
|
97
123
|
if (this.el.parentElement !== host) host.append(this.el);
|
|
98
124
|
this.#position = position(this.#anchor, this.el, {
|
|
99
125
|
placement: this.options.placement,
|
|
@@ -101,9 +127,18 @@ export class Dropdown extends Component {
|
|
|
101
127
|
matchWidth: Boolean(this.options.matchWidth)
|
|
102
128
|
});
|
|
103
129
|
this.el.dataset.state = 'open';
|
|
104
|
-
this
|
|
130
|
+
this.#overlay = pushOverlay({
|
|
131
|
+
kind: 'popup',
|
|
132
|
+
root: this.el,
|
|
133
|
+
owner: this,
|
|
134
|
+
anchor: [this.#anchor, resolveElement(this.options.scope)],
|
|
135
|
+
inside: [this.#anchor],
|
|
136
|
+
onEscape: (event) => this.#onEscape(event),
|
|
137
|
+
onOutsidePointer: () => this.close(),
|
|
138
|
+
onParentClose: () => this.close()
|
|
139
|
+
});
|
|
140
|
+
this.el.dataset.zxOverlayOrder = String(this.#overlay.order);
|
|
105
141
|
this.#anchor.setAttribute('aria-expanded', 'true');
|
|
106
|
-
ensureDismissManager();
|
|
107
142
|
this.emit('open');
|
|
108
143
|
return this;
|
|
109
144
|
}
|
|
@@ -115,13 +150,24 @@ export class Dropdown extends Component {
|
|
|
115
150
|
*/
|
|
116
151
|
close() {
|
|
117
152
|
if (!this.isOpen()) return this;
|
|
153
|
+
const hadFocus = this.el.contains(document.activeElement);
|
|
118
154
|
this.#position?.destroy();
|
|
119
155
|
this.#position = null;
|
|
120
156
|
this.el.dataset.state = 'closed';
|
|
121
157
|
delete this.el.dataset.zxOverlayOrder;
|
|
158
|
+
const overlay = this.#overlay;
|
|
159
|
+
this.#overlay = null;
|
|
122
160
|
this.#anchor.setAttribute('aria-expanded', 'false');
|
|
161
|
+
// A panel that took the Tab order hands focus back rather than dropping it on <body>. Menus and
|
|
162
|
+
// flyouts decide where their own focus goes.
|
|
163
|
+
if (hadFocus && this.#tabReach && this.#anchor instanceof HTMLElement) this.#anchor.focus();
|
|
164
|
+
// Back to the theme scope: a closed panel left inside a dialog would sit in that dialog's
|
|
165
|
+
// focus search and go with it when the dialog is removed.
|
|
166
|
+
if (openDialogOf(this.el.parentElement) && this.el.parentElement?.isConnected) {
|
|
167
|
+
overlayScope(this.options.scope ?? this.#anchor).append(this.el);
|
|
168
|
+
}
|
|
169
|
+
overlay?.release();
|
|
123
170
|
this.emit('close');
|
|
124
|
-
destroyUnusedDismissManager();
|
|
125
171
|
return this;
|
|
126
172
|
}
|
|
127
173
|
|
|
@@ -172,50 +218,78 @@ export class Dropdown extends Component {
|
|
|
172
218
|
super.destroy();
|
|
173
219
|
}
|
|
174
220
|
|
|
175
|
-
/**
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Internal seam for components built on a Dropdown (MenuButton, ContextMenu, the app rail):
|
|
223
|
+
* replaces what Escape does when the overlay stack routes it to this panel, and takes the panel
|
|
224
|
+
* out of the anchor's Tab order — a menu is reached with the arrow keys, not Tab.
|
|
225
|
+
* @param {{onEscape?: ((event: KeyboardEvent) => void)|null, tabReach?: boolean}} settings
|
|
226
|
+
* @returns {this}
|
|
227
|
+
* @internal
|
|
228
|
+
*/
|
|
229
|
+
_configure(settings) {
|
|
230
|
+
if ('onEscape' in settings) this.#escapeHandler = settings.onEscape ?? null;
|
|
231
|
+
if ('tabReach' in settings) this.#tabReach = Boolean(settings.tabReach);
|
|
232
|
+
return this;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** @returns {Element} Where the panel lives while open. */
|
|
236
|
+
#host() {
|
|
237
|
+
const dialog = openDialogOf(this.#anchor) ?? openDialogOf(resolveElement(this.options.scope));
|
|
238
|
+
return dialog ?? overlayHost(this.options.scope ?? this.#anchor);
|
|
181
239
|
}
|
|
182
240
|
|
|
183
241
|
/** @param {KeyboardEvent} event @returns {void} */
|
|
184
|
-
|
|
185
|
-
if (
|
|
186
|
-
|
|
242
|
+
#onEscape(event) {
|
|
243
|
+
if (this.#escapeHandler) {
|
|
244
|
+
this.#escapeHandler(event);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
187
247
|
const restoreFocus = this.el.contains(document.activeElement);
|
|
188
248
|
this.close();
|
|
189
249
|
if (restoreFocus && this.#anchor instanceof HTMLElement) this.#anchor.focus();
|
|
190
250
|
}
|
|
191
251
|
|
|
192
|
-
/** @param {
|
|
193
|
-
#
|
|
194
|
-
this.#
|
|
252
|
+
/** @param {KeyboardEvent} event @returns {void} */
|
|
253
|
+
#onAnchorKeydown(event) {
|
|
254
|
+
if (event.key !== 'Tab' || event.shiftKey || event.defaultPrevented || !this.#tabReach || !this.isOpen()) return;
|
|
255
|
+
const target = tabbables(this.el)[0] ?? (this.el.hasAttribute('tabindex') ? this.el : null);
|
|
256
|
+
if (!(target instanceof HTMLElement)) return;
|
|
257
|
+
event.preventDefault();
|
|
258
|
+
target.focus();
|
|
195
259
|
}
|
|
196
|
-
}
|
|
197
260
|
|
|
198
|
-
/**
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
261
|
+
/** @param {KeyboardEvent} event @returns {void} */
|
|
262
|
+
#onPanelKeydown(event) {
|
|
263
|
+
if (event.key !== 'Tab' || event.defaultPrevented || !this.#tabReach || !this.isOpen()) return;
|
|
264
|
+
const items = tabbables(this.el);
|
|
265
|
+
const active = document.activeElement;
|
|
266
|
+
/*
|
|
267
|
+
* Judged by position, not identity: focus may rest on something Tab itself would never stop
|
|
268
|
+
* at — a composite's `tabindex="-1"` part, a roving item — and what matters is whether any
|
|
269
|
+
* real stop is left in the panel on the side Tab is moving to.
|
|
270
|
+
*/
|
|
271
|
+
const ahead = active && active !== this.el
|
|
272
|
+
? items.filter((item) => !item.contains(active) && (event.shiftKey
|
|
273
|
+
? Boolean(active.compareDocumentPosition(item) & Node.DOCUMENT_POSITION_PRECEDING)
|
|
274
|
+
: Boolean(active.compareDocumentPosition(item) & Node.DOCUMENT_POSITION_FOLLOWING)
|
|
275
|
+
&& !active.contains(item)))
|
|
276
|
+
: (event.shiftKey ? [] : items);
|
|
277
|
+
if (ahead.length > 0) return;
|
|
278
|
+
if (event.shiftKey) {
|
|
279
|
+
if (!(this.#anchor instanceof HTMLElement)) return;
|
|
280
|
+
event.preventDefault();
|
|
281
|
+
this.#anchor.focus();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const next = followingTabbable(this.#anchor, this.el);
|
|
285
|
+
if (!next) return;
|
|
286
|
+
event.preventDefault();
|
|
287
|
+
next.focus();
|
|
212
288
|
}
|
|
213
289
|
|
|
214
|
-
/** @returns {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
document.body.append(marker);
|
|
218
|
-
return marker;
|
|
290
|
+
/** @param {string} name @returns {void} */
|
|
291
|
+
#rememberAnchorAttribute(name) {
|
|
292
|
+
this.#anchorAttributes.set(name, this.#anchor.getAttribute(name));
|
|
219
293
|
}
|
|
220
294
|
}
|
|
221
295
|
|
|
@@ -249,41 +323,54 @@ function replaceContent(target, content) {
|
|
|
249
323
|
}
|
|
250
324
|
}
|
|
251
325
|
|
|
252
|
-
/**
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
topmost = candidate;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
return topmost ? Component.from(topmost) : null;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/** @returns {number} */
|
|
267
|
-
function nextOverlayOrder() {
|
|
268
|
-
const orders = Array.from(document.querySelectorAll('[data-zx-overlay-order]'), (element) =>
|
|
269
|
-
Number(element.getAttribute('data-zx-overlay-order')) || 0
|
|
270
|
-
);
|
|
271
|
-
return Math.max(0, ...orders) + 1;
|
|
326
|
+
/**
|
|
327
|
+
* The controls inside a container Tab would visit, in document order.
|
|
328
|
+
* @param {Element} container
|
|
329
|
+
* @returns {HTMLElement[]}
|
|
330
|
+
*/
|
|
331
|
+
function tabbables(container) {
|
|
332
|
+
return /** @type {HTMLElement[]} */ (Array.from(container.querySelectorAll(TABBABLE_SELECTOR))
|
|
333
|
+
.filter((element) => isTabbable(element)));
|
|
272
334
|
}
|
|
273
335
|
|
|
274
|
-
/**
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
336
|
+
/**
|
|
337
|
+
* The control Tab reaches after `anchor`, skipping the panel that follows it.
|
|
338
|
+
* @param {Element} anchor
|
|
339
|
+
* @param {Element} panel
|
|
340
|
+
* @returns {HTMLElement|null}
|
|
341
|
+
*/
|
|
342
|
+
function followingTabbable(anchor, panel) {
|
|
343
|
+
// Inside a modal dialog nothing beyond it can take focus, so the search stays within it.
|
|
344
|
+
const scope = openDialogOf(anchor) ?? anchor.ownerDocument.body ?? anchor.ownerDocument.documentElement;
|
|
345
|
+
const all = tabbables(scope).filter((element) => !panel.contains(element));
|
|
346
|
+
const index = all.findIndex((element) => element === anchor || anchor.contains(element));
|
|
347
|
+
if (index < 0) return null;
|
|
348
|
+
let next = index + 1;
|
|
349
|
+
while (next < all.length && anchor.contains(all[next])) next += 1;
|
|
350
|
+
return all[next] ?? null;
|
|
279
351
|
}
|
|
280
352
|
|
|
281
|
-
/**
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
353
|
+
/**
|
|
354
|
+
* Whether sequential navigation stops at an element: focusable, a non-negative tab index, not
|
|
355
|
+
* disabled, hidden, inert or `aria-hidden`, rendered, and — for a radio — the one stop its group
|
|
356
|
+
* offers (the checked radio, or the first when none is checked).
|
|
357
|
+
* @param {Element} element
|
|
358
|
+
* @returns {boolean}
|
|
359
|
+
*/
|
|
360
|
+
function isTabbable(element) {
|
|
361
|
+
const html = /** @type {HTMLElement} */ (element);
|
|
362
|
+
if (typeof html.focus !== 'function' || html.tabIndex < 0) return false;
|
|
363
|
+
if (/** @type {HTMLButtonElement} */ (html).disabled) return false;
|
|
364
|
+
if (element.closest('[inert], [hidden], [aria-hidden="true"], fieldset[disabled]')) return false;
|
|
365
|
+
if (html instanceof HTMLInputElement && html.type === 'radio' && html.name && !html.checked) {
|
|
366
|
+
const scope = html.form ?? html.getRootNode();
|
|
367
|
+
const group = /** @type {HTMLInputElement[]} */ (Array.from(
|
|
368
|
+
/** @type {ParentNode} */ (scope).querySelectorAll?.('input[type="radio"]') ?? []
|
|
369
|
+
)).filter((radio) => radio.name === html.name && (radio.form ?? radio.getRootNode()) === scope);
|
|
370
|
+
if (group.some((radio) => radio.checked) || group[0] !== html) return false;
|
|
371
|
+
}
|
|
372
|
+
if (typeof html.checkVisibility === 'function') return html.checkVisibility({ visibilityProperty: true });
|
|
373
|
+
return html.getClientRects().length > 0;
|
|
287
374
|
}
|
|
288
375
|
|
|
289
376
|
/** @param {Element} element @param {string} name @param {string|null} value @returns {void} */
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
|
|
12
12
|
.zx-empty-state__icon {
|
|
13
13
|
display: flex;
|
|
14
|
-
color: var(--zx-color-text-
|
|
14
|
+
color: var(--zx-color-text-subtle);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
.zx-empty-state__title {
|
|
18
18
|
margin: 0;
|
|
19
19
|
color: var(--zx-color-text);
|
|
20
20
|
font-size: var(--zx-text-lg);
|
|
21
|
-
font-weight:
|
|
21
|
+
font-weight: var(--zx-weight-semibold);
|
|
22
22
|
line-height: 1.4;
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -52,7 +52,7 @@ import { isElement } from '../../core/util.js';
|
|
|
52
52
|
* @property {EntityRefMetadata[]} [metadata=[]] Labelled dense metadata.
|
|
53
53
|
* @property {EntityRefLinkValue} [link=null] Optional native primary link.
|
|
54
54
|
* @property {EntityRefActionEntry[]} [actions=[]] Secondary controls; element actions keep their own behavior.
|
|
55
|
-
* @property {'sm'|'md'|'lg'} [size='md']
|
|
55
|
+
* @property {'sm'|'md'|'lg'} [size='md'] Size of the icon and the identity text.
|
|
56
56
|
* @property {boolean} [wrap=false] Whether long identity text may wrap instead of truncating.
|
|
57
57
|
* @property {string} [actionsLabel='Entity actions'] Accessible name for secondary actions.
|
|
58
58
|
* @property {(event: CustomEvent<{id: unknown, entity: Readonly<EntityRefState>, href: string, event: MouseEvent}>) => void} [onactivate]
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
/* Beside a taller field in a multi-column row the field's cell grows, but its label and control
|
|
2
|
+
stay at the top of it rather than spreading over the extra height. */
|
|
1
3
|
.zx-field {
|
|
2
4
|
display: grid;
|
|
5
|
+
align-content: start;
|
|
3
6
|
gap: var(--zx-space-1);
|
|
4
7
|
min-inline-size: 0;
|
|
5
8
|
}
|
|
@@ -10,15 +13,24 @@
|
|
|
10
13
|
align-items: start;
|
|
11
14
|
}
|
|
12
15
|
|
|
16
|
+
/* Beside its control, a label lines up with the control's first line rather than its top edge, so
|
|
17
|
+
"Status" and "In review" share a baseline — and still sits by the first row of a control that
|
|
18
|
+
grows taller. */
|
|
19
|
+
.zx-field[data-layout="inline"] .zx-field__label {
|
|
20
|
+
padding-block-start: max(0px, (var(--zx-control-height) - 1lh) / 2);
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
.zx-field[data-field-type="hidden"] {
|
|
14
24
|
display: none;
|
|
15
25
|
}
|
|
16
26
|
|
|
27
|
+
/* Labels are quieter than the values they name: the small size, a medium weight, the muted text
|
|
28
|
+
colour. The value is what a reader came for. */
|
|
17
29
|
.zx-field__label {
|
|
18
30
|
min-inline-size: 0;
|
|
19
|
-
color: var(--zx-color-text);
|
|
31
|
+
color: var(--zx-color-text-muted);
|
|
20
32
|
font-size: var(--zx-text-sm);
|
|
21
|
-
font-weight:
|
|
33
|
+
font-weight: var(--zx-weight-medium);
|
|
22
34
|
}
|
|
23
35
|
|
|
24
36
|
.zx-field__required,
|
|
@@ -101,17 +113,24 @@
|
|
|
101
113
|
border-color: var(--zx-color-danger);
|
|
102
114
|
}
|
|
103
115
|
|
|
104
|
-
.
|
|
105
|
-
|
|
116
|
+
/* Display content is not an input, so it draws no frame and no fill. It keeps a control's height
|
|
117
|
+
and centres its first line in it, so it sits level with the fields beside it and with an inline
|
|
118
|
+
label; in a quiet form it is inset like the quiet values around it. */
|
|
119
|
+
.zx-field__static {
|
|
106
120
|
min-block-size: var(--zx-control-height);
|
|
107
|
-
padding:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
121
|
+
padding-block: max(0px, (var(--zx-control-height) - 1lh) / 2);
|
|
122
|
+
font-size: var(--zx-control-font-size);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
[data-zx-fields="quiet"] .zx-field__static {
|
|
126
|
+
padding-inline: calc(var(--zx-control-pad-x) + 1px);
|
|
111
127
|
}
|
|
112
128
|
|
|
113
129
|
.zx-field__error-control {
|
|
114
|
-
|
|
130
|
+
min-block-size: var(--zx-control-height);
|
|
131
|
+
padding: var(--zx-space-2) var(--zx-control-pad-x);
|
|
132
|
+
border: 1px solid var(--zx-color-danger);
|
|
133
|
+
border-radius: var(--zx-control-radius);
|
|
115
134
|
background: var(--zx-color-danger-bg);
|
|
116
135
|
color: var(--zx-color-danger);
|
|
117
136
|
}
|