@zeyos/zx 2.3.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +138 -0
- package/DESIGN-SYSTEM.md +96 -0
- package/README.md +29 -39
- package/dist/types/components/account-menu/account-menu.d.ts +144 -0
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +243 -0
- package/dist/types/components/avatar/avatar.d.ts +100 -0
- package/dist/types/components/card/card.d.ts +128 -0
- package/dist/types/components/chart/chart.d.ts +322 -0
- package/dist/types/components/date-picker/month-picker.d.ts +6 -0
- package/dist/types/components/dialog/dialog.d.ts +9 -0
- package/dist/types/components/dock/dock.d.ts +495 -0
- package/dist/types/components/dropdown/dropdown.d.ts +2 -2
- package/dist/types/components/field/field.d.ts +14 -0
- package/dist/types/components/field-upload/field-upload.d.ts +37 -4
- package/dist/types/components/form/form.d.ts +25 -3
- package/dist/types/components/grid/grid.d.ts +172 -0
- package/dist/types/components/launcher/launcher.d.ts +359 -0
- package/dist/types/components/layout/layout.d.ts +4 -4
- package/dist/types/components/menu-button/menu-button.d.ts +10 -2
- package/dist/types/components/modal/modal.d.ts +25 -0
- package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +7 -1
- package/dist/types/components/questionnaire/items.d.ts +327 -0
- package/dist/types/components/questionnaire/questionnaire.d.ts +449 -0
- package/dist/types/components/select/select.d.ts +51 -0
- package/dist/types/components/sheet/sheet-stack.d.ts +167 -0
- package/dist/types/components/sheet/sheet.d.ts +294 -0
- package/dist/types/components/split-view/split-view.d.ts +2 -21
- package/dist/types/components/table/table.d.ts +223 -15
- package/dist/types/components/tooltip/tooltip.d.ts +2 -2
- package/dist/types/components/value-list/value-list.d.ts +24 -3
- package/dist/types/core/dom.d.ts +122 -0
- package/dist/types/core/drag-axis.d.ts +111 -0
- package/dist/types/core/position.d.ts +4 -2
- package/dist/types/index.d.ts +13 -2
- package/dist/types/internal/app-rail.d.ts +265 -0
- package/dist/types/zeyos/icons.d.ts +5 -0
- package/dist/types/zeyos/index.d.ts +2 -1
- package/dist/types/zeyos/launcher.d.ts +147 -0
- package/dist/types/zeyos/select.d.ts +39 -0
- package/dist/zx-zeyos.esm.js +1870 -133
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +2006 -267
- package/dist/zx.esm.js +8987 -2757
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +8987 -2757
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +4396 -2344
- package/docs/llms.md +463 -86
- package/docs/llms.txt +32 -19
- package/package.json +10 -17
- package/src/components/account-menu/account-menu.css +70 -0
- package/src/components/account-menu/account-menu.js +208 -0
- package/src/components/app-rail/app-rail.css +107 -0
- package/src/components/app-sidebar/app-sidebar.css +80 -0
- package/src/components/app-sidebar/app-sidebar.js +588 -0
- package/src/components/avatar/avatar.css +61 -0
- package/src/components/avatar/avatar.js +222 -0
- package/src/components/button/button.css +0 -1
- package/src/components/card/card.css +149 -0
- package/src/components/card/card.js +285 -0
- package/src/components/chart/chart.css +61 -0
- package/src/components/chart/chart.js +523 -0
- package/src/components/date-picker/month-picker.js +9 -0
- package/src/components/dialog/dialog.js +11 -4
- package/src/components/dock/dock.css +228 -0
- package/src/components/dock/dock.js +977 -0
- package/src/components/dropdown/dropdown.js +1 -1
- package/src/components/field/field.js +2 -0
- package/src/components/field-upload/field-upload.js +9 -4
- package/src/components/form/form.js +6 -3
- package/src/components/grid/grid.css +6 -0
- package/src/components/grid/grid.js +220 -0
- package/src/components/launcher/launcher.css +210 -0
- package/src/components/launcher/launcher.js +765 -0
- package/src/components/layout/layout.js +2 -2
- package/src/components/menu-button/menu-button.js +27 -2
- package/src/components/modal/modal.css +5 -1
- package/src/components/modal/modal.js +37 -2
- package/src/components/multi-value-editor/multi-value-editor.js +2 -1
- package/src/components/questionnaire/items.js +370 -0
- package/src/components/questionnaire/questionnaire.css +387 -0
- package/src/components/questionnaire/questionnaire.js +1033 -0
- package/src/components/select/field-adapter.js +9 -4
- package/src/components/select/select.css +144 -11
- package/src/components/select/select.js +259 -27
- package/src/components/sheet/sheet-stack.js +304 -0
- package/src/components/sheet/sheet.css +351 -0
- package/src/components/sheet/sheet.js +551 -0
- package/src/components/split-view/split-view.js +76 -161
- package/src/components/table/table.css +43 -0
- package/src/components/table/table.js +531 -52
- package/src/components/tooltip/tooltip.js +5 -2
- package/src/components/value-list/value-list.js +6 -3
- package/src/core/dom.js +322 -0
- package/src/core/drag-axis.js +203 -0
- package/src/core/position.js +68 -22
- package/src/index.js +20 -2
- package/src/internal/app-rail.js +718 -0
- package/src/zeyos/icons.js +8 -3
- package/src/zeyos/index.js +7 -1
- package/src/zeyos/launcher.js +338 -0
- package/src/zeyos/modules.js +1 -0
- package/src/zeyos/select.js +115 -1
- package/styles/tokens/dark.css +2 -3
- package/styles/tokens/global.css +6 -6
- package/styles/tokens/modules.css +1 -0
- package/styles/tokens/semantic.css +5 -3
- package/styles/tokens/themes.css +1 -1
- package/styles/zx.css +11 -1
- package/MIGRATION.md +0 -598
- package/dist/types/compat/base.d.ts +0 -156
- package/dist/types/compat/globals.d.ts +0 -20
- package/dist/types/compat/index.d.ts +0 -128
- package/dist/types/compat/map/basic.d.ts +0 -175
- package/dist/types/compat/map/data.d.ts +0 -102
- package/dist/types/compat/map/date-time.d.ts +0 -80
- package/dist/types/compat/map/factory.d.ts +0 -9
- package/dist/types/compat/map/form.d.ts +0 -98
- package/dist/types/compat/map/helpers.d.ts +0 -12
- package/dist/types/compat/map/http.d.ts +0 -36
- package/dist/types/compat/map/layout.d.ts +0 -106
- package/dist/types/compat/map/options.d.ts +0 -80
- package/dist/types/compat/map/select.d.ts +0 -95
- package/dist/types/compat/map/stubs.d.ts +0 -32
- package/dist/types/compat/map/utilities.d.ts +0 -37
- package/dist/types/compat-entry.d.ts +0 -5
- package/dist/types/components/permission/permission.d.ts +0 -96
- package/dist/zx-compat.esm.js +0 -17484
- package/dist/zx-compat.esm.min.js +0 -1
- package/dist/zx-compat.global.js +0 -23635
- package/dist/zx-compat.global.min.js +0 -3
- package/src/compat/base.js +0 -291
- package/src/compat/globals.js +0 -165
- package/src/compat/index.js +0 -84
- package/src/compat/map/basic.js +0 -464
- package/src/compat/map/data.js +0 -232
- package/src/compat/map/date-time.js +0 -177
- package/src/compat/map/factory.js +0 -45
- package/src/compat/map/form.js +0 -330
- package/src/compat/map/helpers.js +0 -46
- package/src/compat/map/http.js +0 -117
- package/src/compat/map/layout.js +0 -256
- package/src/compat/map/options.js +0 -267
- package/src/compat/map/select.js +0 -198
- package/src/compat/map/stubs.js +0 -26
- package/src/compat/map/utilities.js +0 -113
- package/src/compat-entry.js +0 -11
- package/src/components/permission/permission.css +0 -47
- package/src/components/permission/permission.js +0 -201
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/** @typedef {import('./sheet.js').Sheet} Sheet */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} SheetStackOptions
|
|
5
|
+
* @property {'stack'|'cascade'} [layout='stack'] How depth is expressed. `stack` slides each
|
|
6
|
+
* covered sheet back toward its edge and scales it down, leaving only the top one usable —
|
|
7
|
+
* the drill-down feel. `cascade` shifts each covered sheet clear of the ones in front so they
|
|
8
|
+
* sit side by side and all stay usable, which is what an ERP screen usually wants: the parent
|
|
9
|
+
* record stays readable while a line item is edited.
|
|
10
|
+
* @property {number} [offset=24] Pixels a `stack` layout moves per depth step.
|
|
11
|
+
* @property {number} [scale=0.04] How much a `stack` layout shrinks per depth step, as a fraction.
|
|
12
|
+
* @property {number} [max=3] Depth at which a sheet stops being drawn. Deeper sheets stay open and
|
|
13
|
+
* in the stack; they are simply no longer visible behind the ones in front.
|
|
14
|
+
* @property {(event: CustomEvent<{sheet: Sheet, depth: number}>) => void} [onpush] Push listener.
|
|
15
|
+
* @property {(event: CustomEvent<{sheet: Sheet}>) => void} [onpop] Pop listener.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* An ordered group of Sheets that read as one drill-down.
|
|
20
|
+
*
|
|
21
|
+
* Deliberately tiny, because the browser already does the hard part: nested dialogs stack in the
|
|
22
|
+
* top layer by open order, so there is no z-index bookkeeping here, and Escape closes only the
|
|
23
|
+
* topmost, so unwinding one level at a time is native behaviour rather than something to
|
|
24
|
+
* implement. What is left is bookkeeping — which sheet is at which depth — written onto the
|
|
25
|
+
* elements as `data-depth` and `--zx-sheet-depth` for CSS to interpret.
|
|
26
|
+
*
|
|
27
|
+
* Owns no element of its own, so it is a controller rather than a `Component`: there is nothing
|
|
28
|
+
* to render, only sheets to arrange.
|
|
29
|
+
*
|
|
30
|
+
* @fires SheetStack#push
|
|
31
|
+
* @fires SheetStack#pop
|
|
32
|
+
*/
|
|
33
|
+
export class SheetStack extends EventTarget {
|
|
34
|
+
/** @type {Readonly<SheetStackOptions>} */
|
|
35
|
+
static defaults = Object.freeze({
|
|
36
|
+
layout: 'stack',
|
|
37
|
+
offset: 24,
|
|
38
|
+
scale: 0.04,
|
|
39
|
+
max: 3
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/** @type {Sheet[]} Bottom first; the last entry is the top of the stack. */
|
|
43
|
+
#sheets = [];
|
|
44
|
+
/** @type {WeakMap<Sheet, () => void>} */
|
|
45
|
+
#closers = new WeakMap();
|
|
46
|
+
#destroyed = false;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates a sheet stack.
|
|
50
|
+
* @param {SheetStackOptions} [options={}] Stack options.
|
|
51
|
+
*/
|
|
52
|
+
constructor(options = {}) {
|
|
53
|
+
super();
|
|
54
|
+
const merged = { ...SheetStack.defaults, ...(options && typeof options === 'object' ? options : {}) };
|
|
55
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
56
|
+
if (!/^on[a-z]/.test(key) || typeof value !== 'function') continue;
|
|
57
|
+
this.on(key.slice(2), value);
|
|
58
|
+
delete merged[key];
|
|
59
|
+
}
|
|
60
|
+
this.options = Object.freeze(merged);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Adds a sheet to the top of the stack, opening it if it is closed. A sheet already in the stack
|
|
65
|
+
* is raised rather than added twice.
|
|
66
|
+
* @param {Sheet} sheet Sheet to push.
|
|
67
|
+
* @returns {this}
|
|
68
|
+
* @fires SheetStack#push
|
|
69
|
+
*/
|
|
70
|
+
push(sheet) {
|
|
71
|
+
if (!sheet || typeof sheet.open !== 'function') throw new TypeError('SheetStack.push expects a Sheet');
|
|
72
|
+
const existing = this.#sheets.indexOf(sheet);
|
|
73
|
+
if (existing !== -1) this.#sheets.splice(existing, 1);
|
|
74
|
+
this.#sheets.push(sheet);
|
|
75
|
+
|
|
76
|
+
if (!this.#closers.has(sheet)) {
|
|
77
|
+
// A sheet dismissed by Escape, its close button, or its own code leaves the stack too.
|
|
78
|
+
const onClose = () => this.#drop(sheet);
|
|
79
|
+
this.#closers.set(sheet, onClose);
|
|
80
|
+
sheet.on('close', onClose);
|
|
81
|
+
}
|
|
82
|
+
if (!sheet.isOpen()) sheet.open();
|
|
83
|
+
this.#sync();
|
|
84
|
+
this.#emit('push', { sheet, depth: 0 });
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Closes the top sheet and removes it from the stack.
|
|
90
|
+
*
|
|
91
|
+
* The removal is synchronous while the close is not — a dialog dispatches `close` in a queued
|
|
92
|
+
* task — so the stack updates itself here rather than waiting for the event. The close listener
|
|
93
|
+
* is left to handle the other direction: a sheet dismissed by Escape or its own button.
|
|
94
|
+
* @returns {Sheet|null} The sheet that was closed, or null on an empty stack.
|
|
95
|
+
* @fires SheetStack#pop
|
|
96
|
+
*/
|
|
97
|
+
pop() {
|
|
98
|
+
const sheet = this.top();
|
|
99
|
+
if (!sheet) return null;
|
|
100
|
+
this.#remove(sheet);
|
|
101
|
+
sheet.close();
|
|
102
|
+
return sheet;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Closes everything above a sheet, leaving it on top.
|
|
107
|
+
* @param {Sheet} sheet Sheet to return to.
|
|
108
|
+
* @returns {this}
|
|
109
|
+
*/
|
|
110
|
+
popTo(sheet) {
|
|
111
|
+
const index = this.#sheets.indexOf(sheet);
|
|
112
|
+
if (index === -1) return this;
|
|
113
|
+
while (this.#sheets.length > index + 1) this.pop();
|
|
114
|
+
return this;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Closes every sheet, from the top down.
|
|
119
|
+
* @returns {this}
|
|
120
|
+
*/
|
|
121
|
+
clear() {
|
|
122
|
+
while (this.#sheets.length > 0) this.pop();
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The sheet currently on top.
|
|
128
|
+
* @returns {Sheet|null}
|
|
129
|
+
*/
|
|
130
|
+
top() {
|
|
131
|
+
return this.#sheets[this.#sheets.length - 1] ?? null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* How many sheets the stack holds.
|
|
136
|
+
* @returns {number}
|
|
137
|
+
*/
|
|
138
|
+
size() {
|
|
139
|
+
return this.#sheets.length;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The sheets, bottom first.
|
|
144
|
+
* @returns {Sheet[]}
|
|
145
|
+
*/
|
|
146
|
+
sheets() {
|
|
147
|
+
return [...this.#sheets];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Whether a sheet is in this stack.
|
|
152
|
+
* @param {Sheet} sheet Sheet to look for.
|
|
153
|
+
* @returns {boolean}
|
|
154
|
+
*/
|
|
155
|
+
has(sheet) {
|
|
156
|
+
return this.#sheets.includes(sheet);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Subscribes to a stack event.
|
|
161
|
+
* @param {string} type Event name.
|
|
162
|
+
* @param {(event: any) => void} fn Listener.
|
|
163
|
+
* @returns {this}
|
|
164
|
+
*/
|
|
165
|
+
on(type, fn) {
|
|
166
|
+
this.addEventListener(type, fn);
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Removes a stack event listener.
|
|
172
|
+
* @param {string} type Event name.
|
|
173
|
+
* @param {(event: any) => void} fn Listener.
|
|
174
|
+
* @returns {this}
|
|
175
|
+
*/
|
|
176
|
+
off(type, fn) {
|
|
177
|
+
this.removeEventListener(type, fn);
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Subscribes to one occurrence of a stack event.
|
|
183
|
+
* @param {string} type Event name.
|
|
184
|
+
* @param {(event: any) => void} fn Listener.
|
|
185
|
+
* @returns {this}
|
|
186
|
+
*/
|
|
187
|
+
once(type, fn) {
|
|
188
|
+
this.addEventListener(type, fn, { once: true });
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Empties the stack without closing anything. The sheets are not the stack's to destroy, so
|
|
194
|
+
* they are simply released with their depth styling removed.
|
|
195
|
+
* @returns {void}
|
|
196
|
+
*/
|
|
197
|
+
destroy() {
|
|
198
|
+
if (this.#destroyed) return;
|
|
199
|
+
this.#destroyed = true;
|
|
200
|
+
for (const sheet of this.#sheets) {
|
|
201
|
+
const onClose = this.#closers.get(sheet);
|
|
202
|
+
if (onClose) sheet.off('close', onClose);
|
|
203
|
+
clearDepth(sheet);
|
|
204
|
+
}
|
|
205
|
+
this.#sheets = [];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Handles a sheet that closed on its own. A sheet popped through `pop()` has already been
|
|
210
|
+
* removed by the time its close event lands, so this is a no-op for that path.
|
|
211
|
+
* @param {Sheet} sheet Sheet that closed.
|
|
212
|
+
* @returns {void}
|
|
213
|
+
*/
|
|
214
|
+
#drop(sheet) {
|
|
215
|
+
if (this.#sheets.includes(sheet)) this.#remove(sheet);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Takes a sheet out of the stack and re-numbers what is left.
|
|
220
|
+
* @param {Sheet} sheet Sheet to remove.
|
|
221
|
+
* @returns {void}
|
|
222
|
+
* @fires SheetStack#pop
|
|
223
|
+
*/
|
|
224
|
+
#remove(sheet) {
|
|
225
|
+
const index = this.#sheets.indexOf(sheet);
|
|
226
|
+
if (index === -1) return;
|
|
227
|
+
this.#sheets.splice(index, 1);
|
|
228
|
+
const onClose = this.#closers.get(sheet);
|
|
229
|
+
if (onClose) sheet.off('close', onClose);
|
|
230
|
+
this.#closers.delete(sheet);
|
|
231
|
+
clearDepth(sheet);
|
|
232
|
+
this.#sync();
|
|
233
|
+
this.#emit('pop', { sheet });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Writes each sheet's depth, and for a cascade the distance it must clear.
|
|
238
|
+
* @returns {void}
|
|
239
|
+
*/
|
|
240
|
+
#sync() {
|
|
241
|
+
const { layout, offset, scale, max } = this.options;
|
|
242
|
+
const cascade = layout === 'cascade';
|
|
243
|
+
/** Cumulative extent of the sheets in front of the one being positioned. */
|
|
244
|
+
let ahead = 0;
|
|
245
|
+
|
|
246
|
+
// Top first, so `ahead` accumulates from the front of the stack backwards.
|
|
247
|
+
for (let depth = 0; depth < this.#sheets.length; depth += 1) {
|
|
248
|
+
const sheet = this.#sheets[this.#sheets.length - 1 - depth];
|
|
249
|
+
const element = /** @type {HTMLElement} */ (sheet.el);
|
|
250
|
+
element.dataset.stack = cascade ? 'cascade' : 'stack';
|
|
251
|
+
element.dataset.depth = String(depth);
|
|
252
|
+
element.style.setProperty('--zx-sheet-depth', String(depth));
|
|
253
|
+
element.style.setProperty('--zx-sheet-stack-offset', `${Number(offset) || 0}px`);
|
|
254
|
+
element.style.setProperty('--zx-sheet-stack-scale', String(Number(scale) || 0));
|
|
255
|
+
element.toggleAttribute('data-stack-hidden', depth >= Math.max(1, Number(max) || 1));
|
|
256
|
+
/*
|
|
257
|
+
* A covered sheet in a drill-down is not just visually behind — it must leave the tab order
|
|
258
|
+
* too. The browser does that for stacked *modal* dialogs on its own, but a non-modal stack
|
|
259
|
+
* gets nothing, so the attribute is set either way.
|
|
260
|
+
*/
|
|
261
|
+
element.inert = !cascade && depth > 0;
|
|
262
|
+
if (cascade) {
|
|
263
|
+
element.style.setProperty('--zx-sheet-depth-offset', `${ahead}px`);
|
|
264
|
+
ahead += sheet.getSize();
|
|
265
|
+
} else {
|
|
266
|
+
element.style.removeProperty('--zx-sheet-depth-offset');
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @param {string} type
|
|
273
|
+
* @param {Record<string, unknown>} detail
|
|
274
|
+
* @returns {void}
|
|
275
|
+
*/
|
|
276
|
+
#emit(type, detail) {
|
|
277
|
+
this.dispatchEvent(new CustomEvent(type, { detail }));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Fired after a sheet is pushed onto the stack.
|
|
283
|
+
* @event SheetStack#push
|
|
284
|
+
* @type {CustomEvent<{sheet: Sheet, depth: number}>}
|
|
285
|
+
*/
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Fired after a sheet leaves the stack, however it was dismissed.
|
|
289
|
+
* @event SheetStack#pop
|
|
290
|
+
* @type {CustomEvent<{sheet: Sheet}>}
|
|
291
|
+
*/
|
|
292
|
+
|
|
293
|
+
/** @param {Sheet} sheet @returns {void} */
|
|
294
|
+
function clearDepth(sheet) {
|
|
295
|
+
const element = /** @type {HTMLElement} */ (sheet.el);
|
|
296
|
+
if (!element) return;
|
|
297
|
+
delete element.dataset.stack;
|
|
298
|
+
delete element.dataset.depth;
|
|
299
|
+
element.removeAttribute('data-stack-hidden');
|
|
300
|
+
element.inert = false;
|
|
301
|
+
for (const property of [
|
|
302
|
+
'--zx-sheet-depth', '--zx-sheet-stack-offset', '--zx-sheet-stack-scale', '--zx-sheet-depth-offset'
|
|
303
|
+
]) element.style.removeProperty(property);
|
|
304
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Sheet builds on `.zx-modal` and `.zx-dialog`, so this file must be imported after both: the
|
|
3
|
+
* overrides below share their specificity and win on source order alone.
|
|
4
|
+
*/
|
|
5
|
+
.zx-sheet {
|
|
6
|
+
position: fixed;
|
|
7
|
+
margin: 0;
|
|
8
|
+
padding: 0;
|
|
9
|
+
max-inline-size: 100vw;
|
|
10
|
+
max-block-size: 100dvh;
|
|
11
|
+
border: 0;
|
|
12
|
+
border-radius: 0;
|
|
13
|
+
/* The header and footer stay put; `.zx-dialog__body` is what scrolls. */
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
box-shadow: var(--zx-overlay-shadow);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Horizontal sheets: full height, sized across the inline axis. */
|
|
19
|
+
.zx-sheet[data-side="start"],
|
|
20
|
+
.zx-sheet[data-side="end"] {
|
|
21
|
+
inset-block: 0;
|
|
22
|
+
block-size: 100dvh;
|
|
23
|
+
inline-size: var(--zx-sheet-size, min(24rem, 100vw));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.zx-sheet[data-side="start"] {
|
|
27
|
+
inset-inline-start: 0;
|
|
28
|
+
inset-inline-end: auto;
|
|
29
|
+
border-inline-end: 1px solid var(--zx-color-border);
|
|
30
|
+
--zx-sheet-enter: -100% 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.zx-sheet[data-side="end"] {
|
|
34
|
+
inset-inline-start: auto;
|
|
35
|
+
inset-inline-end: 0;
|
|
36
|
+
border-inline-start: 1px solid var(--zx-color-border);
|
|
37
|
+
--zx-sheet-enter: 100% 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/*
|
|
41
|
+
* `translate` is physical, so the logical sides need the one direction-aware rule in the library.
|
|
42
|
+
* Attribute rather than `:dir()` because the latter only reached Chrome in December 2023, at the
|
|
43
|
+
* very edge of the supported baseline, and this is the sole place it would buy anything.
|
|
44
|
+
*/
|
|
45
|
+
[dir="rtl"] .zx-sheet[data-side="start"] { --zx-sheet-enter: 100% 0; }
|
|
46
|
+
[dir="rtl"] .zx-sheet[data-side="end"] { --zx-sheet-enter: -100% 0; }
|
|
47
|
+
|
|
48
|
+
/* Vertical sheets: full width, sized down the block axis, inner corners rounded like a drawer. */
|
|
49
|
+
.zx-sheet[data-side="top"],
|
|
50
|
+
.zx-sheet[data-side="bottom"] {
|
|
51
|
+
inset-inline: 0;
|
|
52
|
+
inline-size: 100vw;
|
|
53
|
+
block-size: var(--zx-sheet-size, min(60dvh, 100dvh));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.zx-sheet[data-side="top"] {
|
|
57
|
+
inset-block-start: 0;
|
|
58
|
+
inset-block-end: auto;
|
|
59
|
+
border-block-end: 1px solid var(--zx-color-border);
|
|
60
|
+
border-end-start-radius: var(--zx-sheet-radius, var(--zx-radius-xl));
|
|
61
|
+
border-end-end-radius: var(--zx-sheet-radius, var(--zx-radius-xl));
|
|
62
|
+
--zx-sheet-enter: 0 -100%;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.zx-sheet[data-side="bottom"] {
|
|
66
|
+
inset-block-start: auto;
|
|
67
|
+
inset-block-end: 0;
|
|
68
|
+
border-block-start: 1px solid var(--zx-color-border);
|
|
69
|
+
border-start-start-radius: var(--zx-sheet-radius, var(--zx-radius-xl));
|
|
70
|
+
border-start-end-radius: var(--zx-sheet-radius, var(--zx-radius-xl));
|
|
71
|
+
--zx-sheet-enter: 0 100%;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
* Backdrop treatments. Only a modal sheet has a `::backdrop` at all, which is why `backdrop` is
|
|
76
|
+
* documented as meaningful only alongside `modal: true`.
|
|
77
|
+
*/
|
|
78
|
+
.zx-sheet[data-backdrop="none"]::backdrop {
|
|
79
|
+
background: transparent;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.zx-sheet[data-backdrop="blur"]::backdrop {
|
|
83
|
+
backdrop-filter: blur(var(--zx-overlay-blur));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/*
|
|
87
|
+
* Entry and exit both animate. A `<dialog>` normally cannot animate out — `close()` applies
|
|
88
|
+
* `display: none` immediately — so `display` and `overlay` transition with `allow-discrete`, which
|
|
89
|
+
* defers the discrete jump until the rest of the transition has finished. `@starting-style` gives
|
|
90
|
+
* the entry its from-state. Where either is unsupported the sheet simply appears and disappears.
|
|
91
|
+
*/
|
|
92
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
93
|
+
.zx-sheet {
|
|
94
|
+
transition:
|
|
95
|
+
translate var(--zx-dur-base) var(--zx-ease),
|
|
96
|
+
opacity var(--zx-dur-base) var(--zx-ease),
|
|
97
|
+
display var(--zx-dur-base) allow-discrete,
|
|
98
|
+
overlay var(--zx-dur-base) allow-discrete;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Modal's entry keyframes animate the centre-screen case and would fight the slide. */
|
|
102
|
+
.zx-sheet[data-state="open"] {
|
|
103
|
+
animation: none;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.zx-sheet:not([open]) {
|
|
107
|
+
translate: var(--zx-sheet-enter, 0 0);
|
|
108
|
+
opacity: 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@starting-style {
|
|
112
|
+
.zx-sheet[open] {
|
|
113
|
+
translate: var(--zx-sheet-enter, 0 0);
|
|
114
|
+
opacity: 0;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.zx-sheet::backdrop {
|
|
119
|
+
transition:
|
|
120
|
+
opacity var(--zx-dur-base) var(--zx-ease),
|
|
121
|
+
display var(--zx-dur-base) allow-discrete,
|
|
122
|
+
overlay var(--zx-dur-base) allow-discrete;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.zx-sheet[data-state="open"]::backdrop {
|
|
126
|
+
animation: none;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.zx-sheet:not([open])::backdrop {
|
|
130
|
+
opacity: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@starting-style {
|
|
134
|
+
.zx-sheet[open]::backdrop {
|
|
135
|
+
opacity: 0;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/*
|
|
141
|
+
* A docked sheet is a track in its dock's flow, not an overlay: it drops out of fixed positioning
|
|
142
|
+
* and takes its size from the dock's own custom property, the same one the dock's panes use. It is
|
|
143
|
+
* always non-modal while docked, so there is no backdrop to consider.
|
|
144
|
+
*
|
|
145
|
+
* Closing animates the track to zero rather than sliding the sheet away — in the flow there is
|
|
146
|
+
* nowhere to slide to, and collapsing the space is what the surrounding layout should see.
|
|
147
|
+
*/
|
|
148
|
+
.zx-sheet[data-docked] {
|
|
149
|
+
position: relative;
|
|
150
|
+
inset: auto;
|
|
151
|
+
flex: 0 0 var(--zx-pane-size, var(--zx-sheet-size, min(24rem, 100%)));
|
|
152
|
+
inline-size: auto;
|
|
153
|
+
block-size: auto;
|
|
154
|
+
max-inline-size: none;
|
|
155
|
+
max-block-size: none;
|
|
156
|
+
min-inline-size: 0;
|
|
157
|
+
min-block-size: 0;
|
|
158
|
+
border-radius: 0;
|
|
159
|
+
box-shadow: none;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.zx-dock[data-orientation="vertical"] > .zx-sheet[data-docked] {
|
|
163
|
+
border-inline: 0;
|
|
164
|
+
border-block: 1px solid var(--zx-color-border);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.zx-dock[data-orientation="horizontal"] > .zx-sheet[data-docked] {
|
|
168
|
+
border-block: 0;
|
|
169
|
+
border-inline: 1px solid var(--zx-color-border);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
173
|
+
.zx-sheet[data-docked] {
|
|
174
|
+
transition:
|
|
175
|
+
flex-basis var(--zx-dur-base) var(--zx-ease),
|
|
176
|
+
opacity var(--zx-dur-base) var(--zx-ease),
|
|
177
|
+
display var(--zx-dur-base) allow-discrete,
|
|
178
|
+
overlay var(--zx-dur-base) allow-discrete;
|
|
179
|
+
/* The slide belongs to the overlay life; in the flow the track itself is what moves. */
|
|
180
|
+
translate: none;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.zx-sheet[data-docked]:not([open]) {
|
|
184
|
+
flex-basis: 0;
|
|
185
|
+
translate: none;
|
|
186
|
+
opacity: 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@starting-style {
|
|
190
|
+
.zx-sheet[data-docked][open] {
|
|
191
|
+
flex-basis: 0;
|
|
192
|
+
translate: none;
|
|
193
|
+
opacity: 0;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/*
|
|
199
|
+
* The grab handle sits just inside the sheet's inner edge — the one facing the page. It is a
|
|
200
|
+
* `separator` with live values, so it is draggable, focusable, and steppable with the arrow keys.
|
|
201
|
+
* The hit area is deliberately wider than the pill it draws.
|
|
202
|
+
*/
|
|
203
|
+
.zx-sheet__handle {
|
|
204
|
+
position: absolute;
|
|
205
|
+
z-index: 1;
|
|
206
|
+
display: flex;
|
|
207
|
+
align-items: center;
|
|
208
|
+
justify-content: center;
|
|
209
|
+
touch-action: none;
|
|
210
|
+
user-select: none;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.zx-sheet__handle::after {
|
|
214
|
+
content: "";
|
|
215
|
+
border-radius: var(--zx-radius-full);
|
|
216
|
+
background: var(--zx-color-border-strong);
|
|
217
|
+
opacity: 0;
|
|
218
|
+
transition: opacity var(--zx-dur-fast) var(--zx-ease);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.zx-sheet__handle:hover::after,
|
|
222
|
+
.zx-sheet__handle:focus-visible::after,
|
|
223
|
+
.zx-sheet[data-dragging] .zx-sheet__handle::after {
|
|
224
|
+
opacity: 1;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.zx-sheet__handle:focus-visible {
|
|
228
|
+
outline: none;
|
|
229
|
+
box-shadow: var(--zx-focus-ring);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* Inner edge for each side, and the pill drawn across it. */
|
|
233
|
+
.zx-sheet[data-side="start"] > .zx-sheet__handle,
|
|
234
|
+
.zx-sheet[data-side="end"] > .zx-sheet__handle {
|
|
235
|
+
inset-block: 0;
|
|
236
|
+
inline-size: var(--zx-space-3);
|
|
237
|
+
cursor: col-resize;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.zx-sheet[data-side="start"] > .zx-sheet__handle { inset-inline-end: 0; }
|
|
241
|
+
.zx-sheet[data-side="end"] > .zx-sheet__handle { inset-inline-start: 0; }
|
|
242
|
+
|
|
243
|
+
.zx-sheet[data-side="start"] > .zx-sheet__handle::after,
|
|
244
|
+
.zx-sheet[data-side="end"] > .zx-sheet__handle::after {
|
|
245
|
+
inline-size: 3px;
|
|
246
|
+
block-size: var(--zx-space-8);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.zx-sheet[data-side="top"] > .zx-sheet__handle,
|
|
250
|
+
.zx-sheet[data-side="bottom"] > .zx-sheet__handle {
|
|
251
|
+
inset-inline: 0;
|
|
252
|
+
block-size: var(--zx-space-4);
|
|
253
|
+
cursor: row-resize;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.zx-sheet[data-side="top"] > .zx-sheet__handle { inset-block-end: 0; }
|
|
257
|
+
.zx-sheet[data-side="bottom"] > .zx-sheet__handle { inset-block-start: 0; }
|
|
258
|
+
|
|
259
|
+
.zx-sheet[data-side="top"] > .zx-sheet__handle::after,
|
|
260
|
+
.zx-sheet[data-side="bottom"] > .zx-sheet__handle::after {
|
|
261
|
+
inline-size: var(--zx-space-8);
|
|
262
|
+
block-size: 4px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* A drawer's handle is a standing invitation, so it does not wait to be hovered. */
|
|
266
|
+
.zx-sheet[data-side="top"] > .zx-sheet__handle::after,
|
|
267
|
+
.zx-sheet[data-side="bottom"] > .zx-sheet__handle::after {
|
|
268
|
+
opacity: 1;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* A docked sheet is resized by the dock's divider, so its own handle stands down. */
|
|
272
|
+
.zx-sheet[data-docked] > .zx-sheet__handle {
|
|
273
|
+
display: none;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Nothing may animate while the pointer is driving the size, or the sheet lags behind it. */
|
|
277
|
+
.zx-sheet[data-dragging] {
|
|
278
|
+
transition: none;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/*
|
|
282
|
+
* Stacking. Depth is written by `SheetStack` as `--zx-sheet-depth`; the direction it moves in is
|
|
283
|
+
* a per-side unit vector pointing at the sheet's own edge, so one rule covers all four sides.
|
|
284
|
+
*
|
|
285
|
+
* `transform` rather than `translate`, because the enter and exit animation already owns
|
|
286
|
+
* `translate` — the two are independent properties and compose, so a sheet can slide in and sit at
|
|
287
|
+
* a depth at the same time.
|
|
288
|
+
*/
|
|
289
|
+
.zx-sheet[data-side="start"] { --zx-sheet-dx: -1; --zx-sheet-dy: 0; }
|
|
290
|
+
.zx-sheet[data-side="end"] { --zx-sheet-dx: 1; --zx-sheet-dy: 0; }
|
|
291
|
+
.zx-sheet[data-side="top"] { --zx-sheet-dx: 0; --zx-sheet-dy: -1; }
|
|
292
|
+
.zx-sheet[data-side="bottom"] { --zx-sheet-dx: 0; --zx-sheet-dy: 1; }
|
|
293
|
+
|
|
294
|
+
/* Scaling a covered sheet shrinks it toward its own edge, so it never unglues from it. */
|
|
295
|
+
.zx-sheet[data-side="start"] { transform-origin: left center; }
|
|
296
|
+
.zx-sheet[data-side="end"] { transform-origin: right center; }
|
|
297
|
+
.zx-sheet[data-side="top"] { transform-origin: center top; }
|
|
298
|
+
.zx-sheet[data-side="bottom"] { transform-origin: center bottom; }
|
|
299
|
+
|
|
300
|
+
.zx-sheet[data-stack="stack"] {
|
|
301
|
+
transform:
|
|
302
|
+
translate(
|
|
303
|
+
calc(var(--zx-sheet-dx, 0) * var(--zx-sheet-depth, 0) * var(--zx-sheet-stack-offset, 24px)),
|
|
304
|
+
calc(var(--zx-sheet-dy, 0) * var(--zx-sheet-depth, 0) * var(--zx-sheet-stack-offset, 24px))
|
|
305
|
+
)
|
|
306
|
+
scale(calc(1 - var(--zx-sheet-depth, 0) * var(--zx-sheet-stack-scale, 0.04)));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* A cascade moves each covered sheet clear of everything in front of it, so all stay readable. */
|
|
310
|
+
.zx-sheet[data-stack="cascade"] {
|
|
311
|
+
transform: translate(
|
|
312
|
+
calc(var(--zx-sheet-dx, 0) * var(--zx-sheet-depth-offset, 0px) * -1),
|
|
313
|
+
calc(var(--zx-sheet-dy, 0) * var(--zx-sheet-depth-offset, 0px) * -1)
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/* Past `max` a sheet stays open and in the stack, but stops being drawn. */
|
|
318
|
+
.zx-sheet[data-stack-hidden] {
|
|
319
|
+
opacity: 0;
|
|
320
|
+
pointer-events: none;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/*
|
|
324
|
+
* Only the topmost backdrop shades the page — one per depth would compound into black.
|
|
325
|
+
*/
|
|
326
|
+
.zx-sheet[data-depth]:not([data-depth="0"])::backdrop {
|
|
327
|
+
background: transparent;
|
|
328
|
+
backdrop-filter: none;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/*
|
|
332
|
+
* A cascade shades nothing at all. Its whole point is that the covered sheets stay readable and
|
|
333
|
+
* usable beside the top one, and a backdrop is painted directly beneath its own dialog in the top
|
|
334
|
+
* layer — so the topmost sheet's backdrop would dim exactly the sheets the layout exists to keep
|
|
335
|
+
* available. Pair a cascade with `modal: false` when the page behind should stay live too.
|
|
336
|
+
*/
|
|
337
|
+
.zx-sheet[data-stack="cascade"]::backdrop {
|
|
338
|
+
background: transparent;
|
|
339
|
+
backdrop-filter: none;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
343
|
+
.zx-sheet[data-stack] {
|
|
344
|
+
transition:
|
|
345
|
+
transform var(--zx-dur-base) var(--zx-ease),
|
|
346
|
+
translate var(--zx-dur-base) var(--zx-ease),
|
|
347
|
+
opacity var(--zx-dur-base) var(--zx-ease),
|
|
348
|
+
display var(--zx-dur-base) allow-discrete,
|
|
349
|
+
overlay var(--zx-dur-base) allow-discrete;
|
|
350
|
+
}
|
|
351
|
+
}
|