@zeyos/zx 4.3.0 → 4.4.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 +266 -1
- package/README.md +7 -3
- package/dist/types/components/activity-item/activity-item.d.ts +351 -0
- package/dist/types/components/activity-list/activity-list.d.ts +195 -0
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +28 -0
- package/dist/types/components/button/button.d.ts +8 -0
- package/dist/types/components/calendar/calendar-model.d.ts +22 -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 +12 -4
- package/dist/types/components/card-view/record-card.d.ts +53 -5
- package/dist/types/components/code-editor/code-editor.d.ts +160 -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/dialog/dialog.d.ts +29 -0
- package/dist/types/components/entity-ref/entity-ref.d.ts +331 -0
- package/dist/types/components/file-item/file-item.d.ts +234 -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/kanban-view/kanban-card.d.ts +138 -0
- package/dist/types/components/kanban-view/kanban-policy.d.ts +268 -0
- package/dist/types/components/kanban-view/kanban-view.d.ts +837 -26
- package/dist/types/components/launcher/launcher.d.ts +19 -12
- package/dist/types/components/list-toolbar/list-toolbar.d.ts +599 -0
- package/dist/types/components/menu-button/menu-button.d.ts +4 -37
- package/dist/types/components/message/message.d.ts +7 -0
- package/dist/types/components/modal/modal.d.ts +48 -0
- 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/select.d.ts +4 -0
- package/dist/types/components/sort-control/sort-control.d.ts +105 -0
- package/dist/types/components/stat-tile/stat-tile.d.ts +277 -0
- package/dist/types/components/tabbox/tabbox.d.ts +7 -0
- package/dist/types/components/table/table.d.ts +144 -0
- package/dist/types/components/table-view/table-view.d.ts +11 -5
- package/dist/types/components/timebox/timebox.d.ts +7 -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/component.d.ts +6 -1
- package/dist/types/core/icons.d.ts +17 -0
- package/dist/types/index.d.ts +13 -1
- package/dist/types/internal/app-rail.d.ts +10 -0
- package/dist/types/internal/menu-items.d.ts +134 -0
- package/dist/zx-zeyos.esm.js +651 -88
- package/dist/zx-zeyos.esm.min.js +8 -1
- package/dist/zx.css +6266 -3900
- package/dist/zx.esm.js +8487 -1805
- package/dist/zx.esm.min.js +10 -3
- package/dist/zx.global.js +8487 -1805
- package/dist/zx.global.min.js +10 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +1811 -247
- package/docs/llms.md +345 -40
- package/docs/llms.txt +11 -7
- package/package.json +1 -2
- package/src/components/activity-item/activity-item.css +137 -0
- package/src/components/activity-item/activity-item.js +599 -0
- package/src/components/activity-list/activity-list.css +64 -0
- package/src/components/activity-list/activity-list.js +375 -0
- package/src/components/app-rail/app-rail.css +227 -19
- package/src/components/app-sidebar/app-sidebar.css +414 -24
- package/src/components/app-sidebar/app-sidebar.js +65 -5
- package/src/components/button/button.css +8 -0
- package/src/components/button/button.js +6 -1
- package/src/components/calendar/calendar-model.js +45 -0
- package/src/components/calendar/calendar.css +30 -10
- package/src/components/calendar/calendar.js +41 -13
- package/src/components/card/card.js +12 -1
- package/src/components/card-view/card-view.css +212 -9
- package/src/components/card-view/card-view.js +32 -16
- package/src/components/card-view/record-card.js +98 -21
- package/src/components/code-editor/code-editor.css +67 -0
- package/src/components/code-editor/code-editor.js +288 -0
- package/src/components/code-editor/field-adapter.js +34 -0
- package/src/components/context-menu/context-menu.css +80 -3
- package/src/components/context-menu/context-menu.js +22 -30
- package/src/components/date-picker/date-picker.js +22 -8
- package/src/components/date-picker/month-picker.js +3 -3
- package/src/components/date-picker/time-picker.js +20 -8
- package/src/components/dialog/dialog.js +88 -66
- package/src/components/dropdown/dropdown.js +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/file-item/file-item.css +88 -0
- package/src/components/file-item/file-item.js +371 -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 +199 -45
- package/src/components/filter-panel/filter-panel.css +104 -0
- package/src/components/filter-panel/filter-panel.js +964 -0
- package/src/components/kanban-view/kanban-card.js +239 -0
- package/src/components/kanban-view/kanban-policy.js +412 -0
- package/src/components/kanban-view/kanban-view.css +674 -36
- package/src/components/kanban-view/kanban-view.js +1762 -240
- package/src/components/launcher/launcher.js +59 -27
- package/src/components/list-toolbar/list-toolbar.css +189 -0
- package/src/components/list-toolbar/list-toolbar.js +851 -0
- package/src/components/menu-button/menu-button.css +80 -3
- package/src/components/menu-button/menu-button.js +21 -37
- package/src/components/message/message.css +14 -12
- package/src/components/message/message.js +13 -2
- package/src/components/modal/modal.css +15 -0
- package/src/components/modal/modal.js +141 -0
- package/src/components/navigation-bar/navigation-bar.css +77 -2
- package/src/components/navigation-bar/navigation-bar.js +171 -4
- package/src/components/panel/panel.js +13 -2
- package/src/components/search/search.js +17 -5
- package/src/components/select/field-adapter.js +4 -1
- package/src/components/select/select.js +3 -0
- package/src/components/sort-control/sort-control.css +17 -0
- package/src/components/sort-control/sort-control.js +140 -0
- package/src/components/stat-tile/stat-tile.css +213 -0
- package/src/components/stat-tile/stat-tile.js +590 -0
- package/src/components/tabbox/tabbox.js +12 -1
- package/src/components/table/table.css +51 -2
- package/src/components/table/table.js +298 -19
- 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 +3 -1
- package/src/components/timebox/timebox.js +18 -5
- 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/component.js +8 -3
- package/src/core/icons.js +74 -5
- package/src/index.js +33 -3
- package/src/internal/app-rail.js +19 -1
- package/src/internal/menu-items.js +142 -0
- package/styles/print.css +192 -0
- package/styles/zx.css +11 -0
- package/DESIGN-SYSTEM.md +0 -182
|
@@ -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,5 +1,6 @@
|
|
|
1
1
|
import { Component } from '../../core/component.js';
|
|
2
2
|
import { h } from '../../core/dom.js';
|
|
3
|
+
import { printf } from '../../core/i18n.js';
|
|
3
4
|
import { uid } from '../../core/util.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -49,14 +50,14 @@ export class ValueList extends Component {
|
|
|
49
50
|
this._list = h('ul', {
|
|
50
51
|
class: 'zx-value-list__list',
|
|
51
52
|
role: 'listbox',
|
|
52
|
-
ariaLabel: 'Values',
|
|
53
|
+
ariaLabel: this._message('valueList.values', 'Values'),
|
|
53
54
|
ariaOrientation: 'horizontal'
|
|
54
55
|
});
|
|
55
56
|
this._input = h('input', {
|
|
56
57
|
class: 'zx-value-list__input',
|
|
57
58
|
type: 'text',
|
|
58
59
|
placeholder: String(this.options.placeholder ?? ''),
|
|
59
|
-
ariaLabel: 'Add value',
|
|
60
|
+
ariaLabel: this._message('valueList.add', 'Add value'),
|
|
60
61
|
ariaDescribedby: instructionsId
|
|
61
62
|
});
|
|
62
63
|
this._status = h('div', {
|
|
@@ -67,7 +68,8 @@ export class ValueList extends Component {
|
|
|
67
68
|
this._instructions = h('span', {
|
|
68
69
|
class: 'zx-value-list__instructions',
|
|
69
70
|
id: instructionsId
|
|
70
|
-
}, '
|
|
71
|
+
}, this._message('valueList.instructions',
|
|
72
|
+
'Press Enter to add. Focus a value and press Ctrl plus Left or Right to reorder.'));
|
|
71
73
|
this._ownedNodes.push(this._list, this._input, this._status, this._instructions);
|
|
72
74
|
root.append(this._list, this._input, this._status, this._instructions);
|
|
73
75
|
|
|
@@ -120,13 +122,14 @@ export class ValueList extends Component {
|
|
|
120
122
|
const normalized = String(value ?? '').trim();
|
|
121
123
|
if (!normalized) return false;
|
|
122
124
|
if (this.options.unique && this._values.includes(normalized)) {
|
|
123
|
-
this._showError('This value already exists.');
|
|
125
|
+
this._showError(this._message('valueList.duplicate', 'This value already exists.'));
|
|
124
126
|
return false;
|
|
125
127
|
}
|
|
126
128
|
if (typeof this.options.validate === 'function') {
|
|
127
129
|
const result = this.options.validate(normalized);
|
|
128
130
|
if (result !== true) {
|
|
129
|
-
this._showError(typeof result === 'string' ? result
|
|
131
|
+
this._showError(typeof result === 'string' ? result
|
|
132
|
+
: this._message('valueList.invalid', 'This value is not valid.'));
|
|
130
133
|
return false;
|
|
131
134
|
}
|
|
132
135
|
}
|
|
@@ -196,7 +199,8 @@ export class ValueList extends Component {
|
|
|
196
199
|
draggable: Boolean(this.options.sortable) && !this._disabled,
|
|
197
200
|
ariaSelected: 'false',
|
|
198
201
|
ariaDisabled: String(this._disabled),
|
|
199
|
-
ariaLabel: this.options.deletable
|
|
202
|
+
ariaLabel: this.options.deletable
|
|
203
|
+
? this._message('valueList.removable', '%1. Press Delete to remove.', value) : value,
|
|
200
204
|
dataset: { index }
|
|
201
205
|
}, h('span', { class: 'zx-value-list__value' }, value), this.options.deletable ? h('span', {
|
|
202
206
|
class: 'zx-value-list__remove',
|
|
@@ -302,6 +306,18 @@ export class ValueList extends Component {
|
|
|
302
306
|
this._input.setAttribute('aria-invalid', 'true');
|
|
303
307
|
}
|
|
304
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Resolves a message through the host translator, falling back to the built-in English text.
|
|
311
|
+
* @param {string} key Message key.
|
|
312
|
+
* @param {string} fallback Built-in text, with `%1`-style placeholders.
|
|
313
|
+
* @param {...unknown} args Interpolation values.
|
|
314
|
+
* @returns {string}
|
|
315
|
+
*/
|
|
316
|
+
_message(key, fallback, ...args) {
|
|
317
|
+
const message = this.msg(key, ...args);
|
|
318
|
+
return message === key ? printf(fallback, args) : message;
|
|
319
|
+
}
|
|
320
|
+
|
|
305
321
|
/** @returns {void} */
|
|
306
322
|
destroy() {
|
|
307
323
|
for (const node of this._ownedNodes ?? []) node.remove();
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
margin-inline-start: auto;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/* Mounted into a host toolbar the view does not own: the host decides where it sits. */
|
|
21
|
+
.zx-record-view__field-controls[data-mounted="external"] {
|
|
22
|
+
margin-inline-start: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
.zx-record-view__field-controls > summary {
|
|
21
26
|
display: inline-flex;
|
|
22
27
|
align-items: center;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { Component } from '../../core/component.js';
|
|
3
|
-
import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
|
|
3
|
+
import { h, resolveElement, restoreTarget, snapshotTarget } from '../../core/dom.js';
|
|
4
4
|
import { icon } from '../../core/icons.js';
|
|
5
|
+
import { printf } from '../../core/i18n.js';
|
|
5
6
|
import { sortRows } from '../table/sort.js';
|
|
6
7
|
|
|
7
8
|
/** @typedef {Record<string, any>} ViewRecord */
|
|
@@ -31,6 +32,23 @@ import { sortRows } from '../table/sort.js';
|
|
|
31
32
|
* @property {ViewSortDirection} dir Sort direction.
|
|
32
33
|
*/
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Trigger customisation for the shared field chooser. Passing this object in place of `true` keeps
|
|
37
|
+
* the control and its state model exactly as they are; only its wording and mount point change.
|
|
38
|
+
* @typedef {Object} ViewFieldControlsConfig
|
|
39
|
+
* @property {string} [label] Trigger label. Omitted resolves `recordView.fields`, English `Fields`.
|
|
40
|
+
* @property {Element|string|null} [target=null] Element or selector the disclosure is mounted into
|
|
41
|
+
* instead of the view's own toolbar. An unresolvable target falls back to the view.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {Object} ViewFieldControlsSettings
|
|
46
|
+
* @property {boolean} enabled Whether a concrete view renders the chooser at all.
|
|
47
|
+
* @property {boolean} custom Whether the option was given as a `ViewFieldControlsConfig` object.
|
|
48
|
+
* @property {string|null} label Configured trigger label, or null to resolve the message.
|
|
49
|
+
* @property {Element|string|null} target Configured mount point, before resolution.
|
|
50
|
+
*/
|
|
51
|
+
|
|
34
52
|
/**
|
|
35
53
|
* @typedef {Object} RecordViewState
|
|
36
54
|
* @property {1} version State schema version.
|
|
@@ -50,7 +68,10 @@ import { sortRows } from '../table/sort.js';
|
|
|
50
68
|
* @property {unknown[]} [selection=[]] Initially selected record ids.
|
|
51
69
|
* @property {string[]} [fieldOrder=[]] Field ids in preferred order.
|
|
52
70
|
* @property {string[]} [hiddenFields=[]] Initially hidden field ids.
|
|
53
|
-
* @property {boolean} [fieldControls=true] Whether concrete views show the
|
|
71
|
+
* @property {boolean|ViewFieldControlsConfig} [fieldControls=true] Whether concrete views show the
|
|
72
|
+
* shared field chooser. `true` renders it in the view's own toolbar and `false` omits it; a
|
|
73
|
+
* `ViewFieldControlsConfig` object renders the same control with a translated `label` and, with
|
|
74
|
+
* `target`, mounts it into a host element such as a list toolbar.
|
|
54
75
|
* @property {string|Node|(() => string|Node)|null} [emptyText=null] Empty-result content.
|
|
55
76
|
* @property {(event: CustomEvent<Record<string, unknown>>) => void} [onrecordclick]
|
|
56
77
|
* @property {(event: CustomEvent<Record<string, unknown>>) => void} [onrecorddblclick]
|
|
@@ -117,6 +138,7 @@ export class RecordView extends Component {
|
|
|
117
138
|
this._recordViewDestroyed = true;
|
|
118
139
|
const root = this.el;
|
|
119
140
|
const snapshot = this._recordViewOriginal;
|
|
141
|
+
this._removeViewFieldControls();
|
|
120
142
|
super.destroy();
|
|
121
143
|
if (snapshot && root) restoreTarget(root, snapshot);
|
|
122
144
|
}
|
|
@@ -139,6 +161,9 @@ export class RecordView extends Component {
|
|
|
139
161
|
this._viewSelectionAnchor = null;
|
|
140
162
|
this._viewFieldControls = null;
|
|
141
163
|
this._viewFieldList = null;
|
|
164
|
+
this._viewFieldSettings = normalizeViewFieldControls(this.options.fieldControls);
|
|
165
|
+
// Resolved once, here, so the mount point cannot drift between render and destroy.
|
|
166
|
+
this._viewFieldControlsTarget = resolveElement(this._viewFieldSettings.target);
|
|
142
167
|
this._viewInitialized = true;
|
|
143
168
|
if (this._viewSort && this.options.sortMode === 'local') this._sortViewData();
|
|
144
169
|
this._setInitialSelection(this.options.selection);
|
|
@@ -467,13 +492,15 @@ export class RecordView extends Component {
|
|
|
467
492
|
/**
|
|
468
493
|
* Builds the shared show/hide and ordering disclosure for a concrete view toolbar.
|
|
469
494
|
* @protected
|
|
470
|
-
* @param {string} [label=
|
|
495
|
+
* @param {string|null} [label=null] Explicit trigger label. Omitted resolves the configured
|
|
496
|
+
* `fieldControls.label` and then the `recordView.fields` message.
|
|
471
497
|
* @returns {HTMLElement} Disclosure element.
|
|
472
498
|
*/
|
|
473
|
-
_createViewFieldControls(label =
|
|
499
|
+
_createViewFieldControls(label = null) {
|
|
500
|
+
const text = label ?? this._viewFieldControlsLabel();
|
|
474
501
|
const list = h('div', { class: 'zx-record-view__field-list' });
|
|
475
502
|
const details = h('details', { class: 'zx-record-view__field-controls' },
|
|
476
|
-
h('summary', { class: 'zx-button zx-button--sm' }, icon('fields', { size: 13 }),
|
|
503
|
+
h('summary', { class: 'zx-button zx-button--sm' }, icon('fields', { size: 13 }), text),
|
|
477
504
|
list);
|
|
478
505
|
this._viewFieldControls = details;
|
|
479
506
|
this._viewFieldList = list;
|
|
@@ -498,6 +525,58 @@ export class RecordView extends Component {
|
|
|
498
525
|
return details;
|
|
499
526
|
}
|
|
500
527
|
|
|
528
|
+
/**
|
|
529
|
+
* Builds the shared field disclosure and mounts it where `fieldControls` asks for it: the host
|
|
530
|
+
* element named by `target`, or the view's own toolbar when no target resolves.
|
|
531
|
+
* @protected
|
|
532
|
+
* @param {Element} toolbar The view's own toolbar.
|
|
533
|
+
* @param {string|null} [label=null] Explicit trigger label, for views that own their wording.
|
|
534
|
+
* @returns {HTMLElement} The mounted disclosure.
|
|
535
|
+
*/
|
|
536
|
+
_mountViewFieldControls(toolbar, label = null) {
|
|
537
|
+
const controls = this._createViewFieldControls(label);
|
|
538
|
+
const target = this._viewFieldControlsTarget;
|
|
539
|
+
if (target) controls.dataset.mounted = 'external';
|
|
540
|
+
(target ?? toolbar).append(controls);
|
|
541
|
+
return controls;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Removes the shared field disclosure from wherever it was mounted. An external `target` keeps
|
|
546
|
+
* every node it had before the view was created.
|
|
547
|
+
* @protected
|
|
548
|
+
* @returns {void}
|
|
549
|
+
*/
|
|
550
|
+
_removeViewFieldControls() {
|
|
551
|
+
this._viewFieldControls?.remove?.();
|
|
552
|
+
this._viewFieldControls = null;
|
|
553
|
+
this._viewFieldList = null;
|
|
554
|
+
this._viewFieldControlsTarget = null;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Resolves the field-chooser trigger label: the configured one, else the translated message.
|
|
559
|
+
* @protected
|
|
560
|
+
* @returns {string} Trigger label.
|
|
561
|
+
*/
|
|
562
|
+
_viewFieldControlsLabel() {
|
|
563
|
+
const settings = this._viewFieldSettings ?? normalizeViewFieldControls(this.options.fieldControls);
|
|
564
|
+
return settings.label ?? this._message('recordView.fields', 'Fields');
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Resolves a message through the host translator, falling back to the built-in English text.
|
|
569
|
+
* @protected
|
|
570
|
+
* @param {string} key Message key.
|
|
571
|
+
* @param {string} fallback Built-in text, with `%1`-style placeholders.
|
|
572
|
+
* @param {...unknown} args Interpolation values.
|
|
573
|
+
* @returns {string} Translated or built-in text.
|
|
574
|
+
*/
|
|
575
|
+
_message(key, fallback, ...args) {
|
|
576
|
+
const message = this.msg(key, ...args);
|
|
577
|
+
return message === key ? printf(fallback, args) : message;
|
|
578
|
+
}
|
|
579
|
+
|
|
501
580
|
/** Concrete renderers override this hook. @protected @param {string} _reason Refresh reason. @returns {void} */
|
|
502
581
|
_refreshView(_reason) {}
|
|
503
582
|
|
|
@@ -567,14 +646,14 @@ export class RecordView extends Component {
|
|
|
567
646
|
const up = h('button', {
|
|
568
647
|
class: 'zx-record-view__field-move',
|
|
569
648
|
type: 'button',
|
|
570
|
-
ariaLabel:
|
|
649
|
+
ariaLabel: this._message('recordView.moveFieldUp', 'Move %1 up', field.label),
|
|
571
650
|
disabled: index === 0,
|
|
572
651
|
dataset: { viewFieldAction: 'up', fieldId: field.id }
|
|
573
652
|
}, icon('chevron-up', { size: 11 }));
|
|
574
653
|
const down = h('button', {
|
|
575
654
|
class: 'zx-record-view__field-move',
|
|
576
655
|
type: 'button',
|
|
577
|
-
ariaLabel:
|
|
656
|
+
ariaLabel: this._message('recordView.moveFieldDown', 'Move %1 down', field.label),
|
|
578
657
|
disabled: index === fields.length - 1,
|
|
579
658
|
dataset: { viewFieldAction: 'down', fieldId: field.id }
|
|
580
659
|
}, icon('chevron-down', { size: 11 }));
|
|
@@ -593,6 +672,20 @@ export class RecordView extends Component {
|
|
|
593
672
|
}
|
|
594
673
|
}
|
|
595
674
|
|
|
675
|
+
/**
|
|
676
|
+
* Reads the `fieldControls` option in its three shapes. `true` and `false` keep their exact
|
|
677
|
+
* meaning; an object additionally carries the trigger label and the host element to mount into.
|
|
678
|
+
* @param {boolean|ViewFieldControlsConfig|null|undefined} value Raw option value.
|
|
679
|
+
* @returns {ViewFieldControlsSettings} Normalized settings.
|
|
680
|
+
*/
|
|
681
|
+
export function normalizeViewFieldControls(value) {
|
|
682
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
683
|
+
const label = value.label == null ? null : String(value.label);
|
|
684
|
+
return { enabled: true, custom: true, label, target: value.target ?? null };
|
|
685
|
+
}
|
|
686
|
+
return { enabled: Boolean(value), custom: false, label: null, target: null };
|
|
687
|
+
}
|
|
688
|
+
|
|
596
689
|
/**
|
|
597
690
|
* Validates and clones field descriptors.
|
|
598
691
|
* @param {ViewField[]} fields Fields.
|
package/src/core/component.js
CHANGED
|
@@ -157,21 +157,26 @@ export class Component extends EventTarget {
|
|
|
157
157
|
* Dispatches a component event and a bubbling, composed `zx-*` DOM event.
|
|
158
158
|
* @param {string} type Lowercase event name.
|
|
159
159
|
* @param {Record<string, unknown>} [detail={}] Single event detail object.
|
|
160
|
+
* @param {{honorDomCancellation?:boolean}} [options={}] Mirrored DOM-event behavior. Components
|
|
161
|
+
* with a cancelable mutation boundary may opt into treating `preventDefault()` on the bubbling
|
|
162
|
+
* `zx-*` event exactly like cancellation of the component event.
|
|
160
163
|
* @returns {Event & {detail: Record<string, unknown>}}
|
|
161
164
|
* @fires Component#event
|
|
162
165
|
*/
|
|
163
|
-
emit(type, detail = {}) {
|
|
166
|
+
emit(type, detail = {}, options = {}) {
|
|
164
167
|
if (detail === null || typeof detail !== 'object' || Array.isArray(detail)) {
|
|
165
168
|
throw new TypeError('Event detail must be an object');
|
|
166
169
|
}
|
|
167
170
|
const componentEvent = createCustomEvent(type, detail, { cancelable: true });
|
|
168
171
|
this.dispatchEvent(componentEvent);
|
|
169
172
|
if (this.el) {
|
|
170
|
-
|
|
173
|
+
const domEvent = createCustomEvent(`zx-${type}`, detail, {
|
|
171
174
|
bubbles: true,
|
|
172
175
|
composed: true,
|
|
173
176
|
cancelable: true
|
|
174
|
-
})
|
|
177
|
+
});
|
|
178
|
+
this.el.dispatchEvent(domEvent);
|
|
179
|
+
if (options.honorDomCancellation && domEvent.defaultPrevented) componentEvent.preventDefault();
|
|
175
180
|
}
|
|
176
181
|
return componentEvent;
|
|
177
182
|
}
|
package/src/core/icons.js
CHANGED
|
@@ -22,6 +22,9 @@ const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
|
22
22
|
* @property {string} [style] Font Awesome style for this icon only (`'solid'`, `'duotone'`, …).
|
|
23
23
|
* @property {string} [family] Font Awesome family for this icon only (`'classic'`, `'sharp'`, …).
|
|
24
24
|
* @property {boolean} [fixedWidth] Adds Font Awesome's `fa-fw` for column-aligned glyphs.
|
|
25
|
+
* @property {boolean} [strict=false] Throws `RangeError` on a name the bundled glyph set does not
|
|
26
|
+
* carry, instead of returning the placeholder. For call sites that can afford to fail — a test,
|
|
27
|
+
* a build-time check — rather than a screen rendering server data.
|
|
25
28
|
*/
|
|
26
29
|
|
|
27
30
|
/**
|
|
@@ -34,6 +37,8 @@ const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
|
34
37
|
* @type {Readonly<Record<string, [string, string]>>}
|
|
35
38
|
*/
|
|
36
39
|
export const icons = Object.freeze({
|
|
40
|
+
'arrow-up-wide-short': ['0 0 576 512', 'M151.6 42.4C145.5 35.8 137 32 128 32s-17.5 3.8-23.6 10.4l-88 96c-11.9 13-11.1 33.3 2 45.2s33.3 11.1 45.2-2L96 146.3 96 448c0 17.7 14.3 32 32 32s32-14.3 32-32l0-301.7 32.4 35.4c11.9 13 32.2 13.9 45.2 2s13.9-32.2 2-45.2l-88-96zM320 480l32 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-32 0c-17.7 0-32 14.3-32 32s14.3 32 32 32zm0-128l96 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-96 0c-17.7 0-32 14.3-32 32s14.3 32 32 32zm0-128l160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-160 0c-17.7 0-32 14.3-32 32s14.3 32 32 32zm0-128l224 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L320 32c-17.7 0-32 14.3-32 32s14.3 32 32 32z'],
|
|
41
|
+
'arrow-down-wide-short': ['0 0 576 512', 'M151.6 469.6C145.5 476.2 137 480 128 480s-17.5-3.8-23.6-10.4l-88-96c-11.9-13-11.1-33.3 2-45.2s33.3-11.1 45.2 2L96 365.7 96 64c0-17.7 14.3-32 32-32s32 14.3 32 32l0 301.7 32.4-35.4c11.9-13 32.2-13.9 45.2-2s13.9 32.2 2 45.2l-88 96zM320 480c-17.7 0-32-14.3-32-32s14.3-32 32-32l32 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-32 0zm0-128c-17.7 0-32-14.3-32-32s14.3-32 32-32l96 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-96 0zm0-128c-17.7 0-32-14.3-32-32s14.3-32 32-32l160 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-160 0zm0-128c-17.7 0-32-14.3-32-32s14.3-32 32-32l224 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L320 96z'],
|
|
37
42
|
'chevron-down': ['0 0 512 512', 'M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z'],
|
|
38
43
|
'chevron-up': ['0 0 512 512', 'M233.4 105.4c12.5-12.5 32.8-12.5 45.3 0l192 192c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L256 173.3 86.6 342.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l192-192z'],
|
|
39
44
|
'chevron-left': ['0 0 320 512', 'M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z'],
|
|
@@ -188,9 +193,17 @@ export function iconNames() {
|
|
|
188
193
|
* its renderer itself — `'fa:user'`, `'duotone:user'`, `'kit:zeyos-notes'`, `'builtin:check'`, or
|
|
189
194
|
* a literal Font Awesome class list such as `'fa-solid fa-user'`.
|
|
190
195
|
*
|
|
196
|
+
* **A name this cannot resolve never throws.** Icon names routinely arrive from a server, and one
|
|
197
|
+
* unconfigured name must not take down the screen it appears on. An inline glyph the bundled set
|
|
198
|
+
* does not carry renders as the placeholder described in `missingIcon()` and is reported once per
|
|
199
|
+
* distinct name through `console.warn`; pass `{ strict: true }` to get a `RangeError` instead.
|
|
200
|
+
* Only the inline-SVG path can fail this way: Font Awesome resolves names in the browser at render
|
|
201
|
+
* time, so a name under a loaded kit or stylesheet is never "unknown" here and never warns.
|
|
202
|
+
*
|
|
191
203
|
* @param {string} name Icon name or spec.
|
|
192
204
|
* @param {IconOptions} [options={}] Display and accessibility options.
|
|
193
205
|
* @returns {SVGSVGElement|HTMLElement} An `<svg>` for inline glyphs, an `<i>` for Font Awesome.
|
|
206
|
+
* @throws {RangeError} With `{ strict: true }` only, when the bundled set has no such glyph.
|
|
194
207
|
*/
|
|
195
208
|
export function icon(name, options = {}) {
|
|
196
209
|
const spec = parseIconSpec(name);
|
|
@@ -218,18 +231,37 @@ export function icon(name, options = {}) {
|
|
|
218
231
|
}
|
|
219
232
|
|
|
220
233
|
/**
|
|
221
|
-
* Renders one of the bundled inline SVG glyphs.
|
|
234
|
+
* Renders one of the bundled inline SVG glyphs, or the placeholder when there is no such glyph.
|
|
222
235
|
* @param {string} name Resolved glyph name.
|
|
223
236
|
* @param {IconOptions} options Display and accessibility options.
|
|
224
237
|
* @returns {SVGSVGElement}
|
|
238
|
+
* @throws {RangeError} With `options.strict`, when no glyph carries that name.
|
|
225
239
|
*/
|
|
226
240
|
function builtinIcon(name, options) {
|
|
227
241
|
const entry = extraGlyphs[name] ?? icons[name];
|
|
228
|
-
if (!entry)
|
|
242
|
+
if (!entry) {
|
|
243
|
+
if (options.strict) throw new RangeError(`Unknown icon: ${name}`);
|
|
244
|
+
warnMissingIcon(name);
|
|
245
|
+
return missingIcon(name, options);
|
|
246
|
+
}
|
|
229
247
|
const [viewBox, pathData] = entry;
|
|
248
|
+
const svg = iconSvg(viewBox, options);
|
|
249
|
+
const path = document.createElementNS(SVG_NS, 'path');
|
|
250
|
+
path.setAttribute('d', pathData);
|
|
251
|
+
svg.append(path);
|
|
252
|
+
return svg;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Builds the `<svg>` root every inline glyph shares, so the placeholder is the same element in
|
|
257
|
+
* the same box as the icon it stands in for.
|
|
258
|
+
* @param {string} viewBox The glyph's view box.
|
|
259
|
+
* @param {IconOptions} options Display and accessibility options.
|
|
260
|
+
* @returns {SVGSVGElement}
|
|
261
|
+
*/
|
|
262
|
+
function iconSvg(viewBox, options) {
|
|
230
263
|
const { size = 16 } = options;
|
|
231
264
|
const svg = document.createElementNS(SVG_NS, 'svg');
|
|
232
|
-
const path = document.createElementNS(SVG_NS, 'path');
|
|
233
265
|
svg.setAttribute('class', classList('zx-icon', options.class));
|
|
234
266
|
svg.setAttribute('viewBox', viewBox);
|
|
235
267
|
svg.setAttribute('width', String(size));
|
|
@@ -237,11 +269,48 @@ function builtinIcon(name, options) {
|
|
|
237
269
|
svg.setAttribute('fill', 'currentColor');
|
|
238
270
|
svg.setAttribute('focusable', 'false');
|
|
239
271
|
applyLabel(svg, options.label ?? null);
|
|
240
|
-
path.setAttribute('d', pathData);
|
|
241
|
-
svg.append(path);
|
|
242
272
|
return svg;
|
|
243
273
|
}
|
|
244
274
|
|
|
275
|
+
/** View box the placeholder reserves — the square most Font Awesome glyphs are drawn in. */
|
|
276
|
+
const PLACEHOLDER_VIEW_BOX = '0 0 512 512';
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The element an unresolvable name renders as: the ordinary `zx-icon` root at the requested size,
|
|
280
|
+
* carrying no path.
|
|
281
|
+
*
|
|
282
|
+
* Empty rather than a fallback glyph on purpose. The box is what layout needs — a row of icons
|
|
283
|
+
* keeps its alignment and nothing reflows — while a question mark or an outlined square would
|
|
284
|
+
* read as an icon somebody chose, and would be the wrong one wherever colour or shape carries
|
|
285
|
+
* meaning. The gap is addressed to the developer instead, through the `console.warn` and through
|
|
286
|
+
* `data-zx-icon-missing`, which names it in devtools and lets a test or a page audit find every
|
|
287
|
+
* one of them with a single selector.
|
|
288
|
+
* @param {string} name Resolved glyph name that could not be found.
|
|
289
|
+
* @param {IconOptions} options Display and accessibility options.
|
|
290
|
+
* @returns {SVGSVGElement}
|
|
291
|
+
*/
|
|
292
|
+
function missingIcon(name, options) {
|
|
293
|
+
const svg = iconSvg(PLACEHOLDER_VIEW_BOX, options);
|
|
294
|
+
svg.setAttribute('data-zx-icon-missing', name);
|
|
295
|
+
return svg;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** @type {Set<string>} Names already reported, so each gap is warned about exactly once. */
|
|
299
|
+
const warnedMissing = new Set();
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Reports a missing glyph once per distinct name. A list of four hundred rows built from the same
|
|
303
|
+
* unconfigured name is one gap to fix, so it is one line in the console, not four hundred.
|
|
304
|
+
* @param {string} name Resolved glyph name that could not be found.
|
|
305
|
+
* @returns {void}
|
|
306
|
+
*/
|
|
307
|
+
function warnMissingIcon(name) {
|
|
308
|
+
if (warnedMissing.has(name)) return;
|
|
309
|
+
warnedMissing.add(name);
|
|
310
|
+
console.warn(`[zx.icon] Unknown icon "${name}" — rendered an empty placeholder. `
|
|
311
|
+
+ 'Add it with registerIcons(), or render Font Awesome with useFontAwesome().');
|
|
312
|
+
}
|
|
313
|
+
|
|
245
314
|
/**
|
|
246
315
|
* Renders a Font Awesome icon element. The glyph itself comes from the kit or stylesheet the
|
|
247
316
|
* application loaded; Zx only writes the classes.
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { registerChecklistFieldAdapter } from './components/checklist/field-adapter.js';
|
|
7
|
+
import { registerCodeEditorFieldAdapter } from './components/code-editor/field-adapter.js';
|
|
7
8
|
import { registerDatePickerFieldAdapters } from './components/date-picker/field-adapter.js';
|
|
8
9
|
import { registerDateRangeFieldAdapter } from './components/date-picker/range-field-adapter.js';
|
|
9
10
|
import { registerDateboxFieldAdapters } from './components/datebox/field-adapter.js';
|
|
@@ -25,6 +26,7 @@ import { registerValueListFieldAdapter } from './components/value-list/field-ada
|
|
|
25
26
|
export function registerFieldAdapters() {
|
|
26
27
|
registerSelectFieldAdapter();
|
|
27
28
|
registerChecklistFieldAdapter();
|
|
29
|
+
registerCodeEditorFieldAdapter();
|
|
28
30
|
registerDateboxFieldAdapters();
|
|
29
31
|
registerDatePickerFieldAdapters();
|
|
30
32
|
registerDateRangeFieldAdapter();
|
|
@@ -85,10 +87,12 @@ export { stack, grid, aspect } from './components/layout/layout.js';
|
|
|
85
87
|
export { truncate, isTruncated } from './components/truncate/truncate.js';
|
|
86
88
|
export { copyButton } from './components/copy/copy-button.js';
|
|
87
89
|
export { CopyInput } from './components/copy/copy-input.js';
|
|
90
|
+
export { CodeEditor, indentSelection, editorPosition } from './components/code-editor/code-editor.js';
|
|
88
91
|
export { Toolbar } from './components/toolbar/toolbar.js';
|
|
89
92
|
export { Toggle } from './components/toggle/toggle.js';
|
|
90
93
|
export { Groupbox } from './components/groupbox/groupbox.js';
|
|
91
94
|
export { Card } from './components/card/card.js';
|
|
95
|
+
export { StatTile, statTile } from './components/stat-tile/stat-tile.js';
|
|
92
96
|
export { Aurora } from './components/aurora/aurora.js';
|
|
93
97
|
export { Panel } from './components/panel/panel.js';
|
|
94
98
|
export { MasterPanel } from './components/master-panel/master-panel.js';
|
|
@@ -105,6 +109,18 @@ export { Search } from './components/search/search.js';
|
|
|
105
109
|
export { AppIcon, appIcon } from './components/app-icon/app-icon.js';
|
|
106
110
|
export { Launcher, rankLauncherItems } from './components/launcher/launcher.js';
|
|
107
111
|
export { Avatar, avatarInitials } from './components/avatar/avatar.js';
|
|
112
|
+
export {
|
|
113
|
+
EntityRef, normalizeEntityRef, normalizeEntityRefLink,
|
|
114
|
+
normalizeEntityRefMetadata, normalizeEntityRefActions
|
|
115
|
+
} from './components/entity-ref/entity-ref.js';
|
|
116
|
+
export { FileItem, normalizeFileItem } from './components/file-item/file-item.js';
|
|
117
|
+
export { FileList, normalizeFileListItems } from './components/file-list/file-list.js';
|
|
118
|
+
export {
|
|
119
|
+
ActivityItem, resolveActivityActions, normalizeActivityAction, normalizeActivityTimestamp
|
|
120
|
+
} from './components/activity-item/activity-item.js';
|
|
121
|
+
export {
|
|
122
|
+
ActivityList, groupActivityItems, normalizeActivityItems
|
|
123
|
+
} from './components/activity-list/activity-list.js';
|
|
108
124
|
export { AccountMenu } from './components/account-menu/account-menu.js';
|
|
109
125
|
export { normalizeAppItems } from './internal/app-rail.js';
|
|
110
126
|
export { AppSidebar } from './components/app-sidebar/app-sidebar.js';
|
|
@@ -136,9 +152,9 @@ export {
|
|
|
136
152
|
export { Timebox, splitTime, joinTime } from './components/timebox/timebox.js';
|
|
137
153
|
export { Calendar } from './components/calendar/calendar.js';
|
|
138
154
|
export {
|
|
139
|
-
CALENDAR_VIEWS, addCalendarDays, calendarDayDifference, calendarDayKey,
|
|
140
|
-
calendarEventIntersects, calendarEventSpansDays,
|
|
141
|
-
layoutCalendarSpans, layoutTimedCalendarEvents, normalizeCalendarEvents,
|
|
155
|
+
CALENDAR_VIEWS, addCalendarDays, addCalendarMonths, calendarDayDifference, calendarDayKey,
|
|
156
|
+
calendarEventIntersects, calendarEventSpansDays, calendarPageDate, calendarRange,
|
|
157
|
+
cloneCalendarEvent, layoutCalendarSpans, layoutTimedCalendarEvents, normalizeCalendarEvents,
|
|
142
158
|
shiftCalendarEvent, startOfCalendarDay, startOfCalendarWeek
|
|
143
159
|
} from './components/calendar/calendar-model.js';
|
|
144
160
|
export {
|
|
@@ -149,6 +165,15 @@ export { Table, reorderTableRows } from './components/table/table.js';
|
|
|
149
165
|
export { TableView } from './components/table-view/table-view.js';
|
|
150
166
|
export { CardView } from './components/card-view/card-view.js';
|
|
151
167
|
export { KanbanView } from './components/kanban-view/kanban-view.js';
|
|
168
|
+
export {
|
|
169
|
+
absoluteKanbanIndex, allowsKanbanTransition, createKanbanHistory, evaluateKanbanMove,
|
|
170
|
+
kanbanSearchTerms, locateKanbanRecord, matchesKanbanSearch, normalizeKanbanRules,
|
|
171
|
+
reorderKanbanRecords, resolveKanbanRules
|
|
172
|
+
} from './components/kanban-view/kanban-policy.js';
|
|
173
|
+
export {
|
|
174
|
+
createKanbanAvatars, createKanbanCardHead, createKanbanIndicator, createKanbanProgress,
|
|
175
|
+
kanbanAvatarLimit, resolveKanbanAssignees, resolveKanbanIndicator, resolveKanbanProgress
|
|
176
|
+
} from './components/kanban-view/kanban-card.js';
|
|
152
177
|
export { Grid, billingColumns, isBillingLine } from './components/grid/grid.js';
|
|
153
178
|
export {
|
|
154
179
|
Chart, ChartJsAdapter, chartSummaryTable, cloneChartValue, isEmptyChartData
|
|
@@ -156,6 +181,11 @@ export {
|
|
|
156
181
|
export { TreeView } from './components/tree/tree.js';
|
|
157
182
|
export { Finder } from './components/finder/finder.js';
|
|
158
183
|
export { DataFilter } from './components/data-filter/data-filter.js';
|
|
184
|
+
export {
|
|
185
|
+
FilterPanel, isActiveValue, pruneValue, serializeFilterValue
|
|
186
|
+
} from './components/filter-panel/filter-panel.js';
|
|
187
|
+
export { SortControl } from './components/sort-control/sort-control.js';
|
|
188
|
+
export { ListToolbar } from './components/list-toolbar/list-toolbar.js';
|
|
159
189
|
export { Filter } from './components/filter/filter.js';
|
|
160
190
|
export {
|
|
161
191
|
filterOperators, emptyFilterAst, filterCondition, filterGroup,
|
package/src/internal/app-rail.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Component } from '../core/component.js';
|
|
2
|
+
import { printf } from '../core/i18n.js';
|
|
2
3
|
import { h, safeHref } from '../core/dom.js';
|
|
3
4
|
import { icon } from '../core/icons.js';
|
|
4
5
|
import { Dropdown } from '../components/dropdown/dropdown.js';
|
|
@@ -292,8 +293,12 @@ export class AppRailPresenter extends Component {
|
|
|
292
293
|
});
|
|
293
294
|
const actualPanel = dropdown.getPanel();
|
|
294
295
|
actualPanel.classList.add('zx-app-rail__popover');
|
|
296
|
+
// The panel is portaled out of the rail, so its stylesheet cannot ask which edge the rail
|
|
297
|
+
// occupies. Presentation only: it carries the active marker to the edge facing the screen
|
|
298
|
+
// on a right-hand rail, exactly as the expanded tree does.
|
|
299
|
+
actualPanel.dataset.side = this.el.dataset.side;
|
|
295
300
|
actualPanel.setAttribute('role', 'region');
|
|
296
|
-
actualPanel.setAttribute('aria-label',
|
|
301
|
+
actualPanel.setAttribute('aria-label', this._message('appRail.subNavigation', '%1 sub-navigation', item.label));
|
|
297
302
|
const flyout = { key, parentKey, item, trigger, panel: actualPanel, dropdown };
|
|
298
303
|
this._flyouts.set(key, flyout);
|
|
299
304
|
dropdown.on('open', () => {
|
|
@@ -573,6 +578,19 @@ export class AppRailPresenter extends Component {
|
|
|
573
578
|
this._interaction = null;
|
|
574
579
|
this._flyouts = new Map();
|
|
575
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* Resolves one message through `msg()`, which falls back to the host translator. Copied from
|
|
583
|
+
* `Pagination` rather than invented: the rail's flyout label was the last user-visible string
|
|
584
|
+
* in the navigation stack with no way to change it.
|
|
585
|
+
* @param {string} key Message key.
|
|
586
|
+
* @param {string} fallback English default.
|
|
587
|
+
* @param {...unknown} args Interpolation arguments.
|
|
588
|
+
* @returns {string}
|
|
589
|
+
*/
|
|
590
|
+
_message(key, fallback, ...args) {
|
|
591
|
+
const message = this.msg(key, ...args);
|
|
592
|
+
return message === key ? printf(fallback, args) : message;
|
|
593
|
+
}
|
|
576
594
|
}
|
|
577
595
|
|
|
578
596
|
/** Owns listeners attached to one dynamic rail render so `setItems()` can release them immediately. */
|