@fusion.dev/helix-headless 0.142.1 → 0.143.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 (217) hide show
  1. package/custom-elements.json +16710 -707
  2. package/dist/accordion/item/accordion-item.d.ts +38 -2
  3. package/dist/accordion/item/accordion-item.js +38 -6
  4. package/dist/accordion/root/accordion-root.d.ts +23 -1
  5. package/dist/accordion/root/accordion-root.js +39 -3
  6. package/dist/announcement/root/announcement-root.d.ts +11 -7
  7. package/dist/announcement/root/announcement-root.js +1 -1
  8. package/dist/app-header/root/app-header-root.d.ts +21 -0
  9. package/dist/app-header/root/app-header-root.js +31 -5
  10. package/dist/area-chart/root/area-chart-root.js +16 -5
  11. package/dist/avatar/group/avatar-group.d.ts +24 -3
  12. package/dist/avatar/group/avatar-group.js +34 -2
  13. package/dist/avatar/root/avatar-root.d.ts +3 -1
  14. package/dist/avatar/root/avatar-root.js +8 -1
  15. package/dist/badge/root/badge-root.d.ts +8 -0
  16. package/dist/bar-chart/root/bar-chart-root.d.ts +19 -3
  17. package/dist/bar-chart/root/bar-chart-root.js +43 -13
  18. package/dist/blockquote/root/blockquote-root.d.ts +12 -7
  19. package/dist/blockquote/root/blockquote-root.js +6 -4
  20. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +9 -3
  21. package/dist/breadcrumbs/root/breadcrumbs-root.js +9 -1
  22. package/dist/button/root/button-root.d.ts +37 -0
  23. package/dist/button/root/button-root.js +44 -0
  24. package/dist/callout/root/callout-root.d.ts +14 -5
  25. package/dist/callout/root/callout-root.js +11 -3
  26. package/dist/canvas/root/canvas-root.d.ts +1 -0
  27. package/dist/canvas/root/canvas-root.js +10 -1
  28. package/dist/carousel/root/carousel-root.d.ts +47 -5
  29. package/dist/carousel/root/carousel-root.js +53 -21
  30. package/dist/chat/composer/chat-composer.d.ts +13 -11
  31. package/dist/chat/composer/chat-composer.js +6 -12
  32. package/dist/chat/message/chat-message.js +8 -1
  33. package/dist/chat/root/chat-root.d.ts +1 -0
  34. package/dist/chat/root/chat-root.js +2 -1
  35. package/dist/checkbox/root/checkbox-root.d.ts +7 -0
  36. package/dist/checkbox/root/checkbox-root.js +7 -3
  37. package/dist/checkbox-group/root/checkbox-group-root.d.ts +65 -32
  38. package/dist/checkbox-group/root/checkbox-group-root.js +85 -37
  39. package/dist/code/root/code-root.d.ts +1 -1
  40. package/dist/code-block/root/code-block-root.d.ts +19 -0
  41. package/dist/code-block/root/code-block-root.js +21 -1
  42. package/dist/color-input/root/color-input-root.d.ts +26 -1
  43. package/dist/color-input/root/color-input-root.js +32 -3
  44. package/dist/color-picker/root/color-picker-root.d.ts +37 -7
  45. package/dist/color-picker/root/color-picker-root.js +83 -6
  46. package/dist/combobox/root/combobox-root.d.ts +90 -2
  47. package/dist/combobox/root/combobox-root.js +160 -19
  48. package/dist/command/root/command-root.d.ts +17 -6
  49. package/dist/command/root/command-root.js +12 -1
  50. package/dist/copy-button/root/copy-button-root.d.ts +21 -6
  51. package/dist/copy-button/root/copy-button-root.js +11 -1
  52. package/dist/data-table/root/data-table-root.d.ts +164 -10
  53. package/dist/data-table/root/data-table-root.js +488 -57
  54. package/dist/date-input/root/date-input-root.d.ts +53 -8
  55. package/dist/date-input/root/date-input-root.js +70 -18
  56. package/dist/date-picker/root/date-picker-root.d.ts +84 -17
  57. package/dist/date-picker/root/date-picker-root.js +125 -20
  58. package/dist/dialog/root/dialog-root.d.ts +38 -10
  59. package/dist/dialog/root/dialog-root.js +55 -2
  60. package/dist/donut-chart/root/donut-chart-root.d.ts +39 -15
  61. package/dist/donut-chart/root/donut-chart-root.js +30 -10
  62. package/dist/drawer/root/drawer-root.d.ts +32 -7
  63. package/dist/drawer/root/drawer-root.js +50 -5
  64. package/dist/drilldown/item/drilldown-item.d.ts +16 -13
  65. package/dist/drilldown/item/drilldown-item.js +6 -2
  66. package/dist/drilldown/root/drilldown-root.d.ts +21 -5
  67. package/dist/drilldown/root/drilldown-root.js +26 -10
  68. package/dist/element-events.generated.js +35 -9
  69. package/dist/file-input/root/file-input-root.d.ts +18 -1
  70. package/dist/file-input/root/file-input-root.js +34 -8
  71. package/dist/filter-input/menu/filter-menu.d.ts +9 -0
  72. package/dist/filter-input/menu/filter-menu.js +8 -0
  73. package/dist/filter-input/root/filter-input-root.d.ts +5 -2
  74. package/dist/filter-input/root/filter-input-root.js +22 -7
  75. package/dist/format-date/root/format-date-root.d.ts +11 -3
  76. package/dist/format-date/root/format-date-root.js +26 -5
  77. package/dist/graph/root/graph-root.d.ts +24 -3
  78. package/dist/graph/root/graph-root.js +66 -12
  79. package/dist/heatmap/root/buckets.d.ts +2 -1
  80. package/dist/heatmap/root/buckets.js +5 -1
  81. package/dist/heatmap/root/heatmap-root.d.ts +27 -6
  82. package/dist/heatmap/root/heatmap-root.js +23 -6
  83. package/dist/icon/root/icon-root.d.ts +24 -3
  84. package/dist/icon/root/icon-root.js +34 -1
  85. package/dist/icon-button/root/icon-button-root.d.ts +1 -1
  86. package/dist/image/root/image-root.d.ts +9 -2
  87. package/dist/image/root/image-root.js +8 -2
  88. package/dist/input/root/input-root.d.ts +51 -9
  89. package/dist/input/root/input-root.js +46 -5
  90. package/dist/kbd/root/kbd-root.d.ts +12 -4
  91. package/dist/kbd/root/kbd-root.js +10 -11
  92. package/dist/legend/root/legend-root.d.ts +7 -0
  93. package/dist/legend/root/legend-root.js +7 -2
  94. package/dist/lightbox/item/lightbox-item.d.ts +2 -1
  95. package/dist/lightbox/root/lightbox-root.d.ts +16 -5
  96. package/dist/lightbox/root/lightbox-root.js +24 -3
  97. package/dist/line-chart/root/line-chart-root.d.ts +19 -2
  98. package/dist/line-chart/root/line-chart-root.js +40 -13
  99. package/dist/list/item/list-item.d.ts +11 -5
  100. package/dist/list/item/list-item.js +5 -6
  101. package/dist/listbox/root/listbox-root.d.ts +111 -2
  102. package/dist/listbox/root/listbox-root.js +144 -16
  103. package/dist/map/root/map-root.d.ts +19 -4
  104. package/dist/map/root/map-root.js +36 -7
  105. package/dist/megamenu/root/megamenu-root.d.ts +10 -1
  106. package/dist/megamenu/root/megamenu-root.js +10 -1
  107. package/dist/menu/item/menu-item.d.ts +41 -12
  108. package/dist/menu/item/menu-item.js +46 -3
  109. package/dist/menu/root/menu-root.d.ts +32 -2
  110. package/dist/menu/root/menu-root.js +48 -8
  111. package/dist/meter/root/meter-root.d.ts +6 -0
  112. package/dist/meter/root/meter-root.js +18 -2
  113. package/dist/navigation/root/navigation-root.d.ts +10 -2
  114. package/dist/navigation/root/navigation-root.js +13 -2
  115. package/dist/notifications/group/notification-group.d.ts +1 -0
  116. package/dist/notifications/notification/notification.d.ts +12 -0
  117. package/dist/notifications/notification/notification.js +31 -2
  118. package/dist/notifications/root/notifications-root.d.ts +7 -0
  119. package/dist/notifications/root/notifications-root.js +17 -2
  120. package/dist/number-input/root/number-input-root.d.ts +62 -7
  121. package/dist/number-input/root/number-input-root.js +68 -16
  122. package/dist/object-inspector/root/object-inspector-root.d.ts +48 -5
  123. package/dist/object-inspector/root/object-inspector-root.js +91 -11
  124. package/dist/option/root/option-root.d.ts +4 -1
  125. package/dist/option/root/option-root.js +1 -0
  126. package/dist/outline/root/outline-root.d.ts +13 -2
  127. package/dist/outline/root/outline-root.js +4 -2
  128. package/dist/pagination/root/pagination-root.d.ts +19 -1
  129. package/dist/pagination/root/pagination-root.js +49 -9
  130. package/dist/pagination/size/pagination-size.d.ts +13 -3
  131. package/dist/pagination/size/pagination-size.js +7 -0
  132. package/dist/pin-input/root/pin-input-root.d.ts +16 -0
  133. package/dist/pin-input/root/pin-input-root.js +19 -5
  134. package/dist/popover/root/popover-root.d.ts +19 -2
  135. package/dist/popover/root/popover-root.js +22 -1
  136. package/dist/progress/root/progress-root.d.ts +16 -1
  137. package/dist/progress/root/progress-root.js +31 -7
  138. package/dist/qr-code/root/qr-code-root.d.ts +19 -0
  139. package/dist/qr-code/root/qr-code-root.js +29 -1
  140. package/dist/radio/group/radio-group.d.ts +41 -0
  141. package/dist/radio/group/radio-group.js +50 -14
  142. package/dist/radio/root/radio-root.d.ts +8 -5
  143. package/dist/radio/root/radio-root.js +1 -2
  144. package/dist/rating/root/rating-root.d.ts +24 -1
  145. package/dist/rating/root/rating-root.js +24 -7
  146. package/dist/scatter-chart/root/scatter-chart-root.js +15 -7
  147. package/dist/select/root/select-root.d.ts +69 -3
  148. package/dist/select/root/select-root.js +92 -8
  149. package/dist/skeleton/root/skeleton-root.d.ts +14 -3
  150. package/dist/skeleton/root/skeleton-root.js +17 -3
  151. package/dist/skip-link/root/skip-link-root.d.ts +10 -0
  152. package/dist/skip-link/root/skip-link-root.js +15 -1
  153. package/dist/slider/root/slider-root.d.ts +20 -2
  154. package/dist/slider/root/slider-root.js +41 -3
  155. package/dist/sparkline/root/sparkline-root.d.ts +9 -10
  156. package/dist/speed-dial/item/speed-dial-item.d.ts +11 -5
  157. package/dist/speed-dial/item/speed-dial-item.js +7 -2
  158. package/dist/speed-dial/root/speed-dial-root.d.ts +15 -4
  159. package/dist/speed-dial/root/speed-dial-root.js +19 -5
  160. package/dist/splitter/root/splitter-root.d.ts +28 -6
  161. package/dist/splitter/root/splitter-root.js +35 -1
  162. package/dist/stat/root/stat-root.d.ts +7 -1
  163. package/dist/stat/root/stat-root.js +14 -1
  164. package/dist/steps/root/steps-root.js +1 -1
  165. package/dist/switch/root/switch-root.d.ts +2 -0
  166. package/dist/switch/root/switch-root.js +1 -0
  167. package/dist/table/cell/table-cell.d.ts +24 -4
  168. package/dist/table/cell/table-cell.js +21 -1
  169. package/dist/table/column/table-column.d.ts +32 -6
  170. package/dist/table/column/table-column.js +36 -6
  171. package/dist/table/root/table-root.d.ts +94 -3
  172. package/dist/table/root/table-root.js +128 -5
  173. package/dist/table/row/table-row.d.ts +7 -0
  174. package/dist/table/row/table-row.js +9 -1
  175. package/dist/tabs/root/tabs-root.d.ts +6 -1
  176. package/dist/tabs/root/tabs-root.js +6 -2
  177. package/dist/tag/root/tag-root.d.ts +23 -1
  178. package/dist/tag/root/tag-root.js +16 -2
  179. package/dist/textarea/root/textarea-root.d.ts +4 -0
  180. package/dist/textarea/root/textarea-root.js +1 -1
  181. package/dist/time-input/root/time-input-root.d.ts +28 -2
  182. package/dist/time-input/root/time-input-root.js +46 -10
  183. package/dist/time-picker/root/time-picker-root.d.ts +101 -18
  184. package/dist/time-picker/root/time-picker-root.js +115 -16
  185. package/dist/toast/root/toast-root.d.ts +64 -1
  186. package/dist/toast/root/toast-root.js +147 -2
  187. package/dist/toggle/group/toggle-group.d.ts +38 -1
  188. package/dist/toggle/group/toggle-group.js +34 -8
  189. package/dist/toggle/root/toggle-root.d.ts +14 -10
  190. package/dist/toggle/root/toggle-root.js +3 -3
  191. package/dist/toolbar/root/toolbar-root.d.ts +55 -15
  192. package/dist/toolbar/root/toolbar-root.js +99 -16
  193. package/dist/tooltip/root/tooltip-root.d.ts +37 -4
  194. package/dist/tooltip/root/tooltip-root.js +48 -0
  195. package/dist/tour/root/tour-root.d.ts +34 -5
  196. package/dist/tour/root/tour-root.js +51 -14
  197. package/dist/tour/stop/tour-stop.d.ts +18 -1
  198. package/dist/tour/stop/tour-stop.js +25 -5
  199. package/dist/tree/item/tree-item.d.ts +21 -5
  200. package/dist/tree/item/tree-item.js +14 -4
  201. package/dist/tree/root/tree-root.d.ts +29 -5
  202. package/dist/tree/root/tree-root.js +64 -8
  203. package/dist/trend/root/trend-root.d.ts +23 -1
  204. package/dist/trend/root/trend-root.js +28 -7
  205. package/dist/video/root/keys.d.ts +5 -0
  206. package/dist/video/root/keys.js +13 -1
  207. package/dist/video/root/video-root.d.ts +19 -0
  208. package/dist/video/root/video-root.js +48 -4
  209. package/dist/video-playlist/menu/video-playlist-menu.d.ts +20 -2
  210. package/dist/video-playlist/menu/video-playlist-menu.js +27 -5
  211. package/dist/video-playlist/root/video-playlist-root.js +6 -3
  212. package/dist/virtual-list/root/virtual-list-root.d.ts +52 -4
  213. package/dist/virtual-list/root/virtual-list-root.js +103 -21
  214. package/lint.json +1 -1
  215. package/package.json +5 -5
  216. package/types/jsx.d.ts +4348 -965
  217. package/types/vue.d.ts +972 -232
@@ -1,5 +1,5 @@
1
1
  import { HelixElement } from '@fusion.dev/helix-core';
2
- import { type TemplateResult } from 'lit';
2
+ import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  declare const HelixHeadlessAccordionItem_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
4
4
  /**
5
5
  * @summary One collapsible section of an accordion.
@@ -8,6 +8,10 @@ declare const HelixHeadlessAccordionItem_base: (new (...args: any[]) => import("
8
8
  * Holds its own open state. Used alone, outside an accordion, it works as a
9
9
  * disclosure.
10
10
  *
11
+ * The trigger sits inside a heading, so a screen reader lists the section with
12
+ * the page's headings. The level is the item's `heading-level`, then the
13
+ * accordion's, then 3.
14
+ *
11
15
  * Content stays mounted when the item is closed, so it can animate and its text
12
16
  * is present in server-rendered HTML. Closed content has both `hidden` and
13
17
  * `inert`. `hidden` collapses it when no stylesheet is loaded. `inert` keeps it
@@ -24,12 +28,13 @@ declare const HelixHeadlessAccordionItem_base: (new (...args: any[]) => import("
24
28
  *
25
29
  * @event {HelixBeforeCloseEvent} helix-before-close - The section is about to close. Cancelable: cancel it to keep it open.
26
30
  * @event {HelixBeforeOpenEvent} helix-before-open - The section is about to open. Cancelable: cancel it to keep it closed.
27
- * @event {Event} toggle - The section opened or closed. The event carries `newState` and `oldState`.
31
+ * @event {ToggleEvent} toggle - The section opened or closed. The event carries `newState` and `oldState`.
28
32
  *
29
33
  * @csspart chevron - The open and close indicator.
30
34
  * @csspart container - The outer wrapper.
31
35
  * @csspart content - The region that holds the content.
32
36
  * @csspart content-inner - The clipping box inside the animated region.
37
+ * @csspart heading - The heading that holds the trigger.
33
38
  * @csspart trigger - The button that opens and closes the section, holding the summary.
34
39
  *
35
40
  * @cssstate open - While the item is open.
@@ -48,6 +53,24 @@ export declare class HelixHeadlessAccordionItem extends HelixHeadlessAccordionIt
48
53
  chevronPlacement?: 'auto' | 'start' | 'end' | 'none';
49
54
  /** The chevron, or nothing when suppressed. */
50
55
  private renderChevron;
56
+ /**
57
+ * Whether the section is disabled, so its trigger cannot be pressed or
58
+ * reached with the arrow keys. A script can still open and close it.
59
+ */
60
+ disabled: boolean;
61
+ /**
62
+ * @internal The accordion's `heading-level`, written by the accordion. A
63
+ * property, because an attribute written onto your element after it upgrades
64
+ * causes a hydration mismatch.
65
+ */
66
+ groupHeadingLevel: number | undefined;
67
+ /**
68
+ * The level of the heading around the trigger, from 1 to 6.
69
+ *
70
+ * Unset takes the accordion's `heading-level`, and 3 where that is unset
71
+ * too.
72
+ */
73
+ headingLevel?: number;
51
74
  /**
52
75
  * A group name. Opening an item closes the other items with the same name,
53
76
  * as `<details name>` does.
@@ -69,6 +92,8 @@ export declare class HelixHeadlessAccordionItem extends HelixHeadlessAccordionIt
69
92
  * absence. Caught by `hydration.dom.spec.ts`.
70
93
  */
71
94
  name?: string;
95
+ /** Whether the item was open at the last update, and `undefined` before one. */
96
+ private wasOpen;
72
97
  /**
73
98
  * The nearest accordion above this item, if there is one.
74
99
  *
@@ -95,6 +120,17 @@ export declare class HelixHeadlessAccordionItem extends HelixHeadlessAccordionIt
95
120
  * set collapsed after a listener refused it.
96
121
  */
97
122
  protected handleToggle: () => void;
123
+ /** Closes the other open items in this item's set, where it has one. */
124
+ private closePeers;
125
+ /**
126
+ * Closes the peers of an item opened any other way than its trigger: `open`
127
+ * set from a script, `requestOpen` or `toggleOpen`. The trigger already
128
+ * closed them, so for a press this finds nothing open.
129
+ *
130
+ * Not on the first update. What the markup opens is the accordion's to
131
+ * settle, once every item is there to compare.
132
+ */
133
+ protected updated(changed: PropertyValues): void;
98
134
  /**
99
135
  * Moves focus to this item's trigger.
100
136
  *
@@ -15,7 +15,13 @@ const TRIGGER_ID = 'trigger';
15
15
  let HelixHeadlessAccordionItem = class HelixHeadlessAccordionItem extends MixinControllerOpen(HelixElement) {
16
16
  constructor() {
17
17
  super(...arguments);
18
+ this.disabled = false;
19
+ this.groupHeadingLevel = undefined;
20
+ this.wasOpen = undefined;
18
21
  this.handleToggle = () => {
22
+ if (this.disabled) {
23
+ return;
24
+ }
19
25
  const next = !this.open;
20
26
  const mode = this.accordion?.mode;
21
27
  if (!next && mode === 'single') {
@@ -24,12 +30,8 @@ let HelixHeadlessAccordionItem = class HelixHeadlessAccordionItem extends MixinC
24
30
  if (!this.requestOpen(next)) {
25
31
  return;
26
32
  }
27
- if (next && (mode !== undefined || (this.name !== undefined && this.name !== ''))) {
28
- for (const peer of this.peers) {
29
- if (peer !== this && peer.open) {
30
- peer.requestOpen(false);
31
- }
32
- }
33
+ if (next) {
34
+ this.closePeers();
33
35
  }
34
36
  };
35
37
  }
@@ -57,25 +59,46 @@ let HelixHeadlessAccordionItem = class HelixHeadlessAccordionItem extends MixinC
57
59
  }
58
60
  return accordion.items;
59
61
  }
62
+ closePeers() {
63
+ if (this.accordion?.mode === undefined && (this.name === undefined || this.name === '')) {
64
+ return;
65
+ }
66
+ for (const peer of this.peers) {
67
+ if (peer !== this && peer.open) {
68
+ peer.requestOpen(false);
69
+ }
70
+ }
71
+ }
72
+ updated(changed) {
73
+ super.updated(changed);
74
+ if (this.wasOpen === false && this.open) {
75
+ this.closePeers();
76
+ }
77
+ this.wasOpen = this.open;
78
+ }
60
79
  focusTrigger() {
61
80
  this.renderRoot.querySelector(`#${TRIGGER_ID}`)?.focus();
62
81
  }
63
82
  render() {
64
83
  const open = this.open;
84
+ const level = Math.min(6, Math.max(1, this.headingLevel ?? this.groupHeadingLevel ?? 3));
65
85
  return html `
66
86
  <div part="container">
87
+ <div part="heading" role="heading" aria-level=${level}>
67
88
  <button
68
89
  id=${TRIGGER_ID}
69
90
  aria-controls=${CONTENT_ID}
70
91
  aria-expanded=${open ? 'true' : 'false'}
71
92
  part="trigger"
72
93
  type="button"
94
+ ?disabled=${this.disabled}
73
95
  @click=${this.handleToggle}
74
96
  >
75
97
  ${this.chevronPlacement === 'start' ? this.renderChevron() : nothing}
76
98
  <slot name="summary"></slot>
77
99
  ${this.chevronPlacement === 'start' ? nothing : this.renderChevron()}
78
100
  </button>
101
+ </div>
79
102
  <div
80
103
  id=${CONTENT_ID}
81
104
  aria-labelledby=${TRIGGER_ID}
@@ -95,6 +118,15 @@ let HelixHeadlessAccordionItem = class HelixHeadlessAccordionItem extends MixinC
95
118
  __decorate([
96
119
  property({ attribute: 'chevron-placement', reflect: true })
97
120
  ], HelixHeadlessAccordionItem.prototype, "chevronPlacement", void 0);
121
+ __decorate([
122
+ property({ reflect: true, type: Boolean })
123
+ ], HelixHeadlessAccordionItem.prototype, "disabled", void 0);
124
+ __decorate([
125
+ property({ attribute: false })
126
+ ], HelixHeadlessAccordionItem.prototype, "groupHeadingLevel", void 0);
127
+ __decorate([
128
+ property({ attribute: 'heading-level', type: Number })
129
+ ], HelixHeadlessAccordionItem.prototype, "headingLevel", void 0);
98
130
  __decorate([
99
131
  property({ reflect: true })
100
132
  ], HelixHeadlessAccordionItem.prototype, "name", void 0);
@@ -1,5 +1,5 @@
1
1
  import { HelixElement, KeyboardNavigationController } from '@fusion.dev/helix-core';
2
- import { type TemplateResult } from 'lit';
2
+ import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  import { HelixHeadlessAccordionItem } from '../item/accordion-item.js';
4
4
  /**
5
5
  * @summary A group of collapsible sections.
@@ -20,6 +20,10 @@ import { HelixHeadlessAccordionItem } from '../item/accordion-item.js';
20
20
  * The keyboard listener belongs to the controller and is attached on connect,
21
21
  * so it does not run on the server.
22
22
  *
23
+ * Each trigger sits inside a heading, level 3 unless `heading-level` says
24
+ * otherwise, so the sections are in a screen reader's list of headings. Set the
25
+ * level that fits the page's outline.
26
+ *
23
27
  * @since 0.1.0
24
28
  * @status stable
25
29
  * @category layout
@@ -29,6 +33,12 @@ import { HelixHeadlessAccordionItem } from '../item/accordion-item.js';
29
33
  * @slot - The accordion's items.
30
34
  */
31
35
  export declare class HelixHeadlessAccordion extends HelixElement {
36
+ /**
37
+ * The level of the heading around each trigger, from 1 to 6.
38
+ *
39
+ * Unset is 3. An item's own `heading-level` wins over this.
40
+ */
41
+ headingLevel?: number;
32
42
  /**
33
43
  * How many sections may be open at once.
34
44
  *
@@ -36,6 +46,11 @@ export declare class HelixHeadlessAccordion extends HelixElement {
36
46
  * no attribute on the host. `single` keeps exactly one open and refuses to
37
47
  * close the last; `single-collapsible` keeps at most one and lets it close.
38
48
  *
49
+ * Held however a section opens: a press, `open` set from a script,
50
+ * `toggleOpen()`, or `default-open` in the markup. Opening one closes the
51
+ * others, and on first render only the first section written open stays
52
+ * open. Under `single` the first section opens when none is written open.
53
+ *
39
54
  * A union rather than a mode plus a `collapsible` boolean, which would make
40
55
  * `collapsible` alongside `multiple` expressible and meaningless.
41
56
  */
@@ -43,6 +58,13 @@ export declare class HelixHeadlessAccordion extends HelixElement {
43
58
  /** The accordion's own items, in the order they are written. Browser only. */
44
59
  get items(): HelixHeadlessAccordionItem[];
45
60
  protected readonly keys: KeyboardNavigationController;
61
+ /**
62
+ * Holds the mode over the sections as written, and passes the heading level
63
+ * down. A section opened later closes its peers itself.
64
+ */
65
+ private settle;
66
+ protected firstUpdated(changed: PropertyValues): void;
67
+ protected updated(changed: PropertyValues): void;
46
68
  protected render(): TemplateResult;
47
69
  }
48
70
  declare global {
@@ -4,13 +4,13 @@ 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 { customElement, HelixElement, KeyboardNavigationController, } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, KeyboardNavigationController, whenHydrated, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { HelixHeadlessAccordionItem } from '../item/accordion-item.js';
11
11
  const navigable = (item) => ({
12
12
  contains: (node) => node === item,
13
- disabled: false,
13
+ disabled: item.disabled,
14
14
  focusItem: () => item.focusTrigger(),
15
15
  select: () => { },
16
16
  selected: false,
@@ -27,10 +27,46 @@ let HelixHeadlessAccordion = class HelixHeadlessAccordion extends HelixElement {
27
27
  get items() {
28
28
  return [...this.children].filter((child) => child instanceof HelixHeadlessAccordionItem);
29
29
  }
30
+ settle() {
31
+ const items = this.items;
32
+ for (const item of items) {
33
+ whenHydrated(item, () => {
34
+ item.groupHeadingLevel = this.headingLevel;
35
+ });
36
+ }
37
+ if (this.mode === undefined) {
38
+ return;
39
+ }
40
+ const open = items.filter((item) => item.open);
41
+ for (const extra of open.slice(1)) {
42
+ whenHydrated(extra, () => {
43
+ extra.open = false;
44
+ });
45
+ }
46
+ const first = items[0];
47
+ if (this.mode === 'single' && open.length === 0 && first) {
48
+ whenHydrated(first, () => {
49
+ first.open = true;
50
+ });
51
+ }
52
+ }
53
+ firstUpdated(changed) {
54
+ super.firstUpdated(changed);
55
+ this.settle();
56
+ }
57
+ updated(changed) {
58
+ super.updated(changed);
59
+ if (this.hasUpdated && (changed.has('mode') || changed.has('headingLevel'))) {
60
+ this.settle();
61
+ }
62
+ }
30
63
  render() {
31
- return html `<slot></slot>`;
64
+ return html `<slot @slotchange=${() => this.settle()}></slot>`;
32
65
  }
33
66
  };
67
+ __decorate([
68
+ property({ attribute: 'heading-level', type: Number })
69
+ ], HelixHeadlessAccordion.prototype, "headingLevel", void 0);
34
70
  __decorate([
35
71
  property({ reflect: true })
36
72
  ], HelixHeadlessAccordion.prototype, "mode", void 0);
@@ -16,12 +16,17 @@ declare const HelixHeadlessAnnouncement_base: (new (...args: any[]) => import("@
16
16
  *
17
17
  * The live region is its own part. When the announcement appears after the page
18
18
  * has loaded, the message is written into the region one frame later, so screen
19
- * readers read it reliably. When the announcement is in the served HTML, the
20
- * region is filled at once and nothing is read out.
19
+ * readers read it reliably, and the region is emptied seven seconds later, so
20
+ * the message is not in the page twice. When the announcement is in the served
21
+ * HTML, the region stays empty and nothing is read out.
21
22
  *
22
23
  * The dismiss button fires `helix-dismiss`, and the announcement stays in the
23
24
  * document, so remove it in your listener. The event is cancelable, and
24
- * `dismiss()` returns `false` when a listener calls `preventDefault()`.
25
+ * `dismiss()` returns `false` when a listener calls `preventDefault()`. When
26
+ * your listener removes an announcement whose dismiss button has focus, focus
27
+ * moves to the dismiss button of the announcement beside it. With no other
28
+ * announcement beside it, move focus yourself, for example to the page's main
29
+ * heading.
25
30
  *
26
31
  * @since 0.1.0
27
32
  * @status stable
@@ -39,7 +44,7 @@ declare const HelixHeadlessAnnouncement_base: (new (...args: any[]) => import("@
39
44
  * @csspart content - The region that holds the message.
40
45
  * @csspart dismiss - The button that dismisses the announcement.
41
46
  * @csspart dismiss-mark - The icon on the dismiss button, shown when the `icon` slot is empty.
42
- * @csspart live - A copy of the message in a live region, read by screen readers when the announcement appears after the page has loaded. Visually hidden in the styled tier.
47
+ * @csspart live - A copy of the message in a live region, read by screen readers when the announcement appears after the page has loaded, and emptied seven seconds later. Visually hidden in the styled tier.
43
48
  */
44
49
  export declare class HelixHeadlessAnnouncement extends HelixHeadlessAnnouncement_base {
45
50
  /**
@@ -55,9 +60,8 @@ export declare class HelixHeadlessAnnouncement extends HelixHeadlessAnnouncement
55
60
  * components saying what a thing means, in three vocabularies, one of which
56
61
  * described itself as a fourth.
57
62
  *
58
- * `accent` is the second emphasis, a rotation off the brand seed rather than a
59
- * color of its own, so it stays in relation when somebody rethemes. `primary`
60
- * is the seed itself.
63
+ * `accent` is the second emphasis, the second color a theme names, and the
64
+ * brand itself until one is named. `primary` is the brand.
61
65
  *
62
66
  * **`surface` was never a color and has moved to `separation`**, which is the
63
67
  * axis that owns where a box sits relative to the page. It said the band rode
@@ -34,7 +34,7 @@ let HelixHeadlessAnnouncement = class HelixHeadlessAnnouncement extends MixinDis
34
34
  render() {
35
35
  return html `<div part="content"><slot @slotchange=${() => this.arrival.say()}></slot></div>
36
36
  <span part="live" role="status">${this.arrival.said}</span>
37
- <div part="actions"><slot name="actions"></slot></div>
37
+ <div part="actions"><slot name="actions" @slotchange=${() => this.markEmpty()}></slot></div>
38
38
  ${this.dismissible
39
39
  ? html `<button
40
40
  part="dismiss"
@@ -16,6 +16,9 @@ import { type PropertyValues, type TemplateResult } from 'lit';
16
16
  * `ElementInternals.states` is not in the DOM, so hydration has nothing to
17
17
  * compare. Select the state with `:state(scrolled)`.
18
18
  *
19
+ * The page is the window unless `scroller` names the element that scrolls, for
20
+ * an app shell whose content scrolls inside a container.
21
+ *
19
22
  * @since 0.1.0
20
23
  * @status stable
21
24
  * @category navigation
@@ -56,7 +59,24 @@ export declare class HelixHeadlessAppHeader extends HelixElement {
56
59
  * the property carries the part's name, `content`.
57
60
  */
58
61
  contentAlign?: 'start';
62
+ /**
63
+ * The id of the element whose scrolling lifts the bar, for an app shell that
64
+ * scrolls a container instead of the window. Unset is the window.
65
+ */
66
+ scroller?: string;
59
67
  private scrolled;
68
+ /** What scroll is listened to now, so a change of `scroller` moves it. */
69
+ private source;
70
+ /** The element `scroller` names, or the window. */
71
+ private findSource;
72
+ /**
73
+ * Listens to the scroll source that `scroller` names now.
74
+ *
75
+ * A named element that is not in the document yet, such as a `<main>` the
76
+ * parser has not reached when the header connects, is looked for once more
77
+ * when the document has loaded.
78
+ */
79
+ private listen;
60
80
  /**
61
81
  * A band rather than a line, so the fill cannot flicker at the boundary.
62
82
  *
@@ -79,6 +99,7 @@ export declare class HelixHeadlessAppHeader extends HelixElement {
79
99
  protected willUpdate(changed: PropertyValues): void;
80
100
  connectedCallback(): void;
81
101
  disconnectedCallback(): void;
102
+ protected updated(changed: PropertyValues): void;
82
103
  protected render(): TemplateResult;
83
104
  }
84
105
  declare global {
@@ -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 { customElement, HelixElement, HelixScrolledEvent, hostAria } from '@fusion.dev/helix-core';
7
+ import { customElement, elementById, HelixElement, HelixScrolledEvent, hostAria, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
@@ -12,7 +12,8 @@ let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
12
12
  super(...arguments);
13
13
  this.scrolled = false;
14
14
  this.handleScroll = () => {
15
- const y = window.scrollY;
15
+ const source = this.source ?? window;
16
+ const y = source instanceof Element ? source.scrollTop : window.scrollY;
16
17
  const next = this.scrolled ? y > 2 : y > 8;
17
18
  if (next === this.scrolled) {
18
19
  return;
@@ -22,18 +23,40 @@ let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
22
23
  this.dispatchEvent(new HelixScrolledEvent({ scrolled: next }));
23
24
  };
24
25
  }
26
+ findSource() {
27
+ return (this.scroller ? elementById(this, this.scroller) : null) ?? window;
28
+ }
29
+ listen() {
30
+ const next = this.findSource();
31
+ if (next === window && this.scroller && document.readyState === 'loading') {
32
+ document.addEventListener('DOMContentLoaded', () => this.listen(), { once: true });
33
+ }
34
+ if (next === this.source) {
35
+ return;
36
+ }
37
+ this.source?.removeEventListener('scroll', this.handleScroll);
38
+ this.source = next;
39
+ next.addEventListener('scroll', this.handleScroll, { passive: true });
40
+ this.handleScroll();
41
+ }
25
42
  willUpdate(changed) {
26
43
  super.willUpdate(changed);
27
44
  hostAria(this.internals, { role: 'banner' });
28
45
  }
29
46
  connectedCallback() {
30
47
  super.connectedCallback();
31
- window.addEventListener('scroll', this.handleScroll, { passive: true });
32
- this.handleScroll();
48
+ this.listen();
33
49
  }
34
50
  disconnectedCallback() {
35
51
  super.disconnectedCallback();
36
- window.removeEventListener('scroll', this.handleScroll);
52
+ this.source?.removeEventListener('scroll', this.handleScroll);
53
+ this.source = undefined;
54
+ }
55
+ updated(changed) {
56
+ super.updated(changed);
57
+ if (changed.has('scroller') && this.isConnected) {
58
+ this.listen();
59
+ }
37
60
  }
38
61
  render() {
39
62
  return html `
@@ -48,6 +71,9 @@ let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
48
71
  __decorate([
49
72
  property({ attribute: 'content-align', reflect: true })
50
73
  ], HelixHeadlessAppHeader.prototype, "contentAlign", void 0);
74
+ __decorate([
75
+ property()
76
+ ], HelixHeadlessAppHeader.prototype, "scroller", void 0);
51
77
  HelixHeadlessAppHeader = __decorate([
52
78
  customElement('helix-headless-app-header')
53
79
  ], HelixHeadlessAppHeader);
@@ -8,7 +8,18 @@ import { CHART_PLOT, customElement, drawTooltip, formatNumbers, HelixElement, li
8
8
  import { html, nothing, svg } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import '../../tooltip/index.js';
11
- const percent = new Intl.NumberFormat(undefined, { maximumFractionDigits: 0, style: 'percent' });
11
+ const PERCENTS = new Map();
12
+ const percentIn = (locale) => {
13
+ let found = PERCENTS.get(locale);
14
+ if (!found) {
15
+ found = new Intl.NumberFormat(locale || undefined, {
16
+ maximumFractionDigits: 0,
17
+ style: 'percent',
18
+ });
19
+ PERCENTS.set(locale, found);
20
+ }
21
+ return found;
22
+ };
12
23
  let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSource(HelixElement) {
13
24
  constructor() {
14
25
  super(...arguments);
@@ -150,15 +161,15 @@ let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSour
150
161
  renderSummary(stack, rows) {
151
162
  const last = rows.length - 1;
152
163
  const total = stack.totals[last] ?? 0;
153
- const format = formatNumbers('');
154
- const shares = stack.names.map((name, depth) => `${name} ${percent.format(total > 0 ? (stack.values[depth]?.[last] ?? 0) / total : 0)}`);
164
+ const format = formatNumbers(this.locale);
165
+ const shares = stack.names.map((name, depth) => `${name} ${percentIn(this.locale).format(total > 0 ? (stack.values[depth]?.[last] ?? 0) / total : 0)}`);
155
166
  const named = this.named ? `${this.named}, ` : '';
156
167
  return html `<p part="summary">
157
168
  ${named}${rows[last]?.label ?? ''}: ${this.totalLabel} ${format(total)}; ${shares.join(', ')}.
158
169
  </p>`;
159
170
  }
160
171
  tallied(rows, stack) {
161
- const format = formatNumbers('');
172
+ const format = formatNumbers(this.locale);
162
173
  return rows.map((row, index) => {
163
174
  const total = stack.totals[index] ?? 0;
164
175
  return {
@@ -167,7 +178,7 @@ let HelixHeadlessAreaChart = class HelixHeadlessAreaChart extends MixinChartSour
167
178
  ...row.cells.map((cell, column) => this.share && cell.value !== undefined && stack.columns.includes(column) && total > 0
168
179
  ? {
169
180
  ...cell,
170
- text: `${cell.text} (${percent.format(Math.max(0, cell.value) / total)})`,
181
+ text: `${cell.text} (${percentIn(this.locale).format(Math.max(0, cell.value) / total)})`,
171
182
  }
172
183
  : cell),
173
184
  { name: this.totalLabel, text: format(total) },
@@ -20,8 +20,10 @@ export declare const FOLDS_UP_TO = 12;
20
20
  * There is no slot for it, because the number has to match which avatars are
21
21
  * hidden.
22
22
  *
23
- * Avatars past `max` stay in the DOM and are hidden. Changing `max` re-renders
24
- * nothing, and the accessible name is still built from all of them.
23
+ * Avatars past `max` stay in the DOM and are hidden. The group writes `hidden`
24
+ * on each of them, so they are hidden on the headless tier as well, and the
25
+ * count is read out as `overflow-label`, such as "3 more", so a screen reader
26
+ * hears that there are more people than the ones it can reach.
25
27
  *
26
28
  * @since 0.1.0
27
29
  * @status stable
@@ -30,7 +32,7 @@ export declare const FOLDS_UP_TO = 12;
30
32
  *
31
33
  * @slot - The avatars, in the order they should be read.
32
34
  *
33
- * @csspart overflow - The count of avatars past `max`.
35
+ * @csspart overflow - The count of avatars past `max`, an image named by `overflow-label`.
34
36
  */
35
37
  export declare class HelixHeadlessAvatarGroup extends HelixElement {
36
38
  /**
@@ -56,6 +58,15 @@ export declare class HelixHeadlessAvatarGroup extends HelixElement {
56
58
  * never asked for a limit, including on the server.
57
59
  */
58
60
  max?: number;
61
+ /**
62
+ * What a screen reader is told the count is, with `{count}` for the number.
63
+ *
64
+ * The count shows "+3", which reads as "plus three" and says nothing about
65
+ * what was counted.
66
+ */
67
+ overflowLabel: string;
68
+ /** The avatars this group hid, so it unhides only those. */
69
+ private readonly folded;
59
70
  /** The avatars, in light-DOM order. Empty on the server, which has no tree. */
60
71
  get avatars(): HTMLElement[];
61
72
  /**
@@ -72,6 +83,16 @@ export declare class HelixHeadlessAvatarGroup extends HelixElement {
72
83
  protected willUpdate(changed: PropertyValues): void;
73
84
  protected firstUpdated(changed: PropertyValues): void;
74
85
  protected updated(changed: PropertyValues): void;
86
+ /**
87
+ * Hides the avatars past `max`, with the platform's own `hidden`.
88
+ *
89
+ * **State in markup, so the headless tier folds too.** The fold was a styled
90
+ * rule alone, and on the headless tier every avatar was drawn beside a "+3".
91
+ * The styled rule stays, because it is what folds a server's markup before
92
+ * this runs. Written through `whenHydrated`, so an avatar's hydrating render
93
+ * is the server's, and undone only on the avatars this group hid.
94
+ */
95
+ private fold;
75
96
  protected render(): TemplateResult;
76
97
  }
77
98
  declare global {
@@ -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 { customElement, HelixElement, hostAria, reportRuntime } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, hostAria, reportRuntime, whenHydrated, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  export const FOLDS_UP_TO = 12;
@@ -12,6 +12,8 @@ let HelixHeadlessAvatarGroup = class HelixHeadlessAvatarGroup extends HelixEleme
12
12
  constructor() {
13
13
  super(...arguments);
14
14
  this.label = '';
15
+ this.overflowLabel = '{count} more';
16
+ this.folded = new WeakSet();
15
17
  this.warned = false;
16
18
  }
17
19
  get avatars() {
@@ -32,6 +34,7 @@ let HelixHeadlessAvatarGroup = class HelixHeadlessAvatarGroup extends HelixEleme
32
34
  }
33
35
  updated(changed) {
34
36
  super.updated(changed);
37
+ this.fold();
35
38
  const limit = this.max ?? 0;
36
39
  if (limit > FOLDS_UP_TO && this.avatars.length > limit && !this.warned) {
37
40
  this.warned = true;
@@ -44,10 +47,36 @@ let HelixHeadlessAvatarGroup = class HelixHeadlessAvatarGroup extends HelixEleme
44
47
  });
45
48
  }
46
49
  }
50
+ fold() {
51
+ const limit = this.max ?? 0;
52
+ const limited = limit > 0 && limit <= FOLDS_UP_TO;
53
+ for (const [index, avatar] of this.avatars.entries()) {
54
+ const past = limited && index >= limit;
55
+ if (past && !avatar.hidden) {
56
+ this.folded.add(avatar);
57
+ whenHydrated(avatar, () => {
58
+ avatar.hidden = true;
59
+ });
60
+ }
61
+ else if (!past && this.folded.has(avatar)) {
62
+ this.folded.delete(avatar);
63
+ whenHydrated(avatar, () => {
64
+ avatar.hidden = false;
65
+ });
66
+ }
67
+ }
68
+ }
47
69
  render() {
48
70
  const past = this.overflow;
49
71
  return html `<slot @slotchange=${() => this.requestUpdate()}></slot>
50
- ${past > 0 ? html `<span part="overflow" aria-hidden="true">+${past}</span>` : ''}`;
72
+ ${past > 0
73
+ ? html `<span
74
+ part="overflow"
75
+ role="img"
76
+ aria-label=${this.overflowLabel.replace('{count}', String(past))}
77
+ >+${past}</span
78
+ >`
79
+ : ''}`;
51
80
  }
52
81
  };
53
82
  __decorate([
@@ -56,6 +85,9 @@ __decorate([
56
85
  __decorate([
57
86
  property({ reflect: true, type: Number })
58
87
  ], HelixHeadlessAvatarGroup.prototype, "max", void 0);
88
+ __decorate([
89
+ property({ attribute: 'overflow-label' })
90
+ ], HelixHeadlessAvatarGroup.prototype, "overflowLabel", void 0);
59
91
  HelixHeadlessAvatarGroup = __decorate([
60
92
  customElement('helix-headless-avatar-group')
61
93
  ], HelixHeadlessAvatarGroup);
@@ -46,7 +46,9 @@ export declare class HelixHeadlessAvatar extends HelixElement {
46
46
  src: string;
47
47
  /**
48
48
  * The image's state: `loading` while it is on its way, `loaded` once it
49
- * arrives, or `error` when it fails and the initials replace it.
49
+ * arrives, or `error` when it fails and the initials replace it. An avatar
50
+ * with no `src` has no image to wait for, and reports `error` as well, since
51
+ * the initials are what it shows.
50
52
  *
51
53
  * Read-only to a consumer: it is a report rather than a setting, and the same
52
54
  * report `Image` gives, for the same reason. `load` and `error` fire once, so