@zeyos/zx 2.2.1 → 3.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 +197 -0
- package/DESIGN-SYSTEM.md +136 -0
- package/MIGRATION.md +155 -4
- package/README.md +51 -14
- package/dist/types/compat/map/data.d.ts +3 -2
- package/dist/types/components/account-menu/account-menu.d.ts +144 -0
- package/dist/types/components/app-rail/app-rail.d.ts +250 -0
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +169 -0
- package/dist/types/components/avatar/avatar.d.ts +100 -0
- package/dist/types/components/chart/chart.d.ts +321 -0
- package/dist/types/components/date-picker/month-picker.d.ts +6 -0
- package/dist/types/components/dialog/dialog.d.ts +9 -0
- package/dist/types/components/dock/dock.d.ts +495 -0
- package/dist/types/components/dropdown/dropdown.d.ts +2 -2
- package/dist/types/components/field/field.d.ts +14 -0
- package/dist/types/components/field-upload/field-upload.d.ts +37 -4
- package/dist/types/components/form/form.d.ts +25 -3
- package/dist/types/components/grid/grid.d.ts +172 -0
- package/dist/types/components/launcher/launcher.d.ts +300 -0
- package/dist/types/components/layout/layout.d.ts +4 -4
- package/dist/types/components/menu-button/menu-button.d.ts +10 -2
- package/dist/types/components/modal/modal.d.ts +25 -0
- package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +74 -9
- package/dist/types/components/questionnaire/items.d.ts +327 -0
- package/dist/types/components/questionnaire/questionnaire.d.ts +449 -0
- package/dist/types/components/select/select.d.ts +36 -1
- package/dist/types/components/sheet/sheet-stack.d.ts +167 -0
- package/dist/types/components/sheet/sheet.d.ts +294 -0
- package/dist/types/components/split-view/split-view.d.ts +2 -21
- package/dist/types/components/tabbox/tabbox.d.ts +12 -3
- package/dist/types/components/table/table.d.ts +219 -12
- package/dist/types/components/tooltip/tooltip.d.ts +2 -2
- package/dist/types/components/value-list/value-list.d.ts +24 -3
- package/dist/types/core/dom.d.ts +114 -0
- package/dist/types/core/drag-axis.d.ts +111 -0
- package/dist/types/core/position.d.ts +4 -2
- package/dist/types/index.d.ts +12 -2
- package/dist/zx-compat.esm.js +943 -311
- package/dist/zx-compat.esm.min.js +1 -1
- package/dist/zx-compat.global.js +7893 -2252
- package/dist/zx-compat.global.min.js +3 -3
- package/dist/zx-zeyos.esm.js +826 -114
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +1720 -243
- package/dist/zx.esm.js +8227 -2599
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +8227 -2599
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +2597 -255
- package/docs/llms.md +475 -37
- package/docs/llms.txt +21 -9
- package/package.json +4 -2
- package/src/compat/globals.js +3 -1
- package/src/compat/map/data.js +31 -9
- package/src/components/account-menu/account-menu.css +70 -0
- package/src/components/account-menu/account-menu.js +208 -0
- package/src/components/app-rail/app-rail.css +107 -0
- package/src/components/app-rail/app-rail.js +668 -0
- package/src/components/app-sidebar/app-sidebar.css +68 -0
- package/src/components/app-sidebar/app-sidebar.js +388 -0
- package/src/components/avatar/avatar.css +61 -0
- package/src/components/avatar/avatar.js +222 -0
- package/src/components/button/button.css +0 -1
- package/src/components/chart/chart.css +61 -0
- package/src/components/chart/chart.js +518 -0
- package/src/components/date-picker/date-range-picker.css +7 -0
- package/src/components/date-picker/month-picker.js +9 -0
- package/src/components/dialog/dialog.js +11 -4
- package/src/components/dock/dock.css +228 -0
- package/src/components/dock/dock.js +977 -0
- package/src/components/dropdown/dropdown.js +1 -1
- package/src/components/field/field.js +2 -0
- package/src/components/field-upload/field-upload.js +9 -4
- package/src/components/fieldset/fieldset.css +7 -0
- package/src/components/form/form.css +7 -0
- package/src/components/form/form.js +6 -3
- package/src/components/grid/grid.css +6 -0
- package/src/components/grid/grid.js +213 -0
- package/src/components/launcher/launcher.css +100 -0
- package/src/components/launcher/launcher.js +557 -0
- package/src/components/layout/layout.js +2 -2
- package/src/components/menu-button/menu-button.js +27 -2
- package/src/components/modal/modal.css +5 -1
- package/src/components/modal/modal.js +37 -2
- package/src/components/multi-value-editor/multi-value-editor.css +99 -15
- package/src/components/multi-value-editor/multi-value-editor.js +237 -19
- package/src/components/navigation-bar/navigation-bar.css +7 -0
- package/src/components/questionnaire/items.js +370 -0
- package/src/components/questionnaire/questionnaire.css +387 -0
- package/src/components/questionnaire/questionnaire.js +1033 -0
- package/src/components/search/search.css +10 -2
- package/src/components/select/select.css +6 -1
- package/src/components/select/select.js +99 -11
- package/src/components/sheet/sheet-stack.js +304 -0
- package/src/components/sheet/sheet.css +351 -0
- package/src/components/sheet/sheet.js +551 -0
- package/src/components/split-view/split-view.js +76 -161
- package/src/components/stepper/stepper.css +12 -2
- package/src/components/tabbox/tabbox.css +13 -3
- package/src/components/tabbox/tabbox.js +34 -10
- package/src/components/table/table.css +43 -0
- package/src/components/table/table.js +523 -45
- package/src/components/tooltip/tooltip.js +5 -2
- package/src/components/value-list/value-list.js +6 -3
- package/src/core/dom.js +302 -0
- package/src/core/drag-axis.js +203 -0
- package/src/core/position.js +68 -22
- package/src/index.js +19 -2
- package/styles/tokens/dark.css +15 -13
- package/styles/tokens/global.css +17 -5
- package/styles/tokens/semantic.css +14 -8
- package/styles/tokens/themes.css +72 -0
- package/styles/zx.css +11 -1
- package/dist/types/components/permission/permission.d.ts +0 -93
- package/src/components/permission/permission.css +0 -47
- package/src/components/permission/permission.js +0 -198
package/docs/llms.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# Zx
|
|
1
|
+
# Xenon Design System (Zx)
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
>
|
|
3
|
+
> Xenon is the dependency-free design system for ZeyOS business applications; Zx is its
|
|
4
|
+
> vanilla-JavaScript implementation. ES2022 modules, WAI-ARIA-accessible native
|
|
5
5
|
> controls, semantic `--zx-*` design tokens with light/dark/density theming, and lifecycle-safe
|
|
6
6
|
> components. Source modules run directly in the browser; a build step emits ESM, IIFE-global,
|
|
7
7
|
> gx-compatibility, and CSS bundles. Zero runtime dependencies.
|
|
@@ -15,18 +15,30 @@ exports and as `window.zx.*`.
|
|
|
15
15
|
## Documentation
|
|
16
16
|
- [Documentation site](../website/docs.html): getting started, every component with a live demo and its JavaScript source, and the application layouts. Machine index: [website/llms.txt](../website/llms.txt).
|
|
17
17
|
- [README](../README.md): what Zx is, quick start (ESM + global script), component index, theming, browser support, gx compat.
|
|
18
|
-
- [API reference](llms.md): per-component constructor options, methods, and events for all
|
|
18
|
+
- [API reference](llms.md): per-component constructor options, methods, and events for all public components plus the core helpers.
|
|
19
19
|
- [Skill](../.claude/skills/zx/SKILL.md): task-oriented guide — mental model, recipes, ZeyOS HTTP client, theming, gotchas, commands.
|
|
20
20
|
- [Changelog](../CHANGELOG.md): what changed in each release, including behaviour changes an upgrade has to account for.
|
|
21
21
|
- [Migration guide](../MIGRATION.md): gx→zx class map, script-tag replacement, deliberate behavior changes, unsupported legacy APIs.
|
|
22
22
|
- [Contributor contract](../AGENTS.md): conventions for editing component source (token rules, render-time state, listeners, ARIA/APG).
|
|
23
23
|
|
|
24
24
|
## Components
|
|
25
|
-
- Inputs: `button()`, `buttonGroup()`, `badge()`/`badgeGroup()`, `CheckButton`,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
- Inputs and identity: `button()`, `buttonGroup()`, `badge()`/`badgeGroup()`, `CheckButton`,
|
|
26
|
+
`Toggle`, `Search`, `Launcher`, `Avatar`, `AccountMenu`, `Select` (APG combobox;
|
|
27
|
+
`filter: false|'local'|async`), `Checklist`, `TagPicker`, `NumberField`, `Rating`, `Slider`,
|
|
28
|
+
`copyButton()`/`CopyInput`, `DatePicker`, `MonthPicker`, `TimePicker`,
|
|
29
|
+
`Datebox`/`DateTimeBox`, `DateRangePicker`/`DateRangeBox`, `Timebox`.
|
|
30
|
+
- Overlays: `Message` (toasts/inline/progress), `Modal`, `Dialog` (+ `alert`/`confirm`/`prompt` Promises), `Sheet` (edge-anchored Dialog; `side`, `modal: true|'trap-focus'|false`, `backdrop`, `snap` detents, swipe-to-dismiss), `SheetStack` (drill-down; `stack`/`cascade`), `Dropdown`, `MenuButton`, `ContextMenu` (right-click; `selector` for per-row menus), `Tooltip`/`tooltip()`/`describe()`.
|
|
31
|
+
- Data: `Table` (sortable, selectable, sticky header, opt-in inline cell/row editing via
|
|
32
|
+
`editMode`), `Grid` with `Grid.BillingItems()`, engine-neutral `Chart` with injected
|
|
33
|
+
`ChartJsAdapter`, `DataFilter`, `Pagination` (+ pure `paginationRange()`), `TreeView` (APG tree),
|
|
34
|
+
`Finder` (Miller columns).
|
|
35
|
+
- Forms: `Form`, `Fieldset`, `Questionnaire` (guided one-question-at-a-time flow with branching), `Field` (type registry), `ValueList`, `MultiValueEditor`, `FieldUpload`.
|
|
36
|
+
- Layout: `stack()`/`grid()`/`aspect()` (spacing, an intrinsically reflowing grid, a fixed-ratio
|
|
37
|
+
box), `Groupbox`, `Panel` (header/footer action buttons), `MasterPanel`, `Tabbox`
|
|
38
|
+
(`variant: 'divided'|'bracket'|'line'|'segmented'`), `NavigationBar`, expanded `AppSidebar`,
|
|
39
|
+
minimized `AppRail`, `Toolbar` (APG toolbar with overflow menu), `SplitView`, `Dock`
|
|
40
|
+
(collapsible resizable pane stack; tab groups, regions, adopts a `Sheet`), `Stepper`,
|
|
41
|
+
`Breadcrumb`, `emptyState()`.
|
|
30
42
|
- Feedback: `spinner()` (unknown duration), `ProgressBar` (known share; determinate or indeterminate), `InlineLoading` (a wait resolving into its outcome in place), `skeleton()`/`skeletonText()`/`skeletonTable()` (placeholders shaped like the content; `aria-hidden`, set `aria-busy` on the region).
|
|
31
43
|
- Core: `Component`, `h`/`icon`, `position`, `breakpoints`/`breakpointOf`/`matchBreakpoint`/`onBreakpoint` (named width bands, observed on an element or the window), `Http`/`zeyosService`, i18n, date utils, `defineElements` (declarative `<zx-*>` custom elements).
|
|
32
44
|
- Helpers: `formatNumber`/`formatCurrency`/`formatPercent`/`formatFileSize`/`formatRelativeTime` (locale-aware, cached `Intl`), `debounce`/`throttle`, `groupBy`/`sortBy`/`uniqueBy`, `escapeRegExp`/`highlightMatch`, `storage()` (namespaced JSON, degrades to memory), `toCsv`/`downloadBlob`/`copyToClipboard`, `truncate()`/`isTruncated()` (line clamp with the full value on hover).
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeyos/zx",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Dependency-free
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Dependency-free JavaScript implementation of the Xenon Design System for ZeyOS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://zx.zeyos.com",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"docs/api.json",
|
|
74
74
|
"CHANGELOG.md",
|
|
75
75
|
"README.md",
|
|
76
|
+
"DESIGN-SYSTEM.md",
|
|
76
77
|
"MIGRATION.md",
|
|
77
78
|
"LICENSE"
|
|
78
79
|
],
|
|
@@ -94,6 +95,7 @@
|
|
|
94
95
|
"test:browser": "node tools/run-smoke.js"
|
|
95
96
|
},
|
|
96
97
|
"devDependencies": {
|
|
98
|
+
"chart.js": "4.5.1",
|
|
97
99
|
"esbuild": "latest",
|
|
98
100
|
"playwright-core": "^1.62.1",
|
|
99
101
|
"typescript": "^7.0.2"
|
package/src/compat/globals.js
CHANGED
|
@@ -126,7 +126,9 @@ export function parse(source) {
|
|
|
126
126
|
*/
|
|
127
127
|
export function installGlobals(host = globalThis) {
|
|
128
128
|
installElementStorage(host);
|
|
129
|
-
|
|
129
|
+
// `__` is also the current ZeyOS compact DOM builder. Never replace an existing meaning; the
|
|
130
|
+
// gx parser is still installed for legacy-only hosts where the name is genuinely free.
|
|
131
|
+
if (host.__ === undefined) host.__ = parse;
|
|
130
132
|
const stringPrototype = host.String?.prototype;
|
|
131
133
|
if (stringPrototype && typeof stringPrototype.htmlSpecialChars !== 'function') {
|
|
132
134
|
Object.defineProperty(stringPrototype, 'htmlSpecialChars', {
|
package/src/compat/map/data.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
Checklist as ZxChecklist,
|
|
3
3
|
DataFilter as ZxDataFilter,
|
|
4
4
|
Http,
|
|
5
|
-
|
|
5
|
+
Select as ZxSelect,
|
|
6
6
|
Table as ZxTable
|
|
7
7
|
} from '../../index.js';
|
|
8
8
|
import { GxWrapper } from '../base.js';
|
|
@@ -75,23 +75,45 @@ export class Checklist extends ChecklistBase { static legacyName = 'gx.zeyos.Che
|
|
|
75
75
|
/** Legacy bootstrap checklist. */
|
|
76
76
|
export class BootstrapChecklist extends ChecklistBase { static legacyName = 'gx.bootstrap.Checklist'; }
|
|
77
77
|
|
|
78
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* Legacy `get()`/`change` shape is `'private'|'public'|groupId` (see `gx-zeyos/src/classes/Permission.js`);
|
|
80
|
+
* `Select.permission()`'s own value is the raw ZeyOS tri-state (`false|true|groupId`).
|
|
81
|
+
* @param {unknown} value
|
|
82
|
+
* @returns {unknown}
|
|
83
|
+
*/
|
|
84
|
+
function toLegacyPermission(value) {
|
|
85
|
+
if (value === false) return 'private';
|
|
86
|
+
if (value === true) return 'public';
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
/** @param {unknown} value @returns {unknown} */
|
|
90
|
+
function fromLegacyPermission(value) {
|
|
91
|
+
if (value === false || value === 'private') return false;
|
|
92
|
+
if (value === true || value === 'public') return true;
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Legacy permission selector, backed by the `Select.permission()` preset. */
|
|
79
97
|
export class Permission extends GxWrapper {
|
|
80
98
|
static legacyName = 'gx.zeyos.Permission';
|
|
81
99
|
|
|
82
100
|
/** @param {Element|string|null} display @param {Record<string, any>} [options={}] */
|
|
83
101
|
constructor(display, options = {}) {
|
|
84
102
|
super(options, { value: true, groups: [] });
|
|
85
|
-
const component =
|
|
86
|
-
value: options.value ?? true,
|
|
103
|
+
const component = ZxSelect.permission(display, {
|
|
104
|
+
value: fromLegacyPermission(options.value ?? true),
|
|
87
105
|
groups: options.groups ?? options.data ?? [],
|
|
88
|
-
|
|
89
|
-
|
|
106
|
+
valueKey: options.groupsValueKey ?? options.elementIndex ?? 'ID',
|
|
107
|
+
labelKey: options.groupsLabelKey ?? options.elementLabel ?? 'name'
|
|
108
|
+
});
|
|
109
|
+
this._attach(component, {
|
|
110
|
+
events: { change: { type: 'change', args: (detail) => [toLegacyPermission(detail.value)] } },
|
|
111
|
+
setters: { value: 'set' }
|
|
90
112
|
});
|
|
91
|
-
this._attach(component, { events: { change: { type: 'change', args: (detail) => [detail.value] } }, setters: { value: 'set' } });
|
|
92
113
|
}
|
|
93
|
-
/** @returns {unknown} */ get() { return this._zx.
|
|
94
|
-
/** @param {unknown} value @param {boolean} [silent=false] @returns {this} */
|
|
114
|
+
/** @returns {unknown} */ get() { return toLegacyPermission(this._zx.value); }
|
|
115
|
+
/** @param {unknown} value @param {boolean} [silent=false] @returns {this} */
|
|
116
|
+
set(value, silent = false) { this._zx.set(fromLegacyPermission(value), { silent }); return this; }
|
|
95
117
|
}
|
|
96
118
|
|
|
97
119
|
/** Shared full Table wrapper. */
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
.zx-account-menu {
|
|
2
|
+
display: block;
|
|
3
|
+
min-inline-size: 0;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.zx-account-menu__trigger {
|
|
7
|
+
display: grid;
|
|
8
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
9
|
+
align-items: center;
|
|
10
|
+
gap: var(--zx-space-3);
|
|
11
|
+
inline-size: 100%;
|
|
12
|
+
min-block-size: calc(var(--zx-control-height) + var(--zx-space-2));
|
|
13
|
+
border-color: transparent;
|
|
14
|
+
background: transparent;
|
|
15
|
+
padding: var(--zx-space-2);
|
|
16
|
+
color: inherit;
|
|
17
|
+
text-align: start;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.zx-account-menu__trigger:hover,
|
|
21
|
+
.zx-account-menu__trigger:focus-visible,
|
|
22
|
+
.zx-account-menu__trigger[aria-expanded="true"] {
|
|
23
|
+
background: var(--zx-color-bg-hover);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.zx-account-menu__identity {
|
|
27
|
+
display: grid;
|
|
28
|
+
min-inline-size: 0;
|
|
29
|
+
gap: 1px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.zx-account-menu__name,
|
|
33
|
+
.zx-account-menu__secondary {
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
text-overflow: ellipsis;
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.zx-account-menu__name { font-weight: 650; }
|
|
40
|
+
.zx-account-menu__secondary { color: var(--zx-color-text-muted); font-size: var(--zx-text-xs); }
|
|
41
|
+
.zx-account-menu__chevron { display: inline-flex; }
|
|
42
|
+
|
|
43
|
+
.zx-account-menu[data-compact="true"] {
|
|
44
|
+
inline-size: fit-content;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.zx-account-menu[data-compact="true"] .zx-account-menu__trigger {
|
|
48
|
+
display: inline-grid;
|
|
49
|
+
grid-template-columns: auto;
|
|
50
|
+
inline-size: auto;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.zx-account-menu[data-compact="true"] .zx-account-menu__identity,
|
|
54
|
+
.zx-account-menu[data-compact="true"] .zx-account-menu__chevron {
|
|
55
|
+
display: none;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.zx-account-menu__panel-identity {
|
|
59
|
+
display: grid;
|
|
60
|
+
min-inline-size: 220px;
|
|
61
|
+
gap: 2px;
|
|
62
|
+
margin: calc(-1 * var(--zx-space-1)) calc(-1 * var(--zx-space-1)) var(--zx-space-1);
|
|
63
|
+
border-block-end: 1px solid var(--zx-color-border);
|
|
64
|
+
padding: var(--zx-space-3);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.zx-account-menu__panel-identity span {
|
|
68
|
+
color: var(--zx-color-text-muted);
|
|
69
|
+
font-size: var(--zx-text-xs);
|
|
70
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { Component } from '../../core/component.js';
|
|
2
|
+
import { h } from '../../core/dom.js';
|
|
3
|
+
import { icon } from '../../core/icons.js';
|
|
4
|
+
import { Avatar } from '../avatar/avatar.js';
|
|
5
|
+
import { MenuButton } from '../menu-button/menu-button.js';
|
|
6
|
+
|
|
7
|
+
/** @typedef {import('../menu-button/menu-button.js').MenuButtonItem} MenuButtonItem */
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} AccountIdentity
|
|
10
|
+
* @property {string} [name=''] Primary identity.
|
|
11
|
+
* @property {string} [secondary=''] Email, company, or other secondary identity.
|
|
12
|
+
* @property {string|null} [src=null] Avatar image URL.
|
|
13
|
+
* @property {string|null} [initials=null] Explicit avatar initials.
|
|
14
|
+
* @property {'online'|'away'|'busy'|'offline'|null} [status=null] Presence state.
|
|
15
|
+
* @property {string|null} [statusLabel=null] Presence text alternative.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {Object} AccountMenuOptions
|
|
19
|
+
* @property {AccountIdentity} [account={}] Account presentation supplied by the application.
|
|
20
|
+
* @property {MenuButtonItem[]} [items=[]] Account actions and `'-'` separators.
|
|
21
|
+
* @property {boolean} [compact=false] Whether only the avatar is visually shown in the trigger.
|
|
22
|
+
* @property {string|null} [label=null] Trigger label; defaults to `Account menu for <name>`.
|
|
23
|
+
* @property {'bottom-start'|'bottom-end'|'top-start'|'top-end'|'bottom'|'top'|'right-start'|'right-end'|'left-start'|'left-end'|'right'|'left'} [placement='top-start'] Popup placement.
|
|
24
|
+
* @property {(event: CustomEvent<{value: unknown, item: object}>) => void} [onselect] Preventable action listener.
|
|
25
|
+
* @property {(event: CustomEvent<Record<string, never>>) => void} [onopen] Open listener.
|
|
26
|
+
* @property {(event: CustomEvent<Record<string, never>>) => void} [onclose] Close listener.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Avatar-backed APG account action menu; session/authentication behavior remains application-owned.
|
|
31
|
+
* @fires AccountMenu#select
|
|
32
|
+
* @fires AccountMenu#open
|
|
33
|
+
* @fires AccountMenu#close
|
|
34
|
+
* @extends {Component<AccountMenuOptions>}
|
|
35
|
+
*/
|
|
36
|
+
export class AccountMenu extends Component {
|
|
37
|
+
static cssName = 'account-menu';
|
|
38
|
+
|
|
39
|
+
/** @type {Readonly<AccountMenuOptions>} */
|
|
40
|
+
static defaults = {
|
|
41
|
+
account: {},
|
|
42
|
+
items: [],
|
|
43
|
+
compact: false,
|
|
44
|
+
label: null,
|
|
45
|
+
placement: 'top-start'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates or enhances an account-menu host.
|
|
50
|
+
* @param {Element|string|null} target Host target.
|
|
51
|
+
* @param {AccountMenuOptions} [options={}] Account-menu options.
|
|
52
|
+
*/
|
|
53
|
+
constructor(target = null, options = {}) {
|
|
54
|
+
super(target, options);
|
|
55
|
+
for (const type of ['select', 'open', 'close']) {
|
|
56
|
+
this.listen(this.refs.trigger, `zx-${type}`, (event) => event.stopPropagation());
|
|
57
|
+
}
|
|
58
|
+
this._avatar = new Avatar(this.refs.avatar, avatarOptions(this._account));
|
|
59
|
+
this._menu = new MenuButton(this.refs.trigger, {
|
|
60
|
+
items: this.options.items,
|
|
61
|
+
placement: this.options.placement
|
|
62
|
+
});
|
|
63
|
+
this._menu.on('select', (event) => {
|
|
64
|
+
const selected = this.emit('select', event.detail);
|
|
65
|
+
if (selected.defaultPrevented) event.preventDefault();
|
|
66
|
+
});
|
|
67
|
+
this._menu.on('open', () => this.emit('open'));
|
|
68
|
+
this._menu.on('close', () => this.emit('close'));
|
|
69
|
+
this._renderIdentity();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** @returns {HTMLElement} */
|
|
73
|
+
render() {
|
|
74
|
+
this._createdRoot = this.el === null;
|
|
75
|
+
const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
|
|
76
|
+
this.el = root;
|
|
77
|
+
this._original = this._createdRoot ? null : {
|
|
78
|
+
attributes: Array.from(root.attributes, (attribute) => [attribute.name, attribute.value]),
|
|
79
|
+
children: Array.from(root.childNodes)
|
|
80
|
+
};
|
|
81
|
+
this._account = normalizeAccount(this.options.account);
|
|
82
|
+
root.dataset.compact = String(Boolean(this.options.compact));
|
|
83
|
+
root.replaceChildren(h('button', {
|
|
84
|
+
ref: 'trigger',
|
|
85
|
+
class: 'zx-account-menu__trigger',
|
|
86
|
+
type: 'button'
|
|
87
|
+
},
|
|
88
|
+
h('span', { ref: 'avatar', class: 'zx-account-menu__avatar' }),
|
|
89
|
+
h('span', { class: 'zx-account-menu__identity' },
|
|
90
|
+
h('span', { ref: 'name', class: 'zx-account-menu__name' }),
|
|
91
|
+
h('span', { ref: 'secondary', class: 'zx-account-menu__secondary' })),
|
|
92
|
+
h('span', { class: 'zx-account-menu__chevron', ariaHidden: 'true' }, icon('chevron-down'))));
|
|
93
|
+
return root;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Opens the account menu. @returns {this} */
|
|
97
|
+
open() {
|
|
98
|
+
this._menu.open();
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Closes the account menu. @returns {this} */
|
|
103
|
+
close() {
|
|
104
|
+
this._menu.close();
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Toggles the account menu. @returns {this} */
|
|
109
|
+
toggle() {
|
|
110
|
+
this._menu.toggle();
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Reports whether the menu is open. @returns {boolean} */
|
|
115
|
+
isOpen() {
|
|
116
|
+
return this._menu.isOpen();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Replaces account presentation without touching session state. @param {AccountIdentity} account @returns {this} */
|
|
120
|
+
setAccount(account) {
|
|
121
|
+
this._account = normalizeAccount(account);
|
|
122
|
+
this._avatar.set(avatarOptions(this._account));
|
|
123
|
+
this._renderIdentity();
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Replaces account actions. @param {MenuButtonItem[]} items @returns {this} */
|
|
128
|
+
setItems(items) {
|
|
129
|
+
this._menu.setItems(items);
|
|
130
|
+
this._renderPanelHeader();
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Focuses the account trigger. @returns {this} */
|
|
135
|
+
focus() {
|
|
136
|
+
this._menu.getTrigger().focus();
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Returns the underlying menu panel for shell hover/focus coordination. @returns {HTMLElement} */
|
|
141
|
+
getPanel() {
|
|
142
|
+
return this._menu.getPanel();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Destroys child components and restores/removes the host. @returns {void} */
|
|
146
|
+
destroy() {
|
|
147
|
+
const original = this._original;
|
|
148
|
+
this._menu.destroy();
|
|
149
|
+
this._avatar.destroy();
|
|
150
|
+
super.destroy();
|
|
151
|
+
if (!this._createdRoot && original) {
|
|
152
|
+
for (const attribute of Array.from(this.el.attributes)) this.el.removeAttribute(attribute.name);
|
|
153
|
+
for (const [name, value] of original.attributes) this.el.setAttribute(name, value);
|
|
154
|
+
this.el.replaceChildren(...original.children);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** @returns {void} */
|
|
159
|
+
_renderIdentity() {
|
|
160
|
+
this.refs.name.textContent = this._account.name;
|
|
161
|
+
this.refs.secondary.textContent = this._account.secondary;
|
|
162
|
+
this.refs.secondary.hidden = !this._account.secondary;
|
|
163
|
+
const hasStatus = ['online', 'away', 'busy', 'offline'].includes(this._account.status);
|
|
164
|
+
const status = hasStatus && this._account.statusLabel ? `, ${this._account.statusLabel}` : '';
|
|
165
|
+
const label = this.options.label || `Account menu${this._account.name ? ` for ${this._account.name}` : ''}${status}`;
|
|
166
|
+
this.refs.trigger.setAttribute('aria-label', label);
|
|
167
|
+
this._renderPanelHeader();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** @returns {void} */
|
|
171
|
+
_renderPanelHeader() {
|
|
172
|
+
const panel = this._menu.getPanel();
|
|
173
|
+
panel.querySelector('.zx-account-menu__panel-identity')?.remove();
|
|
174
|
+
panel.prepend(h('div', {
|
|
175
|
+
class: 'zx-account-menu__panel-identity',
|
|
176
|
+
role: 'presentation'
|
|
177
|
+
},
|
|
178
|
+
h('strong', {}, this._account.name || 'Account'),
|
|
179
|
+
this._account.secondary ? h('span', {}, this._account.secondary) : null));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** @param {unknown} value @returns {AccountIdentity & {name: string, secondary: string}} */
|
|
184
|
+
function normalizeAccount(value) {
|
|
185
|
+
const account = value && typeof value === 'object' ? value : {};
|
|
186
|
+
return {
|
|
187
|
+
...account,
|
|
188
|
+
name: String(account.name ?? ''),
|
|
189
|
+
secondary: String(account.secondary ?? '')
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** @param {AccountIdentity} account @returns {import('../avatar/avatar.js').AvatarOptions} */
|
|
194
|
+
function avatarOptions(account) {
|
|
195
|
+
return {
|
|
196
|
+
name: account.name,
|
|
197
|
+
src: account.src ?? null,
|
|
198
|
+
initials: account.initials ?? null,
|
|
199
|
+
status: account.status ?? null,
|
|
200
|
+
statusLabel: account.statusLabel ?? null,
|
|
201
|
+
label: null,
|
|
202
|
+
size: 'md'
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Fired when an action is activated. @event AccountMenu#select @type {CustomEvent<{value: unknown, item: object}>} */
|
|
207
|
+
/** Fired after the popup opens. @event AccountMenu#open @type {CustomEvent<Record<string, never>>} */
|
|
208
|
+
/** Fired after the popup closes. @event AccountMenu#close @type {CustomEvent<Record<string, never>>} */
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
.zx-app-rail {
|
|
2
|
+
display: flex;
|
|
3
|
+
min-inline-size: 0;
|
|
4
|
+
border: 1px solid var(--zx-color-border);
|
|
5
|
+
background: var(--zx-color-bg-raised);
|
|
6
|
+
color: var(--zx-color-text);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.zx-app-rail[data-orientation="vertical"] { flex-direction: column; inline-size: calc(var(--zx-control-height) + var(--zx-space-3)); }
|
|
10
|
+
.zx-app-rail[data-orientation="horizontal"] { flex-direction: row; block-size: calc(var(--zx-control-height) + var(--zx-space-3)); }
|
|
11
|
+
|
|
12
|
+
.zx-app-rail__header,
|
|
13
|
+
.zx-app-rail__footer {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
padding: var(--zx-space-1);
|
|
18
|
+
}
|
|
19
|
+
.zx-app-rail__header[hidden], .zx-app-rail__footer[hidden] { display: none; }
|
|
20
|
+
.zx-app-rail[data-orientation="vertical"] .zx-app-rail__footer { margin-block-start: auto; }
|
|
21
|
+
.zx-app-rail[data-orientation="horizontal"] .zx-app-rail__footer { margin-inline-start: auto; }
|
|
22
|
+
|
|
23
|
+
.zx-app-rail__list {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex: 1;
|
|
26
|
+
gap: var(--zx-space-1);
|
|
27
|
+
margin: 0;
|
|
28
|
+
padding: var(--zx-space-1);
|
|
29
|
+
list-style: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.zx-app-rail[data-orientation="vertical"] .zx-app-rail__list { flex-direction: column; }
|
|
33
|
+
.zx-app-rail[data-orientation="horizontal"] .zx-app-rail__list { flex-direction: row; }
|
|
34
|
+
.zx-app-rail__row { display: flex; }
|
|
35
|
+
|
|
36
|
+
.zx-app-rail__item {
|
|
37
|
+
position: relative;
|
|
38
|
+
display: inline-grid;
|
|
39
|
+
place-items: center;
|
|
40
|
+
inline-size: var(--zx-control-height);
|
|
41
|
+
block-size: var(--zx-control-height);
|
|
42
|
+
border-color: transparent;
|
|
43
|
+
border-radius: var(--zx-radius-md);
|
|
44
|
+
background: transparent;
|
|
45
|
+
color: inherit;
|
|
46
|
+
padding: 0;
|
|
47
|
+
text-decoration: none;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.zx-app-rail__item:hover,
|
|
51
|
+
.zx-app-rail__item:focus-visible,
|
|
52
|
+
.zx-app-rail__item[aria-expanded="true"],
|
|
53
|
+
.zx-app-rail__item[data-active-descendant="true"],
|
|
54
|
+
.zx-app-rail__item[aria-current="page"] {
|
|
55
|
+
background: var(--zx-color-bg-hover);
|
|
56
|
+
color: var(--zx-color-accent);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.zx-app-rail__item[aria-disabled="true"],
|
|
60
|
+
.zx-app-rail__flyout-item[aria-disabled="true"] {
|
|
61
|
+
color: var(--zx-color-text-muted);
|
|
62
|
+
cursor: not-allowed;
|
|
63
|
+
opacity: .65;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.zx-app-rail__item-label { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); }
|
|
67
|
+
.zx-app-rail__item-icon { display: inline-flex; }
|
|
68
|
+
.zx-app-rail__item-badge {
|
|
69
|
+
position: absolute;
|
|
70
|
+
inset-inline-end: 1px;
|
|
71
|
+
inset-block-start: 1px;
|
|
72
|
+
min-inline-size: 15px;
|
|
73
|
+
border-radius: var(--zx-radius-full);
|
|
74
|
+
background: var(--zx-color-accent);
|
|
75
|
+
padding-inline: 3px;
|
|
76
|
+
color: var(--zx-color-on-accent);
|
|
77
|
+
font-size: 10px;
|
|
78
|
+
line-height: 15px;
|
|
79
|
+
text-align: center;
|
|
80
|
+
}
|
|
81
|
+
.zx-app-rail__item-disclosure { position: absolute; inset-inline-end: 1px; inset-block-end: 1px; display: inline-flex; }
|
|
82
|
+
|
|
83
|
+
.zx-app-rail__popover { min-inline-size: 220px; padding: var(--zx-space-2); }
|
|
84
|
+
.zx-app-rail__flyout-title { padding: var(--zx-space-2) var(--zx-space-3); color: var(--zx-color-text-muted); font-size: var(--zx-text-xs); font-weight: 700; }
|
|
85
|
+
.zx-app-rail__flyout-list { display: grid; gap: var(--zx-space-1); margin: 0; padding: 0; list-style: none; }
|
|
86
|
+
.zx-app-rail__flyout-item {
|
|
87
|
+
display: grid;
|
|
88
|
+
grid-template-columns: auto minmax(0, 1fr) auto auto;
|
|
89
|
+
align-items: center;
|
|
90
|
+
gap: var(--zx-space-2);
|
|
91
|
+
inline-size: 100%;
|
|
92
|
+
min-block-size: var(--zx-control-height);
|
|
93
|
+
border-color: transparent;
|
|
94
|
+
border-radius: var(--zx-radius-md);
|
|
95
|
+
background: transparent;
|
|
96
|
+
padding-inline: var(--zx-control-pad-x);
|
|
97
|
+
color: inherit;
|
|
98
|
+
text-align: start;
|
|
99
|
+
text-decoration: none;
|
|
100
|
+
}
|
|
101
|
+
.zx-app-rail__flyout-item:hover,
|
|
102
|
+
.zx-app-rail__flyout-item:focus-visible,
|
|
103
|
+
.zx-app-rail__flyout-item[aria-current="page"] { background: var(--zx-color-bg-hover); color: var(--zx-color-accent); }
|
|
104
|
+
.zx-app-rail__flyout-item-icon { display: inline-flex; grid-column: 1; }
|
|
105
|
+
.zx-app-rail__flyout-item-label { grid-column: 2; }
|
|
106
|
+
.zx-app-rail__flyout-item-badge { grid-column: 3; color: var(--zx-color-text-muted); font-size: var(--zx-text-xs); }
|
|
107
|
+
.zx-app-rail__flyout-item-disclosure { display: inline-flex; grid-column: 4; color: var(--zx-color-text-muted); }
|