@zeyos/zx 4.3.4 → 5.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 +438 -0
- package/README.md +49 -10
- package/dist/types/components/activity-item/activity-item.d.ts +351 -0
- package/dist/types/components/activity-list/activity-list.d.ts +217 -0
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +33 -3
- 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 +23 -0
- package/dist/types/components/calendar/calendar.d.ts +19 -0
- package/dist/types/components/card/card.d.ts +7 -0
- package/dist/types/components/card-view/card-view.d.ts +11 -3
- package/dist/types/components/card-view/record-card.d.ts +7 -1
- 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 +149 -0
- package/dist/types/components/code-editor/field-adapter.d.ts +5 -0
- package/dist/types/components/context-menu/context-menu.d.ts +18 -2
- package/dist/types/components/date-picker/date-picker.d.ts +8 -0
- package/dist/types/components/date-picker/time-picker.d.ts +7 -0
- 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 +75 -6
- package/dist/types/components/dropdown/dropdown.d.ts +2 -2
- package/dist/types/components/entity-ref/entity-ref.d.ts +331 -0
- package/dist/types/components/field/field.d.ts +24 -2
- package/dist/types/components/fieldset/fieldset.d.ts +19 -1
- package/dist/types/components/file-item/file-item.d.ts +262 -0
- package/dist/types/components/file-list/file-list.d.ts +171 -0
- package/dist/types/components/filter/filter.d.ts +91 -36
- package/dist/types/components/filter-panel/filter-panel.d.ts +449 -0
- package/dist/types/components/form/form.d.ts +12 -1
- package/dist/types/components/kanban-view/kanban-card.d.ts +146 -0
- package/dist/types/components/kanban-view/kanban-view.d.ts +170 -4
- package/dist/types/components/launcher/launcher.d.ts +278 -26
- package/dist/types/components/list-toolbar/list-toolbar.d.ts +599 -0
- package/dist/types/components/menu-button/menu-button.d.ts +37 -42
- package/dist/types/components/message/message.d.ts +126 -17
- package/dist/types/components/modal/modal.d.ts +61 -2
- package/dist/types/components/navigation-bar/navigation-bar.d.ts +105 -21
- package/dist/types/components/panel/panel.d.ts +7 -0
- package/dist/types/components/search/search.d.ts +7 -0
- 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 +15 -3
- package/dist/types/components/sort-control/sort-control.d.ts +105 -0
- package/dist/types/components/stat-tile/stat-tile.d.ts +288 -0
- package/dist/types/components/tabbox/tabbox.d.ts +16 -0
- package/dist/types/components/table/table.d.ts +190 -1
- package/dist/types/components/table-view/table-view.d.ts +11 -5
- package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
- package/dist/types/components/timebox/timebox.d.ts +7 -0
- package/dist/types/components/tree/tree.d.ts +13 -0
- package/dist/types/components/value-list/value-list.d.ts +8 -0
- package/dist/types/components/view/record-view.d.ts +96 -5
- package/dist/types/core/breakpoint.d.ts +1 -1
- package/dist/types/core/icons.d.ts +37 -22
- package/dist/types/core/motion.d.ts +59 -0
- package/dist/types/core/overlay-host.d.ts +15 -1
- package/dist/types/index.d.ts +16 -3
- package/dist/types/internal/app-rail.d.ts +10 -0
- package/dist/types/internal/menu-items.d.ts +134 -0
- 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 +1 -1
- package/dist/types/zeyos/query.d.ts +79 -0
- package/dist/types/zeyos/schema.d.ts +24 -1
- package/dist/types/zeyos/table.d.ts +100 -14
- package/dist/types/zeyos/view.d.ts +10 -0
- package/dist/zx-zeyos.esm.js +2206 -489
- package/dist/zx-zeyos.esm.min.js +56 -1
- package/dist/zx.css +3322 -724
- package/dist/zx.esm.js +12244 -5809
- package/dist/zx.esm.min.js +58 -3
- package/dist/zx.global.js +12246 -5811
- package/dist/zx.global.min.js +58 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +1683 -146
- package/docs/llms.md +777 -110
- package/docs/llms.txt +14 -8
- package/package.json +1 -1
- package/src/components/account-menu/account-menu.css +35 -10
- package/src/components/activity-item/activity-item.css +137 -0
- package/src/components/activity-item/activity-item.js +602 -0
- package/src/components/activity-list/activity-list.css +99 -0
- package/src/components/activity-list/activity-list.js +409 -0
- package/src/components/app-icon/app-icon.css +1 -1
- package/src/components/app-rail/app-rail.css +191 -22
- package/src/components/app-sidebar/app-sidebar.css +349 -24
- package/src/components/app-sidebar/app-sidebar.js +86 -11
- 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/button/button.css +74 -8
- package/src/components/button/button.js +44 -1
- package/src/components/calendar/calendar.css +46 -46
- package/src/components/calendar/calendar.js +33 -3
- package/src/components/card/card.css +2 -2
- package/src/components/card/card.js +12 -1
- package/src/components/card-view/card-view.css +10 -7
- package/src/components/card-view/card-view.js +25 -9
- package/src/components/card-view/record-card.js +7 -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 +68 -0
- package/src/components/code-editor/code-editor.js +306 -0
- package/src/components/code-editor/field-adapter.js +34 -0
- package/src/components/context-menu/context-menu.css +87 -5
- package/src/components/context-menu/context-menu.js +22 -30
- package/src/components/copy/copy-button.css +6 -0
- package/src/components/copy/copy-input.css +8 -3
- package/src/components/data-filter/data-filter.css +5 -4
- package/src/components/date-picker/date-picker.css +6 -6
- package/src/components/date-picker/date-picker.js +22 -8
- package/src/components/date-picker/date-range-picker.css +3 -3
- package/src/components/date-picker/month-picker.js +3 -3
- package/src/components/date-picker/time-picker.css +1 -1
- package/src/components/date-picker/time-picker.js +20 -8
- 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 +174 -70
- package/src/components/dropdown/dropdown.css +1 -1
- package/src/components/dropdown/dropdown.js +7 -5
- package/src/components/empty-state/empty-state.css +2 -2
- package/src/components/entity-ref/entity-ref.css +182 -0
- package/src/components/entity-ref/entity-ref.js +496 -0
- package/src/components/field/field.css +28 -9
- package/src/components/field/field.js +68 -26
- package/src/components/field-upload/field-upload.css +1 -1
- package/src/components/fieldset/fieldset.css +5 -3
- package/src/components/fieldset/fieldset.js +28 -5
- package/src/components/file-item/file-item.css +88 -0
- package/src/components/file-item/file-item.js +505 -0
- package/src/components/file-list/file-list.css +51 -0
- package/src/components/file-list/file-list.js +320 -0
- package/src/components/filter/filter.css +27 -1
- package/src/components/filter/filter.js +205 -47
- package/src/components/filter-panel/filter-panel.css +104 -0
- package/src/components/filter-panel/filter-panel.js +964 -0
- package/src/components/finder/finder.css +15 -13
- 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 +247 -0
- package/src/components/kanban-view/kanban-view.css +226 -8
- package/src/components/kanban-view/kanban-view.js +255 -12
- package/src/components/launcher/launcher.css +107 -25
- package/src/components/launcher/launcher.js +557 -63
- package/src/components/list-toolbar/list-toolbar.css +194 -0
- package/src/components/list-toolbar/list-toolbar.js +851 -0
- package/src/components/master-panel/master-panel.css +2 -0
- package/src/components/menu-button/menu-button.css +87 -5
- package/src/components/menu-button/menu-button.js +86 -46
- package/src/components/message/message.css +38 -5
- package/src/components/message/message.js +230 -17
- package/src/components/modal/modal.css +15 -0
- package/src/components/modal/modal.js +159 -3
- package/src/components/navigation-bar/navigation-bar.css +95 -11
- package/src/components/navigation-bar/navigation-bar.js +171 -4
- package/src/components/number-field/field-adapter.js +1 -0
- 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 +8 -6
- package/src/components/panel/panel.css +1 -1
- package/src/components/panel/panel.js +13 -2
- package/src/components/rating/rating.css +7 -0
- package/src/components/search/search.js +17 -5
- package/src/components/select/color.js +22 -0
- package/src/components/select/field-adapter.js +6 -1
- package/src/components/select/glyphs.js +165 -0
- package/src/components/select/select.css +138 -45
- package/src/components/select/select.js +86 -47
- package/src/components/sheet/sheet.css +11 -8
- package/src/components/slider/slider.css +16 -2
- package/src/components/slider/slider.js +1 -1
- package/src/components/sort-control/sort-control.css +20 -0
- package/src/components/sort-control/sort-control.js +140 -0
- package/src/components/stat-tile/stat-tile.css +208 -0
- package/src/components/stat-tile/stat-tile.js +601 -0
- package/src/components/tabbox/tabbox.css +32 -27
- package/src/components/tabbox/tabbox.js +17 -2
- package/src/components/table/table.css +155 -31
- package/src/components/table/table.js +386 -33
- package/src/components/table-view/table-view.css +8 -2
- package/src/components/table-view/table-view.js +21 -6
- package/src/components/tag-picker/tag-picker.css +86 -6
- package/src/components/tag-picker/tag-picker.js +25 -8
- package/src/components/timebox/timebox.css +22 -2
- package/src/components/timebox/timebox.js +18 -5
- 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 +10 -3
- package/src/components/tree/tree.css +25 -16
- package/src/components/tree/tree.js +31 -0
- package/src/components/value-list/value-list.css +3 -3
- package/src/components/value-list/value-list.js +22 -6
- package/src/components/view/record-view.css +5 -0
- package/src/components/view/record-view.js +100 -7
- package/src/core/breakpoint.js +1 -1
- package/src/core/icons.js +259 -57
- package/src/core/motion.js +147 -0
- package/src/core/overlay-host.js +21 -2
- package/src/index.js +30 -3
- package/src/internal/app-rail.js +18 -1
- package/src/internal/menu-items.js +142 -0
- package/src/zeyos/connect.js +6 -2
- package/src/zeyos/form.js +67 -27
- package/src/zeyos/index.js +1 -1
- package/src/zeyos/query.js +153 -0
- package/src/zeyos/schema.js +144 -27
- package/src/zeyos/table.js +68 -5
- package/src/zeyos/view.js +6 -0
- package/styles/base.css +56 -0
- package/styles/icons.css +11 -1
- package/styles/print.css +192 -0
- package/styles/tokens/dark.css +113 -37
- package/styles/tokens/global.css +121 -27
- package/styles/tokens/semantic.css +100 -31
- package/styles/tokens/themes.css +7 -6
- package/styles/zx.css +11 -1
- package/styles/tokens/density.css +0 -9
|
@@ -3,8 +3,12 @@ import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
|
|
|
3
3
|
import { icon } from '../../core/icons.js';
|
|
4
4
|
import { typeahead } from '../../core/keyboard.js';
|
|
5
5
|
import { position } from '../../core/position.js';
|
|
6
|
-
import {
|
|
6
|
+
import { uid } from '../../core/util.js';
|
|
7
|
+
import { itemColor } from './color.js';
|
|
7
8
|
import { matchItems } from './filter.js';
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_STATUSES, STATUS_INDICATORS, STATUS_TONES, normalizePriorityLevel, priorityIcon, statusIcon
|
|
11
|
+
} from './glyphs.js';
|
|
8
12
|
|
|
9
13
|
/** @typedef {string|number|boolean|symbol|bigint|null|undefined} Primitive */
|
|
10
14
|
/** @typedef {Record<string, any>|Primitive} SelectItem */
|
|
@@ -27,6 +31,7 @@ import { matchItems } from './filter.js';
|
|
|
27
31
|
* @property {unknown} [value=null] Initially selected ID or item.
|
|
28
32
|
* @property {boolean} [disabled=false] Whether interaction is disabled.
|
|
29
33
|
* @property {string} [placeholder=''] Empty control text.
|
|
34
|
+
* @property {string} [label=''] Accessible combobox name; falls back to the placeholder.
|
|
30
35
|
* @property {boolean} [clearable=false] Whether selection may be cleared.
|
|
31
36
|
* @property {false|'local'|((query: string) => Promise<SelectItem[]>|SelectItem[])} [filter=false] Filtering mode.
|
|
32
37
|
* @property {Array<SelectValueReader>|null} [searchKeys=null] Fields searched by local filtering.
|
|
@@ -48,7 +53,9 @@ import { matchItems } from './filter.js';
|
|
|
48
53
|
* @property {string} [recentLabel='Recent'] Heading above recent choices.
|
|
49
54
|
* @property {boolean|SelectAction} [create=false] Optional create command at the bottom.
|
|
50
55
|
* @property {string|((item: SelectItem)=>unknown)} [iconKey='icon'] Item icon reader.
|
|
51
|
-
* @property {string|((item: SelectItem)=>unknown)} [colorKey='color'] Optional icon colour reader
|
|
56
|
+
* @property {string|((item: SelectItem)=>unknown)} [colorKey='color'] Optional icon colour reader: a CSS
|
|
57
|
+
* colour or a token reference such as `var(--zx-color-tone-blue-fg)`. Without one the glyph is a
|
|
58
|
+
* neutral tile in muted ink.
|
|
52
59
|
* @property {string|((item: SelectItem)=>unknown)} [subtitleKey='description'] Secondary-text reader.
|
|
53
60
|
* @property {((item: SelectItem, context:{selected:boolean})=>Node|string|null)|null} [renderIcon=null] Entity icon renderer.
|
|
54
61
|
*/
|
|
@@ -68,18 +75,6 @@ const PRIORITY_KEYS = [
|
|
|
68
75
|
];
|
|
69
76
|
const PRIORITY_FALLBACKS = ['Lowest', 'Low', 'Medium', 'High', 'Highest'];
|
|
70
77
|
|
|
71
|
-
const DEFAULT_STATUSES = Object.freeze([
|
|
72
|
-
['backlog', 'status.backlog', 'Backlog', 'dotted', 'muted'],
|
|
73
|
-
['todo', 'status.todo', 'Todo', 'ring', 'neutral'],
|
|
74
|
-
['in-progress', 'status.inProgress', 'In Progress', 'progress', 'warning'],
|
|
75
|
-
['in-review', 'status.inReview', 'In Review', 'review', 'info'],
|
|
76
|
-
['ready', 'status.ready', 'Ready to Merge', 'ready', 'accent'],
|
|
77
|
-
['waiting', 'status.waiting', 'Waiting for feedback', 'waiting', 'warning'],
|
|
78
|
-
['done', 'status.done', 'Done', 'done', 'info'],
|
|
79
|
-
['canceled', 'status.canceled', 'Canceled', 'canceled', 'muted'],
|
|
80
|
-
['duplicate', 'status.duplicate', 'Duplicate', 'duplicate', 'muted']
|
|
81
|
-
]);
|
|
82
|
-
|
|
83
78
|
/**
|
|
84
79
|
* APG editable combobox with optional local or asynchronous filtering.
|
|
85
80
|
*
|
|
@@ -109,6 +104,7 @@ export class Select extends Component {
|
|
|
109
104
|
value: null,
|
|
110
105
|
disabled: false,
|
|
111
106
|
placeholder: '',
|
|
107
|
+
label: '',
|
|
112
108
|
clearable: false,
|
|
113
109
|
filter: false,
|
|
114
110
|
searchKeys: null,
|
|
@@ -123,7 +119,7 @@ export class Select extends Component {
|
|
|
123
119
|
render() {
|
|
124
120
|
// Instance state initialized here because render() runs inside the base
|
|
125
121
|
// constructor, before class-field initializers would run (and clobber it).
|
|
126
|
-
this._items = []; this._visibleItems = []; this._fixedItems = []; this._visibleFixed = []; this._actions = []; this._selected = null; this._optionEntries = []; this._activeIndex = -1; this._open = false; this._disabled = false; this._loading = false; this._requestSequence = 0; this._queryTimer = null; this._position = null; this._createdRoot = false; this._root = null; this._snapshot = null;
|
|
122
|
+
this._items = []; this._visibleItems = []; this._fixedItems = []; this._visibleFixed = []; this._actions = []; this._selected = null; this._optionEntries = []; this._activeIndex = -1; this._open = false; this._disabled = false; this._loading = false; this._requestSequence = 0; this._queryTimer = null; this._position = null; this._createdRoot = false; this._root = null; this._snapshot = null; this._pointerModality = false;
|
|
127
123
|
this._createdRoot = this.el === null;
|
|
128
124
|
const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
|
|
129
125
|
this.el = root;
|
|
@@ -136,6 +132,7 @@ export class Select extends Component {
|
|
|
136
132
|
class: 'zx-select__input',
|
|
137
133
|
type: 'text',
|
|
138
134
|
role: 'combobox',
|
|
135
|
+
ariaLabel: String(this.options.label || this.options.placeholder || 'Select'),
|
|
139
136
|
autocomplete: 'off',
|
|
140
137
|
spellcheck: false,
|
|
141
138
|
ariaExpanded: 'false',
|
|
@@ -206,7 +203,22 @@ export class Select extends Component {
|
|
|
206
203
|
|
|
207
204
|
this.listen(input, 'click', () => this.open());
|
|
208
205
|
this.listen(input, 'input', () => this._onInput());
|
|
209
|
-
this.listen(input, 'keydown', (event) =>
|
|
206
|
+
this.listen(input, 'keydown', (event) => {
|
|
207
|
+
if (!isModifierKey(/** @type {KeyboardEvent} */ (event))) root.removeAttribute('data-focus-origin');
|
|
208
|
+
this._onKeydown(/** @type {KeyboardEvent} */ (event));
|
|
209
|
+
});
|
|
210
|
+
// A text input matches :focus-visible however it was focused, read-only or not, so a click on
|
|
211
|
+
// a read-only Select would draw the keyboard ring. Track how the user last interacted and mark
|
|
212
|
+
// a pointer focus; the ring then follows keyboard focus only, and returns on the first key.
|
|
213
|
+
this.listen(input, 'focus', () => {
|
|
214
|
+
if (this._pointerModality && /** @type {HTMLInputElement} */ (input).readOnly) {
|
|
215
|
+
root.setAttribute('data-focus-origin', 'pointer');
|
|
216
|
+
} else root.removeAttribute('data-focus-origin');
|
|
217
|
+
});
|
|
218
|
+
this.listen(input, 'blur', () => root.removeAttribute('data-focus-origin'));
|
|
219
|
+
this.listen(document, 'keydown', (event) => {
|
|
220
|
+
if (!isModifierKey(/** @type {KeyboardEvent} */ (event))) this._pointerModality = false;
|
|
221
|
+
}, { capture: true });
|
|
210
222
|
this.listen(toggle, 'click', () => {
|
|
211
223
|
this.focus();
|
|
212
224
|
if (this._open) this.close();
|
|
@@ -219,6 +231,7 @@ export class Select extends Component {
|
|
|
219
231
|
this.listen(panel, 'pointerdown', (event) => event.preventDefault());
|
|
220
232
|
this.listen(panel, 'click', (event) => this._onOptionClick(event));
|
|
221
233
|
this.listen(document, 'pointerdown', (event) => {
|
|
234
|
+
this._pointerModality = true;
|
|
222
235
|
if (this._open && !root.contains(/** @type {Node} */ (event.target))) this.close();
|
|
223
236
|
}, { capture: true });
|
|
224
237
|
|
|
@@ -378,26 +391,44 @@ export class Select extends Component {
|
|
|
378
391
|
}
|
|
379
392
|
|
|
380
393
|
/**
|
|
381
|
-
* Creates the five-level priority preset
|
|
394
|
+
* Creates the five-level priority preset: values 0 (lowest) to 4 (highest), each drawn as grey
|
|
395
|
+
* signal bars filled to its level, except the highest, which is the one coloured level — a
|
|
396
|
+
* danger square with an exclamation mark. A "No priority" row (value null, a row of dashes)
|
|
397
|
+
* heads the list and labels the empty control; `clearable: false` removes it.
|
|
382
398
|
* @param {Element|string|null} target Component target.
|
|
383
|
-
* @param {SelectOptions} [options={}] Select overrides other than
|
|
399
|
+
* @param {SelectOptions} [options={}] Select overrides other than items and renderers.
|
|
400
|
+
* `clearable` defaults to true here; `noneLabel` and `placeholder` default to the
|
|
401
|
+
* `priority.none` message ("No priority").
|
|
384
402
|
* @returns {Select}
|
|
385
403
|
*/
|
|
386
404
|
static priority(target, options = {}) {
|
|
387
405
|
const requestedValue = options.value ?? null;
|
|
388
|
-
const
|
|
406
|
+
const clearable = options.clearable ?? true;
|
|
407
|
+
/** @type {Select|null} */
|
|
408
|
+
let select = null;
|
|
409
|
+
// The constructor renders the list once before `select` is assigned; the label is settled by
|
|
410
|
+
// the setItems() below, which renders it again.
|
|
411
|
+
const noneLabel = () => options.noneLabel
|
|
412
|
+
?? (select ? select._message('priority.none', 'No priority') : 'No priority');
|
|
413
|
+
select = new Select(target, {
|
|
389
414
|
...options,
|
|
415
|
+
clearable,
|
|
390
416
|
value: null,
|
|
391
417
|
items: [],
|
|
392
418
|
renderItem(item) {
|
|
393
419
|
return priorityContent(item, true);
|
|
394
420
|
},
|
|
421
|
+
renderNone: options.renderNone ?? (() => priorityContent({ level: null, name: noneLabel() }, true)),
|
|
395
422
|
renderValueAdornment(item) { return priorityIndicator(item.level); }
|
|
396
423
|
});
|
|
424
|
+
if (clearable && !options.placeholder) {
|
|
425
|
+
/** @type {HTMLInputElement} */ (select.refs.input).placeholder = noneLabel();
|
|
426
|
+
}
|
|
427
|
+
const preset = select;
|
|
397
428
|
select.setItems(PRIORITY_KEYS.map((key, level) => {
|
|
398
|
-
let name = level === 2 ?
|
|
399
|
-
if (name === 'priority.medium') name =
|
|
400
|
-
else if (level !== 2) name =
|
|
429
|
+
let name = level === 2 ? preset.msg('priority.medium') : key;
|
|
430
|
+
if (name === 'priority.medium') name = preset._message(key, PRIORITY_FALLBACKS[level]);
|
|
431
|
+
else if (level !== 2) name = preset._message(key, PRIORITY_FALLBACKS[level]);
|
|
401
432
|
return { ID: level, name, level };
|
|
402
433
|
}));
|
|
403
434
|
select.set(requestedValue, { silent: true });
|
|
@@ -450,10 +481,10 @@ export class Select extends Component {
|
|
|
450
481
|
const value = readSelectItem(item, iconKey);
|
|
451
482
|
if (value && typeof value === 'object' && typeof value.nodeType === 'number') return value.cloneNode(true);
|
|
452
483
|
if (typeof value !== 'string' || !value) return null;
|
|
453
|
-
const color = readSelectItem(item, colorKey);
|
|
484
|
+
const color = itemColor(readSelectItem(item, colorKey));
|
|
454
485
|
return h('span', {
|
|
455
486
|
class: 'zx-select__entity-glyph',
|
|
456
|
-
style:
|
|
487
|
+
style: color ? { '--zx-select-entity-color': color } : null,
|
|
457
488
|
ariaHidden: 'true'
|
|
458
489
|
}, icon(value, { size: 14 }));
|
|
459
490
|
};
|
|
@@ -989,28 +1020,26 @@ function priorityContent(item, label) {
|
|
|
989
1020
|
label ? h('span', {}, String(item.name)) : null);
|
|
990
1021
|
}
|
|
991
1022
|
|
|
992
|
-
/**
|
|
1023
|
+
/**
|
|
1024
|
+
* The priority glyph `priorityIcon()` draws, carrying the Select's own class as well so styles
|
|
1025
|
+
* written against it keep applying.
|
|
1026
|
+
* @param {unknown} level Priority level 0–4, or null for none.
|
|
1027
|
+
* @returns {Node}
|
|
1028
|
+
*/
|
|
993
1029
|
function priorityIndicator(level) {
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
dataset: { level: normalized },
|
|
998
|
-
ariaHidden: 'true'
|
|
999
|
-
}, Array.from({ length: 5 }, (_, index) => h('span', {
|
|
1000
|
-
dataset: { filled: String(index <= normalized) }
|
|
1001
|
-
})));
|
|
1030
|
+
const glyph = priorityIcon(/** @type {number|null} */ (normalizePriorityLevel(level)));
|
|
1031
|
+
glyph.classList.add('zx-select__priority-icon');
|
|
1032
|
+
return glyph;
|
|
1002
1033
|
}
|
|
1003
1034
|
|
|
1004
1035
|
/** @param {unknown} input @returns {Record<string, any>|null} */
|
|
1005
1036
|
function normalizeStatusItem(input) {
|
|
1006
1037
|
if (!input || typeof input !== 'object' || input.ID == null || input.name == null) return null;
|
|
1007
|
-
const indicators = ['dotted', 'ring', 'progress', 'review', 'ready', 'waiting', 'done', 'canceled', 'duplicate'];
|
|
1008
|
-
const tones = ['muted', 'neutral', 'warning', 'info', 'accent', 'success', 'danger'];
|
|
1009
1038
|
return {
|
|
1010
1039
|
...input,
|
|
1011
1040
|
name: String(input.name),
|
|
1012
|
-
indicator:
|
|
1013
|
-
tone:
|
|
1041
|
+
indicator: STATUS_INDICATORS.includes(String(input.indicator)) ? String(input.indicator) : 'ring',
|
|
1042
|
+
tone: STATUS_TONES.includes(String(input.tone)) ? String(input.tone) : 'muted',
|
|
1014
1043
|
description: input.description == null ? '' : String(input.description),
|
|
1015
1044
|
shortcut: input.shortcut == null ? '' : String(input.shortcut)
|
|
1016
1045
|
};
|
|
@@ -1026,17 +1055,16 @@ function statusContent(item) {
|
|
|
1026
1055
|
item.shortcut ? h('kbd', { class: 'zx-select__status-shortcut' }, item.shortcut) : null);
|
|
1027
1056
|
}
|
|
1028
1057
|
|
|
1029
|
-
/**
|
|
1058
|
+
/**
|
|
1059
|
+
* The status glyph `statusIcon()` draws for a settled status item, carrying the Select's own class
|
|
1060
|
+
* as well so styles written against it keep applying.
|
|
1061
|
+
* @param {Record<string, any>} item Normalized status item.
|
|
1062
|
+
* @returns {Node}
|
|
1063
|
+
*/
|
|
1030
1064
|
function statusIndicator(item) {
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
dataset: { indicator, tone: String(item.tone ?? 'muted') },
|
|
1035
|
-
ariaHidden: 'true'
|
|
1036
|
-
});
|
|
1037
|
-
if (indicator === 'done') marker.append(icon('check', { size: 8 }));
|
|
1038
|
-
if (indicator === 'canceled') marker.append(icon('x', { size: 7 }));
|
|
1039
|
-
return marker;
|
|
1065
|
+
const glyph = statusIcon({ indicator: item.indicator ?? 'ring', tone: item.tone ?? 'muted' });
|
|
1066
|
+
glyph.classList.add('zx-select__status-indicator');
|
|
1067
|
+
return glyph;
|
|
1040
1068
|
}
|
|
1041
1069
|
|
|
1042
1070
|
/** Change emitted when the selected value changes. @event Select#change @type {CustomEvent<SelectChangeDetail>} */
|
|
@@ -1058,6 +1086,17 @@ function readItem(item, reader) {
|
|
|
1058
1086
|
return item[reader];
|
|
1059
1087
|
}
|
|
1060
1088
|
|
|
1089
|
+
/**
|
|
1090
|
+
* Reports a key press that says nothing about how the user is moving focus: a lone modifier, or a
|
|
1091
|
+
* shortcut chord the page or the browser handles.
|
|
1092
|
+
* @param {KeyboardEvent} event Key event.
|
|
1093
|
+
* @returns {boolean}
|
|
1094
|
+
*/
|
|
1095
|
+
function isModifierKey(event) {
|
|
1096
|
+
return ['Shift', 'Control', 'Alt', 'Meta', 'CapsLock'].includes(event.key)
|
|
1097
|
+
|| event.ctrlKey || event.metaKey;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1061
1100
|
/** @param {KeyboardEvent} event @returns {boolean} */
|
|
1062
1101
|
function isPrintable(event) {
|
|
1063
1102
|
return event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
inset-block-start: 0;
|
|
58
58
|
inset-block-end: auto;
|
|
59
59
|
border-block-end: 1px solid var(--zx-color-overlay-border);
|
|
60
|
-
border-end-start-radius: var(--zx-sheet-radius, var(--zx-radius-
|
|
61
|
-
border-end-end-radius: var(--zx-sheet-radius, var(--zx-radius-
|
|
60
|
+
border-end-start-radius: var(--zx-sheet-radius, var(--zx-radius-lg));
|
|
61
|
+
border-end-end-radius: var(--zx-sheet-radius, var(--zx-radius-lg));
|
|
62
62
|
--zx-sheet-enter: 0 -100%;
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
inset-block-start: auto;
|
|
67
67
|
inset-block-end: 0;
|
|
68
68
|
border-block-start: 1px solid var(--zx-color-overlay-border);
|
|
69
|
-
border-start-start-radius: var(--zx-sheet-radius, var(--zx-radius-
|
|
70
|
-
border-start-end-radius: var(--zx-sheet-radius, var(--zx-radius-
|
|
69
|
+
border-start-start-radius: var(--zx-sheet-radius, var(--zx-radius-lg));
|
|
70
|
+
border-start-end-radius: var(--zx-sheet-radius, var(--zx-radius-lg));
|
|
71
71
|
--zx-sheet-enter: 0 100%;
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -105,11 +105,14 @@
|
|
|
105
105
|
* `display: none` immediately — so `display` and `overlay` transition with `allow-discrete`, which
|
|
106
106
|
* defers the discrete jump until the rest of the transition has finished. `@starting-style` gives
|
|
107
107
|
* the entry its from-state. Where either is unsupported the sheet simply appears and disappears.
|
|
108
|
+
*
|
|
109
|
+
* The slide runs on the drawer curve — most of the distance at once, then a settle — while the
|
|
110
|
+
* fade keeps the ordinary ease, so the panel arrives decisively without flashing in.
|
|
108
111
|
*/
|
|
109
112
|
@media (prefers-reduced-motion: no-preference) {
|
|
110
113
|
.zx-sheet {
|
|
111
114
|
transition:
|
|
112
|
-
translate var(--zx-dur-base) var(--zx-ease),
|
|
115
|
+
translate var(--zx-dur-base) var(--zx-ease-drawer),
|
|
113
116
|
opacity var(--zx-dur-base) var(--zx-ease),
|
|
114
117
|
display var(--zx-dur-base) allow-discrete,
|
|
115
118
|
overlay var(--zx-dur-base) allow-discrete;
|
|
@@ -193,7 +196,7 @@
|
|
|
193
196
|
@media (prefers-reduced-motion: no-preference) {
|
|
194
197
|
.zx-sheet[data-docked] {
|
|
195
198
|
transition:
|
|
196
|
-
flex-basis var(--zx-dur-base) var(--zx-ease),
|
|
199
|
+
flex-basis var(--zx-dur-base) var(--zx-ease-drawer),
|
|
197
200
|
opacity var(--zx-dur-base) var(--zx-ease),
|
|
198
201
|
display var(--zx-dur-base) allow-discrete,
|
|
199
202
|
overlay var(--zx-dur-base) allow-discrete;
|
|
@@ -363,8 +366,8 @@
|
|
|
363
366
|
@media (prefers-reduced-motion: no-preference) {
|
|
364
367
|
.zx-sheet[data-stack] {
|
|
365
368
|
transition:
|
|
366
|
-
transform var(--zx-dur-base) var(--zx-ease),
|
|
367
|
-
translate var(--zx-dur-base) var(--zx-ease),
|
|
369
|
+
transform var(--zx-dur-base) var(--zx-ease-drawer),
|
|
370
|
+
translate var(--zx-dur-base) var(--zx-ease-drawer),
|
|
368
371
|
opacity var(--zx-dur-base) var(--zx-ease),
|
|
369
372
|
display var(--zx-dur-base) allow-discrete,
|
|
370
373
|
overlay var(--zx-dur-base) allow-discrete;
|
|
@@ -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;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.zx-sort-control {
|
|
2
|
+
display: inline-grid;
|
|
3
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
4
|
+
align-items: center;
|
|
5
|
+
gap: var(--zx-space-1);
|
|
6
|
+
min-inline-size: 0;
|
|
7
|
+
inline-size: 100%;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.zx-sort-control__direction {
|
|
11
|
+
inline-size: var(--zx-control-height);
|
|
12
|
+
padding: 0;
|
|
13
|
+
border-radius: var(--zx-control-radius);
|
|
14
|
+
}
|
|
15
|
+
|
|
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); }
|
|
20
|
+
.zx-sort-control .zx-select__control { background: var(--zx-color-bg-control); }
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Component } from '../../core/component.js';
|
|
2
|
+
import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
|
|
3
|
+
import { icon } from '../../core/icons.js';
|
|
4
|
+
import { button } from '../button/button.js';
|
|
5
|
+
import { Select } from '../select/select.js';
|
|
6
|
+
|
|
7
|
+
/** @typedef {{id:string,label:string}} SortField */
|
|
8
|
+
/** @typedef {{id:string,dir:'asc'|'desc'}} SortValue */
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} SortControlOptions
|
|
11
|
+
* @property {SortField[]} [fields=[]] Available sort fields, in display order.
|
|
12
|
+
* @property {SortValue|null} [value=null] Initial sort; null chooses the first field ascending.
|
|
13
|
+
* @property {string} [label='Sort by'] Accessible field label and direction-name prefix.
|
|
14
|
+
* @property {{asc?:string,desc?:string}} [labels={}] Current/next direction labels, defaulting to Ascending/Descending.
|
|
15
|
+
* @property {boolean} [disabled=false] Whether interaction is disabled.
|
|
16
|
+
* @property {(event:CustomEvent<{value:SortValue}>)=>void} [onchange] Atomic field/direction change.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A searchable field selector with an adjacent direction toggle and one shared sort value.
|
|
21
|
+
* @fires SortControl#change
|
|
22
|
+
* @extends {Component<SortControlOptions>}
|
|
23
|
+
*/
|
|
24
|
+
export class SortControl extends Component {
|
|
25
|
+
static cssName = 'sort-control';
|
|
26
|
+
/** @type {SortControlOptions} */
|
|
27
|
+
static defaults = { fields: [], value: null, label: 'Sort by', labels: {}, disabled: false };
|
|
28
|
+
|
|
29
|
+
/** @returns {HTMLElement} */
|
|
30
|
+
render() {
|
|
31
|
+
const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
|
|
32
|
+
this._createdRoot = this.el === null;
|
|
33
|
+
this._snapshot = this._createdRoot ? null : snapshotTarget(root);
|
|
34
|
+
this.el = root;
|
|
35
|
+
this._value = normalizeSortValue(this.options.value, this.options.fields);
|
|
36
|
+
this._disabled = Boolean(this.options.disabled || !this.options.fields.length);
|
|
37
|
+
this._direction = button({ kind: 'ghost', disabled: this._disabled });
|
|
38
|
+
this._direction.classList.add('zx-sort-control__direction');
|
|
39
|
+
this.listen(this._direction, 'click', () => {
|
|
40
|
+
if (this._disabled || !this._value) return;
|
|
41
|
+
this.setValue({ id: this._value.id, dir: this._value.dir === 'asc' ? 'desc' : 'asc' });
|
|
42
|
+
});
|
|
43
|
+
this._select = new Select(null, {
|
|
44
|
+
items: sortChoices(this.options.fields), valueKey: 'id', labelKey: 'label',
|
|
45
|
+
value: this._value?.id ?? null, label: this.options.label,
|
|
46
|
+
filter: 'local', disabled: this._disabled,
|
|
47
|
+
onchange: ({ detail }) => {
|
|
48
|
+
if (!detail.item) return;
|
|
49
|
+
this.setValue({ id: detail.item.id, dir: this._value?.dir ?? 'asc' });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
// The owned Select's DOM event must not duplicate the composite's atomic event.
|
|
53
|
+
this.listen(this._select.el, 'zx-change', (event) => event.stopPropagation());
|
|
54
|
+
root.replaceChildren(this._direction, this._select.el);
|
|
55
|
+
this._syncDirection();
|
|
56
|
+
return root;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Returns a defensive copy of the current sort. @returns {SortValue|null} */
|
|
60
|
+
getValue() { return this._value ? { ...this._value } : null; }
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Replaces the field and direction atomically; rejects unavailable fields and directions.
|
|
64
|
+
* @param {SortValue|null} value Sort state, or null for the first field ascending.
|
|
65
|
+
* @param {{silent?:boolean}} [options={}] Suppress change when silent.
|
|
66
|
+
* @returns {this}
|
|
67
|
+
*/
|
|
68
|
+
setValue(value, { silent = false } = {}) {
|
|
69
|
+
const next = normalizeSortValue(value, this.options.fields);
|
|
70
|
+
const changed = sortKey(next) !== sortKey(this._value);
|
|
71
|
+
this._value = next;
|
|
72
|
+
this._select.set(next?.id ?? null, { silent: true });
|
|
73
|
+
this._syncDirection();
|
|
74
|
+
if (changed && !silent) this.emit('change', { value: this.getValue() });
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Enables both controls when fields are available. @returns {this} */
|
|
79
|
+
enable() {
|
|
80
|
+
this._disabled = !this.options.fields.length;
|
|
81
|
+
if (!this._disabled) this._select.enable();
|
|
82
|
+
this._direction.disabled = this._disabled;
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
/** Disables both controls and closes the option panel. @returns {this} */
|
|
86
|
+
disable() {
|
|
87
|
+
this._disabled = true;
|
|
88
|
+
this._select.disable();
|
|
89
|
+
this._direction.disabled = true;
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Updates the icon and accessible current/next direction text. @private @returns {void} */
|
|
94
|
+
_syncDirection() {
|
|
95
|
+
const dir = this._value?.dir ?? 'asc';
|
|
96
|
+
const next = dir === 'asc' ? 'desc' : 'asc';
|
|
97
|
+
const labels = { asc: 'Ascending', desc: 'Descending', ...this.options.labels };
|
|
98
|
+
this._direction.replaceChildren(icon(dir === 'asc' ? 'arrow-up-wide-short' : 'arrow-down-wide-short'));
|
|
99
|
+
this._direction.dataset.direction = dir;
|
|
100
|
+
this._direction.setAttribute('aria-label', `${this.options.label}: ${labels[dir]}`);
|
|
101
|
+
this._direction.title = `${labels[dir]} → ${labels[next]}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Focuses the searchable control. @returns {this} */
|
|
105
|
+
focus() { this._select.focus(); return this; }
|
|
106
|
+
/** Releases the owned Select and restores an enhanced target. @returns {void} */
|
|
107
|
+
destroy() {
|
|
108
|
+
this._select?.destroy();
|
|
109
|
+
super.destroy();
|
|
110
|
+
if (!this._createdRoot) restoreTarget(this.el, this._snapshot);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Validates a sort against available fields without retaining caller-owned state.
|
|
116
|
+
* @param {SortValue|null} value Requested sort.
|
|
117
|
+
* @param {SortField[]} fields Available fields.
|
|
118
|
+
* @returns {SortValue|null} Normalized sort, or null for an empty catalogue.
|
|
119
|
+
*/
|
|
120
|
+
export function normalizeSortValue(value, fields) {
|
|
121
|
+
if (!fields.length) return null;
|
|
122
|
+
if (value == null) return { id: String(fields[0].id), dir: 'asc' };
|
|
123
|
+
if (!fields.some((field) => String(field.id) === String(value.id))) throw new RangeError('Unknown sort field');
|
|
124
|
+
if (value.dir !== 'asc' && value.dir !== 'desc') throw new RangeError('Unknown sort direction');
|
|
125
|
+
return { id: String(value.id), dir: value.dir };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Builds one searchable record per field without retaining caller-owned objects.
|
|
130
|
+
* @param {SortField[]} fields Available fields.
|
|
131
|
+
* @returns {SortField[]} Choice records.
|
|
132
|
+
*/
|
|
133
|
+
export function sortChoices(fields) {
|
|
134
|
+
return fields.map((field) => ({ id: String(field.id), label: field.label }));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** @param {SortValue|null} value Sort state. @returns {string} Stable choice key. */
|
|
138
|
+
function sortKey(value) { return value ? JSON.stringify([value.id, value.dir]) : ''; }
|
|
139
|
+
|
|
140
|
+
/** @event SortControl#change @type {CustomEvent<{value:SortValue}>} */
|