@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.
Files changed (39) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +28 -2
  3. package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +67 -8
  4. package/dist/types/components/permission/permission.d.ts +3 -0
  5. package/dist/types/components/select/select.d.ts +36 -1
  6. package/dist/types/components/tabbox/tabbox.d.ts +12 -3
  7. package/dist/zx-compat.esm.js +302 -34
  8. package/dist/zx-compat.esm.min.js +1 -1
  9. package/dist/zx-compat.global.js +302 -34
  10. package/dist/zx-compat.global.min.js +3 -3
  11. package/dist/zx-zeyos.esm.js +280 -27
  12. package/dist/zx-zeyos.esm.min.js +1 -1
  13. package/dist/zx.css +159 -33
  14. package/dist/zx.esm.js +302 -34
  15. package/dist/zx.esm.min.js +3 -3
  16. package/dist/zx.global.js +302 -34
  17. package/dist/zx.global.min.js +3 -3
  18. package/dist/zx.min.css +1 -1
  19. package/docs/api.json +420 -17
  20. package/docs/llms.md +54 -12
  21. package/package.json +1 -1
  22. package/src/components/date-picker/date-range-picker.css +7 -0
  23. package/src/components/fieldset/fieldset.css +7 -0
  24. package/src/components/form/form.css +7 -0
  25. package/src/components/multi-value-editor/multi-value-editor.css +99 -15
  26. package/src/components/multi-value-editor/multi-value-editor.js +235 -18
  27. package/src/components/navigation-bar/navigation-bar.css +7 -0
  28. package/src/components/permission/permission.js +3 -0
  29. package/src/components/search/search.css +10 -2
  30. package/src/components/select/select.css +6 -0
  31. package/src/components/select/select.js +99 -11
  32. package/src/components/stepper/stepper.css +12 -2
  33. package/src/components/tabbox/tabbox.css +13 -3
  34. package/src/components/tabbox/tabbox.js +34 -10
  35. package/styles/tokens/dark.css +13 -10
  36. package/styles/tokens/global.css +12 -0
  37. package/styles/tokens/semantic.css +9 -5
  38. package/styles/tokens/themes.css +72 -0
  39. package/styles/zx.css +1 -0
@@ -5250,18 +5250,34 @@ function registerFieldUploadAdapter() {
5250
5250
  }
5251
5251
 
5252
5252
  // src/components/multi-value-editor/multi-value-editor.js
5253
+ var REORDER = Object.freeze({
5254
+ both: { drag: true, buttons: true },
5255
+ buttons: { drag: false, buttons: true },
5256
+ drag: { drag: true, buttons: false },
5257
+ none: { drag: false, buttons: false }
5258
+ });
5259
+ var PARTS = Object.freeze({
5260
+ value: ".zx-multi-value-editor__value",
5261
+ handle: ".zx-multi-value-editor__handle",
5262
+ up: '[data-action="up"]',
5263
+ down: '[data-action="down"]'
5264
+ });
5253
5265
  var MultiValueEditor = class extends Component {
5254
5266
  static cssName = "multi-value-editor";
5255
5267
  /** @type {MultiValueEditorOptions} */
5256
- static defaults = { values: [], options: null, addLabel: "Add value" };
5268
+ static defaults = { values: [], options: null, addLabel: "Add value", reorder: "both" };
5257
5269
  /** @returns {HTMLElement} */
5258
5270
  render() {
5259
5271
  const root = (
5260
5272
  /** @type {HTMLElement} */
5261
5273
  this.el ?? h("div")
5262
5274
  );
5275
+ this.el = root;
5263
5276
  this._values = Array.isArray(this.options.values) ? this.options.values.map(String) : [];
5264
5277
  this._ownedNodes = [];
5278
+ this._initialState = root.getAttribute("data-state");
5279
+ this._dragIndex = -1;
5280
+ this._armed = null;
5265
5281
  this._rows = h("div", { class: "zx-multi-value-editor__rows" });
5266
5282
  this._add = h("button", {
5267
5283
  class: "zx-btn zx-multi-value-editor__add",
@@ -5272,6 +5288,17 @@ var MultiValueEditor = class extends Component {
5272
5288
  root.append(this._rows, this._add);
5273
5289
  this.listen(root, "click", (event) => this._handleClick(event));
5274
5290
  this.listen(this._rows, "change", (event) => this._handleChange(event));
5291
+ this.listen(this._rows, "keydown", (event) => this._handleKeydown(event));
5292
+ if (this._reorder().drag) {
5293
+ this.listen(this._rows, "pointerdown", (event) => this._handlePointerDown(event));
5294
+ this.listen(this._rows, "dragstart", (event) => this._handleDragStart(event));
5295
+ this.listen(this._rows, "dragover", (event) => this._handleDragOver(event));
5296
+ this.listen(this._rows, "dragleave", (event) => this._handleDragLeave(event));
5297
+ this.listen(this._rows, "drop", (event) => this._handleDrop(event));
5298
+ this.listen(this._rows, "dragend", () => this._finishDrag());
5299
+ this.listen(document, "pointerup", () => this._disarm());
5300
+ this.listen(document, "pointercancel", () => this._disarm());
5301
+ }
5275
5302
  this._renderRows();
5276
5303
  return root;
5277
5304
  }
@@ -5294,16 +5321,27 @@ var MultiValueEditor = class extends Component {
5294
5321
  if (!silent) this.emit("change", { values: this.getValues() });
5295
5322
  return this;
5296
5323
  }
5297
- /** @param {number} [focusIndex=-1] @returns {void} */
5298
- _renderRows(focusIndex = -1) {
5324
+ /** @returns {{drag: boolean, buttons: boolean}} */
5325
+ _reorder() {
5326
+ return REORDER[this.options.reorder] ?? REORDER.both;
5327
+ }
5328
+ /**
5329
+ * @param {number} [focusIndex=-1] Row to focus after the render, or -1 for none.
5330
+ * @param {keyof PARTS} [focusPart='value'] Which control of that row takes focus.
5331
+ * @returns {void}
5332
+ */
5333
+ _renderRows(focusIndex = -1, focusPart = "value") {
5299
5334
  const choices = normalizeOptions2(this.options.options);
5300
5335
  const useSelect = this.options.options !== null;
5336
+ const { drag, buttons } = this._reorder();
5337
+ const last = this._values.length - 1;
5301
5338
  const rows = this._values.map((value, index) => {
5339
+ const entries = !useSelect || choices.some(([option]) => option === value) ? choices : [[value, value], ...choices];
5302
5340
  const control = useSelect ? h("select", {
5303
5341
  class: "zx-multi-value-editor__value",
5304
5342
  ariaLabel: `Value ${index + 1}`,
5305
5343
  dataset: { index }
5306
- }, choices.map(([optionValue, label]) => h("option", {
5344
+ }, entries.map(([optionValue, label]) => h("option", {
5307
5345
  value: optionValue,
5308
5346
  selected: optionValue === value
5309
5347
  }, label))) : h("input", {
@@ -5316,40 +5354,52 @@ var MultiValueEditor = class extends Component {
5316
5354
  return h(
5317
5355
  "div",
5318
5356
  { class: "zx-multi-value-editor__row", dataset: { row: index } },
5357
+ drag ? h("button", {
5358
+ class: "zx-multi-value-editor__handle",
5359
+ type: "button",
5360
+ ariaLabel: `Reorder value ${index + 1}. Use the arrow keys to move it.`,
5361
+ dataset: { action: "grab", index }
5362
+ }, icon("drag", { size: 12 })) : null,
5319
5363
  control,
5320
5364
  h(
5321
5365
  "div",
5322
5366
  { class: "zx-multi-value-editor__controls" },
5323
- h("button", {
5367
+ buttons ? h("button", {
5324
5368
  class: "zx-btn",
5325
5369
  type: "button",
5326
- disabled: index === 0,
5370
+ // `aria-disabled` rather than `disabled`: the buttons re-render on every move, and a
5371
+ // control that disables itself under the reader's finger drops focus to the document.
5372
+ ariaDisabled: String(index === 0),
5327
5373
  ariaLabel: `Move value ${index + 1} up`,
5328
5374
  dataset: { action: "up", index }
5329
- }, "\u2191"),
5330
- h("button", {
5375
+ }, icon("chevron-up", { size: 12 })) : null,
5376
+ buttons ? h("button", {
5331
5377
  class: "zx-btn",
5332
5378
  type: "button",
5333
- disabled: index === this._values.length - 1,
5379
+ ariaDisabled: String(index === last),
5334
5380
  ariaLabel: `Move value ${index + 1} down`,
5335
5381
  dataset: { action: "down", index }
5336
- }, "\u2193"),
5382
+ }, icon("chevron-down", { size: 12 })) : null,
5337
5383
  h("button", {
5338
5384
  class: "zx-btn",
5339
5385
  type: "button",
5340
5386
  ariaLabel: `Remove value ${index + 1}`,
5341
5387
  dataset: { action: "remove", index }
5342
- }, "\xD7")
5388
+ }, icon("x", { size: 12 }))
5343
5389
  )
5344
5390
  );
5345
5391
  });
5346
5392
  this._rows.replaceChildren(...rows);
5347
- if (focusIndex >= 0) rows[focusIndex]?.querySelector(".zx-multi-value-editor__value")?.focus();
5393
+ if (focusIndex >= 0) {
5394
+ const row = rows[Math.min(focusIndex, rows.length - 1)];
5395
+ row?.querySelector(PARTS[focusPart] ?? PARTS.value)?.focus();
5396
+ }
5348
5397
  }
5349
5398
  /** @param {MouseEvent} event @returns {void} */
5350
5399
  _handleClick(event) {
5351
5400
  const button = event.target instanceof Element ? event.target.closest("[data-action]") : null;
5352
5401
  if (!button || !this.el.contains(button)) return;
5402
+ if (button.getAttribute("aria-disabled") === "true") return;
5353
5403
  const action = button.dataset.action;
5354
5404
  const index = Number(button.dataset.index);
5355
5405
  if (action === "add") {
@@ -5362,28 +5412,156 @@ var MultiValueEditor = class extends Component {
5362
5412
  this._renderRows(Math.min(index, this._values.length - 1));
5363
5413
  this._emitChange();
5364
5414
  } else if ((action === "up" || action === "down") && Number.isInteger(index)) {
5365
- const target = index + (action === "up" ? -1 : 1);
5366
- if (target < 0 || target >= this._values.length) return;
5367
- [this._values[index], this._values[target]] = [this._values[target], this._values[index]];
5368
- this._renderRows(target);
5369
- this._emitChange();
5415
+ this._move(index, index + (action === "up" ? -1 : 1), action);
5370
5416
  }
5371
5417
  }
5372
5418
  /** @param {Event} event @returns {void} */
5373
5419
  _handleChange(event) {
5374
5420
  const control = event.target instanceof Element ? event.target.closest("[data-index]") : null;
5375
- if (!control) return;
5421
+ if (!control || !("value" in control)) return;
5376
5422
  const index = Number(control.dataset.index);
5377
5423
  if (!Number.isInteger(index) || index < 0 || index >= this._values.length) return;
5378
5424
  this._values[index] = control.value;
5379
5425
  this._emitChange();
5380
5426
  }
5427
+ /**
5428
+ * The handle is the keyboard equivalent of dragging it, so `reorder: 'drag'` is still operable
5429
+ * without a pointer.
5430
+ * @param {KeyboardEvent} event
5431
+ * @returns {void}
5432
+ */
5433
+ _handleKeydown(event) {
5434
+ const handle = event.target instanceof Element ? event.target.closest(".zx-multi-value-editor__handle") : null;
5435
+ if (!handle) return;
5436
+ const index = Number(handle.dataset.index);
5437
+ const target = event.key === "ArrowUp" ? index - 1 : event.key === "ArrowDown" ? index + 1 : event.key === "Home" ? 0 : event.key === "End" ? this._values.length - 1 : -1;
5438
+ if (target < 0 || target >= this._values.length) return;
5439
+ event.preventDefault();
5440
+ this._move(index, target, "handle");
5441
+ }
5442
+ /**
5443
+ * Arms the row for dragging. Only the handle does this, so text selection inside a row's input
5444
+ * keeps working and a stray drag cannot reorder anything.
5445
+ * @param {PointerEvent} event
5446
+ * @returns {void}
5447
+ */
5448
+ _handlePointerDown(event) {
5449
+ const handle = event.target instanceof Element ? event.target.closest(".zx-multi-value-editor__handle") : null;
5450
+ this._disarm();
5451
+ if (!handle || handle.disabled) return;
5452
+ const row = (
5453
+ /** @type {HTMLElement|null} */
5454
+ handle.closest(".zx-multi-value-editor__row")
5455
+ );
5456
+ if (!row) return;
5457
+ row.draggable = true;
5458
+ this._armed = row;
5459
+ }
5460
+ /** @returns {void} */
5461
+ _disarm() {
5462
+ if (this._armed) this._armed.draggable = false;
5463
+ this._armed = null;
5464
+ }
5465
+ /** @param {DragEvent} event @returns {void} */
5466
+ _handleDragStart(event) {
5467
+ const row = event.target instanceof Element ? event.target.closest(".zx-multi-value-editor__row") : null;
5468
+ if (!row || row !== this._armed) {
5469
+ event.preventDefault();
5470
+ return;
5471
+ }
5472
+ this._dragIndex = Number(row.dataset.row);
5473
+ row.dataset.dragging = "true";
5474
+ this.el.setAttribute("data-state", "dragging");
5475
+ event.dataTransfer?.setData("text/plain", String(this._dragIndex));
5476
+ if (event.dataTransfer) event.dataTransfer.effectAllowed = "move";
5477
+ }
5478
+ /** @param {DragEvent} event @returns {void} */
5479
+ _handleDragOver(event) {
5480
+ if (this._dragIndex < 0) return;
5481
+ event.preventDefault();
5482
+ if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
5483
+ this._showDrop(this._dropTarget(event.clientY));
5484
+ }
5485
+ /** @param {DragEvent} event @returns {void} */
5486
+ _handleDragLeave(event) {
5487
+ const to = event.relatedTarget;
5488
+ if (to instanceof Node && this._rows.contains(to)) return;
5489
+ this._clearDrop();
5490
+ }
5491
+ /** @param {DragEvent} event @returns {void} */
5492
+ _handleDrop(event) {
5493
+ if (this._dragIndex < 0) return;
5494
+ event.preventDefault();
5495
+ const from = this._dragIndex;
5496
+ const slot = this._dropTarget(event.clientY);
5497
+ this._finishDrag();
5498
+ this._move(from, slot > from ? slot - 1 : slot, "handle");
5499
+ }
5500
+ /** @returns {void} */
5501
+ _finishDrag() {
5502
+ this._dragIndex = -1;
5503
+ this._disarm();
5504
+ this._clearDrop();
5505
+ for (const row of this._rows.children) delete /** @type {HTMLElement} */
5506
+ row.dataset.dragging;
5507
+ if (this._initialState === null) this.el.removeAttribute("data-state");
5508
+ else this.el.setAttribute("data-state", this._initialState);
5509
+ }
5510
+ /**
5511
+ * Insertion slot (0…length) for a pointer at `clientY`.
5512
+ * @param {number} clientY Pointer position.
5513
+ * @returns {number}
5514
+ */
5515
+ _dropTarget(clientY) {
5516
+ const rows = [...this._rows.children];
5517
+ for (const [index, row] of rows.entries()) {
5518
+ const box = row.getBoundingClientRect();
5519
+ if (clientY < box.top + box.height / 2) return index;
5520
+ }
5521
+ return rows.length;
5522
+ }
5523
+ /** @param {number} slot Insertion slot to mark. @returns {void} */
5524
+ _showDrop(slot) {
5525
+ const rows = [...this._rows.children];
5526
+ const inert = slot === this._dragIndex || slot === this._dragIndex + 1;
5527
+ for (const [index, row] of rows.entries()) {
5528
+ const cell = (
5529
+ /** @type {HTMLElement} */
5530
+ row
5531
+ );
5532
+ if (!inert && index === slot) cell.dataset.drop = "before";
5533
+ else if (!inert && slot === rows.length && index === rows.length - 1) cell.dataset.drop = "after";
5534
+ else delete cell.dataset.drop;
5535
+ }
5536
+ }
5537
+ /** @returns {void} */
5538
+ _clearDrop() {
5539
+ for (const row of this._rows.children) delete /** @type {HTMLElement} */
5540
+ row.dataset.drop;
5541
+ }
5542
+ /**
5543
+ * @param {number} from Index to move.
5544
+ * @param {number} to Destination index.
5545
+ * @param {keyof PARTS} part Control of the moved row that takes focus.
5546
+ * @returns {boolean} Whether anything moved.
5547
+ */
5548
+ _move(from, to, part) {
5549
+ const size = this._values.length;
5550
+ if (from === to || from < 0 || to < 0 || from >= size || to >= size) return false;
5551
+ const [value] = this._values.splice(from, 1);
5552
+ this._values.splice(to, 0, value);
5553
+ this._renderRows(to, part);
5554
+ this._emitChange();
5555
+ return true;
5556
+ }
5381
5557
  /** @returns {void} */
5382
5558
  _emitChange() {
5383
5559
  this.emit("change", { values: this.getValues() });
5384
5560
  }
5385
5561
  /** @returns {void} */
5386
5562
  destroy() {
5563
+ if (this._initialState === null) this.el?.removeAttribute("data-state");
5564
+ else this.el?.setAttribute("data-state", this._initialState);
5387
5565
  for (const node of this._ownedNodes ?? []) node.remove();
5388
5566
  super.destroy();
5389
5567
  }
@@ -6311,6 +6489,7 @@ var Select = class _Select extends Component {
6311
6489
  /** @type {SelectOptions} */
6312
6490
  static defaults = {
6313
6491
  items: [],
6492
+ fixedItems: [],
6314
6493
  valueKey: "ID",
6315
6494
  labelKey: "name",
6316
6495
  renderItem: null,
@@ -6330,6 +6509,8 @@ var Select = class _Select extends Component {
6330
6509
  render() {
6331
6510
  this._items = [];
6332
6511
  this._visibleItems = [];
6512
+ this._fixedItems = [];
6513
+ this._visibleFixed = [];
6333
6514
  this._selected = null;
6334
6515
  this._optionEntries = [];
6335
6516
  this._activeIndex = -1;
@@ -6413,6 +6594,8 @@ var Select = class _Select extends Component {
6413
6594
  root.replaceChildren(control, panel);
6414
6595
  this._items = Array.isArray(this.options.items) ? this.options.items.slice() : [];
6415
6596
  this._visibleItems = this._items.slice();
6597
+ this._fixedItems = Array.isArray(this.options.fixedItems) ? this.options.fixedItems.slice() : [];
6598
+ this._visibleFixed = this._fixedItems.slice();
6416
6599
  this._disabled = Boolean(this.options.disabled);
6417
6600
  this._handleTypeahead = typeahead(
6418
6601
  () => this._optionEntries.filter((entry) => !entry.clear).map((entry) => entry.element),
@@ -6489,7 +6672,22 @@ var Select = class _Select extends Component {
6489
6672
  const selectedValue = this.value;
6490
6673
  this._items = Array.isArray(items) ? items.slice() : [];
6491
6674
  this._visibleItems = this._items.slice();
6492
- this._selected = selectedValue == null ? null : this._items.find((item) => Object.is(this._itemValue(item), selectedValue)) ?? null;
6675
+ this._selected = selectedValue == null ? null : this._findItem(selectedValue);
6676
+ this._syncInputValue();
6677
+ this._renderOptions();
6678
+ this._position?.update();
6679
+ return this;
6680
+ }
6681
+ /**
6682
+ * Replaces the pinned choices and preserves selection by ID when possible.
6683
+ * @param {SelectItem[]} items New fixed items.
6684
+ * @returns {this}
6685
+ */
6686
+ setFixedItems(items) {
6687
+ const selectedValue = this.value;
6688
+ this._fixedItems = Array.isArray(items) ? items.slice() : [];
6689
+ this._visibleFixed = this._fixedItems.slice();
6690
+ this._selected = selectedValue == null ? null : this._findItem(selectedValue);
6493
6691
  this._syncInputValue();
6494
6692
  this._renderOptions();
6495
6693
  this._position?.update();
@@ -6543,6 +6741,7 @@ var Select = class _Select extends Component {
6543
6741
  this.refs.input.removeAttribute("aria-activedescendant");
6544
6742
  this._activeIndex = -1;
6545
6743
  this._visibleItems = this._items.slice();
6744
+ this._visibleFixed = this._fixedItems.slice();
6546
6745
  this._syncInputValue();
6547
6746
  this.emit("close", {});
6548
6747
  return this;
@@ -6598,6 +6797,38 @@ var Select = class _Select extends Component {
6598
6797
  select.set(requestedValue, { silent: true });
6599
6798
  return select;
6600
6799
  }
6800
+ /**
6801
+ * Creates the record-permission picker: the fixed Private and Public choices above the groups a
6802
+ * record can be shared with. The value is the tri-state ZeyOS stores — `false` for private,
6803
+ * `true` for public, or a group ID — so the control binds straight to the record field.
6804
+ *
6805
+ * @param {Element|string|null} target Component target.
6806
+ * @param {SelectOptions & {groups?: SelectItem[]|((query: string) => Promise<SelectItem[]>|SelectItem[])}} [options={}]
6807
+ * Select overrides other than the fixed choices; `groups` holds the shareable groups, either as
6808
+ * an array (filtered locally) or as a loader function (queried remotely).
6809
+ * @returns {Select}
6810
+ */
6811
+ static permission(target, options = {}) {
6812
+ const { groups = [], value = true, valueKey = "ID", labelKey = "name", ...rest } = options;
6813
+ const loader = typeof groups === "function" ? groups : null;
6814
+ const shareable = loader ? [] : groups;
6815
+ const choices = [{ ID: false, name: "Private" }, { ID: true, name: "Public" }];
6816
+ const select = new _Select(target, {
6817
+ filter: loader ?? "local",
6818
+ ...rest,
6819
+ value: null,
6820
+ items: shareable,
6821
+ fixedItems: [],
6822
+ // The two choices carry their own ID and label, so a caller's group readers stay untouched.
6823
+ valueKey: (item) => choices.includes(item) ? item.ID : readItem(item, valueKey),
6824
+ labelKey: (item) => choices.includes(item) ? item.name : String(readItem(item, labelKey) ?? "")
6825
+ });
6826
+ choices[0].name = select._message("permission.private", "Private");
6827
+ choices[1].name = select._message("permission.public", "Public");
6828
+ select.setFixedItems(choices);
6829
+ select.set(value, { silent: true });
6830
+ return select;
6831
+ }
6601
6832
  /** Cleans up the panel, pending async work, and enhanced target contents. @returns {void} */
6602
6833
  destroy() {
6603
6834
  this._cancelQuery();
@@ -6668,8 +6899,8 @@ var Select = class _Select extends Component {
6668
6899
  );
6669
6900
  if (!this._open) this.open();
6670
6901
  if (this.options.filter === "local") {
6671
- const keys = this.options.searchKeys ?? [this.options.labelKey];
6672
- this._visibleItems = matchItems(this._items, query, keys);
6902
+ this._visibleFixed = this._matchFixed(query);
6903
+ this._visibleItems = matchItems(this._items, query, this._searchKeys());
6673
6904
  this._renderOptions();
6674
6905
  this._setActive(this._optionEntries.length > 0 ? 0 : -1);
6675
6906
  this._position?.update();
@@ -6724,6 +6955,10 @@ var Select = class _Select extends Component {
6724
6955
  if (this.options.clearable) {
6725
6956
  this._appendOption(null, true, this.options.placeholder || this._message("select.none", "No selection"));
6726
6957
  }
6958
+ for (const item of this._visibleFixed) this._appendOption(item, false);
6959
+ if (this._visibleFixed.length > 0 && this._visibleItems.length > 0 && !this.options.groupKey) {
6960
+ list.append(h("div", { class: "zx-select__separator", role: "presentation" }));
6961
+ }
6727
6962
  for (const item of this._visibleItems) {
6728
6963
  if (this.options.groupKey) {
6729
6964
  const group = this._read(item, this.options.groupKey);
@@ -6737,7 +6972,7 @@ var Select = class _Select extends Component {
6737
6972
  }
6738
6973
  this._appendOption(item, false);
6739
6974
  }
6740
- if (this._visibleItems.length === 0 && !this._loading) {
6975
+ if (this._visibleItems.length === 0 && this._visibleFixed.length === 0 && !this._loading) {
6741
6976
  list.append(h("div", {
6742
6977
  class: "zx-select__empty",
6743
6978
  role: "status"
@@ -6777,6 +7012,7 @@ var Select = class _Select extends Component {
6777
7012
  if (this._queryTimer !== null) clearTimeout(this._queryTimer);
6778
7013
  this._requestSequence += 1;
6779
7014
  const sequence = this._requestSequence;
7015
+ this._visibleFixed = this._matchFixed(query);
6780
7016
  if (query.length < Math.max(0, Number(this.options.minQuery) || 0)) {
6781
7017
  this._setLoading(false);
6782
7018
  this._visibleItems = [];
@@ -6861,13 +7097,14 @@ var Select = class _Select extends Component {
6861
7097
  }
6862
7098
  /** @param {unknown} candidate @returns {SelectItem|null} */
6863
7099
  _findItem(candidate) {
6864
- const direct = this._items.find((item) => item === candidate);
7100
+ const pool = this._fixedItems.length === 0 ? this._items : [...this._fixedItems, ...this._items];
7101
+ const direct = pool.find((item) => item === candidate);
6865
7102
  if (direct !== void 0) return direct;
6866
7103
  const value = candidate !== null && typeof candidate === "object" ? this._itemValue(
6867
7104
  /** @type {SelectItem} */
6868
7105
  candidate
6869
7106
  ) : candidate;
6870
- return this._items.find((item) => Object.is(this._itemValue(item), value)) ?? null;
7107
+ return pool.find((item) => Object.is(this._itemValue(item), value)) ?? null;
6871
7108
  }
6872
7109
  /** @param {SelectItem|null} item @returns {unknown} */
6873
7110
  _itemValue(item) {
@@ -6880,9 +7117,20 @@ var Select = class _Select extends Component {
6880
7117
  }
6881
7118
  /** @param {SelectItem|null} item @param {SelectValueReader|SelectLabelReader} reader @returns {unknown} */
6882
7119
  _read(item, reader) {
6883
- if (typeof reader === "function") return reader(item);
6884
- if (item === null || typeof item !== "object") return item;
6885
- return item[reader];
7120
+ return readItem(item, reader);
7121
+ }
7122
+ /** @returns {Array<SelectValueReader|SelectLabelReader>} */
7123
+ _searchKeys() {
7124
+ return this.options.searchKeys ?? [this.options.labelKey];
7125
+ }
7126
+ /**
7127
+ * Narrows the pinned choices. They are always matched here and never handed to a filter
7128
+ * function: a fixed choice belongs to the control, not to whatever the remote source returns.
7129
+ * @param {string} query Search text.
7130
+ * @returns {SelectItem[]}
7131
+ */
7132
+ _matchFixed(query) {
7133
+ return matchItems(this._fixedItems, query, this._searchKeys());
6886
7134
  }
6887
7135
  /** @returns {number} */
6888
7136
  _selectedEntryIndex() {
@@ -6905,6 +7153,11 @@ var Select = class _Select extends Component {
6905
7153
  return message === key ? fallback : message;
6906
7154
  }
6907
7155
  };
7156
+ function readItem(item, reader) {
7157
+ if (typeof reader === "function") return reader(item);
7158
+ if (item === null || typeof item !== "object") return item;
7159
+ return item[reader];
7160
+ }
6908
7161
  function isPrintable(event) {
6909
7162
  return event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
6910
7163
  }