@zeyos/zx 4.4.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +447 -0
- package/README.md +44 -11
- package/dist/types/components/account-menu/account-menu.d.ts +8 -0
- package/dist/types/components/activity-list/activity-list.d.ts +31 -1
- package/dist/types/components/app-icon/app-icon.d.ts +14 -2
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +293 -17
- package/dist/types/components/avatar/avatar.d.ts +46 -7
- package/dist/types/components/badge/badge.d.ts +21 -1
- package/dist/types/components/button/button.d.ts +15 -0
- package/dist/types/components/calendar/calendar.d.ts +67 -5
- package/dist/types/components/card-view/card-view.d.ts +7 -4
- package/dist/types/components/card-view/record-card.d.ts +11 -0
- package/dist/types/components/chart/chart.d.ts +10 -0
- package/dist/types/components/checklist/checklist.d.ts +9 -0
- package/dist/types/components/code-editor/code-editor.d.ts +6 -17
- package/dist/types/components/context-menu/context-menu.d.ts +6 -2
- package/dist/types/components/data-filter/data-filter.d.ts +4 -4
- package/dist/types/components/datebox/date-range-box.d.ts +5 -1
- package/dist/types/components/datebox/datebox.d.ts +7 -2
- package/dist/types/components/dialog/dialog.d.ts +46 -6
- package/dist/types/components/dropdown/dropdown.d.ts +24 -9
- package/dist/types/components/entity-ref/entity-ref.d.ts +2 -2
- package/dist/types/components/field/field.d.ts +54 -2
- package/dist/types/components/fieldset/external-adapter.d.ts +83 -0
- package/dist/types/components/fieldset/fieldset.d.ts +26 -1
- package/dist/types/components/file-item/file-item.d.ts +34 -6
- package/dist/types/components/file-list/file-list.d.ts +35 -3
- package/dist/types/components/filter/filter.d.ts +127 -3
- package/dist/types/components/filter-panel/filter-panel.d.ts +36 -7
- package/dist/types/components/form/form.d.ts +12 -1
- package/dist/types/components/kanban-view/kanban-card.d.ts +8 -0
- package/dist/types/components/launcher/launcher.d.ts +268 -15
- package/dist/types/components/list-toolbar/list-toolbar.d.ts +16 -7
- package/dist/types/components/menu-button/menu-button.d.ts +39 -5
- package/dist/types/components/message/message.d.ts +132 -17
- package/dist/types/components/modal/modal.d.ts +43 -2
- package/dist/types/components/module-bar/module-bar.d.ts +395 -0
- package/dist/types/components/pagination/pagination.d.ts +17 -3
- package/dist/types/components/select/color.d.ts +12 -0
- package/dist/types/components/select/glyphs.d.ts +124 -0
- package/dist/types/components/select/select.d.ts +17 -3
- package/dist/types/components/sheet/sheet.d.ts +15 -0
- package/dist/types/components/stat-tile/stat-tile.d.ts +16 -5
- package/dist/types/components/tabbox/tabbox.d.ts +9 -0
- package/dist/types/components/table/table.d.ts +311 -18
- package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
- package/dist/types/components/tree/tree.d.ts +13 -0
- package/dist/types/components/view/record-view.d.ts +59 -2
- package/dist/types/components/view-settings/view-settings.d.ts +879 -0
- package/dist/types/components/workspace-switcher/workspace-switcher.d.ts +327 -0
- package/dist/types/core/breakpoint.d.ts +1 -1
- package/dist/types/core/component.d.ts +12 -2
- package/dist/types/core/i18n.d.ts +27 -3
- package/dist/types/core/icons.d.ts +23 -25
- package/dist/types/core/keyboard.d.ts +98 -1
- package/dist/types/core/messages.d.ts +382 -0
- package/dist/types/core/motion.d.ts +59 -0
- package/dist/types/core/overlay-host.d.ts +42 -1
- package/dist/types/core/overlay-stack.d.ts +260 -0
- package/dist/types/index.d.ts +10 -5
- package/dist/types/internal/app-rail.d.ts +38 -0
- package/dist/types/internal/menu-items.d.ts +71 -3
- package/dist/types/zeyos/connect.d.ts +6 -0
- package/dist/types/zeyos/form.d.ts +50 -9
- package/dist/types/zeyos/index.d.ts +2 -1
- package/dist/types/zeyos/launcher.d.ts +9 -0
- package/dist/types/zeyos/module-bar.d.ts +176 -0
- package/dist/types/zeyos/query.d.ts +110 -5
- package/dist/types/zeyos/schema.d.ts +24 -1
- package/dist/types/zeyos/table.d.ts +345 -22
- package/dist/types/zeyos/view.d.ts +26 -2
- package/dist/zx-zeyos.esm.js +6178 -1008
- package/dist/zx-zeyos.esm.min.js +53 -5
- package/dist/zx.css +2861 -827
- package/dist/zx.esm.js +8909 -1480
- package/dist/zx.esm.min.js +55 -7
- package/dist/zx.global.js +8911 -1482
- package/dist/zx.global.min.js +55 -7
- package/dist/zx.min.css +1 -1
- package/docs/api.json +1079 -117
- package/docs/llms.md +956 -154
- package/docs/llms.txt +9 -5
- package/package.json +1 -1
- package/src/components/account-menu/account-menu.css +35 -10
- package/src/components/account-menu/account-menu.js +12 -0
- package/src/components/activity-item/activity-item.css +1 -1
- package/src/components/activity-item/activity-item.js +3 -0
- package/src/components/activity-list/activity-list.css +38 -3
- package/src/components/activity-list/activity-list.js +61 -1
- package/src/components/app-icon/app-icon.css +14 -3
- package/src/components/app-icon/app-icon.js +11 -1
- package/src/components/app-rail/app-rail.css +59 -79
- package/src/components/app-sidebar/app-sidebar.css +304 -168
- package/src/components/app-sidebar/app-sidebar.js +667 -46
- package/src/components/aurora/aurora.css +5 -3
- package/src/components/avatar/avatar.css +32 -7
- package/src/components/avatar/avatar.js +72 -8
- package/src/components/badge/badge.css +54 -2
- package/src/components/badge/badge.js +29 -2
- package/src/components/breadcrumb/breadcrumb.css +1 -1
- package/src/components/button/button.css +67 -9
- package/src/components/button/button.js +38 -0
- package/src/components/calendar/calendar.css +47 -47
- package/src/components/calendar/calendar.js +26 -52
- package/src/components/card/card.css +4 -4
- package/src/components/card-view/card-view.css +14 -12
- package/src/components/card-view/card-view.js +13 -9
- package/src/components/card-view/record-card.js +14 -3
- package/src/components/chart/chart.css +4 -1
- package/src/components/chart/chart.js +12 -0
- package/src/components/check-button/check-button.css +10 -2
- package/src/components/checklist/checklist.css +9 -2
- package/src/components/checklist/checklist.js +11 -5
- package/src/components/checklist/field-adapter.js +3 -1
- package/src/components/code-editor/code-editor.css +7 -6
- package/src/components/code-editor/code-editor.js +19 -1
- package/src/components/code-editor/field-adapter.js +9 -2
- package/src/components/context-menu/context-menu.css +9 -4
- package/src/components/context-menu/context-menu.js +8 -2
- package/src/components/copy/copy-button.css +6 -0
- package/src/components/copy/copy-input.css +9 -4
- package/src/components/data-filter/data-filter.css +5 -4
- package/src/components/data-filter/data-filter.js +2 -2
- package/src/components/date-picker/date-picker.css +6 -6
- package/src/components/date-picker/date-range-picker.css +3 -3
- package/src/components/date-picker/time-picker.css +3 -3
- package/src/components/datebox/date-range-box.css +79 -2
- package/src/components/datebox/date-range-box.js +13 -2
- package/src/components/datebox/datebox.css +79 -2
- package/src/components/datebox/datebox.js +13 -2
- package/src/components/dialog/dialog.css +26 -0
- package/src/components/dialog/dialog.js +99 -17
- package/src/components/dock/dock.css +5 -4
- package/src/components/dropdown/dropdown.css +1 -1
- package/src/components/dropdown/dropdown.js +159 -72
- package/src/components/empty-state/empty-state.css +2 -2
- package/src/components/entity-ref/entity-ref.css +1 -1
- package/src/components/entity-ref/entity-ref.js +1 -1
- package/src/components/field/field.css +28 -9
- package/src/components/field/field.js +101 -29
- package/src/components/field-upload/field-upload.css +1 -1
- package/src/components/fieldset/external-adapter.js +363 -0
- package/src/components/fieldset/fieldset.css +5 -3
- package/src/components/fieldset/fieldset.js +38 -64
- package/src/components/file-item/file-item.js +144 -10
- package/src/components/file-list/file-list.js +144 -21
- package/src/components/filter/filter.css +86 -2
- package/src/components/filter/filter.js +307 -11
- package/src/components/filter-panel/filter-panel.css +1 -1
- package/src/components/filter-panel/filter-panel.js +149 -32
- package/src/components/finder/finder.css +16 -14
- package/src/components/form/form.css +5 -4
- package/src/components/form/form.js +11 -2
- package/src/components/grid/grid.css +1 -1
- package/src/components/groupbox/groupbox.css +1 -1
- package/src/components/kanban-view/kanban-card.js +11 -3
- package/src/components/kanban-view/kanban-view.css +42 -18
- package/src/components/kanban-view/kanban-view.js +9 -6
- package/src/components/launcher/launcher.css +108 -26
- package/src/components/launcher/launcher.js +530 -42
- package/src/components/list-toolbar/list-toolbar.css +13 -8
- package/src/components/list-toolbar/list-toolbar.js +40 -14
- package/src/components/master-panel/master-panel.css +2 -0
- package/src/components/master-panel/master-panel.js +0 -1
- package/src/components/menu-button/menu-button.css +72 -4
- package/src/components/menu-button/menu-button.js +160 -18
- package/src/components/message/message.css +33 -3
- package/src/components/message/message.js +264 -22
- package/src/components/modal/modal.css +1 -1
- package/src/components/modal/modal.js +83 -15
- package/src/components/module-bar/module-bar.css +486 -0
- package/src/components/module-bar/module-bar.js +1074 -0
- package/src/components/multi-value-editor/multi-value-editor.css +1 -1
- package/src/components/navigation-bar/navigation-bar.css +18 -9
- package/src/components/number-field/field-adapter.js +3 -1
- package/src/components/number-field/number-field.css +70 -0
- package/src/components/number-field/number-field.js +2 -0
- package/src/components/pagination/pagination.css +13 -11
- package/src/components/pagination/pagination.js +20 -4
- package/src/components/panel/panel.css +4 -3
- package/src/components/questionnaire/questionnaire.css +4 -4
- package/src/components/rating/field-adapter.js +2 -1
- package/src/components/rating/rating.css +8 -1
- package/src/components/search/search.css +4 -1
- package/src/components/select/color.js +22 -0
- package/src/components/select/field-adapter.js +2 -0
- package/src/components/select/glyphs.js +165 -0
- package/src/components/select/select.css +138 -45
- package/src/components/select/select.js +92 -47
- package/src/components/sheet/sheet.css +12 -9
- package/src/components/sheet/sheet.js +74 -32
- package/src/components/slider/field-adapter.js +6 -1
- package/src/components/slider/slider.css +18 -4
- package/src/components/slider/slider.js +1 -1
- package/src/components/sort-control/sort-control.css +4 -1
- package/src/components/split-view/split-view.css +1 -1
- package/src/components/stat-tile/stat-tile.css +42 -47
- package/src/components/stat-tile/stat-tile.js +17 -6
- package/src/components/stepper/stepper.css +1 -1
- package/src/components/tabbox/tabbox.css +41 -32
- package/src/components/tabbox/tabbox.js +5 -1
- package/src/components/table/table.css +187 -37
- package/src/components/table/table.js +718 -73
- package/src/components/tag-picker/field-adapter.js +2 -1
- package/src/components/tag-picker/tag-picker.css +83 -5
- package/src/components/tag-picker/tag-picker.js +25 -8
- package/src/components/timebox/timebox.css +22 -2
- package/src/components/toggle/toggle.css +12 -1
- package/src/components/toolbar/toolbar.js +2 -2
- package/src/components/tooltip/tooltip.css +4 -0
- package/src/components/tooltip/tooltip.js +44 -19
- package/src/components/tree/tree.css +26 -17
- package/src/components/tree/tree.js +31 -0
- package/src/components/value-list/value-list.css +3 -3
- package/src/components/view/record-view.css +1 -1
- package/src/components/view/record-view.js +178 -18
- package/src/components/view-settings/view-settings.css +285 -0
- package/src/components/view-settings/view-settings.js +1830 -0
- package/src/components/workspace-switcher/workspace-switcher.css +188 -0
- package/src/components/workspace-switcher/workspace-switcher.js +463 -0
- package/src/core/breakpoint.js +1 -1
- package/src/core/component.js +88 -17
- package/src/core/fontawesome.js +5 -1
- package/src/core/i18n.js +47 -4
- package/src/core/icons.js +204 -61
- package/src/core/keyboard.js +206 -11
- package/src/core/messages.js +427 -0
- package/src/core/motion.js +147 -0
- package/src/core/overlay-host.js +77 -3
- package/src/core/overlay-stack.js +487 -0
- package/src/core/position.js +22 -3
- package/src/index.js +10 -5
- package/src/internal/app-rail.js +135 -55
- package/src/internal/menu-items.js +61 -3
- package/src/zeyos/connect.js +6 -2
- package/src/zeyos/form.js +69 -27
- package/src/zeyos/index.js +4 -1
- package/src/zeyos/launcher.js +8 -3
- package/src/zeyos/module-bar.js +181 -0
- package/src/zeyos/query.js +197 -13
- package/src/zeyos/schema.js +144 -27
- package/src/zeyos/table.js +287 -84
- package/src/zeyos/view.js +49 -93
- package/styles/base.css +72 -1
- package/styles/icons.css +11 -1
- package/styles/print.css +1 -1
- package/styles/tokens/dark.css +117 -37
- package/styles/tokens/global.css +121 -27
- package/styles/tokens/semantic.css +140 -43
- package/styles/tokens/themes.css +7 -6
- package/styles/zx.css +3 -1
- package/styles/tokens/density.css +0 -9
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { STEP, STEP_LARGE, dragAxis, resolveSize } from '../../core/drag-axis.js';
|
|
2
2
|
import { focusTrap } from '../../core/keyboard.js';
|
|
3
|
-
import { h } from '../../core/dom.js';
|
|
3
|
+
import { h, resolveElement } from '../../core/dom.js';
|
|
4
4
|
import { clamp } from '../../core/util.js';
|
|
5
5
|
import { Dialog } from '../dialog/dialog.js';
|
|
6
6
|
|
|
@@ -112,36 +112,24 @@ export class Sheet extends Dialog {
|
|
|
112
112
|
* field would be undefined at best and a private one would throw.
|
|
113
113
|
*/
|
|
114
114
|
this._dock = null;
|
|
115
|
+
/** @type {Element|null} The element focus was on when the current `open()` began. */
|
|
116
|
+
this._openedFrom = null;
|
|
115
117
|
this.setSide(this.options.side);
|
|
116
118
|
this.el.dataset.backdrop = BACKDROPS.has(this.options.backdrop) ? this.options.backdrop : 'dim';
|
|
117
119
|
this.el.dataset.modality = modality(this.options.modal);
|
|
118
120
|
this.refs.close.hidden = !this.options.closeButton;
|
|
119
121
|
|
|
120
|
-
if (this.options.modal !== true) {
|
|
121
|
-
/*
|
|
122
|
-
* Escape and outside-click are free for a modal dialog and absent for a non-modal one: the
|
|
123
|
-
* browser only wires `cancel` for `showModal()`, and there is no `::backdrop` to click.
|
|
124
|
-
*/
|
|
125
|
-
this.listen(this.el, 'keydown', (event) => {
|
|
126
|
-
const keyboardEvent = /** @type {KeyboardEvent} */ (event);
|
|
127
|
-
if (keyboardEvent.key !== 'Escape' || !this.options.closable || this._dock) return;
|
|
128
|
-
keyboardEvent.preventDefault();
|
|
129
|
-
if (!this.emit('cancel').defaultPrevented) this.close();
|
|
130
|
-
});
|
|
131
|
-
if (this.options.lightDismiss) {
|
|
132
|
-
this.listen(document, 'pointerdown', (event) => {
|
|
133
|
-
const target = /** @type {Node} */ (event.target);
|
|
134
|
-
if (!this.isOpen() || this._dock || this.el.contains(target)) return;
|
|
135
|
-
this.close();
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
122
|
/*
|
|
141
123
|
* Registered after Dialog's own restore listener, so focus lands on the opener either way:
|
|
142
124
|
* the trap captured the same element, because `open()` activates it before the sheet shows.
|
|
143
125
|
*/
|
|
144
126
|
this.on('close', () => this.#trap?.deactivate());
|
|
127
|
+
// A sheet that was hosted inside a modal it was opened from goes home once it closes, so it
|
|
128
|
+
// neither sits in that dialog's focus search nor goes with the dialog when it is removed.
|
|
129
|
+
this.on('close', () => {
|
|
130
|
+
const home = super.mountTarget();
|
|
131
|
+
if (!this._dock && this.el.parentElement !== home && this.el.parentElement?.closest('dialog')) home.append(this.el);
|
|
132
|
+
});
|
|
145
133
|
|
|
146
134
|
if (this.#wantsHandle()) this.#buildHandle();
|
|
147
135
|
}
|
|
@@ -320,7 +308,19 @@ export class Sheet extends Dialog {
|
|
|
320
308
|
* @returns {Element}
|
|
321
309
|
*/
|
|
322
310
|
mountTarget() {
|
|
323
|
-
|
|
311
|
+
if (this._dock) return this._dock.el;
|
|
312
|
+
/*
|
|
313
|
+
* A floating non-modal sheet opened from inside a modal dialog lives inside that dialog: its
|
|
314
|
+
* popover still rises above it, but outside the dialog's subtree `showModal()` would leave it
|
|
315
|
+
* inert. Only asked while opening — `this.el` is unset during `render()` — so a sheet at rest
|
|
316
|
+
* stays in its theme scope. A modal sheet stacks above any dialog wherever it lives.
|
|
317
|
+
*/
|
|
318
|
+
if (this.el && this.options.modal !== true) {
|
|
319
|
+
const opener = this._openedFrom ?? resolveElement(this.options.scope);
|
|
320
|
+
const modal = modalAround(opener, this.el);
|
|
321
|
+
if (modal) return modal;
|
|
322
|
+
}
|
|
323
|
+
return super.mountTarget();
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
/**
|
|
@@ -362,11 +362,43 @@ export class Sheet extends Dialog {
|
|
|
362
362
|
this.el.returnValue = '';
|
|
363
363
|
this._present();
|
|
364
364
|
this.el.dataset.state = 'open';
|
|
365
|
-
|
|
366
|
-
else this.el.dataset.zxOverlayOrder = String(nextOverlayOrder());
|
|
365
|
+
this._syncOverlay();
|
|
367
366
|
}
|
|
368
367
|
|
|
369
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Describes the presentation for the overlay stack. A docked sheet is part of the layout and
|
|
371
|
+
* stays off it.
|
|
372
|
+
*
|
|
373
|
+
* Escape and outside-click are free for a modal dialog and absent for a non-modal one — the
|
|
374
|
+
* browser only wires `cancel` for `showModal()`, and there is no `::backdrop` to click — so a
|
|
375
|
+
* floating non-modal sheet takes both from the stack. That is what makes it aware of what it
|
|
376
|
+
* opened: Escape reaches it only once no popup above it takes the key, and a press inside a menu
|
|
377
|
+
* anchored in the sheet counts as inside the sheet, wherever the menu's panel lives.
|
|
378
|
+
* @returns {import('../../core/overlay-stack.js').OverlayEntryOptions|null}
|
|
379
|
+
* @protected
|
|
380
|
+
*/
|
|
381
|
+
_overlayEntry() {
|
|
382
|
+
if (this._dock) return null;
|
|
383
|
+
const entry = super._overlayEntry();
|
|
384
|
+
if (!entry || this.options.modal === true) return entry;
|
|
385
|
+
return {
|
|
386
|
+
...entry,
|
|
387
|
+
// An Escape pressed outside the sheet and everything it opened is not addressed to it.
|
|
388
|
+
onEscape: (event) => {
|
|
389
|
+
if (!this.options.closable || !this._overlayContains(/** @type {Node|null} */ (event.target))) return false;
|
|
390
|
+
if (!this.emit('cancel').defaultPrevented) this.close();
|
|
391
|
+
return undefined;
|
|
392
|
+
},
|
|
393
|
+
onOutsidePointer: this.options.lightDismiss ? () => { this.close(); } : undefined,
|
|
394
|
+
// Hosted inside a modal that has now closed, the sheet would stay open but never be seen.
|
|
395
|
+
onParentClose: () => {
|
|
396
|
+
const holder = this.el.parentElement?.closest('dialog');
|
|
397
|
+
if (holder && !holder.open && !holder.matches(':popover-open')) this.close();
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
370
402
|
/**
|
|
371
403
|
* Opens the sheet, trapping focus first where the modality asks for it.
|
|
372
404
|
* @returns {this}
|
|
@@ -374,11 +406,17 @@ export class Sheet extends Dialog {
|
|
|
374
406
|
*/
|
|
375
407
|
open() {
|
|
376
408
|
if (this.isOpen()) return this;
|
|
409
|
+
// Read before the trap moves focus: the element the reader was on decides where the sheet lives.
|
|
410
|
+
this._openedFrom = document.activeElement;
|
|
377
411
|
if (this.options.modal === 'trap-focus') {
|
|
378
412
|
this.#trap ??= focusTrap(this.el);
|
|
379
413
|
this.#trap.activate();
|
|
380
414
|
}
|
|
381
|
-
|
|
415
|
+
try {
|
|
416
|
+
super.open();
|
|
417
|
+
} finally {
|
|
418
|
+
this._openedFrom = null;
|
|
419
|
+
}
|
|
382
420
|
// The handle is built while the sheet is still closed, where it measures zero; only now is
|
|
383
421
|
// there a real size to report to a screen reader.
|
|
384
422
|
this.#syncHandleAria();
|
|
@@ -507,14 +545,18 @@ export class Sheet extends Dialog {
|
|
|
507
545
|
}
|
|
508
546
|
|
|
509
547
|
/**
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
* @
|
|
548
|
+
* The open modal dialog around an element, other than the sheet itself, or null.
|
|
549
|
+
* @param {Element|null|undefined} element
|
|
550
|
+
* @param {Element} self
|
|
551
|
+
* @returns {Element|null}
|
|
513
552
|
*/
|
|
514
|
-
function
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
553
|
+
function modalAround(element, self) {
|
|
554
|
+
if (!element || typeof element.closest !== 'function' || self.contains(element)) return null;
|
|
555
|
+
try {
|
|
556
|
+
return element.closest('dialog:modal');
|
|
557
|
+
} catch {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
518
560
|
}
|
|
519
561
|
|
|
520
562
|
/**
|
|
@@ -15,9 +15,13 @@ export function registerSliderFieldAdapter() {
|
|
|
15
15
|
...(options.props ?? {})
|
|
16
16
|
}));
|
|
17
17
|
let setting = false;
|
|
18
|
+
// The range input's native `change` bubbles out of the slider after the slider has reported
|
|
19
|
+
// the same commit; without this the Field would emit `change` twice per edit.
|
|
20
|
+
field.listen(slider.el, 'change', (event) => event.stopImmediatePropagation());
|
|
18
21
|
slider.on('change', (event) => {
|
|
19
22
|
if (!setting) field.emit('change', { value: event.detail.value });
|
|
20
23
|
});
|
|
24
|
+
slider.on('input', (event) => field.emit('input', { value: event.detail.value }));
|
|
21
25
|
return {
|
|
22
26
|
el: slider.el,
|
|
23
27
|
get: () => slider.get(),
|
|
@@ -30,7 +34,8 @@ export function registerSliderFieldAdapter() {
|
|
|
30
34
|
}
|
|
31
35
|
},
|
|
32
36
|
focus: () => slider.focus(),
|
|
33
|
-
setDisabled: (value) => (value ? slider.disable() : slider.enable())
|
|
37
|
+
setDisabled: (value) => (value ? slider.disable() : slider.enable()),
|
|
38
|
+
setReadonly: (value) => { slider.setReadonly(value); }
|
|
34
39
|
};
|
|
35
40
|
});
|
|
36
41
|
}
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
border: 2px solid var(--zx-color-bg-surface);
|
|
120
120
|
border-radius: var(--zx-radius-full);
|
|
121
121
|
background: var(--zx-color-accent);
|
|
122
|
-
box-shadow: var(--zx-shadow
|
|
122
|
+
box-shadow: var(--zx-surface-shadow);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
.zx-slider__input::-moz-range-thumb {
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
border: 2px solid var(--zx-color-bg-surface);
|
|
129
129
|
border-radius: var(--zx-radius-full);
|
|
130
130
|
background: var(--zx-color-accent);
|
|
131
|
-
box-shadow: var(--zx-shadow
|
|
131
|
+
box-shadow: var(--zx-surface-shadow);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
.zx-slider__input:focus-visible {
|
|
@@ -147,17 +147,31 @@
|
|
|
147
147
|
font-variant-numeric: tabular-nums;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
/*
|
|
151
|
+
* A mark sits under the thumb's centre at its value — half a thumb in from either end of the rail
|
|
152
|
+
* — and centres its label there, except that a label is never pushed past either end of the
|
|
153
|
+
* track: the outermost ones end flush with it instead of spilling out of the component.
|
|
154
|
+
* `--zx-slider-mark` is the value's position on the scale, from 0 to 1.
|
|
155
|
+
*/
|
|
150
156
|
.zx-slider__marks {
|
|
151
157
|
position: relative;
|
|
158
|
+
inline-size: 100%;
|
|
152
159
|
block-size: var(--zx-space-5);
|
|
153
160
|
margin-block-start: var(--zx-space-1);
|
|
161
|
+
container-type: inline-size;
|
|
154
162
|
}
|
|
155
163
|
|
|
156
164
|
.zx-slider__mark {
|
|
165
|
+
--zx-slider-mark-at: calc(var(--zx-slider-thumb) / 2
|
|
166
|
+
+ (100cqi - var(--zx-slider-thumb)) * var(--zx-slider-mark, 0));
|
|
157
167
|
position: absolute;
|
|
158
168
|
inset-block-start: 0;
|
|
159
|
-
inset-inline-start: var(--zx-slider-mark
|
|
160
|
-
transform: translateX(
|
|
169
|
+
inset-inline-start: var(--zx-slider-mark-at);
|
|
170
|
+
transform: translateX(clamp(
|
|
171
|
+
-1 * var(--zx-slider-mark-at),
|
|
172
|
+
-50%,
|
|
173
|
+
100cqi - var(--zx-slider-mark-at) - 100%
|
|
174
|
+
));
|
|
161
175
|
color: var(--zx-color-text-muted);
|
|
162
176
|
font-size: var(--zx-text-xs);
|
|
163
177
|
white-space: nowrap;
|
|
@@ -279,7 +279,7 @@ export class Slider extends Component {
|
|
|
279
279
|
const label = typeof mark === 'number' ? this._format(value) : String(mark.label ?? this._format(value));
|
|
280
280
|
element.append(h('span', {
|
|
281
281
|
class: 'zx-slider__mark',
|
|
282
|
-
style: { '--zx-slider-mark':
|
|
282
|
+
style: { '--zx-slider-mark': String(this._ratio(value)) }
|
|
283
283
|
}, label));
|
|
284
284
|
}
|
|
285
285
|
return element;
|
|
@@ -10,8 +10,11 @@
|
|
|
10
10
|
.zx-sort-control__direction {
|
|
11
11
|
inline-size: var(--zx-control-height);
|
|
12
12
|
padding: 0;
|
|
13
|
-
border-radius: var(--zx-radius
|
|
13
|
+
border-radius: var(--zx-control-radius);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
.zx-sort-control__direction:focus-visible { box-shadow: var(--zx-focus-ring); }
|
|
17
|
+
/* The arrow is the current direction, not decoration, so it keeps the text colour a ghost glyph
|
|
18
|
+
would otherwise give up. */
|
|
19
|
+
.zx-sort-control .zx-sort-control__direction > .zx-icon { color: var(--zx-color-text); }
|
|
17
20
|
.zx-sort-control .zx-select__control { background: var(--zx-color-bg-control); }
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* The tile is a size ladder, not a colour: the value is the loudest thing on it, the label is
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* a server assigns the metric.
|
|
2
|
+
* The tile is a size ladder, not a colour: the value is the loudest thing on it, the label is a
|
|
3
|
+
* quiet sentence-case line above it, and the caption, delta and sparkline are quieter than both.
|
|
4
|
+
* The icon is a muted glyph, not a coloured chip. `kind` tints the sparkline only — never the
|
|
5
|
+
* value, which has to stay at full text contrast whatever intent a server assigns the metric.
|
|
6
|
+
*
|
|
7
|
+
* A column, with the sparkline pushed to the foot: tiles in one row are as tall as the tallest,
|
|
8
|
+
* so their labels, values and sparklines line up whether or not each tile has every part.
|
|
6
9
|
*/
|
|
7
10
|
.zx-stat-tile {
|
|
8
11
|
--zx-stat-tile-value-size: var(--zx-text-2xl);
|
|
9
|
-
display:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
gap: var(--zx-space-1);
|
|
12
15
|
min-inline-size: 0;
|
|
13
16
|
padding: var(--zx-space-4);
|
|
14
17
|
border: 1px solid var(--zx-color-border);
|
|
@@ -38,7 +41,7 @@ button.zx-stat-tile:hover {
|
|
|
38
41
|
|
|
39
42
|
a.zx-stat-tile:focus-visible,
|
|
40
43
|
button.zx-stat-tile:focus-visible {
|
|
41
|
-
outline:
|
|
44
|
+
outline-color: transparent;
|
|
42
45
|
box-shadow: var(--zx-focus-ring);
|
|
43
46
|
}
|
|
44
47
|
|
|
@@ -60,25 +63,19 @@ button.zx-stat-tile:focus-visible {
|
|
|
60
63
|
.zx-stat-tile__label {
|
|
61
64
|
min-inline-size: 0;
|
|
62
65
|
overflow: hidden;
|
|
63
|
-
font-size: var(--zx-text-
|
|
64
|
-
font-weight:
|
|
65
|
-
letter-spacing: 0.04em;
|
|
66
|
+
font-size: var(--zx-text-sm);
|
|
67
|
+
font-weight: var(--zx-weight-medium);
|
|
66
68
|
line-height: 1.4;
|
|
67
|
-
text-transform: uppercase;
|
|
68
69
|
text-overflow: ellipsis;
|
|
69
70
|
white-space: nowrap;
|
|
70
71
|
color: var(--zx-color-text-muted);
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
.zx-stat-tile__icon {
|
|
74
|
-
display: grid;
|
|
75
|
+
display: inline-grid;
|
|
75
76
|
flex: none;
|
|
76
77
|
place-items: center;
|
|
77
|
-
|
|
78
|
-
block-size: 1.75rem;
|
|
79
|
-
border-radius: var(--zx-radius-full);
|
|
80
|
-
background: var(--zx-color-bg-muted);
|
|
81
|
-
color: var(--zx-color-text-muted);
|
|
78
|
+
color: var(--zx-color-text-subtle);
|
|
82
79
|
}
|
|
83
80
|
|
|
84
81
|
/*
|
|
@@ -87,21 +84,35 @@ button.zx-stat-tile:focus-visible {
|
|
|
87
84
|
*/
|
|
88
85
|
.zx-stat-tile__value {
|
|
89
86
|
min-inline-size: 0;
|
|
87
|
+
margin-block-start: var(--zx-space-1);
|
|
90
88
|
font-size: var(--zx-stat-tile-value-size);
|
|
91
|
-
font-weight:
|
|
89
|
+
font-weight: 650;
|
|
92
90
|
font-variant-numeric: tabular-nums;
|
|
91
|
+
letter-spacing: var(--zx-tracking-title);
|
|
93
92
|
line-height: 1.15;
|
|
94
93
|
overflow-wrap: break-word;
|
|
95
94
|
color: var(--zx-color-text);
|
|
96
95
|
}
|
|
97
96
|
|
|
97
|
+
/* What the value is out of, or measured in: one muted line, read with the value. */
|
|
98
|
+
.zx-stat-tile__caption {
|
|
99
|
+
min-inline-size: 0;
|
|
100
|
+
overflow: hidden;
|
|
101
|
+
color: var(--zx-color-text-muted);
|
|
102
|
+
font-size: var(--zx-text-sm);
|
|
103
|
+
font-variant-numeric: tabular-nums;
|
|
104
|
+
line-height: 1.4;
|
|
105
|
+
text-overflow: ellipsis;
|
|
106
|
+
white-space: nowrap;
|
|
107
|
+
}
|
|
108
|
+
|
|
98
109
|
.zx-stat-tile__delta {
|
|
99
110
|
display: flex;
|
|
100
111
|
gap: var(--zx-space-1);
|
|
101
112
|
align-items: baseline;
|
|
102
113
|
min-inline-size: 0;
|
|
103
114
|
font-size: var(--zx-text-xs);
|
|
104
|
-
font-weight:
|
|
115
|
+
font-weight: var(--zx-weight-medium);
|
|
105
116
|
font-variant-numeric: tabular-nums;
|
|
106
117
|
line-height: 1.4;
|
|
107
118
|
color: var(--zx-color-text-muted);
|
|
@@ -124,6 +135,12 @@ button.zx-stat-tile:focus-visible {
|
|
|
124
135
|
align-self: center;
|
|
125
136
|
}
|
|
126
137
|
|
|
138
|
+
.zx-stat-tile__delta-value {
|
|
139
|
+
/* A figure never breaks ("+23" over "pcs"); the comparison beside it gives way first. */
|
|
140
|
+
flex: none;
|
|
141
|
+
white-space: nowrap;
|
|
142
|
+
}
|
|
143
|
+
|
|
127
144
|
.zx-stat-tile__delta-label {
|
|
128
145
|
min-inline-size: 0;
|
|
129
146
|
overflow: hidden;
|
|
@@ -136,9 +153,12 @@ button.zx-stat-tile:focus-visible {
|
|
|
136
153
|
|
|
137
154
|
.zx-stat-tile__trend {
|
|
138
155
|
display: block;
|
|
156
|
+
flex: none;
|
|
157
|
+
box-sizing: content-box;
|
|
139
158
|
inline-size: 100%;
|
|
140
159
|
block-size: 2rem;
|
|
141
|
-
margin-block-start:
|
|
160
|
+
margin-block-start: auto;
|
|
161
|
+
padding-block-start: var(--zx-space-2);
|
|
142
162
|
color: var(--zx-color-text-placeholder);
|
|
143
163
|
}
|
|
144
164
|
|
|
@@ -147,32 +167,7 @@ button.zx-stat-tile:focus-visible {
|
|
|
147
167
|
gap: var(--zx-space-2);
|
|
148
168
|
}
|
|
149
169
|
|
|
150
|
-
/* Intent. The
|
|
151
|
-
.zx-stat-tile[data-kind="accent"] .zx-stat-tile__icon {
|
|
152
|
-
background: var(--zx-color-accent-subtle);
|
|
153
|
-
color: var(--zx-color-accent);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
.zx-stat-tile[data-kind="success"] .zx-stat-tile__icon {
|
|
157
|
-
background: var(--zx-color-success-bg);
|
|
158
|
-
color: var(--zx-color-success);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
.zx-stat-tile[data-kind="warning"] .zx-stat-tile__icon {
|
|
162
|
-
background: var(--zx-color-warning-bg);
|
|
163
|
-
color: var(--zx-color-warning);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.zx-stat-tile[data-kind="danger"] .zx-stat-tile__icon {
|
|
167
|
-
background: var(--zx-color-danger-bg);
|
|
168
|
-
color: var(--zx-color-danger);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.zx-stat-tile[data-kind="info"] .zx-stat-tile__icon {
|
|
172
|
-
background: var(--zx-color-info-bg);
|
|
173
|
-
color: var(--zx-color-info);
|
|
174
|
-
}
|
|
175
|
-
|
|
170
|
+
/* Intent. The sparkline carries it as its stroke; the icon stays a muted glyph. */
|
|
176
171
|
.zx-stat-tile[data-kind="accent"] .zx-stat-tile__trend {
|
|
177
172
|
color: var(--zx-color-accent);
|
|
178
173
|
}
|
|
@@ -25,6 +25,7 @@ import { skeleton } from '../skeleton/skeleton.js';
|
|
|
25
25
|
* @typedef {Object} StatTileNameParts
|
|
26
26
|
* @property {string} [label] The metric's name.
|
|
27
27
|
* @property {string} [value] The formatted value.
|
|
28
|
+
* @property {string} [caption] The context line under the value, e.g. `'of 13,184'`.
|
|
28
29
|
* @property {string} [delta] The change, already worded for speech (`'up 2'`, not `'+2'`).
|
|
29
30
|
* @property {string} [deltaLabel] What the change is measured against.
|
|
30
31
|
*/
|
|
@@ -36,6 +37,8 @@ import { skeleton } from '../skeleton/skeleton.js';
|
|
|
36
37
|
* @property {StatTileFormat} [format='number'] How a numeric value is rendered: a named mode, a
|
|
37
38
|
* callback receiving the raw value, or null to print the value unformatted.
|
|
38
39
|
* @property {string|null} [currency=null] ISO 4217 code used by `format: 'currency'`.
|
|
40
|
+
* @property {string} [caption=''] A muted context line under the value — what the number is out
|
|
41
|
+
* of or measured in, e.g. `'of 13,184'` or `'open tickets'`. Part of the accessible name.
|
|
39
42
|
* @property {string} [locale] BCP 47 tag for every formatter; defaults to the active language.
|
|
40
43
|
* @property {number|string|null} [delta=null] Change against the previous period. Rendered with
|
|
41
44
|
* its own sign and an arrow, never with colour alone.
|
|
@@ -46,9 +49,10 @@ import { skeleton } from '../skeleton/skeleton.js';
|
|
|
46
49
|
* @property {string} [deltaLabel=''] What the change is measured against, e.g. `'vs. last week'`.
|
|
47
50
|
* @property {number[]} [trend=[]] Values for a decorative sparkline. Fewer than two usable numbers
|
|
48
51
|
* render nothing.
|
|
49
|
-
* @property {string|null} [icon=null] Icon name from `icons.js`, shown beside the label
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
+
* @property {string|null} [icon=null] Icon name from `icons.js`, shown muted beside the label —
|
|
53
|
+
* a glyph, not a coloured chip.
|
|
54
|
+
* @property {StatTileKind} [kind='neutral'] Semantic intent. It tints the sparkline and never the
|
|
55
|
+
* value, which stays at full text contrast whatever intent a record carries.
|
|
52
56
|
* @property {string|null} [href=null] Destination. A tile with an href is an `<a>`.
|
|
53
57
|
* @property {boolean} [clickable=false] Renders a `<button>` without an href. Passing `onclick`
|
|
54
58
|
* sets this implicitly; set it directly to subscribe with `on('click', …)` instead.
|
|
@@ -93,6 +97,7 @@ const defaults = Object.freeze({
|
|
|
93
97
|
value: null,
|
|
94
98
|
format: 'number',
|
|
95
99
|
currency: null,
|
|
100
|
+
caption: '',
|
|
96
101
|
delta: null,
|
|
97
102
|
deltaKind: 'neutral',
|
|
98
103
|
deltaLabel: '',
|
|
@@ -306,17 +311,19 @@ export function statTileTag(options = {}) {
|
|
|
306
311
|
}
|
|
307
312
|
|
|
308
313
|
/**
|
|
309
|
-
* Composes the tile's accessible name: label, then value, then how it changed.
|
|
314
|
+
* Composes the tile's accessible name: label, then value with its caption, then how it changed.
|
|
310
315
|
*
|
|
311
316
|
* The sparkline is decoration and contributes nothing, and the delta arrives already worded —
|
|
312
|
-
* `'up 2'` rather than `'+2'` — because a screen reader should not have to interpret a glyph.
|
|
317
|
+
* `'up 2'` rather than `'+2'` — because a screen reader should not have to interpret a glyph. The
|
|
318
|
+
* caption is read with the value it qualifies (`'2,184 of 13,184'`), not as a separate clause.
|
|
313
319
|
*
|
|
314
320
|
* @param {StatTileNameParts} [parts={}] The pieces, already formatted and translated.
|
|
315
321
|
* @returns {string} The name, with empty pieces dropped rather than left as stray punctuation.
|
|
316
322
|
*/
|
|
317
323
|
export function statTileName(parts = {}) {
|
|
324
|
+
const value = [parts.value, parts.caption].map(trimmed).filter(Boolean).join(' ');
|
|
318
325
|
const change = [parts.delta, parts.deltaLabel].map(trimmed).filter(Boolean).join(' ');
|
|
319
|
-
return [trimmed(parts.label),
|
|
326
|
+
return [trimmed(parts.label), value, change].filter(Boolean).join(', ');
|
|
320
327
|
}
|
|
321
328
|
|
|
322
329
|
/**
|
|
@@ -391,10 +398,12 @@ function fillStatTile(root, options, message) {
|
|
|
391
398
|
currency: options.currency
|
|
392
399
|
});
|
|
393
400
|
const deltaLabel = asText(options.deltaLabel);
|
|
401
|
+
const caption = asText(options.caption).trim();
|
|
394
402
|
|
|
395
403
|
applyName(root, statTileName({
|
|
396
404
|
label,
|
|
397
405
|
value,
|
|
406
|
+
caption,
|
|
398
407
|
delta: delta === null ? '' : spokenDelta(delta, message),
|
|
399
408
|
deltaLabel
|
|
400
409
|
}));
|
|
@@ -407,6 +416,7 @@ function fillStatTile(root, options, message) {
|
|
|
407
416
|
// `Element`, not `HTMLElement`: the sparkline is an SVG element and belongs in the same list.
|
|
408
417
|
/** @type {Element[]} */
|
|
409
418
|
const children = [header, h('div', { class: 'zx-stat-tile__value' }, value)];
|
|
419
|
+
if (caption) children.push(h('div', { class: 'zx-stat-tile__caption' }, caption));
|
|
410
420
|
if (delta !== null) children.push(deltaBody(delta, deltaLabel, options.deltaKind));
|
|
411
421
|
const trend = trendBody(options.trend);
|
|
412
422
|
if (trend) children.push(trend);
|
|
@@ -481,6 +491,7 @@ function loadingBody(options) {
|
|
|
481
491
|
h('div', { class: 'zx-stat-tile__header' }, skeleton({ width: '55%', height: '0.75rem' })),
|
|
482
492
|
skeleton({ width: '70%', height: '1.75rem', radius: 'md' })
|
|
483
493
|
];
|
|
494
|
+
if (asText(options.caption).trim()) blocks.push(skeleton({ width: '35%', height: '0.75rem' }));
|
|
484
495
|
if (options.delta !== null && options.delta !== undefined) {
|
|
485
496
|
blocks.push(skeleton({ width: '40%', height: '0.75rem' }));
|
|
486
497
|
}
|