@fusion.dev/helix-headless 0.145.0 → 0.146.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 (278) hide show
  1. package/custom-elements.json +7373 -3849
  2. package/dist/accordion/item/accordion-item.d.ts +14 -0
  3. package/dist/accordion/item/accordion-item.js +16 -6
  4. package/dist/accordion/root/accordion-root.d.ts +8 -5
  5. package/dist/announcement/root/announcement-root.d.ts +3 -2
  6. package/dist/announcement/root/announcement-root.js +3 -1
  7. package/dist/app-header/root/app-header-root.d.ts +7 -2
  8. package/dist/app-header/root/app-header-root.js +9 -1
  9. package/dist/app-header/sectioning.d.ts +3 -1
  10. package/dist/app-header/sectioning.js +3 -2
  11. package/dist/avatar/group/avatar-group.d.ts +20 -14
  12. package/dist/avatar/group/avatar-group.js +17 -19
  13. package/dist/badge/root/badge-root.d.ts +3 -1
  14. package/dist/badge/root/badge-root.js +2 -2
  15. package/dist/bar-chart/root/bar-chart-root.d.ts +18 -1
  16. package/dist/bar-chart/root/bar-chart-root.js +52 -15
  17. package/dist/bar-chart/stack.d.ts +15 -0
  18. package/dist/bar-chart/stack.js +19 -0
  19. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +25 -0
  20. package/dist/breadcrumbs/root/breadcrumbs-root.js +50 -3
  21. package/dist/button/root/button-root.d.ts +26 -0
  22. package/dist/button/root/button-root.js +38 -18
  23. package/dist/button-group/root/button-group-root.d.ts +2 -1
  24. package/dist/button-group/root/button-group-root.js +5 -1
  25. package/dist/canvas/node/canvas-node.d.ts +4 -3
  26. package/dist/canvas/node/canvas-node.js +3 -2
  27. package/dist/canvas/root/canvas-root.js +2 -2
  28. package/dist/card/root/card-root.d.ts +24 -3
  29. package/dist/card/root/card-root.js +20 -8
  30. package/dist/carousel/root/carousel-root.d.ts +43 -4
  31. package/dist/carousel/root/carousel-root.js +40 -12
  32. package/dist/carousel/stops.d.ts +20 -0
  33. package/dist/carousel/stops.js +30 -0
  34. package/dist/chat/message/chat-message.js +5 -4
  35. package/dist/chat/naming.d.ts +5 -1
  36. package/dist/chat/naming.js +4 -2
  37. package/dist/chat/root/chat-root.d.ts +25 -3
  38. package/dist/chat/root/chat-root.js +22 -5
  39. package/dist/checkbox/root/checkbox-root.d.ts +20 -0
  40. package/dist/checkbox/root/checkbox-root.js +14 -1
  41. package/dist/checkbox-group/root/checkbox-group-root.d.ts +8 -2
  42. package/dist/checkbox-group/root/checkbox-group-root.js +12 -10
  43. package/dist/checkbox-group/select-all.d.ts +27 -0
  44. package/dist/checkbox-group/select-all.js +12 -0
  45. package/dist/code-block/highlight.d.ts +15 -0
  46. package/dist/code-block/highlight.js +4 -0
  47. package/dist/code-block/root/code-block-root.d.ts +49 -1
  48. package/dist/code-block/root/code-block-root.js +58 -3
  49. package/dist/color-input/root/color-input-root.d.ts +3 -0
  50. package/dist/color-input/root/color-input-root.js +6 -3
  51. package/dist/color-picker/root/color-picker-root.d.ts +33 -4
  52. package/dist/color-picker/root/color-picker-root.js +80 -21
  53. package/dist/color-picker/swatches.d.ts +12 -0
  54. package/dist/color-picker/swatches.js +26 -0
  55. package/dist/combobox/root/combobox-root.d.ts +43 -0
  56. package/dist/combobox/root/combobox-root.js +79 -15
  57. package/dist/command/root/command-root.d.ts +16 -1
  58. package/dist/command/root/command-root.js +22 -4
  59. package/dist/copy-button/root/copy-button-root.d.ts +5 -0
  60. package/dist/copy-button/root/copy-button-root.js +10 -3
  61. package/dist/data-table/index.d.ts +1 -1
  62. package/dist/data-table/index.js +1 -1
  63. package/dist/data-table/keys.d.ts +24 -0
  64. package/dist/data-table/keys.js +24 -0
  65. package/dist/data-table/root/data-table-root.d.ts +27 -18
  66. package/dist/data-table/root/data-table-root.js +20 -52
  67. package/dist/data-table/tree.d.ts +16 -1
  68. package/dist/data-table/tree.js +2 -3
  69. package/dist/date-input/root/date-input-root.d.ts +3 -1
  70. package/dist/date-input/root/date-input-root.js +18 -7
  71. package/dist/date-picker/root/date-picker-root.js +12 -6
  72. package/dist/dialog/description.js +15 -1
  73. package/dist/dialog/root/dialog-root.d.ts +8 -2
  74. package/dist/dialog/root/dialog-root.js +16 -1
  75. package/dist/divider/root/divider-root.d.ts +3 -0
  76. package/dist/divider/root/divider-root.js +2 -1
  77. package/dist/donut-chart/root/donut-chart-root.d.ts +31 -0
  78. package/dist/donut-chart/root/donut-chart-root.js +48 -1
  79. package/dist/drawer/reveal.d.ts +8 -0
  80. package/dist/drawer/reveal.js +1 -0
  81. package/dist/drawer/root/drawer-root.d.ts +59 -0
  82. package/dist/drawer/root/drawer-root.js +63 -4
  83. package/dist/drilldown/item/drilldown-item.js +1 -1
  84. package/dist/drilldown/root/drilldown-root.d.ts +21 -3
  85. package/dist/drilldown/root/drilldown-root.js +21 -3
  86. package/dist/element-events.generated.js +31 -5
  87. package/dist/empty/root/empty-root.d.ts +3 -1
  88. package/dist/empty/root/empty-root.js +3 -2
  89. package/dist/file-input/root/file-input-root.d.ts +69 -6
  90. package/dist/file-input/root/file-input-root.js +82 -19
  91. package/dist/file-input/screen.d.ts +2 -1
  92. package/dist/filter-input/menu/filter-menu.d.ts +2 -0
  93. package/dist/filter-input/menu/filter-menu.js +12 -4
  94. package/dist/filter-input/root/filter-input-root.d.ts +30 -6
  95. package/dist/filter-input/root/filter-input-root.js +1 -1
  96. package/dist/filter-input/suggest.d.ts +28 -3
  97. package/dist/filter-input/suggest.js +18 -8
  98. package/dist/format-date/root/format-date-root.d.ts +9 -16
  99. package/dist/format-date/root/format-date-root.js +10 -27
  100. package/dist/heatmap/root/heatmap-root.d.ts +24 -13
  101. package/dist/heatmap/root/heatmap-root.js +34 -12
  102. package/dist/icon/root/icon-root.d.ts +12 -1
  103. package/dist/icon/root/icon-root.js +21 -10
  104. package/dist/icon/settle.d.ts +7 -0
  105. package/dist/icon/settle.js +10 -0
  106. package/dist/icon-button/root/icon-button-root.d.ts +2 -0
  107. package/dist/image/root/image-root.d.ts +5 -1
  108. package/dist/image/root/image-root.js +1 -1
  109. package/dist/input/root/input-root.d.ts +12 -2
  110. package/dist/input/root/input-root.js +11 -2
  111. package/dist/kbd/root/kbd-root.d.ts +35 -1
  112. package/dist/kbd/root/kbd-root.js +23 -4
  113. package/dist/legend/root/legend-root.d.ts +55 -8
  114. package/dist/legend/root/legend-root.js +55 -13
  115. package/dist/lightbox/root/lightbox-root.d.ts +49 -6
  116. package/dist/lightbox/root/lightbox-root.js +86 -19
  117. package/dist/lightbox/zoom-controller.d.ts +59 -0
  118. package/dist/lightbox/zoom-controller.js +128 -0
  119. package/dist/lightbox/zoom.d.ts +22 -0
  120. package/dist/lightbox/zoom.js +16 -0
  121. package/dist/line-chart/root/line-chart-root.d.ts +22 -1
  122. package/dist/line-chart/root/line-chart-root.js +21 -1
  123. package/dist/list/item/list-item.d.ts +2 -0
  124. package/dist/list/item/list-item.js +11 -2
  125. package/dist/list/root/list-root.d.ts +62 -13
  126. package/dist/list/root/list-root.js +70 -13
  127. package/dist/list/selection.d.ts +37 -7
  128. package/dist/list/selection.js +16 -7
  129. package/dist/listbox/root/listbox-root.d.ts +37 -1
  130. package/dist/listbox/root/listbox-root.js +49 -3
  131. package/dist/loader/busy.d.ts +10 -0
  132. package/dist/loader/busy.js +11 -0
  133. package/dist/loader/root/loader-root.d.ts +9 -2
  134. package/dist/loader/root/loader-root.js +23 -3
  135. package/dist/megamenu/root/megamenu-root.d.ts +51 -10
  136. package/dist/megamenu/root/megamenu-root.js +76 -5
  137. package/dist/menu/item/menu-item.d.ts +51 -2
  138. package/dist/menu/item/menu-item.js +47 -23
  139. package/dist/menu/root/menu-root.d.ts +14 -0
  140. package/dist/menu/root/menu-root.js +2 -2
  141. package/dist/meter/root/meter-root.d.ts +20 -1
  142. package/dist/meter/root/meter-root.js +11 -15
  143. package/dist/navigation/root/navigation-root.d.ts +2 -1
  144. package/dist/navigation/root/navigation-root.js +2 -2
  145. package/dist/notifications/root/notifications-root.d.ts +10 -3
  146. package/dist/notifications/root/notifications-root.js +19 -7
  147. package/dist/number-input/root/number-input-root.d.ts +17 -8
  148. package/dist/number-input/root/number-input-root.js +2 -7
  149. package/dist/object-inspector/root/object-inspector-root.d.ts +75 -27
  150. package/dist/object-inspector/root/object-inspector-root.js +117 -76
  151. package/dist/object-inspector/rows.d.ts +18 -0
  152. package/dist/object-inspector/rows.js +33 -0
  153. package/dist/option/root/option-root.d.ts +5 -3
  154. package/dist/outline/root/outline-root.d.ts +18 -0
  155. package/dist/outline/root/outline-root.js +23 -5
  156. package/dist/pagination/size/pagination-size.d.ts +1 -0
  157. package/dist/pagination/size/pagination-size.js +1 -1
  158. package/dist/pin-input/code.d.ts +10 -0
  159. package/dist/pin-input/code.js +13 -0
  160. package/dist/pin-input/root/pin-input-root.d.ts +25 -1
  161. package/dist/pin-input/root/pin-input-root.js +10 -11
  162. package/dist/popover/hide-outside.d.ts +14 -0
  163. package/dist/popover/hide-outside.js +41 -0
  164. package/dist/popover/root/popover-root.d.ts +57 -7
  165. package/dist/popover/root/popover-root.js +58 -3
  166. package/dist/progress/root/progress-root.d.ts +20 -0
  167. package/dist/progress/root/progress-root.js +15 -15
  168. package/dist/qr-code/export.d.ts +26 -1
  169. package/dist/qr-code/export.js +14 -0
  170. package/dist/qr-code/root/qr-code-root.d.ts +19 -5
  171. package/dist/qr-code/root/qr-code-root.js +58 -9
  172. package/dist/quote/marks.d.ts +8 -0
  173. package/dist/quote/marks.js +5 -0
  174. package/dist/quote/root/quote-root.d.ts +14 -2
  175. package/dist/quote/root/quote-root.js +23 -1
  176. package/dist/radio/root/radio-root.d.ts +6 -2
  177. package/dist/radio/root/radio-root.js +6 -1
  178. package/dist/rating/root/rating-root.d.ts +9 -4
  179. package/dist/rating/root/rating-root.js +31 -7
  180. package/dist/rating/steps.d.ts +7 -0
  181. package/dist/rating/steps.js +9 -0
  182. package/dist/scatter-chart/fit.d.ts +15 -0
  183. package/dist/scatter-chart/fit.js +35 -0
  184. package/dist/scatter-chart/root/scatter-chart-root.d.ts +21 -1
  185. package/dist/scatter-chart/root/scatter-chart-root.js +30 -17
  186. package/dist/scroll-area/root/scroll-area-root.d.ts +22 -51
  187. package/dist/scroll-area/root/scroll-area-root.js +57 -173
  188. package/dist/scroll-area/scrollbar-controller.d.ts +111 -0
  189. package/dist/scroll-area/scrollbar-controller.js +172 -0
  190. package/dist/scroll-area/scrollbar.d.ts +27 -0
  191. package/dist/scroll-area/scrollbar.js +9 -0
  192. package/dist/select/root/select-root.d.ts +15 -0
  193. package/dist/select/root/select-root.js +18 -6
  194. package/dist/skeleton/root/skeleton-root.d.ts +3 -0
  195. package/dist/skeleton/root/skeleton-root.js +4 -2
  196. package/dist/skip-link/root/skip-link-root.js +2 -2
  197. package/dist/slider/root/slider-root.d.ts +41 -4
  198. package/dist/slider/root/slider-root.js +23 -3
  199. package/dist/sparkline/readings.d.ts +18 -0
  200. package/dist/sparkline/readings.js +19 -0
  201. package/dist/sparkline/root/sparkline-root.d.ts +78 -7
  202. package/dist/sparkline/root/sparkline-root.js +118 -23
  203. package/dist/speed-dial/root/speed-dial-root.js +2 -2
  204. package/dist/splitter/root/splitter-root.d.ts +7 -12
  205. package/dist/splitter/root/splitter-root.js +209 -204
  206. package/dist/stat/root/stat-root.d.ts +14 -5
  207. package/dist/stat/root/stat-root.js +21 -5
  208. package/dist/steps/reach.d.ts +5 -0
  209. package/dist/steps/reach.js +3 -0
  210. package/dist/steps/root/steps-root.d.ts +49 -3
  211. package/dist/steps/root/steps-root.js +44 -5
  212. package/dist/steps/step/step.d.ts +7 -0
  213. package/dist/steps/step/step.js +7 -2
  214. package/dist/switch/root/switch-root.d.ts +10 -0
  215. package/dist/table/column/table-column.d.ts +0 -10
  216. package/dist/table/column/table-column.js +3 -19
  217. package/dist/table/root/table-root.d.ts +31 -10
  218. package/dist/table/root/table-root.js +2 -13
  219. package/dist/tabs/panel/tab-panel.d.ts +16 -3
  220. package/dist/tabs/panel/tab-panel.js +34 -5
  221. package/dist/tabs/root/tabs-root.d.ts +12 -0
  222. package/dist/tabs/root/tabs-root.js +1 -1
  223. package/dist/tabs/tab/tab.d.ts +5 -0
  224. package/dist/tabs/tab/tab.js +5 -2
  225. package/dist/tag/group/tag-group.d.ts +31 -4
  226. package/dist/tag/group/tag-group.js +46 -7
  227. package/dist/tag/root/tag-root.js +9 -2
  228. package/dist/textarea/root/textarea-root.d.ts +28 -1
  229. package/dist/textarea/root/textarea-root.js +40 -1
  230. package/dist/time-input/root/time-input-root.d.ts +5 -1
  231. package/dist/time-input/root/time-input-root.js +16 -8
  232. package/dist/time-picker/root/time-picker-root.d.ts +18 -4
  233. package/dist/time-picker/root/time-picker-root.js +7 -10
  234. package/dist/time-picker/wheel-name.d.ts +9 -0
  235. package/dist/time-picker/wheel-name.js +15 -0
  236. package/dist/timeline/group/timeline-group.js +2 -2
  237. package/dist/toast/item/toast-item.d.ts +14 -1
  238. package/dist/toast/item/toast-item.js +22 -5
  239. package/dist/toast/root/toast-root.d.ts +6 -0
  240. package/dist/toast/root/toast-root.js +16 -1
  241. package/dist/toggle/group/toggle-group.d.ts +24 -3
  242. package/dist/toggle/group/toggle-group.js +34 -5
  243. package/dist/tooltip/root/tooltip-root.d.ts +13 -2
  244. package/dist/tooltip/root/tooltip-root.js +9 -1
  245. package/dist/tour/back.d.ts +10 -0
  246. package/dist/tour/back.js +8 -0
  247. package/dist/tour/root/tour-root.d.ts +30 -2
  248. package/dist/tour/root/tour-root.js +34 -8
  249. package/dist/tree/item/tree-item.d.ts +7 -3
  250. package/dist/tree/item/tree-item.js +3 -0
  251. package/dist/tree/root/tree-root.d.ts +24 -3
  252. package/dist/tree/root/tree-root.js +32 -13
  253. package/dist/tree/selection.js +2 -15
  254. package/dist/trend/root/trend-root.d.ts +43 -18
  255. package/dist/trend/root/trend-root.js +31 -6
  256. package/dist/video/media-events.d.ts +13 -18
  257. package/dist/video/media-events.js +1 -5
  258. package/dist/video/root/video-root.d.ts +18 -5
  259. package/dist/video/root/video-root.js +19 -10
  260. package/dist/video-playlist/menu/video-playlist-menu.d.ts +2 -0
  261. package/dist/video-playlist/menu/video-playlist-menu.js +2 -1
  262. package/dist/video-playlist/root/video-playlist-root.d.ts +7 -1
  263. package/dist/virtual-list/fill.d.ts +0 -8
  264. package/dist/virtual-list/fill.js +1 -17
  265. package/dist/virtual-list/plan.d.ts +49 -0
  266. package/dist/virtual-list/plan.js +16 -0
  267. package/dist/virtual-list/root/virtual-list-root.d.ts +114 -13
  268. package/dist/virtual-list/root/virtual-list-root.js +183 -40
  269. package/lint.json +1 -1
  270. package/package.json +8 -8
  271. package/types/jsx.d.ts +4577 -835
  272. package/types/vue.d.ts +926 -168
  273. package/dist/chat/relative-clock.d.ts +0 -39
  274. package/dist/chat/relative-clock.js +0 -31
  275. package/dist/data-table/root/order.d.ts +0 -141
  276. package/dist/data-table/root/order.js +0 -76
  277. package/dist/tag/selection.d.ts +0 -10
  278. package/dist/tag/selection.js +0 -12
@@ -4,9 +4,10 @@ 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 { AriaForwardController, customElement, fillTemplate, HelixCompleteEvent, HelixFormElement, MixinControllerValue, MixinField, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, fillTemplate, HelixCompleteEvent, HelixFormElement, MixinControllerValue, MixinField, submitImplicitly, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
+ import { cleanCode } from '../code.js';
10
11
  import { separatorsAfter } from '../format.js';
11
12
  let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerValue(MixinField(HelixFormElement)) {
12
13
  constructor() {
@@ -17,6 +18,7 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
17
18
  this.announcesInput = true;
18
19
  this.autocomplete = 'one-time-code';
19
20
  this.autosubmit = false;
21
+ this.case = 'preserve';
20
22
  this.cellLabel = 'Character {index} of {length}';
21
23
  this.format = '';
22
24
  this.length = 6;
@@ -114,7 +116,7 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
114
116
  };
115
117
  this.handleKeypress = (event) => {
116
118
  if (event.key === 'Enter' && !event.defaultPrevented) {
117
- this.submitImplicitly();
119
+ submitImplicitly(this.form);
118
120
  }
119
121
  };
120
122
  }
@@ -135,14 +137,7 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
135
137
  return Array.from({ length: this.count }, (_, at) => this.value[at] ?? '');
136
138
  }
137
139
  clean(text) {
138
- const trimmed = text.trim();
139
- if (this.type === 'numeric') {
140
- return trimmed.replace(/\D/g, '');
141
- }
142
- if (this.type === 'alphanumeric') {
143
- return trimmed.replace(/[^0-9A-Za-z]/g, '');
144
- }
145
- return trimmed;
140
+ return cleanCode(text, this.type, this.case);
146
141
  }
147
142
  write(from, text) {
148
143
  const cleaned = this.clean(text);
@@ -165,7 +160,7 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
165
160
  this.dispatchEvent(new HelixCompleteEvent({ value: next }));
166
161
  this.commit();
167
162
  if (this.autosubmit) {
168
- this.submitImplicitly();
163
+ submitImplicitly(this.form);
169
164
  }
170
165
  }
171
166
  });
@@ -223,6 +218,7 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
223
218
  }
224
219
  updated(changed) {
225
220
  super.updated(changed);
221
+ this.toggleState('complete', this.value.length >= this.count);
226
222
  this.paint();
227
223
  this.sync();
228
224
  }
@@ -271,6 +267,9 @@ __decorate([
271
267
  __decorate([
272
268
  property({ type: Boolean })
273
269
  ], HelixHeadlessPinInput.prototype, "autosubmit", void 0);
270
+ __decorate([
271
+ property()
272
+ ], HelixHeadlessPinInput.prototype, "case", void 0);
274
273
  __decorate([
275
274
  property({ attribute: 'cell-label' })
276
275
  ], HelixHeadlessPinInput.prototype, "cellLabel", void 0);
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Hides everything on the page from assistive technology except `kept`, and
3
+ * returns a function that shows it again.
4
+ *
5
+ * Climbs from each kept element to the top of the document, through any shadow
6
+ * root it is in, and marks every sibling on the way that holds no kept element
7
+ * with `aria-hidden="true"`. What already says `aria-hidden` is left as it is,
8
+ * and given back as it was. Live regions are kept, so a message that arrives
9
+ * while the page is hidden is still announced.
10
+ *
11
+ * What a modal popover rests on: `Tab` is kept inside the panel, and this keeps
12
+ * a screen reader's virtual cursor there too.
13
+ */
14
+ export declare const hideOutside: (kept: readonly Element[]) => (() => void);
@@ -0,0 +1,41 @@
1
+ const KEEP = 'script, style, template, link, meta, [aria-live], [role="alert"], [role="log"], [role="status"]';
2
+ export const hideOutside = (kept) => {
3
+ const hidden = [];
4
+ const holds = (element) => kept.some((one) => composedContains(element, one));
5
+ const seen = new Set();
6
+ for (const start of kept) {
7
+ let node = start;
8
+ while (node && node !== document.documentElement) {
9
+ const parent = node.parentNode;
10
+ if (!parent || seen.has(parent)) {
11
+ break;
12
+ }
13
+ seen.add(parent);
14
+ for (const sibling of parent.children ?? []) {
15
+ if (sibling === node ||
16
+ holds(sibling) ||
17
+ sibling.matches(KEEP) ||
18
+ sibling.hasAttribute('aria-hidden')) {
19
+ continue;
20
+ }
21
+ sibling.setAttribute('aria-hidden', 'true');
22
+ hidden.push(sibling);
23
+ }
24
+ node = parent instanceof ShadowRoot ? parent.host : parent;
25
+ }
26
+ }
27
+ return () => {
28
+ for (const one of hidden) {
29
+ one.removeAttribute('aria-hidden');
30
+ }
31
+ hidden.length = 0;
32
+ };
33
+ };
34
+ const composedContains = (outer, inner) => {
35
+ for (let node = inner; node; node = node instanceof ShadowRoot ? node.host : node.parentNode) {
36
+ if (node === outer) {
37
+ return true;
38
+ }
39
+ }
40
+ return false;
41
+ };
@@ -9,9 +9,11 @@ declare const HelixHeadlessPopover_base: (new (...args: any[]) => import("@fusio
9
9
  * commands, `Select` holds options, and `Tooltip` holds a description. Popover
10
10
  * holds any content you put in it, so it has no keyboard handling of its own.
11
11
  *
12
- * The trigger is slotted and stays your own element. The popover writes nothing
13
- * onto it, including `aria-expanded`, `id`, and `tabindex`, because changing
14
- * your markup after upgrade causes a React hydration mismatch.
12
+ * The trigger is slotted and stays your own element. A Helix trigger is told
13
+ * whether the popover is open inside its own shadow root. On a plain element,
14
+ * such as a `<button>`, the popover writes `aria-expanded` only once a reader
15
+ * has pressed it, which is after any hydration, and never where you wrote
16
+ * `aria-expanded` yourself. It writes nothing else onto your markup.
15
17
  *
16
18
  * The panel is in the top layer through the popover API, so it needs no
17
19
  * `z-index`, and an ancestor with `overflow: hidden`, `transform`, `filter`, or
@@ -28,7 +30,9 @@ declare const HelixHeadlessPopover_base: (new (...args: any[]) => import("@fusio
28
30
  *
29
31
  * `modal` keeps `Tab` inside the panel while it is open and says so with
30
32
  * `aria-modal`, for a panel that has to be answered before the page goes on.
31
- * Without it the popover does not trap focus.
33
+ * The rest of the page is hidden from screen readers with `aria-hidden` while
34
+ * it is open, so the virtual cursor stays in the panel too. Without it the
35
+ * popover does not trap focus.
32
36
  *
33
37
  * The panel's name is `label` when you set it. Otherwise it is the first
34
38
  * heading in the panel, or the elements an `aria-labelledby` on the host names.
@@ -85,8 +89,9 @@ export declare class HelixHeadlessPopover extends HelixHeadlessPopover_base {
85
89
  */
86
90
  label: string;
87
91
  /**
88
- * Keeps `Tab` inside the panel while it is open, and marks the panel
89
- * `aria-modal`. A press outside and Escape still close it.
92
+ * Keeps `Tab` inside the panel while it is open, marks the panel
93
+ * `aria-modal`, and hides the rest of the page from screen readers. A press
94
+ * outside and Escape still close it.
90
95
  */
91
96
  modal: boolean;
92
97
  protected readonly aria: AriaForwardController;
@@ -138,6 +143,26 @@ export declare class HelixHeadlessPopover extends HelixHeadlessPopover_base {
138
143
  show(): void;
139
144
  /** Closes the panel and puts focus back on the trigger. */
140
145
  hide(): void;
146
+ /**
147
+ * Closes the panel, giving focus back to the trigger where `giveBack` is set
148
+ * and focus is inside the panel that is going away. A popover dismissed by a
149
+ * press somewhere else has not taken focus with it, and pulling focus back to
150
+ * the trigger would move a reader who was already somewhere else on purpose.
151
+ */
152
+ private close;
153
+ /**
154
+ * Whether focus is in the panel, on the panel itself or on anything slotted
155
+ * into it. `deepActiveElement`, which walks into shadow roots:
156
+ * `document.activeElement` stops at the outermost host, so a popover living
157
+ * inside another component's shadow root would never see its own focus.
158
+ */
159
+ private get focusInside();
160
+ /**
161
+ * Whether the update under way closes the panel while focus is inside it,
162
+ * read before the panel is hidden and focus has fallen to the body.
163
+ */
164
+ private closingFromInside;
165
+ protected willUpdate(changed: PropertyValues): void;
141
166
  /**
142
167
  * Focus leaving the control closes the panel.
143
168
  *
@@ -149,10 +174,35 @@ export declare class HelixHeadlessPopover extends HelixHeadlessPopover_base {
149
174
  */
150
175
  private readonly handleFocusOut;
151
176
  connectedCallback(): void;
177
+ /**
178
+ * A press on the slotted trigger, heard on the host on the way down, before
179
+ * the trigger slot's own listener opens the panel and the update that
180
+ * schedules reads it in `tellTrigger`.
181
+ */
182
+ private readonly notePress;
152
183
  disconnectedCallback(): void;
153
184
  private readonly handleTriggerClick;
154
- /** Tells a slotted trigger the disclosure state, by property. See `tellDisclosure`. */
185
+ /** Whether a reader has pressed the trigger, which is after any hydration. */
186
+ private pressed;
187
+ /** The plain trigger this popover writes `aria-expanded` on, once it may. */
188
+ private told;
189
+ /**
190
+ * Tells a slotted trigger the disclosure state.
191
+ *
192
+ * A Helix trigger by property, through `tellDisclosure`. A plain element,
193
+ * which has no shadow root to keep the state out of the markup, by
194
+ * `aria-expanded`, and only from a reader's first press on: by then a server
195
+ * or a framework has long finished writing the element, so the write cannot
196
+ * be a hydration mismatch. One that already carries `aria-expanded` is the
197
+ * consumer's to keep, and is left alone.
198
+ */
155
199
  private tellTrigger;
200
+ /** Whether the first update, which is the server's, has passed. */
201
+ private settled;
202
+ /** Shows the page again, while a modal panel has it hidden. */
203
+ private showOutside;
204
+ /** Hides the page outside a modal panel while it is open, and shows it again after. */
205
+ private hideAround;
156
206
  /**
157
207
  * Puts the panel where `open` says it should be, however it got there.
158
208
  *
@@ -7,6 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { AriaForwardController, anchorElement, arrowReach, CommandController, customElement, deepActiveElement, FocusScopeController, HelixElement, headingIn, MixinControllerOpen, PopoverController, reflectElements, sideOf, tellDisclosure, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
+ import { hideOutside } from '../hide-outside.js';
10
11
  let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpen(HelixElement) {
11
12
  constructor() {
12
13
  super(...arguments);
@@ -60,6 +61,7 @@ let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpe
60
61
  },
61
62
  },
62
63
  });
64
+ this.closingFromInside = false;
63
65
  this.handleFocusOut = (event) => {
64
66
  const next = event.relatedTarget;
65
67
  if (!this.open || !(next instanceof Node)) {
@@ -68,7 +70,15 @@ let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpe
68
70
  if (this.contains(next) || this.renderRoot.contains(next) || this.invoker?.contains(next)) {
69
71
  return;
70
72
  }
71
- this.hide();
73
+ this.close(false);
74
+ };
75
+ this.notePress = (event) => {
76
+ const trigger = this.renderRoot
77
+ ?.querySelector('slot[name="trigger"]')
78
+ ?.assignedElements({ flatten: false })[0];
79
+ if (trigger && event.composedPath().includes(trigger)) {
80
+ this.pressed = true;
81
+ }
72
82
  };
73
83
  this.handleTriggerClick = () => {
74
84
  if (this.open) {
@@ -78,6 +88,8 @@ let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpe
78
88
  this.show();
79
89
  }
80
90
  };
91
+ this.pressed = false;
92
+ this.settled = false;
81
93
  }
82
94
  get slotted() {
83
95
  return (this.renderRoot
@@ -99,33 +111,76 @@ let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpe
99
111
  }
100
112
  }
101
113
  hide() {
114
+ this.close(true);
115
+ }
116
+ close(giveBack) {
102
117
  if (!this.open || !this.requestOpen(false)) {
103
118
  return;
104
119
  }
105
120
  this.panel.hide();
106
- if (this.contains(deepActiveElement())) {
121
+ if (giveBack && this.focusInside) {
107
122
  this.trigger.focus();
108
123
  }
109
124
  }
125
+ get focusInside() {
126
+ return this.scope.holdsFocus || this.contains(deepActiveElement());
127
+ }
128
+ willUpdate(changed) {
129
+ super.willUpdate(changed);
130
+ this.closingFromInside =
131
+ changed.has('open') && !this.open && changed.get('open') === true && this.focusInside;
132
+ }
110
133
  connectedCallback() {
111
134
  super.connectedCallback();
112
135
  this.addEventListener('focusout', this.handleFocusOut);
136
+ this.addEventListener('click', this.notePress, { capture: true });
113
137
  }
114
138
  disconnectedCallback() {
115
139
  super.disconnectedCallback();
116
140
  this.removeEventListener('focusout', this.handleFocusOut);
141
+ this.removeEventListener('click', this.notePress, { capture: true });
142
+ this.hideAround();
117
143
  }
118
144
  tellTrigger() {
119
145
  const slot = this.renderRoot?.querySelector('slot[name="trigger"]');
120
- tellDisclosure(slot?.assignedElements({ flatten: false })[0], this.open);
146
+ const trigger = slot?.assignedElements({ flatten: false })[0];
147
+ if (!trigger || trigger.localName.includes('-')) {
148
+ tellDisclosure(trigger, this.open);
149
+ return;
150
+ }
151
+ if (trigger !== this.told) {
152
+ if (!this.pressed || trigger.hasAttribute('aria-expanded')) {
153
+ return;
154
+ }
155
+ this.told = trigger;
156
+ }
157
+ trigger.setAttribute('aria-expanded', String(this.open));
158
+ }
159
+ hideAround() {
160
+ const hide = this.open && this.modal && this.isConnected;
161
+ if (hide && !this.showOutside) {
162
+ this.showOutside = hideOutside([this, ...(this.invoker ? [this.invoker] : [])]);
163
+ }
164
+ else if (!hide && this.showOutside) {
165
+ this.showOutside();
166
+ this.showOutside = undefined;
167
+ }
121
168
  }
122
169
  updated(changed) {
123
170
  super.updated(changed);
124
171
  this.tellTrigger();
125
172
  this.nameByHeading();
173
+ if (this.settled) {
174
+ this.hideAround();
175
+ }
176
+ this.settled = true;
126
177
  if (changed.has('open') && this.open && changed.get('open') === false) {
127
178
  this.scope.enter();
128
179
  }
180
+ if (this.closingFromInside) {
181
+ this.closingFromInside = false;
182
+ this.trigger.focus();
183
+ }
129
184
  }
130
185
  render() {
131
186
  return html `<slot name="trigger" @click=${this.handleTriggerClick}></slot>
@@ -31,6 +31,20 @@ import { type PropertyValues, type TemplateResult } from 'lit';
31
31
  * @cssproperty --progress-fraction - The value as a fraction of the range, 0 to 1. Pinned to 1 while indeterminate.
32
32
  */
33
33
  export declare class HelixHeadlessProgress extends HelixElement {
34
+ /**
35
+ * `Intl.NumberFormat` options the reading is written with, as React Aria's
36
+ * Meter and ProgressBar take them: a unit, a currency, or a number of
37
+ * decimals. Written as JSON in the attribute.
38
+ *
39
+ * ```html
40
+ * <helix-progress value="18" max="32" format-options='{"style":"unit","unit":"gigabyte"}'></helix-progress>
41
+ * ```
42
+ *
43
+ * A `percent` style writes the share of the range, and any other the value
44
+ * itself, so the example says "18 GB". Absent is a whole percentage. The
45
+ * words are announced as well as shown. `value-text` still wins.
46
+ */
47
+ formatOptions?: Intl.NumberFormatOptions;
34
48
  /**
35
49
  * Whether the length of the task is unknown. The bar then shows activity
36
50
  * without a value.
@@ -119,6 +133,12 @@ export declare class HelixHeadlessProgress extends HelixElement {
119
133
  /** What is done, as a fraction of the whole, between 0 and 1. */
120
134
  get fraction(): number;
121
135
  protected willUpdate(changed: PropertyValues): void;
136
+ /**
137
+ * What the bar says: the consumer's words, or the reading in
138
+ * `format-options`, a share of the range for a percent and the value for
139
+ * anything else.
140
+ */
141
+ private get text();
122
142
  /** A label's words changed, so the name is written again. */
123
143
  private readonly renamed;
124
144
  protected render(): TemplateResult;
@@ -4,21 +4,10 @@ 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, slottedWords } from '@fusion.dev/helix-core';
7
+ import { customElement, formatNumber, HelixElement, hostAria, slottedWords, } 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
+ const WHOLE_PERCENT = { maximumFractionDigits: 0, style: 'percent' };
22
11
  let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
23
12
  constructor() {
24
13
  super(...arguments);
@@ -54,7 +43,15 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
54
43
  this.internals.ariaValueNow = this.indeterminate
55
44
  ? null
56
45
  : String(Math.min(Math.max(this.value || min, min), max));
57
- this.internals.ariaValueText = this.valueText || null;
46
+ this.internals.ariaValueText =
47
+ this.valueText || (this.formatOptions !== undefined && !this.indeterminate ? this.text : null);
48
+ }
49
+ get text() {
50
+ const options = this.formatOptions;
51
+ return (this.valueText ||
52
+ (options && options.style !== 'percent'
53
+ ? formatNumber(Math.min(Math.max(this.value, this.range.min), this.range.max), this.locale, options)
54
+ : formatNumber(this.fraction, this.locale, options ?? WHOLE_PERCENT)));
58
55
  }
59
56
  render() {
60
57
  const fraction = this.indeterminate ? 1 : this.fraction;
@@ -69,11 +66,14 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
69
66
  </div>`}
70
67
  ${this.showValue && !this.indeterminate
71
68
  ? html `<span part="value" aria-hidden="true"
72
- >${this.valueText || percent(this.fraction, this.locale)}</span
69
+ >${this.text}</span
73
70
  >`
74
71
  : ''}`;
75
72
  }
76
73
  };
74
+ __decorate([
75
+ property({ attribute: 'format-options', type: Object })
76
+ ], HelixHeadlessProgress.prototype, "formatOptions", void 0);
77
77
  __decorate([
78
78
  property({ reflect: true, type: Boolean })
79
79
  ], HelixHeadlessProgress.prototype, "indeterminate", void 0);
@@ -21,11 +21,36 @@ export interface QrDrawing {
21
21
  modules: string;
22
22
  /** The quiet zone, in modules, that both paths are moved in by. */
23
23
  quiet: number;
24
+ /** The logo in the middle, where one is slotted and can be written into a file. */
25
+ logo?: QrLogo;
24
26
  }
27
+ /** A place in the drawing, in `viewBox` units: x, y, width and height. */
28
+ export type QrBox = readonly [number, number, number, number];
29
+ /** The slotted logo, read from the element, as a file can carry it. */
30
+ export interface QrLogo {
31
+ /** The box around the mark, which clears the modules under it. */
32
+ frame: QrBox;
33
+ /** The fill of that box. */
34
+ ground: string;
35
+ /** An image's address, written as an `<image>`. */
36
+ href?: string;
37
+ /** Where the mark sits inside the box. */
38
+ mark: QrBox;
39
+ /** An inline SVG's markup, written as a nested `<svg>` at the mark's place. */
40
+ svg?: string;
41
+ }
42
+ /**
43
+ * The logo as the file writes it: its box, then the mark, as an `<image>` or as
44
+ * the slotted SVG moved to the mark's place. Nothing for a mark that is
45
+ * neither, such as text, which a file of its own cannot carry in the page's
46
+ * fonts.
47
+ */
48
+ export declare const qrLogo: (logo: QrLogo) => string;
25
49
  /**
26
50
  * The code as standalone SVG markup, sized `size` pixels square.
27
51
  *
28
52
  * The background is a rectangle under the code, since a scanner needs the
29
- * light quiet zone and a transparent file put on a dark page has none.
53
+ * light quiet zone and a transparent file put on a dark page has none. A
54
+ * logo goes over the code, as it is drawn on the page.
30
55
  */
31
56
  export declare const qrSvg: (drawing: QrDrawing, size: number) => string;
@@ -1,4 +1,17 @@
1
1
  const attribute = (value) => value.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
2
+ const unit = (value) => String(Math.round(value * 100) / 100);
3
+ const placed = ([x, y, width, height]) => `x="${unit(x)}" y="${unit(y)}" width="${unit(width)}" height="${unit(height)}"`;
4
+ export const qrLogo = (logo) => {
5
+ const ground = `<rect ${placed(logo.frame)} fill="${attribute(logo.ground)}"/>`;
6
+ if (logo.href) {
7
+ return `${ground}<image ${placed(logo.mark)} href="${attribute(logo.href)}" preserveAspectRatio="xMidYMid meet"/>`;
8
+ }
9
+ if (logo.svg) {
10
+ const mark = logo.svg.replace(/^\s*<svg\b([^>]*)>/, (_, rest) => `<svg${rest.replace(/\s(?:x|y|width|height|slot)="[^"]*"/g, '')} ${placed(logo.mark)}>`);
11
+ return `${ground}${mark}`;
12
+ }
13
+ return '';
14
+ };
2
15
  export const qrSvg = (drawing, size) => {
3
16
  const shift = `translate(${drawing.quiet} ${drawing.quiet})`;
4
17
  return (`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${drawing.box} ${drawing.box}"` +
@@ -6,5 +19,6 @@ export const qrSvg = (drawing, size) => {
6
19
  `<rect width="${drawing.box}" height="${drawing.box}" fill="${attribute(drawing.background)}"/>` +
7
20
  `<path d="${attribute(drawing.modules)}" transform="${shift}" fill="${attribute(drawing.moduleColor)}"/>` +
8
21
  `<path d="${attribute(drawing.finders)}" transform="${shift}" fill="${attribute(drawing.finderColor)}"/>` +
22
+ (drawing.logo ? qrLogo(drawing.logo) : '') +
9
23
  '</svg>');
10
24
  };
@@ -28,11 +28,13 @@ export type QrStyle = 'dots' | 'rounded' | 'square';
28
28
  *
29
29
  * A higher level holds fewer characters, so a value that fits at `M` can be too
30
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`.
31
+ * draws no code: the element fires `error`, carries `:state(overflow)`, and
32
+ * shows what is in the `fallback` slot, such as the link written out.
32
33
  *
33
34
  * `toSvg()`, `toPng()` and `download()` give the drawn code as a file of its
34
- * own, in the colors it is drawn in, for a page that offers it to save or
35
- * print, as Zag's download trigger does.
35
+ * own, in the colors it is drawn in, with a slotted image or SVG logo where
36
+ * it sits, for a page that offers it to save or print, as Zag's download
37
+ * trigger does.
36
38
  *
37
39
  * @since 0.1.0
38
40
  * @status stable
@@ -40,14 +42,18 @@ export type QrStyle = 'dots' | 'rounded' | 'square';
40
42
  * @aliases QR, Barcode, Scan code
41
43
  * @documentation https://fusion.dev/helix/components/qr-code
42
44
  *
45
+ * @slot fallback - Shown in place of the code when the value is too long for it, such as the link as text.
43
46
  * @slot logo - A logo for the center. A code with a logo scans only at a high correction level.
44
47
  *
45
48
  * @event {Event} error - The value is too long for a code at the correction level, so no code is drawn.
46
49
  *
47
50
  * @csspart code - The square code image.
51
+ * @csspart fallback - The box around the `fallback` slot, drawn only when the value is too long for a code.
48
52
  * @csspart finders - The three corner squares, which can take their own color.
49
53
  * @csspart logo - The box that holds the center logo.
50
54
  * @csspart modules - Every module except the three corner squares.
55
+ *
56
+ * @cssstate overflow - The value is too long for a code at the correction level, so the fallback shows.
51
57
  */
52
58
  export declare class HelixHeadlessQrCode extends HelixElement {
53
59
  /**
@@ -112,10 +118,18 @@ export declare class HelixHeadlessQrCode extends HelixElement {
112
118
  * is drawn in, with its background under the quiet zone.
113
119
  *
114
120
  * `''` where no code is drawn, before the first render or for a value too
115
- * long for the level. A slotted logo is not in the file, being the page's
116
- * own markup; save the file at `H` and the code still scans without it.
121
+ * long for the level. A slotted `<img>` or `<svg>` logo is written in where
122
+ * it is drawn: an image as its address, or as its pixels where the page may
123
+ * read them, which a PNG needs. A logo of text is left out, a file having
124
+ * none of the page's fonts; the code still scans without it.
117
125
  */
118
126
  toSvg(size?: number): string;
127
+ /**
128
+ * The slotted logo as the file draws it, measured against the drawn code, or
129
+ * nothing where none is slotted, where it is not over the code (the headless
130
+ * tier sets it beside it), or where it is neither an image nor an SVG.
131
+ */
132
+ private logoDrawing;
119
133
  /**
120
134
  * The code as a PNG, `size` pixels square, drawn from `toSvg()`.
121
135
  *