@zeyos/zx 3.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +95 -0
- package/DESIGN-SYSTEM.md +87 -71
- package/README.md +16 -37
- package/dist/types/components/app-icon/app-icon.d.ts +88 -0
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +106 -32
- package/dist/types/components/card/card.d.ts +128 -0
- package/dist/types/components/chart/chart.d.ts +1 -0
- package/dist/types/components/filter/filter-model.d.ts +87 -0
- package/dist/types/components/filter/filter.d.ts +331 -0
- package/dist/types/components/launcher/launcher.d.ts +65 -6
- package/dist/types/components/select/select.d.ts +97 -4
- package/dist/types/components/table/table.d.ts +107 -3
- package/dist/types/components/tag-picker/tag-picker.d.ts +16 -0
- package/dist/types/core/dom.d.ts +9 -1
- package/dist/types/core/util.d.ts +9 -0
- package/dist/types/index.d.ts +6 -2
- package/dist/types/{components/app-rail → internal}/app-rail.d.ts +34 -19
- package/dist/types/zeyos/icons.d.ts +24 -2
- package/dist/types/zeyos/index.d.ts +3 -2
- package/dist/types/zeyos/launcher.d.ts +147 -0
- package/dist/types/zeyos/select.d.ts +41 -0
- package/dist/zx-zeyos.esm.js +2018 -113
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +815 -16
- package/dist/zx.esm.js +2661 -338
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +2661 -338
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +602 -110
- package/docs/llms.md +181 -123
- package/docs/llms.txt +24 -21
- package/package.json +8 -17
- package/src/components/app-icon/app-icon.css +105 -0
- package/src/components/app-icon/app-icon.js +134 -0
- package/src/components/app-sidebar/app-sidebar.css +12 -0
- package/src/components/app-sidebar/app-sidebar.js +310 -110
- package/src/components/card/card.css +149 -0
- package/src/components/card/card.js +285 -0
- package/src/components/chart/chart.js +8 -3
- package/src/components/filter/filter-model.js +226 -0
- package/src/components/filter/filter.css +147 -0
- package/src/components/filter/filter.js +554 -0
- package/src/components/grid/grid.css +1 -1
- package/src/components/grid/grid.js +14 -4
- package/src/components/launcher/launcher.css +113 -3
- package/src/components/launcher/launcher.js +234 -26
- package/src/components/message/message.css +36 -3
- package/src/components/select/field-adapter.js +9 -4
- package/src/components/select/select.css +161 -10
- package/src/components/select/select.js +361 -48
- package/src/components/table/table.css +85 -0
- package/src/components/table/table.js +426 -31
- package/src/components/tag-picker/tag-picker.css +19 -0
- package/src/components/tag-picker/tag-picker.js +42 -5
- package/src/core/dom.js +21 -1
- package/src/core/util.js +20 -0
- package/src/index.js +9 -2
- package/src/{components/app-rail → internal}/app-rail.js +80 -30
- package/src/zeyos/icons.js +38 -20
- package/src/zeyos/index.js +8 -2
- package/src/zeyos/launcher.js +338 -0
- package/src/zeyos/modules.js +1 -0
- package/src/zeyos/select.js +116 -1
- package/styles/tokens/global.css +1 -1
- package/styles/tokens/modules.css +1 -0
- package/styles/tokens/semantic.css +9 -0
- package/styles/tokens/themes.css +2 -2
- package/styles/zx.css +3 -0
- package/MIGRATION.md +0 -749
- package/dist/types/compat/base.d.ts +0 -156
- package/dist/types/compat/globals.d.ts +0 -20
- package/dist/types/compat/index.d.ts +0 -128
- package/dist/types/compat/map/basic.d.ts +0 -175
- package/dist/types/compat/map/data.d.ts +0 -103
- package/dist/types/compat/map/date-time.d.ts +0 -80
- package/dist/types/compat/map/factory.d.ts +0 -9
- package/dist/types/compat/map/form.d.ts +0 -98
- package/dist/types/compat/map/helpers.d.ts +0 -12
- package/dist/types/compat/map/http.d.ts +0 -36
- package/dist/types/compat/map/layout.d.ts +0 -106
- package/dist/types/compat/map/options.d.ts +0 -80
- package/dist/types/compat/map/select.d.ts +0 -95
- package/dist/types/compat/map/stubs.d.ts +0 -32
- package/dist/types/compat/map/utilities.d.ts +0 -37
- package/dist/types/compat-entry.d.ts +0 -5
- package/dist/zx-compat.esm.js +0 -17848
- package/dist/zx-compat.esm.min.js +0 -1
- package/dist/zx-compat.global.js +0 -29008
- package/dist/zx-compat.global.min.js +0 -3
- package/src/compat/base.js +0 -291
- package/src/compat/globals.js +0 -167
- package/src/compat/index.js +0 -84
- package/src/compat/map/basic.js +0 -464
- package/src/compat/map/data.js +0 -254
- package/src/compat/map/date-time.js +0 -177
- package/src/compat/map/factory.js +0 -45
- package/src/compat/map/form.js +0 -330
- package/src/compat/map/helpers.js +0 -46
- package/src/compat/map/http.js +0 -117
- package/src/compat/map/layout.js +0 -256
- package/src/compat/map/options.js +0 -267
- package/src/compat/map/select.js +0 -198
- package/src/compat/map/stubs.js +0 -26
- package/src/compat/map/utilities.js +0 -113
- package/src/compat-entry.js +0 -11
package/dist/zx.esm.js
CHANGED
|
@@ -73,6 +73,20 @@ function isElement(value) {
|
|
|
73
73
|
return Boolean(value && typeof value === "object" && /** @type {{nodeType?: unknown}} */
|
|
74
74
|
value.nodeType === 1);
|
|
75
75
|
}
|
|
76
|
+
function isCssColor(value) {
|
|
77
|
+
if (typeof value !== "string") return false;
|
|
78
|
+
const text4 = value.trim();
|
|
79
|
+
if (!text4 || text4.length > 256 || /[;{}\u0000-\u001f\u007f]/.test(text4) || /(?:url|image|image-set|cross-fade|element|var)\s*\(/i.test(text4)) return false;
|
|
80
|
+
const supports = globalThis.CSS?.supports;
|
|
81
|
+
if (typeof supports === "function") {
|
|
82
|
+
try {
|
|
83
|
+
return supports.call(globalThis.CSS, "color", text4);
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return /^(?:#[\da-f]{3,8}|[a-z]+|(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color|color-mix)\([^;{}]+\))$/i.test(text4);
|
|
89
|
+
}
|
|
76
90
|
function clamp(n, min, max) {
|
|
77
91
|
return Math.min(max, Math.max(min, n));
|
|
78
92
|
}
|
|
@@ -267,6 +281,18 @@ function compactElement(compact, properties = null, content = null) {
|
|
|
267
281
|
return element;
|
|
268
282
|
}
|
|
269
283
|
var __ = compactElement;
|
|
284
|
+
function safeHref(value) {
|
|
285
|
+
if (typeof value !== "string") return null;
|
|
286
|
+
const href = value.trim();
|
|
287
|
+
if (!href) return null;
|
|
288
|
+
try {
|
|
289
|
+
const protocol = new URL(href, globalThis.location?.href ?? "https://zx.invalid/").protocol;
|
|
290
|
+
if (["javascript:", "data:", "vbscript:"].includes(protocol.toLowerCase())) return null;
|
|
291
|
+
} catch {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
return href;
|
|
295
|
+
}
|
|
270
296
|
function applyCompactProperties(element, properties) {
|
|
271
297
|
for (const name in properties ?? {}) {
|
|
272
298
|
const value = properties[name];
|
|
@@ -698,9 +724,9 @@ var Component = class extends EventTarget {
|
|
|
698
724
|
let rendered;
|
|
699
725
|
try {
|
|
700
726
|
rendered = h.scope(this.refs, () => this.render());
|
|
701
|
-
} catch (
|
|
727
|
+
} catch (error2) {
|
|
702
728
|
this.#abort.abort();
|
|
703
|
-
throw
|
|
729
|
+
throw error2;
|
|
704
730
|
}
|
|
705
731
|
if (this.#created) {
|
|
706
732
|
const root = isElement(rendered) ? rendered : this.el;
|
|
@@ -991,8 +1017,8 @@ var Field = class _Field extends Component {
|
|
|
991
1017
|
/** @type {Readonly<FieldOptions>} */
|
|
992
1018
|
this.options
|
|
993
1019
|
));
|
|
994
|
-
} catch (
|
|
995
|
-
const message =
|
|
1020
|
+
} catch (error2) {
|
|
1021
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
996
1022
|
console.warn(`[zx.Field] Adapter "${type}" failed: ${message}`);
|
|
997
1023
|
this.adapter = errorAdapter(message, this.controlId);
|
|
998
1024
|
}
|
|
@@ -4975,8 +5001,8 @@ var FieldUpload = class extends Component {
|
|
|
4975
5001
|
return response;
|
|
4976
5002
|
} catch (caught) {
|
|
4977
5003
|
if (runId !== this._runId) return null;
|
|
4978
|
-
const
|
|
4979
|
-
if (
|
|
5004
|
+
const error2 = caught instanceof Error ? caught : new Error(String(caught));
|
|
5005
|
+
if (error2.name === "AbortError") {
|
|
4980
5006
|
this._finishAbort();
|
|
4981
5007
|
return null;
|
|
4982
5008
|
}
|
|
@@ -4985,8 +5011,8 @@ var FieldUpload = class extends Component {
|
|
|
4985
5011
|
this._transportController = null;
|
|
4986
5012
|
this._abort.hidden = true;
|
|
4987
5013
|
this._setState("error");
|
|
4988
|
-
this._status.textContent =
|
|
4989
|
-
this.emit("error", { error });
|
|
5014
|
+
this._status.textContent = error2.message;
|
|
5015
|
+
this.emit("error", { error: error2 });
|
|
4990
5016
|
return null;
|
|
4991
5017
|
}
|
|
4992
5018
|
}
|
|
@@ -5134,10 +5160,10 @@ var FieldUpload = class extends Component {
|
|
|
5134
5160
|
this.emit("abort", {});
|
|
5135
5161
|
}
|
|
5136
5162
|
/** @param {Error} error @returns {void} */
|
|
5137
|
-
_selectionError(
|
|
5163
|
+
_selectionError(error2) {
|
|
5138
5164
|
this._setState("error");
|
|
5139
|
-
this._status.textContent =
|
|
5140
|
-
this.emit("error", { error });
|
|
5165
|
+
this._status.textContent = error2.message;
|
|
5166
|
+
this.emit("error", { error: error2 });
|
|
5141
5167
|
}
|
|
5142
5168
|
/** @returns {void} */
|
|
5143
5169
|
_renderPreview() {
|
|
@@ -5220,9 +5246,9 @@ function parseResponse(text4) {
|
|
|
5220
5246
|
}
|
|
5221
5247
|
function abortError() {
|
|
5222
5248
|
if (typeof DOMException === "function") return new DOMException("Upload aborted", "AbortError");
|
|
5223
|
-
const
|
|
5224
|
-
|
|
5225
|
-
return
|
|
5249
|
+
const error2 = new Error("Upload aborted");
|
|
5250
|
+
error2.name = "AbortError";
|
|
5251
|
+
return error2;
|
|
5226
5252
|
}
|
|
5227
5253
|
|
|
5228
5254
|
// src/components/field-upload/field-adapter.js
|
|
@@ -6348,8 +6374,8 @@ var Rating = class extends Component {
|
|
|
6348
6374
|
const shown = this._preview ?? this._value;
|
|
6349
6375
|
const symbols = this.refs.symbols.querySelectorAll(".zx-rating__symbol");
|
|
6350
6376
|
symbols.forEach((symbol, index) => {
|
|
6351
|
-
const
|
|
6352
|
-
symbol.style.setProperty("--zx-rating-fill", `${
|
|
6377
|
+
const filled2 = Math.min(1, Math.max(0, shown - index));
|
|
6378
|
+
symbol.style.setProperty("--zx-rating-fill", `${filled2 * 100}%`);
|
|
6353
6379
|
symbol.toggleAttribute("data-preview", this._preview !== null);
|
|
6354
6380
|
});
|
|
6355
6381
|
this.el.dataset.value = String(shown);
|
|
@@ -6617,17 +6643,33 @@ var PRIORITY_KEYS = [
|
|
|
6617
6643
|
"priority.high",
|
|
6618
6644
|
"priority.highest"
|
|
6619
6645
|
];
|
|
6620
|
-
var PRIORITY_FALLBACKS = ["Lowest", "Low", "
|
|
6646
|
+
var PRIORITY_FALLBACKS = ["Lowest", "Low", "Medium", "High", "Highest"];
|
|
6647
|
+
var DEFAULT_STATUSES = Object.freeze([
|
|
6648
|
+
["backlog", "status.backlog", "Backlog", "dotted", "muted"],
|
|
6649
|
+
["todo", "status.todo", "Todo", "ring", "neutral"],
|
|
6650
|
+
["in-progress", "status.inProgress", "In Progress", "progress", "warning"],
|
|
6651
|
+
["in-review", "status.inReview", "In Review", "review", "info"],
|
|
6652
|
+
["ready", "status.ready", "Ready to Merge", "ready", "accent"],
|
|
6653
|
+
["waiting", "status.waiting", "Waiting for feedback", "waiting", "warning"],
|
|
6654
|
+
["done", "status.done", "Done", "done", "info"],
|
|
6655
|
+
["canceled", "status.canceled", "Canceled", "canceled", "muted"],
|
|
6656
|
+
["duplicate", "status.duplicate", "Duplicate", "duplicate", "muted"]
|
|
6657
|
+
]);
|
|
6621
6658
|
var Select = class _Select extends Component {
|
|
6622
6659
|
static cssName = "select";
|
|
6623
6660
|
/** @type {SelectOptions} */
|
|
6624
6661
|
static defaults = {
|
|
6625
6662
|
items: [],
|
|
6626
6663
|
fixedItems: [],
|
|
6664
|
+
fixedLabel: null,
|
|
6627
6665
|
valueKey: "ID",
|
|
6628
6666
|
labelKey: "name",
|
|
6629
6667
|
renderItem: null,
|
|
6630
6668
|
renderValue: null,
|
|
6669
|
+
renderValueAdornment: null,
|
|
6670
|
+
renderNone: null,
|
|
6671
|
+
noneLabel: null,
|
|
6672
|
+
actions: [],
|
|
6631
6673
|
value: null,
|
|
6632
6674
|
disabled: false,
|
|
6633
6675
|
placeholder: "",
|
|
@@ -6645,6 +6687,7 @@ var Select = class _Select extends Component {
|
|
|
6645
6687
|
this._visibleItems = [];
|
|
6646
6688
|
this._fixedItems = [];
|
|
6647
6689
|
this._visibleFixed = [];
|
|
6690
|
+
this._actions = [];
|
|
6648
6691
|
this._selected = null;
|
|
6649
6692
|
this._optionEntries = [];
|
|
6650
6693
|
this._activeIndex = -1;
|
|
@@ -6656,8 +6699,7 @@ var Select = class _Select extends Component {
|
|
|
6656
6699
|
this._position = null;
|
|
6657
6700
|
this._createdRoot = false;
|
|
6658
6701
|
this._root = null;
|
|
6659
|
-
this.
|
|
6660
|
-
this._originalAttributes = /* @__PURE__ */ new Map();
|
|
6702
|
+
this._snapshot = null;
|
|
6661
6703
|
this._createdRoot = this.el === null;
|
|
6662
6704
|
const root = (
|
|
6663
6705
|
/** @type {HTMLElement} */
|
|
@@ -6665,10 +6707,7 @@ var Select = class _Select extends Component {
|
|
|
6665
6707
|
);
|
|
6666
6708
|
this.el = root;
|
|
6667
6709
|
this._root = root;
|
|
6668
|
-
|
|
6669
|
-
this._rememberAttribute(root, "data-state");
|
|
6670
|
-
this._rememberAttribute(root, "data-loading");
|
|
6671
|
-
this._rememberAttribute(root, "aria-disabled");
|
|
6710
|
+
this._snapshot = this._createdRoot ? null : snapshotTarget(root);
|
|
6672
6711
|
const listId = uid("zx-select-list");
|
|
6673
6712
|
const input = h("input", {
|
|
6674
6713
|
ref: "input",
|
|
@@ -6683,6 +6722,12 @@ var Select = class _Select extends Component {
|
|
|
6683
6722
|
placeholder: String(this.options.placeholder ?? ""),
|
|
6684
6723
|
readOnly: !this.options.filter
|
|
6685
6724
|
});
|
|
6725
|
+
const valueAdornment = h("span", {
|
|
6726
|
+
ref: "valueAdornment",
|
|
6727
|
+
class: "zx-select__value-adornment",
|
|
6728
|
+
ariaHidden: "true",
|
|
6729
|
+
hidden: true
|
|
6730
|
+
});
|
|
6686
6731
|
const clear = h("button", {
|
|
6687
6732
|
ref: "clear",
|
|
6688
6733
|
class: "zx-icon-btn zx-select__clear",
|
|
@@ -6708,6 +6753,7 @@ var Select = class _Select extends Component {
|
|
|
6708
6753
|
const control = h(
|
|
6709
6754
|
"div",
|
|
6710
6755
|
{ ref: "control", class: "zx-select__control" },
|
|
6756
|
+
valueAdornment,
|
|
6711
6757
|
input,
|
|
6712
6758
|
clear,
|
|
6713
6759
|
loading,
|
|
@@ -6730,9 +6776,10 @@ var Select = class _Select extends Component {
|
|
|
6730
6776
|
this._visibleItems = this._items.slice();
|
|
6731
6777
|
this._fixedItems = Array.isArray(this.options.fixedItems) ? this.options.fixedItems.slice() : [];
|
|
6732
6778
|
this._visibleFixed = this._fixedItems.slice();
|
|
6779
|
+
this._actions = normalizeSelectActions(this.options.actions);
|
|
6733
6780
|
this._disabled = Boolean(this.options.disabled);
|
|
6734
6781
|
this._handleTypeahead = typeahead(
|
|
6735
|
-
() => this._optionEntries.filter((entry) => !entry.clear).map((entry) => entry.element),
|
|
6782
|
+
() => this._optionEntries.filter((entry) => !entry.clear && !entry.action).map((entry) => entry.element),
|
|
6736
6783
|
(_option, index) => {
|
|
6737
6784
|
if (!this._open) this.open();
|
|
6738
6785
|
this._setActive(this._itemEntryIndex(index));
|
|
@@ -6827,6 +6874,13 @@ var Select = class _Select extends Component {
|
|
|
6827
6874
|
this._position?.update();
|
|
6828
6875
|
return this;
|
|
6829
6876
|
}
|
|
6877
|
+
/** Replaces command rows without changing the selected value. @param {SelectAction[]} actions @returns {this} */
|
|
6878
|
+
setActions(actions2) {
|
|
6879
|
+
this._actions = normalizeSelectActions(actions2);
|
|
6880
|
+
this._renderOptions();
|
|
6881
|
+
this._position?.update();
|
|
6882
|
+
return this;
|
|
6883
|
+
}
|
|
6830
6884
|
/**
|
|
6831
6885
|
* Restores the configured initial value.
|
|
6832
6886
|
* @param {{silent?: boolean}} [options={}] Reset behavior.
|
|
@@ -6911,24 +6965,109 @@ var Select = class _Select extends Component {
|
|
|
6911
6965
|
value: null,
|
|
6912
6966
|
items: [],
|
|
6913
6967
|
renderItem(item) {
|
|
6968
|
+
return priorityContent(item, true);
|
|
6969
|
+
},
|
|
6970
|
+
renderValueAdornment(item) {
|
|
6971
|
+
return priorityIndicator(item.level);
|
|
6972
|
+
}
|
|
6973
|
+
});
|
|
6974
|
+
select.setItems(PRIORITY_KEYS.map((key, level) => {
|
|
6975
|
+
let name = level === 2 ? select.msg("priority.medium") : key;
|
|
6976
|
+
if (name === "priority.medium") name = select._message(key, PRIORITY_FALLBACKS[level]);
|
|
6977
|
+
else if (level !== 2) name = select._message(key, PRIORITY_FALLBACKS[level]);
|
|
6978
|
+
return { ID: level, name, level };
|
|
6979
|
+
}));
|
|
6980
|
+
select.set(requestedValue, { silent: true });
|
|
6981
|
+
return select;
|
|
6982
|
+
}
|
|
6983
|
+
/**
|
|
6984
|
+
* Creates a status picker with shape- and color-distinct workflow indicators.
|
|
6985
|
+
* @param {Element|string|null} target Component target.
|
|
6986
|
+
* @param {SelectOptions & {items?: Array<Record<string, any>>}} [options={}] Select overrides and status items.
|
|
6987
|
+
* @returns {Select}
|
|
6988
|
+
*/
|
|
6989
|
+
static status(target, options = {}) {
|
|
6990
|
+
const { items = null, value = null, ...rest } = options;
|
|
6991
|
+
const select = new _Select(target, {
|
|
6992
|
+
...rest,
|
|
6993
|
+
value: null,
|
|
6994
|
+
items: [],
|
|
6995
|
+
renderItem(item) {
|
|
6996
|
+
return statusContent(item);
|
|
6997
|
+
},
|
|
6998
|
+
renderValueAdornment(item) {
|
|
6999
|
+
return statusIndicator(item);
|
|
7000
|
+
}
|
|
7001
|
+
});
|
|
7002
|
+
const statuses = Array.isArray(items) ? items.map(normalizeStatusItem).filter(Boolean) : DEFAULT_STATUSES.map(([ID, key, fallback, indicator, tone], index) => ({
|
|
7003
|
+
ID,
|
|
7004
|
+
name: select._message(key, fallback),
|
|
7005
|
+
indicator,
|
|
7006
|
+
tone,
|
|
7007
|
+
shortcut: String(index + 1)
|
|
7008
|
+
}));
|
|
7009
|
+
select.setItems(statuses);
|
|
7010
|
+
select.set(value, { silent: true });
|
|
7011
|
+
return select;
|
|
7012
|
+
}
|
|
7013
|
+
/**
|
|
7014
|
+
* Creates a rich entity picker with optional Recent choices and a create command.
|
|
7015
|
+
* @param {Element|string|null} target Component target.
|
|
7016
|
+
* @param {SelectOptions & EntitySelectOptions} [options={}] Entity-preset options.
|
|
7017
|
+
* @returns {Select}
|
|
7018
|
+
*/
|
|
7019
|
+
static entity(target, options = {}) {
|
|
7020
|
+
const {
|
|
7021
|
+
recent = [],
|
|
7022
|
+
recentLabel = "Recent",
|
|
7023
|
+
create = false,
|
|
7024
|
+
iconKey = "icon",
|
|
7025
|
+
colorKey = "color",
|
|
7026
|
+
subtitleKey = "description",
|
|
7027
|
+
renderIcon = null,
|
|
7028
|
+
fixedItems = [],
|
|
7029
|
+
actions: actions2 = [],
|
|
7030
|
+
renderItem = null,
|
|
7031
|
+
renderValueAdornment = null,
|
|
7032
|
+
...rest
|
|
7033
|
+
} = options;
|
|
7034
|
+
const visual = (item, selected = false) => {
|
|
7035
|
+
if (typeof renderIcon === "function") return renderIcon(item, { selected });
|
|
7036
|
+
const value = readSelectItem(item, iconKey);
|
|
7037
|
+
if (value && typeof value === "object" && typeof value.nodeType === "number") return value.cloneNode(true);
|
|
7038
|
+
if (typeof value !== "string" || !value) return null;
|
|
7039
|
+
const color = readSelectItem(item, colorKey);
|
|
7040
|
+
return h("span", {
|
|
7041
|
+
class: "zx-select__entity-glyph",
|
|
7042
|
+
style: isCssColor(color) ? { "--zx-select-entity-color": String(color) } : null,
|
|
7043
|
+
ariaHidden: "true"
|
|
7044
|
+
}, icon(value, { size: 14 }));
|
|
7045
|
+
};
|
|
7046
|
+
const createAction = create ? normalizeEntityCreateAction(create) : null;
|
|
7047
|
+
const select = new _Select(target, {
|
|
7048
|
+
...rest,
|
|
7049
|
+
fixedItems: [...Array.isArray(recent) ? recent : [], ...Array.isArray(fixedItems) ? fixedItems : []],
|
|
7050
|
+
fixedLabel: Array.isArray(recent) && recent.length ? recentLabel : rest.fixedLabel ?? null,
|
|
7051
|
+
actions: [...Array.isArray(actions2) ? actions2 : [], ...createAction ? [createAction] : []],
|
|
7052
|
+
renderItem: renderItem ?? ((item) => {
|
|
7053
|
+
const decoration = visual(item, false);
|
|
7054
|
+
const subtitle = readSelectItem(item, subtitleKey);
|
|
7055
|
+
const copy = h(
|
|
7056
|
+
"span",
|
|
7057
|
+
{ class: "zx-select__entity-copy" },
|
|
7058
|
+
h("span", { class: "zx-select__entity-label" }, String(readSelectItem(item, rest.labelKey ?? "name") ?? "")),
|
|
7059
|
+
subtitle == null || subtitle === "" ? null : h("span", { class: "zx-select__entity-subtitle" }, String(subtitle))
|
|
7060
|
+
);
|
|
6914
7061
|
return h(
|
|
6915
7062
|
"span",
|
|
6916
|
-
{ class: "zx-
|
|
6917
|
-
h("span", {
|
|
6918
|
-
|
|
6919
|
-
dataset: { level: item.level },
|
|
6920
|
-
ariaHidden: "true"
|
|
6921
|
-
}),
|
|
6922
|
-
h("span", {}, String(item.name))
|
|
7063
|
+
{ class: "zx-select__entity" },
|
|
7064
|
+
decoration ? h("span", { class: "zx-select__entity-icon", ariaHidden: "true" }, decoration) : null,
|
|
7065
|
+
copy
|
|
6923
7066
|
);
|
|
6924
|
-
}
|
|
7067
|
+
}),
|
|
7068
|
+
renderValueAdornment: renderValueAdornment ?? ((item) => visual(item, true))
|
|
6925
7069
|
});
|
|
6926
|
-
select.
|
|
6927
|
-
ID: level,
|
|
6928
|
-
name: select._message(key, PRIORITY_FALLBACKS[level]),
|
|
6929
|
-
level
|
|
6930
|
-
})));
|
|
6931
|
-
select.set(requestedValue, { silent: true });
|
|
7070
|
+
select.el.dataset.preset = "entity";
|
|
6932
7071
|
return select;
|
|
6933
7072
|
}
|
|
6934
7073
|
/**
|
|
@@ -6943,12 +7082,21 @@ var Select = class _Select extends Component {
|
|
|
6943
7082
|
* @returns {Select}
|
|
6944
7083
|
*/
|
|
6945
7084
|
static permission(target, options = {}) {
|
|
6946
|
-
const {
|
|
7085
|
+
const {
|
|
7086
|
+
groups = [],
|
|
7087
|
+
value = true,
|
|
7088
|
+
valueKey = "ID",
|
|
7089
|
+
labelKey = "name",
|
|
7090
|
+
filter = "local",
|
|
7091
|
+
...rest
|
|
7092
|
+
} = options;
|
|
6947
7093
|
const loader = typeof groups === "function" ? groups : null;
|
|
6948
7094
|
const shareable = loader ? [] : groups;
|
|
6949
7095
|
const choices = [{ ID: false, name: "Private" }, { ID: true, name: "Public" }];
|
|
6950
7096
|
const select = new _Select(target, {
|
|
6951
|
-
|
|
7097
|
+
// An async group loader is the data source and cannot be disconnected by an incidental
|
|
7098
|
+
// `filter` override. Array-backed groups still honor local/disabled filtering.
|
|
7099
|
+
filter: loader ?? filter,
|
|
6952
7100
|
...rest,
|
|
6953
7101
|
value: null,
|
|
6954
7102
|
items: shareable,
|
|
@@ -6972,18 +7120,24 @@ var Select = class _Select extends Component {
|
|
|
6972
7120
|
/** @type {HTMLElement} */
|
|
6973
7121
|
this.el
|
|
6974
7122
|
);
|
|
6975
|
-
if (!this._createdRoot && root)
|
|
6976
|
-
root.replaceChildren(...this._originalChildren);
|
|
6977
|
-
for (const [name, value] of this._originalAttributes) {
|
|
6978
|
-
if (value === null) root.removeAttribute(name);
|
|
6979
|
-
else root.setAttribute(name, value);
|
|
6980
|
-
}
|
|
6981
|
-
}
|
|
7123
|
+
if (!this._createdRoot && root) restoreTarget(root, this._snapshot);
|
|
6982
7124
|
super.destroy();
|
|
6983
7125
|
}
|
|
6984
7126
|
/** @param {KeyboardEvent} event @returns {void} */
|
|
6985
7127
|
_onKeydown(event) {
|
|
6986
7128
|
if (this._disabled) return;
|
|
7129
|
+
if (this._open && isPrintable(event)) {
|
|
7130
|
+
const shortcut = this._optionEntries.findIndex((entry) => !entry.clear && !entry.action && String(
|
|
7131
|
+
/** @type {Record<string, any>|null} */
|
|
7132
|
+
entry.item?.shortcut ?? ""
|
|
7133
|
+
) === event.key);
|
|
7134
|
+
if (shortcut >= 0) {
|
|
7135
|
+
event.preventDefault();
|
|
7136
|
+
this._setActive(shortcut);
|
|
7137
|
+
this._chooseEntry(this._optionEntries[shortcut]);
|
|
7138
|
+
return;
|
|
7139
|
+
}
|
|
7140
|
+
}
|
|
6987
7141
|
if (event.key === "ArrowDown") {
|
|
6988
7142
|
event.preventDefault();
|
|
6989
7143
|
if (!this._open) this.open();
|
|
@@ -7031,6 +7185,7 @@ var Select = class _Select extends Component {
|
|
|
7031
7185
|
/** @type {HTMLInputElement} */
|
|
7032
7186
|
this.refs.input.value
|
|
7033
7187
|
);
|
|
7188
|
+
this._syncValueAdornment();
|
|
7034
7189
|
if (!this._open) this.open();
|
|
7035
7190
|
if (this.options.filter === "local") {
|
|
7036
7191
|
this._visibleFixed = this._matchFixed(query);
|
|
@@ -7054,6 +7209,22 @@ var Select = class _Select extends Component {
|
|
|
7054
7209
|
/** @param {SelectOptionEntry|undefined} entry @returns {void} */
|
|
7055
7210
|
_chooseEntry(entry) {
|
|
7056
7211
|
if (!entry) return;
|
|
7212
|
+
if (entry.action) {
|
|
7213
|
+
const detail = {
|
|
7214
|
+
id: entry.action.id,
|
|
7215
|
+
action: entry.action,
|
|
7216
|
+
query: (
|
|
7217
|
+
/** @type {HTMLInputElement} */
|
|
7218
|
+
this.refs.input.value
|
|
7219
|
+
),
|
|
7220
|
+
select: this
|
|
7221
|
+
};
|
|
7222
|
+
const action = this.emit("action", detail);
|
|
7223
|
+
if (!action.defaultPrevented) entry.action.invoke?.(detail);
|
|
7224
|
+
this.close();
|
|
7225
|
+
this.focus();
|
|
7226
|
+
return;
|
|
7227
|
+
}
|
|
7057
7228
|
this.set(entry.clear ? null : entry.item);
|
|
7058
7229
|
this.close();
|
|
7059
7230
|
this.focus();
|
|
@@ -7086,14 +7257,19 @@ var Select = class _Select extends Component {
|
|
|
7086
7257
|
list.replaceChildren();
|
|
7087
7258
|
this._optionEntries = [];
|
|
7088
7259
|
let lastGroup = /* @__PURE__ */ Symbol("initial-group");
|
|
7260
|
+
const fixed = this._uniqueItems(this._visibleFixed);
|
|
7261
|
+
const items = this._uniqueItems(this._visibleItems, fixed);
|
|
7089
7262
|
if (this.options.clearable) {
|
|
7090
|
-
this._appendOption(null, true, this.options.
|
|
7263
|
+
this._appendOption(null, true, this.options.noneLabel ?? this._message("select.none", "No selection"));
|
|
7091
7264
|
}
|
|
7092
|
-
|
|
7093
|
-
|
|
7265
|
+
if (fixed.length > 0 && this.options.fixedLabel) {
|
|
7266
|
+
list.append(h("div", { class: "zx-select__group", role: "presentation" }, String(this.options.fixedLabel)));
|
|
7267
|
+
}
|
|
7268
|
+
for (const item of fixed) this._appendOption(item, false);
|
|
7269
|
+
if (fixed.length > 0 && items.length > 0 && !this.options.groupKey) {
|
|
7094
7270
|
list.append(h("div", { class: "zx-select__separator", role: "presentation" }));
|
|
7095
7271
|
}
|
|
7096
|
-
for (const item of
|
|
7272
|
+
for (const item of items) {
|
|
7097
7273
|
if (this.options.groupKey) {
|
|
7098
7274
|
const group = this._read(item, this.options.groupKey);
|
|
7099
7275
|
if (!Object.is(group, lastGroup)) {
|
|
@@ -7106,7 +7282,23 @@ var Select = class _Select extends Component {
|
|
|
7106
7282
|
}
|
|
7107
7283
|
this._appendOption(item, false);
|
|
7108
7284
|
}
|
|
7109
|
-
if (this.
|
|
7285
|
+
if (this._actions.length > 0) {
|
|
7286
|
+
if (fixed.length > 0 || items.length > 0 || this.options.clearable) {
|
|
7287
|
+
list.append(h("div", { class: "zx-select__separator", role: "presentation" }));
|
|
7288
|
+
}
|
|
7289
|
+
let actionGroup = /* @__PURE__ */ Symbol("initial-action-group");
|
|
7290
|
+
for (const action of this._actions) {
|
|
7291
|
+
if (action.group && action.group !== actionGroup) {
|
|
7292
|
+
list.append(h("div", {
|
|
7293
|
+
class: "zx-select__group",
|
|
7294
|
+
role: "presentation"
|
|
7295
|
+
}, action.group));
|
|
7296
|
+
actionGroup = action.group;
|
|
7297
|
+
}
|
|
7298
|
+
this._appendAction(action);
|
|
7299
|
+
}
|
|
7300
|
+
}
|
|
7301
|
+
if (items.length === 0 && fixed.length === 0 && this._actions.length === 0 && !this._loading) {
|
|
7110
7302
|
list.append(h("div", {
|
|
7111
7303
|
class: "zx-select__empty",
|
|
7112
7304
|
role: "status"
|
|
@@ -7118,7 +7310,7 @@ var Select = class _Select extends Component {
|
|
|
7118
7310
|
_appendOption(item, clear, label) {
|
|
7119
7311
|
const selected = clear ? this._selected === null : this._selected !== null && Object.is(this._itemValue(item), this.value);
|
|
7120
7312
|
const index = this._optionEntries.length;
|
|
7121
|
-
const content = clear ? String(label ?? "") : this._renderItem(item);
|
|
7313
|
+
const content = clear && typeof this.options.renderNone === "function" ? normalizeRenderedContent(this.options.renderNone(String(label ?? ""))) : clear ? String(label ?? "") : this._renderItem(item);
|
|
7122
7314
|
const option = h("div", {
|
|
7123
7315
|
class: "zx-select__option",
|
|
7124
7316
|
id: uid("zx-select-option"),
|
|
@@ -7128,16 +7320,37 @@ var Select = class _Select extends Component {
|
|
|
7128
7320
|
}, h("span", { class: "zx-select__option-content" }, content));
|
|
7129
7321
|
if (selected) option.append(icon("check", { size: 15 }));
|
|
7130
7322
|
this.refs.list.append(option);
|
|
7131
|
-
this._optionEntries.push({ item, clear, element: option });
|
|
7323
|
+
this._optionEntries.push({ item, clear, action: null, element: option });
|
|
7324
|
+
}
|
|
7325
|
+
/** @param {SelectAction} action @returns {void} */
|
|
7326
|
+
_appendAction(action) {
|
|
7327
|
+
const index = this._optionEntries.length;
|
|
7328
|
+
const visual = selectActionIcon(action.icon);
|
|
7329
|
+
const option = h("div", {
|
|
7330
|
+
class: "zx-select__option zx-select__action",
|
|
7331
|
+
id: uid("zx-select-option"),
|
|
7332
|
+
role: "option",
|
|
7333
|
+
ariaSelected: "false",
|
|
7334
|
+
dataset: { index, kind: "action" }
|
|
7335
|
+
}, h(
|
|
7336
|
+
"span",
|
|
7337
|
+
{ class: "zx-select__option-content zx-select__action-content" },
|
|
7338
|
+
visual ? h("span", { class: "zx-select__action-icon", ariaHidden: "true" }, visual) : null,
|
|
7339
|
+
h(
|
|
7340
|
+
"span",
|
|
7341
|
+
{ class: "zx-select__action-copy" },
|
|
7342
|
+
h("span", { class: "zx-select__action-label" }, action.label),
|
|
7343
|
+
action.description ? h("span", { class: "zx-select__action-description" }, action.description) : null
|
|
7344
|
+
)
|
|
7345
|
+
));
|
|
7346
|
+
this.refs.list.append(option);
|
|
7347
|
+
this._optionEntries.push({ item: null, clear: false, action, element: option });
|
|
7132
7348
|
}
|
|
7133
7349
|
/** @param {SelectItem|null} item @returns {Node|string} */
|
|
7134
7350
|
_renderItem(item) {
|
|
7135
7351
|
if (typeof this.options.renderItem === "function") {
|
|
7136
7352
|
const rendered = this.options.renderItem(item);
|
|
7137
|
-
return rendered
|
|
7138
|
-
/** @type {Node} */
|
|
7139
|
-
rendered
|
|
7140
|
-
) : String(rendered ?? "");
|
|
7353
|
+
return normalizeRenderedContent(rendered);
|
|
7141
7354
|
}
|
|
7142
7355
|
return this._itemLabel(item);
|
|
7143
7356
|
}
|
|
@@ -7208,9 +7421,29 @@ var Select = class _Select extends Component {
|
|
|
7208
7421
|
this.refs.input
|
|
7209
7422
|
);
|
|
7210
7423
|
input.value = this._selected === null ? "" : typeof this.options.renderValue === "function" ? String(this.options.renderValue(this._selected) ?? "") : this._itemLabel(this._selected);
|
|
7424
|
+
this._syncValueAdornment();
|
|
7211
7425
|
this.refs.clear.hidden = !this.options.clearable || this._selected === null;
|
|
7212
7426
|
}
|
|
7213
7427
|
/** @returns {void} */
|
|
7428
|
+
_syncValueAdornment() {
|
|
7429
|
+
const host = this.refs.valueAdornment;
|
|
7430
|
+
if (!host) return;
|
|
7431
|
+
const selectedLabel = this._selected === null ? "" : typeof this.options.renderValue === "function" ? String(this.options.renderValue(this._selected) ?? "") : this._itemLabel(this._selected);
|
|
7432
|
+
const query = String(
|
|
7433
|
+
/** @type {HTMLInputElement} */
|
|
7434
|
+
this.refs.input?.value ?? ""
|
|
7435
|
+
);
|
|
7436
|
+
if (this._selected === null || typeof this.options.renderValueAdornment !== "function" || Boolean(this.options.filter && query !== selectedLabel)) {
|
|
7437
|
+
host.replaceChildren();
|
|
7438
|
+
host.hidden = true;
|
|
7439
|
+
return;
|
|
7440
|
+
}
|
|
7441
|
+
const rendered = this.options.renderValueAdornment(this._selected);
|
|
7442
|
+
const content = normalizeRenderedContent(rendered);
|
|
7443
|
+
host.replaceChildren(content instanceof Node ? content : document.createTextNode(content));
|
|
7444
|
+
host.hidden = rendered == null || typeof content === "string" && content === "";
|
|
7445
|
+
}
|
|
7446
|
+
/** @returns {void} */
|
|
7214
7447
|
_syncDisabled() {
|
|
7215
7448
|
const input = (
|
|
7216
7449
|
/** @type {HTMLInputElement} */
|
|
@@ -7266,6 +7499,16 @@ var Select = class _Select extends Component {
|
|
|
7266
7499
|
_matchFixed(query) {
|
|
7267
7500
|
return matchItems(this._fixedItems, query, this._searchKeys());
|
|
7268
7501
|
}
|
|
7502
|
+
/** De-duplicates choices by public value while preserving caller order. @param {SelectItem[]} items @param {SelectItem[]} [excluded=[]] @returns {SelectItem[]} */
|
|
7503
|
+
_uniqueItems(items, excluded = []) {
|
|
7504
|
+
const values = excluded.map((item) => this._itemValue(item));
|
|
7505
|
+
return items.filter((item) => {
|
|
7506
|
+
const value = this._itemValue(item);
|
|
7507
|
+
if (values.some((candidate) => Object.is(candidate, value))) return false;
|
|
7508
|
+
values.push(value);
|
|
7509
|
+
return true;
|
|
7510
|
+
});
|
|
7511
|
+
}
|
|
7269
7512
|
/** @returns {number} */
|
|
7270
7513
|
_selectedEntryIndex() {
|
|
7271
7514
|
if (this._selected === null) return this.options.clearable ? 0 : -1;
|
|
@@ -7277,16 +7520,104 @@ var Select = class _Select extends Component {
|
|
|
7277
7520
|
_itemEntryIndex(itemIndex) {
|
|
7278
7521
|
return itemIndex + (this.options.clearable ? 1 : 0);
|
|
7279
7522
|
}
|
|
7280
|
-
/** @param {string} name @returns {void} */
|
|
7281
|
-
_rememberAttribute(element, name) {
|
|
7282
|
-
this._originalAttributes.set(name, element.getAttribute(name));
|
|
7283
|
-
}
|
|
7284
7523
|
/** @param {string} key @param {string} fallback @returns {string} */
|
|
7285
7524
|
_message(key, fallback) {
|
|
7286
7525
|
const message = this.msg(key);
|
|
7287
7526
|
return message === key ? fallback : message;
|
|
7288
7527
|
}
|
|
7289
7528
|
};
|
|
7529
|
+
function readSelectItem(item, reader) {
|
|
7530
|
+
if (typeof reader === "function") return reader(item);
|
|
7531
|
+
if (item !== null && typeof item === "object" && typeof reader === "string") return item[reader];
|
|
7532
|
+
return null;
|
|
7533
|
+
}
|
|
7534
|
+
function normalizeEntityCreateAction(value) {
|
|
7535
|
+
const descriptor = value === true ? {} : value;
|
|
7536
|
+
return {
|
|
7537
|
+
id: descriptor.id ?? "create",
|
|
7538
|
+
label: descriptor.label ?? "Create new\u2026",
|
|
7539
|
+
description: descriptor.description,
|
|
7540
|
+
group: descriptor.group ?? "New",
|
|
7541
|
+
icon: descriptor.icon ?? "plus",
|
|
7542
|
+
invoke: descriptor.invoke
|
|
7543
|
+
};
|
|
7544
|
+
}
|
|
7545
|
+
function normalizeRenderedContent(rendered) {
|
|
7546
|
+
return rendered && typeof rendered === "object" && "nodeType" in rendered ? (
|
|
7547
|
+
/** @type {Node} */
|
|
7548
|
+
rendered
|
|
7549
|
+
) : String(rendered ?? "");
|
|
7550
|
+
}
|
|
7551
|
+
function normalizeSelectActions(actions2) {
|
|
7552
|
+
if (!Array.isArray(actions2)) return [];
|
|
7553
|
+
return actions2.filter((action) => action && typeof action === "object" && action.id != null && action.label != null).map((action) => ({
|
|
7554
|
+
...action,
|
|
7555
|
+
label: String(action.label),
|
|
7556
|
+
description: action.description == null ? void 0 : String(action.description),
|
|
7557
|
+
group: action.group == null ? void 0 : String(action.group)
|
|
7558
|
+
}));
|
|
7559
|
+
}
|
|
7560
|
+
function selectActionIcon(visual) {
|
|
7561
|
+
if (typeof visual === "string") return icon(visual);
|
|
7562
|
+
if (visual instanceof Node) return visual.cloneNode(true);
|
|
7563
|
+
return null;
|
|
7564
|
+
}
|
|
7565
|
+
function priorityContent(item, label) {
|
|
7566
|
+
return h(
|
|
7567
|
+
"span",
|
|
7568
|
+
{ class: "zx-select__priority" },
|
|
7569
|
+
priorityIndicator(item.level),
|
|
7570
|
+
label ? h("span", {}, String(item.name)) : null
|
|
7571
|
+
);
|
|
7572
|
+
}
|
|
7573
|
+
function priorityIndicator(level) {
|
|
7574
|
+
const normalized = Math.max(0, Math.min(4, Math.trunc(Number(level) || 0)));
|
|
7575
|
+
return h("span", {
|
|
7576
|
+
class: "zx-select__priority-icon",
|
|
7577
|
+
dataset: { level: normalized },
|
|
7578
|
+
ariaHidden: "true"
|
|
7579
|
+
}, Array.from({ length: 5 }, (_, index) => h("span", {
|
|
7580
|
+
dataset: { filled: String(index <= normalized) }
|
|
7581
|
+
})));
|
|
7582
|
+
}
|
|
7583
|
+
function normalizeStatusItem(input) {
|
|
7584
|
+
if (!input || typeof input !== "object" || input.ID == null || input.name == null) return null;
|
|
7585
|
+
const indicators = ["dotted", "ring", "progress", "review", "ready", "waiting", "done", "canceled", "duplicate"];
|
|
7586
|
+
const tones = ["muted", "neutral", "warning", "info", "accent", "success", "danger"];
|
|
7587
|
+
return {
|
|
7588
|
+
...input,
|
|
7589
|
+
name: String(input.name),
|
|
7590
|
+
indicator: indicators.includes(String(input.indicator)) ? String(input.indicator) : "ring",
|
|
7591
|
+
tone: tones.includes(String(input.tone)) ? String(input.tone) : "muted",
|
|
7592
|
+
description: input.description == null ? "" : String(input.description),
|
|
7593
|
+
shortcut: input.shortcut == null ? "" : String(input.shortcut)
|
|
7594
|
+
};
|
|
7595
|
+
}
|
|
7596
|
+
function statusContent(item) {
|
|
7597
|
+
return h(
|
|
7598
|
+
"span",
|
|
7599
|
+
{ class: "zx-select__status" },
|
|
7600
|
+
statusIndicator(item),
|
|
7601
|
+
h(
|
|
7602
|
+
"span",
|
|
7603
|
+
{ class: "zx-select__status-copy" },
|
|
7604
|
+
h("span", { class: "zx-select__status-label" }, String(item.name)),
|
|
7605
|
+
item.description ? h("span", { class: "zx-select__status-description" }, item.description) : null
|
|
7606
|
+
),
|
|
7607
|
+
item.shortcut ? h("kbd", { class: "zx-select__status-shortcut" }, item.shortcut) : null
|
|
7608
|
+
);
|
|
7609
|
+
}
|
|
7610
|
+
function statusIndicator(item) {
|
|
7611
|
+
const indicator = String(item.indicator ?? "ring");
|
|
7612
|
+
const marker = h("span", {
|
|
7613
|
+
class: "zx-select__status-indicator",
|
|
7614
|
+
dataset: { indicator, tone: String(item.tone ?? "muted") },
|
|
7615
|
+
ariaHidden: "true"
|
|
7616
|
+
});
|
|
7617
|
+
if (indicator === "done") marker.append(icon("check", { size: 8 }));
|
|
7618
|
+
if (indicator === "canceled") marker.append(icon("x", { size: 7 }));
|
|
7619
|
+
return marker;
|
|
7620
|
+
}
|
|
7290
7621
|
function readItem(item, reader) {
|
|
7291
7622
|
if (typeof reader === "function") return reader(item);
|
|
7292
7623
|
if (item === null || typeof item !== "object") return item;
|
|
@@ -7299,10 +7630,11 @@ function isPrintable(event) {
|
|
|
7299
7630
|
// src/components/select/field-adapter.js
|
|
7300
7631
|
function registerSelectFieldAdapter() {
|
|
7301
7632
|
Field.register("zxselect", (field, options) => {
|
|
7302
|
-
const
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7633
|
+
const props = options.props ?? {};
|
|
7634
|
+
const preset = props.preset;
|
|
7635
|
+
const selectOptions = { ...props, value: options.value };
|
|
7636
|
+
delete selectOptions.preset;
|
|
7637
|
+
const select = field.own(preset === "priority" ? Select.priority(null, selectOptions) : preset === "status" ? Select.status(null, selectOptions) : new Select(null, selectOptions));
|
|
7306
7638
|
field.listen(select.el, "change", (event) => event.stopImmediatePropagation());
|
|
7307
7639
|
select.on("change", (event) => field.emit("change", { value: event.detail.value }));
|
|
7308
7640
|
return {
|
|
@@ -7686,6 +8018,8 @@ var TagPicker = class extends Component {
|
|
|
7686
8018
|
values: [],
|
|
7687
8019
|
valueKey: "ID",
|
|
7688
8020
|
labelKey: "name",
|
|
8021
|
+
iconKey: "icon",
|
|
8022
|
+
colorKey: "color",
|
|
7689
8023
|
searchKeys: null,
|
|
7690
8024
|
filter: "local",
|
|
7691
8025
|
minQuery: 0,
|
|
@@ -8026,12 +8360,13 @@ var TagPicker = class extends Component {
|
|
|
8026
8360
|
_renderTags() {
|
|
8027
8361
|
const tags = this._selected.map((item) => {
|
|
8028
8362
|
const label = this._labelOf(item);
|
|
8029
|
-
const
|
|
8030
|
-
const
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8363
|
+
const color = this._colorOf(item);
|
|
8364
|
+
const content = this.options.renderTag ? this.options.renderTag(item) : [this._iconOf(item), h("span", { class: "zx-tag-picker__tag-label" }, label)];
|
|
8365
|
+
const tag = h("span", {
|
|
8366
|
+
class: "zx-tag-picker__tag",
|
|
8367
|
+
dataset: { colored: String(Boolean(color)) },
|
|
8368
|
+
style: color ? { "--zx-tag-color": color } : null
|
|
8369
|
+
}, content);
|
|
8035
8370
|
if (!this._readonly && !this._disabled) {
|
|
8036
8371
|
tag.append(h("button", {
|
|
8037
8372
|
class: "zx-icon-btn zx-tag-picker__remove",
|
|
@@ -8069,7 +8404,7 @@ var TagPicker = class extends Component {
|
|
|
8069
8404
|
h(
|
|
8070
8405
|
"span",
|
|
8071
8406
|
{ class: "zx-tag-picker__option-label" },
|
|
8072
|
-
this.options.renderItem ? this.options.renderItem(item) : this._labelOf(item)
|
|
8407
|
+
this.options.renderItem ? this.options.renderItem(item) : [this._iconOf(item), h("span", {}, this._labelOf(item))]
|
|
8073
8408
|
)
|
|
8074
8409
|
);
|
|
8075
8410
|
if (!selected && this.isFull()) option.setAttribute("aria-disabled", "true");
|
|
@@ -8294,10 +8629,10 @@ var TagPicker = class extends Component {
|
|
|
8294
8629
|
if (sequence !== this._requestSequence) return;
|
|
8295
8630
|
this._items = Array.isArray(result) ? result.slice() : [];
|
|
8296
8631
|
this._visible = this._items.slice();
|
|
8297
|
-
} catch (
|
|
8632
|
+
} catch (error2) {
|
|
8298
8633
|
if (sequence !== this._requestSequence) return;
|
|
8299
8634
|
this._visible = [];
|
|
8300
|
-
this.emit("error", { error });
|
|
8635
|
+
this.emit("error", { error: error2 });
|
|
8301
8636
|
} finally {
|
|
8302
8637
|
if (sequence === this._requestSequence) {
|
|
8303
8638
|
this._setLoading(false);
|
|
@@ -8390,6 +8725,34 @@ var TagPicker = class extends Component {
|
|
|
8390
8725
|
if (item !== null && typeof item === "object") return String(item[key] ?? "");
|
|
8391
8726
|
return String(item ?? "");
|
|
8392
8727
|
}
|
|
8728
|
+
/** @param {unknown} item @returns {Node|null} */
|
|
8729
|
+
_iconOf(item) {
|
|
8730
|
+
const value = this._readItem(item, this.options.iconKey);
|
|
8731
|
+
const visual = value && typeof value === "object" && typeof value.nodeType === "number" ? (
|
|
8732
|
+
/** @type {Node} */
|
|
8733
|
+
value.cloneNode(true)
|
|
8734
|
+
) : typeof value === "string" && value.trim() ? icon(value, { size: 12 }) : null;
|
|
8735
|
+
if (!visual) return null;
|
|
8736
|
+
return h("span", {
|
|
8737
|
+
class: "zx-tag-picker__visual",
|
|
8738
|
+
style: this._colorOf(item) ? { "--zx-tag-color": this._colorOf(item) } : null,
|
|
8739
|
+
ariaHidden: "true"
|
|
8740
|
+
}, visual);
|
|
8741
|
+
}
|
|
8742
|
+
/** @param {unknown} item @returns {string|null} */
|
|
8743
|
+
_colorOf(item) {
|
|
8744
|
+
const value = this._readItem(item, this.options.colorKey);
|
|
8745
|
+
return isCssColor(value) ? value : null;
|
|
8746
|
+
}
|
|
8747
|
+
/** @param {unknown} item @param {unknown} reader @returns {unknown} */
|
|
8748
|
+
_readItem(item, reader) {
|
|
8749
|
+
if (typeof reader === "function") return reader(
|
|
8750
|
+
/** @type {TagItem} */
|
|
8751
|
+
item
|
|
8752
|
+
);
|
|
8753
|
+
if (item !== null && typeof item === "object" && typeof reader === "string") return item[reader];
|
|
8754
|
+
return null;
|
|
8755
|
+
}
|
|
8393
8756
|
/** @returns {void} */
|
|
8394
8757
|
_emitChange() {
|
|
8395
8758
|
this.emit("change", { values: this.getValues(), items: this.getItems() });
|
|
@@ -9274,38 +9637,38 @@ var Http = class {
|
|
|
9274
9637
|
try {
|
|
9275
9638
|
json2 = JSON.parse(text4);
|
|
9276
9639
|
} catch (cause) {
|
|
9277
|
-
const
|
|
9640
|
+
const error2 = (
|
|
9278
9641
|
/** @type {Error & {response?: Response}} */
|
|
9279
9642
|
new Error("Invalid JSON response", { cause })
|
|
9280
9643
|
);
|
|
9281
|
-
|
|
9282
|
-
throw
|
|
9644
|
+
error2.response = response;
|
|
9645
|
+
throw error2;
|
|
9283
9646
|
}
|
|
9284
9647
|
}
|
|
9285
9648
|
if (!response.ok) {
|
|
9286
9649
|
try {
|
|
9287
9650
|
parseResult(json2);
|
|
9288
|
-
} catch (
|
|
9289
|
-
|
|
9290
|
-
throw
|
|
9651
|
+
} catch (error3) {
|
|
9652
|
+
error3.response = response;
|
|
9653
|
+
throw error3;
|
|
9291
9654
|
}
|
|
9292
|
-
const
|
|
9655
|
+
const error2 = (
|
|
9293
9656
|
/** @type {Error & {response?: Response}} */
|
|
9294
9657
|
new Error(`HTTP ${response.status} ${response.statusText}`.trim())
|
|
9295
9658
|
);
|
|
9296
|
-
|
|
9297
|
-
throw
|
|
9659
|
+
error2.response = response;
|
|
9660
|
+
throw error2;
|
|
9298
9661
|
}
|
|
9299
9662
|
return parseResult(json2);
|
|
9300
9663
|
} catch (caught) {
|
|
9301
|
-
const
|
|
9664
|
+
const error2 = caught instanceof Error ? caught : new Error(String(caught));
|
|
9302
9665
|
if (this.onError) {
|
|
9303
9666
|
try {
|
|
9304
|
-
this.onError(
|
|
9667
|
+
this.onError(error2, { path, options });
|
|
9305
9668
|
} catch {
|
|
9306
9669
|
}
|
|
9307
9670
|
}
|
|
9308
|
-
throw
|
|
9671
|
+
throw error2;
|
|
9309
9672
|
} finally {
|
|
9310
9673
|
if (timeoutId !== null) clearTimeout(timeoutId);
|
|
9311
9674
|
}
|
|
@@ -10022,9 +10385,9 @@ var Modal = class extends Component {
|
|
|
10022
10385
|
this.el.returnValue = "";
|
|
10023
10386
|
try {
|
|
10024
10387
|
this._show();
|
|
10025
|
-
} catch (
|
|
10388
|
+
} catch (error2) {
|
|
10026
10389
|
delete this.el.dataset.zxOverlayOrder;
|
|
10027
|
-
throw
|
|
10390
|
+
throw error2;
|
|
10028
10391
|
}
|
|
10029
10392
|
this.el.dataset.state = "open";
|
|
10030
10393
|
this.emit("open");
|
|
@@ -10507,9 +10870,9 @@ var Groupbox = class extends Component {
|
|
|
10507
10870
|
}
|
|
10508
10871
|
try {
|
|
10509
10872
|
super(normalizedTarget, options);
|
|
10510
|
-
} catch (
|
|
10873
|
+
} catch (error2) {
|
|
10511
10874
|
if (adoption) restoreAdoption(adoption);
|
|
10512
|
-
throw
|
|
10875
|
+
throw error2;
|
|
10513
10876
|
}
|
|
10514
10877
|
this._adoption = adoption;
|
|
10515
10878
|
}
|
|
@@ -11237,7 +11600,11 @@ var Table = class extends Component {
|
|
|
11237
11600
|
rowClass: null,
|
|
11238
11601
|
zebra: true,
|
|
11239
11602
|
editMode: false,
|
|
11240
|
-
|
|
11603
|
+
editTrigger: "double",
|
|
11604
|
+
hierarchy: false,
|
|
11605
|
+
rowReorder: false,
|
|
11606
|
+
columnVisibility: false,
|
|
11607
|
+
hiddenColumns: []
|
|
11241
11608
|
};
|
|
11242
11609
|
/**
|
|
11243
11610
|
* Creates a table around a target or creates a new root when target is null.
|
|
@@ -11249,12 +11616,14 @@ var Table = class extends Component {
|
|
|
11249
11616
|
}
|
|
11250
11617
|
/** @returns {HTMLElement} Table root. */
|
|
11251
11618
|
render() {
|
|
11619
|
+
const created = this.el === null;
|
|
11252
11620
|
const root = (
|
|
11253
11621
|
/** @type {HTMLElement} */
|
|
11254
11622
|
this.el ?? h("div")
|
|
11255
11623
|
);
|
|
11256
11624
|
this.el = root;
|
|
11257
|
-
this.
|
|
11625
|
+
this._createdRoot = created;
|
|
11626
|
+
this._original = created ? null : snapshotTarget(root);
|
|
11258
11627
|
this._columns = Array.isArray(this.options.columns) ? this.options.columns.map((column) => ({ ...column })) : [];
|
|
11259
11628
|
this._data = Array.isArray(this.options.data) ? [...this.options.data] : [];
|
|
11260
11629
|
this._selected = /* @__PURE__ */ new Set();
|
|
@@ -11264,6 +11633,7 @@ var Table = class extends Component {
|
|
|
11264
11633
|
this._rowElements = /* @__PURE__ */ new Map();
|
|
11265
11634
|
this._restored = false;
|
|
11266
11635
|
this._editMode = normalizeEditMode(this.options.editMode);
|
|
11636
|
+
this._editTrigger = this.options.editTrigger === "single" ? "single" : "double";
|
|
11267
11637
|
this._edit = null;
|
|
11268
11638
|
this._editMessage = null;
|
|
11269
11639
|
this._stacked = false;
|
|
@@ -11275,6 +11645,10 @@ var Table = class extends Component {
|
|
|
11275
11645
|
this._rovingCell = null;
|
|
11276
11646
|
this._rovingMatched = false;
|
|
11277
11647
|
this._hierarchy = normalizeHierarchy(this.options.hierarchy, this._columns);
|
|
11648
|
+
this._hiddenColumns = new Set((Array.isArray(this.options.hiddenColumns) ? this.options.hiddenColumns : []).map(String).filter((id) => this._columns.some((column) => column.id === id)));
|
|
11649
|
+
if (this._hiddenColumns.size >= this._columns.length && this._columns[0]) this._hiddenColumns.delete(this._columns[0].id);
|
|
11650
|
+
this._dragId = null;
|
|
11651
|
+
this._grabbedId = null;
|
|
11278
11652
|
this._expanded = initialExpanded(this._hierarchy?.expanded);
|
|
11279
11653
|
if (this._sort && this.options.sortMode === "local") this._sortData();
|
|
11280
11654
|
if (this._hierarchy?.expanded === true) {
|
|
@@ -11296,11 +11670,17 @@ var Table = class extends Component {
|
|
|
11296
11670
|
zebra: String(Boolean(this.options.zebra))
|
|
11297
11671
|
}
|
|
11298
11672
|
}, this._table);
|
|
11673
|
+
this._rowMoveMessage = this.options.rowReorder ? h("div", {
|
|
11674
|
+
class: "zx-table__reorder-status",
|
|
11675
|
+
role: "status",
|
|
11676
|
+
ariaLive: "polite"
|
|
11677
|
+
}) : null;
|
|
11299
11678
|
if (this.options.height != null) {
|
|
11300
11679
|
this._scroll.style.blockSize = typeof this.options.height === "number" ? `${this.options.height}px` : String(this.options.height);
|
|
11301
11680
|
}
|
|
11681
|
+
this._columnControls = this.options.columnVisibility ? this._createColumnControls() : null;
|
|
11302
11682
|
if (this._editMode === false) {
|
|
11303
|
-
root.replaceChildren(this._scroll);
|
|
11683
|
+
root.replaceChildren(...[this._columnControls, this._scroll, this._rowMoveMessage].filter(Boolean));
|
|
11304
11684
|
} else {
|
|
11305
11685
|
root.dataset.editMode = this._editMode;
|
|
11306
11686
|
this._editMessage = h("div", {
|
|
@@ -11309,7 +11689,7 @@ var Table = class extends Component {
|
|
|
11309
11689
|
role: "status",
|
|
11310
11690
|
ariaLive: "polite"
|
|
11311
11691
|
});
|
|
11312
|
-
root.replaceChildren(this._scroll, this._editMessage);
|
|
11692
|
+
root.replaceChildren(...[this._columnControls, this._scroll, this._editMessage, this._rowMoveMessage].filter(Boolean));
|
|
11313
11693
|
}
|
|
11314
11694
|
this._renderColumns();
|
|
11315
11695
|
this._renderHeader();
|
|
@@ -11317,6 +11697,34 @@ var Table = class extends Component {
|
|
|
11317
11697
|
this._watchWidth();
|
|
11318
11698
|
this.listen(this._tbody, "click", (event) => this._handleBodyClick(event));
|
|
11319
11699
|
this.listen(this._tbody, "dblclick", (event) => this._handleBodyDoubleClick(event));
|
|
11700
|
+
if (this.options.rowReorder) {
|
|
11701
|
+
this.listen(this._tbody, "dragstart", (event) => this._handleRowDragStart(
|
|
11702
|
+
/** @type {DragEvent} */
|
|
11703
|
+
event
|
|
11704
|
+
));
|
|
11705
|
+
this.listen(this._tbody, "dragover", (event) => this._handleRowDragOver(
|
|
11706
|
+
/** @type {DragEvent} */
|
|
11707
|
+
event
|
|
11708
|
+
));
|
|
11709
|
+
this.listen(this._tbody, "drop", (event) => this._handleRowDrop(
|
|
11710
|
+
/** @type {DragEvent} */
|
|
11711
|
+
event
|
|
11712
|
+
));
|
|
11713
|
+
this.listen(this._tbody, "dragend", () => this._clearRowDrag());
|
|
11714
|
+
this.listen(this._tbody, "keydown", (event) => this._handleRowHandleKeydown(
|
|
11715
|
+
/** @type {KeyboardEvent} */
|
|
11716
|
+
event
|
|
11717
|
+
));
|
|
11718
|
+
}
|
|
11719
|
+
if (this._columnControls) {
|
|
11720
|
+
this.listen(this._columnControls, "change", (event) => {
|
|
11721
|
+
const input = (
|
|
11722
|
+
/** @type {HTMLInputElement|null} */
|
|
11723
|
+
event.target
|
|
11724
|
+
);
|
|
11725
|
+
if (input?.classList.contains("zx-table__column-toggle")) this.setColumnVisible(input.value, input.checked);
|
|
11726
|
+
});
|
|
11727
|
+
}
|
|
11320
11728
|
if (this._editMode !== false) {
|
|
11321
11729
|
this.listen(this._tbody, "keydown", (event) => this._handleEditKeydown(
|
|
11322
11730
|
/** @type {KeyboardEvent} */
|
|
@@ -11329,8 +11737,8 @@ var Table = class extends Component {
|
|
|
11329
11737
|
}
|
|
11330
11738
|
/**
|
|
11331
11739
|
* Toggles a loading state. While loading, the body is dimmed, an indeterminate progress bar is
|
|
11332
|
-
* shown, and `aria-busy` is set — a lightweight
|
|
11333
|
-
*
|
|
11740
|
+
* shown, and `aria-busy` is set — a lightweight enterprise-table busy and skeleton pattern.
|
|
11741
|
+
* Call `setLoading(false)` (or `setData`) when done.
|
|
11334
11742
|
* @param {boolean} [loading=true] Whether the table is loading.
|
|
11335
11743
|
* @returns {this}
|
|
11336
11744
|
*/
|
|
@@ -11432,6 +11840,81 @@ var Table = class extends Component {
|
|
|
11432
11840
|
getData() {
|
|
11433
11841
|
return [...this._data];
|
|
11434
11842
|
}
|
|
11843
|
+
/** Returns visible column IDs in caller order. @returns {string[]} */
|
|
11844
|
+
getVisibleColumns() {
|
|
11845
|
+
return this._visibleColumns().map((column) => column.id);
|
|
11846
|
+
}
|
|
11847
|
+
/** Returns hidden column IDs in caller order. @returns {string[]} */
|
|
11848
|
+
getHiddenColumns() {
|
|
11849
|
+
return this._columns.filter((column) => this._hiddenColumns.has(column.id)).map((column) => column.id);
|
|
11850
|
+
}
|
|
11851
|
+
/**
|
|
11852
|
+
* Shows or hides one column. The final visible column cannot be hidden.
|
|
11853
|
+
* @param {string} id Column ID.
|
|
11854
|
+
* @param {boolean} [visible=true] Desired visibility.
|
|
11855
|
+
* @param {{silent?:boolean}} [options={}] Event behavior.
|
|
11856
|
+
* @returns {this}
|
|
11857
|
+
*/
|
|
11858
|
+
setColumnVisible(id, visible = true, options = {}) {
|
|
11859
|
+
const column = this._columns.find((candidate) => candidate.id === id);
|
|
11860
|
+
if (!column) throw new RangeError(`Unknown table column: ${id}`);
|
|
11861
|
+
const active = (
|
|
11862
|
+
/** @type {HTMLElement|null} */
|
|
11863
|
+
document.activeElement
|
|
11864
|
+
);
|
|
11865
|
+
const restoreFocus = active?.classList.contains("zx-table__column-toggle") && this._columnList?.contains(active) ? (
|
|
11866
|
+
/** @type {HTMLInputElement} */
|
|
11867
|
+
active.value
|
|
11868
|
+
) : null;
|
|
11869
|
+
if (!visible && !this._hiddenColumns.has(id) && this._visibleColumns().length <= 1) {
|
|
11870
|
+
this._renderColumnControls(restoreFocus);
|
|
11871
|
+
return this;
|
|
11872
|
+
}
|
|
11873
|
+
const changed = visible ? this._hiddenColumns.delete(id) : !this._hiddenColumns.has(id);
|
|
11874
|
+
if (!visible && changed) this._hiddenColumns.add(id);
|
|
11875
|
+
if (!changed) return this;
|
|
11876
|
+
this._renderColumns();
|
|
11877
|
+
this._renderHeader();
|
|
11878
|
+
this._renderBody();
|
|
11879
|
+
this._renderColumnControls(restoreFocus);
|
|
11880
|
+
if (!options.silent) this.emit("columnvisibilitychange", {
|
|
11881
|
+
visible: this.getVisibleColumns(),
|
|
11882
|
+
hidden: this.getHiddenColumns()
|
|
11883
|
+
});
|
|
11884
|
+
return this;
|
|
11885
|
+
}
|
|
11886
|
+
/** Toggles one column. @param {string} id @param {{silent?:boolean}} [options={}] @returns {this} */
|
|
11887
|
+
toggleColumn(id, options = {}) {
|
|
11888
|
+
return this.setColumnVisible(id, this._hiddenColumns.has(id), options);
|
|
11889
|
+
}
|
|
11890
|
+
/**
|
|
11891
|
+
* Reorders one row relative to another and emits a cancelable `rowmove` before committing.
|
|
11892
|
+
* Hierarchical rows may only move among siblings; an active local sort disables manual order.
|
|
11893
|
+
* @param {unknown} id Moving row ID.
|
|
11894
|
+
* @param {unknown} targetId Target row ID.
|
|
11895
|
+
* @param {'before'|'after'} [position='before'] Target edge.
|
|
11896
|
+
* @returns {this}
|
|
11897
|
+
*/
|
|
11898
|
+
moveRow(id, targetId, position2 = "before") {
|
|
11899
|
+
if (!this.options.rowReorder || this._sort && this.options.sortMode === "local") return this;
|
|
11900
|
+
const row2 = this.getRow(id);
|
|
11901
|
+
const target = this.getRow(targetId);
|
|
11902
|
+
if (!row2 || !target || Object.is(id, targetId)) return this;
|
|
11903
|
+
const next = reorderTableRows(this._data, this.options.rowId, id, targetId, position2, this._hierarchy?.parentId);
|
|
11904
|
+
if (!next) return this;
|
|
11905
|
+
const event = this.emit("rowmove", { row: row2, id, target, targetId, position: position2 });
|
|
11906
|
+
if (event.defaultPrevented) return this;
|
|
11907
|
+
this._data = next;
|
|
11908
|
+
this._renderBody();
|
|
11909
|
+
if (this._rowMoveMessage) {
|
|
11910
|
+
const siblings = this._siblingIds(id);
|
|
11911
|
+
const index = siblings.findIndex((candidate) => Object.is(candidate, id));
|
|
11912
|
+
this._rowMoveMessage.textContent = `Moved row to position ${index + 1} of ${siblings.length}.`;
|
|
11913
|
+
}
|
|
11914
|
+
this._emitDataChange();
|
|
11915
|
+
queueMicrotask(() => this._rowHandle(id)?.focus());
|
|
11916
|
+
return this;
|
|
11917
|
+
}
|
|
11435
11918
|
/**
|
|
11436
11919
|
* Removes all rows.
|
|
11437
11920
|
* @returns {this}
|
|
@@ -11600,14 +12083,17 @@ var Table = class extends Component {
|
|
|
11600
12083
|
*/
|
|
11601
12084
|
startEdit(rowId, columnId) {
|
|
11602
12085
|
if (this._editMode === false) return this;
|
|
11603
|
-
const
|
|
11604
|
-
if (!
|
|
12086
|
+
const requested = this._columns.find((candidate) => candidate.id === columnId);
|
|
12087
|
+
if (!requested) return this;
|
|
11605
12088
|
const current = this._data.find((candidate) => Object.is(this._idFor(candidate), rowId));
|
|
11606
|
-
if (current === void 0
|
|
12089
|
+
if (current === void 0) return this;
|
|
12090
|
+
const column = this._editMode === "row" && this._hiddenColumns.has(requested.id) ? this._visibleColumns().find((candidate) => resolveEditable(candidate, current) !== false) ?? null : requested;
|
|
12091
|
+
if (!column || resolveEditable(column, current) === false) return this;
|
|
12092
|
+
const activeColumnId = column.id;
|
|
11607
12093
|
if (this._edit) {
|
|
11608
|
-
if (Object.is(this._edit.id, rowId) && this._edit.cells.has(
|
|
11609
|
-
this._edit.columnId =
|
|
11610
|
-
this._focusEditor(
|
|
12094
|
+
if (Object.is(this._edit.id, rowId) && this._edit.cells.has(activeColumnId)) {
|
|
12095
|
+
this._edit.columnId = activeColumnId;
|
|
12096
|
+
this._focusEditor(activeColumnId);
|
|
11611
12097
|
return this;
|
|
11612
12098
|
}
|
|
11613
12099
|
this.commitEdit();
|
|
@@ -11618,7 +12104,7 @@ var Table = class extends Component {
|
|
|
11618
12104
|
const row2 = this._data[index];
|
|
11619
12105
|
const tr = this._rowElements.get(rowId)?.[0];
|
|
11620
12106
|
if (!tr) return this;
|
|
11621
|
-
const targets = this._editMode === "row" ? this.
|
|
12107
|
+
const targets = this._editMode === "row" ? this._visibleColumns().filter((candidate) => resolveEditable(candidate, row2) !== false) : [column];
|
|
11622
12108
|
const cells = /* @__PURE__ */ new Map();
|
|
11623
12109
|
for (const target of targets) {
|
|
11624
12110
|
const td = cellIn(tr, target.id);
|
|
@@ -11627,10 +12113,10 @@ var Table = class extends Component {
|
|
|
11627
12113
|
if (entry) cells.set(target.id, entry);
|
|
11628
12114
|
}
|
|
11629
12115
|
if (cells.size === 0) return this;
|
|
11630
|
-
this._edit = { id: rowId, columnId, row: row2, cells, tr };
|
|
12116
|
+
this._edit = { id: rowId, columnId: activeColumnId, row: row2, cells, tr };
|
|
11631
12117
|
tr.dataset.editing = "true";
|
|
11632
|
-
this.emit("editstart", { row: row2, id: rowId, column, columnId, value: row2?.[
|
|
11633
|
-
this._focusEditor(
|
|
12118
|
+
this.emit("editstart", { row: row2, id: rowId, column, columnId: activeColumnId, value: row2?.[activeColumnId] });
|
|
12119
|
+
this._focusEditor(activeColumnId);
|
|
11634
12120
|
return this;
|
|
11635
12121
|
}
|
|
11636
12122
|
/**
|
|
@@ -11879,60 +12365,245 @@ var Table = class extends Component {
|
|
|
11879
12365
|
this._teardownEdit();
|
|
11880
12366
|
this._width?.destroy();
|
|
11881
12367
|
this._width = null;
|
|
11882
|
-
if (this.
|
|
11883
|
-
|
|
11884
|
-
this.el.replaceChildren(...this._originalChildren);
|
|
12368
|
+
if (!this._restored && !this._createdRoot) {
|
|
12369
|
+
restoreTarget(this.el, this._original);
|
|
11885
12370
|
this._restored = true;
|
|
11886
12371
|
}
|
|
11887
12372
|
super.destroy();
|
|
11888
12373
|
}
|
|
11889
|
-
/** @returns {
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
if (this.options.selectable === "multi") {
|
|
11893
|
-
fragment2.append(h("col", { class: "zx-table__selection-column" }));
|
|
11894
|
-
}
|
|
11895
|
-
const fractions = this._columns.map((column) => parseFraction(column.width));
|
|
11896
|
-
const total = fractions.reduce((sum, value) => sum + value, 0);
|
|
11897
|
-
this._table.dataset.layout = total > 0 ? "fixed" : "auto";
|
|
11898
|
-
this._columns.forEach((column, index) => {
|
|
11899
|
-
const col = h("col");
|
|
11900
|
-
if (fractions[index] > 0) col.style.width = `${fractions[index] / total * 100}%`;
|
|
11901
|
-
else if (column.width && column.width !== "auto") col.style.width = String(column.width);
|
|
11902
|
-
fragment2.append(col);
|
|
11903
|
-
});
|
|
11904
|
-
this._colgroup.replaceChildren(fragment2);
|
|
12374
|
+
/** @returns {TableColumn[]} */
|
|
12375
|
+
_visibleColumns() {
|
|
12376
|
+
return this._columns.filter((column) => !this._hiddenColumns.has(column.id));
|
|
11905
12377
|
}
|
|
11906
|
-
/** @returns {
|
|
11907
|
-
|
|
11908
|
-
const
|
|
11909
|
-
|
|
11910
|
-
|
|
11911
|
-
|
|
11912
|
-
|
|
11913
|
-
|
|
11914
|
-
|
|
11915
|
-
|
|
11916
|
-
|
|
11917
|
-
|
|
11918
|
-
|
|
11919
|
-
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
|
|
11923
|
-
|
|
11924
|
-
|
|
11925
|
-
|
|
11926
|
-
|
|
11927
|
-
|
|
11928
|
-
|
|
11929
|
-
|
|
12378
|
+
/** @returns {HTMLElement} */
|
|
12379
|
+
_createColumnControls() {
|
|
12380
|
+
const list = h("div", { class: "zx-table__column-list" });
|
|
12381
|
+
this._columnList = list;
|
|
12382
|
+
const details = h(
|
|
12383
|
+
"details",
|
|
12384
|
+
{ class: "zx-table__column-controls" },
|
|
12385
|
+
h("summary", { class: "zx-button zx-button--sm" }, icon("fields", { size: 13 }), "Columns"),
|
|
12386
|
+
list
|
|
12387
|
+
);
|
|
12388
|
+
this._renderColumnControls();
|
|
12389
|
+
return details;
|
|
12390
|
+
}
|
|
12391
|
+
/** @param {string|null} [focusColumn=null] Column checkbox that keeps focus. @returns {void} */
|
|
12392
|
+
_renderColumnControls(focusColumn = null) {
|
|
12393
|
+
if (!this._columnList) return;
|
|
12394
|
+
const visible = this._visibleColumns();
|
|
12395
|
+
const current = [...this._columnList.querySelectorAll(".zx-table__column-toggle")];
|
|
12396
|
+
const reusable = current.length === this._columns.length && current.every((control, index) => (
|
|
12397
|
+
/** @type {HTMLInputElement} */
|
|
12398
|
+
control.value === this._columns[index].id
|
|
12399
|
+
));
|
|
12400
|
+
if (reusable) {
|
|
12401
|
+
current.forEach((control, index) => {
|
|
12402
|
+
const input = (
|
|
12403
|
+
/** @type {HTMLInputElement} */
|
|
12404
|
+
control
|
|
12405
|
+
);
|
|
12406
|
+
const checked = !this._hiddenColumns.has(this._columns[index].id);
|
|
12407
|
+
input.checked = checked;
|
|
12408
|
+
input.disabled = checked && visible.length === 1;
|
|
11930
12409
|
});
|
|
11931
|
-
|
|
11932
|
-
|
|
12410
|
+
return;
|
|
12411
|
+
}
|
|
12412
|
+
this._columnList.replaceChildren(...this._columns.map((column) => {
|
|
12413
|
+
const checked = !this._hiddenColumns.has(column.id);
|
|
12414
|
+
return h(
|
|
12415
|
+
"label",
|
|
12416
|
+
{ class: "zx-table__column-option" },
|
|
12417
|
+
h("input", {
|
|
12418
|
+
class: "zx-table__column-toggle",
|
|
12419
|
+
type: "checkbox",
|
|
12420
|
+
value: column.id,
|
|
12421
|
+
checked,
|
|
12422
|
+
disabled: checked && visible.length === 1
|
|
12423
|
+
}),
|
|
12424
|
+
h("span", {}, column.label)
|
|
12425
|
+
);
|
|
12426
|
+
}));
|
|
12427
|
+
if (focusColumn !== null) {
|
|
12428
|
+
const control = [...this._columnList.querySelectorAll(".zx-table__column-toggle")].find((candidate) => (
|
|
12429
|
+
/** @type {HTMLInputElement} */
|
|
12430
|
+
candidate.value === focusColumn
|
|
12431
|
+
));
|
|
12432
|
+
queueMicrotask(() => {
|
|
12433
|
+
if (control?.isConnected) control.focus();
|
|
12434
|
+
});
|
|
12435
|
+
}
|
|
12436
|
+
}
|
|
12437
|
+
/** @param {DragEvent} event @returns {void} */
|
|
12438
|
+
_handleRowDragStart(event) {
|
|
12439
|
+
const handle = (
|
|
12440
|
+
/** @type {Element|null} */
|
|
12441
|
+
event.target?.closest?.(".zx-table__row-handle")
|
|
12442
|
+
);
|
|
12443
|
+
const rowElement = handle?.closest("tr[data-row]");
|
|
12444
|
+
const meta = rowElement ? this._rowMeta.get(rowElement) : null;
|
|
12445
|
+
if (!handle || !meta || this._sort && this.options.sortMode === "local") {
|
|
12446
|
+
event.preventDefault();
|
|
12447
|
+
return;
|
|
12448
|
+
}
|
|
12449
|
+
this._dragId = meta.id;
|
|
12450
|
+
rowElement.dataset.dragging = "true";
|
|
12451
|
+
event.dataTransfer?.setData("text/plain", "zx-table-row");
|
|
12452
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = "move";
|
|
12453
|
+
}
|
|
12454
|
+
/** @param {DragEvent} event @returns {void} */
|
|
12455
|
+
_handleRowDragOver(event) {
|
|
12456
|
+
if (this._dragId == null) return;
|
|
12457
|
+
const rowElement = (
|
|
12458
|
+
/** @type {Element|null} */
|
|
12459
|
+
event.target?.closest?.("tr[data-row]")
|
|
12460
|
+
);
|
|
12461
|
+
const meta = rowElement ? this._rowMeta.get(rowElement) : null;
|
|
12462
|
+
if (!meta || Object.is(meta.id, this._dragId) || !this._canMoveTogether(this._dragId, meta.id)) return;
|
|
12463
|
+
event.preventDefault();
|
|
12464
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
|
|
12465
|
+
this._clearDropMarkers();
|
|
12466
|
+
const rectangle = rowElement.getBoundingClientRect();
|
|
12467
|
+
rowElement.dataset.drop = event.clientY >= rectangle.top + rectangle.height / 2 ? "after" : "before";
|
|
12468
|
+
}
|
|
12469
|
+
/** @param {DragEvent} event @returns {void} */
|
|
12470
|
+
_handleRowDrop(event) {
|
|
12471
|
+
if (this._dragId == null) return;
|
|
12472
|
+
const rowElement = (
|
|
12473
|
+
/** @type {Element|null} */
|
|
12474
|
+
event.target?.closest?.("tr[data-row]")
|
|
12475
|
+
);
|
|
12476
|
+
const meta = rowElement ? this._rowMeta.get(rowElement) : null;
|
|
12477
|
+
const position2 = rowElement?.dataset.drop === "after" ? "after" : "before";
|
|
12478
|
+
if (meta && this._canMoveTogether(this._dragId, meta.id)) {
|
|
12479
|
+
event.preventDefault();
|
|
12480
|
+
this.moveRow(this._dragId, meta.id, position2);
|
|
12481
|
+
}
|
|
12482
|
+
this._clearRowDrag();
|
|
12483
|
+
}
|
|
12484
|
+
/** @param {KeyboardEvent} event @returns {void} */
|
|
12485
|
+
_handleRowHandleKeydown(event) {
|
|
12486
|
+
const handle = (
|
|
12487
|
+
/** @type {Element|null} */
|
|
12488
|
+
event.target?.closest?.(".zx-table__row-handle")
|
|
12489
|
+
);
|
|
12490
|
+
const rowElement = handle?.closest("tr[data-row]");
|
|
12491
|
+
const meta = rowElement ? this._rowMeta.get(rowElement) : null;
|
|
12492
|
+
if (!handle || !meta) return;
|
|
12493
|
+
if (event.key === " " || event.key === "Enter") {
|
|
12494
|
+
event.preventDefault();
|
|
12495
|
+
this._grabbedId = Object.is(this._grabbedId, meta.id) ? null : meta.id;
|
|
12496
|
+
handle.setAttribute("aria-pressed", String(this._grabbedId != null));
|
|
12497
|
+
const status = this._rowMoveMessage ?? this._editMessage;
|
|
12498
|
+
if (status) status.textContent = this._grabbedId == null ? "Row dropped" : "Row grabbed. Use the arrow keys to move it.";
|
|
12499
|
+
return;
|
|
12500
|
+
}
|
|
12501
|
+
if (event.key === "Escape" && this._grabbedId != null) {
|
|
12502
|
+
event.preventDefault();
|
|
12503
|
+
this._grabbedId = null;
|
|
12504
|
+
handle.setAttribute("aria-pressed", "false");
|
|
12505
|
+
return;
|
|
12506
|
+
}
|
|
12507
|
+
if (!Object.is(this._grabbedId, meta.id) || !["ArrowUp", "ArrowDown"].includes(event.key)) return;
|
|
12508
|
+
event.preventDefault();
|
|
12509
|
+
const siblings = this._siblingIds(meta.id);
|
|
12510
|
+
const index = siblings.findIndex((id) => Object.is(id, meta.id));
|
|
12511
|
+
const targetId = siblings[index + (event.key === "ArrowUp" ? -1 : 1)];
|
|
12512
|
+
if (targetId !== void 0) this.moveRow(meta.id, targetId, event.key === "ArrowUp" ? "before" : "after");
|
|
12513
|
+
}
|
|
12514
|
+
/** @param {unknown} id @returns {unknown[]} */
|
|
12515
|
+
_siblingIds(id) {
|
|
12516
|
+
const row2 = this.getRow(id);
|
|
12517
|
+
if (!row2) return [];
|
|
12518
|
+
const parent = this._hierarchy ? readRowValue(this._hierarchy.parentId, row2) : null;
|
|
12519
|
+
return this._data.filter((candidate) => !this._hierarchy || sameHierarchyParent(readRowValue(this._hierarchy.parentId, candidate), parent)).map((candidate) => this._idFor(candidate));
|
|
12520
|
+
}
|
|
12521
|
+
/** @param {unknown} left @param {unknown} right @returns {boolean} */
|
|
12522
|
+
_canMoveTogether(left, right) {
|
|
12523
|
+
if (!this._hierarchy) return true;
|
|
12524
|
+
const a = this.getRow(left);
|
|
12525
|
+
const b = this.getRow(right);
|
|
12526
|
+
return Boolean(a && b && sameHierarchyParent(
|
|
12527
|
+
readRowValue(this._hierarchy.parentId, a),
|
|
12528
|
+
readRowValue(this._hierarchy.parentId, b)
|
|
12529
|
+
));
|
|
12530
|
+
}
|
|
12531
|
+
/** @param {unknown} id @returns {HTMLElement|null} */
|
|
12532
|
+
_rowHandle(id) {
|
|
12533
|
+
return (
|
|
12534
|
+
/** @type {HTMLElement|null} */
|
|
12535
|
+
this._rowElements.get(id)?.[0]?.querySelector(".zx-table__row-handle") ?? null
|
|
12536
|
+
);
|
|
12537
|
+
}
|
|
12538
|
+
/** @returns {void} */
|
|
12539
|
+
_clearDropMarkers() {
|
|
12540
|
+
for (const row2 of this._tbody.querySelectorAll("tr[data-drop]")) delete row2.dataset.drop;
|
|
12541
|
+
}
|
|
12542
|
+
/** @returns {void} */
|
|
12543
|
+
_clearRowDrag() {
|
|
12544
|
+
for (const row2 of this._tbody.querySelectorAll("tr[data-dragging]")) delete row2.dataset.dragging;
|
|
12545
|
+
this._clearDropMarkers();
|
|
12546
|
+
this._dragId = null;
|
|
12547
|
+
}
|
|
12548
|
+
/** @returns {string|null} */
|
|
12549
|
+
_hierarchyColumn() {
|
|
12550
|
+
if (!this._hierarchy) return null;
|
|
12551
|
+
return this._visibleColumns().some((column) => column.id === this._hierarchy.column) ? this._hierarchy.column : this._visibleColumns()[0]?.id ?? null;
|
|
12552
|
+
}
|
|
12553
|
+
/** @returns {void} */
|
|
12554
|
+
_renderColumns() {
|
|
12555
|
+
const fragment2 = document.createDocumentFragment();
|
|
12556
|
+
if (this.options.rowReorder) fragment2.append(h("col", { class: "zx-table__reorder-column" }));
|
|
12557
|
+
if (this.options.selectable === "multi") {
|
|
12558
|
+
fragment2.append(h("col", { class: "zx-table__selection-column" }));
|
|
12559
|
+
}
|
|
12560
|
+
const columns = this._visibleColumns();
|
|
12561
|
+
const fractions = columns.map((column) => parseFraction(column.width));
|
|
12562
|
+
const total = fractions.reduce((sum, value) => sum + value, 0);
|
|
12563
|
+
this._table.dataset.layout = total > 0 ? "fixed" : "auto";
|
|
12564
|
+
columns.forEach((column, index) => {
|
|
12565
|
+
const col = h("col");
|
|
12566
|
+
if (fractions[index] > 0) col.style.width = `${fractions[index] / total * 100}%`;
|
|
12567
|
+
else if (column.width && column.width !== "auto") col.style.width = String(column.width);
|
|
12568
|
+
fragment2.append(col);
|
|
12569
|
+
});
|
|
12570
|
+
this._colgroup.replaceChildren(fragment2);
|
|
12571
|
+
}
|
|
12572
|
+
/** @returns {void} */
|
|
12573
|
+
_renderHeader() {
|
|
12574
|
+
const row2 = h("tr", { class: "zx-table__header-row" });
|
|
12575
|
+
if (this._growStep) row2.setAttribute("aria-rowindex", "1");
|
|
12576
|
+
if (this.options.rowReorder) row2.append(h("th", {
|
|
12577
|
+
class: "zx-table__reorder-header",
|
|
12578
|
+
scope: "col",
|
|
12579
|
+
ariaLabel: "Row order"
|
|
12580
|
+
}));
|
|
12581
|
+
if (this.options.selectable === "multi") {
|
|
12582
|
+
this._selectAll = h("input", {
|
|
12583
|
+
class: "zx-table__checkbox zx-table__select-all",
|
|
12584
|
+
type: "checkbox",
|
|
12585
|
+
ariaLabel: "Select all rows"
|
|
12586
|
+
});
|
|
12587
|
+
const selectionHeader = h("th", {
|
|
12588
|
+
class: "zx-table__selection-header",
|
|
12589
|
+
scope: "col"
|
|
12590
|
+
}, this._selectAll);
|
|
12591
|
+
row2.append(selectionHeader);
|
|
12592
|
+
this.listen(this._selectAll, "click", () => {
|
|
12593
|
+
if (this._selectAll.checked) {
|
|
12594
|
+
for (const dataRow of this._visibleData()) this._selected.add(this._idFor(dataRow));
|
|
12595
|
+
} else {
|
|
12596
|
+
for (const dataRow of this._visibleData()) this._selected.delete(this._idFor(dataRow));
|
|
12597
|
+
}
|
|
12598
|
+
this._selectionAnchorId = null;
|
|
12599
|
+
this._syncSelection();
|
|
12600
|
+
this._emitSelectionChange();
|
|
12601
|
+
});
|
|
12602
|
+
} else {
|
|
12603
|
+
this._selectAll = null;
|
|
11933
12604
|
}
|
|
11934
12605
|
this._headers = /* @__PURE__ */ new Map();
|
|
11935
|
-
for (const column of this.
|
|
12606
|
+
for (const column of this._visibleColumns()) {
|
|
11936
12607
|
const th = h("th", { scope: "col" });
|
|
11937
12608
|
const align = column.align ?? (isNumericColumn(column) ? "end" : null);
|
|
11938
12609
|
if (align) th.dataset.align = align;
|
|
@@ -11972,7 +12643,7 @@ var Table = class extends Component {
|
|
|
11972
12643
|
{ class: "zx-table__empty-row" },
|
|
11973
12644
|
h("td", {
|
|
11974
12645
|
class: "zx-table__empty",
|
|
11975
|
-
colspan: this.
|
|
12646
|
+
colspan: this._visibleColumns().length + (this.options.selectable === "multi" ? 1 : 0) + (this.options.rowReorder ? 1 : 0)
|
|
11976
12647
|
}, this._emptyContent())
|
|
11977
12648
|
));
|
|
11978
12649
|
} else {
|
|
@@ -11981,6 +12652,7 @@ var Table = class extends Component {
|
|
|
11981
12652
|
});
|
|
11982
12653
|
}
|
|
11983
12654
|
this._tbody.replaceChildren(fragment2);
|
|
12655
|
+
this._table.setAttribute("aria-colcount", String(this._visibleColumns().length + (this.options.selectable === "multi" ? 1 : 0) + (this.options.rowReorder ? 1 : 0)));
|
|
11984
12656
|
if (this._growStep) this._table.setAttribute("aria-rowcount", String(projected.length + 1));
|
|
11985
12657
|
this._syncSelectAll();
|
|
11986
12658
|
this._syncGrowing();
|
|
@@ -12024,6 +12696,19 @@ var Table = class extends Component {
|
|
|
12024
12696
|
const elements = this._rowElements.get(id) ?? [];
|
|
12025
12697
|
elements.push(tr);
|
|
12026
12698
|
this._rowElements.set(id, elements);
|
|
12699
|
+
if (this.options.rowReorder) {
|
|
12700
|
+
const handle = h("button", {
|
|
12701
|
+
class: "zx-table__row-handle",
|
|
12702
|
+
type: "button",
|
|
12703
|
+
draggable: true,
|
|
12704
|
+
dataset: { rowHandle: "" },
|
|
12705
|
+
ariaLabel: `Move row ${index + 1}`,
|
|
12706
|
+
ariaPressed: String(Object.is(this._grabbedId, id)),
|
|
12707
|
+
disabled: Boolean(this._sort && this.options.sortMode === "local"),
|
|
12708
|
+
title: this._sort && this.options.sortMode === "local" ? "Clear sorting to reorder rows" : null
|
|
12709
|
+
}, icon("drag", { size: 14 }));
|
|
12710
|
+
tr.append(h("td", { class: "zx-table__reorder-cell" }, handle));
|
|
12711
|
+
}
|
|
12027
12712
|
if (this.options.selectable === "multi") {
|
|
12028
12713
|
const checkbox = h("input", {
|
|
12029
12714
|
class: "zx-table__checkbox zx-table__row-checkbox",
|
|
@@ -12033,7 +12718,7 @@ var Table = class extends Component {
|
|
|
12033
12718
|
});
|
|
12034
12719
|
tr.append(h("td", { class: "zx-table__selection-cell" }, checkbox));
|
|
12035
12720
|
}
|
|
12036
|
-
this.
|
|
12721
|
+
this._visibleColumns().forEach((column) => {
|
|
12037
12722
|
const cell = (
|
|
12038
12723
|
/** @type {HTMLTableCellElement} */
|
|
12039
12724
|
h("td")
|
|
@@ -12069,7 +12754,7 @@ var Table = class extends Component {
|
|
|
12069
12754
|
const content = h("span", { class: "zx-table__cell-content" });
|
|
12070
12755
|
if (value && typeof value === "object" && typeof value.nodeType === "number") content.append(value);
|
|
12071
12756
|
else if (value != null) content.append(document.createTextNode(String(value)));
|
|
12072
|
-
if (!this._hierarchy || column.id !== this.
|
|
12757
|
+
if (!this._hierarchy || column.id !== this._hierarchyColumn() || !tree) {
|
|
12073
12758
|
cell.replaceChildren(...content.childNodes);
|
|
12074
12759
|
return;
|
|
12075
12760
|
}
|
|
@@ -12100,6 +12785,7 @@ var Table = class extends Component {
|
|
|
12100
12785
|
if (!rowElement || !this._tbody.contains(rowElement)) return;
|
|
12101
12786
|
const meta = this._rowMeta.get(rowElement);
|
|
12102
12787
|
if (!meta) return;
|
|
12788
|
+
if (target.closest(".zx-table__row-handle")) return;
|
|
12103
12789
|
if (this._edit) {
|
|
12104
12790
|
this.commitEdit();
|
|
12105
12791
|
if (this._edit) return;
|
|
@@ -12119,6 +12805,14 @@ var Table = class extends Component {
|
|
|
12119
12805
|
);
|
|
12120
12806
|
return;
|
|
12121
12807
|
}
|
|
12808
|
+
if (isInteractiveCellContent(target)) return;
|
|
12809
|
+
if (this._editMode !== false && this._editTrigger === "single") {
|
|
12810
|
+
const cell = target?.closest('td[data-editable="true"]');
|
|
12811
|
+
if (cell && this._tbody.contains(cell)) {
|
|
12812
|
+
this.startEdit(meta.id, String(cell.dataset.column));
|
|
12813
|
+
return;
|
|
12814
|
+
}
|
|
12815
|
+
}
|
|
12122
12816
|
this.emit("rowclick", { ...meta, event });
|
|
12123
12817
|
if (this.options.selectable === "single" && !this._selected.has(meta.id)) {
|
|
12124
12818
|
this._selected = /* @__PURE__ */ new Set([meta.id]);
|
|
@@ -12139,7 +12833,8 @@ var Table = class extends Component {
|
|
|
12139
12833
|
if (!rowElement || !this._tbody.contains(rowElement)) return;
|
|
12140
12834
|
const meta = this._rowMeta.get(rowElement);
|
|
12141
12835
|
if (!meta) return;
|
|
12142
|
-
if (
|
|
12836
|
+
if (isInteractiveCellContent(target)) return;
|
|
12837
|
+
if (this._editMode !== false && this._editTrigger === "double") {
|
|
12143
12838
|
const cell = target?.closest('td[data-editable="true"]');
|
|
12144
12839
|
if (cell && this._tbody.contains(cell)) {
|
|
12145
12840
|
this.startEdit(meta.id, String(cell.dataset.column));
|
|
@@ -12441,13 +13136,14 @@ var Table = class extends Component {
|
|
|
12441
13136
|
* @returns {{id: unknown, columnId: string, index: number}|null} Next cell, or null at the end.
|
|
12442
13137
|
*/
|
|
12443
13138
|
_nextEditableCell(rowIndex, columnId, direction) {
|
|
12444
|
-
const
|
|
13139
|
+
const columns = this._visibleColumns();
|
|
13140
|
+
const columnCount = columns.length;
|
|
12445
13141
|
if (rowIndex < 0 || columnCount === 0) return null;
|
|
12446
13142
|
const visible = this._visibleData();
|
|
12447
13143
|
const startingId = this._idFor(this._data[rowIndex]);
|
|
12448
13144
|
let row2 = visible.findIndex((candidate) => Object.is(this._idFor(candidate), startingId));
|
|
12449
13145
|
if (row2 < 0) return null;
|
|
12450
|
-
let column =
|
|
13146
|
+
let column = columns.findIndex((candidate) => candidate.id === columnId);
|
|
12451
13147
|
const limit = visible.length * columnCount + columnCount;
|
|
12452
13148
|
for (let step = 0; step < limit; step += 1) {
|
|
12453
13149
|
column += direction;
|
|
@@ -12460,7 +13156,7 @@ var Table = class extends Component {
|
|
|
12460
13156
|
}
|
|
12461
13157
|
if (row2 < 0 || row2 >= visible.length) return null;
|
|
12462
13158
|
const candidateRow = visible[row2];
|
|
12463
|
-
const candidateColumn =
|
|
13159
|
+
const candidateColumn = columns[column];
|
|
12464
13160
|
if (resolveEditable(candidateColumn, candidateRow) !== false) {
|
|
12465
13161
|
return {
|
|
12466
13162
|
id: this._idFor(candidateRow),
|
|
@@ -12703,6 +13399,53 @@ function sortHierarchyRows(rows, rowId, parentId, getValue, direction) {
|
|
|
12703
13399
|
}
|
|
12704
13400
|
return result;
|
|
12705
13401
|
}
|
|
13402
|
+
function reorderTableRows(rows, rowId, id, targetId, position2 = "before", parentId = null) {
|
|
13403
|
+
if (!Array.isArray(rows) || Object.is(id, targetId)) return null;
|
|
13404
|
+
const from = rows.findIndex((row2) => Object.is(readRowValue(rowId, row2), id));
|
|
13405
|
+
const target = rows.findIndex((row2) => Object.is(readRowValue(rowId, row2), targetId));
|
|
13406
|
+
if (from < 0 || target < 0) return null;
|
|
13407
|
+
if (parentId && !sameHierarchyParent(
|
|
13408
|
+
readRowValue(parentId, rows[from]),
|
|
13409
|
+
readRowValue(parentId, rows[target])
|
|
13410
|
+
)) return null;
|
|
13411
|
+
if (parentId) {
|
|
13412
|
+
const forest = hierarchyForest(rows, rowId, parentId);
|
|
13413
|
+
const movingNode = forest.nodes[from];
|
|
13414
|
+
const targetNode = forest.nodes[target];
|
|
13415
|
+
if (!movingNode || !targetNode || movingNode.parent !== targetNode.parent) return null;
|
|
13416
|
+
const branchNodes = (root) => {
|
|
13417
|
+
const result = [];
|
|
13418
|
+
const stack2 = [root];
|
|
13419
|
+
while (stack2.length) {
|
|
13420
|
+
const node = (
|
|
13421
|
+
/** @type {TableHierarchyNode} */
|
|
13422
|
+
stack2.pop()
|
|
13423
|
+
);
|
|
13424
|
+
result.push(node);
|
|
13425
|
+
for (let index = node.children.length - 1; index >= 0; index -= 1) stack2.push(node.children[index]);
|
|
13426
|
+
}
|
|
13427
|
+
return result;
|
|
13428
|
+
};
|
|
13429
|
+
const moving2 = new Set(branchNodes(movingNode).map((node) => node.index));
|
|
13430
|
+
const targetBranch = new Set(branchNodes(targetNode).map((node) => node.index));
|
|
13431
|
+
const block = rows.filter((_row, index) => moving2.has(index));
|
|
13432
|
+
const remaining = rows.filter((_row, index) => !moving2.has(index));
|
|
13433
|
+
const remainingSourceIndexes = rows.map((_row, index) => index).filter((index) => !moving2.has(index));
|
|
13434
|
+
let insertion = remainingSourceIndexes.findIndex((index) => index === target);
|
|
13435
|
+
if (position2 === "after") {
|
|
13436
|
+
const targetPositions = remainingSourceIndexes.map((sourceIndex, index) => targetBranch.has(sourceIndex) ? index : -1).filter((index) => index >= 0);
|
|
13437
|
+
insertion = Math.max(...targetPositions) + 1;
|
|
13438
|
+
}
|
|
13439
|
+
if (insertion < 0) return null;
|
|
13440
|
+
remaining.splice(insertion, 0, ...block);
|
|
13441
|
+
return remaining;
|
|
13442
|
+
}
|
|
13443
|
+
const next = [...rows];
|
|
13444
|
+
const [moving] = next.splice(from, 1);
|
|
13445
|
+
const adjustedTarget = next.findIndex((row2) => Object.is(readRowValue(rowId, row2), targetId));
|
|
13446
|
+
next.splice(adjustedTarget + (position2 === "after" ? 1 : 0), 0, moving);
|
|
13447
|
+
return next;
|
|
13448
|
+
}
|
|
12706
13449
|
function resolveEditable(column, row2) {
|
|
12707
13450
|
if (!column || typeof column !== "object") return false;
|
|
12708
13451
|
if (typeof column.editor === "function") {
|
|
@@ -12783,7 +13526,8 @@ function hierarchyForest(rows, rowId, parentId) {
|
|
|
12783
13526
|
for (const node of nodes) if (!byId.has(node.id)) byId.set(node.id, node);
|
|
12784
13527
|
const parentOf = /* @__PURE__ */ new Map();
|
|
12785
13528
|
for (const node of nodes) {
|
|
12786
|
-
const
|
|
13529
|
+
const parentValue = readRowValue(parentId, node.row);
|
|
13530
|
+
const parent = parentValue == null ? null : byId.get(parentValue) ?? null;
|
|
12787
13531
|
parentOf.set(node, parent === node ? null : parent);
|
|
12788
13532
|
}
|
|
12789
13533
|
for (const node of nodes) {
|
|
@@ -12812,6 +13556,9 @@ function hierarchyBranchIds(rows, rowId, parentId) {
|
|
|
12812
13556
|
function readRowValue(accessor, row2) {
|
|
12813
13557
|
return typeof accessor === "function" ? accessor(row2) : row2?.[accessor];
|
|
12814
13558
|
}
|
|
13559
|
+
function sameHierarchyParent(left, right) {
|
|
13560
|
+
return left == null && right == null || Object.is(left, right);
|
|
13561
|
+
}
|
|
12815
13562
|
function normalizeHierarchy(option, columns) {
|
|
12816
13563
|
if (!option || typeof option !== "object") return null;
|
|
12817
13564
|
const parentId = option.parentId;
|
|
@@ -12831,6 +13578,23 @@ function normalizeEditMode(mode) {
|
|
|
12831
13578
|
function isInsideEditor(target) {
|
|
12832
13579
|
return Boolean(target?.closest?.(".zx-table__editor"));
|
|
12833
13580
|
}
|
|
13581
|
+
function isInteractiveCellContent(target) {
|
|
13582
|
+
const cell = target?.closest?.("td[data-column]");
|
|
13583
|
+
const interactive = target?.closest?.([
|
|
13584
|
+
"a[href]",
|
|
13585
|
+
"button",
|
|
13586
|
+
"input",
|
|
13587
|
+
"select",
|
|
13588
|
+
"textarea",
|
|
13589
|
+
"summary",
|
|
13590
|
+
'[contenteditable="true"]',
|
|
13591
|
+
'[role="button"]',
|
|
13592
|
+
'[role="link"]',
|
|
13593
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
13594
|
+
"[data-zx-table-no-edit]"
|
|
13595
|
+
].join(","));
|
|
13596
|
+
return Boolean(cell && interactive && interactive !== cell && cell.contains(interactive));
|
|
13597
|
+
}
|
|
12834
13598
|
function deferToEditor(target) {
|
|
12835
13599
|
if (target.closest("[popover]")) return true;
|
|
12836
13600
|
return target.getAttribute?.("aria-expanded") === "true";
|
|
@@ -15242,6 +16006,244 @@ function normalizeAlign2(value) {
|
|
|
15242
16006
|
) : "start";
|
|
15243
16007
|
}
|
|
15244
16008
|
|
|
16009
|
+
// src/components/card/card.js
|
|
16010
|
+
var Card = class extends Component {
|
|
16011
|
+
static cssName = "card";
|
|
16012
|
+
/** @type {Readonly<CardOptions>} */
|
|
16013
|
+
static defaults = {
|
|
16014
|
+
title: "",
|
|
16015
|
+
media: null,
|
|
16016
|
+
content: null,
|
|
16017
|
+
actions: [],
|
|
16018
|
+
footer: null,
|
|
16019
|
+
link: null,
|
|
16020
|
+
variant: "outlined",
|
|
16021
|
+
orientation: "vertical",
|
|
16022
|
+
headingLevel: 3
|
|
16023
|
+
};
|
|
16024
|
+
/**
|
|
16025
|
+
* Creates or enhances one card.
|
|
16026
|
+
* @param {Element|string|null} [target=null] Existing card container, selector, or null.
|
|
16027
|
+
* @param {CardOptions} [options={}] Card options.
|
|
16028
|
+
*/
|
|
16029
|
+
constructor(target = null, options = {}) {
|
|
16030
|
+
super(target, options);
|
|
16031
|
+
}
|
|
16032
|
+
/** @returns {HTMLElement} */
|
|
16033
|
+
render() {
|
|
16034
|
+
const created = !this.el;
|
|
16035
|
+
const root = (
|
|
16036
|
+
/** @type {HTMLElement} */
|
|
16037
|
+
this.el ?? h("article")
|
|
16038
|
+
);
|
|
16039
|
+
this.el = root;
|
|
16040
|
+
this._createdRoot = created;
|
|
16041
|
+
this._original = created ? null : snapshotTarget(root);
|
|
16042
|
+
this._cleaned = false;
|
|
16043
|
+
this._title = "";
|
|
16044
|
+
this._link = null;
|
|
16045
|
+
this._actionAbort = null;
|
|
16046
|
+
this._linkAbort = null;
|
|
16047
|
+
const originalContent = created ? [] : Array.from(root.childNodes);
|
|
16048
|
+
try {
|
|
16049
|
+
const headingLevel = normalizeHeadingLevel(this.options.headingLevel);
|
|
16050
|
+
const headingId = uid("zx-card-heading");
|
|
16051
|
+
const heading = h(`h${headingLevel}`, {
|
|
16052
|
+
ref: "heading",
|
|
16053
|
+
class: "zx-card__heading",
|
|
16054
|
+
id: headingId
|
|
16055
|
+
});
|
|
16056
|
+
const media = h("div", { ref: "media", class: "zx-card__media", hidden: true });
|
|
16057
|
+
const actions2 = h("div", {
|
|
16058
|
+
ref: "actions",
|
|
16059
|
+
class: "zx-card__actions",
|
|
16060
|
+
role: "group",
|
|
16061
|
+
ariaLabel: "Card actions",
|
|
16062
|
+
hidden: true
|
|
16063
|
+
});
|
|
16064
|
+
const content = h("div", { ref: "content", class: "zx-card__content", hidden: true });
|
|
16065
|
+
const footer = h("footer", { ref: "footer", class: "zx-card__footer", hidden: true });
|
|
16066
|
+
const header = h("header", { ref: "header", class: "zx-card__header" }, heading, actions2);
|
|
16067
|
+
root.replaceChildren(h(
|
|
16068
|
+
"div",
|
|
16069
|
+
{ class: "zx-card__layout" },
|
|
16070
|
+
media,
|
|
16071
|
+
h("div", { class: "zx-card__inner" }, header, content, footer)
|
|
16072
|
+
));
|
|
16073
|
+
root.dataset.variant = normalizeVariant2(this.options.variant);
|
|
16074
|
+
root.dataset.orientation = normalizeOrientation(this.options.orientation);
|
|
16075
|
+
this.setTitle(this.options.title);
|
|
16076
|
+
this.setMedia(this.options.media);
|
|
16077
|
+
if (this.options.content !== null) this.setContent(this.options.content);
|
|
16078
|
+
else if (originalContent.length) {
|
|
16079
|
+
content.replaceChildren(...originalContent);
|
|
16080
|
+
content.hidden = false;
|
|
16081
|
+
}
|
|
16082
|
+
this.setActions(this.options.actions);
|
|
16083
|
+
this.setFooter(this.options.footer);
|
|
16084
|
+
this.setLink(this.options.link);
|
|
16085
|
+
return root;
|
|
16086
|
+
} catch (error2) {
|
|
16087
|
+
this._actionAbort?.abort();
|
|
16088
|
+
this._linkAbort?.abort();
|
|
16089
|
+
if (!created) restoreTarget(root, this._original);
|
|
16090
|
+
throw error2;
|
|
16091
|
+
}
|
|
16092
|
+
}
|
|
16093
|
+
/** Replaces the card title. @param {string} title Next title. @returns {this} */
|
|
16094
|
+
setTitle(title) {
|
|
16095
|
+
const next = String(title ?? "");
|
|
16096
|
+
if (this._link && !next.trim()) throw new TypeError("A linked Card requires a non-empty title");
|
|
16097
|
+
this._title = next;
|
|
16098
|
+
this._renderHeading();
|
|
16099
|
+
return this;
|
|
16100
|
+
}
|
|
16101
|
+
/** Replaces or removes the media region. @param {CardContent} content Media. @returns {this} */
|
|
16102
|
+
setMedia(content) {
|
|
16103
|
+
replaceCardContent(this.refs.media, content, "Card media");
|
|
16104
|
+
this.refs.media.hidden = content === null;
|
|
16105
|
+
return this;
|
|
16106
|
+
}
|
|
16107
|
+
/** Replaces or clears the main content. @param {CardContent} content Content. @returns {this} */
|
|
16108
|
+
setContent(content) {
|
|
16109
|
+
replaceCardContent(this.refs.content, content, "Card content");
|
|
16110
|
+
this.refs.content.hidden = content === null;
|
|
16111
|
+
return this;
|
|
16112
|
+
}
|
|
16113
|
+
/** Replaces the secondary action controls. @param {CardAction[]} actions Actions. @returns {this} */
|
|
16114
|
+
setActions(actions2) {
|
|
16115
|
+
if (!Array.isArray(actions2)) throw new TypeError("Card actions must be an array");
|
|
16116
|
+
const nextAbort = new AbortController();
|
|
16117
|
+
let controls;
|
|
16118
|
+
try {
|
|
16119
|
+
controls = actions2.map((action) => {
|
|
16120
|
+
if (isElement(action)) return action;
|
|
16121
|
+
if (!action || typeof action !== "object" || Array.isArray(action)) {
|
|
16122
|
+
throw new TypeError("Card actions must be Elements or button descriptors");
|
|
16123
|
+
}
|
|
16124
|
+
const descriptor = { ...action };
|
|
16125
|
+
const onclick = descriptor.onclick;
|
|
16126
|
+
delete descriptor.onclick;
|
|
16127
|
+
const control = button(descriptor);
|
|
16128
|
+
if (onclick !== void 0 && typeof onclick !== "function") {
|
|
16129
|
+
throw new TypeError("Card action onclick must be a function");
|
|
16130
|
+
}
|
|
16131
|
+
if (onclick) control.addEventListener("click", onclick, { signal: nextAbort.signal });
|
|
16132
|
+
return control;
|
|
16133
|
+
});
|
|
16134
|
+
} catch (error2) {
|
|
16135
|
+
nextAbort.abort();
|
|
16136
|
+
throw error2;
|
|
16137
|
+
}
|
|
16138
|
+
this._actionAbort?.abort();
|
|
16139
|
+
this._actionAbort = nextAbort;
|
|
16140
|
+
this.refs.actions.replaceChildren(...controls);
|
|
16141
|
+
this.refs.actions.hidden = controls.length === 0;
|
|
16142
|
+
this._syncHeader();
|
|
16143
|
+
return this;
|
|
16144
|
+
}
|
|
16145
|
+
/** Replaces or removes the footer. @param {CardContent} content Footer. @returns {this} */
|
|
16146
|
+
setFooter(content) {
|
|
16147
|
+
replaceCardContent(this.refs.footer, content, "Card footer");
|
|
16148
|
+
this.refs.footer.hidden = content === null;
|
|
16149
|
+
return this;
|
|
16150
|
+
}
|
|
16151
|
+
/** Sets or removes the title's native primary link. @param {CardLink} link Link. @returns {this} */
|
|
16152
|
+
setLink(link) {
|
|
16153
|
+
const next = normalizeCardLink(link);
|
|
16154
|
+
if (next && !this._title.trim()) throw new TypeError("A linked Card requires a non-empty title");
|
|
16155
|
+
this._link = next;
|
|
16156
|
+
this.el.dataset.linked = next ? "true" : "false";
|
|
16157
|
+
this._renderHeading();
|
|
16158
|
+
return this;
|
|
16159
|
+
}
|
|
16160
|
+
/** Restores an enhanced target exactly, or removes an owned card. @returns {void} */
|
|
16161
|
+
destroy() {
|
|
16162
|
+
if (this._cleaned) return;
|
|
16163
|
+
this._cleaned = true;
|
|
16164
|
+
this._actionAbort?.abort();
|
|
16165
|
+
this._linkAbort?.abort();
|
|
16166
|
+
super.destroy();
|
|
16167
|
+
if (!this._createdRoot) restoreTarget(this.el, this._original);
|
|
16168
|
+
}
|
|
16169
|
+
/** @returns {void} */
|
|
16170
|
+
_renderHeading() {
|
|
16171
|
+
const title = this._title;
|
|
16172
|
+
const nextAbort = new AbortController();
|
|
16173
|
+
const node = this._link ? h("a", {
|
|
16174
|
+
class: "zx-card__primary",
|
|
16175
|
+
href: this._link.href,
|
|
16176
|
+
target: this._link.target,
|
|
16177
|
+
rel: this._link.rel
|
|
16178
|
+
}, title) : h("span", { class: "zx-card__title" }, title);
|
|
16179
|
+
if (this._link?.onclick) node.addEventListener("click", this._link.onclick, { signal: nextAbort.signal });
|
|
16180
|
+
this._linkAbort?.abort();
|
|
16181
|
+
this._linkAbort = nextAbort;
|
|
16182
|
+
this.refs.heading.replaceChildren(node);
|
|
16183
|
+
this.refs.heading.hidden = title === "";
|
|
16184
|
+
if (title) this.el.setAttribute("aria-labelledby", this.refs.heading.id);
|
|
16185
|
+
else this.el.removeAttribute("aria-labelledby");
|
|
16186
|
+
this._syncHeader();
|
|
16187
|
+
}
|
|
16188
|
+
/** @returns {void} */
|
|
16189
|
+
_syncHeader() {
|
|
16190
|
+
if (!this.refs.header) return;
|
|
16191
|
+
this.refs.header.hidden = this._title === "" && this.refs.actions.childElementCount === 0;
|
|
16192
|
+
}
|
|
16193
|
+
};
|
|
16194
|
+
function replaceCardContent(host, content, label) {
|
|
16195
|
+
if (content === null) {
|
|
16196
|
+
host.replaceChildren();
|
|
16197
|
+
return;
|
|
16198
|
+
}
|
|
16199
|
+
if (typeof content === "string") {
|
|
16200
|
+
host.textContent = content;
|
|
16201
|
+
return;
|
|
16202
|
+
}
|
|
16203
|
+
const node = content instanceof Component ? content.toElement() : content;
|
|
16204
|
+
if (node instanceof Node) {
|
|
16205
|
+
host.replaceChildren(node);
|
|
16206
|
+
return;
|
|
16207
|
+
}
|
|
16208
|
+
throw new TypeError(`${label} must be a string, Node, Component, or null`);
|
|
16209
|
+
}
|
|
16210
|
+
function normalizeCardLink(value) {
|
|
16211
|
+
if (value === null) return null;
|
|
16212
|
+
const descriptor = typeof value === "string" ? { href: value } : value;
|
|
16213
|
+
if (!descriptor || typeof descriptor !== "object" || Array.isArray(descriptor)) {
|
|
16214
|
+
throw new TypeError("Card link must be a URL string, descriptor, or null");
|
|
16215
|
+
}
|
|
16216
|
+
const href = safeHref(descriptor.href);
|
|
16217
|
+
if (href === null) {
|
|
16218
|
+
throw new TypeError("Card link requires a safe non-empty href");
|
|
16219
|
+
}
|
|
16220
|
+
if (descriptor.onclick !== void 0 && typeof descriptor.onclick !== "function") {
|
|
16221
|
+
throw new TypeError("Card link onclick must be a function");
|
|
16222
|
+
}
|
|
16223
|
+
const target = descriptor.target == null ? void 0 : String(descriptor.target);
|
|
16224
|
+
let rel = descriptor.rel == null ? "" : String(descriptor.rel).trim();
|
|
16225
|
+
if (target === "_blank" && !/(^|\s)noopener(\s|$)/i.test(rel)) {
|
|
16226
|
+
rel = `${rel} noopener`.trim();
|
|
16227
|
+
}
|
|
16228
|
+
return { href, target, rel: rel || void 0, onclick: descriptor.onclick };
|
|
16229
|
+
}
|
|
16230
|
+
function normalizeVariant2(value) {
|
|
16231
|
+
return ["raised", "filled"].includes(String(value)) ? (
|
|
16232
|
+
/** @type {'raised'|'filled'} */
|
|
16233
|
+
value
|
|
16234
|
+
) : "outlined";
|
|
16235
|
+
}
|
|
16236
|
+
function normalizeOrientation(value) {
|
|
16237
|
+
return value === "horizontal" ? "horizontal" : "vertical";
|
|
16238
|
+
}
|
|
16239
|
+
function normalizeHeadingLevel(value) {
|
|
16240
|
+
const level = Math.trunc(Number(value));
|
|
16241
|
+
return (
|
|
16242
|
+
/** @type {1|2|3|4|5|6} */
|
|
16243
|
+
level >= 1 && level <= 6 ? level : 3
|
|
16244
|
+
);
|
|
16245
|
+
}
|
|
16246
|
+
|
|
15245
16247
|
// src/components/panel/panel.js
|
|
15246
16248
|
var Panel = class extends Component {
|
|
15247
16249
|
static cssName = "panel";
|
|
@@ -18572,6 +19574,106 @@ function count(value) {
|
|
|
18572
19574
|
return value.toLocaleString();
|
|
18573
19575
|
}
|
|
18574
19576
|
|
|
19577
|
+
// src/components/app-icon/app-icon.js
|
|
19578
|
+
var AppIcon = class extends Component {
|
|
19579
|
+
static cssName = "app-icon";
|
|
19580
|
+
/** @type {Readonly<AppIconOptions>} */
|
|
19581
|
+
static defaults = {
|
|
19582
|
+
icon: null,
|
|
19583
|
+
color: null,
|
|
19584
|
+
size: 36,
|
|
19585
|
+
iconSize: "52%",
|
|
19586
|
+
label: null,
|
|
19587
|
+
badge: null,
|
|
19588
|
+
selected: false,
|
|
19589
|
+
glass: "subtle",
|
|
19590
|
+
class: []
|
|
19591
|
+
};
|
|
19592
|
+
/** @returns {HTMLElement} */
|
|
19593
|
+
render() {
|
|
19594
|
+
this._createdRoot = this.el === null;
|
|
19595
|
+
const root = (
|
|
19596
|
+
/** @type {HTMLElement} */
|
|
19597
|
+
this.el ?? h("span")
|
|
19598
|
+
);
|
|
19599
|
+
this.el = root;
|
|
19600
|
+
this._snapshot = this._createdRoot ? null : snapshotTarget(root);
|
|
19601
|
+
this._state = { ...this.options };
|
|
19602
|
+
this._appliedClasses = [];
|
|
19603
|
+
this._sync();
|
|
19604
|
+
return root;
|
|
19605
|
+
}
|
|
19606
|
+
/** Updates identity or presentation without replacing the root. @param {Partial<AppIconOptions>} values @returns {this} */
|
|
19607
|
+
set(values = {}) {
|
|
19608
|
+
if (!values || typeof values !== "object") return this;
|
|
19609
|
+
Object.assign(this._state, values);
|
|
19610
|
+
this._sync();
|
|
19611
|
+
return this;
|
|
19612
|
+
}
|
|
19613
|
+
/** Restores an enhanced target. @returns {void} */
|
|
19614
|
+
destroy() {
|
|
19615
|
+
const root = this.el;
|
|
19616
|
+
const snapshot10 = this._snapshot;
|
|
19617
|
+
super.destroy();
|
|
19618
|
+
if (!this._createdRoot && root) restoreTarget(root, snapshot10);
|
|
19619
|
+
}
|
|
19620
|
+
/** @returns {void} */
|
|
19621
|
+
_sync() {
|
|
19622
|
+
const state = this._state;
|
|
19623
|
+
this.el.classList.remove(...this._appliedClasses);
|
|
19624
|
+
this._appliedClasses = [state.class].flat().flatMap((value) => String(value ?? "").split(/\s+/)).filter(Boolean);
|
|
19625
|
+
this.el.classList.add(...this._appliedClasses);
|
|
19626
|
+
const size2 = cssLength2(state.size, "36px");
|
|
19627
|
+
const glyphSize = cssLength2(state.iconSize, "52%");
|
|
19628
|
+
this.el.style.setProperty("--zx-app-icon-size", size2);
|
|
19629
|
+
this.el.style.setProperty("--zx-app-icon-glyph-size", glyphSize);
|
|
19630
|
+
if (isCssColor(state.color)) this.el.style.setProperty("--zx-app-icon-color", String(state.color));
|
|
19631
|
+
else this.el.style.removeProperty("--zx-app-icon-color");
|
|
19632
|
+
this.el.dataset.glass = normalizeGlass(state.glass);
|
|
19633
|
+
this.el.toggleAttribute("data-selected", Boolean(state.selected));
|
|
19634
|
+
const label = state.label == null ? "" : String(state.label).trim();
|
|
19635
|
+
if (label) {
|
|
19636
|
+
this.el.setAttribute("role", "img");
|
|
19637
|
+
this.el.setAttribute("aria-label", label);
|
|
19638
|
+
this.el.removeAttribute("aria-hidden");
|
|
19639
|
+
} else {
|
|
19640
|
+
this.el.removeAttribute("role");
|
|
19641
|
+
this.el.removeAttribute("aria-label");
|
|
19642
|
+
this.el.setAttribute("aria-hidden", "true");
|
|
19643
|
+
}
|
|
19644
|
+
const visual = resolveVisual(state.icon);
|
|
19645
|
+
const badge2 = state.badge == null || state.badge === "" ? null : h("span", {
|
|
19646
|
+
class: "zx-app-icon__badge",
|
|
19647
|
+
ariaHidden: "true"
|
|
19648
|
+
}, String(state.badge));
|
|
19649
|
+
this.el.replaceChildren(
|
|
19650
|
+
h("span", { class: "zx-app-icon__shine", ariaHidden: "true" }),
|
|
19651
|
+
h("span", { class: "zx-app-icon__glyph", ariaHidden: "true" }, visual),
|
|
19652
|
+
badge2
|
|
19653
|
+
);
|
|
19654
|
+
}
|
|
19655
|
+
};
|
|
19656
|
+
function appIcon(options = {}) {
|
|
19657
|
+
return new AppIcon(null, options).toElement();
|
|
19658
|
+
}
|
|
19659
|
+
function resolveVisual(value) {
|
|
19660
|
+
const resolved = typeof value === "function" ? value() : value;
|
|
19661
|
+
if (typeof resolved === "string") return icon(resolved, { size: "1em" });
|
|
19662
|
+
return resolved && typeof resolved === "object" && typeof resolved.nodeType === "number" ? (
|
|
19663
|
+
/** @type {Node} */
|
|
19664
|
+
resolved
|
|
19665
|
+
) : null;
|
|
19666
|
+
}
|
|
19667
|
+
function cssLength2(value, fallback) {
|
|
19668
|
+
if (typeof value === "number" && Number.isFinite(value)) return `${Math.max(1, value)}px`;
|
|
19669
|
+
const text4 = String(value ?? "").trim();
|
|
19670
|
+
return text4 && !/[;{}]/.test(text4) ? text4 : fallback;
|
|
19671
|
+
}
|
|
19672
|
+
function normalizeGlass(value) {
|
|
19673
|
+
if (value === false || value === "none") return "none";
|
|
19674
|
+
return value === "strong" ? "strong" : "subtle";
|
|
19675
|
+
}
|
|
19676
|
+
|
|
18575
19677
|
// src/components/launcher/launcher.js
|
|
18576
19678
|
var Launcher = class extends Component {
|
|
18577
19679
|
static cssName = "launcher";
|
|
@@ -18585,9 +19687,12 @@ var Launcher = class extends Component {
|
|
|
18585
19687
|
maxResults: 100,
|
|
18586
19688
|
placeholder: "Search applications and records",
|
|
18587
19689
|
label: "Launcher",
|
|
19690
|
+
closeLabel: "Close launcher",
|
|
19691
|
+
resultsLabel: "Launcher results",
|
|
18588
19692
|
emptyText: "No results",
|
|
18589
19693
|
loadingText: "Searching\u2026",
|
|
18590
|
-
shortcut: "mod+k"
|
|
19694
|
+
shortcut: "mod+k",
|
|
19695
|
+
hints: { move: "Move", open: "Open", close: "Close" }
|
|
18591
19696
|
};
|
|
18592
19697
|
/**
|
|
18593
19698
|
* Creates an owned launcher dialog or enhances an existing `<dialog>` target.
|
|
@@ -18616,6 +19721,7 @@ var Launcher = class extends Component {
|
|
|
18616
19721
|
this._sources = normalizeSources(this.options.sources);
|
|
18617
19722
|
this._sourceItems = /* @__PURE__ */ new Map();
|
|
18618
19723
|
this._query = String(this.options.query ?? "");
|
|
19724
|
+
this._renderedQuery = null;
|
|
18619
19725
|
this._results = [];
|
|
18620
19726
|
this._active = -1;
|
|
18621
19727
|
this._request = null;
|
|
@@ -18642,7 +19748,7 @@ var Launcher = class extends Component {
|
|
|
18642
19748
|
ref: "close",
|
|
18643
19749
|
class: "zx-icon-btn zx-launcher__close",
|
|
18644
19750
|
type: "button",
|
|
18645
|
-
ariaLabel:
|
|
19751
|
+
ariaLabel: this.options.closeLabel
|
|
18646
19752
|
}, icon("x"));
|
|
18647
19753
|
const status = h("div", {
|
|
18648
19754
|
ref: "status",
|
|
@@ -18655,16 +19761,25 @@ var Launcher = class extends Component {
|
|
|
18655
19761
|
class: "zx-launcher__results",
|
|
18656
19762
|
id: listId,
|
|
18657
19763
|
role: "listbox",
|
|
18658
|
-
ariaLabel:
|
|
19764
|
+
ariaLabel: this.options.resultsLabel
|
|
18659
19765
|
});
|
|
19766
|
+
const shortcut = this.options.shortcut === "mod+k" ? h("kbd", {
|
|
19767
|
+
class: "zx-launcher__shortcut",
|
|
19768
|
+
ariaHidden: "true"
|
|
19769
|
+
}, launcherShortcutLabel()) : null;
|
|
19770
|
+
const footer = launcherHints(this.options.hints);
|
|
18660
19771
|
dialog.setAttribute("aria-label", String(this.options.label));
|
|
18661
19772
|
dialog.replaceChildren(
|
|
18662
19773
|
h(
|
|
18663
19774
|
"div",
|
|
18664
19775
|
{ class: "zx-launcher__surface" },
|
|
18665
|
-
h("div", {
|
|
19776
|
+
h("div", {
|
|
19777
|
+
class: "zx-launcher__search",
|
|
19778
|
+
dataset: { shortcut: shortcut ? "true" : "false" }
|
|
19779
|
+
}, icon("search"), input, shortcut, close),
|
|
18666
19780
|
status,
|
|
18667
|
-
results
|
|
19781
|
+
results,
|
|
19782
|
+
footer
|
|
18668
19783
|
)
|
|
18669
19784
|
);
|
|
18670
19785
|
dialog.dataset.state = "closed";
|
|
@@ -18688,6 +19803,17 @@ var Launcher = class extends Component {
|
|
|
18688
19803
|
if (!option || !results.contains(option)) return;
|
|
18689
19804
|
this._activate(Number(option.getAttribute("data-launcher-index")), event);
|
|
18690
19805
|
});
|
|
19806
|
+
this.listen(results, "pointermove", (event) => {
|
|
19807
|
+
const option = (
|
|
19808
|
+
/** @type {Element|null} */
|
|
19809
|
+
event.target?.closest?.("[data-launcher-index]")
|
|
19810
|
+
);
|
|
19811
|
+
if (!option || !results.contains(option)) return;
|
|
19812
|
+
const index = Number(option.getAttribute("data-launcher-index"));
|
|
19813
|
+
if (!Number.isInteger(index) || this._results[index]?.disabled || index === this._active) return;
|
|
19814
|
+
this._active = index;
|
|
19815
|
+
this._syncActive();
|
|
19816
|
+
});
|
|
18691
19817
|
this.listen(dialog, "cancel", (event) => {
|
|
18692
19818
|
event.preventDefault();
|
|
18693
19819
|
this.close();
|
|
@@ -18757,8 +19883,15 @@ var Launcher = class extends Component {
|
|
|
18757
19883
|
this._renderResults();
|
|
18758
19884
|
return this;
|
|
18759
19885
|
}
|
|
18760
|
-
/**
|
|
18761
|
-
|
|
19886
|
+
/** Marks one local item as the current destination. @param {string|number|null} id Item id. @returns {this} */
|
|
19887
|
+
setCurrent(id) {
|
|
19888
|
+
const key = id == null ? null : String(id);
|
|
19889
|
+
this._items = this._items.map((item) => ({ ...item, current: key !== null && String(item.id) === key }));
|
|
19890
|
+
this._renderResults();
|
|
19891
|
+
return this;
|
|
19892
|
+
}
|
|
19893
|
+
/** Replaces asynchronous sources and aborts work from the old set. @param {LauncherSource[]} sources @returns {this} */
|
|
19894
|
+
setSources(sources) {
|
|
18762
19895
|
this._abortRequest();
|
|
18763
19896
|
this._sources = normalizeSources(sources);
|
|
18764
19897
|
this._sourceItems.clear();
|
|
@@ -18792,7 +19925,7 @@ var Launcher = class extends Component {
|
|
|
18792
19925
|
async _loadSources() {
|
|
18793
19926
|
this._queryTimer = null;
|
|
18794
19927
|
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));
|
|
19928
|
+
const sources = this._sources.filter((source) => visibleForQuery(source.when, query) && query.length >= Math.max(Number(this.options.minQuery) || 0, Number(source.minQuery) || 0));
|
|
18796
19929
|
if (sources.length === 0) return;
|
|
18797
19930
|
const request = new AbortController();
|
|
18798
19931
|
const sequence = ++this._requestSequence;
|
|
@@ -18802,15 +19935,17 @@ var Launcher = class extends Component {
|
|
|
18802
19935
|
try {
|
|
18803
19936
|
const loaded = await source.load(query, { signal: request.signal });
|
|
18804
19937
|
if (request.signal.aborted || sequence !== this._requestSequence) return;
|
|
18805
|
-
this._sourceItems.set(source.id, normalizeItems2(loaded).map((item) => ({
|
|
19938
|
+
this._sourceItems.set(source.id, normalizeItems2(loaded).map((item, itemOrder) => ({
|
|
18806
19939
|
...item,
|
|
18807
19940
|
_source: source.id,
|
|
18808
|
-
|
|
19941
|
+
_sourceOrder: source.order === "source",
|
|
19942
|
+
group: item.group ?? source.label ?? "",
|
|
19943
|
+
itemOrder: item.itemOrder ?? (source.order === "source" ? itemOrder : void 0)
|
|
18809
19944
|
})));
|
|
18810
19945
|
this._renderResults();
|
|
18811
|
-
} catch (
|
|
19946
|
+
} catch (error2) {
|
|
18812
19947
|
if (request.signal.aborted || sequence !== this._requestSequence) return;
|
|
18813
|
-
this.emit("error", { source, error });
|
|
19948
|
+
this.emit("error", { source, error: error2 });
|
|
18814
19949
|
}
|
|
18815
19950
|
}));
|
|
18816
19951
|
if (request.signal.aborted || sequence !== this._requestSequence) return;
|
|
@@ -18834,23 +19969,36 @@ var Launcher = class extends Component {
|
|
|
18834
19969
|
}
|
|
18835
19970
|
/** @returns {void} */
|
|
18836
19971
|
_renderResults() {
|
|
18837
|
-
const
|
|
19972
|
+
const queryKey = this._query;
|
|
19973
|
+
const activeId = this._renderedQuery === queryKey && this._active >= 0 && this._results[this._active] ? String(this._results[this._active].id) : null;
|
|
19974
|
+
const query = this._query.trim();
|
|
19975
|
+
const local = this._items.filter((item) => visibleForQuery(item.when, query));
|
|
19976
|
+
const rankedLocal = rankLauncherItems(local, query, this.options.maxResults);
|
|
19977
|
+
const remote = this._sources.flatMap((source) => {
|
|
19978
|
+
const loaded = this._sourceItems.get(String(source.id)) ?? [];
|
|
19979
|
+
return source.order === "source" ? loaded.filter((item) => visibleForQuery(item.when, query)) : rankLauncherItems(
|
|
19980
|
+
loaded.filter((item) => visibleForQuery(item.when, query)),
|
|
19981
|
+
query,
|
|
19982
|
+
this.options.maxResults
|
|
19983
|
+
);
|
|
19984
|
+
});
|
|
18838
19985
|
const seen = /* @__PURE__ */ new Set();
|
|
18839
|
-
const combined = [...
|
|
19986
|
+
const combined = [...rankedLocal.map((item) => ({ ...item, _source: null })), ...remote].filter((item) => {
|
|
18840
19987
|
const key = String(item.id);
|
|
18841
19988
|
if (seen.has(key)) return false;
|
|
18842
19989
|
seen.add(key);
|
|
18843
19990
|
return true;
|
|
18844
|
-
});
|
|
18845
|
-
const ranked = rankLauncherItems(combined, this._query, this.options.maxResults);
|
|
19991
|
+
}).slice(0, Math.max(0, Number(this.options.maxResults) || 0));
|
|
18846
19992
|
const groups = /* @__PURE__ */ new Map();
|
|
18847
|
-
for (const item of
|
|
19993
|
+
for (const item of combined) {
|
|
18848
19994
|
const group = String(item.group ?? "");
|
|
18849
19995
|
if (!groups.has(group)) groups.set(group, []);
|
|
18850
19996
|
groups.get(group).push(item);
|
|
18851
19997
|
}
|
|
18852
19998
|
this._results = [...groups.values()].flat();
|
|
18853
|
-
this.
|
|
19999
|
+
this._renderedQuery = queryKey;
|
|
20000
|
+
const preserved = activeId === null ? -1 : this._results.findIndex((item) => !item.disabled && String(item.id) === activeId);
|
|
20001
|
+
this._active = preserved >= 0 ? preserved : this._results.findIndex((item) => !item.disabled);
|
|
18854
20002
|
const nodes = [];
|
|
18855
20003
|
let index = 0;
|
|
18856
20004
|
for (const [group, items] of groups) {
|
|
@@ -18858,7 +20006,8 @@ var Launcher = class extends Component {
|
|
|
18858
20006
|
if (group) nodes.push(h("div", {
|
|
18859
20007
|
class: "zx-launcher__group",
|
|
18860
20008
|
role: "group",
|
|
18861
|
-
ariaLabel: group
|
|
20009
|
+
ariaLabel: group,
|
|
20010
|
+
dataset: { layout: items.every((item) => item.kind === "application") ? "grid" : "list" }
|
|
18862
20011
|
}, h("div", { class: "zx-launcher__group-label", ariaHidden: "true" }, group), options));
|
|
18863
20012
|
else nodes.push(...options);
|
|
18864
20013
|
}
|
|
@@ -18868,9 +20017,14 @@ var Launcher = class extends Component {
|
|
|
18868
20017
|
}
|
|
18869
20018
|
/** @param {RankedLauncherItem} item @param {number} index @returns {HTMLElement} */
|
|
18870
20019
|
_option(item, index) {
|
|
18871
|
-
const
|
|
20020
|
+
const href = safeHref(item.href);
|
|
20021
|
+
const tag = href ? "a" : "button";
|
|
18872
20022
|
const children = [];
|
|
18873
|
-
|
|
20023
|
+
const visual = launcherItemIcon(item);
|
|
20024
|
+
if (visual) children.push(h("span", {
|
|
20025
|
+
class: "zx-launcher__icon",
|
|
20026
|
+
ariaHidden: "true"
|
|
20027
|
+
}, visual));
|
|
18874
20028
|
children.push(h(
|
|
18875
20029
|
"span",
|
|
18876
20030
|
{ class: "zx-launcher__copy" },
|
|
@@ -18883,14 +20037,20 @@ var Launcher = class extends Component {
|
|
|
18883
20037
|
h(tag, {
|
|
18884
20038
|
class: "zx-launcher__option",
|
|
18885
20039
|
type: tag === "button" ? "button" : null,
|
|
18886
|
-
href
|
|
20040
|
+
href,
|
|
18887
20041
|
target: item.target ?? null,
|
|
20042
|
+
rel: tag === "a" ? "noopener" : null,
|
|
18888
20043
|
role: "option",
|
|
18889
20044
|
id: uid("zx-launcher-option"),
|
|
18890
20045
|
tabIndex: -1,
|
|
18891
20046
|
ariaSelected: "false",
|
|
18892
20047
|
ariaDisabled: item.disabled ? "true" : null,
|
|
18893
|
-
|
|
20048
|
+
ariaCurrent: item.current ? "page" : null,
|
|
20049
|
+
dataset: {
|
|
20050
|
+
launcherIndex: index,
|
|
20051
|
+
pinned: item.pinned !== void 0 && item.pinned !== false ? "true" : null,
|
|
20052
|
+
kind: normalizeLauncherKind(item.kind)
|
|
20053
|
+
}
|
|
18894
20054
|
}, children)
|
|
18895
20055
|
);
|
|
18896
20056
|
}
|
|
@@ -18927,6 +20087,17 @@ var Launcher = class extends Component {
|
|
|
18927
20087
|
}
|
|
18928
20088
|
const enabled = this._results.map((item, index) => item.disabled ? -1 : index).filter((index) => index >= 0);
|
|
18929
20089
|
if (enabled.length === 0) return;
|
|
20090
|
+
if (/^Arrow(?:Left|Right|Up|Down)$/.test(event.key)) {
|
|
20091
|
+
const next = this._spatialIndex(event.key, enabled);
|
|
20092
|
+
if (next !== null) {
|
|
20093
|
+
event.preventDefault();
|
|
20094
|
+
this._active = next;
|
|
20095
|
+
this._syncActive();
|
|
20096
|
+
this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`)?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
20097
|
+
return;
|
|
20098
|
+
}
|
|
20099
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowRight") return;
|
|
20100
|
+
}
|
|
18930
20101
|
let position2 = enabled.indexOf(this._active);
|
|
18931
20102
|
if (event.key === "Home" || event.key === "PageUp") position2 = 0;
|
|
18932
20103
|
else if (event.key === "End" || event.key === "PageDown") position2 = enabled.length - 1;
|
|
@@ -18938,6 +20109,15 @@ var Launcher = class extends Component {
|
|
|
18938
20109
|
this._syncActive();
|
|
18939
20110
|
this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`)?.scrollIntoView({ block: "nearest" });
|
|
18940
20111
|
}
|
|
20112
|
+
/** @param {string} direction @param {number[]} enabled @returns {number|null} */
|
|
20113
|
+
_spatialIndex(direction, enabled) {
|
|
20114
|
+
const current = this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`);
|
|
20115
|
+
if (!(current instanceof HTMLElement)) return null;
|
|
20116
|
+
if ((direction === "ArrowLeft" || direction === "ArrowRight") && this._results[this._active]?.kind !== "application") return null;
|
|
20117
|
+
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"));
|
|
20118
|
+
const next = findSpatialLauncherOption(candidates, current, direction);
|
|
20119
|
+
return next ? Number(next.getAttribute("data-launcher-index")) : null;
|
|
20120
|
+
}
|
|
18941
20121
|
/** @param {number} index @param {Event} event @returns {void} */
|
|
18942
20122
|
_activate(index, event) {
|
|
18943
20123
|
const item = this._results[index];
|
|
@@ -18963,25 +20143,103 @@ var Launcher = class extends Component {
|
|
|
18963
20143
|
}
|
|
18964
20144
|
/** @param {KeyboardEvent} event @returns {void} */
|
|
18965
20145
|
_onDocumentKeydown(event) {
|
|
18966
|
-
if (this.options.shortcut !== "mod+k" || event.isComposing || event.key.toLowerCase() !== "k" ||
|
|
20146
|
+
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
20147
|
const active = document.activeElement;
|
|
18968
|
-
if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement || active instanceof HTMLElement && active.isContentEditable) return;
|
|
20148
|
+
if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement || active instanceof HTMLSelectElement || active instanceof HTMLElement && active.isContentEditable) return;
|
|
18969
20149
|
const modal = document.querySelector("dialog:modal");
|
|
18970
20150
|
if (modal && modal !== this.el) return;
|
|
18971
20151
|
event.preventDefault();
|
|
18972
20152
|
this.open();
|
|
18973
20153
|
}
|
|
18974
20154
|
};
|
|
20155
|
+
function launcherShortcutLabel() {
|
|
20156
|
+
const platform = String(
|
|
20157
|
+
/** @type {any} */
|
|
20158
|
+
navigator.userAgentData?.platform ?? navigator.platform ?? ""
|
|
20159
|
+
);
|
|
20160
|
+
return /mac|iphone|ipad/i.test(platform) ? "\u2318 K" : "Ctrl K";
|
|
20161
|
+
}
|
|
20162
|
+
function launcherHints(hints) {
|
|
20163
|
+
if (hints === false) return null;
|
|
20164
|
+
const labels = hints && typeof hints === "object" ? hints : {};
|
|
20165
|
+
return h(
|
|
20166
|
+
"footer",
|
|
20167
|
+
{ class: "zx-launcher__hints", ariaHidden: "true" },
|
|
20168
|
+
h("span", {}, h("kbd", {}, "\u2191\u2193\u2190\u2192"), labels.move ?? "Move"),
|
|
20169
|
+
h("span", {}, h("kbd", {}, "\u21B5"), labels.open ?? "Open"),
|
|
20170
|
+
h("span", {}, h("kbd", {}, "Esc"), labels.close ?? "Close")
|
|
20171
|
+
);
|
|
20172
|
+
}
|
|
20173
|
+
function launcherItemIcon(item) {
|
|
20174
|
+
let visual = item.icon;
|
|
20175
|
+
if (typeof visual === "function") visual = visual(item);
|
|
20176
|
+
if (typeof visual === "string") return icon(visual);
|
|
20177
|
+
if (visual instanceof Node) return visual.cloneNode(true);
|
|
20178
|
+
if (visual && typeof visual === "object" && typeof /** @type {any} */
|
|
20179
|
+
visual.toElement === "function") {
|
|
20180
|
+
const element = (
|
|
20181
|
+
/** @type {any} */
|
|
20182
|
+
visual.toElement()
|
|
20183
|
+
);
|
|
20184
|
+
return element instanceof Node ? element.cloneNode(true) : null;
|
|
20185
|
+
}
|
|
20186
|
+
return null;
|
|
20187
|
+
}
|
|
20188
|
+
function normalizeLauncherKind(kind) {
|
|
20189
|
+
return kind === "application" || kind === "record" ? kind : "action";
|
|
20190
|
+
}
|
|
20191
|
+
function visibleForQuery(when, query) {
|
|
20192
|
+
if (when === "empty") return query === "";
|
|
20193
|
+
if (when === "query") return query !== "";
|
|
20194
|
+
return true;
|
|
20195
|
+
}
|
|
20196
|
+
function findSpatialLauncherOption(options, current, direction) {
|
|
20197
|
+
const origin = current.getBoundingClientRect();
|
|
20198
|
+
if (origin.width === 0 && origin.height === 0) return null;
|
|
20199
|
+
const originX = origin.left + origin.width / 2;
|
|
20200
|
+
const originY = origin.top + origin.height / 2;
|
|
20201
|
+
let best = null;
|
|
20202
|
+
let bestScore = Infinity;
|
|
20203
|
+
for (const option of options) {
|
|
20204
|
+
if (option === current) continue;
|
|
20205
|
+
const rect = option.getBoundingClientRect();
|
|
20206
|
+
const dx = rect.left + rect.width / 2 - originX;
|
|
20207
|
+
const dy = rect.top + rect.height / 2 - originY;
|
|
20208
|
+
let valid = false;
|
|
20209
|
+
let score = Infinity;
|
|
20210
|
+
if (direction === "ArrowLeft") {
|
|
20211
|
+
valid = dx < -2 && Math.abs(dy) < Math.max(origin.height, rect.height) / 2;
|
|
20212
|
+
score = Math.abs(dx) + Math.abs(dy) * 4;
|
|
20213
|
+
} else if (direction === "ArrowRight") {
|
|
20214
|
+
valid = dx > 2 && Math.abs(dy) < Math.max(origin.height, rect.height) / 2;
|
|
20215
|
+
score = Math.abs(dx) + Math.abs(dy) * 4;
|
|
20216
|
+
} else if (direction === "ArrowUp") {
|
|
20217
|
+
valid = dy < -2;
|
|
20218
|
+
score = Math.abs(dy) * 10 + Math.abs(dx);
|
|
20219
|
+
} else if (direction === "ArrowDown") {
|
|
20220
|
+
valid = dy > 2;
|
|
20221
|
+
score = Math.abs(dy) * 10 + Math.abs(dx);
|
|
20222
|
+
}
|
|
20223
|
+
if (valid && score < bestScore) {
|
|
20224
|
+
best = option;
|
|
20225
|
+
bestScore = score;
|
|
20226
|
+
}
|
|
20227
|
+
}
|
|
20228
|
+
return best;
|
|
20229
|
+
}
|
|
18975
20230
|
function rankLauncherItems(items, query, limit = 100) {
|
|
18976
20231
|
const needle = searchable(query).trim();
|
|
18977
20232
|
const ranked = items.map((item, order) => ({ item, order, score: launcherScore(item, needle) })).filter(({ score }) => score < Infinity);
|
|
18978
20233
|
ranked.sort((a, b) => {
|
|
20234
|
+
const aGroup = finiteOrder(a.item.groupOrder);
|
|
20235
|
+
const bGroup = finiteOrder(b.item.groupOrder);
|
|
20236
|
+
if (aGroup !== bGroup) return aGroup - bGroup;
|
|
18979
20237
|
if (needle === "") {
|
|
18980
20238
|
const aPinned = pinOrder(a.item.pinned, a.order);
|
|
18981
20239
|
const bPinned = pinOrder(b.item.pinned, b.order);
|
|
18982
20240
|
if (aPinned !== bPinned) return aPinned - bPinned;
|
|
18983
20241
|
}
|
|
18984
|
-
return a.score - b.score || compareLauncherText(a.item.label, b.item.label) || a.order - b.order;
|
|
20242
|
+
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
20243
|
});
|
|
18986
20244
|
const maximum = Math.max(0, Number(limit) || 0);
|
|
18987
20245
|
return ranked.slice(0, maximum || 0).map(({ item, score }) => ({ ...item, _score: score }));
|
|
@@ -19005,6 +20263,9 @@ function pinOrder(pinned, order) {
|
|
|
19005
20263
|
if (pinned) return order;
|
|
19006
20264
|
return Number.MAX_SAFE_INTEGER;
|
|
19007
20265
|
}
|
|
20266
|
+
function finiteOrder(value) {
|
|
20267
|
+
return typeof value === "number" && Number.isFinite(value) ? value : Number.MAX_SAFE_INTEGER;
|
|
20268
|
+
}
|
|
19008
20269
|
function searchable(value) {
|
|
19009
20270
|
return String(value ?? "").normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase();
|
|
19010
20271
|
}
|
|
@@ -19019,15 +20280,23 @@ function compareLauncherText(left, right) {
|
|
|
19019
20280
|
}
|
|
19020
20281
|
function normalizeItems2(items) {
|
|
19021
20282
|
if (!Array.isArray(items)) return [];
|
|
19022
|
-
return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) => (
|
|
20283
|
+
return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) => normalizeLauncherItem(item));
|
|
20284
|
+
}
|
|
20285
|
+
function normalizeLauncherItem(item) {
|
|
20286
|
+
const normalized = { ...item, label: String(item.label) };
|
|
20287
|
+
if (item.href == null) return normalized;
|
|
20288
|
+
const href = safeHref(item.href);
|
|
20289
|
+
if (href !== null) normalized.href = href;
|
|
20290
|
+
else {
|
|
20291
|
+
delete normalized.href;
|
|
20292
|
+
if (typeof normalized.invoke !== "function") normalized.disabled = true;
|
|
20293
|
+
}
|
|
20294
|
+
return normalized;
|
|
19023
20295
|
}
|
|
19024
20296
|
function normalizeSources(sources) {
|
|
19025
20297
|
if (!Array.isArray(sources)) return [];
|
|
19026
20298
|
return sources.filter((source) => source && typeof source === "object" && source.id != null && typeof source.load === "function").map((source) => ({ ...source, id: String(source.id) }));
|
|
19027
20299
|
}
|
|
19028
|
-
function orderedLauncherSourceItems(sources, sourceItems) {
|
|
19029
|
-
return sources.flatMap((source) => sourceItems.get(String(source.id)) ?? []);
|
|
19030
|
-
}
|
|
19031
20300
|
|
|
19032
20301
|
// src/components/avatar/avatar.js
|
|
19033
20302
|
var Avatar = class extends Component {
|
|
@@ -19446,7 +20715,7 @@ var Tooltip = class extends Component {
|
|
|
19446
20715
|
this.#content = this.options.content;
|
|
19447
20716
|
this.#disabled = Boolean(this.options.disabled);
|
|
19448
20717
|
this.#describedBy = resolved.getAttribute("aria-describedby");
|
|
19449
|
-
this.el.style.setProperty("--zx-tooltip-max-inline-size",
|
|
20718
|
+
this.el.style.setProperty("--zx-tooltip-max-inline-size", cssLength3(this.options.maxWidth));
|
|
19450
20719
|
const trigger = TRIGGERS.has(this.options.trigger) ? this.options.trigger : "both";
|
|
19451
20720
|
if (trigger === "manual") return;
|
|
19452
20721
|
if (trigger === "hover" || trigger === "both") {
|
|
@@ -19704,7 +20973,7 @@ function appendToken(list, token) {
|
|
|
19704
20973
|
if (!tokens.includes(token)) tokens.push(token);
|
|
19705
20974
|
return tokens.join(" ");
|
|
19706
20975
|
}
|
|
19707
|
-
function
|
|
20976
|
+
function cssLength3(value) {
|
|
19708
20977
|
return typeof value === "number" && Number.isFinite(value) ? `${value}px` : String(value);
|
|
19709
20978
|
}
|
|
19710
20979
|
function isFocusVisible(element) {
|
|
@@ -19715,10 +20984,10 @@ function isFocusVisible(element) {
|
|
|
19715
20984
|
}
|
|
19716
20985
|
}
|
|
19717
20986
|
|
|
19718
|
-
// src/
|
|
19719
|
-
var
|
|
20987
|
+
// src/internal/app-rail.js
|
|
20988
|
+
var AppRailPresenter = class extends Component {
|
|
19720
20989
|
static cssName = "app-rail";
|
|
19721
|
-
/** @type {Readonly<
|
|
20990
|
+
/** @type {Readonly<AppRailPresenterOptions>} */
|
|
19722
20991
|
static defaults = {
|
|
19723
20992
|
items: [],
|
|
19724
20993
|
active: null,
|
|
@@ -19728,12 +20997,13 @@ var AppRail = class extends Component {
|
|
|
19728
20997
|
header: null,
|
|
19729
20998
|
footer: null,
|
|
19730
20999
|
openDelay: 80,
|
|
19731
|
-
closeDelay: 160
|
|
21000
|
+
closeDelay: 160,
|
|
21001
|
+
renderIcon: null
|
|
19732
21002
|
};
|
|
19733
21003
|
/**
|
|
19734
21004
|
* Creates or enhances a rail host.
|
|
19735
21005
|
* @param {Element|string|null} target Rail target.
|
|
19736
|
-
* @param {
|
|
21006
|
+
* @param {AppRailPresenterOptions} [options={}] Rail options.
|
|
19737
21007
|
*/
|
|
19738
21008
|
constructor(target = null, options = {}) {
|
|
19739
21009
|
super(target, options);
|
|
@@ -19883,7 +21153,14 @@ var AppRail = class extends Component {
|
|
|
19883
21153
|
index: String(item.id),
|
|
19884
21154
|
iconOnly: true,
|
|
19885
21155
|
disclosure: hasChildren,
|
|
19886
|
-
disclosureIcon: railDisclosureIcon(this.el.dataset.side)
|
|
21156
|
+
disclosureIcon: railDisclosureIcon(this.el.dataset.side),
|
|
21157
|
+
visual: appNavIcon(item, this.options.renderIcon, {
|
|
21158
|
+
orientation: this.el.dataset.orientation,
|
|
21159
|
+
collapsed: true,
|
|
21160
|
+
side: this.el.dataset.side,
|
|
21161
|
+
level: 1,
|
|
21162
|
+
location: "root"
|
|
21163
|
+
})
|
|
19887
21164
|
});
|
|
19888
21165
|
const row2 = h("li", { class: "zx-app-rail__row" }, control);
|
|
19889
21166
|
this.refs.list.append(row2);
|
|
@@ -19924,7 +21201,14 @@ var AppRail = class extends Component {
|
|
|
19924
21201
|
index: childKey,
|
|
19925
21202
|
iconOnly: false,
|
|
19926
21203
|
disclosure: hasChildren,
|
|
19927
|
-
disclosureIcon: nestedDisclosureIcon(this.el.dataset.side)
|
|
21204
|
+
disclosureIcon: nestedDisclosureIcon(this.el.dataset.side),
|
|
21205
|
+
visual: appNavIcon(child, this.options.renderIcon, {
|
|
21206
|
+
orientation: this.el.dataset.orientation,
|
|
21207
|
+
collapsed: true,
|
|
21208
|
+
side: this.el.dataset.side,
|
|
21209
|
+
level: childKey.split("/").length,
|
|
21210
|
+
location: "flyout"
|
|
21211
|
+
})
|
|
19928
21212
|
});
|
|
19929
21213
|
list.append(h("li", {}, control));
|
|
19930
21214
|
if (hasChildren) this._buildFlyout(child, control, childKey, key);
|
|
@@ -19978,7 +21262,6 @@ var AppRail = class extends Component {
|
|
|
19978
21262
|
this._interaction.listen(trigger, "click", (event) => {
|
|
19979
21263
|
event.preventDefault();
|
|
19980
21264
|
if (event instanceof MouseEvent && event.detail === 0) this.openFlyout(key, { focus: true });
|
|
19981
|
-
else if (dropdown.isOpen()) this.closeFlyout(key);
|
|
19982
21265
|
else this.openFlyout(key);
|
|
19983
21266
|
});
|
|
19984
21267
|
this._interaction.listen(actualPanel, "pointerenter", () => this._cancelFlyoutTimers(flyout, true));
|
|
@@ -20233,14 +21516,43 @@ var RailListenerScope = class extends Component {
|
|
|
20233
21516
|
};
|
|
20234
21517
|
function normalizeAppItems(items) {
|
|
20235
21518
|
if (!Array.isArray(items)) return [];
|
|
20236
|
-
return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) =>
|
|
20237
|
-
|
|
20238
|
-
|
|
20239
|
-
|
|
20240
|
-
|
|
21519
|
+
return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) => {
|
|
21520
|
+
const normalized = {
|
|
21521
|
+
...item,
|
|
21522
|
+
label: String(item.label),
|
|
21523
|
+
children: Array.isArray(item.children) ? normalizeAppItems(item.children) : []
|
|
21524
|
+
};
|
|
21525
|
+
if (item.href == null) return normalized;
|
|
21526
|
+
const href = safeHref(item.href);
|
|
21527
|
+
if (href !== null) normalized.href = href;
|
|
21528
|
+
else {
|
|
21529
|
+
delete normalized.href;
|
|
21530
|
+
if (normalized.children.length === 0 && typeof normalized.invoke !== "function") {
|
|
21531
|
+
normalized.disabled = true;
|
|
21532
|
+
}
|
|
21533
|
+
}
|
|
21534
|
+
return normalized;
|
|
21535
|
+
});
|
|
20241
21536
|
}
|
|
20242
|
-
function
|
|
20243
|
-
|
|
21537
|
+
function appNavIcon(item, renderer, context) {
|
|
21538
|
+
let visual = typeof renderer === "function" ? renderer(item, context) : null;
|
|
21539
|
+
if (visual == null) visual = item.icon;
|
|
21540
|
+
if (typeof visual === "function") visual = visual(context, item);
|
|
21541
|
+
if (typeof visual === "string") return icon(visual);
|
|
21542
|
+
if (visual instanceof Node) return visual.cloneNode(true);
|
|
21543
|
+
if (visual && typeof visual === "object" && typeof /** @type {any} */
|
|
21544
|
+
visual.toElement === "function") {
|
|
21545
|
+
const element = (
|
|
21546
|
+
/** @type {any} */
|
|
21547
|
+
visual.toElement()
|
|
21548
|
+
);
|
|
21549
|
+
return element instanceof Node ? element.cloneNode(true) : null;
|
|
21550
|
+
}
|
|
21551
|
+
return null;
|
|
21552
|
+
}
|
|
21553
|
+
function navControl(item, { className, index, iconOnly, disclosure, disclosureIcon, visual }) {
|
|
21554
|
+
const href = safeHref(item.href);
|
|
21555
|
+
const tag = href && !disclosure ? "a" : "button";
|
|
20244
21556
|
return (
|
|
20245
21557
|
/** @type {HTMLElement} */
|
|
20246
21558
|
h(
|
|
@@ -20248,13 +21560,14 @@ function navControl(item, { className, index, iconOnly, disclosure, disclosureIc
|
|
|
20248
21560
|
{
|
|
20249
21561
|
class: className,
|
|
20250
21562
|
type: tag === "button" ? "button" : null,
|
|
20251
|
-
href: tag === "a" ?
|
|
21563
|
+
href: tag === "a" ? href : null,
|
|
20252
21564
|
target: tag === "a" ? item.target ?? null : null,
|
|
21565
|
+
rel: tag === "a" ? "noopener" : null,
|
|
20253
21566
|
ariaLabel: iconOnly ? item.label : null,
|
|
20254
21567
|
ariaDisabled: item.disabled ? "true" : null,
|
|
20255
21568
|
dataset: { appNavId: index, appNavValueId: String(item.id) }
|
|
20256
21569
|
},
|
|
20257
|
-
|
|
21570
|
+
visual ? h("span", { class: `${className}-icon`, ariaHidden: "true" }, visual) : null,
|
|
20258
21571
|
h("span", { class: `${className}-label` }, item.label),
|
|
20259
21572
|
item.badge != null ? h("span", { class: `${className}-badge` }, String(item.badge)) : null,
|
|
20260
21573
|
disclosure ? h("span", { class: `${className}-disclosure`, ariaHidden: "true" }, icon(disclosureIcon)) : null
|
|
@@ -20318,47 +21631,28 @@ var AppSidebar = class extends Component {
|
|
|
20318
21631
|
active: null,
|
|
20319
21632
|
expanded: [],
|
|
20320
21633
|
collapsed: false,
|
|
21634
|
+
collapsible: true,
|
|
21635
|
+
orientation: "vertical",
|
|
20321
21636
|
side: "left",
|
|
20322
21637
|
label: "Applications",
|
|
20323
21638
|
header: null,
|
|
20324
21639
|
footer: null,
|
|
20325
21640
|
railHeader: null,
|
|
20326
|
-
railFooter: null
|
|
21641
|
+
railFooter: null,
|
|
21642
|
+
openDelay: 80,
|
|
21643
|
+
closeDelay: 160,
|
|
21644
|
+
renderIcon: null
|
|
20327
21645
|
};
|
|
20328
21646
|
/**
|
|
20329
|
-
* Creates or enhances an application-
|
|
20330
|
-
* @param {Element|string|null} target
|
|
20331
|
-
* @param {AppSidebarOptions} [options={}]
|
|
21647
|
+
* Creates or enhances an application-navigation host.
|
|
21648
|
+
* @param {Element|string|null} [target=null] Navigation target.
|
|
21649
|
+
* @param {AppSidebarOptions} [options={}] Navigation options.
|
|
20332
21650
|
*/
|
|
20333
21651
|
constructor(target = null, options = {}) {
|
|
20334
21652
|
super(target, options);
|
|
20335
|
-
this.
|
|
20336
|
-
|
|
20337
|
-
|
|
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 });
|
|
21653
|
+
this._rail = null;
|
|
21654
|
+
this._railExpandButton = null;
|
|
21655
|
+
this._renderMode();
|
|
20362
21656
|
}
|
|
20363
21657
|
/** @returns {HTMLElement} */
|
|
20364
21658
|
render() {
|
|
@@ -20376,57 +21670,39 @@ var AppSidebar = class extends Component {
|
|
|
20376
21670
|
this._active = this.options.active;
|
|
20377
21671
|
this._expanded = new Set(Array.isArray(this.options.expanded) ? this.options.expanded.map(String) : []);
|
|
20378
21672
|
this._collapsed = Boolean(this.options.collapsed);
|
|
20379
|
-
|
|
20380
|
-
|
|
20381
|
-
|
|
20382
|
-
|
|
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));
|
|
21673
|
+
this._orientation = this.options.orientation === "horizontal" ? "horizontal" : "vertical";
|
|
21674
|
+
this._side = normalizeSide(this._orientation, this.options.side);
|
|
21675
|
+
this._modeAbort = null;
|
|
21676
|
+
root.replaceChildren();
|
|
20408
21677
|
return root;
|
|
20409
21678
|
}
|
|
20410
|
-
/** Replaces the navigation tree in
|
|
21679
|
+
/** Replaces the navigation tree in the active presentation. @param {AppNavItem[]} items @returns {this} */
|
|
20411
21680
|
setItems(items) {
|
|
20412
21681
|
this._items = normalizeAppItems(items);
|
|
20413
21682
|
this._reconcileExpanded();
|
|
20414
|
-
this.
|
|
20415
|
-
this.
|
|
21683
|
+
if (this._rail) this._rail.setItems(this._items);
|
|
21684
|
+
else this._renderTree();
|
|
20416
21685
|
return this;
|
|
20417
21686
|
}
|
|
20418
|
-
/**
|
|
21687
|
+
/** Returns a shallow copy of normalized items. @returns {AppNavItem[]} */
|
|
21688
|
+
getItems() {
|
|
21689
|
+
return this._items.map((item) => ({
|
|
21690
|
+
...item,
|
|
21691
|
+
children: item.children?.map((child) => ({ ...child }))
|
|
21692
|
+
}));
|
|
21693
|
+
}
|
|
21694
|
+
/** Sets the active id without routing. @param {string|number|null} id @returns {this} */
|
|
20419
21695
|
setActive(id) {
|
|
20420
21696
|
this._active = id;
|
|
20421
|
-
this.
|
|
20422
|
-
this.
|
|
21697
|
+
if (this._rail) this._rail.setActive(id);
|
|
21698
|
+
else this._syncActive();
|
|
20423
21699
|
return this;
|
|
20424
21700
|
}
|
|
20425
21701
|
/** Replaces expanded branch ids. @param {Array<string|number>} ids @param {{silent?: boolean}} [options={}] @returns {this} */
|
|
20426
21702
|
setExpanded(ids, { silent = false } = {}) {
|
|
20427
21703
|
this._expanded = new Set(Array.isArray(ids) ? ids.map(String) : []);
|
|
20428
21704
|
this._reconcileExpanded();
|
|
20429
|
-
this._renderTree();
|
|
21705
|
+
if (!this._rail) this._renderTree();
|
|
20430
21706
|
if (!silent) this.emit("branchchange", {
|
|
20431
21707
|
item: null,
|
|
20432
21708
|
id: null,
|
|
@@ -20451,47 +21727,105 @@ var AppSidebar = class extends Component {
|
|
|
20451
21727
|
const expanded = !this._expanded.has(key);
|
|
20452
21728
|
if (expanded) this._expanded.add(key);
|
|
20453
21729
|
else this._expanded.delete(key);
|
|
20454
|
-
this._renderTree();
|
|
20455
|
-
if (!silent) this.emit("branchchange", {
|
|
21730
|
+
if (!this._rail) this._renderTree();
|
|
21731
|
+
if (!silent) this.emit("branchchange", {
|
|
21732
|
+
item,
|
|
21733
|
+
id: item.id,
|
|
21734
|
+
expanded,
|
|
21735
|
+
ids: this.getExpanded()
|
|
21736
|
+
});
|
|
20456
21737
|
return this;
|
|
20457
21738
|
}
|
|
20458
|
-
/** Shows the expanded
|
|
21739
|
+
/** Shows the expanded vertical presentation. @param {{silent?: boolean}} [options={}] @returns {this} */
|
|
20459
21740
|
expand(options = {}) {
|
|
20460
21741
|
return this.setCollapsed(false, options);
|
|
20461
21742
|
}
|
|
20462
|
-
/** Shows the minimized
|
|
21743
|
+
/** Shows the minimized presentation. @param {{silent?: boolean}} [options={}] @returns {this} */
|
|
20463
21744
|
collapse(options = {}) {
|
|
20464
21745
|
return this.setCollapsed(true, options);
|
|
20465
21746
|
}
|
|
20466
|
-
/** Sets expanded/minimized
|
|
21747
|
+
/** Sets the saved vertical expanded/minimized state. @param {boolean} collapsed @param {{silent?: boolean}} [options={}] @returns {this} */
|
|
20467
21748
|
setCollapsed(collapsed, { silent = false } = {}) {
|
|
20468
|
-
const
|
|
20469
|
-
const changed = next !== this._collapsed;
|
|
21749
|
+
const previous = this.isCollapsed();
|
|
20470
21750
|
const active = document.activeElement;
|
|
20471
|
-
const
|
|
20472
|
-
|
|
20473
|
-
|
|
20474
|
-
|
|
20475
|
-
|
|
20476
|
-
|
|
20477
|
-
|
|
20478
|
-
|
|
20479
|
-
|
|
20480
|
-
|
|
20481
|
-
|
|
20482
|
-
|
|
20483
|
-
|
|
21751
|
+
const outgoingOwnedFocus = Boolean(active && (this.el.contains(active) || this._rail?._containsNode(active)));
|
|
21752
|
+
this._collapsed = Boolean(collapsed);
|
|
21753
|
+
const next = this.isCollapsed();
|
|
21754
|
+
if (previous !== next) {
|
|
21755
|
+
this._renderMode();
|
|
21756
|
+
if (outgoingOwnedFocus) {
|
|
21757
|
+
if (next) this._railExpandButton?.focus();
|
|
21758
|
+
else this.refs.collapse?.focus();
|
|
21759
|
+
}
|
|
21760
|
+
if (!silent) this.emit("collapsechange", { collapsed: next });
|
|
21761
|
+
}
|
|
21762
|
+
return this;
|
|
21763
|
+
}
|
|
21764
|
+
/** Reports the effective presentation state; horizontal navigation is always minimized. @returns {boolean} */
|
|
20484
21765
|
isCollapsed() {
|
|
20485
|
-
return this._collapsed;
|
|
21766
|
+
return this._orientation === "horizontal" || this._collapsed;
|
|
21767
|
+
}
|
|
21768
|
+
/**
|
|
21769
|
+
* Changes navigation flow/edge without replacing state.
|
|
21770
|
+
* @param {{orientation?: 'vertical'|'horizontal', side?: 'left'|'right'|'top'|'bottom'}} layout
|
|
21771
|
+
* @param {{silent?: boolean}} [options]
|
|
21772
|
+
* @returns {this}
|
|
21773
|
+
*/
|
|
21774
|
+
setLayout(layout = {}, { silent = false } = {}) {
|
|
21775
|
+
const orientation = layout.orientation === "horizontal" ? "horizontal" : layout.orientation === "vertical" ? "vertical" : this._orientation;
|
|
21776
|
+
const side = normalizeSide(orientation, layout.side ?? this._side);
|
|
21777
|
+
if (orientation === this._orientation && side === this._side) return this;
|
|
21778
|
+
const previous = this.isCollapsed();
|
|
21779
|
+
const active = document.activeElement;
|
|
21780
|
+
const outgoingOwnedFocus = Boolean(active && (this.el.contains(active) || this._rail?._containsNode(active)));
|
|
21781
|
+
const focusedId = outgoingOwnedFocus && active instanceof Element ? active.closest("[data-app-nav-id]")?.getAttribute("data-app-nav-id") ?? null : null;
|
|
21782
|
+
this._orientation = orientation;
|
|
21783
|
+
this._side = side;
|
|
21784
|
+
this._renderMode();
|
|
21785
|
+
const next = this.isCollapsed();
|
|
21786
|
+
if (outgoingOwnedFocus) {
|
|
21787
|
+
const replacement = focusedId === null ? null : [...this.el.querySelectorAll("[data-app-nav-id]")].find((control) => control.getAttribute("data-app-nav-id") === focusedId) ?? null;
|
|
21788
|
+
if (replacement instanceof HTMLElement) replacement.focus();
|
|
21789
|
+
else if (next) this.focus();
|
|
21790
|
+
else if (this.refs.collapse instanceof HTMLElement && !this.refs.collapse.hidden) {
|
|
21791
|
+
this.refs.collapse.focus();
|
|
21792
|
+
} else visibleTreeControls(this.refs.list)[0]?.focus();
|
|
21793
|
+
}
|
|
21794
|
+
if (previous !== next && !silent) this.emit("collapsechange", { collapsed: next });
|
|
21795
|
+
return this;
|
|
20486
21796
|
}
|
|
20487
|
-
/**
|
|
20488
|
-
|
|
20489
|
-
|
|
21797
|
+
/** Opens a minimized descendant flyout. @param {string|number} id @param {{focus?: boolean}} [options={}] @returns {this} */
|
|
21798
|
+
openFlyout(id, options = {}) {
|
|
21799
|
+
this._rail?.openFlyout(id, options);
|
|
21800
|
+
return this;
|
|
21801
|
+
}
|
|
21802
|
+
/** Closes a minimized descendant flyout. @param {string|number} id @returns {this} */
|
|
21803
|
+
closeFlyout(id) {
|
|
21804
|
+
this._rail?.closeFlyout(id);
|
|
21805
|
+
return this;
|
|
20490
21806
|
}
|
|
20491
|
-
/**
|
|
21807
|
+
/** Closes every minimized flyout. @returns {this} */
|
|
21808
|
+
closeAllFlyouts() {
|
|
21809
|
+
this._rail?.closeAllFlyouts();
|
|
21810
|
+
return this;
|
|
21811
|
+
}
|
|
21812
|
+
/** Reports whether a minimized flyout is open. @param {string|number} id @returns {boolean} */
|
|
21813
|
+
isFlyoutOpen(id) {
|
|
21814
|
+
return this._rail?.isFlyoutOpen(id) ?? false;
|
|
21815
|
+
}
|
|
21816
|
+
/** Focuses the first navigation action. @returns {this} */
|
|
21817
|
+
focus() {
|
|
21818
|
+
if (this._rail) this._rail.focus();
|
|
21819
|
+
else visibleTreeControls(this.refs.list)[0]?.focus();
|
|
21820
|
+
return this;
|
|
21821
|
+
}
|
|
21822
|
+
/** Destroys flyouts and restores/removes the host. @returns {void} */
|
|
20492
21823
|
destroy() {
|
|
20493
21824
|
const original = this._original;
|
|
20494
|
-
this.
|
|
21825
|
+
this._modeAbort?.abort();
|
|
21826
|
+
this._modeAbort = null;
|
|
21827
|
+
this._rail?.destroy();
|
|
21828
|
+
this._rail = null;
|
|
20495
21829
|
super.destroy();
|
|
20496
21830
|
if (!this._createdRoot && original) {
|
|
20497
21831
|
for (const attribute of Array.from(this.el.attributes)) this.el.removeAttribute(attribute.name);
|
|
@@ -20499,8 +21833,99 @@ var AppSidebar = class extends Component {
|
|
|
20499
21833
|
this.el.replaceChildren(...original.children);
|
|
20500
21834
|
}
|
|
20501
21835
|
}
|
|
21836
|
+
/** Mounts exactly one expanded tree or minimized flyout presentation. @returns {void} */
|
|
21837
|
+
_renderMode() {
|
|
21838
|
+
this._modeAbort?.abort();
|
|
21839
|
+
this._modeAbort = new AbortController();
|
|
21840
|
+
this._rail?.destroy();
|
|
21841
|
+
this._rail = null;
|
|
21842
|
+
this._railExpandButton = null;
|
|
21843
|
+
for (const key of ["expanded", "list", "collapse", "rail"]) delete this.refs[key];
|
|
21844
|
+
this.el.replaceChildren();
|
|
21845
|
+
this.el.dataset.orientation = this._orientation;
|
|
21846
|
+
this.el.dataset.side = this._side;
|
|
21847
|
+
this.el.dataset.collapsed = String(this.isCollapsed());
|
|
21848
|
+
if (this.isCollapsed()) this._renderRail();
|
|
21849
|
+
else this._renderExpanded();
|
|
21850
|
+
}
|
|
21851
|
+
/** @returns {void} */
|
|
21852
|
+
_renderExpanded() {
|
|
21853
|
+
const headerContent = h("div", { class: "zx-app-sidebar__header-content" });
|
|
21854
|
+
appendSlot2(headerContent, this.options.header);
|
|
21855
|
+
const collapseButton = h("button", {
|
|
21856
|
+
class: "zx-app-sidebar__collapse zx-icon-btn",
|
|
21857
|
+
type: "button",
|
|
21858
|
+
ariaLabel: "Minimize application sidebar"
|
|
21859
|
+
}, icon(this._side === "right" ? "chevron-right" : "chevron-left"));
|
|
21860
|
+
collapseButton.hidden = !this.options.collapsible;
|
|
21861
|
+
const footer = h("footer", { class: "zx-app-sidebar__footer" });
|
|
21862
|
+
appendSlot2(footer, this.options.footer);
|
|
21863
|
+
footer.hidden = !footer.hasChildNodes();
|
|
21864
|
+
const list = h("ul", { class: "zx-app-sidebar__list" });
|
|
21865
|
+
const expanded = h(
|
|
21866
|
+
"div",
|
|
21867
|
+
{ class: "zx-app-sidebar__expanded" },
|
|
21868
|
+
h("header", { class: "zx-app-sidebar__header" }, headerContent, collapseButton),
|
|
21869
|
+
h("nav", { class: "zx-app-sidebar__nav", ariaLabel: this.options.label }, list),
|
|
21870
|
+
footer
|
|
21871
|
+
);
|
|
21872
|
+
this.refs.expanded = expanded;
|
|
21873
|
+
this.refs.list = list;
|
|
21874
|
+
this.refs.collapse = collapseButton;
|
|
21875
|
+
this.el.append(expanded);
|
|
21876
|
+
this._listenMode(collapseButton, "click", () => this.collapse());
|
|
21877
|
+
this._listenMode(list, "click", (event) => this._onTreeClick(event));
|
|
21878
|
+
this._listenMode(list, "keydown", (event) => this._onTreeKeydown(
|
|
21879
|
+
/** @type {KeyboardEvent} */
|
|
21880
|
+
event
|
|
21881
|
+
));
|
|
21882
|
+
this._renderTree();
|
|
21883
|
+
}
|
|
21884
|
+
/** @returns {void} */
|
|
21885
|
+
_renderRail() {
|
|
21886
|
+
const railHost = h("nav", { class: "zx-app-sidebar__rail" });
|
|
21887
|
+
this.el.append(railHost);
|
|
21888
|
+
this.refs.rail = railHost;
|
|
21889
|
+
const showToggle = this._orientation === "vertical" && Boolean(this.options.collapsible);
|
|
21890
|
+
const railHeader = h("div", { class: "zx-app-sidebar__rail-header" });
|
|
21891
|
+
if (showToggle) {
|
|
21892
|
+
const expandButton = h("button", {
|
|
21893
|
+
class: "zx-app-sidebar__rail-toggle zx-icon-btn",
|
|
21894
|
+
type: "button",
|
|
21895
|
+
ariaLabel: "Expand application sidebar"
|
|
21896
|
+
}, icon(this._side === "right" ? "chevron-left" : "chevron-right"));
|
|
21897
|
+
railHeader.append(expandButton);
|
|
21898
|
+
this._railExpandButton = expandButton;
|
|
21899
|
+
this._listenMode(expandButton, "click", () => this.expand());
|
|
21900
|
+
}
|
|
21901
|
+
appendSlot2(railHeader, this.options.railHeader);
|
|
21902
|
+
this._rail = new AppRailPresenter(railHost, {
|
|
21903
|
+
items: this._items,
|
|
21904
|
+
active: this._active,
|
|
21905
|
+
orientation: this._orientation,
|
|
21906
|
+
side: this._side,
|
|
21907
|
+
label: this.options.label,
|
|
21908
|
+
header: railHeader.hasChildNodes() ? railHeader : null,
|
|
21909
|
+
footer: this.options.railFooter,
|
|
21910
|
+
openDelay: this.options.openDelay,
|
|
21911
|
+
closeDelay: this.options.closeDelay,
|
|
21912
|
+
renderIcon: this.options.renderIcon,
|
|
21913
|
+
onselect: (event) => {
|
|
21914
|
+
const selected = this.emit("select", event.detail);
|
|
21915
|
+
if (selected.defaultPrevented) event.preventDefault();
|
|
21916
|
+
},
|
|
21917
|
+
onflyoutchange: (event) => this.emit("flyoutchange", event.detail)
|
|
21918
|
+
});
|
|
21919
|
+
this._listenMode(railHost, "zx-select", (event) => event.stopPropagation());
|
|
21920
|
+
this._listenMode(railHost, "zx-flyoutchange", (event) => event.stopPropagation());
|
|
21921
|
+
}
|
|
21922
|
+
/** @param {EventTarget} target @param {string} type @param {EventListener} listener @returns {void} */
|
|
21923
|
+
_listenMode(target, type, listener) {
|
|
21924
|
+
target.addEventListener(type, listener, { signal: this._modeAbort.signal });
|
|
21925
|
+
}
|
|
20502
21926
|
/** @returns {void} */
|
|
20503
21927
|
_renderTree() {
|
|
21928
|
+
if (!this.refs.list) return;
|
|
20504
21929
|
const focused = this.refs.list.contains(document.activeElement) ? (
|
|
20505
21930
|
/** @type {Element} */
|
|
20506
21931
|
document.activeElement.closest("[data-app-nav-id]")?.getAttribute("data-app-nav-id")
|
|
@@ -20519,7 +21944,14 @@ var AppSidebar = class extends Component {
|
|
|
20519
21944
|
const key = String(item.id);
|
|
20520
21945
|
const expanded = hasChildren && this._expanded.has(key);
|
|
20521
21946
|
const childId = hasChildren ? uid("zx-app-sidebar-branch") : null;
|
|
20522
|
-
const
|
|
21947
|
+
const visual = appNavIcon(item, this.options.renderIcon, {
|
|
21948
|
+
orientation: this._orientation,
|
|
21949
|
+
collapsed: false,
|
|
21950
|
+
side: this._side,
|
|
21951
|
+
level,
|
|
21952
|
+
location: level === 1 ? "root" : "inline"
|
|
21953
|
+
});
|
|
21954
|
+
const control = navControl2(item, hasChildren, expanded, childId, visual);
|
|
20523
21955
|
const row2 = h("li", {
|
|
20524
21956
|
class: "zx-app-sidebar__row",
|
|
20525
21957
|
dataset: { level }
|
|
@@ -20533,7 +21965,7 @@ var AppSidebar = class extends Component {
|
|
|
20533
21965
|
}
|
|
20534
21966
|
return row2;
|
|
20535
21967
|
}
|
|
20536
|
-
/**
|
|
21968
|
+
/** @param {Event} event @returns {void} */
|
|
20537
21969
|
_onTreeClick(event) {
|
|
20538
21970
|
const control = (
|
|
20539
21971
|
/** @type {Element|null} */
|
|
@@ -20548,6 +21980,45 @@ var AppSidebar = class extends Component {
|
|
|
20548
21980
|
if (item.children?.length) this.toggleBranch(item.id);
|
|
20549
21981
|
else this._select(item, event);
|
|
20550
21982
|
}
|
|
21983
|
+
/** @param {KeyboardEvent} event @returns {void} */
|
|
21984
|
+
_onTreeKeydown(event) {
|
|
21985
|
+
const current = (
|
|
21986
|
+
/** @type {Element|null} */
|
|
21987
|
+
event.target?.closest?.("[data-app-nav-id]")
|
|
21988
|
+
);
|
|
21989
|
+
if (!(current instanceof HTMLElement)) return;
|
|
21990
|
+
const item = findItem(this._items, current.getAttribute("data-app-nav-id"));
|
|
21991
|
+
const controls = visibleTreeControls(this.refs.list);
|
|
21992
|
+
const index = controls.indexOf(current);
|
|
21993
|
+
if (event.key === "ArrowRight" && item?.children?.length) {
|
|
21994
|
+
event.preventDefault();
|
|
21995
|
+
if (!this._expanded.has(String(item.id))) this.toggleBranch(item.id);
|
|
21996
|
+
else visibleTreeControls(current.parentElement?.querySelector(".zx-app-sidebar__sublist"))[0]?.focus();
|
|
21997
|
+
return;
|
|
21998
|
+
}
|
|
21999
|
+
if (event.key === "ArrowLeft") {
|
|
22000
|
+
if (item?.children?.length && this._expanded.has(String(item.id))) {
|
|
22001
|
+
event.preventDefault();
|
|
22002
|
+
this.toggleBranch(item.id);
|
|
22003
|
+
return;
|
|
22004
|
+
}
|
|
22005
|
+
const parentList = current.closest(".zx-app-sidebar__sublist");
|
|
22006
|
+
const parentControl = parentList?.parentElement?.querySelector(":scope > [data-app-nav-id]");
|
|
22007
|
+
if (parentControl instanceof HTMLElement) {
|
|
22008
|
+
event.preventDefault();
|
|
22009
|
+
parentControl.focus();
|
|
22010
|
+
}
|
|
22011
|
+
return;
|
|
22012
|
+
}
|
|
22013
|
+
let nextIndex;
|
|
22014
|
+
if (event.key === "Home") nextIndex = 0;
|
|
22015
|
+
else if (event.key === "End") nextIndex = controls.length - 1;
|
|
22016
|
+
else if (event.key === "ArrowDown") nextIndex = Math.min(controls.length - 1, index + 1);
|
|
22017
|
+
else if (event.key === "ArrowUp") nextIndex = Math.max(0, index - 1);
|
|
22018
|
+
else return;
|
|
22019
|
+
event.preventDefault();
|
|
22020
|
+
controls[nextIndex]?.focus();
|
|
22021
|
+
}
|
|
20551
22022
|
/** @param {AppNavItem} item @param {Event} event @returns {void} */
|
|
20552
22023
|
_select(item, event) {
|
|
20553
22024
|
if (item.disabled) {
|
|
@@ -20569,14 +22040,14 @@ var AppSidebar = class extends Component {
|
|
|
20569
22040
|
}
|
|
20570
22041
|
/** @returns {void} */
|
|
20571
22042
|
_syncActive() {
|
|
22043
|
+
if (!this.refs.list) return;
|
|
20572
22044
|
const activeId = this._active == null ? null : String(this._active);
|
|
20573
22045
|
for (const control of this.refs.list.querySelectorAll("[data-app-nav-id]")) {
|
|
20574
22046
|
const item = findItem(this._items, control.getAttribute("data-app-nav-id"));
|
|
20575
22047
|
const direct = activeId !== null && control.getAttribute("data-app-nav-id") === activeId;
|
|
20576
22048
|
const descendant = activeId !== null && item ? containsItemId2(item.children ?? [], activeId) : false;
|
|
20577
|
-
if (direct)
|
|
20578
|
-
|
|
20579
|
-
} else control.removeAttribute("aria-current");
|
|
22049
|
+
if (direct) control.setAttribute("aria-current", "page");
|
|
22050
|
+
else control.removeAttribute("aria-current");
|
|
20580
22051
|
if (descendant) control.dataset.activeDescendant = "true";
|
|
20581
22052
|
else delete control.dataset.activeDescendant;
|
|
20582
22053
|
}
|
|
@@ -20590,7 +22061,7 @@ var AppSidebar = class extends Component {
|
|
|
20590
22061
|
for (const id of this._expanded) if (!branchIds.has(id)) this._expanded.delete(id);
|
|
20591
22062
|
}
|
|
20592
22063
|
};
|
|
20593
|
-
function navControl2(item, disclosure, expanded, controls) {
|
|
22064
|
+
function navControl2(item, disclosure, expanded, controls, visual) {
|
|
20594
22065
|
const tag = item.href && !disclosure ? "a" : "button";
|
|
20595
22066
|
return (
|
|
20596
22067
|
/** @type {HTMLElement} */
|
|
@@ -20606,7 +22077,7 @@ function navControl2(item, disclosure, expanded, controls) {
|
|
|
20606
22077
|
ariaControls: controls,
|
|
20607
22078
|
dataset: { appNavId: String(item.id) }
|
|
20608
22079
|
},
|
|
20609
|
-
|
|
22080
|
+
visual ? h("span", { class: "zx-app-sidebar__item-icon", ariaHidden: "true" }, visual) : null,
|
|
20610
22081
|
h("span", { class: "zx-app-sidebar__item-label" }, item.label),
|
|
20611
22082
|
item.badge != null ? h("span", { class: "zx-app-sidebar__item-badge" }, String(item.badge)) : null,
|
|
20612
22083
|
disclosure ? h(
|
|
@@ -20617,6 +22088,13 @@ function navControl2(item, disclosure, expanded, controls) {
|
|
|
20617
22088
|
)
|
|
20618
22089
|
);
|
|
20619
22090
|
}
|
|
22091
|
+
function visibleTreeControls(root) {
|
|
22092
|
+
if (!root) return [];
|
|
22093
|
+
return (
|
|
22094
|
+
/** @type {HTMLElement[]} */
|
|
22095
|
+
[...root.querySelectorAll("[data-app-nav-id]")].filter((control) => !control.closest(".zx-app-sidebar__sublist[hidden]"))
|
|
22096
|
+
);
|
|
22097
|
+
}
|
|
20620
22098
|
function visibleTreeControl(control) {
|
|
20621
22099
|
let candidate = (
|
|
20622
22100
|
/** @type {HTMLElement|null} */
|
|
@@ -20649,12 +22127,17 @@ function containsItemId2(items, id) {
|
|
|
20649
22127
|
}
|
|
20650
22128
|
function appendSlot2(target, content) {
|
|
20651
22129
|
if (content == null) return;
|
|
20652
|
-
if (typeof content === "string" || typeof content === "number")
|
|
20653
|
-
|
|
22130
|
+
if (typeof content === "string" || typeof content === "number") {
|
|
22131
|
+
target.append(document.createTextNode(String(content)));
|
|
22132
|
+
} else if (typeof content === "object" && typeof content.toElement === "function") {
|
|
20654
22133
|
const element = content.toElement();
|
|
20655
22134
|
if (element) target.append(element);
|
|
20656
22135
|
} else if (typeof content === "object" && typeof content.nodeType === "number") target.append(content);
|
|
20657
22136
|
}
|
|
22137
|
+
function normalizeSide(orientation, side) {
|
|
22138
|
+
if (orientation === "horizontal") return side === "bottom" ? "bottom" : "top";
|
|
22139
|
+
return side === "right" ? "right" : "left";
|
|
22140
|
+
}
|
|
20658
22141
|
|
|
20659
22142
|
// src/components/message/message.js
|
|
20660
22143
|
var MessageQueue = class {
|
|
@@ -21991,7 +23474,7 @@ var Grid = class _Grid extends Table {
|
|
|
21991
23474
|
* @param {GridOptions & import('../table/table.js').TableOptions} [options={}] Grid/Table options.
|
|
21992
23475
|
*/
|
|
21993
23476
|
constructor(target = null, options = {}) {
|
|
21994
|
-
super(target, options);
|
|
23477
|
+
super(target, { editTrigger: "single", ...options });
|
|
21995
23478
|
}
|
|
21996
23479
|
/**
|
|
21997
23480
|
* Creates an editable hierarchical billing-line grid.
|
|
@@ -22022,14 +23505,17 @@ function billingItemsConfig(options = {}) {
|
|
|
22022
23505
|
} = options;
|
|
22023
23506
|
const fields = { ...DEFAULT_FIELDS, ...requestedFields };
|
|
22024
23507
|
const calculateLineTotal = typeof lineTotal === "function" ? lineTotal : (row2) => {
|
|
22025
|
-
const quantity =
|
|
22026
|
-
const price =
|
|
22027
|
-
return
|
|
23508
|
+
const quantity = billingNumber(row2?.[fields.quantity]);
|
|
23509
|
+
const price = billingNumber(row2?.[fields.unitPrice]);
|
|
23510
|
+
return quantity !== null && price !== null ? quantity * price : null;
|
|
22028
23511
|
};
|
|
22029
23512
|
const columns = Array.isArray(suppliedColumns) ? suppliedColumns.map((column) => ({ ...column })) : billingColumns(fields, { units, currencies, currency, locale, decimals, columnOverrides });
|
|
22030
23513
|
return {
|
|
22031
23514
|
rowId: fields.id,
|
|
22032
23515
|
editMode: "cell",
|
|
23516
|
+
editTrigger: "single",
|
|
23517
|
+
rowReorder: true,
|
|
23518
|
+
columnVisibility: true,
|
|
22033
23519
|
hierarchy: { parentId: fields.parent, column: fields.item, expanded: true },
|
|
22034
23520
|
responsive: "sm",
|
|
22035
23521
|
...tableOptions,
|
|
@@ -22129,6 +23615,11 @@ function billingColumns(fields, { units, currencies, currency, locale, decimals,
|
|
|
22129
23615
|
function hasChoices(value) {
|
|
22130
23616
|
return Array.isArray(value) ? value.length > 0 : Boolean(value && typeof value === "object" && Object.keys(value).length);
|
|
22131
23617
|
}
|
|
23618
|
+
function billingNumber(value) {
|
|
23619
|
+
if (value == null || typeof value === "string" && value.trim() === "") return null;
|
|
23620
|
+
const number2 = Number(value);
|
|
23621
|
+
return Number.isFinite(number2) ? number2 : null;
|
|
23622
|
+
}
|
|
22132
23623
|
|
|
22133
23624
|
// src/components/chart/chart.js
|
|
22134
23625
|
var Chart = class extends Component {
|
|
@@ -22176,6 +23667,7 @@ var Chart = class extends Component {
|
|
|
22176
23667
|
this._chartOptions = cloneChartValue(this.options.chartOptions);
|
|
22177
23668
|
this._loading = Boolean(this.options.loading);
|
|
22178
23669
|
this._error = null;
|
|
23670
|
+
this._hasError = false;
|
|
22179
23671
|
this._handle = null;
|
|
22180
23672
|
this._destroyed = false;
|
|
22181
23673
|
const descriptionId = uid("zx-chart-description");
|
|
@@ -22221,6 +23713,7 @@ var Chart = class extends Component {
|
|
|
22221
23713
|
setData(data) {
|
|
22222
23714
|
this._data = normalizeChartData(data);
|
|
22223
23715
|
this._error = null;
|
|
23716
|
+
this._hasError = false;
|
|
22224
23717
|
this._loading = false;
|
|
22225
23718
|
this._renderSummary();
|
|
22226
23719
|
this._mountOrUpdate(true);
|
|
@@ -22254,8 +23747,9 @@ var Chart = class extends Component {
|
|
|
22254
23747
|
return this;
|
|
22255
23748
|
}
|
|
22256
23749
|
/** Shows an error and destroys the current engine. @param {unknown} error @returns {this} */
|
|
22257
|
-
setError(
|
|
22258
|
-
this._error =
|
|
23750
|
+
setError(error2) {
|
|
23751
|
+
this._error = error2 ?? new Error(String(this.options.errorText));
|
|
23752
|
+
this._hasError = true;
|
|
22259
23753
|
this._loading = false;
|
|
22260
23754
|
this._destroyHandle();
|
|
22261
23755
|
this._syncState();
|
|
@@ -22265,6 +23759,7 @@ var Chart = class extends Component {
|
|
|
22265
23759
|
/** Clears the error and remounts when data and an adapter are available. @returns {this} */
|
|
22266
23760
|
clearError() {
|
|
22267
23761
|
this._error = null;
|
|
23762
|
+
this._hasError = false;
|
|
22268
23763
|
this._mountOrUpdate(false);
|
|
22269
23764
|
return this;
|
|
22270
23765
|
}
|
|
@@ -22272,8 +23767,8 @@ var Chart = class extends Component {
|
|
|
22272
23767
|
resize() {
|
|
22273
23768
|
try {
|
|
22274
23769
|
this._handle?.resize?.();
|
|
22275
|
-
} catch (
|
|
22276
|
-
this.setError(
|
|
23770
|
+
} catch (error2) {
|
|
23771
|
+
this.setError(error2);
|
|
22277
23772
|
}
|
|
22278
23773
|
return this;
|
|
22279
23774
|
}
|
|
@@ -22303,7 +23798,7 @@ var Chart = class extends Component {
|
|
|
22303
23798
|
/** @param {boolean} emitUpdate @returns {void} */
|
|
22304
23799
|
_mountOrUpdate(emitUpdate) {
|
|
22305
23800
|
this._syncState();
|
|
22306
|
-
if (this._loading || this.
|
|
23801
|
+
if (this._loading || this._hasError || isEmptyChartData(this._data)) {
|
|
22307
23802
|
this._destroyHandle();
|
|
22308
23803
|
return;
|
|
22309
23804
|
}
|
|
@@ -22326,8 +23821,8 @@ var Chart = class extends Component {
|
|
|
22326
23821
|
this.emit("ready", { instance: this.getInstance() });
|
|
22327
23822
|
}
|
|
22328
23823
|
if (emitUpdate) this.emit("update", { data: this._data, type: this._type });
|
|
22329
|
-
} catch (
|
|
22330
|
-
this.setError(
|
|
23824
|
+
} catch (error2) {
|
|
23825
|
+
this.setError(error2);
|
|
22331
23826
|
}
|
|
22332
23827
|
}
|
|
22333
23828
|
/** @returns {ChartSpec} */
|
|
@@ -22358,7 +23853,7 @@ var Chart = class extends Component {
|
|
|
22358
23853
|
/** @returns {void} */
|
|
22359
23854
|
_syncState() {
|
|
22360
23855
|
const empty = isEmptyChartData(this._data);
|
|
22361
|
-
const state = this._loading ? "loading" : this.
|
|
23856
|
+
const state = this._loading ? "loading" : this._hasError ? "error" : empty ? "empty" : "ready";
|
|
22362
23857
|
this.el.dataset.state = state;
|
|
22363
23858
|
this.refs.viewport.hidden = state !== "ready";
|
|
22364
23859
|
let text4 = "";
|
|
@@ -22569,8 +24064,9 @@ function themeChartOptions(root) {
|
|
|
22569
24064
|
borderColor: borderColor || void 0
|
|
22570
24065
|
};
|
|
22571
24066
|
}
|
|
22572
|
-
function errorMessage(
|
|
22573
|
-
|
|
24067
|
+
function errorMessage(error2, fallback) {
|
|
24068
|
+
const message = error2 && typeof error2 === "object" && "message" in error2 ? String(error2.message) : "";
|
|
24069
|
+
return message.trim() ? message : String(fallback);
|
|
22574
24070
|
}
|
|
22575
24071
|
|
|
22576
24072
|
// src/components/tree/hierarchy.js
|
|
@@ -23208,8 +24704,8 @@ var TreeView = class extends Component {
|
|
|
23208
24704
|
node[this._childrenKey] = Array.isArray(children) ? children : [];
|
|
23209
24705
|
if (node[this._childrenKey].length === 0) node.hasChildren = false;
|
|
23210
24706
|
return true;
|
|
23211
|
-
} catch (
|
|
23212
|
-
this.emit("error", { error, node, id });
|
|
24707
|
+
} catch (error2) {
|
|
24708
|
+
this.emit("error", { error: error2, node, id });
|
|
23213
24709
|
return false;
|
|
23214
24710
|
} finally {
|
|
23215
24711
|
this._loading.delete(id);
|
|
@@ -23697,9 +25193,9 @@ var Finder = class extends Component {
|
|
|
23697
25193
|
const children = await this.options.load(node);
|
|
23698
25194
|
node[this._childrenKey] = Array.isArray(children) ? children : [];
|
|
23699
25195
|
if (node[this._childrenKey].length === 0) node.hasChildren = false;
|
|
23700
|
-
} catch (
|
|
25196
|
+
} catch (error2) {
|
|
23701
25197
|
node[this._childrenKey] = [];
|
|
23702
|
-
this.emit("error", { error, node, id });
|
|
25198
|
+
this.emit("error", { error: error2, node, id });
|
|
23703
25199
|
} finally {
|
|
23704
25200
|
this._loading.delete(id);
|
|
23705
25201
|
}
|
|
@@ -24129,6 +25625,821 @@ function restoreCustomElements(origins) {
|
|
|
24129
25625
|
}
|
|
24130
25626
|
}
|
|
24131
25627
|
|
|
25628
|
+
// src/components/filter/filter-model.js
|
|
25629
|
+
var filterOperators = Object.freeze([
|
|
25630
|
+
op("contains", "contains", "single", ["text"]),
|
|
25631
|
+
op("notContains", "does not contain", "single", ["text"]),
|
|
25632
|
+
op("eq", "is", "single", ["text", "number", "money", "boolean", "enum", "status", "priority", "country", "currency", "unit", "entity", "date", "datetime"]),
|
|
25633
|
+
op("neq", "is not", "single", ["text", "number", "money", "boolean", "enum", "status", "priority", "country", "currency", "unit", "entity", "date", "datetime"]),
|
|
25634
|
+
op("startsWith", "starts with", "single", ["text"]),
|
|
25635
|
+
op("endsWith", "ends with", "single", ["text"]),
|
|
25636
|
+
op("lt", "is less than", "single", ["number", "money"]),
|
|
25637
|
+
op("lte", "is at most", "single", ["number", "money"]),
|
|
25638
|
+
op("gt", "is greater than", "single", ["number", "money"]),
|
|
25639
|
+
op("gte", "is at least", "single", ["number", "money"]),
|
|
25640
|
+
op("on", "is on", "single", ["date", "datetime"]),
|
|
25641
|
+
op("notOn", "is not on", "single", ["date", "datetime"]),
|
|
25642
|
+
op("before", "is before", "single", ["date", "datetime"]),
|
|
25643
|
+
op("onOrBefore", "is on or before", "single", ["date", "datetime"]),
|
|
25644
|
+
op("after", "is after", "single", ["date", "datetime"]),
|
|
25645
|
+
op("onOrAfter", "is on or after", "single", ["date", "datetime"]),
|
|
25646
|
+
op("between", "is between", "pair", ["number", "money", "date", "datetime"]),
|
|
25647
|
+
op("notBetween", "is not between", "pair", ["number", "money", "date", "datetime"]),
|
|
25648
|
+
op("withinLast", "is within the last", "pair", ["date", "datetime"]),
|
|
25649
|
+
op("withinNext", "is within the next", "pair", ["date", "datetime"]),
|
|
25650
|
+
op("anyOf", "is any of", "many", ["enum", "status", "priority", "country", "currency", "unit", "entity"]),
|
|
25651
|
+
op("noneOf", "is none of", "many", ["enum", "status", "priority", "country", "currency", "unit", "entity"]),
|
|
25652
|
+
op("containsAny", "contains any", "many", ["tags"]),
|
|
25653
|
+
op("containsAll", "contains all", "many", ["tags"]),
|
|
25654
|
+
op("containsNone", "contains none", "many", ["tags"]),
|
|
25655
|
+
op("isEmpty", "is empty", "none", ["text", "number", "money", "date", "datetime", "boolean", "enum", "status", "priority", "country", "currency", "unit", "entity", "tags"]),
|
|
25656
|
+
op("isNotEmpty", "is not empty", "none", ["text", "number", "money", "date", "datetime", "boolean", "enum", "status", "priority", "country", "currency", "unit", "entity", "tags"])
|
|
25657
|
+
]);
|
|
25658
|
+
function emptyFilterAst() {
|
|
25659
|
+
return { version: 1, root: { kind: "group", id: uid("filter-root"), logic: "and", children: [] } };
|
|
25660
|
+
}
|
|
25661
|
+
function filterCondition(initial = {}) {
|
|
25662
|
+
return {
|
|
25663
|
+
kind: "condition",
|
|
25664
|
+
id: String(initial.id || uid("filter-condition")),
|
|
25665
|
+
field: String(initial.field ?? ""),
|
|
25666
|
+
operator: String(initial.operator ?? ""),
|
|
25667
|
+
value: cloneFilterValue(initial.value ?? null)
|
|
25668
|
+
};
|
|
25669
|
+
}
|
|
25670
|
+
function cloneFilterValue(value) {
|
|
25671
|
+
return cloneJson(value);
|
|
25672
|
+
}
|
|
25673
|
+
function filterGroup(logic = "and", children = []) {
|
|
25674
|
+
return { kind: "group", id: uid("filter-group"), logic, children };
|
|
25675
|
+
}
|
|
25676
|
+
function parseFilterAst(value, limits = {}) {
|
|
25677
|
+
const maxDepth = finiteLimit(limits.maxDepth, 3);
|
|
25678
|
+
const maxConditions = finiteLimit(limits.maxConditions, 50);
|
|
25679
|
+
if (!plainObject(value) || value.version !== 1 || !plainObject(value.root)) {
|
|
25680
|
+
throw new TypeError("Filter AST must have version 1 and a root group");
|
|
25681
|
+
}
|
|
25682
|
+
const ids = /* @__PURE__ */ new Set();
|
|
25683
|
+
let conditions = 0;
|
|
25684
|
+
const visit = (raw2, depth, root2 = false) => {
|
|
25685
|
+
if (!plainObject(raw2)) throw new TypeError("Filter nodes must be plain objects");
|
|
25686
|
+
const id = String(raw2.id ?? "").trim();
|
|
25687
|
+
if (!id || ids.has(id)) throw new TypeError("Filter node IDs must be non-empty and unique");
|
|
25688
|
+
ids.add(id);
|
|
25689
|
+
if (raw2.kind === "group") {
|
|
25690
|
+
if (depth > maxDepth) throw new RangeError("Filter group depth exceeds maxDepth");
|
|
25691
|
+
if (raw2.logic !== "and" && raw2.logic !== "or") throw new TypeError("Filter group logic must be and or or");
|
|
25692
|
+
if (!Array.isArray(raw2.children)) throw new TypeError("Filter group children must be an array");
|
|
25693
|
+
return {
|
|
25694
|
+
kind: "group",
|
|
25695
|
+
id,
|
|
25696
|
+
logic: raw2.logic,
|
|
25697
|
+
children: raw2.children.map((child) => visit(child, depth + 1, false))
|
|
25698
|
+
};
|
|
25699
|
+
}
|
|
25700
|
+
if (root2 || raw2.kind !== "condition") throw new TypeError("Filter root must be a group");
|
|
25701
|
+
conditions += 1;
|
|
25702
|
+
if (conditions > maxConditions) throw new RangeError("Filter condition count exceeds maxConditions");
|
|
25703
|
+
return {
|
|
25704
|
+
kind: "condition",
|
|
25705
|
+
id,
|
|
25706
|
+
field: String(raw2.field ?? ""),
|
|
25707
|
+
operator: String(raw2.operator ?? ""),
|
|
25708
|
+
value: cloneJson(raw2.value)
|
|
25709
|
+
};
|
|
25710
|
+
};
|
|
25711
|
+
const root = visit(value.root, 0, true);
|
|
25712
|
+
if (root.kind !== "group") throw new TypeError("Filter root must be a group");
|
|
25713
|
+
return (
|
|
25714
|
+
/** @type {FilterAst} */
|
|
25715
|
+
{ version: 1, root }
|
|
25716
|
+
);
|
|
25717
|
+
}
|
|
25718
|
+
function cloneFilterAst(value) {
|
|
25719
|
+
return parseFilterAst(value, { maxDepth: Infinity, maxConditions: Infinity });
|
|
25720
|
+
}
|
|
25721
|
+
function stringifyFilterAst(value) {
|
|
25722
|
+
return JSON.stringify(cloneFilterAst(value));
|
|
25723
|
+
}
|
|
25724
|
+
function validateFilterAst(ast, fields, operators = filterOperators) {
|
|
25725
|
+
const errors = [];
|
|
25726
|
+
const fieldMap = new Map((Array.isArray(fields) ? fields : []).map((field) => [String(field.id), field]));
|
|
25727
|
+
const operatorMap = new Map((Array.isArray(operators) ? operators : []).map((operator) => [String(operator.id), operator]));
|
|
25728
|
+
const visit = (node, root = false) => {
|
|
25729
|
+
if (node.kind === "group") {
|
|
25730
|
+
if (!root && node.children.length === 0) errors.push(error(node.id, "empty-group", "Nested groups need at least one condition"));
|
|
25731
|
+
node.children.forEach((child) => visit(child));
|
|
25732
|
+
return;
|
|
25733
|
+
}
|
|
25734
|
+
const field = fieldMap.get(node.field);
|
|
25735
|
+
if (!field) errors.push(error(node.id, "unknown-field", node.field ? `Unavailable field: ${node.field}` : "Choose a field"));
|
|
25736
|
+
const operator = operatorMap.get(node.operator);
|
|
25737
|
+
if (!operator) {
|
|
25738
|
+
errors.push(error(node.id, "unknown-operator", node.operator ? `Unavailable operator: ${node.operator}` : "Choose an operator"));
|
|
25739
|
+
return;
|
|
25740
|
+
}
|
|
25741
|
+
if (field) {
|
|
25742
|
+
const allowed = Array.isArray(field.operators) && field.operators.length ? field.operators : operators.filter((candidate) => candidate.types.includes(field.type)).map((candidate) => candidate.id);
|
|
25743
|
+
if (!allowed.includes(operator.id) || !operator.types.includes(field.type)) {
|
|
25744
|
+
errors.push(error(node.id, "operator-type", `${operator.label} is not available for ${field.label}`));
|
|
25745
|
+
}
|
|
25746
|
+
}
|
|
25747
|
+
if (!validArity(operator.arity, node.value)) {
|
|
25748
|
+
errors.push(error(node.id, "value", operator.arity === "pair" ? "Enter both values" : operator.arity === "many" ? "Choose at least one value" : operator.arity === "none" ? "This operator does not accept a value" : "Enter a value"));
|
|
25749
|
+
}
|
|
25750
|
+
};
|
|
25751
|
+
visit(ast.root, true);
|
|
25752
|
+
return { valid: errors.length === 0, errors };
|
|
25753
|
+
}
|
|
25754
|
+
function op(id, label, arity, types) {
|
|
25755
|
+
return Object.freeze({ id, label, arity, types: Object.freeze(types) });
|
|
25756
|
+
}
|
|
25757
|
+
function error(nodeId, code, message) {
|
|
25758
|
+
return { nodeId, code, message };
|
|
25759
|
+
}
|
|
25760
|
+
function validArity(arity, value) {
|
|
25761
|
+
if (arity === "none") return value === null;
|
|
25762
|
+
if (arity === "pair") return Array.isArray(value) && value.length === 2 && value.every(filled);
|
|
25763
|
+
if (arity === "many") return Array.isArray(value) && value.length > 0 && value.every(filled);
|
|
25764
|
+
return filled(value);
|
|
25765
|
+
}
|
|
25766
|
+
function filled(value) {
|
|
25767
|
+
return value !== null && value !== void 0 && !(typeof value === "string" && value.trim() === "");
|
|
25768
|
+
}
|
|
25769
|
+
function cloneJson(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
25770
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
25771
|
+
if (typeof value === "number") {
|
|
25772
|
+
if (!Number.isFinite(value)) throw new TypeError("Filter values cannot contain non-finite numbers");
|
|
25773
|
+
return value;
|
|
25774
|
+
}
|
|
25775
|
+
if (typeof value !== "object" || value instanceof Date || typeof value.nodeType === "number") {
|
|
25776
|
+
throw new TypeError("Filter values must be JSON-safe");
|
|
25777
|
+
}
|
|
25778
|
+
if (seen.has(value)) throw new TypeError("Filter values cannot contain cycles");
|
|
25779
|
+
seen.add(value);
|
|
25780
|
+
if (Array.isArray(value)) {
|
|
25781
|
+
const result2 = value.map((item) => cloneJson(item, seen));
|
|
25782
|
+
seen.delete(value);
|
|
25783
|
+
return result2;
|
|
25784
|
+
}
|
|
25785
|
+
if (!plainObject(value)) throw new TypeError("Filter values must be plain JSON objects");
|
|
25786
|
+
const result = {};
|
|
25787
|
+
for (const [key, item] of Object.entries(value)) {
|
|
25788
|
+
Object.defineProperty(result, key, {
|
|
25789
|
+
value: cloneJson(item, seen),
|
|
25790
|
+
enumerable: true,
|
|
25791
|
+
configurable: true,
|
|
25792
|
+
writable: true
|
|
25793
|
+
});
|
|
25794
|
+
}
|
|
25795
|
+
seen.delete(value);
|
|
25796
|
+
return result;
|
|
25797
|
+
}
|
|
25798
|
+
function plainObject(value) {
|
|
25799
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
25800
|
+
const prototype = Object.getPrototypeOf(value);
|
|
25801
|
+
return prototype === Object.prototype || prototype === null;
|
|
25802
|
+
}
|
|
25803
|
+
function finiteLimit(value, fallback) {
|
|
25804
|
+
if (value === Infinity) return Infinity;
|
|
25805
|
+
const number2 = Number(value);
|
|
25806
|
+
return Number.isFinite(number2) && number2 >= 0 ? Math.floor(number2) : fallback;
|
|
25807
|
+
}
|
|
25808
|
+
|
|
25809
|
+
// src/components/filter/filter.js
|
|
25810
|
+
var Filter = class extends Component {
|
|
25811
|
+
static cssName = "filter";
|
|
25812
|
+
/** @type {Readonly<FilterOptions>} */
|
|
25813
|
+
static defaults = {
|
|
25814
|
+
fields: [],
|
|
25815
|
+
value: null,
|
|
25816
|
+
operators: [],
|
|
25817
|
+
allowGroups: true,
|
|
25818
|
+
maxDepth: 3,
|
|
25819
|
+
maxConditions: 50,
|
|
25820
|
+
autoApply: false,
|
|
25821
|
+
readonly: false,
|
|
25822
|
+
disabled: false,
|
|
25823
|
+
applyLabel: "Apply filters",
|
|
25824
|
+
clearLabel: "Clear"
|
|
25825
|
+
};
|
|
25826
|
+
/** @returns {HTMLElement} */
|
|
25827
|
+
render() {
|
|
25828
|
+
this._createdRoot = this.el === null;
|
|
25829
|
+
const root = (
|
|
25830
|
+
/** @type {HTMLElement} */
|
|
25831
|
+
this.el ?? h("div")
|
|
25832
|
+
);
|
|
25833
|
+
this.el = root;
|
|
25834
|
+
this._snapshot = this._createdRoot ? null : snapshotTarget(root);
|
|
25835
|
+
this._fields = (Array.isArray(this.options.fields) ? this.options.fields : []).map((field) => ({ ...field }));
|
|
25836
|
+
this._operators = mergeOperators(filterOperators, this.options.operators);
|
|
25837
|
+
this._readonly = Boolean(this.options.readonly);
|
|
25838
|
+
this._disabled = Boolean(this.options.disabled);
|
|
25839
|
+
this._async = /* @__PURE__ */ new Map();
|
|
25840
|
+
this._customEditors = [];
|
|
25841
|
+
this._value = this.options.value == null ? emptyFilterAst() : parseFilterAst(this.options.value, this._limits());
|
|
25842
|
+
this.listen(root, "change", (event) => this._handleChange(event));
|
|
25843
|
+
this.listen(root, "input", (event) => this._handleInput(event));
|
|
25844
|
+
this.listen(root, "click", (event) => this._handleClick(event));
|
|
25845
|
+
this._render();
|
|
25846
|
+
return root;
|
|
25847
|
+
}
|
|
25848
|
+
/** Returns a defensive AST copy. @returns {FilterAst} */
|
|
25849
|
+
getValue() {
|
|
25850
|
+
return cloneFilterAst(this._value);
|
|
25851
|
+
}
|
|
25852
|
+
/** Atomically replaces the AST. @param {unknown} value @param {{silent?:boolean}} [options={}] @returns {this} */
|
|
25853
|
+
setValue(value, { silent = false } = {}) {
|
|
25854
|
+
const parsed = parseFilterAst(value, this._limits());
|
|
25855
|
+
this._abortAll();
|
|
25856
|
+
this._value = parsed;
|
|
25857
|
+
this._render();
|
|
25858
|
+
if (!silent) this._changed("set", this._value.root.id);
|
|
25859
|
+
return this;
|
|
25860
|
+
}
|
|
25861
|
+
/** Adds a condition to a group. @param {string|null} [parentId=null] @param {Record<string,unknown>} [initial={}] @returns {string|null} */
|
|
25862
|
+
addCondition(parentId = null, initial = {}) {
|
|
25863
|
+
if (!this._canMutate() || conditionCount(this._value.root) >= Number(this.options.maxConditions)) return null;
|
|
25864
|
+
const parent = findGroup(this._value.root, parentId ?? this._value.root.id);
|
|
25865
|
+
if (!parent) return null;
|
|
25866
|
+
const first = this._fields[0];
|
|
25867
|
+
const field = String(initial.field ?? first?.id ?? "");
|
|
25868
|
+
const definition = this._field(field);
|
|
25869
|
+
const node = filterCondition({
|
|
25870
|
+
...initial,
|
|
25871
|
+
field,
|
|
25872
|
+
operator: String(initial.operator ?? this._defaultOperator(definition) ?? "")
|
|
25873
|
+
});
|
|
25874
|
+
parent.children.push(node);
|
|
25875
|
+
this._render();
|
|
25876
|
+
this._changed("add", node.id);
|
|
25877
|
+
this.focus(node.id);
|
|
25878
|
+
return node.id;
|
|
25879
|
+
}
|
|
25880
|
+
/** Adds a nested group. @param {string|null} [parentId=null] @param {'and'|'or'} [logic='and'] @returns {string|null} */
|
|
25881
|
+
addGroup(parentId = null, logic = "and") {
|
|
25882
|
+
if (!this._canMutate() || !this.options.allowGroups) return null;
|
|
25883
|
+
const parent = findGroup(this._value.root, parentId ?? this._value.root.id);
|
|
25884
|
+
if (!parent || groupDepth(this._value.root, parent.id) >= Number(this.options.maxDepth)) return null;
|
|
25885
|
+
const group = filterGroup(logic === "or" ? "or" : "and");
|
|
25886
|
+
parent.children.push(group);
|
|
25887
|
+
this._render();
|
|
25888
|
+
this._changed("add", group.id);
|
|
25889
|
+
this.focus(group.id);
|
|
25890
|
+
return group.id;
|
|
25891
|
+
}
|
|
25892
|
+
/** Updates a node by stable ID. @param {string} id @param {Record<string,unknown>} patch @returns {this} */
|
|
25893
|
+
update(id, patch = {}) {
|
|
25894
|
+
if (!this._canMutate()) return this;
|
|
25895
|
+
const node = findNode2(this._value.root, id);
|
|
25896
|
+
if (!node) return this;
|
|
25897
|
+
if (node.kind === "group") {
|
|
25898
|
+
if (patch.logic === "and" || patch.logic === "or") node.logic = patch.logic;
|
|
25899
|
+
} else {
|
|
25900
|
+
if (Object.hasOwn(patch, "field")) node.field = String(patch.field ?? "");
|
|
25901
|
+
if (Object.hasOwn(patch, "operator")) node.operator = String(patch.operator ?? "");
|
|
25902
|
+
if (Object.hasOwn(patch, "value")) node.value = parseValueJson(patch.value);
|
|
25903
|
+
}
|
|
25904
|
+
this._abort(id);
|
|
25905
|
+
this._render();
|
|
25906
|
+
this._changed("update", id);
|
|
25907
|
+
return this;
|
|
25908
|
+
}
|
|
25909
|
+
/** Removes a non-root node. @param {string} id @returns {this} */
|
|
25910
|
+
remove(id) {
|
|
25911
|
+
if (!this._canMutate() || id === this._value.root.id) return this;
|
|
25912
|
+
const parent = findParent(this._value.root, id);
|
|
25913
|
+
if (!parent) return this;
|
|
25914
|
+
const index = parent.children.findIndex((child) => child.id === id);
|
|
25915
|
+
const focusId = parent.children[index + 1]?.id ?? parent.children[index - 1]?.id ?? parent.id;
|
|
25916
|
+
const removed = removeNode(this._value.root, id);
|
|
25917
|
+
if (!removed) return this;
|
|
25918
|
+
this._abortTree(removed);
|
|
25919
|
+
this._render();
|
|
25920
|
+
const label = removed.kind === "condition" ? this._field(removed.field)?.label ?? "Filter" : "Filter group";
|
|
25921
|
+
this._announce(`${label} removed`);
|
|
25922
|
+
this._changed("remove", id);
|
|
25923
|
+
this.focus(focusId);
|
|
25924
|
+
return this;
|
|
25925
|
+
}
|
|
25926
|
+
/** Moves a node into a group while preserving its identity. @param {string} id @param {string} parentId @param {number} index @returns {this} */
|
|
25927
|
+
move(id, parentId, index) {
|
|
25928
|
+
const node = findNode2(this._value.root, id);
|
|
25929
|
+
const parent = findGroup(this._value.root, parentId);
|
|
25930
|
+
const previous = findParent(this._value.root, id);
|
|
25931
|
+
if (!this._canMutate() || id === this._value.root.id || !node || !parent || !previous || containsNode(node, parentId)) return this;
|
|
25932
|
+
const previousIndex = previous.children.findIndex((child) => child.id === id);
|
|
25933
|
+
const removed = previous.children.splice(previousIndex, 1)[0];
|
|
25934
|
+
parent.children.splice(Math.max(0, Math.min(parent.children.length, Number(index) || 0)), 0, removed);
|
|
25935
|
+
try {
|
|
25936
|
+
parseFilterAst(this._value, this._limits());
|
|
25937
|
+
} catch {
|
|
25938
|
+
parent.children.splice(parent.children.indexOf(removed), 1);
|
|
25939
|
+
previous.children.splice(previousIndex, 0, removed);
|
|
25940
|
+
return this;
|
|
25941
|
+
}
|
|
25942
|
+
this._render();
|
|
25943
|
+
this._changed("move", id);
|
|
25944
|
+
return this;
|
|
25945
|
+
}
|
|
25946
|
+
/** Returns semantic validity and errors. @returns {{valid:boolean,errors:Array<Record<string,string>>}} */
|
|
25947
|
+
validate() {
|
|
25948
|
+
return validateFilterAst(this._value, this._fields, this._operators);
|
|
25949
|
+
}
|
|
25950
|
+
/** Applies only a valid AST. @returns {FilterAst|null} */
|
|
25951
|
+
apply() {
|
|
25952
|
+
if (this._disabled) return null;
|
|
25953
|
+
const result = this.validate();
|
|
25954
|
+
if (!result.valid) {
|
|
25955
|
+
this.emit("invalid", { errors: result.errors });
|
|
25956
|
+
this.focus(result.errors[0]?.nodeId);
|
|
25957
|
+
return null;
|
|
25958
|
+
}
|
|
25959
|
+
const value = this.getValue();
|
|
25960
|
+
this.emit("apply", { value });
|
|
25961
|
+
return value;
|
|
25962
|
+
}
|
|
25963
|
+
/** Clears all conditions. @param {{silent?:boolean}} [options={}] @returns {this} */
|
|
25964
|
+
clear({ silent = false } = {}) {
|
|
25965
|
+
if (!this._canMutate()) return this;
|
|
25966
|
+
this._abortAll();
|
|
25967
|
+
this._value.root.children = [];
|
|
25968
|
+
this._render();
|
|
25969
|
+
if (!silent) this._changed("clear", this._value.root.id);
|
|
25970
|
+
this.refs.add?.focus();
|
|
25971
|
+
return this;
|
|
25972
|
+
}
|
|
25973
|
+
/** Focuses a condition's field or the Add button. @param {string|null} [id=null] @returns {this} */
|
|
25974
|
+
focus(id = null) {
|
|
25975
|
+
const selector = id ? `[data-node-id="${cssEscape(id)}"] :is([data-filter-field], [data-filter-logic], [data-filter-action="add"])` : '[data-filter-action="add"]';
|
|
25976
|
+
queueMicrotask(() => (
|
|
25977
|
+
/** @type {HTMLElement|null} */
|
|
25978
|
+
this.el.querySelector(selector)?.focus()
|
|
25979
|
+
));
|
|
25980
|
+
return this;
|
|
25981
|
+
}
|
|
25982
|
+
/** @param {boolean} readonly @returns {this} */
|
|
25983
|
+
setReadonly(readonly) {
|
|
25984
|
+
this._readonly = Boolean(readonly);
|
|
25985
|
+
this._render();
|
|
25986
|
+
return this;
|
|
25987
|
+
}
|
|
25988
|
+
/** @returns {this} */
|
|
25989
|
+
enable() {
|
|
25990
|
+
this._disabled = false;
|
|
25991
|
+
this._render();
|
|
25992
|
+
return this;
|
|
25993
|
+
}
|
|
25994
|
+
/** @returns {this} */
|
|
25995
|
+
disable() {
|
|
25996
|
+
this._disabled = true;
|
|
25997
|
+
this._abortAll();
|
|
25998
|
+
this._render();
|
|
25999
|
+
return this;
|
|
26000
|
+
}
|
|
26001
|
+
/** @returns {void} */
|
|
26002
|
+
destroy() {
|
|
26003
|
+
this._abortAll();
|
|
26004
|
+
this._destroyEditors();
|
|
26005
|
+
const root = this.el;
|
|
26006
|
+
const snapshot10 = this._snapshot;
|
|
26007
|
+
super.destroy();
|
|
26008
|
+
if (!this._createdRoot && root) restoreTarget(root, snapshot10);
|
|
26009
|
+
}
|
|
26010
|
+
/** @returns {void} */
|
|
26011
|
+
_render() {
|
|
26012
|
+
this._destroyEditors();
|
|
26013
|
+
this._renderVersion = (this._renderVersion ?? 0) + 1;
|
|
26014
|
+
const status = h("div", { ref: "status", class: "zx-filter__status", role: "status", ariaLive: "polite" });
|
|
26015
|
+
const tree = this._renderGroup(this._value.root, 0, true);
|
|
26016
|
+
const actions2 = h(
|
|
26017
|
+
"div",
|
|
26018
|
+
{ class: "zx-filter__actions" },
|
|
26019
|
+
actionButton("add", this._value.root.id, "plus", "Add filter", { ref: "add", disabled: this._blocked() }),
|
|
26020
|
+
this.options.allowGroups ? actionButton("add-group", this._value.root.id, "folder", "Add group", { disabled: this._blocked() }) : null,
|
|
26021
|
+
actionButton("clear", "", "x", String(this.options.clearLabel), { disabled: this._blocked() }),
|
|
26022
|
+
actionButton("apply", "", "check", String(this.options.applyLabel), { class: "zx-button--primary", disabled: this._disabled })
|
|
26023
|
+
);
|
|
26024
|
+
this.el.replaceChildren(status, tree, actions2);
|
|
26025
|
+
this.refs.status = status;
|
|
26026
|
+
this.refs.add = /** @type {HTMLElement} */
|
|
26027
|
+
this.el.querySelector('[data-filter-action="add"]');
|
|
26028
|
+
this.el.toggleAttribute("data-readonly", this._readonly);
|
|
26029
|
+
this.el.toggleAttribute("data-disabled", this._disabled);
|
|
26030
|
+
const result = this.validate();
|
|
26031
|
+
this.el.dataset.valid = String(result.valid);
|
|
26032
|
+
for (const error2 of result.errors) this.el.querySelector(`[data-node-id="${cssEscape(error2.nodeId)}"]`)?.setAttribute("data-invalid", "");
|
|
26033
|
+
}
|
|
26034
|
+
/** @param {any} group @param {number} depth @param {boolean} root @returns {HTMLElement} */
|
|
26035
|
+
_renderGroup(group, depth, root) {
|
|
26036
|
+
const children = h(
|
|
26037
|
+
"ol",
|
|
26038
|
+
{ class: "zx-filter__children" },
|
|
26039
|
+
group.children.map((node) => node.kind === "group" ? this._renderGroup(node, depth + 1, false) : this._renderCondition(node))
|
|
26040
|
+
);
|
|
26041
|
+
const logic = h(
|
|
26042
|
+
"select",
|
|
26043
|
+
{
|
|
26044
|
+
class: "zx-filter__logic",
|
|
26045
|
+
dataset: { filterLogic: "", nodeId: group.id },
|
|
26046
|
+
ariaLabel: root ? "Root filter logic" : "Group filter logic",
|
|
26047
|
+
disabled: this._blocked()
|
|
26048
|
+
},
|
|
26049
|
+
h("option", { value: "and", selected: group.logic === "and" }, "Match all"),
|
|
26050
|
+
h("option", { value: "or", selected: group.logic === "or" }, "Match any")
|
|
26051
|
+
);
|
|
26052
|
+
const legend = h("legend", { class: "zx-filter__legend" }, logic, h("span", {}, root ? "conditions" : "in this group"));
|
|
26053
|
+
const controls = h(
|
|
26054
|
+
"div",
|
|
26055
|
+
{ class: "zx-filter__group-actions" },
|
|
26056
|
+
actionButton("add", group.id, "plus", "Add filter", { disabled: this._blocked() }),
|
|
26057
|
+
this.options.allowGroups && depth < Number(this.options.maxDepth) ? actionButton("add-group", group.id, "folder", "Add subgroup", { disabled: this._blocked() }) : null,
|
|
26058
|
+
root ? null : actionButton("remove", group.id, "trash", "Remove group", { disabled: this._blocked() })
|
|
26059
|
+
);
|
|
26060
|
+
return h(root ? "fieldset" : "li", {
|
|
26061
|
+
class: "zx-filter__group",
|
|
26062
|
+
dataset: { nodeId: group.id, depth: String(depth) }
|
|
26063
|
+
}, root ? legend : h("fieldset", {}, legend, children, controls), root ? children : null, root ? controls : null);
|
|
26064
|
+
}
|
|
26065
|
+
/** @param {any} node @returns {HTMLElement} */
|
|
26066
|
+
_renderCondition(node) {
|
|
26067
|
+
const field = this._field(node.field);
|
|
26068
|
+
const operators = this._operatorsFor(field);
|
|
26069
|
+
const operator = this._operator(node.operator);
|
|
26070
|
+
const fieldSelect = h(
|
|
26071
|
+
"select",
|
|
26072
|
+
{
|
|
26073
|
+
class: "zx-filter__field",
|
|
26074
|
+
dataset: { filterField: "", nodeId: node.id },
|
|
26075
|
+
ariaLabel: "Filter field",
|
|
26076
|
+
disabled: this._blocked()
|
|
26077
|
+
},
|
|
26078
|
+
h("option", { value: "", selected: !node.field }, "Choose a field\u2026"),
|
|
26079
|
+
this._fields.map((candidate) => h("option", { value: candidate.id, selected: candidate.id === node.field }, candidate.label)),
|
|
26080
|
+
node.field && !field ? h("option", { value: node.field, selected: true }, `Unavailable: ${node.field}`) : null
|
|
26081
|
+
);
|
|
26082
|
+
const operatorSelect = h(
|
|
26083
|
+
"select",
|
|
26084
|
+
{
|
|
26085
|
+
class: "zx-filter__operator",
|
|
26086
|
+
dataset: { filterOperator: "", nodeId: node.id },
|
|
26087
|
+
ariaLabel: "Filter operator",
|
|
26088
|
+
disabled: this._blocked()
|
|
26089
|
+
},
|
|
26090
|
+
h("option", { value: "", selected: !node.operator }, "Choose an operator\u2026"),
|
|
26091
|
+
operators.map((candidate) => h("option", { value: candidate.id, selected: candidate.id === node.operator }, candidate.label)),
|
|
26092
|
+
node.operator && !operators.some((candidate) => candidate.id === node.operator) ? h("option", { value: node.operator, selected: true }, `Unavailable: ${node.operator}`) : null
|
|
26093
|
+
);
|
|
26094
|
+
return h(
|
|
26095
|
+
"li",
|
|
26096
|
+
{ class: "zx-filter__condition", dataset: { nodeId: node.id } },
|
|
26097
|
+
fieldSelect,
|
|
26098
|
+
operatorSelect,
|
|
26099
|
+
this._renderValue(node, field, operator),
|
|
26100
|
+
actionButton("remove", node.id, "trash", `Remove ${field?.label ?? "filter"}`, { disabled: this._blocked() })
|
|
26101
|
+
);
|
|
26102
|
+
}
|
|
26103
|
+
/** @param {any} node @param {FilterField|null} field @param {any} operator @returns {HTMLElement} */
|
|
26104
|
+
_renderValue(node, field, operator) {
|
|
26105
|
+
const host = h("div", { class: "zx-filter__value" });
|
|
26106
|
+
if (!field || !operator) return host;
|
|
26107
|
+
if (operator.arity === "none") return h("div", { class: "zx-filter__value zx-filter__value--empty" }, "No value");
|
|
26108
|
+
if (field.type === "custom" && typeof field.editor === "function") {
|
|
26109
|
+
const renderVersion = this._renderVersion;
|
|
26110
|
+
queueMicrotask(() => {
|
|
26111
|
+
if (renderVersion !== this._renderVersion || !this.el.contains(host)) return;
|
|
26112
|
+
const handle = field.editor(host, {
|
|
26113
|
+
value: node.value,
|
|
26114
|
+
field,
|
|
26115
|
+
conditionId: node.id,
|
|
26116
|
+
setValue: (value) => this._setNodeValue(node.id, parseValueJson(value))
|
|
26117
|
+
});
|
|
26118
|
+
if (handle && typeof handle.destroy === "function") this._customEditors.push(handle);
|
|
26119
|
+
});
|
|
26120
|
+
return host;
|
|
26121
|
+
}
|
|
26122
|
+
const values = operator.arity === "pair" ? Array.isArray(node.value) ? node.value : [null, null] : [node.value];
|
|
26123
|
+
if (operator.arity === "many") {
|
|
26124
|
+
const control = choiceSelect(field, node, values[0], true, this._blocked());
|
|
26125
|
+
if (control) return h("div", { class: "zx-filter__value" }, control);
|
|
26126
|
+
return h("div", { class: "zx-filter__value" }, valueInput(field, node.id, Array.isArray(node.value) ? node.value.join(", ") : "", 0, this._blocked(), true));
|
|
26127
|
+
}
|
|
26128
|
+
const controls = values.map((value, index) => choiceSelect(field, node, value, false, this._blocked(), index) ?? valueInput(field, node.id, value, index, this._blocked()));
|
|
26129
|
+
return h("div", { class: "zx-filter__value", dataset: { arity: operator.arity } }, controls);
|
|
26130
|
+
}
|
|
26131
|
+
/** @param {Event} event @returns {void} */
|
|
26132
|
+
_handleChange(event) {
|
|
26133
|
+
if (!this._canMutate()) return;
|
|
26134
|
+
const target = (
|
|
26135
|
+
/** @type {HTMLInputElement|HTMLSelectElement|null} */
|
|
26136
|
+
event.target
|
|
26137
|
+
);
|
|
26138
|
+
const id = target?.dataset.nodeId;
|
|
26139
|
+
if (!target || !id) return;
|
|
26140
|
+
if (target.dataset.filterField !== void 0) {
|
|
26141
|
+
const node = findNode2(this._value.root, id);
|
|
26142
|
+
if (node?.kind !== "condition") return;
|
|
26143
|
+
node.field = target.value;
|
|
26144
|
+
const field = this._field(node.field);
|
|
26145
|
+
node.operator = this._defaultOperator(field) ?? "";
|
|
26146
|
+
node.value = null;
|
|
26147
|
+
this._abort(id);
|
|
26148
|
+
this._render();
|
|
26149
|
+
this._changed("update", id);
|
|
26150
|
+
this.focus(id);
|
|
26151
|
+
} else if (target.dataset.filterOperator !== void 0) {
|
|
26152
|
+
const node = findNode2(this._value.root, id);
|
|
26153
|
+
if (node?.kind !== "condition") return;
|
|
26154
|
+
node.operator = target.value;
|
|
26155
|
+
node.value = null;
|
|
26156
|
+
this._abort(id);
|
|
26157
|
+
this._render();
|
|
26158
|
+
this._changed("update", id);
|
|
26159
|
+
queueMicrotask(() => (
|
|
26160
|
+
/** @type {HTMLElement|null} */
|
|
26161
|
+
this.el.querySelector(`[data-node-id="${cssEscape(id)}"] [data-filter-value]`)?.focus()
|
|
26162
|
+
));
|
|
26163
|
+
} else if (target.dataset.filterLogic !== void 0) {
|
|
26164
|
+
const node = findNode2(this._value.root, id);
|
|
26165
|
+
if (node?.kind === "group") node.logic = target.value === "or" ? "or" : "and";
|
|
26166
|
+
this._changed("update", id);
|
|
26167
|
+
} else if (target.dataset.filterValue !== void 0) this._valueInput(target);
|
|
26168
|
+
}
|
|
26169
|
+
/** @param {Event} event @returns {void} */
|
|
26170
|
+
_handleInput(event) {
|
|
26171
|
+
if (!this._canMutate()) return;
|
|
26172
|
+
const target = (
|
|
26173
|
+
/** @type {HTMLInputElement|null} */
|
|
26174
|
+
event.target
|
|
26175
|
+
);
|
|
26176
|
+
if (!target || target.dataset.filterValue === void 0) return;
|
|
26177
|
+
this._valueInput(target);
|
|
26178
|
+
const id = target.dataset.nodeId;
|
|
26179
|
+
const node = findNode2(this._value.root, id);
|
|
26180
|
+
const field = node?.kind === "condition" ? this._field(node.field) : null;
|
|
26181
|
+
if (field?.loadChoices) this._scheduleChoices(node.id, field, target.value);
|
|
26182
|
+
}
|
|
26183
|
+
/** @param {HTMLInputElement|HTMLSelectElement} target @returns {void} */
|
|
26184
|
+
_valueInput(target) {
|
|
26185
|
+
const node = findNode2(this._value.root, target.dataset.nodeId);
|
|
26186
|
+
if (node?.kind !== "condition") return;
|
|
26187
|
+
const operator = this._operator(node.operator);
|
|
26188
|
+
const field = this._field(node.field);
|
|
26189
|
+
if (!operator || !field) return;
|
|
26190
|
+
let value;
|
|
26191
|
+
if (target instanceof HTMLSelectElement && target.multiple) {
|
|
26192
|
+
value = Array.from(target.selectedOptions, (option) => parseScalar(option.value, field.type));
|
|
26193
|
+
} else if (operator.arity === "many") {
|
|
26194
|
+
value = target.value.split(",").map((item) => item.trim()).filter(Boolean).map((item) => parseScalar(item, field.type));
|
|
26195
|
+
} else if (operator.arity === "pair") {
|
|
26196
|
+
const pair = Array.isArray(node.value) ? [...node.value] : [null, null];
|
|
26197
|
+
pair[Number(target.dataset.valueIndex) || 0] = parseScalar(target.value, field.type);
|
|
26198
|
+
value = pair;
|
|
26199
|
+
} else value = parseScalar(target.value, field.type);
|
|
26200
|
+
this._setNodeValue(
|
|
26201
|
+
node.id,
|
|
26202
|
+
/** @type {any} */
|
|
26203
|
+
value
|
|
26204
|
+
);
|
|
26205
|
+
}
|
|
26206
|
+
/** @param {MouseEvent} event @returns {void} */
|
|
26207
|
+
_handleClick(event) {
|
|
26208
|
+
const button2 = (
|
|
26209
|
+
/** @type {HTMLElement|null} */
|
|
26210
|
+
event.target?.closest?.("[data-filter-action]")
|
|
26211
|
+
);
|
|
26212
|
+
if (!button2 || !this.el.contains(button2)) return;
|
|
26213
|
+
const action = button2.dataset.filterAction;
|
|
26214
|
+
const id = button2.dataset.nodeId || null;
|
|
26215
|
+
if (action === "add") this.addCondition(id);
|
|
26216
|
+
else if (action === "add-group") this.addGroup(id);
|
|
26217
|
+
else if (action === "remove" && id) this.remove(id);
|
|
26218
|
+
else if (action === "clear") this.clear();
|
|
26219
|
+
else if (action === "apply") this.apply();
|
|
26220
|
+
}
|
|
26221
|
+
/** @param {string} id @param {any} value @returns {void} */
|
|
26222
|
+
_setNodeValue(id, value) {
|
|
26223
|
+
const node = findNode2(this._value.root, id);
|
|
26224
|
+
if (node?.kind !== "condition") return;
|
|
26225
|
+
node.value = value;
|
|
26226
|
+
const result = this.validate();
|
|
26227
|
+
this.el.dataset.valid = String(result.valid);
|
|
26228
|
+
const row2 = this.el.querySelector(`[data-node-id="${cssEscape(id)}"]`);
|
|
26229
|
+
row2?.toggleAttribute("data-invalid", result.errors.some((candidate) => candidate.nodeId === id));
|
|
26230
|
+
this._changed("update", id);
|
|
26231
|
+
}
|
|
26232
|
+
/** @param {string} reason @param {string} nodeId @returns {void} */
|
|
26233
|
+
_changed(reason, nodeId) {
|
|
26234
|
+
const result = this.validate();
|
|
26235
|
+
const value = this.getValue();
|
|
26236
|
+
this.emit("change", { value, valid: result.valid, errors: result.errors, reason, nodeId });
|
|
26237
|
+
if (this.options.autoApply && result.valid) this.emit("apply", { value });
|
|
26238
|
+
}
|
|
26239
|
+
/** @param {string} id @param {FilterField} field @param {string} query @returns {void} */
|
|
26240
|
+
_scheduleChoices(id, field, query) {
|
|
26241
|
+
this._abort(id);
|
|
26242
|
+
if (query.length < Number(field.minQuery ?? 0)) return;
|
|
26243
|
+
const controller = new AbortController();
|
|
26244
|
+
this._setAsyncBusy(id, true);
|
|
26245
|
+
const entry = { controller, timer: setTimeout(async () => {
|
|
26246
|
+
this.emit("query", { conditionId: id, fieldId: field.id, query });
|
|
26247
|
+
try {
|
|
26248
|
+
const choices = await field.loadChoices({ query, signal: controller.signal, conditionId: id, field });
|
|
26249
|
+
if (controller.signal.aborted || this._async.get(id)?.controller !== controller) return;
|
|
26250
|
+
const input = this.el.querySelector(`[data-node-id="${cssEscape(id)}"] [data-filter-value]`);
|
|
26251
|
+
const listId = input?.getAttribute("list");
|
|
26252
|
+
const list = listId ? this.el.querySelector(`#${cssEscape(listId)}`) : null;
|
|
26253
|
+
if (list) list.replaceChildren(...(Array.isArray(choices) ? choices : []).map((choice) => h("option", { value: choice.value }, choice.label)));
|
|
26254
|
+
this.emit("loaded", { conditionId: id, fieldId: field.id, query, choices });
|
|
26255
|
+
} catch (error2) {
|
|
26256
|
+
if (!controller.signal.aborted) this.emit("error", { conditionId: id, fieldId: field.id, query, error: error2 instanceof Error ? error2 : new Error(String(error2)) });
|
|
26257
|
+
} finally {
|
|
26258
|
+
if (this._async.get(id)?.controller === controller) {
|
|
26259
|
+
this._async.delete(id);
|
|
26260
|
+
this._setAsyncBusy(id, false);
|
|
26261
|
+
}
|
|
26262
|
+
}
|
|
26263
|
+
}, Math.max(0, Number(field.debounce ?? 200))) };
|
|
26264
|
+
this._async.set(id, entry);
|
|
26265
|
+
}
|
|
26266
|
+
/** @param {string} id @returns {void} */
|
|
26267
|
+
_abort(id) {
|
|
26268
|
+
const entry = this._async.get(id);
|
|
26269
|
+
if (entry) {
|
|
26270
|
+
clearTimeout(entry.timer);
|
|
26271
|
+
entry.controller.abort();
|
|
26272
|
+
this._async.delete(id);
|
|
26273
|
+
this._setAsyncBusy(id, false);
|
|
26274
|
+
}
|
|
26275
|
+
}
|
|
26276
|
+
/** @param {string} id @param {boolean} busy @returns {void} */
|
|
26277
|
+
_setAsyncBusy(id, busy) {
|
|
26278
|
+
const input = this.el.querySelector(`[data-node-id="${cssEscape(id)}"] [data-filter-value]`);
|
|
26279
|
+
if (busy) input?.setAttribute("aria-busy", "true");
|
|
26280
|
+
else input?.removeAttribute("aria-busy");
|
|
26281
|
+
}
|
|
26282
|
+
/** @param {any} node @returns {void} */
|
|
26283
|
+
_abortTree(node) {
|
|
26284
|
+
if (node.kind === "condition") this._abort(node.id);
|
|
26285
|
+
else node.children.forEach((child) => this._abortTree(child));
|
|
26286
|
+
}
|
|
26287
|
+
/** @returns {void} */
|
|
26288
|
+
_abortAll() {
|
|
26289
|
+
for (const id of [...this._async.keys()]) this._abort(id);
|
|
26290
|
+
}
|
|
26291
|
+
/** @returns {void} */
|
|
26292
|
+
_destroyEditors() {
|
|
26293
|
+
for (const editor of this._customEditors ?? []) editor.destroy?.();
|
|
26294
|
+
this._customEditors = [];
|
|
26295
|
+
}
|
|
26296
|
+
/** @returns {boolean} */
|
|
26297
|
+
_blocked() {
|
|
26298
|
+
return this._readonly || this._disabled;
|
|
26299
|
+
}
|
|
26300
|
+
/** @returns {boolean} */
|
|
26301
|
+
_canMutate() {
|
|
26302
|
+
return !this._blocked();
|
|
26303
|
+
}
|
|
26304
|
+
/** @returns {{maxDepth:number,maxConditions:number}} */
|
|
26305
|
+
_limits() {
|
|
26306
|
+
return { maxDepth: Number(this.options.maxDepth), maxConditions: Number(this.options.maxConditions) };
|
|
26307
|
+
}
|
|
26308
|
+
/** @param {string} id @returns {FilterField|null} */
|
|
26309
|
+
_field(id) {
|
|
26310
|
+
return this._fields.find((field) => field.id === id) ?? null;
|
|
26311
|
+
}
|
|
26312
|
+
/** @param {string} id @returns {any} */
|
|
26313
|
+
_operator(id) {
|
|
26314
|
+
return this._operators.find((operator) => operator.id === id) ?? null;
|
|
26315
|
+
}
|
|
26316
|
+
/** @param {FilterField|null} field @returns {any[]} */
|
|
26317
|
+
_operatorsFor(field) {
|
|
26318
|
+
if (!field) return [];
|
|
26319
|
+
const ids = field.operators?.length ? field.operators : null;
|
|
26320
|
+
return this._operators.filter((operator) => ids ? ids.includes(operator.id) : operator.types.includes(field.type));
|
|
26321
|
+
}
|
|
26322
|
+
/** @param {FilterField|null} field @returns {string|null} */
|
|
26323
|
+
_defaultOperator(field) {
|
|
26324
|
+
return field?.defaultOperator && this._operatorsFor(field).some((operator) => operator.id === field.defaultOperator) ? field.defaultOperator : this._operatorsFor(field)[0]?.id ?? null;
|
|
26325
|
+
}
|
|
26326
|
+
/** @param {string} text @returns {void} */
|
|
26327
|
+
_announce(text4) {
|
|
26328
|
+
if (this.refs.status) this.refs.status.textContent = text4;
|
|
26329
|
+
}
|
|
26330
|
+
};
|
|
26331
|
+
function actionButton(action, id, iconName, label, props = {}) {
|
|
26332
|
+
return h("button", { type: "button", class: ["zx-button", "zx-button--sm", props.class].filter(Boolean), ...props, dataset: { filterAction: action, nodeId: id } }, icon(iconName, { size: 13 }), h("span", {}, label));
|
|
26333
|
+
}
|
|
26334
|
+
function choiceSelect(field, node, value, multiple, disabled, index = 0) {
|
|
26335
|
+
const choices = Array.isArray(field.choices) && field.choices.length ? field.choices : field.type === "boolean" ? [{ value: true, label: "True" }, { value: false, label: "False" }] : null;
|
|
26336
|
+
if (!choices) return null;
|
|
26337
|
+
const values = new Set(Array.isArray(value) ? value.map(String) : [String(value ?? "")]);
|
|
26338
|
+
return (
|
|
26339
|
+
/** @type {HTMLSelectElement} */
|
|
26340
|
+
h(
|
|
26341
|
+
"select",
|
|
26342
|
+
{
|
|
26343
|
+
dataset: { filterValue: "", nodeId: node.id, valueIndex: String(index) },
|
|
26344
|
+
ariaLabel: `${field.label} value`,
|
|
26345
|
+
multiple,
|
|
26346
|
+
disabled
|
|
26347
|
+
},
|
|
26348
|
+
!multiple ? h("option", { value: "", selected: !values.has(String(value)) || value == null }, "Choose\u2026") : null,
|
|
26349
|
+
choices.map((choice) => h("option", { value: choice.value, selected: values.has(String(choice.value)) }, choice.label))
|
|
26350
|
+
)
|
|
26351
|
+
);
|
|
26352
|
+
}
|
|
26353
|
+
function valueInput(field, id, value, index, disabled, many = false) {
|
|
26354
|
+
const type = many ? "text" : ["number", "money"].includes(field.type) ? "number" : field.type === "date" ? "date" : field.type === "datetime" ? "datetime-local" : "text";
|
|
26355
|
+
const listId = field.loadChoices ? `zx-filter-choices-${id}-${index}` : null;
|
|
26356
|
+
const input = (
|
|
26357
|
+
/** @type {HTMLInputElement} */
|
|
26358
|
+
h("input", {
|
|
26359
|
+
type,
|
|
26360
|
+
value: value ?? "",
|
|
26361
|
+
dataset: { filterValue: "", nodeId: id, valueIndex: String(index) },
|
|
26362
|
+
ariaLabel: `${field.label} value${index ? ` ${index + 1}` : ""}`,
|
|
26363
|
+
disabled,
|
|
26364
|
+
placeholder: many ? "Comma-separated values" : "",
|
|
26365
|
+
list: listId
|
|
26366
|
+
})
|
|
26367
|
+
);
|
|
26368
|
+
return h(
|
|
26369
|
+
"span",
|
|
26370
|
+
{ class: "zx-filter__value-control" },
|
|
26371
|
+
input,
|
|
26372
|
+
listId ? h("datalist", { id: listId }) : null
|
|
26373
|
+
);
|
|
26374
|
+
}
|
|
26375
|
+
function parseScalar(value, type) {
|
|
26376
|
+
if (value === "") return null;
|
|
26377
|
+
if (["number", "money"].includes(type)) {
|
|
26378
|
+
const number2 = Number(value);
|
|
26379
|
+
return Number.isFinite(number2) ? number2 : null;
|
|
26380
|
+
}
|
|
26381
|
+
if (type === "boolean") return value === "true";
|
|
26382
|
+
return value;
|
|
26383
|
+
}
|
|
26384
|
+
function parseValueJson(value) {
|
|
26385
|
+
return cloneFilterValue(value === void 0 ? null : value);
|
|
26386
|
+
}
|
|
26387
|
+
function findNode2(root, id) {
|
|
26388
|
+
if (!root || root.id === id) return root ?? null;
|
|
26389
|
+
if (root.kind === "group") for (const child of root.children) {
|
|
26390
|
+
const match = findNode2(child, id);
|
|
26391
|
+
if (match) return match;
|
|
26392
|
+
}
|
|
26393
|
+
return null;
|
|
26394
|
+
}
|
|
26395
|
+
function findGroup(root, id) {
|
|
26396
|
+
const node = findNode2(root, id);
|
|
26397
|
+
return node?.kind === "group" ? node : null;
|
|
26398
|
+
}
|
|
26399
|
+
function findParent(root, id) {
|
|
26400
|
+
if (root?.kind !== "group") return null;
|
|
26401
|
+
if (root.children.some((child) => child.id === id)) return root;
|
|
26402
|
+
for (const child of root.children) {
|
|
26403
|
+
const parent = findParent(child, id);
|
|
26404
|
+
if (parent) return parent;
|
|
26405
|
+
}
|
|
26406
|
+
return null;
|
|
26407
|
+
}
|
|
26408
|
+
function removeNode(root, id) {
|
|
26409
|
+
if (root.kind !== "group") return null;
|
|
26410
|
+
const index = root.children.findIndex((child) => child.id === id);
|
|
26411
|
+
if (index >= 0) return root.children.splice(index, 1)[0];
|
|
26412
|
+
for (const child of root.children) {
|
|
26413
|
+
const found = removeNode(child, id);
|
|
26414
|
+
if (found) return found;
|
|
26415
|
+
}
|
|
26416
|
+
return null;
|
|
26417
|
+
}
|
|
26418
|
+
function containsNode(node, id) {
|
|
26419
|
+
return Boolean(node && findNode2(node, id));
|
|
26420
|
+
}
|
|
26421
|
+
function conditionCount(node) {
|
|
26422
|
+
return node.kind === "condition" ? 1 : node.children.reduce((sum, child) => sum + conditionCount(child), 0);
|
|
26423
|
+
}
|
|
26424
|
+
function groupDepth(root, id, depth = 0) {
|
|
26425
|
+
if (root.id === id) return depth;
|
|
26426
|
+
if (root.kind === "group") for (const child of root.children) {
|
|
26427
|
+
if (child.kind === "group") {
|
|
26428
|
+
const found = groupDepth(child, id, depth + 1);
|
|
26429
|
+
if (found >= 0) return found;
|
|
26430
|
+
}
|
|
26431
|
+
}
|
|
26432
|
+
return -1;
|
|
26433
|
+
}
|
|
26434
|
+
function mergeOperators(base, extra) {
|
|
26435
|
+
const map = new Map(base.map((item) => [item.id, { ...item, types: [...item.types] }]));
|
|
26436
|
+
for (const item of Array.isArray(extra) ? extra : []) if (item?.id) map.set(String(item.id), { ...item, id: String(item.id), types: Array.isArray(item.types) ? [...item.types] : [] });
|
|
26437
|
+
return [...map.values()];
|
|
26438
|
+
}
|
|
26439
|
+
function cssEscape(value) {
|
|
26440
|
+
return globalThis.CSS?.escape ? CSS.escape(value) : String(value).replaceAll(/[^a-zA-Z0-9_-]/g, "\\$&");
|
|
26441
|
+
}
|
|
26442
|
+
|
|
24132
26443
|
// src/components/fieldset/fieldset.js
|
|
24133
26444
|
var Fieldset = class extends Component {
|
|
24134
26445
|
static cssName = "fieldset";
|
|
@@ -25296,8 +27607,8 @@ var Questionnaire = class extends Component {
|
|
|
25296
27607
|
this._setBusy(true);
|
|
25297
27608
|
try {
|
|
25298
27609
|
message = await item.validate(answer, this.getAnswers());
|
|
25299
|
-
} catch (
|
|
25300
|
-
message =
|
|
27610
|
+
} catch (error2) {
|
|
27611
|
+
message = error2 instanceof Error ? error2.message : String(error2);
|
|
25301
27612
|
} finally {
|
|
25302
27613
|
if (!this._destroyed) this._setBusy(false);
|
|
25303
27614
|
}
|
|
@@ -25435,10 +27746,10 @@ var Questionnaire = class extends Component {
|
|
|
25435
27746
|
}
|
|
25436
27747
|
/** @param {NormalizedItem} item @returns {void} */
|
|
25437
27748
|
_paintError(item) {
|
|
25438
|
-
const
|
|
25439
|
-
if (
|
|
25440
|
-
|
|
25441
|
-
|
|
27749
|
+
const error2 = this._itemHost.querySelector(".zx-questionnaire__error");
|
|
27750
|
+
if (error2 instanceof HTMLElement) {
|
|
27751
|
+
error2.textContent = this._error;
|
|
27752
|
+
error2.hidden = !this._error;
|
|
25442
27753
|
}
|
|
25443
27754
|
if (this._error) this._itemHost.setAttribute("aria-invalid", "true");
|
|
25444
27755
|
else this._itemHost.removeAttribute("aria-invalid");
|
|
@@ -25446,7 +27757,7 @@ var Questionnaire = class extends Component {
|
|
|
25446
27757
|
if (field) {
|
|
25447
27758
|
if (this._error) field.setHighlight(this._error, "danger");
|
|
25448
27759
|
else field.clearHighlight();
|
|
25449
|
-
if (
|
|
27760
|
+
if (error2 instanceof HTMLElement) error2.hidden = true;
|
|
25450
27761
|
}
|
|
25451
27762
|
}
|
|
25452
27763
|
/** @returns {void} */
|
|
@@ -25669,10 +27980,11 @@ function registerFieldAdapters() {
|
|
|
25669
27980
|
registerFieldAdapters();
|
|
25670
27981
|
export {
|
|
25671
27982
|
AccountMenu,
|
|
25672
|
-
|
|
27983
|
+
AppIcon,
|
|
25673
27984
|
AppSidebar,
|
|
25674
27985
|
Avatar,
|
|
25675
27986
|
Breadcrumb,
|
|
27987
|
+
Card,
|
|
25676
27988
|
Chart,
|
|
25677
27989
|
ChartJsAdapter,
|
|
25678
27990
|
CheckButton,
|
|
@@ -25692,6 +28004,7 @@ export {
|
|
|
25692
28004
|
Field,
|
|
25693
28005
|
FieldUpload,
|
|
25694
28006
|
Fieldset,
|
|
28007
|
+
Filter,
|
|
25695
28008
|
Finder,
|
|
25696
28009
|
Form,
|
|
25697
28010
|
Grid,
|
|
@@ -25732,6 +28045,7 @@ export {
|
|
|
25732
28045
|
__,
|
|
25733
28046
|
addDays,
|
|
25734
28047
|
addMonths,
|
|
28048
|
+
appIcon,
|
|
25735
28049
|
appendCompact,
|
|
25736
28050
|
appendCompactChild,
|
|
25737
28051
|
appendContent,
|
|
@@ -25751,6 +28065,7 @@ export {
|
|
|
25751
28065
|
clampRange,
|
|
25752
28066
|
clampSize,
|
|
25753
28067
|
cloneChartValue,
|
|
28068
|
+
cloneFilterAst,
|
|
25754
28069
|
compactElement,
|
|
25755
28070
|
configureIcons,
|
|
25756
28071
|
copyButton,
|
|
@@ -25761,12 +28076,16 @@ export {
|
|
|
25761
28076
|
defineElements,
|
|
25762
28077
|
describe,
|
|
25763
28078
|
downloadBlob,
|
|
28079
|
+
emptyFilterAst,
|
|
25764
28080
|
emptyState,
|
|
25765
28081
|
escapeRegExp,
|
|
25766
28082
|
faFamilies,
|
|
25767
28083
|
faIconClasses,
|
|
25768
28084
|
faNames,
|
|
25769
28085
|
faStyles,
|
|
28086
|
+
filterCondition,
|
|
28087
|
+
filterGroup,
|
|
28088
|
+
filterOperators,
|
|
25770
28089
|
fireCompactEvent,
|
|
25771
28090
|
focusTrap,
|
|
25772
28091
|
formatCurrency,
|
|
@@ -25806,6 +28125,7 @@ export {
|
|
|
25806
28125
|
paginationRange,
|
|
25807
28126
|
parseCompactTag,
|
|
25808
28127
|
parseDate,
|
|
28128
|
+
parseFilterAst,
|
|
25809
28129
|
parseIconSpec,
|
|
25810
28130
|
parseNumber,
|
|
25811
28131
|
parseRangeText,
|
|
@@ -25817,6 +28137,7 @@ export {
|
|
|
25817
28137
|
rankLauncherItems,
|
|
25818
28138
|
registerFieldAdapters,
|
|
25819
28139
|
registerIcons,
|
|
28140
|
+
reorderTableRows,
|
|
25820
28141
|
resolveElement,
|
|
25821
28142
|
resolveSize,
|
|
25822
28143
|
rovingTabindex,
|
|
@@ -25833,6 +28154,7 @@ export {
|
|
|
25833
28154
|
stack,
|
|
25834
28155
|
stepPrecision,
|
|
25835
28156
|
storage,
|
|
28157
|
+
stringifyFilterAst,
|
|
25836
28158
|
throttle,
|
|
25837
28159
|
toArray,
|
|
25838
28160
|
toCsv,
|
|
@@ -25844,5 +28166,6 @@ export {
|
|
|
25844
28166
|
uniqueBy,
|
|
25845
28167
|
useBuiltinIcons,
|
|
25846
28168
|
useFontAwesome,
|
|
28169
|
+
validateFilterAst,
|
|
25847
28170
|
zeyosService
|
|
25848
28171
|
};
|