@zeyos/zx 4.4.0 → 5.2.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 +447 -0
- package/README.md +44 -11
- package/dist/types/components/account-menu/account-menu.d.ts +8 -0
- package/dist/types/components/activity-list/activity-list.d.ts +31 -1
- package/dist/types/components/app-icon/app-icon.d.ts +14 -2
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +293 -17
- 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 +15 -0
- package/dist/types/components/calendar/calendar.d.ts +67 -5
- package/dist/types/components/card-view/card-view.d.ts +7 -4
- package/dist/types/components/card-view/record-card.d.ts +11 -0
- 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 +6 -17
- package/dist/types/components/context-menu/context-menu.d.ts +6 -2
- package/dist/types/components/data-filter/data-filter.d.ts +4 -4
- 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 +46 -6
- package/dist/types/components/dropdown/dropdown.d.ts +24 -9
- package/dist/types/components/entity-ref/entity-ref.d.ts +2 -2
- package/dist/types/components/field/field.d.ts +54 -2
- package/dist/types/components/fieldset/external-adapter.d.ts +83 -0
- package/dist/types/components/fieldset/fieldset.d.ts +26 -1
- package/dist/types/components/file-item/file-item.d.ts +34 -6
- package/dist/types/components/file-list/file-list.d.ts +35 -3
- package/dist/types/components/filter/filter.d.ts +127 -3
- package/dist/types/components/filter-panel/filter-panel.d.ts +36 -7
- package/dist/types/components/form/form.d.ts +12 -1
- package/dist/types/components/kanban-view/kanban-card.d.ts +8 -0
- package/dist/types/components/launcher/launcher.d.ts +268 -15
- package/dist/types/components/list-toolbar/list-toolbar.d.ts +16 -7
- package/dist/types/components/menu-button/menu-button.d.ts +39 -5
- package/dist/types/components/message/message.d.ts +132 -17
- package/dist/types/components/modal/modal.d.ts +43 -2
- package/dist/types/components/module-bar/module-bar.d.ts +395 -0
- package/dist/types/components/pagination/pagination.d.ts +17 -3
- 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 +17 -3
- package/dist/types/components/sheet/sheet.d.ts +15 -0
- package/dist/types/components/stat-tile/stat-tile.d.ts +16 -5
- package/dist/types/components/tabbox/tabbox.d.ts +9 -0
- package/dist/types/components/table/table.d.ts +311 -18
- package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
- package/dist/types/components/tree/tree.d.ts +13 -0
- package/dist/types/components/view/record-view.d.ts +59 -2
- package/dist/types/components/view-settings/view-settings.d.ts +879 -0
- package/dist/types/components/workspace-switcher/workspace-switcher.d.ts +327 -0
- package/dist/types/core/breakpoint.d.ts +1 -1
- package/dist/types/core/component.d.ts +12 -2
- package/dist/types/core/i18n.d.ts +27 -3
- package/dist/types/core/icons.d.ts +23 -25
- package/dist/types/core/keyboard.d.ts +98 -1
- package/dist/types/core/messages.d.ts +382 -0
- package/dist/types/core/motion.d.ts +59 -0
- package/dist/types/core/overlay-host.d.ts +42 -1
- package/dist/types/core/overlay-stack.d.ts +260 -0
- package/dist/types/index.d.ts +10 -5
- package/dist/types/internal/app-rail.d.ts +38 -0
- package/dist/types/internal/menu-items.d.ts +71 -3
- 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 +2 -1
- package/dist/types/zeyos/launcher.d.ts +9 -0
- package/dist/types/zeyos/module-bar.d.ts +176 -0
- package/dist/types/zeyos/query.d.ts +110 -5
- package/dist/types/zeyos/schema.d.ts +24 -1
- package/dist/types/zeyos/table.d.ts +345 -22
- package/dist/types/zeyos/view.d.ts +26 -2
- package/dist/zx-zeyos.esm.js +6178 -1008
- package/dist/zx-zeyos.esm.min.js +53 -5
- package/dist/zx.css +2861 -827
- package/dist/zx.esm.js +8909 -1480
- package/dist/zx.esm.min.js +55 -7
- package/dist/zx.global.js +8911 -1482
- package/dist/zx.global.min.js +55 -7
- package/dist/zx.min.css +1 -1
- package/docs/api.json +1079 -117
- package/docs/llms.md +956 -154
- package/docs/llms.txt +9 -5
- package/package.json +1 -1
- package/src/components/account-menu/account-menu.css +35 -10
- package/src/components/account-menu/account-menu.js +12 -0
- package/src/components/activity-item/activity-item.css +1 -1
- package/src/components/activity-item/activity-item.js +3 -0
- package/src/components/activity-list/activity-list.css +38 -3
- package/src/components/activity-list/activity-list.js +61 -1
- package/src/components/app-icon/app-icon.css +14 -3
- package/src/components/app-icon/app-icon.js +11 -1
- package/src/components/app-rail/app-rail.css +59 -79
- package/src/components/app-sidebar/app-sidebar.css +304 -168
- package/src/components/app-sidebar/app-sidebar.js +667 -46
- 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/breadcrumb/breadcrumb.css +1 -1
- package/src/components/button/button.css +67 -9
- package/src/components/button/button.js +38 -0
- package/src/components/calendar/calendar.css +47 -47
- package/src/components/calendar/calendar.js +26 -52
- package/src/components/card/card.css +4 -4
- package/src/components/card-view/card-view.css +14 -12
- package/src/components/card-view/card-view.js +13 -9
- package/src/components/card-view/record-card.js +14 -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 +7 -6
- package/src/components/code-editor/code-editor.js +19 -1
- package/src/components/code-editor/field-adapter.js +9 -2
- package/src/components/context-menu/context-menu.css +9 -4
- package/src/components/context-menu/context-menu.js +8 -2
- package/src/components/copy/copy-button.css +6 -0
- package/src/components/copy/copy-input.css +9 -4
- package/src/components/data-filter/data-filter.css +5 -4
- package/src/components/data-filter/data-filter.js +2 -2
- package/src/components/date-picker/date-picker.css +6 -6
- package/src/components/date-picker/date-range-picker.css +3 -3
- package/src/components/date-picker/time-picker.css +3 -3
- 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 +99 -17
- package/src/components/dock/dock.css +5 -4
- package/src/components/dropdown/dropdown.css +1 -1
- package/src/components/dropdown/dropdown.js +159 -72
- package/src/components/empty-state/empty-state.css +2 -2
- package/src/components/entity-ref/entity-ref.css +1 -1
- package/src/components/entity-ref/entity-ref.js +1 -1
- package/src/components/field/field.css +28 -9
- package/src/components/field/field.js +101 -29
- package/src/components/field-upload/field-upload.css +1 -1
- package/src/components/fieldset/external-adapter.js +363 -0
- package/src/components/fieldset/fieldset.css +5 -3
- package/src/components/fieldset/fieldset.js +38 -64
- package/src/components/file-item/file-item.js +144 -10
- package/src/components/file-list/file-list.js +144 -21
- package/src/components/filter/filter.css +86 -2
- package/src/components/filter/filter.js +307 -11
- package/src/components/filter-panel/filter-panel.css +1 -1
- package/src/components/filter-panel/filter-panel.js +149 -32
- package/src/components/finder/finder.css +16 -14
- 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 +11 -3
- package/src/components/kanban-view/kanban-view.css +42 -18
- package/src/components/kanban-view/kanban-view.js +9 -6
- package/src/components/launcher/launcher.css +108 -26
- package/src/components/launcher/launcher.js +530 -42
- package/src/components/list-toolbar/list-toolbar.css +13 -8
- package/src/components/list-toolbar/list-toolbar.js +40 -14
- package/src/components/master-panel/master-panel.css +2 -0
- package/src/components/master-panel/master-panel.js +0 -1
- package/src/components/menu-button/menu-button.css +72 -4
- package/src/components/menu-button/menu-button.js +160 -18
- package/src/components/message/message.css +33 -3
- package/src/components/message/message.js +264 -22
- package/src/components/modal/modal.css +1 -1
- package/src/components/modal/modal.js +83 -15
- package/src/components/module-bar/module-bar.css +486 -0
- package/src/components/module-bar/module-bar.js +1074 -0
- package/src/components/multi-value-editor/multi-value-editor.css +1 -1
- package/src/components/navigation-bar/navigation-bar.css +18 -9
- package/src/components/number-field/field-adapter.js +3 -1
- 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 +13 -11
- package/src/components/pagination/pagination.js +20 -4
- package/src/components/panel/panel.css +4 -3
- package/src/components/questionnaire/questionnaire.css +4 -4
- package/src/components/rating/field-adapter.js +2 -1
- package/src/components/rating/rating.css +8 -1
- package/src/components/search/search.css +4 -1
- package/src/components/select/color.js +22 -0
- package/src/components/select/field-adapter.js +2 -0
- package/src/components/select/glyphs.js +165 -0
- package/src/components/select/select.css +138 -45
- package/src/components/select/select.js +92 -47
- package/src/components/sheet/sheet.css +12 -9
- package/src/components/sheet/sheet.js +74 -32
- package/src/components/slider/field-adapter.js +6 -1
- package/src/components/slider/slider.css +18 -4
- package/src/components/slider/slider.js +1 -1
- package/src/components/sort-control/sort-control.css +4 -1
- package/src/components/split-view/split-view.css +1 -1
- package/src/components/stat-tile/stat-tile.css +42 -47
- package/src/components/stat-tile/stat-tile.js +17 -6
- package/src/components/stepper/stepper.css +1 -1
- package/src/components/tabbox/tabbox.css +41 -32
- package/src/components/tabbox/tabbox.js +5 -1
- package/src/components/table/table.css +187 -37
- package/src/components/table/table.js +718 -73
- package/src/components/tag-picker/field-adapter.js +2 -1
- package/src/components/tag-picker/tag-picker.css +83 -5
- package/src/components/tag-picker/tag-picker.js +25 -8
- package/src/components/timebox/timebox.css +22 -2
- 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 +44 -19
- package/src/components/tree/tree.css +26 -17
- package/src/components/tree/tree.js +31 -0
- package/src/components/value-list/value-list.css +3 -3
- package/src/components/view/record-view.css +1 -1
- package/src/components/view/record-view.js +178 -18
- package/src/components/view-settings/view-settings.css +285 -0
- package/src/components/view-settings/view-settings.js +1830 -0
- package/src/components/workspace-switcher/workspace-switcher.css +188 -0
- package/src/components/workspace-switcher/workspace-switcher.js +463 -0
- package/src/core/breakpoint.js +1 -1
- package/src/core/component.js +88 -17
- package/src/core/fontawesome.js +5 -1
- package/src/core/i18n.js +47 -4
- package/src/core/icons.js +204 -61
- package/src/core/keyboard.js +206 -11
- package/src/core/messages.js +427 -0
- package/src/core/motion.js +147 -0
- package/src/core/overlay-host.js +77 -3
- package/src/core/overlay-stack.js +487 -0
- package/src/core/position.js +22 -3
- package/src/index.js +10 -5
- package/src/internal/app-rail.js +135 -55
- package/src/internal/menu-items.js +61 -3
- package/src/zeyos/connect.js +6 -2
- package/src/zeyos/form.js +69 -27
- package/src/zeyos/index.js +4 -1
- package/src/zeyos/launcher.js +8 -3
- package/src/zeyos/module-bar.js +181 -0
- package/src/zeyos/query.js +197 -13
- package/src/zeyos/schema.js +144 -27
- package/src/zeyos/table.js +287 -84
- package/src/zeyos/view.js +49 -93
- package/styles/base.css +72 -1
- package/styles/icons.css +11 -1
- package/styles/print.css +1 -1
- package/styles/tokens/dark.css +117 -37
- package/styles/tokens/global.css +121 -27
- package/styles/tokens/semantic.css +140 -43
- package/styles/tokens/themes.css +7 -6
- package/styles/zx.css +3 -1
- package/styles/tokens/density.css +0 -9
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WorkspaceSwitcher — the current workspace at the head of a navigation column.
|
|
3
|
+
*
|
|
4
|
+
* The tile is the module bar's tile: one control less a space-1 inset, so a switcher above a
|
|
5
|
+
* module strip lines its tile up with the module tiles under it, at every control height.
|
|
6
|
+
*/
|
|
7
|
+
.zx-workspace-switcher {
|
|
8
|
+
--zx-workspace-switcher-tile: calc(var(--zx-control-height) - var(--zx-space-1));
|
|
9
|
+
display: block;
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
/* Full width of whatever holds it, so it fills a sidebar header even inside a flex row. */
|
|
12
|
+
inline-size: 100%;
|
|
13
|
+
min-inline-size: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* The trigger is a flat row, not a button. MenuButton makes it a `.zx-btn.zx-menu-button`, and
|
|
18
|
+
* both of those stylesheets load after this one, so every rule here is scoped under the root to
|
|
19
|
+
* out-specify them, and takes back each property they set: the border, the glass fill, the blur,
|
|
20
|
+
* the shadow, the centred inline-flex pill.
|
|
21
|
+
*/
|
|
22
|
+
.zx-workspace-switcher .zx-workspace-switcher__trigger {
|
|
23
|
+
display: grid;
|
|
24
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
25
|
+
align-items: center;
|
|
26
|
+
justify-content: stretch;
|
|
27
|
+
gap: var(--zx-space-2);
|
|
28
|
+
box-sizing: border-box;
|
|
29
|
+
inline-size: 100%;
|
|
30
|
+
min-block-size: calc(var(--zx-control-height) + var(--zx-space-2));
|
|
31
|
+
border: 0;
|
|
32
|
+
border-radius: var(--zx-radius-md);
|
|
33
|
+
background: transparent;
|
|
34
|
+
-webkit-backdrop-filter: none;
|
|
35
|
+
backdrop-filter: none;
|
|
36
|
+
box-shadow: none;
|
|
37
|
+
/* A space-1 start inset puts the tile where a module-bar row puts its tile, list inset and row
|
|
38
|
+
padding together, so the switcher heads a module strip in line with the tiles under it. */
|
|
39
|
+
padding-block: var(--zx-space-1);
|
|
40
|
+
padding-inline: var(--zx-space-1) var(--zx-space-2);
|
|
41
|
+
color: var(--zx-color-text);
|
|
42
|
+
font-weight: var(--zx-weight-medium);
|
|
43
|
+
line-height: 1.3;
|
|
44
|
+
text-align: start;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.zx-workspace-switcher .zx-workspace-switcher__trigger:is(:hover, [aria-expanded="true"]):not(:disabled) {
|
|
48
|
+
background: var(--zx-color-bg-hover);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.zx-workspace-switcher .zx-workspace-switcher__trigger:active:not(:disabled) {
|
|
52
|
+
background: var(--zx-color-bg-current);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.zx-workspace-switcher .zx-workspace-switcher__trigger:focus-visible {
|
|
56
|
+
outline-color: transparent;
|
|
57
|
+
box-shadow: var(--zx-focus-ring);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.zx-workspace-switcher__tile {
|
|
61
|
+
display: inline-grid;
|
|
62
|
+
place-items: center;
|
|
63
|
+
inline-size: var(--zx-workspace-switcher-tile);
|
|
64
|
+
block-size: var(--zx-workspace-switcher-tile);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.zx-workspace-switcher__tile[hidden] { display: none; }
|
|
68
|
+
|
|
69
|
+
.zx-workspace-switcher__text {
|
|
70
|
+
display: grid;
|
|
71
|
+
min-inline-size: 0;
|
|
72
|
+
gap: 1px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.zx-workspace-switcher__name,
|
|
76
|
+
.zx-workspace-switcher__secondary {
|
|
77
|
+
overflow: hidden;
|
|
78
|
+
text-overflow: ellipsis;
|
|
79
|
+
white-space: nowrap;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.zx-workspace-switcher__name {
|
|
83
|
+
font-size: var(--zx-text-md);
|
|
84
|
+
font-weight: var(--zx-weight-semibold);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.zx-workspace-switcher__secondary {
|
|
88
|
+
color: var(--zx-color-text-muted);
|
|
89
|
+
font-size: var(--zx-text-xs);
|
|
90
|
+
font-weight: 400;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.zx-workspace-switcher__secondary[hidden] { display: none; }
|
|
94
|
+
|
|
95
|
+
.zx-workspace-switcher__chevron {
|
|
96
|
+
display: inline-flex;
|
|
97
|
+
color: var(--zx-color-text-subtle);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
101
|
+
.zx-workspace-switcher .zx-workspace-switcher__trigger {
|
|
102
|
+
transition: background-color var(--zx-dur-fast) var(--zx-ease);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.zx-workspace-switcher__chevron {
|
|
106
|
+
transition: transform var(--zx-dur-fast) var(--zx-ease);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.zx-workspace-switcher__trigger[aria-expanded="true"] .zx-workspace-switcher__chevron {
|
|
111
|
+
transform: rotate(180deg);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Compact: the tile alone, in a square target the size of a module-bar target. */
|
|
115
|
+
.zx-workspace-switcher[data-compact="true"] {
|
|
116
|
+
inline-size: fit-content;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.zx-workspace-switcher[data-compact="true"] .zx-workspace-switcher__trigger {
|
|
120
|
+
display: inline-grid;
|
|
121
|
+
grid-template-columns: auto;
|
|
122
|
+
place-content: center;
|
|
123
|
+
inline-size: calc(var(--zx-control-height) + var(--zx-space-2));
|
|
124
|
+
block-size: calc(var(--zx-control-height) + var(--zx-space-2));
|
|
125
|
+
padding: 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.zx-workspace-switcher[data-compact="true"] .zx-workspace-switcher__text,
|
|
129
|
+
.zx-workspace-switcher[data-compact="true"] .zx-workspace-switcher__chevron {
|
|
130
|
+
display: none;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* An image tile: the same box and corner as an AppIcon tile. */
|
|
134
|
+
.zx-workspace-switcher__image {
|
|
135
|
+
display: block;
|
|
136
|
+
overflow: hidden;
|
|
137
|
+
inline-size: var(--zx-workspace-switcher-tile);
|
|
138
|
+
block-size: var(--zx-workspace-switcher-tile);
|
|
139
|
+
border-radius: var(--zx-radius-sm);
|
|
140
|
+
background: var(--zx-color-bg-muted);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.zx-workspace-switcher__image > img {
|
|
144
|
+
display: block;
|
|
145
|
+
inline-size: 100%;
|
|
146
|
+
block-size: 100%;
|
|
147
|
+
object-fit: cover;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.zx-workspace-switcher__initials {
|
|
151
|
+
font-weight: var(--zx-weight-semibold);
|
|
152
|
+
line-height: 1;
|
|
153
|
+
letter-spacing: 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* The menu: a workspace row leads with its tile and ends with a check on the current one. */
|
|
157
|
+
.zx-workspace-switcher__menu {
|
|
158
|
+
--zx-workspace-switcher-tile: calc(var(--zx-control-height) - var(--zx-space-2));
|
|
159
|
+
min-inline-size: 240px;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* Every row's leading column is a tile wide, so action labels line up with workspace names. */
|
|
163
|
+
.zx-workspace-switcher__menu .zx-menu-button__leading {
|
|
164
|
+
inline-size: var(--zx-workspace-switcher-tile);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.zx-workspace-switcher__menu .zx-workspace-switcher__option[aria-checked="true"] {
|
|
168
|
+
background: var(--zx-color-bg-current);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.zx-workspace-switcher__menu .zx-workspace-switcher__option[aria-checked="true"]:is(:hover, :focus-visible) {
|
|
172
|
+
background: var(--zx-color-overlay-hover);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.zx-workspace-switcher__check {
|
|
176
|
+
display: inline-flex;
|
|
177
|
+
color: var(--zx-color-accent);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.zx-workspace-switcher__option[aria-checked="false"] .zx-workspace-switcher__check {
|
|
181
|
+
visibility: hidden;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@media (forced-colors: active) {
|
|
185
|
+
.zx-workspace-switcher .zx-workspace-switcher__trigger:focus-visible {
|
|
186
|
+
outline: 2px solid Highlight;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { Component } from '../../core/component.js';
|
|
2
|
+
import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
|
|
3
|
+
import { printf } from '../../core/i18n.js';
|
|
4
|
+
import { icon } from '../../core/icons.js';
|
|
5
|
+
import { APP_ICON_TONES, appIcon } from '../app-icon/app-icon.js';
|
|
6
|
+
import { avatarInitials, avatarTone } from '../avatar/avatar.js';
|
|
7
|
+
import { MenuButton } from '../menu-button/menu-button.js';
|
|
8
|
+
|
|
9
|
+
/** @typedef {import('../menu-button/menu-button.js').MenuButtonItem} MenuButtonItem */
|
|
10
|
+
/** @typedef {import('../app-icon/app-icon.js').AppIconTone} AppIconTone */
|
|
11
|
+
/**
|
|
12
|
+
* One workspace, account, or organisation the user can switch to.
|
|
13
|
+
* @typedef {Object} WorkspaceItem
|
|
14
|
+
* @property {string|number} id Stable identifier; the `active` option and `setActive()` name it.
|
|
15
|
+
* @property {string} label Workspace name.
|
|
16
|
+
* @property {string} [secondary] Secondary line: plan, role, domain.
|
|
17
|
+
* @property {string|Node} [icon] Icon name or node drawn on the tile instead of the initial. A node
|
|
18
|
+
* is cloned for each place the tile appears.
|
|
19
|
+
* @property {AppIconTone} [tone] Tile colour from the theme's categorical palette. Without `tone`
|
|
20
|
+
* or `color` the tone is derived from the label, so a workspace keeps its colour everywhere.
|
|
21
|
+
* @property {string} [color] Tile colour, any CSS colour; takes precedence over `tone`.
|
|
22
|
+
* @property {string|null} [src] Image drawn as the tile instead of the initial or icon.
|
|
23
|
+
* @property {string} [initials] Explicit tile initials; derived from the label when omitted.
|
|
24
|
+
* @property {string|number} [badge] Compact trailing metadata in the menu, such as an unread count.
|
|
25
|
+
* @property {boolean} [disabled=false] Whether the workspace is listed but cannot be chosen.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {Object} WorkspaceSwitcherSelectDetail
|
|
29
|
+
* @property {WorkspaceItem} workspace The workspace being switched to.
|
|
30
|
+
* @property {string|number} id Its id.
|
|
31
|
+
* @property {WorkspaceItem|null} previous The workspace active until now.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {Object} WorkspaceSwitcherOptions
|
|
35
|
+
* @property {WorkspaceItem[]} [workspaces=[]] Workspaces in menu order. Entries without an id are
|
|
36
|
+
* skipped, and a repeated id keeps its first entry.
|
|
37
|
+
* @property {string|number|null} [active=null] Active workspace id. Defaults to the first enabled
|
|
38
|
+
* workspace, which also stands in for an id that is not listed or is disabled.
|
|
39
|
+
* @property {MenuButtonItem[]} [items=[]] Extra actions listed after the workspaces — "Create
|
|
40
|
+
* workspace", "Workspace settings", `'-'` separators, "Sign out". A separator is inserted
|
|
41
|
+
* between the workspaces and the actions automatically.
|
|
42
|
+
* @property {boolean} [compact=false] Whether the trigger shows the tile alone.
|
|
43
|
+
* @property {string|null} [heading=null] Group label above the workspaces; null resolves message
|
|
44
|
+
* `workspaceSwitcher.heading` ("Workspaces"), and an empty string leaves the label out.
|
|
45
|
+
* @property {string|null} [label=null] Trigger accessible name, in which `%1` is the active
|
|
46
|
+
* workspace's name and is filled in again after every switch. Null resolves message
|
|
47
|
+
* `workspaceSwitcher.label` ("Switch workspace: %1"), or `workspaceSwitcher.labelEmpty`
|
|
48
|
+
* ("Switch workspace") while no workspace is active.
|
|
49
|
+
* @property {'bottom-start'|'bottom-end'|'top-start'|'top-end'|'bottom'|'top'|'right-start'|'right-end'|'left-start'|'left-end'|'right'|'left'} [placement='bottom-start'] Popup placement.
|
|
50
|
+
* @property {(event: CustomEvent<WorkspaceSwitcherSelectDetail>) => void} [onselect] Preventable
|
|
51
|
+
* workspace switch; `preventDefault()` keeps the previous workspace active and the menu open.
|
|
52
|
+
* @property {(event: CustomEvent<{value: unknown, item: object}>) => void} [onaction] Preventable
|
|
53
|
+
* extra action; `preventDefault()` skips the item's own callback and keeps the menu open.
|
|
54
|
+
* @property {(event: CustomEvent<Record<string, never>>) => void} [onopen] Open listener.
|
|
55
|
+
* @property {(event: CustomEvent<Record<string, never>>) => void} [onclose] Close listener.
|
|
56
|
+
* @property {Record<string, unknown>} [msg] Component-local message overrides, by key:
|
|
57
|
+
* `workspaceSwitcher.heading` ("Workspaces"), `workspaceSwitcher.label` ("Switch workspace: %1",
|
|
58
|
+
* `%1` the active workspace's name), and `workspaceSwitcher.labelEmpty` ("Switch workspace",
|
|
59
|
+
* the trigger's name while no workspace is active).
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Workspace switcher for the head of an application's navigation: a trigger with the current
|
|
64
|
+
* workspace's tile, name, and secondary line, opening an APG menu of the workspaces — the current
|
|
65
|
+
* one checked — followed by the application's own actions. Which workspaces a user may see, and
|
|
66
|
+
* what switching means, stay application-owned.
|
|
67
|
+
* @fires WorkspaceSwitcher#select
|
|
68
|
+
* @fires WorkspaceSwitcher#action
|
|
69
|
+
* @fires WorkspaceSwitcher#open
|
|
70
|
+
* @fires WorkspaceSwitcher#close
|
|
71
|
+
* @extends {Component<WorkspaceSwitcherOptions>}
|
|
72
|
+
*/
|
|
73
|
+
export class WorkspaceSwitcher extends Component {
|
|
74
|
+
static cssName = 'workspace-switcher';
|
|
75
|
+
|
|
76
|
+
/** @type {Readonly<WorkspaceSwitcherOptions>} */
|
|
77
|
+
static defaults = {
|
|
78
|
+
workspaces: [],
|
|
79
|
+
active: null,
|
|
80
|
+
items: [],
|
|
81
|
+
compact: false,
|
|
82
|
+
heading: null,
|
|
83
|
+
label: null,
|
|
84
|
+
placement: 'bottom-start'
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Creates or enhances a workspace-switcher host.
|
|
89
|
+
* @param {Element|string|null} target Host target.
|
|
90
|
+
* @param {WorkspaceSwitcherOptions} [options={}] Workspace-switcher options.
|
|
91
|
+
*/
|
|
92
|
+
constructor(target = null, options = {}) {
|
|
93
|
+
super(target, options);
|
|
94
|
+
// MenuButton's own events bubble from the trigger; this component re-emits them once.
|
|
95
|
+
for (const type of ['select', 'open', 'close']) {
|
|
96
|
+
this.listen(this.refs.trigger, `zx-${type}`, (event) => event.stopPropagation());
|
|
97
|
+
}
|
|
98
|
+
this._menu = new MenuButton(this.refs.trigger, { items: [], placement: this.options.placement });
|
|
99
|
+
this._menu.getPanel().classList.add('zx-workspace-switcher__menu');
|
|
100
|
+
this._menu.on('select', (event) => this._onMenuSelect(event));
|
|
101
|
+
this._menu.on('open', () => this.emit('open'));
|
|
102
|
+
this._menu.on('close', () => this.emit('close'));
|
|
103
|
+
this._sync();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** @returns {HTMLElement} */
|
|
107
|
+
render() {
|
|
108
|
+
this._createdRoot = this.el === null;
|
|
109
|
+
const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
|
|
110
|
+
this.el = root;
|
|
111
|
+
this._snapshot = this._createdRoot ? null : snapshotTarget(root);
|
|
112
|
+
this._workspaces = normalizeWorkspaces(this.options.workspaces);
|
|
113
|
+
this._activeId = resolveActive(this._workspaces, this.options.active);
|
|
114
|
+
this._items = Array.isArray(this.options.items) ? this.options.items.slice() : [];
|
|
115
|
+
/** @type {Map<object, WorkspaceItem>} Menu entries that stand for a workspace. */
|
|
116
|
+
this._entries = new Map();
|
|
117
|
+
root.dataset.compact = String(Boolean(this.options.compact));
|
|
118
|
+
root.replaceChildren(h('button', {
|
|
119
|
+
ref: 'trigger',
|
|
120
|
+
class: 'zx-workspace-switcher__trigger',
|
|
121
|
+
type: 'button'
|
|
122
|
+
},
|
|
123
|
+
h('span', { ref: 'tile', class: 'zx-workspace-switcher__tile', ariaHidden: 'true' }),
|
|
124
|
+
h('span', { class: 'zx-workspace-switcher__text' },
|
|
125
|
+
h('span', { ref: 'name', class: 'zx-workspace-switcher__name' }),
|
|
126
|
+
h('span', { ref: 'secondary', class: 'zx-workspace-switcher__secondary' })),
|
|
127
|
+
h('span', { class: 'zx-workspace-switcher__chevron', ariaHidden: 'true' }, icon('chevron-down'))));
|
|
128
|
+
return root;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Returns the active workspace, or null when no workspace is enabled. @returns {WorkspaceItem|null} */
|
|
132
|
+
getActive() {
|
|
133
|
+
return findWorkspace(this._workspaces, this._activeId);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Makes another workspace active. An unknown or disabled id changes nothing.
|
|
138
|
+
* @param {string|number} id Workspace id.
|
|
139
|
+
* @param {{silent?: boolean}} [options={}] `silent: false` emits the preventable `select` first;
|
|
140
|
+
* by default the change is silent, as a change the application made itself.
|
|
141
|
+
* @returns {this}
|
|
142
|
+
* @fires WorkspaceSwitcher#select
|
|
143
|
+
*/
|
|
144
|
+
setActive(id, { silent = true } = {}) {
|
|
145
|
+
const workspace = findWorkspace(this._workspaces, id);
|
|
146
|
+
if (!workspace || workspace.disabled || sameId(workspace.id, this._activeId)) return this;
|
|
147
|
+
if (!silent && this._emitSelect(workspace).defaultPrevented) return this;
|
|
148
|
+
this._activeId = workspace.id;
|
|
149
|
+
this._sync();
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Replaces the workspaces. The active workspace stays active while it is still listed and
|
|
155
|
+
* enabled; otherwise the first enabled workspace becomes active, silently.
|
|
156
|
+
* @param {WorkspaceItem[]} workspaces Workspaces in menu order.
|
|
157
|
+
* @returns {this}
|
|
158
|
+
*/
|
|
159
|
+
setWorkspaces(workspaces) {
|
|
160
|
+
this._workspaces = normalizeWorkspaces(workspaces);
|
|
161
|
+
this._activeId = resolveActive(this._workspaces, this._activeId);
|
|
162
|
+
this._sync();
|
|
163
|
+
return this;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Replaces the extra actions after the workspaces. @param {MenuButtonItem[]} items @returns {this} */
|
|
167
|
+
setItems(items) {
|
|
168
|
+
this._items = Array.isArray(items) ? items.slice() : [];
|
|
169
|
+
this._renderMenu();
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Shows the tile alone, or the tile with the name, secondary line, and chevron — for a navigation
|
|
175
|
+
* column that narrows to icons and widens again. The trigger keeps its full accessible name and
|
|
176
|
+
* gains the workspace name as a tooltip while compact.
|
|
177
|
+
* @param {boolean} compact
|
|
178
|
+
* @returns {this}
|
|
179
|
+
*/
|
|
180
|
+
setCompact(compact) {
|
|
181
|
+
this.el.dataset.compact = String(Boolean(compact));
|
|
182
|
+
this._renderTrigger();
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Opens the menu. @returns {this} */
|
|
187
|
+
open() {
|
|
188
|
+
this._menu.open();
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Closes the menu. @returns {this} */
|
|
193
|
+
close() {
|
|
194
|
+
this._menu.close();
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Toggles the menu. @returns {this} */
|
|
199
|
+
toggle() {
|
|
200
|
+
if (this._menu.isOpen()) this._menu.close();
|
|
201
|
+
else this._menu.open();
|
|
202
|
+
return this;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Reports whether the menu is open. @returns {boolean} */
|
|
206
|
+
isOpen() {
|
|
207
|
+
return this._menu.isOpen();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Focuses the trigger. @returns {this} */
|
|
211
|
+
focus() {
|
|
212
|
+
this._menu.getTrigger().focus();
|
|
213
|
+
return this;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Returns the menu panel for shell hover and focus coordination. @returns {HTMLElement} */
|
|
217
|
+
getPanel() {
|
|
218
|
+
return this._menu.getPanel();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Destroys the menu and restores or removes the host. @returns {void} */
|
|
222
|
+
destroy() {
|
|
223
|
+
const root = this.el;
|
|
224
|
+
const snapshot = this._snapshot;
|
|
225
|
+
this._menu?.destroy();
|
|
226
|
+
super.destroy();
|
|
227
|
+
if (!this._createdRoot && root) restoreTarget(root, snapshot);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** @returns {void} */
|
|
231
|
+
_sync() {
|
|
232
|
+
this._renderTrigger();
|
|
233
|
+
this._renderMenu();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** @returns {void} */
|
|
237
|
+
_renderTrigger() {
|
|
238
|
+
const workspace = this.getActive();
|
|
239
|
+
const name = workspace?.label ?? '';
|
|
240
|
+
this.refs.tile.replaceChildren(...(workspace ? [workspaceTile(workspace)] : []));
|
|
241
|
+
this.refs.tile.hidden = !workspace;
|
|
242
|
+
this.refs.name.textContent = name;
|
|
243
|
+
this.refs.secondary.textContent = workspace?.secondary ?? '';
|
|
244
|
+
this.refs.secondary.hidden = !workspace?.secondary;
|
|
245
|
+
const label = this.options.label ? formatWorkspaceLabel(this.options.label, name) : (name
|
|
246
|
+
? this._message('workspaceSwitcher.label', 'Switch workspace: %1', name)
|
|
247
|
+
: this._message('workspaceSwitcher.labelEmpty', 'Switch workspace'));
|
|
248
|
+
this.refs.trigger.setAttribute('aria-label', label);
|
|
249
|
+
if (this.el.dataset.compact === 'true' && name) this.refs.trigger.setAttribute('title', name);
|
|
250
|
+
else this.refs.trigger.removeAttribute('title');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** @returns {void} */
|
|
254
|
+
_renderMenu() {
|
|
255
|
+
this._entries = new Map();
|
|
256
|
+
/** @type {MenuButtonItem[]} */
|
|
257
|
+
const entries = [];
|
|
258
|
+
const heading = this.options.heading == null
|
|
259
|
+
? this._message('workspaceSwitcher.heading', 'Workspaces')
|
|
260
|
+
: String(this.options.heading);
|
|
261
|
+
if (heading && this._workspaces.length) entries.push({ type: 'heading', label: heading });
|
|
262
|
+
const rows = [];
|
|
263
|
+
for (const workspace of this._workspaces) {
|
|
264
|
+
const entry = {
|
|
265
|
+
label: workspace.label,
|
|
266
|
+
value: workspace.id,
|
|
267
|
+
role: /** @type {const} */ ('menuitemradio'),
|
|
268
|
+
checked: sameId(workspace.id, this._activeId),
|
|
269
|
+
description: workspace.secondary || undefined,
|
|
270
|
+
badge: workspace.badge ?? undefined,
|
|
271
|
+
disabled: workspace.disabled,
|
|
272
|
+
adornment: h('span', { class: 'zx-workspace-switcher__check', ariaHidden: 'true' }, icon('check'))
|
|
273
|
+
};
|
|
274
|
+
this._entries.set(entry, workspace);
|
|
275
|
+
rows.push([entries.length, workspace]);
|
|
276
|
+
entries.push(entry);
|
|
277
|
+
}
|
|
278
|
+
if (this._workspaces.length && this._items.length && !isSeparator(this._items[0])) entries.push('-');
|
|
279
|
+
entries.push(...this._items);
|
|
280
|
+
this._menu.setItems(entries);
|
|
281
|
+
// The shared menu row draws a radio circle; a workspace row leads with its tile instead.
|
|
282
|
+
const panel = this._menu.getPanel();
|
|
283
|
+
for (const [index, workspace] of rows) {
|
|
284
|
+
const row = panel.querySelector(`[data-menu-item="${index}"]`);
|
|
285
|
+
if (!row) continue;
|
|
286
|
+
row.classList.add('zx-workspace-switcher__option');
|
|
287
|
+
row.querySelector('.zx-menu-button__leading')?.replaceChildren(workspaceTile(workspace));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** @param {CustomEvent<{value: unknown, item: object}>} event @returns {void} */
|
|
292
|
+
_onMenuSelect(event) {
|
|
293
|
+
const workspace = this._entries.get(event.detail.item);
|
|
294
|
+
if (!workspace) {
|
|
295
|
+
const action = this.emit('action', event.detail, { honorDomCancellation: true });
|
|
296
|
+
if (action.defaultPrevented) event.preventDefault();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// Choosing the current workspace again only closes the menu.
|
|
300
|
+
if (sameId(workspace.id, this._activeId)) return;
|
|
301
|
+
if (this._emitSelect(workspace).defaultPrevented) {
|
|
302
|
+
event.preventDefault();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
// MenuButton has already returned focus to the trigger and closes the menu after this.
|
|
306
|
+
this._activeId = workspace.id;
|
|
307
|
+
this._sync();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** @param {WorkspaceItem} workspace @returns {CustomEvent<WorkspaceSwitcherSelectDetail>} */
|
|
311
|
+
_emitSelect(workspace) {
|
|
312
|
+
return /** @type {CustomEvent<WorkspaceSwitcherSelectDetail>} */ (/** @type {unknown} */ (this.emit('select', {
|
|
313
|
+
workspace,
|
|
314
|
+
id: workspace.id,
|
|
315
|
+
previous: this.getActive()
|
|
316
|
+
}, { honorDomCancellation: true })));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Resolves a message, falling back to English when no translation is registered.
|
|
321
|
+
* @param {string} key Message key.
|
|
322
|
+
* @param {string} fallback English fallback with `%1` placeholders.
|
|
323
|
+
* @param {...unknown} args Interpolation values.
|
|
324
|
+
* @returns {string}
|
|
325
|
+
*/
|
|
326
|
+
_message(key, fallback, ...args) {
|
|
327
|
+
const message = this.msg(key, ...args);
|
|
328
|
+
return message === key ? printf(fallback, args) : message;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Normalises one workspace definition: a string id or label, a tone the palette knows, a colour
|
|
334
|
+
* only when no tone was asked for, and initials derived from the label when none were given.
|
|
335
|
+
* @param {unknown} value Candidate workspace.
|
|
336
|
+
* @returns {(WorkspaceItem & {label: string, secondary: string, initials: string, disabled: boolean, tone: AppIconTone|null, color: string|null, src: string|null}) | null}
|
|
337
|
+
* The workspace, or null when it has no usable id.
|
|
338
|
+
*/
|
|
339
|
+
export function normalizeWorkspace(value) {
|
|
340
|
+
if (!value || typeof value !== 'object') return null;
|
|
341
|
+
const source = /** @type {Record<string, any>} */ (value);
|
|
342
|
+
const id = source.id;
|
|
343
|
+
if ((typeof id !== 'string' || id === '') && (typeof id !== 'number' || !Number.isFinite(id))) return null;
|
|
344
|
+
const label = String(source.label ?? id).trim() || String(id);
|
|
345
|
+
const color = typeof source.color === 'string' && source.color.trim() ? source.color.trim() : null;
|
|
346
|
+
const tone = APP_ICON_TONES.includes(source.tone) ? source.tone : (color ? null : avatarTone(label));
|
|
347
|
+
const src = typeof source.src === 'string' && source.src.trim() ? source.src.trim() : null;
|
|
348
|
+
const badge = source.badge == null || source.badge === '' ? undefined : source.badge;
|
|
349
|
+
return {
|
|
350
|
+
...source,
|
|
351
|
+
id,
|
|
352
|
+
label,
|
|
353
|
+
secondary: source.secondary == null ? '' : String(source.secondary),
|
|
354
|
+
tone,
|
|
355
|
+
color,
|
|
356
|
+
src,
|
|
357
|
+
badge,
|
|
358
|
+
initials: workspaceInitials(label, source.initials),
|
|
359
|
+
disabled: Boolean(source.disabled)
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Normalises a workspace list, skipping entries without an id and repeats of an id already listed.
|
|
365
|
+
* @param {unknown} list Candidate workspaces.
|
|
366
|
+
* @returns {WorkspaceItem[]}
|
|
367
|
+
*/
|
|
368
|
+
export function normalizeWorkspaces(list) {
|
|
369
|
+
if (!Array.isArray(list)) return [];
|
|
370
|
+
const seen = new Set();
|
|
371
|
+
const result = [];
|
|
372
|
+
for (const value of list) {
|
|
373
|
+
const workspace = normalizeWorkspace(value);
|
|
374
|
+
if (!workspace || seen.has(String(workspace.id))) continue;
|
|
375
|
+
seen.add(String(workspace.id));
|
|
376
|
+
result.push(workspace);
|
|
377
|
+
}
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Resolves the active workspace id: the requested one when it is listed and enabled, otherwise the
|
|
383
|
+
* first enabled workspace, otherwise null. Ids compare as strings, so `3` finds `'3'`.
|
|
384
|
+
* @param {WorkspaceItem[]} workspaces Normalised workspaces.
|
|
385
|
+
* @param {string|number|null|undefined} active Requested id.
|
|
386
|
+
* @returns {string|number|null}
|
|
387
|
+
*/
|
|
388
|
+
export function resolveActive(workspaces, active) {
|
|
389
|
+
const requested = active == null ? null : findWorkspace(workspaces, active);
|
|
390
|
+
if (requested && !requested.disabled) return requested.id;
|
|
391
|
+
return (workspaces.find((workspace) => !workspace.disabled) ?? null)?.id ?? null;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* The tile initials for a workspace: explicit initials trimmed to two characters, otherwise the
|
|
396
|
+
* first letter of the label, uppercased.
|
|
397
|
+
* @param {unknown} label Workspace name.
|
|
398
|
+
* @param {unknown} [initials] Explicit initials.
|
|
399
|
+
* @returns {string}
|
|
400
|
+
*/
|
|
401
|
+
export function workspaceInitials(label, initials) {
|
|
402
|
+
const explicit = typeof initials === 'string' ? initials.trim() : '';
|
|
403
|
+
if (explicit) return Array.from(explicit).slice(0, 2).join('');
|
|
404
|
+
return avatarInitials(label, 1);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Fills a custom trigger label: `%1` becomes the active workspace's name (empty while none is
|
|
409
|
+
* active), so the label follows every switch.
|
|
410
|
+
* @param {unknown} template Label, with an optional `%1`.
|
|
411
|
+
* @param {unknown} name Active workspace name.
|
|
412
|
+
* @returns {string}
|
|
413
|
+
*/
|
|
414
|
+
export function formatWorkspaceLabel(template, name) {
|
|
415
|
+
return printf(String(template), [name == null ? '' : String(name)]);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** @param {WorkspaceItem[]} workspaces @param {unknown} id @returns {WorkspaceItem|null} */
|
|
419
|
+
function findWorkspace(workspaces, id) {
|
|
420
|
+
if (id == null) return null;
|
|
421
|
+
return workspaces.find((workspace) => sameId(workspace.id, id)) ?? null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** @param {unknown} a @param {unknown} b @returns {boolean} */
|
|
425
|
+
function sameId(a, b) {
|
|
426
|
+
return a != null && b != null && String(a) === String(b);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** @param {unknown} entry @returns {boolean} */
|
|
430
|
+
function isSeparator(entry) {
|
|
431
|
+
return entry === '-' || Boolean(entry && typeof entry === 'object'
|
|
432
|
+
&& /** @type {{type?: unknown}} */ (entry).type === 'separator');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* The workspace's identity tile: its image, or its icon or initial on its tone.
|
|
437
|
+
* @param {WorkspaceItem} workspace Normalised workspace.
|
|
438
|
+
* @returns {HTMLElement}
|
|
439
|
+
*/
|
|
440
|
+
function workspaceTile(workspace) {
|
|
441
|
+
if (workspace.src) {
|
|
442
|
+
return h('span', { class: 'zx-workspace-switcher__image' },
|
|
443
|
+
h('img', { src: workspace.src, alt: '', decoding: 'async' }));
|
|
444
|
+
}
|
|
445
|
+
const glyph = workspace.icon && typeof workspace.icon === 'object' && 'nodeType' in workspace.icon
|
|
446
|
+
? /** @type {Node} */ (workspace.icon).cloneNode(true)
|
|
447
|
+
: typeof workspace.icon === 'string' && workspace.icon
|
|
448
|
+
? workspace.icon
|
|
449
|
+
: h('span', { class: 'zx-workspace-switcher__initials' }, workspace.initials ?? '');
|
|
450
|
+
return appIcon({
|
|
451
|
+
icon: glyph,
|
|
452
|
+
tone: workspace.tone ?? null,
|
|
453
|
+
color: workspace.color ?? null,
|
|
454
|
+
size: 'var(--zx-workspace-switcher-tile)',
|
|
455
|
+
iconSize: typeof glyph === 'string' ? '58%' : '50%',
|
|
456
|
+
glass: 'subtle'
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** Fired before the active workspace changes; preventable. @event WorkspaceSwitcher#select @type {CustomEvent<WorkspaceSwitcherSelectDetail>} */
|
|
461
|
+
/** Fired when an extra action is chosen; preventable. @event WorkspaceSwitcher#action @type {CustomEvent<{value: unknown, item: object}>} */
|
|
462
|
+
/** Fired after the menu opens. @event WorkspaceSwitcher#open @type {CustomEvent<Record<string, never>>} */
|
|
463
|
+
/** Fired after the menu closes. @event WorkspaceSwitcher#close @type {CustomEvent<Record<string, never>>} */
|
package/src/core/breakpoint.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* used inside a media query, so a `--zx-bp-*` token would be a value no stylesheet could read.
|
|
7
7
|
* Zx's own layout utilities avoid the problem entirely — `.zx-grid` reflows on intrinsic track
|
|
8
8
|
* widths, not on breakpoints — which leaves this module for the cases only script can decide:
|
|
9
|
-
* rendering a Table as cards below a width, collapsing a MasterPanel, choosing a chart
|
|
9
|
+
* rendering a Table as cards below a width, collapsing a MasterPanel, choosing how many labels a chart shows.
|
|
10
10
|
* @module zx/core/breakpoint
|
|
11
11
|
*/
|
|
12
12
|
|