@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-compat.global.js
CHANGED
|
@@ -5147,18 +5147,34 @@
|
|
|
5147
5147
|
}
|
|
5148
5148
|
|
|
5149
5149
|
// src/components/multi-value-editor/multi-value-editor.js
|
|
5150
|
+
var REORDER = Object.freeze({
|
|
5151
|
+
both: { drag: true, buttons: true },
|
|
5152
|
+
buttons: { drag: false, buttons: true },
|
|
5153
|
+
drag: { drag: true, buttons: false },
|
|
5154
|
+
none: { drag: false, buttons: false }
|
|
5155
|
+
});
|
|
5156
|
+
var PARTS = Object.freeze({
|
|
5157
|
+
value: ".zx-multi-value-editor__value",
|
|
5158
|
+
handle: ".zx-multi-value-editor__handle",
|
|
5159
|
+
up: '[data-action="up"]',
|
|
5160
|
+
down: '[data-action="down"]'
|
|
5161
|
+
});
|
|
5150
5162
|
var MultiValueEditor = class extends Component {
|
|
5151
5163
|
static cssName = "multi-value-editor";
|
|
5152
5164
|
/** @type {MultiValueEditorOptions} */
|
|
5153
|
-
static defaults = { values: [], options: null, addLabel: "Add value" };
|
|
5165
|
+
static defaults = { values: [], options: null, addLabel: "Add value", reorder: "both" };
|
|
5154
5166
|
/** @returns {HTMLElement} */
|
|
5155
5167
|
render() {
|
|
5156
5168
|
const root = (
|
|
5157
5169
|
/** @type {HTMLElement} */
|
|
5158
5170
|
this.el ?? h("div")
|
|
5159
5171
|
);
|
|
5172
|
+
this.el = root;
|
|
5160
5173
|
this._values = Array.isArray(this.options.values) ? this.options.values.map(String) : [];
|
|
5161
5174
|
this._ownedNodes = [];
|
|
5175
|
+
this._initialState = root.getAttribute("data-state");
|
|
5176
|
+
this._dragIndex = -1;
|
|
5177
|
+
this._armed = null;
|
|
5162
5178
|
this._rows = h("div", { class: "zx-multi-value-editor__rows" });
|
|
5163
5179
|
this._add = h("button", {
|
|
5164
5180
|
class: "zx-btn zx-multi-value-editor__add",
|
|
@@ -5169,6 +5185,17 @@
|
|
|
5169
5185
|
root.append(this._rows, this._add);
|
|
5170
5186
|
this.listen(root, "click", (event) => this._handleClick(event));
|
|
5171
5187
|
this.listen(this._rows, "change", (event) => this._handleChange(event));
|
|
5188
|
+
this.listen(this._rows, "keydown", (event) => this._handleKeydown(event));
|
|
5189
|
+
if (this._reorder().drag) {
|
|
5190
|
+
this.listen(this._rows, "pointerdown", (event) => this._handlePointerDown(event));
|
|
5191
|
+
this.listen(this._rows, "dragstart", (event) => this._handleDragStart(event));
|
|
5192
|
+
this.listen(this._rows, "dragover", (event) => this._handleDragOver(event));
|
|
5193
|
+
this.listen(this._rows, "dragleave", (event) => this._handleDragLeave(event));
|
|
5194
|
+
this.listen(this._rows, "drop", (event) => this._handleDrop(event));
|
|
5195
|
+
this.listen(this._rows, "dragend", () => this._finishDrag());
|
|
5196
|
+
this.listen(document, "pointerup", () => this._disarm());
|
|
5197
|
+
this.listen(document, "pointercancel", () => this._disarm());
|
|
5198
|
+
}
|
|
5172
5199
|
this._renderRows();
|
|
5173
5200
|
return root;
|
|
5174
5201
|
}
|
|
@@ -5191,16 +5218,27 @@
|
|
|
5191
5218
|
if (!silent) this.emit("change", { values: this.getValues() });
|
|
5192
5219
|
return this;
|
|
5193
5220
|
}
|
|
5194
|
-
/** @
|
|
5195
|
-
|
|
5221
|
+
/** @returns {{drag: boolean, buttons: boolean}} */
|
|
5222
|
+
_reorder() {
|
|
5223
|
+
return REORDER[this.options.reorder] ?? REORDER.both;
|
|
5224
|
+
}
|
|
5225
|
+
/**
|
|
5226
|
+
* @param {number} [focusIndex=-1] Row to focus after the render, or -1 for none.
|
|
5227
|
+
* @param {keyof PARTS} [focusPart='value'] Which control of that row takes focus.
|
|
5228
|
+
* @returns {void}
|
|
5229
|
+
*/
|
|
5230
|
+
_renderRows(focusIndex = -1, focusPart = "value") {
|
|
5196
5231
|
const choices = normalizeOptions2(this.options.options);
|
|
5197
5232
|
const useSelect = this.options.options !== null;
|
|
5233
|
+
const { drag, buttons } = this._reorder();
|
|
5234
|
+
const last = this._values.length - 1;
|
|
5198
5235
|
const rows = this._values.map((value, index) => {
|
|
5236
|
+
const entries = !useSelect || choices.some(([option]) => option === value) ? choices : [[value, value], ...choices];
|
|
5199
5237
|
const control = useSelect ? h("select", {
|
|
5200
5238
|
class: "zx-multi-value-editor__value",
|
|
5201
5239
|
ariaLabel: `Value ${index + 1}`,
|
|
5202
5240
|
dataset: { index }
|
|
5203
|
-
},
|
|
5241
|
+
}, entries.map(([optionValue, label]) => h("option", {
|
|
5204
5242
|
value: optionValue,
|
|
5205
5243
|
selected: optionValue === value
|
|
5206
5244
|
}, label))) : h("input", {
|
|
@@ -5213,40 +5251,52 @@
|
|
|
5213
5251
|
return h(
|
|
5214
5252
|
"div",
|
|
5215
5253
|
{ class: "zx-multi-value-editor__row", dataset: { row: index } },
|
|
5254
|
+
drag ? h("button", {
|
|
5255
|
+
class: "zx-multi-value-editor__handle",
|
|
5256
|
+
type: "button",
|
|
5257
|
+
ariaLabel: `Reorder value ${index + 1}. Use the arrow keys to move it.`,
|
|
5258
|
+
dataset: { action: "grab", index }
|
|
5259
|
+
}, icon("drag", { size: 12 })) : null,
|
|
5216
5260
|
control,
|
|
5217
5261
|
h(
|
|
5218
5262
|
"div",
|
|
5219
5263
|
{ class: "zx-multi-value-editor__controls" },
|
|
5220
|
-
h("button", {
|
|
5264
|
+
buttons ? h("button", {
|
|
5221
5265
|
class: "zx-btn",
|
|
5222
5266
|
type: "button",
|
|
5223
|
-
disabled
|
|
5267
|
+
// `aria-disabled` rather than `disabled`: the buttons re-render on every move, and a
|
|
5268
|
+
// control that disables itself under the reader's finger drops focus to the document.
|
|
5269
|
+
ariaDisabled: String(index === 0),
|
|
5224
5270
|
ariaLabel: `Move value ${index + 1} up`,
|
|
5225
5271
|
dataset: { action: "up", index }
|
|
5226
|
-
}, "
|
|
5227
|
-
h("button", {
|
|
5272
|
+
}, icon("chevron-up", { size: 12 })) : null,
|
|
5273
|
+
buttons ? h("button", {
|
|
5228
5274
|
class: "zx-btn",
|
|
5229
5275
|
type: "button",
|
|
5230
|
-
|
|
5276
|
+
ariaDisabled: String(index === last),
|
|
5231
5277
|
ariaLabel: `Move value ${index + 1} down`,
|
|
5232
5278
|
dataset: { action: "down", index }
|
|
5233
|
-
}, "
|
|
5279
|
+
}, icon("chevron-down", { size: 12 })) : null,
|
|
5234
5280
|
h("button", {
|
|
5235
5281
|
class: "zx-btn",
|
|
5236
5282
|
type: "button",
|
|
5237
5283
|
ariaLabel: `Remove value ${index + 1}`,
|
|
5238
5284
|
dataset: { action: "remove", index }
|
|
5239
|
-
}, "
|
|
5285
|
+
}, icon("x", { size: 12 }))
|
|
5240
5286
|
)
|
|
5241
5287
|
);
|
|
5242
5288
|
});
|
|
5243
5289
|
this._rows.replaceChildren(...rows);
|
|
5244
|
-
if (focusIndex >= 0)
|
|
5290
|
+
if (focusIndex >= 0) {
|
|
5291
|
+
const row2 = rows[Math.min(focusIndex, rows.length - 1)];
|
|
5292
|
+
row2?.querySelector(PARTS[focusPart] ?? PARTS.value)?.focus();
|
|
5293
|
+
}
|
|
5245
5294
|
}
|
|
5246
5295
|
/** @param {MouseEvent} event @returns {void} */
|
|
5247
5296
|
_handleClick(event) {
|
|
5248
5297
|
const button2 = event.target instanceof Element ? event.target.closest("[data-action]") : null;
|
|
5249
5298
|
if (!button2 || !this.el.contains(button2)) return;
|
|
5299
|
+
if (button2.getAttribute("aria-disabled") === "true") return;
|
|
5250
5300
|
const action = button2.dataset.action;
|
|
5251
5301
|
const index = Number(button2.dataset.index);
|
|
5252
5302
|
if (action === "add") {
|
|
@@ -5259,28 +5309,156 @@
|
|
|
5259
5309
|
this._renderRows(Math.min(index, this._values.length - 1));
|
|
5260
5310
|
this._emitChange();
|
|
5261
5311
|
} else if ((action === "up" || action === "down") && Number.isInteger(index)) {
|
|
5262
|
-
|
|
5263
|
-
if (target < 0 || target >= this._values.length) return;
|
|
5264
|
-
[this._values[index], this._values[target]] = [this._values[target], this._values[index]];
|
|
5265
|
-
this._renderRows(target);
|
|
5266
|
-
this._emitChange();
|
|
5312
|
+
this._move(index, index + (action === "up" ? -1 : 1), action);
|
|
5267
5313
|
}
|
|
5268
5314
|
}
|
|
5269
5315
|
/** @param {Event} event @returns {void} */
|
|
5270
5316
|
_handleChange(event) {
|
|
5271
5317
|
const control = event.target instanceof Element ? event.target.closest("[data-index]") : null;
|
|
5272
|
-
if (!control) return;
|
|
5318
|
+
if (!control || !("value" in control)) return;
|
|
5273
5319
|
const index = Number(control.dataset.index);
|
|
5274
5320
|
if (!Number.isInteger(index) || index < 0 || index >= this._values.length) return;
|
|
5275
5321
|
this._values[index] = control.value;
|
|
5276
5322
|
this._emitChange();
|
|
5277
5323
|
}
|
|
5324
|
+
/**
|
|
5325
|
+
* The handle is the keyboard equivalent of dragging it, so `reorder: 'drag'` is still operable
|
|
5326
|
+
* without a pointer.
|
|
5327
|
+
* @param {KeyboardEvent} event
|
|
5328
|
+
* @returns {void}
|
|
5329
|
+
*/
|
|
5330
|
+
_handleKeydown(event) {
|
|
5331
|
+
const handle = event.target instanceof Element ? event.target.closest(".zx-multi-value-editor__handle") : null;
|
|
5332
|
+
if (!handle) return;
|
|
5333
|
+
const index = Number(handle.dataset.index);
|
|
5334
|
+
const target = event.key === "ArrowUp" ? index - 1 : event.key === "ArrowDown" ? index + 1 : event.key === "Home" ? 0 : event.key === "End" ? this._values.length - 1 : -1;
|
|
5335
|
+
if (target < 0 || target >= this._values.length) return;
|
|
5336
|
+
event.preventDefault();
|
|
5337
|
+
this._move(index, target, "handle");
|
|
5338
|
+
}
|
|
5339
|
+
/**
|
|
5340
|
+
* Arms the row for dragging. Only the handle does this, so text selection inside a row's input
|
|
5341
|
+
* keeps working and a stray drag cannot reorder anything.
|
|
5342
|
+
* @param {PointerEvent} event
|
|
5343
|
+
* @returns {void}
|
|
5344
|
+
*/
|
|
5345
|
+
_handlePointerDown(event) {
|
|
5346
|
+
const handle = event.target instanceof Element ? event.target.closest(".zx-multi-value-editor__handle") : null;
|
|
5347
|
+
this._disarm();
|
|
5348
|
+
if (!handle || handle.disabled) return;
|
|
5349
|
+
const row2 = (
|
|
5350
|
+
/** @type {HTMLElement|null} */
|
|
5351
|
+
handle.closest(".zx-multi-value-editor__row")
|
|
5352
|
+
);
|
|
5353
|
+
if (!row2) return;
|
|
5354
|
+
row2.draggable = true;
|
|
5355
|
+
this._armed = row2;
|
|
5356
|
+
}
|
|
5357
|
+
/** @returns {void} */
|
|
5358
|
+
_disarm() {
|
|
5359
|
+
if (this._armed) this._armed.draggable = false;
|
|
5360
|
+
this._armed = null;
|
|
5361
|
+
}
|
|
5362
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5363
|
+
_handleDragStart(event) {
|
|
5364
|
+
const row2 = event.target instanceof Element ? event.target.closest(".zx-multi-value-editor__row") : null;
|
|
5365
|
+
if (!row2 || row2 !== this._armed) {
|
|
5366
|
+
event.preventDefault();
|
|
5367
|
+
return;
|
|
5368
|
+
}
|
|
5369
|
+
this._dragIndex = Number(row2.dataset.row);
|
|
5370
|
+
row2.dataset.dragging = "true";
|
|
5371
|
+
this.el.setAttribute("data-state", "dragging");
|
|
5372
|
+
event.dataTransfer?.setData("text/plain", String(this._dragIndex));
|
|
5373
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = "move";
|
|
5374
|
+
}
|
|
5375
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5376
|
+
_handleDragOver(event) {
|
|
5377
|
+
if (this._dragIndex < 0) return;
|
|
5378
|
+
event.preventDefault();
|
|
5379
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
|
|
5380
|
+
this._showDrop(this._dropTarget(event.clientY));
|
|
5381
|
+
}
|
|
5382
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5383
|
+
_handleDragLeave(event) {
|
|
5384
|
+
const to = event.relatedTarget;
|
|
5385
|
+
if (to instanceof Node && this._rows.contains(to)) return;
|
|
5386
|
+
this._clearDrop();
|
|
5387
|
+
}
|
|
5388
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5389
|
+
_handleDrop(event) {
|
|
5390
|
+
if (this._dragIndex < 0) return;
|
|
5391
|
+
event.preventDefault();
|
|
5392
|
+
const from = this._dragIndex;
|
|
5393
|
+
const slot = this._dropTarget(event.clientY);
|
|
5394
|
+
this._finishDrag();
|
|
5395
|
+
this._move(from, slot > from ? slot - 1 : slot, "handle");
|
|
5396
|
+
}
|
|
5397
|
+
/** @returns {void} */
|
|
5398
|
+
_finishDrag() {
|
|
5399
|
+
this._dragIndex = -1;
|
|
5400
|
+
this._disarm();
|
|
5401
|
+
this._clearDrop();
|
|
5402
|
+
for (const row2 of this._rows.children) delete /** @type {HTMLElement} */
|
|
5403
|
+
row2.dataset.dragging;
|
|
5404
|
+
if (this._initialState === null) this.el.removeAttribute("data-state");
|
|
5405
|
+
else this.el.setAttribute("data-state", this._initialState);
|
|
5406
|
+
}
|
|
5407
|
+
/**
|
|
5408
|
+
* Insertion slot (0…length) for a pointer at `clientY`.
|
|
5409
|
+
* @param {number} clientY Pointer position.
|
|
5410
|
+
* @returns {number}
|
|
5411
|
+
*/
|
|
5412
|
+
_dropTarget(clientY) {
|
|
5413
|
+
const rows = [...this._rows.children];
|
|
5414
|
+
for (const [index, row2] of rows.entries()) {
|
|
5415
|
+
const box = row2.getBoundingClientRect();
|
|
5416
|
+
if (clientY < box.top + box.height / 2) return index;
|
|
5417
|
+
}
|
|
5418
|
+
return rows.length;
|
|
5419
|
+
}
|
|
5420
|
+
/** @param {number} slot Insertion slot to mark. @returns {void} */
|
|
5421
|
+
_showDrop(slot) {
|
|
5422
|
+
const rows = [...this._rows.children];
|
|
5423
|
+
const inert = slot === this._dragIndex || slot === this._dragIndex + 1;
|
|
5424
|
+
for (const [index, row2] of rows.entries()) {
|
|
5425
|
+
const cell = (
|
|
5426
|
+
/** @type {HTMLElement} */
|
|
5427
|
+
row2
|
|
5428
|
+
);
|
|
5429
|
+
if (!inert && index === slot) cell.dataset.drop = "before";
|
|
5430
|
+
else if (!inert && slot === rows.length && index === rows.length - 1) cell.dataset.drop = "after";
|
|
5431
|
+
else delete cell.dataset.drop;
|
|
5432
|
+
}
|
|
5433
|
+
}
|
|
5434
|
+
/** @returns {void} */
|
|
5435
|
+
_clearDrop() {
|
|
5436
|
+
for (const row2 of this._rows.children) delete /** @type {HTMLElement} */
|
|
5437
|
+
row2.dataset.drop;
|
|
5438
|
+
}
|
|
5439
|
+
/**
|
|
5440
|
+
* @param {number} from Index to move.
|
|
5441
|
+
* @param {number} to Destination index.
|
|
5442
|
+
* @param {keyof PARTS} part Control of the moved row that takes focus.
|
|
5443
|
+
* @returns {boolean} Whether anything moved.
|
|
5444
|
+
*/
|
|
5445
|
+
_move(from, to, part) {
|
|
5446
|
+
const size2 = this._values.length;
|
|
5447
|
+
if (from === to || from < 0 || to < 0 || from >= size2 || to >= size2) return false;
|
|
5448
|
+
const [value] = this._values.splice(from, 1);
|
|
5449
|
+
this._values.splice(to, 0, value);
|
|
5450
|
+
this._renderRows(to, part);
|
|
5451
|
+
this._emitChange();
|
|
5452
|
+
return true;
|
|
5453
|
+
}
|
|
5278
5454
|
/** @returns {void} */
|
|
5279
5455
|
_emitChange() {
|
|
5280
5456
|
this.emit("change", { values: this.getValues() });
|
|
5281
5457
|
}
|
|
5282
5458
|
/** @returns {void} */
|
|
5283
5459
|
destroy() {
|
|
5460
|
+
if (this._initialState === null) this.el?.removeAttribute("data-state");
|
|
5461
|
+
else this.el?.setAttribute("data-state", this._initialState);
|
|
5284
5462
|
for (const node of this._ownedNodes ?? []) node.remove();
|
|
5285
5463
|
super.destroy();
|
|
5286
5464
|
}
|
|
@@ -6350,6 +6528,7 @@
|
|
|
6350
6528
|
/** @type {SelectOptions} */
|
|
6351
6529
|
static defaults = {
|
|
6352
6530
|
items: [],
|
|
6531
|
+
fixedItems: [],
|
|
6353
6532
|
valueKey: "ID",
|
|
6354
6533
|
labelKey: "name",
|
|
6355
6534
|
renderItem: null,
|
|
@@ -6369,6 +6548,8 @@
|
|
|
6369
6548
|
render() {
|
|
6370
6549
|
this._items = [];
|
|
6371
6550
|
this._visibleItems = [];
|
|
6551
|
+
this._fixedItems = [];
|
|
6552
|
+
this._visibleFixed = [];
|
|
6372
6553
|
this._selected = null;
|
|
6373
6554
|
this._optionEntries = [];
|
|
6374
6555
|
this._activeIndex = -1;
|
|
@@ -6452,6 +6633,8 @@
|
|
|
6452
6633
|
root.replaceChildren(control, panel);
|
|
6453
6634
|
this._items = Array.isArray(this.options.items) ? this.options.items.slice() : [];
|
|
6454
6635
|
this._visibleItems = this._items.slice();
|
|
6636
|
+
this._fixedItems = Array.isArray(this.options.fixedItems) ? this.options.fixedItems.slice() : [];
|
|
6637
|
+
this._visibleFixed = this._fixedItems.slice();
|
|
6455
6638
|
this._disabled = Boolean(this.options.disabled);
|
|
6456
6639
|
this._handleTypeahead = typeahead(
|
|
6457
6640
|
() => this._optionEntries.filter((entry) => !entry.clear).map((entry) => entry.element),
|
|
@@ -6528,7 +6711,22 @@
|
|
|
6528
6711
|
const selectedValue = this.value;
|
|
6529
6712
|
this._items = Array.isArray(items) ? items.slice() : [];
|
|
6530
6713
|
this._visibleItems = this._items.slice();
|
|
6531
|
-
this._selected = selectedValue == null ? null : this.
|
|
6714
|
+
this._selected = selectedValue == null ? null : this._findItem(selectedValue);
|
|
6715
|
+
this._syncInputValue();
|
|
6716
|
+
this._renderOptions();
|
|
6717
|
+
this._position?.update();
|
|
6718
|
+
return this;
|
|
6719
|
+
}
|
|
6720
|
+
/**
|
|
6721
|
+
* Replaces the pinned choices and preserves selection by ID when possible.
|
|
6722
|
+
* @param {SelectItem[]} items New fixed items.
|
|
6723
|
+
* @returns {this}
|
|
6724
|
+
*/
|
|
6725
|
+
setFixedItems(items) {
|
|
6726
|
+
const selectedValue = this.value;
|
|
6727
|
+
this._fixedItems = Array.isArray(items) ? items.slice() : [];
|
|
6728
|
+
this._visibleFixed = this._fixedItems.slice();
|
|
6729
|
+
this._selected = selectedValue == null ? null : this._findItem(selectedValue);
|
|
6532
6730
|
this._syncInputValue();
|
|
6533
6731
|
this._renderOptions();
|
|
6534
6732
|
this._position?.update();
|
|
@@ -6582,6 +6780,7 @@
|
|
|
6582
6780
|
this.refs.input.removeAttribute("aria-activedescendant");
|
|
6583
6781
|
this._activeIndex = -1;
|
|
6584
6782
|
this._visibleItems = this._items.slice();
|
|
6783
|
+
this._visibleFixed = this._fixedItems.slice();
|
|
6585
6784
|
this._syncInputValue();
|
|
6586
6785
|
this.emit("close", {});
|
|
6587
6786
|
return this;
|
|
@@ -6637,6 +6836,38 @@
|
|
|
6637
6836
|
select.set(requestedValue, { silent: true });
|
|
6638
6837
|
return select;
|
|
6639
6838
|
}
|
|
6839
|
+
/**
|
|
6840
|
+
* Creates the record-permission picker: the fixed Private and Public choices above the groups a
|
|
6841
|
+
* record can be shared with. The value is the tri-state ZeyOS stores — `false` for private,
|
|
6842
|
+
* `true` for public, or a group ID — so the control binds straight to the record field.
|
|
6843
|
+
*
|
|
6844
|
+
* @param {Element|string|null} target Component target.
|
|
6845
|
+
* @param {SelectOptions & {groups?: SelectItem[]|((query: string) => Promise<SelectItem[]>|SelectItem[])}} [options={}]
|
|
6846
|
+
* Select overrides other than the fixed choices; `groups` holds the shareable groups, either as
|
|
6847
|
+
* an array (filtered locally) or as a loader function (queried remotely).
|
|
6848
|
+
* @returns {Select}
|
|
6849
|
+
*/
|
|
6850
|
+
static permission(target, options = {}) {
|
|
6851
|
+
const { groups = [], value = true, valueKey = "ID", labelKey = "name", ...rest } = options;
|
|
6852
|
+
const loader = typeof groups === "function" ? groups : null;
|
|
6853
|
+
const shareable = loader ? [] : groups;
|
|
6854
|
+
const choices = [{ ID: false, name: "Private" }, { ID: true, name: "Public" }];
|
|
6855
|
+
const select = new _Select(target, {
|
|
6856
|
+
filter: loader ?? "local",
|
|
6857
|
+
...rest,
|
|
6858
|
+
value: null,
|
|
6859
|
+
items: shareable,
|
|
6860
|
+
fixedItems: [],
|
|
6861
|
+
// The two choices carry their own ID and label, so a caller's group readers stay untouched.
|
|
6862
|
+
valueKey: (item) => choices.includes(item) ? item.ID : readItem(item, valueKey),
|
|
6863
|
+
labelKey: (item) => choices.includes(item) ? item.name : String(readItem(item, labelKey) ?? "")
|
|
6864
|
+
});
|
|
6865
|
+
choices[0].name = select._message("permission.private", "Private");
|
|
6866
|
+
choices[1].name = select._message("permission.public", "Public");
|
|
6867
|
+
select.setFixedItems(choices);
|
|
6868
|
+
select.set(value, { silent: true });
|
|
6869
|
+
return select;
|
|
6870
|
+
}
|
|
6640
6871
|
/** Cleans up the panel, pending async work, and enhanced target contents. @returns {void} */
|
|
6641
6872
|
destroy() {
|
|
6642
6873
|
this._cancelQuery();
|
|
@@ -6707,8 +6938,8 @@
|
|
|
6707
6938
|
);
|
|
6708
6939
|
if (!this._open) this.open();
|
|
6709
6940
|
if (this.options.filter === "local") {
|
|
6710
|
-
|
|
6711
|
-
this._visibleItems = matchItems(this._items, query,
|
|
6941
|
+
this._visibleFixed = this._matchFixed(query);
|
|
6942
|
+
this._visibleItems = matchItems(this._items, query, this._searchKeys());
|
|
6712
6943
|
this._renderOptions();
|
|
6713
6944
|
this._setActive(this._optionEntries.length > 0 ? 0 : -1);
|
|
6714
6945
|
this._position?.update();
|
|
@@ -6763,6 +6994,10 @@
|
|
|
6763
6994
|
if (this.options.clearable) {
|
|
6764
6995
|
this._appendOption(null, true, this.options.placeholder || this._message("select.none", "No selection"));
|
|
6765
6996
|
}
|
|
6997
|
+
for (const item of this._visibleFixed) this._appendOption(item, false);
|
|
6998
|
+
if (this._visibleFixed.length > 0 && this._visibleItems.length > 0 && !this.options.groupKey) {
|
|
6999
|
+
list.append(h("div", { class: "zx-select__separator", role: "presentation" }));
|
|
7000
|
+
}
|
|
6766
7001
|
for (const item of this._visibleItems) {
|
|
6767
7002
|
if (this.options.groupKey) {
|
|
6768
7003
|
const group = this._read(item, this.options.groupKey);
|
|
@@ -6776,7 +7011,7 @@
|
|
|
6776
7011
|
}
|
|
6777
7012
|
this._appendOption(item, false);
|
|
6778
7013
|
}
|
|
6779
|
-
if (this._visibleItems.length === 0 && !this._loading) {
|
|
7014
|
+
if (this._visibleItems.length === 0 && this._visibleFixed.length === 0 && !this._loading) {
|
|
6780
7015
|
list.append(h("div", {
|
|
6781
7016
|
class: "zx-select__empty",
|
|
6782
7017
|
role: "status"
|
|
@@ -6816,6 +7051,7 @@
|
|
|
6816
7051
|
if (this._queryTimer !== null) clearTimeout(this._queryTimer);
|
|
6817
7052
|
this._requestSequence += 1;
|
|
6818
7053
|
const sequence = this._requestSequence;
|
|
7054
|
+
this._visibleFixed = this._matchFixed(query);
|
|
6819
7055
|
if (query.length < Math.max(0, Number(this.options.minQuery) || 0)) {
|
|
6820
7056
|
this._setLoading(false);
|
|
6821
7057
|
this._visibleItems = [];
|
|
@@ -6900,13 +7136,14 @@
|
|
|
6900
7136
|
}
|
|
6901
7137
|
/** @param {unknown} candidate @returns {SelectItem|null} */
|
|
6902
7138
|
_findItem(candidate) {
|
|
6903
|
-
const
|
|
7139
|
+
const pool = this._fixedItems.length === 0 ? this._items : [...this._fixedItems, ...this._items];
|
|
7140
|
+
const direct = pool.find((item) => item === candidate);
|
|
6904
7141
|
if (direct !== void 0) return direct;
|
|
6905
7142
|
const value = candidate !== null && typeof candidate === "object" ? this._itemValue(
|
|
6906
7143
|
/** @type {SelectItem} */
|
|
6907
7144
|
candidate
|
|
6908
7145
|
) : candidate;
|
|
6909
|
-
return
|
|
7146
|
+
return pool.find((item) => Object.is(this._itemValue(item), value)) ?? null;
|
|
6910
7147
|
}
|
|
6911
7148
|
/** @param {SelectItem|null} item @returns {unknown} */
|
|
6912
7149
|
_itemValue(item) {
|
|
@@ -6919,9 +7156,20 @@
|
|
|
6919
7156
|
}
|
|
6920
7157
|
/** @param {SelectItem|null} item @param {SelectValueReader|SelectLabelReader} reader @returns {unknown} */
|
|
6921
7158
|
_read(item, reader) {
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
7159
|
+
return readItem(item, reader);
|
|
7160
|
+
}
|
|
7161
|
+
/** @returns {Array<SelectValueReader|SelectLabelReader>} */
|
|
7162
|
+
_searchKeys() {
|
|
7163
|
+
return this.options.searchKeys ?? [this.options.labelKey];
|
|
7164
|
+
}
|
|
7165
|
+
/**
|
|
7166
|
+
* Narrows the pinned choices. They are always matched here and never handed to a filter
|
|
7167
|
+
* function: a fixed choice belongs to the control, not to whatever the remote source returns.
|
|
7168
|
+
* @param {string} query Search text.
|
|
7169
|
+
* @returns {SelectItem[]}
|
|
7170
|
+
*/
|
|
7171
|
+
_matchFixed(query) {
|
|
7172
|
+
return matchItems(this._fixedItems, query, this._searchKeys());
|
|
6925
7173
|
}
|
|
6926
7174
|
/** @returns {number} */
|
|
6927
7175
|
_selectedEntryIndex() {
|
|
@@ -6944,6 +7192,11 @@
|
|
|
6944
7192
|
return message === key ? fallback : message;
|
|
6945
7193
|
}
|
|
6946
7194
|
};
|
|
7195
|
+
function readItem(item, reader) {
|
|
7196
|
+
if (typeof reader === "function") return reader(item);
|
|
7197
|
+
if (item === null || typeof item !== "object") return item;
|
|
7198
|
+
return item[reader];
|
|
7199
|
+
}
|
|
6947
7200
|
function isPrintable(event) {
|
|
6948
7201
|
return event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
6949
7202
|
}
|
|
@@ -10457,6 +10710,10 @@
|
|
|
10457
10710
|
if (!tab || !tablist.contains(tab)) return;
|
|
10458
10711
|
const record = this._recordForTab(tab);
|
|
10459
10712
|
if (!record || record.definition.disabled) return;
|
|
10713
|
+
if (event.target.closest?.(".zx-tabbox__close")) {
|
|
10714
|
+
this._closeTab(record);
|
|
10715
|
+
return;
|
|
10716
|
+
}
|
|
10460
10717
|
this._setFocused(record);
|
|
10461
10718
|
this.openTab(record.definition.name);
|
|
10462
10719
|
});
|
|
@@ -10506,8 +10763,9 @@
|
|
|
10506
10763
|
children.push(title, badge2);
|
|
10507
10764
|
if (normalized.closable) {
|
|
10508
10765
|
children.push(h("span", {
|
|
10509
|
-
class: "zx-tabbox__close",
|
|
10510
|
-
ariaHidden: "true"
|
|
10766
|
+
class: "zx-tabbox__close zx-icon-btn",
|
|
10767
|
+
ariaHidden: "true",
|
|
10768
|
+
title: "Close"
|
|
10511
10769
|
}, icon("x", { size: 14 })));
|
|
10512
10770
|
}
|
|
10513
10771
|
const tab = (
|
|
@@ -10681,13 +10939,23 @@
|
|
|
10681
10939
|
}
|
|
10682
10940
|
if (event.key === "Delete" && current.definition.closable) {
|
|
10683
10941
|
event.preventDefault();
|
|
10684
|
-
|
|
10685
|
-
if (!this._removeTab(current.definition.name, true)) return;
|
|
10686
|
-
const remaining = this._tabs[Math.min(index, this._tabs.length - 1)] ?? this._tabs[index - 1];
|
|
10687
|
-
const focusTarget = remaining && !remaining.definition.disabled ? remaining : this._active;
|
|
10688
|
-
if (focusTarget) this._setFocused(focusTarget);
|
|
10942
|
+
this._closeTab(current);
|
|
10689
10943
|
}
|
|
10690
10944
|
}
|
|
10945
|
+
/**
|
|
10946
|
+
* Closes a tab on a user gesture, keeping roving focus inside the tablist.
|
|
10947
|
+
* @param {TabRecord} record Tab to close.
|
|
10948
|
+
* @returns {void}
|
|
10949
|
+
* @fires Tabbox#close
|
|
10950
|
+
*/
|
|
10951
|
+
_closeTab(record) {
|
|
10952
|
+
const index = this._tabs.indexOf(record);
|
|
10953
|
+
const held = record.tab.contains(document.activeElement);
|
|
10954
|
+
if (!this._removeTab(record.definition.name, true)) return;
|
|
10955
|
+
const remaining = this._tabs[Math.min(index, this._tabs.length - 1)] ?? this._tabs[index - 1];
|
|
10956
|
+
const focusTarget = remaining && !remaining.definition.disabled ? remaining : this._active;
|
|
10957
|
+
if (focusTarget) this._setFocused(focusTarget, held);
|
|
10958
|
+
}
|
|
10691
10959
|
/** @param {string} name @param {boolean} emitClose @returns {boolean} */
|
|
10692
10960
|
_removeTab(name, emitClose) {
|
|
10693
10961
|
const record = this._find(name);
|