@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.esm.js CHANGED
@@ -267,6 +267,18 @@ function compactElement(compact, properties = null, content = null) {
267
267
  return element;
268
268
  }
269
269
  var __ = compactElement;
270
+ function safeHref(value) {
271
+ if (typeof value !== "string") return null;
272
+ const href = value.trim();
273
+ if (!href) return null;
274
+ try {
275
+ const protocol = new URL(href, globalThis.location?.href ?? "https://zx.invalid/").protocol;
276
+ if (["javascript:", "data:", "vbscript:"].includes(protocol.toLowerCase())) return null;
277
+ } catch {
278
+ return null;
279
+ }
280
+ return href;
281
+ }
270
282
  function applyCompactProperties(element, properties) {
271
283
  for (const name in properties ?? {}) {
272
284
  const value = properties[name];
@@ -6617,7 +6629,18 @@ var PRIORITY_KEYS = [
6617
6629
  "priority.high",
6618
6630
  "priority.highest"
6619
6631
  ];
6620
- var PRIORITY_FALLBACKS = ["Lowest", "Low", "Normal", "High", "Highest"];
6632
+ var PRIORITY_FALLBACKS = ["Lowest", "Low", "Medium", "High", "Highest"];
6633
+ var DEFAULT_STATUSES = Object.freeze([
6634
+ ["backlog", "status.backlog", "Backlog", "dotted", "muted"],
6635
+ ["todo", "status.todo", "Todo", "ring", "neutral"],
6636
+ ["in-progress", "status.inProgress", "In Progress", "progress", "warning"],
6637
+ ["in-review", "status.inReview", "In Review", "review", "info"],
6638
+ ["ready", "status.ready", "Ready to Merge", "ready", "accent"],
6639
+ ["waiting", "status.waiting", "Waiting for feedback", "waiting", "warning"],
6640
+ ["done", "status.done", "Done", "done", "info"],
6641
+ ["canceled", "status.canceled", "Canceled", "canceled", "muted"],
6642
+ ["duplicate", "status.duplicate", "Duplicate", "duplicate", "muted"]
6643
+ ]);
6621
6644
  var Select = class _Select extends Component {
6622
6645
  static cssName = "select";
6623
6646
  /** @type {SelectOptions} */
@@ -6628,6 +6651,10 @@ var Select = class _Select extends Component {
6628
6651
  labelKey: "name",
6629
6652
  renderItem: null,
6630
6653
  renderValue: null,
6654
+ renderValueAdornment: null,
6655
+ renderNone: null,
6656
+ noneLabel: null,
6657
+ actions: [],
6631
6658
  value: null,
6632
6659
  disabled: false,
6633
6660
  placeholder: "",
@@ -6645,6 +6672,7 @@ var Select = class _Select extends Component {
6645
6672
  this._visibleItems = [];
6646
6673
  this._fixedItems = [];
6647
6674
  this._visibleFixed = [];
6675
+ this._actions = [];
6648
6676
  this._selected = null;
6649
6677
  this._optionEntries = [];
6650
6678
  this._activeIndex = -1;
@@ -6683,6 +6711,12 @@ var Select = class _Select extends Component {
6683
6711
  placeholder: String(this.options.placeholder ?? ""),
6684
6712
  readOnly: !this.options.filter
6685
6713
  });
6714
+ const valueAdornment = h("span", {
6715
+ ref: "valueAdornment",
6716
+ class: "zx-select__value-adornment",
6717
+ ariaHidden: "true",
6718
+ hidden: true
6719
+ });
6686
6720
  const clear = h("button", {
6687
6721
  ref: "clear",
6688
6722
  class: "zx-icon-btn zx-select__clear",
@@ -6708,6 +6742,7 @@ var Select = class _Select extends Component {
6708
6742
  const control = h(
6709
6743
  "div",
6710
6744
  { ref: "control", class: "zx-select__control" },
6745
+ valueAdornment,
6711
6746
  input,
6712
6747
  clear,
6713
6748
  loading,
@@ -6730,9 +6765,10 @@ var Select = class _Select extends Component {
6730
6765
  this._visibleItems = this._items.slice();
6731
6766
  this._fixedItems = Array.isArray(this.options.fixedItems) ? this.options.fixedItems.slice() : [];
6732
6767
  this._visibleFixed = this._fixedItems.slice();
6768
+ this._actions = normalizeSelectActions(this.options.actions);
6733
6769
  this._disabled = Boolean(this.options.disabled);
6734
6770
  this._handleTypeahead = typeahead(
6735
- () => this._optionEntries.filter((entry) => !entry.clear).map((entry) => entry.element),
6771
+ () => this._optionEntries.filter((entry) => !entry.clear && !entry.action).map((entry) => entry.element),
6736
6772
  (_option, index) => {
6737
6773
  if (!this._open) this.open();
6738
6774
  this._setActive(this._itemEntryIndex(index));
@@ -6827,6 +6863,13 @@ var Select = class _Select extends Component {
6827
6863
  this._position?.update();
6828
6864
  return this;
6829
6865
  }
6866
+ /** Replaces command rows without changing the selected value. @param {SelectAction[]} actions @returns {this} */
6867
+ setActions(actions2) {
6868
+ this._actions = normalizeSelectActions(actions2);
6869
+ this._renderOptions();
6870
+ this._position?.update();
6871
+ return this;
6872
+ }
6830
6873
  /**
6831
6874
  * Restores the configured initial value.
6832
6875
  * @param {{silent?: boolean}} [options={}] Reset behavior.
@@ -6911,26 +6954,51 @@ var Select = class _Select extends Component {
6911
6954
  value: null,
6912
6955
  items: [],
6913
6956
  renderItem(item) {
6914
- return h(
6915
- "span",
6916
- { class: "zx-select__priority" },
6917
- h("span", {
6918
- class: "zx-select__priority-icon",
6919
- dataset: { level: item.level },
6920
- ariaHidden: "true"
6921
- }),
6922
- h("span", {}, String(item.name))
6923
- );
6957
+ return priorityContent(item, true);
6958
+ },
6959
+ renderValueAdornment(item) {
6960
+ return priorityIndicator(item.level);
6924
6961
  }
6925
6962
  });
6926
- select.setItems(PRIORITY_KEYS.map((key, level) => ({
6927
- ID: level,
6928
- name: select._message(key, PRIORITY_FALLBACKS[level]),
6929
- level
6930
- })));
6963
+ select.setItems(PRIORITY_KEYS.map((key, level) => {
6964
+ let name = level === 2 ? select.msg("priority.medium") : key;
6965
+ if (name === "priority.medium") name = select._message(key, PRIORITY_FALLBACKS[level]);
6966
+ else if (level !== 2) name = select._message(key, PRIORITY_FALLBACKS[level]);
6967
+ return { ID: level, name, level };
6968
+ }));
6931
6969
  select.set(requestedValue, { silent: true });
6932
6970
  return select;
6933
6971
  }
6972
+ /**
6973
+ * Creates a status picker with shape- and color-distinct workflow indicators.
6974
+ * @param {Element|string|null} target Component target.
6975
+ * @param {SelectOptions & {items?: Array<Record<string, any>>}} [options={}] Select overrides and status items.
6976
+ * @returns {Select}
6977
+ */
6978
+ static status(target, options = {}) {
6979
+ const { items = null, value = null, ...rest } = options;
6980
+ const select = new _Select(target, {
6981
+ ...rest,
6982
+ value: null,
6983
+ items: [],
6984
+ renderItem(item) {
6985
+ return statusContent(item);
6986
+ },
6987
+ renderValueAdornment(item) {
6988
+ return statusIndicator(item);
6989
+ }
6990
+ });
6991
+ const statuses = Array.isArray(items) ? items.map(normalizeStatusItem).filter(Boolean) : DEFAULT_STATUSES.map(([ID, key, fallback, indicator, tone], index) => ({
6992
+ ID,
6993
+ name: select._message(key, fallback),
6994
+ indicator,
6995
+ tone,
6996
+ shortcut: String(index + 1)
6997
+ }));
6998
+ select.setItems(statuses);
6999
+ select.set(value, { silent: true });
7000
+ return select;
7001
+ }
6934
7002
  /**
6935
7003
  * Creates the record-permission picker: the fixed Private and Public choices above the groups a
6936
7004
  * record can be shared with. The value is the tri-state ZeyOS stores — `false` for private,
@@ -6943,12 +7011,21 @@ var Select = class _Select extends Component {
6943
7011
  * @returns {Select}
6944
7012
  */
6945
7013
  static permission(target, options = {}) {
6946
- const { groups = [], value = true, valueKey = "ID", labelKey = "name", ...rest } = options;
7014
+ const {
7015
+ groups = [],
7016
+ value = true,
7017
+ valueKey = "ID",
7018
+ labelKey = "name",
7019
+ filter = "local",
7020
+ ...rest
7021
+ } = options;
6947
7022
  const loader = typeof groups === "function" ? groups : null;
6948
7023
  const shareable = loader ? [] : groups;
6949
7024
  const choices = [{ ID: false, name: "Private" }, { ID: true, name: "Public" }];
6950
7025
  const select = new _Select(target, {
6951
- filter: loader ?? "local",
7026
+ // An async group loader is the data source and cannot be disconnected by an incidental
7027
+ // `filter` override. Array-backed groups still honor local/disabled filtering.
7028
+ filter: loader ?? filter,
6952
7029
  ...rest,
6953
7030
  value: null,
6954
7031
  items: shareable,
@@ -6984,6 +7061,18 @@ var Select = class _Select extends Component {
6984
7061
  /** @param {KeyboardEvent} event @returns {void} */
6985
7062
  _onKeydown(event) {
6986
7063
  if (this._disabled) return;
7064
+ if (this._open && isPrintable(event)) {
7065
+ const shortcut = this._optionEntries.findIndex((entry) => !entry.clear && !entry.action && String(
7066
+ /** @type {Record<string, any>|null} */
7067
+ entry.item?.shortcut ?? ""
7068
+ ) === event.key);
7069
+ if (shortcut >= 0) {
7070
+ event.preventDefault();
7071
+ this._setActive(shortcut);
7072
+ this._chooseEntry(this._optionEntries[shortcut]);
7073
+ return;
7074
+ }
7075
+ }
6987
7076
  if (event.key === "ArrowDown") {
6988
7077
  event.preventDefault();
6989
7078
  if (!this._open) this.open();
@@ -7031,6 +7120,7 @@ var Select = class _Select extends Component {
7031
7120
  /** @type {HTMLInputElement} */
7032
7121
  this.refs.input.value
7033
7122
  );
7123
+ this._syncValueAdornment();
7034
7124
  if (!this._open) this.open();
7035
7125
  if (this.options.filter === "local") {
7036
7126
  this._visibleFixed = this._matchFixed(query);
@@ -7054,6 +7144,22 @@ var Select = class _Select extends Component {
7054
7144
  /** @param {SelectOptionEntry|undefined} entry @returns {void} */
7055
7145
  _chooseEntry(entry) {
7056
7146
  if (!entry) return;
7147
+ if (entry.action) {
7148
+ const detail = {
7149
+ id: entry.action.id,
7150
+ action: entry.action,
7151
+ query: (
7152
+ /** @type {HTMLInputElement} */
7153
+ this.refs.input.value
7154
+ ),
7155
+ select: this
7156
+ };
7157
+ const action = this.emit("action", detail);
7158
+ if (!action.defaultPrevented) entry.action.invoke?.(detail);
7159
+ this.close();
7160
+ this.focus();
7161
+ return;
7162
+ }
7057
7163
  this.set(entry.clear ? null : entry.item);
7058
7164
  this.close();
7059
7165
  this.focus();
@@ -7087,7 +7193,7 @@ var Select = class _Select extends Component {
7087
7193
  this._optionEntries = [];
7088
7194
  let lastGroup = /* @__PURE__ */ Symbol("initial-group");
7089
7195
  if (this.options.clearable) {
7090
- this._appendOption(null, true, this.options.placeholder || this._message("select.none", "No selection"));
7196
+ this._appendOption(null, true, this.options.noneLabel ?? this._message("select.none", "No selection"));
7091
7197
  }
7092
7198
  for (const item of this._visibleFixed) this._appendOption(item, false);
7093
7199
  if (this._visibleFixed.length > 0 && this._visibleItems.length > 0 && !this.options.groupKey) {
@@ -7106,7 +7212,23 @@ var Select = class _Select extends Component {
7106
7212
  }
7107
7213
  this._appendOption(item, false);
7108
7214
  }
7109
- if (this._visibleItems.length === 0 && this._visibleFixed.length === 0 && !this._loading) {
7215
+ if (this._actions.length > 0) {
7216
+ if (this._visibleFixed.length > 0 || this._visibleItems.length > 0 || this.options.clearable) {
7217
+ list.append(h("div", { class: "zx-select__separator", role: "presentation" }));
7218
+ }
7219
+ let actionGroup = /* @__PURE__ */ Symbol("initial-action-group");
7220
+ for (const action of this._actions) {
7221
+ if (action.group && action.group !== actionGroup) {
7222
+ list.append(h("div", {
7223
+ class: "zx-select__group",
7224
+ role: "presentation"
7225
+ }, action.group));
7226
+ actionGroup = action.group;
7227
+ }
7228
+ this._appendAction(action);
7229
+ }
7230
+ }
7231
+ if (this._visibleItems.length === 0 && this._visibleFixed.length === 0 && this._actions.length === 0 && !this._loading) {
7110
7232
  list.append(h("div", {
7111
7233
  class: "zx-select__empty",
7112
7234
  role: "status"
@@ -7118,7 +7240,7 @@ var Select = class _Select extends Component {
7118
7240
  _appendOption(item, clear, label) {
7119
7241
  const selected = clear ? this._selected === null : this._selected !== null && Object.is(this._itemValue(item), this.value);
7120
7242
  const index = this._optionEntries.length;
7121
- const content = clear ? String(label ?? "") : this._renderItem(item);
7243
+ const content = clear && typeof this.options.renderNone === "function" ? normalizeRenderedContent(this.options.renderNone(String(label ?? ""))) : clear ? String(label ?? "") : this._renderItem(item);
7122
7244
  const option = h("div", {
7123
7245
  class: "zx-select__option",
7124
7246
  id: uid("zx-select-option"),
@@ -7128,16 +7250,37 @@ var Select = class _Select extends Component {
7128
7250
  }, h("span", { class: "zx-select__option-content" }, content));
7129
7251
  if (selected) option.append(icon("check", { size: 15 }));
7130
7252
  this.refs.list.append(option);
7131
- this._optionEntries.push({ item, clear, element: option });
7253
+ this._optionEntries.push({ item, clear, action: null, element: option });
7254
+ }
7255
+ /** @param {SelectAction} action @returns {void} */
7256
+ _appendAction(action) {
7257
+ const index = this._optionEntries.length;
7258
+ const visual = selectActionIcon(action.icon);
7259
+ const option = h("div", {
7260
+ class: "zx-select__option zx-select__action",
7261
+ id: uid("zx-select-option"),
7262
+ role: "option",
7263
+ ariaSelected: "false",
7264
+ dataset: { index, kind: "action" }
7265
+ }, h(
7266
+ "span",
7267
+ { class: "zx-select__option-content zx-select__action-content" },
7268
+ visual ? h("span", { class: "zx-select__action-icon", ariaHidden: "true" }, visual) : null,
7269
+ h(
7270
+ "span",
7271
+ { class: "zx-select__action-copy" },
7272
+ h("span", { class: "zx-select__action-label" }, action.label),
7273
+ action.description ? h("span", { class: "zx-select__action-description" }, action.description) : null
7274
+ )
7275
+ ));
7276
+ this.refs.list.append(option);
7277
+ this._optionEntries.push({ item: null, clear: false, action, element: option });
7132
7278
  }
7133
7279
  /** @param {SelectItem|null} item @returns {Node|string} */
7134
7280
  _renderItem(item) {
7135
7281
  if (typeof this.options.renderItem === "function") {
7136
7282
  const rendered = this.options.renderItem(item);
7137
- return rendered && typeof rendered === "object" && "nodeType" in rendered ? (
7138
- /** @type {Node} */
7139
- rendered
7140
- ) : String(rendered ?? "");
7283
+ return normalizeRenderedContent(rendered);
7141
7284
  }
7142
7285
  return this._itemLabel(item);
7143
7286
  }
@@ -7208,9 +7351,29 @@ var Select = class _Select extends Component {
7208
7351
  this.refs.input
7209
7352
  );
7210
7353
  input.value = this._selected === null ? "" : typeof this.options.renderValue === "function" ? String(this.options.renderValue(this._selected) ?? "") : this._itemLabel(this._selected);
7354
+ this._syncValueAdornment();
7211
7355
  this.refs.clear.hidden = !this.options.clearable || this._selected === null;
7212
7356
  }
7213
7357
  /** @returns {void} */
7358
+ _syncValueAdornment() {
7359
+ const host = this.refs.valueAdornment;
7360
+ if (!host) return;
7361
+ const selectedLabel = this._selected === null ? "" : typeof this.options.renderValue === "function" ? String(this.options.renderValue(this._selected) ?? "") : this._itemLabel(this._selected);
7362
+ const query = String(
7363
+ /** @type {HTMLInputElement} */
7364
+ this.refs.input?.value ?? ""
7365
+ );
7366
+ if (this._selected === null || typeof this.options.renderValueAdornment !== "function" || Boolean(this.options.filter && query !== selectedLabel)) {
7367
+ host.replaceChildren();
7368
+ host.hidden = true;
7369
+ return;
7370
+ }
7371
+ const rendered = this.options.renderValueAdornment(this._selected);
7372
+ const content = normalizeRenderedContent(rendered);
7373
+ host.replaceChildren(content instanceof Node ? content : document.createTextNode(content));
7374
+ host.hidden = rendered == null || typeof content === "string" && content === "";
7375
+ }
7376
+ /** @returns {void} */
7214
7377
  _syncDisabled() {
7215
7378
  const input = (
7216
7379
  /** @type {HTMLInputElement} */
@@ -7287,6 +7450,82 @@ var Select = class _Select extends Component {
7287
7450
  return message === key ? fallback : message;
7288
7451
  }
7289
7452
  };
7453
+ function normalizeRenderedContent(rendered) {
7454
+ return rendered && typeof rendered === "object" && "nodeType" in rendered ? (
7455
+ /** @type {Node} */
7456
+ rendered
7457
+ ) : String(rendered ?? "");
7458
+ }
7459
+ function normalizeSelectActions(actions2) {
7460
+ if (!Array.isArray(actions2)) return [];
7461
+ return actions2.filter((action) => action && typeof action === "object" && action.id != null && action.label != null).map((action) => ({
7462
+ ...action,
7463
+ label: String(action.label),
7464
+ description: action.description == null ? void 0 : String(action.description),
7465
+ group: action.group == null ? void 0 : String(action.group)
7466
+ }));
7467
+ }
7468
+ function selectActionIcon(visual) {
7469
+ if (typeof visual === "string") return icon(visual);
7470
+ if (visual instanceof Node) return visual.cloneNode(true);
7471
+ return null;
7472
+ }
7473
+ function priorityContent(item, label) {
7474
+ return h(
7475
+ "span",
7476
+ { class: "zx-select__priority" },
7477
+ priorityIndicator(item.level),
7478
+ label ? h("span", {}, String(item.name)) : null
7479
+ );
7480
+ }
7481
+ function priorityIndicator(level) {
7482
+ const normalized = Math.max(0, Math.min(4, Math.trunc(Number(level) || 0)));
7483
+ return h("span", {
7484
+ class: "zx-select__priority-icon",
7485
+ dataset: { level: normalized },
7486
+ ariaHidden: "true"
7487
+ }, Array.from({ length: 5 }, (_, index) => h("span", {
7488
+ dataset: { filled: String(index <= normalized) }
7489
+ })));
7490
+ }
7491
+ function normalizeStatusItem(input) {
7492
+ if (!input || typeof input !== "object" || input.ID == null || input.name == null) return null;
7493
+ const indicators = ["dotted", "ring", "progress", "review", "ready", "waiting", "done", "canceled", "duplicate"];
7494
+ const tones = ["muted", "neutral", "warning", "info", "accent", "success", "danger"];
7495
+ return {
7496
+ ...input,
7497
+ name: String(input.name),
7498
+ indicator: indicators.includes(String(input.indicator)) ? String(input.indicator) : "ring",
7499
+ tone: tones.includes(String(input.tone)) ? String(input.tone) : "muted",
7500
+ description: input.description == null ? "" : String(input.description),
7501
+ shortcut: input.shortcut == null ? "" : String(input.shortcut)
7502
+ };
7503
+ }
7504
+ function statusContent(item) {
7505
+ return h(
7506
+ "span",
7507
+ { class: "zx-select__status" },
7508
+ statusIndicator(item),
7509
+ h(
7510
+ "span",
7511
+ { class: "zx-select__status-copy" },
7512
+ h("span", { class: "zx-select__status-label" }, String(item.name)),
7513
+ item.description ? h("span", { class: "zx-select__status-description" }, item.description) : null
7514
+ ),
7515
+ item.shortcut ? h("kbd", { class: "zx-select__status-shortcut" }, item.shortcut) : null
7516
+ );
7517
+ }
7518
+ function statusIndicator(item) {
7519
+ const indicator = String(item.indicator ?? "ring");
7520
+ const marker = h("span", {
7521
+ class: "zx-select__status-indicator",
7522
+ dataset: { indicator, tone: String(item.tone ?? "muted") },
7523
+ ariaHidden: "true"
7524
+ });
7525
+ if (indicator === "done") marker.append(icon("check", { size: 8 }));
7526
+ if (indicator === "canceled") marker.append(icon("x", { size: 7 }));
7527
+ return marker;
7528
+ }
7290
7529
  function readItem(item, reader) {
7291
7530
  if (typeof reader === "function") return reader(item);
7292
7531
  if (item === null || typeof item !== "object") return item;
@@ -7299,10 +7538,11 @@ function isPrintable(event) {
7299
7538
  // src/components/select/field-adapter.js
7300
7539
  function registerSelectFieldAdapter() {
7301
7540
  Field.register("zxselect", (field, options) => {
7302
- const select = field.own(new Select(null, {
7303
- ...options.props ?? {},
7304
- value: options.value
7305
- }));
7541
+ const props = options.props ?? {};
7542
+ const preset = props.preset;
7543
+ const selectOptions = { ...props, value: options.value };
7544
+ delete selectOptions.preset;
7545
+ const select = field.own(preset === "priority" ? Select.priority(null, selectOptions) : preset === "status" ? Select.status(null, selectOptions) : new Select(null, selectOptions));
7306
7546
  field.listen(select.el, "change", (event) => event.stopImmediatePropagation());
7307
7547
  select.on("change", (event) => field.emit("change", { value: event.detail.value }));
7308
7548
  return {
@@ -11249,12 +11489,14 @@ var Table = class extends Component {
11249
11489
  }
11250
11490
  /** @returns {HTMLElement} Table root. */
11251
11491
  render() {
11492
+ const created = this.el === null;
11252
11493
  const root = (
11253
11494
  /** @type {HTMLElement} */
11254
11495
  this.el ?? h("div")
11255
11496
  );
11256
11497
  this.el = root;
11257
- this._originalChildren = this.el ? Array.from(this.el.childNodes) : null;
11498
+ this._createdRoot = created;
11499
+ this._original = created ? null : snapshotTarget(root);
11258
11500
  this._columns = Array.isArray(this.options.columns) ? this.options.columns.map((column) => ({ ...column })) : [];
11259
11501
  this._data = Array.isArray(this.options.data) ? [...this.options.data] : [];
11260
11502
  this._selected = /* @__PURE__ */ new Set();
@@ -11329,8 +11571,8 @@ var Table = class extends Component {
11329
11571
  }
11330
11572
  /**
11331
11573
  * Toggles a loading state. While loading, the body is dimmed, an indeterminate progress bar is
11332
- * shown, and `aria-busy` is set — a lightweight take on the busy/skeleton pattern used by
11333
- * enterprise tables (SAP UI5, Salesforce SLDS). Call `setLoading(false)` (or `setData`) when done.
11574
+ * shown, and `aria-busy` is set — a lightweight enterprise-table busy and skeleton pattern.
11575
+ * Call `setLoading(false)` (or `setData`) when done.
11334
11576
  * @param {boolean} [loading=true] Whether the table is loading.
11335
11577
  * @returns {this}
11336
11578
  */
@@ -11879,9 +12121,8 @@ var Table = class extends Component {
11879
12121
  this._teardownEdit();
11880
12122
  this._width?.destroy();
11881
12123
  this._width = null;
11882
- if (this.el) delete this.el.dataset.editMode;
11883
- if (!this._restored && this._originalChildren) {
11884
- this.el.replaceChildren(...this._originalChildren);
12124
+ if (!this._restored && !this._createdRoot) {
12125
+ restoreTarget(this.el, this._original);
11885
12126
  this._restored = true;
11886
12127
  }
11887
12128
  super.destroy();
@@ -15242,6 +15483,244 @@ function normalizeAlign2(value) {
15242
15483
  ) : "start";
15243
15484
  }
15244
15485
 
15486
+ // src/components/card/card.js
15487
+ var Card = class extends Component {
15488
+ static cssName = "card";
15489
+ /** @type {Readonly<CardOptions>} */
15490
+ static defaults = {
15491
+ title: "",
15492
+ media: null,
15493
+ content: null,
15494
+ actions: [],
15495
+ footer: null,
15496
+ link: null,
15497
+ variant: "outlined",
15498
+ orientation: "vertical",
15499
+ headingLevel: 3
15500
+ };
15501
+ /**
15502
+ * Creates or enhances one card.
15503
+ * @param {Element|string|null} [target=null] Existing card container, selector, or null.
15504
+ * @param {CardOptions} [options={}] Card options.
15505
+ */
15506
+ constructor(target = null, options = {}) {
15507
+ super(target, options);
15508
+ }
15509
+ /** @returns {HTMLElement} */
15510
+ render() {
15511
+ const created = !this.el;
15512
+ const root = (
15513
+ /** @type {HTMLElement} */
15514
+ this.el ?? h("article")
15515
+ );
15516
+ this.el = root;
15517
+ this._createdRoot = created;
15518
+ this._original = created ? null : snapshotTarget(root);
15519
+ this._cleaned = false;
15520
+ this._title = "";
15521
+ this._link = null;
15522
+ this._actionAbort = null;
15523
+ this._linkAbort = null;
15524
+ const originalContent = created ? [] : Array.from(root.childNodes);
15525
+ try {
15526
+ const headingLevel = normalizeHeadingLevel(this.options.headingLevel);
15527
+ const headingId = uid("zx-card-heading");
15528
+ const heading = h(`h${headingLevel}`, {
15529
+ ref: "heading",
15530
+ class: "zx-card__heading",
15531
+ id: headingId
15532
+ });
15533
+ const media = h("div", { ref: "media", class: "zx-card__media", hidden: true });
15534
+ const actions2 = h("div", {
15535
+ ref: "actions",
15536
+ class: "zx-card__actions",
15537
+ role: "group",
15538
+ ariaLabel: "Card actions",
15539
+ hidden: true
15540
+ });
15541
+ const content = h("div", { ref: "content", class: "zx-card__content", hidden: true });
15542
+ const footer = h("footer", { ref: "footer", class: "zx-card__footer", hidden: true });
15543
+ const header = h("header", { ref: "header", class: "zx-card__header" }, heading, actions2);
15544
+ root.replaceChildren(h(
15545
+ "div",
15546
+ { class: "zx-card__layout" },
15547
+ media,
15548
+ h("div", { class: "zx-card__inner" }, header, content, footer)
15549
+ ));
15550
+ root.dataset.variant = normalizeVariant2(this.options.variant);
15551
+ root.dataset.orientation = normalizeOrientation(this.options.orientation);
15552
+ this.setTitle(this.options.title);
15553
+ this.setMedia(this.options.media);
15554
+ if (this.options.content !== null) this.setContent(this.options.content);
15555
+ else if (originalContent.length) {
15556
+ content.replaceChildren(...originalContent);
15557
+ content.hidden = false;
15558
+ }
15559
+ this.setActions(this.options.actions);
15560
+ this.setFooter(this.options.footer);
15561
+ this.setLink(this.options.link);
15562
+ return root;
15563
+ } catch (error) {
15564
+ this._actionAbort?.abort();
15565
+ this._linkAbort?.abort();
15566
+ if (!created) restoreTarget(root, this._original);
15567
+ throw error;
15568
+ }
15569
+ }
15570
+ /** Replaces the card title. @param {string} title Next title. @returns {this} */
15571
+ setTitle(title) {
15572
+ const next = String(title ?? "");
15573
+ if (this._link && !next.trim()) throw new TypeError("A linked Card requires a non-empty title");
15574
+ this._title = next;
15575
+ this._renderHeading();
15576
+ return this;
15577
+ }
15578
+ /** Replaces or removes the media region. @param {CardContent} content Media. @returns {this} */
15579
+ setMedia(content) {
15580
+ replaceCardContent(this.refs.media, content, "Card media");
15581
+ this.refs.media.hidden = content === null;
15582
+ return this;
15583
+ }
15584
+ /** Replaces or clears the main content. @param {CardContent} content Content. @returns {this} */
15585
+ setContent(content) {
15586
+ replaceCardContent(this.refs.content, content, "Card content");
15587
+ this.refs.content.hidden = content === null;
15588
+ return this;
15589
+ }
15590
+ /** Replaces the secondary action controls. @param {CardAction[]} actions Actions. @returns {this} */
15591
+ setActions(actions2) {
15592
+ if (!Array.isArray(actions2)) throw new TypeError("Card actions must be an array");
15593
+ const nextAbort = new AbortController();
15594
+ let controls;
15595
+ try {
15596
+ controls = actions2.map((action) => {
15597
+ if (isElement(action)) return action;
15598
+ if (!action || typeof action !== "object" || Array.isArray(action)) {
15599
+ throw new TypeError("Card actions must be Elements or button descriptors");
15600
+ }
15601
+ const descriptor = { ...action };
15602
+ const onclick = descriptor.onclick;
15603
+ delete descriptor.onclick;
15604
+ const control = button(descriptor);
15605
+ if (onclick !== void 0 && typeof onclick !== "function") {
15606
+ throw new TypeError("Card action onclick must be a function");
15607
+ }
15608
+ if (onclick) control.addEventListener("click", onclick, { signal: nextAbort.signal });
15609
+ return control;
15610
+ });
15611
+ } catch (error) {
15612
+ nextAbort.abort();
15613
+ throw error;
15614
+ }
15615
+ this._actionAbort?.abort();
15616
+ this._actionAbort = nextAbort;
15617
+ this.refs.actions.replaceChildren(...controls);
15618
+ this.refs.actions.hidden = controls.length === 0;
15619
+ this._syncHeader();
15620
+ return this;
15621
+ }
15622
+ /** Replaces or removes the footer. @param {CardContent} content Footer. @returns {this} */
15623
+ setFooter(content) {
15624
+ replaceCardContent(this.refs.footer, content, "Card footer");
15625
+ this.refs.footer.hidden = content === null;
15626
+ return this;
15627
+ }
15628
+ /** Sets or removes the title's native primary link. @param {CardLink} link Link. @returns {this} */
15629
+ setLink(link) {
15630
+ const next = normalizeCardLink(link);
15631
+ if (next && !this._title.trim()) throw new TypeError("A linked Card requires a non-empty title");
15632
+ this._link = next;
15633
+ this.el.dataset.linked = next ? "true" : "false";
15634
+ this._renderHeading();
15635
+ return this;
15636
+ }
15637
+ /** Restores an enhanced target exactly, or removes an owned card. @returns {void} */
15638
+ destroy() {
15639
+ if (this._cleaned) return;
15640
+ this._cleaned = true;
15641
+ this._actionAbort?.abort();
15642
+ this._linkAbort?.abort();
15643
+ super.destroy();
15644
+ if (!this._createdRoot) restoreTarget(this.el, this._original);
15645
+ }
15646
+ /** @returns {void} */
15647
+ _renderHeading() {
15648
+ const title = this._title;
15649
+ const nextAbort = new AbortController();
15650
+ const node = this._link ? h("a", {
15651
+ class: "zx-card__primary",
15652
+ href: this._link.href,
15653
+ target: this._link.target,
15654
+ rel: this._link.rel
15655
+ }, title) : h("span", { class: "zx-card__title" }, title);
15656
+ if (this._link?.onclick) node.addEventListener("click", this._link.onclick, { signal: nextAbort.signal });
15657
+ this._linkAbort?.abort();
15658
+ this._linkAbort = nextAbort;
15659
+ this.refs.heading.replaceChildren(node);
15660
+ this.refs.heading.hidden = title === "";
15661
+ if (title) this.el.setAttribute("aria-labelledby", this.refs.heading.id);
15662
+ else this.el.removeAttribute("aria-labelledby");
15663
+ this._syncHeader();
15664
+ }
15665
+ /** @returns {void} */
15666
+ _syncHeader() {
15667
+ if (!this.refs.header) return;
15668
+ this.refs.header.hidden = this._title === "" && this.refs.actions.childElementCount === 0;
15669
+ }
15670
+ };
15671
+ function replaceCardContent(host, content, label) {
15672
+ if (content === null) {
15673
+ host.replaceChildren();
15674
+ return;
15675
+ }
15676
+ if (typeof content === "string") {
15677
+ host.textContent = content;
15678
+ return;
15679
+ }
15680
+ const node = content instanceof Component ? content.toElement() : content;
15681
+ if (node instanceof Node) {
15682
+ host.replaceChildren(node);
15683
+ return;
15684
+ }
15685
+ throw new TypeError(`${label} must be a string, Node, Component, or null`);
15686
+ }
15687
+ function normalizeCardLink(value) {
15688
+ if (value === null) return null;
15689
+ const descriptor = typeof value === "string" ? { href: value } : value;
15690
+ if (!descriptor || typeof descriptor !== "object" || Array.isArray(descriptor)) {
15691
+ throw new TypeError("Card link must be a URL string, descriptor, or null");
15692
+ }
15693
+ const href = safeHref(descriptor.href);
15694
+ if (href === null) {
15695
+ throw new TypeError("Card link requires a safe non-empty href");
15696
+ }
15697
+ if (descriptor.onclick !== void 0 && typeof descriptor.onclick !== "function") {
15698
+ throw new TypeError("Card link onclick must be a function");
15699
+ }
15700
+ const target = descriptor.target == null ? void 0 : String(descriptor.target);
15701
+ let rel = descriptor.rel == null ? "" : String(descriptor.rel).trim();
15702
+ if (target === "_blank" && !/(^|\s)noopener(\s|$)/i.test(rel)) {
15703
+ rel = `${rel} noopener`.trim();
15704
+ }
15705
+ return { href, target, rel: rel || void 0, onclick: descriptor.onclick };
15706
+ }
15707
+ function normalizeVariant2(value) {
15708
+ return ["raised", "filled"].includes(String(value)) ? (
15709
+ /** @type {'raised'|'filled'} */
15710
+ value
15711
+ ) : "outlined";
15712
+ }
15713
+ function normalizeOrientation(value) {
15714
+ return value === "horizontal" ? "horizontal" : "vertical";
15715
+ }
15716
+ function normalizeHeadingLevel(value) {
15717
+ const level = Math.trunc(Number(value));
15718
+ return (
15719
+ /** @type {1|2|3|4|5|6} */
15720
+ level >= 1 && level <= 6 ? level : 3
15721
+ );
15722
+ }
15723
+
15245
15724
  // src/components/panel/panel.js
15246
15725
  var Panel = class extends Component {
15247
15726
  static cssName = "panel";
@@ -18585,9 +19064,12 @@ var Launcher = class extends Component {
18585
19064
  maxResults: 100,
18586
19065
  placeholder: "Search applications and records",
18587
19066
  label: "Launcher",
19067
+ closeLabel: "Close launcher",
19068
+ resultsLabel: "Launcher results",
18588
19069
  emptyText: "No results",
18589
19070
  loadingText: "Searching\u2026",
18590
- shortcut: "mod+k"
19071
+ shortcut: "mod+k",
19072
+ hints: { move: "Move", open: "Open", close: "Close" }
18591
19073
  };
18592
19074
  /**
18593
19075
  * Creates an owned launcher dialog or enhances an existing `<dialog>` target.
@@ -18616,6 +19098,7 @@ var Launcher = class extends Component {
18616
19098
  this._sources = normalizeSources(this.options.sources);
18617
19099
  this._sourceItems = /* @__PURE__ */ new Map();
18618
19100
  this._query = String(this.options.query ?? "");
19101
+ this._renderedQuery = null;
18619
19102
  this._results = [];
18620
19103
  this._active = -1;
18621
19104
  this._request = null;
@@ -18642,7 +19125,7 @@ var Launcher = class extends Component {
18642
19125
  ref: "close",
18643
19126
  class: "zx-icon-btn zx-launcher__close",
18644
19127
  type: "button",
18645
- ariaLabel: "Close launcher"
19128
+ ariaLabel: this.options.closeLabel
18646
19129
  }, icon("x"));
18647
19130
  const status = h("div", {
18648
19131
  ref: "status",
@@ -18655,16 +19138,25 @@ var Launcher = class extends Component {
18655
19138
  class: "zx-launcher__results",
18656
19139
  id: listId,
18657
19140
  role: "listbox",
18658
- ariaLabel: "Launcher results"
19141
+ ariaLabel: this.options.resultsLabel
18659
19142
  });
19143
+ const shortcut = this.options.shortcut === "mod+k" ? h("kbd", {
19144
+ class: "zx-launcher__shortcut",
19145
+ ariaHidden: "true"
19146
+ }, launcherShortcutLabel()) : null;
19147
+ const footer = launcherHints(this.options.hints);
18660
19148
  dialog.setAttribute("aria-label", String(this.options.label));
18661
19149
  dialog.replaceChildren(
18662
19150
  h(
18663
19151
  "div",
18664
19152
  { class: "zx-launcher__surface" },
18665
- h("div", { class: "zx-launcher__search" }, icon("search"), input, close),
19153
+ h("div", {
19154
+ class: "zx-launcher__search",
19155
+ dataset: { shortcut: shortcut ? "true" : "false" }
19156
+ }, icon("search"), input, shortcut, close),
18666
19157
  status,
18667
- results
19158
+ results,
19159
+ footer
18668
19160
  )
18669
19161
  );
18670
19162
  dialog.dataset.state = "closed";
@@ -18688,6 +19180,17 @@ var Launcher = class extends Component {
18688
19180
  if (!option || !results.contains(option)) return;
18689
19181
  this._activate(Number(option.getAttribute("data-launcher-index")), event);
18690
19182
  });
19183
+ this.listen(results, "pointermove", (event) => {
19184
+ const option = (
19185
+ /** @type {Element|null} */
19186
+ event.target?.closest?.("[data-launcher-index]")
19187
+ );
19188
+ if (!option || !results.contains(option)) return;
19189
+ const index = Number(option.getAttribute("data-launcher-index"));
19190
+ if (!Number.isInteger(index) || this._results[index]?.disabled || index === this._active) return;
19191
+ this._active = index;
19192
+ this._syncActive();
19193
+ });
18691
19194
  this.listen(dialog, "cancel", (event) => {
18692
19195
  event.preventDefault();
18693
19196
  this.close();
@@ -18757,6 +19260,13 @@ var Launcher = class extends Component {
18757
19260
  this._renderResults();
18758
19261
  return this;
18759
19262
  }
19263
+ /** Marks one local item as the current destination. @param {string|number|null} id Item id. @returns {this} */
19264
+ setCurrent(id) {
19265
+ const key = id == null ? null : String(id);
19266
+ this._items = this._items.map((item) => ({ ...item, current: key !== null && String(item.id) === key }));
19267
+ this._renderResults();
19268
+ return this;
19269
+ }
18760
19270
  /** Replaces asynchronous sources and aborts work from the old set. @param {LauncherSource[]} sources @returns {this} */
18761
19271
  setSources(sources) {
18762
19272
  this._abortRequest();
@@ -18792,7 +19302,7 @@ var Launcher = class extends Component {
18792
19302
  async _loadSources() {
18793
19303
  this._queryTimer = null;
18794
19304
  const query = this._query.trim();
18795
- const sources = this._sources.filter((source) => query.length >= Math.max(Number(this.options.minQuery) || 0, Number(source.minQuery) || 0));
19305
+ const sources = this._sources.filter((source) => visibleForQuery(source.when, query) && query.length >= Math.max(Number(this.options.minQuery) || 0, Number(source.minQuery) || 0));
18796
19306
  if (sources.length === 0) return;
18797
19307
  const request = new AbortController();
18798
19308
  const sequence = ++this._requestSequence;
@@ -18802,10 +19312,12 @@ var Launcher = class extends Component {
18802
19312
  try {
18803
19313
  const loaded = await source.load(query, { signal: request.signal });
18804
19314
  if (request.signal.aborted || sequence !== this._requestSequence) return;
18805
- this._sourceItems.set(source.id, normalizeItems2(loaded).map((item) => ({
19315
+ this._sourceItems.set(source.id, normalizeItems2(loaded).map((item, itemOrder) => ({
18806
19316
  ...item,
18807
19317
  _source: source.id,
18808
- group: item.group ?? source.label ?? ""
19318
+ _sourceOrder: source.order === "source",
19319
+ group: item.group ?? source.label ?? "",
19320
+ itemOrder: item.itemOrder ?? (source.order === "source" ? itemOrder : void 0)
18809
19321
  })));
18810
19322
  this._renderResults();
18811
19323
  } catch (error) {
@@ -18834,23 +19346,36 @@ var Launcher = class extends Component {
18834
19346
  }
18835
19347
  /** @returns {void} */
18836
19348
  _renderResults() {
18837
- const remote = orderedLauncherSourceItems(this._sources, this._sourceItems);
19349
+ const queryKey = this._query;
19350
+ const activeId = this._renderedQuery === queryKey && this._active >= 0 && this._results[this._active] ? String(this._results[this._active].id) : null;
19351
+ const query = this._query.trim();
19352
+ const local = this._items.filter((item) => visibleForQuery(item.when, query));
19353
+ const rankedLocal = rankLauncherItems(local, query, this.options.maxResults);
19354
+ const remote = this._sources.flatMap((source) => {
19355
+ const loaded = this._sourceItems.get(String(source.id)) ?? [];
19356
+ return source.order === "source" ? loaded.filter((item) => visibleForQuery(item.when, query)) : rankLauncherItems(
19357
+ loaded.filter((item) => visibleForQuery(item.when, query)),
19358
+ query,
19359
+ this.options.maxResults
19360
+ );
19361
+ });
18838
19362
  const seen = /* @__PURE__ */ new Set();
18839
- const combined = [...this._items.map((item) => ({ ...item, _source: null })), ...remote].filter((item) => {
19363
+ const combined = [...rankedLocal.map((item) => ({ ...item, _source: null })), ...remote].filter((item) => {
18840
19364
  const key = String(item.id);
18841
19365
  if (seen.has(key)) return false;
18842
19366
  seen.add(key);
18843
19367
  return true;
18844
- });
18845
- const ranked = rankLauncherItems(combined, this._query, this.options.maxResults);
19368
+ }).slice(0, Math.max(0, Number(this.options.maxResults) || 0));
18846
19369
  const groups = /* @__PURE__ */ new Map();
18847
- for (const item of ranked) {
19370
+ for (const item of combined) {
18848
19371
  const group = String(item.group ?? "");
18849
19372
  if (!groups.has(group)) groups.set(group, []);
18850
19373
  groups.get(group).push(item);
18851
19374
  }
18852
19375
  this._results = [...groups.values()].flat();
18853
- this._active = this._results.findIndex((item) => !item.disabled);
19376
+ this._renderedQuery = queryKey;
19377
+ const preserved = activeId === null ? -1 : this._results.findIndex((item) => !item.disabled && String(item.id) === activeId);
19378
+ this._active = preserved >= 0 ? preserved : this._results.findIndex((item) => !item.disabled);
18854
19379
  const nodes = [];
18855
19380
  let index = 0;
18856
19381
  for (const [group, items] of groups) {
@@ -18858,7 +19383,8 @@ var Launcher = class extends Component {
18858
19383
  if (group) nodes.push(h("div", {
18859
19384
  class: "zx-launcher__group",
18860
19385
  role: "group",
18861
- ariaLabel: group
19386
+ ariaLabel: group,
19387
+ dataset: { layout: items.every((item) => item.kind === "application") ? "grid" : "list" }
18862
19388
  }, h("div", { class: "zx-launcher__group-label", ariaHidden: "true" }, group), options));
18863
19389
  else nodes.push(...options);
18864
19390
  }
@@ -18868,9 +19394,14 @@ var Launcher = class extends Component {
18868
19394
  }
18869
19395
  /** @param {RankedLauncherItem} item @param {number} index @returns {HTMLElement} */
18870
19396
  _option(item, index) {
18871
- const tag = item.href ? "a" : "button";
19397
+ const href = safeHref(item.href);
19398
+ const tag = href ? "a" : "button";
18872
19399
  const children = [];
18873
- if (item.icon) children.push(h("span", { class: "zx-launcher__icon" }, icon(item.icon)));
19400
+ const visual = launcherItemIcon(item);
19401
+ if (visual) children.push(h("span", {
19402
+ class: "zx-launcher__icon",
19403
+ ariaHidden: "true"
19404
+ }, visual));
18874
19405
  children.push(h(
18875
19406
  "span",
18876
19407
  { class: "zx-launcher__copy" },
@@ -18883,14 +19414,20 @@ var Launcher = class extends Component {
18883
19414
  h(tag, {
18884
19415
  class: "zx-launcher__option",
18885
19416
  type: tag === "button" ? "button" : null,
18886
- href: item.href ?? null,
19417
+ href,
18887
19418
  target: item.target ?? null,
19419
+ rel: tag === "a" ? "noopener" : null,
18888
19420
  role: "option",
18889
19421
  id: uid("zx-launcher-option"),
18890
19422
  tabIndex: -1,
18891
19423
  ariaSelected: "false",
18892
19424
  ariaDisabled: item.disabled ? "true" : null,
18893
- dataset: { launcherIndex: index, pinned: item.pinned ? "true" : null }
19425
+ ariaCurrent: item.current ? "page" : null,
19426
+ dataset: {
19427
+ launcherIndex: index,
19428
+ pinned: item.pinned !== void 0 && item.pinned !== false ? "true" : null,
19429
+ kind: normalizeLauncherKind(item.kind)
19430
+ }
18894
19431
  }, children)
18895
19432
  );
18896
19433
  }
@@ -18927,6 +19464,17 @@ var Launcher = class extends Component {
18927
19464
  }
18928
19465
  const enabled = this._results.map((item, index) => item.disabled ? -1 : index).filter((index) => index >= 0);
18929
19466
  if (enabled.length === 0) return;
19467
+ if (/^Arrow(?:Left|Right|Up|Down)$/.test(event.key)) {
19468
+ const next = this._spatialIndex(event.key, enabled);
19469
+ if (next !== null) {
19470
+ event.preventDefault();
19471
+ this._active = next;
19472
+ this._syncActive();
19473
+ this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`)?.scrollIntoView({ block: "nearest", inline: "nearest" });
19474
+ return;
19475
+ }
19476
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") return;
19477
+ }
18930
19478
  let position2 = enabled.indexOf(this._active);
18931
19479
  if (event.key === "Home" || event.key === "PageUp") position2 = 0;
18932
19480
  else if (event.key === "End" || event.key === "PageDown") position2 = enabled.length - 1;
@@ -18938,6 +19486,15 @@ var Launcher = class extends Component {
18938
19486
  this._syncActive();
18939
19487
  this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`)?.scrollIntoView({ block: "nearest" });
18940
19488
  }
19489
+ /** @param {string} direction @param {number[]} enabled @returns {number|null} */
19490
+ _spatialIndex(direction, enabled) {
19491
+ const current = this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`);
19492
+ if (!(current instanceof HTMLElement)) return null;
19493
+ if ((direction === "ArrowLeft" || direction === "ArrowRight") && this._results[this._active]?.kind !== "application") return null;
19494
+ 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"));
19495
+ const next = findSpatialLauncherOption(candidates, current, direction);
19496
+ return next ? Number(next.getAttribute("data-launcher-index")) : null;
19497
+ }
18941
19498
  /** @param {number} index @param {Event} event @returns {void} */
18942
19499
  _activate(index, event) {
18943
19500
  const item = this._results[index];
@@ -18963,25 +19520,103 @@ var Launcher = class extends Component {
18963
19520
  }
18964
19521
  /** @param {KeyboardEvent} event @returns {void} */
18965
19522
  _onDocumentKeydown(event) {
18966
- if (this.options.shortcut !== "mod+k" || event.isComposing || event.key.toLowerCase() !== "k" || !event.metaKey && !event.ctrlKey || event.altKey) return;
19523
+ if (this.options.shortcut !== "mod+k" || event.isComposing || event.key.toLowerCase() !== "k" || Number(event.metaKey) + Number(event.ctrlKey) !== 1 || event.altKey || event.shiftKey) return;
18967
19524
  const active = document.activeElement;
18968
- if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement || active instanceof HTMLElement && active.isContentEditable) return;
19525
+ if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement || active instanceof HTMLSelectElement || active instanceof HTMLElement && active.isContentEditable) return;
18969
19526
  const modal = document.querySelector("dialog:modal");
18970
19527
  if (modal && modal !== this.el) return;
18971
19528
  event.preventDefault();
18972
19529
  this.open();
18973
19530
  }
18974
19531
  };
19532
+ function launcherShortcutLabel() {
19533
+ const platform = String(
19534
+ /** @type {any} */
19535
+ navigator.userAgentData?.platform ?? navigator.platform ?? ""
19536
+ );
19537
+ return /mac|iphone|ipad/i.test(platform) ? "\u2318 K" : "Ctrl K";
19538
+ }
19539
+ function launcherHints(hints) {
19540
+ if (hints === false) return null;
19541
+ const labels = hints && typeof hints === "object" ? hints : {};
19542
+ return h(
19543
+ "footer",
19544
+ { class: "zx-launcher__hints", ariaHidden: "true" },
19545
+ h("span", {}, h("kbd", {}, "\u2191\u2193\u2190\u2192"), labels.move ?? "Move"),
19546
+ h("span", {}, h("kbd", {}, "\u21B5"), labels.open ?? "Open"),
19547
+ h("span", {}, h("kbd", {}, "Esc"), labels.close ?? "Close")
19548
+ );
19549
+ }
19550
+ function launcherItemIcon(item) {
19551
+ let visual = item.icon;
19552
+ if (typeof visual === "function") visual = visual(item);
19553
+ if (typeof visual === "string") return icon(visual);
19554
+ if (visual instanceof Node) return visual.cloneNode(true);
19555
+ if (visual && typeof visual === "object" && typeof /** @type {any} */
19556
+ visual.toElement === "function") {
19557
+ const element = (
19558
+ /** @type {any} */
19559
+ visual.toElement()
19560
+ );
19561
+ return element instanceof Node ? element.cloneNode(true) : null;
19562
+ }
19563
+ return null;
19564
+ }
19565
+ function normalizeLauncherKind(kind) {
19566
+ return kind === "application" || kind === "record" ? kind : "action";
19567
+ }
19568
+ function visibleForQuery(when, query) {
19569
+ if (when === "empty") return query === "";
19570
+ if (when === "query") return query !== "";
19571
+ return true;
19572
+ }
19573
+ function findSpatialLauncherOption(options, current, direction) {
19574
+ const origin = current.getBoundingClientRect();
19575
+ if (origin.width === 0 && origin.height === 0) return null;
19576
+ const originX = origin.left + origin.width / 2;
19577
+ const originY = origin.top + origin.height / 2;
19578
+ let best = null;
19579
+ let bestScore = Infinity;
19580
+ for (const option of options) {
19581
+ if (option === current) continue;
19582
+ const rect = option.getBoundingClientRect();
19583
+ const dx = rect.left + rect.width / 2 - originX;
19584
+ const dy = rect.top + rect.height / 2 - originY;
19585
+ let valid = false;
19586
+ let score = Infinity;
19587
+ if (direction === "ArrowLeft") {
19588
+ valid = dx < -2 && Math.abs(dy) < Math.max(origin.height, rect.height) / 2;
19589
+ score = Math.abs(dx) + Math.abs(dy) * 4;
19590
+ } else if (direction === "ArrowRight") {
19591
+ valid = dx > 2 && Math.abs(dy) < Math.max(origin.height, rect.height) / 2;
19592
+ score = Math.abs(dx) + Math.abs(dy) * 4;
19593
+ } else if (direction === "ArrowUp") {
19594
+ valid = dy < -2;
19595
+ score = Math.abs(dy) * 10 + Math.abs(dx);
19596
+ } else if (direction === "ArrowDown") {
19597
+ valid = dy > 2;
19598
+ score = Math.abs(dy) * 10 + Math.abs(dx);
19599
+ }
19600
+ if (valid && score < bestScore) {
19601
+ best = option;
19602
+ bestScore = score;
19603
+ }
19604
+ }
19605
+ return best;
19606
+ }
18975
19607
  function rankLauncherItems(items, query, limit = 100) {
18976
19608
  const needle = searchable(query).trim();
18977
19609
  const ranked = items.map((item, order) => ({ item, order, score: launcherScore(item, needle) })).filter(({ score }) => score < Infinity);
18978
19610
  ranked.sort((a, b) => {
19611
+ const aGroup = finiteOrder(a.item.groupOrder);
19612
+ const bGroup = finiteOrder(b.item.groupOrder);
19613
+ if (aGroup !== bGroup) return aGroup - bGroup;
18979
19614
  if (needle === "") {
18980
19615
  const aPinned = pinOrder(a.item.pinned, a.order);
18981
19616
  const bPinned = pinOrder(b.item.pinned, b.order);
18982
19617
  if (aPinned !== bPinned) return aPinned - bPinned;
18983
19618
  }
18984
- return a.score - b.score || compareLauncherText(a.item.label, b.item.label) || a.order - b.order;
19619
+ return a.score - b.score || finiteOrder(a.item.itemOrder) - finiteOrder(b.item.itemOrder) || compareLauncherText(a.item.label, b.item.label) || a.order - b.order;
18985
19620
  });
18986
19621
  const maximum = Math.max(0, Number(limit) || 0);
18987
19622
  return ranked.slice(0, maximum || 0).map(({ item, score }) => ({ ...item, _score: score }));
@@ -19005,6 +19640,9 @@ function pinOrder(pinned, order) {
19005
19640
  if (pinned) return order;
19006
19641
  return Number.MAX_SAFE_INTEGER;
19007
19642
  }
19643
+ function finiteOrder(value) {
19644
+ return typeof value === "number" && Number.isFinite(value) ? value : Number.MAX_SAFE_INTEGER;
19645
+ }
19008
19646
  function searchable(value) {
19009
19647
  return String(value ?? "").normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase();
19010
19648
  }
@@ -19019,15 +19657,23 @@ function compareLauncherText(left, right) {
19019
19657
  }
19020
19658
  function normalizeItems2(items) {
19021
19659
  if (!Array.isArray(items)) return [];
19022
- return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) => ({ ...item, label: String(item.label) }));
19660
+ return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) => normalizeLauncherItem(item));
19661
+ }
19662
+ function normalizeLauncherItem(item) {
19663
+ const normalized = { ...item, label: String(item.label) };
19664
+ if (item.href == null) return normalized;
19665
+ const href = safeHref(item.href);
19666
+ if (href !== null) normalized.href = href;
19667
+ else {
19668
+ delete normalized.href;
19669
+ if (typeof normalized.invoke !== "function") normalized.disabled = true;
19670
+ }
19671
+ return normalized;
19023
19672
  }
19024
19673
  function normalizeSources(sources) {
19025
19674
  if (!Array.isArray(sources)) return [];
19026
19675
  return sources.filter((source) => source && typeof source === "object" && source.id != null && typeof source.load === "function").map((source) => ({ ...source, id: String(source.id) }));
19027
19676
  }
19028
- function orderedLauncherSourceItems(sources, sourceItems) {
19029
- return sources.flatMap((source) => sourceItems.get(String(source.id)) ?? []);
19030
- }
19031
19677
 
19032
19678
  // src/components/avatar/avatar.js
19033
19679
  var Avatar = class extends Component {
@@ -19715,10 +20361,10 @@ function isFocusVisible(element) {
19715
20361
  }
19716
20362
  }
19717
20363
 
19718
- // src/components/app-rail/app-rail.js
19719
- var AppRail = class extends Component {
20364
+ // src/internal/app-rail.js
20365
+ var AppRailPresenter = class extends Component {
19720
20366
  static cssName = "app-rail";
19721
- /** @type {Readonly<AppRailOptions>} */
20367
+ /** @type {Readonly<AppRailPresenterOptions>} */
19722
20368
  static defaults = {
19723
20369
  items: [],
19724
20370
  active: null,
@@ -19728,12 +20374,13 @@ var AppRail = class extends Component {
19728
20374
  header: null,
19729
20375
  footer: null,
19730
20376
  openDelay: 80,
19731
- closeDelay: 160
20377
+ closeDelay: 160,
20378
+ renderIcon: null
19732
20379
  };
19733
20380
  /**
19734
20381
  * Creates or enhances a rail host.
19735
20382
  * @param {Element|string|null} target Rail target.
19736
- * @param {AppRailOptions} [options={}] Rail options.
20383
+ * @param {AppRailPresenterOptions} [options={}] Rail options.
19737
20384
  */
19738
20385
  constructor(target = null, options = {}) {
19739
20386
  super(target, options);
@@ -19883,7 +20530,14 @@ var AppRail = class extends Component {
19883
20530
  index: String(item.id),
19884
20531
  iconOnly: true,
19885
20532
  disclosure: hasChildren,
19886
- disclosureIcon: railDisclosureIcon(this.el.dataset.side)
20533
+ disclosureIcon: railDisclosureIcon(this.el.dataset.side),
20534
+ visual: appNavIcon(item, this.options.renderIcon, {
20535
+ orientation: this.el.dataset.orientation,
20536
+ collapsed: true,
20537
+ side: this.el.dataset.side,
20538
+ level: 1,
20539
+ location: "root"
20540
+ })
19887
20541
  });
19888
20542
  const row2 = h("li", { class: "zx-app-rail__row" }, control);
19889
20543
  this.refs.list.append(row2);
@@ -19924,7 +20578,14 @@ var AppRail = class extends Component {
19924
20578
  index: childKey,
19925
20579
  iconOnly: false,
19926
20580
  disclosure: hasChildren,
19927
- disclosureIcon: nestedDisclosureIcon(this.el.dataset.side)
20581
+ disclosureIcon: nestedDisclosureIcon(this.el.dataset.side),
20582
+ visual: appNavIcon(child, this.options.renderIcon, {
20583
+ orientation: this.el.dataset.orientation,
20584
+ collapsed: true,
20585
+ side: this.el.dataset.side,
20586
+ level: childKey.split("/").length,
20587
+ location: "flyout"
20588
+ })
19928
20589
  });
19929
20590
  list.append(h("li", {}, control));
19930
20591
  if (hasChildren) this._buildFlyout(child, control, childKey, key);
@@ -19978,7 +20639,6 @@ var AppRail = class extends Component {
19978
20639
  this._interaction.listen(trigger, "click", (event) => {
19979
20640
  event.preventDefault();
19980
20641
  if (event instanceof MouseEvent && event.detail === 0) this.openFlyout(key, { focus: true });
19981
- else if (dropdown.isOpen()) this.closeFlyout(key);
19982
20642
  else this.openFlyout(key);
19983
20643
  });
19984
20644
  this._interaction.listen(actualPanel, "pointerenter", () => this._cancelFlyoutTimers(flyout, true));
@@ -20233,14 +20893,43 @@ var RailListenerScope = class extends Component {
20233
20893
  };
20234
20894
  function normalizeAppItems(items) {
20235
20895
  if (!Array.isArray(items)) return [];
20236
- return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) => ({
20237
- ...item,
20238
- label: String(item.label),
20239
- children: Array.isArray(item.children) ? normalizeAppItems(item.children) : []
20240
- }));
20896
+ return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) => {
20897
+ const normalized = {
20898
+ ...item,
20899
+ label: String(item.label),
20900
+ children: Array.isArray(item.children) ? normalizeAppItems(item.children) : []
20901
+ };
20902
+ if (item.href == null) return normalized;
20903
+ const href = safeHref(item.href);
20904
+ if (href !== null) normalized.href = href;
20905
+ else {
20906
+ delete normalized.href;
20907
+ if (normalized.children.length === 0 && typeof normalized.invoke !== "function") {
20908
+ normalized.disabled = true;
20909
+ }
20910
+ }
20911
+ return normalized;
20912
+ });
20241
20913
  }
20242
- function navControl(item, { className, index, iconOnly, disclosure, disclosureIcon }) {
20243
- const tag = item.href && !disclosure ? "a" : "button";
20914
+ function appNavIcon(item, renderer, context) {
20915
+ let visual = typeof renderer === "function" ? renderer(item, context) : null;
20916
+ if (visual == null) visual = item.icon;
20917
+ if (typeof visual === "function") visual = visual(context, item);
20918
+ if (typeof visual === "string") return icon(visual);
20919
+ if (visual instanceof Node) return visual.cloneNode(true);
20920
+ if (visual && typeof visual === "object" && typeof /** @type {any} */
20921
+ visual.toElement === "function") {
20922
+ const element = (
20923
+ /** @type {any} */
20924
+ visual.toElement()
20925
+ );
20926
+ return element instanceof Node ? element.cloneNode(true) : null;
20927
+ }
20928
+ return null;
20929
+ }
20930
+ function navControl(item, { className, index, iconOnly, disclosure, disclosureIcon, visual }) {
20931
+ const href = safeHref(item.href);
20932
+ const tag = href && !disclosure ? "a" : "button";
20244
20933
  return (
20245
20934
  /** @type {HTMLElement} */
20246
20935
  h(
@@ -20248,13 +20937,14 @@ function navControl(item, { className, index, iconOnly, disclosure, disclosureIc
20248
20937
  {
20249
20938
  class: className,
20250
20939
  type: tag === "button" ? "button" : null,
20251
- href: tag === "a" ? item.href : null,
20940
+ href: tag === "a" ? href : null,
20252
20941
  target: tag === "a" ? item.target ?? null : null,
20942
+ rel: tag === "a" ? "noopener" : null,
20253
20943
  ariaLabel: iconOnly ? item.label : null,
20254
20944
  ariaDisabled: item.disabled ? "true" : null,
20255
20945
  dataset: { appNavId: index, appNavValueId: String(item.id) }
20256
20946
  },
20257
- item.icon ? h("span", { class: `${className}-icon`, ariaHidden: "true" }, icon(item.icon)) : null,
20947
+ visual ? h("span", { class: `${className}-icon`, ariaHidden: "true" }, visual) : null,
20258
20948
  h("span", { class: `${className}-label` }, item.label),
20259
20949
  item.badge != null ? h("span", { class: `${className}-badge` }, String(item.badge)) : null,
20260
20950
  disclosure ? h("span", { class: `${className}-disclosure`, ariaHidden: "true" }, icon(disclosureIcon)) : null
@@ -20318,47 +21008,28 @@ var AppSidebar = class extends Component {
20318
21008
  active: null,
20319
21009
  expanded: [],
20320
21010
  collapsed: false,
21011
+ collapsible: true,
21012
+ orientation: "vertical",
20321
21013
  side: "left",
20322
21014
  label: "Applications",
20323
21015
  header: null,
20324
21016
  footer: null,
20325
21017
  railHeader: null,
20326
- railFooter: null
21018
+ railFooter: null,
21019
+ openDelay: 80,
21020
+ closeDelay: 160,
21021
+ renderIcon: null
20327
21022
  };
20328
21023
  /**
20329
- * Creates or enhances an application-sidebar host.
20330
- * @param {Element|string|null} target Sidebar target.
20331
- * @param {AppSidebarOptions} [options={}] Sidebar options.
21024
+ * Creates or enhances an application-navigation host.
21025
+ * @param {Element|string|null} [target=null] Navigation target.
21026
+ * @param {AppSidebarOptions} [options={}] Navigation options.
20332
21027
  */
20333
21028
  constructor(target = null, options = {}) {
20334
21029
  super(target, options);
20335
- this.listen(this.refs.rail, "zx-select", (event) => event.stopPropagation());
20336
- const expandButton = h("button", {
20337
- class: "zx-app-sidebar__rail-toggle zx-icon-btn",
20338
- type: "button",
20339
- ariaLabel: "Expand application sidebar"
20340
- }, icon(this.el.dataset.side === "right" ? "chevron-left" : "chevron-right"));
20341
- this._railExpandButton = expandButton;
20342
- const railHeader = h("div", { class: "zx-app-sidebar__rail-header" }, expandButton);
20343
- appendSlot2(railHeader, this.options.railHeader);
20344
- this._rail = new AppRail(this.refs.rail, {
20345
- items: this._items,
20346
- active: this._active,
20347
- orientation: "vertical",
20348
- side: this.el.dataset.side,
20349
- label: this.options.label,
20350
- header: railHeader,
20351
- footer: this.options.railFooter,
20352
- onselect: (event) => {
20353
- const selected = this.emit("select", event.detail);
20354
- if (selected.defaultPrevented) event.preventDefault();
20355
- }
20356
- });
20357
- this.listen(expandButton, "click", () => {
20358
- this.expand();
20359
- });
20360
- this._renderTree();
20361
- this.setCollapsed(Boolean(this.options.collapsed), { silent: true });
21030
+ this._rail = null;
21031
+ this._railExpandButton = null;
21032
+ this._renderMode();
20362
21033
  }
20363
21034
  /** @returns {HTMLElement} */
20364
21035
  render() {
@@ -20376,57 +21047,39 @@ var AppSidebar = class extends Component {
20376
21047
  this._active = this.options.active;
20377
21048
  this._expanded = new Set(Array.isArray(this.options.expanded) ? this.options.expanded.map(String) : []);
20378
21049
  this._collapsed = Boolean(this.options.collapsed);
20379
- root.dataset.side = this.options.side === "right" ? "right" : "left";
20380
- const headerContent = h("div", { class: "zx-app-sidebar__header-content" });
20381
- appendSlot2(headerContent, this.options.header);
20382
- const collapseButton = h("button", {
20383
- ref: "collapse",
20384
- class: "zx-app-sidebar__collapse zx-icon-btn",
20385
- type: "button",
20386
- ariaLabel: "Minimize application sidebar"
20387
- }, icon(root.dataset.side === "right" ? "chevron-right" : "chevron-left"));
20388
- const footer = h("footer", { class: "zx-app-sidebar__footer" });
20389
- appendSlot2(footer, this.options.footer);
20390
- footer.hidden = !footer.hasChildNodes();
20391
- const expanded = h(
20392
- "div",
20393
- { ref: "expanded", class: "zx-app-sidebar__expanded" },
20394
- h("header", { class: "zx-app-sidebar__header" }, headerContent, collapseButton),
20395
- h(
20396
- "nav",
20397
- { class: "zx-app-sidebar__nav", ariaLabel: this.options.label },
20398
- h("ul", { ref: "list", class: "zx-app-sidebar__list" })
20399
- ),
20400
- footer
20401
- );
20402
- const rail = h("div", { ref: "rail", class: "zx-app-sidebar__rail" });
20403
- root.replaceChildren(expanded, rail);
20404
- this.listen(collapseButton, "click", () => {
20405
- this.collapse();
20406
- });
20407
- this.listen(this.refs.list, "click", (event) => this._onTreeClick(event));
21050
+ this._orientation = this.options.orientation === "horizontal" ? "horizontal" : "vertical";
21051
+ this._side = normalizeSide(this._orientation, this.options.side);
21052
+ this._modeAbort = null;
21053
+ root.replaceChildren();
20408
21054
  return root;
20409
21055
  }
20410
- /** Replaces the navigation tree in both expanded and rail presentations. @param {AppNavItem[]} items @returns {this} */
21056
+ /** Replaces the navigation tree in the active presentation. @param {AppNavItem[]} items @returns {this} */
20411
21057
  setItems(items) {
20412
21058
  this._items = normalizeAppItems(items);
20413
21059
  this._reconcileExpanded();
20414
- this._renderTree();
20415
- this._rail.setItems(this._items);
21060
+ if (this._rail) this._rail.setItems(this._items);
21061
+ else this._renderTree();
20416
21062
  return this;
20417
21063
  }
20418
- /** Sets the active id in both presentations without routing. @param {string|number|null} id @returns {this} */
21064
+ /** Returns a shallow copy of normalized items. @returns {AppNavItem[]} */
21065
+ getItems() {
21066
+ return this._items.map((item) => ({
21067
+ ...item,
21068
+ children: item.children?.map((child) => ({ ...child }))
21069
+ }));
21070
+ }
21071
+ /** Sets the active id without routing. @param {string|number|null} id @returns {this} */
20419
21072
  setActive(id) {
20420
21073
  this._active = id;
20421
- this._syncActive();
20422
- this._rail.setActive(id);
21074
+ if (this._rail) this._rail.setActive(id);
21075
+ else this._syncActive();
20423
21076
  return this;
20424
21077
  }
20425
21078
  /** Replaces expanded branch ids. @param {Array<string|number>} ids @param {{silent?: boolean}} [options={}] @returns {this} */
20426
21079
  setExpanded(ids, { silent = false } = {}) {
20427
21080
  this._expanded = new Set(Array.isArray(ids) ? ids.map(String) : []);
20428
21081
  this._reconcileExpanded();
20429
- this._renderTree();
21082
+ if (!this._rail) this._renderTree();
20430
21083
  if (!silent) this.emit("branchchange", {
20431
21084
  item: null,
20432
21085
  id: null,
@@ -20451,47 +21104,105 @@ var AppSidebar = class extends Component {
20451
21104
  const expanded = !this._expanded.has(key);
20452
21105
  if (expanded) this._expanded.add(key);
20453
21106
  else this._expanded.delete(key);
20454
- this._renderTree();
20455
- if (!silent) this.emit("branchchange", { item, id: item.id, expanded, ids: this.getExpanded() });
21107
+ if (!this._rail) this._renderTree();
21108
+ if (!silent) this.emit("branchchange", {
21109
+ item,
21110
+ id: item.id,
21111
+ expanded,
21112
+ ids: this.getExpanded()
21113
+ });
20456
21114
  return this;
20457
21115
  }
20458
- /** Shows the expanded sidebar presentation. @param {{silent?: boolean}} [options={}] @returns {this} */
21116
+ /** Shows the expanded vertical presentation. @param {{silent?: boolean}} [options={}] @returns {this} */
20459
21117
  expand(options = {}) {
20460
21118
  return this.setCollapsed(false, options);
20461
21119
  }
20462
- /** Shows the minimized AppRail presentation. @param {{silent?: boolean}} [options={}] @returns {this} */
21120
+ /** Shows the minimized presentation. @param {{silent?: boolean}} [options={}] @returns {this} */
20463
21121
  collapse(options = {}) {
20464
21122
  return this.setCollapsed(true, options);
20465
21123
  }
20466
- /** Sets expanded/minimized presentation while preserving branch state. @param {boolean} collapsed @param {{silent?: boolean}} [options={}] @returns {this} */
21124
+ /** Sets the saved vertical expanded/minimized state. @param {boolean} collapsed @param {{silent?: boolean}} [options={}] @returns {this} */
20467
21125
  setCollapsed(collapsed, { silent = false } = {}) {
20468
- const next = Boolean(collapsed);
20469
- const changed = next !== this._collapsed;
21126
+ const previous = this.isCollapsed();
20470
21127
  const active = document.activeElement;
20471
- const focusRailToggle = next && Boolean(active && this.refs.expanded.contains(active));
20472
- const focusSidebarToggle = !next && this._rail?._containsNode(active);
20473
- if (!next) this._rail?.closeAllFlyouts();
20474
- this._collapsed = next;
20475
- this.el.dataset.collapsed = String(next);
20476
- this.refs.expanded.hidden = next;
20477
- this.refs.rail.hidden = !next;
20478
- if (focusRailToggle) this._railExpandButton?.focus();
20479
- else if (focusSidebarToggle) this.refs.collapse.focus();
20480
- if (changed && !silent) this.emit("collapsechange", { collapsed: next });
20481
- return this;
20482
- }
20483
- /** Reports whether the rail presentation is active. @returns {boolean} */
21128
+ const outgoingOwnedFocus = Boolean(active && (this.el.contains(active) || this._rail?._containsNode(active)));
21129
+ this._collapsed = Boolean(collapsed);
21130
+ const next = this.isCollapsed();
21131
+ if (previous !== next) {
21132
+ this._renderMode();
21133
+ if (outgoingOwnedFocus) {
21134
+ if (next) this._railExpandButton?.focus();
21135
+ else this.refs.collapse?.focus();
21136
+ }
21137
+ if (!silent) this.emit("collapsechange", { collapsed: next });
21138
+ }
21139
+ return this;
21140
+ }
21141
+ /** Reports the effective presentation state; horizontal navigation is always minimized. @returns {boolean} */
20484
21142
  isCollapsed() {
20485
- return this._collapsed;
21143
+ return this._orientation === "horizontal" || this._collapsed;
20486
21144
  }
20487
- /** Returns the composed minimized rail. @returns {AppRail} */
20488
- getRail() {
20489
- return this._rail;
21145
+ /**
21146
+ * Changes navigation flow/edge without replacing state.
21147
+ * @param {{orientation?: 'vertical'|'horizontal', side?: 'left'|'right'|'top'|'bottom'}} layout
21148
+ * @param {{silent?: boolean}} [options]
21149
+ * @returns {this}
21150
+ */
21151
+ setLayout(layout = {}, { silent = false } = {}) {
21152
+ const orientation = layout.orientation === "horizontal" ? "horizontal" : layout.orientation === "vertical" ? "vertical" : this._orientation;
21153
+ const side = normalizeSide(orientation, layout.side ?? this._side);
21154
+ if (orientation === this._orientation && side === this._side) return this;
21155
+ const previous = this.isCollapsed();
21156
+ const active = document.activeElement;
21157
+ const outgoingOwnedFocus = Boolean(active && (this.el.contains(active) || this._rail?._containsNode(active)));
21158
+ const focusedId = outgoingOwnedFocus && active instanceof Element ? active.closest("[data-app-nav-id]")?.getAttribute("data-app-nav-id") ?? null : null;
21159
+ this._orientation = orientation;
21160
+ this._side = side;
21161
+ this._renderMode();
21162
+ const next = this.isCollapsed();
21163
+ if (outgoingOwnedFocus) {
21164
+ const replacement = focusedId === null ? null : [...this.el.querySelectorAll("[data-app-nav-id]")].find((control) => control.getAttribute("data-app-nav-id") === focusedId) ?? null;
21165
+ if (replacement instanceof HTMLElement) replacement.focus();
21166
+ else if (next) this.focus();
21167
+ else if (this.refs.collapse instanceof HTMLElement && !this.refs.collapse.hidden) {
21168
+ this.refs.collapse.focus();
21169
+ } else visibleTreeControls(this.refs.list)[0]?.focus();
21170
+ }
21171
+ if (previous !== next && !silent) this.emit("collapsechange", { collapsed: next });
21172
+ return this;
20490
21173
  }
20491
- /** Destroys the rail and restores/removes the sidebar host. @returns {void} */
21174
+ /** Opens a minimized descendant flyout. @param {string|number} id @param {{focus?: boolean}} [options={}] @returns {this} */
21175
+ openFlyout(id, options = {}) {
21176
+ this._rail?.openFlyout(id, options);
21177
+ return this;
21178
+ }
21179
+ /** Closes a minimized descendant flyout. @param {string|number} id @returns {this} */
21180
+ closeFlyout(id) {
21181
+ this._rail?.closeFlyout(id);
21182
+ return this;
21183
+ }
21184
+ /** Closes every minimized flyout. @returns {this} */
21185
+ closeAllFlyouts() {
21186
+ this._rail?.closeAllFlyouts();
21187
+ return this;
21188
+ }
21189
+ /** Reports whether a minimized flyout is open. @param {string|number} id @returns {boolean} */
21190
+ isFlyoutOpen(id) {
21191
+ return this._rail?.isFlyoutOpen(id) ?? false;
21192
+ }
21193
+ /** Focuses the first navigation action. @returns {this} */
21194
+ focus() {
21195
+ if (this._rail) this._rail.focus();
21196
+ else visibleTreeControls(this.refs.list)[0]?.focus();
21197
+ return this;
21198
+ }
21199
+ /** Destroys flyouts and restores/removes the host. @returns {void} */
20492
21200
  destroy() {
20493
21201
  const original = this._original;
20494
- this._rail.destroy();
21202
+ this._modeAbort?.abort();
21203
+ this._modeAbort = null;
21204
+ this._rail?.destroy();
21205
+ this._rail = null;
20495
21206
  super.destroy();
20496
21207
  if (!this._createdRoot && original) {
20497
21208
  for (const attribute of Array.from(this.el.attributes)) this.el.removeAttribute(attribute.name);
@@ -20499,8 +21210,99 @@ var AppSidebar = class extends Component {
20499
21210
  this.el.replaceChildren(...original.children);
20500
21211
  }
20501
21212
  }
21213
+ /** Mounts exactly one expanded tree or minimized flyout presentation. @returns {void} */
21214
+ _renderMode() {
21215
+ this._modeAbort?.abort();
21216
+ this._modeAbort = new AbortController();
21217
+ this._rail?.destroy();
21218
+ this._rail = null;
21219
+ this._railExpandButton = null;
21220
+ for (const key of ["expanded", "list", "collapse", "rail"]) delete this.refs[key];
21221
+ this.el.replaceChildren();
21222
+ this.el.dataset.orientation = this._orientation;
21223
+ this.el.dataset.side = this._side;
21224
+ this.el.dataset.collapsed = String(this.isCollapsed());
21225
+ if (this.isCollapsed()) this._renderRail();
21226
+ else this._renderExpanded();
21227
+ }
21228
+ /** @returns {void} */
21229
+ _renderExpanded() {
21230
+ const headerContent = h("div", { class: "zx-app-sidebar__header-content" });
21231
+ appendSlot2(headerContent, this.options.header);
21232
+ const collapseButton = h("button", {
21233
+ class: "zx-app-sidebar__collapse zx-icon-btn",
21234
+ type: "button",
21235
+ ariaLabel: "Minimize application sidebar"
21236
+ }, icon(this._side === "right" ? "chevron-right" : "chevron-left"));
21237
+ collapseButton.hidden = !this.options.collapsible;
21238
+ const footer = h("footer", { class: "zx-app-sidebar__footer" });
21239
+ appendSlot2(footer, this.options.footer);
21240
+ footer.hidden = !footer.hasChildNodes();
21241
+ const list = h("ul", { class: "zx-app-sidebar__list" });
21242
+ const expanded = h(
21243
+ "div",
21244
+ { class: "zx-app-sidebar__expanded" },
21245
+ h("header", { class: "zx-app-sidebar__header" }, headerContent, collapseButton),
21246
+ h("nav", { class: "zx-app-sidebar__nav", ariaLabel: this.options.label }, list),
21247
+ footer
21248
+ );
21249
+ this.refs.expanded = expanded;
21250
+ this.refs.list = list;
21251
+ this.refs.collapse = collapseButton;
21252
+ this.el.append(expanded);
21253
+ this._listenMode(collapseButton, "click", () => this.collapse());
21254
+ this._listenMode(list, "click", (event) => this._onTreeClick(event));
21255
+ this._listenMode(list, "keydown", (event) => this._onTreeKeydown(
21256
+ /** @type {KeyboardEvent} */
21257
+ event
21258
+ ));
21259
+ this._renderTree();
21260
+ }
21261
+ /** @returns {void} */
21262
+ _renderRail() {
21263
+ const railHost = h("nav", { class: "zx-app-sidebar__rail" });
21264
+ this.el.append(railHost);
21265
+ this.refs.rail = railHost;
21266
+ const showToggle = this._orientation === "vertical" && Boolean(this.options.collapsible);
21267
+ const railHeader = h("div", { class: "zx-app-sidebar__rail-header" });
21268
+ if (showToggle) {
21269
+ const expandButton = h("button", {
21270
+ class: "zx-app-sidebar__rail-toggle zx-icon-btn",
21271
+ type: "button",
21272
+ ariaLabel: "Expand application sidebar"
21273
+ }, icon(this._side === "right" ? "chevron-left" : "chevron-right"));
21274
+ railHeader.append(expandButton);
21275
+ this._railExpandButton = expandButton;
21276
+ this._listenMode(expandButton, "click", () => this.expand());
21277
+ }
21278
+ appendSlot2(railHeader, this.options.railHeader);
21279
+ this._rail = new AppRailPresenter(railHost, {
21280
+ items: this._items,
21281
+ active: this._active,
21282
+ orientation: this._orientation,
21283
+ side: this._side,
21284
+ label: this.options.label,
21285
+ header: railHeader.hasChildNodes() ? railHeader : null,
21286
+ footer: this.options.railFooter,
21287
+ openDelay: this.options.openDelay,
21288
+ closeDelay: this.options.closeDelay,
21289
+ renderIcon: this.options.renderIcon,
21290
+ onselect: (event) => {
21291
+ const selected = this.emit("select", event.detail);
21292
+ if (selected.defaultPrevented) event.preventDefault();
21293
+ },
21294
+ onflyoutchange: (event) => this.emit("flyoutchange", event.detail)
21295
+ });
21296
+ this._listenMode(railHost, "zx-select", (event) => event.stopPropagation());
21297
+ this._listenMode(railHost, "zx-flyoutchange", (event) => event.stopPropagation());
21298
+ }
21299
+ /** @param {EventTarget} target @param {string} type @param {EventListener} listener @returns {void} */
21300
+ _listenMode(target, type, listener) {
21301
+ target.addEventListener(type, listener, { signal: this._modeAbort.signal });
21302
+ }
20502
21303
  /** @returns {void} */
20503
21304
  _renderTree() {
21305
+ if (!this.refs.list) return;
20504
21306
  const focused = this.refs.list.contains(document.activeElement) ? (
20505
21307
  /** @type {Element} */
20506
21308
  document.activeElement.closest("[data-app-nav-id]")?.getAttribute("data-app-nav-id")
@@ -20519,7 +21321,14 @@ var AppSidebar = class extends Component {
20519
21321
  const key = String(item.id);
20520
21322
  const expanded = hasChildren && this._expanded.has(key);
20521
21323
  const childId = hasChildren ? uid("zx-app-sidebar-branch") : null;
20522
- const control = navControl2(item, hasChildren, expanded, childId);
21324
+ const visual = appNavIcon(item, this.options.renderIcon, {
21325
+ orientation: this._orientation,
21326
+ collapsed: false,
21327
+ side: this._side,
21328
+ level,
21329
+ location: level === 1 ? "root" : "inline"
21330
+ });
21331
+ const control = navControl2(item, hasChildren, expanded, childId, visual);
20523
21332
  const row2 = h("li", {
20524
21333
  class: "zx-app-sidebar__row",
20525
21334
  dataset: { level }
@@ -20533,7 +21342,7 @@ var AppSidebar = class extends Component {
20533
21342
  }
20534
21343
  return row2;
20535
21344
  }
20536
- /** Handles dynamic tree controls without retaining detached nodes after a rerender. @param {Event} event @returns {void} */
21345
+ /** @param {Event} event @returns {void} */
20537
21346
  _onTreeClick(event) {
20538
21347
  const control = (
20539
21348
  /** @type {Element|null} */
@@ -20548,6 +21357,45 @@ var AppSidebar = class extends Component {
20548
21357
  if (item.children?.length) this.toggleBranch(item.id);
20549
21358
  else this._select(item, event);
20550
21359
  }
21360
+ /** @param {KeyboardEvent} event @returns {void} */
21361
+ _onTreeKeydown(event) {
21362
+ const current = (
21363
+ /** @type {Element|null} */
21364
+ event.target?.closest?.("[data-app-nav-id]")
21365
+ );
21366
+ if (!(current instanceof HTMLElement)) return;
21367
+ const item = findItem(this._items, current.getAttribute("data-app-nav-id"));
21368
+ const controls = visibleTreeControls(this.refs.list);
21369
+ const index = controls.indexOf(current);
21370
+ if (event.key === "ArrowRight" && item?.children?.length) {
21371
+ event.preventDefault();
21372
+ if (!this._expanded.has(String(item.id))) this.toggleBranch(item.id);
21373
+ else visibleTreeControls(current.parentElement?.querySelector(".zx-app-sidebar__sublist"))[0]?.focus();
21374
+ return;
21375
+ }
21376
+ if (event.key === "ArrowLeft") {
21377
+ if (item?.children?.length && this._expanded.has(String(item.id))) {
21378
+ event.preventDefault();
21379
+ this.toggleBranch(item.id);
21380
+ return;
21381
+ }
21382
+ const parentList = current.closest(".zx-app-sidebar__sublist");
21383
+ const parentControl = parentList?.parentElement?.querySelector(":scope > [data-app-nav-id]");
21384
+ if (parentControl instanceof HTMLElement) {
21385
+ event.preventDefault();
21386
+ parentControl.focus();
21387
+ }
21388
+ return;
21389
+ }
21390
+ let nextIndex;
21391
+ if (event.key === "Home") nextIndex = 0;
21392
+ else if (event.key === "End") nextIndex = controls.length - 1;
21393
+ else if (event.key === "ArrowDown") nextIndex = Math.min(controls.length - 1, index + 1);
21394
+ else if (event.key === "ArrowUp") nextIndex = Math.max(0, index - 1);
21395
+ else return;
21396
+ event.preventDefault();
21397
+ controls[nextIndex]?.focus();
21398
+ }
20551
21399
  /** @param {AppNavItem} item @param {Event} event @returns {void} */
20552
21400
  _select(item, event) {
20553
21401
  if (item.disabled) {
@@ -20569,14 +21417,14 @@ var AppSidebar = class extends Component {
20569
21417
  }
20570
21418
  /** @returns {void} */
20571
21419
  _syncActive() {
21420
+ if (!this.refs.list) return;
20572
21421
  const activeId = this._active == null ? null : String(this._active);
20573
21422
  for (const control of this.refs.list.querySelectorAll("[data-app-nav-id]")) {
20574
21423
  const item = findItem(this._items, control.getAttribute("data-app-nav-id"));
20575
21424
  const direct = activeId !== null && control.getAttribute("data-app-nav-id") === activeId;
20576
21425
  const descendant = activeId !== null && item ? containsItemId2(item.children ?? [], activeId) : false;
20577
- if (direct) {
20578
- control.setAttribute("aria-current", "page");
20579
- } else control.removeAttribute("aria-current");
21426
+ if (direct) control.setAttribute("aria-current", "page");
21427
+ else control.removeAttribute("aria-current");
20580
21428
  if (descendant) control.dataset.activeDescendant = "true";
20581
21429
  else delete control.dataset.activeDescendant;
20582
21430
  }
@@ -20590,7 +21438,7 @@ var AppSidebar = class extends Component {
20590
21438
  for (const id of this._expanded) if (!branchIds.has(id)) this._expanded.delete(id);
20591
21439
  }
20592
21440
  };
20593
- function navControl2(item, disclosure, expanded, controls) {
21441
+ function navControl2(item, disclosure, expanded, controls, visual) {
20594
21442
  const tag = item.href && !disclosure ? "a" : "button";
20595
21443
  return (
20596
21444
  /** @type {HTMLElement} */
@@ -20606,7 +21454,7 @@ function navControl2(item, disclosure, expanded, controls) {
20606
21454
  ariaControls: controls,
20607
21455
  dataset: { appNavId: String(item.id) }
20608
21456
  },
20609
- item.icon ? h("span", { class: "zx-app-sidebar__item-icon", ariaHidden: "true" }, icon(item.icon)) : null,
21457
+ visual ? h("span", { class: "zx-app-sidebar__item-icon", ariaHidden: "true" }, visual) : null,
20610
21458
  h("span", { class: "zx-app-sidebar__item-label" }, item.label),
20611
21459
  item.badge != null ? h("span", { class: "zx-app-sidebar__item-badge" }, String(item.badge)) : null,
20612
21460
  disclosure ? h(
@@ -20617,6 +21465,13 @@ function navControl2(item, disclosure, expanded, controls) {
20617
21465
  )
20618
21466
  );
20619
21467
  }
21468
+ function visibleTreeControls(root) {
21469
+ if (!root) return [];
21470
+ return (
21471
+ /** @type {HTMLElement[]} */
21472
+ [...root.querySelectorAll("[data-app-nav-id]")].filter((control) => !control.closest(".zx-app-sidebar__sublist[hidden]"))
21473
+ );
21474
+ }
20620
21475
  function visibleTreeControl(control) {
20621
21476
  let candidate = (
20622
21477
  /** @type {HTMLElement|null} */
@@ -20649,12 +21504,17 @@ function containsItemId2(items, id) {
20649
21504
  }
20650
21505
  function appendSlot2(target, content) {
20651
21506
  if (content == null) return;
20652
- if (typeof content === "string" || typeof content === "number") target.append(document.createTextNode(String(content)));
20653
- else if (typeof content === "object" && typeof content.toElement === "function") {
21507
+ if (typeof content === "string" || typeof content === "number") {
21508
+ target.append(document.createTextNode(String(content)));
21509
+ } else if (typeof content === "object" && typeof content.toElement === "function") {
20654
21510
  const element = content.toElement();
20655
21511
  if (element) target.append(element);
20656
21512
  } else if (typeof content === "object" && typeof content.nodeType === "number") target.append(content);
20657
21513
  }
21514
+ function normalizeSide(orientation, side) {
21515
+ if (orientation === "horizontal") return side === "bottom" ? "bottom" : "top";
21516
+ return side === "right" ? "right" : "left";
21517
+ }
20658
21518
 
20659
21519
  // src/components/message/message.js
20660
21520
  var MessageQueue = class {
@@ -22022,9 +22882,9 @@ function billingItemsConfig(options = {}) {
22022
22882
  } = options;
22023
22883
  const fields = { ...DEFAULT_FIELDS, ...requestedFields };
22024
22884
  const calculateLineTotal = typeof lineTotal === "function" ? lineTotal : (row2) => {
22025
- const quantity = Number(row2?.[fields.quantity]);
22026
- const price = Number(row2?.[fields.unitPrice]);
22027
- return Number.isFinite(quantity) && Number.isFinite(price) ? quantity * price : null;
22885
+ const quantity = billingNumber(row2?.[fields.quantity]);
22886
+ const price = billingNumber(row2?.[fields.unitPrice]);
22887
+ return quantity !== null && price !== null ? quantity * price : null;
22028
22888
  };
22029
22889
  const columns = Array.isArray(suppliedColumns) ? suppliedColumns.map((column) => ({ ...column })) : billingColumns(fields, { units, currencies, currency, locale, decimals, columnOverrides });
22030
22890
  return {
@@ -22129,6 +22989,11 @@ function billingColumns(fields, { units, currencies, currency, locale, decimals,
22129
22989
  function hasChoices(value) {
22130
22990
  return Array.isArray(value) ? value.length > 0 : Boolean(value && typeof value === "object" && Object.keys(value).length);
22131
22991
  }
22992
+ function billingNumber(value) {
22993
+ if (value == null || typeof value === "string" && value.trim() === "") return null;
22994
+ const number2 = Number(value);
22995
+ return Number.isFinite(number2) ? number2 : null;
22996
+ }
22132
22997
 
22133
22998
  // src/components/chart/chart.js
22134
22999
  var Chart = class extends Component {
@@ -22176,6 +23041,7 @@ var Chart = class extends Component {
22176
23041
  this._chartOptions = cloneChartValue(this.options.chartOptions);
22177
23042
  this._loading = Boolean(this.options.loading);
22178
23043
  this._error = null;
23044
+ this._hasError = false;
22179
23045
  this._handle = null;
22180
23046
  this._destroyed = false;
22181
23047
  const descriptionId = uid("zx-chart-description");
@@ -22221,6 +23087,7 @@ var Chart = class extends Component {
22221
23087
  setData(data) {
22222
23088
  this._data = normalizeChartData(data);
22223
23089
  this._error = null;
23090
+ this._hasError = false;
22224
23091
  this._loading = false;
22225
23092
  this._renderSummary();
22226
23093
  this._mountOrUpdate(true);
@@ -22256,6 +23123,7 @@ var Chart = class extends Component {
22256
23123
  /** Shows an error and destroys the current engine. @param {unknown} error @returns {this} */
22257
23124
  setError(error) {
22258
23125
  this._error = error ?? new Error(String(this.options.errorText));
23126
+ this._hasError = true;
22259
23127
  this._loading = false;
22260
23128
  this._destroyHandle();
22261
23129
  this._syncState();
@@ -22265,6 +23133,7 @@ var Chart = class extends Component {
22265
23133
  /** Clears the error and remounts when data and an adapter are available. @returns {this} */
22266
23134
  clearError() {
22267
23135
  this._error = null;
23136
+ this._hasError = false;
22268
23137
  this._mountOrUpdate(false);
22269
23138
  return this;
22270
23139
  }
@@ -22303,7 +23172,7 @@ var Chart = class extends Component {
22303
23172
  /** @param {boolean} emitUpdate @returns {void} */
22304
23173
  _mountOrUpdate(emitUpdate) {
22305
23174
  this._syncState();
22306
- if (this._loading || this._error || isEmptyChartData(this._data)) {
23175
+ if (this._loading || this._hasError || isEmptyChartData(this._data)) {
22307
23176
  this._destroyHandle();
22308
23177
  return;
22309
23178
  }
@@ -22358,7 +23227,7 @@ var Chart = class extends Component {
22358
23227
  /** @returns {void} */
22359
23228
  _syncState() {
22360
23229
  const empty = isEmptyChartData(this._data);
22361
- const state = this._loading ? "loading" : this._error ? "error" : empty ? "empty" : "ready";
23230
+ const state = this._loading ? "loading" : this._hasError ? "error" : empty ? "empty" : "ready";
22362
23231
  this.el.dataset.state = state;
22363
23232
  this.refs.viewport.hidden = state !== "ready";
22364
23233
  let text4 = "";
@@ -22570,7 +23439,8 @@ function themeChartOptions(root) {
22570
23439
  };
22571
23440
  }
22572
23441
  function errorMessage(error, fallback) {
22573
- return error && typeof error === "object" && "message" in error ? String(error.message) : String(fallback);
23442
+ const message = error && typeof error === "object" && "message" in error ? String(error.message) : "";
23443
+ return message.trim() ? message : String(fallback);
22574
23444
  }
22575
23445
 
22576
23446
  // src/components/tree/hierarchy.js
@@ -25669,10 +26539,10 @@ function registerFieldAdapters() {
25669
26539
  registerFieldAdapters();
25670
26540
  export {
25671
26541
  AccountMenu,
25672
- AppRail,
25673
26542
  AppSidebar,
25674
26543
  Avatar,
25675
26544
  Breadcrumb,
26545
+ Card,
25676
26546
  Chart,
25677
26547
  ChartJsAdapter,
25678
26548
  CheckButton,