@ixfx/components 0.11.6 → 0.11.7

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 (35) hide show
  1. package/bundle/index.d.ts +61 -1
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +638 -2
  4. package/bundle/index.js.map +1 -1
  5. package/dist/checkbox.d.ts +3 -0
  6. package/dist/checkbox.d.ts.map +1 -1
  7. package/dist/checkbox.js +24 -1
  8. package/dist/checkbox.js.map +1 -1
  9. package/dist/checked-select-CglRukbP.d.ts +59 -0
  10. package/dist/checked-select-CglRukbP.d.ts.map +1 -0
  11. package/dist/checked-select-Dejl13qd.js +604 -0
  12. package/dist/checked-select-Dejl13qd.js.map +1 -0
  13. package/dist/checked-select.d.ts +2 -0
  14. package/dist/checked-select.js +3 -0
  15. package/dist/{detail-list-CmdCxddo.d.ts → detail-list-CgkOHYOa.d.ts} +3 -1
  16. package/dist/{detail-list-CmdCxddo.d.ts.map → detail-list-CgkOHYOa.d.ts.map} +1 -1
  17. package/dist/detail-list.d.ts +1 -1
  18. package/dist/detail-list.js +22 -0
  19. package/dist/detail-list.js.map +1 -1
  20. package/dist/{hex-editor-BiRfwTz1.js → hex-editor-DiYpgIKs.js} +2 -2
  21. package/dist/{hex-editor-BiRfwTz1.js.map → hex-editor-DiYpgIKs.js.map} +1 -1
  22. package/dist/hex.js +1 -1
  23. package/dist/index.d.ts +3 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +5 -4
  26. package/dist/index.js.map +1 -1
  27. package/dist/plots.js +1 -1
  28. package/dist/{xy-axis-COKw338Q.js → xy-axis-CyMd_Bn1.js} +2 -2
  29. package/dist/{xy-axis-COKw338Q.js.map → xy-axis-CyMd_Bn1.js.map} +1 -1
  30. package/docs-user/README.md +1 -0
  31. package/docs-user/checked-select.md +63 -0
  32. package/docs-user/index.json +6 -1
  33. package/docs-user/llms.txt +1 -0
  34. package/llms.txt +1 -0
  35. package/package.json +5 -1
package/bundle/index.js CHANGED
@@ -5,10 +5,10 @@ import { styleMap } from "lit/directives/style-map.js";
5
5
  import { unsafeHTML } from "lit/directives/unsafe-html.js";
6
6
  import { Fzf } from "fzf";
7
7
  import { createCommandRegistry } from "@clinth/ui-commands";
8
+ import { repeat } from "lit/directives/repeat.js";
8
9
  import { Colour } from "@ixfx/visual";
9
10
  import { Trees } from "@ixfx/collections";
10
11
  import { clamp, clamper, rangeStream, round, scaler } from "@ixfx/numbers";
11
- import { repeat } from "lit/directives/repeat.js";
12
12
  import { classMap } from "lit/directives/class-map.js";
13
13
  import { createRef, ref } from "lit/directives/ref.js";
14
14
  import { Easings } from "@ixfx/modulation";
@@ -8238,6 +8238,7 @@ SplitButton = __decorate([safeCustomElement(`ixfx-split-button`)], SplitButton);
8238
8238
  * Properties
8239
8239
  * checked – checkbox state (default: false)
8240
8240
  * disabled – disabled state (default: false)
8241
+ * readonly – display-only: no pointer/keyboard interaction, full opacity (default: false)
8241
8242
  * label – label text
8242
8243
  * name – form field name
8243
8244
  * value – form field value
@@ -8269,12 +8270,14 @@ let Checkbox = class Checkbox extends LitElement {
8269
8270
  this.checked = false;
8270
8271
  this.disabled = false;
8271
8272
  this.indeterminate = false;
8273
+ this.readonly = false;
8272
8274
  this.label = ``;
8273
8275
  this.name = ``;
8274
8276
  this.value = ``;
8275
8277
  this.#handleChange = (e) => {
8276
8278
  const target = e.target;
8277
8279
  this.checked = target.checked;
8280
+ this.indeterminate = false;
8278
8281
  this.dispatchEvent(new CustomEvent(`change`, {
8279
8282
  detail: { checked: this.checked },
8280
8283
  bubbles: true,
@@ -8337,6 +8340,16 @@ let Checkbox = class Checkbox extends LitElement {
8337
8340
  opacity: var(--box-opacity-checked);
8338
8341
  }
8339
8342
 
8343
+ /* Display-only: inert, keeps full opacity, no hover reveal */
8344
+ :host([readonly]) {
8345
+ pointer-events: none;
8346
+ }
8347
+ :host([readonly]) label,
8348
+ :host([readonly]) label:hover {
8349
+ cursor: default;
8350
+ opacity: var(--box-opacity-checked);
8351
+ }
8352
+
8340
8353
  input {
8341
8354
  position: absolute;
8342
8355
  opacity: 0;
@@ -8448,6 +8461,10 @@ let Checkbox = class Checkbox extends LitElement {
8448
8461
  `
8449
8462
  ];
8450
8463
  }
8464
+ /** Focus the inner input, so the host can be focused like a native checkbox. */
8465
+ focus(options) {
8466
+ this.renderRoot.querySelector(`input`)?.focus(options);
8467
+ }
8451
8468
  #handleChange;
8452
8469
  render() {
8453
8470
  return html`
@@ -8457,6 +8474,8 @@ let Checkbox = class Checkbox extends LitElement {
8457
8474
  type="checkbox"
8458
8475
  .checked=${this.checked}
8459
8476
  .disabled=${this.disabled}
8477
+ tabindex=${this.readonly ? `-1` : nothing}
8478
+ aria-hidden=${this.readonly ? `true` : nothing}
8460
8479
  .indeterminate=${this.indeterminate}
8461
8480
  .name=${this.name}
8462
8481
  .value=${this.value}
@@ -8471,10 +8490,605 @@ let Checkbox = class Checkbox extends LitElement {
8471
8490
  __decorate([property({ type: Boolean })], Checkbox.prototype, "checked", void 0);
8472
8491
  __decorate([property({ type: Boolean })], Checkbox.prototype, "disabled", void 0);
8473
8492
  __decorate([property({ type: Boolean })], Checkbox.prototype, "indeterminate", void 0);
8493
+ __decorate([property({
8494
+ type: Boolean,
8495
+ reflect: true
8496
+ })], Checkbox.prototype, "readonly", void 0);
8474
8497
  __decorate([property({ type: String })], Checkbox.prototype, "label", void 0);
8475
8498
  __decorate([property({ type: String })], Checkbox.prototype, "name", void 0);
8476
8499
  __decorate([property({ type: String })], Checkbox.prototype, "value", void 0);
8477
8500
  Checkbox = __decorate([safeCustomElement(`ixfx-checkbox`)], Checkbox);
8501
+ //#endregion
8502
+ //#region src/checked-select/logic.ts
8503
+ function byLabel(a, b) {
8504
+ return a.label.localeCompare(b.label, void 0, { sensitivity: `base` });
8505
+ }
8506
+ /** Checked/indeterminate items first, then the rest; each group sorted by label. */
8507
+ function sortForOpen(items) {
8508
+ const on = items.filter((i) => i.checked !== `false`).toSorted(byLabel);
8509
+ const off = items.filter((i) => i.checked === `false`).toSorted(byLabel);
8510
+ return [...on, ...off];
8511
+ }
8512
+ /** User toggle: indeterminate and unchecked become checked, checked becomes unchecked. */
8513
+ function toggleState(state) {
8514
+ return state === `true` ? `false` : `true`;
8515
+ }
8516
+ /** Items shown in the resting state, alphabetically. */
8517
+ function visibleAtRest(items) {
8518
+ return items.filter((i) => i.checked !== `false`).toSorted(byLabel);
8519
+ }
8520
+ const VARIES_LABEL = `(varies)`;
8521
+ /** Resting text entries: checked labels, then a single `(varies)` if any are indeterminate. */
8522
+ function restTextParts(items) {
8523
+ const on = items.filter((i) => i.checked === `true`).toSorted(byLabel).map((i) => i.label);
8524
+ const varies = items.some((i) => i.checked === `indeterminate`) ? [VARIES_LABEL] : [];
8525
+ return [...on, ...varies];
8526
+ }
8527
+ /** Plain-text resting summary, comma separated. */
8528
+ function restText(items) {
8529
+ return restTextParts(items).join(`, `);
8530
+ }
8531
+ /** Returns a new list with the item matching `value` toggled. */
8532
+ function applyToggle(items, value) {
8533
+ return items.map((i) => i.value === value ? {
8534
+ ...i,
8535
+ checked: toggleState(i.checked)
8536
+ } : i);
8537
+ }
8538
+ /**
8539
+ * Index of the next label after `current` beginning with `char` (case-insensitive),
8540
+ * wrapping around. Returns -1 if nothing matches.
8541
+ */
8542
+ function nextMatchIndex(labels, current, char) {
8543
+ const c = char.toLocaleLowerCase();
8544
+ const count = labels.length;
8545
+ for (let step = 1; step <= count; step++) {
8546
+ const index = (current + step + count) % count;
8547
+ if (labels[index].toLocaleLowerCase().startsWith(c)) return index;
8548
+ }
8549
+ return -1;
8550
+ }
8551
+ /** Items in `draft` whose state differs from `original`. */
8552
+ function diffItems(original, draft) {
8553
+ const before = new Map(original.map((i) => [i.value, i.checked]));
8554
+ return draft.filter((i) => before.get(i.value) !== i.checked);
8555
+ }
8556
+ function clampSize(size, min, max) {
8557
+ return {
8558
+ width: Math.min(Math.max(size.width, min.width), Math.max(max.width, min.width)),
8559
+ height: Math.min(Math.max(size.height, min.height), Math.max(max.height, min.height))
8560
+ };
8561
+ }
8562
+ //#endregion
8563
+ //#region src/checked-select/checked-select.ts
8564
+ let instanceCounter = 0;
8565
+ let CheckedSelect = class CheckedSelect extends LitElement {
8566
+ constructor(..._args) {
8567
+ super(..._args);
8568
+ this.items = [];
8569
+ this.multiline = false;
8570
+ this.itemsInert = false;
8571
+ this.itemDisplay = `checkbox`;
8572
+ this.orientation = `horizontal`;
8573
+ this.placeholder = ``;
8574
+ this.disabled = false;
8575
+ this._open = false;
8576
+ this._draft = [];
8577
+ this._order = [];
8578
+ this.#anchorName = `--checked-select-anchor-${instanceCounter++}`;
8579
+ this.#tickled = new TickledController(this, { getItems: () => this._order.map((_, i) => ({ id: i })) });
8580
+ this.#onRestCheckboxChange = (e, value) => {
8581
+ e.stopPropagation();
8582
+ if (this._open) return;
8583
+ const next = applyToggle(this.items, value);
8584
+ this.#emitChange(next, diffItems(this.items, next));
8585
+ this.#focusRestCheckbox(value);
8586
+ };
8587
+ this.#onDocumentPointerDown = (e) => {
8588
+ const path = e.composedPath();
8589
+ const popup = this.#popup;
8590
+ const trigger = this.#trigger;
8591
+ if (popup && path.includes(popup) || trigger && path.includes(trigger)) return;
8592
+ this.cancel();
8593
+ };
8594
+ this.#onTriggerClick = (e) => {
8595
+ if (e.target.closest?.(`ixfx-checkbox`)) return;
8596
+ const scroller = e.composedPath()[0];
8597
+ if (scroller.classList?.contains(`rest-items`) && e instanceof MouseEvent && (e.offsetX > scroller.clientWidth || e.offsetY > scroller.clientHeight)) return;
8598
+ if (this._open) this.commit();
8599
+ else this.open();
8600
+ };
8601
+ this.#onTriggerKeyDown = (e) => {
8602
+ if (this._open || e.target !== e.currentTarget) return;
8603
+ if (e.key === `Enter` || e.key === ` ` || e.key === `ArrowDown`) {
8604
+ e.preventDefault();
8605
+ this.open();
8606
+ }
8607
+ };
8608
+ this.#onListKeyDown = (e) => {
8609
+ const rows = this.#rows();
8610
+ switch (e.key) {
8611
+ case `ArrowDown`:
8612
+ e.preventDefault();
8613
+ this.#tickled.handleKeyboardNavigation(`next`);
8614
+ this.#scrollToCursor();
8615
+ return;
8616
+ case `ArrowUp`:
8617
+ e.preventDefault();
8618
+ this.#tickled.handleKeyboardNavigation(`previous`);
8619
+ this.#scrollToCursor();
8620
+ return;
8621
+ case `Home`:
8622
+ e.preventDefault();
8623
+ this.#tickled.handleKeyboardNavigation(`first`);
8624
+ this.#scrollToCursor();
8625
+ return;
8626
+ case `End`:
8627
+ e.preventDefault();
8628
+ this.#tickled.handleKeyboardNavigation(`last`);
8629
+ this.#scrollToCursor();
8630
+ return;
8631
+ case ` `: {
8632
+ e.preventDefault();
8633
+ const row = rows[this.#tickled.tickledId ?? -1];
8634
+ if (row) this.#toggleRow(row.value);
8635
+ return;
8636
+ }
8637
+ case `Enter`:
8638
+ e.preventDefault();
8639
+ this.commit();
8640
+ return;
8641
+ case `Escape`:
8642
+ e.preventDefault();
8643
+ this.cancel();
8644
+ return;
8645
+ case `Tab`:
8646
+ this.cancel();
8647
+ return;
8648
+ }
8649
+ if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
8650
+ const next = nextMatchIndex(rows.map((r) => r.label), this.#tickled.tickledId ?? -1, e.key);
8651
+ if (next >= 0) {
8652
+ this.#tickled.handleKeyboardNavigation(next);
8653
+ this.#scrollToCursor();
8654
+ }
8655
+ }
8656
+ };
8657
+ this.#onResizeDown = (e) => {
8658
+ const frame = this.shadowRoot?.querySelector(`.frame`);
8659
+ const zone = e.currentTarget;
8660
+ if (!frame) return;
8661
+ e.preventDefault();
8662
+ const start = frame.getBoundingClientRect();
8663
+ const originX = e.clientX;
8664
+ const originY = e.clientY;
8665
+ const style = getComputedStyle(this);
8666
+ const min = {
8667
+ width: Number.parseFloat(style.getPropertyValue(`--checked-select-min-width`)) || 128,
8668
+ height: Number.parseFloat(style.getPropertyValue(`--checked-select-min-height`)) || 80
8669
+ };
8670
+ const max = {
8671
+ width: window.innerWidth - start.left - 8,
8672
+ height: window.innerHeight - start.top - 8
8673
+ };
8674
+ zone.setPointerCapture?.(e.pointerId);
8675
+ zone.setAttribute(`data-active`, ``);
8676
+ const onMove = (m) => {
8677
+ this._size = clampSize({
8678
+ width: start.width + m.clientX - originX,
8679
+ height: start.height + m.clientY - originY
8680
+ }, min, max);
8681
+ };
8682
+ const onEnd = () => {
8683
+ zone.removeAttribute(`data-active`);
8684
+ zone.removeEventListener(`pointermove`, onMove);
8685
+ zone.removeEventListener(`pointerup`, onEnd);
8686
+ zone.removeEventListener(`pointercancel`, onEnd);
8687
+ this.#listbox?.focus();
8688
+ };
8689
+ zone.addEventListener(`pointermove`, onMove);
8690
+ zone.addEventListener(`pointerup`, onEnd);
8691
+ zone.addEventListener(`pointercancel`, onEnd);
8692
+ };
8693
+ }
8694
+ static {
8695
+ this.styles = [
8696
+ themeFallbacks,
8697
+ handleStyles,
8698
+ tickledStyles,
8699
+ tickledItemStyles,
8700
+ css`
8701
+ :host {
8702
+ --checked-select-gap: var(--space-m);
8703
+ --checked-select-padding: var(--space-s) var(--space-m);
8704
+ --checked-select-caret-width: 2rem;
8705
+ --checked-select-rest-max-height: 8rem;
8706
+ --checked-select-max-height: 16rem;
8707
+ --checked-select-min-width: 8rem;
8708
+ --checked-select-min-height: 5rem;
8709
+
8710
+ display: block;
8711
+ position: relative;
8712
+ }
8713
+
8714
+ :host([disabled]) {
8715
+ opacity: var(--prominence-disabled-opacity, 0.4);
8716
+ pointer-events: none;
8717
+ }
8718
+
8719
+ .rest {
8720
+ position: relative;
8721
+ box-sizing: border-box;
8722
+ cursor: pointer;
8723
+ background: var(--surface-4);
8724
+ color: var(--surface-4-text);
8725
+ border: var(--border);
8726
+ border-radius: var(--radius-m);
8727
+ user-select: none;
8728
+ }
8729
+
8730
+ .rest-items {
8731
+ display: flex;
8732
+ flex-wrap: nowrap;
8733
+ align-items: center;
8734
+ gap: var(--checked-select-gap);
8735
+ padding: var(--checked-select-padding);
8736
+ /* Room to scroll the last item clear of the caret */
8737
+ padding-inline-end: var(--checked-select-caret-width);
8738
+ min-height: 1.5em;
8739
+ box-sizing: border-box;
8740
+ overflow-x: auto;
8741
+ overflow-y: hidden;
8742
+ scrollbar-width: thin;
8743
+ }
8744
+ :host([multiline]) .rest-items {
8745
+ flex-wrap: wrap;
8746
+ overflow-x: hidden;
8747
+ overflow-y: auto;
8748
+ max-height: var(--checked-select-rest-max-height);
8749
+ /* Wrapping never runs under the caret, so keep its column clear (and the scrollbar beside it) */
8750
+ padding-inline-end: var(--space-m);
8751
+ margin-inline-end: var(--checked-select-caret-width);
8752
+ }
8753
+
8754
+ /* Vertical: one item per row, scrolling vertically beside the caret. Must follow the multiline rule. */
8755
+ :host([orientation="vertical"]) .rest-items {
8756
+ flex-direction: column;
8757
+ flex-wrap: nowrap;
8758
+ align-items: flex-start;
8759
+ overflow-x: hidden;
8760
+ overflow-y: auto;
8761
+ max-height: var(--checked-select-rest-max-height);
8762
+ padding-inline-end: var(--space-m);
8763
+ margin-inline-end: var(--checked-select-caret-width);
8764
+ }
8765
+
8766
+ /* Always-visible caret; items scroll underneath it */
8767
+ .caret {
8768
+ position: absolute;
8769
+ inset-block: 0;
8770
+ inset-inline-end: 0;
8771
+ width: var(--checked-select-caret-width);
8772
+ display: grid;
8773
+ place-items: center;
8774
+ background: inherit;
8775
+ border-radius: inherit;
8776
+ pointer-events: auto;
8777
+ }
8778
+ .caret svg {
8779
+ width: 1.1em;
8780
+ height: 1.1em;
8781
+ fill: none;
8782
+ stroke: currentColor;
8783
+ stroke-width: 2;
8784
+ stroke-linecap: round;
8785
+ stroke-linejoin: round;
8786
+ transition: rotate var(--transition, 0.1s ease-out);
8787
+ }
8788
+ .rest[aria-expanded="true"] .caret svg {
8789
+ rotate: 180deg;
8790
+ }
8791
+ /* While the popup is open its draft is authoritative */
8792
+ .rest[aria-expanded="true"] ixfx-checkbox {
8793
+ pointer-events: none;
8794
+ }
8795
+ .rest:hover {
8796
+ background: var(--surface-5);
8797
+ }
8798
+ .rest:focus-visible {
8799
+ outline: 2px solid var(--accent);
8800
+ outline-offset: 2px;
8801
+ }
8802
+ .rest ixfx-checkbox {
8803
+ flex-shrink: 0;
8804
+ }
8805
+ .rest-text {
8806
+ white-space: nowrap;
8807
+ }
8808
+ :host([multiline]) .rest-text {
8809
+ white-space: normal;
8810
+ }
8811
+ .placeholder {
8812
+ opacity: 0.6;
8813
+ }
8814
+
8815
+ .popup {
8816
+ /* Reset popover UA styles */
8817
+ inset: unset;
8818
+ margin: 0;
8819
+ padding: 0;
8820
+ border: none;
8821
+ background: transparent;
8822
+ overflow: visible;
8823
+
8824
+ /* CSS anchor positioning — position-anchor set inline */
8825
+ position: fixed;
8826
+ left: anchor(left);
8827
+ top: anchor(bottom);
8828
+ /* anchor-size() only works on the anchored element itself, so the width lives here */
8829
+ width: anchor-size(width);
8830
+ min-width: var(--checked-select-min-width);
8831
+ position-try-fallbacks: flip-block;
8832
+ margin-block: 4px;
8833
+ z-index: 100;
8834
+ }
8835
+
8836
+ .frame {
8837
+ position: relative;
8838
+ box-sizing: border-box;
8839
+ display: flex;
8840
+ flex-direction: column;
8841
+ width: 100%;
8842
+ max-height: var(--checked-select-max-height);
8843
+ background: var(--surface-3);
8844
+ color: var(--surface-3-text);
8845
+ border: var(--border);
8846
+ border-radius: var(--radius-m);
8847
+ }
8848
+
8849
+ .listbox {
8850
+ flex: 1;
8851
+ min-height: 0;
8852
+ overflow-y: auto;
8853
+ padding: var(--space-s);
8854
+ outline: none;
8855
+ }
8856
+ .listbox:focus-visible {
8857
+ outline: 2px solid var(--accent);
8858
+ outline-offset: -2px;
8859
+ }
8860
+
8861
+ .item {
8862
+ display: flex;
8863
+ width: 100%;
8864
+ box-sizing: border-box;
8865
+ }
8866
+
8867
+ /* Key-repeat outpaces the fade, smearing the cursor across rows. Snap while
8868
+ the keyboard drives the cursor; hover (mouse mode) keeps the transition. */
8869
+ .listbox[data-input-mode="keyboard"] .item {
8870
+ transition: none;
8871
+ }
8872
+
8873
+ .resize-zone {
8874
+ position: absolute;
8875
+ inset-block-end: 0;
8876
+ inset-inline-end: 0;
8877
+ width: 16px;
8878
+ height: 16px;
8879
+ display: grid;
8880
+ place-items: center;
8881
+ cursor: nwse-resize;
8882
+ touch-action: none;
8883
+ }
8884
+ .resize-zone .ixfx-handle {
8885
+ --ixfx-handle-spot-size: 10px;
8886
+ }
8887
+ `
8888
+ ];
8889
+ }
8890
+ #anchorName;
8891
+ #tickled;
8892
+ get isOpen() {
8893
+ return this._open;
8894
+ }
8895
+ get #trigger() {
8896
+ return this.shadowRoot?.querySelector(`.rest`) ?? null;
8897
+ }
8898
+ get #popup() {
8899
+ return this.shadowRoot?.querySelector(`.popup`) ?? null;
8900
+ }
8901
+ get #listbox() {
8902
+ return this.shadowRoot?.querySelector(`.listbox`) ?? null;
8903
+ }
8904
+ /** Draft items in their frozen open-time order. */
8905
+ #rows() {
8906
+ const byValue = new Map(this._draft.map((i) => [i.value, i]));
8907
+ return this._order.flatMap((v) => byValue.get(v) ?? []);
8908
+ }
8909
+ disconnectedCallback() {
8910
+ super.disconnectedCallback();
8911
+ document.removeEventListener(`pointerdown`, this.#onDocumentPointerDown, true);
8912
+ }
8913
+ willUpdate(changed) {
8914
+ super.willUpdate(changed);
8915
+ if (changed.has(`disabled`) && this.disabled && this._open) this.cancel();
8916
+ }
8917
+ async open() {
8918
+ if (this._open || this.disabled) return;
8919
+ this._draft = this.items;
8920
+ this._order = sortForOpen(this.items).map((i) => i.value);
8921
+ this._size = void 0;
8922
+ this._open = true;
8923
+ if (this._order.length > 0) this.#tickled.handleKeyboardNavigation(`first`);
8924
+ document.addEventListener(`pointerdown`, this.#onDocumentPointerDown, true);
8925
+ await this.updateComplete;
8926
+ this.#popup?.showPopover?.();
8927
+ this.#listbox?.focus();
8928
+ }
8929
+ commit() {
8930
+ if (!this._open) return;
8931
+ const changed = diffItems(this.items, this._draft);
8932
+ const next = this._draft;
8933
+ this.#close();
8934
+ if (changed.length === 0) return;
8935
+ this.#emitChange(next, changed);
8936
+ }
8937
+ #emitChange(next, changed) {
8938
+ this.items = next;
8939
+ this.dispatchEvent(new CustomEvent(`change`, {
8940
+ detail: {
8941
+ items: next,
8942
+ changed
8943
+ },
8944
+ bubbles: true,
8945
+ composed: true
8946
+ }));
8947
+ }
8948
+ /** A resting checkbox was used directly: toggle and commit immediately. */
8949
+ #onRestCheckboxChange;
8950
+ async #focusRestCheckbox(value) {
8951
+ await this.updateComplete;
8952
+ (this.shadowRoot?.querySelector(`.rest ixfx-checkbox[data-value="${CSS.escape(value)}"]`) ?? this.#trigger)?.focus();
8953
+ }
8954
+ cancel() {
8955
+ if (!this._open) return;
8956
+ this.#close();
8957
+ }
8958
+ #close() {
8959
+ document.removeEventListener(`pointerdown`, this.#onDocumentPointerDown, true);
8960
+ this.#popup?.hidePopover?.();
8961
+ this._open = false;
8962
+ this._draft = [];
8963
+ this.#tickled.clearTickled();
8964
+ this.#trigger?.focus();
8965
+ }
8966
+ #onDocumentPointerDown;
8967
+ #onTriggerClick;
8968
+ #onTriggerKeyDown;
8969
+ #toggleRow(value) {
8970
+ this._draft = applyToggle(this._draft, value);
8971
+ }
8972
+ #onRowClick(value, index) {
8973
+ this.#tickled.setTickled(index);
8974
+ this.#toggleRow(value);
8975
+ }
8976
+ #onListKeyDown;
8977
+ async #scrollToCursor() {
8978
+ await this.updateComplete;
8979
+ const index = this.#tickled.tickledId;
8980
+ this.shadowRoot?.querySelector(`[data-index="${index}"]`)?.scrollIntoView?.({ block: `nearest` });
8981
+ }
8982
+ #onResizeDown;
8983
+ #renderRest() {
8984
+ const shown = visibleAtRest(this.items);
8985
+ if (shown.length === 0) return html`<span class="placeholder">${this.placeholder}</span>`;
8986
+ if (this.itemDisplay === `text`) {
8987
+ if (this.orientation === `vertical`) return restTextParts(this.items).map((part) => html`<span class="rest-text">${part}</span>`);
8988
+ return html`<span class="rest-text">${restText(this.items)}</span>`;
8989
+ }
8990
+ return repeat(shown, (i) => i.value, (i) => html`
8991
+ <ixfx-checkbox
8992
+ ?readonly=${this.itemsInert}
8993
+ data-value=${i.value}
8994
+ label=${i.label}
8995
+ .checked=${i.checked === `true`}
8996
+ .indeterminate=${i.checked === `indeterminate`}
8997
+ @change=${(e) => this.#onRestCheckboxChange(e, i.value)}
8998
+ ></ixfx-checkbox>`);
8999
+ }
9000
+ #renderRow(row, index) {
9001
+ return html`
9002
+ <div
9003
+ class="item ${this.#tickled.isTickled(index) ? `tickled` : ``}"
9004
+ role="option"
9005
+ id="opt-${index}"
9006
+ data-index=${index}
9007
+ aria-checked=${row.checked === `indeterminate` ? `mixed` : row.checked}
9008
+ @pointerenter=${() => this.#tickled.handlePointerEnter(index)}
9009
+ @click=${() => this.#onRowClick(row.value, index)}
9010
+ >
9011
+ <ixfx-checkbox
9012
+ readonly
9013
+ label=${row.label}
9014
+ .checked=${row.checked === `true`}
9015
+ .indeterminate=${row.checked === `indeterminate`}
9016
+ ></ixfx-checkbox>
9017
+ </div>`;
9018
+ }
9019
+ render() {
9020
+ const rows = this._open ? this.#rows() : [];
9021
+ const cursor = this.#tickled.tickledId;
9022
+ const popupSize = this._size ? `width: ${this._size.width}px;` : ``;
9023
+ const frameStyle = this._size ? `height: ${this._size.height}px; max-height: none;` : ``;
9024
+ const summary = visibleAtRest(this.items).map((i) => i.label).join(`, `);
9025
+ return html`
9026
+ <div
9027
+ class="rest"
9028
+ role="combobox"
9029
+ tabindex=${this.disabled ? `-1` : `0`}
9030
+ aria-haspopup="listbox"
9031
+ aria-expanded=${this._open ? `true` : `false`}
9032
+ aria-disabled=${this.disabled ? `true` : nothing}
9033
+ title=${summary || nothing}
9034
+ style="anchor-name: ${this.#anchorName}"
9035
+ @click=${this.#onTriggerClick}
9036
+ @keydown=${this.#onTriggerKeyDown}
9037
+ >
9038
+ <div class="rest-items">${this.#renderRest()}</div>
9039
+ <div class="caret" aria-hidden="true">
9040
+ <svg viewBox="0 0 24 24"><path d="m6 9 6 6 6-6" /></svg>
9041
+ </div>
9042
+ </div>
9043
+ <div class="popup" popover="manual" style="position-anchor: ${this.#anchorName}; ${popupSize}">
9044
+ <div class="frame" style=${frameStyle}>
9045
+ <div
9046
+ class="listbox"
9047
+ role="listbox"
9048
+ aria-multiselectable="true"
9049
+ tabindex="-1"
9050
+ aria-activedescendant=${cursor === void 0 ? nothing : `opt-${cursor}`}
9051
+ data-input-mode=${this.#tickled.inputMode}
9052
+ @pointerleave=${(e) => this.#tickled.handlePointerLeave(e)}
9053
+ @keydown=${this.#onListKeyDown}
9054
+ >${rows.map((r, i) => this.#renderRow(r, i))}</div>
9055
+ <div class="resize-zone" @pointerdown=${this.#onResizeDown}>
9056
+ <span class="ixfx-handle" data-handle="dotted" data-shape="spot" part="handle"></span>
9057
+ </div>
9058
+ </div>
9059
+ </div>
9060
+ `;
9061
+ }
9062
+ };
9063
+ __decorate([property({ type: Array })], CheckedSelect.prototype, "items", void 0);
9064
+ __decorate([property({
9065
+ type: Boolean,
9066
+ reflect: true
9067
+ })], CheckedSelect.prototype, "multiline", void 0);
9068
+ __decorate([property({
9069
+ type: Boolean,
9070
+ attribute: `items-inert`,
9071
+ reflect: true
9072
+ })], CheckedSelect.prototype, "itemsInert", void 0);
9073
+ __decorate([property({
9074
+ type: String,
9075
+ attribute: `item-display`,
9076
+ reflect: true
9077
+ })], CheckedSelect.prototype, "itemDisplay", void 0);
9078
+ __decorate([property({
9079
+ type: String,
9080
+ reflect: true
9081
+ })], CheckedSelect.prototype, "orientation", void 0);
9082
+ __decorate([property({ type: String })], CheckedSelect.prototype, "placeholder", void 0);
9083
+ __decorate([property({
9084
+ type: Boolean,
9085
+ reflect: true
9086
+ })], CheckedSelect.prototype, "disabled", void 0);
9087
+ __decorate([state()], CheckedSelect.prototype, "_open", void 0);
9088
+ __decorate([state()], CheckedSelect.prototype, "_draft", void 0);
9089
+ __decorate([state()], CheckedSelect.prototype, "_order", void 0);
9090
+ __decorate([state()], CheckedSelect.prototype, "_size", void 0);
9091
+ CheckedSelect = __decorate([safeCustomElement(`ixfx-checked-select`)], CheckedSelect);
8478
9092
  const RANGE_LIMITS = {
8479
9093
  l: [0, 1],
8480
9094
  c: [0, .4],
@@ -16562,6 +17176,14 @@ DataTableElement = __decorate([safeCustomElement(`ixfx-data-table`)], DataTableE
16562
17176
  //#region src/detail-list/detail-list.ts
16563
17177
  const ITEM_SELECTOR$3 = `:scope > li`;
16564
17178
  const SEARCH_HIGHLIGHT_KEY = `detail-list-search`;
17179
+ const NAV_KEYS = /* @__PURE__ */ new Set([
17180
+ `ArrowDown`,
17181
+ `ArrowUp`,
17182
+ `ArrowLeft`,
17183
+ `ArrowRight`,
17184
+ `Home`,
17185
+ `End`
17186
+ ]);
16565
17187
  const LABEL_SPAN_CSS = `overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0`;
16566
17188
  /**
16567
17189
  * Layout for the per-item badge wrapper `#ensureBadge` adds. Applied as inline
@@ -16732,6 +17354,9 @@ let DetailListElement = class DetailListElement extends LitElement {
16732
17354
  this._onPointerLeave = (e) => {
16733
17355
  this.tickled.handlePointerLeave(e);
16734
17356
  };
17357
+ this._endKeyboardNav = () => {
17358
+ this.removeAttribute(`data-keyboard-nav`);
17359
+ };
16735
17360
  this._onKeyDown = (e) => {
16736
17361
  if (this.search.isOpen) return;
16737
17362
  if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
@@ -16741,6 +17366,7 @@ let DetailListElement = class DetailListElement extends LitElement {
16741
17366
  }
16742
17367
  return;
16743
17368
  }
17369
+ if (NAV_KEYS.has(e.key)) this.setAttribute(`data-keyboard-nav`, ``);
16744
17370
  switch (e.key) {
16745
17371
  case `ArrowDown`:
16746
17372
  e.preventDefault();
@@ -16929,6 +17555,8 @@ let DetailListElement = class DetailListElement extends LitElement {
16929
17555
  this.addEventListener(`pointerover`, this._onPointerOver);
16930
17556
  this.addEventListener(`pointerleave`, this._onPointerLeave);
16931
17557
  this.addEventListener(`keydown`, this._onKeyDown);
17558
+ this.addEventListener(`pointermove`, this._endKeyboardNav);
17559
+ this.addEventListener(`pointerdown`, this._endKeyboardNav);
16932
17560
  this.addEventListener(`click`, this._onClick);
16933
17561
  this.addEventListener(`dblclick`, this._onDblClick);
16934
17562
  this.addEventListener(`wheel`, this._onWheel);
@@ -16975,6 +17603,8 @@ let DetailListElement = class DetailListElement extends LitElement {
16975
17603
  this.removeEventListener(`pointerover`, this._onPointerOver);
16976
17604
  this.removeEventListener(`pointerleave`, this._onPointerLeave);
16977
17605
  this.removeEventListener(`keydown`, this._onKeyDown);
17606
+ this.removeEventListener(`pointermove`, this._endKeyboardNav);
17607
+ this.removeEventListener(`pointerdown`, this._endKeyboardNav);
16978
17608
  this.removeEventListener(`click`, this._onClick);
16979
17609
  this.removeEventListener(`dblclick`, this._onDblClick);
16980
17610
  this.removeEventListener(`wheel`, this._onWheel);
@@ -17779,6 +18409,12 @@ let DetailListElement = class DetailListElement extends LitElement {
17779
18409
  outline: 1px solid transparent;
17780
18410
  }
17781
18411
 
18412
+ /* While navigating by keyboard, key-repeat outpaces the 0.1s fade so the
18413
+ cursor smears across rows. Snap instead. */
18414
+ :host([data-keyboard-nav]) ::slotted(li) {
18415
+ transition: none;
18416
+ }
18417
+
17782
18418
  /* In checked mode, only the checkbox column is clickable; the row body
17783
18419
  gets a default cursor so it doesn't suggest otherwise. */
17784
18420
  :host([interaction-mode="checked"]) ::slotted(li) {
@@ -53313,6 +53949,6 @@ function init() {
53313
53949
  console.log(`Init`);
53314
53950
  }
53315
53951
  //#endregion
53316
- export { AcTextElement, AcTokenElement, BottomDrawerElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DEFAULT_SIBLING_SPACING, DataController, DataDisplayComponent, DataGridElement, DataTableElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GridListElement, GroupedItemListerElement, HyperbolicTreeListElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IcicleTreeListElement, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListDragSelectController, ListNativeDragController, ListSearchController, ListSelectionController, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, MulticolListElement, NarrowedTextElement, NotificationManager, NotificationPillElement, PanTextElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, RatingElement, SelectHorizElement, ShapefillValueElement, SliderInputElement, SlidingNumberElement, SnackbarElement, SnapContainer, SplitButton, SplitLayoutElement, SplitPanelElement, SwipeElement, TabController, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, TextShimmerElement, TickledController, TimelineController, TimelineTrackTabElement, TitlebarElement, ToolbarElement, ToolbarGroupElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeDropController, TreeKeyboardNavController, TreeListElement, TreemapListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applyIsometry, applySnapshot, applyTreeDrop, attachTimelineInteractions, bottomDrawerCommandId, buildHighlightTargets, buildModel, captionForEntry, clampToDisk, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, composeIsometry, computeCanonicalPositions, computeColour, computeHyperbolicLayout, computeIcicleLayout, computeLeafGeoms, computeTreemapLayout, createAcCompletionProvider, createDataListSearchAdapter, createDefaultListDragPreview, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, decomposeViewIsometry, distribute, dropTargetFor, euclideanRadiusForHyperbolicDistance, extractStack, findHyperbolicIdOf, findHyperbolicKeyPath, findHyperbolicNode, findHyperbolicPath, findHyperbolicPathById, findIcicleNode, findIciclePath, findNode, findPanelNode, findParentOf, findStackOf, findStackPathByName, findTreemapNode, findTreemapPath, geodesicInterpolate, geodesicPath, geodesicSegment, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, hyperbolicBranchId, hyperbolicChildIds, hyperbolicDistanceForEuclideanRadius, hyperbolicIdDepth, hyperbolicRelativeFrame, iconBus, identityIsometry, init, insertBeside, invertIsometry, isDragging, isHyperbolicDescendantId, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, mobiusAdd, mobiusFromOrigin, mobiusToOrigin, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, norm, normalise, normaliseValue, normalize, panelGapIndex, parseColourScaleAttr, parseLayout, poincareDistance, pointerOverAnyHost, prefersReducedMotion, preserveRestoreWeights, pruneDraggedDescendants, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, rotationIsometry, serializeLayout, setActive, setIconFont, setPanelRestoreWeight, startListItemOutAnimation, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, toolbarGapIndex, translationFromOrigin, viewIsometry, walk, wrapRoot };
53952
+ export { AcTextElement, AcTokenElement, BottomDrawerElement, ButtonElement, Checkbox, CheckedSelect, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DEFAULT_SIBLING_SPACING, DataController, DataDisplayComponent, DataGridElement, DataTableElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GridListElement, GroupedItemListerElement, HyperbolicTreeListElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IcicleTreeListElement, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListDragSelectController, ListNativeDragController, ListSearchController, ListSelectionController, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, MulticolListElement, NarrowedTextElement, NotificationManager, NotificationPillElement, PanTextElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, RatingElement, SelectHorizElement, ShapefillValueElement, SliderInputElement, SlidingNumberElement, SnackbarElement, SnapContainer, SplitButton, SplitLayoutElement, SplitPanelElement, SwipeElement, TabController, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, TextShimmerElement, TickledController, TimelineController, TimelineTrackTabElement, TitlebarElement, ToolbarElement, ToolbarGroupElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeDropController, TreeKeyboardNavController, TreeListElement, TreemapListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applyIsometry, applySnapshot, applyTreeDrop, attachTimelineInteractions, bottomDrawerCommandId, buildHighlightTargets, buildModel, captionForEntry, clampToDisk, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, composeIsometry, computeCanonicalPositions, computeColour, computeHyperbolicLayout, computeIcicleLayout, computeLeafGeoms, computeTreemapLayout, createAcCompletionProvider, createDataListSearchAdapter, createDefaultListDragPreview, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, decomposeViewIsometry, distribute, dropTargetFor, euclideanRadiusForHyperbolicDistance, extractStack, findHyperbolicIdOf, findHyperbolicKeyPath, findHyperbolicNode, findHyperbolicPath, findHyperbolicPathById, findIcicleNode, findIciclePath, findNode, findPanelNode, findParentOf, findStackOf, findStackPathByName, findTreemapNode, findTreemapPath, geodesicInterpolate, geodesicPath, geodesicSegment, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, hyperbolicBranchId, hyperbolicChildIds, hyperbolicDistanceForEuclideanRadius, hyperbolicIdDepth, hyperbolicRelativeFrame, iconBus, identityIsometry, init, insertBeside, invertIsometry, isDragging, isHyperbolicDescendantId, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, mobiusAdd, mobiusFromOrigin, mobiusToOrigin, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, norm, normalise, normaliseValue, normalize, panelGapIndex, parseColourScaleAttr, parseLayout, poincareDistance, pointerOverAnyHost, prefersReducedMotion, preserveRestoreWeights, pruneDraggedDescendants, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, rotationIsometry, serializeLayout, setActive, setIconFont, setPanelRestoreWeight, startListItemOutAnimation, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, toolbarGapIndex, translationFromOrigin, viewIsometry, walk, wrapRoot };
53317
53953
 
53318
53954
  //# sourceMappingURL=index.js.map