@zeyos/zx 2.2.1 → 2.3.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 +110 -0
- package/README.md +28 -2
- package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +67 -8
- package/dist/types/components/permission/permission.d.ts +3 -0
- package/dist/types/components/select/select.d.ts +36 -1
- package/dist/types/components/tabbox/tabbox.d.ts +12 -3
- package/dist/zx-compat.esm.js +302 -34
- package/dist/zx-compat.esm.min.js +1 -1
- package/dist/zx-compat.global.js +302 -34
- package/dist/zx-compat.global.min.js +3 -3
- package/dist/zx-zeyos.esm.js +280 -27
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +159 -33
- package/dist/zx.esm.js +302 -34
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +302 -34
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +420 -17
- package/docs/llms.md +54 -12
- package/package.json +1 -1
- package/src/components/date-picker/date-range-picker.css +7 -0
- package/src/components/fieldset/fieldset.css +7 -0
- package/src/components/form/form.css +7 -0
- package/src/components/multi-value-editor/multi-value-editor.css +99 -15
- package/src/components/multi-value-editor/multi-value-editor.js +235 -18
- package/src/components/navigation-bar/navigation-bar.css +7 -0
- package/src/components/permission/permission.js +3 -0
- package/src/components/search/search.css +10 -2
- package/src/components/select/select.css +6 -0
- package/src/components/select/select.js +99 -11
- package/src/components/stepper/stepper.css +12 -2
- package/src/components/tabbox/tabbox.css +13 -3
- package/src/components/tabbox/tabbox.js +34 -10
- package/styles/tokens/dark.css +13 -10
- package/styles/tokens/global.css +12 -0
- package/styles/tokens/semantic.css +9 -5
- package/styles/tokens/themes.css +72 -0
- package/styles/zx.css +1 -0
package/dist/zx.global.js
CHANGED
|
@@ -5159,18 +5159,34 @@ var zx = (() => {
|
|
|
5159
5159
|
}
|
|
5160
5160
|
|
|
5161
5161
|
// src/components/multi-value-editor/multi-value-editor.js
|
|
5162
|
+
var REORDER = Object.freeze({
|
|
5163
|
+
both: { drag: true, buttons: true },
|
|
5164
|
+
buttons: { drag: false, buttons: true },
|
|
5165
|
+
drag: { drag: true, buttons: false },
|
|
5166
|
+
none: { drag: false, buttons: false }
|
|
5167
|
+
});
|
|
5168
|
+
var PARTS = Object.freeze({
|
|
5169
|
+
value: ".zx-multi-value-editor__value",
|
|
5170
|
+
handle: ".zx-multi-value-editor__handle",
|
|
5171
|
+
up: '[data-action="up"]',
|
|
5172
|
+
down: '[data-action="down"]'
|
|
5173
|
+
});
|
|
5162
5174
|
var MultiValueEditor = class extends Component {
|
|
5163
5175
|
static cssName = "multi-value-editor";
|
|
5164
5176
|
/** @type {MultiValueEditorOptions} */
|
|
5165
|
-
static defaults = { values: [], options: null, addLabel: "Add value" };
|
|
5177
|
+
static defaults = { values: [], options: null, addLabel: "Add value", reorder: "both" };
|
|
5166
5178
|
/** @returns {HTMLElement} */
|
|
5167
5179
|
render() {
|
|
5168
5180
|
const root = (
|
|
5169
5181
|
/** @type {HTMLElement} */
|
|
5170
5182
|
this.el ?? h("div")
|
|
5171
5183
|
);
|
|
5184
|
+
this.el = root;
|
|
5172
5185
|
this._values = Array.isArray(this.options.values) ? this.options.values.map(String) : [];
|
|
5173
5186
|
this._ownedNodes = [];
|
|
5187
|
+
this._initialState = root.getAttribute("data-state");
|
|
5188
|
+
this._dragIndex = -1;
|
|
5189
|
+
this._armed = null;
|
|
5174
5190
|
this._rows = h("div", { class: "zx-multi-value-editor__rows" });
|
|
5175
5191
|
this._add = h("button", {
|
|
5176
5192
|
class: "zx-btn zx-multi-value-editor__add",
|
|
@@ -5181,6 +5197,17 @@ var zx = (() => {
|
|
|
5181
5197
|
root.append(this._rows, this._add);
|
|
5182
5198
|
this.listen(root, "click", (event) => this._handleClick(event));
|
|
5183
5199
|
this.listen(this._rows, "change", (event) => this._handleChange(event));
|
|
5200
|
+
this.listen(this._rows, "keydown", (event) => this._handleKeydown(event));
|
|
5201
|
+
if (this._reorder().drag) {
|
|
5202
|
+
this.listen(this._rows, "pointerdown", (event) => this._handlePointerDown(event));
|
|
5203
|
+
this.listen(this._rows, "dragstart", (event) => this._handleDragStart(event));
|
|
5204
|
+
this.listen(this._rows, "dragover", (event) => this._handleDragOver(event));
|
|
5205
|
+
this.listen(this._rows, "dragleave", (event) => this._handleDragLeave(event));
|
|
5206
|
+
this.listen(this._rows, "drop", (event) => this._handleDrop(event));
|
|
5207
|
+
this.listen(this._rows, "dragend", () => this._finishDrag());
|
|
5208
|
+
this.listen(document, "pointerup", () => this._disarm());
|
|
5209
|
+
this.listen(document, "pointercancel", () => this._disarm());
|
|
5210
|
+
}
|
|
5184
5211
|
this._renderRows();
|
|
5185
5212
|
return root;
|
|
5186
5213
|
}
|
|
@@ -5203,16 +5230,27 @@ var zx = (() => {
|
|
|
5203
5230
|
if (!silent) this.emit("change", { values: this.getValues() });
|
|
5204
5231
|
return this;
|
|
5205
5232
|
}
|
|
5206
|
-
/** @
|
|
5207
|
-
|
|
5233
|
+
/** @returns {{drag: boolean, buttons: boolean}} */
|
|
5234
|
+
_reorder() {
|
|
5235
|
+
return REORDER[this.options.reorder] ?? REORDER.both;
|
|
5236
|
+
}
|
|
5237
|
+
/**
|
|
5238
|
+
* @param {number} [focusIndex=-1] Row to focus after the render, or -1 for none.
|
|
5239
|
+
* @param {keyof PARTS} [focusPart='value'] Which control of that row takes focus.
|
|
5240
|
+
* @returns {void}
|
|
5241
|
+
*/
|
|
5242
|
+
_renderRows(focusIndex = -1, focusPart = "value") {
|
|
5208
5243
|
const choices = normalizeOptions2(this.options.options);
|
|
5209
5244
|
const useSelect = this.options.options !== null;
|
|
5245
|
+
const { drag, buttons } = this._reorder();
|
|
5246
|
+
const last = this._values.length - 1;
|
|
5210
5247
|
const rows = this._values.map((value, index) => {
|
|
5248
|
+
const entries = !useSelect || choices.some(([option]) => option === value) ? choices : [[value, value], ...choices];
|
|
5211
5249
|
const control = useSelect ? h("select", {
|
|
5212
5250
|
class: "zx-multi-value-editor__value",
|
|
5213
5251
|
ariaLabel: `Value ${index + 1}`,
|
|
5214
5252
|
dataset: { index }
|
|
5215
|
-
},
|
|
5253
|
+
}, entries.map(([optionValue, label]) => h("option", {
|
|
5216
5254
|
value: optionValue,
|
|
5217
5255
|
selected: optionValue === value
|
|
5218
5256
|
}, label))) : h("input", {
|
|
@@ -5225,40 +5263,52 @@ var zx = (() => {
|
|
|
5225
5263
|
return h(
|
|
5226
5264
|
"div",
|
|
5227
5265
|
{ class: "zx-multi-value-editor__row", dataset: { row: index } },
|
|
5266
|
+
drag ? h("button", {
|
|
5267
|
+
class: "zx-multi-value-editor__handle",
|
|
5268
|
+
type: "button",
|
|
5269
|
+
ariaLabel: `Reorder value ${index + 1}. Use the arrow keys to move it.`,
|
|
5270
|
+
dataset: { action: "grab", index }
|
|
5271
|
+
}, icon("drag", { size: 12 })) : null,
|
|
5228
5272
|
control,
|
|
5229
5273
|
h(
|
|
5230
5274
|
"div",
|
|
5231
5275
|
{ class: "zx-multi-value-editor__controls" },
|
|
5232
|
-
h("button", {
|
|
5276
|
+
buttons ? h("button", {
|
|
5233
5277
|
class: "zx-btn",
|
|
5234
5278
|
type: "button",
|
|
5235
|
-
disabled
|
|
5279
|
+
// `aria-disabled` rather than `disabled`: the buttons re-render on every move, and a
|
|
5280
|
+
// control that disables itself under the reader's finger drops focus to the document.
|
|
5281
|
+
ariaDisabled: String(index === 0),
|
|
5236
5282
|
ariaLabel: `Move value ${index + 1} up`,
|
|
5237
5283
|
dataset: { action: "up", index }
|
|
5238
|
-
}, "
|
|
5239
|
-
h("button", {
|
|
5284
|
+
}, icon("chevron-up", { size: 12 })) : null,
|
|
5285
|
+
buttons ? h("button", {
|
|
5240
5286
|
class: "zx-btn",
|
|
5241
5287
|
type: "button",
|
|
5242
|
-
|
|
5288
|
+
ariaDisabled: String(index === last),
|
|
5243
5289
|
ariaLabel: `Move value ${index + 1} down`,
|
|
5244
5290
|
dataset: { action: "down", index }
|
|
5245
|
-
}, "
|
|
5291
|
+
}, icon("chevron-down", { size: 12 })) : null,
|
|
5246
5292
|
h("button", {
|
|
5247
5293
|
class: "zx-btn",
|
|
5248
5294
|
type: "button",
|
|
5249
5295
|
ariaLabel: `Remove value ${index + 1}`,
|
|
5250
5296
|
dataset: { action: "remove", index }
|
|
5251
|
-
}, "
|
|
5297
|
+
}, icon("x", { size: 12 }))
|
|
5252
5298
|
)
|
|
5253
5299
|
);
|
|
5254
5300
|
});
|
|
5255
5301
|
this._rows.replaceChildren(...rows);
|
|
5256
|
-
if (focusIndex >= 0)
|
|
5302
|
+
if (focusIndex >= 0) {
|
|
5303
|
+
const row2 = rows[Math.min(focusIndex, rows.length - 1)];
|
|
5304
|
+
row2?.querySelector(PARTS[focusPart] ?? PARTS.value)?.focus();
|
|
5305
|
+
}
|
|
5257
5306
|
}
|
|
5258
5307
|
/** @param {MouseEvent} event @returns {void} */
|
|
5259
5308
|
_handleClick(event) {
|
|
5260
5309
|
const button2 = event.target instanceof Element ? event.target.closest("[data-action]") : null;
|
|
5261
5310
|
if (!button2 || !this.el.contains(button2)) return;
|
|
5311
|
+
if (button2.getAttribute("aria-disabled") === "true") return;
|
|
5262
5312
|
const action = button2.dataset.action;
|
|
5263
5313
|
const index = Number(button2.dataset.index);
|
|
5264
5314
|
if (action === "add") {
|
|
@@ -5271,28 +5321,156 @@ var zx = (() => {
|
|
|
5271
5321
|
this._renderRows(Math.min(index, this._values.length - 1));
|
|
5272
5322
|
this._emitChange();
|
|
5273
5323
|
} else if ((action === "up" || action === "down") && Number.isInteger(index)) {
|
|
5274
|
-
|
|
5275
|
-
if (target < 0 || target >= this._values.length) return;
|
|
5276
|
-
[this._values[index], this._values[target]] = [this._values[target], this._values[index]];
|
|
5277
|
-
this._renderRows(target);
|
|
5278
|
-
this._emitChange();
|
|
5324
|
+
this._move(index, index + (action === "up" ? -1 : 1), action);
|
|
5279
5325
|
}
|
|
5280
5326
|
}
|
|
5281
5327
|
/** @param {Event} event @returns {void} */
|
|
5282
5328
|
_handleChange(event) {
|
|
5283
5329
|
const control = event.target instanceof Element ? event.target.closest("[data-index]") : null;
|
|
5284
|
-
if (!control) return;
|
|
5330
|
+
if (!control || !("value" in control)) return;
|
|
5285
5331
|
const index = Number(control.dataset.index);
|
|
5286
5332
|
if (!Number.isInteger(index) || index < 0 || index >= this._values.length) return;
|
|
5287
5333
|
this._values[index] = control.value;
|
|
5288
5334
|
this._emitChange();
|
|
5289
5335
|
}
|
|
5336
|
+
/**
|
|
5337
|
+
* The handle is the keyboard equivalent of dragging it, so `reorder: 'drag'` is still operable
|
|
5338
|
+
* without a pointer.
|
|
5339
|
+
* @param {KeyboardEvent} event
|
|
5340
|
+
* @returns {void}
|
|
5341
|
+
*/
|
|
5342
|
+
_handleKeydown(event) {
|
|
5343
|
+
const handle = event.target instanceof Element ? event.target.closest(".zx-multi-value-editor__handle") : null;
|
|
5344
|
+
if (!handle) return;
|
|
5345
|
+
const index = Number(handle.dataset.index);
|
|
5346
|
+
const target = event.key === "ArrowUp" ? index - 1 : event.key === "ArrowDown" ? index + 1 : event.key === "Home" ? 0 : event.key === "End" ? this._values.length - 1 : -1;
|
|
5347
|
+
if (target < 0 || target >= this._values.length) return;
|
|
5348
|
+
event.preventDefault();
|
|
5349
|
+
this._move(index, target, "handle");
|
|
5350
|
+
}
|
|
5351
|
+
/**
|
|
5352
|
+
* Arms the row for dragging. Only the handle does this, so text selection inside a row's input
|
|
5353
|
+
* keeps working and a stray drag cannot reorder anything.
|
|
5354
|
+
* @param {PointerEvent} event
|
|
5355
|
+
* @returns {void}
|
|
5356
|
+
*/
|
|
5357
|
+
_handlePointerDown(event) {
|
|
5358
|
+
const handle = event.target instanceof Element ? event.target.closest(".zx-multi-value-editor__handle") : null;
|
|
5359
|
+
this._disarm();
|
|
5360
|
+
if (!handle || handle.disabled) return;
|
|
5361
|
+
const row2 = (
|
|
5362
|
+
/** @type {HTMLElement|null} */
|
|
5363
|
+
handle.closest(".zx-multi-value-editor__row")
|
|
5364
|
+
);
|
|
5365
|
+
if (!row2) return;
|
|
5366
|
+
row2.draggable = true;
|
|
5367
|
+
this._armed = row2;
|
|
5368
|
+
}
|
|
5369
|
+
/** @returns {void} */
|
|
5370
|
+
_disarm() {
|
|
5371
|
+
if (this._armed) this._armed.draggable = false;
|
|
5372
|
+
this._armed = null;
|
|
5373
|
+
}
|
|
5374
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5375
|
+
_handleDragStart(event) {
|
|
5376
|
+
const row2 = event.target instanceof Element ? event.target.closest(".zx-multi-value-editor__row") : null;
|
|
5377
|
+
if (!row2 || row2 !== this._armed) {
|
|
5378
|
+
event.preventDefault();
|
|
5379
|
+
return;
|
|
5380
|
+
}
|
|
5381
|
+
this._dragIndex = Number(row2.dataset.row);
|
|
5382
|
+
row2.dataset.dragging = "true";
|
|
5383
|
+
this.el.setAttribute("data-state", "dragging");
|
|
5384
|
+
event.dataTransfer?.setData("text/plain", String(this._dragIndex));
|
|
5385
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = "move";
|
|
5386
|
+
}
|
|
5387
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5388
|
+
_handleDragOver(event) {
|
|
5389
|
+
if (this._dragIndex < 0) return;
|
|
5390
|
+
event.preventDefault();
|
|
5391
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
|
|
5392
|
+
this._showDrop(this._dropTarget(event.clientY));
|
|
5393
|
+
}
|
|
5394
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5395
|
+
_handleDragLeave(event) {
|
|
5396
|
+
const to = event.relatedTarget;
|
|
5397
|
+
if (to instanceof Node && this._rows.contains(to)) return;
|
|
5398
|
+
this._clearDrop();
|
|
5399
|
+
}
|
|
5400
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5401
|
+
_handleDrop(event) {
|
|
5402
|
+
if (this._dragIndex < 0) return;
|
|
5403
|
+
event.preventDefault();
|
|
5404
|
+
const from = this._dragIndex;
|
|
5405
|
+
const slot = this._dropTarget(event.clientY);
|
|
5406
|
+
this._finishDrag();
|
|
5407
|
+
this._move(from, slot > from ? slot - 1 : slot, "handle");
|
|
5408
|
+
}
|
|
5409
|
+
/** @returns {void} */
|
|
5410
|
+
_finishDrag() {
|
|
5411
|
+
this._dragIndex = -1;
|
|
5412
|
+
this._disarm();
|
|
5413
|
+
this._clearDrop();
|
|
5414
|
+
for (const row2 of this._rows.children) delete /** @type {HTMLElement} */
|
|
5415
|
+
row2.dataset.dragging;
|
|
5416
|
+
if (this._initialState === null) this.el.removeAttribute("data-state");
|
|
5417
|
+
else this.el.setAttribute("data-state", this._initialState);
|
|
5418
|
+
}
|
|
5419
|
+
/**
|
|
5420
|
+
* Insertion slot (0…length) for a pointer at `clientY`.
|
|
5421
|
+
* @param {number} clientY Pointer position.
|
|
5422
|
+
* @returns {number}
|
|
5423
|
+
*/
|
|
5424
|
+
_dropTarget(clientY) {
|
|
5425
|
+
const rows = [...this._rows.children];
|
|
5426
|
+
for (const [index, row2] of rows.entries()) {
|
|
5427
|
+
const box = row2.getBoundingClientRect();
|
|
5428
|
+
if (clientY < box.top + box.height / 2) return index;
|
|
5429
|
+
}
|
|
5430
|
+
return rows.length;
|
|
5431
|
+
}
|
|
5432
|
+
/** @param {number} slot Insertion slot to mark. @returns {void} */
|
|
5433
|
+
_showDrop(slot) {
|
|
5434
|
+
const rows = [...this._rows.children];
|
|
5435
|
+
const inert = slot === this._dragIndex || slot === this._dragIndex + 1;
|
|
5436
|
+
for (const [index, row2] of rows.entries()) {
|
|
5437
|
+
const cell = (
|
|
5438
|
+
/** @type {HTMLElement} */
|
|
5439
|
+
row2
|
|
5440
|
+
);
|
|
5441
|
+
if (!inert && index === slot) cell.dataset.drop = "before";
|
|
5442
|
+
else if (!inert && slot === rows.length && index === rows.length - 1) cell.dataset.drop = "after";
|
|
5443
|
+
else delete cell.dataset.drop;
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5446
|
+
/** @returns {void} */
|
|
5447
|
+
_clearDrop() {
|
|
5448
|
+
for (const row2 of this._rows.children) delete /** @type {HTMLElement} */
|
|
5449
|
+
row2.dataset.drop;
|
|
5450
|
+
}
|
|
5451
|
+
/**
|
|
5452
|
+
* @param {number} from Index to move.
|
|
5453
|
+
* @param {number} to Destination index.
|
|
5454
|
+
* @param {keyof PARTS} part Control of the moved row that takes focus.
|
|
5455
|
+
* @returns {boolean} Whether anything moved.
|
|
5456
|
+
*/
|
|
5457
|
+
_move(from, to, part) {
|
|
5458
|
+
const size2 = this._values.length;
|
|
5459
|
+
if (from === to || from < 0 || to < 0 || from >= size2 || to >= size2) return false;
|
|
5460
|
+
const [value] = this._values.splice(from, 1);
|
|
5461
|
+
this._values.splice(to, 0, value);
|
|
5462
|
+
this._renderRows(to, part);
|
|
5463
|
+
this._emitChange();
|
|
5464
|
+
return true;
|
|
5465
|
+
}
|
|
5290
5466
|
/** @returns {void} */
|
|
5291
5467
|
_emitChange() {
|
|
5292
5468
|
this.emit("change", { values: this.getValues() });
|
|
5293
5469
|
}
|
|
5294
5470
|
/** @returns {void} */
|
|
5295
5471
|
destroy() {
|
|
5472
|
+
if (this._initialState === null) this.el?.removeAttribute("data-state");
|
|
5473
|
+
else this.el?.setAttribute("data-state", this._initialState);
|
|
5296
5474
|
for (const node of this._ownedNodes ?? []) node.remove();
|
|
5297
5475
|
super.destroy();
|
|
5298
5476
|
}
|
|
@@ -6362,6 +6540,7 @@ var zx = (() => {
|
|
|
6362
6540
|
/** @type {SelectOptions} */
|
|
6363
6541
|
static defaults = {
|
|
6364
6542
|
items: [],
|
|
6543
|
+
fixedItems: [],
|
|
6365
6544
|
valueKey: "ID",
|
|
6366
6545
|
labelKey: "name",
|
|
6367
6546
|
renderItem: null,
|
|
@@ -6381,6 +6560,8 @@ var zx = (() => {
|
|
|
6381
6560
|
render() {
|
|
6382
6561
|
this._items = [];
|
|
6383
6562
|
this._visibleItems = [];
|
|
6563
|
+
this._fixedItems = [];
|
|
6564
|
+
this._visibleFixed = [];
|
|
6384
6565
|
this._selected = null;
|
|
6385
6566
|
this._optionEntries = [];
|
|
6386
6567
|
this._activeIndex = -1;
|
|
@@ -6464,6 +6645,8 @@ var zx = (() => {
|
|
|
6464
6645
|
root.replaceChildren(control, panel);
|
|
6465
6646
|
this._items = Array.isArray(this.options.items) ? this.options.items.slice() : [];
|
|
6466
6647
|
this._visibleItems = this._items.slice();
|
|
6648
|
+
this._fixedItems = Array.isArray(this.options.fixedItems) ? this.options.fixedItems.slice() : [];
|
|
6649
|
+
this._visibleFixed = this._fixedItems.slice();
|
|
6467
6650
|
this._disabled = Boolean(this.options.disabled);
|
|
6468
6651
|
this._handleTypeahead = typeahead(
|
|
6469
6652
|
() => this._optionEntries.filter((entry) => !entry.clear).map((entry) => entry.element),
|
|
@@ -6540,7 +6723,22 @@ var zx = (() => {
|
|
|
6540
6723
|
const selectedValue = this.value;
|
|
6541
6724
|
this._items = Array.isArray(items) ? items.slice() : [];
|
|
6542
6725
|
this._visibleItems = this._items.slice();
|
|
6543
|
-
this._selected = selectedValue == null ? null : this.
|
|
6726
|
+
this._selected = selectedValue == null ? null : this._findItem(selectedValue);
|
|
6727
|
+
this._syncInputValue();
|
|
6728
|
+
this._renderOptions();
|
|
6729
|
+
this._position?.update();
|
|
6730
|
+
return this;
|
|
6731
|
+
}
|
|
6732
|
+
/**
|
|
6733
|
+
* Replaces the pinned choices and preserves selection by ID when possible.
|
|
6734
|
+
* @param {SelectItem[]} items New fixed items.
|
|
6735
|
+
* @returns {this}
|
|
6736
|
+
*/
|
|
6737
|
+
setFixedItems(items) {
|
|
6738
|
+
const selectedValue = this.value;
|
|
6739
|
+
this._fixedItems = Array.isArray(items) ? items.slice() : [];
|
|
6740
|
+
this._visibleFixed = this._fixedItems.slice();
|
|
6741
|
+
this._selected = selectedValue == null ? null : this._findItem(selectedValue);
|
|
6544
6742
|
this._syncInputValue();
|
|
6545
6743
|
this._renderOptions();
|
|
6546
6744
|
this._position?.update();
|
|
@@ -6594,6 +6792,7 @@ var zx = (() => {
|
|
|
6594
6792
|
this.refs.input.removeAttribute("aria-activedescendant");
|
|
6595
6793
|
this._activeIndex = -1;
|
|
6596
6794
|
this._visibleItems = this._items.slice();
|
|
6795
|
+
this._visibleFixed = this._fixedItems.slice();
|
|
6597
6796
|
this._syncInputValue();
|
|
6598
6797
|
this.emit("close", {});
|
|
6599
6798
|
return this;
|
|
@@ -6649,6 +6848,38 @@ var zx = (() => {
|
|
|
6649
6848
|
select.set(requestedValue, { silent: true });
|
|
6650
6849
|
return select;
|
|
6651
6850
|
}
|
|
6851
|
+
/**
|
|
6852
|
+
* Creates the record-permission picker: the fixed Private and Public choices above the groups a
|
|
6853
|
+
* record can be shared with. The value is the tri-state ZeyOS stores — `false` for private,
|
|
6854
|
+
* `true` for public, or a group ID — so the control binds straight to the record field.
|
|
6855
|
+
*
|
|
6856
|
+
* @param {Element|string|null} target Component target.
|
|
6857
|
+
* @param {SelectOptions & {groups?: SelectItem[]|((query: string) => Promise<SelectItem[]>|SelectItem[])}} [options={}]
|
|
6858
|
+
* Select overrides other than the fixed choices; `groups` holds the shareable groups, either as
|
|
6859
|
+
* an array (filtered locally) or as a loader function (queried remotely).
|
|
6860
|
+
* @returns {Select}
|
|
6861
|
+
*/
|
|
6862
|
+
static permission(target, options = {}) {
|
|
6863
|
+
const { groups = [], value = true, valueKey = "ID", labelKey = "name", ...rest } = options;
|
|
6864
|
+
const loader = typeof groups === "function" ? groups : null;
|
|
6865
|
+
const shareable = loader ? [] : groups;
|
|
6866
|
+
const choices = [{ ID: false, name: "Private" }, { ID: true, name: "Public" }];
|
|
6867
|
+
const select = new _Select(target, {
|
|
6868
|
+
filter: loader ?? "local",
|
|
6869
|
+
...rest,
|
|
6870
|
+
value: null,
|
|
6871
|
+
items: shareable,
|
|
6872
|
+
fixedItems: [],
|
|
6873
|
+
// The two choices carry their own ID and label, so a caller's group readers stay untouched.
|
|
6874
|
+
valueKey: (item) => choices.includes(item) ? item.ID : readItem(item, valueKey),
|
|
6875
|
+
labelKey: (item) => choices.includes(item) ? item.name : String(readItem(item, labelKey) ?? "")
|
|
6876
|
+
});
|
|
6877
|
+
choices[0].name = select._message("permission.private", "Private");
|
|
6878
|
+
choices[1].name = select._message("permission.public", "Public");
|
|
6879
|
+
select.setFixedItems(choices);
|
|
6880
|
+
select.set(value, { silent: true });
|
|
6881
|
+
return select;
|
|
6882
|
+
}
|
|
6652
6883
|
/** Cleans up the panel, pending async work, and enhanced target contents. @returns {void} */
|
|
6653
6884
|
destroy() {
|
|
6654
6885
|
this._cancelQuery();
|
|
@@ -6719,8 +6950,8 @@ var zx = (() => {
|
|
|
6719
6950
|
);
|
|
6720
6951
|
if (!this._open) this.open();
|
|
6721
6952
|
if (this.options.filter === "local") {
|
|
6722
|
-
|
|
6723
|
-
this._visibleItems = matchItems(this._items, query,
|
|
6953
|
+
this._visibleFixed = this._matchFixed(query);
|
|
6954
|
+
this._visibleItems = matchItems(this._items, query, this._searchKeys());
|
|
6724
6955
|
this._renderOptions();
|
|
6725
6956
|
this._setActive(this._optionEntries.length > 0 ? 0 : -1);
|
|
6726
6957
|
this._position?.update();
|
|
@@ -6775,6 +7006,10 @@ var zx = (() => {
|
|
|
6775
7006
|
if (this.options.clearable) {
|
|
6776
7007
|
this._appendOption(null, true, this.options.placeholder || this._message("select.none", "No selection"));
|
|
6777
7008
|
}
|
|
7009
|
+
for (const item of this._visibleFixed) this._appendOption(item, false);
|
|
7010
|
+
if (this._visibleFixed.length > 0 && this._visibleItems.length > 0 && !this.options.groupKey) {
|
|
7011
|
+
list.append(h("div", { class: "zx-select__separator", role: "presentation" }));
|
|
7012
|
+
}
|
|
6778
7013
|
for (const item of this._visibleItems) {
|
|
6779
7014
|
if (this.options.groupKey) {
|
|
6780
7015
|
const group = this._read(item, this.options.groupKey);
|
|
@@ -6788,7 +7023,7 @@ var zx = (() => {
|
|
|
6788
7023
|
}
|
|
6789
7024
|
this._appendOption(item, false);
|
|
6790
7025
|
}
|
|
6791
|
-
if (this._visibleItems.length === 0 && !this._loading) {
|
|
7026
|
+
if (this._visibleItems.length === 0 && this._visibleFixed.length === 0 && !this._loading) {
|
|
6792
7027
|
list.append(h("div", {
|
|
6793
7028
|
class: "zx-select__empty",
|
|
6794
7029
|
role: "status"
|
|
@@ -6828,6 +7063,7 @@ var zx = (() => {
|
|
|
6828
7063
|
if (this._queryTimer !== null) clearTimeout(this._queryTimer);
|
|
6829
7064
|
this._requestSequence += 1;
|
|
6830
7065
|
const sequence = this._requestSequence;
|
|
7066
|
+
this._visibleFixed = this._matchFixed(query);
|
|
6831
7067
|
if (query.length < Math.max(0, Number(this.options.minQuery) || 0)) {
|
|
6832
7068
|
this._setLoading(false);
|
|
6833
7069
|
this._visibleItems = [];
|
|
@@ -6912,13 +7148,14 @@ var zx = (() => {
|
|
|
6912
7148
|
}
|
|
6913
7149
|
/** @param {unknown} candidate @returns {SelectItem|null} */
|
|
6914
7150
|
_findItem(candidate) {
|
|
6915
|
-
const
|
|
7151
|
+
const pool = this._fixedItems.length === 0 ? this._items : [...this._fixedItems, ...this._items];
|
|
7152
|
+
const direct = pool.find((item) => item === candidate);
|
|
6916
7153
|
if (direct !== void 0) return direct;
|
|
6917
7154
|
const value = candidate !== null && typeof candidate === "object" ? this._itemValue(
|
|
6918
7155
|
/** @type {SelectItem} */
|
|
6919
7156
|
candidate
|
|
6920
7157
|
) : candidate;
|
|
6921
|
-
return
|
|
7158
|
+
return pool.find((item) => Object.is(this._itemValue(item), value)) ?? null;
|
|
6922
7159
|
}
|
|
6923
7160
|
/** @param {SelectItem|null} item @returns {unknown} */
|
|
6924
7161
|
_itemValue(item) {
|
|
@@ -6931,9 +7168,20 @@ var zx = (() => {
|
|
|
6931
7168
|
}
|
|
6932
7169
|
/** @param {SelectItem|null} item @param {SelectValueReader|SelectLabelReader} reader @returns {unknown} */
|
|
6933
7170
|
_read(item, reader) {
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
7171
|
+
return readItem(item, reader);
|
|
7172
|
+
}
|
|
7173
|
+
/** @returns {Array<SelectValueReader|SelectLabelReader>} */
|
|
7174
|
+
_searchKeys() {
|
|
7175
|
+
return this.options.searchKeys ?? [this.options.labelKey];
|
|
7176
|
+
}
|
|
7177
|
+
/**
|
|
7178
|
+
* Narrows the pinned choices. They are always matched here and never handed to a filter
|
|
7179
|
+
* function: a fixed choice belongs to the control, not to whatever the remote source returns.
|
|
7180
|
+
* @param {string} query Search text.
|
|
7181
|
+
* @returns {SelectItem[]}
|
|
7182
|
+
*/
|
|
7183
|
+
_matchFixed(query) {
|
|
7184
|
+
return matchItems(this._fixedItems, query, this._searchKeys());
|
|
6937
7185
|
}
|
|
6938
7186
|
/** @returns {number} */
|
|
6939
7187
|
_selectedEntryIndex() {
|
|
@@ -6956,6 +7204,11 @@ var zx = (() => {
|
|
|
6956
7204
|
return message === key ? fallback : message;
|
|
6957
7205
|
}
|
|
6958
7206
|
};
|
|
7207
|
+
function readItem(item, reader) {
|
|
7208
|
+
if (typeof reader === "function") return reader(item);
|
|
7209
|
+
if (item === null || typeof item !== "object") return item;
|
|
7210
|
+
return item[reader];
|
|
7211
|
+
}
|
|
6959
7212
|
function isPrintable(event) {
|
|
6960
7213
|
return event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
6961
7214
|
}
|
|
@@ -10469,6 +10722,10 @@ var zx = (() => {
|
|
|
10469
10722
|
if (!tab || !tablist.contains(tab)) return;
|
|
10470
10723
|
const record = this._recordForTab(tab);
|
|
10471
10724
|
if (!record || record.definition.disabled) return;
|
|
10725
|
+
if (event.target.closest?.(".zx-tabbox__close")) {
|
|
10726
|
+
this._closeTab(record);
|
|
10727
|
+
return;
|
|
10728
|
+
}
|
|
10472
10729
|
this._setFocused(record);
|
|
10473
10730
|
this.openTab(record.definition.name);
|
|
10474
10731
|
});
|
|
@@ -10518,8 +10775,9 @@ var zx = (() => {
|
|
|
10518
10775
|
children.push(title, badge2);
|
|
10519
10776
|
if (normalized.closable) {
|
|
10520
10777
|
children.push(h("span", {
|
|
10521
|
-
class: "zx-tabbox__close",
|
|
10522
|
-
ariaHidden: "true"
|
|
10778
|
+
class: "zx-tabbox__close zx-icon-btn",
|
|
10779
|
+
ariaHidden: "true",
|
|
10780
|
+
title: "Close"
|
|
10523
10781
|
}, icon("x", { size: 14 })));
|
|
10524
10782
|
}
|
|
10525
10783
|
const tab = (
|
|
@@ -10693,13 +10951,23 @@ var zx = (() => {
|
|
|
10693
10951
|
}
|
|
10694
10952
|
if (event.key === "Delete" && current.definition.closable) {
|
|
10695
10953
|
event.preventDefault();
|
|
10696
|
-
|
|
10697
|
-
if (!this._removeTab(current.definition.name, true)) return;
|
|
10698
|
-
const remaining = this._tabs[Math.min(index, this._tabs.length - 1)] ?? this._tabs[index - 1];
|
|
10699
|
-
const focusTarget = remaining && !remaining.definition.disabled ? remaining : this._active;
|
|
10700
|
-
if (focusTarget) this._setFocused(focusTarget);
|
|
10954
|
+
this._closeTab(current);
|
|
10701
10955
|
}
|
|
10702
10956
|
}
|
|
10957
|
+
/**
|
|
10958
|
+
* Closes a tab on a user gesture, keeping roving focus inside the tablist.
|
|
10959
|
+
* @param {TabRecord} record Tab to close.
|
|
10960
|
+
* @returns {void}
|
|
10961
|
+
* @fires Tabbox#close
|
|
10962
|
+
*/
|
|
10963
|
+
_closeTab(record) {
|
|
10964
|
+
const index = this._tabs.indexOf(record);
|
|
10965
|
+
const held = record.tab.contains(document.activeElement);
|
|
10966
|
+
if (!this._removeTab(record.definition.name, true)) return;
|
|
10967
|
+
const remaining = this._tabs[Math.min(index, this._tabs.length - 1)] ?? this._tabs[index - 1];
|
|
10968
|
+
const focusTarget = remaining && !remaining.definition.disabled ? remaining : this._active;
|
|
10969
|
+
if (focusTarget) this._setFocused(focusTarget, held);
|
|
10970
|
+
}
|
|
10703
10971
|
/** @param {string} name @param {boolean} emitClose @returns {boolean} */
|
|
10704
10972
|
_removeTab(name, emitClose) {
|
|
10705
10973
|
const record = this._find(name);
|