@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
@@ -4,10 +4,11 @@ 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, fillTemplate, formatNumbers, HelixElement, HelixNavigateEvent, hostAria, requestChange, } from '@fusion.dev/helix-core';
7
+ import { customElement, deepActiveElement, fillTemplate, formatNumbers, HelixActivateEvent, HelixElement, HelixNavigateEvent, hostAria, requestChange, } from '@fusion.dev/helix-core';
8
8
  import { caretDoubleLeft, caretDoubleRight, caretLeft, caretRight, } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
+ import { repeat } from 'lit/directives/repeat.js';
11
12
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
13
  const fill = (template, page, pages, format = String) => fillTemplate(template, { page: format(page), pages: format(pages) });
13
14
  let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement {
@@ -34,9 +35,15 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
34
35
  this.announcement = '';
35
36
  this.announced = undefined;
36
37
  this.pageState = undefined;
37
- this.go = (next, event) => {
38
+ this.used = undefined;
39
+ this.go = (next, event, gap = false) => {
40
+ const control = event.currentTarget;
38
41
  if (!this.request(next)) {
39
42
  event.preventDefault();
43
+ return;
44
+ }
45
+ if (control && deepActiveElement() === control) {
46
+ this.used = { control, gap };
40
47
  }
41
48
  };
42
49
  }
@@ -104,15 +111,43 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
104
111
  }
105
112
  return requestChange({
106
113
  apply: (page) => {
107
- const previous = this.page;
108
- this.pageState = page;
109
- this.requestUpdate('page', previous);
114
+ if (this.href) {
115
+ setTimeout(() => this.applyPage(page));
116
+ return;
117
+ }
118
+ this.applyPage(page);
110
119
  },
111
- event: (page) => new HelixNavigateEvent({ value: String(page) }),
120
+ event: (page) => new HelixNavigateEvent({ page, value: String(page) }),
112
121
  host: this,
113
122
  next,
114
123
  });
115
124
  }
125
+ applyPage(page) {
126
+ const previous = this.page;
127
+ this.pageState = page;
128
+ this.requestUpdate('page', previous);
129
+ this.dispatchEvent(new HelixActivateEvent({ value: String(page) }));
130
+ }
131
+ restoreFocus() {
132
+ const used = this.used;
133
+ this.used = undefined;
134
+ if (!used) {
135
+ return;
136
+ }
137
+ const { control, gap } = used;
138
+ const usable = (element) => element instanceof HTMLElement &&
139
+ element.isConnected &&
140
+ !element.disabled &&
141
+ element.getAttribute('aria-disabled') !== 'true';
142
+ if (!gap && usable(control)) {
143
+ return;
144
+ }
145
+ const target = [
146
+ this.renderRoot.querySelector('[part~="page-current"]'),
147
+ ...['previous', 'next', 'first', 'last'].map((name) => this.renderRoot.querySelector(`[part="${name}"]`)),
148
+ ].find(usable);
149
+ target?.focus();
150
+ }
116
151
  willUpdate(changed) {
117
152
  super.willUpdate(changed);
118
153
  hostAria(this.internals, { label: this.label, role: 'navigation' });
@@ -120,6 +155,7 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
120
155
  }
121
156
  updated(changed) {
122
157
  super.updated(changed);
158
+ this.restoreFocus();
123
159
  if (this.announced === undefined) {
124
160
  this.announced = this.page;
125
161
  return;
@@ -163,14 +199,14 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
163
199
  part="gap"
164
200
  href=${fill(this.href, to, this.count)}
165
201
  aria-label=${name}
166
- @click=${(event) => this.go(to, event)}
202
+ @click=${(event) => this.go(to, event, true)}
167
203
  >&hellip;</a
168
204
  >`
169
205
  : html `<button
170
206
  part="gap"
171
207
  type="button"
172
208
  aria-label=${name}
173
- @click=${(event) => this.go(to, event)}
209
+ @click=${(event) => this.go(to, event, true)}
174
210
  >
175
211
  &hellip;
176
212
  </button>`}
@@ -253,7 +289,11 @@ let HelixHeadlessPagination = class HelixHeadlessPagination extends HelixElement
253
289
  }
254
290
  return html `${this.ends ? this.renderFirst() : nothing}${this.renderPrevious()}
255
291
  <ol part="list" role="list">
256
- ${this.slots.map((slot) => typeof slot === 'number' ? this.renderPage(slot) : this.renderGap(slot))}
292
+ ${repeat(this.slots, (slot) => typeof slot === 'number'
293
+ ? slot
294
+ : slot.from < this.current
295
+ ? 'gap-before'
296
+ : 'gap-after', (slot) => (typeof slot === 'number' ? this.renderPage(slot) : this.renderGap(slot)))}
257
297
  </ol>
258
298
  ${this.renderNext()}${this.ends ? this.renderLast() : nothing}${this.renderLive()}`;
259
299
  }
@@ -23,16 +23,19 @@ import '../../select/index.js';
23
23
  * `ElementInternals.states`.
24
24
  *
25
25
  * `value` is the current size, and `default-value` is the size from the markup.
26
- * Changing it dispatches `helix-before-change`. Re-querying and returning to
27
- * page one is up to you, because only you know whether the current position
28
- * should be kept.
26
+ * Changing it dispatches `helix-before-change`, and then `input` and `change`
27
+ * once the size has moved, as a native `<select>` sends them together.
28
+ * Re-querying and returning to page one is up to you, because only you know
29
+ * whether the current position should be kept.
29
30
  *
30
31
  * @since 0.1.0
31
32
  * @status stable
32
33
  * @aliases Page size, Rows per page
33
34
  * @documentation https://fusion.dev/helix/components/pagination
34
35
  *
36
+ * @event {Event} change - Fired after the size changes.
35
37
  * @event {HelixBeforeChangeEvent} helix-before-change - Fired before the size changes, and cancelable. Canceling it is the only way to stop a size change.
38
+ * @event {Event} input - Fired after the size changes, with `change`.
36
39
  *
37
40
  * @csspart control - The select.
38
41
  * @csspart control-feedback - The message that says what is wrong with the value.
@@ -105,6 +108,13 @@ export declare class HelixHeadlessPaginationSize extends HelixElement {
105
108
  /** The one in force, which is the first offered where nothing named one. */
106
109
  private get current();
107
110
  private readonly handleChange;
111
+ /**
112
+ * The select's own `input` and `change`, stopped so this element is the one
113
+ * voice for a size. `input` is composed, so left alone it crossed this
114
+ * shadow boundary and arrived looking like this element's, with no `change`
115
+ * after it, and a refused size still sent it.
116
+ */
117
+ private readonly stopNative;
108
118
  protected willUpdate(changed: PropertyValues): void;
109
119
  /**
110
120
  * The control itself, which is the whole of this part, behind a seam the styled tier can move.
@@ -29,6 +29,8 @@ let HelixHeadlessPaginationSize = class HelixHeadlessPaginationSize extends Heli
29
29
  const previous = this.value;
30
30
  this.valueState = size;
31
31
  this.requestUpdate('value', previous);
32
+ this.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
33
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: false }));
32
34
  },
33
35
  event: (size) => new HelixBeforeChangeEvent({ value: String(size) }),
34
36
  host: this,
@@ -41,6 +43,9 @@ let HelixHeadlessPaginationSize = class HelixHeadlessPaginationSize extends Heli
41
43
  }
42
44
  });
43
45
  };
46
+ this.stopNative = (event) => {
47
+ event.stopPropagation();
48
+ };
44
49
  }
45
50
  get value() {
46
51
  return this.valueState ?? this.defaultValue;
@@ -83,6 +88,8 @@ let HelixHeadlessPaginationSize = class HelixHeadlessPaginationSize extends Heli
83
88
  label=${this.label}
84
89
  .value=${String(this.current)}
85
90
  @helix-before-change=${this.handleChange}
91
+ @input=${this.stopNative}
92
+ @change=${this.stopNative}
86
93
  >
87
94
  ${this.options.map((size) => html `<${option}
88
95
  part="option"
@@ -33,6 +33,10 @@ declare const HelixHeadlessPinInput_base: (new (...args: any[]) => import("@fusi
33
33
  * @aliases PinField, OTP, One-time code, Verification code, InputPin
34
34
  * @documentation https://fusion.dev/helix/components/pin-input
35
35
  *
36
+ * @slot feedback - The message that says what is wrong, for content richer than a string. It shows while there is a message.
37
+ * @slot hint - Help under the control, for content richer than the `hint` text.
38
+ * @slot label - The label, for content richer than the `label` text.
39
+ *
36
40
  * @event {Event} change - The code was filled, or an edited code lost focus.
37
41
  * @event {HelixBeforeChangeEvent} helix-before-change - The code is about to change. Cancelable.
38
42
  * @event {HelixCompleteEvent} helix-complete - The last box was filled.
@@ -150,6 +154,9 @@ export declare class HelixHeadlessPinInput extends HelixHeadlessPinInput_base {
150
154
  * Backspace in an empty box goes back and clears the one it lands in, which
151
155
  * is what makes a code correctable without the reader hunting for the box
152
156
  * they meant. In a full box it does what it always does and is left alone.
157
+ *
158
+ * Home goes to the first box and End to the end of the code, the box after
159
+ * its last character, which is where the caret goes in a single text field.
153
160
  */
154
161
  private readonly handleKeydown;
155
162
  /**
@@ -272,6 +279,15 @@ export declare class HelixHeadlessPinInput extends HelixHeadlessPinInput_base {
272
279
  clear(): void;
273
280
  protected firstUpdated(changed: PropertyValues): void;
274
281
  protected updated(changed: PropertyValues): void;
282
+ /**
283
+ * Enter submits the form, as it does in a native text field.
284
+ *
285
+ * On `keypress`, where the platform runs its own implicit submission, so a
286
+ * consumer who prevents the `keydown` for Enter stops this too.
287
+ */
288
+ private readonly handleKeypress;
289
+ /** @internal A single line of text counts against implicit submission. */
290
+ protected get blocksImplicitSubmission(): boolean;
275
291
  protected render(): TemplateResult;
276
292
  }
277
293
  declare global {
@@ -11,7 +11,7 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
11
11
  constructor() {
12
12
  super(...arguments);
13
13
  this.aria = new AriaForwardController(this, {
14
- target: () => this.partNamed('cell'),
14
+ target: () => this.partNamed('field'),
15
15
  });
16
16
  this.announcesInput = true;
17
17
  this.autocomplete = 'one-time-code';
@@ -44,7 +44,7 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
44
44
  this.handlePaste = (event) => {
45
45
  const cell = event.target;
46
46
  const at = this.cells.indexOf(cell);
47
- if (at === -1) {
47
+ if (at === -1 || this.isDisabled || this.readOnly) {
48
48
  return;
49
49
  }
50
50
  event.preventDefault();
@@ -56,7 +56,12 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
56
56
  if (at === -1 || this.isDisabled || this.readOnly) {
57
57
  return;
58
58
  }
59
- const to = at + (event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0);
59
+ const step = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0;
60
+ const to = event.key === 'Home'
61
+ ? 0
62
+ : event.key === 'End'
63
+ ? Math.min(this.value.length, this.count - 1)
64
+ : at + step;
60
65
  if (event.key === 'Backspace' && cell.value === '' && at > 0) {
61
66
  const characters = this.characters;
62
67
  characters[at - 1] = '';
@@ -95,6 +100,11 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
95
100
  this.within = false;
96
101
  this.commit();
97
102
  };
103
+ this.handleKeypress = (event) => {
104
+ if (event.key === 'Enter' && !event.defaultPrevented) {
105
+ this.submitImplicitly();
106
+ }
107
+ };
98
108
  }
99
109
  static { this.shadowRootOptions = {
100
110
  ...HelixFormElement.shadowRootOptions,
@@ -131,12 +141,12 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
131
141
  }
132
142
  const next = characters.join('');
133
143
  const filled = Math.min(this.count - 1, start + cleaned.length);
134
- this.requestValue(next);
144
+ const applied = this.requestValue(next);
135
145
  this.updateComplete.then(() => {
136
146
  this.paint();
137
147
  this.cells[filled]?.focus();
138
148
  this.cells[filled]?.select();
139
- if (next.length === this.count) {
149
+ if (applied && this.value === next && next.length === this.count) {
140
150
  this.dispatchEvent(new HelixCompleteEvent({ value: next }));
141
151
  this.commit();
142
152
  }
@@ -198,6 +208,9 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
198
208
  this.paint();
199
209
  this.sync();
200
210
  }
211
+ get blocksImplicitSubmission() {
212
+ return true;
213
+ }
201
214
  render() {
202
215
  const characters = this.characters;
203
216
  return html `${this.renderGroupLabel()}
@@ -224,6 +237,7 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
224
237
  @focus=${this.handleFocus}
225
238
  @input=${this.handleInput}
226
239
  @keydown=${this.handleKeydown}
240
+ @keypress=${this.handleKeypress}
227
241
  @paste=${this.handlePaste}
228
242
  />`)}
229
243
  </div>
@@ -1,4 +1,4 @@
1
- import { CommandController, HelixElement, type Placement } from '@fusion.dev/helix-core';
1
+ import { AriaForwardController, CommandController, HelixElement, type Placement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  declare const HelixHeadlessPopover_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
4
4
  /**
@@ -21,6 +21,10 @@ declare const HelixHeadlessPopover_base: (new (...args: any[]) => import("@fusio
21
21
  * commands work as they do on `Dialog` and `Drawer`, so a button anywhere on
22
22
  * the page can open the popover with no script.
23
23
  *
24
+ * It closes on Escape, on a press outside it, and when focus moves out of the
25
+ * trigger and the panel to somewhere else on the page. It does not trap focus.
26
+ * An `aria-labelledby` on the host names the panel in place of `label`.
27
+ *
24
28
  * @since 0.1.0
25
29
  * @status stable
26
30
  * @category overlays
@@ -32,7 +36,7 @@ declare const HelixHeadlessPopover_base: (new (...args: any[]) => import("@fusio
32
36
  *
33
37
  * @event {HelixBeforeCloseEvent} helix-before-close - Fired before it closes, and cancelable.
34
38
  * @event {HelixBeforeOpenEvent} helix-before-open - Fired before it opens, and cancelable.
35
- * @event {Event} toggle - Fired after the panel opens or closes, with `newState` and `oldState`.
39
+ * @event {ToggleEvent} toggle - Fired after the panel opens or closes, with `newState` and `oldState`.
36
40
  *
37
41
  * @csspart content - The scrolling region inside the panel.
38
42
  * @csspart popover - The panel.
@@ -44,6 +48,7 @@ export declare class HelixHeadlessPopover extends HelixHeadlessPopover_base {
44
48
  * The panel's name, read by screen readers.
45
49
  */
46
50
  label: string;
51
+ protected readonly aria: AriaForwardController;
47
52
  /**
48
53
  * Space between the trigger and the panel, in pixels.
49
54
  */
@@ -75,6 +80,18 @@ export declare class HelixHeadlessPopover extends HelixHeadlessPopover_base {
75
80
  show(): void;
76
81
  /** Closes the panel and puts focus back on the trigger. */
77
82
  hide(): void;
83
+ /**
84
+ * Focus leaving the control closes the panel.
85
+ *
86
+ * A popover does not trap focus, so `Tab` past its last control walks on
87
+ * into the page, and a panel left open there covers what the reader moved
88
+ * to. Focus moving between the trigger, the panel and a command's invoker
89
+ * stays inside the control. With no `relatedTarget`, focus left the
90
+ * document or went nowhere, and a press outside is already its own signal.
91
+ */
92
+ private readonly handleFocusOut;
93
+ connectedCallback(): void;
94
+ disconnectedCallback(): void;
78
95
  private readonly handleTriggerClick;
79
96
  /** Tells a slotted trigger the disclosure state, by property. See `tellDisclosure`. */
80
97
  private tellTrigger;
@@ -4,13 +4,16 @@ 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 { anchorElement, CommandController, customElement, deepActiveElement, HelixElement, MixinControllerOpen, PopoverController, tellDisclosure, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, anchorElement, CommandController, customElement, deepActiveElement, HelixElement, MixinControllerOpen, PopoverController, tellDisclosure, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpen(HelixElement) {
11
11
  constructor() {
12
12
  super(...arguments);
13
13
  this.label = 'Popover';
14
+ this.aria = new AriaForwardController(this, {
15
+ target: () => this.partNamed('popover'),
16
+ });
14
17
  this.offset = 4;
15
18
  this.placement = 'bottom-start';
16
19
  this.panel = new PopoverController(this, {
@@ -40,6 +43,16 @@ let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpe
40
43
  },
41
44
  },
42
45
  });
46
+ this.handleFocusOut = (event) => {
47
+ const next = event.relatedTarget;
48
+ if (!this.open || !(next instanceof Node)) {
49
+ return;
50
+ }
51
+ if (this.contains(next) || this.renderRoot.contains(next) || this.invoker?.contains(next)) {
52
+ return;
53
+ }
54
+ this.hide();
55
+ };
43
56
  this.handleTriggerClick = () => {
44
57
  if (this.open) {
45
58
  this.hide();
@@ -67,6 +80,14 @@ let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpe
67
80
  this.trigger.focus();
68
81
  }
69
82
  }
83
+ connectedCallback() {
84
+ super.connectedCallback();
85
+ this.addEventListener('focusout', this.handleFocusOut);
86
+ }
87
+ disconnectedCallback() {
88
+ super.disconnectedCallback();
89
+ this.removeEventListener('focusout', this.handleFocusOut);
90
+ }
70
91
  tellTrigger() {
71
92
  const slot = this.renderRoot?.querySelector('slot[name="trigger"]');
72
93
  tellDisclosure(slot?.assignedElements({ flatten: false })[0], this.open);
@@ -48,10 +48,23 @@ export declare class HelixHeadlessProgress extends HelixElement {
48
48
  * Accessible name for what is in progress.
49
49
  */
50
50
  label: string;
51
+ /**
52
+ * The locale the percentage is written in, such as `fr` for `72 %`.
53
+ *
54
+ * Absent uses the runtime's own.
55
+ */
56
+ locale: string;
51
57
  /**
52
58
  * The total the work is measured against.
53
59
  */
54
60
  max: number;
61
+ /**
62
+ * Where the work starts, in the same units as `max`. Defaults to zero.
63
+ *
64
+ * For a measure that does not start at nothing, such as a migration from
65
+ * version 12 to version 20, where 12 is the empty bar.
66
+ */
67
+ min: number;
55
68
  /**
56
69
  * Whether the value is drawn as a bar or as a ring.
57
70
  *
@@ -82,7 +95,7 @@ export declare class HelixHeadlessProgress extends HelixElement {
82
95
  /**
83
96
  * What has been done so far, in the same units as `max`.
84
97
  *
85
- * Clamped between zero and `max` for both the fill and the announcement, so
98
+ * Clamped between `min` and `max` for both the fill and the announcement, so
86
99
  * the two can never disagree about what is on screen.
87
100
  */
88
101
  value: number;
@@ -96,6 +109,8 @@ export declare class HelixHeadlessProgress extends HelixElement {
96
109
  * report progress.
97
110
  */
98
111
  valueText: string;
112
+ /** The range the value is measured in, with a top above its bottom. */
113
+ private get range();
99
114
  /** What is done, as a fraction of the whole, between 0 and 1. */
100
115
  get fraction(): number;
101
116
  protected willUpdate(changed: PropertyValues): void;
@@ -7,29 +7,47 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
+ const percents = new Map();
11
+ const percent = (share, locale) => {
12
+ let format = percents.get(locale);
13
+ if (!format) {
14
+ format = new Intl.NumberFormat(locale || undefined, {
15
+ maximumFractionDigits: 0,
16
+ style: 'percent',
17
+ });
18
+ percents.set(locale, format);
19
+ }
20
+ return format.format(share);
21
+ };
10
22
  let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
11
23
  constructor() {
12
24
  super(...arguments);
13
25
  this.indeterminate = false;
14
26
  this.label = 'Progress';
27
+ this.locale = '';
15
28
  this.max = 100;
29
+ this.min = 0;
16
30
  this.showValue = false;
17
31
  this.value = 0;
18
32
  this.valueText = '';
19
33
  }
34
+ get range() {
35
+ const min = Number.isFinite(this.min) ? this.min : 0;
36
+ return { max: this.max > min ? this.max : min + 100, min };
37
+ }
20
38
  get fraction() {
21
- const max = this.max > 0 ? this.max : 100;
22
- return Math.min(Math.max(this.value / max, 0), 1) || 0;
39
+ const { max, min } = this.range;
40
+ return Math.min(Math.max((this.value - min) / (max - min), 0), 1) || 0;
23
41
  }
24
42
  willUpdate(changed) {
25
43
  super.willUpdate(changed);
26
44
  hostAria(this.internals, { label: this.label, role: 'progressbar' });
27
- this.internals.ariaValueMin = '0';
28
- this.internals.ariaValueMax = String(this.max > 0 ? this.max : 100);
29
- const ceiling = this.max > 0 ? this.max : 100;
45
+ const { max, min } = this.range;
46
+ this.internals.ariaValueMin = String(min);
47
+ this.internals.ariaValueMax = String(max);
30
48
  this.internals.ariaValueNow = this.indeterminate
31
49
  ? null
32
- : String(Math.min(Math.max(this.value || 0, 0), ceiling));
50
+ : String(Math.min(Math.max(this.value || min, min), max));
33
51
  this.internals.ariaValueText = this.valueText || null;
34
52
  }
35
53
  render() {
@@ -45,7 +63,7 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
45
63
  </div>`}
46
64
  ${this.showValue && !this.indeterminate
47
65
  ? html `<span part="value" aria-hidden="true"
48
- >${this.valueText || `${Math.round(this.fraction * 100)}%`}</span
66
+ >${this.valueText || percent(this.fraction, this.locale)}</span
49
67
  >`
50
68
  : ''}`;
51
69
  }
@@ -56,9 +74,15 @@ __decorate([
56
74
  __decorate([
57
75
  property()
58
76
  ], HelixHeadlessProgress.prototype, "label", void 0);
77
+ __decorate([
78
+ property()
79
+ ], HelixHeadlessProgress.prototype, "locale", void 0);
59
80
  __decorate([
60
81
  property({ type: Number })
61
82
  ], HelixHeadlessProgress.prototype, "max", void 0);
83
+ __decorate([
84
+ property({ type: Number })
85
+ ], HelixHeadlessProgress.prototype, "min", void 0);
62
86
  __decorate([
63
87
  property({ reflect: true })
64
88
  ], HelixHeadlessProgress.prototype, "shape", void 0);
@@ -26,6 +26,10 @@ export type QrStyle = 'dots' | 'rounded' | 'square';
26
26
  * the same mark makes the code unscannable. The default is `M`, and `H` when the
27
27
  * `logo` slot is filled.
28
28
  *
29
+ * A higher level holds fewer characters, so a value that fits at `M` can be too
30
+ * long once a logo raises the level to `H`. A value too long for the level
31
+ * draws no code, and the element fires `error`.
32
+ *
29
33
  * @since 0.1.0
30
34
  * @status stable
31
35
  * @category media
@@ -34,6 +38,8 @@ export type QrStyle = 'dots' | 'rounded' | 'square';
34
38
  *
35
39
  * @slot logo - A logo for the center. A code with a logo scans only at a high correction level.
36
40
  *
41
+ * @event {Event} error - The value is too long for a code at the correction level, so no code is drawn.
42
+ *
37
43
  * @csspart code - The square code image.
38
44
  * @csspart finders - The three corner squares, which can take their own color.
39
45
  * @csspart logo - The box that holds the center logo.
@@ -84,6 +90,19 @@ export declare class HelixHeadlessQrCode extends HelixElement {
84
90
  protected firstUpdated(changed: PropertyValues): void;
85
91
  /** The level actually used, which the slot can raise. */
86
92
  private get level();
93
+ /** The value and level that did not fit, so `error` fires once for each. */
94
+ private overflowed;
95
+ /**
96
+ * The code for the value at the level, or undefined where the value is too
97
+ * long for it.
98
+ *
99
+ * `encode` throws for a value past the level's capacity, and it threw inside
100
+ * the render: the update rejected and the last code stayed on screen. A
101
+ * logo raising the level to `H` was the easy way to get there, since `H`
102
+ * holds the fewest characters.
103
+ */
104
+ private encoded;
105
+ protected updated(changed: PropertyValues): void;
87
106
  private renderCode;
88
107
  protected render(): TemplateResult;
89
108
  }
@@ -65,6 +65,7 @@ let HelixHeadlessQrCode = class HelixHeadlessQrCode extends HelixElement {
65
65
  this.label = '';
66
66
  this.value = '';
67
67
  this.logo = false;
68
+ this.overflowed = '';
68
69
  }
69
70
  willUpdate(changed) {
70
71
  super.willUpdate(changed);
@@ -79,11 +80,38 @@ let HelixHeadlessQrCode = class HelixHeadlessQrCode extends HelixElement {
79
80
  get level() {
80
81
  return this.correction ?? (this.logo ? 'H' : 'M');
81
82
  }
83
+ encoded() {
84
+ try {
85
+ return encode(this.value, { border: 0, ecc: this.level });
86
+ }
87
+ catch (error) {
88
+ if (error instanceof RangeError) {
89
+ return undefined;
90
+ }
91
+ throw error;
92
+ }
93
+ }
94
+ updated(changed) {
95
+ super.updated(changed);
96
+ const key = `${this.level}:${this.value}`;
97
+ if (this.value !== '' && this.encoded() === undefined) {
98
+ if (this.overflowed !== key) {
99
+ this.overflowed = key;
100
+ this.dispatchEvent(new Event('error', { bubbles: false, cancelable: false, composed: false }));
101
+ }
102
+ }
103
+ else {
104
+ this.overflowed = '';
105
+ }
106
+ }
82
107
  renderCode() {
83
108
  if (this.value === '') {
84
109
  return nothing;
85
110
  }
86
- const result = encode(this.value, { border: 0, ecc: this.level });
111
+ const result = this.encoded();
112
+ if (result === undefined) {
113
+ return nothing;
114
+ }
87
115
  const size = result.size;
88
116
  const quiet = 4;
89
117
  const box = size + quiet * 2;
@@ -29,6 +29,7 @@ declare const HelixHeadlessRadioGroup_base: (new (...args: any[]) => import("@fu
29
29
  * @documentation https://fusion.dev/helix/components/radio
30
30
  *
31
31
  * @slot - The radios.
32
+ * @slot feedback - The error message, for content richer than a string. It shows while there is a message.
32
33
  * @slot hint - The help text under the group, for content richer than a string.
33
34
  * @slot label - The group's label, for a question that needs more than a string.
34
35
  *
@@ -45,6 +46,18 @@ declare const HelixHeadlessRadioGroup_base: (new (...args: any[]) => import("@fu
45
46
  * @cssstate invalid - The group has an error to show, from the browser's validation or from your `feedback`.
46
47
  */
47
48
  export declare class HelixHeadlessRadioGroup extends HelixHeadlessRadioGroup_base {
49
+ /**
50
+ * Whether the group holds a tab stop of its own.
51
+ *
52
+ * A toolbar is one tab stop, so a toolbar the group sits in sets this to
53
+ * `false` while its stop is on another control, and every radio, the chosen
54
+ * one too, is then out of the tab order. The arrow keys inside the group
55
+ * work as before once focus is in it.
56
+ *
57
+ * No attribute: it is coordination between elements, and a consumer has no
58
+ * reason to set it.
59
+ */
60
+ focusable: boolean;
48
61
  /**
49
62
  * The direction the choices run. `vertical` puts one choice on each line.
50
63
  *
@@ -76,8 +89,36 @@ export declare class HelixHeadlessRadioGroup extends HelixHeadlessRadioGroup_bas
76
89
  * the item's is the one that was doing nothing.
77
90
  */
78
91
  private readonly handleSelection;
92
+ /**
93
+ * The radio holding the answer, or nothing.
94
+ *
95
+ * Nothing where `value` names no radio here: a chosen radio that was removed
96
+ * is no longer an answer, as a native group drops it from the form. `value`
97
+ * keeps the name, so a value written before its radios arrive takes once they
98
+ * do.
99
+ */
100
+ private get answer();
101
+ /** Whether the group holds an answer the form can submit. */
102
+ private get answered();
103
+ /**
104
+ * Takes the answer from a radio whose markup says `selected`, where nothing
105
+ * else declared one.
106
+ *
107
+ * As `Toggle.Group` does, so `<helix-radio selected>` preselects as
108
+ * `<option selected>` and `<helix-toggle selected>` do, and a reset returns
109
+ * to it. Client only: the server's element shim has no children to read.
110
+ */
111
+ private adopt;
79
112
  /** Writes the answer onto the items, and nothing onto the host. */
80
113
  private place;
114
+ /** The radio that holds the group's tab stop: the answer, or the first that can be chosen. */
115
+ private entry;
116
+ /**
117
+ * Focus goes to the radio that holds the stop, and to the radio that would
118
+ * hold it while a toolbar holds the stop instead, so a label press or a
119
+ * refused submission still lands in the group.
120
+ */
121
+ protected get focusTarget(): HTMLElement | null;
81
122
  /** Reports the answer and the verdict on it. */
82
123
  private sync;
83
124
  connectedCallback(): void;