@zeyos/zx 4.2.0 → 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 +37 -0
- package/README.md +5 -0
- package/dist/types/components/calendar/calendar.d.ts +19 -2
- package/dist/types/components/dialog/dialog.d.ts +6 -1
- package/dist/types/components/dropdown/dropdown.d.ts +5 -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/core/overlay-host.d.ts +12 -0
- package/dist/zx-zeyos.esm.js +138 -32
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +259 -132
- package/dist/zx.esm.js +277 -82
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +277 -82
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +36 -0
- package/docs/llms.md +37 -18
- package/docs/llms.txt +1 -1
- package/package.json +1 -1
- package/src/components/account-menu/account-menu.css +1 -1
- package/src/components/app-rail/app-rail.css +1 -1
- package/src/components/calendar/calendar.css +66 -24
- package/src/components/calendar/calendar.js +114 -19
- package/src/components/context-menu/context-menu.css +2 -2
- package/src/components/context-menu/context-menu.js +5 -1
- package/src/components/date-picker/date-picker.css +18 -11
- package/src/components/datebox/date-range-box.css +4 -2
- package/src/components/datebox/datebox.css +4 -2
- package/src/components/dialog/dialog.css +4 -3
- package/src/components/dialog/dialog.js +2 -1
- package/src/components/dropdown/dropdown.css +4 -2
- package/src/components/dropdown/dropdown.js +7 -2
- package/src/components/launcher/launcher.css +10 -8
- package/src/components/launcher/launcher.js +9 -2
- package/src/components/menu-button/menu-button.css +2 -2
- package/src/components/message/message.css +7 -16
- package/src/components/message/message.js +17 -11
- package/src/components/modal/modal.css +6 -4
- package/src/components/modal/modal.js +99 -25
- package/src/components/select/select.css +9 -7
- package/src/components/sheet/sheet.css +25 -7
- package/src/components/sheet/sheet.js +43 -23
- package/src/components/table/table.css +5 -3
- package/src/components/tag-picker/tag-picker.css +5 -3
- package/src/components/tooltip/tooltip.css +4 -2
- package/src/components/tooltip/tooltip.js +4 -1
- package/src/core/overlay-host.js +23 -0
- package/styles/tokens/dark.css +4 -0
- package/styles/tokens/semantic.css +83 -8
|
@@ -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
|
|
|
@@ -98,10 +98,12 @@
|
|
|
98
98
|
overflow: hidden;
|
|
99
99
|
border: 1px solid var(--zx-color-overlay-border);
|
|
100
100
|
border-radius: var(--zx-radius-md);
|
|
101
|
-
background: var(--zx-color-overlay-surface);
|
|
101
|
+
background-color: var(--zx-color-overlay-surface);
|
|
102
|
+
background-image: var(--zx-overlay-surface-image);
|
|
103
|
+
background-clip: padding-box;
|
|
102
104
|
-webkit-backdrop-filter: var(--zx-glass-filter-overlay);
|
|
103
105
|
backdrop-filter: var(--zx-glass-filter-overlay);
|
|
104
|
-
box-shadow: var(--zx-glass-
|
|
106
|
+
box-shadow: var(--zx-glass-overlay-shadow);
|
|
105
107
|
color: var(--zx-color-text);
|
|
106
108
|
padding: var(--zx-space-1);
|
|
107
109
|
}
|
|
@@ -128,15 +130,15 @@
|
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
.zx-select__option[data-active="true"] {
|
|
131
|
-
background: var(--zx-color-
|
|
133
|
+
background: var(--zx-color-overlay-hover);
|
|
132
134
|
}
|
|
133
135
|
|
|
134
136
|
.zx-select__option:hover {
|
|
135
|
-
background: var(--zx-color-
|
|
137
|
+
background: var(--zx-color-overlay-hover);
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
.zx-select__option[aria-selected="true"] {
|
|
139
|
-
background: var(--zx-color-
|
|
141
|
+
background: var(--zx-color-overlay-selected);
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
.zx-select__option > .zx-icon {
|
|
@@ -148,7 +150,7 @@
|
|
|
148
150
|
position: sticky;
|
|
149
151
|
z-index: 1;
|
|
150
152
|
inset-block-start: 0;
|
|
151
|
-
background: var(--zx-color-overlay-
|
|
153
|
+
background: var(--zx-color-overlay-scrim);
|
|
152
154
|
color: var(--zx-color-text-muted);
|
|
153
155
|
font-size: var(--zx-text-xs);
|
|
154
156
|
font-weight: 700;
|
|
@@ -174,7 +176,7 @@
|
|
|
174
176
|
.zx-select__separator {
|
|
175
177
|
block-size: 1px;
|
|
176
178
|
margin: var(--zx-space-1) var(--zx-control-pad-x);
|
|
177
|
-
background: var(--zx-color-
|
|
179
|
+
background: var(--zx-color-overlay-divider);
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
.zx-select__empty {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
border-radius: 0;
|
|
13
13
|
/* The header and footer stay put; `.zx-dialog__body` is what scrolls. */
|
|
14
14
|
overflow: hidden;
|
|
15
|
-
box-shadow: var(--zx-glass-
|
|
15
|
+
box-shadow: var(--zx-glass-panel-shadow);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/* Horizontal sheets: full height, sized across the inline axis. */
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
.zx-sheet[data-side="start"] {
|
|
27
27
|
inset-inline-start: 0;
|
|
28
28
|
inset-inline-end: auto;
|
|
29
|
-
border-inline-end: 1px solid var(--zx-color-border);
|
|
29
|
+
border-inline-end: 1px solid var(--zx-color-overlay-border);
|
|
30
30
|
--zx-sheet-enter: -100% 0;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
.zx-sheet[data-side="end"] {
|
|
34
34
|
inset-inline-start: auto;
|
|
35
35
|
inset-inline-end: 0;
|
|
36
|
-
border-inline-start: 1px solid var(--zx-color-border);
|
|
36
|
+
border-inline-start: 1px solid var(--zx-color-overlay-border);
|
|
37
37
|
--zx-sheet-enter: 100% 0;
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
.zx-sheet[data-side="top"] {
|
|
57
57
|
inset-block-start: 0;
|
|
58
58
|
inset-block-end: auto;
|
|
59
|
-
border-block-end: 1px solid var(--zx-color-border);
|
|
59
|
+
border-block-end: 1px solid var(--zx-color-overlay-border);
|
|
60
60
|
border-end-start-radius: var(--zx-sheet-radius, var(--zx-radius-xl));
|
|
61
61
|
border-end-end-radius: var(--zx-sheet-radius, var(--zx-radius-xl));
|
|
62
62
|
--zx-sheet-enter: 0 -100%;
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
.zx-sheet[data-side="bottom"] {
|
|
66
66
|
inset-block-start: auto;
|
|
67
67
|
inset-block-end: 0;
|
|
68
|
-
border-block-start: 1px solid var(--zx-color-border);
|
|
68
|
+
border-block-start: 1px solid var(--zx-color-overlay-border);
|
|
69
69
|
border-start-start-radius: var(--zx-sheet-radius, var(--zx-radius-xl));
|
|
70
70
|
border-start-end-radius: var(--zx-sheet-radius, var(--zx-radius-xl));
|
|
71
71
|
--zx-sheet-enter: 0 100%;
|
|
@@ -80,9 +80,26 @@
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
.zx-sheet[data-backdrop="blur"]::backdrop {
|
|
83
|
+
-webkit-backdrop-filter: blur(var(--zx-overlay-blur));
|
|
83
84
|
backdrop-filter: blur(var(--zx-overlay-blur));
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
/* A non-modal Sheet uses the popover top layer to escape transformed/clipped application shells,
|
|
88
|
+
but it must not gain a visual or interactive modal backdrop in the process. */
|
|
89
|
+
.zx-sheet[data-modality="none"]::backdrop,
|
|
90
|
+
.zx-sheet[data-modality="trap-focus"]::backdrop {
|
|
91
|
+
background: transparent;
|
|
92
|
+
-webkit-backdrop-filter: none;
|
|
93
|
+
backdrop-filter: none;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more), (forced-colors: active) {
|
|
97
|
+
.zx-sheet[data-backdrop="blur"]::backdrop {
|
|
98
|
+
-webkit-backdrop-filter: none;
|
|
99
|
+
backdrop-filter: none;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
86
103
|
/*
|
|
87
104
|
* Entry and exit both animate. A `<dialog>` normally cannot animate out — `close()` applies
|
|
88
105
|
* `display: none` immediately — so `display` and `overlay` transition with `allow-discrete`, which
|
|
@@ -103,13 +120,14 @@
|
|
|
103
120
|
animation: none;
|
|
104
121
|
}
|
|
105
122
|
|
|
106
|
-
.zx-sheet:not([open]) {
|
|
123
|
+
.zx-sheet:not([open]):not(:popover-open) {
|
|
107
124
|
translate: var(--zx-sheet-enter, 0 0);
|
|
108
125
|
opacity: 0;
|
|
109
126
|
}
|
|
110
127
|
|
|
111
128
|
@starting-style {
|
|
112
|
-
.zx-sheet[open]
|
|
129
|
+
.zx-sheet[open],
|
|
130
|
+
.zx-sheet:popover-open {
|
|
113
131
|
translate: var(--zx-sheet-enter, 0 0);
|
|
114
132
|
opacity: 0;
|
|
115
133
|
}
|
|
@@ -45,6 +45,7 @@ const GROW_SIGN = Object.freeze({ start: 1, end: -1, top: 1, bottom: -1 });
|
|
|
45
45
|
* @property {boolean} [lightDismiss=true] Whether a click outside closes the sheet — the backdrop
|
|
46
46
|
* when modal, a document-level pointer press when not.
|
|
47
47
|
* @property {boolean} [destroyOnClose=false] Whether to destroy the sheet after it closes.
|
|
48
|
+
* @property {Element|string|null} [scope=null] Element whose nearest Zx theme scope owns a floating sheet; defaults to the opener.
|
|
48
49
|
* @property {(event: CustomEvent<Record<string, never>>) => void} [onopen] Open event listener.
|
|
49
50
|
* @property {(event: CustomEvent<{result: unknown}>) => void} [onclose] Close event listener.
|
|
50
51
|
* @property {(event: CustomEvent<Record<string, never>>) => void} [oncancel] Cancel event listener.
|
|
@@ -61,9 +62,11 @@ const GROW_SIGN = Object.freeze({ start: 1, end: -1, top: 1, bottom: -1 });
|
|
|
61
62
|
*
|
|
62
63
|
* Modality is a three-way choice rather than a boolean because the three real behaviours do not
|
|
63
64
|
* collapse into two: a modal sheet delegates focus containment, page inertness, and Escape to the
|
|
64
|
-
* browser via `showModal()`;
|
|
65
|
-
*
|
|
66
|
-
*
|
|
65
|
+
* browser via `showModal()`; floating non-modal sheets use the manual Popover top layer so a
|
|
66
|
+
* transformed or clipped application scope cannot displace them, and re-implement only what they
|
|
67
|
+
* still want. Docked sheets remain native non-modal dialogs in flow. That is also why `backdrop`
|
|
68
|
+
* applies only to `modal: true`: painting one for either non-modal mode would make the sheet look
|
|
69
|
+
* blocking while it is not.
|
|
67
70
|
* @fires Sheet#open
|
|
68
71
|
* @fires Sheet#close
|
|
69
72
|
* @fires Sheet#cancel
|
|
@@ -109,7 +112,6 @@ export class Sheet extends Dialog {
|
|
|
109
112
|
* field would be undefined at best and a private one would throw.
|
|
110
113
|
*/
|
|
111
114
|
this._dock = null;
|
|
112
|
-
this._handoff = false;
|
|
113
115
|
this.setSide(this.options.side);
|
|
114
116
|
this.el.dataset.backdrop = BACKDROPS.has(this.options.backdrop) ? this.options.backdrop : 'dim';
|
|
115
117
|
this.el.dataset.modality = modality(this.options.modal);
|
|
@@ -318,7 +320,7 @@ export class Sheet extends Dialog {
|
|
|
318
320
|
* @returns {Element}
|
|
319
321
|
*/
|
|
320
322
|
mountTarget() {
|
|
321
|
-
return this._dock?.el ??
|
|
323
|
+
return this._dock?.el ?? super.mountTarget();
|
|
322
324
|
}
|
|
323
325
|
|
|
324
326
|
/**
|
|
@@ -347,35 +349,23 @@ export class Sheet extends Dialog {
|
|
|
347
349
|
/**
|
|
348
350
|
* Moves the dialog to its current mount and, when it was open, reopens it there.
|
|
349
351
|
*
|
|
350
|
-
* The close is unavoidable — a dialog cannot cross between
|
|
351
|
-
*
|
|
352
|
-
* `open()` so no lifecycle event is emitted for what is only a change of address.
|
|
352
|
+
* The close is unavoidable — a dialog cannot cross between presentation layers while open —
|
|
353
|
+
* but it is invisible: Modal's presentation generation drops the stale event, and reopening
|
|
354
|
+
* bypasses `open()` so no lifecycle event is emitted for what is only a change of address.
|
|
353
355
|
* @returns {void}
|
|
354
356
|
*/
|
|
355
357
|
_rehost() {
|
|
356
358
|
const wasOpen = this.isOpen();
|
|
357
|
-
if (wasOpen)
|
|
358
|
-
this._handoff = true;
|
|
359
|
-
/** @type {HTMLDialogElement} */ (this.el).close();
|
|
360
|
-
}
|
|
359
|
+
if (wasOpen) this._dismiss();
|
|
361
360
|
this.mountTarget().append(this.el);
|
|
362
361
|
if (!wasOpen) return;
|
|
363
362
|
this.el.returnValue = '';
|
|
364
|
-
this.
|
|
363
|
+
this._present();
|
|
365
364
|
this.el.dataset.state = 'open';
|
|
366
365
|
if (this._dock) delete /** @type {HTMLElement} */ (this.el).dataset.zxOverlayOrder;
|
|
367
366
|
else this.el.dataset.zxOverlayOrder = String(nextOverlayOrder());
|
|
368
367
|
}
|
|
369
368
|
|
|
370
|
-
/**
|
|
371
|
-
* Drops the stale `close` a re-hosting handoff leaves behind.
|
|
372
|
-
* @returns {boolean}
|
|
373
|
-
*/
|
|
374
|
-
_isRealClose() {
|
|
375
|
-
if (!this._handoff) return true;
|
|
376
|
-
this._handoff = false;
|
|
377
|
-
return false;
|
|
378
|
-
}
|
|
379
369
|
|
|
380
370
|
/**
|
|
381
371
|
* Opens the sheet, trapping focus first where the modality asks for it.
|
|
@@ -465,10 +455,40 @@ export class Sheet extends Dialog {
|
|
|
465
455
|
*/
|
|
466
456
|
_show() {
|
|
467
457
|
if (this.options.modal === true && !this._dock) {
|
|
458
|
+
this.el.removeAttribute('popover');
|
|
459
|
+
this.el.removeAttribute('aria-modal');
|
|
468
460
|
super._show();
|
|
469
461
|
return;
|
|
470
462
|
}
|
|
471
|
-
|
|
463
|
+
this.el.setAttribute('aria-modal', 'false');
|
|
464
|
+
if (this._dock) {
|
|
465
|
+
this.el.removeAttribute('popover');
|
|
466
|
+
/** @type {HTMLDialogElement} */ (this.el).show();
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
this.el.setAttribute('popover', 'manual');
|
|
470
|
+
/** @type {HTMLElement} */ (this.el).showPopover();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Hides a native dialog or a non-modal Sheet promoted through the popover top layer.
|
|
475
|
+
* @param {string} [result=''] Native string return value.
|
|
476
|
+
* @param {number} [presentation] Presentation generation being hidden.
|
|
477
|
+
* @returns {void}
|
|
478
|
+
*/
|
|
479
|
+
_hide(result = '', presentation) {
|
|
480
|
+
if (!this.el.matches(':popover-open')) {
|
|
481
|
+
super._hide(result, presentation);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
this.el.returnValue = result;
|
|
485
|
+
/** @type {HTMLElement} */ (this.el).hidePopover();
|
|
486
|
+
queueMicrotask(() => this._settleClose(presentation));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** @returns {boolean} */
|
|
490
|
+
_isShown() {
|
|
491
|
+
return this.el.matches(':popover-open') || super._isShown();
|
|
472
492
|
}
|
|
473
493
|
|
|
474
494
|
/**
|
|
@@ -35,10 +35,12 @@
|
|
|
35
35
|
min-inline-size: 190px;
|
|
36
36
|
border: 1px solid var(--zx-color-overlay-border);
|
|
37
37
|
border-radius: var(--zx-radius-md);
|
|
38
|
-
background: var(--zx-color-overlay-surface);
|
|
38
|
+
background-color: var(--zx-color-overlay-surface);
|
|
39
|
+
background-image: var(--zx-overlay-surface-image);
|
|
40
|
+
background-clip: padding-box;
|
|
39
41
|
-webkit-backdrop-filter: var(--zx-glass-filter-overlay);
|
|
40
42
|
backdrop-filter: var(--zx-glass-filter-overlay);
|
|
41
|
-
box-shadow: var(--zx-glass-
|
|
43
|
+
box-shadow: var(--zx-glass-overlay-shadow);
|
|
42
44
|
padding: var(--zx-space-2);
|
|
43
45
|
max-block-size: min(360px, 70vh);
|
|
44
46
|
overflow: auto;
|
|
@@ -64,7 +66,7 @@
|
|
|
64
66
|
padding-inline: var(--zx-space-2);
|
|
65
67
|
}
|
|
66
68
|
|
|
67
|
-
.zx-table__column-option:hover { background: var(--zx-color-
|
|
69
|
+
.zx-table__column-option:hover { background: var(--zx-color-overlay-hover); }
|
|
68
70
|
|
|
69
71
|
.zx-table__column-option > label,
|
|
70
72
|
.zx-table__column-label {
|
|
@@ -124,10 +124,12 @@
|
|
|
124
124
|
overflow: hidden;
|
|
125
125
|
border: 1px solid var(--zx-color-overlay-border);
|
|
126
126
|
border-radius: var(--zx-radius-md);
|
|
127
|
-
background: var(--zx-color-overlay-surface);
|
|
127
|
+
background-color: var(--zx-color-overlay-surface);
|
|
128
|
+
background-image: var(--zx-overlay-surface-image);
|
|
129
|
+
background-clip: padding-box;
|
|
128
130
|
-webkit-backdrop-filter: var(--zx-glass-filter-overlay);
|
|
129
131
|
backdrop-filter: var(--zx-glass-filter-overlay);
|
|
130
|
-
box-shadow: var(--zx-glass-
|
|
132
|
+
box-shadow: var(--zx-glass-overlay-shadow);
|
|
131
133
|
color: var(--zx-color-text);
|
|
132
134
|
padding: var(--zx-space-1);
|
|
133
135
|
}
|
|
@@ -158,7 +160,7 @@
|
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
.zx-tag-picker__option[data-active] {
|
|
161
|
-
background: var(--zx-color-
|
|
163
|
+
background: var(--zx-color-overlay-hover);
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
.zx-tag-picker__option[aria-selected="true"] {
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
max-inline-size: var(--zx-tooltip-max-inline-size, 260px);
|
|
9
9
|
border: 1px solid var(--zx-color-overlay-border);
|
|
10
10
|
border-radius: var(--zx-radius-md);
|
|
11
|
-
background: var(--zx-color-overlay-surface);
|
|
11
|
+
background-color: var(--zx-color-overlay-surface);
|
|
12
|
+
background-image: var(--zx-overlay-surface-image);
|
|
13
|
+
background-clip: padding-box;
|
|
12
14
|
-webkit-backdrop-filter: var(--zx-glass-filter-overlay);
|
|
13
15
|
backdrop-filter: var(--zx-glass-filter-overlay);
|
|
14
|
-
box-shadow: var(--zx-glass-
|
|
16
|
+
box-shadow: var(--zx-glass-overlay-shadow);
|
|
15
17
|
padding: var(--zx-space-1) var(--zx-space-2);
|
|
16
18
|
color: var(--zx-color-text);
|
|
17
19
|
font-size: var(--zx-text-sm);
|
|
@@ -1,5 +1,6 @@
|
|
|
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
4
|
import { position } from '../../core/position.js';
|
|
4
5
|
import { uid } from '../../core/util.js';
|
|
5
6
|
|
|
@@ -95,6 +96,7 @@ export class Tooltip extends Component {
|
|
|
95
96
|
super(null, options);
|
|
96
97
|
|
|
97
98
|
this.#anchor = resolved;
|
|
99
|
+
overlayHost(resolved).append(this.el);
|
|
98
100
|
this.#content = this.options.content;
|
|
99
101
|
this.#disabled = Boolean(this.options.disabled);
|
|
100
102
|
this.#describedBy = resolved.getAttribute('aria-describedby');
|
|
@@ -154,7 +156,6 @@ export class Tooltip extends Component {
|
|
|
154
156
|
popover: 'manual'
|
|
155
157
|
});
|
|
156
158
|
panel.dataset.state = 'closed';
|
|
157
|
-
document.body.append(panel);
|
|
158
159
|
return panel;
|
|
159
160
|
}
|
|
160
161
|
|
|
@@ -167,6 +168,8 @@ export class Tooltip extends Component {
|
|
|
167
168
|
show() {
|
|
168
169
|
if (this.#destroyed || this.#disabled || this.isOpen()) return this;
|
|
169
170
|
this.#cancelTimers();
|
|
171
|
+
const host = overlayHost(this.#anchor);
|
|
172
|
+
if (this.el.parentElement !== host) host.append(this.el);
|
|
170
173
|
const node = resolveContent(this.#content);
|
|
171
174
|
if (node === null) return this;
|
|
172
175
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { resolveElement } from './dom.js';
|
|
2
|
+
|
|
3
|
+
const SCOPE_SELECTOR = '.zx-scope, [data-zx-theme], [data-zx-density], [data-zx-preset]';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Finds the closest styling scope for a top-layer element.
|
|
7
|
+
*
|
|
8
|
+
* Popovers and dialogs escape clipping even when their DOM parent is inside an application shell,
|
|
9
|
+
* so keeping them below that parent preserves inherited theme, density, material, and product
|
|
10
|
+
* token overrides without sacrificing top-layer behaviour. The document body remains the safe
|
|
11
|
+
* fallback for unscoped applications.
|
|
12
|
+
*
|
|
13
|
+
* @param {Element|string|null} [source=null] Element or selector whose styling scope should own the overlay.
|
|
14
|
+
* @returns {Element}
|
|
15
|
+
*/
|
|
16
|
+
export function overlayHost(source = null) {
|
|
17
|
+
const context = source == null
|
|
18
|
+
? (document.activeElement instanceof Element ? document.activeElement : null)
|
|
19
|
+
: resolveElement(source);
|
|
20
|
+
const scope = context?.closest?.(SCOPE_SELECTOR) ?? null;
|
|
21
|
+
if (!scope || scope === document.documentElement || scope === document.body) return document.body;
|
|
22
|
+
return scope;
|
|
23
|
+
}
|
package/styles/tokens/dark.css
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
--zx-color-bg-hover: color-mix(in srgb, var(--zx-gray-0) 8%, transparent);
|
|
20
20
|
--zx-color-bg-selected: color-mix(in srgb, var(--zx-accent-400) 18%, var(--zx-gray-900));
|
|
21
21
|
--zx-color-bg-backdrop: color-mix(in srgb, var(--zx-gray-950) 75%, transparent);
|
|
22
|
+
--zx-color-overlay-base: color-mix(in srgb, var(--zx-color-bg-raised) 42%, var(--zx-color-bg-page));
|
|
23
|
+
--zx-color-overlay-backdrop: color-mix(in srgb, var(--zx-gray-950) 38%, transparent);
|
|
22
24
|
--zx-color-border: color-mix(in srgb, var(--zx-gray-0) 10%, transparent);
|
|
23
25
|
--zx-color-border-strong: color-mix(in srgb, var(--zx-gray-0) 28%, transparent);
|
|
24
26
|
--zx-color-border-control: color-mix(in srgb, var(--zx-gray-0) 15%, transparent);
|
|
@@ -57,6 +59,8 @@
|
|
|
57
59
|
--zx-color-bg-hover: color-mix(in srgb, var(--zx-gray-0) 8%, transparent);
|
|
58
60
|
--zx-color-bg-selected: color-mix(in srgb, var(--zx-accent-400) 18%, var(--zx-gray-900));
|
|
59
61
|
--zx-color-bg-backdrop: color-mix(in srgb, var(--zx-gray-950) 75%, transparent);
|
|
62
|
+
--zx-color-overlay-base: color-mix(in srgb, var(--zx-color-bg-raised) 42%, var(--zx-color-bg-page));
|
|
63
|
+
--zx-color-overlay-backdrop: color-mix(in srgb, var(--zx-gray-950) 38%, transparent);
|
|
60
64
|
--zx-color-border: color-mix(in srgb, var(--zx-gray-0) 10%, transparent);
|
|
61
65
|
--zx-color-border-strong: color-mix(in srgb, var(--zx-gray-0) 28%, transparent);
|
|
62
66
|
--zx-color-border-control: color-mix(in srgb, var(--zx-gray-0) 15%, transparent);
|