@zeyos/zx 3.0.0 → 4.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 (88) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/DESIGN-SYSTEM.md +28 -68
  3. package/README.md +16 -37
  4. package/dist/types/components/app-sidebar/app-sidebar.d.ts +106 -32
  5. package/dist/types/components/card/card.d.ts +128 -0
  6. package/dist/types/components/chart/chart.d.ts +1 -0
  7. package/dist/types/components/launcher/launcher.d.ts +65 -6
  8. package/dist/types/components/select/select.d.ts +51 -0
  9. package/dist/types/components/table/table.d.ts +4 -3
  10. package/dist/types/core/dom.d.ts +8 -0
  11. package/dist/types/index.d.ts +2 -1
  12. package/dist/types/{components/app-rail → internal}/app-rail.d.ts +34 -19
  13. package/dist/types/zeyos/icons.d.ts +5 -0
  14. package/dist/types/zeyos/index.d.ts +2 -1
  15. package/dist/types/zeyos/launcher.d.ts +147 -0
  16. package/dist/types/zeyos/select.d.ts +39 -0
  17. package/dist/zx-zeyos.esm.js +1324 -46
  18. package/dist/zx-zeyos.esm.min.js +1 -1
  19. package/dist/zx.css +400 -12
  20. package/dist/zx.esm.js +1059 -189
  21. package/dist/zx.esm.min.js +3 -3
  22. package/dist/zx.global.js +1059 -189
  23. package/dist/zx.global.min.js +3 -3
  24. package/dist/zx.min.css +1 -1
  25. package/docs/api.json +258 -145
  26. package/docs/llms.md +93 -112
  27. package/docs/llms.txt +16 -15
  28. package/package.json +8 -17
  29. package/src/components/app-sidebar/app-sidebar.css +12 -0
  30. package/src/components/app-sidebar/app-sidebar.js +310 -110
  31. package/src/components/card/card.css +149 -0
  32. package/src/components/card/card.js +285 -0
  33. package/src/components/chart/chart.js +8 -3
  34. package/src/components/grid/grid.js +10 -3
  35. package/src/components/launcher/launcher.css +113 -3
  36. package/src/components/launcher/launcher.js +234 -26
  37. package/src/components/select/field-adapter.js +9 -4
  38. package/src/components/select/select.css +144 -10
  39. package/src/components/select/select.js +259 -27
  40. package/src/components/table/table.js +8 -7
  41. package/src/core/dom.js +20 -0
  42. package/src/index.js +2 -1
  43. package/src/{components/app-rail → internal}/app-rail.js +80 -30
  44. package/src/zeyos/icons.js +8 -3
  45. package/src/zeyos/index.js +7 -1
  46. package/src/zeyos/launcher.js +338 -0
  47. package/src/zeyos/modules.js +1 -0
  48. package/src/zeyos/select.js +115 -1
  49. package/styles/tokens/global.css +1 -1
  50. package/styles/tokens/modules.css +1 -0
  51. package/styles/zx.css +1 -0
  52. package/MIGRATION.md +0 -749
  53. package/dist/types/compat/base.d.ts +0 -156
  54. package/dist/types/compat/globals.d.ts +0 -20
  55. package/dist/types/compat/index.d.ts +0 -128
  56. package/dist/types/compat/map/basic.d.ts +0 -175
  57. package/dist/types/compat/map/data.d.ts +0 -103
  58. package/dist/types/compat/map/date-time.d.ts +0 -80
  59. package/dist/types/compat/map/factory.d.ts +0 -9
  60. package/dist/types/compat/map/form.d.ts +0 -98
  61. package/dist/types/compat/map/helpers.d.ts +0 -12
  62. package/dist/types/compat/map/http.d.ts +0 -36
  63. package/dist/types/compat/map/layout.d.ts +0 -106
  64. package/dist/types/compat/map/options.d.ts +0 -80
  65. package/dist/types/compat/map/select.d.ts +0 -95
  66. package/dist/types/compat/map/stubs.d.ts +0 -32
  67. package/dist/types/compat/map/utilities.d.ts +0 -37
  68. package/dist/types/compat-entry.d.ts +0 -5
  69. package/dist/zx-compat.esm.js +0 -17848
  70. package/dist/zx-compat.esm.min.js +0 -1
  71. package/dist/zx-compat.global.js +0 -29008
  72. package/dist/zx-compat.global.min.js +0 -3
  73. package/src/compat/base.js +0 -291
  74. package/src/compat/globals.js +0 -167
  75. package/src/compat/index.js +0 -84
  76. package/src/compat/map/basic.js +0 -464
  77. package/src/compat/map/data.js +0 -254
  78. package/src/compat/map/date-time.js +0 -177
  79. package/src/compat/map/factory.js +0 -45
  80. package/src/compat/map/form.js +0 -330
  81. package/src/compat/map/helpers.js +0 -46
  82. package/src/compat/map/http.js +0 -117
  83. package/src/compat/map/layout.js +0 -256
  84. package/src/compat/map/options.js +0 -267
  85. package/src/compat/map/select.js +0 -198
  86. package/src/compat/map/stubs.js +0 -26
  87. package/src/compat/map/utilities.js +0 -113
  88. package/src/compat-entry.js +0 -11
package/dist/zx.global.js CHANGED
@@ -21,10 +21,10 @@ var zx = (() => {
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AccountMenu: () => AccountMenu,
24
- AppRail: () => AppRail,
25
24
  AppSidebar: () => AppSidebar,
26
25
  Avatar: () => Avatar,
27
26
  Breadcrumb: () => Breadcrumb,
27
+ Card: () => Card,
28
28
  Chart: () => Chart,
29
29
  ChartJsAdapter: () => ChartJsAdapter,
30
30
  CheckButton: () => CheckButton,
@@ -468,6 +468,18 @@ var zx = (() => {
468
468
  return element;
469
469
  }
470
470
  var __ = compactElement;
471
+ function safeHref(value) {
472
+ if (typeof value !== "string") return null;
473
+ const href = value.trim();
474
+ if (!href) return null;
475
+ try {
476
+ const protocol = new URL(href, globalThis.location?.href ?? "https://zx.invalid/").protocol;
477
+ if (["javascript:", "data:", "vbscript:"].includes(protocol.toLowerCase())) return null;
478
+ } catch {
479
+ return null;
480
+ }
481
+ return href;
482
+ }
471
483
  function applyCompactProperties(element, properties) {
472
484
  for (const name in properties ?? {}) {
473
485
  const value = properties[name];
@@ -6818,7 +6830,18 @@ var zx = (() => {
6818
6830
  "priority.high",
6819
6831
  "priority.highest"
6820
6832
  ];
6821
- var PRIORITY_FALLBACKS = ["Lowest", "Low", "Normal", "High", "Highest"];
6833
+ var PRIORITY_FALLBACKS = ["Lowest", "Low", "Medium", "High", "Highest"];
6834
+ var DEFAULT_STATUSES = Object.freeze([
6835
+ ["backlog", "status.backlog", "Backlog", "dotted", "muted"],
6836
+ ["todo", "status.todo", "Todo", "ring", "neutral"],
6837
+ ["in-progress", "status.inProgress", "In Progress", "progress", "warning"],
6838
+ ["in-review", "status.inReview", "In Review", "review", "info"],
6839
+ ["ready", "status.ready", "Ready to Merge", "ready", "accent"],
6840
+ ["waiting", "status.waiting", "Waiting for feedback", "waiting", "warning"],
6841
+ ["done", "status.done", "Done", "done", "info"],
6842
+ ["canceled", "status.canceled", "Canceled", "canceled", "muted"],
6843
+ ["duplicate", "status.duplicate", "Duplicate", "duplicate", "muted"]
6844
+ ]);
6822
6845
  var Select = class _Select extends Component {
6823
6846
  static cssName = "select";
6824
6847
  /** @type {SelectOptions} */
@@ -6829,6 +6852,10 @@ var zx = (() => {
6829
6852
  labelKey: "name",
6830
6853
  renderItem: null,
6831
6854
  renderValue: null,
6855
+ renderValueAdornment: null,
6856
+ renderNone: null,
6857
+ noneLabel: null,
6858
+ actions: [],
6832
6859
  value: null,
6833
6860
  disabled: false,
6834
6861
  placeholder: "",
@@ -6846,6 +6873,7 @@ var zx = (() => {
6846
6873
  this._visibleItems = [];
6847
6874
  this._fixedItems = [];
6848
6875
  this._visibleFixed = [];
6876
+ this._actions = [];
6849
6877
  this._selected = null;
6850
6878
  this._optionEntries = [];
6851
6879
  this._activeIndex = -1;
@@ -6884,6 +6912,12 @@ var zx = (() => {
6884
6912
  placeholder: String(this.options.placeholder ?? ""),
6885
6913
  readOnly: !this.options.filter
6886
6914
  });
6915
+ const valueAdornment = h("span", {
6916
+ ref: "valueAdornment",
6917
+ class: "zx-select__value-adornment",
6918
+ ariaHidden: "true",
6919
+ hidden: true
6920
+ });
6887
6921
  const clear = h("button", {
6888
6922
  ref: "clear",
6889
6923
  class: "zx-icon-btn zx-select__clear",
@@ -6909,6 +6943,7 @@ var zx = (() => {
6909
6943
  const control = h(
6910
6944
  "div",
6911
6945
  { ref: "control", class: "zx-select__control" },
6946
+ valueAdornment,
6912
6947
  input,
6913
6948
  clear,
6914
6949
  loading,
@@ -6931,9 +6966,10 @@ var zx = (() => {
6931
6966
  this._visibleItems = this._items.slice();
6932
6967
  this._fixedItems = Array.isArray(this.options.fixedItems) ? this.options.fixedItems.slice() : [];
6933
6968
  this._visibleFixed = this._fixedItems.slice();
6969
+ this._actions = normalizeSelectActions(this.options.actions);
6934
6970
  this._disabled = Boolean(this.options.disabled);
6935
6971
  this._handleTypeahead = typeahead(
6936
- () => this._optionEntries.filter((entry) => !entry.clear).map((entry) => entry.element),
6972
+ () => this._optionEntries.filter((entry) => !entry.clear && !entry.action).map((entry) => entry.element),
6937
6973
  (_option, index) => {
6938
6974
  if (!this._open) this.open();
6939
6975
  this._setActive(this._itemEntryIndex(index));
@@ -7028,6 +7064,13 @@ var zx = (() => {
7028
7064
  this._position?.update();
7029
7065
  return this;
7030
7066
  }
7067
+ /** Replaces command rows without changing the selected value. @param {SelectAction[]} actions @returns {this} */
7068
+ setActions(actions2) {
7069
+ this._actions = normalizeSelectActions(actions2);
7070
+ this._renderOptions();
7071
+ this._position?.update();
7072
+ return this;
7073
+ }
7031
7074
  /**
7032
7075
  * Restores the configured initial value.
7033
7076
  * @param {{silent?: boolean}} [options={}] Reset behavior.
@@ -7112,26 +7155,51 @@ var zx = (() => {
7112
7155
  value: null,
7113
7156
  items: [],
7114
7157
  renderItem(item) {
7115
- return h(
7116
- "span",
7117
- { class: "zx-select__priority" },
7118
- h("span", {
7119
- class: "zx-select__priority-icon",
7120
- dataset: { level: item.level },
7121
- ariaHidden: "true"
7122
- }),
7123
- h("span", {}, String(item.name))
7124
- );
7158
+ return priorityContent(item, true);
7159
+ },
7160
+ renderValueAdornment(item) {
7161
+ return priorityIndicator(item.level);
7125
7162
  }
7126
7163
  });
7127
- select.setItems(PRIORITY_KEYS.map((key, level) => ({
7128
- ID: level,
7129
- name: select._message(key, PRIORITY_FALLBACKS[level]),
7130
- level
7131
- })));
7164
+ select.setItems(PRIORITY_KEYS.map((key, level) => {
7165
+ let name = level === 2 ? select.msg("priority.medium") : key;
7166
+ if (name === "priority.medium") name = select._message(key, PRIORITY_FALLBACKS[level]);
7167
+ else if (level !== 2) name = select._message(key, PRIORITY_FALLBACKS[level]);
7168
+ return { ID: level, name, level };
7169
+ }));
7132
7170
  select.set(requestedValue, { silent: true });
7133
7171
  return select;
7134
7172
  }
7173
+ /**
7174
+ * Creates a status picker with shape- and color-distinct workflow indicators.
7175
+ * @param {Element|string|null} target Component target.
7176
+ * @param {SelectOptions & {items?: Array<Record<string, any>>}} [options={}] Select overrides and status items.
7177
+ * @returns {Select}
7178
+ */
7179
+ static status(target, options = {}) {
7180
+ const { items = null, value = null, ...rest } = options;
7181
+ const select = new _Select(target, {
7182
+ ...rest,
7183
+ value: null,
7184
+ items: [],
7185
+ renderItem(item) {
7186
+ return statusContent(item);
7187
+ },
7188
+ renderValueAdornment(item) {
7189
+ return statusIndicator(item);
7190
+ }
7191
+ });
7192
+ const statuses = Array.isArray(items) ? items.map(normalizeStatusItem).filter(Boolean) : DEFAULT_STATUSES.map(([ID, key, fallback, indicator, tone], index) => ({
7193
+ ID,
7194
+ name: select._message(key, fallback),
7195
+ indicator,
7196
+ tone,
7197
+ shortcut: String(index + 1)
7198
+ }));
7199
+ select.setItems(statuses);
7200
+ select.set(value, { silent: true });
7201
+ return select;
7202
+ }
7135
7203
  /**
7136
7204
  * Creates the record-permission picker: the fixed Private and Public choices above the groups a
7137
7205
  * record can be shared with. The value is the tri-state ZeyOS stores — `false` for private,
@@ -7144,12 +7212,21 @@ var zx = (() => {
7144
7212
  * @returns {Select}
7145
7213
  */
7146
7214
  static permission(target, options = {}) {
7147
- const { groups = [], value = true, valueKey = "ID", labelKey = "name", ...rest } = options;
7215
+ const {
7216
+ groups = [],
7217
+ value = true,
7218
+ valueKey = "ID",
7219
+ labelKey = "name",
7220
+ filter = "local",
7221
+ ...rest
7222
+ } = options;
7148
7223
  const loader = typeof groups === "function" ? groups : null;
7149
7224
  const shareable = loader ? [] : groups;
7150
7225
  const choices = [{ ID: false, name: "Private" }, { ID: true, name: "Public" }];
7151
7226
  const select = new _Select(target, {
7152
- filter: loader ?? "local",
7227
+ // An async group loader is the data source and cannot be disconnected by an incidental
7228
+ // `filter` override. Array-backed groups still honor local/disabled filtering.
7229
+ filter: loader ?? filter,
7153
7230
  ...rest,
7154
7231
  value: null,
7155
7232
  items: shareable,
@@ -7185,6 +7262,18 @@ var zx = (() => {
7185
7262
  /** @param {KeyboardEvent} event @returns {void} */
7186
7263
  _onKeydown(event) {
7187
7264
  if (this._disabled) return;
7265
+ if (this._open && isPrintable(event)) {
7266
+ const shortcut = this._optionEntries.findIndex((entry) => !entry.clear && !entry.action && String(
7267
+ /** @type {Record<string, any>|null} */
7268
+ entry.item?.shortcut ?? ""
7269
+ ) === event.key);
7270
+ if (shortcut >= 0) {
7271
+ event.preventDefault();
7272
+ this._setActive(shortcut);
7273
+ this._chooseEntry(this._optionEntries[shortcut]);
7274
+ return;
7275
+ }
7276
+ }
7188
7277
  if (event.key === "ArrowDown") {
7189
7278
  event.preventDefault();
7190
7279
  if (!this._open) this.open();
@@ -7232,6 +7321,7 @@ var zx = (() => {
7232
7321
  /** @type {HTMLInputElement} */
7233
7322
  this.refs.input.value
7234
7323
  );
7324
+ this._syncValueAdornment();
7235
7325
  if (!this._open) this.open();
7236
7326
  if (this.options.filter === "local") {
7237
7327
  this._visibleFixed = this._matchFixed(query);
@@ -7255,6 +7345,22 @@ var zx = (() => {
7255
7345
  /** @param {SelectOptionEntry|undefined} entry @returns {void} */
7256
7346
  _chooseEntry(entry) {
7257
7347
  if (!entry) return;
7348
+ if (entry.action) {
7349
+ const detail = {
7350
+ id: entry.action.id,
7351
+ action: entry.action,
7352
+ query: (
7353
+ /** @type {HTMLInputElement} */
7354
+ this.refs.input.value
7355
+ ),
7356
+ select: this
7357
+ };
7358
+ const action = this.emit("action", detail);
7359
+ if (!action.defaultPrevented) entry.action.invoke?.(detail);
7360
+ this.close();
7361
+ this.focus();
7362
+ return;
7363
+ }
7258
7364
  this.set(entry.clear ? null : entry.item);
7259
7365
  this.close();
7260
7366
  this.focus();
@@ -7288,7 +7394,7 @@ var zx = (() => {
7288
7394
  this._optionEntries = [];
7289
7395
  let lastGroup = /* @__PURE__ */ Symbol("initial-group");
7290
7396
  if (this.options.clearable) {
7291
- this._appendOption(null, true, this.options.placeholder || this._message("select.none", "No selection"));
7397
+ this._appendOption(null, true, this.options.noneLabel ?? this._message("select.none", "No selection"));
7292
7398
  }
7293
7399
  for (const item of this._visibleFixed) this._appendOption(item, false);
7294
7400
  if (this._visibleFixed.length > 0 && this._visibleItems.length > 0 && !this.options.groupKey) {
@@ -7307,7 +7413,23 @@ var zx = (() => {
7307
7413
  }
7308
7414
  this._appendOption(item, false);
7309
7415
  }
7310
- if (this._visibleItems.length === 0 && this._visibleFixed.length === 0 && !this._loading) {
7416
+ if (this._actions.length > 0) {
7417
+ if (this._visibleFixed.length > 0 || this._visibleItems.length > 0 || this.options.clearable) {
7418
+ list.append(h("div", { class: "zx-select__separator", role: "presentation" }));
7419
+ }
7420
+ let actionGroup = /* @__PURE__ */ Symbol("initial-action-group");
7421
+ for (const action of this._actions) {
7422
+ if (action.group && action.group !== actionGroup) {
7423
+ list.append(h("div", {
7424
+ class: "zx-select__group",
7425
+ role: "presentation"
7426
+ }, action.group));
7427
+ actionGroup = action.group;
7428
+ }
7429
+ this._appendAction(action);
7430
+ }
7431
+ }
7432
+ if (this._visibleItems.length === 0 && this._visibleFixed.length === 0 && this._actions.length === 0 && !this._loading) {
7311
7433
  list.append(h("div", {
7312
7434
  class: "zx-select__empty",
7313
7435
  role: "status"
@@ -7319,7 +7441,7 @@ var zx = (() => {
7319
7441
  _appendOption(item, clear, label) {
7320
7442
  const selected = clear ? this._selected === null : this._selected !== null && Object.is(this._itemValue(item), this.value);
7321
7443
  const index = this._optionEntries.length;
7322
- const content = clear ? String(label ?? "") : this._renderItem(item);
7444
+ const content = clear && typeof this.options.renderNone === "function" ? normalizeRenderedContent(this.options.renderNone(String(label ?? ""))) : clear ? String(label ?? "") : this._renderItem(item);
7323
7445
  const option = h("div", {
7324
7446
  class: "zx-select__option",
7325
7447
  id: uid("zx-select-option"),
@@ -7329,16 +7451,37 @@ var zx = (() => {
7329
7451
  }, h("span", { class: "zx-select__option-content" }, content));
7330
7452
  if (selected) option.append(icon("check", { size: 15 }));
7331
7453
  this.refs.list.append(option);
7332
- this._optionEntries.push({ item, clear, element: option });
7454
+ this._optionEntries.push({ item, clear, action: null, element: option });
7455
+ }
7456
+ /** @param {SelectAction} action @returns {void} */
7457
+ _appendAction(action) {
7458
+ const index = this._optionEntries.length;
7459
+ const visual = selectActionIcon(action.icon);
7460
+ const option = h("div", {
7461
+ class: "zx-select__option zx-select__action",
7462
+ id: uid("zx-select-option"),
7463
+ role: "option",
7464
+ ariaSelected: "false",
7465
+ dataset: { index, kind: "action" }
7466
+ }, h(
7467
+ "span",
7468
+ { class: "zx-select__option-content zx-select__action-content" },
7469
+ visual ? h("span", { class: "zx-select__action-icon", ariaHidden: "true" }, visual) : null,
7470
+ h(
7471
+ "span",
7472
+ { class: "zx-select__action-copy" },
7473
+ h("span", { class: "zx-select__action-label" }, action.label),
7474
+ action.description ? h("span", { class: "zx-select__action-description" }, action.description) : null
7475
+ )
7476
+ ));
7477
+ this.refs.list.append(option);
7478
+ this._optionEntries.push({ item: null, clear: false, action, element: option });
7333
7479
  }
7334
7480
  /** @param {SelectItem|null} item @returns {Node|string} */
7335
7481
  _renderItem(item) {
7336
7482
  if (typeof this.options.renderItem === "function") {
7337
7483
  const rendered = this.options.renderItem(item);
7338
- return rendered && typeof rendered === "object" && "nodeType" in rendered ? (
7339
- /** @type {Node} */
7340
- rendered
7341
- ) : String(rendered ?? "");
7484
+ return normalizeRenderedContent(rendered);
7342
7485
  }
7343
7486
  return this._itemLabel(item);
7344
7487
  }
@@ -7409,9 +7552,29 @@ var zx = (() => {
7409
7552
  this.refs.input
7410
7553
  );
7411
7554
  input.value = this._selected === null ? "" : typeof this.options.renderValue === "function" ? String(this.options.renderValue(this._selected) ?? "") : this._itemLabel(this._selected);
7555
+ this._syncValueAdornment();
7412
7556
  this.refs.clear.hidden = !this.options.clearable || this._selected === null;
7413
7557
  }
7414
7558
  /** @returns {void} */
7559
+ _syncValueAdornment() {
7560
+ const host = this.refs.valueAdornment;
7561
+ if (!host) return;
7562
+ const selectedLabel = this._selected === null ? "" : typeof this.options.renderValue === "function" ? String(this.options.renderValue(this._selected) ?? "") : this._itemLabel(this._selected);
7563
+ const query = String(
7564
+ /** @type {HTMLInputElement} */
7565
+ this.refs.input?.value ?? ""
7566
+ );
7567
+ if (this._selected === null || typeof this.options.renderValueAdornment !== "function" || Boolean(this.options.filter && query !== selectedLabel)) {
7568
+ host.replaceChildren();
7569
+ host.hidden = true;
7570
+ return;
7571
+ }
7572
+ const rendered = this.options.renderValueAdornment(this._selected);
7573
+ const content = normalizeRenderedContent(rendered);
7574
+ host.replaceChildren(content instanceof Node ? content : document.createTextNode(content));
7575
+ host.hidden = rendered == null || typeof content === "string" && content === "";
7576
+ }
7577
+ /** @returns {void} */
7415
7578
  _syncDisabled() {
7416
7579
  const input = (
7417
7580
  /** @type {HTMLInputElement} */
@@ -7488,6 +7651,82 @@ var zx = (() => {
7488
7651
  return message === key ? fallback : message;
7489
7652
  }
7490
7653
  };
7654
+ function normalizeRenderedContent(rendered) {
7655
+ return rendered && typeof rendered === "object" && "nodeType" in rendered ? (
7656
+ /** @type {Node} */
7657
+ rendered
7658
+ ) : String(rendered ?? "");
7659
+ }
7660
+ function normalizeSelectActions(actions2) {
7661
+ if (!Array.isArray(actions2)) return [];
7662
+ return actions2.filter((action) => action && typeof action === "object" && action.id != null && action.label != null).map((action) => ({
7663
+ ...action,
7664
+ label: String(action.label),
7665
+ description: action.description == null ? void 0 : String(action.description),
7666
+ group: action.group == null ? void 0 : String(action.group)
7667
+ }));
7668
+ }
7669
+ function selectActionIcon(visual) {
7670
+ if (typeof visual === "string") return icon(visual);
7671
+ if (visual instanceof Node) return visual.cloneNode(true);
7672
+ return null;
7673
+ }
7674
+ function priorityContent(item, label) {
7675
+ return h(
7676
+ "span",
7677
+ { class: "zx-select__priority" },
7678
+ priorityIndicator(item.level),
7679
+ label ? h("span", {}, String(item.name)) : null
7680
+ );
7681
+ }
7682
+ function priorityIndicator(level) {
7683
+ const normalized = Math.max(0, Math.min(4, Math.trunc(Number(level) || 0)));
7684
+ return h("span", {
7685
+ class: "zx-select__priority-icon",
7686
+ dataset: { level: normalized },
7687
+ ariaHidden: "true"
7688
+ }, Array.from({ length: 5 }, (_, index) => h("span", {
7689
+ dataset: { filled: String(index <= normalized) }
7690
+ })));
7691
+ }
7692
+ function normalizeStatusItem(input) {
7693
+ if (!input || typeof input !== "object" || input.ID == null || input.name == null) return null;
7694
+ const indicators = ["dotted", "ring", "progress", "review", "ready", "waiting", "done", "canceled", "duplicate"];
7695
+ const tones = ["muted", "neutral", "warning", "info", "accent", "success", "danger"];
7696
+ return {
7697
+ ...input,
7698
+ name: String(input.name),
7699
+ indicator: indicators.includes(String(input.indicator)) ? String(input.indicator) : "ring",
7700
+ tone: tones.includes(String(input.tone)) ? String(input.tone) : "muted",
7701
+ description: input.description == null ? "" : String(input.description),
7702
+ shortcut: input.shortcut == null ? "" : String(input.shortcut)
7703
+ };
7704
+ }
7705
+ function statusContent(item) {
7706
+ return h(
7707
+ "span",
7708
+ { class: "zx-select__status" },
7709
+ statusIndicator(item),
7710
+ h(
7711
+ "span",
7712
+ { class: "zx-select__status-copy" },
7713
+ h("span", { class: "zx-select__status-label" }, String(item.name)),
7714
+ item.description ? h("span", { class: "zx-select__status-description" }, item.description) : null
7715
+ ),
7716
+ item.shortcut ? h("kbd", { class: "zx-select__status-shortcut" }, item.shortcut) : null
7717
+ );
7718
+ }
7719
+ function statusIndicator(item) {
7720
+ const indicator = String(item.indicator ?? "ring");
7721
+ const marker = h("span", {
7722
+ class: "zx-select__status-indicator",
7723
+ dataset: { indicator, tone: String(item.tone ?? "muted") },
7724
+ ariaHidden: "true"
7725
+ });
7726
+ if (indicator === "done") marker.append(icon("check", { size: 8 }));
7727
+ if (indicator === "canceled") marker.append(icon("x", { size: 7 }));
7728
+ return marker;
7729
+ }
7491
7730
  function readItem(item, reader) {
7492
7731
  if (typeof reader === "function") return reader(item);
7493
7732
  if (item === null || typeof item !== "object") return item;
@@ -7500,10 +7739,11 @@ var zx = (() => {
7500
7739
  // src/components/select/field-adapter.js
7501
7740
  function registerSelectFieldAdapter() {
7502
7741
  Field.register("zxselect", (field, options) => {
7503
- const select = field.own(new Select(null, {
7504
- ...options.props ?? {},
7505
- value: options.value
7506
- }));
7742
+ const props = options.props ?? {};
7743
+ const preset = props.preset;
7744
+ const selectOptions = { ...props, value: options.value };
7745
+ delete selectOptions.preset;
7746
+ const select = field.own(preset === "priority" ? Select.priority(null, selectOptions) : preset === "status" ? Select.status(null, selectOptions) : new Select(null, selectOptions));
7507
7747
  field.listen(select.el, "change", (event) => event.stopImmediatePropagation());
7508
7748
  select.on("change", (event) => field.emit("change", { value: event.detail.value }));
7509
7749
  return {
@@ -11450,12 +11690,14 @@ var zx = (() => {
11450
11690
  }
11451
11691
  /** @returns {HTMLElement} Table root. */
11452
11692
  render() {
11693
+ const created = this.el === null;
11453
11694
  const root = (
11454
11695
  /** @type {HTMLElement} */
11455
11696
  this.el ?? h("div")
11456
11697
  );
11457
11698
  this.el = root;
11458
- this._originalChildren = this.el ? Array.from(this.el.childNodes) : null;
11699
+ this._createdRoot = created;
11700
+ this._original = created ? null : snapshotTarget(root);
11459
11701
  this._columns = Array.isArray(this.options.columns) ? this.options.columns.map((column) => ({ ...column })) : [];
11460
11702
  this._data = Array.isArray(this.options.data) ? [...this.options.data] : [];
11461
11703
  this._selected = /* @__PURE__ */ new Set();
@@ -11530,8 +11772,8 @@ var zx = (() => {
11530
11772
  }
11531
11773
  /**
11532
11774
  * Toggles a loading state. While loading, the body is dimmed, an indeterminate progress bar is
11533
- * shown, and `aria-busy` is set — a lightweight take on the busy/skeleton pattern used by
11534
- * enterprise tables (SAP UI5, Salesforce SLDS). Call `setLoading(false)` (or `setData`) when done.
11775
+ * shown, and `aria-busy` is set — a lightweight enterprise-table busy and skeleton pattern.
11776
+ * Call `setLoading(false)` (or `setData`) when done.
11535
11777
  * @param {boolean} [loading=true] Whether the table is loading.
11536
11778
  * @returns {this}
11537
11779
  */
@@ -12080,9 +12322,8 @@ var zx = (() => {
12080
12322
  this._teardownEdit();
12081
12323
  this._width?.destroy();
12082
12324
  this._width = null;
12083
- if (this.el) delete this.el.dataset.editMode;
12084
- if (!this._restored && this._originalChildren) {
12085
- this.el.replaceChildren(...this._originalChildren);
12325
+ if (!this._restored && !this._createdRoot) {
12326
+ restoreTarget(this.el, this._original);
12086
12327
  this._restored = true;
12087
12328
  }
12088
12329
  super.destroy();
@@ -15443,6 +15684,244 @@ var zx = (() => {
15443
15684
  ) : "start";
15444
15685
  }
15445
15686
 
15687
+ // src/components/card/card.js
15688
+ var Card = class extends Component {
15689
+ static cssName = "card";
15690
+ /** @type {Readonly<CardOptions>} */
15691
+ static defaults = {
15692
+ title: "",
15693
+ media: null,
15694
+ content: null,
15695
+ actions: [],
15696
+ footer: null,
15697
+ link: null,
15698
+ variant: "outlined",
15699
+ orientation: "vertical",
15700
+ headingLevel: 3
15701
+ };
15702
+ /**
15703
+ * Creates or enhances one card.
15704
+ * @param {Element|string|null} [target=null] Existing card container, selector, or null.
15705
+ * @param {CardOptions} [options={}] Card options.
15706
+ */
15707
+ constructor(target = null, options = {}) {
15708
+ super(target, options);
15709
+ }
15710
+ /** @returns {HTMLElement} */
15711
+ render() {
15712
+ const created = !this.el;
15713
+ const root = (
15714
+ /** @type {HTMLElement} */
15715
+ this.el ?? h("article")
15716
+ );
15717
+ this.el = root;
15718
+ this._createdRoot = created;
15719
+ this._original = created ? null : snapshotTarget(root);
15720
+ this._cleaned = false;
15721
+ this._title = "";
15722
+ this._link = null;
15723
+ this._actionAbort = null;
15724
+ this._linkAbort = null;
15725
+ const originalContent = created ? [] : Array.from(root.childNodes);
15726
+ try {
15727
+ const headingLevel = normalizeHeadingLevel(this.options.headingLevel);
15728
+ const headingId = uid("zx-card-heading");
15729
+ const heading = h(`h${headingLevel}`, {
15730
+ ref: "heading",
15731
+ class: "zx-card__heading",
15732
+ id: headingId
15733
+ });
15734
+ const media = h("div", { ref: "media", class: "zx-card__media", hidden: true });
15735
+ const actions2 = h("div", {
15736
+ ref: "actions",
15737
+ class: "zx-card__actions",
15738
+ role: "group",
15739
+ ariaLabel: "Card actions",
15740
+ hidden: true
15741
+ });
15742
+ const content = h("div", { ref: "content", class: "zx-card__content", hidden: true });
15743
+ const footer = h("footer", { ref: "footer", class: "zx-card__footer", hidden: true });
15744
+ const header = h("header", { ref: "header", class: "zx-card__header" }, heading, actions2);
15745
+ root.replaceChildren(h(
15746
+ "div",
15747
+ { class: "zx-card__layout" },
15748
+ media,
15749
+ h("div", { class: "zx-card__inner" }, header, content, footer)
15750
+ ));
15751
+ root.dataset.variant = normalizeVariant2(this.options.variant);
15752
+ root.dataset.orientation = normalizeOrientation(this.options.orientation);
15753
+ this.setTitle(this.options.title);
15754
+ this.setMedia(this.options.media);
15755
+ if (this.options.content !== null) this.setContent(this.options.content);
15756
+ else if (originalContent.length) {
15757
+ content.replaceChildren(...originalContent);
15758
+ content.hidden = false;
15759
+ }
15760
+ this.setActions(this.options.actions);
15761
+ this.setFooter(this.options.footer);
15762
+ this.setLink(this.options.link);
15763
+ return root;
15764
+ } catch (error) {
15765
+ this._actionAbort?.abort();
15766
+ this._linkAbort?.abort();
15767
+ if (!created) restoreTarget(root, this._original);
15768
+ throw error;
15769
+ }
15770
+ }
15771
+ /** Replaces the card title. @param {string} title Next title. @returns {this} */
15772
+ setTitle(title) {
15773
+ const next = String(title ?? "");
15774
+ if (this._link && !next.trim()) throw new TypeError("A linked Card requires a non-empty title");
15775
+ this._title = next;
15776
+ this._renderHeading();
15777
+ return this;
15778
+ }
15779
+ /** Replaces or removes the media region. @param {CardContent} content Media. @returns {this} */
15780
+ setMedia(content) {
15781
+ replaceCardContent(this.refs.media, content, "Card media");
15782
+ this.refs.media.hidden = content === null;
15783
+ return this;
15784
+ }
15785
+ /** Replaces or clears the main content. @param {CardContent} content Content. @returns {this} */
15786
+ setContent(content) {
15787
+ replaceCardContent(this.refs.content, content, "Card content");
15788
+ this.refs.content.hidden = content === null;
15789
+ return this;
15790
+ }
15791
+ /** Replaces the secondary action controls. @param {CardAction[]} actions Actions. @returns {this} */
15792
+ setActions(actions2) {
15793
+ if (!Array.isArray(actions2)) throw new TypeError("Card actions must be an array");
15794
+ const nextAbort = new AbortController();
15795
+ let controls;
15796
+ try {
15797
+ controls = actions2.map((action) => {
15798
+ if (isElement(action)) return action;
15799
+ if (!action || typeof action !== "object" || Array.isArray(action)) {
15800
+ throw new TypeError("Card actions must be Elements or button descriptors");
15801
+ }
15802
+ const descriptor = { ...action };
15803
+ const onclick = descriptor.onclick;
15804
+ delete descriptor.onclick;
15805
+ const control = button(descriptor);
15806
+ if (onclick !== void 0 && typeof onclick !== "function") {
15807
+ throw new TypeError("Card action onclick must be a function");
15808
+ }
15809
+ if (onclick) control.addEventListener("click", onclick, { signal: nextAbort.signal });
15810
+ return control;
15811
+ });
15812
+ } catch (error) {
15813
+ nextAbort.abort();
15814
+ throw error;
15815
+ }
15816
+ this._actionAbort?.abort();
15817
+ this._actionAbort = nextAbort;
15818
+ this.refs.actions.replaceChildren(...controls);
15819
+ this.refs.actions.hidden = controls.length === 0;
15820
+ this._syncHeader();
15821
+ return this;
15822
+ }
15823
+ /** Replaces or removes the footer. @param {CardContent} content Footer. @returns {this} */
15824
+ setFooter(content) {
15825
+ replaceCardContent(this.refs.footer, content, "Card footer");
15826
+ this.refs.footer.hidden = content === null;
15827
+ return this;
15828
+ }
15829
+ /** Sets or removes the title's native primary link. @param {CardLink} link Link. @returns {this} */
15830
+ setLink(link) {
15831
+ const next = normalizeCardLink(link);
15832
+ if (next && !this._title.trim()) throw new TypeError("A linked Card requires a non-empty title");
15833
+ this._link = next;
15834
+ this.el.dataset.linked = next ? "true" : "false";
15835
+ this._renderHeading();
15836
+ return this;
15837
+ }
15838
+ /** Restores an enhanced target exactly, or removes an owned card. @returns {void} */
15839
+ destroy() {
15840
+ if (this._cleaned) return;
15841
+ this._cleaned = true;
15842
+ this._actionAbort?.abort();
15843
+ this._linkAbort?.abort();
15844
+ super.destroy();
15845
+ if (!this._createdRoot) restoreTarget(this.el, this._original);
15846
+ }
15847
+ /** @returns {void} */
15848
+ _renderHeading() {
15849
+ const title = this._title;
15850
+ const nextAbort = new AbortController();
15851
+ const node = this._link ? h("a", {
15852
+ class: "zx-card__primary",
15853
+ href: this._link.href,
15854
+ target: this._link.target,
15855
+ rel: this._link.rel
15856
+ }, title) : h("span", { class: "zx-card__title" }, title);
15857
+ if (this._link?.onclick) node.addEventListener("click", this._link.onclick, { signal: nextAbort.signal });
15858
+ this._linkAbort?.abort();
15859
+ this._linkAbort = nextAbort;
15860
+ this.refs.heading.replaceChildren(node);
15861
+ this.refs.heading.hidden = title === "";
15862
+ if (title) this.el.setAttribute("aria-labelledby", this.refs.heading.id);
15863
+ else this.el.removeAttribute("aria-labelledby");
15864
+ this._syncHeader();
15865
+ }
15866
+ /** @returns {void} */
15867
+ _syncHeader() {
15868
+ if (!this.refs.header) return;
15869
+ this.refs.header.hidden = this._title === "" && this.refs.actions.childElementCount === 0;
15870
+ }
15871
+ };
15872
+ function replaceCardContent(host, content, label) {
15873
+ if (content === null) {
15874
+ host.replaceChildren();
15875
+ return;
15876
+ }
15877
+ if (typeof content === "string") {
15878
+ host.textContent = content;
15879
+ return;
15880
+ }
15881
+ const node = content instanceof Component ? content.toElement() : content;
15882
+ if (node instanceof Node) {
15883
+ host.replaceChildren(node);
15884
+ return;
15885
+ }
15886
+ throw new TypeError(`${label} must be a string, Node, Component, or null`);
15887
+ }
15888
+ function normalizeCardLink(value) {
15889
+ if (value === null) return null;
15890
+ const descriptor = typeof value === "string" ? { href: value } : value;
15891
+ if (!descriptor || typeof descriptor !== "object" || Array.isArray(descriptor)) {
15892
+ throw new TypeError("Card link must be a URL string, descriptor, or null");
15893
+ }
15894
+ const href = safeHref(descriptor.href);
15895
+ if (href === null) {
15896
+ throw new TypeError("Card link requires a safe non-empty href");
15897
+ }
15898
+ if (descriptor.onclick !== void 0 && typeof descriptor.onclick !== "function") {
15899
+ throw new TypeError("Card link onclick must be a function");
15900
+ }
15901
+ const target = descriptor.target == null ? void 0 : String(descriptor.target);
15902
+ let rel = descriptor.rel == null ? "" : String(descriptor.rel).trim();
15903
+ if (target === "_blank" && !/(^|\s)noopener(\s|$)/i.test(rel)) {
15904
+ rel = `${rel} noopener`.trim();
15905
+ }
15906
+ return { href, target, rel: rel || void 0, onclick: descriptor.onclick };
15907
+ }
15908
+ function normalizeVariant2(value) {
15909
+ return ["raised", "filled"].includes(String(value)) ? (
15910
+ /** @type {'raised'|'filled'} */
15911
+ value
15912
+ ) : "outlined";
15913
+ }
15914
+ function normalizeOrientation(value) {
15915
+ return value === "horizontal" ? "horizontal" : "vertical";
15916
+ }
15917
+ function normalizeHeadingLevel(value) {
15918
+ const level = Math.trunc(Number(value));
15919
+ return (
15920
+ /** @type {1|2|3|4|5|6} */
15921
+ level >= 1 && level <= 6 ? level : 3
15922
+ );
15923
+ }
15924
+
15446
15925
  // src/components/panel/panel.js
15447
15926
  var Panel = class extends Component {
15448
15927
  static cssName = "panel";
@@ -18786,9 +19265,12 @@ var zx = (() => {
18786
19265
  maxResults: 100,
18787
19266
  placeholder: "Search applications and records",
18788
19267
  label: "Launcher",
19268
+ closeLabel: "Close launcher",
19269
+ resultsLabel: "Launcher results",
18789
19270
  emptyText: "No results",
18790
19271
  loadingText: "Searching\u2026",
18791
- shortcut: "mod+k"
19272
+ shortcut: "mod+k",
19273
+ hints: { move: "Move", open: "Open", close: "Close" }
18792
19274
  };
18793
19275
  /**
18794
19276
  * Creates an owned launcher dialog or enhances an existing `<dialog>` target.
@@ -18817,6 +19299,7 @@ var zx = (() => {
18817
19299
  this._sources = normalizeSources(this.options.sources);
18818
19300
  this._sourceItems = /* @__PURE__ */ new Map();
18819
19301
  this._query = String(this.options.query ?? "");
19302
+ this._renderedQuery = null;
18820
19303
  this._results = [];
18821
19304
  this._active = -1;
18822
19305
  this._request = null;
@@ -18843,7 +19326,7 @@ var zx = (() => {
18843
19326
  ref: "close",
18844
19327
  class: "zx-icon-btn zx-launcher__close",
18845
19328
  type: "button",
18846
- ariaLabel: "Close launcher"
19329
+ ariaLabel: this.options.closeLabel
18847
19330
  }, icon("x"));
18848
19331
  const status = h("div", {
18849
19332
  ref: "status",
@@ -18856,16 +19339,25 @@ var zx = (() => {
18856
19339
  class: "zx-launcher__results",
18857
19340
  id: listId,
18858
19341
  role: "listbox",
18859
- ariaLabel: "Launcher results"
19342
+ ariaLabel: this.options.resultsLabel
18860
19343
  });
19344
+ const shortcut = this.options.shortcut === "mod+k" ? h("kbd", {
19345
+ class: "zx-launcher__shortcut",
19346
+ ariaHidden: "true"
19347
+ }, launcherShortcutLabel()) : null;
19348
+ const footer = launcherHints(this.options.hints);
18861
19349
  dialog.setAttribute("aria-label", String(this.options.label));
18862
19350
  dialog.replaceChildren(
18863
19351
  h(
18864
19352
  "div",
18865
19353
  { class: "zx-launcher__surface" },
18866
- h("div", { class: "zx-launcher__search" }, icon("search"), input, close),
19354
+ h("div", {
19355
+ class: "zx-launcher__search",
19356
+ dataset: { shortcut: shortcut ? "true" : "false" }
19357
+ }, icon("search"), input, shortcut, close),
18867
19358
  status,
18868
- results
19359
+ results,
19360
+ footer
18869
19361
  )
18870
19362
  );
18871
19363
  dialog.dataset.state = "closed";
@@ -18889,6 +19381,17 @@ var zx = (() => {
18889
19381
  if (!option || !results.contains(option)) return;
18890
19382
  this._activate(Number(option.getAttribute("data-launcher-index")), event);
18891
19383
  });
19384
+ this.listen(results, "pointermove", (event) => {
19385
+ const option = (
19386
+ /** @type {Element|null} */
19387
+ event.target?.closest?.("[data-launcher-index]")
19388
+ );
19389
+ if (!option || !results.contains(option)) return;
19390
+ const index = Number(option.getAttribute("data-launcher-index"));
19391
+ if (!Number.isInteger(index) || this._results[index]?.disabled || index === this._active) return;
19392
+ this._active = index;
19393
+ this._syncActive();
19394
+ });
18892
19395
  this.listen(dialog, "cancel", (event) => {
18893
19396
  event.preventDefault();
18894
19397
  this.close();
@@ -18958,6 +19461,13 @@ var zx = (() => {
18958
19461
  this._renderResults();
18959
19462
  return this;
18960
19463
  }
19464
+ /** Marks one local item as the current destination. @param {string|number|null} id Item id. @returns {this} */
19465
+ setCurrent(id) {
19466
+ const key = id == null ? null : String(id);
19467
+ this._items = this._items.map((item) => ({ ...item, current: key !== null && String(item.id) === key }));
19468
+ this._renderResults();
19469
+ return this;
19470
+ }
18961
19471
  /** Replaces asynchronous sources and aborts work from the old set. @param {LauncherSource[]} sources @returns {this} */
18962
19472
  setSources(sources) {
18963
19473
  this._abortRequest();
@@ -18993,7 +19503,7 @@ var zx = (() => {
18993
19503
  async _loadSources() {
18994
19504
  this._queryTimer = null;
18995
19505
  const query = this._query.trim();
18996
- const sources = this._sources.filter((source) => query.length >= Math.max(Number(this.options.minQuery) || 0, Number(source.minQuery) || 0));
19506
+ const sources = this._sources.filter((source) => visibleForQuery(source.when, query) && query.length >= Math.max(Number(this.options.minQuery) || 0, Number(source.minQuery) || 0));
18997
19507
  if (sources.length === 0) return;
18998
19508
  const request = new AbortController();
18999
19509
  const sequence = ++this._requestSequence;
@@ -19003,10 +19513,12 @@ var zx = (() => {
19003
19513
  try {
19004
19514
  const loaded = await source.load(query, { signal: request.signal });
19005
19515
  if (request.signal.aborted || sequence !== this._requestSequence) return;
19006
- this._sourceItems.set(source.id, normalizeItems2(loaded).map((item) => ({
19516
+ this._sourceItems.set(source.id, normalizeItems2(loaded).map((item, itemOrder) => ({
19007
19517
  ...item,
19008
19518
  _source: source.id,
19009
- group: item.group ?? source.label ?? ""
19519
+ _sourceOrder: source.order === "source",
19520
+ group: item.group ?? source.label ?? "",
19521
+ itemOrder: item.itemOrder ?? (source.order === "source" ? itemOrder : void 0)
19010
19522
  })));
19011
19523
  this._renderResults();
19012
19524
  } catch (error) {
@@ -19035,23 +19547,36 @@ var zx = (() => {
19035
19547
  }
19036
19548
  /** @returns {void} */
19037
19549
  _renderResults() {
19038
- const remote = orderedLauncherSourceItems(this._sources, this._sourceItems);
19550
+ const queryKey = this._query;
19551
+ const activeId = this._renderedQuery === queryKey && this._active >= 0 && this._results[this._active] ? String(this._results[this._active].id) : null;
19552
+ const query = this._query.trim();
19553
+ const local = this._items.filter((item) => visibleForQuery(item.when, query));
19554
+ const rankedLocal = rankLauncherItems(local, query, this.options.maxResults);
19555
+ const remote = this._sources.flatMap((source) => {
19556
+ const loaded = this._sourceItems.get(String(source.id)) ?? [];
19557
+ return source.order === "source" ? loaded.filter((item) => visibleForQuery(item.when, query)) : rankLauncherItems(
19558
+ loaded.filter((item) => visibleForQuery(item.when, query)),
19559
+ query,
19560
+ this.options.maxResults
19561
+ );
19562
+ });
19039
19563
  const seen = /* @__PURE__ */ new Set();
19040
- const combined = [...this._items.map((item) => ({ ...item, _source: null })), ...remote].filter((item) => {
19564
+ const combined = [...rankedLocal.map((item) => ({ ...item, _source: null })), ...remote].filter((item) => {
19041
19565
  const key = String(item.id);
19042
19566
  if (seen.has(key)) return false;
19043
19567
  seen.add(key);
19044
19568
  return true;
19045
- });
19046
- const ranked = rankLauncherItems(combined, this._query, this.options.maxResults);
19569
+ }).slice(0, Math.max(0, Number(this.options.maxResults) || 0));
19047
19570
  const groups = /* @__PURE__ */ new Map();
19048
- for (const item of ranked) {
19571
+ for (const item of combined) {
19049
19572
  const group = String(item.group ?? "");
19050
19573
  if (!groups.has(group)) groups.set(group, []);
19051
19574
  groups.get(group).push(item);
19052
19575
  }
19053
19576
  this._results = [...groups.values()].flat();
19054
- this._active = this._results.findIndex((item) => !item.disabled);
19577
+ this._renderedQuery = queryKey;
19578
+ const preserved = activeId === null ? -1 : this._results.findIndex((item) => !item.disabled && String(item.id) === activeId);
19579
+ this._active = preserved >= 0 ? preserved : this._results.findIndex((item) => !item.disabled);
19055
19580
  const nodes = [];
19056
19581
  let index = 0;
19057
19582
  for (const [group, items] of groups) {
@@ -19059,7 +19584,8 @@ var zx = (() => {
19059
19584
  if (group) nodes.push(h("div", {
19060
19585
  class: "zx-launcher__group",
19061
19586
  role: "group",
19062
- ariaLabel: group
19587
+ ariaLabel: group,
19588
+ dataset: { layout: items.every((item) => item.kind === "application") ? "grid" : "list" }
19063
19589
  }, h("div", { class: "zx-launcher__group-label", ariaHidden: "true" }, group), options));
19064
19590
  else nodes.push(...options);
19065
19591
  }
@@ -19069,9 +19595,14 @@ var zx = (() => {
19069
19595
  }
19070
19596
  /** @param {RankedLauncherItem} item @param {number} index @returns {HTMLElement} */
19071
19597
  _option(item, index) {
19072
- const tag = item.href ? "a" : "button";
19598
+ const href = safeHref(item.href);
19599
+ const tag = href ? "a" : "button";
19073
19600
  const children = [];
19074
- if (item.icon) children.push(h("span", { class: "zx-launcher__icon" }, icon(item.icon)));
19601
+ const visual = launcherItemIcon(item);
19602
+ if (visual) children.push(h("span", {
19603
+ class: "zx-launcher__icon",
19604
+ ariaHidden: "true"
19605
+ }, visual));
19075
19606
  children.push(h(
19076
19607
  "span",
19077
19608
  { class: "zx-launcher__copy" },
@@ -19084,14 +19615,20 @@ var zx = (() => {
19084
19615
  h(tag, {
19085
19616
  class: "zx-launcher__option",
19086
19617
  type: tag === "button" ? "button" : null,
19087
- href: item.href ?? null,
19618
+ href,
19088
19619
  target: item.target ?? null,
19620
+ rel: tag === "a" ? "noopener" : null,
19089
19621
  role: "option",
19090
19622
  id: uid("zx-launcher-option"),
19091
19623
  tabIndex: -1,
19092
19624
  ariaSelected: "false",
19093
19625
  ariaDisabled: item.disabled ? "true" : null,
19094
- dataset: { launcherIndex: index, pinned: item.pinned ? "true" : null }
19626
+ ariaCurrent: item.current ? "page" : null,
19627
+ dataset: {
19628
+ launcherIndex: index,
19629
+ pinned: item.pinned !== void 0 && item.pinned !== false ? "true" : null,
19630
+ kind: normalizeLauncherKind(item.kind)
19631
+ }
19095
19632
  }, children)
19096
19633
  );
19097
19634
  }
@@ -19128,6 +19665,17 @@ var zx = (() => {
19128
19665
  }
19129
19666
  const enabled = this._results.map((item, index) => item.disabled ? -1 : index).filter((index) => index >= 0);
19130
19667
  if (enabled.length === 0) return;
19668
+ if (/^Arrow(?:Left|Right|Up|Down)$/.test(event.key)) {
19669
+ const next = this._spatialIndex(event.key, enabled);
19670
+ if (next !== null) {
19671
+ event.preventDefault();
19672
+ this._active = next;
19673
+ this._syncActive();
19674
+ this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`)?.scrollIntoView({ block: "nearest", inline: "nearest" });
19675
+ return;
19676
+ }
19677
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") return;
19678
+ }
19131
19679
  let position2 = enabled.indexOf(this._active);
19132
19680
  if (event.key === "Home" || event.key === "PageUp") position2 = 0;
19133
19681
  else if (event.key === "End" || event.key === "PageDown") position2 = enabled.length - 1;
@@ -19139,6 +19687,15 @@ var zx = (() => {
19139
19687
  this._syncActive();
19140
19688
  this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`)?.scrollIntoView({ block: "nearest" });
19141
19689
  }
19690
+ /** @param {string} direction @param {number[]} enabled @returns {number|null} */
19691
+ _spatialIndex(direction, enabled) {
19692
+ const current = this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`);
19693
+ if (!(current instanceof HTMLElement)) return null;
19694
+ if ((direction === "ArrowLeft" || direction === "ArrowRight") && this._results[this._active]?.kind !== "application") return null;
19695
+ const candidates = [...this.refs.results.querySelectorAll("[data-launcher-index]")].filter((option) => enabled.includes(Number(option.getAttribute("data-launcher-index"))) && (direction !== "ArrowLeft" && direction !== "ArrowRight" || option.getAttribute("data-kind") === "application"));
19696
+ const next = findSpatialLauncherOption(candidates, current, direction);
19697
+ return next ? Number(next.getAttribute("data-launcher-index")) : null;
19698
+ }
19142
19699
  /** @param {number} index @param {Event} event @returns {void} */
19143
19700
  _activate(index, event) {
19144
19701
  const item = this._results[index];
@@ -19164,25 +19721,103 @@ var zx = (() => {
19164
19721
  }
19165
19722
  /** @param {KeyboardEvent} event @returns {void} */
19166
19723
  _onDocumentKeydown(event) {
19167
- if (this.options.shortcut !== "mod+k" || event.isComposing || event.key.toLowerCase() !== "k" || !event.metaKey && !event.ctrlKey || event.altKey) return;
19724
+ if (this.options.shortcut !== "mod+k" || event.isComposing || event.key.toLowerCase() !== "k" || Number(event.metaKey) + Number(event.ctrlKey) !== 1 || event.altKey || event.shiftKey) return;
19168
19725
  const active = document.activeElement;
19169
- if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement || active instanceof HTMLElement && active.isContentEditable) return;
19726
+ if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement || active instanceof HTMLSelectElement || active instanceof HTMLElement && active.isContentEditable) return;
19170
19727
  const modal = document.querySelector("dialog:modal");
19171
19728
  if (modal && modal !== this.el) return;
19172
19729
  event.preventDefault();
19173
19730
  this.open();
19174
19731
  }
19175
19732
  };
19733
+ function launcherShortcutLabel() {
19734
+ const platform = String(
19735
+ /** @type {any} */
19736
+ navigator.userAgentData?.platform ?? navigator.platform ?? ""
19737
+ );
19738
+ return /mac|iphone|ipad/i.test(platform) ? "\u2318 K" : "Ctrl K";
19739
+ }
19740
+ function launcherHints(hints) {
19741
+ if (hints === false) return null;
19742
+ const labels = hints && typeof hints === "object" ? hints : {};
19743
+ return h(
19744
+ "footer",
19745
+ { class: "zx-launcher__hints", ariaHidden: "true" },
19746
+ h("span", {}, h("kbd", {}, "\u2191\u2193\u2190\u2192"), labels.move ?? "Move"),
19747
+ h("span", {}, h("kbd", {}, "\u21B5"), labels.open ?? "Open"),
19748
+ h("span", {}, h("kbd", {}, "Esc"), labels.close ?? "Close")
19749
+ );
19750
+ }
19751
+ function launcherItemIcon(item) {
19752
+ let visual = item.icon;
19753
+ if (typeof visual === "function") visual = visual(item);
19754
+ if (typeof visual === "string") return icon(visual);
19755
+ if (visual instanceof Node) return visual.cloneNode(true);
19756
+ if (visual && typeof visual === "object" && typeof /** @type {any} */
19757
+ visual.toElement === "function") {
19758
+ const element = (
19759
+ /** @type {any} */
19760
+ visual.toElement()
19761
+ );
19762
+ return element instanceof Node ? element.cloneNode(true) : null;
19763
+ }
19764
+ return null;
19765
+ }
19766
+ function normalizeLauncherKind(kind) {
19767
+ return kind === "application" || kind === "record" ? kind : "action";
19768
+ }
19769
+ function visibleForQuery(when, query) {
19770
+ if (when === "empty") return query === "";
19771
+ if (when === "query") return query !== "";
19772
+ return true;
19773
+ }
19774
+ function findSpatialLauncherOption(options, current, direction) {
19775
+ const origin = current.getBoundingClientRect();
19776
+ if (origin.width === 0 && origin.height === 0) return null;
19777
+ const originX = origin.left + origin.width / 2;
19778
+ const originY = origin.top + origin.height / 2;
19779
+ let best = null;
19780
+ let bestScore = Infinity;
19781
+ for (const option of options) {
19782
+ if (option === current) continue;
19783
+ const rect = option.getBoundingClientRect();
19784
+ const dx = rect.left + rect.width / 2 - originX;
19785
+ const dy = rect.top + rect.height / 2 - originY;
19786
+ let valid = false;
19787
+ let score = Infinity;
19788
+ if (direction === "ArrowLeft") {
19789
+ valid = dx < -2 && Math.abs(dy) < Math.max(origin.height, rect.height) / 2;
19790
+ score = Math.abs(dx) + Math.abs(dy) * 4;
19791
+ } else if (direction === "ArrowRight") {
19792
+ valid = dx > 2 && Math.abs(dy) < Math.max(origin.height, rect.height) / 2;
19793
+ score = Math.abs(dx) + Math.abs(dy) * 4;
19794
+ } else if (direction === "ArrowUp") {
19795
+ valid = dy < -2;
19796
+ score = Math.abs(dy) * 10 + Math.abs(dx);
19797
+ } else if (direction === "ArrowDown") {
19798
+ valid = dy > 2;
19799
+ score = Math.abs(dy) * 10 + Math.abs(dx);
19800
+ }
19801
+ if (valid && score < bestScore) {
19802
+ best = option;
19803
+ bestScore = score;
19804
+ }
19805
+ }
19806
+ return best;
19807
+ }
19176
19808
  function rankLauncherItems(items, query, limit = 100) {
19177
19809
  const needle = searchable(query).trim();
19178
19810
  const ranked = items.map((item, order) => ({ item, order, score: launcherScore(item, needle) })).filter(({ score }) => score < Infinity);
19179
19811
  ranked.sort((a, b) => {
19812
+ const aGroup = finiteOrder(a.item.groupOrder);
19813
+ const bGroup = finiteOrder(b.item.groupOrder);
19814
+ if (aGroup !== bGroup) return aGroup - bGroup;
19180
19815
  if (needle === "") {
19181
19816
  const aPinned = pinOrder(a.item.pinned, a.order);
19182
19817
  const bPinned = pinOrder(b.item.pinned, b.order);
19183
19818
  if (aPinned !== bPinned) return aPinned - bPinned;
19184
19819
  }
19185
- return a.score - b.score || compareLauncherText(a.item.label, b.item.label) || a.order - b.order;
19820
+ return a.score - b.score || finiteOrder(a.item.itemOrder) - finiteOrder(b.item.itemOrder) || compareLauncherText(a.item.label, b.item.label) || a.order - b.order;
19186
19821
  });
19187
19822
  const maximum = Math.max(0, Number(limit) || 0);
19188
19823
  return ranked.slice(0, maximum || 0).map(({ item, score }) => ({ ...item, _score: score }));
@@ -19206,6 +19841,9 @@ var zx = (() => {
19206
19841
  if (pinned) return order;
19207
19842
  return Number.MAX_SAFE_INTEGER;
19208
19843
  }
19844
+ function finiteOrder(value) {
19845
+ return typeof value === "number" && Number.isFinite(value) ? value : Number.MAX_SAFE_INTEGER;
19846
+ }
19209
19847
  function searchable(value) {
19210
19848
  return String(value ?? "").normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase();
19211
19849
  }
@@ -19220,15 +19858,23 @@ var zx = (() => {
19220
19858
  }
19221
19859
  function normalizeItems2(items) {
19222
19860
  if (!Array.isArray(items)) return [];
19223
- return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) => ({ ...item, label: String(item.label) }));
19861
+ return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) => normalizeLauncherItem(item));
19862
+ }
19863
+ function normalizeLauncherItem(item) {
19864
+ const normalized = { ...item, label: String(item.label) };
19865
+ if (item.href == null) return normalized;
19866
+ const href = safeHref(item.href);
19867
+ if (href !== null) normalized.href = href;
19868
+ else {
19869
+ delete normalized.href;
19870
+ if (typeof normalized.invoke !== "function") normalized.disabled = true;
19871
+ }
19872
+ return normalized;
19224
19873
  }
19225
19874
  function normalizeSources(sources) {
19226
19875
  if (!Array.isArray(sources)) return [];
19227
19876
  return sources.filter((source) => source && typeof source === "object" && source.id != null && typeof source.load === "function").map((source) => ({ ...source, id: String(source.id) }));
19228
19877
  }
19229
- function orderedLauncherSourceItems(sources, sourceItems) {
19230
- return sources.flatMap((source) => sourceItems.get(String(source.id)) ?? []);
19231
- }
19232
19878
 
19233
19879
  // src/components/avatar/avatar.js
19234
19880
  var Avatar = class extends Component {
@@ -19916,10 +20562,10 @@ var zx = (() => {
19916
20562
  }
19917
20563
  }
19918
20564
 
19919
- // src/components/app-rail/app-rail.js
19920
- var AppRail = class extends Component {
20565
+ // src/internal/app-rail.js
20566
+ var AppRailPresenter = class extends Component {
19921
20567
  static cssName = "app-rail";
19922
- /** @type {Readonly<AppRailOptions>} */
20568
+ /** @type {Readonly<AppRailPresenterOptions>} */
19923
20569
  static defaults = {
19924
20570
  items: [],
19925
20571
  active: null,
@@ -19929,12 +20575,13 @@ var zx = (() => {
19929
20575
  header: null,
19930
20576
  footer: null,
19931
20577
  openDelay: 80,
19932
- closeDelay: 160
20578
+ closeDelay: 160,
20579
+ renderIcon: null
19933
20580
  };
19934
20581
  /**
19935
20582
  * Creates or enhances a rail host.
19936
20583
  * @param {Element|string|null} target Rail target.
19937
- * @param {AppRailOptions} [options={}] Rail options.
20584
+ * @param {AppRailPresenterOptions} [options={}] Rail options.
19938
20585
  */
19939
20586
  constructor(target = null, options = {}) {
19940
20587
  super(target, options);
@@ -20084,7 +20731,14 @@ var zx = (() => {
20084
20731
  index: String(item.id),
20085
20732
  iconOnly: true,
20086
20733
  disclosure: hasChildren,
20087
- disclosureIcon: railDisclosureIcon(this.el.dataset.side)
20734
+ disclosureIcon: railDisclosureIcon(this.el.dataset.side),
20735
+ visual: appNavIcon(item, this.options.renderIcon, {
20736
+ orientation: this.el.dataset.orientation,
20737
+ collapsed: true,
20738
+ side: this.el.dataset.side,
20739
+ level: 1,
20740
+ location: "root"
20741
+ })
20088
20742
  });
20089
20743
  const row2 = h("li", { class: "zx-app-rail__row" }, control);
20090
20744
  this.refs.list.append(row2);
@@ -20125,7 +20779,14 @@ var zx = (() => {
20125
20779
  index: childKey,
20126
20780
  iconOnly: false,
20127
20781
  disclosure: hasChildren,
20128
- disclosureIcon: nestedDisclosureIcon(this.el.dataset.side)
20782
+ disclosureIcon: nestedDisclosureIcon(this.el.dataset.side),
20783
+ visual: appNavIcon(child, this.options.renderIcon, {
20784
+ orientation: this.el.dataset.orientation,
20785
+ collapsed: true,
20786
+ side: this.el.dataset.side,
20787
+ level: childKey.split("/").length,
20788
+ location: "flyout"
20789
+ })
20129
20790
  });
20130
20791
  list.append(h("li", {}, control));
20131
20792
  if (hasChildren) this._buildFlyout(child, control, childKey, key);
@@ -20179,7 +20840,6 @@ var zx = (() => {
20179
20840
  this._interaction.listen(trigger, "click", (event) => {
20180
20841
  event.preventDefault();
20181
20842
  if (event instanceof MouseEvent && event.detail === 0) this.openFlyout(key, { focus: true });
20182
- else if (dropdown.isOpen()) this.closeFlyout(key);
20183
20843
  else this.openFlyout(key);
20184
20844
  });
20185
20845
  this._interaction.listen(actualPanel, "pointerenter", () => this._cancelFlyoutTimers(flyout, true));
@@ -20434,14 +21094,43 @@ var zx = (() => {
20434
21094
  };
20435
21095
  function normalizeAppItems(items) {
20436
21096
  if (!Array.isArray(items)) return [];
20437
- return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) => ({
20438
- ...item,
20439
- label: String(item.label),
20440
- children: Array.isArray(item.children) ? normalizeAppItems(item.children) : []
20441
- }));
21097
+ return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) => {
21098
+ const normalized = {
21099
+ ...item,
21100
+ label: String(item.label),
21101
+ children: Array.isArray(item.children) ? normalizeAppItems(item.children) : []
21102
+ };
21103
+ if (item.href == null) return normalized;
21104
+ const href = safeHref(item.href);
21105
+ if (href !== null) normalized.href = href;
21106
+ else {
21107
+ delete normalized.href;
21108
+ if (normalized.children.length === 0 && typeof normalized.invoke !== "function") {
21109
+ normalized.disabled = true;
21110
+ }
21111
+ }
21112
+ return normalized;
21113
+ });
20442
21114
  }
20443
- function navControl(item, { className, index, iconOnly, disclosure, disclosureIcon }) {
20444
- const tag = item.href && !disclosure ? "a" : "button";
21115
+ function appNavIcon(item, renderer, context) {
21116
+ let visual = typeof renderer === "function" ? renderer(item, context) : null;
21117
+ if (visual == null) visual = item.icon;
21118
+ if (typeof visual === "function") visual = visual(context, item);
21119
+ if (typeof visual === "string") return icon(visual);
21120
+ if (visual instanceof Node) return visual.cloneNode(true);
21121
+ if (visual && typeof visual === "object" && typeof /** @type {any} */
21122
+ visual.toElement === "function") {
21123
+ const element = (
21124
+ /** @type {any} */
21125
+ visual.toElement()
21126
+ );
21127
+ return element instanceof Node ? element.cloneNode(true) : null;
21128
+ }
21129
+ return null;
21130
+ }
21131
+ function navControl(item, { className, index, iconOnly, disclosure, disclosureIcon, visual }) {
21132
+ const href = safeHref(item.href);
21133
+ const tag = href && !disclosure ? "a" : "button";
20445
21134
  return (
20446
21135
  /** @type {HTMLElement} */
20447
21136
  h(
@@ -20449,13 +21138,14 @@ var zx = (() => {
20449
21138
  {
20450
21139
  class: className,
20451
21140
  type: tag === "button" ? "button" : null,
20452
- href: tag === "a" ? item.href : null,
21141
+ href: tag === "a" ? href : null,
20453
21142
  target: tag === "a" ? item.target ?? null : null,
21143
+ rel: tag === "a" ? "noopener" : null,
20454
21144
  ariaLabel: iconOnly ? item.label : null,
20455
21145
  ariaDisabled: item.disabled ? "true" : null,
20456
21146
  dataset: { appNavId: index, appNavValueId: String(item.id) }
20457
21147
  },
20458
- item.icon ? h("span", { class: `${className}-icon`, ariaHidden: "true" }, icon(item.icon)) : null,
21148
+ visual ? h("span", { class: `${className}-icon`, ariaHidden: "true" }, visual) : null,
20459
21149
  h("span", { class: `${className}-label` }, item.label),
20460
21150
  item.badge != null ? h("span", { class: `${className}-badge` }, String(item.badge)) : null,
20461
21151
  disclosure ? h("span", { class: `${className}-disclosure`, ariaHidden: "true" }, icon(disclosureIcon)) : null
@@ -20519,47 +21209,28 @@ var zx = (() => {
20519
21209
  active: null,
20520
21210
  expanded: [],
20521
21211
  collapsed: false,
21212
+ collapsible: true,
21213
+ orientation: "vertical",
20522
21214
  side: "left",
20523
21215
  label: "Applications",
20524
21216
  header: null,
20525
21217
  footer: null,
20526
21218
  railHeader: null,
20527
- railFooter: null
21219
+ railFooter: null,
21220
+ openDelay: 80,
21221
+ closeDelay: 160,
21222
+ renderIcon: null
20528
21223
  };
20529
21224
  /**
20530
- * Creates or enhances an application-sidebar host.
20531
- * @param {Element|string|null} target Sidebar target.
20532
- * @param {AppSidebarOptions} [options={}] Sidebar options.
21225
+ * Creates or enhances an application-navigation host.
21226
+ * @param {Element|string|null} [target=null] Navigation target.
21227
+ * @param {AppSidebarOptions} [options={}] Navigation options.
20533
21228
  */
20534
21229
  constructor(target = null, options = {}) {
20535
21230
  super(target, options);
20536
- this.listen(this.refs.rail, "zx-select", (event) => event.stopPropagation());
20537
- const expandButton = h("button", {
20538
- class: "zx-app-sidebar__rail-toggle zx-icon-btn",
20539
- type: "button",
20540
- ariaLabel: "Expand application sidebar"
20541
- }, icon(this.el.dataset.side === "right" ? "chevron-left" : "chevron-right"));
20542
- this._railExpandButton = expandButton;
20543
- const railHeader = h("div", { class: "zx-app-sidebar__rail-header" }, expandButton);
20544
- appendSlot2(railHeader, this.options.railHeader);
20545
- this._rail = new AppRail(this.refs.rail, {
20546
- items: this._items,
20547
- active: this._active,
20548
- orientation: "vertical",
20549
- side: this.el.dataset.side,
20550
- label: this.options.label,
20551
- header: railHeader,
20552
- footer: this.options.railFooter,
20553
- onselect: (event) => {
20554
- const selected = this.emit("select", event.detail);
20555
- if (selected.defaultPrevented) event.preventDefault();
20556
- }
20557
- });
20558
- this.listen(expandButton, "click", () => {
20559
- this.expand();
20560
- });
20561
- this._renderTree();
20562
- this.setCollapsed(Boolean(this.options.collapsed), { silent: true });
21231
+ this._rail = null;
21232
+ this._railExpandButton = null;
21233
+ this._renderMode();
20563
21234
  }
20564
21235
  /** @returns {HTMLElement} */
20565
21236
  render() {
@@ -20577,57 +21248,39 @@ var zx = (() => {
20577
21248
  this._active = this.options.active;
20578
21249
  this._expanded = new Set(Array.isArray(this.options.expanded) ? this.options.expanded.map(String) : []);
20579
21250
  this._collapsed = Boolean(this.options.collapsed);
20580
- root.dataset.side = this.options.side === "right" ? "right" : "left";
20581
- const headerContent = h("div", { class: "zx-app-sidebar__header-content" });
20582
- appendSlot2(headerContent, this.options.header);
20583
- const collapseButton = h("button", {
20584
- ref: "collapse",
20585
- class: "zx-app-sidebar__collapse zx-icon-btn",
20586
- type: "button",
20587
- ariaLabel: "Minimize application sidebar"
20588
- }, icon(root.dataset.side === "right" ? "chevron-right" : "chevron-left"));
20589
- const footer = h("footer", { class: "zx-app-sidebar__footer" });
20590
- appendSlot2(footer, this.options.footer);
20591
- footer.hidden = !footer.hasChildNodes();
20592
- const expanded = h(
20593
- "div",
20594
- { ref: "expanded", class: "zx-app-sidebar__expanded" },
20595
- h("header", { class: "zx-app-sidebar__header" }, headerContent, collapseButton),
20596
- h(
20597
- "nav",
20598
- { class: "zx-app-sidebar__nav", ariaLabel: this.options.label },
20599
- h("ul", { ref: "list", class: "zx-app-sidebar__list" })
20600
- ),
20601
- footer
20602
- );
20603
- const rail = h("div", { ref: "rail", class: "zx-app-sidebar__rail" });
20604
- root.replaceChildren(expanded, rail);
20605
- this.listen(collapseButton, "click", () => {
20606
- this.collapse();
20607
- });
20608
- this.listen(this.refs.list, "click", (event) => this._onTreeClick(event));
21251
+ this._orientation = this.options.orientation === "horizontal" ? "horizontal" : "vertical";
21252
+ this._side = normalizeSide(this._orientation, this.options.side);
21253
+ this._modeAbort = null;
21254
+ root.replaceChildren();
20609
21255
  return root;
20610
21256
  }
20611
- /** Replaces the navigation tree in both expanded and rail presentations. @param {AppNavItem[]} items @returns {this} */
21257
+ /** Replaces the navigation tree in the active presentation. @param {AppNavItem[]} items @returns {this} */
20612
21258
  setItems(items) {
20613
21259
  this._items = normalizeAppItems(items);
20614
21260
  this._reconcileExpanded();
20615
- this._renderTree();
20616
- this._rail.setItems(this._items);
21261
+ if (this._rail) this._rail.setItems(this._items);
21262
+ else this._renderTree();
20617
21263
  return this;
20618
21264
  }
20619
- /** Sets the active id in both presentations without routing. @param {string|number|null} id @returns {this} */
21265
+ /** Returns a shallow copy of normalized items. @returns {AppNavItem[]} */
21266
+ getItems() {
21267
+ return this._items.map((item) => ({
21268
+ ...item,
21269
+ children: item.children?.map((child) => ({ ...child }))
21270
+ }));
21271
+ }
21272
+ /** Sets the active id without routing. @param {string|number|null} id @returns {this} */
20620
21273
  setActive(id) {
20621
21274
  this._active = id;
20622
- this._syncActive();
20623
- this._rail.setActive(id);
21275
+ if (this._rail) this._rail.setActive(id);
21276
+ else this._syncActive();
20624
21277
  return this;
20625
21278
  }
20626
21279
  /** Replaces expanded branch ids. @param {Array<string|number>} ids @param {{silent?: boolean}} [options={}] @returns {this} */
20627
21280
  setExpanded(ids, { silent = false } = {}) {
20628
21281
  this._expanded = new Set(Array.isArray(ids) ? ids.map(String) : []);
20629
21282
  this._reconcileExpanded();
20630
- this._renderTree();
21283
+ if (!this._rail) this._renderTree();
20631
21284
  if (!silent) this.emit("branchchange", {
20632
21285
  item: null,
20633
21286
  id: null,
@@ -20652,47 +21305,105 @@ var zx = (() => {
20652
21305
  const expanded = !this._expanded.has(key);
20653
21306
  if (expanded) this._expanded.add(key);
20654
21307
  else this._expanded.delete(key);
20655
- this._renderTree();
20656
- if (!silent) this.emit("branchchange", { item, id: item.id, expanded, ids: this.getExpanded() });
21308
+ if (!this._rail) this._renderTree();
21309
+ if (!silent) this.emit("branchchange", {
21310
+ item,
21311
+ id: item.id,
21312
+ expanded,
21313
+ ids: this.getExpanded()
21314
+ });
20657
21315
  return this;
20658
21316
  }
20659
- /** Shows the expanded sidebar presentation. @param {{silent?: boolean}} [options={}] @returns {this} */
21317
+ /** Shows the expanded vertical presentation. @param {{silent?: boolean}} [options={}] @returns {this} */
20660
21318
  expand(options = {}) {
20661
21319
  return this.setCollapsed(false, options);
20662
21320
  }
20663
- /** Shows the minimized AppRail presentation. @param {{silent?: boolean}} [options={}] @returns {this} */
21321
+ /** Shows the minimized presentation. @param {{silent?: boolean}} [options={}] @returns {this} */
20664
21322
  collapse(options = {}) {
20665
21323
  return this.setCollapsed(true, options);
20666
21324
  }
20667
- /** Sets expanded/minimized presentation while preserving branch state. @param {boolean} collapsed @param {{silent?: boolean}} [options={}] @returns {this} */
21325
+ /** Sets the saved vertical expanded/minimized state. @param {boolean} collapsed @param {{silent?: boolean}} [options={}] @returns {this} */
20668
21326
  setCollapsed(collapsed, { silent = false } = {}) {
20669
- const next = Boolean(collapsed);
20670
- const changed = next !== this._collapsed;
21327
+ const previous = this.isCollapsed();
20671
21328
  const active = document.activeElement;
20672
- const focusRailToggle = next && Boolean(active && this.refs.expanded.contains(active));
20673
- const focusSidebarToggle = !next && this._rail?._containsNode(active);
20674
- if (!next) this._rail?.closeAllFlyouts();
20675
- this._collapsed = next;
20676
- this.el.dataset.collapsed = String(next);
20677
- this.refs.expanded.hidden = next;
20678
- this.refs.rail.hidden = !next;
20679
- if (focusRailToggle) this._railExpandButton?.focus();
20680
- else if (focusSidebarToggle) this.refs.collapse.focus();
20681
- if (changed && !silent) this.emit("collapsechange", { collapsed: next });
20682
- return this;
20683
- }
20684
- /** Reports whether the rail presentation is active. @returns {boolean} */
21329
+ const outgoingOwnedFocus = Boolean(active && (this.el.contains(active) || this._rail?._containsNode(active)));
21330
+ this._collapsed = Boolean(collapsed);
21331
+ const next = this.isCollapsed();
21332
+ if (previous !== next) {
21333
+ this._renderMode();
21334
+ if (outgoingOwnedFocus) {
21335
+ if (next) this._railExpandButton?.focus();
21336
+ else this.refs.collapse?.focus();
21337
+ }
21338
+ if (!silent) this.emit("collapsechange", { collapsed: next });
21339
+ }
21340
+ return this;
21341
+ }
21342
+ /** Reports the effective presentation state; horizontal navigation is always minimized. @returns {boolean} */
20685
21343
  isCollapsed() {
20686
- return this._collapsed;
21344
+ return this._orientation === "horizontal" || this._collapsed;
20687
21345
  }
20688
- /** Returns the composed minimized rail. @returns {AppRail} */
20689
- getRail() {
20690
- return this._rail;
21346
+ /**
21347
+ * Changes navigation flow/edge without replacing state.
21348
+ * @param {{orientation?: 'vertical'|'horizontal', side?: 'left'|'right'|'top'|'bottom'}} layout
21349
+ * @param {{silent?: boolean}} [options]
21350
+ * @returns {this}
21351
+ */
21352
+ setLayout(layout = {}, { silent = false } = {}) {
21353
+ const orientation = layout.orientation === "horizontal" ? "horizontal" : layout.orientation === "vertical" ? "vertical" : this._orientation;
21354
+ const side = normalizeSide(orientation, layout.side ?? this._side);
21355
+ if (orientation === this._orientation && side === this._side) return this;
21356
+ const previous = this.isCollapsed();
21357
+ const active = document.activeElement;
21358
+ const outgoingOwnedFocus = Boolean(active && (this.el.contains(active) || this._rail?._containsNode(active)));
21359
+ const focusedId = outgoingOwnedFocus && active instanceof Element ? active.closest("[data-app-nav-id]")?.getAttribute("data-app-nav-id") ?? null : null;
21360
+ this._orientation = orientation;
21361
+ this._side = side;
21362
+ this._renderMode();
21363
+ const next = this.isCollapsed();
21364
+ if (outgoingOwnedFocus) {
21365
+ const replacement = focusedId === null ? null : [...this.el.querySelectorAll("[data-app-nav-id]")].find((control) => control.getAttribute("data-app-nav-id") === focusedId) ?? null;
21366
+ if (replacement instanceof HTMLElement) replacement.focus();
21367
+ else if (next) this.focus();
21368
+ else if (this.refs.collapse instanceof HTMLElement && !this.refs.collapse.hidden) {
21369
+ this.refs.collapse.focus();
21370
+ } else visibleTreeControls(this.refs.list)[0]?.focus();
21371
+ }
21372
+ if (previous !== next && !silent) this.emit("collapsechange", { collapsed: next });
21373
+ return this;
20691
21374
  }
20692
- /** Destroys the rail and restores/removes the sidebar host. @returns {void} */
21375
+ /** Opens a minimized descendant flyout. @param {string|number} id @param {{focus?: boolean}} [options={}] @returns {this} */
21376
+ openFlyout(id, options = {}) {
21377
+ this._rail?.openFlyout(id, options);
21378
+ return this;
21379
+ }
21380
+ /** Closes a minimized descendant flyout. @param {string|number} id @returns {this} */
21381
+ closeFlyout(id) {
21382
+ this._rail?.closeFlyout(id);
21383
+ return this;
21384
+ }
21385
+ /** Closes every minimized flyout. @returns {this} */
21386
+ closeAllFlyouts() {
21387
+ this._rail?.closeAllFlyouts();
21388
+ return this;
21389
+ }
21390
+ /** Reports whether a minimized flyout is open. @param {string|number} id @returns {boolean} */
21391
+ isFlyoutOpen(id) {
21392
+ return this._rail?.isFlyoutOpen(id) ?? false;
21393
+ }
21394
+ /** Focuses the first navigation action. @returns {this} */
21395
+ focus() {
21396
+ if (this._rail) this._rail.focus();
21397
+ else visibleTreeControls(this.refs.list)[0]?.focus();
21398
+ return this;
21399
+ }
21400
+ /** Destroys flyouts and restores/removes the host. @returns {void} */
20693
21401
  destroy() {
20694
21402
  const original = this._original;
20695
- this._rail.destroy();
21403
+ this._modeAbort?.abort();
21404
+ this._modeAbort = null;
21405
+ this._rail?.destroy();
21406
+ this._rail = null;
20696
21407
  super.destroy();
20697
21408
  if (!this._createdRoot && original) {
20698
21409
  for (const attribute of Array.from(this.el.attributes)) this.el.removeAttribute(attribute.name);
@@ -20700,8 +21411,99 @@ var zx = (() => {
20700
21411
  this.el.replaceChildren(...original.children);
20701
21412
  }
20702
21413
  }
21414
+ /** Mounts exactly one expanded tree or minimized flyout presentation. @returns {void} */
21415
+ _renderMode() {
21416
+ this._modeAbort?.abort();
21417
+ this._modeAbort = new AbortController();
21418
+ this._rail?.destroy();
21419
+ this._rail = null;
21420
+ this._railExpandButton = null;
21421
+ for (const key of ["expanded", "list", "collapse", "rail"]) delete this.refs[key];
21422
+ this.el.replaceChildren();
21423
+ this.el.dataset.orientation = this._orientation;
21424
+ this.el.dataset.side = this._side;
21425
+ this.el.dataset.collapsed = String(this.isCollapsed());
21426
+ if (this.isCollapsed()) this._renderRail();
21427
+ else this._renderExpanded();
21428
+ }
21429
+ /** @returns {void} */
21430
+ _renderExpanded() {
21431
+ const headerContent = h("div", { class: "zx-app-sidebar__header-content" });
21432
+ appendSlot2(headerContent, this.options.header);
21433
+ const collapseButton = h("button", {
21434
+ class: "zx-app-sidebar__collapse zx-icon-btn",
21435
+ type: "button",
21436
+ ariaLabel: "Minimize application sidebar"
21437
+ }, icon(this._side === "right" ? "chevron-right" : "chevron-left"));
21438
+ collapseButton.hidden = !this.options.collapsible;
21439
+ const footer = h("footer", { class: "zx-app-sidebar__footer" });
21440
+ appendSlot2(footer, this.options.footer);
21441
+ footer.hidden = !footer.hasChildNodes();
21442
+ const list = h("ul", { class: "zx-app-sidebar__list" });
21443
+ const expanded = h(
21444
+ "div",
21445
+ { class: "zx-app-sidebar__expanded" },
21446
+ h("header", { class: "zx-app-sidebar__header" }, headerContent, collapseButton),
21447
+ h("nav", { class: "zx-app-sidebar__nav", ariaLabel: this.options.label }, list),
21448
+ footer
21449
+ );
21450
+ this.refs.expanded = expanded;
21451
+ this.refs.list = list;
21452
+ this.refs.collapse = collapseButton;
21453
+ this.el.append(expanded);
21454
+ this._listenMode(collapseButton, "click", () => this.collapse());
21455
+ this._listenMode(list, "click", (event) => this._onTreeClick(event));
21456
+ this._listenMode(list, "keydown", (event) => this._onTreeKeydown(
21457
+ /** @type {KeyboardEvent} */
21458
+ event
21459
+ ));
21460
+ this._renderTree();
21461
+ }
21462
+ /** @returns {void} */
21463
+ _renderRail() {
21464
+ const railHost = h("nav", { class: "zx-app-sidebar__rail" });
21465
+ this.el.append(railHost);
21466
+ this.refs.rail = railHost;
21467
+ const showToggle = this._orientation === "vertical" && Boolean(this.options.collapsible);
21468
+ const railHeader = h("div", { class: "zx-app-sidebar__rail-header" });
21469
+ if (showToggle) {
21470
+ const expandButton = h("button", {
21471
+ class: "zx-app-sidebar__rail-toggle zx-icon-btn",
21472
+ type: "button",
21473
+ ariaLabel: "Expand application sidebar"
21474
+ }, icon(this._side === "right" ? "chevron-left" : "chevron-right"));
21475
+ railHeader.append(expandButton);
21476
+ this._railExpandButton = expandButton;
21477
+ this._listenMode(expandButton, "click", () => this.expand());
21478
+ }
21479
+ appendSlot2(railHeader, this.options.railHeader);
21480
+ this._rail = new AppRailPresenter(railHost, {
21481
+ items: this._items,
21482
+ active: this._active,
21483
+ orientation: this._orientation,
21484
+ side: this._side,
21485
+ label: this.options.label,
21486
+ header: railHeader.hasChildNodes() ? railHeader : null,
21487
+ footer: this.options.railFooter,
21488
+ openDelay: this.options.openDelay,
21489
+ closeDelay: this.options.closeDelay,
21490
+ renderIcon: this.options.renderIcon,
21491
+ onselect: (event) => {
21492
+ const selected = this.emit("select", event.detail);
21493
+ if (selected.defaultPrevented) event.preventDefault();
21494
+ },
21495
+ onflyoutchange: (event) => this.emit("flyoutchange", event.detail)
21496
+ });
21497
+ this._listenMode(railHost, "zx-select", (event) => event.stopPropagation());
21498
+ this._listenMode(railHost, "zx-flyoutchange", (event) => event.stopPropagation());
21499
+ }
21500
+ /** @param {EventTarget} target @param {string} type @param {EventListener} listener @returns {void} */
21501
+ _listenMode(target, type, listener) {
21502
+ target.addEventListener(type, listener, { signal: this._modeAbort.signal });
21503
+ }
20703
21504
  /** @returns {void} */
20704
21505
  _renderTree() {
21506
+ if (!this.refs.list) return;
20705
21507
  const focused = this.refs.list.contains(document.activeElement) ? (
20706
21508
  /** @type {Element} */
20707
21509
  document.activeElement.closest("[data-app-nav-id]")?.getAttribute("data-app-nav-id")
@@ -20720,7 +21522,14 @@ var zx = (() => {
20720
21522
  const key = String(item.id);
20721
21523
  const expanded = hasChildren && this._expanded.has(key);
20722
21524
  const childId = hasChildren ? uid("zx-app-sidebar-branch") : null;
20723
- const control = navControl2(item, hasChildren, expanded, childId);
21525
+ const visual = appNavIcon(item, this.options.renderIcon, {
21526
+ orientation: this._orientation,
21527
+ collapsed: false,
21528
+ side: this._side,
21529
+ level,
21530
+ location: level === 1 ? "root" : "inline"
21531
+ });
21532
+ const control = navControl2(item, hasChildren, expanded, childId, visual);
20724
21533
  const row2 = h("li", {
20725
21534
  class: "zx-app-sidebar__row",
20726
21535
  dataset: { level }
@@ -20734,7 +21543,7 @@ var zx = (() => {
20734
21543
  }
20735
21544
  return row2;
20736
21545
  }
20737
- /** Handles dynamic tree controls without retaining detached nodes after a rerender. @param {Event} event @returns {void} */
21546
+ /** @param {Event} event @returns {void} */
20738
21547
  _onTreeClick(event) {
20739
21548
  const control = (
20740
21549
  /** @type {Element|null} */
@@ -20749,6 +21558,45 @@ var zx = (() => {
20749
21558
  if (item.children?.length) this.toggleBranch(item.id);
20750
21559
  else this._select(item, event);
20751
21560
  }
21561
+ /** @param {KeyboardEvent} event @returns {void} */
21562
+ _onTreeKeydown(event) {
21563
+ const current = (
21564
+ /** @type {Element|null} */
21565
+ event.target?.closest?.("[data-app-nav-id]")
21566
+ );
21567
+ if (!(current instanceof HTMLElement)) return;
21568
+ const item = findItem(this._items, current.getAttribute("data-app-nav-id"));
21569
+ const controls = visibleTreeControls(this.refs.list);
21570
+ const index = controls.indexOf(current);
21571
+ if (event.key === "ArrowRight" && item?.children?.length) {
21572
+ event.preventDefault();
21573
+ if (!this._expanded.has(String(item.id))) this.toggleBranch(item.id);
21574
+ else visibleTreeControls(current.parentElement?.querySelector(".zx-app-sidebar__sublist"))[0]?.focus();
21575
+ return;
21576
+ }
21577
+ if (event.key === "ArrowLeft") {
21578
+ if (item?.children?.length && this._expanded.has(String(item.id))) {
21579
+ event.preventDefault();
21580
+ this.toggleBranch(item.id);
21581
+ return;
21582
+ }
21583
+ const parentList = current.closest(".zx-app-sidebar__sublist");
21584
+ const parentControl = parentList?.parentElement?.querySelector(":scope > [data-app-nav-id]");
21585
+ if (parentControl instanceof HTMLElement) {
21586
+ event.preventDefault();
21587
+ parentControl.focus();
21588
+ }
21589
+ return;
21590
+ }
21591
+ let nextIndex;
21592
+ if (event.key === "Home") nextIndex = 0;
21593
+ else if (event.key === "End") nextIndex = controls.length - 1;
21594
+ else if (event.key === "ArrowDown") nextIndex = Math.min(controls.length - 1, index + 1);
21595
+ else if (event.key === "ArrowUp") nextIndex = Math.max(0, index - 1);
21596
+ else return;
21597
+ event.preventDefault();
21598
+ controls[nextIndex]?.focus();
21599
+ }
20752
21600
  /** @param {AppNavItem} item @param {Event} event @returns {void} */
20753
21601
  _select(item, event) {
20754
21602
  if (item.disabled) {
@@ -20770,14 +21618,14 @@ var zx = (() => {
20770
21618
  }
20771
21619
  /** @returns {void} */
20772
21620
  _syncActive() {
21621
+ if (!this.refs.list) return;
20773
21622
  const activeId = this._active == null ? null : String(this._active);
20774
21623
  for (const control of this.refs.list.querySelectorAll("[data-app-nav-id]")) {
20775
21624
  const item = findItem(this._items, control.getAttribute("data-app-nav-id"));
20776
21625
  const direct = activeId !== null && control.getAttribute("data-app-nav-id") === activeId;
20777
21626
  const descendant = activeId !== null && item ? containsItemId2(item.children ?? [], activeId) : false;
20778
- if (direct) {
20779
- control.setAttribute("aria-current", "page");
20780
- } else control.removeAttribute("aria-current");
21627
+ if (direct) control.setAttribute("aria-current", "page");
21628
+ else control.removeAttribute("aria-current");
20781
21629
  if (descendant) control.dataset.activeDescendant = "true";
20782
21630
  else delete control.dataset.activeDescendant;
20783
21631
  }
@@ -20791,7 +21639,7 @@ var zx = (() => {
20791
21639
  for (const id of this._expanded) if (!branchIds.has(id)) this._expanded.delete(id);
20792
21640
  }
20793
21641
  };
20794
- function navControl2(item, disclosure, expanded, controls) {
21642
+ function navControl2(item, disclosure, expanded, controls, visual) {
20795
21643
  const tag = item.href && !disclosure ? "a" : "button";
20796
21644
  return (
20797
21645
  /** @type {HTMLElement} */
@@ -20807,7 +21655,7 @@ var zx = (() => {
20807
21655
  ariaControls: controls,
20808
21656
  dataset: { appNavId: String(item.id) }
20809
21657
  },
20810
- item.icon ? h("span", { class: "zx-app-sidebar__item-icon", ariaHidden: "true" }, icon(item.icon)) : null,
21658
+ visual ? h("span", { class: "zx-app-sidebar__item-icon", ariaHidden: "true" }, visual) : null,
20811
21659
  h("span", { class: "zx-app-sidebar__item-label" }, item.label),
20812
21660
  item.badge != null ? h("span", { class: "zx-app-sidebar__item-badge" }, String(item.badge)) : null,
20813
21661
  disclosure ? h(
@@ -20818,6 +21666,13 @@ var zx = (() => {
20818
21666
  )
20819
21667
  );
20820
21668
  }
21669
+ function visibleTreeControls(root) {
21670
+ if (!root) return [];
21671
+ return (
21672
+ /** @type {HTMLElement[]} */
21673
+ [...root.querySelectorAll("[data-app-nav-id]")].filter((control) => !control.closest(".zx-app-sidebar__sublist[hidden]"))
21674
+ );
21675
+ }
20821
21676
  function visibleTreeControl(control) {
20822
21677
  let candidate = (
20823
21678
  /** @type {HTMLElement|null} */
@@ -20850,12 +21705,17 @@ var zx = (() => {
20850
21705
  }
20851
21706
  function appendSlot2(target, content) {
20852
21707
  if (content == null) return;
20853
- if (typeof content === "string" || typeof content === "number") target.append(document.createTextNode(String(content)));
20854
- else if (typeof content === "object" && typeof content.toElement === "function") {
21708
+ if (typeof content === "string" || typeof content === "number") {
21709
+ target.append(document.createTextNode(String(content)));
21710
+ } else if (typeof content === "object" && typeof content.toElement === "function") {
20855
21711
  const element = content.toElement();
20856
21712
  if (element) target.append(element);
20857
21713
  } else if (typeof content === "object" && typeof content.nodeType === "number") target.append(content);
20858
21714
  }
21715
+ function normalizeSide(orientation, side) {
21716
+ if (orientation === "horizontal") return side === "bottom" ? "bottom" : "top";
21717
+ return side === "right" ? "right" : "left";
21718
+ }
20859
21719
 
20860
21720
  // src/components/message/message.js
20861
21721
  var MessageQueue = class {
@@ -22223,9 +23083,9 @@ var zx = (() => {
22223
23083
  } = options;
22224
23084
  const fields = { ...DEFAULT_FIELDS, ...requestedFields };
22225
23085
  const calculateLineTotal = typeof lineTotal === "function" ? lineTotal : (row2) => {
22226
- const quantity = Number(row2?.[fields.quantity]);
22227
- const price = Number(row2?.[fields.unitPrice]);
22228
- return Number.isFinite(quantity) && Number.isFinite(price) ? quantity * price : null;
23086
+ const quantity = billingNumber(row2?.[fields.quantity]);
23087
+ const price = billingNumber(row2?.[fields.unitPrice]);
23088
+ return quantity !== null && price !== null ? quantity * price : null;
22229
23089
  };
22230
23090
  const columns = Array.isArray(suppliedColumns) ? suppliedColumns.map((column) => ({ ...column })) : billingColumns(fields, { units, currencies, currency, locale, decimals, columnOverrides });
22231
23091
  return {
@@ -22330,6 +23190,11 @@ var zx = (() => {
22330
23190
  function hasChoices(value) {
22331
23191
  return Array.isArray(value) ? value.length > 0 : Boolean(value && typeof value === "object" && Object.keys(value).length);
22332
23192
  }
23193
+ function billingNumber(value) {
23194
+ if (value == null || typeof value === "string" && value.trim() === "") return null;
23195
+ const number2 = Number(value);
23196
+ return Number.isFinite(number2) ? number2 : null;
23197
+ }
22333
23198
 
22334
23199
  // src/components/chart/chart.js
22335
23200
  var Chart = class extends Component {
@@ -22377,6 +23242,7 @@ var zx = (() => {
22377
23242
  this._chartOptions = cloneChartValue(this.options.chartOptions);
22378
23243
  this._loading = Boolean(this.options.loading);
22379
23244
  this._error = null;
23245
+ this._hasError = false;
22380
23246
  this._handle = null;
22381
23247
  this._destroyed = false;
22382
23248
  const descriptionId = uid("zx-chart-description");
@@ -22422,6 +23288,7 @@ var zx = (() => {
22422
23288
  setData(data) {
22423
23289
  this._data = normalizeChartData(data);
22424
23290
  this._error = null;
23291
+ this._hasError = false;
22425
23292
  this._loading = false;
22426
23293
  this._renderSummary();
22427
23294
  this._mountOrUpdate(true);
@@ -22457,6 +23324,7 @@ var zx = (() => {
22457
23324
  /** Shows an error and destroys the current engine. @param {unknown} error @returns {this} */
22458
23325
  setError(error) {
22459
23326
  this._error = error ?? new Error(String(this.options.errorText));
23327
+ this._hasError = true;
22460
23328
  this._loading = false;
22461
23329
  this._destroyHandle();
22462
23330
  this._syncState();
@@ -22466,6 +23334,7 @@ var zx = (() => {
22466
23334
  /** Clears the error and remounts when data and an adapter are available. @returns {this} */
22467
23335
  clearError() {
22468
23336
  this._error = null;
23337
+ this._hasError = false;
22469
23338
  this._mountOrUpdate(false);
22470
23339
  return this;
22471
23340
  }
@@ -22504,7 +23373,7 @@ var zx = (() => {
22504
23373
  /** @param {boolean} emitUpdate @returns {void} */
22505
23374
  _mountOrUpdate(emitUpdate) {
22506
23375
  this._syncState();
22507
- if (this._loading || this._error || isEmptyChartData(this._data)) {
23376
+ if (this._loading || this._hasError || isEmptyChartData(this._data)) {
22508
23377
  this._destroyHandle();
22509
23378
  return;
22510
23379
  }
@@ -22559,7 +23428,7 @@ var zx = (() => {
22559
23428
  /** @returns {void} */
22560
23429
  _syncState() {
22561
23430
  const empty = isEmptyChartData(this._data);
22562
- const state = this._loading ? "loading" : this._error ? "error" : empty ? "empty" : "ready";
23431
+ const state = this._loading ? "loading" : this._hasError ? "error" : empty ? "empty" : "ready";
22563
23432
  this.el.dataset.state = state;
22564
23433
  this.refs.viewport.hidden = state !== "ready";
22565
23434
  let text4 = "";
@@ -22771,7 +23640,8 @@ var zx = (() => {
22771
23640
  };
22772
23641
  }
22773
23642
  function errorMessage(error, fallback) {
22774
- return error && typeof error === "object" && "message" in error ? String(error.message) : String(fallback);
23643
+ const message = error && typeof error === "object" && "message" in error ? String(error.message) : "";
23644
+ return message.trim() ? message : String(fallback);
22775
23645
  }
22776
23646
 
22777
23647
  // src/components/tree/hierarchy.js