@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.esm.js
CHANGED
|
@@ -5118,18 +5118,34 @@ function registerFieldUploadAdapter() {
|
|
|
5118
5118
|
}
|
|
5119
5119
|
|
|
5120
5120
|
// src/components/multi-value-editor/multi-value-editor.js
|
|
5121
|
+
var REORDER = Object.freeze({
|
|
5122
|
+
both: { drag: true, buttons: true },
|
|
5123
|
+
buttons: { drag: false, buttons: true },
|
|
5124
|
+
drag: { drag: true, buttons: false },
|
|
5125
|
+
none: { drag: false, buttons: false }
|
|
5126
|
+
});
|
|
5127
|
+
var PARTS = Object.freeze({
|
|
5128
|
+
value: ".zx-multi-value-editor__value",
|
|
5129
|
+
handle: ".zx-multi-value-editor__handle",
|
|
5130
|
+
up: '[data-action="up"]',
|
|
5131
|
+
down: '[data-action="down"]'
|
|
5132
|
+
});
|
|
5121
5133
|
var MultiValueEditor = class extends Component {
|
|
5122
5134
|
static cssName = "multi-value-editor";
|
|
5123
5135
|
/** @type {MultiValueEditorOptions} */
|
|
5124
|
-
static defaults = { values: [], options: null, addLabel: "Add value" };
|
|
5136
|
+
static defaults = { values: [], options: null, addLabel: "Add value", reorder: "both" };
|
|
5125
5137
|
/** @returns {HTMLElement} */
|
|
5126
5138
|
render() {
|
|
5127
5139
|
const root = (
|
|
5128
5140
|
/** @type {HTMLElement} */
|
|
5129
5141
|
this.el ?? h("div")
|
|
5130
5142
|
);
|
|
5143
|
+
this.el = root;
|
|
5131
5144
|
this._values = Array.isArray(this.options.values) ? this.options.values.map(String) : [];
|
|
5132
5145
|
this._ownedNodes = [];
|
|
5146
|
+
this._initialState = root.getAttribute("data-state");
|
|
5147
|
+
this._dragIndex = -1;
|
|
5148
|
+
this._armed = null;
|
|
5133
5149
|
this._rows = h("div", { class: "zx-multi-value-editor__rows" });
|
|
5134
5150
|
this._add = h("button", {
|
|
5135
5151
|
class: "zx-btn zx-multi-value-editor__add",
|
|
@@ -5140,6 +5156,17 @@ var MultiValueEditor = class extends Component {
|
|
|
5140
5156
|
root.append(this._rows, this._add);
|
|
5141
5157
|
this.listen(root, "click", (event) => this._handleClick(event));
|
|
5142
5158
|
this.listen(this._rows, "change", (event) => this._handleChange(event));
|
|
5159
|
+
this.listen(this._rows, "keydown", (event) => this._handleKeydown(event));
|
|
5160
|
+
if (this._reorder().drag) {
|
|
5161
|
+
this.listen(this._rows, "pointerdown", (event) => this._handlePointerDown(event));
|
|
5162
|
+
this.listen(this._rows, "dragstart", (event) => this._handleDragStart(event));
|
|
5163
|
+
this.listen(this._rows, "dragover", (event) => this._handleDragOver(event));
|
|
5164
|
+
this.listen(this._rows, "dragleave", (event) => this._handleDragLeave(event));
|
|
5165
|
+
this.listen(this._rows, "drop", (event) => this._handleDrop(event));
|
|
5166
|
+
this.listen(this._rows, "dragend", () => this._finishDrag());
|
|
5167
|
+
this.listen(document, "pointerup", () => this._disarm());
|
|
5168
|
+
this.listen(document, "pointercancel", () => this._disarm());
|
|
5169
|
+
}
|
|
5143
5170
|
this._renderRows();
|
|
5144
5171
|
return root;
|
|
5145
5172
|
}
|
|
@@ -5162,16 +5189,27 @@ var MultiValueEditor = class extends Component {
|
|
|
5162
5189
|
if (!silent) this.emit("change", { values: this.getValues() });
|
|
5163
5190
|
return this;
|
|
5164
5191
|
}
|
|
5165
|
-
/** @
|
|
5166
|
-
|
|
5192
|
+
/** @returns {{drag: boolean, buttons: boolean}} */
|
|
5193
|
+
_reorder() {
|
|
5194
|
+
return REORDER[this.options.reorder] ?? REORDER.both;
|
|
5195
|
+
}
|
|
5196
|
+
/**
|
|
5197
|
+
* @param {number} [focusIndex=-1] Row to focus after the render, or -1 for none.
|
|
5198
|
+
* @param {keyof PARTS} [focusPart='value'] Which control of that row takes focus.
|
|
5199
|
+
* @returns {void}
|
|
5200
|
+
*/
|
|
5201
|
+
_renderRows(focusIndex = -1, focusPart = "value") {
|
|
5167
5202
|
const choices = normalizeOptions2(this.options.options);
|
|
5168
5203
|
const useSelect = this.options.options !== null;
|
|
5204
|
+
const { drag, buttons } = this._reorder();
|
|
5205
|
+
const last = this._values.length - 1;
|
|
5169
5206
|
const rows = this._values.map((value, index) => {
|
|
5207
|
+
const entries = !useSelect || choices.some(([option]) => option === value) ? choices : [[value, value], ...choices];
|
|
5170
5208
|
const control = useSelect ? h("select", {
|
|
5171
5209
|
class: "zx-multi-value-editor__value",
|
|
5172
5210
|
ariaLabel: `Value ${index + 1}`,
|
|
5173
5211
|
dataset: { index }
|
|
5174
|
-
},
|
|
5212
|
+
}, entries.map(([optionValue, label]) => h("option", {
|
|
5175
5213
|
value: optionValue,
|
|
5176
5214
|
selected: optionValue === value
|
|
5177
5215
|
}, label))) : h("input", {
|
|
@@ -5184,40 +5222,52 @@ var MultiValueEditor = class extends Component {
|
|
|
5184
5222
|
return h(
|
|
5185
5223
|
"div",
|
|
5186
5224
|
{ class: "zx-multi-value-editor__row", dataset: { row: index } },
|
|
5225
|
+
drag ? h("button", {
|
|
5226
|
+
class: "zx-multi-value-editor__handle",
|
|
5227
|
+
type: "button",
|
|
5228
|
+
ariaLabel: `Reorder value ${index + 1}. Use the arrow keys to move it.`,
|
|
5229
|
+
dataset: { action: "grab", index }
|
|
5230
|
+
}, icon("drag", { size: 12 })) : null,
|
|
5187
5231
|
control,
|
|
5188
5232
|
h(
|
|
5189
5233
|
"div",
|
|
5190
5234
|
{ class: "zx-multi-value-editor__controls" },
|
|
5191
|
-
h("button", {
|
|
5235
|
+
buttons ? h("button", {
|
|
5192
5236
|
class: "zx-btn",
|
|
5193
5237
|
type: "button",
|
|
5194
|
-
disabled
|
|
5238
|
+
// `aria-disabled` rather than `disabled`: the buttons re-render on every move, and a
|
|
5239
|
+
// control that disables itself under the reader's finger drops focus to the document.
|
|
5240
|
+
ariaDisabled: String(index === 0),
|
|
5195
5241
|
ariaLabel: `Move value ${index + 1} up`,
|
|
5196
5242
|
dataset: { action: "up", index }
|
|
5197
|
-
}, "
|
|
5198
|
-
h("button", {
|
|
5243
|
+
}, icon("chevron-up", { size: 12 })) : null,
|
|
5244
|
+
buttons ? h("button", {
|
|
5199
5245
|
class: "zx-btn",
|
|
5200
5246
|
type: "button",
|
|
5201
|
-
|
|
5247
|
+
ariaDisabled: String(index === last),
|
|
5202
5248
|
ariaLabel: `Move value ${index + 1} down`,
|
|
5203
5249
|
dataset: { action: "down", index }
|
|
5204
|
-
}, "
|
|
5250
|
+
}, icon("chevron-down", { size: 12 })) : null,
|
|
5205
5251
|
h("button", {
|
|
5206
5252
|
class: "zx-btn",
|
|
5207
5253
|
type: "button",
|
|
5208
5254
|
ariaLabel: `Remove value ${index + 1}`,
|
|
5209
5255
|
dataset: { action: "remove", index }
|
|
5210
|
-
}, "
|
|
5256
|
+
}, icon("x", { size: 12 }))
|
|
5211
5257
|
)
|
|
5212
5258
|
);
|
|
5213
5259
|
});
|
|
5214
5260
|
this._rows.replaceChildren(...rows);
|
|
5215
|
-
if (focusIndex >= 0)
|
|
5261
|
+
if (focusIndex >= 0) {
|
|
5262
|
+
const row = rows[Math.min(focusIndex, rows.length - 1)];
|
|
5263
|
+
row?.querySelector(PARTS[focusPart] ?? PARTS.value)?.focus();
|
|
5264
|
+
}
|
|
5216
5265
|
}
|
|
5217
5266
|
/** @param {MouseEvent} event @returns {void} */
|
|
5218
5267
|
_handleClick(event) {
|
|
5219
5268
|
const button2 = event.target instanceof Element ? event.target.closest("[data-action]") : null;
|
|
5220
5269
|
if (!button2 || !this.el.contains(button2)) return;
|
|
5270
|
+
if (button2.getAttribute("aria-disabled") === "true") return;
|
|
5221
5271
|
const action = button2.dataset.action;
|
|
5222
5272
|
const index = Number(button2.dataset.index);
|
|
5223
5273
|
if (action === "add") {
|
|
@@ -5230,28 +5280,156 @@ var MultiValueEditor = class extends Component {
|
|
|
5230
5280
|
this._renderRows(Math.min(index, this._values.length - 1));
|
|
5231
5281
|
this._emitChange();
|
|
5232
5282
|
} else if ((action === "up" || action === "down") && Number.isInteger(index)) {
|
|
5233
|
-
|
|
5234
|
-
if (target < 0 || target >= this._values.length) return;
|
|
5235
|
-
[this._values[index], this._values[target]] = [this._values[target], this._values[index]];
|
|
5236
|
-
this._renderRows(target);
|
|
5237
|
-
this._emitChange();
|
|
5283
|
+
this._move(index, index + (action === "up" ? -1 : 1), action);
|
|
5238
5284
|
}
|
|
5239
5285
|
}
|
|
5240
5286
|
/** @param {Event} event @returns {void} */
|
|
5241
5287
|
_handleChange(event) {
|
|
5242
5288
|
const control = event.target instanceof Element ? event.target.closest("[data-index]") : null;
|
|
5243
|
-
if (!control) return;
|
|
5289
|
+
if (!control || !("value" in control)) return;
|
|
5244
5290
|
const index = Number(control.dataset.index);
|
|
5245
5291
|
if (!Number.isInteger(index) || index < 0 || index >= this._values.length) return;
|
|
5246
5292
|
this._values[index] = control.value;
|
|
5247
5293
|
this._emitChange();
|
|
5248
5294
|
}
|
|
5295
|
+
/**
|
|
5296
|
+
* The handle is the keyboard equivalent of dragging it, so `reorder: 'drag'` is still operable
|
|
5297
|
+
* without a pointer.
|
|
5298
|
+
* @param {KeyboardEvent} event
|
|
5299
|
+
* @returns {void}
|
|
5300
|
+
*/
|
|
5301
|
+
_handleKeydown(event) {
|
|
5302
|
+
const handle = event.target instanceof Element ? event.target.closest(".zx-multi-value-editor__handle") : null;
|
|
5303
|
+
if (!handle) return;
|
|
5304
|
+
const index = Number(handle.dataset.index);
|
|
5305
|
+
const target = event.key === "ArrowUp" ? index - 1 : event.key === "ArrowDown" ? index + 1 : event.key === "Home" ? 0 : event.key === "End" ? this._values.length - 1 : -1;
|
|
5306
|
+
if (target < 0 || target >= this._values.length) return;
|
|
5307
|
+
event.preventDefault();
|
|
5308
|
+
this._move(index, target, "handle");
|
|
5309
|
+
}
|
|
5310
|
+
/**
|
|
5311
|
+
* Arms the row for dragging. Only the handle does this, so text selection inside a row's input
|
|
5312
|
+
* keeps working and a stray drag cannot reorder anything.
|
|
5313
|
+
* @param {PointerEvent} event
|
|
5314
|
+
* @returns {void}
|
|
5315
|
+
*/
|
|
5316
|
+
_handlePointerDown(event) {
|
|
5317
|
+
const handle = event.target instanceof Element ? event.target.closest(".zx-multi-value-editor__handle") : null;
|
|
5318
|
+
this._disarm();
|
|
5319
|
+
if (!handle || handle.disabled) return;
|
|
5320
|
+
const row = (
|
|
5321
|
+
/** @type {HTMLElement|null} */
|
|
5322
|
+
handle.closest(".zx-multi-value-editor__row")
|
|
5323
|
+
);
|
|
5324
|
+
if (!row) return;
|
|
5325
|
+
row.draggable = true;
|
|
5326
|
+
this._armed = row;
|
|
5327
|
+
}
|
|
5328
|
+
/** @returns {void} */
|
|
5329
|
+
_disarm() {
|
|
5330
|
+
if (this._armed) this._armed.draggable = false;
|
|
5331
|
+
this._armed = null;
|
|
5332
|
+
}
|
|
5333
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5334
|
+
_handleDragStart(event) {
|
|
5335
|
+
const row = event.target instanceof Element ? event.target.closest(".zx-multi-value-editor__row") : null;
|
|
5336
|
+
if (!row || row !== this._armed) {
|
|
5337
|
+
event.preventDefault();
|
|
5338
|
+
return;
|
|
5339
|
+
}
|
|
5340
|
+
this._dragIndex = Number(row.dataset.row);
|
|
5341
|
+
row.dataset.dragging = "true";
|
|
5342
|
+
this.el.setAttribute("data-state", "dragging");
|
|
5343
|
+
event.dataTransfer?.setData("text/plain", String(this._dragIndex));
|
|
5344
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = "move";
|
|
5345
|
+
}
|
|
5346
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5347
|
+
_handleDragOver(event) {
|
|
5348
|
+
if (this._dragIndex < 0) return;
|
|
5349
|
+
event.preventDefault();
|
|
5350
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
|
|
5351
|
+
this._showDrop(this._dropTarget(event.clientY));
|
|
5352
|
+
}
|
|
5353
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5354
|
+
_handleDragLeave(event) {
|
|
5355
|
+
const to = event.relatedTarget;
|
|
5356
|
+
if (to instanceof Node && this._rows.contains(to)) return;
|
|
5357
|
+
this._clearDrop();
|
|
5358
|
+
}
|
|
5359
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5360
|
+
_handleDrop(event) {
|
|
5361
|
+
if (this._dragIndex < 0) return;
|
|
5362
|
+
event.preventDefault();
|
|
5363
|
+
const from = this._dragIndex;
|
|
5364
|
+
const slot = this._dropTarget(event.clientY);
|
|
5365
|
+
this._finishDrag();
|
|
5366
|
+
this._move(from, slot > from ? slot - 1 : slot, "handle");
|
|
5367
|
+
}
|
|
5368
|
+
/** @returns {void} */
|
|
5369
|
+
_finishDrag() {
|
|
5370
|
+
this._dragIndex = -1;
|
|
5371
|
+
this._disarm();
|
|
5372
|
+
this._clearDrop();
|
|
5373
|
+
for (const row of this._rows.children) delete /** @type {HTMLElement} */
|
|
5374
|
+
row.dataset.dragging;
|
|
5375
|
+
if (this._initialState === null) this.el.removeAttribute("data-state");
|
|
5376
|
+
else this.el.setAttribute("data-state", this._initialState);
|
|
5377
|
+
}
|
|
5378
|
+
/**
|
|
5379
|
+
* Insertion slot (0…length) for a pointer at `clientY`.
|
|
5380
|
+
* @param {number} clientY Pointer position.
|
|
5381
|
+
* @returns {number}
|
|
5382
|
+
*/
|
|
5383
|
+
_dropTarget(clientY) {
|
|
5384
|
+
const rows = [...this._rows.children];
|
|
5385
|
+
for (const [index, row] of rows.entries()) {
|
|
5386
|
+
const box = row.getBoundingClientRect();
|
|
5387
|
+
if (clientY < box.top + box.height / 2) return index;
|
|
5388
|
+
}
|
|
5389
|
+
return rows.length;
|
|
5390
|
+
}
|
|
5391
|
+
/** @param {number} slot Insertion slot to mark. @returns {void} */
|
|
5392
|
+
_showDrop(slot) {
|
|
5393
|
+
const rows = [...this._rows.children];
|
|
5394
|
+
const inert = slot === this._dragIndex || slot === this._dragIndex + 1;
|
|
5395
|
+
for (const [index, row] of rows.entries()) {
|
|
5396
|
+
const cell = (
|
|
5397
|
+
/** @type {HTMLElement} */
|
|
5398
|
+
row
|
|
5399
|
+
);
|
|
5400
|
+
if (!inert && index === slot) cell.dataset.drop = "before";
|
|
5401
|
+
else if (!inert && slot === rows.length && index === rows.length - 1) cell.dataset.drop = "after";
|
|
5402
|
+
else delete cell.dataset.drop;
|
|
5403
|
+
}
|
|
5404
|
+
}
|
|
5405
|
+
/** @returns {void} */
|
|
5406
|
+
_clearDrop() {
|
|
5407
|
+
for (const row of this._rows.children) delete /** @type {HTMLElement} */
|
|
5408
|
+
row.dataset.drop;
|
|
5409
|
+
}
|
|
5410
|
+
/**
|
|
5411
|
+
* @param {number} from Index to move.
|
|
5412
|
+
* @param {number} to Destination index.
|
|
5413
|
+
* @param {keyof PARTS} part Control of the moved row that takes focus.
|
|
5414
|
+
* @returns {boolean} Whether anything moved.
|
|
5415
|
+
*/
|
|
5416
|
+
_move(from, to, part) {
|
|
5417
|
+
const size = this._values.length;
|
|
5418
|
+
if (from === to || from < 0 || to < 0 || from >= size || to >= size) return false;
|
|
5419
|
+
const [value] = this._values.splice(from, 1);
|
|
5420
|
+
this._values.splice(to, 0, value);
|
|
5421
|
+
this._renderRows(to, part);
|
|
5422
|
+
this._emitChange();
|
|
5423
|
+
return true;
|
|
5424
|
+
}
|
|
5249
5425
|
/** @returns {void} */
|
|
5250
5426
|
_emitChange() {
|
|
5251
5427
|
this.emit("change", { values: this.getValues() });
|
|
5252
5428
|
}
|
|
5253
5429
|
/** @returns {void} */
|
|
5254
5430
|
destroy() {
|
|
5431
|
+
if (this._initialState === null) this.el?.removeAttribute("data-state");
|
|
5432
|
+
else this.el?.setAttribute("data-state", this._initialState);
|
|
5255
5433
|
for (const node of this._ownedNodes ?? []) node.remove();
|
|
5256
5434
|
super.destroy();
|
|
5257
5435
|
}
|
|
@@ -6276,6 +6454,7 @@ var Select = class _Select extends Component {
|
|
|
6276
6454
|
/** @type {SelectOptions} */
|
|
6277
6455
|
static defaults = {
|
|
6278
6456
|
items: [],
|
|
6457
|
+
fixedItems: [],
|
|
6279
6458
|
valueKey: "ID",
|
|
6280
6459
|
labelKey: "name",
|
|
6281
6460
|
renderItem: null,
|
|
@@ -6295,6 +6474,8 @@ var Select = class _Select extends Component {
|
|
|
6295
6474
|
render() {
|
|
6296
6475
|
this._items = [];
|
|
6297
6476
|
this._visibleItems = [];
|
|
6477
|
+
this._fixedItems = [];
|
|
6478
|
+
this._visibleFixed = [];
|
|
6298
6479
|
this._selected = null;
|
|
6299
6480
|
this._optionEntries = [];
|
|
6300
6481
|
this._activeIndex = -1;
|
|
@@ -6378,6 +6559,8 @@ var Select = class _Select extends Component {
|
|
|
6378
6559
|
root.replaceChildren(control, panel);
|
|
6379
6560
|
this._items = Array.isArray(this.options.items) ? this.options.items.slice() : [];
|
|
6380
6561
|
this._visibleItems = this._items.slice();
|
|
6562
|
+
this._fixedItems = Array.isArray(this.options.fixedItems) ? this.options.fixedItems.slice() : [];
|
|
6563
|
+
this._visibleFixed = this._fixedItems.slice();
|
|
6381
6564
|
this._disabled = Boolean(this.options.disabled);
|
|
6382
6565
|
this._handleTypeahead = typeahead(
|
|
6383
6566
|
() => this._optionEntries.filter((entry) => !entry.clear).map((entry) => entry.element),
|
|
@@ -6454,7 +6637,22 @@ var Select = class _Select extends Component {
|
|
|
6454
6637
|
const selectedValue = this.value;
|
|
6455
6638
|
this._items = Array.isArray(items) ? items.slice() : [];
|
|
6456
6639
|
this._visibleItems = this._items.slice();
|
|
6457
|
-
this._selected = selectedValue == null ? null : this.
|
|
6640
|
+
this._selected = selectedValue == null ? null : this._findItem(selectedValue);
|
|
6641
|
+
this._syncInputValue();
|
|
6642
|
+
this._renderOptions();
|
|
6643
|
+
this._position?.update();
|
|
6644
|
+
return this;
|
|
6645
|
+
}
|
|
6646
|
+
/**
|
|
6647
|
+
* Replaces the pinned choices and preserves selection by ID when possible.
|
|
6648
|
+
* @param {SelectItem[]} items New fixed items.
|
|
6649
|
+
* @returns {this}
|
|
6650
|
+
*/
|
|
6651
|
+
setFixedItems(items) {
|
|
6652
|
+
const selectedValue = this.value;
|
|
6653
|
+
this._fixedItems = Array.isArray(items) ? items.slice() : [];
|
|
6654
|
+
this._visibleFixed = this._fixedItems.slice();
|
|
6655
|
+
this._selected = selectedValue == null ? null : this._findItem(selectedValue);
|
|
6458
6656
|
this._syncInputValue();
|
|
6459
6657
|
this._renderOptions();
|
|
6460
6658
|
this._position?.update();
|
|
@@ -6508,6 +6706,7 @@ var Select = class _Select extends Component {
|
|
|
6508
6706
|
this.refs.input.removeAttribute("aria-activedescendant");
|
|
6509
6707
|
this._activeIndex = -1;
|
|
6510
6708
|
this._visibleItems = this._items.slice();
|
|
6709
|
+
this._visibleFixed = this._fixedItems.slice();
|
|
6511
6710
|
this._syncInputValue();
|
|
6512
6711
|
this.emit("close", {});
|
|
6513
6712
|
return this;
|
|
@@ -6563,6 +6762,38 @@ var Select = class _Select extends Component {
|
|
|
6563
6762
|
select.set(requestedValue, { silent: true });
|
|
6564
6763
|
return select;
|
|
6565
6764
|
}
|
|
6765
|
+
/**
|
|
6766
|
+
* Creates the record-permission picker: the fixed Private and Public choices above the groups a
|
|
6767
|
+
* record can be shared with. The value is the tri-state ZeyOS stores — `false` for private,
|
|
6768
|
+
* `true` for public, or a group ID — so the control binds straight to the record field.
|
|
6769
|
+
*
|
|
6770
|
+
* @param {Element|string|null} target Component target.
|
|
6771
|
+
* @param {SelectOptions & {groups?: SelectItem[]|((query: string) => Promise<SelectItem[]>|SelectItem[])}} [options={}]
|
|
6772
|
+
* Select overrides other than the fixed choices; `groups` holds the shareable groups, either as
|
|
6773
|
+
* an array (filtered locally) or as a loader function (queried remotely).
|
|
6774
|
+
* @returns {Select}
|
|
6775
|
+
*/
|
|
6776
|
+
static permission(target, options = {}) {
|
|
6777
|
+
const { groups = [], value = true, valueKey = "ID", labelKey = "name", ...rest } = options;
|
|
6778
|
+
const loader = typeof groups === "function" ? groups : null;
|
|
6779
|
+
const shareable = loader ? [] : groups;
|
|
6780
|
+
const choices = [{ ID: false, name: "Private" }, { ID: true, name: "Public" }];
|
|
6781
|
+
const select = new _Select(target, {
|
|
6782
|
+
filter: loader ?? "local",
|
|
6783
|
+
...rest,
|
|
6784
|
+
value: null,
|
|
6785
|
+
items: shareable,
|
|
6786
|
+
fixedItems: [],
|
|
6787
|
+
// The two choices carry their own ID and label, so a caller's group readers stay untouched.
|
|
6788
|
+
valueKey: (item) => choices.includes(item) ? item.ID : readItem(item, valueKey),
|
|
6789
|
+
labelKey: (item) => choices.includes(item) ? item.name : String(readItem(item, labelKey) ?? "")
|
|
6790
|
+
});
|
|
6791
|
+
choices[0].name = select._message("permission.private", "Private");
|
|
6792
|
+
choices[1].name = select._message("permission.public", "Public");
|
|
6793
|
+
select.setFixedItems(choices);
|
|
6794
|
+
select.set(value, { silent: true });
|
|
6795
|
+
return select;
|
|
6796
|
+
}
|
|
6566
6797
|
/** Cleans up the panel, pending async work, and enhanced target contents. @returns {void} */
|
|
6567
6798
|
destroy() {
|
|
6568
6799
|
this._cancelQuery();
|
|
@@ -6633,8 +6864,8 @@ var Select = class _Select extends Component {
|
|
|
6633
6864
|
);
|
|
6634
6865
|
if (!this._open) this.open();
|
|
6635
6866
|
if (this.options.filter === "local") {
|
|
6636
|
-
|
|
6637
|
-
this._visibleItems = matchItems(this._items, query,
|
|
6867
|
+
this._visibleFixed = this._matchFixed(query);
|
|
6868
|
+
this._visibleItems = matchItems(this._items, query, this._searchKeys());
|
|
6638
6869
|
this._renderOptions();
|
|
6639
6870
|
this._setActive(this._optionEntries.length > 0 ? 0 : -1);
|
|
6640
6871
|
this._position?.update();
|
|
@@ -6689,6 +6920,10 @@ var Select = class _Select extends Component {
|
|
|
6689
6920
|
if (this.options.clearable) {
|
|
6690
6921
|
this._appendOption(null, true, this.options.placeholder || this._message("select.none", "No selection"));
|
|
6691
6922
|
}
|
|
6923
|
+
for (const item of this._visibleFixed) this._appendOption(item, false);
|
|
6924
|
+
if (this._visibleFixed.length > 0 && this._visibleItems.length > 0 && !this.options.groupKey) {
|
|
6925
|
+
list.append(h("div", { class: "zx-select__separator", role: "presentation" }));
|
|
6926
|
+
}
|
|
6692
6927
|
for (const item of this._visibleItems) {
|
|
6693
6928
|
if (this.options.groupKey) {
|
|
6694
6929
|
const group = this._read(item, this.options.groupKey);
|
|
@@ -6702,7 +6937,7 @@ var Select = class _Select extends Component {
|
|
|
6702
6937
|
}
|
|
6703
6938
|
this._appendOption(item, false);
|
|
6704
6939
|
}
|
|
6705
|
-
if (this._visibleItems.length === 0 && !this._loading) {
|
|
6940
|
+
if (this._visibleItems.length === 0 && this._visibleFixed.length === 0 && !this._loading) {
|
|
6706
6941
|
list.append(h("div", {
|
|
6707
6942
|
class: "zx-select__empty",
|
|
6708
6943
|
role: "status"
|
|
@@ -6742,6 +6977,7 @@ var Select = class _Select extends Component {
|
|
|
6742
6977
|
if (this._queryTimer !== null) clearTimeout(this._queryTimer);
|
|
6743
6978
|
this._requestSequence += 1;
|
|
6744
6979
|
const sequence = this._requestSequence;
|
|
6980
|
+
this._visibleFixed = this._matchFixed(query);
|
|
6745
6981
|
if (query.length < Math.max(0, Number(this.options.minQuery) || 0)) {
|
|
6746
6982
|
this._setLoading(false);
|
|
6747
6983
|
this._visibleItems = [];
|
|
@@ -6826,13 +7062,14 @@ var Select = class _Select extends Component {
|
|
|
6826
7062
|
}
|
|
6827
7063
|
/** @param {unknown} candidate @returns {SelectItem|null} */
|
|
6828
7064
|
_findItem(candidate) {
|
|
6829
|
-
const
|
|
7065
|
+
const pool = this._fixedItems.length === 0 ? this._items : [...this._fixedItems, ...this._items];
|
|
7066
|
+
const direct = pool.find((item) => item === candidate);
|
|
6830
7067
|
if (direct !== void 0) return direct;
|
|
6831
7068
|
const value = candidate !== null && typeof candidate === "object" ? this._itemValue(
|
|
6832
7069
|
/** @type {SelectItem} */
|
|
6833
7070
|
candidate
|
|
6834
7071
|
) : candidate;
|
|
6835
|
-
return
|
|
7072
|
+
return pool.find((item) => Object.is(this._itemValue(item), value)) ?? null;
|
|
6836
7073
|
}
|
|
6837
7074
|
/** @param {SelectItem|null} item @returns {unknown} */
|
|
6838
7075
|
_itemValue(item) {
|
|
@@ -6845,9 +7082,20 @@ var Select = class _Select extends Component {
|
|
|
6845
7082
|
}
|
|
6846
7083
|
/** @param {SelectItem|null} item @param {SelectValueReader|SelectLabelReader} reader @returns {unknown} */
|
|
6847
7084
|
_read(item, reader) {
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
7085
|
+
return readItem(item, reader);
|
|
7086
|
+
}
|
|
7087
|
+
/** @returns {Array<SelectValueReader|SelectLabelReader>} */
|
|
7088
|
+
_searchKeys() {
|
|
7089
|
+
return this.options.searchKeys ?? [this.options.labelKey];
|
|
7090
|
+
}
|
|
7091
|
+
/**
|
|
7092
|
+
* Narrows the pinned choices. They are always matched here and never handed to a filter
|
|
7093
|
+
* function: a fixed choice belongs to the control, not to whatever the remote source returns.
|
|
7094
|
+
* @param {string} query Search text.
|
|
7095
|
+
* @returns {SelectItem[]}
|
|
7096
|
+
*/
|
|
7097
|
+
_matchFixed(query) {
|
|
7098
|
+
return matchItems(this._fixedItems, query, this._searchKeys());
|
|
6851
7099
|
}
|
|
6852
7100
|
/** @returns {number} */
|
|
6853
7101
|
_selectedEntryIndex() {
|
|
@@ -6870,6 +7118,11 @@ var Select = class _Select extends Component {
|
|
|
6870
7118
|
return message === key ? fallback : message;
|
|
6871
7119
|
}
|
|
6872
7120
|
};
|
|
7121
|
+
function readItem(item, reader) {
|
|
7122
|
+
if (typeof reader === "function") return reader(item);
|
|
7123
|
+
if (item === null || typeof item !== "object") return item;
|
|
7124
|
+
return item[reader];
|
|
7125
|
+
}
|
|
6873
7126
|
function isPrintable(event) {
|
|
6874
7127
|
return event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
6875
7128
|
}
|
|
@@ -9996,6 +10249,10 @@ var Tabbox = class extends Component {
|
|
|
9996
10249
|
if (!tab || !tablist.contains(tab)) return;
|
|
9997
10250
|
const record = this._recordForTab(tab);
|
|
9998
10251
|
if (!record || record.definition.disabled) return;
|
|
10252
|
+
if (event.target.closest?.(".zx-tabbox__close")) {
|
|
10253
|
+
this._closeTab(record);
|
|
10254
|
+
return;
|
|
10255
|
+
}
|
|
9999
10256
|
this._setFocused(record);
|
|
10000
10257
|
this.openTab(record.definition.name);
|
|
10001
10258
|
});
|
|
@@ -10045,8 +10302,9 @@ var Tabbox = class extends Component {
|
|
|
10045
10302
|
children.push(title, badge);
|
|
10046
10303
|
if (normalized.closable) {
|
|
10047
10304
|
children.push(h("span", {
|
|
10048
|
-
class: "zx-tabbox__close",
|
|
10049
|
-
ariaHidden: "true"
|
|
10305
|
+
class: "zx-tabbox__close zx-icon-btn",
|
|
10306
|
+
ariaHidden: "true",
|
|
10307
|
+
title: "Close"
|
|
10050
10308
|
}, icon("x", { size: 14 })));
|
|
10051
10309
|
}
|
|
10052
10310
|
const tab = (
|
|
@@ -10220,13 +10478,23 @@ var Tabbox = class extends Component {
|
|
|
10220
10478
|
}
|
|
10221
10479
|
if (event.key === "Delete" && current.definition.closable) {
|
|
10222
10480
|
event.preventDefault();
|
|
10223
|
-
|
|
10224
|
-
if (!this._removeTab(current.definition.name, true)) return;
|
|
10225
|
-
const remaining = this._tabs[Math.min(index, this._tabs.length - 1)] ?? this._tabs[index - 1];
|
|
10226
|
-
const focusTarget = remaining && !remaining.definition.disabled ? remaining : this._active;
|
|
10227
|
-
if (focusTarget) this._setFocused(focusTarget);
|
|
10481
|
+
this._closeTab(current);
|
|
10228
10482
|
}
|
|
10229
10483
|
}
|
|
10484
|
+
/**
|
|
10485
|
+
* Closes a tab on a user gesture, keeping roving focus inside the tablist.
|
|
10486
|
+
* @param {TabRecord} record Tab to close.
|
|
10487
|
+
* @returns {void}
|
|
10488
|
+
* @fires Tabbox#close
|
|
10489
|
+
*/
|
|
10490
|
+
_closeTab(record) {
|
|
10491
|
+
const index = this._tabs.indexOf(record);
|
|
10492
|
+
const held = record.tab.contains(document.activeElement);
|
|
10493
|
+
if (!this._removeTab(record.definition.name, true)) return;
|
|
10494
|
+
const remaining = this._tabs[Math.min(index, this._tabs.length - 1)] ?? this._tabs[index - 1];
|
|
10495
|
+
const focusTarget = remaining && !remaining.definition.disabled ? remaining : this._active;
|
|
10496
|
+
if (focusTarget) this._setFocused(focusTarget, held);
|
|
10497
|
+
}
|
|
10230
10498
|
/** @param {string} name @param {boolean} emitClose @returns {boolean} */
|
|
10231
10499
|
_removeTab(name, emitClose) {
|
|
10232
10500
|
const record = this._find(name);
|