@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
|
@@ -4,7 +4,10 @@ import { uid } from '../../core/util.js';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @typedef {Object} FieldOptions
|
|
7
|
-
* @property {string|null} [id=null]
|
|
7
|
+
* @property {string|null} [id=null] DOM id of the control. Generated when omitted; a Fieldset
|
|
8
|
+
* derives it from its `idPrefix` and the field key, so a key never becomes a DOM id by itself.
|
|
9
|
+
* @property {string|null} [name=null] Field name: the `name` of a native control and the key the
|
|
10
|
+
* field is filed under. A Fieldset sets it to the field key; a standalone field falls back to `id`.
|
|
8
11
|
* @property {string} [type='text'] Registered field type.
|
|
9
12
|
* @property {string} [label=''] Visible label.
|
|
10
13
|
* @property {string} [description=''] Supporting description.
|
|
@@ -28,6 +31,9 @@ import { uid } from '../../core/util.js';
|
|
|
28
31
|
* @property {() => void} focus Focus the control.
|
|
29
32
|
* @property {(disabled: boolean) => void} setDisabled Set disabled state.
|
|
30
33
|
* @property {() => void} [destroy] Release adapter-owned resources.
|
|
34
|
+
* @property {Element} [labelTarget] The element the field's label, description, and validity
|
|
35
|
+
* belong to when it is not `el` — the text input inside a composite control. Left out, a
|
|
36
|
+
* wrapper that cannot be labelled hands them to the first unnamed form control inside it.
|
|
31
37
|
*/
|
|
32
38
|
|
|
33
39
|
/** @typedef {(field: Field, options: Readonly<FieldOptions>) => FieldAdapter} FieldAdapterFactory */
|
|
@@ -45,6 +51,7 @@ export class Field extends Component {
|
|
|
45
51
|
/** @type {FieldOptions & {msg: Record<string, string>}} */
|
|
46
52
|
static defaults = {
|
|
47
53
|
id: null,
|
|
54
|
+
name: null,
|
|
48
55
|
type: 'text',
|
|
49
56
|
label: '',
|
|
50
57
|
description: '',
|
|
@@ -88,9 +95,10 @@ export class Field extends Component {
|
|
|
88
95
|
return Field.#adapters.has(String(type).trim().toLowerCase());
|
|
89
96
|
}
|
|
90
97
|
|
|
91
|
-
// `adapter` (FieldAdapter), `initialValue`,
|
|
92
|
-
//
|
|
93
|
-
// constructor (which calls render())
|
|
98
|
+
// `adapter` (FieldAdapter), `initialValue`, `controlId` (the control's DOM id), and `name` (the
|
|
99
|
+
// field name, '' when there is none) are assigned in render(); declaring them as class fields
|
|
100
|
+
// would re-initialize them to undefined after the base constructor (which calls render())
|
|
101
|
+
// returns — see AGENTS.md.
|
|
94
102
|
|
|
95
103
|
/** @returns {HTMLElement} */
|
|
96
104
|
render() {
|
|
@@ -104,7 +112,10 @@ export class Field extends Component {
|
|
|
104
112
|
root.dataset.layout = layout;
|
|
105
113
|
root.dataset.fieldType = type;
|
|
106
114
|
|
|
115
|
+
/** @type {string} The control's DOM id: `options.id`, or a generated one. */
|
|
107
116
|
this.controlId = this.options.id ? String(this.options.id) : uid('zx-field');
|
|
117
|
+
/** @type {string} The field name (`options.name`, else `options.id`), '' when there is none. */
|
|
118
|
+
this.name = String(this.options.name ?? this.options.id ?? '');
|
|
108
119
|
const labelId = `${this.controlId}-label`;
|
|
109
120
|
const descriptionId = `${this.controlId}-description`;
|
|
110
121
|
const highlightId = `${this.controlId}-highlight`;
|
|
@@ -148,9 +159,14 @@ export class Field extends Component {
|
|
|
148
159
|
|
|
149
160
|
const control = this.adapter.el;
|
|
150
161
|
if (!control.id) control.id = this.controlId;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
162
|
+
// A wrapper (a `custom` adapter's box) cannot carry a name; the control inside it can.
|
|
163
|
+
const labelled = this.adapter.labelTarget
|
|
164
|
+
?? (isLabelable(control) ? control : unnamedControlWithin(control)) ?? control;
|
|
165
|
+
this._labelled = labelled;
|
|
166
|
+
if (labelled !== control && isLabelable(labelled) && !labelled.id) labelled.id = `${this.controlId}-input`;
|
|
167
|
+
label.htmlFor = isLabelable(labelled) ? labelled.id : control.id;
|
|
168
|
+
if (!isLabelable(labelled)) labelled.setAttribute('aria-labelledby', labelId);
|
|
169
|
+
this._baseDescribedBy = labelled.getAttribute('aria-describedby') ?? '';
|
|
154
170
|
controlSlot.append(control);
|
|
155
171
|
this._descriptionId = String(this.options.description ?? '') ? descriptionId : '';
|
|
156
172
|
this._highlightId = highlightId;
|
|
@@ -231,11 +247,11 @@ export class Field extends Component {
|
|
|
231
247
|
this._highlight.hidden = !message;
|
|
232
248
|
if (normalizedKind === 'danger' && message) {
|
|
233
249
|
this._highlight.setAttribute('role', 'alert');
|
|
234
|
-
this.
|
|
250
|
+
this._labelled.setAttribute('aria-invalid', 'true');
|
|
235
251
|
this.emit('invalid', { message: String(message) });
|
|
236
252
|
} else {
|
|
237
253
|
this._highlight.removeAttribute('role');
|
|
238
|
-
this.
|
|
254
|
+
this._labelled.removeAttribute('aria-invalid');
|
|
239
255
|
}
|
|
240
256
|
this._syncDescription(Boolean(message));
|
|
241
257
|
return this;
|
|
@@ -250,7 +266,7 @@ export class Field extends Component {
|
|
|
250
266
|
this._highlight.textContent = '';
|
|
251
267
|
this._highlight.hidden = true;
|
|
252
268
|
this._highlight.removeAttribute('role');
|
|
253
|
-
this.
|
|
269
|
+
this._labelled.removeAttribute('aria-invalid');
|
|
254
270
|
this._syncDescription(false);
|
|
255
271
|
return this;
|
|
256
272
|
}
|
|
@@ -279,8 +295,8 @@ export class Field extends Component {
|
|
|
279
295
|
_syncDescription(highlighted = false) {
|
|
280
296
|
const ids = [this._baseDescribedBy, this._descriptionId, highlighted ? this._highlightId : '']
|
|
281
297
|
.flatMap((value) => value.split(/\s+/)).filter(Boolean);
|
|
282
|
-
if (ids.length) this.
|
|
283
|
-
else this.
|
|
298
|
+
if (ids.length) this._labelled.setAttribute('aria-describedby', [...new Set(ids)].join(' '));
|
|
299
|
+
else this._labelled.removeAttribute('aria-describedby');
|
|
284
300
|
}
|
|
285
301
|
|
|
286
302
|
/** @returns {void} */
|
|
@@ -400,6 +416,22 @@ function isLabelable(element) {
|
|
|
400
416
|
return ['BUTTON', 'INPUT', 'METER', 'OUTPUT', 'PROGRESS', 'SELECT', 'TEXTAREA'].includes(element.tagName);
|
|
401
417
|
}
|
|
402
418
|
|
|
419
|
+
/**
|
|
420
|
+
* The first form control inside `root` that nothing names yet — no `aria-label`, no
|
|
421
|
+
* `aria-labelledby`, no `<label>` with any text — or null.
|
|
422
|
+
* @param {Element} root
|
|
423
|
+
* @returns {Element|null}
|
|
424
|
+
*/
|
|
425
|
+
function unnamedControlWithin(root) {
|
|
426
|
+
for (const candidate of root.querySelectorAll('input:not([type="hidden"]), select, textarea')) {
|
|
427
|
+
if (candidate.hasAttribute('aria-label') || candidate.hasAttribute('aria-labelledby')) continue;
|
|
428
|
+
const labels = [.../** @type {HTMLInputElement} */ (candidate).labels ?? []];
|
|
429
|
+
if (labels.some((label) => label.textContent?.trim())) continue;
|
|
430
|
+
return candidate;
|
|
431
|
+
}
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
|
|
403
435
|
/** @param {HTMLElement} element @param {string[]} names @returns {Map<string, string|null>} */
|
|
404
436
|
function rememberAttributes(element, names) {
|
|
405
437
|
return new Map(names.map((name) => [name, element.getAttribute(name)]));
|
|
@@ -413,19 +445,26 @@ function restoreAttributes(element, attributes) {
|
|
|
413
445
|
}
|
|
414
446
|
}
|
|
415
447
|
|
|
416
|
-
/**
|
|
417
|
-
|
|
448
|
+
/**
|
|
449
|
+
* The properties a native control is built with: the adapter's own `base` over the caller's
|
|
450
|
+
* `props`, with the field name as the control's `name` unless `props` gives one.
|
|
451
|
+
* @param {Field} field Field the control belongs to.
|
|
452
|
+
* @param {Readonly<FieldOptions>} options Field options.
|
|
453
|
+
* @param {Record<string, unknown>} base Properties the adapter owns.
|
|
454
|
+
* @returns {Record<string, unknown>}
|
|
455
|
+
*/
|
|
456
|
+
function controlProps(field, options, base) {
|
|
418
457
|
const props = { ...(options.props ?? {}) };
|
|
419
458
|
for (const key of Object.keys(props)) {
|
|
420
459
|
if (/^on[a-z]/i.test(key) || key === 'content') delete props[key];
|
|
421
460
|
}
|
|
422
|
-
return { ...props, ...base };
|
|
461
|
+
return { name: field.name || undefined, ...props, ...base };
|
|
423
462
|
}
|
|
424
463
|
|
|
425
464
|
/** @param {'input'|'textarea'} tag @param {string} type @returns {FieldAdapterFactory} */
|
|
426
465
|
function textAdapter(tag, type) {
|
|
427
|
-
return (
|
|
428
|
-
const el = h(tag, controlProps(options, {
|
|
466
|
+
return (field, options) => {
|
|
467
|
+
const el = h(tag, controlProps(field, options, {
|
|
429
468
|
type: tag === 'input' ? type : undefined,
|
|
430
469
|
placeholder: options.placeholder,
|
|
431
470
|
required: options.required,
|
|
@@ -450,7 +489,7 @@ function elementValueAdapter(el) {
|
|
|
450
489
|
function numericAdapter(kind) {
|
|
451
490
|
return (field, options) => {
|
|
452
491
|
const coerce = kind === 'int' ? coerceInteger : coerceFloat;
|
|
453
|
-
const input = h('input', controlProps(options, {
|
|
492
|
+
const input = h('input', controlProps(field, options, {
|
|
454
493
|
type: 'text',
|
|
455
494
|
inputMode: kind === 'int' ? 'numeric' : 'decimal',
|
|
456
495
|
placeholder: options.placeholder,
|
|
@@ -474,8 +513,8 @@ function numericAdapter(kind) {
|
|
|
474
513
|
}
|
|
475
514
|
|
|
476
515
|
/** @type {FieldAdapterFactory} */
|
|
477
|
-
const checkboxAdapter = (
|
|
478
|
-
const input = h('input', controlProps(options, {
|
|
516
|
+
const checkboxAdapter = (field, options) => {
|
|
517
|
+
const input = h('input', controlProps(field, options, {
|
|
479
518
|
type: 'checkbox',
|
|
480
519
|
required: options.required,
|
|
481
520
|
disabled: options.disabled
|
|
@@ -490,8 +529,8 @@ const checkboxAdapter = (_field, options) => {
|
|
|
490
529
|
};
|
|
491
530
|
|
|
492
531
|
/** @type {FieldAdapterFactory} */
|
|
493
|
-
const selectAdapter = (
|
|
494
|
-
const select = h('select', controlProps(options, {
|
|
532
|
+
const selectAdapter = (field, options) => {
|
|
533
|
+
const select = h('select', controlProps(field, options, {
|
|
495
534
|
required: options.required,
|
|
496
535
|
disabled: options.disabled
|
|
497
536
|
}));
|
|
@@ -502,12 +541,15 @@ const selectAdapter = (_field, options) => {
|
|
|
502
541
|
};
|
|
503
542
|
|
|
504
543
|
/** @type {FieldAdapterFactory} */
|
|
505
|
-
const optionListAdapter = (
|
|
544
|
+
const optionListAdapter = (field, options) => {
|
|
506
545
|
const root = h('div', {
|
|
507
546
|
class: 'zx-field__option-list',
|
|
508
547
|
role: 'radiogroup'
|
|
509
548
|
});
|
|
510
|
-
|
|
549
|
+
// The radios are grouped by the control's id, not by the field key: radios outside a <form>
|
|
550
|
+
// share one group per name across the whole document, so the same fieldset mounted twice
|
|
551
|
+
// would otherwise check one copy's option by clicking the other's.
|
|
552
|
+
const name = field.controlId;
|
|
511
553
|
const radios = normalizeOptions(options.options).map(([value, label], index) => {
|
|
512
554
|
const input = h('input', {
|
|
513
555
|
type: 'radio',
|
|
@@ -534,8 +576,8 @@ const optionListAdapter = (_field, options) => {
|
|
|
534
576
|
};
|
|
535
577
|
|
|
536
578
|
/** @type {FieldAdapterFactory} */
|
|
537
|
-
const htmlAdapter = (
|
|
538
|
-
const el = h('div', controlProps(options, { class: 'zx-field__static' }));
|
|
579
|
+
const htmlAdapter = (field, options) => {
|
|
580
|
+
const el = h('div', controlProps(field, options, { class: 'zx-field__static', name: undefined }));
|
|
539
581
|
let adopted = null;
|
|
540
582
|
const set = (value) => {
|
|
541
583
|
restoreAdopted(adopted);
|
|
@@ -15,11 +15,13 @@
|
|
|
15
15
|
inline-size: 100%;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/* A caption for the group, in sentence case and the same quiet voice as the field labels inside
|
|
19
|
+
it — the fields are the content. */
|
|
18
20
|
.zx-fieldset__legend {
|
|
19
21
|
padding-inline: var(--zx-space-2);
|
|
20
|
-
color: var(--zx-color-text);
|
|
21
|
-
font-size: var(--zx-text-
|
|
22
|
-
font-weight:
|
|
22
|
+
color: var(--zx-color-text-muted);
|
|
23
|
+
font-size: var(--zx-text-sm);
|
|
24
|
+
font-weight: var(--zx-weight-medium);
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
.zx-fieldset__grid {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Component } from '../../core/component.js';
|
|
2
2
|
import { h } from '../../core/dom.js';
|
|
3
|
-
import { isElement } from '../../core/util.js';
|
|
3
|
+
import { isElement, uid } from '../../core/util.js';
|
|
4
4
|
import { Field } from '../field/field.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -8,6 +8,9 @@ import { Field } from '../field/field.js';
|
|
|
8
8
|
* @property {string} [title=''] Legend text.
|
|
9
9
|
* @property {1|2|3} [columns=1] Grid column count.
|
|
10
10
|
* @property {Record<string, FieldOptions|Field|Element|Component>} [fields={}] Initial fields.
|
|
11
|
+
* @property {string|null} [idPrefix=null] Prefix of the DOM ids generated for the fields, as
|
|
12
|
+
* `<idPrefix>-<key>`. Null generates one per instance, so the same fieldset mounted twice
|
|
13
|
+
* never repeats an id and a field key never collides with an element id elsewhere on the page.
|
|
11
14
|
*/
|
|
12
15
|
|
|
13
16
|
/** @typedef {import('../field/field.js').FieldOptions} FieldOptions */
|
|
@@ -20,7 +23,7 @@ export class Fieldset extends Component {
|
|
|
20
23
|
static cssName = 'fieldset';
|
|
21
24
|
|
|
22
25
|
/** @type {FieldsetOptions} */
|
|
23
|
-
static defaults = { title: '', columns: 1, fields: {} };
|
|
26
|
+
static defaults = { title: '', columns: 1, fields: {}, idPrefix: null };
|
|
24
27
|
|
|
25
28
|
/** @returns {HTMLElement} */
|
|
26
29
|
render() {
|
|
@@ -30,6 +33,7 @@ export class Fieldset extends Component {
|
|
|
30
33
|
this._origins = new Map();
|
|
31
34
|
this._fieldIdAttributes = new Map();
|
|
32
35
|
this._ownedNodes = [];
|
|
36
|
+
this._idPrefix = this.options.idPrefix ? String(this.options.idPrefix) : uid('zx-fieldset');
|
|
33
37
|
this._columnAttribute = root.getAttribute('data-columns');
|
|
34
38
|
const columns = [1, 2, 3].includes(Number(this.options.columns)) ? Number(this.options.columns) : 1;
|
|
35
39
|
root.dataset.columns = String(columns);
|
|
@@ -49,7 +53,9 @@ export class Fieldset extends Component {
|
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
/**
|
|
52
|
-
* Adds a field from options, an existing field, an element, or a component.
|
|
56
|
+
* Adds a field from options, an existing field, an element, or a component. The key becomes the
|
|
57
|
+
* field's `name` and its `data-field-id`; the control's DOM id is `<idPrefix>-<key>`, unless the
|
|
58
|
+
* field options give an `id` of their own that differs from the key.
|
|
53
59
|
* @param {string} id Logical field identifier.
|
|
54
60
|
* @param {FieldOptions|Field|Element|Component} field Field source.
|
|
55
61
|
* @returns {Field}
|
|
@@ -64,7 +70,8 @@ export class Fieldset extends Component {
|
|
|
64
70
|
} else if (isElement(field) || field instanceof Component) {
|
|
65
71
|
const source = field;
|
|
66
72
|
instance = new Field(null, {
|
|
67
|
-
id: key,
|
|
73
|
+
id: this._controlId(key),
|
|
74
|
+
name: key,
|
|
68
75
|
type: 'custom',
|
|
69
76
|
label: key,
|
|
70
77
|
value: readExternalValue(source),
|
|
@@ -74,7 +81,14 @@ export class Fieldset extends Component {
|
|
|
74
81
|
owned = true;
|
|
75
82
|
} else {
|
|
76
83
|
const options = field && typeof field === 'object' ? field : {};
|
|
77
|
-
|
|
84
|
+
// An `id` equal to the key is the key restated (schema descriptors carry both), not a
|
|
85
|
+
// request for that DOM id.
|
|
86
|
+
const explicit = options.id != null && String(options.id) !== '' && String(options.id) !== key;
|
|
87
|
+
instance = new Field(null, {
|
|
88
|
+
...options,
|
|
89
|
+
id: explicit ? String(options.id) : this._controlId(key),
|
|
90
|
+
name: options.name ?? key
|
|
91
|
+
});
|
|
78
92
|
owned = true;
|
|
79
93
|
}
|
|
80
94
|
|
|
@@ -218,6 +232,15 @@ export class Fieldset extends Component {
|
|
|
218
232
|
return this;
|
|
219
233
|
}
|
|
220
234
|
|
|
235
|
+
/**
|
|
236
|
+
* The DOM id generated for a field key: unique per instance unless `idPrefix` was given.
|
|
237
|
+
* @param {string} key Field key.
|
|
238
|
+
* @returns {string}
|
|
239
|
+
*/
|
|
240
|
+
_controlId(key) {
|
|
241
|
+
return `${this._idPrefix}-${key.trim().replace(/\s+/g, '-')}`;
|
|
242
|
+
}
|
|
243
|
+
|
|
221
244
|
/** @param {Field} field @returns {void} */
|
|
222
245
|
_releaseField(field) {
|
|
223
246
|
const fieldId = this._fieldIdAttributes.get(field);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
.zx-file-item {
|
|
2
|
+
display: grid;
|
|
3
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
4
|
+
gap: var(--zx-space-2) var(--zx-space-3);
|
|
5
|
+
align-items: center;
|
|
6
|
+
min-inline-size: 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.zx-file-item__entity {
|
|
10
|
+
min-inline-size: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.zx-file-item__status {
|
|
14
|
+
justify-self: end;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.zx-file-item__status[hidden],
|
|
18
|
+
.zx-file-item__progress[hidden] {
|
|
19
|
+
display: none;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.zx-file-item__progress {
|
|
23
|
+
display: grid;
|
|
24
|
+
grid-column: 1 / -1;
|
|
25
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
26
|
+
gap: var(--zx-space-2);
|
|
27
|
+
align-items: center;
|
|
28
|
+
padding-inline-start: calc(var(--zx-control-height) + var(--zx-space-2));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.zx-file-item__progress-head {
|
|
32
|
+
grid-column: 2;
|
|
33
|
+
color: var(--zx-color-text-muted);
|
|
34
|
+
font-size: var(--zx-text-sm);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.zx-file-item__progress-track {
|
|
38
|
+
grid-column: 1 / -1;
|
|
39
|
+
block-size: 4px;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
border-radius: var(--zx-radius-pill);
|
|
42
|
+
background: var(--zx-color-bg-muted);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.zx-file-item__progress-fill {
|
|
46
|
+
inline-size: var(--zx-file-progress, 0%);
|
|
47
|
+
block-size: 100%;
|
|
48
|
+
border-radius: inherit;
|
|
49
|
+
background: var(--zx-color-accent);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.zx-file-item[data-status="success"] .zx-file-item__progress-fill {
|
|
53
|
+
background: var(--zx-color-success);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.zx-file-item[data-status="error"] .zx-file-item__progress-fill {
|
|
57
|
+
background: var(--zx-color-danger);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.zx-file-item__progress[data-indeterminate] .zx-file-item__progress-fill {
|
|
61
|
+
inline-size: 35%;
|
|
62
|
+
transform: translateX(-110%);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
66
|
+
.zx-file-item__progress-fill {
|
|
67
|
+
transition: inline-size var(--zx-dur-fast) var(--zx-ease);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.zx-file-item__progress[data-indeterminate] .zx-file-item__progress-fill {
|
|
71
|
+
animation: zx-file-progress-slide 1200ms var(--zx-ease) infinite;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@keyframes zx-file-progress-slide {
|
|
76
|
+
to { transform: translateX(320%); }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@media (max-width: 36rem) {
|
|
80
|
+
.zx-file-item {
|
|
81
|
+
grid-template-columns: minmax(0, 1fr);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.zx-file-item__status {
|
|
85
|
+
justify-self: start;
|
|
86
|
+
padding-inline-start: calc(var(--zx-control-height) + var(--zx-space-2));
|
|
87
|
+
}
|
|
88
|
+
}
|