@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
|
@@ -6,13 +6,19 @@
|
|
|
6
6
|
min-inline-size: 0;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
.zx-table-
|
|
9
|
+
.zx-table-view__toolbar {
|
|
10
|
+
min-inline-size: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.zx-table-view[data-loading="true"] .zx-table__column-controls,
|
|
14
|
+
.zx-table-view[data-loading="true"] .zx-record-view__field-controls {
|
|
10
15
|
pointer-events: none;
|
|
11
16
|
opacity: .62;
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
@media (prefers-reduced-motion: no-preference) {
|
|
15
|
-
.zx-table-view .zx-table__column-controls
|
|
20
|
+
.zx-table-view .zx-table__column-controls,
|
|
21
|
+
.zx-table-view .zx-record-view__field-controls {
|
|
16
22
|
transition: opacity var(--zx-dur-fast) var(--zx-ease);
|
|
17
23
|
}
|
|
18
24
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
|
|
3
3
|
import { formatTableCell, Table } from '../table/table.js';
|
|
4
|
-
import { RecordView, readViewField } from '../view/record-view.js';
|
|
4
|
+
import { normalizeViewFieldControls, RecordView, readViewField } from '../view/record-view.js';
|
|
5
5
|
|
|
6
6
|
/** @typedef {Record<string, any>} TableViewRecord */
|
|
7
7
|
/** @typedef {import('../view/record-view.js').ViewField} ViewField */
|
|
@@ -20,7 +20,10 @@ import { RecordView, readViewField } from '../view/record-view.js';
|
|
|
20
20
|
* @property {unknown[]} [selection=[]] Initially selected record ids.
|
|
21
21
|
* @property {string[]} [fieldOrder=[]] Preferred stable field order.
|
|
22
22
|
* @property {string[]} [hiddenFields=[]] Initially hidden field ids.
|
|
23
|
-
* @property {boolean} [fieldControls=true]
|
|
23
|
+
* @property {boolean|import('../view/record-view.js').ViewFieldControlsConfig} [fieldControls=true]
|
|
24
|
+
* Show the accessible visibility and reorder disclosure. `true` keeps the composed Table's own
|
|
25
|
+
* column chooser and `false` omits it; an object renders the shared record-view chooser instead,
|
|
26
|
+
* with a translated `label` and, with `target`, mounted into a host element.
|
|
24
27
|
* @property {string|Node|(()=>string|Node)|null} [emptyText=null] Empty-result content.
|
|
25
28
|
* @property {TableOptions} [table={}] Advanced low-level Table options. Shared options win where
|
|
26
29
|
* their contracts overlap; all other options and callbacks are forwarded unchanged.
|
|
@@ -74,9 +77,15 @@ export class TableView extends RecordView {
|
|
|
74
77
|
this._tableViewDestroyed = false;
|
|
75
78
|
this._tableHost = h('div', { class: 'zx-table-view__table' });
|
|
76
79
|
this._initRecordView(root);
|
|
80
|
+
// Only a `fieldControls` object replaces Table's own chooser, and only an unresolved target
|
|
81
|
+
// keeps that replacement inside the view — so `true` and `false` render exactly as before.
|
|
82
|
+
const fieldControls = normalizeViewFieldControls(this.options.fieldControls);
|
|
83
|
+
this._tableViewToolbar = fieldControls.custom && !this._viewFieldControlsTarget
|
|
84
|
+
? h('div', { class: 'zx-record-view__toolbar zx-table-view__toolbar' })
|
|
85
|
+
: null;
|
|
77
86
|
|
|
78
87
|
try {
|
|
79
|
-
root.replaceChildren(this._tableHost);
|
|
88
|
+
root.replaceChildren(...[this._tableViewToolbar, this._tableHost].filter(Boolean));
|
|
80
89
|
const viewOptions = /** @type {Readonly<TableViewOptions>} */ (this.options);
|
|
81
90
|
const options = tableOptionsForView(viewOptions.table, {
|
|
82
91
|
fields: this.getFields(),
|
|
@@ -93,9 +102,11 @@ export class TableView extends RecordView {
|
|
|
93
102
|
// Seed selection before bridging events so construction stays silent like RecordView itself.
|
|
94
103
|
this.table.setSelection(this.getSelectionIds());
|
|
95
104
|
this._bridgeTableEvents();
|
|
105
|
+
if (fieldControls.custom) this._mountViewFieldControls(this._tableViewToolbar ?? root);
|
|
96
106
|
return root;
|
|
97
107
|
} catch (error) {
|
|
98
108
|
this.table?.destroy();
|
|
109
|
+
this._removeViewFieldControls();
|
|
99
110
|
if (!created) restoreTarget(root, this._tableViewOriginal);
|
|
100
111
|
throw error;
|
|
101
112
|
}
|
|
@@ -302,10 +313,14 @@ export function fieldsToTableColumns(fields) {
|
|
|
302
313
|
* Builds the composed Table configuration. Shared options deliberately overwrite overlapping
|
|
303
314
|
* low-level options; every advanced option remains untouched.
|
|
304
315
|
* @param {TableOptions|undefined} table Advanced Table options.
|
|
305
|
-
* @param {{fields:ViewField[],data:TableViewRecord[],recordId:string|((record:TableViewRecord)=>unknown),sort:ViewSort|null,sortMode:'local'|'server',selectable:false|'single'|'multi',hiddenFields:string[],fieldControls:boolean,emptyText:string|Node|(()=>string|Node)|null}} shared Shared state.
|
|
316
|
+
* @param {{fields:ViewField[],data:TableViewRecord[],recordId:string|((record:TableViewRecord)=>unknown),sort:ViewSort|null,sortMode:'local'|'server',selectable:false|'single'|'multi',hiddenFields:string[],fieldControls:boolean|import('../view/record-view.js').ViewFieldControlsConfig,emptyText:string|Node|(()=>string|Node)|null}} shared Shared state.
|
|
306
317
|
* @returns {TableOptions} Table configuration.
|
|
307
318
|
*/
|
|
308
319
|
export function tableOptionsForView(table, shared) {
|
|
320
|
+
// A customised chooser is rendered by TableView from the shared field state, so Table's own one
|
|
321
|
+
// stands down; `true`/`false` keep Table's chooser exactly as it has always been.
|
|
322
|
+
const controls = normalizeViewFieldControls(shared.fieldControls);
|
|
323
|
+
const tableChooser = controls.enabled && !controls.custom;
|
|
309
324
|
return {
|
|
310
325
|
...(table && typeof table === 'object' ? table : {}),
|
|
311
326
|
columns: fieldsToTableColumns(shared.fields),
|
|
@@ -315,8 +330,8 @@ export function tableOptionsForView(table, shared) {
|
|
|
315
330
|
sortMode: shared.sortMode,
|
|
316
331
|
selectable: shared.selectable,
|
|
317
332
|
hiddenColumns: [...shared.hiddenFields],
|
|
318
|
-
columnVisibility:
|
|
319
|
-
columnReorder:
|
|
333
|
+
columnVisibility: tableChooser,
|
|
334
|
+
columnReorder: tableChooser,
|
|
320
335
|
emptyText: shared.emptyText
|
|
321
336
|
};
|
|
322
337
|
}
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
align-items: center;
|
|
11
11
|
gap: var(--zx-space-1);
|
|
12
12
|
min-block-size: var(--zx-control-height);
|
|
13
|
-
padding: 3px
|
|
13
|
+
padding: 3px;
|
|
14
|
+
/* The tags sit further from the field's leading edge than from its other three. */
|
|
15
|
+
padding-inline-start: var(--zx-space-2);
|
|
14
16
|
border: 1px solid var(--zx-color-border-control);
|
|
15
17
|
border-radius: var(--zx-control-radius);
|
|
16
18
|
background: var(--zx-color-bg-control);
|
|
@@ -30,6 +32,12 @@
|
|
|
30
32
|
cursor: default;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
/* Read-only, the picker is its tags: nothing to type into and no list to open, so no input and no
|
|
36
|
+
chevron — and a full row of tags does not wrap them onto an empty second line. */
|
|
37
|
+
.zx-tag-picker[data-readonly] :is(.zx-tag-picker__input, .zx-tag-picker__toggle) {
|
|
38
|
+
display: none;
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
.zx-tag-picker__tags {
|
|
34
42
|
display: contents;
|
|
35
43
|
}
|
|
@@ -63,6 +71,38 @@
|
|
|
63
71
|
color: var(--zx-tag-color, var(--zx-color-accent));
|
|
64
72
|
}
|
|
65
73
|
|
|
74
|
+
/* A categorical tone: the tag takes the Badge tone palette — the tone's tint as fill, its ink as
|
|
75
|
+
text, no border — and an icon tile the same pair, so a label keeps one colour everywhere. */
|
|
76
|
+
.zx-tag-picker__tag[data-tone] {
|
|
77
|
+
border-color: transparent;
|
|
78
|
+
background: var(--zx-tag-tone-bg);
|
|
79
|
+
color: var(--zx-tag-tone-fg);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.zx-tag-picker__visual[data-tone] {
|
|
83
|
+
background: var(--zx-tag-tone-bg);
|
|
84
|
+
color: var(--zx-tag-tone-fg);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.zx-tag-picker .zx-tag-picker__tag[data-tone] .zx-tag-picker__remove {
|
|
88
|
+
color: inherit;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.zx-tag-picker .zx-tag-picker__tag[data-tone] .zx-tag-picker__remove:hover {
|
|
92
|
+
background: color-mix(in srgb, currentColor 14%, transparent);
|
|
93
|
+
color: inherit;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
:is(.zx-tag-picker__tag, .zx-tag-picker__visual)[data-tone="blue"] { --zx-tag-tone-bg: var(--zx-color-tone-blue-bg); --zx-tag-tone-fg: var(--zx-color-tone-blue-fg); }
|
|
97
|
+
:is(.zx-tag-picker__tag, .zx-tag-picker__visual)[data-tone="teal"] { --zx-tag-tone-bg: var(--zx-color-tone-teal-bg); --zx-tag-tone-fg: var(--zx-color-tone-teal-fg); }
|
|
98
|
+
:is(.zx-tag-picker__tag, .zx-tag-picker__visual)[data-tone="green"] { --zx-tag-tone-bg: var(--zx-color-tone-green-bg); --zx-tag-tone-fg: var(--zx-color-tone-green-fg); }
|
|
99
|
+
:is(.zx-tag-picker__tag, .zx-tag-picker__visual)[data-tone="amber"] { --zx-tag-tone-bg: var(--zx-color-tone-amber-bg); --zx-tag-tone-fg: var(--zx-color-tone-amber-fg); }
|
|
100
|
+
:is(.zx-tag-picker__tag, .zx-tag-picker__visual)[data-tone="orange"] { --zx-tag-tone-bg: var(--zx-color-tone-orange-bg); --zx-tag-tone-fg: var(--zx-color-tone-orange-fg); }
|
|
101
|
+
:is(.zx-tag-picker__tag, .zx-tag-picker__visual)[data-tone="red"] { --zx-tag-tone-bg: var(--zx-color-tone-red-bg); --zx-tag-tone-fg: var(--zx-color-tone-red-fg); }
|
|
102
|
+
:is(.zx-tag-picker__tag, .zx-tag-picker__visual)[data-tone="pink"] { --zx-tag-tone-bg: var(--zx-color-tone-pink-bg); --zx-tag-tone-fg: var(--zx-color-tone-pink-fg); }
|
|
103
|
+
:is(.zx-tag-picker__tag, .zx-tag-picker__visual)[data-tone="violet"] { --zx-tag-tone-bg: var(--zx-color-tone-violet-bg); --zx-tag-tone-fg: var(--zx-color-tone-violet-fg); }
|
|
104
|
+
:is(.zx-tag-picker__tag, .zx-tag-picker__visual)[data-tone="slate"] { --zx-tag-tone-bg: var(--zx-color-tone-slate-bg); --zx-tag-tone-fg: var(--zx-color-tone-slate-fg); }
|
|
105
|
+
|
|
66
106
|
.zx-tag-picker__tag:not(:has(.zx-tag-picker__remove)) {
|
|
67
107
|
padding-inline-end: var(--zx-space-2);
|
|
68
108
|
}
|
|
@@ -123,7 +163,7 @@
|
|
|
123
163
|
max-inline-size: calc(100vw - var(--zx-space-4));
|
|
124
164
|
overflow: hidden;
|
|
125
165
|
border: 1px solid var(--zx-color-overlay-border);
|
|
126
|
-
border-radius: var(--zx-radius-
|
|
166
|
+
border-radius: var(--zx-radius-lg);
|
|
127
167
|
background-color: var(--zx-color-overlay-surface);
|
|
128
168
|
background-image: var(--zx-overlay-surface-image);
|
|
129
169
|
background-clip: padding-box;
|
|
@@ -154,8 +194,11 @@
|
|
|
154
194
|
display: flex;
|
|
155
195
|
align-items: center;
|
|
156
196
|
gap: var(--zx-space-2);
|
|
157
|
-
|
|
158
|
-
|
|
197
|
+
/* The same row as a Select option and a menu item. */
|
|
198
|
+
min-block-size: calc(var(--zx-control-height) - 2px);
|
|
199
|
+
padding: var(--zx-space-1) var(--zx-space-2);
|
|
200
|
+
border-radius: var(--zx-radius-sm);
|
|
201
|
+
line-height: 1.35;
|
|
159
202
|
cursor: pointer;
|
|
160
203
|
}
|
|
161
204
|
|
|
@@ -163,9 +206,9 @@
|
|
|
163
206
|
background: var(--zx-color-overlay-hover);
|
|
164
207
|
}
|
|
165
208
|
|
|
209
|
+
/* A chosen tag keeps its check mark; the row itself stays in the text colour. */
|
|
166
210
|
.zx-tag-picker__option[aria-selected="true"] {
|
|
167
|
-
color: var(--zx-color-
|
|
168
|
-
font-weight: 550;
|
|
211
|
+
color: var(--zx-color-text);
|
|
169
212
|
}
|
|
170
213
|
|
|
171
214
|
.zx-tag-picker__option[aria-disabled="true"] {
|
|
@@ -200,3 +243,40 @@
|
|
|
200
243
|
font-size: var(--zx-text-sm);
|
|
201
244
|
text-align: center;
|
|
202
245
|
}
|
|
246
|
+
|
|
247
|
+
/*
|
|
248
|
+
* Quiet fields. Inside `[data-zx-fields="quiet"]` the tags read as the value: no border, no fill,
|
|
249
|
+
* no chevron. Hover brings the hover fill and the chevron back; focus or an open list brings back
|
|
250
|
+
* the border, the control fill and the focus ring. Disabled and read-only pickers never light up.
|
|
251
|
+
*/
|
|
252
|
+
[data-zx-fields="quiet"] .zx-tag-picker__control {
|
|
253
|
+
border-color: transparent;
|
|
254
|
+
background: transparent;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
[data-zx-fields="quiet"] .zx-tag-picker__toggle {
|
|
258
|
+
opacity: 0;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
[data-zx-fields="quiet"] .zx-tag-picker:not([data-disabled], [data-readonly]):hover .zx-tag-picker__control {
|
|
262
|
+
background: var(--zx-color-bg-hover);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
[data-zx-fields="quiet"] .zx-tag-picker:not([data-disabled], [data-readonly]):is(:hover, [data-focus], [data-state="open"]) .zx-tag-picker__toggle {
|
|
266
|
+
opacity: 1;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
[data-zx-fields="quiet"] .zx-tag-picker:not([data-disabled], [data-readonly]):is([data-focus], [data-state="open"]) .zx-tag-picker__control {
|
|
270
|
+
border-color: var(--zx-color-border-control);
|
|
271
|
+
background: var(--zx-color-bg-control);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
275
|
+
[data-zx-fields="quiet"] .zx-tag-picker__control {
|
|
276
|
+
transition: background-color var(--zx-dur-fast) var(--zx-ease), border-color var(--zx-dur-fast) var(--zx-ease);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
[data-zx-fields="quiet"] .zx-tag-picker__toggle {
|
|
280
|
+
transition: opacity var(--zx-dur-fast) var(--zx-ease);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
@@ -2,7 +2,9 @@ import { Component } from '../../core/component.js';
|
|
|
2
2
|
import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
|
|
3
3
|
import { icon } from '../../core/icons.js';
|
|
4
4
|
import { position } from '../../core/position.js';
|
|
5
|
-
import {
|
|
5
|
+
import { uid } from '../../core/util.js';
|
|
6
|
+
import { normalizeBadgeTone } from '../badge/badge.js';
|
|
7
|
+
import { itemColor } from '../select/color.js';
|
|
6
8
|
import { matchItems } from '../select/filter.js';
|
|
7
9
|
|
|
8
10
|
/** @typedef {Record<string, any>|string|number} TagItem */
|
|
@@ -15,7 +17,12 @@ import { matchItems } from '../select/filter.js';
|
|
|
15
17
|
* @property {TagValueReader} [valueKey='ID'] Item ID property or reader.
|
|
16
18
|
* @property {TagLabelReader} [labelKey='name'] Item label property or reader.
|
|
17
19
|
* @property {TagValueReader} [iconKey='icon'] Optional item icon property or reader.
|
|
18
|
-
* @property {TagValueReader} [colorKey='color'] Optional item colour property or reader
|
|
20
|
+
* @property {TagValueReader} [colorKey='color'] Optional item colour property or reader: a CSS colour
|
|
21
|
+
* or a token reference such as `var(--zx-color-tone-blue-fg)`.
|
|
22
|
+
* @property {TagValueReader} [toneKey='tone'] Optional item tone property or reader: a categorical
|
|
23
|
+
* tone name from the Badge palette (`blue`, `teal`, `green`, `amber`, `orange`, `red`, `pink`,
|
|
24
|
+
* `violet`, `slate`). A toned tag takes the palette's fill and ink, as a Badge with that `tone`
|
|
25
|
+
* does; a tone wins over a colour, and unknown names are ignored.
|
|
19
26
|
* @property {Array<TagValueReader>|null} [searchKeys=null] Fields searched by local filtering.
|
|
20
27
|
* @property {'local'|((query: string) => Promise<TagItem[]>|TagItem[])} [filter='local'] Filtering mode.
|
|
21
28
|
* @property {number} [minQuery=0] Minimum async query length.
|
|
@@ -26,7 +33,8 @@ import { matchItems } from '../select/filter.js';
|
|
|
26
33
|
* @property {string} [placeholder=''] Empty-state input text.
|
|
27
34
|
* @property {number} [listHeight=260] Maximum list height in pixels.
|
|
28
35
|
* @property {boolean} [disabled=false] Whether interaction is disabled.
|
|
29
|
-
* @property {boolean} [readonly=false]
|
|
36
|
+
* @property {boolean} [readonly=false] When true the picker shows only its tags — no input, chevron, or
|
|
37
|
+
* remove buttons.
|
|
30
38
|
* @property {((item: TagItem) => Node|string)|null} [renderItem=null] Option renderer.
|
|
31
39
|
* @property {((item: TagItem) => Node|string)|null} [renderTag=null] Tag-label renderer.
|
|
32
40
|
* @property {Record<string, string>|Record<string, Record<string, string>>} [msg] Localized messages.
|
|
@@ -69,6 +77,7 @@ export class TagPicker extends Component {
|
|
|
69
77
|
labelKey: 'name',
|
|
70
78
|
iconKey: 'icon',
|
|
71
79
|
colorKey: 'color',
|
|
80
|
+
toneKey: 'tone',
|
|
72
81
|
searchKeys: null,
|
|
73
82
|
filter: 'local',
|
|
74
83
|
minQuery: 0,
|
|
@@ -415,12 +424,13 @@ export class TagPicker extends Component {
|
|
|
415
424
|
_renderTags() {
|
|
416
425
|
const tags = this._selected.map((item) => {
|
|
417
426
|
const label = this._labelOf(item);
|
|
418
|
-
const
|
|
427
|
+
const tone = this._toneOf(item);
|
|
428
|
+
const color = tone ? null : this._colorOf(item);
|
|
419
429
|
const content = this.options.renderTag ? this.options.renderTag(item)
|
|
420
430
|
: [this._iconOf(item), h('span', { class: 'zx-tag-picker__tag-label' }, label)];
|
|
421
431
|
const tag = h('span', {
|
|
422
432
|
class: 'zx-tag-picker__tag',
|
|
423
|
-
dataset: { colored: String(Boolean(color)) },
|
|
433
|
+
dataset: { colored: String(Boolean(color)), tone: tone ?? undefined },
|
|
424
434
|
style: color ? { '--zx-tag-color': color } : null
|
|
425
435
|
}, content);
|
|
426
436
|
if (!this._readonly && !this._disabled) {
|
|
@@ -790,17 +800,24 @@ export class TagPicker extends Component {
|
|
|
790
800
|
? /** @type {Node} */ (value).cloneNode(true)
|
|
791
801
|
: typeof value === 'string' && value.trim() ? icon(value, { size: 12 }) : null;
|
|
792
802
|
if (!visual) return null;
|
|
803
|
+
const tone = this._toneOf(item);
|
|
804
|
+
const color = tone ? null : this._colorOf(item);
|
|
793
805
|
return h('span', {
|
|
794
806
|
class: 'zx-tag-picker__visual',
|
|
795
|
-
|
|
807
|
+
dataset: { tone: tone ?? undefined },
|
|
808
|
+
style: color ? { '--zx-tag-color': color } : null,
|
|
796
809
|
ariaHidden: 'true'
|
|
797
810
|
}, visual);
|
|
798
811
|
}
|
|
799
812
|
|
|
800
813
|
/** @param {unknown} item @returns {string|null} */
|
|
801
814
|
_colorOf(item) {
|
|
802
|
-
|
|
803
|
-
|
|
815
|
+
return itemColor(this._readItem(item, this.options.colorKey));
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/** @param {unknown} item @returns {string|null} */
|
|
819
|
+
_toneOf(item) {
|
|
820
|
+
return normalizeBadgeTone(this._readItem(item, this.options.toneKey));
|
|
804
821
|
}
|
|
805
822
|
|
|
806
823
|
/** @param {unknown} item @param {unknown} reader @returns {unknown} */
|
|
@@ -28,14 +28,14 @@
|
|
|
28
28
|
|
|
29
29
|
.zx-timebox__separator {
|
|
30
30
|
color: var(--zx-color-text-muted);
|
|
31
|
-
font-weight:
|
|
31
|
+
font-weight: var(--zx-weight-medium);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
.zx-timebox__sign {
|
|
35
35
|
min-inline-size: var(--zx-control-height);
|
|
36
36
|
padding: 0;
|
|
37
37
|
border-color: var(--zx-color-border-control);
|
|
38
|
-
font-weight:
|
|
38
|
+
font-weight: var(--zx-weight-semibold);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
.zx-timebox__sign[data-state="positive"] {
|
|
@@ -49,3 +49,23 @@
|
|
|
49
49
|
background: var(--zx-color-danger-bg);
|
|
50
50
|
color: var(--zx-color-danger);
|
|
51
51
|
}
|
|
52
|
+
|
|
53
|
+
/*
|
|
54
|
+
* Quiet fields. The hour and minute segments are native inputs, which the base quiet-fields rule
|
|
55
|
+
* already quiets. The sign button follows them: no border and no fill until hover or focus, its
|
|
56
|
+
* glyph keeping the colour that says which way the duration runs.
|
|
57
|
+
*/
|
|
58
|
+
[data-zx-fields="quiet"] .zx-timebox__sign,
|
|
59
|
+
[data-zx-fields="quiet"] .zx-timebox__sign[data-state] {
|
|
60
|
+
border-color: transparent;
|
|
61
|
+
background: transparent;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
[data-zx-fields="quiet"] .zx-timebox:not([aria-disabled="true"]) .zx-timebox__sign:hover {
|
|
65
|
+
background: var(--zx-color-bg-hover);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
[data-zx-fields="quiet"] .zx-timebox__sign:focus-visible {
|
|
69
|
+
border-color: var(--zx-color-border-control);
|
|
70
|
+
background: var(--zx-color-bg-control);
|
|
71
|
+
}
|
|
@@ -75,7 +75,9 @@ export class Timebox extends Component {
|
|
|
75
75
|
const content = h('div', {
|
|
76
76
|
class: 'zx-timebox__content',
|
|
77
77
|
role: 'group',
|
|
78
|
-
ariaLabel: this.options.seconds
|
|
78
|
+
ariaLabel: this.options.seconds
|
|
79
|
+
? this._message('timebox.durationWithSeconds', 'Duration in hours, minutes, and seconds')
|
|
80
|
+
: this._message('timebox.duration', 'Duration in hours and minutes')
|
|
79
81
|
});
|
|
80
82
|
this._content = content;
|
|
81
83
|
|
|
@@ -84,7 +86,7 @@ export class Timebox extends Component {
|
|
|
84
86
|
ref: 'sign',
|
|
85
87
|
class: 'zx-timebox__sign',
|
|
86
88
|
type: 'button',
|
|
87
|
-
ariaLabel: 'Toggle duration sign',
|
|
89
|
+
ariaLabel: this._message('timebox.toggleSign', 'Toggle duration sign'),
|
|
88
90
|
ariaPressed: 'false'
|
|
89
91
|
}, '+'));
|
|
90
92
|
this.listen(this.refs.sign, 'click', () => {
|
|
@@ -95,12 +97,12 @@ export class Timebox extends Component {
|
|
|
95
97
|
});
|
|
96
98
|
}
|
|
97
99
|
|
|
98
|
-
this._addSegment(content, 'hours', 'Hours', false);
|
|
100
|
+
this._addSegment(content, 'hours', this._message('timebox.hours', 'Hours'), false);
|
|
99
101
|
content.append(h('span', { class: 'zx-timebox__separator', ariaHidden: 'true' }, ':'));
|
|
100
|
-
this._addSegment(content, 'minutes', 'Minutes', true);
|
|
102
|
+
this._addSegment(content, 'minutes', this._message('timebox.minutes', 'Minutes'), true);
|
|
101
103
|
if (this.options.seconds) {
|
|
102
104
|
content.append(h('span', { class: 'zx-timebox__separator', ariaHidden: 'true' }, ':'));
|
|
103
|
-
this._addSegment(content, 'seconds', 'Seconds', true);
|
|
105
|
+
this._addSegment(content, 'seconds', this._message('timebox.seconds', 'Seconds'), true);
|
|
104
106
|
}
|
|
105
107
|
root.append(content);
|
|
106
108
|
this.set(this.options.value, this.options.unit, { silent: true });
|
|
@@ -237,6 +239,17 @@ export class Timebox extends Component {
|
|
|
237
239
|
_emitChange() {
|
|
238
240
|
this.emit('change', { value: this.get(this.options.unit) });
|
|
239
241
|
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Resolves a message through the host translator, falling back to the built-in English text.
|
|
245
|
+
* @param {string} key Message key.
|
|
246
|
+
* @param {string} fallback English default.
|
|
247
|
+
* @returns {string}
|
|
248
|
+
*/
|
|
249
|
+
_message(key, fallback) {
|
|
250
|
+
const message = this.msg(key);
|
|
251
|
+
return message === key ? fallback : message;
|
|
252
|
+
}
|
|
240
253
|
}
|
|
241
254
|
|
|
242
255
|
/** @event Timebox#change @type {CustomEvent<{value: number}>} */
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
/* A label reads like text beside the switch: start-aligned, wrapping within the available width,
|
|
2
|
+
with the track level with its first line. A one-line toggle is still a control's height tall,
|
|
3
|
+
its track and its text centred in it. */
|
|
1
4
|
.zx-toggle {
|
|
2
5
|
display: inline-flex;
|
|
3
|
-
align-items:
|
|
6
|
+
align-items: flex-start;
|
|
4
7
|
gap: var(--zx-space-2);
|
|
8
|
+
max-inline-size: 100%;
|
|
5
9
|
min-block-size: var(--zx-control-height);
|
|
6
10
|
border: 0;
|
|
7
11
|
background: none;
|
|
@@ -9,6 +13,7 @@
|
|
|
9
13
|
padding: 0;
|
|
10
14
|
font: inherit;
|
|
11
15
|
font-size: var(--zx-control-font-size);
|
|
16
|
+
text-align: start;
|
|
12
17
|
cursor: pointer;
|
|
13
18
|
}
|
|
14
19
|
|
|
@@ -31,12 +36,18 @@
|
|
|
31
36
|
flex: none;
|
|
32
37
|
inline-size: 34px;
|
|
33
38
|
block-size: 20px;
|
|
39
|
+
margin-block-start: max(0px, (var(--zx-control-height) - 20px) / 2);
|
|
34
40
|
border: 1px solid var(--zx-color-border-strong);
|
|
35
41
|
border-radius: var(--zx-radius-full);
|
|
36
42
|
background: var(--zx-color-bg-control);
|
|
37
43
|
padding: 2px;
|
|
38
44
|
}
|
|
39
45
|
|
|
46
|
+
.zx-toggle__label {
|
|
47
|
+
min-inline-size: 0;
|
|
48
|
+
padding-block: max(0px, (var(--zx-control-height) - 1lh) / 2);
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
.zx-toggle__thumb {
|
|
41
52
|
display: block;
|
|
42
53
|
inline-size: 14px;
|
|
@@ -80,8 +80,8 @@ export class Toolbar extends Component {
|
|
|
80
80
|
/** Inline size of the last completed measure pass; `-1` forces the next one. */
|
|
81
81
|
#lastWidth = -1;
|
|
82
82
|
/**
|
|
83
|
-
* Block size of the last completed measure pass.
|
|
84
|
-
* changing the row width, so the height belongs in the cache key.
|
|
83
|
+
* Block size of the last completed measure pass. A theme change (control height, spacing unit)
|
|
84
|
+
* resizes the controls without changing the row width, so the height belongs in the cache key.
|
|
85
85
|
*/
|
|
86
86
|
#lastHeight = -1;
|
|
87
87
|
/** Handle of the frame a pending observer-driven measure pass is scheduled in; `0` if none. */
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
.zx-tooltip {
|
|
8
|
+
/* As wide as its text up to the maximum, never narrower: squeezed into too little room beside its
|
|
9
|
+
anchor, the bubble overflows instead of wrapping word by word, which is what makes the
|
|
10
|
+
position fall back to the other side, or above or below. */
|
|
11
|
+
inline-size: max-content;
|
|
8
12
|
max-inline-size: var(--zx-tooltip-max-inline-size, 260px);
|
|
9
13
|
border: 1px solid var(--zx-color-overlay-border);
|
|
10
14
|
border-radius: var(--zx-radius-md);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Component } from '../../core/component.js';
|
|
2
2
|
import { h, resolveElement } from '../../core/dom.js';
|
|
3
|
-
import { overlayHost } from '../../core/overlay-host.js';
|
|
3
|
+
import { connectedOverlayHost, overlayHost } from '../../core/overlay-host.js';
|
|
4
4
|
import { position } from '../../core/position.js';
|
|
5
5
|
import { uid } from '../../core/util.js';
|
|
6
6
|
|
|
@@ -96,7 +96,8 @@ export class Tooltip extends Component {
|
|
|
96
96
|
super(null, options);
|
|
97
97
|
|
|
98
98
|
this.#anchor = resolved;
|
|
99
|
-
|
|
99
|
+
// A detached anchor has no theme scope yet: the bubble waits and finds its host in show().
|
|
100
|
+
connectedOverlayHost(resolved)?.append(this.el);
|
|
100
101
|
this.#content = this.options.content;
|
|
101
102
|
this.#disabled = Boolean(this.options.disabled);
|
|
102
103
|
this.#describedBy = resolved.getAttribute('aria-describedby');
|
|
@@ -174,11 +175,17 @@ export class Tooltip extends Component {
|
|
|
174
175
|
if (node === null) return this;
|
|
175
176
|
|
|
176
177
|
this.el.replaceChildren(node);
|
|
178
|
+
const placement = PLACEMENTS.has(this.options.placement) ? this.options.placement : 'top';
|
|
177
179
|
this.#position = position(this.#anchor, /** @type {HTMLElement} */ (this.el), {
|
|
178
|
-
placement
|
|
180
|
+
placement,
|
|
179
181
|
offset: Number(this.options.offset),
|
|
180
182
|
flip: true
|
|
181
183
|
});
|
|
184
|
+
// A bubble beside its anchor that fits on neither side goes above or below it instead. Only
|
|
185
|
+
// the anchor-positioned path sets fallbacks; the script fallback clamps into the viewport.
|
|
186
|
+
if (/^(left|right)/.test(placement) && this.el.style.getPropertyValue('position-try-fallbacks')) {
|
|
187
|
+
this.el.style.setProperty('position-try-fallbacks', 'flip-inline, top span-all, bottom span-all');
|
|
188
|
+
}
|
|
182
189
|
this.el.dataset.state = 'open';
|
|
183
190
|
this.#anchor.setAttribute('aria-describedby', appendToken(this.#describedBy, this.el.id));
|
|
184
191
|
this.emit('open');
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
.zx-tree {
|
|
2
|
-
|
|
2
|
+
/* One step per level: enough to read the nesting, not so much that a deep branch walks off the
|
|
3
|
+
edge of a sidebar. */
|
|
4
|
+
--zx-tree-indent: var(--zx-space-3);
|
|
3
5
|
display: block;
|
|
4
6
|
min-inline-size: 0;
|
|
5
7
|
color: var(--zx-color-text);
|
|
@@ -10,10 +12,12 @@
|
|
|
10
12
|
display: flex;
|
|
11
13
|
align-items: center;
|
|
12
14
|
gap: var(--zx-space-2);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
/* The same row as a Finder column: a space-1 margin round one line of text. */
|
|
16
|
+
min-block-size: calc(var(--zx-control-height) - var(--zx-space-1));
|
|
17
|
+
padding: var(--zx-space-1) var(--zx-space-2);
|
|
15
18
|
padding-inline-start: calc(var(--zx-space-2) + var(--zx-tree-level, 0) * var(--zx-tree-indent));
|
|
16
|
-
border-radius: var(--zx-radius-
|
|
19
|
+
border-radius: var(--zx-radius-sm);
|
|
20
|
+
line-height: 1.25;
|
|
17
21
|
cursor: pointer;
|
|
18
22
|
}
|
|
19
23
|
|
|
@@ -21,10 +25,18 @@
|
|
|
21
25
|
background: var(--zx-color-bg-hover);
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
/* The selected node of a single-selection tree is where you are, not something you picked: the
|
|
29
|
+
neutral current fill, the full text colour and a heavier label. A multi-selection is a set of
|
|
30
|
+
picks and keeps the accent tint, so the two never read as the same thing. */
|
|
24
31
|
.zx-tree__row[aria-selected="true"] {
|
|
32
|
+
background: var(--zx-color-bg-current);
|
|
33
|
+
color: var(--zx-color-text);
|
|
34
|
+
font-weight: var(--zx-weight-semibold);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.zx-tree[aria-multiselectable="true"] .zx-tree__row[aria-selected="true"] {
|
|
25
38
|
background: var(--zx-color-bg-selected);
|
|
26
|
-
|
|
27
|
-
font-weight: 550;
|
|
39
|
+
font-weight: inherit;
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
.zx-tree__row:focus-visible {
|
|
@@ -38,7 +50,7 @@
|
|
|
38
50
|
inline-size: 14px;
|
|
39
51
|
block-size: 14px;
|
|
40
52
|
flex: none;
|
|
41
|
-
color: var(--zx-color-text-
|
|
53
|
+
color: var(--zx-color-text-subtle);
|
|
42
54
|
}
|
|
43
55
|
|
|
44
56
|
.zx-tree__row[aria-expanded="true"] .zx-tree__twisty {
|
|
@@ -60,19 +72,17 @@
|
|
|
60
72
|
margin: 0;
|
|
61
73
|
}
|
|
62
74
|
|
|
75
|
+
/* Icons are quieter than labels, folders included: a branch is told apart by its glyph and its
|
|
76
|
+
twisty, not by borrowing a status colour. The current row's icon takes the text colour. */
|
|
63
77
|
.zx-tree__icon {
|
|
64
78
|
display: inline-grid;
|
|
65
79
|
place-items: center;
|
|
66
80
|
flex: none;
|
|
67
|
-
color: var(--zx-color-text-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.zx-tree__row[data-branch] .zx-tree__icon {
|
|
71
|
-
color: var(--zx-color-warning);
|
|
81
|
+
color: var(--zx-color-text-subtle);
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
.zx-tree__row[aria-selected="true"] .zx-tree__icon {
|
|
75
|
-
color:
|
|
85
|
+
color: var(--zx-color-text);
|
|
76
86
|
}
|
|
77
87
|
|
|
78
88
|
.zx-tree__label {
|
|
@@ -83,11 +93,10 @@
|
|
|
83
93
|
|
|
84
94
|
.zx-tree__badge {
|
|
85
95
|
margin-inline-start: auto;
|
|
86
|
-
padding-inline: var(--zx-space-
|
|
87
|
-
border-radius: var(--zx-radius-full);
|
|
88
|
-
background: var(--zx-color-bg-muted);
|
|
96
|
+
padding-inline: var(--zx-space-1);
|
|
89
97
|
color: var(--zx-color-text-muted);
|
|
90
98
|
font-size: var(--zx-text-xs);
|
|
99
|
+
font-weight: var(--zx-weight-medium);
|
|
91
100
|
font-variant-numeric: tabular-nums;
|
|
92
101
|
}
|
|
93
102
|
|
|
@@ -611,6 +611,11 @@ export class TreeView extends Component {
|
|
|
611
611
|
this._expandSiblings(row);
|
|
612
612
|
return;
|
|
613
613
|
default:
|
|
614
|
+
// A printable key typed on a row belongs to the type-ahead: claimed, so the browser's
|
|
615
|
+
// find-as-you-type and the page's single-key shortcuts do not act on it as well. Typing
|
|
616
|
+
// into a control that `renderLabel` placed in a row stays that control's.
|
|
617
|
+
if (!isTreeTypeaheadKey(event) || isTextEntry(event.target)) return;
|
|
618
|
+
event.preventDefault();
|
|
614
619
|
this._handleTypeahead(event);
|
|
615
620
|
}
|
|
616
621
|
}
|
|
@@ -782,3 +787,29 @@ function cloneNodes(nodes) {
|
|
|
782
787
|
function toIterable(value) {
|
|
783
788
|
return Array.isArray(value) ? value : [];
|
|
784
789
|
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Whether a key press is one the type-ahead consumes: a single printable character, not a space
|
|
793
|
+
* (Space selects), with no Ctrl, Meta, or Alt (those are shortcuts). Shift is allowed, since it
|
|
794
|
+
* only changes the letter's case.
|
|
795
|
+
* @param {{key?: string, ctrlKey?: boolean, metaKey?: boolean, altKey?: boolean}} event Key event.
|
|
796
|
+
* @returns {boolean}
|
|
797
|
+
*/
|
|
798
|
+
export function isTreeTypeaheadKey(event) {
|
|
799
|
+
if (!event || event.ctrlKey || event.metaKey || event.altKey) return false;
|
|
800
|
+
const key = typeof event.key === 'string' ? event.key : '';
|
|
801
|
+
// The same test the shared type-ahead applies, so a key is claimed exactly when it is used.
|
|
802
|
+
return key.length === 1 && !/\s/.test(key);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/** @param {EventTarget|null} target @returns {boolean} Whether typing into the target edits text. */
|
|
806
|
+
function isTextEntry(target) {
|
|
807
|
+
const element = /** @type {HTMLElement|null} */ (target && /** @type {Node} */ (target).nodeType === 1
|
|
808
|
+
? target : null);
|
|
809
|
+
if (!element) return false;
|
|
810
|
+
if (element.isContentEditable) return true;
|
|
811
|
+
if (element.localName === 'textarea' || element.localName === 'select') return true;
|
|
812
|
+
if (element.localName !== 'input') return false;
|
|
813
|
+
return !['checkbox', 'radio', 'button', 'submit', 'reset', 'image', 'range', 'color', 'file']
|
|
814
|
+
.includes(/** @type {HTMLInputElement} */ (element).type);
|
|
815
|
+
}
|