@fusion.dev/helix-headless 0.146.0 → 0.147.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 (239) hide show
  1. package/custom-elements.json +5266 -785
  2. package/dist/accordion/item/accordion-item.d.ts +38 -19
  3. package/dist/accordion/item/accordion-item.js +31 -9
  4. package/dist/accordion/root/accordion-root.d.ts +8 -2
  5. package/dist/accordion/root/accordion-root.js +3 -3
  6. package/dist/announcement/root/announcement-root.d.ts +13 -4
  7. package/dist/announcement/root/announcement-root.js +34 -6
  8. package/dist/area-chart/root/area-chart-root.d.ts +18 -1
  9. package/dist/area-chart/root/area-chart-root.js +11 -3
  10. package/dist/avatar/root/avatar-root.d.ts +31 -1
  11. package/dist/avatar/root/avatar-root.js +11 -1
  12. package/dist/badge/root/badge-root.d.ts +57 -6
  13. package/dist/badge/root/badge-root.js +88 -5
  14. package/dist/bar-chart/root/bar-chart-root.js +11 -20
  15. package/dist/bar-chart/stack.d.ts +10 -0
  16. package/dist/bar-chart/stack.js +8 -0
  17. package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +14 -0
  18. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +12 -2
  19. package/dist/breadcrumbs/root/breadcrumbs-root.js +2 -2
  20. package/dist/button/root/button-root.d.ts +24 -38
  21. package/dist/button/root/button-root.js +6 -37
  22. package/dist/button/submitter.d.ts +27 -0
  23. package/dist/button/submitter.js +25 -0
  24. package/dist/card/root/card-root.d.ts +12 -1
  25. package/dist/card/root/card-root.js +11 -4
  26. package/dist/chat/root/chat-root.d.ts +15 -1
  27. package/dist/chat/root/chat-root.js +11 -2
  28. package/dist/checkbox/root/checkbox-root.js +5 -7
  29. package/dist/checkbox-group/root/checkbox-group-root.js +4 -3
  30. package/dist/checkbox-group/select-all.d.ts +0 -6
  31. package/dist/checkbox-group/select-all.js +0 -5
  32. package/dist/code-block/root/code-block-root.d.ts +35 -14
  33. package/dist/code-block/root/code-block-root.js +17 -3
  34. package/dist/color-input/root/color-input-root.d.ts +11 -0
  35. package/dist/color-input/root/color-input-root.js +12 -5
  36. package/dist/color-picker/channels.js +1 -2
  37. package/dist/color-picker/root/color-picker-root.d.ts +32 -24
  38. package/dist/color-picker/root/color-picker-root.js +101 -51
  39. package/dist/color-picker/wheel.d.ts +17 -0
  40. package/dist/color-picker/wheel.js +6 -0
  41. package/dist/combobox/root/combobox-root.d.ts +13 -0
  42. package/dist/combobox/root/combobox-root.js +14 -1
  43. package/dist/command/root/command-root.d.ts +3 -1
  44. package/dist/command/root/command-root.js +2 -19
  45. package/dist/copy-button/root/copy-button-root.d.ts +16 -1
  46. package/dist/copy-button/root/copy-button-root.js +7 -2
  47. package/dist/data-table/keys.d.ts +0 -18
  48. package/dist/data-table/keys.js +1 -11
  49. package/dist/data-table/root/data-table-root.js +13 -7
  50. package/dist/date-input/root/date-input-root.d.ts +8 -1
  51. package/dist/date-input/root/date-input-root.js +8 -5
  52. package/dist/date-picker/root/date-picker-root.d.ts +14 -9
  53. package/dist/date-picker/root/date-picker-root.js +21 -16
  54. package/dist/description-list/item/description-list-item.d.ts +18 -4
  55. package/dist/description-list/item/description-list-item.js +8 -4
  56. package/dist/donut-chart/root/donut-chart-root.js +13 -6
  57. package/dist/donut-chart/slice-label.d.ts +35 -0
  58. package/dist/donut-chart/slice-label.js +11 -0
  59. package/dist/drawer/root/drawer-root.d.ts +4 -2
  60. package/dist/drawer/root/drawer-root.js +15 -3
  61. package/dist/drilldown/item/drilldown-item.d.ts +21 -1
  62. package/dist/drilldown/item/drilldown-item.js +39 -4
  63. package/dist/drilldown/root/drilldown-root.js +3 -2
  64. package/dist/element-events.generated.js +4 -1
  65. package/dist/empty/root/empty-root.d.ts +31 -1
  66. package/dist/empty/root/empty-root.js +26 -5
  67. package/dist/file-input/root/file-input-root.d.ts +34 -6
  68. package/dist/file-input/root/file-input-root.js +54 -19
  69. package/dist/file-input/status.d.ts +24 -0
  70. package/dist/file-input/status.js +10 -0
  71. package/dist/filter-input/chip/filter-chip.d.ts +23 -0
  72. package/dist/filter-input/chip/filter-chip.js +11 -2
  73. package/dist/filter-input/root/filter-input-root.d.ts +8 -0
  74. package/dist/filter-input/root/filter-input-root.js +17 -27
  75. package/dist/format-date/options.d.ts +3 -4
  76. package/dist/format-date/options.js +2 -2
  77. package/dist/format-date/root/format-date-root.d.ts +18 -1
  78. package/dist/format-date/root/format-date-root.js +19 -23
  79. package/dist/heatmap/root/buckets.d.ts +21 -0
  80. package/dist/heatmap/root/buckets.js +34 -0
  81. package/dist/heatmap/root/heatmap-root.d.ts +64 -3
  82. package/dist/heatmap/root/heatmap-root.js +89 -19
  83. package/dist/icon/index.d.ts +1 -1
  84. package/dist/icon/index.js +1 -1
  85. package/dist/icon/registry.d.ts +26 -1
  86. package/dist/icon/registry.js +12 -1
  87. package/dist/icon/root/icon-root.d.ts +13 -0
  88. package/dist/icon/root/icon-root.js +23 -2
  89. package/dist/icon/root/index.d.ts +1 -1
  90. package/dist/icon/root/index.js +1 -1
  91. package/dist/image/root/image-root.d.ts +9 -0
  92. package/dist/image/root/image-root.js +6 -1
  93. package/dist/input/root/input-root.d.ts +23 -2
  94. package/dist/input/root/input-root.js +12 -8
  95. package/dist/kbd/keys.d.ts +29 -0
  96. package/dist/kbd/keys.js +52 -0
  97. package/dist/kbd/root/kbd-root.d.ts +20 -3
  98. package/dist/kbd/root/kbd-root.js +24 -50
  99. package/dist/legend/hidden-series.d.ts +10 -2
  100. package/dist/legend/hidden-series.js +5 -2
  101. package/dist/legend/root/legend-root.d.ts +25 -7
  102. package/dist/legend/root/legend-root.js +22 -10
  103. package/dist/lightbox/root/lightbox-root.d.ts +50 -5
  104. package/dist/lightbox/root/lightbox-root.js +76 -8
  105. package/dist/lightbox/zoom.d.ts +2 -0
  106. package/dist/lightbox/zoom.js +5 -3
  107. package/dist/line-chart/root/line-chart-root.d.ts +16 -0
  108. package/dist/line-chart/root/line-chart-root.js +10 -2
  109. package/dist/list/root/list-root.d.ts +10 -2
  110. package/dist/list/root/list-root.js +26 -5
  111. package/dist/list/selection.d.ts +17 -0
  112. package/dist/list/selection.js +16 -0
  113. package/dist/listbox/root/listbox-root.d.ts +9 -5
  114. package/dist/listbox/root/listbox-root.js +29 -37
  115. package/dist/megamenu/hand-over.d.ts +75 -0
  116. package/dist/megamenu/hand-over.js +67 -0
  117. package/dist/megamenu/root/megamenu-root.d.ts +38 -14
  118. package/dist/megamenu/root/megamenu-root.js +36 -50
  119. package/dist/menu/item/menu-item.js +4 -4
  120. package/dist/menu/root/menu-root.d.ts +3 -0
  121. package/dist/meter/root/meter-root.d.ts +42 -0
  122. package/dist/meter/root/meter-root.js +33 -7
  123. package/dist/notifications/root/notifications-root.d.ts +10 -1
  124. package/dist/notifications/root/notifications-root.js +11 -1
  125. package/dist/number-input/grain.d.ts +28 -0
  126. package/dist/number-input/grain.js +14 -0
  127. package/dist/number-input/root/number-input-root.d.ts +17 -1
  128. package/dist/number-input/root/number-input-root.js +29 -18
  129. package/dist/object-inspector/root/object-inspector-root.d.ts +13 -6
  130. package/dist/object-inspector/root/object-inspector-root.js +18 -17
  131. package/dist/option/index.d.ts +1 -0
  132. package/dist/option/index.js +1 -0
  133. package/dist/option/separator/option-separator.d.ts +30 -0
  134. package/dist/option/separator/option-separator.js +21 -0
  135. package/dist/outline/root/outline-root.d.ts +12 -0
  136. package/dist/outline/root/outline-root.js +5 -19
  137. package/dist/pagination/size/pagination-size.d.ts +5 -0
  138. package/dist/pagination/size/pagination-size.js +4 -2
  139. package/dist/pin-input/code.d.ts +28 -1
  140. package/dist/pin-input/code.js +12 -0
  141. package/dist/pin-input/root/pin-input-root.d.ts +98 -121
  142. package/dist/pin-input/root/pin-input-root.js +157 -148
  143. package/dist/popover/hide-outside.js +2 -9
  144. package/dist/popover/root/popover-root.d.ts +7 -1
  145. package/dist/popover/root/popover-root.js +9 -1
  146. package/dist/progress/root/progress-root.d.ts +27 -0
  147. package/dist/progress/root/progress-root.js +25 -7
  148. package/dist/qr-code/export.d.ts +19 -0
  149. package/dist/qr-code/export.js +14 -0
  150. package/dist/qr-code/name.d.ts +11 -0
  151. package/dist/qr-code/name.js +7 -0
  152. package/dist/qr-code/root/qr-code-root.d.ts +17 -6
  153. package/dist/qr-code/root/qr-code-root.js +10 -12
  154. package/dist/radio/group/radio-group.d.ts +15 -0
  155. package/dist/radio/root/radio-root.js +2 -2
  156. package/dist/rating/root/rating-root.d.ts +22 -9
  157. package/dist/rating/root/rating-root.js +15 -39
  158. package/dist/scatter-chart/root/scatter-chart-root.d.ts +30 -0
  159. package/dist/scatter-chart/root/scatter-chart-root.js +34 -7
  160. package/dist/scroll-area/scrollbar.d.ts +0 -8
  161. package/dist/scroll-area/scrollbar.js +1 -1
  162. package/dist/select/align.d.ts +47 -0
  163. package/dist/select/align.js +7 -0
  164. package/dist/select/root/select-root.d.ts +59 -3
  165. package/dist/select/root/select-root.js +72 -12
  166. package/dist/slider/root/slider-root.d.ts +18 -1
  167. package/dist/slider/root/slider-root.js +22 -0
  168. package/dist/sparkline/readings.d.ts +30 -12
  169. package/dist/sparkline/readings.js +32 -15
  170. package/dist/sparkline/root/sparkline-root.d.ts +46 -3
  171. package/dist/sparkline/root/sparkline-root.js +60 -21
  172. package/dist/speed-dial/item/speed-dial-item.d.ts +1 -1
  173. package/dist/speed-dial/item/speed-dial-item.js +2 -2
  174. package/dist/speed-dial/root/speed-dial-root.d.ts +6 -0
  175. package/dist/speed-dial/root/speed-dial-root.js +9 -1
  176. package/dist/splitter/root/splitter-root.d.ts +3 -0
  177. package/dist/splitter/root/splitter-root.js +2 -0
  178. package/dist/steps/root/steps-root.d.ts +8 -1
  179. package/dist/steps/root/steps-root.js +6 -1
  180. package/dist/steps/step/step.d.ts +1 -0
  181. package/dist/steps/step/step.js +1 -1
  182. package/dist/table/column/table-column.d.ts +6 -0
  183. package/dist/table/column/table-column.js +5 -1
  184. package/dist/table/root/table-root.js +13 -6
  185. package/dist/table/row/table-row.d.ts +24 -0
  186. package/dist/table/row/table-row.js +37 -1
  187. package/dist/tabs/root/tabs-root.d.ts +5 -3
  188. package/dist/tag/group/tag-group.d.ts +16 -4
  189. package/dist/tag/group/tag-group.js +19 -7
  190. package/dist/textarea/root/textarea-root.d.ts +14 -2
  191. package/dist/textarea/root/textarea-root.js +6 -7
  192. package/dist/time-input/root/time-input-root.d.ts +5 -1
  193. package/dist/time-input/root/time-input-root.js +10 -7
  194. package/dist/time-picker/root/time-picker-root.d.ts +11 -14
  195. package/dist/time-picker/root/time-picker-root.js +15 -23
  196. package/dist/toast/item/toast-item.d.ts +3 -3
  197. package/dist/toast/item/toast-item.js +0 -1
  198. package/dist/toast/root/toast-root.d.ts +28 -4
  199. package/dist/toast/root/toast-root.js +58 -12
  200. package/dist/toast/words.d.ts +12 -0
  201. package/dist/toast/words.js +25 -0
  202. package/dist/toggle/group/toggle-group.d.ts +29 -5
  203. package/dist/toggle/group/toggle-group.js +26 -41
  204. package/dist/toggle/root/toggle-root.js +2 -4
  205. package/dist/toolbar/root/toolbar-root.d.ts +21 -5
  206. package/dist/toolbar/root/toolbar-root.js +52 -8
  207. package/dist/tooltip/root/tooltip-root.d.ts +7 -1
  208. package/dist/tooltip/root/tooltip-root.js +10 -1
  209. package/dist/tour/root/tour-root.d.ts +15 -0
  210. package/dist/tour/root/tour-root.js +20 -1
  211. package/dist/tour/stop/tour-stop.d.ts +3 -0
  212. package/dist/tour/stop/tour-stop.js +3 -3
  213. package/dist/tree/root/tree-root.js +2 -2
  214. package/dist/trend/figure.d.ts +16 -0
  215. package/dist/trend/figure.js +8 -0
  216. package/dist/trend/root/trend-root.d.ts +41 -2
  217. package/dist/trend/root/trend-root.js +29 -3
  218. package/dist/video/root/extra-controls.d.ts +12 -0
  219. package/dist/video/root/extra-controls.js +6 -2
  220. package/dist/video/root/video-root.d.ts +5 -1
  221. package/dist/video-playlist/root/video-playlist-root.d.ts +11 -1
  222. package/dist/video-playlist/root/video-playlist-root.js +3 -0
  223. package/dist/virtual-list/fill.d.ts +11 -0
  224. package/dist/virtual-list/fill.js +9 -0
  225. package/dist/virtual-list/keys.d.ts +16 -7
  226. package/dist/virtual-list/keys.js +17 -6
  227. package/dist/virtual-list/plan.d.ts +17 -1
  228. package/dist/virtual-list/plan.js +13 -2
  229. package/dist/virtual-list/root/virtual-list-root.d.ts +25 -3
  230. package/dist/virtual-list/root/virtual-list-root.js +48 -24
  231. package/lint.json +1 -1
  232. package/package.json +5 -5
  233. package/types/jsx.d.ts +2979 -738
  234. package/types/svelte.d.ts +1 -0
  235. package/types/vue.d.ts +619 -202
  236. package/dist/color-picker/swatches.d.ts +0 -12
  237. package/dist/color-picker/swatches.js +0 -26
  238. package/dist/rating/steps.d.ts +0 -7
  239. package/dist/rating/steps.js +0 -9
@@ -4,16 +4,18 @@ 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, submitImplicitly, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, customElement, fillTemplate, HelixCompleteEvent, HelixFormElement, MixinControllerValue, MixinField, restoreValue, submitImplicitly, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
- import { property } from 'lit/decorators.js';
10
- import { cleanCode } from '../code.js';
9
+ import { property, state } from 'lit/decorators.js';
10
+ import { cleanCode, overwriteCode, readTyped, } from '../code.js';
11
11
  import { separatorsAfter } from '../format.js';
12
+ const UNSEEN = 'position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;font-size:16px';
13
+ const keepFocus = (event) => event.preventDefault();
12
14
  let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerValue(MixinField(HelixFormElement)) {
13
15
  constructor() {
14
16
  super(...arguments);
15
17
  this.aria = new AriaForwardController(this, {
16
- target: () => this.partNamed('field'),
18
+ target: () => this.control,
17
19
  });
18
20
  this.announcesInput = true;
19
21
  this.autocomplete = 'one-time-code';
@@ -23,95 +25,85 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
23
25
  this.format = '';
24
26
  this.length = 6;
25
27
  this.mask = false;
28
+ this.maskCharacter = '•';
26
29
  this.placeholder = '';
27
30
  this.readOnly = false;
28
31
  this.type = 'numeric';
32
+ this.active = -1;
33
+ this.pressedAt = undefined;
29
34
  this.handleInput = (event) => {
30
35
  event.stopPropagation();
31
- const cell = event.target;
32
- const at = this.cells.indexOf(cell);
33
- if (at === -1) {
34
- return;
35
- }
36
- if (cell.value === '') {
37
- const characters = this.characters;
38
- characters[at] = '';
39
- this.requestValue(characters.join(''));
40
- this.updateComplete.then(() => {
41
- this.paint();
42
- this.cells[at]?.focus();
43
- });
44
- return;
45
- }
46
- this.write(at, cell.value);
47
- this.updateComplete.then(() => this.paint());
36
+ const control = event.target;
37
+ this.take(readTyped(control.value, control.selectionStart ?? control.value.length, this.type, this.case, this.count));
48
38
  };
49
39
  this.handlePaste = (event) => {
50
- const cell = event.target;
51
- const at = this.cells.indexOf(cell);
52
- if (at === -1 || this.isDisabled || this.readOnly) {
40
+ event.preventDefault();
41
+ const control = this.control;
42
+ if (!control || this.isDisabled || this.readOnly) {
53
43
  return;
54
44
  }
55
- event.preventDefault();
56
- this.write(at, event.clipboardData?.getData('text') ?? '');
45
+ const text = cleanCode(event.clipboardData?.getData('text') ?? '', this.type, this.case);
46
+ if (text === '') {
47
+ return;
48
+ }
49
+ const start = control.selectionStart ?? this.end;
50
+ const end = control.selectionEnd ?? start;
51
+ const kept = end - start > 1 ? this.value.slice(0, start) + this.value.slice(end) : this.value;
52
+ this.take(overwriteCode(kept, start, text, this.count));
57
53
  };
58
54
  this.handleKeydown = (event) => {
59
- const cell = event.target;
60
- const at = this.cells.indexOf(cell);
61
- if (at === -1 || this.isDisabled || this.readOnly) {
55
+ if (event.isComposing || this.isDisabled) {
62
56
  return;
63
57
  }
64
- const step = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0;
65
- const to = event.key === 'Home'
66
- ? 0
67
- : event.key === 'End'
68
- ? Math.min(this.value.length, this.count - 1)
69
- : at + step;
70
- if (event.key === 'Delete' && at < this.value.length) {
71
- const characters = this.characters;
72
- characters.splice(at, 1);
73
- this.requestValue(characters.join('').replace(/\s+$/, ''));
74
- this.updateComplete.then(() => {
75
- this.paint();
76
- this.cells[at]?.focus();
77
- });
78
- }
79
- else if (event.key === 'Backspace' && cell.value === '' && at > 0) {
80
- const characters = this.characters;
81
- characters[at - 1] = '';
82
- this.requestValue(characters.join('').replace(/\s+$/, ''));
83
- this.updateComplete.then(() => {
84
- this.paint();
85
- this.cells[at - 1]?.focus();
86
- });
87
- }
88
- else if (to !== at && to >= 0 && to < this.count) {
89
- this.cells[to]?.focus();
90
- this.cells[to]?.select();
58
+ if (this.readOnly) {
59
+ if (event.key === 'Backspace' || event.key === 'Delete') {
60
+ event.preventDefault();
61
+ }
62
+ return;
91
63
  }
92
- else {
64
+ const to = event.key === 'ArrowRight'
65
+ ? this.caretBox + 1
66
+ : event.key === 'ArrowLeft'
67
+ ? this.caretBox - 1
68
+ : event.key === 'Home'
69
+ ? 0
70
+ : event.key === 'End'
71
+ ? this.end
72
+ : undefined;
73
+ if (to === undefined) {
93
74
  return;
94
75
  }
95
76
  event.preventDefault();
77
+ this.active = Math.max(0, Math.min(to, this.end));
78
+ this.placeCaret(true);
96
79
  };
97
- this.committed = '';
98
- this.handleFocus = (event) => {
99
- ;
100
- event.target.select();
101
- };
102
- this.within = false;
103
- this.handleFocusIn = () => {
104
- if (this.within) {
80
+ this.handlePress = (event) => {
81
+ const control = this.control;
82
+ if (!control || this.isDisabled) {
105
83
  return;
106
84
  }
107
- this.within = true;
108
- this.committed = this.value;
109
- };
110
- this.handleFocusOut = (event) => {
111
- if (this.cells.includes(event.relatedTarget)) {
85
+ const boxes = [...this.renderRoot.querySelectorAll('[part~="cell"]')];
86
+ const box = event.target.closest('[part~="cell"]');
87
+ const at = box ? boxes.indexOf(box) : this.end;
88
+ if (this.shadowRoot?.activeElement === control) {
89
+ if (!this.readOnly) {
90
+ this.active = Math.min(at, this.end);
91
+ this.placeCaret(true);
92
+ }
112
93
  return;
113
94
  }
114
- this.within = false;
95
+ this.pressedAt = at;
96
+ control.focus();
97
+ };
98
+ this.committed = '';
99
+ this.handleFocus = () => {
100
+ this.committed = this.value;
101
+ this.active = this.readOnly ? -1 : Math.min(this.pressedAt ?? this.end, this.end);
102
+ this.pressedAt = undefined;
103
+ this.placeCaret(true);
104
+ };
105
+ this.handleBlur = () => {
106
+ this.active = -1;
115
107
  this.commit();
116
108
  };
117
109
  this.handleKeypress = (event) => {
@@ -120,9 +112,6 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
120
112
  }
121
113
  };
122
114
  }
123
- get namedControl() {
124
- return this.partNamed('field');
125
- }
126
115
  static { this.shadowRootOptions = {
127
116
  ...HelixFormElement.shadowRootOptions,
128
117
  delegatesFocus: true,
@@ -130,49 +119,45 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
130
119
  get count() {
131
120
  return Math.max(1, Math.floor(this.length) || 1);
132
121
  }
133
- get cells() {
134
- return [...this.renderRoot.querySelectorAll('[part="cell"]')];
122
+ get control() {
123
+ return this.partNamed('control');
135
124
  }
136
- get characters() {
137
- return Array.from({ length: this.count }, (_, at) => this.value[at] ?? '');
125
+ get end() {
126
+ return Math.min(this.value.length, this.count - 1);
138
127
  }
139
- clean(text) {
140
- return cleanCode(text, this.type, this.case);
128
+ get caretBox() {
129
+ return this.active < 0 ? -1 : Math.min(this.active, this.end);
141
130
  }
142
- write(from, text) {
143
- const cleaned = this.clean(text);
144
- if (cleaned === '') {
145
- return;
131
+ take({ caret, code }) {
132
+ const applied = this.requestValue(code);
133
+ const control = this.control;
134
+ if (control) {
135
+ restoreValue(control, this.value);
146
136
  }
147
- const start = Math.min(from, this.value.length);
148
- const characters = this.characters;
149
- for (let at = 0; at < cleaned.length && start + at < this.count; at += 1) {
150
- characters[start + at] = cleaned[at];
137
+ if (this.active >= 0 && applied) {
138
+ this.active = caret;
151
139
  }
152
- const next = characters.join('');
153
- const filled = Math.min(this.count - 1, start + cleaned.length);
154
- const applied = this.requestValue(next);
155
- this.updateComplete.then(() => {
156
- this.paint();
157
- this.cells[filled]?.focus();
158
- this.cells[filled]?.select();
159
- if (applied && this.value === next && next.length === this.count) {
160
- this.dispatchEvent(new HelixCompleteEvent({ value: next }));
161
- this.commit();
162
- if (this.autosubmit) {
163
- submitImplicitly(this.form);
164
- }
140
+ this.placeCaret(true);
141
+ if (applied && this.value === code && code.length === this.count) {
142
+ this.dispatchEvent(new HelixCompleteEvent({ value: code }));
143
+ this.commit();
144
+ if (this.autosubmit) {
145
+ submitImplicitly(this.form);
165
146
  }
166
- });
147
+ }
167
148
  }
168
- paint() {
169
- const characters = this.characters;
170
- for (const [at, cell] of this.cells.entries()) {
171
- const want = characters[at] ?? '';
172
- if (cell.value !== want) {
173
- cell.value = want;
174
- }
149
+ placeCaret(force = false) {
150
+ const control = this.control;
151
+ const at = this.caretBox;
152
+ if (!control || at < 0) {
153
+ return;
175
154
  }
155
+ const start = control.selectionStart ?? 0;
156
+ const end = control.selectionEnd ?? start;
157
+ if (!force && end - start > 1) {
158
+ return;
159
+ }
160
+ control.setSelectionRange(at, at < this.value.length ? at + 1 : at);
176
161
  }
177
162
  commit() {
178
163
  if (this.value === this.committed) {
@@ -183,34 +168,34 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
183
168
  }
184
169
  sync() {
185
170
  this.internals.setFormValue(this.value);
186
- const first = this.cells[0];
187
- if (!first) {
171
+ const control = this.control;
172
+ if (!control) {
188
173
  return;
189
174
  }
190
175
  if (this.required && this.value === '') {
191
- this.internals.setValidity({ valueMissing: true }, this.valueMissingMessage || 'Please fill in this field.', first);
176
+ this.internals.setValidity({ valueMissing: true }, this.valueMissingMessage || 'Please fill in this field.', control);
192
177
  }
193
178
  else if (this.value !== '' && this.value.length < this.count) {
194
179
  this.internals.setValidity({ tooShort: true }, fillTemplate(this.tooShortMessage || 'Please enter all {length} characters.', {
195
180
  length: this.count,
196
- }), first);
181
+ }), control);
197
182
  }
198
183
  else {
199
- this.internals.setValidity({}, '', first);
184
+ this.internals.setValidity({}, '', control);
200
185
  }
201
186
  this.refreshFeedback();
202
187
  }
203
188
  focus(options) {
204
- const cells = this.cells;
205
- const at = Math.min(this.value.length, cells.length - 1);
206
- cells[at]?.focus(options);
189
+ this.control?.focus(options);
207
190
  }
208
191
  clear() {
209
192
  this.requestValue('');
210
- this.updateComplete.then(() => {
211
- this.paint();
212
- this.cells[0]?.focus();
213
- });
193
+ const control = this.control;
194
+ if (control) {
195
+ restoreValue(control, this.value);
196
+ this.pressedAt = 0;
197
+ control.focus();
198
+ }
214
199
  }
215
200
  firstUpdated(changed) {
216
201
  super.firstUpdated(changed);
@@ -219,44 +204,62 @@ let HelixHeadlessPinInput = class HelixHeadlessPinInput extends MixinControllerV
219
204
  updated(changed) {
220
205
  super.updated(changed);
221
206
  this.toggleState('complete', this.value.length >= this.count);
222
- this.paint();
223
207
  this.sync();
208
+ this.placeCaret();
224
209
  }
225
210
  get blocksImplicitSubmission() {
226
211
  return true;
227
212
  }
213
+ renderBox(character, at) {
214
+ const caret = at === this.caretBox;
215
+ const content = character !== ''
216
+ ? this.mask
217
+ ? this.maskCharacter
218
+ : character
219
+ : caret
220
+ ? html `<span part="caret"></span>`
221
+ : this.placeholder
222
+ ? html `<span part="placeholder">${this.placeholder}</span>`
223
+ : nothing;
224
+ return html `<div
225
+ part="cell${caret ? ' cell-active' : ''}${character === '' ? '' : ' cell-filled'}"
226
+ aria-hidden="true"
227
+ >${content}</div>`;
228
+ }
228
229
  render() {
229
- const characters = this.characters;
230
230
  const separators = separatorsAfter(this.format, this.count);
231
- return html `${this.renderGroupLabel()}
231
+ return html `${this.renderLabel()}
232
232
  <div
233
233
  dir="ltr"
234
234
  part="field"
235
- role="group"
236
- aria-describedby=${this.describedBy ?? nothing}
237
- aria-labelledby=${this.labeled ? this.labelId : nothing}
238
- @focusin=${this.handleFocusIn}
239
- @focusout=${this.handleFocusOut}
235
+ @click=${this.handlePress}
236
+ @mousedown=${keepFocus}
240
237
  >
241
- ${characters.map((character, at) => html `<input
242
- part="cell"
243
- .value=${character}
244
- type=${this.mask ? 'password' : 'text'}
245
- placeholder=${this.placeholder || nothing}
246
- inputmode=${this.type === 'numeric' ? 'numeric' : 'text'}
247
- autocomplete=${at === 0 ? this.autocomplete || nothing : 'off'}
248
- aria-label=${fillTemplate(this.cellLabel, { index: at + 1, length: this.count })}
249
- aria-invalid=${this.invalidity ?? nothing}
250
- ?disabled=${this.isDisabled}
251
- ?readonly=${this.readOnly}
252
- @focus=${this.handleFocus}
253
- @input=${this.handleInput}
254
- @keydown=${this.handleKeydown}
255
- @keypress=${this.handleKeypress}
256
- @paste=${this.handlePaste}
257
- />${separators[at]
258
- ? html `<span part="separator" aria-hidden="true">${separators[at]}</span>`
259
- : nothing}`)}
238
+ <input
239
+ part="control"
240
+ id=${this.controlId}
241
+ style=${UNSEEN}
242
+ .value=${this.value}
243
+ type=${this.mask ? 'password' : 'text'}
244
+ inputmode=${this.type === 'numeric' ? 'numeric' : 'text'}
245
+ autocomplete=${this.autocomplete || nothing}
246
+ aria-describedby=${this.describedBy ?? nothing}
247
+ aria-invalid=${this.invalidity ?? nothing}
248
+ aria-required=${this.required ? 'true' : nothing}
249
+ ?disabled=${this.isDisabled}
250
+ ?readonly=${this.readOnly}
251
+ @blur=${this.handleBlur}
252
+ @focus=${this.handleFocus}
253
+ @input=${this.handleInput}
254
+ @keydown=${this.handleKeydown}
255
+ @keypress=${this.handleKeypress}
256
+ @paste=${this.handlePaste}
257
+ />${Array.from({ length: this.count }, (_, at) => {
258
+ const separator = separators[at];
259
+ return html `${this.renderBox(this.value[at] ?? '', at)}${separator
260
+ ? html `<span part="separator" aria-hidden="true">${separator}</span>`
261
+ : nothing}`;
262
+ })}
260
263
  </div>
261
264
  ${this.renderHint()}${this.renderFeedback()}`;
262
265
  }
@@ -282,6 +285,9 @@ __decorate([
282
285
  __decorate([
283
286
  property({ reflect: true, type: Boolean })
284
287
  ], HelixHeadlessPinInput.prototype, "mask", void 0);
288
+ __decorate([
289
+ property({ attribute: 'mask-character' })
290
+ ], HelixHeadlessPinInput.prototype, "maskCharacter", void 0);
285
291
  __decorate([
286
292
  property()
287
293
  ], HelixHeadlessPinInput.prototype, "placeholder", void 0);
@@ -294,6 +300,9 @@ __decorate([
294
300
  __decorate([
295
301
  property()
296
302
  ], HelixHeadlessPinInput.prototype, "type", void 0);
303
+ __decorate([
304
+ state()
305
+ ], HelixHeadlessPinInput.prototype, "active", void 0);
297
306
  HelixHeadlessPinInput = __decorate([
298
307
  customElement('helix-headless-pin-input')
299
308
  ], HelixHeadlessPinInput);
@@ -1,7 +1,8 @@
1
+ import { deepContains } from '@fusion.dev/helix-core';
1
2
  const KEEP = 'script, style, template, link, meta, [aria-live], [role="alert"], [role="log"], [role="status"]';
2
3
  export const hideOutside = (kept) => {
3
4
  const hidden = [];
4
- const holds = (element) => kept.some((one) => composedContains(element, one));
5
+ const holds = (element) => kept.some((one) => deepContains(element, one));
5
6
  const seen = new Set();
6
7
  for (const start of kept) {
7
8
  let node = start;
@@ -31,11 +32,3 @@ export const hideOutside = (kept) => {
31
32
  hidden.length = 0;
32
33
  };
33
34
  };
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
- };
@@ -1,4 +1,4 @@
1
- import { AriaForwardController, CommandController, HelixElement, type Placement } from '@fusion.dev/helix-core';
1
+ import { AriaForwardController, CommandController, HelixElement, OpenPhaseController, 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
  /**
@@ -69,6 +69,11 @@ declare const HelixHeadlessPopover_base: (new (...args: any[]) => import("@fusio
69
69
  * @csspart content - The scrolling region inside the panel.
70
70
  * @csspart popover - The panel.
71
71
  *
72
+ * @cssproperty --floating-anchor-inline-size - The trigger's width, written on the panel while it is open, as in `min-inline-size: var(--floating-anchor-inline-size)`. Read it; a value set here is written over.
73
+ * @cssproperty --floating-available-block-size - The room between the panel and the edge of the screen it opens toward, written on the panel while it is open. Read it; a value set here is written over.
74
+ *
75
+ * @cssstate entering - From the moment the panel shows until its entrance has finished.
76
+ * @cssstate exiting - From the moment the panel hides until its exit has finished.
72
77
  * @cssstate open - While the popover is open.
73
78
  * @cssstate placed-bottom - While the panel sits below its trigger.
74
79
  * @cssstate placed-left - While the panel sits to the left of its trigger.
@@ -105,6 +110,7 @@ export declare class HelixHeadlessPopover extends HelixHeadlessPopover_base {
105
110
  * It moves if that side does not fit.
106
111
  */
107
112
  placement: Placement;
113
+ protected readonly phases: OpenPhaseController;
108
114
  private readonly panel;
109
115
  /**
110
116
  * The side of its trigger the panel last landed on, which names the arrow's
@@ -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 { AriaForwardController, anchorElement, arrowReach, CommandController, customElement, deepActiveElement, FocusScopeController, HelixElement, headingIn, MixinControllerOpen, PopoverController, reflectElements, sideOf, tellDisclosure, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, anchorElement, arrowReach, CommandController, customElement, deepActiveElement, FocusScopeController, HelixElement, headingIn, MixinControllerOpen, OpenPhaseController, 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
10
  import { hideOutside } from '../hide-outside.js';
@@ -19,6 +19,14 @@ let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpe
19
19
  });
20
20
  this.offset = 4;
21
21
  this.placement = 'bottom-start';
22
+ this.phases = new OpenPhaseController(this, {
23
+ mark: {
24
+ entering: (on) => this.toggleState('entering', on),
25
+ exiting: (on) => this.toggleState('exiting', on),
26
+ },
27
+ open: () => this.open,
28
+ surface: () => this.partNamed('popover'),
29
+ });
22
30
  this.panel = new PopoverController(this, {
23
31
  anchor: () => this.trigger,
24
32
  arrow: () => this.partNamed('arrow'),
@@ -15,6 +15,13 @@ import { type PropertyValues, type TemplateResult } from 'lit';
15
15
  *
16
16
  * The fill is a fraction of the track, written as a custom property.
17
17
  *
18
+ * **Reaching `max` is said and sent.** When the value arrives at `max` from
19
+ * below, the bar says `complete-label`, "Complete" by default, in a polite
20
+ * `live` region and sends `helix-complete`, so a reader hears that the work is
21
+ * done and a page can move on. A bar that starts full says nothing, and an
22
+ * empty `complete-label` keeps it quiet. The `complete` custom state is set
23
+ * while the bar is full.
24
+ *
18
25
  * @since 0.1.0
19
26
  * @status stable
20
27
  * @category feedback
@@ -23,14 +30,27 @@ import { type PropertyValues, type TemplateResult } from 'lit';
23
30
  *
24
31
  * @slot label - A visible label. Its words name the progress bar in place of `label`.
25
32
  *
33
+ * @event {HelixCompleteEvent} helix-complete - The value reached `max` from below. Its `value` is `max`. Not sent for a bar that starts full.
34
+ *
26
35
  * @csspart groove - The unfilled circle under the ring's filled arc.
27
36
  * @csspart indicator - The filled part that shows the work done.
37
+ * @csspart live - A polite region that says `complete-label` when the work is done, rendered from that moment. Hide it visually.
28
38
  * @csspart track - The full length the work is measured against.
29
39
  * @csspart value - The value shown beside the bar when `show-value` is set.
30
40
  *
31
41
  * @cssproperty --progress-fraction - The value as a fraction of the range, 0 to 1. Pinned to 1 while indeterminate.
42
+ *
43
+ * @cssstate complete - The value is at `max` and the bar is not indeterminate.
32
44
  */
33
45
  export declare class HelixHeadlessProgress extends HelixElement {
46
+ /**
47
+ * The words a screen reader hears when the value reaches `max`, such as
48
+ * "Upload complete".
49
+ *
50
+ * Said once, politely, as the work finishes. Empty says nothing, for a bar
51
+ * whose page announces the result itself.
52
+ */
53
+ completeLabel: string;
34
54
  /**
35
55
  * `Intl.NumberFormat` options the reading is written with, as React Aria's
36
56
  * Meter and ProgressBar take them: a unit, a currency, or a number of
@@ -128,11 +148,18 @@ export declare class HelixHeadlessProgress extends HelixElement {
128
148
  * report progress.
129
149
  */
130
150
  valueText: string;
151
+ /** The region the bar says `complete-label` in, rendered from the first time it does. */
152
+ private readonly status;
153
+ /** Whether the bar was full at the last update, or unknown before the first. */
154
+ private wasComplete;
155
+ /** Whether the work is done: determinate and at `max`. */
156
+ private get complete();
131
157
  /** The range the value is measured in, with a top above its bottom. */
132
158
  private get range();
133
159
  /** What is done, as a fraction of the whole, between 0 and 1. */
134
160
  get fraction(): number;
135
161
  protected willUpdate(changed: PropertyValues): void;
162
+ protected updated(changed: PropertyValues): void;
136
163
  /**
137
164
  * What the bar says: the consumer's words, or the reading in
138
165
  * `format-options`, a share of the range for a percent and the value for
@@ -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, formatNumber, HelixElement, hostAria, slottedWords, } from '@fusion.dev/helix-core';
7
+ import { customElement, drawStatus, HelixCompleteEvent, HelixElement, hostAria, readingText, StatusController, slottedWords, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
- const WHOLE_PERCENT = { maximumFractionDigits: 0, style: 'percent' };
11
10
  let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
12
11
  constructor() {
13
12
  super(...arguments);
13
+ this.completeLabel = 'Complete';
14
14
  this.indeterminate = false;
15
15
  this.label = 'Progress';
16
16
  this.locale = '';
@@ -19,10 +19,14 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
19
19
  this.showValue = false;
20
20
  this.value = 0;
21
21
  this.valueText = '';
22
+ this.status = new StatusController(this);
22
23
  this.renamed = () => {
23
24
  this.requestUpdate();
24
25
  };
25
26
  }
27
+ get complete() {
28
+ return !this.indeterminate && this.fraction >= 1;
29
+ }
26
30
  get range() {
27
31
  const min = Number.isFinite(this.min) ? this.min : 0;
28
32
  return { max: this.max > min ? this.max : min + 100, min };
@@ -45,13 +49,24 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
45
49
  : String(Math.min(Math.max(this.value || min, min), max));
46
50
  this.internals.ariaValueText =
47
51
  this.valueText || (this.formatOptions !== undefined && !this.indeterminate ? this.text : null);
52
+ this.toggleState('complete', this.complete);
53
+ }
54
+ updated(changed) {
55
+ super.updated(changed);
56
+ const complete = this.complete;
57
+ const arrived = complete && this.wasComplete === false;
58
+ this.wasComplete = complete;
59
+ if (!arrived) {
60
+ return;
61
+ }
62
+ if (this.completeLabel !== '') {
63
+ this.status.say(this.completeLabel);
64
+ }
65
+ this.dispatchEvent(new HelixCompleteEvent({ value: String(this.range.max) }));
48
66
  }
49
67
  get text() {
50
- const options = this.formatOptions;
51
68
  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)));
69
+ readingText(Math.min(Math.max(this.value, this.range.min), this.range.max), this.fraction, this.locale, this.formatOptions));
55
70
  }
56
71
  render() {
57
72
  const fraction = this.indeterminate ? 1 : this.fraction;
@@ -68,9 +83,12 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
68
83
  ? html `<span part="value" aria-hidden="true"
69
84
  >${this.text}</span
70
85
  >`
71
- : ''}`;
86
+ : ''}${drawStatus(this.status)}`;
72
87
  }
73
88
  };
89
+ __decorate([
90
+ property({ attribute: 'complete-label' })
91
+ ], HelixHeadlessProgress.prototype, "completeLabel", void 0);
74
92
  __decorate([
75
93
  property({ attribute: 'format-options', type: Object })
76
94
  ], HelixHeadlessProgress.prototype, "formatOptions", void 0);
@@ -39,6 +39,25 @@ export interface QrLogo {
39
39
  /** An inline SVG's markup, written as a nested `<svg>` at the mark's place. */
40
40
  svg?: string;
41
41
  }
42
+ /** A rectangle the page measured, as `getBoundingClientRect` gives one. */
43
+ export interface QrRect {
44
+ readonly height: number;
45
+ readonly left: number;
46
+ readonly top: number;
47
+ readonly width: number;
48
+ }
49
+ /**
50
+ * A rectangle measured on the page as a place in the drawing: `area` is the
51
+ * drawn code's own rectangle and `box` the side of its `viewBox`, so a logo
52
+ * set over the code by a stylesheet lands where it was drawn.
53
+ */
54
+ export declare const boxIn: (rect: QrRect, area: QrRect, box: number) => QrBox;
55
+ /**
56
+ * The logo's box in the drawing, or nothing where any of it falls outside the
57
+ * code, such as a logo the headless tier sets beside the code rather than
58
+ * over it, which a file of the code alone has no place for.
59
+ */
60
+ export declare const logoBox: (rect: QrRect, area: QrRect, box: number) => QrBox | undefined;
42
61
  /**
43
62
  * The logo as the file writes it: its box, then the mark, as an `<image>` or as
44
63
  * the slotted SVG moved to the mark's place. Nothing for a mark that is