@fusion.dev/helix-headless 0.147.0 → 0.148.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 (141) hide show
  1. package/custom-elements.json +4512 -790
  2. package/dist/accordion/item/accordion-item.d.ts +5 -2
  3. package/dist/accordion/item/accordion-item.js +11 -7
  4. package/dist/accordion/root/accordion-root.js +4 -6
  5. package/dist/announcement/root/announcement-root.d.ts +56 -5
  6. package/dist/announcement/root/announcement-root.js +124 -21
  7. package/dist/area-chart/root/area-chart-root.js +3 -4
  8. package/dist/badge/count.d.ts +14 -0
  9. package/dist/badge/count.js +20 -0
  10. package/dist/badge/root/badge-root.js +8 -22
  11. package/dist/blockquote/root/blockquote-root.d.ts +66 -8
  12. package/dist/blockquote/root/blockquote-root.js +85 -8
  13. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +2 -2
  14. package/dist/button-group/arrows.d.ts +14 -0
  15. package/dist/button-group/arrows.js +10 -0
  16. package/dist/button-group/root/button-group-root.d.ts +141 -3
  17. package/dist/button-group/root/button-group-root.js +142 -2
  18. package/dist/canvas/edge-pan.d.ts +61 -0
  19. package/dist/canvas/edge-pan.js +63 -0
  20. package/dist/canvas/node/canvas-node.d.ts +12 -2
  21. package/dist/canvas/node/canvas-node.js +6 -2
  22. package/dist/canvas/root/canvas-document.d.ts +10 -0
  23. package/dist/canvas/root/canvas-document.js +1 -0
  24. package/dist/canvas/root/canvas-root.d.ts +107 -5
  25. package/dist/canvas/root/canvas-root.js +140 -36
  26. package/dist/canvas/snap.d.ts +42 -0
  27. package/dist/canvas/snap.js +32 -0
  28. package/dist/carousel/root/carousel-root.d.ts +4 -3
  29. package/dist/carousel/root/carousel-root.js +2 -6
  30. package/dist/chat/root/chat-root.d.ts +13 -3
  31. package/dist/chat/root/chat-root.js +2 -6
  32. package/dist/checkbox-group/root/checkbox-group-root.d.ts +13 -0
  33. package/dist/checkbox-group/root/checkbox-group-root.js +18 -10
  34. package/dist/checkbox-group/select-all.d.ts +11 -0
  35. package/dist/checkbox-group/select-all.js +5 -0
  36. package/dist/code-block/root/code-block-root.d.ts +8 -6
  37. package/dist/code-block/root/code-block-root.js +4 -3
  38. package/dist/color-picker/root/color-picker-root.js +6 -12
  39. package/dist/color-picker/wheel.d.ts +11 -0
  40. package/dist/color-picker/wheel.js +9 -0
  41. package/dist/combobox/root/combobox-root.d.ts +70 -11
  42. package/dist/combobox/root/combobox-root.js +68 -18
  43. package/dist/command/root/command-root.d.ts +4 -3
  44. package/dist/data-table/root/data-table-root.js +17 -44
  45. package/dist/description-list/item/description-list-item.d.ts +4 -0
  46. package/dist/description-list/item/description-list-item.js +7 -1
  47. package/dist/divider/root/divider-root.d.ts +45 -16
  48. package/dist/divider/root/divider-root.js +28 -4
  49. package/dist/drilldown/item/drilldown-item.js +3 -4
  50. package/dist/element-events.generated.js +1 -0
  51. package/dist/empty/root/empty-root.d.ts +17 -3
  52. package/dist/empty/root/empty-root.js +29 -5
  53. package/dist/file-input/root/file-input-root.js +3 -4
  54. package/dist/file-input/status.js +2 -1
  55. package/dist/filter-input/chip/filter-chip.js +2 -3
  56. package/dist/format-date/fields.d.ts +20 -0
  57. package/dist/format-date/fields.js +14 -0
  58. package/dist/format-date/options.d.ts +2 -1
  59. package/dist/format-date/options.js +9 -38
  60. package/dist/format-date/root/format-date-root.d.ts +35 -3
  61. package/dist/format-date/root/format-date-root.js +24 -32
  62. package/dist/graph/root/graph-root.d.ts +23 -5
  63. package/dist/graph/root/graph-root.js +17 -20
  64. package/dist/heatmap/root/heatmap-root.d.ts +12 -0
  65. package/dist/heatmap/root/heatmap-root.js +26 -11
  66. package/dist/image/root/image-root.d.ts +11 -0
  67. package/dist/kbd/root/kbd-root.js +1 -2
  68. package/dist/legend/root/legend-root.js +4 -17
  69. package/dist/list/root/list-root.js +8 -22
  70. package/dist/listbox/root/listbox-root.d.ts +119 -24
  71. package/dist/listbox/root/listbox-root.js +130 -132
  72. package/dist/map/marker/map-marker.d.ts +2 -1
  73. package/dist/map/root/map-root.d.ts +40 -2
  74. package/dist/map/root/map-root.js +64 -23
  75. package/dist/megamenu/root/megamenu-root.js +4 -11
  76. package/dist/menu/root/menu-root.d.ts +2 -2
  77. package/dist/navigation/root/navigation-root.js +5 -8
  78. package/dist/notifications/group/notification-group.d.ts +8 -1
  79. package/dist/notifications/group/notification-group.js +12 -6
  80. package/dist/notifications/notification/notification.d.ts +12 -1
  81. package/dist/notifications/notification/notification.js +3 -1
  82. package/dist/notifications/root/notifications-root.d.ts +41 -5
  83. package/dist/notifications/root/notifications-root.js +27 -7
  84. package/dist/object-inspector/root/object-inspector-root.d.ts +5 -1
  85. package/dist/object-inspector/root/object-inspector-root.js +21 -11
  86. package/dist/pagination/size/pagination-size.d.ts +1 -0
  87. package/dist/pagination/size/pagination-size.js +1 -1
  88. package/dist/pin-input/code.d.ts +23 -0
  89. package/dist/pin-input/code.js +25 -0
  90. package/dist/pin-input/root/pin-input-root.js +9 -23
  91. package/dist/qr-code/root/qr-code-root.d.ts +14 -3
  92. package/dist/qr-code/root/qr-code-root.js +19 -5
  93. package/dist/scatter-chart/measures.d.ts +22 -0
  94. package/dist/scatter-chart/measures.js +11 -0
  95. package/dist/scatter-chart/root/scatter-chart-root.d.ts +2 -6
  96. package/dist/scatter-chart/root/scatter-chart-root.js +6 -19
  97. package/dist/select/root/select-root.d.ts +56 -8
  98. package/dist/select/root/select-root.js +33 -9
  99. package/dist/speed-dial/group/speed-dial-group.d.ts +48 -0
  100. package/dist/speed-dial/group/speed-dial-group.js +43 -0
  101. package/dist/speed-dial/index.d.ts +1 -0
  102. package/dist/speed-dial/index.js +1 -0
  103. package/dist/speed-dial/item/speed-dial-item.d.ts +26 -1
  104. package/dist/speed-dial/item/speed-dial-item.js +41 -3
  105. package/dist/speed-dial/long-press.d.ts +50 -0
  106. package/dist/speed-dial/long-press.js +48 -0
  107. package/dist/speed-dial/root/speed-dial-root.d.ts +80 -1
  108. package/dist/speed-dial/root/speed-dial-root.js +114 -5
  109. package/dist/splitter/root/splitter-root.d.ts +7 -3
  110. package/dist/splitter/root/splitter-root.js +16 -3
  111. package/dist/table/cell/table-cell.d.ts +8 -1
  112. package/dist/table/cell/table-cell.js +6 -2
  113. package/dist/table/grid-controller.d.ts +117 -0
  114. package/dist/table/grid-controller.js +293 -0
  115. package/dist/table/root/table-root.d.ts +47 -3
  116. package/dist/table/root/table-root.js +81 -24
  117. package/dist/table/row/table-row.d.ts +13 -1
  118. package/dist/table/row/table-row.js +20 -6
  119. package/dist/tag/group/tag-group.js +6 -15
  120. package/dist/time-picker/root/time-picker-root.d.ts +1 -0
  121. package/dist/time-picker/root/time-picker-root.js +3 -0
  122. package/dist/toast/item/toast-item.d.ts +6 -3
  123. package/dist/toast/item/toast-item.js +1 -0
  124. package/dist/toast/root/toast-root.d.ts +13 -1
  125. package/dist/toast/root/toast-root.js +20 -14
  126. package/dist/toggle/group/toggle-group.js +3 -5
  127. package/dist/toolbar/root/toolbar-root.d.ts +31 -0
  128. package/dist/toolbar/root/toolbar-root.js +39 -8
  129. package/dist/tooltip/root/tooltip-root.js +1 -2
  130. package/dist/video/root/video-root.d.ts +11 -0
  131. package/dist/video/root/video-root.js +15 -1
  132. package/dist/virtual-list/root/virtual-list-root.js +6 -18
  133. package/lint.json +1 -1
  134. package/package.json +5 -5
  135. package/types/jsx.d.ts +2005 -266
  136. package/types/svelte.d.ts +1 -0
  137. package/types/vue.d.ts +338 -62
  138. package/dist/kbd/keys.d.ts +0 -29
  139. package/dist/kbd/keys.js +0 -52
  140. package/dist/tooltip/focus-target.d.ts +0 -6
  141. package/dist/tooltip/focus-target.js +0 -9
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Where a card lands on a board that snaps: the arithmetic of a ruling, kept
3
+ * apart from the board that applies it.
4
+ *
5
+ * Every value is in the document's own units, and a step of nothing, zero or
6
+ * less is no snapping at all, so a board without `snap` moves exactly as it
7
+ * did before there was one.
8
+ */
9
+ /** A box on the board, in the document's own units. */
10
+ export interface SnapBox {
11
+ x: number;
12
+ y: number;
13
+ width: number;
14
+ height: number;
15
+ }
16
+ /**
17
+ * A value at the nearest multiple of `step`, or the value as it is where there
18
+ * is no step. A drag lets go wherever the hand is, and lands on the line
19
+ * nearest it.
20
+ */
21
+ export declare const snapTo: (value: number, step: number | undefined) => number;
22
+ /**
23
+ * The next multiple of `step` from `value`, the way a key points: `way` is `1`
24
+ * or `-1`.
25
+ *
26
+ * **On to the next line, and never back onto the same one.** A card sitting
27
+ * between two lines goes to the line ahead of it, and one on a line goes a
28
+ * whole step, so every press moves the card and every press lands on a line.
29
+ */
30
+ export declare const stepTo: (value: number, way: number, step: number) => number;
31
+ /**
32
+ * A box resized from a corner, with the edges that corner moves snapped, and
33
+ * held to a least size.
34
+ *
35
+ * Only the moving edges: the corner opposite the one in hand stays where it
36
+ * was, as it does without a ruling. Where the line nearest is inside the least
37
+ * size, the box stays at the least size rather than turning inside out.
38
+ */
39
+ export declare const snapBox: (box: SnapBox, start: SnapBox, corner: string, step: number | undefined, least: {
40
+ width: number;
41
+ height: number;
42
+ }) => SnapBox;
@@ -0,0 +1,32 @@
1
+ const snaps = (step) => step !== undefined && Number.isFinite(step) && step > 0;
2
+ export const snapTo = (value, step) => snaps(step) ? Math.round(value / step) * step : value;
3
+ export const stepTo = (value, way, step) => {
4
+ if (way === 0) {
5
+ return value;
6
+ }
7
+ const at = value / step;
8
+ const near = Math.round(at);
9
+ const on = Math.abs(at - near) < 1e-9 ? near : at;
10
+ return (way > 0 ? Math.floor(on) + 1 : Math.ceil(on) - 1) * step;
11
+ };
12
+ export const snapBox = (box, start, corner, step, least) => {
13
+ if (!snaps(step)) {
14
+ return box;
15
+ }
16
+ const left = corner.endsWith('left');
17
+ const top = corner.startsWith('top');
18
+ const right = start.x + start.width;
19
+ const bottom = start.y + start.height;
20
+ const width = left
21
+ ? Math.max(least.width, right - snapTo(box.x, step))
22
+ : Math.max(least.width, snapTo(box.x + box.width, step) - start.x);
23
+ const height = top
24
+ ? Math.max(least.height, bottom - snapTo(box.y, step))
25
+ : Math.max(least.height, snapTo(box.y + box.height, step) - start.y);
26
+ return {
27
+ height,
28
+ width,
29
+ x: left ? right - width : start.x,
30
+ y: top ? bottom - height : start.y,
31
+ };
32
+ };
@@ -1,4 +1,4 @@
1
- import { HelixElement } from '@fusion.dev/helix-core';
1
+ import { ChildrenController, HelixElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  /**
4
4
  * @summary Shows one slide of several, with controls to move between them.
@@ -727,9 +727,10 @@ export declare class HelixHeadlessCarousel extends HelixElement {
727
727
  * that changes an assignment is this component calling `assign`, so a slide
728
728
  * appended by a consumer would reach no slot, render nowhere and be counted by
729
729
  * nothing. A default slot needs no watcher, taking whatever is there; a manual
730
- * one does. `Chat` watches for the same reason.
730
+ * one does. `Chat` watches for the same reason, through the same core
731
+ * controller.
731
732
  */
732
- private watcher;
733
+ protected readonly childWatch: ChildrenController;
733
734
  connectedCallback(): void;
734
735
  disconnectedCallback(): void;
735
736
  /** Pauses without forgetting that the reader wanted it running. */
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { capturePointer, customElement, fillTemplate, HelixActivateEvent, HelixElement, HelixSelectEvent, hostAria, inlineStep, isRtl, requestChange, } from '@fusion.dev/helix-core';
7
+ import { ChildrenController, capturePointer, customElement, fillTemplate, HelixActivateEvent, HelixElement, HelixSelectEvent, hostAria, inlineStep, isRtl, requestChange, } from '@fusion.dev/helix-core';
8
8
  import { caretLeft, caretRight, play, stop } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -185,7 +185,7 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
185
185
  };
186
186
  this.scrollFrame = 0;
187
187
  this.stopped = false;
188
- this.watcher = undefined;
188
+ this.childWatch = new ChildrenController(this);
189
189
  this.pause = () => {
190
190
  if (!this.stopped) {
191
191
  this.rotating = false;
@@ -388,8 +388,6 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
388
388
  super.connectedCallback();
389
389
  this.addEventListener('focusin', this.pause);
390
390
  this.addEventListener('pointerenter', this.pause);
391
- this.watcher = new MutationObserver(() => this.requestUpdate());
392
- this.watcher.observe(this, { childList: true });
393
391
  this.watchPane();
394
392
  if (this.hasUpdated) {
395
393
  this.runTimer();
@@ -403,8 +401,6 @@ let HelixHeadlessCarousel = class HelixHeadlessCarousel extends HelixElement {
403
401
  }
404
402
  this.removeEventListener('focusin', this.pause);
405
403
  this.removeEventListener('pointerenter', this.pause);
406
- this.watcher?.disconnect();
407
- this.watcher = undefined;
408
404
  this.sizes?.disconnect();
409
405
  this.sizes = undefined;
410
406
  this.watched = undefined;
@@ -1,4 +1,4 @@
1
- import { HelixElement } from '@fusion.dev/helix-core';
1
+ import { ChildrenController, HelixElement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  import { type HelixHeadlessChatMessage } from '../message/chat-message.js';
4
4
  /** One turn handed over as data, which is what `turns` holds. */
@@ -253,8 +253,18 @@ export declare class HelixHeadlessChat extends HelixHeadlessChat_base {
253
253
  * a turn is told again only when they change.
254
254
  */
255
255
  private words;
256
- /** Watches for turns arriving, since a manual slot hears nothing about them. */
257
- private watcher;
256
+ /**
257
+ * Watches for turns arriving, since a manual slot hears nothing about them.
258
+ *
259
+ * **A turn that arrives after first paint reaches nothing on its own.**
260
+ * Slots are assigned by hand here, so appending a child assigns it to
261
+ * nothing, fires no `slotchange`, and schedules no render: the transcript
262
+ * went on showing "Nothing to show" over six children it already had, and
263
+ * the one thing a conversation does is grow. Measured with
264
+ * `assignedElements()` at zero against `children` at nine. Children only,
265
+ * never a turn's own text, which a streaming turn rewrites every frame.
266
+ */
267
+ protected readonly childWatch: ChildrenController;
258
268
  /** Watches the turns' own `state`, which is when one of them settles. */
259
269
  private settling;
260
270
  /** Watches the turns and the log for size, which scrolling does not report. */
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  var HelixHeadlessChat_1;
8
- import { customElement, fillTemplate, HelixBeforePinEvent, HelixElement, HelixHistoryEvent, HelixPinEvent, hostAria, MixinCollection, onUnlessFalse, requestChange, VirtualizeController, whenHydrated, wordsConverter, } from '@fusion.dev/helix-core';
8
+ import { ChildrenController, customElement, fillTemplate, HelixBeforePinEvent, HelixElement, HelixHistoryEvent, HelixPinEvent, hostAria, MixinCollection, onUnlessFalse, requestChange, VirtualizeController, whenHydrated, wordsConverter, } from '@fusion.dev/helix-core';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { keyed } from 'lit/directives/keyed.js';
@@ -56,7 +56,7 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
56
56
  this.announcement = '';
57
57
  this.said = 0;
58
58
  this.words = TURN_WORDS;
59
- this.watcher = undefined;
59
+ this.childWatch = new ChildrenController(this, () => this.handleSlotChange());
60
60
  this.settling = undefined;
61
61
  this.grower = undefined;
62
62
  this.watched = [];
@@ -349,8 +349,6 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
349
349
  }
350
350
  connectedCallback() {
351
351
  super.connectedCallback();
352
- this.watcher = new MutationObserver(this.handleSlotChange);
353
- this.watcher.observe(this, { childList: true });
354
352
  this.settling = new MutationObserver(this.handleSettle);
355
353
  this.settling.observe(this, { attributeFilter: ['state'], subtree: true });
356
354
  this.opening = true;
@@ -363,8 +361,6 @@ let HelixHeadlessChat = class HelixHeadlessChat extends MixinCollection(HelixEle
363
361
  }
364
362
  disconnectedCallback() {
365
363
  super.disconnectedCallback();
366
- this.watcher?.disconnect();
367
- this.watcher = undefined;
368
364
  this.settling?.disconnect();
369
365
  this.settling = undefined;
370
366
  this.grower?.disconnect();
@@ -96,8 +96,21 @@ export declare class HelixHeadlessCheckboxGroup extends HelixHeadlessCheckboxGro
96
96
  */
97
97
  get values(): string[];
98
98
  set values(next: string[]);
99
+ /**
100
+ * Writes the set the boxes are given at the next update: by value, or, for
101
+ * a set a press decided, by box.
102
+ */
103
+ private choose;
99
104
  /** The values last written, which the boxes are given at the next update. */
100
105
  private declared;
106
+ /**
107
+ * The boxes to check at the next update, where a press on a box or on the
108
+ * select-all box decided the set.
109
+ *
110
+ * **By box, since boxes can share a value.** Boxes written with no `value`
111
+ * all submit `on`, and pushed by value, a press on one checked every one.
112
+ */
113
+ private checking;
101
114
  /**
102
115
  * Whether `declared` has yet to reach the boxes.
103
116
  *
@@ -5,19 +5,20 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  var HelixHeadlessCheckboxGroup_1;
8
- import { commaList, customElement, DisabledCascadeController, HelixBeforeChangeEvent, HelixFormElement, hostAria, MixinField, toggleEvery, } from '@fusion.dev/helix-core';
8
+ import { commaList, customElement, DisabledCascadeController, HelixBeforeChangeEvent, HelixFormElement, hostAria, MixinField, } from '@fusion.dev/helix-core';
9
9
  import { nothing } from 'lit';
10
10
  import { property } from 'lit/decorators.js';
11
11
  import { html } from 'lit/static-html.js';
12
12
  import { HelixHeadlessCheckbox } from '../../checkbox/root/checkbox-root.js';
13
13
  import '../../checkbox/index.js';
14
- import { selectAllState } from '../select-all.js';
14
+ import { selectAllPress, selectAllState } from '../select-all.js';
15
15
  let HelixHeadlessCheckboxGroup = HelixHeadlessCheckboxGroup_1 = class HelixHeadlessCheckboxGroup extends MixinField(HelixFormElement) {
16
16
  constructor() {
17
17
  super(...arguments);
18
18
  this.selectAll = false;
19
19
  this.selectAllLabel = 'Select all';
20
20
  this.declared = [];
21
+ this.checking = undefined;
21
22
  this.pending = false;
22
23
  this.watched = new Set();
23
24
  this.member = {
@@ -41,14 +42,13 @@ let HelixHeadlessCheckboxGroup = HelixHeadlessCheckboxGroup_1 = class HelixHeadl
41
42
  return;
42
43
  }
43
44
  const wanted = event.detail.checked;
44
- const next = this.boxes
45
- .filter((box) => (box === changed ? wanted : box.checked))
46
- .map((box) => box.value);
45
+ const checking = this.boxes.filter((box) => (box === changed ? wanted : box.checked));
46
+ const next = checking.map((box) => box.value);
47
47
  if (!this.dispatchEvent(new HelixBeforeChangeEvent({ value: next.join(',') }))) {
48
48
  event.preventDefault();
49
49
  return;
50
50
  }
51
- this.values = next;
51
+ this.choose(next, checking);
52
52
  this.announceInput();
53
53
  this.announceChange();
54
54
  };
@@ -60,12 +60,14 @@ let HelixHeadlessCheckboxGroup = HelixHeadlessCheckboxGroup_1 = class HelixHeadl
60
60
  this.handleSelectAll = (event) => {
61
61
  event.preventDefault();
62
62
  event.stopPropagation();
63
- const boxes = this.setBoxes();
64
- const next = toggleEvery(boxes.filter((box) => box.checked).map((box) => box.value), boxes.filter((box) => !box.disabled).map((box) => box.value), boxes.map((box) => box.value));
63
+ const boxes = this.boxes;
64
+ const pressed = selectAllPress(this.setBoxes());
65
+ const checking = boxes.filter((_, at) => pressed[at]);
66
+ const next = checking.map((box) => box.value);
65
67
  if (!this.dispatchEvent(new HelixBeforeChangeEvent({ value: next.join(',') }))) {
66
68
  return;
67
69
  }
68
- this.values = next;
70
+ this.choose(next, checking);
69
71
  this.announceInput();
70
72
  this.announceChange();
71
73
  };
@@ -86,8 +88,12 @@ let HelixHeadlessCheckboxGroup = HelixHeadlessCheckboxGroup_1 = class HelixHeadl
86
88
  : boxes.filter((box) => box.checked).map((box) => box.value);
87
89
  }
88
90
  set values(next) {
91
+ this.choose(next);
92
+ }
93
+ choose(next, boxes) {
89
94
  const previous = this.value;
90
95
  this.declared = [...next];
96
+ this.checking = boxes ? new Set(boxes) : undefined;
91
97
  this.pending = true;
92
98
  this.requestUpdate('value', previous);
93
99
  }
@@ -125,8 +131,10 @@ let HelixHeadlessCheckboxGroup = HelixHeadlessCheckboxGroup_1 = class HelixHeadl
125
131
  }
126
132
  this.pending = false;
127
133
  const wanted = new Set(this.declared);
134
+ const checking = this.checking;
135
+ this.checking = undefined;
128
136
  for (const box of boxes) {
129
- box.checked = wanted.has(box.value);
137
+ box.checked = checking ? checking.has(box) : wanted.has(box.value);
130
138
  }
131
139
  }
132
140
  connectedCallback() {
@@ -19,3 +19,14 @@ export declare const selectAllState: (boxes: readonly SetBox[]) => {
19
19
  checked: boolean;
20
20
  indeterminate: boolean;
21
21
  };
22
+ /**
23
+ * What a press on the select-all box does to each box, in the set's order:
24
+ * every enabled box checked, or every one cleared once all of them are, with a
25
+ * disabled box keeping its state. A set with no enabled box stays as it is.
26
+ *
27
+ * **By box and not by value**, so boxes that share a value, such as boxes
28
+ * written with none, which all submit `on`, are each moved on their own: by
29
+ * value, one checked box made every box with its value count as checked, and
30
+ * the press cleared the set where it should have filled it.
31
+ */
32
+ export declare const selectAllPress: (boxes: readonly SetBox[]) => boolean[];
@@ -5,3 +5,8 @@ export const selectAllState = (boxes) => {
5
5
  const checked = counted.length > 0 && ticked === counted.length;
6
6
  return { checked, indeterminate: ticked > 0 && !checked };
7
7
  };
8
+ export const selectAllPress = (boxes) => {
9
+ const enabled = boxes.filter((box) => !box.disabled);
10
+ const every = enabled.length > 0 && enabled.every((box) => box.checked);
11
+ return boxes.map((box) => (box.disabled ? box.checked : !every));
12
+ };
@@ -78,7 +78,7 @@ import '../../copy-button/index.js';
78
78
  * @csspart gutter - The line numbers beside the code, shown when `line-numbers` is set.
79
79
  * @csspart header - The bar that holds the name and the copy button, shown when there is a name.
80
80
  * @csspart hunk - In a diff, a `@@` line saying where the lines under it are.
81
- * @csspart label - The file name or language, where one is given.
81
+ * @csspart label - The block's name: its `label`, or its `language` where it has no label.
82
82
  * @csspart marker - In a diff, the `+`, `-`, or space that starts a line.
83
83
  * @csspart new - In a split diff, the new version's `<pre>`.
84
84
  * @csspart number - In a diff, a line number.
@@ -151,18 +151,20 @@ export declare class HelixHeadlessCodeBlock extends HelixElement {
151
151
  */
152
152
  highlight?: (code: string, language: string) => string;
153
153
  /**
154
- * The block's name, usually a file name or a language.
154
+ * The block's name, usually a file name, drawn in the header.
155
155
  *
156
- * In a diff, each file is named from the patch, and this names a patch that
157
- * carries no name of its own.
156
+ * Absent, the block is named by `language` where there is one. In a diff,
157
+ * each file is named from the patch, and this names a patch that carries no
158
+ * name of its own.
158
159
  */
159
160
  label: string;
160
161
  /**
161
162
  * The language the code is written in, such as `ts` or `css`, handed to
162
163
  * `highlight` as its second argument.
163
164
  *
164
- * Absent hands it an empty string. It draws nothing of its own: the block's
165
- * name is `label`.
165
+ * A block with no `label` is named by it, in the header and on the copy
166
+ * button, as a docs site names a snippet by its language. Absent hands
167
+ * `highlight` an empty string and names nothing.
166
168
  */
167
169
  language: string;
168
170
  /**
@@ -109,10 +109,11 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
109
109
  const gutter = this.lineNumbers
110
110
  ? html `<span part="gutter" aria-hidden="true">${lines === 0 ? nothing : gutterNumbers(lines)}</span>`
111
111
  : nothing;
112
- return html `${this.renderHeader(this.label)}
112
+ const name = this.label || this.language;
113
+ return html `${this.renderHeader(name)}
113
114
  <div part="frame">
114
115
  <pre id="block" part="block">${gutter}${code}</pre>
115
- ${this.label ? nothing : this.renderCopy()}
116
+ ${name ? nothing : this.renderCopy()}
116
117
  </div>${this.renderExpand()}`;
117
118
  }
118
119
  renderExpand() {
@@ -140,7 +141,7 @@ let HelixHeadlessCodeBlock = class HelixHeadlessCodeBlock extends HelixElement {
140
141
  renderCopy(value, name) {
141
142
  return this.noCopy ? nothing : this.renderCopyButton(value, name);
142
143
  }
143
- renderCopyButton(value = this.text, name = this.label) {
144
+ renderCopyButton(value = this.text, name = this.label || this.language) {
144
145
  return html `<${this.tag('copy-button')}
145
146
  part="copy"
146
147
  exportparts="button: copy-button, text: copy-text, mark: copy-mark, live: copy-live, spinner: copy-spinner, tooltip: copy-tooltip,
@@ -4,14 +4,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { CopyController, capturePointer, clamp, colorName, customElement, drawIcon, fillTemplate, formatColor, formatNumber, HelixFormElement, HelixSampleEndEvent, HelixSampleStartEvent, hostAria, hueName, isRtl, KeyboardNavigationController, localeOf, MixinControllerValue, MixinPickerFeedback, parseColor, rovingStop, splitValues, toCss, wordsConverter, } from '@fusion.dev/helix-core';
7
+ import { CopyController, capturePointer, clamp, colorName, customElement, drawIcon, fillTemplate, focusItemOf, formatColor, formatNumber, HelixFormElement, HelixSampleEndEvent, HelixSampleStartEvent, hostAria, hueName, isRtl, KeyboardNavigationController, localeOf, MixinControllerValue, MixinPickerFeedback, parseColor, rovingStop, splitValues, toCss, wordsConverter, } from '@fusion.dev/helix-core';
8
8
  import { eyedropper } from '@fusion.dev/helix-icons';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { live } from 'lit/directives/live.js';
12
12
  import { html } from 'lit/static-html.js';
13
13
  import { channelFields, writeChannel } from '../channels.js';
14
- import { hueAt } from '../wheel.js';
14
+ import { areaAt, hueAt } from '../wheel.js';
15
15
  import '../../button/index.js';
16
16
  import '../../button-group/index.js';
17
17
  import '../../slider/index.js';
@@ -99,15 +99,12 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
99
99
  items: () => [
100
100
  ...(this.renderRoot?.querySelectorAll('[part="swatches"] [part="swatch"]') ??
101
101
  []),
102
- ].map((swatch, index) => ({
103
- contains: (node) => swatch === node,
104
- disabled: false,
105
- focusItem: () => {
102
+ ].map((swatch, index) => focusItemOf(swatch, {
103
+ focus: () => {
106
104
  this.swatchAt = index;
107
105
  swatch.focus();
108
106
  },
109
- select: () => { },
110
- selected: false,
107
+ owns: (node) => node === swatch,
111
108
  })),
112
109
  origin: 'focus',
113
110
  selects: () => false,
@@ -165,10 +162,7 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinPicke
165
162
  if (box.width === 0 || box.height === 0) {
166
163
  return;
167
164
  }
168
- const fromLeft = Math.min(1, Math.max(0, (event.clientX - box.left) / box.width));
169
- const across = isRtl(grid) ? 1 - fromLeft : fromLeft;
170
- const down = Math.min(1, Math.max(0, (event.clientY - box.top) / box.height));
171
- this.move({ brightness: Math.round((1 - down) * 100), saturation: Math.round(across * 100) });
165
+ this.move(areaAt(event.clientX, event.clientY, box, isRtl(grid)));
172
166
  };
173
167
  this.handleWheelPointer = (event) => {
174
168
  const wheel = event.currentTarget;
@@ -15,3 +15,14 @@ export interface WheelBox {
15
15
  * whatever the writing direction, since a circle has no line of text.
16
16
  */
17
17
  export declare const hueAt: (x: number, y: number, box: WheelBox) => number;
18
+ /**
19
+ * The saturation and brightness at a point in the color square, 0 to 100:
20
+ * brightness falls downward, and saturation runs along the line, the way the
21
+ * two ramps under the square do, being native ranges. On a right-to-left page
22
+ * (`rtl`) it grows toward the left. A point outside the square is held to its
23
+ * edge.
24
+ */
25
+ export declare const areaAt: (x: number, y: number, box: WheelBox, rtl: boolean) => {
26
+ brightness: number;
27
+ saturation: number;
28
+ };
@@ -1,6 +1,15 @@
1
+ import { clamp } from '@fusion.dev/helix-core';
1
2
  export const hueAt = (x, y, box) => {
2
3
  const across = x - (box.left + box.width / 2);
3
4
  const down = y - (box.top + box.height / 2);
4
5
  const degrees = Math.round((Math.atan2(across, -down) * 180) / Math.PI);
5
6
  return (degrees + 360) % 360;
6
7
  };
8
+ export const areaAt = (x, y, box, rtl) => {
9
+ const fromLeft = clamp((x - box.left) / box.width, 0, 1);
10
+ const down = clamp((y - box.top) / box.height, 0, 1);
11
+ return {
12
+ brightness: Math.round((1 - down) * 100),
13
+ saturation: Math.round((rtl ? 1 - fromLeft : fromLeft) * 100),
14
+ };
15
+ };
@@ -17,13 +17,33 @@ declare const HelixHeadlessCombobox_base: (new (...args: any[]) => import("@fusi
17
17
  * reopened combobox starts from the whole list.
18
18
  *
19
19
  * Focus stays in the text field. The arrow keys move through the list with the
20
- * listbox's public `navigate` method. `role="combobox"` is on the input itself.
21
- * ArrowDown and ArrowUp open the list on the chosen option, and Enter chooses
22
- * the highlighted one. As typing narrows the list, a screen reader hears how
23
- * many options are left, such as "3 results". Space, Home, End and letters edit the text, and Enter in
24
- * a closed field is left to the form. Emptying the field and leaving it clears
25
- * a single choice; Escape puts the chosen words back, and a second Escape on
26
- * the closed field clears the choice.
20
+ * listbox's public `navigate` method. As typing narrows the list, a screen
21
+ * reader hears how many options are left, such as "3 results". Emptying the
22
+ * field and leaving it clears a single choice.
23
+ *
24
+ * **Keyboard.**
25
+ *
26
+ * | Key | What it does |
27
+ * |---|---|
28
+ * | Down, Up | On a closed field, opens the list on the chosen option, or the first or the last. In the open list, moves the highlight, round from the last option to the first. |
29
+ * | Page Down, Page Up | In the open list, moves the highlight by as many options as the list shows at once. |
30
+ * | Enter | Chooses the highlighted option. With nothing left in the list, creates what was typed with `creatable`, or takes it with `allows-custom-value`. On a closed field, submits the form. |
31
+ * | Escape | Closes the list and puts the chosen words back. On a closed field, a second Escape clears a single choice. |
32
+ * | Tab | Closes the list and moves on. |
33
+ * | Backspace | With `multiple`, on an empty field, removes the last tag. |
34
+ * | Space, Home, End, letters | Edit the text. |
35
+ *
36
+ * **ARIA.** The input is the `combobox`, named by the label, with
37
+ * `aria-expanded`, `aria-controls` naming the list, `aria-autocomplete`
38
+ * (`both` with `suggest`, `list` otherwise) and `aria-activedescendant` on
39
+ * the highlighted option. The list is the listbox `Listbox` describes.
40
+ *
41
+ * On Apple devices, where VoiceOver does not read the option the field points
42
+ * to, the field says it in a live region as the highlight moves, says how many
43
+ * options there are as the list opens, and says a choice once it is made, as
44
+ * React Aria's combobox does there.
45
+ *
46
+ * Set `autocomplete` to let a browser's autofill choose an option.
27
47
  *
28
48
  * With `multiple`, each chosen option is a tag in the field with its own remove
29
49
  * control. The panel stays open and the filter clears after each choice, so the
@@ -83,6 +103,7 @@ declare const HelixHeadlessCombobox_base: (new (...args: any[]) => import("@fusi
83
103
  * @csspart listbox-loading - Shown while the options are loading.
84
104
  * @csspart listbox-search - The region holding the field that narrows the list.
85
105
  * @csspart listbox-search-field - The filter field inside that region.
106
+ * @csspart live - The region VoiceOver hears the highlight, the count and a choice from on Apple devices, drawn once there is one to say. Visually hidden in the styled tier.
86
107
  * @csspart rest - The count of choices held beyond `max-tags`. A `<button>` with `expandable`, a `<span>` without it.
87
108
  * @csspart surface - The floating panel.
88
109
  * @csspart tag - One chosen option.
@@ -94,13 +115,22 @@ declare const HelixHeadlessCombobox_base: (new (...args: any[]) => import("@fusi
94
115
  * @csspart tag-start - The category before the label.
95
116
  * @csspart tags - The row of chosen options, when `multiple` is set.
96
117
  *
118
+ * @cssstate blank - While nothing is chosen.
97
119
  * @cssstate error - While `load-error` is set.
98
120
  * @cssstate invalid - The control has an error to show, from the browser's validation or from your `feedback`.
121
+ * @cssstate loading - While `loading` is set.
99
122
  * @cssstate no-results - What was typed hid every option of a list that has some.
100
123
  * @cssstate open - While the list is open.
101
124
  */
102
125
  export declare class HelixHeadlessCombobox extends HelixHeadlessCombobox_base {
103
126
  protected readonly aria: AriaForwardController;
127
+ /** The region VoiceOver hears the highlight, the count and a choice from. */
128
+ private readonly status;
129
+ /**
130
+ * The option, or the create row, the region last said: `null` for none,
131
+ * and `undefined` until it has spoken since the list opened.
132
+ */
133
+ private told;
104
134
  /**
105
135
  * Whether text that names no option is taken as the value, as React Aria's
106
136
  * `allowsCustomValue`.
@@ -113,12 +143,19 @@ export declare class HelixHeadlessCombobox extends HelixHeadlessCombobox_base {
113
143
  */
114
144
  allowsCustomValue: boolean;
115
145
  /**
116
- * What the browser may autofill in the text field, as a platform token.
146
+ * What the browser may autofill, as a platform token such as `country`.
117
147
  *
118
148
  * **Defaults to `off`, which is not the platform's default and is the
119
149
  * point.** The field filters a list rather than collecting a value, so a
120
150
  * browser offering to fill it with a saved address is competing for the same
121
151
  * keystrokes as the listbox. A consumer who wants autofill can still ask.
152
+ *
153
+ * Set, a combobox whose answer is one of its options keeps a hidden native
154
+ * `<select>` with the same options for the browser to fill, as React Aria's
155
+ * combobox and Select do, and the option the browser picks, by its value or
156
+ * its words, becomes the value. The text field itself stays `off`, so the
157
+ * browser fills the answer and not the filter. With `allows-custom-value`
158
+ * the token goes on the text field, whose words are the answer.
122
159
  */
123
160
  autocomplete: string;
124
161
  /**
@@ -155,10 +192,11 @@ export declare class HelixHeadlessCombobox extends HelixHeadlessCombobox_base {
155
192
  listLabel: string;
156
193
  /**
157
194
  * What the list says when its options could not be loaded, such as after a
158
- * request to a server fails. Empty is no error. Passed to the list, which
159
- * shows it under any options there are and tells screen readers.
195
+ * request to a server fails. Empty, null or no attribute is no error, so
196
+ * removing the attribute clears it. Passed to the list, which shows it
197
+ * under any options there are and tells screen readers.
160
198
  */
161
- loadError: string;
199
+ loadError: string | null;
162
200
  /**
163
201
  * Whether the options are still loading.
164
202
  *
@@ -236,6 +274,11 @@ export declare class HelixHeadlessCombobox extends HelixHeadlessCombobox_base {
236
274
  * two.
237
275
  */
238
276
  resultsLabel: string;
277
+ /**
278
+ * What VoiceOver hears for a chosen option, as the highlight reaches it and
279
+ * once it is chosen, on Apple devices. `{name}` is the option's words.
280
+ */
281
+ selectedLabel: string;
239
282
  /**
240
283
  * Whether the field completes the word as the user types.
241
284
  *
@@ -459,6 +502,20 @@ export declare class HelixHeadlessCombobox extends HelixHeadlessCombobox_base {
459
502
  * is missing.
460
503
  */
461
504
  private linkActive;
505
+ /**
506
+ * Says the highlight, and the count as the list opens, where VoiceOver
507
+ * would not.
508
+ *
509
+ * **VoiceOver does not read the option `aria-activedescendant` points to**
510
+ * while focus is in a text field, so on Apple devices the arrows moved
511
+ * through the list in silence. React Aria says the option there, with
512
+ * "selected" where it is chosen, and how many there are as the list opens.
513
+ * Only when the highlight moves to another option, so an update that leaves
514
+ * it where it was says nothing.
515
+ */
516
+ private tell;
517
+ /** How many options there are, said as the list opens, until an option is said with it. */
518
+ private counted;
462
519
  /**
463
520
  * Takes what the list announced and stops it there.
464
521
  *
@@ -565,6 +622,8 @@ export declare class HelixHeadlessCombobox extends HelixHeadlessCombobox_base {
565
622
  */
566
623
  private renderCreate;
567
624
  protected render(): TemplateResult;
625
+ /** Whether a browser's autofill fills the hidden select, rather than the text field. */
626
+ private get fills();
568
627
  }
569
628
  declare global {
570
629
  interface HTMLElementTagNameMap {