@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
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { Component } from '../../core/component.js';
|
|
3
|
+
import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
|
|
4
|
+
import { uid } from '../../core/util.js';
|
|
5
|
+
import { skeleton, skeletonText } from '../skeleton/skeleton.js';
|
|
6
|
+
import { ActivityItem } from '../activity-item/activity-item.js';
|
|
7
|
+
|
|
8
|
+
/** @typedef {import('../activity-item/activity-item.js').ActivityItemOptions} ActivityItemOptions */
|
|
9
|
+
/** @typedef {string|number|Node|Component|null} ActivityListContent */
|
|
10
|
+
/** @typedef {string|((item:ActivityItemOptions,index:number)=>unknown)|null} ActivityGroupReader */
|
|
11
|
+
/** @typedef {(group:unknown,items:ActivityItemOptions[])=>ActivityListContent} ActivityGroupLabel */
|
|
12
|
+
/**
|
|
13
|
+
* Entry layout. `'default'` gives each entry its avatar, a heading row, and room for content;
|
|
14
|
+
* below 30rem the time moves under the heading. `'compact'` is one line per entry at any width —
|
|
15
|
+
* actor and summary reading as one sentence, the time at the trailing end, a small avatar, and
|
|
16
|
+
* metadata as a muted second line — for a summary card narrower than 30rem.
|
|
17
|
+
* @typedef {'default'|'compact'} ActivityListVariant
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} ActivityGroup
|
|
22
|
+
* @property {unknown} id Group identity returned by the configured reader.
|
|
23
|
+
* @property {ActivityItemOptions[]} items Activities in stable source order.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Object} ActivityListOptions
|
|
28
|
+
* @property {ActivityItemOptions[]} [items=[]] Initial activities in host-supplied chronological order.
|
|
29
|
+
* @property {ActivityGroupReader} [groupBy=null] Property or resolver used for stable grouping.
|
|
30
|
+
* @property {ActivityGroupLabel|null} [groupLabel=null] Group-heading renderer.
|
|
31
|
+
* @property {string} [label='Activity'] Accessible collection name.
|
|
32
|
+
* @property {ActivityListVariant} [variant='default'] Entry layout, reflected as `data-variant` on
|
|
33
|
+
* the root. `'compact'` also makes an avatar descriptor without a `size` small.
|
|
34
|
+
* @property {boolean} [loading=false] Whether initial or incremental activity is loading.
|
|
35
|
+
* @property {number} [loadingCount=3] Placeholder rows shown for an empty loading list.
|
|
36
|
+
* @property {string} [loadingText='Loading activity'] Loading status text.
|
|
37
|
+
* @property {ActivityListContent} [emptyText='No activity yet'] Empty-state content.
|
|
38
|
+
* @property {1|2|3|4|5|6} [groupHeadingLevel=2] Semantic group-heading level.
|
|
39
|
+
* @property {1|2|3|4|5|6} [itemHeadingLevel=3] Default item-heading level.
|
|
40
|
+
* @property {(event:CustomEvent<{items:ActivityItemOptions[]}>)=>void} [ondatachange]
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Accessible chronological activity collection with stable grouping, loading/empty states, and
|
|
45
|
+
* incremental update methods. Ordering stays host-controlled so paginated and live server results
|
|
46
|
+
* are never silently re-sorted. Item `zx-action` events bubble through the list naturally.
|
|
47
|
+
*
|
|
48
|
+
* @fires ActivityList#datachange
|
|
49
|
+
* @extends {Component<ActivityListOptions>}
|
|
50
|
+
*/
|
|
51
|
+
export class ActivityList extends Component {
|
|
52
|
+
static cssName = 'activity-list';
|
|
53
|
+
|
|
54
|
+
/** @type {Readonly<ActivityListOptions>} */
|
|
55
|
+
static defaults = {
|
|
56
|
+
items: [],
|
|
57
|
+
groupBy: null,
|
|
58
|
+
groupLabel: null,
|
|
59
|
+
label: 'Activity',
|
|
60
|
+
variant: 'default',
|
|
61
|
+
loading: false,
|
|
62
|
+
loadingCount: 3,
|
|
63
|
+
loadingText: 'Loading activity',
|
|
64
|
+
emptyText: 'No activity yet',
|
|
65
|
+
groupHeadingLevel: 2,
|
|
66
|
+
itemHeadingLevel: 3
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Creates or enhances an activity collection.
|
|
71
|
+
* @param {Element|string|null} [target=null] Existing container, selector, or null.
|
|
72
|
+
* @param {ActivityListOptions} [options={}] Collection options.
|
|
73
|
+
*/
|
|
74
|
+
constructor(target = null, options = {}) {
|
|
75
|
+
super(target, options);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** @returns {HTMLElement} */
|
|
79
|
+
render() {
|
|
80
|
+
const created = !this.el;
|
|
81
|
+
const root = /** @type {HTMLElement} */ (this.el ?? h('section'));
|
|
82
|
+
this.el = root;
|
|
83
|
+
this._createdRoot = created;
|
|
84
|
+
this._original = created ? null : snapshotTarget(root);
|
|
85
|
+
this._cleaned = false;
|
|
86
|
+
this._items = normalizeActivityItems(this.options.items);
|
|
87
|
+
this._loading = Boolean(this.options.loading);
|
|
88
|
+
this._itemComponents = [];
|
|
89
|
+
this._groupSequence = 0;
|
|
90
|
+
root.setAttribute('aria-label', String(this.options.label || 'Activity'));
|
|
91
|
+
this._variant = normalizeActivityListVariant(this.options.variant);
|
|
92
|
+
if (this._variant === 'compact') root.dataset.variant = 'compact';
|
|
93
|
+
try {
|
|
94
|
+
this._renderList();
|
|
95
|
+
return root;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
this._destroyItems();
|
|
98
|
+
if (!created) restoreTarget(root, this._original);
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Replaces all activities.
|
|
105
|
+
* @param {ActivityItemOptions[]} items Activities in desired chronological order.
|
|
106
|
+
* @param {{silent?:boolean}} [options={}] Event behavior.
|
|
107
|
+
* @returns {this}
|
|
108
|
+
*/
|
|
109
|
+
setItems(items, options = {}) {
|
|
110
|
+
this._items = normalizeActivityItems(items);
|
|
111
|
+
this._renderList();
|
|
112
|
+
if (!options.silent) this._emitDataChange();
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Returns shallow descriptor copies in current display order. @returns {ActivityItemOptions[]} */
|
|
117
|
+
getItems() {
|
|
118
|
+
return this._items.map(cloneActivityDescriptor);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Returns a shallow descriptor copy by stable host ID. @param {unknown} id @returns {ActivityItemOptions|null} */
|
|
122
|
+
getItem(id) {
|
|
123
|
+
const item = this._items.find((candidate) => Object.is(candidate.id, id));
|
|
124
|
+
return item ? cloneActivityDescriptor(item) : null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Adds activities after the current source order for older-page or forward-stream updates.
|
|
129
|
+
* @param {ActivityItemOptions|ActivityItemOptions[]} items New activities.
|
|
130
|
+
* @param {{silent?:boolean}} [options={}] Event behavior.
|
|
131
|
+
* @returns {this}
|
|
132
|
+
*/
|
|
133
|
+
appendItems(items, options = {}) {
|
|
134
|
+
const additions = normalizeActivityItems(Array.isArray(items) ? items : [items]);
|
|
135
|
+
if (additions.length === 0) return this;
|
|
136
|
+
this._items = normalizeActivityItems([...this._items, ...additions]);
|
|
137
|
+
this._renderList();
|
|
138
|
+
if (!options.silent) this._emitDataChange();
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Adds activities before the current source order for newest-first live updates.
|
|
144
|
+
* @param {ActivityItemOptions|ActivityItemOptions[]} items New activities.
|
|
145
|
+
* @param {{silent?:boolean}} [options={}] Event behavior.
|
|
146
|
+
* @returns {this}
|
|
147
|
+
*/
|
|
148
|
+
prependItems(items, options = {}) {
|
|
149
|
+
const additions = normalizeActivityItems(Array.isArray(items) ? items : [items]);
|
|
150
|
+
if (additions.length === 0) return this;
|
|
151
|
+
this._items = normalizeActivityItems([...additions, ...this._items]);
|
|
152
|
+
this._renderList();
|
|
153
|
+
if (!options.silent) this._emitDataChange();
|
|
154
|
+
return this;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Patches the first activity whose ID is `Object.is`-equal to the requested ID.
|
|
159
|
+
* @param {unknown} id Stable host identifier.
|
|
160
|
+
* @param {Partial<ActivityItemOptions>} values Replacement fields.
|
|
161
|
+
* @param {{silent?:boolean}} [options={}] Event behavior.
|
|
162
|
+
* @returns {this}
|
|
163
|
+
*/
|
|
164
|
+
updateItem(id, values, options = {}) {
|
|
165
|
+
if (!values || typeof values !== 'object' || Array.isArray(values)) {
|
|
166
|
+
throw new TypeError('ActivityList update values must be an object');
|
|
167
|
+
}
|
|
168
|
+
const index = this._items.findIndex((candidate) => Object.is(candidate.id, id));
|
|
169
|
+
if (index < 0) return this;
|
|
170
|
+
const next = this._items.slice();
|
|
171
|
+
next[index] = { ...next[index], ...values };
|
|
172
|
+
this._items = normalizeActivityItems(next);
|
|
173
|
+
this._renderList();
|
|
174
|
+
if (!options.silent) this._emitDataChange();
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Removes the first activity whose ID is `Object.is`-equal to the requested ID.
|
|
180
|
+
* @param {unknown} id Stable host identifier.
|
|
181
|
+
* @param {{silent?:boolean}} [options={}] Event behavior.
|
|
182
|
+
* @returns {this}
|
|
183
|
+
*/
|
|
184
|
+
removeItem(id, options = {}) {
|
|
185
|
+
const index = this._items.findIndex((candidate) => Object.is(candidate.id, id));
|
|
186
|
+
if (index < 0) return this;
|
|
187
|
+
this._items.splice(index, 1);
|
|
188
|
+
this._renderList();
|
|
189
|
+
if (!options.silent) this._emitDataChange();
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Sets initial/incremental loading presentation. @param {boolean} loading @returns {this} */
|
|
194
|
+
setLoading(loading) {
|
|
195
|
+
const next = Boolean(loading);
|
|
196
|
+
if (next === this._loading) return this;
|
|
197
|
+
this._loading = next;
|
|
198
|
+
this._renderList();
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Restores an enhanced target exactly, or removes an owned list. @returns {void} */
|
|
203
|
+
destroy() {
|
|
204
|
+
if (this._cleaned) return;
|
|
205
|
+
this._cleaned = true;
|
|
206
|
+
this._destroyItems();
|
|
207
|
+
super.destroy();
|
|
208
|
+
if (!this._createdRoot) restoreTarget(this.el, this._original);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** @private @returns {void} */
|
|
212
|
+
_renderList() {
|
|
213
|
+
this._destroyItems();
|
|
214
|
+
this._groupSequence = 0;
|
|
215
|
+
const root = /** @type {HTMLElement} */ (this.el);
|
|
216
|
+
root.setAttribute('aria-busy', String(this._loading));
|
|
217
|
+
/** @type {HTMLElement[]} */
|
|
218
|
+
const content = [];
|
|
219
|
+
try {
|
|
220
|
+
if (this._items.length > 0) {
|
|
221
|
+
const groupBy = this.options.groupBy ?? null;
|
|
222
|
+
const groups = groupActivityItems(this._items, groupBy);
|
|
223
|
+
const grouped = groupBy !== null;
|
|
224
|
+
for (const group of groups) content.push(this._createGroup(group, grouped));
|
|
225
|
+
} else if (!this._loading) {
|
|
226
|
+
content.push(h('div', {
|
|
227
|
+
class: 'zx-activity-list__empty', role: 'status'
|
|
228
|
+
}, listContent(this.options.emptyText, 'ActivityList emptyText')));
|
|
229
|
+
}
|
|
230
|
+
if (this._loading) content.push(this._createLoading(this._items.length === 0));
|
|
231
|
+
root.replaceChildren(...content);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
this._destroyItems();
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** @private @param {ActivityGroup} group @param {boolean} grouped @returns {HTMLElement} */
|
|
239
|
+
_createGroup(group, grouped) {
|
|
240
|
+
const list = h('ol', { class: 'zx-activity-list__items' });
|
|
241
|
+
for (const item of group.items) {
|
|
242
|
+
const component = new ActivityItem(null, {
|
|
243
|
+
...item,
|
|
244
|
+
avatar: this._variant === 'compact' ? compactAvatar(item.avatar) : item.avatar,
|
|
245
|
+
headingLevel: item.headingLevel ?? this.options.itemHeadingLevel
|
|
246
|
+
});
|
|
247
|
+
this._itemComponents.push(component);
|
|
248
|
+
list.append(component.toElement());
|
|
249
|
+
}
|
|
250
|
+
if (!grouped) return list;
|
|
251
|
+
this._groupSequence += 1;
|
|
252
|
+
const headingId = uid(`zx-activity-list-group-${this._groupSequence}`);
|
|
253
|
+
const label = this.options.groupLabel
|
|
254
|
+
? this.options.groupLabel(group.id, group.items.map(cloneActivityDescriptor))
|
|
255
|
+
: String(group.id ?? 'Other');
|
|
256
|
+
list.setAttribute('aria-labelledby', headingId);
|
|
257
|
+
return h('section', { class: 'zx-activity-list__group', ariaLabelledby: headingId },
|
|
258
|
+
h(`h${normalizeHeadingLevel(this.options.groupHeadingLevel, 2)}`, {
|
|
259
|
+
class: 'zx-activity-list__group-heading', id: headingId
|
|
260
|
+
}, listContent(label, 'ActivityList groupLabel')), list);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** @private @param {boolean} placeholders @returns {HTMLElement} */
|
|
264
|
+
_createLoading(placeholders) {
|
|
265
|
+
const region = h('div', {
|
|
266
|
+
class: 'zx-activity-list__loading', role: 'status', ariaLive: 'polite'
|
|
267
|
+
}, h('span', { class: 'zx-activity-list__loading-label' }, String(this.options.loadingText)));
|
|
268
|
+
if (!placeholders) return region;
|
|
269
|
+
const rows = h('div', { class: 'zx-activity-list__skeletons', ariaHidden: 'true' });
|
|
270
|
+
const count = normalizeLoadingCount(this.options.loadingCount);
|
|
271
|
+
for (let index = 0; index < count; index += 1) {
|
|
272
|
+
rows.append(h('div', { class: 'zx-activity-list__skeleton' },
|
|
273
|
+
skeleton({ width: '2.25rem', height: '2.25rem', radius: 'full' }),
|
|
274
|
+
skeletonText({ lines: 2, heading: true, lastLineWidth: index % 2 ? '58%' : '72%' })));
|
|
275
|
+
}
|
|
276
|
+
region.append(rows);
|
|
277
|
+
return region;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** @private @returns {void} */
|
|
281
|
+
_destroyItems() {
|
|
282
|
+
for (const item of this._itemComponents) item.destroy();
|
|
283
|
+
this._itemComponents.length = 0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** @private @returns {void} */
|
|
287
|
+
_emitDataChange() {
|
|
288
|
+
this.emit('datachange', { items: this.getItems() });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Groups descriptors in first-seen group order while preserving source order within every group.
|
|
294
|
+
* A null reader returns one ungrouped collection. Neither descriptors nor the input array mutate.
|
|
295
|
+
* @param {ActivityItemOptions[]} items Activity descriptors.
|
|
296
|
+
* @param {ActivityGroupReader} groupBy Group reader.
|
|
297
|
+
* @returns {ActivityGroup[]}
|
|
298
|
+
*/
|
|
299
|
+
export function groupActivityItems(items, groupBy = null) {
|
|
300
|
+
const source = normalizeActivityItems(items);
|
|
301
|
+
if (groupBy === null) return source.length ? [{ id: null, items: source }] : [];
|
|
302
|
+
if (typeof groupBy !== 'string' && typeof groupBy !== 'function') {
|
|
303
|
+
throw new TypeError('ActivityList groupBy must be a property, function, or null');
|
|
304
|
+
}
|
|
305
|
+
const groups = new Map();
|
|
306
|
+
source.forEach((item, index) => {
|
|
307
|
+
const id = typeof groupBy === 'function' ? groupBy(item, index) : item[groupBy];
|
|
308
|
+
if (!groups.has(id)) groups.set(id, []);
|
|
309
|
+
groups.get(id).push(item);
|
|
310
|
+
});
|
|
311
|
+
return Array.from(groups, ([id, grouped]) => ({ id, items: grouped }));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Validates and shallow-clones activity descriptors, including each slot array. Explicit
|
|
316
|
+
* duplicate IDs are rejected while id-less entries remain valid.
|
|
317
|
+
* @param {unknown} value Candidate descriptor array.
|
|
318
|
+
* @returns {ActivityItemOptions[]}
|
|
319
|
+
*/
|
|
320
|
+
export function normalizeActivityItems(value) {
|
|
321
|
+
if (!Array.isArray(value)) throw new TypeError('ActivityList items must be an array');
|
|
322
|
+
const ids = new Set();
|
|
323
|
+
return value.map((item) => {
|
|
324
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
325
|
+
throw new TypeError('ActivityList items must be descriptor objects');
|
|
326
|
+
}
|
|
327
|
+
for (const key of ['metadata', 'attachments', 'actions']) {
|
|
328
|
+
if (item[key] !== undefined && !Array.isArray(item[key])) {
|
|
329
|
+
throw new TypeError(`ActivityList item ${key} must be an array`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const descriptor = /** @type {ActivityItemOptions} */ (item);
|
|
333
|
+
if (descriptor.id !== null && descriptor.id !== undefined) {
|
|
334
|
+
if (ids.has(descriptor.id)) throw new RangeError(`Duplicate ActivityList id: ${String(descriptor.id)}`);
|
|
335
|
+
ids.add(descriptor.id);
|
|
336
|
+
}
|
|
337
|
+
return cloneActivityDescriptor(descriptor);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** @param {ActivityItemOptions} item @returns {ActivityItemOptions} */
|
|
342
|
+
function cloneActivityDescriptor(item) {
|
|
343
|
+
const avatar = item.avatar && isPlainObject(item.avatar) ? { ...item.avatar } : item.avatar;
|
|
344
|
+
return {
|
|
345
|
+
...item,
|
|
346
|
+
avatar,
|
|
347
|
+
metadata: [...(item.metadata ?? [])],
|
|
348
|
+
attachments: [...(item.attachments ?? [])],
|
|
349
|
+
actions: [...(item.actions ?? [])]
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** @param {ActivityListContent} value @param {string} label @returns {Node|string} */
|
|
354
|
+
function listContent(value, label) {
|
|
355
|
+
if (value === null) return '';
|
|
356
|
+
if (typeof value === 'string' || typeof value === 'number') return String(value);
|
|
357
|
+
if (value && typeof value === 'object'
|
|
358
|
+
&& typeof (/** @type {{nodeType?:unknown}} */ (value)).nodeType === 'number') {
|
|
359
|
+
return /** @type {Node} */ (value);
|
|
360
|
+
}
|
|
361
|
+
if (value instanceof Component) return value.toElement();
|
|
362
|
+
throw new TypeError(`${label} must be text, a Node, a Component, or null`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** @param {unknown} value @returns {boolean} */
|
|
366
|
+
function isPlainObject(value) {
|
|
367
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
368
|
+
return typeof (/** @type {{nodeType?:unknown}} */ (value)).nodeType !== 'number'
|
|
369
|
+
&& !(value instanceof Component);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** @param {unknown} value @param {1|2|3|4|5|6} fallback @returns {1|2|3|4|5|6} */
|
|
373
|
+
function normalizeHeadingLevel(value, fallback) {
|
|
374
|
+
const level = Math.trunc(Number(value));
|
|
375
|
+
return /** @type {1|2|3|4|5|6} */ (level >= 1 && level <= 6 ? level : fallback);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Resolves the entry layout; anything but `'compact'` is the default layout.
|
|
380
|
+
* @param {unknown} value Requested variant.
|
|
381
|
+
* @returns {ActivityListVariant}
|
|
382
|
+
*/
|
|
383
|
+
export function normalizeActivityListVariant(value) {
|
|
384
|
+
return value === 'compact' ? 'compact' : 'default';
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* A compact entry is one line tall, so an avatar descriptor that leaves its size open gets the
|
|
389
|
+
* small one. An explicit size, an Element, or no avatar passes through untouched.
|
|
390
|
+
* @param {ActivityItemOptions['avatar']} avatar Avatar descriptor, Element, or null.
|
|
391
|
+
* @returns {ActivityItemOptions['avatar']}
|
|
392
|
+
*/
|
|
393
|
+
function compactAvatar(avatar) {
|
|
394
|
+
if (!isPlainObject(avatar)) return avatar;
|
|
395
|
+
const descriptor = /** @type {import('../avatar/avatar.js').AvatarOptions} */ (avatar);
|
|
396
|
+
return descriptor.size == null ? { ...descriptor, size: 'sm' } : descriptor;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** @param {unknown} value @returns {number} */
|
|
400
|
+
function normalizeLoadingCount(value) {
|
|
401
|
+
const count = Math.trunc(Number(value));
|
|
402
|
+
return Number.isFinite(count) ? Math.max(1, Math.min(12, count)) : 3;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Activity descriptors changed through a list mutation method.
|
|
407
|
+
* @event ActivityList#datachange
|
|
408
|
+
* @type {CustomEvent<{items:ActivityItemOptions[]}>}
|
|
409
|
+
*/
|
|
@@ -48,17 +48,22 @@
|
|
|
48
48
|
text-decoration: none;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/* Hover, an open flyout, and a branch over the active route are one light step; the current page
|
|
52
|
+
is the neutral current fill. Neither is the accent: where you are is not something you picked. */
|
|
51
53
|
.zx-app-rail__item:hover,
|
|
52
54
|
.zx-app-rail__item:focus-visible,
|
|
53
55
|
.zx-app-rail__item[aria-expanded="true"],
|
|
54
|
-
.zx-app-rail__item[data-active-descendant="true"]
|
|
55
|
-
.zx-app-rail__item[aria-current="page"] {
|
|
56
|
+
.zx-app-rail__item[data-active-descendant="true"] {
|
|
56
57
|
background: var(--zx-color-bg-hover);
|
|
57
|
-
color: var(--zx-color-
|
|
58
|
+
color: var(--zx-color-text);
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
.zx-app-rail__item[aria-
|
|
61
|
-
|
|
61
|
+
.zx-app-rail__item[aria-current="page"] {
|
|
62
|
+
background: var(--zx-color-bg-current);
|
|
63
|
+
color: var(--zx-color-text);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.zx-app-rail__item[aria-disabled="true"] {
|
|
62
67
|
color: var(--zx-color-text-muted);
|
|
63
68
|
cursor: not-allowed;
|
|
64
69
|
opacity: .65;
|
|
@@ -81,31 +86,195 @@
|
|
|
81
86
|
}
|
|
82
87
|
.zx-app-rail__item-disclosure { position: absolute; inset-inline-end: 1px; inset-block-end: 1px; display: inline-flex; }
|
|
83
88
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
/*
|
|
90
|
+
* The flyout.
|
|
91
|
+
*
|
|
92
|
+
* A minimized sidebar is the same navigation as an expanded one, so the panel a rail item opens
|
|
93
|
+
* has to read as a slice of the tree rather than as a menu that happens to hang off it: the same
|
|
94
|
+
* icon gutter, the same item height, labels that wrap instead of truncating, and the same active
|
|
95
|
+
* recipe — the neutral current fill and a heavier label.
|
|
96
|
+
*
|
|
97
|
+
* The panel is portaled to the overlay host, so `.zx-app-sidebar` descendant selectors and the
|
|
98
|
+
* `--zx-app-sidebar-*` scale declared on that root cannot reach it. The scale is re-declared here
|
|
99
|
+
* against the same tier-2 control and spacing tokens the sidebar derives it from, so the two
|
|
100
|
+
* surfaces move together when a token moves rather than agreeing on a number frozen twice.
|
|
101
|
+
*/
|
|
102
|
+
.zx-app-rail__popover {
|
|
103
|
+
--zx-app-rail-gutter: var(--zx-space-2);
|
|
104
|
+
--zx-app-rail-icon: var(--zx-space-6);
|
|
105
|
+
--zx-app-rail-item-gap: var(--zx-space-2);
|
|
106
|
+
--zx-app-rail-item-height: var(--zx-control-height);
|
|
107
|
+
--zx-app-rail-pad-x: var(--zx-space-2);
|
|
108
|
+
--zx-app-rail-row-gap: 2px;
|
|
109
|
+
--zx-app-rail-radius: var(--zx-radius-md);
|
|
110
|
+
--zx-app-rail-hover: var(--zx-color-bg-hover);
|
|
111
|
+
--zx-app-rail-current: var(--zx-color-bg-current);
|
|
112
|
+
--zx-app-rail-press: color-mix(in srgb, var(--zx-color-text) 12%, transparent);
|
|
113
|
+
min-inline-size: 240px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* `styles/zx.css` imports this file before `dropdown.css`, so the two properties the flyout has
|
|
117
|
+
to take back from the generic overlay surface — the padding, which is the tree's own gutter,
|
|
118
|
+
and the width the panel may grow to, which is the tree's own 280px — have to
|
|
119
|
+
out-specify it instead of merely following it. The viewport clamp the dropdown guarantees is
|
|
120
|
+
carried over rather than dropped. */
|
|
121
|
+
.zx-dropdown.zx-app-rail__popover {
|
|
122
|
+
max-inline-size: min(280px, calc(100vw - (2 * var(--zx-space-3))));
|
|
123
|
+
padding: var(--zx-space-1) var(--zx-app-rail-gutter);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* The branch's own label, naming what the panel is a slice of. It aligns with the row padding
|
|
127
|
+
rather than with the labels under it: a caption for the list, not a row in it. */
|
|
128
|
+
.zx-app-rail__flyout-title {
|
|
129
|
+
padding: var(--zx-space-1) var(--zx-app-rail-pad-x);
|
|
130
|
+
color: var(--zx-color-text-muted);
|
|
131
|
+
font-size: var(--zx-text-xs);
|
|
132
|
+
font-weight: var(--zx-weight-semibold);
|
|
133
|
+
line-height: 1.3;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.zx-app-rail__flyout-list {
|
|
137
|
+
display: grid;
|
|
138
|
+
gap: var(--zx-app-rail-row-gap);
|
|
139
|
+
margin: 0;
|
|
140
|
+
padding: 0;
|
|
141
|
+
list-style: none;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Same bargain the tree makes: a list with no icons at all does not pay for the gutter. */
|
|
145
|
+
.zx-app-rail__flyout-list:not(:has(.zx-app-rail__flyout-item-icon)) {
|
|
146
|
+
--zx-app-rail-icon: 0px;
|
|
147
|
+
--zx-app-rail-item-gap: 0px;
|
|
148
|
+
}
|
|
149
|
+
|
|
87
150
|
.zx-app-rail__flyout-item {
|
|
151
|
+
position: relative;
|
|
88
152
|
display: grid;
|
|
89
|
-
grid-template-columns:
|
|
153
|
+
grid-template-columns: calc(var(--zx-app-rail-icon) + var(--zx-app-rail-item-gap)) minmax(0, 1fr) auto auto;
|
|
90
154
|
align-items: center;
|
|
91
|
-
gap:
|
|
155
|
+
column-gap: 0;
|
|
92
156
|
inline-size: 100%;
|
|
93
|
-
min-block-size: var(--zx-
|
|
94
|
-
border
|
|
95
|
-
border-radius: var(--zx-radius
|
|
157
|
+
min-block-size: var(--zx-app-rail-item-height);
|
|
158
|
+
border: 0;
|
|
159
|
+
border-radius: var(--zx-app-rail-radius);
|
|
96
160
|
background: transparent;
|
|
97
|
-
|
|
98
|
-
|
|
161
|
+
/* The icon column fills the row exactly; a row without one keeps a space-1 margin round its
|
|
162
|
+
label. Either way a single-line row is one control tall, and a wrapped one grows. */
|
|
163
|
+
padding-block: min(var(--zx-space-1), (var(--zx-app-rail-item-height) - var(--zx-app-rail-icon)) / 2);
|
|
164
|
+
padding-inline: var(--zx-app-rail-pad-x);
|
|
165
|
+
color: var(--zx-color-text);
|
|
166
|
+
font-size: var(--zx-text-md);
|
|
167
|
+
font-weight: 500;
|
|
168
|
+
line-height: 1.35;
|
|
99
169
|
text-align: start;
|
|
100
170
|
text-decoration: none;
|
|
101
171
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
.zx-app-rail__flyout-item
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
.zx-app-rail__flyout-item-
|
|
108
|
-
|
|
172
|
+
|
|
173
|
+
@media (pointer: coarse) {
|
|
174
|
+
.zx-app-rail__flyout-item { min-block-size: 40px; }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.zx-app-rail__flyout-item-icon {
|
|
178
|
+
grid-column: 1;
|
|
179
|
+
display: flex;
|
|
180
|
+
align-items: center;
|
|
181
|
+
justify-content: center;
|
|
182
|
+
inline-size: var(--zx-app-rail-icon);
|
|
183
|
+
color: var(--zx-color-text-muted);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.zx-app-rail__flyout-item-label {
|
|
187
|
+
grid-column: 2;
|
|
188
|
+
min-inline-size: 0;
|
|
189
|
+
hyphens: auto;
|
|
190
|
+
overflow-wrap: break-word;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.zx-app-rail__flyout-item-badge {
|
|
194
|
+
grid-column: 3;
|
|
195
|
+
margin-inline-start: var(--zx-space-2);
|
|
196
|
+
color: var(--zx-color-text-muted);
|
|
197
|
+
font-size: var(--zx-text-xs);
|
|
198
|
+
font-variant-numeric: tabular-nums;
|
|
199
|
+
line-height: 1;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.zx-app-rail__flyout-item-disclosure {
|
|
203
|
+
grid-column: 4;
|
|
204
|
+
display: inline-flex;
|
|
205
|
+
margin-inline-start: var(--zx-space-1);
|
|
206
|
+
color: var(--zx-color-text-muted);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.zx-app-rail__flyout-item:hover { background: var(--zx-app-rail-hover); }
|
|
210
|
+
.zx-app-rail__flyout-item:hover .zx-app-rail__flyout-item-icon,
|
|
211
|
+
.zx-app-rail__flyout-item:hover .zx-app-rail__flyout-item-disclosure { color: var(--zx-color-text); }
|
|
212
|
+
.zx-app-rail__flyout-item:active { background: var(--zx-app-rail-press); }
|
|
213
|
+
|
|
214
|
+
/* An open nested branch holds the hover fill: the chain that leads to the panel in front stays
|
|
215
|
+
readable while the pointer is two panels away. The rail item does the same thing. */
|
|
216
|
+
.zx-app-rail__flyout-item[aria-expanded="true"]:not([aria-current="page"]) {
|
|
217
|
+
background: var(--zx-app-rail-hover);
|
|
218
|
+
}
|
|
219
|
+
.zx-app-rail__flyout-item[aria-expanded="true"]:not([aria-current="page"]) .zx-app-rail__flyout-item-icon,
|
|
220
|
+
.zx-app-rail__flyout-item[aria-expanded="true"]:not([aria-current="page"]) .zx-app-rail__flyout-item-disclosure {
|
|
221
|
+
color: var(--zx-color-text);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.zx-app-rail__flyout-item[data-active-descendant="true"][aria-expanded="false"] {
|
|
225
|
+
font-weight: var(--zx-weight-semibold);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.zx-app-rail__flyout-item[data-active-descendant="true"][aria-expanded="false"] .zx-app-rail__flyout-item-icon {
|
|
229
|
+
color: var(--zx-color-text);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* The current page, as in the tree: the neutral current fill, the full text colour and a heavier
|
|
233
|
+
label, with the shared focus ring drawn on top when a keyboard user arrives on it. */
|
|
234
|
+
.zx-app-rail__flyout-item[aria-current="page"] {
|
|
235
|
+
background: var(--zx-app-rail-current);
|
|
236
|
+
color: var(--zx-color-text);
|
|
237
|
+
font-weight: var(--zx-weight-semibold);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.zx-app-rail__flyout-item[aria-current="page"]:active {
|
|
241
|
+
background: var(--zx-app-rail-press);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.zx-app-rail__flyout-item[aria-current="page"] .zx-app-rail__flyout-item-icon,
|
|
245
|
+
.zx-app-rail__flyout-item[aria-current="page"] .zx-app-rail__flyout-item-disclosure {
|
|
246
|
+
color: var(--zx-color-text);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* Unavailable, and still readable: the icon carries the dimming so the label can keep the muted
|
|
250
|
+
text role instead of falling under 3:1 with the rest of the row. */
|
|
251
|
+
.zx-app-rail__flyout-item[aria-disabled="true"] {
|
|
252
|
+
color: var(--zx-color-text-muted);
|
|
253
|
+
cursor: not-allowed;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.zx-app-rail__flyout-item[aria-disabled="true"] .zx-app-rail__flyout-item-icon { opacity: .55; }
|
|
257
|
+
|
|
258
|
+
.zx-app-rail__flyout-item[aria-disabled="true"]:hover,
|
|
259
|
+
.zx-app-rail__flyout-item[aria-disabled="true"]:active { background: transparent; }
|
|
260
|
+
|
|
261
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
262
|
+
.zx-app-rail__flyout-item {
|
|
263
|
+
transition: background-color var(--zx-dur-fast) var(--zx-ease),
|
|
264
|
+
color var(--zx-dur-fast) var(--zx-ease),
|
|
265
|
+
box-shadow var(--zx-dur-fast) var(--zx-ease);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.zx-app-rail__flyout-item:active { transform: scale(.985); }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* Forced colours drop the fill; the system's selected pair carries the state there instead. */
|
|
272
|
+
@media (forced-colors: active) {
|
|
273
|
+
.zx-app-rail__flyout-item[aria-current="page"] {
|
|
274
|
+
background: Highlight;
|
|
275
|
+
color: HighlightText;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
109
278
|
|
|
110
279
|
@media (prefers-reduced-transparency: reduce) {
|
|
111
280
|
.zx-app-rail {
|