@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.esm.js
CHANGED
|
@@ -4988,18 +4988,34 @@ function registerFieldUploadAdapter() {
|
|
|
4988
4988
|
}
|
|
4989
4989
|
|
|
4990
4990
|
// src/components/multi-value-editor/multi-value-editor.js
|
|
4991
|
+
var REORDER = Object.freeze({
|
|
4992
|
+
both: { drag: true, buttons: true },
|
|
4993
|
+
buttons: { drag: false, buttons: true },
|
|
4994
|
+
drag: { drag: true, buttons: false },
|
|
4995
|
+
none: { drag: false, buttons: false }
|
|
4996
|
+
});
|
|
4997
|
+
var PARTS = Object.freeze({
|
|
4998
|
+
value: ".zx-multi-value-editor__value",
|
|
4999
|
+
handle: ".zx-multi-value-editor__handle",
|
|
5000
|
+
up: '[data-action="up"]',
|
|
5001
|
+
down: '[data-action="down"]'
|
|
5002
|
+
});
|
|
4991
5003
|
var MultiValueEditor = class extends Component {
|
|
4992
5004
|
static cssName = "multi-value-editor";
|
|
4993
5005
|
/** @type {MultiValueEditorOptions} */
|
|
4994
|
-
static defaults = { values: [], options: null, addLabel: "Add value" };
|
|
5006
|
+
static defaults = { values: [], options: null, addLabel: "Add value", reorder: "both" };
|
|
4995
5007
|
/** @returns {HTMLElement} */
|
|
4996
5008
|
render() {
|
|
4997
5009
|
const root = (
|
|
4998
5010
|
/** @type {HTMLElement} */
|
|
4999
5011
|
this.el ?? h("div")
|
|
5000
5012
|
);
|
|
5013
|
+
this.el = root;
|
|
5001
5014
|
this._values = Array.isArray(this.options.values) ? this.options.values.map(String) : [];
|
|
5002
5015
|
this._ownedNodes = [];
|
|
5016
|
+
this._initialState = root.getAttribute("data-state");
|
|
5017
|
+
this._dragIndex = -1;
|
|
5018
|
+
this._armed = null;
|
|
5003
5019
|
this._rows = h("div", { class: "zx-multi-value-editor__rows" });
|
|
5004
5020
|
this._add = h("button", {
|
|
5005
5021
|
class: "zx-btn zx-multi-value-editor__add",
|
|
@@ -5010,6 +5026,17 @@ var MultiValueEditor = class extends Component {
|
|
|
5010
5026
|
root.append(this._rows, this._add);
|
|
5011
5027
|
this.listen(root, "click", (event) => this._handleClick(event));
|
|
5012
5028
|
this.listen(this._rows, "change", (event) => this._handleChange(event));
|
|
5029
|
+
this.listen(this._rows, "keydown", (event) => this._handleKeydown(event));
|
|
5030
|
+
if (this._reorder().drag) {
|
|
5031
|
+
this.listen(this._rows, "pointerdown", (event) => this._handlePointerDown(event));
|
|
5032
|
+
this.listen(this._rows, "dragstart", (event) => this._handleDragStart(event));
|
|
5033
|
+
this.listen(this._rows, "dragover", (event) => this._handleDragOver(event));
|
|
5034
|
+
this.listen(this._rows, "dragleave", (event) => this._handleDragLeave(event));
|
|
5035
|
+
this.listen(this._rows, "drop", (event) => this._handleDrop(event));
|
|
5036
|
+
this.listen(this._rows, "dragend", () => this._finishDrag());
|
|
5037
|
+
this.listen(document, "pointerup", () => this._disarm());
|
|
5038
|
+
this.listen(document, "pointercancel", () => this._disarm());
|
|
5039
|
+
}
|
|
5013
5040
|
this._renderRows();
|
|
5014
5041
|
return root;
|
|
5015
5042
|
}
|
|
@@ -5032,16 +5059,27 @@ var MultiValueEditor = class extends Component {
|
|
|
5032
5059
|
if (!silent) this.emit("change", { values: this.getValues() });
|
|
5033
5060
|
return this;
|
|
5034
5061
|
}
|
|
5035
|
-
/** @
|
|
5036
|
-
|
|
5062
|
+
/** @returns {{drag: boolean, buttons: boolean}} */
|
|
5063
|
+
_reorder() {
|
|
5064
|
+
return REORDER[this.options.reorder] ?? REORDER.both;
|
|
5065
|
+
}
|
|
5066
|
+
/**
|
|
5067
|
+
* @param {number} [focusIndex=-1] Row to focus after the render, or -1 for none.
|
|
5068
|
+
* @param {keyof PARTS} [focusPart='value'] Which control of that row takes focus.
|
|
5069
|
+
* @returns {void}
|
|
5070
|
+
*/
|
|
5071
|
+
_renderRows(focusIndex = -1, focusPart = "value") {
|
|
5037
5072
|
const choices = normalizeOptions2(this.options.options);
|
|
5038
5073
|
const useSelect = this.options.options !== null;
|
|
5074
|
+
const { drag, buttons } = this._reorder();
|
|
5075
|
+
const last = this._values.length - 1;
|
|
5039
5076
|
const rows = this._values.map((value, index) => {
|
|
5077
|
+
const entries = !useSelect || choices.some(([option]) => option === value) ? choices : [[value, value], ...choices];
|
|
5040
5078
|
const control = useSelect ? h("select", {
|
|
5041
5079
|
class: "zx-multi-value-editor__value",
|
|
5042
5080
|
ariaLabel: `Value ${index + 1}`,
|
|
5043
5081
|
dataset: { index }
|
|
5044
|
-
},
|
|
5082
|
+
}, entries.map(([optionValue, label]) => h("option", {
|
|
5045
5083
|
value: optionValue,
|
|
5046
5084
|
selected: optionValue === value
|
|
5047
5085
|
}, label))) : h("input", {
|
|
@@ -5054,40 +5092,52 @@ var MultiValueEditor = class extends Component {
|
|
|
5054
5092
|
return h(
|
|
5055
5093
|
"div",
|
|
5056
5094
|
{ class: "zx-multi-value-editor__row", dataset: { row: index } },
|
|
5095
|
+
drag ? h("button", {
|
|
5096
|
+
class: "zx-multi-value-editor__handle",
|
|
5097
|
+
type: "button",
|
|
5098
|
+
ariaLabel: `Reorder value ${index + 1}. Use the arrow keys to move it.`,
|
|
5099
|
+
dataset: { action: "grab", index }
|
|
5100
|
+
}, icon("drag", { size: 12 })) : null,
|
|
5057
5101
|
control,
|
|
5058
5102
|
h(
|
|
5059
5103
|
"div",
|
|
5060
5104
|
{ class: "zx-multi-value-editor__controls" },
|
|
5061
|
-
h("button", {
|
|
5105
|
+
buttons ? h("button", {
|
|
5062
5106
|
class: "zx-btn",
|
|
5063
5107
|
type: "button",
|
|
5064
|
-
disabled
|
|
5108
|
+
// `aria-disabled` rather than `disabled`: the buttons re-render on every move, and a
|
|
5109
|
+
// control that disables itself under the reader's finger drops focus to the document.
|
|
5110
|
+
ariaDisabled: String(index === 0),
|
|
5065
5111
|
ariaLabel: `Move value ${index + 1} up`,
|
|
5066
5112
|
dataset: { action: "up", index }
|
|
5067
|
-
}, "
|
|
5068
|
-
h("button", {
|
|
5113
|
+
}, icon("chevron-up", { size: 12 })) : null,
|
|
5114
|
+
buttons ? h("button", {
|
|
5069
5115
|
class: "zx-btn",
|
|
5070
5116
|
type: "button",
|
|
5071
|
-
|
|
5117
|
+
ariaDisabled: String(index === last),
|
|
5072
5118
|
ariaLabel: `Move value ${index + 1} down`,
|
|
5073
5119
|
dataset: { action: "down", index }
|
|
5074
|
-
}, "
|
|
5120
|
+
}, icon("chevron-down", { size: 12 })) : null,
|
|
5075
5121
|
h("button", {
|
|
5076
5122
|
class: "zx-btn",
|
|
5077
5123
|
type: "button",
|
|
5078
5124
|
ariaLabel: `Remove value ${index + 1}`,
|
|
5079
5125
|
dataset: { action: "remove", index }
|
|
5080
|
-
}, "
|
|
5126
|
+
}, icon("x", { size: 12 }))
|
|
5081
5127
|
)
|
|
5082
5128
|
);
|
|
5083
5129
|
});
|
|
5084
5130
|
this._rows.replaceChildren(...rows);
|
|
5085
|
-
if (focusIndex >= 0)
|
|
5131
|
+
if (focusIndex >= 0) {
|
|
5132
|
+
const row2 = rows[Math.min(focusIndex, rows.length - 1)];
|
|
5133
|
+
row2?.querySelector(PARTS[focusPart] ?? PARTS.value)?.focus();
|
|
5134
|
+
}
|
|
5086
5135
|
}
|
|
5087
5136
|
/** @param {MouseEvent} event @returns {void} */
|
|
5088
5137
|
_handleClick(event) {
|
|
5089
5138
|
const button2 = event.target instanceof Element ? event.target.closest("[data-action]") : null;
|
|
5090
5139
|
if (!button2 || !this.el.contains(button2)) return;
|
|
5140
|
+
if (button2.getAttribute("aria-disabled") === "true") return;
|
|
5091
5141
|
const action = button2.dataset.action;
|
|
5092
5142
|
const index = Number(button2.dataset.index);
|
|
5093
5143
|
if (action === "add") {
|
|
@@ -5100,28 +5150,156 @@ var MultiValueEditor = class extends Component {
|
|
|
5100
5150
|
this._renderRows(Math.min(index, this._values.length - 1));
|
|
5101
5151
|
this._emitChange();
|
|
5102
5152
|
} else if ((action === "up" || action === "down") && Number.isInteger(index)) {
|
|
5103
|
-
|
|
5104
|
-
if (target < 0 || target >= this._values.length) return;
|
|
5105
|
-
[this._values[index], this._values[target]] = [this._values[target], this._values[index]];
|
|
5106
|
-
this._renderRows(target);
|
|
5107
|
-
this._emitChange();
|
|
5153
|
+
this._move(index, index + (action === "up" ? -1 : 1), action);
|
|
5108
5154
|
}
|
|
5109
5155
|
}
|
|
5110
5156
|
/** @param {Event} event @returns {void} */
|
|
5111
5157
|
_handleChange(event) {
|
|
5112
5158
|
const control = event.target instanceof Element ? event.target.closest("[data-index]") : null;
|
|
5113
|
-
if (!control) return;
|
|
5159
|
+
if (!control || !("value" in control)) return;
|
|
5114
5160
|
const index = Number(control.dataset.index);
|
|
5115
5161
|
if (!Number.isInteger(index) || index < 0 || index >= this._values.length) return;
|
|
5116
5162
|
this._values[index] = control.value;
|
|
5117
5163
|
this._emitChange();
|
|
5118
5164
|
}
|
|
5165
|
+
/**
|
|
5166
|
+
* The handle is the keyboard equivalent of dragging it, so `reorder: 'drag'` is still operable
|
|
5167
|
+
* without a pointer.
|
|
5168
|
+
* @param {KeyboardEvent} event
|
|
5169
|
+
* @returns {void}
|
|
5170
|
+
*/
|
|
5171
|
+
_handleKeydown(event) {
|
|
5172
|
+
const handle = event.target instanceof Element ? event.target.closest(".zx-multi-value-editor__handle") : null;
|
|
5173
|
+
if (!handle) return;
|
|
5174
|
+
const index = Number(handle.dataset.index);
|
|
5175
|
+
const target = event.key === "ArrowUp" ? index - 1 : event.key === "ArrowDown" ? index + 1 : event.key === "Home" ? 0 : event.key === "End" ? this._values.length - 1 : -1;
|
|
5176
|
+
if (target < 0 || target >= this._values.length) return;
|
|
5177
|
+
event.preventDefault();
|
|
5178
|
+
this._move(index, target, "handle");
|
|
5179
|
+
}
|
|
5180
|
+
/**
|
|
5181
|
+
* Arms the row for dragging. Only the handle does this, so text selection inside a row's input
|
|
5182
|
+
* keeps working and a stray drag cannot reorder anything.
|
|
5183
|
+
* @param {PointerEvent} event
|
|
5184
|
+
* @returns {void}
|
|
5185
|
+
*/
|
|
5186
|
+
_handlePointerDown(event) {
|
|
5187
|
+
const handle = event.target instanceof Element ? event.target.closest(".zx-multi-value-editor__handle") : null;
|
|
5188
|
+
this._disarm();
|
|
5189
|
+
if (!handle || handle.disabled) return;
|
|
5190
|
+
const row2 = (
|
|
5191
|
+
/** @type {HTMLElement|null} */
|
|
5192
|
+
handle.closest(".zx-multi-value-editor__row")
|
|
5193
|
+
);
|
|
5194
|
+
if (!row2) return;
|
|
5195
|
+
row2.draggable = true;
|
|
5196
|
+
this._armed = row2;
|
|
5197
|
+
}
|
|
5198
|
+
/** @returns {void} */
|
|
5199
|
+
_disarm() {
|
|
5200
|
+
if (this._armed) this._armed.draggable = false;
|
|
5201
|
+
this._armed = null;
|
|
5202
|
+
}
|
|
5203
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5204
|
+
_handleDragStart(event) {
|
|
5205
|
+
const row2 = event.target instanceof Element ? event.target.closest(".zx-multi-value-editor__row") : null;
|
|
5206
|
+
if (!row2 || row2 !== this._armed) {
|
|
5207
|
+
event.preventDefault();
|
|
5208
|
+
return;
|
|
5209
|
+
}
|
|
5210
|
+
this._dragIndex = Number(row2.dataset.row);
|
|
5211
|
+
row2.dataset.dragging = "true";
|
|
5212
|
+
this.el.setAttribute("data-state", "dragging");
|
|
5213
|
+
event.dataTransfer?.setData("text/plain", String(this._dragIndex));
|
|
5214
|
+
if (event.dataTransfer) event.dataTransfer.effectAllowed = "move";
|
|
5215
|
+
}
|
|
5216
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5217
|
+
_handleDragOver(event) {
|
|
5218
|
+
if (this._dragIndex < 0) return;
|
|
5219
|
+
event.preventDefault();
|
|
5220
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
|
|
5221
|
+
this._showDrop(this._dropTarget(event.clientY));
|
|
5222
|
+
}
|
|
5223
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5224
|
+
_handleDragLeave(event) {
|
|
5225
|
+
const to = event.relatedTarget;
|
|
5226
|
+
if (to instanceof Node && this._rows.contains(to)) return;
|
|
5227
|
+
this._clearDrop();
|
|
5228
|
+
}
|
|
5229
|
+
/** @param {DragEvent} event @returns {void} */
|
|
5230
|
+
_handleDrop(event) {
|
|
5231
|
+
if (this._dragIndex < 0) return;
|
|
5232
|
+
event.preventDefault();
|
|
5233
|
+
const from = this._dragIndex;
|
|
5234
|
+
const slot = this._dropTarget(event.clientY);
|
|
5235
|
+
this._finishDrag();
|
|
5236
|
+
this._move(from, slot > from ? slot - 1 : slot, "handle");
|
|
5237
|
+
}
|
|
5238
|
+
/** @returns {void} */
|
|
5239
|
+
_finishDrag() {
|
|
5240
|
+
this._dragIndex = -1;
|
|
5241
|
+
this._disarm();
|
|
5242
|
+
this._clearDrop();
|
|
5243
|
+
for (const row2 of this._rows.children) delete /** @type {HTMLElement} */
|
|
5244
|
+
row2.dataset.dragging;
|
|
5245
|
+
if (this._initialState === null) this.el.removeAttribute("data-state");
|
|
5246
|
+
else this.el.setAttribute("data-state", this._initialState);
|
|
5247
|
+
}
|
|
5248
|
+
/**
|
|
5249
|
+
* Insertion slot (0…length) for a pointer at `clientY`.
|
|
5250
|
+
* @param {number} clientY Pointer position.
|
|
5251
|
+
* @returns {number}
|
|
5252
|
+
*/
|
|
5253
|
+
_dropTarget(clientY) {
|
|
5254
|
+
const rows = [...this._rows.children];
|
|
5255
|
+
for (const [index, row2] of rows.entries()) {
|
|
5256
|
+
const box = row2.getBoundingClientRect();
|
|
5257
|
+
if (clientY < box.top + box.height / 2) return index;
|
|
5258
|
+
}
|
|
5259
|
+
return rows.length;
|
|
5260
|
+
}
|
|
5261
|
+
/** @param {number} slot Insertion slot to mark. @returns {void} */
|
|
5262
|
+
_showDrop(slot) {
|
|
5263
|
+
const rows = [...this._rows.children];
|
|
5264
|
+
const inert = slot === this._dragIndex || slot === this._dragIndex + 1;
|
|
5265
|
+
for (const [index, row2] of rows.entries()) {
|
|
5266
|
+
const cell = (
|
|
5267
|
+
/** @type {HTMLElement} */
|
|
5268
|
+
row2
|
|
5269
|
+
);
|
|
5270
|
+
if (!inert && index === slot) cell.dataset.drop = "before";
|
|
5271
|
+
else if (!inert && slot === rows.length && index === rows.length - 1) cell.dataset.drop = "after";
|
|
5272
|
+
else delete cell.dataset.drop;
|
|
5273
|
+
}
|
|
5274
|
+
}
|
|
5275
|
+
/** @returns {void} */
|
|
5276
|
+
_clearDrop() {
|
|
5277
|
+
for (const row2 of this._rows.children) delete /** @type {HTMLElement} */
|
|
5278
|
+
row2.dataset.drop;
|
|
5279
|
+
}
|
|
5280
|
+
/**
|
|
5281
|
+
* @param {number} from Index to move.
|
|
5282
|
+
* @param {number} to Destination index.
|
|
5283
|
+
* @param {keyof PARTS} part Control of the moved row that takes focus.
|
|
5284
|
+
* @returns {boolean} Whether anything moved.
|
|
5285
|
+
*/
|
|
5286
|
+
_move(from, to, part) {
|
|
5287
|
+
const size2 = this._values.length;
|
|
5288
|
+
if (from === to || from < 0 || to < 0 || from >= size2 || to >= size2) return false;
|
|
5289
|
+
const [value] = this._values.splice(from, 1);
|
|
5290
|
+
this._values.splice(to, 0, value);
|
|
5291
|
+
this._renderRows(to, part);
|
|
5292
|
+
this._emitChange();
|
|
5293
|
+
return true;
|
|
5294
|
+
}
|
|
5119
5295
|
/** @returns {void} */
|
|
5120
5296
|
_emitChange() {
|
|
5121
5297
|
this.emit("change", { values: this.getValues() });
|
|
5122
5298
|
}
|
|
5123
5299
|
/** @returns {void} */
|
|
5124
5300
|
destroy() {
|
|
5301
|
+
if (this._initialState === null) this.el?.removeAttribute("data-state");
|
|
5302
|
+
else this.el?.setAttribute("data-state", this._initialState);
|
|
5125
5303
|
for (const node of this._ownedNodes ?? []) node.remove();
|
|
5126
5304
|
super.destroy();
|
|
5127
5305
|
}
|
|
@@ -6191,6 +6369,7 @@ var Select = class _Select extends Component {
|
|
|
6191
6369
|
/** @type {SelectOptions} */
|
|
6192
6370
|
static defaults = {
|
|
6193
6371
|
items: [],
|
|
6372
|
+
fixedItems: [],
|
|
6194
6373
|
valueKey: "ID",
|
|
6195
6374
|
labelKey: "name",
|
|
6196
6375
|
renderItem: null,
|
|
@@ -6210,6 +6389,8 @@ var Select = class _Select extends Component {
|
|
|
6210
6389
|
render() {
|
|
6211
6390
|
this._items = [];
|
|
6212
6391
|
this._visibleItems = [];
|
|
6392
|
+
this._fixedItems = [];
|
|
6393
|
+
this._visibleFixed = [];
|
|
6213
6394
|
this._selected = null;
|
|
6214
6395
|
this._optionEntries = [];
|
|
6215
6396
|
this._activeIndex = -1;
|
|
@@ -6293,6 +6474,8 @@ var Select = class _Select extends Component {
|
|
|
6293
6474
|
root.replaceChildren(control, panel);
|
|
6294
6475
|
this._items = Array.isArray(this.options.items) ? this.options.items.slice() : [];
|
|
6295
6476
|
this._visibleItems = this._items.slice();
|
|
6477
|
+
this._fixedItems = Array.isArray(this.options.fixedItems) ? this.options.fixedItems.slice() : [];
|
|
6478
|
+
this._visibleFixed = this._fixedItems.slice();
|
|
6296
6479
|
this._disabled = Boolean(this.options.disabled);
|
|
6297
6480
|
this._handleTypeahead = typeahead(
|
|
6298
6481
|
() => this._optionEntries.filter((entry) => !entry.clear).map((entry) => entry.element),
|
|
@@ -6369,7 +6552,22 @@ var Select = class _Select extends Component {
|
|
|
6369
6552
|
const selectedValue = this.value;
|
|
6370
6553
|
this._items = Array.isArray(items) ? items.slice() : [];
|
|
6371
6554
|
this._visibleItems = this._items.slice();
|
|
6372
|
-
this._selected = selectedValue == null ? null : this.
|
|
6555
|
+
this._selected = selectedValue == null ? null : this._findItem(selectedValue);
|
|
6556
|
+
this._syncInputValue();
|
|
6557
|
+
this._renderOptions();
|
|
6558
|
+
this._position?.update();
|
|
6559
|
+
return this;
|
|
6560
|
+
}
|
|
6561
|
+
/**
|
|
6562
|
+
* Replaces the pinned choices and preserves selection by ID when possible.
|
|
6563
|
+
* @param {SelectItem[]} items New fixed items.
|
|
6564
|
+
* @returns {this}
|
|
6565
|
+
*/
|
|
6566
|
+
setFixedItems(items) {
|
|
6567
|
+
const selectedValue = this.value;
|
|
6568
|
+
this._fixedItems = Array.isArray(items) ? items.slice() : [];
|
|
6569
|
+
this._visibleFixed = this._fixedItems.slice();
|
|
6570
|
+
this._selected = selectedValue == null ? null : this._findItem(selectedValue);
|
|
6373
6571
|
this._syncInputValue();
|
|
6374
6572
|
this._renderOptions();
|
|
6375
6573
|
this._position?.update();
|
|
@@ -6423,6 +6621,7 @@ var Select = class _Select extends Component {
|
|
|
6423
6621
|
this.refs.input.removeAttribute("aria-activedescendant");
|
|
6424
6622
|
this._activeIndex = -1;
|
|
6425
6623
|
this._visibleItems = this._items.slice();
|
|
6624
|
+
this._visibleFixed = this._fixedItems.slice();
|
|
6426
6625
|
this._syncInputValue();
|
|
6427
6626
|
this.emit("close", {});
|
|
6428
6627
|
return this;
|
|
@@ -6478,6 +6677,38 @@ var Select = class _Select extends Component {
|
|
|
6478
6677
|
select.set(requestedValue, { silent: true });
|
|
6479
6678
|
return select;
|
|
6480
6679
|
}
|
|
6680
|
+
/**
|
|
6681
|
+
* Creates the record-permission picker: the fixed Private and Public choices above the groups a
|
|
6682
|
+
* record can be shared with. The value is the tri-state ZeyOS stores — `false` for private,
|
|
6683
|
+
* `true` for public, or a group ID — so the control binds straight to the record field.
|
|
6684
|
+
*
|
|
6685
|
+
* @param {Element|string|null} target Component target.
|
|
6686
|
+
* @param {SelectOptions & {groups?: SelectItem[]|((query: string) => Promise<SelectItem[]>|SelectItem[])}} [options={}]
|
|
6687
|
+
* Select overrides other than the fixed choices; `groups` holds the shareable groups, either as
|
|
6688
|
+
* an array (filtered locally) or as a loader function (queried remotely).
|
|
6689
|
+
* @returns {Select}
|
|
6690
|
+
*/
|
|
6691
|
+
static permission(target, options = {}) {
|
|
6692
|
+
const { groups = [], value = true, valueKey = "ID", labelKey = "name", ...rest } = options;
|
|
6693
|
+
const loader = typeof groups === "function" ? groups : null;
|
|
6694
|
+
const shareable = loader ? [] : groups;
|
|
6695
|
+
const choices = [{ ID: false, name: "Private" }, { ID: true, name: "Public" }];
|
|
6696
|
+
const select = new _Select(target, {
|
|
6697
|
+
filter: loader ?? "local",
|
|
6698
|
+
...rest,
|
|
6699
|
+
value: null,
|
|
6700
|
+
items: shareable,
|
|
6701
|
+
fixedItems: [],
|
|
6702
|
+
// The two choices carry their own ID and label, so a caller's group readers stay untouched.
|
|
6703
|
+
valueKey: (item) => choices.includes(item) ? item.ID : readItem(item, valueKey),
|
|
6704
|
+
labelKey: (item) => choices.includes(item) ? item.name : String(readItem(item, labelKey) ?? "")
|
|
6705
|
+
});
|
|
6706
|
+
choices[0].name = select._message("permission.private", "Private");
|
|
6707
|
+
choices[1].name = select._message("permission.public", "Public");
|
|
6708
|
+
select.setFixedItems(choices);
|
|
6709
|
+
select.set(value, { silent: true });
|
|
6710
|
+
return select;
|
|
6711
|
+
}
|
|
6481
6712
|
/** Cleans up the panel, pending async work, and enhanced target contents. @returns {void} */
|
|
6482
6713
|
destroy() {
|
|
6483
6714
|
this._cancelQuery();
|
|
@@ -6548,8 +6779,8 @@ var Select = class _Select extends Component {
|
|
|
6548
6779
|
);
|
|
6549
6780
|
if (!this._open) this.open();
|
|
6550
6781
|
if (this.options.filter === "local") {
|
|
6551
|
-
|
|
6552
|
-
this._visibleItems = matchItems(this._items, query,
|
|
6782
|
+
this._visibleFixed = this._matchFixed(query);
|
|
6783
|
+
this._visibleItems = matchItems(this._items, query, this._searchKeys());
|
|
6553
6784
|
this._renderOptions();
|
|
6554
6785
|
this._setActive(this._optionEntries.length > 0 ? 0 : -1);
|
|
6555
6786
|
this._position?.update();
|
|
@@ -6604,6 +6835,10 @@ var Select = class _Select extends Component {
|
|
|
6604
6835
|
if (this.options.clearable) {
|
|
6605
6836
|
this._appendOption(null, true, this.options.placeholder || this._message("select.none", "No selection"));
|
|
6606
6837
|
}
|
|
6838
|
+
for (const item of this._visibleFixed) this._appendOption(item, false);
|
|
6839
|
+
if (this._visibleFixed.length > 0 && this._visibleItems.length > 0 && !this.options.groupKey) {
|
|
6840
|
+
list.append(h("div", { class: "zx-select__separator", role: "presentation" }));
|
|
6841
|
+
}
|
|
6607
6842
|
for (const item of this._visibleItems) {
|
|
6608
6843
|
if (this.options.groupKey) {
|
|
6609
6844
|
const group = this._read(item, this.options.groupKey);
|
|
@@ -6617,7 +6852,7 @@ var Select = class _Select extends Component {
|
|
|
6617
6852
|
}
|
|
6618
6853
|
this._appendOption(item, false);
|
|
6619
6854
|
}
|
|
6620
|
-
if (this._visibleItems.length === 0 && !this._loading) {
|
|
6855
|
+
if (this._visibleItems.length === 0 && this._visibleFixed.length === 0 && !this._loading) {
|
|
6621
6856
|
list.append(h("div", {
|
|
6622
6857
|
class: "zx-select__empty",
|
|
6623
6858
|
role: "status"
|
|
@@ -6657,6 +6892,7 @@ var Select = class _Select extends Component {
|
|
|
6657
6892
|
if (this._queryTimer !== null) clearTimeout(this._queryTimer);
|
|
6658
6893
|
this._requestSequence += 1;
|
|
6659
6894
|
const sequence = this._requestSequence;
|
|
6895
|
+
this._visibleFixed = this._matchFixed(query);
|
|
6660
6896
|
if (query.length < Math.max(0, Number(this.options.minQuery) || 0)) {
|
|
6661
6897
|
this._setLoading(false);
|
|
6662
6898
|
this._visibleItems = [];
|
|
@@ -6741,13 +6977,14 @@ var Select = class _Select extends Component {
|
|
|
6741
6977
|
}
|
|
6742
6978
|
/** @param {unknown} candidate @returns {SelectItem|null} */
|
|
6743
6979
|
_findItem(candidate) {
|
|
6744
|
-
const
|
|
6980
|
+
const pool = this._fixedItems.length === 0 ? this._items : [...this._fixedItems, ...this._items];
|
|
6981
|
+
const direct = pool.find((item) => item === candidate);
|
|
6745
6982
|
if (direct !== void 0) return direct;
|
|
6746
6983
|
const value = candidate !== null && typeof candidate === "object" ? this._itemValue(
|
|
6747
6984
|
/** @type {SelectItem} */
|
|
6748
6985
|
candidate
|
|
6749
6986
|
) : candidate;
|
|
6750
|
-
return
|
|
6987
|
+
return pool.find((item) => Object.is(this._itemValue(item), value)) ?? null;
|
|
6751
6988
|
}
|
|
6752
6989
|
/** @param {SelectItem|null} item @returns {unknown} */
|
|
6753
6990
|
_itemValue(item) {
|
|
@@ -6760,9 +6997,20 @@ var Select = class _Select extends Component {
|
|
|
6760
6997
|
}
|
|
6761
6998
|
/** @param {SelectItem|null} item @param {SelectValueReader|SelectLabelReader} reader @returns {unknown} */
|
|
6762
6999
|
_read(item, reader) {
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
7000
|
+
return readItem(item, reader);
|
|
7001
|
+
}
|
|
7002
|
+
/** @returns {Array<SelectValueReader|SelectLabelReader>} */
|
|
7003
|
+
_searchKeys() {
|
|
7004
|
+
return this.options.searchKeys ?? [this.options.labelKey];
|
|
7005
|
+
}
|
|
7006
|
+
/**
|
|
7007
|
+
* Narrows the pinned choices. They are always matched here and never handed to a filter
|
|
7008
|
+
* function: a fixed choice belongs to the control, not to whatever the remote source returns.
|
|
7009
|
+
* @param {string} query Search text.
|
|
7010
|
+
* @returns {SelectItem[]}
|
|
7011
|
+
*/
|
|
7012
|
+
_matchFixed(query) {
|
|
7013
|
+
return matchItems(this._fixedItems, query, this._searchKeys());
|
|
6766
7014
|
}
|
|
6767
7015
|
/** @returns {number} */
|
|
6768
7016
|
_selectedEntryIndex() {
|
|
@@ -6785,6 +7033,11 @@ var Select = class _Select extends Component {
|
|
|
6785
7033
|
return message === key ? fallback : message;
|
|
6786
7034
|
}
|
|
6787
7035
|
};
|
|
7036
|
+
function readItem(item, reader) {
|
|
7037
|
+
if (typeof reader === "function") return reader(item);
|
|
7038
|
+
if (item === null || typeof item !== "object") return item;
|
|
7039
|
+
return item[reader];
|
|
7040
|
+
}
|
|
6788
7041
|
function isPrintable(event) {
|
|
6789
7042
|
return event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
6790
7043
|
}
|
|
@@ -10298,6 +10551,10 @@ var Tabbox = class extends Component {
|
|
|
10298
10551
|
if (!tab || !tablist.contains(tab)) return;
|
|
10299
10552
|
const record = this._recordForTab(tab);
|
|
10300
10553
|
if (!record || record.definition.disabled) return;
|
|
10554
|
+
if (event.target.closest?.(".zx-tabbox__close")) {
|
|
10555
|
+
this._closeTab(record);
|
|
10556
|
+
return;
|
|
10557
|
+
}
|
|
10301
10558
|
this._setFocused(record);
|
|
10302
10559
|
this.openTab(record.definition.name);
|
|
10303
10560
|
});
|
|
@@ -10347,8 +10604,9 @@ var Tabbox = class extends Component {
|
|
|
10347
10604
|
children.push(title, badge2);
|
|
10348
10605
|
if (normalized.closable) {
|
|
10349
10606
|
children.push(h("span", {
|
|
10350
|
-
class: "zx-tabbox__close",
|
|
10351
|
-
ariaHidden: "true"
|
|
10607
|
+
class: "zx-tabbox__close zx-icon-btn",
|
|
10608
|
+
ariaHidden: "true",
|
|
10609
|
+
title: "Close"
|
|
10352
10610
|
}, icon("x", { size: 14 })));
|
|
10353
10611
|
}
|
|
10354
10612
|
const tab = (
|
|
@@ -10522,13 +10780,23 @@ var Tabbox = class extends Component {
|
|
|
10522
10780
|
}
|
|
10523
10781
|
if (event.key === "Delete" && current.definition.closable) {
|
|
10524
10782
|
event.preventDefault();
|
|
10525
|
-
|
|
10526
|
-
if (!this._removeTab(current.definition.name, true)) return;
|
|
10527
|
-
const remaining = this._tabs[Math.min(index, this._tabs.length - 1)] ?? this._tabs[index - 1];
|
|
10528
|
-
const focusTarget = remaining && !remaining.definition.disabled ? remaining : this._active;
|
|
10529
|
-
if (focusTarget) this._setFocused(focusTarget);
|
|
10783
|
+
this._closeTab(current);
|
|
10530
10784
|
}
|
|
10531
10785
|
}
|
|
10786
|
+
/**
|
|
10787
|
+
* Closes a tab on a user gesture, keeping roving focus inside the tablist.
|
|
10788
|
+
* @param {TabRecord} record Tab to close.
|
|
10789
|
+
* @returns {void}
|
|
10790
|
+
* @fires Tabbox#close
|
|
10791
|
+
*/
|
|
10792
|
+
_closeTab(record) {
|
|
10793
|
+
const index = this._tabs.indexOf(record);
|
|
10794
|
+
const held = record.tab.contains(document.activeElement);
|
|
10795
|
+
if (!this._removeTab(record.definition.name, true)) return;
|
|
10796
|
+
const remaining = this._tabs[Math.min(index, this._tabs.length - 1)] ?? this._tabs[index - 1];
|
|
10797
|
+
const focusTarget = remaining && !remaining.definition.disabled ? remaining : this._active;
|
|
10798
|
+
if (focusTarget) this._setFocused(focusTarget, held);
|
|
10799
|
+
}
|
|
10532
10800
|
/** @param {string} name @param {boolean} emitClose @returns {boolean} */
|
|
10533
10801
|
_removeTab(name, emitClose) {
|
|
10534
10802
|
const record = this._find(name);
|