@zeyos/zx 2.3.0 → 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.
Files changed (105) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/DESIGN-SYSTEM.md +136 -0
  3. package/MIGRATION.md +155 -4
  4. package/README.md +24 -13
  5. package/dist/types/compat/map/data.d.ts +3 -2
  6. package/dist/types/components/account-menu/account-menu.d.ts +144 -0
  7. package/dist/types/components/app-rail/app-rail.d.ts +250 -0
  8. package/dist/types/components/app-sidebar/app-sidebar.d.ts +169 -0
  9. package/dist/types/components/avatar/avatar.d.ts +100 -0
  10. package/dist/types/components/chart/chart.d.ts +321 -0
  11. package/dist/types/components/date-picker/month-picker.d.ts +6 -0
  12. package/dist/types/components/dialog/dialog.d.ts +9 -0
  13. package/dist/types/components/dock/dock.d.ts +495 -0
  14. package/dist/types/components/dropdown/dropdown.d.ts +2 -2
  15. package/dist/types/components/field/field.d.ts +14 -0
  16. package/dist/types/components/field-upload/field-upload.d.ts +37 -4
  17. package/dist/types/components/form/form.d.ts +25 -3
  18. package/dist/types/components/grid/grid.d.ts +172 -0
  19. package/dist/types/components/launcher/launcher.d.ts +300 -0
  20. package/dist/types/components/layout/layout.d.ts +4 -4
  21. package/dist/types/components/menu-button/menu-button.d.ts +10 -2
  22. package/dist/types/components/modal/modal.d.ts +25 -0
  23. package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +7 -1
  24. package/dist/types/components/questionnaire/items.d.ts +327 -0
  25. package/dist/types/components/questionnaire/questionnaire.d.ts +449 -0
  26. package/dist/types/components/sheet/sheet-stack.d.ts +167 -0
  27. package/dist/types/components/sheet/sheet.d.ts +294 -0
  28. package/dist/types/components/split-view/split-view.d.ts +2 -21
  29. package/dist/types/components/table/table.d.ts +219 -12
  30. package/dist/types/components/tooltip/tooltip.d.ts +2 -2
  31. package/dist/types/components/value-list/value-list.d.ts +24 -3
  32. package/dist/types/core/dom.d.ts +114 -0
  33. package/dist/types/core/drag-axis.d.ts +111 -0
  34. package/dist/types/core/position.d.ts +4 -2
  35. package/dist/types/index.d.ts +12 -2
  36. package/dist/zx-compat.esm.js +641 -277
  37. package/dist/zx-compat.esm.min.js +1 -1
  38. package/dist/zx-compat.global.js +7609 -2236
  39. package/dist/zx-compat.global.min.js +3 -3
  40. package/dist/zx-zeyos.esm.js +546 -87
  41. package/dist/zx-zeyos.esm.min.js +1 -1
  42. package/dist/zx.css +1452 -101
  43. package/dist/zx.esm.js +7952 -2592
  44. package/dist/zx.esm.min.js +3 -3
  45. package/dist/zx.global.js +7952 -2592
  46. package/dist/zx.global.min.js +3 -3
  47. package/dist/zx.min.css +1 -1
  48. package/docs/api.json +2276 -337
  49. package/docs/llms.md +426 -30
  50. package/docs/llms.txt +21 -9
  51. package/package.json +4 -2
  52. package/src/compat/globals.js +3 -1
  53. package/src/compat/map/data.js +31 -9
  54. package/src/components/account-menu/account-menu.css +70 -0
  55. package/src/components/account-menu/account-menu.js +208 -0
  56. package/src/components/app-rail/app-rail.css +107 -0
  57. package/src/components/app-rail/app-rail.js +668 -0
  58. package/src/components/app-sidebar/app-sidebar.css +68 -0
  59. package/src/components/app-sidebar/app-sidebar.js +388 -0
  60. package/src/components/avatar/avatar.css +61 -0
  61. package/src/components/avatar/avatar.js +222 -0
  62. package/src/components/button/button.css +0 -1
  63. package/src/components/chart/chart.css +61 -0
  64. package/src/components/chart/chart.js +518 -0
  65. package/src/components/date-picker/month-picker.js +9 -0
  66. package/src/components/dialog/dialog.js +11 -4
  67. package/src/components/dock/dock.css +228 -0
  68. package/src/components/dock/dock.js +977 -0
  69. package/src/components/dropdown/dropdown.js +1 -1
  70. package/src/components/field/field.js +2 -0
  71. package/src/components/field-upload/field-upload.js +9 -4
  72. package/src/components/form/form.js +6 -3
  73. package/src/components/grid/grid.css +6 -0
  74. package/src/components/grid/grid.js +213 -0
  75. package/src/components/launcher/launcher.css +100 -0
  76. package/src/components/launcher/launcher.js +557 -0
  77. package/src/components/layout/layout.js +2 -2
  78. package/src/components/menu-button/menu-button.js +27 -2
  79. package/src/components/modal/modal.css +5 -1
  80. package/src/components/modal/modal.js +37 -2
  81. package/src/components/multi-value-editor/multi-value-editor.js +2 -1
  82. package/src/components/questionnaire/items.js +370 -0
  83. package/src/components/questionnaire/questionnaire.css +387 -0
  84. package/src/components/questionnaire/questionnaire.js +1033 -0
  85. package/src/components/select/select.css +0 -1
  86. package/src/components/sheet/sheet-stack.js +304 -0
  87. package/src/components/sheet/sheet.css +351 -0
  88. package/src/components/sheet/sheet.js +551 -0
  89. package/src/components/split-view/split-view.js +76 -161
  90. package/src/components/table/table.css +43 -0
  91. package/src/components/table/table.js +523 -45
  92. package/src/components/tooltip/tooltip.js +5 -2
  93. package/src/components/value-list/value-list.js +6 -3
  94. package/src/core/dom.js +302 -0
  95. package/src/core/drag-axis.js +203 -0
  96. package/src/core/position.js +68 -22
  97. package/src/index.js +19 -2
  98. package/styles/tokens/dark.css +2 -3
  99. package/styles/tokens/global.css +5 -5
  100. package/styles/tokens/semantic.css +5 -3
  101. package/styles/tokens/themes.css +1 -1
  102. package/styles/zx.css +10 -1
  103. package/dist/types/components/permission/permission.d.ts +0 -96
  104. package/src/components/permission/permission.css +0 -47
  105. package/src/components/permission/permission.js +0 -201
package/docs/llms.txt CHANGED
@@ -1,7 +1,7 @@
1
- # Zx
1
+ # Xenon Design System (Zx)
2
2
 
3
- > Zx is the dependency-free, vanilla-JavaScript UI component library for ZeyOS business
4
- > applications. ES2022 modules, WAI-ARIA-accessible native
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 30+ components plus the core helpers.
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`, `Toggle`, `Search`, `Select` (APG combobox; `filter: false|'local'|async`), `Checklist`, `TagPicker`, `NumberField`, `Rating`, `Slider`, `copyButton()`/`CopyInput`, `DatePicker`, `MonthPicker`, `TimePicker`, `Datebox`/`DateTimeBox`, `DateRangePicker`/`DateRangeBox`, `Timebox`.
26
- - Overlays: `Message` (toasts/inline/progress), `Modal`, `Dialog` (+ `alert`/`confirm`/`prompt` Promises), `Dropdown`, `MenuButton`, `ContextMenu` (right-click; `selector` for per-row menus), `Tooltip`/`tooltip()`/`describe()`.
27
- - Data: `Table` (sortable, selectable, sticky header, opt-in inline cell/row editing via `editMode`), `DataFilter`, `Pagination` (+ pure `paginationRange()`), `TreeView` (APG tree), `Finder` (Miller columns).
28
- - Forms: `Form`, `Fieldset`, `Field` (type registry), `ValueList`, `MultiValueEditor`, `FieldUpload`, `Permission`.
29
- - Layout: `stack()`/`grid()`/`aspect()` (spacing, an intrinsically reflowing grid, a fixed-ratio box), `Groupbox`, `Panel` (header/footer action buttons), `MasterPanel`, `Tabbox` (`variant: 'divided'|'bracket'|'line'|'segmented'`), `NavigationBar`, `Toolbar` (APG toolbar with overflow menu), `SplitView`, `Stepper`, `Breadcrumb`, `emptyState()`.
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": "2.3.0",
4
- "description": "Dependency-free, accessible UI component library for ZeyOS business applications.",
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"
@@ -126,7 +126,9 @@ export function parse(source) {
126
126
  */
127
127
  export function installGlobals(host = globalThis) {
128
128
  installElementStorage(host);
129
- host.__ = parse;
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', {
@@ -2,7 +2,7 @@ import {
2
2
  Checklist as ZxChecklist,
3
3
  DataFilter as ZxDataFilter,
4
4
  Http,
5
- Permission as ZxPermission,
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
- /** Legacy permission selector. */
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 = new ZxPermission(display, {
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
- groupsValueKey: options.groupsValueKey ?? options.elementIndex ?? 'ID',
89
- groupsLabelKey: options.groupsLabelKey ?? options.elementLabel ?? 'name'
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.get(); }
94
- /** @param {unknown} value @param {boolean} [silent=false] @returns {this} */ set(value, silent = false) { this._zx.set(value, { silent }); return 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); }