@zeyos/zx 4.1.1 → 4.3.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 +81 -0
- package/DESIGN-SYSTEM.md +33 -3
- package/README.md +22 -5
- package/dist/types/components/app-icon/app-icon.d.ts +8 -1
- package/dist/types/components/aurora/aurora.d.ts +72 -0
- package/dist/types/components/calendar/calendar-model.d.ts +202 -0
- package/dist/types/components/calendar/calendar.d.ts +517 -0
- package/dist/types/components/card-view/card-view.d.ts +303 -0
- package/dist/types/components/card-view/record-card.d.ts +242 -0
- package/dist/types/components/dialog/dialog.d.ts +6 -1
- package/dist/types/components/dropdown/dropdown.d.ts +5 -0
- package/dist/types/components/kanban-view/kanban-view.d.ts +572 -0
- package/dist/types/components/launcher/launcher.d.ts +5 -0
- package/dist/types/components/message/message.d.ts +9 -1
- package/dist/types/components/modal/modal.d.ts +44 -7
- package/dist/types/components/sheet/sheet.d.ts +22 -12
- package/dist/types/components/table/table.d.ts +120 -31
- package/dist/types/components/table-view/table-view.d.ts +192 -0
- package/dist/types/components/view/record-view.d.ts +464 -0
- package/dist/types/core/overlay-host.d.ts +12 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/zeyos/calendar.d.ts +91 -0
- package/dist/types/zeyos/icons.d.ts +18 -2
- package/dist/types/zeyos/index.d.ts +4 -0
- package/dist/types/zeyos/legacy-saved-views.d.ts +66 -0
- package/dist/types/zeyos/saved-views.d.ts +331 -0
- package/dist/types/zeyos/view.d.ts +305 -0
- package/dist/zx-zeyos.esm.js +4742 -1470
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +2536 -471
- package/dist/zx.esm.js +5193 -223
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +5193 -223
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +1729 -182
- package/docs/llms.md +236 -32
- package/docs/llms.txt +16 -6
- package/package.json +1 -1
- package/src/components/account-menu/account-menu.css +1 -1
- package/src/components/app-icon/app-icon.css +77 -51
- package/src/components/app-icon/app-icon.js +28 -4
- package/src/components/app-rail/app-rail.css +12 -3
- package/src/components/app-sidebar/app-sidebar.css +11 -2
- package/src/components/aurora/aurora.css +143 -0
- package/src/components/aurora/aurora.js +146 -0
- package/src/components/avatar/avatar.css +1 -1
- package/src/components/button/button.css +2 -2
- package/src/components/calendar/calendar-model.js +402 -0
- package/src/components/calendar/calendar.css +609 -0
- package/src/components/calendar/calendar.js +1619 -0
- package/src/components/card/card.css +2 -2
- package/src/components/card-view/card-view.css +389 -0
- package/src/components/card-view/card-view.js +527 -0
- package/src/components/card-view/record-card.js +392 -0
- package/src/components/context-menu/context-menu.css +4 -4
- package/src/components/context-menu/context-menu.js +5 -1
- package/src/components/date-picker/date-picker.css +31 -12
- package/src/components/datebox/date-range-box.css +17 -2
- package/src/components/datebox/datebox.css +18 -3
- package/src/components/dialog/dialog.css +4 -3
- package/src/components/dialog/dialog.js +2 -1
- package/src/components/dropdown/dropdown.css +17 -3
- package/src/components/dropdown/dropdown.js +7 -2
- package/src/components/kanban-view/kanban-view.css +216 -0
- package/src/components/kanban-view/kanban-view.js +1228 -0
- package/src/components/launcher/launcher.css +24 -9
- package/src/components/launcher/launcher.js +9 -2
- package/src/components/master-panel/master-panel.css +14 -3
- package/src/components/menu-button/menu-button.css +4 -4
- package/src/components/message/message.css +13 -21
- package/src/components/message/message.js +17 -11
- package/src/components/modal/modal.css +19 -5
- package/src/components/modal/modal.js +99 -25
- package/src/components/navigation-bar/navigation-bar.css +17 -5
- package/src/components/panel/panel.css +10 -2
- package/src/components/select/select.css +26 -8
- package/src/components/sheet/sheet.css +28 -7
- package/src/components/sheet/sheet.js +43 -23
- package/src/components/table/table.css +83 -5
- package/src/components/table/table.js +322 -37
- package/src/components/table-view/table-view.css +18 -0
- package/src/components/table-view/table-view.js +347 -0
- package/src/components/tag-picker/tag-picker.css +18 -4
- package/src/components/tooltip/tooltip.css +17 -3
- package/src/components/tooltip/tooltip.js +4 -1
- package/src/components/view/record-view.css +114 -0
- package/src/components/view/record-view.js +770 -0
- package/src/core/overlay-host.js +23 -0
- package/src/index.js +15 -0
- package/src/zeyos/calendar.js +115 -0
- package/src/zeyos/icons.js +11 -4
- package/src/zeyos/index.js +13 -0
- package/src/zeyos/launcher.js +2 -2
- package/src/zeyos/legacy-saved-views.js +155 -0
- package/src/zeyos/saved-views.js +815 -0
- package/src/zeyos/table.js +3 -1
- package/src/zeyos/view.js +543 -0
- package/styles/icons.css +3 -10
- package/styles/tokens/dark.css +4 -0
- package/styles/tokens/semantic.css +142 -3
- package/styles/zx.css +6 -0
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
.zx-launcher {
|
|
2
2
|
inline-size: min(760px, calc(100vw - (2 * var(--zx-space-4))));
|
|
3
3
|
max-block-size: min(720px, calc(100vh - (2 * var(--zx-space-6))));
|
|
4
|
-
border: 1px solid var(--zx-color-border);
|
|
4
|
+
border: 1px solid var(--zx-color-overlay-border);
|
|
5
5
|
border-radius: var(--zx-radius-xl);
|
|
6
|
-
background: var(--zx-color-
|
|
7
|
-
|
|
6
|
+
background-color: var(--zx-color-overlay-panel);
|
|
7
|
+
background-image: var(--zx-overlay-panel-image);
|
|
8
|
+
background-clip: padding-box;
|
|
9
|
+
-webkit-backdrop-filter: var(--zx-glass-filter-panel);
|
|
10
|
+
backdrop-filter: var(--zx-glass-filter-panel);
|
|
11
|
+
box-shadow: var(--zx-glass-panel-shadow);
|
|
8
12
|
color: var(--zx-color-text);
|
|
9
13
|
padding: 0;
|
|
10
14
|
}
|
|
11
15
|
|
|
16
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
17
|
+
.zx-launcher {
|
|
18
|
+
border-color: var(--zx-color-border);
|
|
19
|
+
background: var(--zx-color-bg-raised);
|
|
20
|
+
-webkit-backdrop-filter: none;
|
|
21
|
+
backdrop-filter: none;
|
|
22
|
+
box-shadow: var(--zx-overlay-shadow);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
.zx-launcher::backdrop {
|
|
13
|
-
background: var(--zx-color-
|
|
27
|
+
background: var(--zx-color-overlay-backdrop);
|
|
14
28
|
}
|
|
15
29
|
|
|
16
30
|
.zx-launcher__surface {
|
|
@@ -24,7 +38,7 @@
|
|
|
24
38
|
grid-template-columns: auto minmax(0, 1fr) auto auto;
|
|
25
39
|
align-items: center;
|
|
26
40
|
gap: var(--zx-space-3);
|
|
27
|
-
border-block-end: 1px solid var(--zx-color-
|
|
41
|
+
border-block-end: 1px solid var(--zx-color-overlay-divider);
|
|
28
42
|
padding: var(--zx-space-3) var(--zx-space-4);
|
|
29
43
|
}
|
|
30
44
|
|
|
@@ -117,11 +131,12 @@
|
|
|
117
131
|
|
|
118
132
|
.zx-launcher__option[aria-selected="true"],
|
|
119
133
|
.zx-launcher__option:hover {
|
|
120
|
-
background: var(--zx-color-
|
|
134
|
+
background: var(--zx-color-overlay-hover);
|
|
121
135
|
}
|
|
122
136
|
|
|
123
137
|
.zx-launcher__option[aria-current="page"] {
|
|
124
|
-
|
|
138
|
+
background: var(--zx-color-overlay-selected);
|
|
139
|
+
color: var(--zx-color-accent);
|
|
125
140
|
}
|
|
126
141
|
|
|
127
142
|
.zx-launcher__option[data-kind="application"] {
|
|
@@ -135,7 +150,7 @@
|
|
|
135
150
|
}
|
|
136
151
|
|
|
137
152
|
.zx-launcher__option[data-kind="application"][aria-current="page"] {
|
|
138
|
-
|
|
153
|
+
background: var(--zx-color-overlay-selected);
|
|
139
154
|
}
|
|
140
155
|
|
|
141
156
|
.zx-launcher__option[data-kind="application"] .zx-launcher__icon {
|
|
@@ -174,7 +189,7 @@
|
|
|
174
189
|
justify-content: end;
|
|
175
190
|
gap: var(--zx-space-4);
|
|
176
191
|
padding: var(--zx-space-2) var(--zx-space-4);
|
|
177
|
-
border-block-start: 1px solid var(--zx-color-
|
|
192
|
+
border-block-start: 1px solid var(--zx-color-overlay-divider);
|
|
178
193
|
color: var(--zx-color-text-muted);
|
|
179
194
|
font-size: var(--zx-text-xs);
|
|
180
195
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Component } from '../../core/component.js';
|
|
2
2
|
import { h, safeHref } from '../../core/dom.js';
|
|
3
3
|
import { icon } from '../../core/icons.js';
|
|
4
|
+
import { overlayHost } from '../../core/overlay-host.js';
|
|
4
5
|
import { uid } from '../../core/util.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -49,6 +50,7 @@ import { uid } from '../../core/util.js';
|
|
|
49
50
|
* @property {string} [loadingText='Searching…'] Loading-state text.
|
|
50
51
|
* @property {'mod+k'|false} [shortcut='mod+k'] Optional global keyboard shortcut.
|
|
51
52
|
* @property {false|{move?: string, open?: string, close?: string}} [hints] Visible keyboard hints, or false.
|
|
53
|
+
* @property {Element|string|null} [scope=null] Element whose nearest Zx theme scope owns an internally created launcher; defaults to the opener.
|
|
52
54
|
* @property {(event: CustomEvent<LauncherSelectDetail>) => void} [onselect] Preventable selection listener.
|
|
53
55
|
* @property {(event: CustomEvent<{query: string}>) => void} [onquery] Query-change listener.
|
|
54
56
|
* @property {(event: CustomEvent<Record<string, never>>) => void} [onopen] Open listener.
|
|
@@ -85,7 +87,8 @@ export class Launcher extends Component {
|
|
|
85
87
|
emptyText: 'No results',
|
|
86
88
|
loadingText: 'Searching…',
|
|
87
89
|
shortcut: 'mod+k',
|
|
88
|
-
hints: { move: 'Move', open: 'Open', close: 'Close' }
|
|
90
|
+
hints: { move: 'Move', open: 'Open', close: 'Close' },
|
|
91
|
+
scope: null
|
|
89
92
|
};
|
|
90
93
|
|
|
91
94
|
/**
|
|
@@ -172,7 +175,7 @@ export class Launcher extends Component {
|
|
|
172
175
|
results,
|
|
173
176
|
footer));
|
|
174
177
|
dialog.dataset.state = 'closed';
|
|
175
|
-
if (this._createdRoot)
|
|
178
|
+
if (this._createdRoot) overlayHost(this.options.scope).append(dialog);
|
|
176
179
|
|
|
177
180
|
this.listen(input, 'input', () => {
|
|
178
181
|
this._query = /** @type {HTMLInputElement} */ (input).value;
|
|
@@ -212,6 +215,10 @@ export class Launcher extends Component {
|
|
|
212
215
|
/** Opens the launcher and focuses/selects its query. @returns {this} @fires Launcher#open */
|
|
213
216
|
open() {
|
|
214
217
|
if (this.isOpen() || this._destroyed) return this;
|
|
218
|
+
if (this._createdRoot) {
|
|
219
|
+
const host = overlayHost(this.options.scope);
|
|
220
|
+
if (this.el.parentElement !== host) host.append(this.el);
|
|
221
|
+
}
|
|
215
222
|
this.el.showModal();
|
|
216
223
|
this.el.dataset.state = 'open';
|
|
217
224
|
this.refs.input.setAttribute('aria-expanded', 'true');
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
overflow: hidden;
|
|
7
7
|
border: 1px solid var(--zx-color-border);
|
|
8
8
|
border-radius: var(--zx-radius-lg);
|
|
9
|
-
background: var(--zx-color-
|
|
9
|
+
background: var(--zx-color-glass-chrome);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
.zx-master-panel__header {
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
align-items: center;
|
|
16
16
|
gap: var(--zx-space-4);
|
|
17
17
|
min-block-size: calc(var(--zx-control-height) + var(--zx-space-4));
|
|
18
|
-
border-block-start: 4px solid var(--zx-master-panel-accent, var(--zx-module-default));
|
|
19
18
|
border-block-end: 1px solid var(--zx-color-border);
|
|
20
|
-
background: var(--zx-color-
|
|
19
|
+
background: var(--zx-color-glass-chrome);
|
|
20
|
+
box-shadow: var(--zx-glass-chrome-shadow);
|
|
21
21
|
padding: var(--zx-space-2) var(--zx-space-4);
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -49,3 +49,14 @@
|
|
|
49
49
|
padding: var(--zx-space-3) var(--zx-space-4);
|
|
50
50
|
color: var(--zx-color-text-muted);
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
54
|
+
.zx-master-panel {
|
|
55
|
+
background: var(--zx-color-bg-surface);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.zx-master-panel__header {
|
|
59
|
+
background: var(--zx-color-bg-raised);
|
|
60
|
+
box-shadow: none;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
gap: var(--zx-space-2);
|
|
17
17
|
inline-size: 100%;
|
|
18
18
|
min-block-size: var(--zx-control-height);
|
|
19
|
-
border-color:
|
|
20
|
-
background:
|
|
19
|
+
border-color: transparent;
|
|
20
|
+
background: transparent;
|
|
21
21
|
padding-inline: var(--zx-control-pad-x);
|
|
22
22
|
text-align: start;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
.zx-menu-button__item:hover,
|
|
26
26
|
.zx-menu-button__item:focus-visible {
|
|
27
|
-
background: var(--zx-color-
|
|
27
|
+
background: var(--zx-color-overlay-hover);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
.zx-menu-button__item[data-danger="true"] {
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
.zx-menu-button__separator {
|
|
46
46
|
block-size: 1px;
|
|
47
47
|
margin: var(--zx-space-1) var(--zx-space-2);
|
|
48
|
-
background: var(--zx-color-
|
|
48
|
+
background: var(--zx-color-overlay-divider);
|
|
49
49
|
}
|
|
@@ -32,11 +32,10 @@
|
|
|
32
32
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
33
33
|
align-items: start;
|
|
34
34
|
gap: var(--zx-space-3);
|
|
35
|
-
border: 1px solid var(--zx-color-border);
|
|
36
|
-
border-inline-start: 4px solid var(--zx-message-kind);
|
|
35
|
+
border: 1px solid var(--zx-color-overlay-border);
|
|
37
36
|
border-radius: var(--zx-radius-md);
|
|
38
37
|
background: var(--zx-color-info-bg);
|
|
39
|
-
box-shadow: var(--zx-
|
|
38
|
+
box-shadow: inset 0 0 0 1px var(--zx-message-kind), var(--zx-glass-raised-shadow);
|
|
40
39
|
padding: var(--zx-space-3);
|
|
41
40
|
color: var(--zx-color-text);
|
|
42
41
|
}
|
|
@@ -56,31 +55,24 @@
|
|
|
56
55
|
background: var(--zx-color-danger-bg);
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
@supports (backdrop-filter: blur(1px)) {
|
|
58
|
+
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
|
|
60
59
|
.zx-message__toast {
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
background: color-mix(in srgb, var(--zx-color-success-bg) 38%, var(--zx-color-glass-surface));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.zx-message__toast[data-kind="warning"] {
|
|
72
|
-
background: color-mix(in srgb, var(--zx-color-warning-bg) 38%, var(--zx-color-glass-surface));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.zx-message__toast[data-kind="error"] {
|
|
76
|
-
background: color-mix(in srgb, var(--zx-color-danger-bg) 38%, var(--zx-color-glass-surface));
|
|
60
|
+
border-color: color-mix(in srgb, var(--zx-message-kind) 28%, var(--zx-color-overlay-border));
|
|
61
|
+
background-color: var(--zx-color-overlay-surface);
|
|
62
|
+
background-image: var(--zx-overlay-toast-image);
|
|
63
|
+
background-clip: padding-box;
|
|
64
|
+
-webkit-backdrop-filter: var(--zx-glass-filter-overlay);
|
|
65
|
+
backdrop-filter: var(--zx-glass-filter-overlay);
|
|
66
|
+
box-shadow: var(--zx-glass-overlay-shadow);
|
|
77
67
|
}
|
|
78
68
|
}
|
|
79
69
|
|
|
80
70
|
@media (prefers-reduced-transparency: reduce) {
|
|
81
71
|
.zx-message__toast {
|
|
72
|
+
border-color: var(--zx-color-border);
|
|
82
73
|
-webkit-backdrop-filter: none;
|
|
83
74
|
backdrop-filter: none;
|
|
75
|
+
box-shadow: var(--zx-overlay-shadow);
|
|
84
76
|
}
|
|
85
77
|
.zx-message__toast[data-kind="info"] { background: var(--zx-color-info-bg); }
|
|
86
78
|
.zx-message__toast[data-kind="success"] { background: var(--zx-color-success-bg); }
|
|
@@ -110,7 +102,7 @@
|
|
|
110
102
|
block-size: 4px;
|
|
111
103
|
margin-block-start: var(--zx-space-2);
|
|
112
104
|
border-radius: var(--zx-radius-full);
|
|
113
|
-
background: var(--zx-color-
|
|
105
|
+
background: var(--zx-color-overlay-divider);
|
|
114
106
|
}
|
|
115
107
|
|
|
116
108
|
.zx-message__progress-fill {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Component } from '../../core/component.js';
|
|
2
2
|
import { h } from '../../core/dom.js';
|
|
3
3
|
import { icon } from '../../core/icons.js';
|
|
4
|
+
import { overlayHost } from '../../core/overlay-host.js';
|
|
4
5
|
|
|
5
6
|
/** @typedef {'info'|'success'|'warning'|'error'} MessageKind */
|
|
6
7
|
|
|
@@ -9,6 +10,7 @@ import { icon } from '../../core/icons.js';
|
|
|
9
10
|
* @property {MessageKind|'danger'} [kind='info'] Message intent (`danger` aliases `error`).
|
|
10
11
|
* @property {number} [timeout=4000] Auto-dismiss delay in milliseconds; zero persists.
|
|
11
12
|
* @property {boolean} [closable=true] Whether to render a close button.
|
|
13
|
+
* @property {Element|string|null} [scope=null] Element whose nearest Zx theme scope owns a floating toast.
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -133,8 +135,8 @@ export class Message extends Component {
|
|
|
133
135
|
maxVisible: 5
|
|
134
136
|
};
|
|
135
137
|
|
|
136
|
-
/** @type {Message
|
|
137
|
-
static #floating =
|
|
138
|
+
/** @type {WeakMap<Element, Message>} */
|
|
139
|
+
static #floating = new WeakMap();
|
|
138
140
|
|
|
139
141
|
/** @returns {HTMLElement} */
|
|
140
142
|
render() {
|
|
@@ -217,16 +219,17 @@ export class Message extends Component {
|
|
|
217
219
|
* @returns {MessageHandle}
|
|
218
220
|
*/
|
|
219
221
|
static show(msg, options = {}) {
|
|
220
|
-
return this.#floatingRegion().show(msg, options);
|
|
222
|
+
return this.#floatingRegion(options.scope).show(msg, options);
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
/**
|
|
224
226
|
* Shows a floating progress status.
|
|
225
227
|
* @param {string} text Initial status text.
|
|
228
|
+
* @param {{scope?: Element|string|null}} [options={}] Floating-region scope.
|
|
226
229
|
* @returns {ProgressHandle}
|
|
227
230
|
*/
|
|
228
|
-
static progress(text) {
|
|
229
|
-
return this.#floatingRegion()._progress(text);
|
|
231
|
+
static progress(text, options = {}) {
|
|
232
|
+
return this.#floatingRegion(options.scope)._progress(text);
|
|
230
233
|
}
|
|
231
234
|
|
|
232
235
|
/** @returns {void} */
|
|
@@ -414,19 +417,22 @@ export class Message extends Component {
|
|
|
414
417
|
this._queue.remove(entry);
|
|
415
418
|
}
|
|
416
419
|
|
|
417
|
-
/** @returns {Message} */
|
|
418
|
-
static #floatingRegion() {
|
|
419
|
-
|
|
420
|
+
/** @param {Element|string|null|undefined} scope @returns {Message} */
|
|
421
|
+
static #floatingRegion(scope) {
|
|
422
|
+
const host = overlayHost(scope ?? null);
|
|
423
|
+
const existing = this.#floating.get(host);
|
|
424
|
+
if (existing) return existing;
|
|
420
425
|
const region = h('div', {
|
|
421
426
|
class: 'zx-message-region',
|
|
422
427
|
popover: 'manual',
|
|
423
428
|
role: 'status',
|
|
424
429
|
ariaLive: 'polite'
|
|
425
430
|
});
|
|
426
|
-
|
|
427
|
-
|
|
431
|
+
host.append(region);
|
|
432
|
+
const floating = new Message(region);
|
|
433
|
+
this.#floating.set(host, floating);
|
|
428
434
|
if (typeof region.showPopover === 'function') region.showPopover();
|
|
429
|
-
return
|
|
435
|
+
return floating;
|
|
430
436
|
}
|
|
431
437
|
}
|
|
432
438
|
|
|
@@ -2,16 +2,30 @@
|
|
|
2
2
|
max-inline-size: calc(100vw - (2 * var(--zx-space-4)));
|
|
3
3
|
max-block-size: calc(100vh - (2 * var(--zx-space-4)));
|
|
4
4
|
overflow: auto;
|
|
5
|
-
border: 1px solid var(--zx-color-border);
|
|
5
|
+
border: 1px solid var(--zx-color-overlay-border);
|
|
6
6
|
border-radius: var(--zx-radius-lg);
|
|
7
|
-
background: var(--zx-color-
|
|
8
|
-
|
|
7
|
+
background-color: var(--zx-color-overlay-panel);
|
|
8
|
+
background-image: var(--zx-overlay-panel-image);
|
|
9
|
+
background-clip: padding-box;
|
|
10
|
+
-webkit-backdrop-filter: var(--zx-glass-filter-panel);
|
|
11
|
+
backdrop-filter: var(--zx-glass-filter-panel);
|
|
12
|
+
box-shadow: var(--zx-glass-panel-shadow);
|
|
9
13
|
color: var(--zx-color-text);
|
|
10
14
|
padding: 0;
|
|
11
15
|
}
|
|
12
16
|
|
|
17
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
18
|
+
.zx-modal {
|
|
19
|
+
border-color: var(--zx-color-border);
|
|
20
|
+
background: var(--zx-color-bg-raised);
|
|
21
|
+
-webkit-backdrop-filter: none;
|
|
22
|
+
backdrop-filter: none;
|
|
23
|
+
box-shadow: var(--zx-overlay-shadow);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
13
27
|
.zx-modal::backdrop {
|
|
14
|
-
background: var(--zx-color-
|
|
28
|
+
background: var(--zx-color-overlay-backdrop);
|
|
15
29
|
}
|
|
16
30
|
|
|
17
31
|
.zx-modal__content {
|
|
@@ -22,7 +36,7 @@
|
|
|
22
36
|
* `:modal` rather than `[open]`: only a dialog opened with `showModal()` blocks the page, and a
|
|
23
37
|
* non-modal one (Sheet's `modal: false`) must leave the page scrollable behind it.
|
|
24
38
|
*/
|
|
25
|
-
body:has(
|
|
39
|
+
body:has(.zx-modal:modal) {
|
|
26
40
|
overflow: hidden;
|
|
27
41
|
overscroll-behavior: none;
|
|
28
42
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Component } from '../../core/component.js';
|
|
2
2
|
import { h } from '../../core/dom.js';
|
|
3
|
+
import { overlayHost } from '../../core/overlay-host.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* @typedef {Object} ModalOptions
|
|
@@ -8,6 +9,7 @@ import { h } from '../../core/dom.js';
|
|
|
8
9
|
* @property {boolean} [closable=true] Whether Escape may close the modal.
|
|
9
10
|
* @property {boolean} [lightDismiss=false] Whether a backdrop click closes the modal.
|
|
10
11
|
* @property {boolean} [destroyOnClose=false] Whether to destroy the modal after it closes.
|
|
12
|
+
* @property {Element|string|null} [scope=null] Element whose nearest Zx theme scope owns the overlay; defaults to the opener.
|
|
11
13
|
* @property {(event: CustomEvent<Record<string, never>>) => void} [onopen] Open event listener.
|
|
12
14
|
* @property {(event: CustomEvent<{result: unknown}>) => void} [onclose] Close event listener.
|
|
13
15
|
* @property {(event: CustomEvent<Record<string, never>>) => void} [oncancel] Cancel event listener.
|
|
@@ -30,15 +32,19 @@ export class Modal extends Component {
|
|
|
30
32
|
width: 'auto',
|
|
31
33
|
closable: true,
|
|
32
34
|
lightDismiss: false,
|
|
33
|
-
destroyOnClose: false
|
|
35
|
+
destroyOnClose: false,
|
|
36
|
+
scope: null
|
|
34
37
|
};
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
#
|
|
39
|
+
/** @type {Map<number, {result: unknown}>} */
|
|
40
|
+
#pendingResults = new Map();
|
|
38
41
|
#destroying = false;
|
|
42
|
+
#presentation = 0;
|
|
43
|
+
/** @type {number[]} */
|
|
44
|
+
#nativeClosingPresentations = [];
|
|
39
45
|
|
|
40
46
|
/**
|
|
41
|
-
* Creates a modal
|
|
47
|
+
* Creates a modal in the configured theme scope, or at document level when none is present.
|
|
42
48
|
* @param {Element|string|null} [_target=null] Ignored; Modal always owns its root dialog.
|
|
43
49
|
* @param {ModalOptions} [options={}] Modal options.
|
|
44
50
|
*/
|
|
@@ -52,14 +58,7 @@ export class Modal extends Component {
|
|
|
52
58
|
if (!this.options.closable || cancelEvent.defaultPrevented) event.preventDefault();
|
|
53
59
|
});
|
|
54
60
|
this.listen(this.el, 'close', () => {
|
|
55
|
-
|
|
56
|
-
this.el.dataset.state = 'closed';
|
|
57
|
-
delete this.el.dataset.zxOverlayOrder;
|
|
58
|
-
const result = this.#hasPendingResult ? this.#pendingResult : (this.el.returnValue || undefined);
|
|
59
|
-
this.#pendingResult = undefined;
|
|
60
|
-
this.#hasPendingResult = false;
|
|
61
|
-
this.emit('close', { result });
|
|
62
|
-
if (this.options.destroyOnClose && !this.#destroying) this.destroy();
|
|
61
|
+
this._settleClose(this.#nativeClosingPresentations.shift());
|
|
63
62
|
});
|
|
64
63
|
this.listen(this.el, 'click', (event) => {
|
|
65
64
|
if (!this.options.lightDismiss || event.target !== this.el || !isBackdropClick(this.el, event)) return;
|
|
@@ -76,7 +75,7 @@ export class Modal extends Component {
|
|
|
76
75
|
* @returns {Element}
|
|
77
76
|
*/
|
|
78
77
|
mountTarget() {
|
|
79
|
-
return
|
|
78
|
+
return overlayHost(this.options.scope);
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
/**
|
|
@@ -87,17 +86,85 @@ export class Modal extends Component {
|
|
|
87
86
|
this.el.showModal();
|
|
88
87
|
}
|
|
89
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Removes the dialog from its current presentation layer.
|
|
91
|
+
* Subclasses may replace native dialog presentation while preserving Modal lifecycle events.
|
|
92
|
+
* @param {string} [result=''] Native string return value.
|
|
93
|
+
* @param {number} [presentation=this.#presentation] Presentation generation being hidden.
|
|
94
|
+
* @returns {void}
|
|
95
|
+
*/
|
|
96
|
+
_hide(result = '', presentation = this.#presentation) {
|
|
97
|
+
this.#nativeClosingPresentations.push(presentation);
|
|
98
|
+
try {
|
|
99
|
+
this.el.close(result);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
this.#nativeClosingPresentations.pop();
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Shows a new presentation and gives it a generation of its own. Close events are queued by the
|
|
108
|
+
* platform, so the generation lets an event from an earlier presentation expire harmlessly if
|
|
109
|
+
* the same overlay has already reopened.
|
|
110
|
+
* @returns {void}
|
|
111
|
+
*/
|
|
112
|
+
_present() {
|
|
113
|
+
this._show();
|
|
114
|
+
this.#presentation += 1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Hides the current presentation and records which generation its queued close event belongs to.
|
|
119
|
+
* @param {string} [result=''] Native string return value.
|
|
120
|
+
* @returns {void}
|
|
121
|
+
*/
|
|
122
|
+
_dismiss(result = '') {
|
|
123
|
+
this._hide(result, this.#presentation);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Reports whether the current presentation layer is visible.
|
|
128
|
+
* @returns {boolean}
|
|
129
|
+
*/
|
|
130
|
+
_isShown() {
|
|
131
|
+
return this.el.open;
|
|
132
|
+
}
|
|
133
|
+
|
|
90
134
|
/**
|
|
91
135
|
* Whether an incoming `close` event is a real dismissal.
|
|
92
136
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* `destroyOnClose`. Returning false from an override drops it at the source instead.
|
|
137
|
+
* `close` is dispatched in a queued task, not synchronously. By the time it arrives the overlay
|
|
138
|
+
* may have reopened or crossed presentation layers, so only the event for the current hidden
|
|
139
|
+
* generation may settle state, emit lifecycle events, or trigger `destroyOnClose`.
|
|
140
|
+
* @param {number|undefined} closingPresentation Presentation that produced the event, if known.
|
|
98
141
|
* @returns {boolean}
|
|
99
142
|
*/
|
|
100
|
-
_isRealClose() {
|
|
143
|
+
_isRealClose(closingPresentation) {
|
|
144
|
+
if (closingPresentation !== undefined && closingPresentation !== this.#presentation) return false;
|
|
145
|
+
return !this._isShown();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Settles one presentation's close without allowing a queued event to invalidate a newer one.
|
|
150
|
+
* Popover-based subclasses call this directly; native dialog events arrive through the listener.
|
|
151
|
+
* @param {number|undefined} closingPresentation Presentation that was hidden, if known.
|
|
152
|
+
* @returns {boolean} Whether this event closed the current presentation.
|
|
153
|
+
*/
|
|
154
|
+
_settleClose(closingPresentation) {
|
|
155
|
+
if (!this._isRealClose(closingPresentation)) {
|
|
156
|
+
if (closingPresentation !== undefined) this.#pendingResults.delete(closingPresentation);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
this.el.dataset.state = 'closed';
|
|
160
|
+
delete this.el.dataset.zxOverlayOrder;
|
|
161
|
+
const pending = closingPresentation === undefined
|
|
162
|
+
? undefined
|
|
163
|
+
: this.#pendingResults.get(closingPresentation);
|
|
164
|
+
if (closingPresentation !== undefined) this.#pendingResults.delete(closingPresentation);
|
|
165
|
+
const result = pending ? pending.result : (this.el.returnValue || undefined);
|
|
166
|
+
this.emit('close', { result });
|
|
167
|
+
if (this.options.destroyOnClose && !this.#destroying) this.destroy();
|
|
101
168
|
return true;
|
|
102
169
|
}
|
|
103
170
|
|
|
@@ -121,10 +188,12 @@ export class Modal extends Component {
|
|
|
121
188
|
*/
|
|
122
189
|
open() {
|
|
123
190
|
if (this.isOpen()) return this;
|
|
191
|
+
const host = this.mountTarget();
|
|
192
|
+
if (this.el.parentElement !== host) host.append(this.el);
|
|
124
193
|
this.el.dataset.zxOverlayOrder = String(nextOverlayOrder());
|
|
125
194
|
this.el.returnValue = '';
|
|
126
195
|
try {
|
|
127
|
-
this.
|
|
196
|
+
this._present();
|
|
128
197
|
} catch (error) {
|
|
129
198
|
delete this.el.dataset.zxOverlayOrder;
|
|
130
199
|
throw error;
|
|
@@ -141,9 +210,14 @@ export class Modal extends Component {
|
|
|
141
210
|
*/
|
|
142
211
|
close(result) {
|
|
143
212
|
if (!this.isOpen()) return this;
|
|
144
|
-
|
|
145
|
-
this.#
|
|
146
|
-
|
|
213
|
+
const presentation = this.#presentation;
|
|
214
|
+
this.#pendingResults.set(presentation, { result });
|
|
215
|
+
try {
|
|
216
|
+
this._dismiss(typeof result === 'string' ? result : '');
|
|
217
|
+
} catch (error) {
|
|
218
|
+
this.#pendingResults.delete(presentation);
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
147
221
|
return this;
|
|
148
222
|
}
|
|
149
223
|
|
|
@@ -162,7 +236,7 @@ export class Modal extends Component {
|
|
|
162
236
|
* @returns {boolean}
|
|
163
237
|
*/
|
|
164
238
|
isOpen() {
|
|
165
|
-
return this.
|
|
239
|
+
return this._isShown();
|
|
166
240
|
}
|
|
167
241
|
|
|
168
242
|
/**
|
|
@@ -172,7 +246,7 @@ export class Modal extends Component {
|
|
|
172
246
|
destroy() {
|
|
173
247
|
if (this.#destroying) return;
|
|
174
248
|
this.#destroying = true;
|
|
175
|
-
if (this.isOpen()) this.
|
|
249
|
+
if (this.isOpen()) this._dismiss();
|
|
176
250
|
super.destroy();
|
|
177
251
|
}
|
|
178
252
|
|
|
@@ -15,11 +15,13 @@
|
|
|
15
15
|
min-inline-size: 0;
|
|
16
16
|
align-items: center;
|
|
17
17
|
gap: var(--zx-space-3);
|
|
18
|
-
border: 1px solid var(--zx-color-border);
|
|
18
|
+
border: 1px solid var(--zx-color-glass-border);
|
|
19
19
|
border-radius: var(--zx-radius-lg);
|
|
20
|
-
background: var(--zx-color-
|
|
20
|
+
background: var(--zx-color-glass-chrome);
|
|
21
21
|
padding: var(--zx-space-2) var(--zx-space-3);
|
|
22
|
-
|
|
22
|
+
-webkit-backdrop-filter: var(--zx-glass-filter-chrome);
|
|
23
|
+
backdrop-filter: var(--zx-glass-filter-chrome);
|
|
24
|
+
box-shadow: var(--zx-glass-chrome-shadow);
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
.zx-navigation-bar__brand {
|
|
@@ -113,9 +115,19 @@
|
|
|
113
115
|
|
|
114
116
|
.zx-navigation-bar__panels {
|
|
115
117
|
border: 1px solid var(--zx-color-border);
|
|
116
|
-
border-
|
|
117
|
-
border-radius: 0 0 var(--zx-radius-lg) var(--zx-radius-lg);
|
|
118
|
+
border-radius: var(--zx-radius-lg);
|
|
118
119
|
background: var(--zx-color-bg-surface);
|
|
120
|
+
margin-block-start: var(--zx-space-2);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
124
|
+
.zx-navigation-bar__bar {
|
|
125
|
+
border-color: var(--zx-color-border);
|
|
126
|
+
background: var(--zx-color-bg-raised);
|
|
127
|
+
-webkit-backdrop-filter: none;
|
|
128
|
+
backdrop-filter: none;
|
|
129
|
+
box-shadow: var(--zx-overlay-shadow);
|
|
130
|
+
}
|
|
119
131
|
}
|
|
120
132
|
|
|
121
133
|
.zx-navigation-bar__panel {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
border: 1px solid var(--zx-color-border);
|
|
4
4
|
border-radius: var(--zx-radius-lg);
|
|
5
5
|
background: var(--zx-color-bg-surface);
|
|
6
|
-
box-shadow: var(--zx-
|
|
6
|
+
box-shadow: var(--zx-shadow-1);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
/* The header is a row, not one big button: the collapse control takes the title area and the
|
|
@@ -13,7 +13,15 @@
|
|
|
13
13
|
align-items: center;
|
|
14
14
|
gap: var(--zx-space-2);
|
|
15
15
|
min-block-size: var(--zx-control-height);
|
|
16
|
-
background: var(--zx-panel-header-bg, var(--zx-color-
|
|
16
|
+
background: var(--zx-panel-header-bg, var(--zx-color-glass-chrome));
|
|
17
|
+
box-shadow: var(--zx-glass-chrome-shadow);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
21
|
+
.zx-panel__header {
|
|
22
|
+
background: var(--zx-panel-header-bg, var(--zx-color-bg-raised));
|
|
23
|
+
box-shadow: none;
|
|
24
|
+
}
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
.zx-panel__toggle {
|