@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.global.js
CHANGED
|
@@ -21,10 +21,11 @@ var zx = (() => {
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
AccountMenu: () => AccountMenu,
|
|
24
|
-
|
|
24
|
+
AppIcon: () => AppIcon,
|
|
25
25
|
AppSidebar: () => AppSidebar,
|
|
26
26
|
Avatar: () => Avatar,
|
|
27
27
|
Breadcrumb: () => Breadcrumb,
|
|
28
|
+
Card: () => Card,
|
|
28
29
|
Chart: () => Chart,
|
|
29
30
|
ChartJsAdapter: () => ChartJsAdapter,
|
|
30
31
|
CheckButton: () => CheckButton,
|
|
@@ -44,6 +45,7 @@ var zx = (() => {
|
|
|
44
45
|
Field: () => Field,
|
|
45
46
|
FieldUpload: () => FieldUpload,
|
|
46
47
|
Fieldset: () => Fieldset,
|
|
48
|
+
Filter: () => Filter,
|
|
47
49
|
Finder: () => Finder,
|
|
48
50
|
Form: () => Form,
|
|
49
51
|
Grid: () => Grid,
|
|
@@ -84,6 +86,7 @@ var zx = (() => {
|
|
|
84
86
|
__: () => __,
|
|
85
87
|
addDays: () => addDays,
|
|
86
88
|
addMonths: () => addMonths,
|
|
89
|
+
appIcon: () => appIcon,
|
|
87
90
|
appendCompact: () => appendCompact,
|
|
88
91
|
appendCompactChild: () => appendCompactChild,
|
|
89
92
|
appendContent: () => appendContent,
|
|
@@ -103,6 +106,7 @@ var zx = (() => {
|
|
|
103
106
|
clampRange: () => clampRange,
|
|
104
107
|
clampSize: () => clampSize,
|
|
105
108
|
cloneChartValue: () => cloneChartValue,
|
|
109
|
+
cloneFilterAst: () => cloneFilterAst,
|
|
106
110
|
compactElement: () => compactElement,
|
|
107
111
|
configureIcons: () => configureIcons,
|
|
108
112
|
copyButton: () => copyButton,
|
|
@@ -113,12 +117,16 @@ var zx = (() => {
|
|
|
113
117
|
defineElements: () => defineElements,
|
|
114
118
|
describe: () => describe,
|
|
115
119
|
downloadBlob: () => downloadBlob,
|
|
120
|
+
emptyFilterAst: () => emptyFilterAst,
|
|
116
121
|
emptyState: () => emptyState,
|
|
117
122
|
escapeRegExp: () => escapeRegExp,
|
|
118
123
|
faFamilies: () => faFamilies,
|
|
119
124
|
faIconClasses: () => faIconClasses,
|
|
120
125
|
faNames: () => faNames,
|
|
121
126
|
faStyles: () => faStyles,
|
|
127
|
+
filterCondition: () => filterCondition,
|
|
128
|
+
filterGroup: () => filterGroup,
|
|
129
|
+
filterOperators: () => filterOperators,
|
|
122
130
|
fireCompactEvent: () => fireCompactEvent,
|
|
123
131
|
focusTrap: () => focusTrap,
|
|
124
132
|
formatCurrency: () => formatCurrency,
|
|
@@ -158,6 +166,7 @@ var zx = (() => {
|
|
|
158
166
|
paginationRange: () => paginationRange,
|
|
159
167
|
parseCompactTag: () => parseCompactTag,
|
|
160
168
|
parseDate: () => parseDate,
|
|
169
|
+
parseFilterAst: () => parseFilterAst,
|
|
161
170
|
parseIconSpec: () => parseIconSpec,
|
|
162
171
|
parseNumber: () => parseNumber,
|
|
163
172
|
parseRangeText: () => parseRangeText,
|
|
@@ -169,6 +178,7 @@ var zx = (() => {
|
|
|
169
178
|
rankLauncherItems: () => rankLauncherItems,
|
|
170
179
|
registerFieldAdapters: () => registerFieldAdapters,
|
|
171
180
|
registerIcons: () => registerIcons,
|
|
181
|
+
reorderTableRows: () => reorderTableRows,
|
|
172
182
|
resolveElement: () => resolveElement,
|
|
173
183
|
resolveSize: () => resolveSize,
|
|
174
184
|
rovingTabindex: () => rovingTabindex,
|
|
@@ -185,6 +195,7 @@ var zx = (() => {
|
|
|
185
195
|
stack: () => stack,
|
|
186
196
|
stepPrecision: () => stepPrecision,
|
|
187
197
|
storage: () => storage,
|
|
198
|
+
stringifyFilterAst: () => stringifyFilterAst,
|
|
188
199
|
throttle: () => throttle,
|
|
189
200
|
toArray: () => toArray,
|
|
190
201
|
toCsv: () => toCsv,
|
|
@@ -196,6 +207,7 @@ var zx = (() => {
|
|
|
196
207
|
uniqueBy: () => uniqueBy,
|
|
197
208
|
useBuiltinIcons: () => useBuiltinIcons,
|
|
198
209
|
useFontAwesome: () => useFontAwesome,
|
|
210
|
+
validateFilterAst: () => validateFilterAst,
|
|
199
211
|
zeyosService: () => zeyosService
|
|
200
212
|
});
|
|
201
213
|
|
|
@@ -274,6 +286,20 @@ var zx = (() => {
|
|
|
274
286
|
return Boolean(value && typeof value === "object" && /** @type {{nodeType?: unknown}} */
|
|
275
287
|
value.nodeType === 1);
|
|
276
288
|
}
|
|
289
|
+
function isCssColor(value) {
|
|
290
|
+
if (typeof value !== "string") return false;
|
|
291
|
+
const text4 = value.trim();
|
|
292
|
+
if (!text4 || text4.length > 256 || /[;{}\u0000-\u001f\u007f]/.test(text4) || /(?:url|image|image-set|cross-fade|element|var)\s*\(/i.test(text4)) return false;
|
|
293
|
+
const supports = globalThis.CSS?.supports;
|
|
294
|
+
if (typeof supports === "function") {
|
|
295
|
+
try {
|
|
296
|
+
return supports.call(globalThis.CSS, "color", text4);
|
|
297
|
+
} catch {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return /^(?:#[\da-f]{3,8}|[a-z]+|(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color|color-mix)\([^;{}]+\))$/i.test(text4);
|
|
302
|
+
}
|
|
277
303
|
function clamp(n, min, max) {
|
|
278
304
|
return Math.min(max, Math.max(min, n));
|
|
279
305
|
}
|
|
@@ -468,6 +494,18 @@ var zx = (() => {
|
|
|
468
494
|
return element;
|
|
469
495
|
}
|
|
470
496
|
var __ = compactElement;
|
|
497
|
+
function safeHref(value) {
|
|
498
|
+
if (typeof value !== "string") return null;
|
|
499
|
+
const href = value.trim();
|
|
500
|
+
if (!href) return null;
|
|
501
|
+
try {
|
|
502
|
+
const protocol = new URL(href, globalThis.location?.href ?? "https://zx.invalid/").protocol;
|
|
503
|
+
if (["javascript:", "data:", "vbscript:"].includes(protocol.toLowerCase())) return null;
|
|
504
|
+
} catch {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
return href;
|
|
508
|
+
}
|
|
471
509
|
function applyCompactProperties(element, properties) {
|
|
472
510
|
for (const name in properties ?? {}) {
|
|
473
511
|
const value = properties[name];
|
|
@@ -899,9 +937,9 @@ var zx = (() => {
|
|
|
899
937
|
let rendered;
|
|
900
938
|
try {
|
|
901
939
|
rendered = h.scope(this.refs, () => this.render());
|
|
902
|
-
} catch (
|
|
940
|
+
} catch (error2) {
|
|
903
941
|
this.#abort.abort();
|
|
904
|
-
throw
|
|
942
|
+
throw error2;
|
|
905
943
|
}
|
|
906
944
|
if (this.#created) {
|
|
907
945
|
const root = isElement(rendered) ? rendered : this.el;
|
|
@@ -1192,8 +1230,8 @@ var zx = (() => {
|
|
|
1192
1230
|
/** @type {Readonly<FieldOptions>} */
|
|
1193
1231
|
this.options
|
|
1194
1232
|
));
|
|
1195
|
-
} catch (
|
|
1196
|
-
const message =
|
|
1233
|
+
} catch (error2) {
|
|
1234
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
1197
1235
|
console.warn(`[zx.Field] Adapter "${type}" failed: ${message}`);
|
|
1198
1236
|
this.adapter = errorAdapter(message, this.controlId);
|
|
1199
1237
|
}
|
|
@@ -5176,8 +5214,8 @@ var zx = (() => {
|
|
|
5176
5214
|
return response;
|
|
5177
5215
|
} catch (caught) {
|
|
5178
5216
|
if (runId !== this._runId) return null;
|
|
5179
|
-
const
|
|
5180
|
-
if (
|
|
5217
|
+
const error2 = caught instanceof Error ? caught : new Error(String(caught));
|
|
5218
|
+
if (error2.name === "AbortError") {
|
|
5181
5219
|
this._finishAbort();
|
|
5182
5220
|
return null;
|
|
5183
5221
|
}
|
|
@@ -5186,8 +5224,8 @@ var zx = (() => {
|
|
|
5186
5224
|
this._transportController = null;
|
|
5187
5225
|
this._abort.hidden = true;
|
|
5188
5226
|
this._setState("error");
|
|
5189
|
-
this._status.textContent =
|
|
5190
|
-
this.emit("error", { error });
|
|
5227
|
+
this._status.textContent = error2.message;
|
|
5228
|
+
this.emit("error", { error: error2 });
|
|
5191
5229
|
return null;
|
|
5192
5230
|
}
|
|
5193
5231
|
}
|
|
@@ -5335,10 +5373,10 @@ var zx = (() => {
|
|
|
5335
5373
|
this.emit("abort", {});
|
|
5336
5374
|
}
|
|
5337
5375
|
/** @param {Error} error @returns {void} */
|
|
5338
|
-
_selectionError(
|
|
5376
|
+
_selectionError(error2) {
|
|
5339
5377
|
this._setState("error");
|
|
5340
|
-
this._status.textContent =
|
|
5341
|
-
this.emit("error", { error });
|
|
5378
|
+
this._status.textContent = error2.message;
|
|
5379
|
+
this.emit("error", { error: error2 });
|
|
5342
5380
|
}
|
|
5343
5381
|
/** @returns {void} */
|
|
5344
5382
|
_renderPreview() {
|
|
@@ -5421,9 +5459,9 @@ var zx = (() => {
|
|
|
5421
5459
|
}
|
|
5422
5460
|
function abortError() {
|
|
5423
5461
|
if (typeof DOMException === "function") return new DOMException("Upload aborted", "AbortError");
|
|
5424
|
-
const
|
|
5425
|
-
|
|
5426
|
-
return
|
|
5462
|
+
const error2 = new Error("Upload aborted");
|
|
5463
|
+
error2.name = "AbortError";
|
|
5464
|
+
return error2;
|
|
5427
5465
|
}
|
|
5428
5466
|
|
|
5429
5467
|
// src/components/field-upload/field-adapter.js
|
|
@@ -6549,8 +6587,8 @@ var zx = (() => {
|
|
|
6549
6587
|
const shown = this._preview ?? this._value;
|
|
6550
6588
|
const symbols = this.refs.symbols.querySelectorAll(".zx-rating__symbol");
|
|
6551
6589
|
symbols.forEach((symbol, index) => {
|
|
6552
|
-
const
|
|
6553
|
-
symbol.style.setProperty("--zx-rating-fill", `${
|
|
6590
|
+
const filled2 = Math.min(1, Math.max(0, shown - index));
|
|
6591
|
+
symbol.style.setProperty("--zx-rating-fill", `${filled2 * 100}%`);
|
|
6554
6592
|
symbol.toggleAttribute("data-preview", this._preview !== null);
|
|
6555
6593
|
});
|
|
6556
6594
|
this.el.dataset.value = String(shown);
|
|
@@ -6818,17 +6856,33 @@ var zx = (() => {
|
|
|
6818
6856
|
"priority.high",
|
|
6819
6857
|
"priority.highest"
|
|
6820
6858
|
];
|
|
6821
|
-
var PRIORITY_FALLBACKS = ["Lowest", "Low", "
|
|
6859
|
+
var PRIORITY_FALLBACKS = ["Lowest", "Low", "Medium", "High", "Highest"];
|
|
6860
|
+
var DEFAULT_STATUSES = Object.freeze([
|
|
6861
|
+
["backlog", "status.backlog", "Backlog", "dotted", "muted"],
|
|
6862
|
+
["todo", "status.todo", "Todo", "ring", "neutral"],
|
|
6863
|
+
["in-progress", "status.inProgress", "In Progress", "progress", "warning"],
|
|
6864
|
+
["in-review", "status.inReview", "In Review", "review", "info"],
|
|
6865
|
+
["ready", "status.ready", "Ready to Merge", "ready", "accent"],
|
|
6866
|
+
["waiting", "status.waiting", "Waiting for feedback", "waiting", "warning"],
|
|
6867
|
+
["done", "status.done", "Done", "done", "info"],
|
|
6868
|
+
["canceled", "status.canceled", "Canceled", "canceled", "muted"],
|
|
6869
|
+
["duplicate", "status.duplicate", "Duplicate", "duplicate", "muted"]
|
|
6870
|
+
]);
|
|
6822
6871
|
var Select = class _Select extends Component {
|
|
6823
6872
|
static cssName = "select";
|
|
6824
6873
|
/** @type {SelectOptions} */
|
|
6825
6874
|
static defaults = {
|
|
6826
6875
|
items: [],
|
|
6827
6876
|
fixedItems: [],
|
|
6877
|
+
fixedLabel: null,
|
|
6828
6878
|
valueKey: "ID",
|
|
6829
6879
|
labelKey: "name",
|
|
6830
6880
|
renderItem: null,
|
|
6831
6881
|
renderValue: null,
|
|
6882
|
+
renderValueAdornment: null,
|
|
6883
|
+
renderNone: null,
|
|
6884
|
+
noneLabel: null,
|
|
6885
|
+
actions: [],
|
|
6832
6886
|
value: null,
|
|
6833
6887
|
disabled: false,
|
|
6834
6888
|
placeholder: "",
|
|
@@ -6846,6 +6900,7 @@ var zx = (() => {
|
|
|
6846
6900
|
this._visibleItems = [];
|
|
6847
6901
|
this._fixedItems = [];
|
|
6848
6902
|
this._visibleFixed = [];
|
|
6903
|
+
this._actions = [];
|
|
6849
6904
|
this._selected = null;
|
|
6850
6905
|
this._optionEntries = [];
|
|
6851
6906
|
this._activeIndex = -1;
|
|
@@ -6857,8 +6912,7 @@ var zx = (() => {
|
|
|
6857
6912
|
this._position = null;
|
|
6858
6913
|
this._createdRoot = false;
|
|
6859
6914
|
this._root = null;
|
|
6860
|
-
this.
|
|
6861
|
-
this._originalAttributes = /* @__PURE__ */ new Map();
|
|
6915
|
+
this._snapshot = null;
|
|
6862
6916
|
this._createdRoot = this.el === null;
|
|
6863
6917
|
const root = (
|
|
6864
6918
|
/** @type {HTMLElement} */
|
|
@@ -6866,10 +6920,7 @@ var zx = (() => {
|
|
|
6866
6920
|
);
|
|
6867
6921
|
this.el = root;
|
|
6868
6922
|
this._root = root;
|
|
6869
|
-
|
|
6870
|
-
this._rememberAttribute(root, "data-state");
|
|
6871
|
-
this._rememberAttribute(root, "data-loading");
|
|
6872
|
-
this._rememberAttribute(root, "aria-disabled");
|
|
6923
|
+
this._snapshot = this._createdRoot ? null : snapshotTarget(root);
|
|
6873
6924
|
const listId = uid("zx-select-list");
|
|
6874
6925
|
const input = h("input", {
|
|
6875
6926
|
ref: "input",
|
|
@@ -6884,6 +6935,12 @@ var zx = (() => {
|
|
|
6884
6935
|
placeholder: String(this.options.placeholder ?? ""),
|
|
6885
6936
|
readOnly: !this.options.filter
|
|
6886
6937
|
});
|
|
6938
|
+
const valueAdornment = h("span", {
|
|
6939
|
+
ref: "valueAdornment",
|
|
6940
|
+
class: "zx-select__value-adornment",
|
|
6941
|
+
ariaHidden: "true",
|
|
6942
|
+
hidden: true
|
|
6943
|
+
});
|
|
6887
6944
|
const clear = h("button", {
|
|
6888
6945
|
ref: "clear",
|
|
6889
6946
|
class: "zx-icon-btn zx-select__clear",
|
|
@@ -6909,6 +6966,7 @@ var zx = (() => {
|
|
|
6909
6966
|
const control = h(
|
|
6910
6967
|
"div",
|
|
6911
6968
|
{ ref: "control", class: "zx-select__control" },
|
|
6969
|
+
valueAdornment,
|
|
6912
6970
|
input,
|
|
6913
6971
|
clear,
|
|
6914
6972
|
loading,
|
|
@@ -6931,9 +6989,10 @@ var zx = (() => {
|
|
|
6931
6989
|
this._visibleItems = this._items.slice();
|
|
6932
6990
|
this._fixedItems = Array.isArray(this.options.fixedItems) ? this.options.fixedItems.slice() : [];
|
|
6933
6991
|
this._visibleFixed = this._fixedItems.slice();
|
|
6992
|
+
this._actions = normalizeSelectActions(this.options.actions);
|
|
6934
6993
|
this._disabled = Boolean(this.options.disabled);
|
|
6935
6994
|
this._handleTypeahead = typeahead(
|
|
6936
|
-
() => this._optionEntries.filter((entry) => !entry.clear).map((entry) => entry.element),
|
|
6995
|
+
() => this._optionEntries.filter((entry) => !entry.clear && !entry.action).map((entry) => entry.element),
|
|
6937
6996
|
(_option, index) => {
|
|
6938
6997
|
if (!this._open) this.open();
|
|
6939
6998
|
this._setActive(this._itemEntryIndex(index));
|
|
@@ -7028,6 +7087,13 @@ var zx = (() => {
|
|
|
7028
7087
|
this._position?.update();
|
|
7029
7088
|
return this;
|
|
7030
7089
|
}
|
|
7090
|
+
/** Replaces command rows without changing the selected value. @param {SelectAction[]} actions @returns {this} */
|
|
7091
|
+
setActions(actions2) {
|
|
7092
|
+
this._actions = normalizeSelectActions(actions2);
|
|
7093
|
+
this._renderOptions();
|
|
7094
|
+
this._position?.update();
|
|
7095
|
+
return this;
|
|
7096
|
+
}
|
|
7031
7097
|
/**
|
|
7032
7098
|
* Restores the configured initial value.
|
|
7033
7099
|
* @param {{silent?: boolean}} [options={}] Reset behavior.
|
|
@@ -7112,24 +7178,109 @@ var zx = (() => {
|
|
|
7112
7178
|
value: null,
|
|
7113
7179
|
items: [],
|
|
7114
7180
|
renderItem(item) {
|
|
7181
|
+
return priorityContent(item, true);
|
|
7182
|
+
},
|
|
7183
|
+
renderValueAdornment(item) {
|
|
7184
|
+
return priorityIndicator(item.level);
|
|
7185
|
+
}
|
|
7186
|
+
});
|
|
7187
|
+
select.setItems(PRIORITY_KEYS.map((key, level) => {
|
|
7188
|
+
let name = level === 2 ? select.msg("priority.medium") : key;
|
|
7189
|
+
if (name === "priority.medium") name = select._message(key, PRIORITY_FALLBACKS[level]);
|
|
7190
|
+
else if (level !== 2) name = select._message(key, PRIORITY_FALLBACKS[level]);
|
|
7191
|
+
return { ID: level, name, level };
|
|
7192
|
+
}));
|
|
7193
|
+
select.set(requestedValue, { silent: true });
|
|
7194
|
+
return select;
|
|
7195
|
+
}
|
|
7196
|
+
/**
|
|
7197
|
+
* Creates a status picker with shape- and color-distinct workflow indicators.
|
|
7198
|
+
* @param {Element|string|null} target Component target.
|
|
7199
|
+
* @param {SelectOptions & {items?: Array<Record<string, any>>}} [options={}] Select overrides and status items.
|
|
7200
|
+
* @returns {Select}
|
|
7201
|
+
*/
|
|
7202
|
+
static status(target, options = {}) {
|
|
7203
|
+
const { items = null, value = null, ...rest } = options;
|
|
7204
|
+
const select = new _Select(target, {
|
|
7205
|
+
...rest,
|
|
7206
|
+
value: null,
|
|
7207
|
+
items: [],
|
|
7208
|
+
renderItem(item) {
|
|
7209
|
+
return statusContent(item);
|
|
7210
|
+
},
|
|
7211
|
+
renderValueAdornment(item) {
|
|
7212
|
+
return statusIndicator(item);
|
|
7213
|
+
}
|
|
7214
|
+
});
|
|
7215
|
+
const statuses = Array.isArray(items) ? items.map(normalizeStatusItem).filter(Boolean) : DEFAULT_STATUSES.map(([ID, key, fallback, indicator, tone], index) => ({
|
|
7216
|
+
ID,
|
|
7217
|
+
name: select._message(key, fallback),
|
|
7218
|
+
indicator,
|
|
7219
|
+
tone,
|
|
7220
|
+
shortcut: String(index + 1)
|
|
7221
|
+
}));
|
|
7222
|
+
select.setItems(statuses);
|
|
7223
|
+
select.set(value, { silent: true });
|
|
7224
|
+
return select;
|
|
7225
|
+
}
|
|
7226
|
+
/**
|
|
7227
|
+
* Creates a rich entity picker with optional Recent choices and a create command.
|
|
7228
|
+
* @param {Element|string|null} target Component target.
|
|
7229
|
+
* @param {SelectOptions & EntitySelectOptions} [options={}] Entity-preset options.
|
|
7230
|
+
* @returns {Select}
|
|
7231
|
+
*/
|
|
7232
|
+
static entity(target, options = {}) {
|
|
7233
|
+
const {
|
|
7234
|
+
recent = [],
|
|
7235
|
+
recentLabel = "Recent",
|
|
7236
|
+
create = false,
|
|
7237
|
+
iconKey = "icon",
|
|
7238
|
+
colorKey = "color",
|
|
7239
|
+
subtitleKey = "description",
|
|
7240
|
+
renderIcon = null,
|
|
7241
|
+
fixedItems = [],
|
|
7242
|
+
actions: actions2 = [],
|
|
7243
|
+
renderItem = null,
|
|
7244
|
+
renderValueAdornment = null,
|
|
7245
|
+
...rest
|
|
7246
|
+
} = options;
|
|
7247
|
+
const visual = (item, selected = false) => {
|
|
7248
|
+
if (typeof renderIcon === "function") return renderIcon(item, { selected });
|
|
7249
|
+
const value = readSelectItem(item, iconKey);
|
|
7250
|
+
if (value && typeof value === "object" && typeof value.nodeType === "number") return value.cloneNode(true);
|
|
7251
|
+
if (typeof value !== "string" || !value) return null;
|
|
7252
|
+
const color = readSelectItem(item, colorKey);
|
|
7253
|
+
return h("span", {
|
|
7254
|
+
class: "zx-select__entity-glyph",
|
|
7255
|
+
style: isCssColor(color) ? { "--zx-select-entity-color": String(color) } : null,
|
|
7256
|
+
ariaHidden: "true"
|
|
7257
|
+
}, icon(value, { size: 14 }));
|
|
7258
|
+
};
|
|
7259
|
+
const createAction = create ? normalizeEntityCreateAction(create) : null;
|
|
7260
|
+
const select = new _Select(target, {
|
|
7261
|
+
...rest,
|
|
7262
|
+
fixedItems: [...Array.isArray(recent) ? recent : [], ...Array.isArray(fixedItems) ? fixedItems : []],
|
|
7263
|
+
fixedLabel: Array.isArray(recent) && recent.length ? recentLabel : rest.fixedLabel ?? null,
|
|
7264
|
+
actions: [...Array.isArray(actions2) ? actions2 : [], ...createAction ? [createAction] : []],
|
|
7265
|
+
renderItem: renderItem ?? ((item) => {
|
|
7266
|
+
const decoration = visual(item, false);
|
|
7267
|
+
const subtitle = readSelectItem(item, subtitleKey);
|
|
7268
|
+
const copy = h(
|
|
7269
|
+
"span",
|
|
7270
|
+
{ class: "zx-select__entity-copy" },
|
|
7271
|
+
h("span", { class: "zx-select__entity-label" }, String(readSelectItem(item, rest.labelKey ?? "name") ?? "")),
|
|
7272
|
+
subtitle == null || subtitle === "" ? null : h("span", { class: "zx-select__entity-subtitle" }, String(subtitle))
|
|
7273
|
+
);
|
|
7115
7274
|
return h(
|
|
7116
7275
|
"span",
|
|
7117
|
-
{ class: "zx-
|
|
7118
|
-
h("span", {
|
|
7119
|
-
|
|
7120
|
-
dataset: { level: item.level },
|
|
7121
|
-
ariaHidden: "true"
|
|
7122
|
-
}),
|
|
7123
|
-
h("span", {}, String(item.name))
|
|
7276
|
+
{ class: "zx-select__entity" },
|
|
7277
|
+
decoration ? h("span", { class: "zx-select__entity-icon", ariaHidden: "true" }, decoration) : null,
|
|
7278
|
+
copy
|
|
7124
7279
|
);
|
|
7125
|
-
}
|
|
7280
|
+
}),
|
|
7281
|
+
renderValueAdornment: renderValueAdornment ?? ((item) => visual(item, true))
|
|
7126
7282
|
});
|
|
7127
|
-
select.
|
|
7128
|
-
ID: level,
|
|
7129
|
-
name: select._message(key, PRIORITY_FALLBACKS[level]),
|
|
7130
|
-
level
|
|
7131
|
-
})));
|
|
7132
|
-
select.set(requestedValue, { silent: true });
|
|
7283
|
+
select.el.dataset.preset = "entity";
|
|
7133
7284
|
return select;
|
|
7134
7285
|
}
|
|
7135
7286
|
/**
|
|
@@ -7144,12 +7295,21 @@ var zx = (() => {
|
|
|
7144
7295
|
* @returns {Select}
|
|
7145
7296
|
*/
|
|
7146
7297
|
static permission(target, options = {}) {
|
|
7147
|
-
const {
|
|
7298
|
+
const {
|
|
7299
|
+
groups = [],
|
|
7300
|
+
value = true,
|
|
7301
|
+
valueKey = "ID",
|
|
7302
|
+
labelKey = "name",
|
|
7303
|
+
filter = "local",
|
|
7304
|
+
...rest
|
|
7305
|
+
} = options;
|
|
7148
7306
|
const loader = typeof groups === "function" ? groups : null;
|
|
7149
7307
|
const shareable = loader ? [] : groups;
|
|
7150
7308
|
const choices = [{ ID: false, name: "Private" }, { ID: true, name: "Public" }];
|
|
7151
7309
|
const select = new _Select(target, {
|
|
7152
|
-
|
|
7310
|
+
// An async group loader is the data source and cannot be disconnected by an incidental
|
|
7311
|
+
// `filter` override. Array-backed groups still honor local/disabled filtering.
|
|
7312
|
+
filter: loader ?? filter,
|
|
7153
7313
|
...rest,
|
|
7154
7314
|
value: null,
|
|
7155
7315
|
items: shareable,
|
|
@@ -7173,18 +7333,24 @@ var zx = (() => {
|
|
|
7173
7333
|
/** @type {HTMLElement} */
|
|
7174
7334
|
this.el
|
|
7175
7335
|
);
|
|
7176
|
-
if (!this._createdRoot && root)
|
|
7177
|
-
root.replaceChildren(...this._originalChildren);
|
|
7178
|
-
for (const [name, value] of this._originalAttributes) {
|
|
7179
|
-
if (value === null) root.removeAttribute(name);
|
|
7180
|
-
else root.setAttribute(name, value);
|
|
7181
|
-
}
|
|
7182
|
-
}
|
|
7336
|
+
if (!this._createdRoot && root) restoreTarget(root, this._snapshot);
|
|
7183
7337
|
super.destroy();
|
|
7184
7338
|
}
|
|
7185
7339
|
/** @param {KeyboardEvent} event @returns {void} */
|
|
7186
7340
|
_onKeydown(event) {
|
|
7187
7341
|
if (this._disabled) return;
|
|
7342
|
+
if (this._open && isPrintable(event)) {
|
|
7343
|
+
const shortcut = this._optionEntries.findIndex((entry) => !entry.clear && !entry.action && String(
|
|
7344
|
+
/** @type {Record<string, any>|null} */
|
|
7345
|
+
entry.item?.shortcut ?? ""
|
|
7346
|
+
) === event.key);
|
|
7347
|
+
if (shortcut >= 0) {
|
|
7348
|
+
event.preventDefault();
|
|
7349
|
+
this._setActive(shortcut);
|
|
7350
|
+
this._chooseEntry(this._optionEntries[shortcut]);
|
|
7351
|
+
return;
|
|
7352
|
+
}
|
|
7353
|
+
}
|
|
7188
7354
|
if (event.key === "ArrowDown") {
|
|
7189
7355
|
event.preventDefault();
|
|
7190
7356
|
if (!this._open) this.open();
|
|
@@ -7232,6 +7398,7 @@ var zx = (() => {
|
|
|
7232
7398
|
/** @type {HTMLInputElement} */
|
|
7233
7399
|
this.refs.input.value
|
|
7234
7400
|
);
|
|
7401
|
+
this._syncValueAdornment();
|
|
7235
7402
|
if (!this._open) this.open();
|
|
7236
7403
|
if (this.options.filter === "local") {
|
|
7237
7404
|
this._visibleFixed = this._matchFixed(query);
|
|
@@ -7255,6 +7422,22 @@ var zx = (() => {
|
|
|
7255
7422
|
/** @param {SelectOptionEntry|undefined} entry @returns {void} */
|
|
7256
7423
|
_chooseEntry(entry) {
|
|
7257
7424
|
if (!entry) return;
|
|
7425
|
+
if (entry.action) {
|
|
7426
|
+
const detail = {
|
|
7427
|
+
id: entry.action.id,
|
|
7428
|
+
action: entry.action,
|
|
7429
|
+
query: (
|
|
7430
|
+
/** @type {HTMLInputElement} */
|
|
7431
|
+
this.refs.input.value
|
|
7432
|
+
),
|
|
7433
|
+
select: this
|
|
7434
|
+
};
|
|
7435
|
+
const action = this.emit("action", detail);
|
|
7436
|
+
if (!action.defaultPrevented) entry.action.invoke?.(detail);
|
|
7437
|
+
this.close();
|
|
7438
|
+
this.focus();
|
|
7439
|
+
return;
|
|
7440
|
+
}
|
|
7258
7441
|
this.set(entry.clear ? null : entry.item);
|
|
7259
7442
|
this.close();
|
|
7260
7443
|
this.focus();
|
|
@@ -7287,14 +7470,19 @@ var zx = (() => {
|
|
|
7287
7470
|
list.replaceChildren();
|
|
7288
7471
|
this._optionEntries = [];
|
|
7289
7472
|
let lastGroup = /* @__PURE__ */ Symbol("initial-group");
|
|
7473
|
+
const fixed = this._uniqueItems(this._visibleFixed);
|
|
7474
|
+
const items = this._uniqueItems(this._visibleItems, fixed);
|
|
7290
7475
|
if (this.options.clearable) {
|
|
7291
|
-
this._appendOption(null, true, this.options.
|
|
7476
|
+
this._appendOption(null, true, this.options.noneLabel ?? this._message("select.none", "No selection"));
|
|
7292
7477
|
}
|
|
7293
|
-
|
|
7294
|
-
|
|
7478
|
+
if (fixed.length > 0 && this.options.fixedLabel) {
|
|
7479
|
+
list.append(h("div", { class: "zx-select__group", role: "presentation" }, String(this.options.fixedLabel)));
|
|
7480
|
+
}
|
|
7481
|
+
for (const item of fixed) this._appendOption(item, false);
|
|
7482
|
+
if (fixed.length > 0 && items.length > 0 && !this.options.groupKey) {
|
|
7295
7483
|
list.append(h("div", { class: "zx-select__separator", role: "presentation" }));
|
|
7296
7484
|
}
|
|
7297
|
-
for (const item of
|
|
7485
|
+
for (const item of items) {
|
|
7298
7486
|
if (this.options.groupKey) {
|
|
7299
7487
|
const group = this._read(item, this.options.groupKey);
|
|
7300
7488
|
if (!Object.is(group, lastGroup)) {
|
|
@@ -7307,7 +7495,23 @@ var zx = (() => {
|
|
|
7307
7495
|
}
|
|
7308
7496
|
this._appendOption(item, false);
|
|
7309
7497
|
}
|
|
7310
|
-
if (this.
|
|
7498
|
+
if (this._actions.length > 0) {
|
|
7499
|
+
if (fixed.length > 0 || items.length > 0 || this.options.clearable) {
|
|
7500
|
+
list.append(h("div", { class: "zx-select__separator", role: "presentation" }));
|
|
7501
|
+
}
|
|
7502
|
+
let actionGroup = /* @__PURE__ */ Symbol("initial-action-group");
|
|
7503
|
+
for (const action of this._actions) {
|
|
7504
|
+
if (action.group && action.group !== actionGroup) {
|
|
7505
|
+
list.append(h("div", {
|
|
7506
|
+
class: "zx-select__group",
|
|
7507
|
+
role: "presentation"
|
|
7508
|
+
}, action.group));
|
|
7509
|
+
actionGroup = action.group;
|
|
7510
|
+
}
|
|
7511
|
+
this._appendAction(action);
|
|
7512
|
+
}
|
|
7513
|
+
}
|
|
7514
|
+
if (items.length === 0 && fixed.length === 0 && this._actions.length === 0 && !this._loading) {
|
|
7311
7515
|
list.append(h("div", {
|
|
7312
7516
|
class: "zx-select__empty",
|
|
7313
7517
|
role: "status"
|
|
@@ -7319,7 +7523,7 @@ var zx = (() => {
|
|
|
7319
7523
|
_appendOption(item, clear, label) {
|
|
7320
7524
|
const selected = clear ? this._selected === null : this._selected !== null && Object.is(this._itemValue(item), this.value);
|
|
7321
7525
|
const index = this._optionEntries.length;
|
|
7322
|
-
const content = clear ? String(label ?? "") : this._renderItem(item);
|
|
7526
|
+
const content = clear && typeof this.options.renderNone === "function" ? normalizeRenderedContent(this.options.renderNone(String(label ?? ""))) : clear ? String(label ?? "") : this._renderItem(item);
|
|
7323
7527
|
const option = h("div", {
|
|
7324
7528
|
class: "zx-select__option",
|
|
7325
7529
|
id: uid("zx-select-option"),
|
|
@@ -7329,16 +7533,37 @@ var zx = (() => {
|
|
|
7329
7533
|
}, h("span", { class: "zx-select__option-content" }, content));
|
|
7330
7534
|
if (selected) option.append(icon("check", { size: 15 }));
|
|
7331
7535
|
this.refs.list.append(option);
|
|
7332
|
-
this._optionEntries.push({ item, clear, element: option });
|
|
7536
|
+
this._optionEntries.push({ item, clear, action: null, element: option });
|
|
7537
|
+
}
|
|
7538
|
+
/** @param {SelectAction} action @returns {void} */
|
|
7539
|
+
_appendAction(action) {
|
|
7540
|
+
const index = this._optionEntries.length;
|
|
7541
|
+
const visual = selectActionIcon(action.icon);
|
|
7542
|
+
const option = h("div", {
|
|
7543
|
+
class: "zx-select__option zx-select__action",
|
|
7544
|
+
id: uid("zx-select-option"),
|
|
7545
|
+
role: "option",
|
|
7546
|
+
ariaSelected: "false",
|
|
7547
|
+
dataset: { index, kind: "action" }
|
|
7548
|
+
}, h(
|
|
7549
|
+
"span",
|
|
7550
|
+
{ class: "zx-select__option-content zx-select__action-content" },
|
|
7551
|
+
visual ? h("span", { class: "zx-select__action-icon", ariaHidden: "true" }, visual) : null,
|
|
7552
|
+
h(
|
|
7553
|
+
"span",
|
|
7554
|
+
{ class: "zx-select__action-copy" },
|
|
7555
|
+
h("span", { class: "zx-select__action-label" }, action.label),
|
|
7556
|
+
action.description ? h("span", { class: "zx-select__action-description" }, action.description) : null
|
|
7557
|
+
)
|
|
7558
|
+
));
|
|
7559
|
+
this.refs.list.append(option);
|
|
7560
|
+
this._optionEntries.push({ item: null, clear: false, action, element: option });
|
|
7333
7561
|
}
|
|
7334
7562
|
/** @param {SelectItem|null} item @returns {Node|string} */
|
|
7335
7563
|
_renderItem(item) {
|
|
7336
7564
|
if (typeof this.options.renderItem === "function") {
|
|
7337
7565
|
const rendered = this.options.renderItem(item);
|
|
7338
|
-
return rendered
|
|
7339
|
-
/** @type {Node} */
|
|
7340
|
-
rendered
|
|
7341
|
-
) : String(rendered ?? "");
|
|
7566
|
+
return normalizeRenderedContent(rendered);
|
|
7342
7567
|
}
|
|
7343
7568
|
return this._itemLabel(item);
|
|
7344
7569
|
}
|
|
@@ -7409,9 +7634,29 @@ var zx = (() => {
|
|
|
7409
7634
|
this.refs.input
|
|
7410
7635
|
);
|
|
7411
7636
|
input.value = this._selected === null ? "" : typeof this.options.renderValue === "function" ? String(this.options.renderValue(this._selected) ?? "") : this._itemLabel(this._selected);
|
|
7637
|
+
this._syncValueAdornment();
|
|
7412
7638
|
this.refs.clear.hidden = !this.options.clearable || this._selected === null;
|
|
7413
7639
|
}
|
|
7414
7640
|
/** @returns {void} */
|
|
7641
|
+
_syncValueAdornment() {
|
|
7642
|
+
const host = this.refs.valueAdornment;
|
|
7643
|
+
if (!host) return;
|
|
7644
|
+
const selectedLabel = this._selected === null ? "" : typeof this.options.renderValue === "function" ? String(this.options.renderValue(this._selected) ?? "") : this._itemLabel(this._selected);
|
|
7645
|
+
const query = String(
|
|
7646
|
+
/** @type {HTMLInputElement} */
|
|
7647
|
+
this.refs.input?.value ?? ""
|
|
7648
|
+
);
|
|
7649
|
+
if (this._selected === null || typeof this.options.renderValueAdornment !== "function" || Boolean(this.options.filter && query !== selectedLabel)) {
|
|
7650
|
+
host.replaceChildren();
|
|
7651
|
+
host.hidden = true;
|
|
7652
|
+
return;
|
|
7653
|
+
}
|
|
7654
|
+
const rendered = this.options.renderValueAdornment(this._selected);
|
|
7655
|
+
const content = normalizeRenderedContent(rendered);
|
|
7656
|
+
host.replaceChildren(content instanceof Node ? content : document.createTextNode(content));
|
|
7657
|
+
host.hidden = rendered == null || typeof content === "string" && content === "";
|
|
7658
|
+
}
|
|
7659
|
+
/** @returns {void} */
|
|
7415
7660
|
_syncDisabled() {
|
|
7416
7661
|
const input = (
|
|
7417
7662
|
/** @type {HTMLInputElement} */
|
|
@@ -7467,6 +7712,16 @@ var zx = (() => {
|
|
|
7467
7712
|
_matchFixed(query) {
|
|
7468
7713
|
return matchItems(this._fixedItems, query, this._searchKeys());
|
|
7469
7714
|
}
|
|
7715
|
+
/** De-duplicates choices by public value while preserving caller order. @param {SelectItem[]} items @param {SelectItem[]} [excluded=[]] @returns {SelectItem[]} */
|
|
7716
|
+
_uniqueItems(items, excluded = []) {
|
|
7717
|
+
const values = excluded.map((item) => this._itemValue(item));
|
|
7718
|
+
return items.filter((item) => {
|
|
7719
|
+
const value = this._itemValue(item);
|
|
7720
|
+
if (values.some((candidate) => Object.is(candidate, value))) return false;
|
|
7721
|
+
values.push(value);
|
|
7722
|
+
return true;
|
|
7723
|
+
});
|
|
7724
|
+
}
|
|
7470
7725
|
/** @returns {number} */
|
|
7471
7726
|
_selectedEntryIndex() {
|
|
7472
7727
|
if (this._selected === null) return this.options.clearable ? 0 : -1;
|
|
@@ -7478,16 +7733,104 @@ var zx = (() => {
|
|
|
7478
7733
|
_itemEntryIndex(itemIndex) {
|
|
7479
7734
|
return itemIndex + (this.options.clearable ? 1 : 0);
|
|
7480
7735
|
}
|
|
7481
|
-
/** @param {string} name @returns {void} */
|
|
7482
|
-
_rememberAttribute(element, name) {
|
|
7483
|
-
this._originalAttributes.set(name, element.getAttribute(name));
|
|
7484
|
-
}
|
|
7485
7736
|
/** @param {string} key @param {string} fallback @returns {string} */
|
|
7486
7737
|
_message(key, fallback) {
|
|
7487
7738
|
const message = this.msg(key);
|
|
7488
7739
|
return message === key ? fallback : message;
|
|
7489
7740
|
}
|
|
7490
7741
|
};
|
|
7742
|
+
function readSelectItem(item, reader) {
|
|
7743
|
+
if (typeof reader === "function") return reader(item);
|
|
7744
|
+
if (item !== null && typeof item === "object" && typeof reader === "string") return item[reader];
|
|
7745
|
+
return null;
|
|
7746
|
+
}
|
|
7747
|
+
function normalizeEntityCreateAction(value) {
|
|
7748
|
+
const descriptor = value === true ? {} : value;
|
|
7749
|
+
return {
|
|
7750
|
+
id: descriptor.id ?? "create",
|
|
7751
|
+
label: descriptor.label ?? "Create new\u2026",
|
|
7752
|
+
description: descriptor.description,
|
|
7753
|
+
group: descriptor.group ?? "New",
|
|
7754
|
+
icon: descriptor.icon ?? "plus",
|
|
7755
|
+
invoke: descriptor.invoke
|
|
7756
|
+
};
|
|
7757
|
+
}
|
|
7758
|
+
function normalizeRenderedContent(rendered) {
|
|
7759
|
+
return rendered && typeof rendered === "object" && "nodeType" in rendered ? (
|
|
7760
|
+
/** @type {Node} */
|
|
7761
|
+
rendered
|
|
7762
|
+
) : String(rendered ?? "");
|
|
7763
|
+
}
|
|
7764
|
+
function normalizeSelectActions(actions2) {
|
|
7765
|
+
if (!Array.isArray(actions2)) return [];
|
|
7766
|
+
return actions2.filter((action) => action && typeof action === "object" && action.id != null && action.label != null).map((action) => ({
|
|
7767
|
+
...action,
|
|
7768
|
+
label: String(action.label),
|
|
7769
|
+
description: action.description == null ? void 0 : String(action.description),
|
|
7770
|
+
group: action.group == null ? void 0 : String(action.group)
|
|
7771
|
+
}));
|
|
7772
|
+
}
|
|
7773
|
+
function selectActionIcon(visual) {
|
|
7774
|
+
if (typeof visual === "string") return icon(visual);
|
|
7775
|
+
if (visual instanceof Node) return visual.cloneNode(true);
|
|
7776
|
+
return null;
|
|
7777
|
+
}
|
|
7778
|
+
function priorityContent(item, label) {
|
|
7779
|
+
return h(
|
|
7780
|
+
"span",
|
|
7781
|
+
{ class: "zx-select__priority" },
|
|
7782
|
+
priorityIndicator(item.level),
|
|
7783
|
+
label ? h("span", {}, String(item.name)) : null
|
|
7784
|
+
);
|
|
7785
|
+
}
|
|
7786
|
+
function priorityIndicator(level) {
|
|
7787
|
+
const normalized = Math.max(0, Math.min(4, Math.trunc(Number(level) || 0)));
|
|
7788
|
+
return h("span", {
|
|
7789
|
+
class: "zx-select__priority-icon",
|
|
7790
|
+
dataset: { level: normalized },
|
|
7791
|
+
ariaHidden: "true"
|
|
7792
|
+
}, Array.from({ length: 5 }, (_, index) => h("span", {
|
|
7793
|
+
dataset: { filled: String(index <= normalized) }
|
|
7794
|
+
})));
|
|
7795
|
+
}
|
|
7796
|
+
function normalizeStatusItem(input) {
|
|
7797
|
+
if (!input || typeof input !== "object" || input.ID == null || input.name == null) return null;
|
|
7798
|
+
const indicators = ["dotted", "ring", "progress", "review", "ready", "waiting", "done", "canceled", "duplicate"];
|
|
7799
|
+
const tones = ["muted", "neutral", "warning", "info", "accent", "success", "danger"];
|
|
7800
|
+
return {
|
|
7801
|
+
...input,
|
|
7802
|
+
name: String(input.name),
|
|
7803
|
+
indicator: indicators.includes(String(input.indicator)) ? String(input.indicator) : "ring",
|
|
7804
|
+
tone: tones.includes(String(input.tone)) ? String(input.tone) : "muted",
|
|
7805
|
+
description: input.description == null ? "" : String(input.description),
|
|
7806
|
+
shortcut: input.shortcut == null ? "" : String(input.shortcut)
|
|
7807
|
+
};
|
|
7808
|
+
}
|
|
7809
|
+
function statusContent(item) {
|
|
7810
|
+
return h(
|
|
7811
|
+
"span",
|
|
7812
|
+
{ class: "zx-select__status" },
|
|
7813
|
+
statusIndicator(item),
|
|
7814
|
+
h(
|
|
7815
|
+
"span",
|
|
7816
|
+
{ class: "zx-select__status-copy" },
|
|
7817
|
+
h("span", { class: "zx-select__status-label" }, String(item.name)),
|
|
7818
|
+
item.description ? h("span", { class: "zx-select__status-description" }, item.description) : null
|
|
7819
|
+
),
|
|
7820
|
+
item.shortcut ? h("kbd", { class: "zx-select__status-shortcut" }, item.shortcut) : null
|
|
7821
|
+
);
|
|
7822
|
+
}
|
|
7823
|
+
function statusIndicator(item) {
|
|
7824
|
+
const indicator = String(item.indicator ?? "ring");
|
|
7825
|
+
const marker = h("span", {
|
|
7826
|
+
class: "zx-select__status-indicator",
|
|
7827
|
+
dataset: { indicator, tone: String(item.tone ?? "muted") },
|
|
7828
|
+
ariaHidden: "true"
|
|
7829
|
+
});
|
|
7830
|
+
if (indicator === "done") marker.append(icon("check", { size: 8 }));
|
|
7831
|
+
if (indicator === "canceled") marker.append(icon("x", { size: 7 }));
|
|
7832
|
+
return marker;
|
|
7833
|
+
}
|
|
7491
7834
|
function readItem(item, reader) {
|
|
7492
7835
|
if (typeof reader === "function") return reader(item);
|
|
7493
7836
|
if (item === null || typeof item !== "object") return item;
|
|
@@ -7500,10 +7843,11 @@ var zx = (() => {
|
|
|
7500
7843
|
// src/components/select/field-adapter.js
|
|
7501
7844
|
function registerSelectFieldAdapter() {
|
|
7502
7845
|
Field.register("zxselect", (field, options) => {
|
|
7503
|
-
const
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7846
|
+
const props = options.props ?? {};
|
|
7847
|
+
const preset = props.preset;
|
|
7848
|
+
const selectOptions = { ...props, value: options.value };
|
|
7849
|
+
delete selectOptions.preset;
|
|
7850
|
+
const select = field.own(preset === "priority" ? Select.priority(null, selectOptions) : preset === "status" ? Select.status(null, selectOptions) : new Select(null, selectOptions));
|
|
7507
7851
|
field.listen(select.el, "change", (event) => event.stopImmediatePropagation());
|
|
7508
7852
|
select.on("change", (event) => field.emit("change", { value: event.detail.value }));
|
|
7509
7853
|
return {
|
|
@@ -7887,6 +8231,8 @@ var zx = (() => {
|
|
|
7887
8231
|
values: [],
|
|
7888
8232
|
valueKey: "ID",
|
|
7889
8233
|
labelKey: "name",
|
|
8234
|
+
iconKey: "icon",
|
|
8235
|
+
colorKey: "color",
|
|
7890
8236
|
searchKeys: null,
|
|
7891
8237
|
filter: "local",
|
|
7892
8238
|
minQuery: 0,
|
|
@@ -8227,12 +8573,13 @@ var zx = (() => {
|
|
|
8227
8573
|
_renderTags() {
|
|
8228
8574
|
const tags = this._selected.map((item) => {
|
|
8229
8575
|
const label = this._labelOf(item);
|
|
8230
|
-
const
|
|
8231
|
-
const
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8576
|
+
const color = this._colorOf(item);
|
|
8577
|
+
const content = this.options.renderTag ? this.options.renderTag(item) : [this._iconOf(item), h("span", { class: "zx-tag-picker__tag-label" }, label)];
|
|
8578
|
+
const tag = h("span", {
|
|
8579
|
+
class: "zx-tag-picker__tag",
|
|
8580
|
+
dataset: { colored: String(Boolean(color)) },
|
|
8581
|
+
style: color ? { "--zx-tag-color": color } : null
|
|
8582
|
+
}, content);
|
|
8236
8583
|
if (!this._readonly && !this._disabled) {
|
|
8237
8584
|
tag.append(h("button", {
|
|
8238
8585
|
class: "zx-icon-btn zx-tag-picker__remove",
|
|
@@ -8270,7 +8617,7 @@ var zx = (() => {
|
|
|
8270
8617
|
h(
|
|
8271
8618
|
"span",
|
|
8272
8619
|
{ class: "zx-tag-picker__option-label" },
|
|
8273
|
-
this.options.renderItem ? this.options.renderItem(item) : this._labelOf(item)
|
|
8620
|
+
this.options.renderItem ? this.options.renderItem(item) : [this._iconOf(item), h("span", {}, this._labelOf(item))]
|
|
8274
8621
|
)
|
|
8275
8622
|
);
|
|
8276
8623
|
if (!selected && this.isFull()) option.setAttribute("aria-disabled", "true");
|
|
@@ -8495,10 +8842,10 @@ var zx = (() => {
|
|
|
8495
8842
|
if (sequence !== this._requestSequence) return;
|
|
8496
8843
|
this._items = Array.isArray(result) ? result.slice() : [];
|
|
8497
8844
|
this._visible = this._items.slice();
|
|
8498
|
-
} catch (
|
|
8845
|
+
} catch (error2) {
|
|
8499
8846
|
if (sequence !== this._requestSequence) return;
|
|
8500
8847
|
this._visible = [];
|
|
8501
|
-
this.emit("error", { error });
|
|
8848
|
+
this.emit("error", { error: error2 });
|
|
8502
8849
|
} finally {
|
|
8503
8850
|
if (sequence === this._requestSequence) {
|
|
8504
8851
|
this._setLoading(false);
|
|
@@ -8591,6 +8938,34 @@ var zx = (() => {
|
|
|
8591
8938
|
if (item !== null && typeof item === "object") return String(item[key] ?? "");
|
|
8592
8939
|
return String(item ?? "");
|
|
8593
8940
|
}
|
|
8941
|
+
/** @param {unknown} item @returns {Node|null} */
|
|
8942
|
+
_iconOf(item) {
|
|
8943
|
+
const value = this._readItem(item, this.options.iconKey);
|
|
8944
|
+
const visual = value && typeof value === "object" && typeof value.nodeType === "number" ? (
|
|
8945
|
+
/** @type {Node} */
|
|
8946
|
+
value.cloneNode(true)
|
|
8947
|
+
) : typeof value === "string" && value.trim() ? icon(value, { size: 12 }) : null;
|
|
8948
|
+
if (!visual) return null;
|
|
8949
|
+
return h("span", {
|
|
8950
|
+
class: "zx-tag-picker__visual",
|
|
8951
|
+
style: this._colorOf(item) ? { "--zx-tag-color": this._colorOf(item) } : null,
|
|
8952
|
+
ariaHidden: "true"
|
|
8953
|
+
}, visual);
|
|
8954
|
+
}
|
|
8955
|
+
/** @param {unknown} item @returns {string|null} */
|
|
8956
|
+
_colorOf(item) {
|
|
8957
|
+
const value = this._readItem(item, this.options.colorKey);
|
|
8958
|
+
return isCssColor(value) ? value : null;
|
|
8959
|
+
}
|
|
8960
|
+
/** @param {unknown} item @param {unknown} reader @returns {unknown} */
|
|
8961
|
+
_readItem(item, reader) {
|
|
8962
|
+
if (typeof reader === "function") return reader(
|
|
8963
|
+
/** @type {TagItem} */
|
|
8964
|
+
item
|
|
8965
|
+
);
|
|
8966
|
+
if (item !== null && typeof item === "object" && typeof reader === "string") return item[reader];
|
|
8967
|
+
return null;
|
|
8968
|
+
}
|
|
8594
8969
|
/** @returns {void} */
|
|
8595
8970
|
_emitChange() {
|
|
8596
8971
|
this.emit("change", { values: this.getValues(), items: this.getItems() });
|
|
@@ -9475,38 +9850,38 @@ var zx = (() => {
|
|
|
9475
9850
|
try {
|
|
9476
9851
|
json2 = JSON.parse(text4);
|
|
9477
9852
|
} catch (cause) {
|
|
9478
|
-
const
|
|
9853
|
+
const error2 = (
|
|
9479
9854
|
/** @type {Error & {response?: Response}} */
|
|
9480
9855
|
new Error("Invalid JSON response", { cause })
|
|
9481
9856
|
);
|
|
9482
|
-
|
|
9483
|
-
throw
|
|
9857
|
+
error2.response = response;
|
|
9858
|
+
throw error2;
|
|
9484
9859
|
}
|
|
9485
9860
|
}
|
|
9486
9861
|
if (!response.ok) {
|
|
9487
9862
|
try {
|
|
9488
9863
|
parseResult(json2);
|
|
9489
|
-
} catch (
|
|
9490
|
-
|
|
9491
|
-
throw
|
|
9864
|
+
} catch (error3) {
|
|
9865
|
+
error3.response = response;
|
|
9866
|
+
throw error3;
|
|
9492
9867
|
}
|
|
9493
|
-
const
|
|
9868
|
+
const error2 = (
|
|
9494
9869
|
/** @type {Error & {response?: Response}} */
|
|
9495
9870
|
new Error(`HTTP ${response.status} ${response.statusText}`.trim())
|
|
9496
9871
|
);
|
|
9497
|
-
|
|
9498
|
-
throw
|
|
9872
|
+
error2.response = response;
|
|
9873
|
+
throw error2;
|
|
9499
9874
|
}
|
|
9500
9875
|
return parseResult(json2);
|
|
9501
9876
|
} catch (caught) {
|
|
9502
|
-
const
|
|
9877
|
+
const error2 = caught instanceof Error ? caught : new Error(String(caught));
|
|
9503
9878
|
if (this.onError) {
|
|
9504
9879
|
try {
|
|
9505
|
-
this.onError(
|
|
9880
|
+
this.onError(error2, { path, options });
|
|
9506
9881
|
} catch {
|
|
9507
9882
|
}
|
|
9508
9883
|
}
|
|
9509
|
-
throw
|
|
9884
|
+
throw error2;
|
|
9510
9885
|
} finally {
|
|
9511
9886
|
if (timeoutId !== null) clearTimeout(timeoutId);
|
|
9512
9887
|
}
|
|
@@ -10223,9 +10598,9 @@ var zx = (() => {
|
|
|
10223
10598
|
this.el.returnValue = "";
|
|
10224
10599
|
try {
|
|
10225
10600
|
this._show();
|
|
10226
|
-
} catch (
|
|
10601
|
+
} catch (error2) {
|
|
10227
10602
|
delete this.el.dataset.zxOverlayOrder;
|
|
10228
|
-
throw
|
|
10603
|
+
throw error2;
|
|
10229
10604
|
}
|
|
10230
10605
|
this.el.dataset.state = "open";
|
|
10231
10606
|
this.emit("open");
|
|
@@ -10708,9 +11083,9 @@ var zx = (() => {
|
|
|
10708
11083
|
}
|
|
10709
11084
|
try {
|
|
10710
11085
|
super(normalizedTarget, options);
|
|
10711
|
-
} catch (
|
|
11086
|
+
} catch (error2) {
|
|
10712
11087
|
if (adoption) restoreAdoption(adoption);
|
|
10713
|
-
throw
|
|
11088
|
+
throw error2;
|
|
10714
11089
|
}
|
|
10715
11090
|
this._adoption = adoption;
|
|
10716
11091
|
}
|
|
@@ -11438,7 +11813,11 @@ var zx = (() => {
|
|
|
11438
11813
|
rowClass: null,
|
|
11439
11814
|
zebra: true,
|
|
11440
11815
|
editMode: false,
|
|
11441
|
-
|
|
11816
|
+
editTrigger: "double",
|
|
11817
|
+
hierarchy: false,
|
|
11818
|
+
rowReorder: false,
|
|
11819
|
+
columnVisibility: false,
|
|
11820
|
+
hiddenColumns: []
|
|
11442
11821
|
};
|
|
11443
11822
|
/**
|
|
11444
11823
|
* Creates a table around a target or creates a new root when target is null.
|
|
@@ -11450,12 +11829,14 @@ var zx = (() => {
|
|
|
11450
11829
|
}
|
|
11451
11830
|
/** @returns {HTMLElement} Table root. */
|
|
11452
11831
|
render() {
|
|
11832
|
+
const created = this.el === null;
|
|
11453
11833
|
const root = (
|
|
11454
11834
|
/** @type {HTMLElement} */
|
|
11455
11835
|
this.el ?? h("div")
|
|
11456
11836
|
);
|
|
11457
11837
|
this.el = root;
|
|
11458
|
-
this.
|
|
11838
|
+
this._createdRoot = created;
|
|
11839
|
+
this._original = created ? null : snapshotTarget(root);
|
|
11459
11840
|
this._columns = Array.isArray(this.options.columns) ? this.options.columns.map((column) => ({ ...column })) : [];
|
|
11460
11841
|
this._data = Array.isArray(this.options.data) ? [...this.options.data] : [];
|
|
11461
11842
|
this._selected = /* @__PURE__ */ new Set();
|
|
@@ -11465,6 +11846,7 @@ var zx = (() => {
|
|
|
11465
11846
|
this._rowElements = /* @__PURE__ */ new Map();
|
|
11466
11847
|
this._restored = false;
|
|
11467
11848
|
this._editMode = normalizeEditMode(this.options.editMode);
|
|
11849
|
+
this._editTrigger = this.options.editTrigger === "single" ? "single" : "double";
|
|
11468
11850
|
this._edit = null;
|
|
11469
11851
|
this._editMessage = null;
|
|
11470
11852
|
this._stacked = false;
|
|
@@ -11476,6 +11858,10 @@ var zx = (() => {
|
|
|
11476
11858
|
this._rovingCell = null;
|
|
11477
11859
|
this._rovingMatched = false;
|
|
11478
11860
|
this._hierarchy = normalizeHierarchy(this.options.hierarchy, this._columns);
|
|
11861
|
+
this._hiddenColumns = new Set((Array.isArray(this.options.hiddenColumns) ? this.options.hiddenColumns : []).map(String).filter((id) => this._columns.some((column) => column.id === id)));
|
|
11862
|
+
if (this._hiddenColumns.size >= this._columns.length && this._columns[0]) this._hiddenColumns.delete(this._columns[0].id);
|
|
11863
|
+
this._dragId = null;
|
|
11864
|
+
this._grabbedId = null;
|
|
11479
11865
|
this._expanded = initialExpanded(this._hierarchy?.expanded);
|
|
11480
11866
|
if (this._sort && this.options.sortMode === "local") this._sortData();
|
|
11481
11867
|
if (this._hierarchy?.expanded === true) {
|
|
@@ -11497,11 +11883,17 @@ var zx = (() => {
|
|
|
11497
11883
|
zebra: String(Boolean(this.options.zebra))
|
|
11498
11884
|
}
|
|
11499
11885
|
}, this._table);
|
|
11886
|
+
this._rowMoveMessage = this.options.rowReorder ? h("div", {
|
|
11887
|
+
class: "zx-table__reorder-status",
|
|
11888
|
+
role: "status",
|
|
11889
|
+
ariaLive: "polite"
|
|
11890
|
+
}) : null;
|
|
11500
11891
|
if (this.options.height != null) {
|
|
11501
11892
|
this._scroll.style.blockSize = typeof this.options.height === "number" ? `${this.options.height}px` : String(this.options.height);
|
|
11502
11893
|
}
|
|
11894
|
+
this._columnControls = this.options.columnVisibility ? this._createColumnControls() : null;
|
|
11503
11895
|
if (this._editMode === false) {
|
|
11504
|
-
root.replaceChildren(this._scroll);
|
|
11896
|
+
root.replaceChildren(...[this._columnControls, this._scroll, this._rowMoveMessage].filter(Boolean));
|
|
11505
11897
|
} else {
|
|
11506
11898
|
root.dataset.editMode = this._editMode;
|
|
11507
11899
|
this._editMessage = h("div", {
|
|
@@ -11510,7 +11902,7 @@ var zx = (() => {
|
|
|
11510
11902
|
role: "status",
|
|
11511
11903
|
ariaLive: "polite"
|
|
11512
11904
|
});
|
|
11513
|
-
root.replaceChildren(this._scroll, this._editMessage);
|
|
11905
|
+
root.replaceChildren(...[this._columnControls, this._scroll, this._editMessage, this._rowMoveMessage].filter(Boolean));
|
|
11514
11906
|
}
|
|
11515
11907
|
this._renderColumns();
|
|
11516
11908
|
this._renderHeader();
|
|
@@ -11518,6 +11910,34 @@ var zx = (() => {
|
|
|
11518
11910
|
this._watchWidth();
|
|
11519
11911
|
this.listen(this._tbody, "click", (event) => this._handleBodyClick(event));
|
|
11520
11912
|
this.listen(this._tbody, "dblclick", (event) => this._handleBodyDoubleClick(event));
|
|
11913
|
+
if (this.options.rowReorder) {
|
|
11914
|
+
this.listen(this._tbody, "dragstart", (event) => this._handleRowDragStart(
|
|
11915
|
+
/** @type {DragEvent} */
|
|
11916
|
+
event
|
|
11917
|
+
));
|
|
11918
|
+
this.listen(this._tbody, "dragover", (event) => this._handleRowDragOver(
|
|
11919
|
+
/** @type {DragEvent} */
|
|
11920
|
+
event
|
|
11921
|
+
));
|
|
11922
|
+
this.listen(this._tbody, "drop", (event) => this._handleRowDrop(
|
|
11923
|
+
/** @type {DragEvent} */
|
|
11924
|
+
event
|
|
11925
|
+
));
|
|
11926
|
+
this.listen(this._tbody, "dragend", () => this._clearRowDrag());
|
|
11927
|
+
this.listen(this._tbody, "keydown", (event) => this._handleRowHandleKeydown(
|
|
11928
|
+
/** @type {KeyboardEvent} */
|
|
11929
|
+
event
|
|
11930
|
+
));
|
|
11931
|
+
}
|
|
11932
|
+
if (this._columnControls) {
|
|
11933
|
+
this.listen(this._columnControls, "change", (event) => {
|
|
11934
|
+
const input = (
|
|
11935
|
+
/** @type {HTMLInputElement|null} */
|
|
11936
|
+
event.target
|
|
11937
|
+
);
|
|
11938
|
+
if (input?.classList.contains("zx-table__column-toggle")) this.setColumnVisible(input.value, input.checked);
|
|
11939
|
+
});
|
|
11940
|
+
}
|
|
11521
11941
|
if (this._editMode !== false) {
|
|
11522
11942
|
this.listen(this._tbody, "keydown", (event) => this._handleEditKeydown(
|
|
11523
11943
|
/** @type {KeyboardEvent} */
|
|
@@ -11530,8 +11950,8 @@ var zx = (() => {
|
|
|
11530
11950
|
}
|
|
11531
11951
|
/**
|
|
11532
11952
|
* Toggles a loading state. While loading, the body is dimmed, an indeterminate progress bar is
|
|
11533
|
-
* shown, and `aria-busy` is set — a lightweight
|
|
11534
|
-
*
|
|
11953
|
+
* shown, and `aria-busy` is set — a lightweight enterprise-table busy and skeleton pattern.
|
|
11954
|
+
* Call `setLoading(false)` (or `setData`) when done.
|
|
11535
11955
|
* @param {boolean} [loading=true] Whether the table is loading.
|
|
11536
11956
|
* @returns {this}
|
|
11537
11957
|
*/
|
|
@@ -11633,6 +12053,81 @@ var zx = (() => {
|
|
|
11633
12053
|
getData() {
|
|
11634
12054
|
return [...this._data];
|
|
11635
12055
|
}
|
|
12056
|
+
/** Returns visible column IDs in caller order. @returns {string[]} */
|
|
12057
|
+
getVisibleColumns() {
|
|
12058
|
+
return this._visibleColumns().map((column) => column.id);
|
|
12059
|
+
}
|
|
12060
|
+
/** Returns hidden column IDs in caller order. @returns {string[]} */
|
|
12061
|
+
getHiddenColumns() {
|
|
12062
|
+
return this._columns.filter((column) => this._hiddenColumns.has(column.id)).map((column) => column.id);
|
|
12063
|
+
}
|
|
12064
|
+
/**
|
|
12065
|
+
* Shows or hides one column. The final visible column cannot be hidden.
|
|
12066
|
+
* @param {string} id Column ID.
|
|
12067
|
+
* @param {boolean} [visible=true] Desired visibility.
|
|
12068
|
+
* @param {{silent?:boolean}} [options={}] Event behavior.
|
|
12069
|
+
* @returns {this}
|
|
12070
|
+
*/
|
|
12071
|
+
setColumnVisible(id, visible = true, options = {}) {
|
|
12072
|
+
const column = this._columns.find((candidate) => candidate.id === id);
|
|
12073
|
+
if (!column) throw new RangeError(`Unknown table column: ${id}`);
|
|
12074
|
+
const active = (
|
|
12075
|
+
/** @type {HTMLElement|null} */
|
|
12076
|
+
document.activeElement
|
|
12077
|
+
);
|
|
12078
|
+
const restoreFocus = active?.classList.contains("zx-table__column-toggle") && this._columnList?.contains(active) ? (
|
|
12079
|
+
/** @type {HTMLInputElement} */
|
|
12080
|
+
active.value
|
|
12081
|
+
) : null;
|
|
12082
|
+
if (!visible && !this._hiddenColumns.has(id) && this._visibleColumns().length <= 1) {
|
|
12083
|
+
this._renderColumnControls(restoreFocus);
|
|
12084
|
+
return this;
|
|
12085
|
+
}
|
|
12086
|
+
const changed = visible ? this._hiddenColumns.delete(id) : !this._hiddenColumns.has(id);
|
|
12087
|
+
if (!visible && changed) this._hiddenColumns.add(id);
|
|
12088
|
+
if (!changed) return this;
|
|
12089
|
+
this._renderColumns();
|
|
12090
|
+
this._renderHeader();
|
|
12091
|
+
this._renderBody();
|
|
12092
|
+
this._renderColumnControls(restoreFocus);
|
|
12093
|
+
if (!options.silent) this.emit("columnvisibilitychange", {
|
|
12094
|
+
visible: this.getVisibleColumns(),
|
|
12095
|
+
hidden: this.getHiddenColumns()
|
|
12096
|
+
});
|
|
12097
|
+
return this;
|
|
12098
|
+
}
|
|
12099
|
+
/** Toggles one column. @param {string} id @param {{silent?:boolean}} [options={}] @returns {this} */
|
|
12100
|
+
toggleColumn(id, options = {}) {
|
|
12101
|
+
return this.setColumnVisible(id, this._hiddenColumns.has(id), options);
|
|
12102
|
+
}
|
|
12103
|
+
/**
|
|
12104
|
+
* Reorders one row relative to another and emits a cancelable `rowmove` before committing.
|
|
12105
|
+
* Hierarchical rows may only move among siblings; an active local sort disables manual order.
|
|
12106
|
+
* @param {unknown} id Moving row ID.
|
|
12107
|
+
* @param {unknown} targetId Target row ID.
|
|
12108
|
+
* @param {'before'|'after'} [position='before'] Target edge.
|
|
12109
|
+
* @returns {this}
|
|
12110
|
+
*/
|
|
12111
|
+
moveRow(id, targetId, position2 = "before") {
|
|
12112
|
+
if (!this.options.rowReorder || this._sort && this.options.sortMode === "local") return this;
|
|
12113
|
+
const row2 = this.getRow(id);
|
|
12114
|
+
const target = this.getRow(targetId);
|
|
12115
|
+
if (!row2 || !target || Object.is(id, targetId)) return this;
|
|
12116
|
+
const next = reorderTableRows(this._data, this.options.rowId, id, targetId, position2, this._hierarchy?.parentId);
|
|
12117
|
+
if (!next) return this;
|
|
12118
|
+
const event = this.emit("rowmove", { row: row2, id, target, targetId, position: position2 });
|
|
12119
|
+
if (event.defaultPrevented) return this;
|
|
12120
|
+
this._data = next;
|
|
12121
|
+
this._renderBody();
|
|
12122
|
+
if (this._rowMoveMessage) {
|
|
12123
|
+
const siblings = this._siblingIds(id);
|
|
12124
|
+
const index = siblings.findIndex((candidate) => Object.is(candidate, id));
|
|
12125
|
+
this._rowMoveMessage.textContent = `Moved row to position ${index + 1} of ${siblings.length}.`;
|
|
12126
|
+
}
|
|
12127
|
+
this._emitDataChange();
|
|
12128
|
+
queueMicrotask(() => this._rowHandle(id)?.focus());
|
|
12129
|
+
return this;
|
|
12130
|
+
}
|
|
11636
12131
|
/**
|
|
11637
12132
|
* Removes all rows.
|
|
11638
12133
|
* @returns {this}
|
|
@@ -11801,14 +12296,17 @@ var zx = (() => {
|
|
|
11801
12296
|
*/
|
|
11802
12297
|
startEdit(rowId, columnId) {
|
|
11803
12298
|
if (this._editMode === false) return this;
|
|
11804
|
-
const
|
|
11805
|
-
if (!
|
|
12299
|
+
const requested = this._columns.find((candidate) => candidate.id === columnId);
|
|
12300
|
+
if (!requested) return this;
|
|
11806
12301
|
const current = this._data.find((candidate) => Object.is(this._idFor(candidate), rowId));
|
|
11807
|
-
if (current === void 0
|
|
12302
|
+
if (current === void 0) return this;
|
|
12303
|
+
const column = this._editMode === "row" && this._hiddenColumns.has(requested.id) ? this._visibleColumns().find((candidate) => resolveEditable(candidate, current) !== false) ?? null : requested;
|
|
12304
|
+
if (!column || resolveEditable(column, current) === false) return this;
|
|
12305
|
+
const activeColumnId = column.id;
|
|
11808
12306
|
if (this._edit) {
|
|
11809
|
-
if (Object.is(this._edit.id, rowId) && this._edit.cells.has(
|
|
11810
|
-
this._edit.columnId =
|
|
11811
|
-
this._focusEditor(
|
|
12307
|
+
if (Object.is(this._edit.id, rowId) && this._edit.cells.has(activeColumnId)) {
|
|
12308
|
+
this._edit.columnId = activeColumnId;
|
|
12309
|
+
this._focusEditor(activeColumnId);
|
|
11812
12310
|
return this;
|
|
11813
12311
|
}
|
|
11814
12312
|
this.commitEdit();
|
|
@@ -11819,7 +12317,7 @@ var zx = (() => {
|
|
|
11819
12317
|
const row2 = this._data[index];
|
|
11820
12318
|
const tr = this._rowElements.get(rowId)?.[0];
|
|
11821
12319
|
if (!tr) return this;
|
|
11822
|
-
const targets = this._editMode === "row" ? this.
|
|
12320
|
+
const targets = this._editMode === "row" ? this._visibleColumns().filter((candidate) => resolveEditable(candidate, row2) !== false) : [column];
|
|
11823
12321
|
const cells = /* @__PURE__ */ new Map();
|
|
11824
12322
|
for (const target of targets) {
|
|
11825
12323
|
const td = cellIn(tr, target.id);
|
|
@@ -11828,10 +12326,10 @@ var zx = (() => {
|
|
|
11828
12326
|
if (entry) cells.set(target.id, entry);
|
|
11829
12327
|
}
|
|
11830
12328
|
if (cells.size === 0) return this;
|
|
11831
|
-
this._edit = { id: rowId, columnId, row: row2, cells, tr };
|
|
12329
|
+
this._edit = { id: rowId, columnId: activeColumnId, row: row2, cells, tr };
|
|
11832
12330
|
tr.dataset.editing = "true";
|
|
11833
|
-
this.emit("editstart", { row: row2, id: rowId, column, columnId, value: row2?.[
|
|
11834
|
-
this._focusEditor(
|
|
12331
|
+
this.emit("editstart", { row: row2, id: rowId, column, columnId: activeColumnId, value: row2?.[activeColumnId] });
|
|
12332
|
+
this._focusEditor(activeColumnId);
|
|
11835
12333
|
return this;
|
|
11836
12334
|
}
|
|
11837
12335
|
/**
|
|
@@ -12080,60 +12578,245 @@ var zx = (() => {
|
|
|
12080
12578
|
this._teardownEdit();
|
|
12081
12579
|
this._width?.destroy();
|
|
12082
12580
|
this._width = null;
|
|
12083
|
-
if (this.
|
|
12084
|
-
|
|
12085
|
-
this.el.replaceChildren(...this._originalChildren);
|
|
12581
|
+
if (!this._restored && !this._createdRoot) {
|
|
12582
|
+
restoreTarget(this.el, this._original);
|
|
12086
12583
|
this._restored = true;
|
|
12087
12584
|
}
|
|
12088
12585
|
super.destroy();
|
|
12089
12586
|
}
|
|
12090
|
-
/** @returns {
|
|
12091
|
-
|
|
12092
|
-
|
|
12093
|
-
if (this.options.selectable === "multi") {
|
|
12094
|
-
fragment2.append(h("col", { class: "zx-table__selection-column" }));
|
|
12095
|
-
}
|
|
12096
|
-
const fractions = this._columns.map((column) => parseFraction(column.width));
|
|
12097
|
-
const total = fractions.reduce((sum, value) => sum + value, 0);
|
|
12098
|
-
this._table.dataset.layout = total > 0 ? "fixed" : "auto";
|
|
12099
|
-
this._columns.forEach((column, index) => {
|
|
12100
|
-
const col = h("col");
|
|
12101
|
-
if (fractions[index] > 0) col.style.width = `${fractions[index] / total * 100}%`;
|
|
12102
|
-
else if (column.width && column.width !== "auto") col.style.width = String(column.width);
|
|
12103
|
-
fragment2.append(col);
|
|
12104
|
-
});
|
|
12105
|
-
this._colgroup.replaceChildren(fragment2);
|
|
12587
|
+
/** @returns {TableColumn[]} */
|
|
12588
|
+
_visibleColumns() {
|
|
12589
|
+
return this._columns.filter((column) => !this._hiddenColumns.has(column.id));
|
|
12106
12590
|
}
|
|
12107
|
-
/** @returns {
|
|
12108
|
-
|
|
12109
|
-
const
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
|
|
12123
|
-
|
|
12124
|
-
|
|
12125
|
-
|
|
12126
|
-
|
|
12127
|
-
|
|
12128
|
-
|
|
12129
|
-
|
|
12130
|
-
|
|
12591
|
+
/** @returns {HTMLElement} */
|
|
12592
|
+
_createColumnControls() {
|
|
12593
|
+
const list = h("div", { class: "zx-table__column-list" });
|
|
12594
|
+
this._columnList = list;
|
|
12595
|
+
const details = h(
|
|
12596
|
+
"details",
|
|
12597
|
+
{ class: "zx-table__column-controls" },
|
|
12598
|
+
h("summary", { class: "zx-button zx-button--sm" }, icon("fields", { size: 13 }), "Columns"),
|
|
12599
|
+
list
|
|
12600
|
+
);
|
|
12601
|
+
this._renderColumnControls();
|
|
12602
|
+
return details;
|
|
12603
|
+
}
|
|
12604
|
+
/** @param {string|null} [focusColumn=null] Column checkbox that keeps focus. @returns {void} */
|
|
12605
|
+
_renderColumnControls(focusColumn = null) {
|
|
12606
|
+
if (!this._columnList) return;
|
|
12607
|
+
const visible = this._visibleColumns();
|
|
12608
|
+
const current = [...this._columnList.querySelectorAll(".zx-table__column-toggle")];
|
|
12609
|
+
const reusable = current.length === this._columns.length && current.every((control, index) => (
|
|
12610
|
+
/** @type {HTMLInputElement} */
|
|
12611
|
+
control.value === this._columns[index].id
|
|
12612
|
+
));
|
|
12613
|
+
if (reusable) {
|
|
12614
|
+
current.forEach((control, index) => {
|
|
12615
|
+
const input = (
|
|
12616
|
+
/** @type {HTMLInputElement} */
|
|
12617
|
+
control
|
|
12618
|
+
);
|
|
12619
|
+
const checked = !this._hiddenColumns.has(this._columns[index].id);
|
|
12620
|
+
input.checked = checked;
|
|
12621
|
+
input.disabled = checked && visible.length === 1;
|
|
12131
12622
|
});
|
|
12132
|
-
|
|
12133
|
-
|
|
12623
|
+
return;
|
|
12624
|
+
}
|
|
12625
|
+
this._columnList.replaceChildren(...this._columns.map((column) => {
|
|
12626
|
+
const checked = !this._hiddenColumns.has(column.id);
|
|
12627
|
+
return h(
|
|
12628
|
+
"label",
|
|
12629
|
+
{ class: "zx-table__column-option" },
|
|
12630
|
+
h("input", {
|
|
12631
|
+
class: "zx-table__column-toggle",
|
|
12632
|
+
type: "checkbox",
|
|
12633
|
+
value: column.id,
|
|
12634
|
+
checked,
|
|
12635
|
+
disabled: checked && visible.length === 1
|
|
12636
|
+
}),
|
|
12637
|
+
h("span", {}, column.label)
|
|
12638
|
+
);
|
|
12639
|
+
}));
|
|
12640
|
+
if (focusColumn !== null) {
|
|
12641
|
+
const control = [...this._columnList.querySelectorAll(".zx-table__column-toggle")].find((candidate) => (
|
|
12642
|
+
/** @type {HTMLInputElement} */
|
|
12643
|
+
candidate.value === focusColumn
|
|
12644
|
+
));
|
|
12645
|
+
queueMicrotask(() => {
|
|
12646
|
+
if (control?.isConnected) control.focus();
|
|
12647
|
+
});
|
|
12648
|
+
}
|
|
12649
|
+
}
|
|
12650
|
+
/** @param {DragEvent} event @returns {void} */
|
|
12651
|
+
_handleRowDragStart(event) {
|
|
12652
|
+
const handle = (
|
|
12653
|
+
/** @type {Element|null} */
|
|
12654
|
+
event.target?.closest?.(".zx-table__row-handle")
|
|
12655
|
+
);
|
|
12656
|
+
const rowElement = handle?.closest("tr[data-row]");
|
|
12657
|
+
const meta = rowElement ? this._rowMeta.get(rowElement) : null;
|
|
12658
|
+
if (!handle || !meta || this._sort && this.options.sortMode === "local") {
|
|
12659
|
+
event.preventDefault();
|
|
12660
|
+
return;
|
|
12661
|
+
}
|
|
12662
|
+
this._dragId = meta.id;
|
|
12663
|
+
rowElement.dataset.dragging = "true";
|
|
12664
|
+
event.dataTransfer?.setData("text/plain", "zx-table-row");
|
|
12665
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = "move";
|
|
12666
|
+
}
|
|
12667
|
+
/** @param {DragEvent} event @returns {void} */
|
|
12668
|
+
_handleRowDragOver(event) {
|
|
12669
|
+
if (this._dragId == null) return;
|
|
12670
|
+
const rowElement = (
|
|
12671
|
+
/** @type {Element|null} */
|
|
12672
|
+
event.target?.closest?.("tr[data-row]")
|
|
12673
|
+
);
|
|
12674
|
+
const meta = rowElement ? this._rowMeta.get(rowElement) : null;
|
|
12675
|
+
if (!meta || Object.is(meta.id, this._dragId) || !this._canMoveTogether(this._dragId, meta.id)) return;
|
|
12676
|
+
event.preventDefault();
|
|
12677
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
|
|
12678
|
+
this._clearDropMarkers();
|
|
12679
|
+
const rectangle = rowElement.getBoundingClientRect();
|
|
12680
|
+
rowElement.dataset.drop = event.clientY >= rectangle.top + rectangle.height / 2 ? "after" : "before";
|
|
12681
|
+
}
|
|
12682
|
+
/** @param {DragEvent} event @returns {void} */
|
|
12683
|
+
_handleRowDrop(event) {
|
|
12684
|
+
if (this._dragId == null) return;
|
|
12685
|
+
const rowElement = (
|
|
12686
|
+
/** @type {Element|null} */
|
|
12687
|
+
event.target?.closest?.("tr[data-row]")
|
|
12688
|
+
);
|
|
12689
|
+
const meta = rowElement ? this._rowMeta.get(rowElement) : null;
|
|
12690
|
+
const position2 = rowElement?.dataset.drop === "after" ? "after" : "before";
|
|
12691
|
+
if (meta && this._canMoveTogether(this._dragId, meta.id)) {
|
|
12692
|
+
event.preventDefault();
|
|
12693
|
+
this.moveRow(this._dragId, meta.id, position2);
|
|
12694
|
+
}
|
|
12695
|
+
this._clearRowDrag();
|
|
12696
|
+
}
|
|
12697
|
+
/** @param {KeyboardEvent} event @returns {void} */
|
|
12698
|
+
_handleRowHandleKeydown(event) {
|
|
12699
|
+
const handle = (
|
|
12700
|
+
/** @type {Element|null} */
|
|
12701
|
+
event.target?.closest?.(".zx-table__row-handle")
|
|
12702
|
+
);
|
|
12703
|
+
const rowElement = handle?.closest("tr[data-row]");
|
|
12704
|
+
const meta = rowElement ? this._rowMeta.get(rowElement) : null;
|
|
12705
|
+
if (!handle || !meta) return;
|
|
12706
|
+
if (event.key === " " || event.key === "Enter") {
|
|
12707
|
+
event.preventDefault();
|
|
12708
|
+
this._grabbedId = Object.is(this._grabbedId, meta.id) ? null : meta.id;
|
|
12709
|
+
handle.setAttribute("aria-pressed", String(this._grabbedId != null));
|
|
12710
|
+
const status = this._rowMoveMessage ?? this._editMessage;
|
|
12711
|
+
if (status) status.textContent = this._grabbedId == null ? "Row dropped" : "Row grabbed. Use the arrow keys to move it.";
|
|
12712
|
+
return;
|
|
12713
|
+
}
|
|
12714
|
+
if (event.key === "Escape" && this._grabbedId != null) {
|
|
12715
|
+
event.preventDefault();
|
|
12716
|
+
this._grabbedId = null;
|
|
12717
|
+
handle.setAttribute("aria-pressed", "false");
|
|
12718
|
+
return;
|
|
12719
|
+
}
|
|
12720
|
+
if (!Object.is(this._grabbedId, meta.id) || !["ArrowUp", "ArrowDown"].includes(event.key)) return;
|
|
12721
|
+
event.preventDefault();
|
|
12722
|
+
const siblings = this._siblingIds(meta.id);
|
|
12723
|
+
const index = siblings.findIndex((id) => Object.is(id, meta.id));
|
|
12724
|
+
const targetId = siblings[index + (event.key === "ArrowUp" ? -1 : 1)];
|
|
12725
|
+
if (targetId !== void 0) this.moveRow(meta.id, targetId, event.key === "ArrowUp" ? "before" : "after");
|
|
12726
|
+
}
|
|
12727
|
+
/** @param {unknown} id @returns {unknown[]} */
|
|
12728
|
+
_siblingIds(id) {
|
|
12729
|
+
const row2 = this.getRow(id);
|
|
12730
|
+
if (!row2) return [];
|
|
12731
|
+
const parent = this._hierarchy ? readRowValue(this._hierarchy.parentId, row2) : null;
|
|
12732
|
+
return this._data.filter((candidate) => !this._hierarchy || sameHierarchyParent(readRowValue(this._hierarchy.parentId, candidate), parent)).map((candidate) => this._idFor(candidate));
|
|
12733
|
+
}
|
|
12734
|
+
/** @param {unknown} left @param {unknown} right @returns {boolean} */
|
|
12735
|
+
_canMoveTogether(left, right) {
|
|
12736
|
+
if (!this._hierarchy) return true;
|
|
12737
|
+
const a = this.getRow(left);
|
|
12738
|
+
const b = this.getRow(right);
|
|
12739
|
+
return Boolean(a && b && sameHierarchyParent(
|
|
12740
|
+
readRowValue(this._hierarchy.parentId, a),
|
|
12741
|
+
readRowValue(this._hierarchy.parentId, b)
|
|
12742
|
+
));
|
|
12743
|
+
}
|
|
12744
|
+
/** @param {unknown} id @returns {HTMLElement|null} */
|
|
12745
|
+
_rowHandle(id) {
|
|
12746
|
+
return (
|
|
12747
|
+
/** @type {HTMLElement|null} */
|
|
12748
|
+
this._rowElements.get(id)?.[0]?.querySelector(".zx-table__row-handle") ?? null
|
|
12749
|
+
);
|
|
12750
|
+
}
|
|
12751
|
+
/** @returns {void} */
|
|
12752
|
+
_clearDropMarkers() {
|
|
12753
|
+
for (const row2 of this._tbody.querySelectorAll("tr[data-drop]")) delete row2.dataset.drop;
|
|
12754
|
+
}
|
|
12755
|
+
/** @returns {void} */
|
|
12756
|
+
_clearRowDrag() {
|
|
12757
|
+
for (const row2 of this._tbody.querySelectorAll("tr[data-dragging]")) delete row2.dataset.dragging;
|
|
12758
|
+
this._clearDropMarkers();
|
|
12759
|
+
this._dragId = null;
|
|
12760
|
+
}
|
|
12761
|
+
/** @returns {string|null} */
|
|
12762
|
+
_hierarchyColumn() {
|
|
12763
|
+
if (!this._hierarchy) return null;
|
|
12764
|
+
return this._visibleColumns().some((column) => column.id === this._hierarchy.column) ? this._hierarchy.column : this._visibleColumns()[0]?.id ?? null;
|
|
12765
|
+
}
|
|
12766
|
+
/** @returns {void} */
|
|
12767
|
+
_renderColumns() {
|
|
12768
|
+
const fragment2 = document.createDocumentFragment();
|
|
12769
|
+
if (this.options.rowReorder) fragment2.append(h("col", { class: "zx-table__reorder-column" }));
|
|
12770
|
+
if (this.options.selectable === "multi") {
|
|
12771
|
+
fragment2.append(h("col", { class: "zx-table__selection-column" }));
|
|
12772
|
+
}
|
|
12773
|
+
const columns = this._visibleColumns();
|
|
12774
|
+
const fractions = columns.map((column) => parseFraction(column.width));
|
|
12775
|
+
const total = fractions.reduce((sum, value) => sum + value, 0);
|
|
12776
|
+
this._table.dataset.layout = total > 0 ? "fixed" : "auto";
|
|
12777
|
+
columns.forEach((column, index) => {
|
|
12778
|
+
const col = h("col");
|
|
12779
|
+
if (fractions[index] > 0) col.style.width = `${fractions[index] / total * 100}%`;
|
|
12780
|
+
else if (column.width && column.width !== "auto") col.style.width = String(column.width);
|
|
12781
|
+
fragment2.append(col);
|
|
12782
|
+
});
|
|
12783
|
+
this._colgroup.replaceChildren(fragment2);
|
|
12784
|
+
}
|
|
12785
|
+
/** @returns {void} */
|
|
12786
|
+
_renderHeader() {
|
|
12787
|
+
const row2 = h("tr", { class: "zx-table__header-row" });
|
|
12788
|
+
if (this._growStep) row2.setAttribute("aria-rowindex", "1");
|
|
12789
|
+
if (this.options.rowReorder) row2.append(h("th", {
|
|
12790
|
+
class: "zx-table__reorder-header",
|
|
12791
|
+
scope: "col",
|
|
12792
|
+
ariaLabel: "Row order"
|
|
12793
|
+
}));
|
|
12794
|
+
if (this.options.selectable === "multi") {
|
|
12795
|
+
this._selectAll = h("input", {
|
|
12796
|
+
class: "zx-table__checkbox zx-table__select-all",
|
|
12797
|
+
type: "checkbox",
|
|
12798
|
+
ariaLabel: "Select all rows"
|
|
12799
|
+
});
|
|
12800
|
+
const selectionHeader = h("th", {
|
|
12801
|
+
class: "zx-table__selection-header",
|
|
12802
|
+
scope: "col"
|
|
12803
|
+
}, this._selectAll);
|
|
12804
|
+
row2.append(selectionHeader);
|
|
12805
|
+
this.listen(this._selectAll, "click", () => {
|
|
12806
|
+
if (this._selectAll.checked) {
|
|
12807
|
+
for (const dataRow of this._visibleData()) this._selected.add(this._idFor(dataRow));
|
|
12808
|
+
} else {
|
|
12809
|
+
for (const dataRow of this._visibleData()) this._selected.delete(this._idFor(dataRow));
|
|
12810
|
+
}
|
|
12811
|
+
this._selectionAnchorId = null;
|
|
12812
|
+
this._syncSelection();
|
|
12813
|
+
this._emitSelectionChange();
|
|
12814
|
+
});
|
|
12815
|
+
} else {
|
|
12816
|
+
this._selectAll = null;
|
|
12134
12817
|
}
|
|
12135
12818
|
this._headers = /* @__PURE__ */ new Map();
|
|
12136
|
-
for (const column of this.
|
|
12819
|
+
for (const column of this._visibleColumns()) {
|
|
12137
12820
|
const th = h("th", { scope: "col" });
|
|
12138
12821
|
const align = column.align ?? (isNumericColumn(column) ? "end" : null);
|
|
12139
12822
|
if (align) th.dataset.align = align;
|
|
@@ -12173,7 +12856,7 @@ var zx = (() => {
|
|
|
12173
12856
|
{ class: "zx-table__empty-row" },
|
|
12174
12857
|
h("td", {
|
|
12175
12858
|
class: "zx-table__empty",
|
|
12176
|
-
colspan: this.
|
|
12859
|
+
colspan: this._visibleColumns().length + (this.options.selectable === "multi" ? 1 : 0) + (this.options.rowReorder ? 1 : 0)
|
|
12177
12860
|
}, this._emptyContent())
|
|
12178
12861
|
));
|
|
12179
12862
|
} else {
|
|
@@ -12182,6 +12865,7 @@ var zx = (() => {
|
|
|
12182
12865
|
});
|
|
12183
12866
|
}
|
|
12184
12867
|
this._tbody.replaceChildren(fragment2);
|
|
12868
|
+
this._table.setAttribute("aria-colcount", String(this._visibleColumns().length + (this.options.selectable === "multi" ? 1 : 0) + (this.options.rowReorder ? 1 : 0)));
|
|
12185
12869
|
if (this._growStep) this._table.setAttribute("aria-rowcount", String(projected.length + 1));
|
|
12186
12870
|
this._syncSelectAll();
|
|
12187
12871
|
this._syncGrowing();
|
|
@@ -12225,6 +12909,19 @@ var zx = (() => {
|
|
|
12225
12909
|
const elements = this._rowElements.get(id) ?? [];
|
|
12226
12910
|
elements.push(tr);
|
|
12227
12911
|
this._rowElements.set(id, elements);
|
|
12912
|
+
if (this.options.rowReorder) {
|
|
12913
|
+
const handle = h("button", {
|
|
12914
|
+
class: "zx-table__row-handle",
|
|
12915
|
+
type: "button",
|
|
12916
|
+
draggable: true,
|
|
12917
|
+
dataset: { rowHandle: "" },
|
|
12918
|
+
ariaLabel: `Move row ${index + 1}`,
|
|
12919
|
+
ariaPressed: String(Object.is(this._grabbedId, id)),
|
|
12920
|
+
disabled: Boolean(this._sort && this.options.sortMode === "local"),
|
|
12921
|
+
title: this._sort && this.options.sortMode === "local" ? "Clear sorting to reorder rows" : null
|
|
12922
|
+
}, icon("drag", { size: 14 }));
|
|
12923
|
+
tr.append(h("td", { class: "zx-table__reorder-cell" }, handle));
|
|
12924
|
+
}
|
|
12228
12925
|
if (this.options.selectable === "multi") {
|
|
12229
12926
|
const checkbox = h("input", {
|
|
12230
12927
|
class: "zx-table__checkbox zx-table__row-checkbox",
|
|
@@ -12234,7 +12931,7 @@ var zx = (() => {
|
|
|
12234
12931
|
});
|
|
12235
12932
|
tr.append(h("td", { class: "zx-table__selection-cell" }, checkbox));
|
|
12236
12933
|
}
|
|
12237
|
-
this.
|
|
12934
|
+
this._visibleColumns().forEach((column) => {
|
|
12238
12935
|
const cell = (
|
|
12239
12936
|
/** @type {HTMLTableCellElement} */
|
|
12240
12937
|
h("td")
|
|
@@ -12270,7 +12967,7 @@ var zx = (() => {
|
|
|
12270
12967
|
const content = h("span", { class: "zx-table__cell-content" });
|
|
12271
12968
|
if (value && typeof value === "object" && typeof value.nodeType === "number") content.append(value);
|
|
12272
12969
|
else if (value != null) content.append(document.createTextNode(String(value)));
|
|
12273
|
-
if (!this._hierarchy || column.id !== this.
|
|
12970
|
+
if (!this._hierarchy || column.id !== this._hierarchyColumn() || !tree) {
|
|
12274
12971
|
cell.replaceChildren(...content.childNodes);
|
|
12275
12972
|
return;
|
|
12276
12973
|
}
|
|
@@ -12301,6 +12998,7 @@ var zx = (() => {
|
|
|
12301
12998
|
if (!rowElement || !this._tbody.contains(rowElement)) return;
|
|
12302
12999
|
const meta = this._rowMeta.get(rowElement);
|
|
12303
13000
|
if (!meta) return;
|
|
13001
|
+
if (target.closest(".zx-table__row-handle")) return;
|
|
12304
13002
|
if (this._edit) {
|
|
12305
13003
|
this.commitEdit();
|
|
12306
13004
|
if (this._edit) return;
|
|
@@ -12320,6 +13018,14 @@ var zx = (() => {
|
|
|
12320
13018
|
);
|
|
12321
13019
|
return;
|
|
12322
13020
|
}
|
|
13021
|
+
if (isInteractiveCellContent(target)) return;
|
|
13022
|
+
if (this._editMode !== false && this._editTrigger === "single") {
|
|
13023
|
+
const cell = target?.closest('td[data-editable="true"]');
|
|
13024
|
+
if (cell && this._tbody.contains(cell)) {
|
|
13025
|
+
this.startEdit(meta.id, String(cell.dataset.column));
|
|
13026
|
+
return;
|
|
13027
|
+
}
|
|
13028
|
+
}
|
|
12323
13029
|
this.emit("rowclick", { ...meta, event });
|
|
12324
13030
|
if (this.options.selectable === "single" && !this._selected.has(meta.id)) {
|
|
12325
13031
|
this._selected = /* @__PURE__ */ new Set([meta.id]);
|
|
@@ -12340,7 +13046,8 @@ var zx = (() => {
|
|
|
12340
13046
|
if (!rowElement || !this._tbody.contains(rowElement)) return;
|
|
12341
13047
|
const meta = this._rowMeta.get(rowElement);
|
|
12342
13048
|
if (!meta) return;
|
|
12343
|
-
if (
|
|
13049
|
+
if (isInteractiveCellContent(target)) return;
|
|
13050
|
+
if (this._editMode !== false && this._editTrigger === "double") {
|
|
12344
13051
|
const cell = target?.closest('td[data-editable="true"]');
|
|
12345
13052
|
if (cell && this._tbody.contains(cell)) {
|
|
12346
13053
|
this.startEdit(meta.id, String(cell.dataset.column));
|
|
@@ -12642,13 +13349,14 @@ var zx = (() => {
|
|
|
12642
13349
|
* @returns {{id: unknown, columnId: string, index: number}|null} Next cell, or null at the end.
|
|
12643
13350
|
*/
|
|
12644
13351
|
_nextEditableCell(rowIndex, columnId, direction) {
|
|
12645
|
-
const
|
|
13352
|
+
const columns = this._visibleColumns();
|
|
13353
|
+
const columnCount = columns.length;
|
|
12646
13354
|
if (rowIndex < 0 || columnCount === 0) return null;
|
|
12647
13355
|
const visible = this._visibleData();
|
|
12648
13356
|
const startingId = this._idFor(this._data[rowIndex]);
|
|
12649
13357
|
let row2 = visible.findIndex((candidate) => Object.is(this._idFor(candidate), startingId));
|
|
12650
13358
|
if (row2 < 0) return null;
|
|
12651
|
-
let column =
|
|
13359
|
+
let column = columns.findIndex((candidate) => candidate.id === columnId);
|
|
12652
13360
|
const limit = visible.length * columnCount + columnCount;
|
|
12653
13361
|
for (let step = 0; step < limit; step += 1) {
|
|
12654
13362
|
column += direction;
|
|
@@ -12661,7 +13369,7 @@ var zx = (() => {
|
|
|
12661
13369
|
}
|
|
12662
13370
|
if (row2 < 0 || row2 >= visible.length) return null;
|
|
12663
13371
|
const candidateRow = visible[row2];
|
|
12664
|
-
const candidateColumn =
|
|
13372
|
+
const candidateColumn = columns[column];
|
|
12665
13373
|
if (resolveEditable(candidateColumn, candidateRow) !== false) {
|
|
12666
13374
|
return {
|
|
12667
13375
|
id: this._idFor(candidateRow),
|
|
@@ -12904,6 +13612,53 @@ var zx = (() => {
|
|
|
12904
13612
|
}
|
|
12905
13613
|
return result;
|
|
12906
13614
|
}
|
|
13615
|
+
function reorderTableRows(rows, rowId, id, targetId, position2 = "before", parentId = null) {
|
|
13616
|
+
if (!Array.isArray(rows) || Object.is(id, targetId)) return null;
|
|
13617
|
+
const from = rows.findIndex((row2) => Object.is(readRowValue(rowId, row2), id));
|
|
13618
|
+
const target = rows.findIndex((row2) => Object.is(readRowValue(rowId, row2), targetId));
|
|
13619
|
+
if (from < 0 || target < 0) return null;
|
|
13620
|
+
if (parentId && !sameHierarchyParent(
|
|
13621
|
+
readRowValue(parentId, rows[from]),
|
|
13622
|
+
readRowValue(parentId, rows[target])
|
|
13623
|
+
)) return null;
|
|
13624
|
+
if (parentId) {
|
|
13625
|
+
const forest = hierarchyForest(rows, rowId, parentId);
|
|
13626
|
+
const movingNode = forest.nodes[from];
|
|
13627
|
+
const targetNode = forest.nodes[target];
|
|
13628
|
+
if (!movingNode || !targetNode || movingNode.parent !== targetNode.parent) return null;
|
|
13629
|
+
const branchNodes = (root) => {
|
|
13630
|
+
const result = [];
|
|
13631
|
+
const stack2 = [root];
|
|
13632
|
+
while (stack2.length) {
|
|
13633
|
+
const node = (
|
|
13634
|
+
/** @type {TableHierarchyNode} */
|
|
13635
|
+
stack2.pop()
|
|
13636
|
+
);
|
|
13637
|
+
result.push(node);
|
|
13638
|
+
for (let index = node.children.length - 1; index >= 0; index -= 1) stack2.push(node.children[index]);
|
|
13639
|
+
}
|
|
13640
|
+
return result;
|
|
13641
|
+
};
|
|
13642
|
+
const moving2 = new Set(branchNodes(movingNode).map((node) => node.index));
|
|
13643
|
+
const targetBranch = new Set(branchNodes(targetNode).map((node) => node.index));
|
|
13644
|
+
const block = rows.filter((_row, index) => moving2.has(index));
|
|
13645
|
+
const remaining = rows.filter((_row, index) => !moving2.has(index));
|
|
13646
|
+
const remainingSourceIndexes = rows.map((_row, index) => index).filter((index) => !moving2.has(index));
|
|
13647
|
+
let insertion = remainingSourceIndexes.findIndex((index) => index === target);
|
|
13648
|
+
if (position2 === "after") {
|
|
13649
|
+
const targetPositions = remainingSourceIndexes.map((sourceIndex, index) => targetBranch.has(sourceIndex) ? index : -1).filter((index) => index >= 0);
|
|
13650
|
+
insertion = Math.max(...targetPositions) + 1;
|
|
13651
|
+
}
|
|
13652
|
+
if (insertion < 0) return null;
|
|
13653
|
+
remaining.splice(insertion, 0, ...block);
|
|
13654
|
+
return remaining;
|
|
13655
|
+
}
|
|
13656
|
+
const next = [...rows];
|
|
13657
|
+
const [moving] = next.splice(from, 1);
|
|
13658
|
+
const adjustedTarget = next.findIndex((row2) => Object.is(readRowValue(rowId, row2), targetId));
|
|
13659
|
+
next.splice(adjustedTarget + (position2 === "after" ? 1 : 0), 0, moving);
|
|
13660
|
+
return next;
|
|
13661
|
+
}
|
|
12907
13662
|
function resolveEditable(column, row2) {
|
|
12908
13663
|
if (!column || typeof column !== "object") return false;
|
|
12909
13664
|
if (typeof column.editor === "function") {
|
|
@@ -12984,7 +13739,8 @@ var zx = (() => {
|
|
|
12984
13739
|
for (const node of nodes) if (!byId.has(node.id)) byId.set(node.id, node);
|
|
12985
13740
|
const parentOf = /* @__PURE__ */ new Map();
|
|
12986
13741
|
for (const node of nodes) {
|
|
12987
|
-
const
|
|
13742
|
+
const parentValue = readRowValue(parentId, node.row);
|
|
13743
|
+
const parent = parentValue == null ? null : byId.get(parentValue) ?? null;
|
|
12988
13744
|
parentOf.set(node, parent === node ? null : parent);
|
|
12989
13745
|
}
|
|
12990
13746
|
for (const node of nodes) {
|
|
@@ -13013,6 +13769,9 @@ var zx = (() => {
|
|
|
13013
13769
|
function readRowValue(accessor, row2) {
|
|
13014
13770
|
return typeof accessor === "function" ? accessor(row2) : row2?.[accessor];
|
|
13015
13771
|
}
|
|
13772
|
+
function sameHierarchyParent(left, right) {
|
|
13773
|
+
return left == null && right == null || Object.is(left, right);
|
|
13774
|
+
}
|
|
13016
13775
|
function normalizeHierarchy(option, columns) {
|
|
13017
13776
|
if (!option || typeof option !== "object") return null;
|
|
13018
13777
|
const parentId = option.parentId;
|
|
@@ -13032,6 +13791,23 @@ var zx = (() => {
|
|
|
13032
13791
|
function isInsideEditor(target) {
|
|
13033
13792
|
return Boolean(target?.closest?.(".zx-table__editor"));
|
|
13034
13793
|
}
|
|
13794
|
+
function isInteractiveCellContent(target) {
|
|
13795
|
+
const cell = target?.closest?.("td[data-column]");
|
|
13796
|
+
const interactive = target?.closest?.([
|
|
13797
|
+
"a[href]",
|
|
13798
|
+
"button",
|
|
13799
|
+
"input",
|
|
13800
|
+
"select",
|
|
13801
|
+
"textarea",
|
|
13802
|
+
"summary",
|
|
13803
|
+
'[contenteditable="true"]',
|
|
13804
|
+
'[role="button"]',
|
|
13805
|
+
'[role="link"]',
|
|
13806
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
13807
|
+
"[data-zx-table-no-edit]"
|
|
13808
|
+
].join(","));
|
|
13809
|
+
return Boolean(cell && interactive && interactive !== cell && cell.contains(interactive));
|
|
13810
|
+
}
|
|
13035
13811
|
function deferToEditor(target) {
|
|
13036
13812
|
if (target.closest("[popover]")) return true;
|
|
13037
13813
|
return target.getAttribute?.("aria-expanded") === "true";
|
|
@@ -15443,6 +16219,244 @@ var zx = (() => {
|
|
|
15443
16219
|
) : "start";
|
|
15444
16220
|
}
|
|
15445
16221
|
|
|
16222
|
+
// src/components/card/card.js
|
|
16223
|
+
var Card = class extends Component {
|
|
16224
|
+
static cssName = "card";
|
|
16225
|
+
/** @type {Readonly<CardOptions>} */
|
|
16226
|
+
static defaults = {
|
|
16227
|
+
title: "",
|
|
16228
|
+
media: null,
|
|
16229
|
+
content: null,
|
|
16230
|
+
actions: [],
|
|
16231
|
+
footer: null,
|
|
16232
|
+
link: null,
|
|
16233
|
+
variant: "outlined",
|
|
16234
|
+
orientation: "vertical",
|
|
16235
|
+
headingLevel: 3
|
|
16236
|
+
};
|
|
16237
|
+
/**
|
|
16238
|
+
* Creates or enhances one card.
|
|
16239
|
+
* @param {Element|string|null} [target=null] Existing card container, selector, or null.
|
|
16240
|
+
* @param {CardOptions} [options={}] Card options.
|
|
16241
|
+
*/
|
|
16242
|
+
constructor(target = null, options = {}) {
|
|
16243
|
+
super(target, options);
|
|
16244
|
+
}
|
|
16245
|
+
/** @returns {HTMLElement} */
|
|
16246
|
+
render() {
|
|
16247
|
+
const created = !this.el;
|
|
16248
|
+
const root = (
|
|
16249
|
+
/** @type {HTMLElement} */
|
|
16250
|
+
this.el ?? h("article")
|
|
16251
|
+
);
|
|
16252
|
+
this.el = root;
|
|
16253
|
+
this._createdRoot = created;
|
|
16254
|
+
this._original = created ? null : snapshotTarget(root);
|
|
16255
|
+
this._cleaned = false;
|
|
16256
|
+
this._title = "";
|
|
16257
|
+
this._link = null;
|
|
16258
|
+
this._actionAbort = null;
|
|
16259
|
+
this._linkAbort = null;
|
|
16260
|
+
const originalContent = created ? [] : Array.from(root.childNodes);
|
|
16261
|
+
try {
|
|
16262
|
+
const headingLevel = normalizeHeadingLevel(this.options.headingLevel);
|
|
16263
|
+
const headingId = uid("zx-card-heading");
|
|
16264
|
+
const heading = h(`h${headingLevel}`, {
|
|
16265
|
+
ref: "heading",
|
|
16266
|
+
class: "zx-card__heading",
|
|
16267
|
+
id: headingId
|
|
16268
|
+
});
|
|
16269
|
+
const media = h("div", { ref: "media", class: "zx-card__media", hidden: true });
|
|
16270
|
+
const actions2 = h("div", {
|
|
16271
|
+
ref: "actions",
|
|
16272
|
+
class: "zx-card__actions",
|
|
16273
|
+
role: "group",
|
|
16274
|
+
ariaLabel: "Card actions",
|
|
16275
|
+
hidden: true
|
|
16276
|
+
});
|
|
16277
|
+
const content = h("div", { ref: "content", class: "zx-card__content", hidden: true });
|
|
16278
|
+
const footer = h("footer", { ref: "footer", class: "zx-card__footer", hidden: true });
|
|
16279
|
+
const header = h("header", { ref: "header", class: "zx-card__header" }, heading, actions2);
|
|
16280
|
+
root.replaceChildren(h(
|
|
16281
|
+
"div",
|
|
16282
|
+
{ class: "zx-card__layout" },
|
|
16283
|
+
media,
|
|
16284
|
+
h("div", { class: "zx-card__inner" }, header, content, footer)
|
|
16285
|
+
));
|
|
16286
|
+
root.dataset.variant = normalizeVariant2(this.options.variant);
|
|
16287
|
+
root.dataset.orientation = normalizeOrientation(this.options.orientation);
|
|
16288
|
+
this.setTitle(this.options.title);
|
|
16289
|
+
this.setMedia(this.options.media);
|
|
16290
|
+
if (this.options.content !== null) this.setContent(this.options.content);
|
|
16291
|
+
else if (originalContent.length) {
|
|
16292
|
+
content.replaceChildren(...originalContent);
|
|
16293
|
+
content.hidden = false;
|
|
16294
|
+
}
|
|
16295
|
+
this.setActions(this.options.actions);
|
|
16296
|
+
this.setFooter(this.options.footer);
|
|
16297
|
+
this.setLink(this.options.link);
|
|
16298
|
+
return root;
|
|
16299
|
+
} catch (error2) {
|
|
16300
|
+
this._actionAbort?.abort();
|
|
16301
|
+
this._linkAbort?.abort();
|
|
16302
|
+
if (!created) restoreTarget(root, this._original);
|
|
16303
|
+
throw error2;
|
|
16304
|
+
}
|
|
16305
|
+
}
|
|
16306
|
+
/** Replaces the card title. @param {string} title Next title. @returns {this} */
|
|
16307
|
+
setTitle(title) {
|
|
16308
|
+
const next = String(title ?? "");
|
|
16309
|
+
if (this._link && !next.trim()) throw new TypeError("A linked Card requires a non-empty title");
|
|
16310
|
+
this._title = next;
|
|
16311
|
+
this._renderHeading();
|
|
16312
|
+
return this;
|
|
16313
|
+
}
|
|
16314
|
+
/** Replaces or removes the media region. @param {CardContent} content Media. @returns {this} */
|
|
16315
|
+
setMedia(content) {
|
|
16316
|
+
replaceCardContent(this.refs.media, content, "Card media");
|
|
16317
|
+
this.refs.media.hidden = content === null;
|
|
16318
|
+
return this;
|
|
16319
|
+
}
|
|
16320
|
+
/** Replaces or clears the main content. @param {CardContent} content Content. @returns {this} */
|
|
16321
|
+
setContent(content) {
|
|
16322
|
+
replaceCardContent(this.refs.content, content, "Card content");
|
|
16323
|
+
this.refs.content.hidden = content === null;
|
|
16324
|
+
return this;
|
|
16325
|
+
}
|
|
16326
|
+
/** Replaces the secondary action controls. @param {CardAction[]} actions Actions. @returns {this} */
|
|
16327
|
+
setActions(actions2) {
|
|
16328
|
+
if (!Array.isArray(actions2)) throw new TypeError("Card actions must be an array");
|
|
16329
|
+
const nextAbort = new AbortController();
|
|
16330
|
+
let controls;
|
|
16331
|
+
try {
|
|
16332
|
+
controls = actions2.map((action) => {
|
|
16333
|
+
if (isElement(action)) return action;
|
|
16334
|
+
if (!action || typeof action !== "object" || Array.isArray(action)) {
|
|
16335
|
+
throw new TypeError("Card actions must be Elements or button descriptors");
|
|
16336
|
+
}
|
|
16337
|
+
const descriptor = { ...action };
|
|
16338
|
+
const onclick = descriptor.onclick;
|
|
16339
|
+
delete descriptor.onclick;
|
|
16340
|
+
const control = button(descriptor);
|
|
16341
|
+
if (onclick !== void 0 && typeof onclick !== "function") {
|
|
16342
|
+
throw new TypeError("Card action onclick must be a function");
|
|
16343
|
+
}
|
|
16344
|
+
if (onclick) control.addEventListener("click", onclick, { signal: nextAbort.signal });
|
|
16345
|
+
return control;
|
|
16346
|
+
});
|
|
16347
|
+
} catch (error2) {
|
|
16348
|
+
nextAbort.abort();
|
|
16349
|
+
throw error2;
|
|
16350
|
+
}
|
|
16351
|
+
this._actionAbort?.abort();
|
|
16352
|
+
this._actionAbort = nextAbort;
|
|
16353
|
+
this.refs.actions.replaceChildren(...controls);
|
|
16354
|
+
this.refs.actions.hidden = controls.length === 0;
|
|
16355
|
+
this._syncHeader();
|
|
16356
|
+
return this;
|
|
16357
|
+
}
|
|
16358
|
+
/** Replaces or removes the footer. @param {CardContent} content Footer. @returns {this} */
|
|
16359
|
+
setFooter(content) {
|
|
16360
|
+
replaceCardContent(this.refs.footer, content, "Card footer");
|
|
16361
|
+
this.refs.footer.hidden = content === null;
|
|
16362
|
+
return this;
|
|
16363
|
+
}
|
|
16364
|
+
/** Sets or removes the title's native primary link. @param {CardLink} link Link. @returns {this} */
|
|
16365
|
+
setLink(link) {
|
|
16366
|
+
const next = normalizeCardLink(link);
|
|
16367
|
+
if (next && !this._title.trim()) throw new TypeError("A linked Card requires a non-empty title");
|
|
16368
|
+
this._link = next;
|
|
16369
|
+
this.el.dataset.linked = next ? "true" : "false";
|
|
16370
|
+
this._renderHeading();
|
|
16371
|
+
return this;
|
|
16372
|
+
}
|
|
16373
|
+
/** Restores an enhanced target exactly, or removes an owned card. @returns {void} */
|
|
16374
|
+
destroy() {
|
|
16375
|
+
if (this._cleaned) return;
|
|
16376
|
+
this._cleaned = true;
|
|
16377
|
+
this._actionAbort?.abort();
|
|
16378
|
+
this._linkAbort?.abort();
|
|
16379
|
+
super.destroy();
|
|
16380
|
+
if (!this._createdRoot) restoreTarget(this.el, this._original);
|
|
16381
|
+
}
|
|
16382
|
+
/** @returns {void} */
|
|
16383
|
+
_renderHeading() {
|
|
16384
|
+
const title = this._title;
|
|
16385
|
+
const nextAbort = new AbortController();
|
|
16386
|
+
const node = this._link ? h("a", {
|
|
16387
|
+
class: "zx-card__primary",
|
|
16388
|
+
href: this._link.href,
|
|
16389
|
+
target: this._link.target,
|
|
16390
|
+
rel: this._link.rel
|
|
16391
|
+
}, title) : h("span", { class: "zx-card__title" }, title);
|
|
16392
|
+
if (this._link?.onclick) node.addEventListener("click", this._link.onclick, { signal: nextAbort.signal });
|
|
16393
|
+
this._linkAbort?.abort();
|
|
16394
|
+
this._linkAbort = nextAbort;
|
|
16395
|
+
this.refs.heading.replaceChildren(node);
|
|
16396
|
+
this.refs.heading.hidden = title === "";
|
|
16397
|
+
if (title) this.el.setAttribute("aria-labelledby", this.refs.heading.id);
|
|
16398
|
+
else this.el.removeAttribute("aria-labelledby");
|
|
16399
|
+
this._syncHeader();
|
|
16400
|
+
}
|
|
16401
|
+
/** @returns {void} */
|
|
16402
|
+
_syncHeader() {
|
|
16403
|
+
if (!this.refs.header) return;
|
|
16404
|
+
this.refs.header.hidden = this._title === "" && this.refs.actions.childElementCount === 0;
|
|
16405
|
+
}
|
|
16406
|
+
};
|
|
16407
|
+
function replaceCardContent(host, content, label) {
|
|
16408
|
+
if (content === null) {
|
|
16409
|
+
host.replaceChildren();
|
|
16410
|
+
return;
|
|
16411
|
+
}
|
|
16412
|
+
if (typeof content === "string") {
|
|
16413
|
+
host.textContent = content;
|
|
16414
|
+
return;
|
|
16415
|
+
}
|
|
16416
|
+
const node = content instanceof Component ? content.toElement() : content;
|
|
16417
|
+
if (node instanceof Node) {
|
|
16418
|
+
host.replaceChildren(node);
|
|
16419
|
+
return;
|
|
16420
|
+
}
|
|
16421
|
+
throw new TypeError(`${label} must be a string, Node, Component, or null`);
|
|
16422
|
+
}
|
|
16423
|
+
function normalizeCardLink(value) {
|
|
16424
|
+
if (value === null) return null;
|
|
16425
|
+
const descriptor = typeof value === "string" ? { href: value } : value;
|
|
16426
|
+
if (!descriptor || typeof descriptor !== "object" || Array.isArray(descriptor)) {
|
|
16427
|
+
throw new TypeError("Card link must be a URL string, descriptor, or null");
|
|
16428
|
+
}
|
|
16429
|
+
const href = safeHref(descriptor.href);
|
|
16430
|
+
if (href === null) {
|
|
16431
|
+
throw new TypeError("Card link requires a safe non-empty href");
|
|
16432
|
+
}
|
|
16433
|
+
if (descriptor.onclick !== void 0 && typeof descriptor.onclick !== "function") {
|
|
16434
|
+
throw new TypeError("Card link onclick must be a function");
|
|
16435
|
+
}
|
|
16436
|
+
const target = descriptor.target == null ? void 0 : String(descriptor.target);
|
|
16437
|
+
let rel = descriptor.rel == null ? "" : String(descriptor.rel).trim();
|
|
16438
|
+
if (target === "_blank" && !/(^|\s)noopener(\s|$)/i.test(rel)) {
|
|
16439
|
+
rel = `${rel} noopener`.trim();
|
|
16440
|
+
}
|
|
16441
|
+
return { href, target, rel: rel || void 0, onclick: descriptor.onclick };
|
|
16442
|
+
}
|
|
16443
|
+
function normalizeVariant2(value) {
|
|
16444
|
+
return ["raised", "filled"].includes(String(value)) ? (
|
|
16445
|
+
/** @type {'raised'|'filled'} */
|
|
16446
|
+
value
|
|
16447
|
+
) : "outlined";
|
|
16448
|
+
}
|
|
16449
|
+
function normalizeOrientation(value) {
|
|
16450
|
+
return value === "horizontal" ? "horizontal" : "vertical";
|
|
16451
|
+
}
|
|
16452
|
+
function normalizeHeadingLevel(value) {
|
|
16453
|
+
const level = Math.trunc(Number(value));
|
|
16454
|
+
return (
|
|
16455
|
+
/** @type {1|2|3|4|5|6} */
|
|
16456
|
+
level >= 1 && level <= 6 ? level : 3
|
|
16457
|
+
);
|
|
16458
|
+
}
|
|
16459
|
+
|
|
15446
16460
|
// src/components/panel/panel.js
|
|
15447
16461
|
var Panel = class extends Component {
|
|
15448
16462
|
static cssName = "panel";
|
|
@@ -18773,6 +19787,106 @@ var zx = (() => {
|
|
|
18773
19787
|
return value.toLocaleString();
|
|
18774
19788
|
}
|
|
18775
19789
|
|
|
19790
|
+
// src/components/app-icon/app-icon.js
|
|
19791
|
+
var AppIcon = class extends Component {
|
|
19792
|
+
static cssName = "app-icon";
|
|
19793
|
+
/** @type {Readonly<AppIconOptions>} */
|
|
19794
|
+
static defaults = {
|
|
19795
|
+
icon: null,
|
|
19796
|
+
color: null,
|
|
19797
|
+
size: 36,
|
|
19798
|
+
iconSize: "52%",
|
|
19799
|
+
label: null,
|
|
19800
|
+
badge: null,
|
|
19801
|
+
selected: false,
|
|
19802
|
+
glass: "subtle",
|
|
19803
|
+
class: []
|
|
19804
|
+
};
|
|
19805
|
+
/** @returns {HTMLElement} */
|
|
19806
|
+
render() {
|
|
19807
|
+
this._createdRoot = this.el === null;
|
|
19808
|
+
const root = (
|
|
19809
|
+
/** @type {HTMLElement} */
|
|
19810
|
+
this.el ?? h("span")
|
|
19811
|
+
);
|
|
19812
|
+
this.el = root;
|
|
19813
|
+
this._snapshot = this._createdRoot ? null : snapshotTarget(root);
|
|
19814
|
+
this._state = { ...this.options };
|
|
19815
|
+
this._appliedClasses = [];
|
|
19816
|
+
this._sync();
|
|
19817
|
+
return root;
|
|
19818
|
+
}
|
|
19819
|
+
/** Updates identity or presentation without replacing the root. @param {Partial<AppIconOptions>} values @returns {this} */
|
|
19820
|
+
set(values = {}) {
|
|
19821
|
+
if (!values || typeof values !== "object") return this;
|
|
19822
|
+
Object.assign(this._state, values);
|
|
19823
|
+
this._sync();
|
|
19824
|
+
return this;
|
|
19825
|
+
}
|
|
19826
|
+
/** Restores an enhanced target. @returns {void} */
|
|
19827
|
+
destroy() {
|
|
19828
|
+
const root = this.el;
|
|
19829
|
+
const snapshot10 = this._snapshot;
|
|
19830
|
+
super.destroy();
|
|
19831
|
+
if (!this._createdRoot && root) restoreTarget(root, snapshot10);
|
|
19832
|
+
}
|
|
19833
|
+
/** @returns {void} */
|
|
19834
|
+
_sync() {
|
|
19835
|
+
const state = this._state;
|
|
19836
|
+
this.el.classList.remove(...this._appliedClasses);
|
|
19837
|
+
this._appliedClasses = [state.class].flat().flatMap((value) => String(value ?? "").split(/\s+/)).filter(Boolean);
|
|
19838
|
+
this.el.classList.add(...this._appliedClasses);
|
|
19839
|
+
const size2 = cssLength2(state.size, "36px");
|
|
19840
|
+
const glyphSize = cssLength2(state.iconSize, "52%");
|
|
19841
|
+
this.el.style.setProperty("--zx-app-icon-size", size2);
|
|
19842
|
+
this.el.style.setProperty("--zx-app-icon-glyph-size", glyphSize);
|
|
19843
|
+
if (isCssColor(state.color)) this.el.style.setProperty("--zx-app-icon-color", String(state.color));
|
|
19844
|
+
else this.el.style.removeProperty("--zx-app-icon-color");
|
|
19845
|
+
this.el.dataset.glass = normalizeGlass(state.glass);
|
|
19846
|
+
this.el.toggleAttribute("data-selected", Boolean(state.selected));
|
|
19847
|
+
const label = state.label == null ? "" : String(state.label).trim();
|
|
19848
|
+
if (label) {
|
|
19849
|
+
this.el.setAttribute("role", "img");
|
|
19850
|
+
this.el.setAttribute("aria-label", label);
|
|
19851
|
+
this.el.removeAttribute("aria-hidden");
|
|
19852
|
+
} else {
|
|
19853
|
+
this.el.removeAttribute("role");
|
|
19854
|
+
this.el.removeAttribute("aria-label");
|
|
19855
|
+
this.el.setAttribute("aria-hidden", "true");
|
|
19856
|
+
}
|
|
19857
|
+
const visual = resolveVisual(state.icon);
|
|
19858
|
+
const badge2 = state.badge == null || state.badge === "" ? null : h("span", {
|
|
19859
|
+
class: "zx-app-icon__badge",
|
|
19860
|
+
ariaHidden: "true"
|
|
19861
|
+
}, String(state.badge));
|
|
19862
|
+
this.el.replaceChildren(
|
|
19863
|
+
h("span", { class: "zx-app-icon__shine", ariaHidden: "true" }),
|
|
19864
|
+
h("span", { class: "zx-app-icon__glyph", ariaHidden: "true" }, visual),
|
|
19865
|
+
badge2
|
|
19866
|
+
);
|
|
19867
|
+
}
|
|
19868
|
+
};
|
|
19869
|
+
function appIcon(options = {}) {
|
|
19870
|
+
return new AppIcon(null, options).toElement();
|
|
19871
|
+
}
|
|
19872
|
+
function resolveVisual(value) {
|
|
19873
|
+
const resolved = typeof value === "function" ? value() : value;
|
|
19874
|
+
if (typeof resolved === "string") return icon(resolved, { size: "1em" });
|
|
19875
|
+
return resolved && typeof resolved === "object" && typeof resolved.nodeType === "number" ? (
|
|
19876
|
+
/** @type {Node} */
|
|
19877
|
+
resolved
|
|
19878
|
+
) : null;
|
|
19879
|
+
}
|
|
19880
|
+
function cssLength2(value, fallback) {
|
|
19881
|
+
if (typeof value === "number" && Number.isFinite(value)) return `${Math.max(1, value)}px`;
|
|
19882
|
+
const text4 = String(value ?? "").trim();
|
|
19883
|
+
return text4 && !/[;{}]/.test(text4) ? text4 : fallback;
|
|
19884
|
+
}
|
|
19885
|
+
function normalizeGlass(value) {
|
|
19886
|
+
if (value === false || value === "none") return "none";
|
|
19887
|
+
return value === "strong" ? "strong" : "subtle";
|
|
19888
|
+
}
|
|
19889
|
+
|
|
18776
19890
|
// src/components/launcher/launcher.js
|
|
18777
19891
|
var Launcher = class extends Component {
|
|
18778
19892
|
static cssName = "launcher";
|
|
@@ -18786,9 +19900,12 @@ var zx = (() => {
|
|
|
18786
19900
|
maxResults: 100,
|
|
18787
19901
|
placeholder: "Search applications and records",
|
|
18788
19902
|
label: "Launcher",
|
|
19903
|
+
closeLabel: "Close launcher",
|
|
19904
|
+
resultsLabel: "Launcher results",
|
|
18789
19905
|
emptyText: "No results",
|
|
18790
19906
|
loadingText: "Searching\u2026",
|
|
18791
|
-
shortcut: "mod+k"
|
|
19907
|
+
shortcut: "mod+k",
|
|
19908
|
+
hints: { move: "Move", open: "Open", close: "Close" }
|
|
18792
19909
|
};
|
|
18793
19910
|
/**
|
|
18794
19911
|
* Creates an owned launcher dialog or enhances an existing `<dialog>` target.
|
|
@@ -18817,6 +19934,7 @@ var zx = (() => {
|
|
|
18817
19934
|
this._sources = normalizeSources(this.options.sources);
|
|
18818
19935
|
this._sourceItems = /* @__PURE__ */ new Map();
|
|
18819
19936
|
this._query = String(this.options.query ?? "");
|
|
19937
|
+
this._renderedQuery = null;
|
|
18820
19938
|
this._results = [];
|
|
18821
19939
|
this._active = -1;
|
|
18822
19940
|
this._request = null;
|
|
@@ -18843,7 +19961,7 @@ var zx = (() => {
|
|
|
18843
19961
|
ref: "close",
|
|
18844
19962
|
class: "zx-icon-btn zx-launcher__close",
|
|
18845
19963
|
type: "button",
|
|
18846
|
-
ariaLabel:
|
|
19964
|
+
ariaLabel: this.options.closeLabel
|
|
18847
19965
|
}, icon("x"));
|
|
18848
19966
|
const status = h("div", {
|
|
18849
19967
|
ref: "status",
|
|
@@ -18856,16 +19974,25 @@ var zx = (() => {
|
|
|
18856
19974
|
class: "zx-launcher__results",
|
|
18857
19975
|
id: listId,
|
|
18858
19976
|
role: "listbox",
|
|
18859
|
-
ariaLabel:
|
|
19977
|
+
ariaLabel: this.options.resultsLabel
|
|
18860
19978
|
});
|
|
19979
|
+
const shortcut = this.options.shortcut === "mod+k" ? h("kbd", {
|
|
19980
|
+
class: "zx-launcher__shortcut",
|
|
19981
|
+
ariaHidden: "true"
|
|
19982
|
+
}, launcherShortcutLabel()) : null;
|
|
19983
|
+
const footer = launcherHints(this.options.hints);
|
|
18861
19984
|
dialog.setAttribute("aria-label", String(this.options.label));
|
|
18862
19985
|
dialog.replaceChildren(
|
|
18863
19986
|
h(
|
|
18864
19987
|
"div",
|
|
18865
19988
|
{ class: "zx-launcher__surface" },
|
|
18866
|
-
h("div", {
|
|
19989
|
+
h("div", {
|
|
19990
|
+
class: "zx-launcher__search",
|
|
19991
|
+
dataset: { shortcut: shortcut ? "true" : "false" }
|
|
19992
|
+
}, icon("search"), input, shortcut, close),
|
|
18867
19993
|
status,
|
|
18868
|
-
results
|
|
19994
|
+
results,
|
|
19995
|
+
footer
|
|
18869
19996
|
)
|
|
18870
19997
|
);
|
|
18871
19998
|
dialog.dataset.state = "closed";
|
|
@@ -18889,6 +20016,17 @@ var zx = (() => {
|
|
|
18889
20016
|
if (!option || !results.contains(option)) return;
|
|
18890
20017
|
this._activate(Number(option.getAttribute("data-launcher-index")), event);
|
|
18891
20018
|
});
|
|
20019
|
+
this.listen(results, "pointermove", (event) => {
|
|
20020
|
+
const option = (
|
|
20021
|
+
/** @type {Element|null} */
|
|
20022
|
+
event.target?.closest?.("[data-launcher-index]")
|
|
20023
|
+
);
|
|
20024
|
+
if (!option || !results.contains(option)) return;
|
|
20025
|
+
const index = Number(option.getAttribute("data-launcher-index"));
|
|
20026
|
+
if (!Number.isInteger(index) || this._results[index]?.disabled || index === this._active) return;
|
|
20027
|
+
this._active = index;
|
|
20028
|
+
this._syncActive();
|
|
20029
|
+
});
|
|
18892
20030
|
this.listen(dialog, "cancel", (event) => {
|
|
18893
20031
|
event.preventDefault();
|
|
18894
20032
|
this.close();
|
|
@@ -18958,8 +20096,15 @@ var zx = (() => {
|
|
|
18958
20096
|
this._renderResults();
|
|
18959
20097
|
return this;
|
|
18960
20098
|
}
|
|
18961
|
-
/**
|
|
18962
|
-
|
|
20099
|
+
/** Marks one local item as the current destination. @param {string|number|null} id Item id. @returns {this} */
|
|
20100
|
+
setCurrent(id) {
|
|
20101
|
+
const key = id == null ? null : String(id);
|
|
20102
|
+
this._items = this._items.map((item) => ({ ...item, current: key !== null && String(item.id) === key }));
|
|
20103
|
+
this._renderResults();
|
|
20104
|
+
return this;
|
|
20105
|
+
}
|
|
20106
|
+
/** Replaces asynchronous sources and aborts work from the old set. @param {LauncherSource[]} sources @returns {this} */
|
|
20107
|
+
setSources(sources) {
|
|
18963
20108
|
this._abortRequest();
|
|
18964
20109
|
this._sources = normalizeSources(sources);
|
|
18965
20110
|
this._sourceItems.clear();
|
|
@@ -18993,7 +20138,7 @@ var zx = (() => {
|
|
|
18993
20138
|
async _loadSources() {
|
|
18994
20139
|
this._queryTimer = null;
|
|
18995
20140
|
const query = this._query.trim();
|
|
18996
|
-
const sources = this._sources.filter((source) => query.length >= Math.max(Number(this.options.minQuery) || 0, Number(source.minQuery) || 0));
|
|
20141
|
+
const sources = this._sources.filter((source) => visibleForQuery(source.when, query) && query.length >= Math.max(Number(this.options.minQuery) || 0, Number(source.minQuery) || 0));
|
|
18997
20142
|
if (sources.length === 0) return;
|
|
18998
20143
|
const request = new AbortController();
|
|
18999
20144
|
const sequence = ++this._requestSequence;
|
|
@@ -19003,15 +20148,17 @@ var zx = (() => {
|
|
|
19003
20148
|
try {
|
|
19004
20149
|
const loaded = await source.load(query, { signal: request.signal });
|
|
19005
20150
|
if (request.signal.aborted || sequence !== this._requestSequence) return;
|
|
19006
|
-
this._sourceItems.set(source.id, normalizeItems2(loaded).map((item) => ({
|
|
20151
|
+
this._sourceItems.set(source.id, normalizeItems2(loaded).map((item, itemOrder) => ({
|
|
19007
20152
|
...item,
|
|
19008
20153
|
_source: source.id,
|
|
19009
|
-
|
|
20154
|
+
_sourceOrder: source.order === "source",
|
|
20155
|
+
group: item.group ?? source.label ?? "",
|
|
20156
|
+
itemOrder: item.itemOrder ?? (source.order === "source" ? itemOrder : void 0)
|
|
19010
20157
|
})));
|
|
19011
20158
|
this._renderResults();
|
|
19012
|
-
} catch (
|
|
20159
|
+
} catch (error2) {
|
|
19013
20160
|
if (request.signal.aborted || sequence !== this._requestSequence) return;
|
|
19014
|
-
this.emit("error", { source, error });
|
|
20161
|
+
this.emit("error", { source, error: error2 });
|
|
19015
20162
|
}
|
|
19016
20163
|
}));
|
|
19017
20164
|
if (request.signal.aborted || sequence !== this._requestSequence) return;
|
|
@@ -19035,23 +20182,36 @@ var zx = (() => {
|
|
|
19035
20182
|
}
|
|
19036
20183
|
/** @returns {void} */
|
|
19037
20184
|
_renderResults() {
|
|
19038
|
-
const
|
|
20185
|
+
const queryKey = this._query;
|
|
20186
|
+
const activeId = this._renderedQuery === queryKey && this._active >= 0 && this._results[this._active] ? String(this._results[this._active].id) : null;
|
|
20187
|
+
const query = this._query.trim();
|
|
20188
|
+
const local = this._items.filter((item) => visibleForQuery(item.when, query));
|
|
20189
|
+
const rankedLocal = rankLauncherItems(local, query, this.options.maxResults);
|
|
20190
|
+
const remote = this._sources.flatMap((source) => {
|
|
20191
|
+
const loaded = this._sourceItems.get(String(source.id)) ?? [];
|
|
20192
|
+
return source.order === "source" ? loaded.filter((item) => visibleForQuery(item.when, query)) : rankLauncherItems(
|
|
20193
|
+
loaded.filter((item) => visibleForQuery(item.when, query)),
|
|
20194
|
+
query,
|
|
20195
|
+
this.options.maxResults
|
|
20196
|
+
);
|
|
20197
|
+
});
|
|
19039
20198
|
const seen = /* @__PURE__ */ new Set();
|
|
19040
|
-
const combined = [...
|
|
20199
|
+
const combined = [...rankedLocal.map((item) => ({ ...item, _source: null })), ...remote].filter((item) => {
|
|
19041
20200
|
const key = String(item.id);
|
|
19042
20201
|
if (seen.has(key)) return false;
|
|
19043
20202
|
seen.add(key);
|
|
19044
20203
|
return true;
|
|
19045
|
-
});
|
|
19046
|
-
const ranked = rankLauncherItems(combined, this._query, this.options.maxResults);
|
|
20204
|
+
}).slice(0, Math.max(0, Number(this.options.maxResults) || 0));
|
|
19047
20205
|
const groups = /* @__PURE__ */ new Map();
|
|
19048
|
-
for (const item of
|
|
20206
|
+
for (const item of combined) {
|
|
19049
20207
|
const group = String(item.group ?? "");
|
|
19050
20208
|
if (!groups.has(group)) groups.set(group, []);
|
|
19051
20209
|
groups.get(group).push(item);
|
|
19052
20210
|
}
|
|
19053
20211
|
this._results = [...groups.values()].flat();
|
|
19054
|
-
this.
|
|
20212
|
+
this._renderedQuery = queryKey;
|
|
20213
|
+
const preserved = activeId === null ? -1 : this._results.findIndex((item) => !item.disabled && String(item.id) === activeId);
|
|
20214
|
+
this._active = preserved >= 0 ? preserved : this._results.findIndex((item) => !item.disabled);
|
|
19055
20215
|
const nodes = [];
|
|
19056
20216
|
let index = 0;
|
|
19057
20217
|
for (const [group, items] of groups) {
|
|
@@ -19059,7 +20219,8 @@ var zx = (() => {
|
|
|
19059
20219
|
if (group) nodes.push(h("div", {
|
|
19060
20220
|
class: "zx-launcher__group",
|
|
19061
20221
|
role: "group",
|
|
19062
|
-
ariaLabel: group
|
|
20222
|
+
ariaLabel: group,
|
|
20223
|
+
dataset: { layout: items.every((item) => item.kind === "application") ? "grid" : "list" }
|
|
19063
20224
|
}, h("div", { class: "zx-launcher__group-label", ariaHidden: "true" }, group), options));
|
|
19064
20225
|
else nodes.push(...options);
|
|
19065
20226
|
}
|
|
@@ -19069,9 +20230,14 @@ var zx = (() => {
|
|
|
19069
20230
|
}
|
|
19070
20231
|
/** @param {RankedLauncherItem} item @param {number} index @returns {HTMLElement} */
|
|
19071
20232
|
_option(item, index) {
|
|
19072
|
-
const
|
|
20233
|
+
const href = safeHref(item.href);
|
|
20234
|
+
const tag = href ? "a" : "button";
|
|
19073
20235
|
const children = [];
|
|
19074
|
-
|
|
20236
|
+
const visual = launcherItemIcon(item);
|
|
20237
|
+
if (visual) children.push(h("span", {
|
|
20238
|
+
class: "zx-launcher__icon",
|
|
20239
|
+
ariaHidden: "true"
|
|
20240
|
+
}, visual));
|
|
19075
20241
|
children.push(h(
|
|
19076
20242
|
"span",
|
|
19077
20243
|
{ class: "zx-launcher__copy" },
|
|
@@ -19084,14 +20250,20 @@ var zx = (() => {
|
|
|
19084
20250
|
h(tag, {
|
|
19085
20251
|
class: "zx-launcher__option",
|
|
19086
20252
|
type: tag === "button" ? "button" : null,
|
|
19087
|
-
href
|
|
20253
|
+
href,
|
|
19088
20254
|
target: item.target ?? null,
|
|
20255
|
+
rel: tag === "a" ? "noopener" : null,
|
|
19089
20256
|
role: "option",
|
|
19090
20257
|
id: uid("zx-launcher-option"),
|
|
19091
20258
|
tabIndex: -1,
|
|
19092
20259
|
ariaSelected: "false",
|
|
19093
20260
|
ariaDisabled: item.disabled ? "true" : null,
|
|
19094
|
-
|
|
20261
|
+
ariaCurrent: item.current ? "page" : null,
|
|
20262
|
+
dataset: {
|
|
20263
|
+
launcherIndex: index,
|
|
20264
|
+
pinned: item.pinned !== void 0 && item.pinned !== false ? "true" : null,
|
|
20265
|
+
kind: normalizeLauncherKind(item.kind)
|
|
20266
|
+
}
|
|
19095
20267
|
}, children)
|
|
19096
20268
|
);
|
|
19097
20269
|
}
|
|
@@ -19128,6 +20300,17 @@ var zx = (() => {
|
|
|
19128
20300
|
}
|
|
19129
20301
|
const enabled = this._results.map((item, index) => item.disabled ? -1 : index).filter((index) => index >= 0);
|
|
19130
20302
|
if (enabled.length === 0) return;
|
|
20303
|
+
if (/^Arrow(?:Left|Right|Up|Down)$/.test(event.key)) {
|
|
20304
|
+
const next = this._spatialIndex(event.key, enabled);
|
|
20305
|
+
if (next !== null) {
|
|
20306
|
+
event.preventDefault();
|
|
20307
|
+
this._active = next;
|
|
20308
|
+
this._syncActive();
|
|
20309
|
+
this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`)?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
20310
|
+
return;
|
|
20311
|
+
}
|
|
20312
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowRight") return;
|
|
20313
|
+
}
|
|
19131
20314
|
let position2 = enabled.indexOf(this._active);
|
|
19132
20315
|
if (event.key === "Home" || event.key === "PageUp") position2 = 0;
|
|
19133
20316
|
else if (event.key === "End" || event.key === "PageDown") position2 = enabled.length - 1;
|
|
@@ -19139,6 +20322,15 @@ var zx = (() => {
|
|
|
19139
20322
|
this._syncActive();
|
|
19140
20323
|
this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`)?.scrollIntoView({ block: "nearest" });
|
|
19141
20324
|
}
|
|
20325
|
+
/** @param {string} direction @param {number[]} enabled @returns {number|null} */
|
|
20326
|
+
_spatialIndex(direction, enabled) {
|
|
20327
|
+
const current = this.refs.results.querySelector(`[data-launcher-index="${this._active}"]`);
|
|
20328
|
+
if (!(current instanceof HTMLElement)) return null;
|
|
20329
|
+
if ((direction === "ArrowLeft" || direction === "ArrowRight") && this._results[this._active]?.kind !== "application") return null;
|
|
20330
|
+
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"));
|
|
20331
|
+
const next = findSpatialLauncherOption(candidates, current, direction);
|
|
20332
|
+
return next ? Number(next.getAttribute("data-launcher-index")) : null;
|
|
20333
|
+
}
|
|
19142
20334
|
/** @param {number} index @param {Event} event @returns {void} */
|
|
19143
20335
|
_activate(index, event) {
|
|
19144
20336
|
const item = this._results[index];
|
|
@@ -19164,25 +20356,103 @@ var zx = (() => {
|
|
|
19164
20356
|
}
|
|
19165
20357
|
/** @param {KeyboardEvent} event @returns {void} */
|
|
19166
20358
|
_onDocumentKeydown(event) {
|
|
19167
|
-
if (this.options.shortcut !== "mod+k" || event.isComposing || event.key.toLowerCase() !== "k" ||
|
|
20359
|
+
if (this.options.shortcut !== "mod+k" || event.isComposing || event.key.toLowerCase() !== "k" || Number(event.metaKey) + Number(event.ctrlKey) !== 1 || event.altKey || event.shiftKey) return;
|
|
19168
20360
|
const active = document.activeElement;
|
|
19169
|
-
if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement || active instanceof HTMLElement && active.isContentEditable) return;
|
|
20361
|
+
if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement || active instanceof HTMLSelectElement || active instanceof HTMLElement && active.isContentEditable) return;
|
|
19170
20362
|
const modal = document.querySelector("dialog:modal");
|
|
19171
20363
|
if (modal && modal !== this.el) return;
|
|
19172
20364
|
event.preventDefault();
|
|
19173
20365
|
this.open();
|
|
19174
20366
|
}
|
|
19175
20367
|
};
|
|
20368
|
+
function launcherShortcutLabel() {
|
|
20369
|
+
const platform = String(
|
|
20370
|
+
/** @type {any} */
|
|
20371
|
+
navigator.userAgentData?.platform ?? navigator.platform ?? ""
|
|
20372
|
+
);
|
|
20373
|
+
return /mac|iphone|ipad/i.test(platform) ? "\u2318 K" : "Ctrl K";
|
|
20374
|
+
}
|
|
20375
|
+
function launcherHints(hints) {
|
|
20376
|
+
if (hints === false) return null;
|
|
20377
|
+
const labels = hints && typeof hints === "object" ? hints : {};
|
|
20378
|
+
return h(
|
|
20379
|
+
"footer",
|
|
20380
|
+
{ class: "zx-launcher__hints", ariaHidden: "true" },
|
|
20381
|
+
h("span", {}, h("kbd", {}, "\u2191\u2193\u2190\u2192"), labels.move ?? "Move"),
|
|
20382
|
+
h("span", {}, h("kbd", {}, "\u21B5"), labels.open ?? "Open"),
|
|
20383
|
+
h("span", {}, h("kbd", {}, "Esc"), labels.close ?? "Close")
|
|
20384
|
+
);
|
|
20385
|
+
}
|
|
20386
|
+
function launcherItemIcon(item) {
|
|
20387
|
+
let visual = item.icon;
|
|
20388
|
+
if (typeof visual === "function") visual = visual(item);
|
|
20389
|
+
if (typeof visual === "string") return icon(visual);
|
|
20390
|
+
if (visual instanceof Node) return visual.cloneNode(true);
|
|
20391
|
+
if (visual && typeof visual === "object" && typeof /** @type {any} */
|
|
20392
|
+
visual.toElement === "function") {
|
|
20393
|
+
const element = (
|
|
20394
|
+
/** @type {any} */
|
|
20395
|
+
visual.toElement()
|
|
20396
|
+
);
|
|
20397
|
+
return element instanceof Node ? element.cloneNode(true) : null;
|
|
20398
|
+
}
|
|
20399
|
+
return null;
|
|
20400
|
+
}
|
|
20401
|
+
function normalizeLauncherKind(kind) {
|
|
20402
|
+
return kind === "application" || kind === "record" ? kind : "action";
|
|
20403
|
+
}
|
|
20404
|
+
function visibleForQuery(when, query) {
|
|
20405
|
+
if (when === "empty") return query === "";
|
|
20406
|
+
if (when === "query") return query !== "";
|
|
20407
|
+
return true;
|
|
20408
|
+
}
|
|
20409
|
+
function findSpatialLauncherOption(options, current, direction) {
|
|
20410
|
+
const origin = current.getBoundingClientRect();
|
|
20411
|
+
if (origin.width === 0 && origin.height === 0) return null;
|
|
20412
|
+
const originX = origin.left + origin.width / 2;
|
|
20413
|
+
const originY = origin.top + origin.height / 2;
|
|
20414
|
+
let best = null;
|
|
20415
|
+
let bestScore = Infinity;
|
|
20416
|
+
for (const option of options) {
|
|
20417
|
+
if (option === current) continue;
|
|
20418
|
+
const rect = option.getBoundingClientRect();
|
|
20419
|
+
const dx = rect.left + rect.width / 2 - originX;
|
|
20420
|
+
const dy = rect.top + rect.height / 2 - originY;
|
|
20421
|
+
let valid = false;
|
|
20422
|
+
let score = Infinity;
|
|
20423
|
+
if (direction === "ArrowLeft") {
|
|
20424
|
+
valid = dx < -2 && Math.abs(dy) < Math.max(origin.height, rect.height) / 2;
|
|
20425
|
+
score = Math.abs(dx) + Math.abs(dy) * 4;
|
|
20426
|
+
} else if (direction === "ArrowRight") {
|
|
20427
|
+
valid = dx > 2 && Math.abs(dy) < Math.max(origin.height, rect.height) / 2;
|
|
20428
|
+
score = Math.abs(dx) + Math.abs(dy) * 4;
|
|
20429
|
+
} else if (direction === "ArrowUp") {
|
|
20430
|
+
valid = dy < -2;
|
|
20431
|
+
score = Math.abs(dy) * 10 + Math.abs(dx);
|
|
20432
|
+
} else if (direction === "ArrowDown") {
|
|
20433
|
+
valid = dy > 2;
|
|
20434
|
+
score = Math.abs(dy) * 10 + Math.abs(dx);
|
|
20435
|
+
}
|
|
20436
|
+
if (valid && score < bestScore) {
|
|
20437
|
+
best = option;
|
|
20438
|
+
bestScore = score;
|
|
20439
|
+
}
|
|
20440
|
+
}
|
|
20441
|
+
return best;
|
|
20442
|
+
}
|
|
19176
20443
|
function rankLauncherItems(items, query, limit = 100) {
|
|
19177
20444
|
const needle = searchable(query).trim();
|
|
19178
20445
|
const ranked = items.map((item, order) => ({ item, order, score: launcherScore(item, needle) })).filter(({ score }) => score < Infinity);
|
|
19179
20446
|
ranked.sort((a, b) => {
|
|
20447
|
+
const aGroup = finiteOrder(a.item.groupOrder);
|
|
20448
|
+
const bGroup = finiteOrder(b.item.groupOrder);
|
|
20449
|
+
if (aGroup !== bGroup) return aGroup - bGroup;
|
|
19180
20450
|
if (needle === "") {
|
|
19181
20451
|
const aPinned = pinOrder(a.item.pinned, a.order);
|
|
19182
20452
|
const bPinned = pinOrder(b.item.pinned, b.order);
|
|
19183
20453
|
if (aPinned !== bPinned) return aPinned - bPinned;
|
|
19184
20454
|
}
|
|
19185
|
-
return a.score - b.score || compareLauncherText(a.item.label, b.item.label) || a.order - b.order;
|
|
20455
|
+
return a.score - b.score || finiteOrder(a.item.itemOrder) - finiteOrder(b.item.itemOrder) || compareLauncherText(a.item.label, b.item.label) || a.order - b.order;
|
|
19186
20456
|
});
|
|
19187
20457
|
const maximum = Math.max(0, Number(limit) || 0);
|
|
19188
20458
|
return ranked.slice(0, maximum || 0).map(({ item, score }) => ({ ...item, _score: score }));
|
|
@@ -19206,6 +20476,9 @@ var zx = (() => {
|
|
|
19206
20476
|
if (pinned) return order;
|
|
19207
20477
|
return Number.MAX_SAFE_INTEGER;
|
|
19208
20478
|
}
|
|
20479
|
+
function finiteOrder(value) {
|
|
20480
|
+
return typeof value === "number" && Number.isFinite(value) ? value : Number.MAX_SAFE_INTEGER;
|
|
20481
|
+
}
|
|
19209
20482
|
function searchable(value) {
|
|
19210
20483
|
return String(value ?? "").normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase();
|
|
19211
20484
|
}
|
|
@@ -19220,15 +20493,23 @@ var zx = (() => {
|
|
|
19220
20493
|
}
|
|
19221
20494
|
function normalizeItems2(items) {
|
|
19222
20495
|
if (!Array.isArray(items)) return [];
|
|
19223
|
-
return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) => (
|
|
20496
|
+
return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) => normalizeLauncherItem(item));
|
|
20497
|
+
}
|
|
20498
|
+
function normalizeLauncherItem(item) {
|
|
20499
|
+
const normalized = { ...item, label: String(item.label) };
|
|
20500
|
+
if (item.href == null) return normalized;
|
|
20501
|
+
const href = safeHref(item.href);
|
|
20502
|
+
if (href !== null) normalized.href = href;
|
|
20503
|
+
else {
|
|
20504
|
+
delete normalized.href;
|
|
20505
|
+
if (typeof normalized.invoke !== "function") normalized.disabled = true;
|
|
20506
|
+
}
|
|
20507
|
+
return normalized;
|
|
19224
20508
|
}
|
|
19225
20509
|
function normalizeSources(sources) {
|
|
19226
20510
|
if (!Array.isArray(sources)) return [];
|
|
19227
20511
|
return sources.filter((source) => source && typeof source === "object" && source.id != null && typeof source.load === "function").map((source) => ({ ...source, id: String(source.id) }));
|
|
19228
20512
|
}
|
|
19229
|
-
function orderedLauncherSourceItems(sources, sourceItems) {
|
|
19230
|
-
return sources.flatMap((source) => sourceItems.get(String(source.id)) ?? []);
|
|
19231
|
-
}
|
|
19232
20513
|
|
|
19233
20514
|
// src/components/avatar/avatar.js
|
|
19234
20515
|
var Avatar = class extends Component {
|
|
@@ -19647,7 +20928,7 @@ var zx = (() => {
|
|
|
19647
20928
|
this.#content = this.options.content;
|
|
19648
20929
|
this.#disabled = Boolean(this.options.disabled);
|
|
19649
20930
|
this.#describedBy = resolved.getAttribute("aria-describedby");
|
|
19650
|
-
this.el.style.setProperty("--zx-tooltip-max-inline-size",
|
|
20931
|
+
this.el.style.setProperty("--zx-tooltip-max-inline-size", cssLength3(this.options.maxWidth));
|
|
19651
20932
|
const trigger = TRIGGERS.has(this.options.trigger) ? this.options.trigger : "both";
|
|
19652
20933
|
if (trigger === "manual") return;
|
|
19653
20934
|
if (trigger === "hover" || trigger === "both") {
|
|
@@ -19905,7 +21186,7 @@ var zx = (() => {
|
|
|
19905
21186
|
if (!tokens.includes(token)) tokens.push(token);
|
|
19906
21187
|
return tokens.join(" ");
|
|
19907
21188
|
}
|
|
19908
|
-
function
|
|
21189
|
+
function cssLength3(value) {
|
|
19909
21190
|
return typeof value === "number" && Number.isFinite(value) ? `${value}px` : String(value);
|
|
19910
21191
|
}
|
|
19911
21192
|
function isFocusVisible(element) {
|
|
@@ -19916,10 +21197,10 @@ var zx = (() => {
|
|
|
19916
21197
|
}
|
|
19917
21198
|
}
|
|
19918
21199
|
|
|
19919
|
-
// src/
|
|
19920
|
-
var
|
|
21200
|
+
// src/internal/app-rail.js
|
|
21201
|
+
var AppRailPresenter = class extends Component {
|
|
19921
21202
|
static cssName = "app-rail";
|
|
19922
|
-
/** @type {Readonly<
|
|
21203
|
+
/** @type {Readonly<AppRailPresenterOptions>} */
|
|
19923
21204
|
static defaults = {
|
|
19924
21205
|
items: [],
|
|
19925
21206
|
active: null,
|
|
@@ -19929,12 +21210,13 @@ var zx = (() => {
|
|
|
19929
21210
|
header: null,
|
|
19930
21211
|
footer: null,
|
|
19931
21212
|
openDelay: 80,
|
|
19932
|
-
closeDelay: 160
|
|
21213
|
+
closeDelay: 160,
|
|
21214
|
+
renderIcon: null
|
|
19933
21215
|
};
|
|
19934
21216
|
/**
|
|
19935
21217
|
* Creates or enhances a rail host.
|
|
19936
21218
|
* @param {Element|string|null} target Rail target.
|
|
19937
|
-
* @param {
|
|
21219
|
+
* @param {AppRailPresenterOptions} [options={}] Rail options.
|
|
19938
21220
|
*/
|
|
19939
21221
|
constructor(target = null, options = {}) {
|
|
19940
21222
|
super(target, options);
|
|
@@ -20084,7 +21366,14 @@ var zx = (() => {
|
|
|
20084
21366
|
index: String(item.id),
|
|
20085
21367
|
iconOnly: true,
|
|
20086
21368
|
disclosure: hasChildren,
|
|
20087
|
-
disclosureIcon: railDisclosureIcon(this.el.dataset.side)
|
|
21369
|
+
disclosureIcon: railDisclosureIcon(this.el.dataset.side),
|
|
21370
|
+
visual: appNavIcon(item, this.options.renderIcon, {
|
|
21371
|
+
orientation: this.el.dataset.orientation,
|
|
21372
|
+
collapsed: true,
|
|
21373
|
+
side: this.el.dataset.side,
|
|
21374
|
+
level: 1,
|
|
21375
|
+
location: "root"
|
|
21376
|
+
})
|
|
20088
21377
|
});
|
|
20089
21378
|
const row2 = h("li", { class: "zx-app-rail__row" }, control);
|
|
20090
21379
|
this.refs.list.append(row2);
|
|
@@ -20125,7 +21414,14 @@ var zx = (() => {
|
|
|
20125
21414
|
index: childKey,
|
|
20126
21415
|
iconOnly: false,
|
|
20127
21416
|
disclosure: hasChildren,
|
|
20128
|
-
disclosureIcon: nestedDisclosureIcon(this.el.dataset.side)
|
|
21417
|
+
disclosureIcon: nestedDisclosureIcon(this.el.dataset.side),
|
|
21418
|
+
visual: appNavIcon(child, this.options.renderIcon, {
|
|
21419
|
+
orientation: this.el.dataset.orientation,
|
|
21420
|
+
collapsed: true,
|
|
21421
|
+
side: this.el.dataset.side,
|
|
21422
|
+
level: childKey.split("/").length,
|
|
21423
|
+
location: "flyout"
|
|
21424
|
+
})
|
|
20129
21425
|
});
|
|
20130
21426
|
list.append(h("li", {}, control));
|
|
20131
21427
|
if (hasChildren) this._buildFlyout(child, control, childKey, key);
|
|
@@ -20179,7 +21475,6 @@ var zx = (() => {
|
|
|
20179
21475
|
this._interaction.listen(trigger, "click", (event) => {
|
|
20180
21476
|
event.preventDefault();
|
|
20181
21477
|
if (event instanceof MouseEvent && event.detail === 0) this.openFlyout(key, { focus: true });
|
|
20182
|
-
else if (dropdown.isOpen()) this.closeFlyout(key);
|
|
20183
21478
|
else this.openFlyout(key);
|
|
20184
21479
|
});
|
|
20185
21480
|
this._interaction.listen(actualPanel, "pointerenter", () => this._cancelFlyoutTimers(flyout, true));
|
|
@@ -20434,14 +21729,43 @@ var zx = (() => {
|
|
|
20434
21729
|
};
|
|
20435
21730
|
function normalizeAppItems(items) {
|
|
20436
21731
|
if (!Array.isArray(items)) return [];
|
|
20437
|
-
return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) =>
|
|
20438
|
-
|
|
20439
|
-
|
|
20440
|
-
|
|
20441
|
-
|
|
21732
|
+
return items.filter((item) => item && typeof item === "object" && item.id != null && item.label != null).map((item) => {
|
|
21733
|
+
const normalized = {
|
|
21734
|
+
...item,
|
|
21735
|
+
label: String(item.label),
|
|
21736
|
+
children: Array.isArray(item.children) ? normalizeAppItems(item.children) : []
|
|
21737
|
+
};
|
|
21738
|
+
if (item.href == null) return normalized;
|
|
21739
|
+
const href = safeHref(item.href);
|
|
21740
|
+
if (href !== null) normalized.href = href;
|
|
21741
|
+
else {
|
|
21742
|
+
delete normalized.href;
|
|
21743
|
+
if (normalized.children.length === 0 && typeof normalized.invoke !== "function") {
|
|
21744
|
+
normalized.disabled = true;
|
|
21745
|
+
}
|
|
21746
|
+
}
|
|
21747
|
+
return normalized;
|
|
21748
|
+
});
|
|
20442
21749
|
}
|
|
20443
|
-
function
|
|
20444
|
-
|
|
21750
|
+
function appNavIcon(item, renderer, context) {
|
|
21751
|
+
let visual = typeof renderer === "function" ? renderer(item, context) : null;
|
|
21752
|
+
if (visual == null) visual = item.icon;
|
|
21753
|
+
if (typeof visual === "function") visual = visual(context, item);
|
|
21754
|
+
if (typeof visual === "string") return icon(visual);
|
|
21755
|
+
if (visual instanceof Node) return visual.cloneNode(true);
|
|
21756
|
+
if (visual && typeof visual === "object" && typeof /** @type {any} */
|
|
21757
|
+
visual.toElement === "function") {
|
|
21758
|
+
const element = (
|
|
21759
|
+
/** @type {any} */
|
|
21760
|
+
visual.toElement()
|
|
21761
|
+
);
|
|
21762
|
+
return element instanceof Node ? element.cloneNode(true) : null;
|
|
21763
|
+
}
|
|
21764
|
+
return null;
|
|
21765
|
+
}
|
|
21766
|
+
function navControl(item, { className, index, iconOnly, disclosure, disclosureIcon, visual }) {
|
|
21767
|
+
const href = safeHref(item.href);
|
|
21768
|
+
const tag = href && !disclosure ? "a" : "button";
|
|
20445
21769
|
return (
|
|
20446
21770
|
/** @type {HTMLElement} */
|
|
20447
21771
|
h(
|
|
@@ -20449,13 +21773,14 @@ var zx = (() => {
|
|
|
20449
21773
|
{
|
|
20450
21774
|
class: className,
|
|
20451
21775
|
type: tag === "button" ? "button" : null,
|
|
20452
|
-
href: tag === "a" ?
|
|
21776
|
+
href: tag === "a" ? href : null,
|
|
20453
21777
|
target: tag === "a" ? item.target ?? null : null,
|
|
21778
|
+
rel: tag === "a" ? "noopener" : null,
|
|
20454
21779
|
ariaLabel: iconOnly ? item.label : null,
|
|
20455
21780
|
ariaDisabled: item.disabled ? "true" : null,
|
|
20456
21781
|
dataset: { appNavId: index, appNavValueId: String(item.id) }
|
|
20457
21782
|
},
|
|
20458
|
-
|
|
21783
|
+
visual ? h("span", { class: `${className}-icon`, ariaHidden: "true" }, visual) : null,
|
|
20459
21784
|
h("span", { class: `${className}-label` }, item.label),
|
|
20460
21785
|
item.badge != null ? h("span", { class: `${className}-badge` }, String(item.badge)) : null,
|
|
20461
21786
|
disclosure ? h("span", { class: `${className}-disclosure`, ariaHidden: "true" }, icon(disclosureIcon)) : null
|
|
@@ -20519,47 +21844,28 @@ var zx = (() => {
|
|
|
20519
21844
|
active: null,
|
|
20520
21845
|
expanded: [],
|
|
20521
21846
|
collapsed: false,
|
|
21847
|
+
collapsible: true,
|
|
21848
|
+
orientation: "vertical",
|
|
20522
21849
|
side: "left",
|
|
20523
21850
|
label: "Applications",
|
|
20524
21851
|
header: null,
|
|
20525
21852
|
footer: null,
|
|
20526
21853
|
railHeader: null,
|
|
20527
|
-
railFooter: null
|
|
21854
|
+
railFooter: null,
|
|
21855
|
+
openDelay: 80,
|
|
21856
|
+
closeDelay: 160,
|
|
21857
|
+
renderIcon: null
|
|
20528
21858
|
};
|
|
20529
21859
|
/**
|
|
20530
|
-
* Creates or enhances an application-
|
|
20531
|
-
* @param {Element|string|null} target
|
|
20532
|
-
* @param {AppSidebarOptions} [options={}]
|
|
21860
|
+
* Creates or enhances an application-navigation host.
|
|
21861
|
+
* @param {Element|string|null} [target=null] Navigation target.
|
|
21862
|
+
* @param {AppSidebarOptions} [options={}] Navigation options.
|
|
20533
21863
|
*/
|
|
20534
21864
|
constructor(target = null, options = {}) {
|
|
20535
21865
|
super(target, options);
|
|
20536
|
-
this.
|
|
20537
|
-
|
|
20538
|
-
|
|
20539
|
-
type: "button",
|
|
20540
|
-
ariaLabel: "Expand application sidebar"
|
|
20541
|
-
}, icon(this.el.dataset.side === "right" ? "chevron-left" : "chevron-right"));
|
|
20542
|
-
this._railExpandButton = expandButton;
|
|
20543
|
-
const railHeader = h("div", { class: "zx-app-sidebar__rail-header" }, expandButton);
|
|
20544
|
-
appendSlot2(railHeader, this.options.railHeader);
|
|
20545
|
-
this._rail = new AppRail(this.refs.rail, {
|
|
20546
|
-
items: this._items,
|
|
20547
|
-
active: this._active,
|
|
20548
|
-
orientation: "vertical",
|
|
20549
|
-
side: this.el.dataset.side,
|
|
20550
|
-
label: this.options.label,
|
|
20551
|
-
header: railHeader,
|
|
20552
|
-
footer: this.options.railFooter,
|
|
20553
|
-
onselect: (event) => {
|
|
20554
|
-
const selected = this.emit("select", event.detail);
|
|
20555
|
-
if (selected.defaultPrevented) event.preventDefault();
|
|
20556
|
-
}
|
|
20557
|
-
});
|
|
20558
|
-
this.listen(expandButton, "click", () => {
|
|
20559
|
-
this.expand();
|
|
20560
|
-
});
|
|
20561
|
-
this._renderTree();
|
|
20562
|
-
this.setCollapsed(Boolean(this.options.collapsed), { silent: true });
|
|
21866
|
+
this._rail = null;
|
|
21867
|
+
this._railExpandButton = null;
|
|
21868
|
+
this._renderMode();
|
|
20563
21869
|
}
|
|
20564
21870
|
/** @returns {HTMLElement} */
|
|
20565
21871
|
render() {
|
|
@@ -20577,57 +21883,39 @@ var zx = (() => {
|
|
|
20577
21883
|
this._active = this.options.active;
|
|
20578
21884
|
this._expanded = new Set(Array.isArray(this.options.expanded) ? this.options.expanded.map(String) : []);
|
|
20579
21885
|
this._collapsed = Boolean(this.options.collapsed);
|
|
20580
|
-
|
|
20581
|
-
|
|
20582
|
-
|
|
20583
|
-
|
|
20584
|
-
ref: "collapse",
|
|
20585
|
-
class: "zx-app-sidebar__collapse zx-icon-btn",
|
|
20586
|
-
type: "button",
|
|
20587
|
-
ariaLabel: "Minimize application sidebar"
|
|
20588
|
-
}, icon(root.dataset.side === "right" ? "chevron-right" : "chevron-left"));
|
|
20589
|
-
const footer = h("footer", { class: "zx-app-sidebar__footer" });
|
|
20590
|
-
appendSlot2(footer, this.options.footer);
|
|
20591
|
-
footer.hidden = !footer.hasChildNodes();
|
|
20592
|
-
const expanded = h(
|
|
20593
|
-
"div",
|
|
20594
|
-
{ ref: "expanded", class: "zx-app-sidebar__expanded" },
|
|
20595
|
-
h("header", { class: "zx-app-sidebar__header" }, headerContent, collapseButton),
|
|
20596
|
-
h(
|
|
20597
|
-
"nav",
|
|
20598
|
-
{ class: "zx-app-sidebar__nav", ariaLabel: this.options.label },
|
|
20599
|
-
h("ul", { ref: "list", class: "zx-app-sidebar__list" })
|
|
20600
|
-
),
|
|
20601
|
-
footer
|
|
20602
|
-
);
|
|
20603
|
-
const rail = h("div", { ref: "rail", class: "zx-app-sidebar__rail" });
|
|
20604
|
-
root.replaceChildren(expanded, rail);
|
|
20605
|
-
this.listen(collapseButton, "click", () => {
|
|
20606
|
-
this.collapse();
|
|
20607
|
-
});
|
|
20608
|
-
this.listen(this.refs.list, "click", (event) => this._onTreeClick(event));
|
|
21886
|
+
this._orientation = this.options.orientation === "horizontal" ? "horizontal" : "vertical";
|
|
21887
|
+
this._side = normalizeSide(this._orientation, this.options.side);
|
|
21888
|
+
this._modeAbort = null;
|
|
21889
|
+
root.replaceChildren();
|
|
20609
21890
|
return root;
|
|
20610
21891
|
}
|
|
20611
|
-
/** Replaces the navigation tree in
|
|
21892
|
+
/** Replaces the navigation tree in the active presentation. @param {AppNavItem[]} items @returns {this} */
|
|
20612
21893
|
setItems(items) {
|
|
20613
21894
|
this._items = normalizeAppItems(items);
|
|
20614
21895
|
this._reconcileExpanded();
|
|
20615
|
-
this.
|
|
20616
|
-
this.
|
|
21896
|
+
if (this._rail) this._rail.setItems(this._items);
|
|
21897
|
+
else this._renderTree();
|
|
20617
21898
|
return this;
|
|
20618
21899
|
}
|
|
20619
|
-
/**
|
|
21900
|
+
/** Returns a shallow copy of normalized items. @returns {AppNavItem[]} */
|
|
21901
|
+
getItems() {
|
|
21902
|
+
return this._items.map((item) => ({
|
|
21903
|
+
...item,
|
|
21904
|
+
children: item.children?.map((child) => ({ ...child }))
|
|
21905
|
+
}));
|
|
21906
|
+
}
|
|
21907
|
+
/** Sets the active id without routing. @param {string|number|null} id @returns {this} */
|
|
20620
21908
|
setActive(id) {
|
|
20621
21909
|
this._active = id;
|
|
20622
|
-
this.
|
|
20623
|
-
this.
|
|
21910
|
+
if (this._rail) this._rail.setActive(id);
|
|
21911
|
+
else this._syncActive();
|
|
20624
21912
|
return this;
|
|
20625
21913
|
}
|
|
20626
21914
|
/** Replaces expanded branch ids. @param {Array<string|number>} ids @param {{silent?: boolean}} [options={}] @returns {this} */
|
|
20627
21915
|
setExpanded(ids, { silent = false } = {}) {
|
|
20628
21916
|
this._expanded = new Set(Array.isArray(ids) ? ids.map(String) : []);
|
|
20629
21917
|
this._reconcileExpanded();
|
|
20630
|
-
this._renderTree();
|
|
21918
|
+
if (!this._rail) this._renderTree();
|
|
20631
21919
|
if (!silent) this.emit("branchchange", {
|
|
20632
21920
|
item: null,
|
|
20633
21921
|
id: null,
|
|
@@ -20652,47 +21940,105 @@ var zx = (() => {
|
|
|
20652
21940
|
const expanded = !this._expanded.has(key);
|
|
20653
21941
|
if (expanded) this._expanded.add(key);
|
|
20654
21942
|
else this._expanded.delete(key);
|
|
20655
|
-
this._renderTree();
|
|
20656
|
-
if (!silent) this.emit("branchchange", {
|
|
21943
|
+
if (!this._rail) this._renderTree();
|
|
21944
|
+
if (!silent) this.emit("branchchange", {
|
|
21945
|
+
item,
|
|
21946
|
+
id: item.id,
|
|
21947
|
+
expanded,
|
|
21948
|
+
ids: this.getExpanded()
|
|
21949
|
+
});
|
|
20657
21950
|
return this;
|
|
20658
21951
|
}
|
|
20659
|
-
/** Shows the expanded
|
|
21952
|
+
/** Shows the expanded vertical presentation. @param {{silent?: boolean}} [options={}] @returns {this} */
|
|
20660
21953
|
expand(options = {}) {
|
|
20661
21954
|
return this.setCollapsed(false, options);
|
|
20662
21955
|
}
|
|
20663
|
-
/** Shows the minimized
|
|
21956
|
+
/** Shows the minimized presentation. @param {{silent?: boolean}} [options={}] @returns {this} */
|
|
20664
21957
|
collapse(options = {}) {
|
|
20665
21958
|
return this.setCollapsed(true, options);
|
|
20666
21959
|
}
|
|
20667
|
-
/** Sets expanded/minimized
|
|
21960
|
+
/** Sets the saved vertical expanded/minimized state. @param {boolean} collapsed @param {{silent?: boolean}} [options={}] @returns {this} */
|
|
20668
21961
|
setCollapsed(collapsed, { silent = false } = {}) {
|
|
20669
|
-
const
|
|
20670
|
-
const changed = next !== this._collapsed;
|
|
21962
|
+
const previous = this.isCollapsed();
|
|
20671
21963
|
const active = document.activeElement;
|
|
20672
|
-
const
|
|
20673
|
-
|
|
20674
|
-
|
|
20675
|
-
|
|
20676
|
-
|
|
20677
|
-
|
|
20678
|
-
|
|
20679
|
-
|
|
20680
|
-
|
|
20681
|
-
|
|
20682
|
-
|
|
20683
|
-
|
|
20684
|
-
|
|
21964
|
+
const outgoingOwnedFocus = Boolean(active && (this.el.contains(active) || this._rail?._containsNode(active)));
|
|
21965
|
+
this._collapsed = Boolean(collapsed);
|
|
21966
|
+
const next = this.isCollapsed();
|
|
21967
|
+
if (previous !== next) {
|
|
21968
|
+
this._renderMode();
|
|
21969
|
+
if (outgoingOwnedFocus) {
|
|
21970
|
+
if (next) this._railExpandButton?.focus();
|
|
21971
|
+
else this.refs.collapse?.focus();
|
|
21972
|
+
}
|
|
21973
|
+
if (!silent) this.emit("collapsechange", { collapsed: next });
|
|
21974
|
+
}
|
|
21975
|
+
return this;
|
|
21976
|
+
}
|
|
21977
|
+
/** Reports the effective presentation state; horizontal navigation is always minimized. @returns {boolean} */
|
|
20685
21978
|
isCollapsed() {
|
|
20686
|
-
return this._collapsed;
|
|
21979
|
+
return this._orientation === "horizontal" || this._collapsed;
|
|
21980
|
+
}
|
|
21981
|
+
/**
|
|
21982
|
+
* Changes navigation flow/edge without replacing state.
|
|
21983
|
+
* @param {{orientation?: 'vertical'|'horizontal', side?: 'left'|'right'|'top'|'bottom'}} layout
|
|
21984
|
+
* @param {{silent?: boolean}} [options]
|
|
21985
|
+
* @returns {this}
|
|
21986
|
+
*/
|
|
21987
|
+
setLayout(layout = {}, { silent = false } = {}) {
|
|
21988
|
+
const orientation = layout.orientation === "horizontal" ? "horizontal" : layout.orientation === "vertical" ? "vertical" : this._orientation;
|
|
21989
|
+
const side = normalizeSide(orientation, layout.side ?? this._side);
|
|
21990
|
+
if (orientation === this._orientation && side === this._side) return this;
|
|
21991
|
+
const previous = this.isCollapsed();
|
|
21992
|
+
const active = document.activeElement;
|
|
21993
|
+
const outgoingOwnedFocus = Boolean(active && (this.el.contains(active) || this._rail?._containsNode(active)));
|
|
21994
|
+
const focusedId = outgoingOwnedFocus && active instanceof Element ? active.closest("[data-app-nav-id]")?.getAttribute("data-app-nav-id") ?? null : null;
|
|
21995
|
+
this._orientation = orientation;
|
|
21996
|
+
this._side = side;
|
|
21997
|
+
this._renderMode();
|
|
21998
|
+
const next = this.isCollapsed();
|
|
21999
|
+
if (outgoingOwnedFocus) {
|
|
22000
|
+
const replacement = focusedId === null ? null : [...this.el.querySelectorAll("[data-app-nav-id]")].find((control) => control.getAttribute("data-app-nav-id") === focusedId) ?? null;
|
|
22001
|
+
if (replacement instanceof HTMLElement) replacement.focus();
|
|
22002
|
+
else if (next) this.focus();
|
|
22003
|
+
else if (this.refs.collapse instanceof HTMLElement && !this.refs.collapse.hidden) {
|
|
22004
|
+
this.refs.collapse.focus();
|
|
22005
|
+
} else visibleTreeControls(this.refs.list)[0]?.focus();
|
|
22006
|
+
}
|
|
22007
|
+
if (previous !== next && !silent) this.emit("collapsechange", { collapsed: next });
|
|
22008
|
+
return this;
|
|
20687
22009
|
}
|
|
20688
|
-
/**
|
|
20689
|
-
|
|
20690
|
-
|
|
22010
|
+
/** Opens a minimized descendant flyout. @param {string|number} id @param {{focus?: boolean}} [options={}] @returns {this} */
|
|
22011
|
+
openFlyout(id, options = {}) {
|
|
22012
|
+
this._rail?.openFlyout(id, options);
|
|
22013
|
+
return this;
|
|
22014
|
+
}
|
|
22015
|
+
/** Closes a minimized descendant flyout. @param {string|number} id @returns {this} */
|
|
22016
|
+
closeFlyout(id) {
|
|
22017
|
+
this._rail?.closeFlyout(id);
|
|
22018
|
+
return this;
|
|
20691
22019
|
}
|
|
20692
|
-
/**
|
|
22020
|
+
/** Closes every minimized flyout. @returns {this} */
|
|
22021
|
+
closeAllFlyouts() {
|
|
22022
|
+
this._rail?.closeAllFlyouts();
|
|
22023
|
+
return this;
|
|
22024
|
+
}
|
|
22025
|
+
/** Reports whether a minimized flyout is open. @param {string|number} id @returns {boolean} */
|
|
22026
|
+
isFlyoutOpen(id) {
|
|
22027
|
+
return this._rail?.isFlyoutOpen(id) ?? false;
|
|
22028
|
+
}
|
|
22029
|
+
/** Focuses the first navigation action. @returns {this} */
|
|
22030
|
+
focus() {
|
|
22031
|
+
if (this._rail) this._rail.focus();
|
|
22032
|
+
else visibleTreeControls(this.refs.list)[0]?.focus();
|
|
22033
|
+
return this;
|
|
22034
|
+
}
|
|
22035
|
+
/** Destroys flyouts and restores/removes the host. @returns {void} */
|
|
20693
22036
|
destroy() {
|
|
20694
22037
|
const original = this._original;
|
|
20695
|
-
this.
|
|
22038
|
+
this._modeAbort?.abort();
|
|
22039
|
+
this._modeAbort = null;
|
|
22040
|
+
this._rail?.destroy();
|
|
22041
|
+
this._rail = null;
|
|
20696
22042
|
super.destroy();
|
|
20697
22043
|
if (!this._createdRoot && original) {
|
|
20698
22044
|
for (const attribute of Array.from(this.el.attributes)) this.el.removeAttribute(attribute.name);
|
|
@@ -20700,8 +22046,99 @@ var zx = (() => {
|
|
|
20700
22046
|
this.el.replaceChildren(...original.children);
|
|
20701
22047
|
}
|
|
20702
22048
|
}
|
|
22049
|
+
/** Mounts exactly one expanded tree or minimized flyout presentation. @returns {void} */
|
|
22050
|
+
_renderMode() {
|
|
22051
|
+
this._modeAbort?.abort();
|
|
22052
|
+
this._modeAbort = new AbortController();
|
|
22053
|
+
this._rail?.destroy();
|
|
22054
|
+
this._rail = null;
|
|
22055
|
+
this._railExpandButton = null;
|
|
22056
|
+
for (const key of ["expanded", "list", "collapse", "rail"]) delete this.refs[key];
|
|
22057
|
+
this.el.replaceChildren();
|
|
22058
|
+
this.el.dataset.orientation = this._orientation;
|
|
22059
|
+
this.el.dataset.side = this._side;
|
|
22060
|
+
this.el.dataset.collapsed = String(this.isCollapsed());
|
|
22061
|
+
if (this.isCollapsed()) this._renderRail();
|
|
22062
|
+
else this._renderExpanded();
|
|
22063
|
+
}
|
|
22064
|
+
/** @returns {void} */
|
|
22065
|
+
_renderExpanded() {
|
|
22066
|
+
const headerContent = h("div", { class: "zx-app-sidebar__header-content" });
|
|
22067
|
+
appendSlot2(headerContent, this.options.header);
|
|
22068
|
+
const collapseButton = h("button", {
|
|
22069
|
+
class: "zx-app-sidebar__collapse zx-icon-btn",
|
|
22070
|
+
type: "button",
|
|
22071
|
+
ariaLabel: "Minimize application sidebar"
|
|
22072
|
+
}, icon(this._side === "right" ? "chevron-right" : "chevron-left"));
|
|
22073
|
+
collapseButton.hidden = !this.options.collapsible;
|
|
22074
|
+
const footer = h("footer", { class: "zx-app-sidebar__footer" });
|
|
22075
|
+
appendSlot2(footer, this.options.footer);
|
|
22076
|
+
footer.hidden = !footer.hasChildNodes();
|
|
22077
|
+
const list = h("ul", { class: "zx-app-sidebar__list" });
|
|
22078
|
+
const expanded = h(
|
|
22079
|
+
"div",
|
|
22080
|
+
{ class: "zx-app-sidebar__expanded" },
|
|
22081
|
+
h("header", { class: "zx-app-sidebar__header" }, headerContent, collapseButton),
|
|
22082
|
+
h("nav", { class: "zx-app-sidebar__nav", ariaLabel: this.options.label }, list),
|
|
22083
|
+
footer
|
|
22084
|
+
);
|
|
22085
|
+
this.refs.expanded = expanded;
|
|
22086
|
+
this.refs.list = list;
|
|
22087
|
+
this.refs.collapse = collapseButton;
|
|
22088
|
+
this.el.append(expanded);
|
|
22089
|
+
this._listenMode(collapseButton, "click", () => this.collapse());
|
|
22090
|
+
this._listenMode(list, "click", (event) => this._onTreeClick(event));
|
|
22091
|
+
this._listenMode(list, "keydown", (event) => this._onTreeKeydown(
|
|
22092
|
+
/** @type {KeyboardEvent} */
|
|
22093
|
+
event
|
|
22094
|
+
));
|
|
22095
|
+
this._renderTree();
|
|
22096
|
+
}
|
|
22097
|
+
/** @returns {void} */
|
|
22098
|
+
_renderRail() {
|
|
22099
|
+
const railHost = h("nav", { class: "zx-app-sidebar__rail" });
|
|
22100
|
+
this.el.append(railHost);
|
|
22101
|
+
this.refs.rail = railHost;
|
|
22102
|
+
const showToggle = this._orientation === "vertical" && Boolean(this.options.collapsible);
|
|
22103
|
+
const railHeader = h("div", { class: "zx-app-sidebar__rail-header" });
|
|
22104
|
+
if (showToggle) {
|
|
22105
|
+
const expandButton = h("button", {
|
|
22106
|
+
class: "zx-app-sidebar__rail-toggle zx-icon-btn",
|
|
22107
|
+
type: "button",
|
|
22108
|
+
ariaLabel: "Expand application sidebar"
|
|
22109
|
+
}, icon(this._side === "right" ? "chevron-left" : "chevron-right"));
|
|
22110
|
+
railHeader.append(expandButton);
|
|
22111
|
+
this._railExpandButton = expandButton;
|
|
22112
|
+
this._listenMode(expandButton, "click", () => this.expand());
|
|
22113
|
+
}
|
|
22114
|
+
appendSlot2(railHeader, this.options.railHeader);
|
|
22115
|
+
this._rail = new AppRailPresenter(railHost, {
|
|
22116
|
+
items: this._items,
|
|
22117
|
+
active: this._active,
|
|
22118
|
+
orientation: this._orientation,
|
|
22119
|
+
side: this._side,
|
|
22120
|
+
label: this.options.label,
|
|
22121
|
+
header: railHeader.hasChildNodes() ? railHeader : null,
|
|
22122
|
+
footer: this.options.railFooter,
|
|
22123
|
+
openDelay: this.options.openDelay,
|
|
22124
|
+
closeDelay: this.options.closeDelay,
|
|
22125
|
+
renderIcon: this.options.renderIcon,
|
|
22126
|
+
onselect: (event) => {
|
|
22127
|
+
const selected = this.emit("select", event.detail);
|
|
22128
|
+
if (selected.defaultPrevented) event.preventDefault();
|
|
22129
|
+
},
|
|
22130
|
+
onflyoutchange: (event) => this.emit("flyoutchange", event.detail)
|
|
22131
|
+
});
|
|
22132
|
+
this._listenMode(railHost, "zx-select", (event) => event.stopPropagation());
|
|
22133
|
+
this._listenMode(railHost, "zx-flyoutchange", (event) => event.stopPropagation());
|
|
22134
|
+
}
|
|
22135
|
+
/** @param {EventTarget} target @param {string} type @param {EventListener} listener @returns {void} */
|
|
22136
|
+
_listenMode(target, type, listener) {
|
|
22137
|
+
target.addEventListener(type, listener, { signal: this._modeAbort.signal });
|
|
22138
|
+
}
|
|
20703
22139
|
/** @returns {void} */
|
|
20704
22140
|
_renderTree() {
|
|
22141
|
+
if (!this.refs.list) return;
|
|
20705
22142
|
const focused = this.refs.list.contains(document.activeElement) ? (
|
|
20706
22143
|
/** @type {Element} */
|
|
20707
22144
|
document.activeElement.closest("[data-app-nav-id]")?.getAttribute("data-app-nav-id")
|
|
@@ -20720,7 +22157,14 @@ var zx = (() => {
|
|
|
20720
22157
|
const key = String(item.id);
|
|
20721
22158
|
const expanded = hasChildren && this._expanded.has(key);
|
|
20722
22159
|
const childId = hasChildren ? uid("zx-app-sidebar-branch") : null;
|
|
20723
|
-
const
|
|
22160
|
+
const visual = appNavIcon(item, this.options.renderIcon, {
|
|
22161
|
+
orientation: this._orientation,
|
|
22162
|
+
collapsed: false,
|
|
22163
|
+
side: this._side,
|
|
22164
|
+
level,
|
|
22165
|
+
location: level === 1 ? "root" : "inline"
|
|
22166
|
+
});
|
|
22167
|
+
const control = navControl2(item, hasChildren, expanded, childId, visual);
|
|
20724
22168
|
const row2 = h("li", {
|
|
20725
22169
|
class: "zx-app-sidebar__row",
|
|
20726
22170
|
dataset: { level }
|
|
@@ -20734,7 +22178,7 @@ var zx = (() => {
|
|
|
20734
22178
|
}
|
|
20735
22179
|
return row2;
|
|
20736
22180
|
}
|
|
20737
|
-
/**
|
|
22181
|
+
/** @param {Event} event @returns {void} */
|
|
20738
22182
|
_onTreeClick(event) {
|
|
20739
22183
|
const control = (
|
|
20740
22184
|
/** @type {Element|null} */
|
|
@@ -20749,6 +22193,45 @@ var zx = (() => {
|
|
|
20749
22193
|
if (item.children?.length) this.toggleBranch(item.id);
|
|
20750
22194
|
else this._select(item, event);
|
|
20751
22195
|
}
|
|
22196
|
+
/** @param {KeyboardEvent} event @returns {void} */
|
|
22197
|
+
_onTreeKeydown(event) {
|
|
22198
|
+
const current = (
|
|
22199
|
+
/** @type {Element|null} */
|
|
22200
|
+
event.target?.closest?.("[data-app-nav-id]")
|
|
22201
|
+
);
|
|
22202
|
+
if (!(current instanceof HTMLElement)) return;
|
|
22203
|
+
const item = findItem(this._items, current.getAttribute("data-app-nav-id"));
|
|
22204
|
+
const controls = visibleTreeControls(this.refs.list);
|
|
22205
|
+
const index = controls.indexOf(current);
|
|
22206
|
+
if (event.key === "ArrowRight" && item?.children?.length) {
|
|
22207
|
+
event.preventDefault();
|
|
22208
|
+
if (!this._expanded.has(String(item.id))) this.toggleBranch(item.id);
|
|
22209
|
+
else visibleTreeControls(current.parentElement?.querySelector(".zx-app-sidebar__sublist"))[0]?.focus();
|
|
22210
|
+
return;
|
|
22211
|
+
}
|
|
22212
|
+
if (event.key === "ArrowLeft") {
|
|
22213
|
+
if (item?.children?.length && this._expanded.has(String(item.id))) {
|
|
22214
|
+
event.preventDefault();
|
|
22215
|
+
this.toggleBranch(item.id);
|
|
22216
|
+
return;
|
|
22217
|
+
}
|
|
22218
|
+
const parentList = current.closest(".zx-app-sidebar__sublist");
|
|
22219
|
+
const parentControl = parentList?.parentElement?.querySelector(":scope > [data-app-nav-id]");
|
|
22220
|
+
if (parentControl instanceof HTMLElement) {
|
|
22221
|
+
event.preventDefault();
|
|
22222
|
+
parentControl.focus();
|
|
22223
|
+
}
|
|
22224
|
+
return;
|
|
22225
|
+
}
|
|
22226
|
+
let nextIndex;
|
|
22227
|
+
if (event.key === "Home") nextIndex = 0;
|
|
22228
|
+
else if (event.key === "End") nextIndex = controls.length - 1;
|
|
22229
|
+
else if (event.key === "ArrowDown") nextIndex = Math.min(controls.length - 1, index + 1);
|
|
22230
|
+
else if (event.key === "ArrowUp") nextIndex = Math.max(0, index - 1);
|
|
22231
|
+
else return;
|
|
22232
|
+
event.preventDefault();
|
|
22233
|
+
controls[nextIndex]?.focus();
|
|
22234
|
+
}
|
|
20752
22235
|
/** @param {AppNavItem} item @param {Event} event @returns {void} */
|
|
20753
22236
|
_select(item, event) {
|
|
20754
22237
|
if (item.disabled) {
|
|
@@ -20770,14 +22253,14 @@ var zx = (() => {
|
|
|
20770
22253
|
}
|
|
20771
22254
|
/** @returns {void} */
|
|
20772
22255
|
_syncActive() {
|
|
22256
|
+
if (!this.refs.list) return;
|
|
20773
22257
|
const activeId = this._active == null ? null : String(this._active);
|
|
20774
22258
|
for (const control of this.refs.list.querySelectorAll("[data-app-nav-id]")) {
|
|
20775
22259
|
const item = findItem(this._items, control.getAttribute("data-app-nav-id"));
|
|
20776
22260
|
const direct = activeId !== null && control.getAttribute("data-app-nav-id") === activeId;
|
|
20777
22261
|
const descendant = activeId !== null && item ? containsItemId2(item.children ?? [], activeId) : false;
|
|
20778
|
-
if (direct)
|
|
20779
|
-
|
|
20780
|
-
} else control.removeAttribute("aria-current");
|
|
22262
|
+
if (direct) control.setAttribute("aria-current", "page");
|
|
22263
|
+
else control.removeAttribute("aria-current");
|
|
20781
22264
|
if (descendant) control.dataset.activeDescendant = "true";
|
|
20782
22265
|
else delete control.dataset.activeDescendant;
|
|
20783
22266
|
}
|
|
@@ -20791,7 +22274,7 @@ var zx = (() => {
|
|
|
20791
22274
|
for (const id of this._expanded) if (!branchIds.has(id)) this._expanded.delete(id);
|
|
20792
22275
|
}
|
|
20793
22276
|
};
|
|
20794
|
-
function navControl2(item, disclosure, expanded, controls) {
|
|
22277
|
+
function navControl2(item, disclosure, expanded, controls, visual) {
|
|
20795
22278
|
const tag = item.href && !disclosure ? "a" : "button";
|
|
20796
22279
|
return (
|
|
20797
22280
|
/** @type {HTMLElement} */
|
|
@@ -20807,7 +22290,7 @@ var zx = (() => {
|
|
|
20807
22290
|
ariaControls: controls,
|
|
20808
22291
|
dataset: { appNavId: String(item.id) }
|
|
20809
22292
|
},
|
|
20810
|
-
|
|
22293
|
+
visual ? h("span", { class: "zx-app-sidebar__item-icon", ariaHidden: "true" }, visual) : null,
|
|
20811
22294
|
h("span", { class: "zx-app-sidebar__item-label" }, item.label),
|
|
20812
22295
|
item.badge != null ? h("span", { class: "zx-app-sidebar__item-badge" }, String(item.badge)) : null,
|
|
20813
22296
|
disclosure ? h(
|
|
@@ -20818,6 +22301,13 @@ var zx = (() => {
|
|
|
20818
22301
|
)
|
|
20819
22302
|
);
|
|
20820
22303
|
}
|
|
22304
|
+
function visibleTreeControls(root) {
|
|
22305
|
+
if (!root) return [];
|
|
22306
|
+
return (
|
|
22307
|
+
/** @type {HTMLElement[]} */
|
|
22308
|
+
[...root.querySelectorAll("[data-app-nav-id]")].filter((control) => !control.closest(".zx-app-sidebar__sublist[hidden]"))
|
|
22309
|
+
);
|
|
22310
|
+
}
|
|
20821
22311
|
function visibleTreeControl(control) {
|
|
20822
22312
|
let candidate = (
|
|
20823
22313
|
/** @type {HTMLElement|null} */
|
|
@@ -20850,12 +22340,17 @@ var zx = (() => {
|
|
|
20850
22340
|
}
|
|
20851
22341
|
function appendSlot2(target, content) {
|
|
20852
22342
|
if (content == null) return;
|
|
20853
|
-
if (typeof content === "string" || typeof content === "number")
|
|
20854
|
-
|
|
22343
|
+
if (typeof content === "string" || typeof content === "number") {
|
|
22344
|
+
target.append(document.createTextNode(String(content)));
|
|
22345
|
+
} else if (typeof content === "object" && typeof content.toElement === "function") {
|
|
20855
22346
|
const element = content.toElement();
|
|
20856
22347
|
if (element) target.append(element);
|
|
20857
22348
|
} else if (typeof content === "object" && typeof content.nodeType === "number") target.append(content);
|
|
20858
22349
|
}
|
|
22350
|
+
function normalizeSide(orientation, side) {
|
|
22351
|
+
if (orientation === "horizontal") return side === "bottom" ? "bottom" : "top";
|
|
22352
|
+
return side === "right" ? "right" : "left";
|
|
22353
|
+
}
|
|
20859
22354
|
|
|
20860
22355
|
// src/components/message/message.js
|
|
20861
22356
|
var MessageQueue = class {
|
|
@@ -22192,7 +23687,7 @@ var zx = (() => {
|
|
|
22192
23687
|
* @param {GridOptions & import('../table/table.js').TableOptions} [options={}] Grid/Table options.
|
|
22193
23688
|
*/
|
|
22194
23689
|
constructor(target = null, options = {}) {
|
|
22195
|
-
super(target, options);
|
|
23690
|
+
super(target, { editTrigger: "single", ...options });
|
|
22196
23691
|
}
|
|
22197
23692
|
/**
|
|
22198
23693
|
* Creates an editable hierarchical billing-line grid.
|
|
@@ -22223,14 +23718,17 @@ var zx = (() => {
|
|
|
22223
23718
|
} = options;
|
|
22224
23719
|
const fields = { ...DEFAULT_FIELDS, ...requestedFields };
|
|
22225
23720
|
const calculateLineTotal = typeof lineTotal === "function" ? lineTotal : (row2) => {
|
|
22226
|
-
const quantity =
|
|
22227
|
-
const price =
|
|
22228
|
-
return
|
|
23721
|
+
const quantity = billingNumber(row2?.[fields.quantity]);
|
|
23722
|
+
const price = billingNumber(row2?.[fields.unitPrice]);
|
|
23723
|
+
return quantity !== null && price !== null ? quantity * price : null;
|
|
22229
23724
|
};
|
|
22230
23725
|
const columns = Array.isArray(suppliedColumns) ? suppliedColumns.map((column) => ({ ...column })) : billingColumns(fields, { units, currencies, currency, locale, decimals, columnOverrides });
|
|
22231
23726
|
return {
|
|
22232
23727
|
rowId: fields.id,
|
|
22233
23728
|
editMode: "cell",
|
|
23729
|
+
editTrigger: "single",
|
|
23730
|
+
rowReorder: true,
|
|
23731
|
+
columnVisibility: true,
|
|
22234
23732
|
hierarchy: { parentId: fields.parent, column: fields.item, expanded: true },
|
|
22235
23733
|
responsive: "sm",
|
|
22236
23734
|
...tableOptions,
|
|
@@ -22330,6 +23828,11 @@ var zx = (() => {
|
|
|
22330
23828
|
function hasChoices(value) {
|
|
22331
23829
|
return Array.isArray(value) ? value.length > 0 : Boolean(value && typeof value === "object" && Object.keys(value).length);
|
|
22332
23830
|
}
|
|
23831
|
+
function billingNumber(value) {
|
|
23832
|
+
if (value == null || typeof value === "string" && value.trim() === "") return null;
|
|
23833
|
+
const number2 = Number(value);
|
|
23834
|
+
return Number.isFinite(number2) ? number2 : null;
|
|
23835
|
+
}
|
|
22333
23836
|
|
|
22334
23837
|
// src/components/chart/chart.js
|
|
22335
23838
|
var Chart = class extends Component {
|
|
@@ -22377,6 +23880,7 @@ var zx = (() => {
|
|
|
22377
23880
|
this._chartOptions = cloneChartValue(this.options.chartOptions);
|
|
22378
23881
|
this._loading = Boolean(this.options.loading);
|
|
22379
23882
|
this._error = null;
|
|
23883
|
+
this._hasError = false;
|
|
22380
23884
|
this._handle = null;
|
|
22381
23885
|
this._destroyed = false;
|
|
22382
23886
|
const descriptionId = uid("zx-chart-description");
|
|
@@ -22422,6 +23926,7 @@ var zx = (() => {
|
|
|
22422
23926
|
setData(data) {
|
|
22423
23927
|
this._data = normalizeChartData(data);
|
|
22424
23928
|
this._error = null;
|
|
23929
|
+
this._hasError = false;
|
|
22425
23930
|
this._loading = false;
|
|
22426
23931
|
this._renderSummary();
|
|
22427
23932
|
this._mountOrUpdate(true);
|
|
@@ -22455,8 +23960,9 @@ var zx = (() => {
|
|
|
22455
23960
|
return this;
|
|
22456
23961
|
}
|
|
22457
23962
|
/** Shows an error and destroys the current engine. @param {unknown} error @returns {this} */
|
|
22458
|
-
setError(
|
|
22459
|
-
this._error =
|
|
23963
|
+
setError(error2) {
|
|
23964
|
+
this._error = error2 ?? new Error(String(this.options.errorText));
|
|
23965
|
+
this._hasError = true;
|
|
22460
23966
|
this._loading = false;
|
|
22461
23967
|
this._destroyHandle();
|
|
22462
23968
|
this._syncState();
|
|
@@ -22466,6 +23972,7 @@ var zx = (() => {
|
|
|
22466
23972
|
/** Clears the error and remounts when data and an adapter are available. @returns {this} */
|
|
22467
23973
|
clearError() {
|
|
22468
23974
|
this._error = null;
|
|
23975
|
+
this._hasError = false;
|
|
22469
23976
|
this._mountOrUpdate(false);
|
|
22470
23977
|
return this;
|
|
22471
23978
|
}
|
|
@@ -22473,8 +23980,8 @@ var zx = (() => {
|
|
|
22473
23980
|
resize() {
|
|
22474
23981
|
try {
|
|
22475
23982
|
this._handle?.resize?.();
|
|
22476
|
-
} catch (
|
|
22477
|
-
this.setError(
|
|
23983
|
+
} catch (error2) {
|
|
23984
|
+
this.setError(error2);
|
|
22478
23985
|
}
|
|
22479
23986
|
return this;
|
|
22480
23987
|
}
|
|
@@ -22504,7 +24011,7 @@ var zx = (() => {
|
|
|
22504
24011
|
/** @param {boolean} emitUpdate @returns {void} */
|
|
22505
24012
|
_mountOrUpdate(emitUpdate) {
|
|
22506
24013
|
this._syncState();
|
|
22507
|
-
if (this._loading || this.
|
|
24014
|
+
if (this._loading || this._hasError || isEmptyChartData(this._data)) {
|
|
22508
24015
|
this._destroyHandle();
|
|
22509
24016
|
return;
|
|
22510
24017
|
}
|
|
@@ -22527,8 +24034,8 @@ var zx = (() => {
|
|
|
22527
24034
|
this.emit("ready", { instance: this.getInstance() });
|
|
22528
24035
|
}
|
|
22529
24036
|
if (emitUpdate) this.emit("update", { data: this._data, type: this._type });
|
|
22530
|
-
} catch (
|
|
22531
|
-
this.setError(
|
|
24037
|
+
} catch (error2) {
|
|
24038
|
+
this.setError(error2);
|
|
22532
24039
|
}
|
|
22533
24040
|
}
|
|
22534
24041
|
/** @returns {ChartSpec} */
|
|
@@ -22559,7 +24066,7 @@ var zx = (() => {
|
|
|
22559
24066
|
/** @returns {void} */
|
|
22560
24067
|
_syncState() {
|
|
22561
24068
|
const empty = isEmptyChartData(this._data);
|
|
22562
|
-
const state = this._loading ? "loading" : this.
|
|
24069
|
+
const state = this._loading ? "loading" : this._hasError ? "error" : empty ? "empty" : "ready";
|
|
22563
24070
|
this.el.dataset.state = state;
|
|
22564
24071
|
this.refs.viewport.hidden = state !== "ready";
|
|
22565
24072
|
let text4 = "";
|
|
@@ -22770,8 +24277,9 @@ var zx = (() => {
|
|
|
22770
24277
|
borderColor: borderColor || void 0
|
|
22771
24278
|
};
|
|
22772
24279
|
}
|
|
22773
|
-
function errorMessage(
|
|
22774
|
-
|
|
24280
|
+
function errorMessage(error2, fallback) {
|
|
24281
|
+
const message = error2 && typeof error2 === "object" && "message" in error2 ? String(error2.message) : "";
|
|
24282
|
+
return message.trim() ? message : String(fallback);
|
|
22775
24283
|
}
|
|
22776
24284
|
|
|
22777
24285
|
// src/components/tree/hierarchy.js
|
|
@@ -23409,8 +24917,8 @@ var zx = (() => {
|
|
|
23409
24917
|
node[this._childrenKey] = Array.isArray(children) ? children : [];
|
|
23410
24918
|
if (node[this._childrenKey].length === 0) node.hasChildren = false;
|
|
23411
24919
|
return true;
|
|
23412
|
-
} catch (
|
|
23413
|
-
this.emit("error", { error, node, id });
|
|
24920
|
+
} catch (error2) {
|
|
24921
|
+
this.emit("error", { error: error2, node, id });
|
|
23414
24922
|
return false;
|
|
23415
24923
|
} finally {
|
|
23416
24924
|
this._loading.delete(id);
|
|
@@ -23898,9 +25406,9 @@ var zx = (() => {
|
|
|
23898
25406
|
const children = await this.options.load(node);
|
|
23899
25407
|
node[this._childrenKey] = Array.isArray(children) ? children : [];
|
|
23900
25408
|
if (node[this._childrenKey].length === 0) node.hasChildren = false;
|
|
23901
|
-
} catch (
|
|
25409
|
+
} catch (error2) {
|
|
23902
25410
|
node[this._childrenKey] = [];
|
|
23903
|
-
this.emit("error", { error, node, id });
|
|
25411
|
+
this.emit("error", { error: error2, node, id });
|
|
23904
25412
|
} finally {
|
|
23905
25413
|
this._loading.delete(id);
|
|
23906
25414
|
}
|
|
@@ -24330,6 +25838,821 @@ var zx = (() => {
|
|
|
24330
25838
|
}
|
|
24331
25839
|
}
|
|
24332
25840
|
|
|
25841
|
+
// src/components/filter/filter-model.js
|
|
25842
|
+
var filterOperators = Object.freeze([
|
|
25843
|
+
op("contains", "contains", "single", ["text"]),
|
|
25844
|
+
op("notContains", "does not contain", "single", ["text"]),
|
|
25845
|
+
op("eq", "is", "single", ["text", "number", "money", "boolean", "enum", "status", "priority", "country", "currency", "unit", "entity", "date", "datetime"]),
|
|
25846
|
+
op("neq", "is not", "single", ["text", "number", "money", "boolean", "enum", "status", "priority", "country", "currency", "unit", "entity", "date", "datetime"]),
|
|
25847
|
+
op("startsWith", "starts with", "single", ["text"]),
|
|
25848
|
+
op("endsWith", "ends with", "single", ["text"]),
|
|
25849
|
+
op("lt", "is less than", "single", ["number", "money"]),
|
|
25850
|
+
op("lte", "is at most", "single", ["number", "money"]),
|
|
25851
|
+
op("gt", "is greater than", "single", ["number", "money"]),
|
|
25852
|
+
op("gte", "is at least", "single", ["number", "money"]),
|
|
25853
|
+
op("on", "is on", "single", ["date", "datetime"]),
|
|
25854
|
+
op("notOn", "is not on", "single", ["date", "datetime"]),
|
|
25855
|
+
op("before", "is before", "single", ["date", "datetime"]),
|
|
25856
|
+
op("onOrBefore", "is on or before", "single", ["date", "datetime"]),
|
|
25857
|
+
op("after", "is after", "single", ["date", "datetime"]),
|
|
25858
|
+
op("onOrAfter", "is on or after", "single", ["date", "datetime"]),
|
|
25859
|
+
op("between", "is between", "pair", ["number", "money", "date", "datetime"]),
|
|
25860
|
+
op("notBetween", "is not between", "pair", ["number", "money", "date", "datetime"]),
|
|
25861
|
+
op("withinLast", "is within the last", "pair", ["date", "datetime"]),
|
|
25862
|
+
op("withinNext", "is within the next", "pair", ["date", "datetime"]),
|
|
25863
|
+
op("anyOf", "is any of", "many", ["enum", "status", "priority", "country", "currency", "unit", "entity"]),
|
|
25864
|
+
op("noneOf", "is none of", "many", ["enum", "status", "priority", "country", "currency", "unit", "entity"]),
|
|
25865
|
+
op("containsAny", "contains any", "many", ["tags"]),
|
|
25866
|
+
op("containsAll", "contains all", "many", ["tags"]),
|
|
25867
|
+
op("containsNone", "contains none", "many", ["tags"]),
|
|
25868
|
+
op("isEmpty", "is empty", "none", ["text", "number", "money", "date", "datetime", "boolean", "enum", "status", "priority", "country", "currency", "unit", "entity", "tags"]),
|
|
25869
|
+
op("isNotEmpty", "is not empty", "none", ["text", "number", "money", "date", "datetime", "boolean", "enum", "status", "priority", "country", "currency", "unit", "entity", "tags"])
|
|
25870
|
+
]);
|
|
25871
|
+
function emptyFilterAst() {
|
|
25872
|
+
return { version: 1, root: { kind: "group", id: uid("filter-root"), logic: "and", children: [] } };
|
|
25873
|
+
}
|
|
25874
|
+
function filterCondition(initial = {}) {
|
|
25875
|
+
return {
|
|
25876
|
+
kind: "condition",
|
|
25877
|
+
id: String(initial.id || uid("filter-condition")),
|
|
25878
|
+
field: String(initial.field ?? ""),
|
|
25879
|
+
operator: String(initial.operator ?? ""),
|
|
25880
|
+
value: cloneFilterValue(initial.value ?? null)
|
|
25881
|
+
};
|
|
25882
|
+
}
|
|
25883
|
+
function cloneFilterValue(value) {
|
|
25884
|
+
return cloneJson(value);
|
|
25885
|
+
}
|
|
25886
|
+
function filterGroup(logic = "and", children = []) {
|
|
25887
|
+
return { kind: "group", id: uid("filter-group"), logic, children };
|
|
25888
|
+
}
|
|
25889
|
+
function parseFilterAst(value, limits = {}) {
|
|
25890
|
+
const maxDepth = finiteLimit(limits.maxDepth, 3);
|
|
25891
|
+
const maxConditions = finiteLimit(limits.maxConditions, 50);
|
|
25892
|
+
if (!plainObject(value) || value.version !== 1 || !plainObject(value.root)) {
|
|
25893
|
+
throw new TypeError("Filter AST must have version 1 and a root group");
|
|
25894
|
+
}
|
|
25895
|
+
const ids = /* @__PURE__ */ new Set();
|
|
25896
|
+
let conditions = 0;
|
|
25897
|
+
const visit = (raw2, depth, root2 = false) => {
|
|
25898
|
+
if (!plainObject(raw2)) throw new TypeError("Filter nodes must be plain objects");
|
|
25899
|
+
const id = String(raw2.id ?? "").trim();
|
|
25900
|
+
if (!id || ids.has(id)) throw new TypeError("Filter node IDs must be non-empty and unique");
|
|
25901
|
+
ids.add(id);
|
|
25902
|
+
if (raw2.kind === "group") {
|
|
25903
|
+
if (depth > maxDepth) throw new RangeError("Filter group depth exceeds maxDepth");
|
|
25904
|
+
if (raw2.logic !== "and" && raw2.logic !== "or") throw new TypeError("Filter group logic must be and or or");
|
|
25905
|
+
if (!Array.isArray(raw2.children)) throw new TypeError("Filter group children must be an array");
|
|
25906
|
+
return {
|
|
25907
|
+
kind: "group",
|
|
25908
|
+
id,
|
|
25909
|
+
logic: raw2.logic,
|
|
25910
|
+
children: raw2.children.map((child) => visit(child, depth + 1, false))
|
|
25911
|
+
};
|
|
25912
|
+
}
|
|
25913
|
+
if (root2 || raw2.kind !== "condition") throw new TypeError("Filter root must be a group");
|
|
25914
|
+
conditions += 1;
|
|
25915
|
+
if (conditions > maxConditions) throw new RangeError("Filter condition count exceeds maxConditions");
|
|
25916
|
+
return {
|
|
25917
|
+
kind: "condition",
|
|
25918
|
+
id,
|
|
25919
|
+
field: String(raw2.field ?? ""),
|
|
25920
|
+
operator: String(raw2.operator ?? ""),
|
|
25921
|
+
value: cloneJson(raw2.value)
|
|
25922
|
+
};
|
|
25923
|
+
};
|
|
25924
|
+
const root = visit(value.root, 0, true);
|
|
25925
|
+
if (root.kind !== "group") throw new TypeError("Filter root must be a group");
|
|
25926
|
+
return (
|
|
25927
|
+
/** @type {FilterAst} */
|
|
25928
|
+
{ version: 1, root }
|
|
25929
|
+
);
|
|
25930
|
+
}
|
|
25931
|
+
function cloneFilterAst(value) {
|
|
25932
|
+
return parseFilterAst(value, { maxDepth: Infinity, maxConditions: Infinity });
|
|
25933
|
+
}
|
|
25934
|
+
function stringifyFilterAst(value) {
|
|
25935
|
+
return JSON.stringify(cloneFilterAst(value));
|
|
25936
|
+
}
|
|
25937
|
+
function validateFilterAst(ast, fields, operators = filterOperators) {
|
|
25938
|
+
const errors = [];
|
|
25939
|
+
const fieldMap = new Map((Array.isArray(fields) ? fields : []).map((field) => [String(field.id), field]));
|
|
25940
|
+
const operatorMap = new Map((Array.isArray(operators) ? operators : []).map((operator) => [String(operator.id), operator]));
|
|
25941
|
+
const visit = (node, root = false) => {
|
|
25942
|
+
if (node.kind === "group") {
|
|
25943
|
+
if (!root && node.children.length === 0) errors.push(error(node.id, "empty-group", "Nested groups need at least one condition"));
|
|
25944
|
+
node.children.forEach((child) => visit(child));
|
|
25945
|
+
return;
|
|
25946
|
+
}
|
|
25947
|
+
const field = fieldMap.get(node.field);
|
|
25948
|
+
if (!field) errors.push(error(node.id, "unknown-field", node.field ? `Unavailable field: ${node.field}` : "Choose a field"));
|
|
25949
|
+
const operator = operatorMap.get(node.operator);
|
|
25950
|
+
if (!operator) {
|
|
25951
|
+
errors.push(error(node.id, "unknown-operator", node.operator ? `Unavailable operator: ${node.operator}` : "Choose an operator"));
|
|
25952
|
+
return;
|
|
25953
|
+
}
|
|
25954
|
+
if (field) {
|
|
25955
|
+
const allowed = Array.isArray(field.operators) && field.operators.length ? field.operators : operators.filter((candidate) => candidate.types.includes(field.type)).map((candidate) => candidate.id);
|
|
25956
|
+
if (!allowed.includes(operator.id) || !operator.types.includes(field.type)) {
|
|
25957
|
+
errors.push(error(node.id, "operator-type", `${operator.label} is not available for ${field.label}`));
|
|
25958
|
+
}
|
|
25959
|
+
}
|
|
25960
|
+
if (!validArity(operator.arity, node.value)) {
|
|
25961
|
+
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"));
|
|
25962
|
+
}
|
|
25963
|
+
};
|
|
25964
|
+
visit(ast.root, true);
|
|
25965
|
+
return { valid: errors.length === 0, errors };
|
|
25966
|
+
}
|
|
25967
|
+
function op(id, label, arity, types) {
|
|
25968
|
+
return Object.freeze({ id, label, arity, types: Object.freeze(types) });
|
|
25969
|
+
}
|
|
25970
|
+
function error(nodeId, code, message) {
|
|
25971
|
+
return { nodeId, code, message };
|
|
25972
|
+
}
|
|
25973
|
+
function validArity(arity, value) {
|
|
25974
|
+
if (arity === "none") return value === null;
|
|
25975
|
+
if (arity === "pair") return Array.isArray(value) && value.length === 2 && value.every(filled);
|
|
25976
|
+
if (arity === "many") return Array.isArray(value) && value.length > 0 && value.every(filled);
|
|
25977
|
+
return filled(value);
|
|
25978
|
+
}
|
|
25979
|
+
function filled(value) {
|
|
25980
|
+
return value !== null && value !== void 0 && !(typeof value === "string" && value.trim() === "");
|
|
25981
|
+
}
|
|
25982
|
+
function cloneJson(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
25983
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
25984
|
+
if (typeof value === "number") {
|
|
25985
|
+
if (!Number.isFinite(value)) throw new TypeError("Filter values cannot contain non-finite numbers");
|
|
25986
|
+
return value;
|
|
25987
|
+
}
|
|
25988
|
+
if (typeof value !== "object" || value instanceof Date || typeof value.nodeType === "number") {
|
|
25989
|
+
throw new TypeError("Filter values must be JSON-safe");
|
|
25990
|
+
}
|
|
25991
|
+
if (seen.has(value)) throw new TypeError("Filter values cannot contain cycles");
|
|
25992
|
+
seen.add(value);
|
|
25993
|
+
if (Array.isArray(value)) {
|
|
25994
|
+
const result2 = value.map((item) => cloneJson(item, seen));
|
|
25995
|
+
seen.delete(value);
|
|
25996
|
+
return result2;
|
|
25997
|
+
}
|
|
25998
|
+
if (!plainObject(value)) throw new TypeError("Filter values must be plain JSON objects");
|
|
25999
|
+
const result = {};
|
|
26000
|
+
for (const [key, item] of Object.entries(value)) {
|
|
26001
|
+
Object.defineProperty(result, key, {
|
|
26002
|
+
value: cloneJson(item, seen),
|
|
26003
|
+
enumerable: true,
|
|
26004
|
+
configurable: true,
|
|
26005
|
+
writable: true
|
|
26006
|
+
});
|
|
26007
|
+
}
|
|
26008
|
+
seen.delete(value);
|
|
26009
|
+
return result;
|
|
26010
|
+
}
|
|
26011
|
+
function plainObject(value) {
|
|
26012
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
26013
|
+
const prototype = Object.getPrototypeOf(value);
|
|
26014
|
+
return prototype === Object.prototype || prototype === null;
|
|
26015
|
+
}
|
|
26016
|
+
function finiteLimit(value, fallback) {
|
|
26017
|
+
if (value === Infinity) return Infinity;
|
|
26018
|
+
const number2 = Number(value);
|
|
26019
|
+
return Number.isFinite(number2) && number2 >= 0 ? Math.floor(number2) : fallback;
|
|
26020
|
+
}
|
|
26021
|
+
|
|
26022
|
+
// src/components/filter/filter.js
|
|
26023
|
+
var Filter = class extends Component {
|
|
26024
|
+
static cssName = "filter";
|
|
26025
|
+
/** @type {Readonly<FilterOptions>} */
|
|
26026
|
+
static defaults = {
|
|
26027
|
+
fields: [],
|
|
26028
|
+
value: null,
|
|
26029
|
+
operators: [],
|
|
26030
|
+
allowGroups: true,
|
|
26031
|
+
maxDepth: 3,
|
|
26032
|
+
maxConditions: 50,
|
|
26033
|
+
autoApply: false,
|
|
26034
|
+
readonly: false,
|
|
26035
|
+
disabled: false,
|
|
26036
|
+
applyLabel: "Apply filters",
|
|
26037
|
+
clearLabel: "Clear"
|
|
26038
|
+
};
|
|
26039
|
+
/** @returns {HTMLElement} */
|
|
26040
|
+
render() {
|
|
26041
|
+
this._createdRoot = this.el === null;
|
|
26042
|
+
const root = (
|
|
26043
|
+
/** @type {HTMLElement} */
|
|
26044
|
+
this.el ?? h("div")
|
|
26045
|
+
);
|
|
26046
|
+
this.el = root;
|
|
26047
|
+
this._snapshot = this._createdRoot ? null : snapshotTarget(root);
|
|
26048
|
+
this._fields = (Array.isArray(this.options.fields) ? this.options.fields : []).map((field) => ({ ...field }));
|
|
26049
|
+
this._operators = mergeOperators(filterOperators, this.options.operators);
|
|
26050
|
+
this._readonly = Boolean(this.options.readonly);
|
|
26051
|
+
this._disabled = Boolean(this.options.disabled);
|
|
26052
|
+
this._async = /* @__PURE__ */ new Map();
|
|
26053
|
+
this._customEditors = [];
|
|
26054
|
+
this._value = this.options.value == null ? emptyFilterAst() : parseFilterAst(this.options.value, this._limits());
|
|
26055
|
+
this.listen(root, "change", (event) => this._handleChange(event));
|
|
26056
|
+
this.listen(root, "input", (event) => this._handleInput(event));
|
|
26057
|
+
this.listen(root, "click", (event) => this._handleClick(event));
|
|
26058
|
+
this._render();
|
|
26059
|
+
return root;
|
|
26060
|
+
}
|
|
26061
|
+
/** Returns a defensive AST copy. @returns {FilterAst} */
|
|
26062
|
+
getValue() {
|
|
26063
|
+
return cloneFilterAst(this._value);
|
|
26064
|
+
}
|
|
26065
|
+
/** Atomically replaces the AST. @param {unknown} value @param {{silent?:boolean}} [options={}] @returns {this} */
|
|
26066
|
+
setValue(value, { silent = false } = {}) {
|
|
26067
|
+
const parsed = parseFilterAst(value, this._limits());
|
|
26068
|
+
this._abortAll();
|
|
26069
|
+
this._value = parsed;
|
|
26070
|
+
this._render();
|
|
26071
|
+
if (!silent) this._changed("set", this._value.root.id);
|
|
26072
|
+
return this;
|
|
26073
|
+
}
|
|
26074
|
+
/** Adds a condition to a group. @param {string|null} [parentId=null] @param {Record<string,unknown>} [initial={}] @returns {string|null} */
|
|
26075
|
+
addCondition(parentId = null, initial = {}) {
|
|
26076
|
+
if (!this._canMutate() || conditionCount(this._value.root) >= Number(this.options.maxConditions)) return null;
|
|
26077
|
+
const parent = findGroup(this._value.root, parentId ?? this._value.root.id);
|
|
26078
|
+
if (!parent) return null;
|
|
26079
|
+
const first = this._fields[0];
|
|
26080
|
+
const field = String(initial.field ?? first?.id ?? "");
|
|
26081
|
+
const definition = this._field(field);
|
|
26082
|
+
const node = filterCondition({
|
|
26083
|
+
...initial,
|
|
26084
|
+
field,
|
|
26085
|
+
operator: String(initial.operator ?? this._defaultOperator(definition) ?? "")
|
|
26086
|
+
});
|
|
26087
|
+
parent.children.push(node);
|
|
26088
|
+
this._render();
|
|
26089
|
+
this._changed("add", node.id);
|
|
26090
|
+
this.focus(node.id);
|
|
26091
|
+
return node.id;
|
|
26092
|
+
}
|
|
26093
|
+
/** Adds a nested group. @param {string|null} [parentId=null] @param {'and'|'or'} [logic='and'] @returns {string|null} */
|
|
26094
|
+
addGroup(parentId = null, logic = "and") {
|
|
26095
|
+
if (!this._canMutate() || !this.options.allowGroups) return null;
|
|
26096
|
+
const parent = findGroup(this._value.root, parentId ?? this._value.root.id);
|
|
26097
|
+
if (!parent || groupDepth(this._value.root, parent.id) >= Number(this.options.maxDepth)) return null;
|
|
26098
|
+
const group = filterGroup(logic === "or" ? "or" : "and");
|
|
26099
|
+
parent.children.push(group);
|
|
26100
|
+
this._render();
|
|
26101
|
+
this._changed("add", group.id);
|
|
26102
|
+
this.focus(group.id);
|
|
26103
|
+
return group.id;
|
|
26104
|
+
}
|
|
26105
|
+
/** Updates a node by stable ID. @param {string} id @param {Record<string,unknown>} patch @returns {this} */
|
|
26106
|
+
update(id, patch = {}) {
|
|
26107
|
+
if (!this._canMutate()) return this;
|
|
26108
|
+
const node = findNode2(this._value.root, id);
|
|
26109
|
+
if (!node) return this;
|
|
26110
|
+
if (node.kind === "group") {
|
|
26111
|
+
if (patch.logic === "and" || patch.logic === "or") node.logic = patch.logic;
|
|
26112
|
+
} else {
|
|
26113
|
+
if (Object.hasOwn(patch, "field")) node.field = String(patch.field ?? "");
|
|
26114
|
+
if (Object.hasOwn(patch, "operator")) node.operator = String(patch.operator ?? "");
|
|
26115
|
+
if (Object.hasOwn(patch, "value")) node.value = parseValueJson(patch.value);
|
|
26116
|
+
}
|
|
26117
|
+
this._abort(id);
|
|
26118
|
+
this._render();
|
|
26119
|
+
this._changed("update", id);
|
|
26120
|
+
return this;
|
|
26121
|
+
}
|
|
26122
|
+
/** Removes a non-root node. @param {string} id @returns {this} */
|
|
26123
|
+
remove(id) {
|
|
26124
|
+
if (!this._canMutate() || id === this._value.root.id) return this;
|
|
26125
|
+
const parent = findParent(this._value.root, id);
|
|
26126
|
+
if (!parent) return this;
|
|
26127
|
+
const index = parent.children.findIndex((child) => child.id === id);
|
|
26128
|
+
const focusId = parent.children[index + 1]?.id ?? parent.children[index - 1]?.id ?? parent.id;
|
|
26129
|
+
const removed = removeNode(this._value.root, id);
|
|
26130
|
+
if (!removed) return this;
|
|
26131
|
+
this._abortTree(removed);
|
|
26132
|
+
this._render();
|
|
26133
|
+
const label = removed.kind === "condition" ? this._field(removed.field)?.label ?? "Filter" : "Filter group";
|
|
26134
|
+
this._announce(`${label} removed`);
|
|
26135
|
+
this._changed("remove", id);
|
|
26136
|
+
this.focus(focusId);
|
|
26137
|
+
return this;
|
|
26138
|
+
}
|
|
26139
|
+
/** Moves a node into a group while preserving its identity. @param {string} id @param {string} parentId @param {number} index @returns {this} */
|
|
26140
|
+
move(id, parentId, index) {
|
|
26141
|
+
const node = findNode2(this._value.root, id);
|
|
26142
|
+
const parent = findGroup(this._value.root, parentId);
|
|
26143
|
+
const previous = findParent(this._value.root, id);
|
|
26144
|
+
if (!this._canMutate() || id === this._value.root.id || !node || !parent || !previous || containsNode(node, parentId)) return this;
|
|
26145
|
+
const previousIndex = previous.children.findIndex((child) => child.id === id);
|
|
26146
|
+
const removed = previous.children.splice(previousIndex, 1)[0];
|
|
26147
|
+
parent.children.splice(Math.max(0, Math.min(parent.children.length, Number(index) || 0)), 0, removed);
|
|
26148
|
+
try {
|
|
26149
|
+
parseFilterAst(this._value, this._limits());
|
|
26150
|
+
} catch {
|
|
26151
|
+
parent.children.splice(parent.children.indexOf(removed), 1);
|
|
26152
|
+
previous.children.splice(previousIndex, 0, removed);
|
|
26153
|
+
return this;
|
|
26154
|
+
}
|
|
26155
|
+
this._render();
|
|
26156
|
+
this._changed("move", id);
|
|
26157
|
+
return this;
|
|
26158
|
+
}
|
|
26159
|
+
/** Returns semantic validity and errors. @returns {{valid:boolean,errors:Array<Record<string,string>>}} */
|
|
26160
|
+
validate() {
|
|
26161
|
+
return validateFilterAst(this._value, this._fields, this._operators);
|
|
26162
|
+
}
|
|
26163
|
+
/** Applies only a valid AST. @returns {FilterAst|null} */
|
|
26164
|
+
apply() {
|
|
26165
|
+
if (this._disabled) return null;
|
|
26166
|
+
const result = this.validate();
|
|
26167
|
+
if (!result.valid) {
|
|
26168
|
+
this.emit("invalid", { errors: result.errors });
|
|
26169
|
+
this.focus(result.errors[0]?.nodeId);
|
|
26170
|
+
return null;
|
|
26171
|
+
}
|
|
26172
|
+
const value = this.getValue();
|
|
26173
|
+
this.emit("apply", { value });
|
|
26174
|
+
return value;
|
|
26175
|
+
}
|
|
26176
|
+
/** Clears all conditions. @param {{silent?:boolean}} [options={}] @returns {this} */
|
|
26177
|
+
clear({ silent = false } = {}) {
|
|
26178
|
+
if (!this._canMutate()) return this;
|
|
26179
|
+
this._abortAll();
|
|
26180
|
+
this._value.root.children = [];
|
|
26181
|
+
this._render();
|
|
26182
|
+
if (!silent) this._changed("clear", this._value.root.id);
|
|
26183
|
+
this.refs.add?.focus();
|
|
26184
|
+
return this;
|
|
26185
|
+
}
|
|
26186
|
+
/** Focuses a condition's field or the Add button. @param {string|null} [id=null] @returns {this} */
|
|
26187
|
+
focus(id = null) {
|
|
26188
|
+
const selector = id ? `[data-node-id="${cssEscape(id)}"] :is([data-filter-field], [data-filter-logic], [data-filter-action="add"])` : '[data-filter-action="add"]';
|
|
26189
|
+
queueMicrotask(() => (
|
|
26190
|
+
/** @type {HTMLElement|null} */
|
|
26191
|
+
this.el.querySelector(selector)?.focus()
|
|
26192
|
+
));
|
|
26193
|
+
return this;
|
|
26194
|
+
}
|
|
26195
|
+
/** @param {boolean} readonly @returns {this} */
|
|
26196
|
+
setReadonly(readonly) {
|
|
26197
|
+
this._readonly = Boolean(readonly);
|
|
26198
|
+
this._render();
|
|
26199
|
+
return this;
|
|
26200
|
+
}
|
|
26201
|
+
/** @returns {this} */
|
|
26202
|
+
enable() {
|
|
26203
|
+
this._disabled = false;
|
|
26204
|
+
this._render();
|
|
26205
|
+
return this;
|
|
26206
|
+
}
|
|
26207
|
+
/** @returns {this} */
|
|
26208
|
+
disable() {
|
|
26209
|
+
this._disabled = true;
|
|
26210
|
+
this._abortAll();
|
|
26211
|
+
this._render();
|
|
26212
|
+
return this;
|
|
26213
|
+
}
|
|
26214
|
+
/** @returns {void} */
|
|
26215
|
+
destroy() {
|
|
26216
|
+
this._abortAll();
|
|
26217
|
+
this._destroyEditors();
|
|
26218
|
+
const root = this.el;
|
|
26219
|
+
const snapshot10 = this._snapshot;
|
|
26220
|
+
super.destroy();
|
|
26221
|
+
if (!this._createdRoot && root) restoreTarget(root, snapshot10);
|
|
26222
|
+
}
|
|
26223
|
+
/** @returns {void} */
|
|
26224
|
+
_render() {
|
|
26225
|
+
this._destroyEditors();
|
|
26226
|
+
this._renderVersion = (this._renderVersion ?? 0) + 1;
|
|
26227
|
+
const status = h("div", { ref: "status", class: "zx-filter__status", role: "status", ariaLive: "polite" });
|
|
26228
|
+
const tree = this._renderGroup(this._value.root, 0, true);
|
|
26229
|
+
const actions2 = h(
|
|
26230
|
+
"div",
|
|
26231
|
+
{ class: "zx-filter__actions" },
|
|
26232
|
+
actionButton("add", this._value.root.id, "plus", "Add filter", { ref: "add", disabled: this._blocked() }),
|
|
26233
|
+
this.options.allowGroups ? actionButton("add-group", this._value.root.id, "folder", "Add group", { disabled: this._blocked() }) : null,
|
|
26234
|
+
actionButton("clear", "", "x", String(this.options.clearLabel), { disabled: this._blocked() }),
|
|
26235
|
+
actionButton("apply", "", "check", String(this.options.applyLabel), { class: "zx-button--primary", disabled: this._disabled })
|
|
26236
|
+
);
|
|
26237
|
+
this.el.replaceChildren(status, tree, actions2);
|
|
26238
|
+
this.refs.status = status;
|
|
26239
|
+
this.refs.add = /** @type {HTMLElement} */
|
|
26240
|
+
this.el.querySelector('[data-filter-action="add"]');
|
|
26241
|
+
this.el.toggleAttribute("data-readonly", this._readonly);
|
|
26242
|
+
this.el.toggleAttribute("data-disabled", this._disabled);
|
|
26243
|
+
const result = this.validate();
|
|
26244
|
+
this.el.dataset.valid = String(result.valid);
|
|
26245
|
+
for (const error2 of result.errors) this.el.querySelector(`[data-node-id="${cssEscape(error2.nodeId)}"]`)?.setAttribute("data-invalid", "");
|
|
26246
|
+
}
|
|
26247
|
+
/** @param {any} group @param {number} depth @param {boolean} root @returns {HTMLElement} */
|
|
26248
|
+
_renderGroup(group, depth, root) {
|
|
26249
|
+
const children = h(
|
|
26250
|
+
"ol",
|
|
26251
|
+
{ class: "zx-filter__children" },
|
|
26252
|
+
group.children.map((node) => node.kind === "group" ? this._renderGroup(node, depth + 1, false) : this._renderCondition(node))
|
|
26253
|
+
);
|
|
26254
|
+
const logic = h(
|
|
26255
|
+
"select",
|
|
26256
|
+
{
|
|
26257
|
+
class: "zx-filter__logic",
|
|
26258
|
+
dataset: { filterLogic: "", nodeId: group.id },
|
|
26259
|
+
ariaLabel: root ? "Root filter logic" : "Group filter logic",
|
|
26260
|
+
disabled: this._blocked()
|
|
26261
|
+
},
|
|
26262
|
+
h("option", { value: "and", selected: group.logic === "and" }, "Match all"),
|
|
26263
|
+
h("option", { value: "or", selected: group.logic === "or" }, "Match any")
|
|
26264
|
+
);
|
|
26265
|
+
const legend = h("legend", { class: "zx-filter__legend" }, logic, h("span", {}, root ? "conditions" : "in this group"));
|
|
26266
|
+
const controls = h(
|
|
26267
|
+
"div",
|
|
26268
|
+
{ class: "zx-filter__group-actions" },
|
|
26269
|
+
actionButton("add", group.id, "plus", "Add filter", { disabled: this._blocked() }),
|
|
26270
|
+
this.options.allowGroups && depth < Number(this.options.maxDepth) ? actionButton("add-group", group.id, "folder", "Add subgroup", { disabled: this._blocked() }) : null,
|
|
26271
|
+
root ? null : actionButton("remove", group.id, "trash", "Remove group", { disabled: this._blocked() })
|
|
26272
|
+
);
|
|
26273
|
+
return h(root ? "fieldset" : "li", {
|
|
26274
|
+
class: "zx-filter__group",
|
|
26275
|
+
dataset: { nodeId: group.id, depth: String(depth) }
|
|
26276
|
+
}, root ? legend : h("fieldset", {}, legend, children, controls), root ? children : null, root ? controls : null);
|
|
26277
|
+
}
|
|
26278
|
+
/** @param {any} node @returns {HTMLElement} */
|
|
26279
|
+
_renderCondition(node) {
|
|
26280
|
+
const field = this._field(node.field);
|
|
26281
|
+
const operators = this._operatorsFor(field);
|
|
26282
|
+
const operator = this._operator(node.operator);
|
|
26283
|
+
const fieldSelect = h(
|
|
26284
|
+
"select",
|
|
26285
|
+
{
|
|
26286
|
+
class: "zx-filter__field",
|
|
26287
|
+
dataset: { filterField: "", nodeId: node.id },
|
|
26288
|
+
ariaLabel: "Filter field",
|
|
26289
|
+
disabled: this._blocked()
|
|
26290
|
+
},
|
|
26291
|
+
h("option", { value: "", selected: !node.field }, "Choose a field\u2026"),
|
|
26292
|
+
this._fields.map((candidate) => h("option", { value: candidate.id, selected: candidate.id === node.field }, candidate.label)),
|
|
26293
|
+
node.field && !field ? h("option", { value: node.field, selected: true }, `Unavailable: ${node.field}`) : null
|
|
26294
|
+
);
|
|
26295
|
+
const operatorSelect = h(
|
|
26296
|
+
"select",
|
|
26297
|
+
{
|
|
26298
|
+
class: "zx-filter__operator",
|
|
26299
|
+
dataset: { filterOperator: "", nodeId: node.id },
|
|
26300
|
+
ariaLabel: "Filter operator",
|
|
26301
|
+
disabled: this._blocked()
|
|
26302
|
+
},
|
|
26303
|
+
h("option", { value: "", selected: !node.operator }, "Choose an operator\u2026"),
|
|
26304
|
+
operators.map((candidate) => h("option", { value: candidate.id, selected: candidate.id === node.operator }, candidate.label)),
|
|
26305
|
+
node.operator && !operators.some((candidate) => candidate.id === node.operator) ? h("option", { value: node.operator, selected: true }, `Unavailable: ${node.operator}`) : null
|
|
26306
|
+
);
|
|
26307
|
+
return h(
|
|
26308
|
+
"li",
|
|
26309
|
+
{ class: "zx-filter__condition", dataset: { nodeId: node.id } },
|
|
26310
|
+
fieldSelect,
|
|
26311
|
+
operatorSelect,
|
|
26312
|
+
this._renderValue(node, field, operator),
|
|
26313
|
+
actionButton("remove", node.id, "trash", `Remove ${field?.label ?? "filter"}`, { disabled: this._blocked() })
|
|
26314
|
+
);
|
|
26315
|
+
}
|
|
26316
|
+
/** @param {any} node @param {FilterField|null} field @param {any} operator @returns {HTMLElement} */
|
|
26317
|
+
_renderValue(node, field, operator) {
|
|
26318
|
+
const host = h("div", { class: "zx-filter__value" });
|
|
26319
|
+
if (!field || !operator) return host;
|
|
26320
|
+
if (operator.arity === "none") return h("div", { class: "zx-filter__value zx-filter__value--empty" }, "No value");
|
|
26321
|
+
if (field.type === "custom" && typeof field.editor === "function") {
|
|
26322
|
+
const renderVersion = this._renderVersion;
|
|
26323
|
+
queueMicrotask(() => {
|
|
26324
|
+
if (renderVersion !== this._renderVersion || !this.el.contains(host)) return;
|
|
26325
|
+
const handle = field.editor(host, {
|
|
26326
|
+
value: node.value,
|
|
26327
|
+
field,
|
|
26328
|
+
conditionId: node.id,
|
|
26329
|
+
setValue: (value) => this._setNodeValue(node.id, parseValueJson(value))
|
|
26330
|
+
});
|
|
26331
|
+
if (handle && typeof handle.destroy === "function") this._customEditors.push(handle);
|
|
26332
|
+
});
|
|
26333
|
+
return host;
|
|
26334
|
+
}
|
|
26335
|
+
const values = operator.arity === "pair" ? Array.isArray(node.value) ? node.value : [null, null] : [node.value];
|
|
26336
|
+
if (operator.arity === "many") {
|
|
26337
|
+
const control = choiceSelect(field, node, values[0], true, this._blocked());
|
|
26338
|
+
if (control) return h("div", { class: "zx-filter__value" }, control);
|
|
26339
|
+
return h("div", { class: "zx-filter__value" }, valueInput(field, node.id, Array.isArray(node.value) ? node.value.join(", ") : "", 0, this._blocked(), true));
|
|
26340
|
+
}
|
|
26341
|
+
const controls = values.map((value, index) => choiceSelect(field, node, value, false, this._blocked(), index) ?? valueInput(field, node.id, value, index, this._blocked()));
|
|
26342
|
+
return h("div", { class: "zx-filter__value", dataset: { arity: operator.arity } }, controls);
|
|
26343
|
+
}
|
|
26344
|
+
/** @param {Event} event @returns {void} */
|
|
26345
|
+
_handleChange(event) {
|
|
26346
|
+
if (!this._canMutate()) return;
|
|
26347
|
+
const target = (
|
|
26348
|
+
/** @type {HTMLInputElement|HTMLSelectElement|null} */
|
|
26349
|
+
event.target
|
|
26350
|
+
);
|
|
26351
|
+
const id = target?.dataset.nodeId;
|
|
26352
|
+
if (!target || !id) return;
|
|
26353
|
+
if (target.dataset.filterField !== void 0) {
|
|
26354
|
+
const node = findNode2(this._value.root, id);
|
|
26355
|
+
if (node?.kind !== "condition") return;
|
|
26356
|
+
node.field = target.value;
|
|
26357
|
+
const field = this._field(node.field);
|
|
26358
|
+
node.operator = this._defaultOperator(field) ?? "";
|
|
26359
|
+
node.value = null;
|
|
26360
|
+
this._abort(id);
|
|
26361
|
+
this._render();
|
|
26362
|
+
this._changed("update", id);
|
|
26363
|
+
this.focus(id);
|
|
26364
|
+
} else if (target.dataset.filterOperator !== void 0) {
|
|
26365
|
+
const node = findNode2(this._value.root, id);
|
|
26366
|
+
if (node?.kind !== "condition") return;
|
|
26367
|
+
node.operator = target.value;
|
|
26368
|
+
node.value = null;
|
|
26369
|
+
this._abort(id);
|
|
26370
|
+
this._render();
|
|
26371
|
+
this._changed("update", id);
|
|
26372
|
+
queueMicrotask(() => (
|
|
26373
|
+
/** @type {HTMLElement|null} */
|
|
26374
|
+
this.el.querySelector(`[data-node-id="${cssEscape(id)}"] [data-filter-value]`)?.focus()
|
|
26375
|
+
));
|
|
26376
|
+
} else if (target.dataset.filterLogic !== void 0) {
|
|
26377
|
+
const node = findNode2(this._value.root, id);
|
|
26378
|
+
if (node?.kind === "group") node.logic = target.value === "or" ? "or" : "and";
|
|
26379
|
+
this._changed("update", id);
|
|
26380
|
+
} else if (target.dataset.filterValue !== void 0) this._valueInput(target);
|
|
26381
|
+
}
|
|
26382
|
+
/** @param {Event} event @returns {void} */
|
|
26383
|
+
_handleInput(event) {
|
|
26384
|
+
if (!this._canMutate()) return;
|
|
26385
|
+
const target = (
|
|
26386
|
+
/** @type {HTMLInputElement|null} */
|
|
26387
|
+
event.target
|
|
26388
|
+
);
|
|
26389
|
+
if (!target || target.dataset.filterValue === void 0) return;
|
|
26390
|
+
this._valueInput(target);
|
|
26391
|
+
const id = target.dataset.nodeId;
|
|
26392
|
+
const node = findNode2(this._value.root, id);
|
|
26393
|
+
const field = node?.kind === "condition" ? this._field(node.field) : null;
|
|
26394
|
+
if (field?.loadChoices) this._scheduleChoices(node.id, field, target.value);
|
|
26395
|
+
}
|
|
26396
|
+
/** @param {HTMLInputElement|HTMLSelectElement} target @returns {void} */
|
|
26397
|
+
_valueInput(target) {
|
|
26398
|
+
const node = findNode2(this._value.root, target.dataset.nodeId);
|
|
26399
|
+
if (node?.kind !== "condition") return;
|
|
26400
|
+
const operator = this._operator(node.operator);
|
|
26401
|
+
const field = this._field(node.field);
|
|
26402
|
+
if (!operator || !field) return;
|
|
26403
|
+
let value;
|
|
26404
|
+
if (target instanceof HTMLSelectElement && target.multiple) {
|
|
26405
|
+
value = Array.from(target.selectedOptions, (option) => parseScalar(option.value, field.type));
|
|
26406
|
+
} else if (operator.arity === "many") {
|
|
26407
|
+
value = target.value.split(",").map((item) => item.trim()).filter(Boolean).map((item) => parseScalar(item, field.type));
|
|
26408
|
+
} else if (operator.arity === "pair") {
|
|
26409
|
+
const pair = Array.isArray(node.value) ? [...node.value] : [null, null];
|
|
26410
|
+
pair[Number(target.dataset.valueIndex) || 0] = parseScalar(target.value, field.type);
|
|
26411
|
+
value = pair;
|
|
26412
|
+
} else value = parseScalar(target.value, field.type);
|
|
26413
|
+
this._setNodeValue(
|
|
26414
|
+
node.id,
|
|
26415
|
+
/** @type {any} */
|
|
26416
|
+
value
|
|
26417
|
+
);
|
|
26418
|
+
}
|
|
26419
|
+
/** @param {MouseEvent} event @returns {void} */
|
|
26420
|
+
_handleClick(event) {
|
|
26421
|
+
const button2 = (
|
|
26422
|
+
/** @type {HTMLElement|null} */
|
|
26423
|
+
event.target?.closest?.("[data-filter-action]")
|
|
26424
|
+
);
|
|
26425
|
+
if (!button2 || !this.el.contains(button2)) return;
|
|
26426
|
+
const action = button2.dataset.filterAction;
|
|
26427
|
+
const id = button2.dataset.nodeId || null;
|
|
26428
|
+
if (action === "add") this.addCondition(id);
|
|
26429
|
+
else if (action === "add-group") this.addGroup(id);
|
|
26430
|
+
else if (action === "remove" && id) this.remove(id);
|
|
26431
|
+
else if (action === "clear") this.clear();
|
|
26432
|
+
else if (action === "apply") this.apply();
|
|
26433
|
+
}
|
|
26434
|
+
/** @param {string} id @param {any} value @returns {void} */
|
|
26435
|
+
_setNodeValue(id, value) {
|
|
26436
|
+
const node = findNode2(this._value.root, id);
|
|
26437
|
+
if (node?.kind !== "condition") return;
|
|
26438
|
+
node.value = value;
|
|
26439
|
+
const result = this.validate();
|
|
26440
|
+
this.el.dataset.valid = String(result.valid);
|
|
26441
|
+
const row2 = this.el.querySelector(`[data-node-id="${cssEscape(id)}"]`);
|
|
26442
|
+
row2?.toggleAttribute("data-invalid", result.errors.some((candidate) => candidate.nodeId === id));
|
|
26443
|
+
this._changed("update", id);
|
|
26444
|
+
}
|
|
26445
|
+
/** @param {string} reason @param {string} nodeId @returns {void} */
|
|
26446
|
+
_changed(reason, nodeId) {
|
|
26447
|
+
const result = this.validate();
|
|
26448
|
+
const value = this.getValue();
|
|
26449
|
+
this.emit("change", { value, valid: result.valid, errors: result.errors, reason, nodeId });
|
|
26450
|
+
if (this.options.autoApply && result.valid) this.emit("apply", { value });
|
|
26451
|
+
}
|
|
26452
|
+
/** @param {string} id @param {FilterField} field @param {string} query @returns {void} */
|
|
26453
|
+
_scheduleChoices(id, field, query) {
|
|
26454
|
+
this._abort(id);
|
|
26455
|
+
if (query.length < Number(field.minQuery ?? 0)) return;
|
|
26456
|
+
const controller = new AbortController();
|
|
26457
|
+
this._setAsyncBusy(id, true);
|
|
26458
|
+
const entry = { controller, timer: setTimeout(async () => {
|
|
26459
|
+
this.emit("query", { conditionId: id, fieldId: field.id, query });
|
|
26460
|
+
try {
|
|
26461
|
+
const choices = await field.loadChoices({ query, signal: controller.signal, conditionId: id, field });
|
|
26462
|
+
if (controller.signal.aborted || this._async.get(id)?.controller !== controller) return;
|
|
26463
|
+
const input = this.el.querySelector(`[data-node-id="${cssEscape(id)}"] [data-filter-value]`);
|
|
26464
|
+
const listId = input?.getAttribute("list");
|
|
26465
|
+
const list = listId ? this.el.querySelector(`#${cssEscape(listId)}`) : null;
|
|
26466
|
+
if (list) list.replaceChildren(...(Array.isArray(choices) ? choices : []).map((choice) => h("option", { value: choice.value }, choice.label)));
|
|
26467
|
+
this.emit("loaded", { conditionId: id, fieldId: field.id, query, choices });
|
|
26468
|
+
} catch (error2) {
|
|
26469
|
+
if (!controller.signal.aborted) this.emit("error", { conditionId: id, fieldId: field.id, query, error: error2 instanceof Error ? error2 : new Error(String(error2)) });
|
|
26470
|
+
} finally {
|
|
26471
|
+
if (this._async.get(id)?.controller === controller) {
|
|
26472
|
+
this._async.delete(id);
|
|
26473
|
+
this._setAsyncBusy(id, false);
|
|
26474
|
+
}
|
|
26475
|
+
}
|
|
26476
|
+
}, Math.max(0, Number(field.debounce ?? 200))) };
|
|
26477
|
+
this._async.set(id, entry);
|
|
26478
|
+
}
|
|
26479
|
+
/** @param {string} id @returns {void} */
|
|
26480
|
+
_abort(id) {
|
|
26481
|
+
const entry = this._async.get(id);
|
|
26482
|
+
if (entry) {
|
|
26483
|
+
clearTimeout(entry.timer);
|
|
26484
|
+
entry.controller.abort();
|
|
26485
|
+
this._async.delete(id);
|
|
26486
|
+
this._setAsyncBusy(id, false);
|
|
26487
|
+
}
|
|
26488
|
+
}
|
|
26489
|
+
/** @param {string} id @param {boolean} busy @returns {void} */
|
|
26490
|
+
_setAsyncBusy(id, busy) {
|
|
26491
|
+
const input = this.el.querySelector(`[data-node-id="${cssEscape(id)}"] [data-filter-value]`);
|
|
26492
|
+
if (busy) input?.setAttribute("aria-busy", "true");
|
|
26493
|
+
else input?.removeAttribute("aria-busy");
|
|
26494
|
+
}
|
|
26495
|
+
/** @param {any} node @returns {void} */
|
|
26496
|
+
_abortTree(node) {
|
|
26497
|
+
if (node.kind === "condition") this._abort(node.id);
|
|
26498
|
+
else node.children.forEach((child) => this._abortTree(child));
|
|
26499
|
+
}
|
|
26500
|
+
/** @returns {void} */
|
|
26501
|
+
_abortAll() {
|
|
26502
|
+
for (const id of [...this._async.keys()]) this._abort(id);
|
|
26503
|
+
}
|
|
26504
|
+
/** @returns {void} */
|
|
26505
|
+
_destroyEditors() {
|
|
26506
|
+
for (const editor of this._customEditors ?? []) editor.destroy?.();
|
|
26507
|
+
this._customEditors = [];
|
|
26508
|
+
}
|
|
26509
|
+
/** @returns {boolean} */
|
|
26510
|
+
_blocked() {
|
|
26511
|
+
return this._readonly || this._disabled;
|
|
26512
|
+
}
|
|
26513
|
+
/** @returns {boolean} */
|
|
26514
|
+
_canMutate() {
|
|
26515
|
+
return !this._blocked();
|
|
26516
|
+
}
|
|
26517
|
+
/** @returns {{maxDepth:number,maxConditions:number}} */
|
|
26518
|
+
_limits() {
|
|
26519
|
+
return { maxDepth: Number(this.options.maxDepth), maxConditions: Number(this.options.maxConditions) };
|
|
26520
|
+
}
|
|
26521
|
+
/** @param {string} id @returns {FilterField|null} */
|
|
26522
|
+
_field(id) {
|
|
26523
|
+
return this._fields.find((field) => field.id === id) ?? null;
|
|
26524
|
+
}
|
|
26525
|
+
/** @param {string} id @returns {any} */
|
|
26526
|
+
_operator(id) {
|
|
26527
|
+
return this._operators.find((operator) => operator.id === id) ?? null;
|
|
26528
|
+
}
|
|
26529
|
+
/** @param {FilterField|null} field @returns {any[]} */
|
|
26530
|
+
_operatorsFor(field) {
|
|
26531
|
+
if (!field) return [];
|
|
26532
|
+
const ids = field.operators?.length ? field.operators : null;
|
|
26533
|
+
return this._operators.filter((operator) => ids ? ids.includes(operator.id) : operator.types.includes(field.type));
|
|
26534
|
+
}
|
|
26535
|
+
/** @param {FilterField|null} field @returns {string|null} */
|
|
26536
|
+
_defaultOperator(field) {
|
|
26537
|
+
return field?.defaultOperator && this._operatorsFor(field).some((operator) => operator.id === field.defaultOperator) ? field.defaultOperator : this._operatorsFor(field)[0]?.id ?? null;
|
|
26538
|
+
}
|
|
26539
|
+
/** @param {string} text @returns {void} */
|
|
26540
|
+
_announce(text4) {
|
|
26541
|
+
if (this.refs.status) this.refs.status.textContent = text4;
|
|
26542
|
+
}
|
|
26543
|
+
};
|
|
26544
|
+
function actionButton(action, id, iconName, label, props = {}) {
|
|
26545
|
+
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));
|
|
26546
|
+
}
|
|
26547
|
+
function choiceSelect(field, node, value, multiple, disabled, index = 0) {
|
|
26548
|
+
const choices = Array.isArray(field.choices) && field.choices.length ? field.choices : field.type === "boolean" ? [{ value: true, label: "True" }, { value: false, label: "False" }] : null;
|
|
26549
|
+
if (!choices) return null;
|
|
26550
|
+
const values = new Set(Array.isArray(value) ? value.map(String) : [String(value ?? "")]);
|
|
26551
|
+
return (
|
|
26552
|
+
/** @type {HTMLSelectElement} */
|
|
26553
|
+
h(
|
|
26554
|
+
"select",
|
|
26555
|
+
{
|
|
26556
|
+
dataset: { filterValue: "", nodeId: node.id, valueIndex: String(index) },
|
|
26557
|
+
ariaLabel: `${field.label} value`,
|
|
26558
|
+
multiple,
|
|
26559
|
+
disabled
|
|
26560
|
+
},
|
|
26561
|
+
!multiple ? h("option", { value: "", selected: !values.has(String(value)) || value == null }, "Choose\u2026") : null,
|
|
26562
|
+
choices.map((choice) => h("option", { value: choice.value, selected: values.has(String(choice.value)) }, choice.label))
|
|
26563
|
+
)
|
|
26564
|
+
);
|
|
26565
|
+
}
|
|
26566
|
+
function valueInput(field, id, value, index, disabled, many = false) {
|
|
26567
|
+
const type = many ? "text" : ["number", "money"].includes(field.type) ? "number" : field.type === "date" ? "date" : field.type === "datetime" ? "datetime-local" : "text";
|
|
26568
|
+
const listId = field.loadChoices ? `zx-filter-choices-${id}-${index}` : null;
|
|
26569
|
+
const input = (
|
|
26570
|
+
/** @type {HTMLInputElement} */
|
|
26571
|
+
h("input", {
|
|
26572
|
+
type,
|
|
26573
|
+
value: value ?? "",
|
|
26574
|
+
dataset: { filterValue: "", nodeId: id, valueIndex: String(index) },
|
|
26575
|
+
ariaLabel: `${field.label} value${index ? ` ${index + 1}` : ""}`,
|
|
26576
|
+
disabled,
|
|
26577
|
+
placeholder: many ? "Comma-separated values" : "",
|
|
26578
|
+
list: listId
|
|
26579
|
+
})
|
|
26580
|
+
);
|
|
26581
|
+
return h(
|
|
26582
|
+
"span",
|
|
26583
|
+
{ class: "zx-filter__value-control" },
|
|
26584
|
+
input,
|
|
26585
|
+
listId ? h("datalist", { id: listId }) : null
|
|
26586
|
+
);
|
|
26587
|
+
}
|
|
26588
|
+
function parseScalar(value, type) {
|
|
26589
|
+
if (value === "") return null;
|
|
26590
|
+
if (["number", "money"].includes(type)) {
|
|
26591
|
+
const number2 = Number(value);
|
|
26592
|
+
return Number.isFinite(number2) ? number2 : null;
|
|
26593
|
+
}
|
|
26594
|
+
if (type === "boolean") return value === "true";
|
|
26595
|
+
return value;
|
|
26596
|
+
}
|
|
26597
|
+
function parseValueJson(value) {
|
|
26598
|
+
return cloneFilterValue(value === void 0 ? null : value);
|
|
26599
|
+
}
|
|
26600
|
+
function findNode2(root, id) {
|
|
26601
|
+
if (!root || root.id === id) return root ?? null;
|
|
26602
|
+
if (root.kind === "group") for (const child of root.children) {
|
|
26603
|
+
const match = findNode2(child, id);
|
|
26604
|
+
if (match) return match;
|
|
26605
|
+
}
|
|
26606
|
+
return null;
|
|
26607
|
+
}
|
|
26608
|
+
function findGroup(root, id) {
|
|
26609
|
+
const node = findNode2(root, id);
|
|
26610
|
+
return node?.kind === "group" ? node : null;
|
|
26611
|
+
}
|
|
26612
|
+
function findParent(root, id) {
|
|
26613
|
+
if (root?.kind !== "group") return null;
|
|
26614
|
+
if (root.children.some((child) => child.id === id)) return root;
|
|
26615
|
+
for (const child of root.children) {
|
|
26616
|
+
const parent = findParent(child, id);
|
|
26617
|
+
if (parent) return parent;
|
|
26618
|
+
}
|
|
26619
|
+
return null;
|
|
26620
|
+
}
|
|
26621
|
+
function removeNode(root, id) {
|
|
26622
|
+
if (root.kind !== "group") return null;
|
|
26623
|
+
const index = root.children.findIndex((child) => child.id === id);
|
|
26624
|
+
if (index >= 0) return root.children.splice(index, 1)[0];
|
|
26625
|
+
for (const child of root.children) {
|
|
26626
|
+
const found = removeNode(child, id);
|
|
26627
|
+
if (found) return found;
|
|
26628
|
+
}
|
|
26629
|
+
return null;
|
|
26630
|
+
}
|
|
26631
|
+
function containsNode(node, id) {
|
|
26632
|
+
return Boolean(node && findNode2(node, id));
|
|
26633
|
+
}
|
|
26634
|
+
function conditionCount(node) {
|
|
26635
|
+
return node.kind === "condition" ? 1 : node.children.reduce((sum, child) => sum + conditionCount(child), 0);
|
|
26636
|
+
}
|
|
26637
|
+
function groupDepth(root, id, depth = 0) {
|
|
26638
|
+
if (root.id === id) return depth;
|
|
26639
|
+
if (root.kind === "group") for (const child of root.children) {
|
|
26640
|
+
if (child.kind === "group") {
|
|
26641
|
+
const found = groupDepth(child, id, depth + 1);
|
|
26642
|
+
if (found >= 0) return found;
|
|
26643
|
+
}
|
|
26644
|
+
}
|
|
26645
|
+
return -1;
|
|
26646
|
+
}
|
|
26647
|
+
function mergeOperators(base, extra) {
|
|
26648
|
+
const map = new Map(base.map((item) => [item.id, { ...item, types: [...item.types] }]));
|
|
26649
|
+
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] : [] });
|
|
26650
|
+
return [...map.values()];
|
|
26651
|
+
}
|
|
26652
|
+
function cssEscape(value) {
|
|
26653
|
+
return globalThis.CSS?.escape ? CSS.escape(value) : String(value).replaceAll(/[^a-zA-Z0-9_-]/g, "\\$&");
|
|
26654
|
+
}
|
|
26655
|
+
|
|
24333
26656
|
// src/components/fieldset/fieldset.js
|
|
24334
26657
|
var Fieldset = class extends Component {
|
|
24335
26658
|
static cssName = "fieldset";
|
|
@@ -25497,8 +27820,8 @@ var zx = (() => {
|
|
|
25497
27820
|
this._setBusy(true);
|
|
25498
27821
|
try {
|
|
25499
27822
|
message = await item.validate(answer, this.getAnswers());
|
|
25500
|
-
} catch (
|
|
25501
|
-
message =
|
|
27823
|
+
} catch (error2) {
|
|
27824
|
+
message = error2 instanceof Error ? error2.message : String(error2);
|
|
25502
27825
|
} finally {
|
|
25503
27826
|
if (!this._destroyed) this._setBusy(false);
|
|
25504
27827
|
}
|
|
@@ -25636,10 +27959,10 @@ var zx = (() => {
|
|
|
25636
27959
|
}
|
|
25637
27960
|
/** @param {NormalizedItem} item @returns {void} */
|
|
25638
27961
|
_paintError(item) {
|
|
25639
|
-
const
|
|
25640
|
-
if (
|
|
25641
|
-
|
|
25642
|
-
|
|
27962
|
+
const error2 = this._itemHost.querySelector(".zx-questionnaire__error");
|
|
27963
|
+
if (error2 instanceof HTMLElement) {
|
|
27964
|
+
error2.textContent = this._error;
|
|
27965
|
+
error2.hidden = !this._error;
|
|
25643
27966
|
}
|
|
25644
27967
|
if (this._error) this._itemHost.setAttribute("aria-invalid", "true");
|
|
25645
27968
|
else this._itemHost.removeAttribute("aria-invalid");
|
|
@@ -25647,7 +27970,7 @@ var zx = (() => {
|
|
|
25647
27970
|
if (field) {
|
|
25648
27971
|
if (this._error) field.setHighlight(this._error, "danger");
|
|
25649
27972
|
else field.clearHighlight();
|
|
25650
|
-
if (
|
|
27973
|
+
if (error2 instanceof HTMLElement) error2.hidden = true;
|
|
25651
27974
|
}
|
|
25652
27975
|
}
|
|
25653
27976
|
/** @returns {void} */
|