@fusion.dev/helix-headless 0.146.0 → 0.148.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/custom-elements.json +9287 -1084
  2. package/dist/accordion/item/accordion-item.d.ts +43 -21
  3. package/dist/accordion/item/accordion-item.js +37 -11
  4. package/dist/accordion/root/accordion-root.d.ts +8 -2
  5. package/dist/accordion/root/accordion-root.js +6 -8
  6. package/dist/announcement/root/announcement-root.d.ts +66 -6
  7. package/dist/announcement/root/announcement-root.js +144 -13
  8. package/dist/area-chart/root/area-chart-root.d.ts +18 -1
  9. package/dist/area-chart/root/area-chart-root.js +14 -7
  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/count.d.ts +14 -0
  13. package/dist/badge/count.js +20 -0
  14. package/dist/badge/root/badge-root.d.ts +57 -6
  15. package/dist/badge/root/badge-root.js +77 -8
  16. package/dist/bar-chart/root/bar-chart-root.js +11 -20
  17. package/dist/bar-chart/stack.d.ts +10 -0
  18. package/dist/bar-chart/stack.js +8 -0
  19. package/dist/blockquote/root/blockquote-root.d.ts +66 -8
  20. package/dist/blockquote/root/blockquote-root.js +85 -8
  21. package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +14 -0
  22. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +12 -2
  23. package/dist/breadcrumbs/root/breadcrumbs-root.js +2 -2
  24. package/dist/button/root/button-root.d.ts +24 -38
  25. package/dist/button/root/button-root.js +6 -37
  26. package/dist/button/submitter.d.ts +27 -0
  27. package/dist/button/submitter.js +25 -0
  28. package/dist/button-group/arrows.d.ts +14 -0
  29. package/dist/button-group/arrows.js +10 -0
  30. package/dist/button-group/root/button-group-root.d.ts +141 -3
  31. package/dist/button-group/root/button-group-root.js +142 -2
  32. package/dist/canvas/edge-pan.d.ts +61 -0
  33. package/dist/canvas/edge-pan.js +63 -0
  34. package/dist/canvas/node/canvas-node.d.ts +12 -2
  35. package/dist/canvas/node/canvas-node.js +6 -2
  36. package/dist/canvas/root/canvas-document.d.ts +10 -0
  37. package/dist/canvas/root/canvas-document.js +1 -0
  38. package/dist/canvas/root/canvas-root.d.ts +107 -5
  39. package/dist/canvas/root/canvas-root.js +140 -36
  40. package/dist/canvas/snap.d.ts +42 -0
  41. package/dist/canvas/snap.js +32 -0
  42. package/dist/card/root/card-root.d.ts +12 -1
  43. package/dist/card/root/card-root.js +11 -4
  44. package/dist/carousel/root/carousel-root.d.ts +4 -3
  45. package/dist/carousel/root/carousel-root.js +2 -6
  46. package/dist/chat/root/chat-root.d.ts +28 -4
  47. package/dist/chat/root/chat-root.js +12 -7
  48. package/dist/checkbox/root/checkbox-root.js +5 -7
  49. package/dist/checkbox-group/root/checkbox-group-root.d.ts +13 -0
  50. package/dist/checkbox-group/root/checkbox-group-root.js +16 -7
  51. package/dist/checkbox-group/select-all.d.ts +9 -4
  52. package/dist/checkbox-group/select-all.js +1 -1
  53. package/dist/code-block/root/code-block-root.d.ts +41 -18
  54. package/dist/code-block/root/code-block-root.js +21 -6
  55. package/dist/color-input/root/color-input-root.d.ts +11 -0
  56. package/dist/color-input/root/color-input-root.js +12 -5
  57. package/dist/color-picker/channels.js +1 -2
  58. package/dist/color-picker/root/color-picker-root.d.ts +32 -24
  59. package/dist/color-picker/root/color-picker-root.js +99 -55
  60. package/dist/color-picker/wheel.d.ts +28 -0
  61. package/dist/color-picker/wheel.js +15 -0
  62. package/dist/combobox/root/combobox-root.d.ts +83 -11
  63. package/dist/combobox/root/combobox-root.js +81 -18
  64. package/dist/command/root/command-root.d.ts +7 -4
  65. package/dist/command/root/command-root.js +2 -19
  66. package/dist/copy-button/root/copy-button-root.d.ts +16 -1
  67. package/dist/copy-button/root/copy-button-root.js +7 -2
  68. package/dist/data-table/keys.d.ts +0 -18
  69. package/dist/data-table/keys.js +1 -11
  70. package/dist/data-table/root/data-table-root.js +29 -50
  71. package/dist/date-input/root/date-input-root.d.ts +8 -1
  72. package/dist/date-input/root/date-input-root.js +8 -5
  73. package/dist/date-picker/root/date-picker-root.d.ts +14 -9
  74. package/dist/date-picker/root/date-picker-root.js +21 -16
  75. package/dist/description-list/item/description-list-item.d.ts +22 -4
  76. package/dist/description-list/item/description-list-item.js +14 -4
  77. package/dist/divider/root/divider-root.d.ts +45 -16
  78. package/dist/divider/root/divider-root.js +28 -4
  79. package/dist/donut-chart/root/donut-chart-root.js +13 -6
  80. package/dist/donut-chart/slice-label.d.ts +35 -0
  81. package/dist/donut-chart/slice-label.js +11 -0
  82. package/dist/drawer/root/drawer-root.d.ts +4 -2
  83. package/dist/drawer/root/drawer-root.js +15 -3
  84. package/dist/drilldown/item/drilldown-item.d.ts +21 -1
  85. package/dist/drilldown/item/drilldown-item.js +38 -4
  86. package/dist/drilldown/root/drilldown-root.js +3 -2
  87. package/dist/element-events.generated.js +5 -1
  88. package/dist/empty/root/empty-root.d.ts +46 -2
  89. package/dist/empty/root/empty-root.js +50 -5
  90. package/dist/file-input/root/file-input-root.d.ts +34 -6
  91. package/dist/file-input/root/file-input-root.js +55 -21
  92. package/dist/file-input/status.d.ts +24 -0
  93. package/dist/file-input/status.js +11 -0
  94. package/dist/filter-input/chip/filter-chip.d.ts +23 -0
  95. package/dist/filter-input/chip/filter-chip.js +10 -2
  96. package/dist/filter-input/root/filter-input-root.d.ts +8 -0
  97. package/dist/filter-input/root/filter-input-root.js +17 -27
  98. package/dist/format-date/fields.d.ts +20 -0
  99. package/dist/format-date/fields.js +14 -0
  100. package/dist/format-date/options.d.ts +5 -5
  101. package/dist/format-date/options.js +10 -39
  102. package/dist/format-date/root/format-date-root.d.ts +53 -4
  103. package/dist/format-date/root/format-date-root.js +38 -50
  104. package/dist/graph/root/graph-root.d.ts +23 -5
  105. package/dist/graph/root/graph-root.js +17 -20
  106. package/dist/heatmap/root/buckets.d.ts +21 -0
  107. package/dist/heatmap/root/buckets.js +34 -0
  108. package/dist/heatmap/root/heatmap-root.d.ts +76 -3
  109. package/dist/heatmap/root/heatmap-root.js +105 -20
  110. package/dist/icon/index.d.ts +1 -1
  111. package/dist/icon/index.js +1 -1
  112. package/dist/icon/registry.d.ts +26 -1
  113. package/dist/icon/registry.js +12 -1
  114. package/dist/icon/root/icon-root.d.ts +13 -0
  115. package/dist/icon/root/icon-root.js +23 -2
  116. package/dist/icon/root/index.d.ts +1 -1
  117. package/dist/icon/root/index.js +1 -1
  118. package/dist/image/root/image-root.d.ts +20 -0
  119. package/dist/image/root/image-root.js +6 -1
  120. package/dist/input/root/input-root.d.ts +23 -2
  121. package/dist/input/root/input-root.js +12 -8
  122. package/dist/kbd/root/kbd-root.d.ts +20 -3
  123. package/dist/kbd/root/kbd-root.js +23 -50
  124. package/dist/legend/hidden-series.d.ts +10 -2
  125. package/dist/legend/hidden-series.js +5 -2
  126. package/dist/legend/root/legend-root.d.ts +25 -7
  127. package/dist/legend/root/legend-root.js +25 -26
  128. package/dist/lightbox/root/lightbox-root.d.ts +50 -5
  129. package/dist/lightbox/root/lightbox-root.js +76 -8
  130. package/dist/lightbox/zoom.d.ts +2 -0
  131. package/dist/lightbox/zoom.js +5 -3
  132. package/dist/line-chart/root/line-chart-root.d.ts +16 -0
  133. package/dist/line-chart/root/line-chart-root.js +10 -2
  134. package/dist/list/root/list-root.d.ts +10 -2
  135. package/dist/list/root/list-root.js +31 -24
  136. package/dist/list/selection.d.ts +17 -0
  137. package/dist/list/selection.js +16 -0
  138. package/dist/listbox/root/listbox-root.d.ts +125 -26
  139. package/dist/listbox/root/listbox-root.js +156 -166
  140. package/dist/map/marker/map-marker.d.ts +2 -1
  141. package/dist/map/root/map-root.d.ts +40 -2
  142. package/dist/map/root/map-root.js +64 -23
  143. package/dist/megamenu/hand-over.d.ts +75 -0
  144. package/dist/megamenu/hand-over.js +67 -0
  145. package/dist/megamenu/root/megamenu-root.d.ts +38 -14
  146. package/dist/megamenu/root/megamenu-root.js +29 -50
  147. package/dist/menu/item/menu-item.js +4 -4
  148. package/dist/menu/root/menu-root.d.ts +5 -2
  149. package/dist/meter/root/meter-root.d.ts +42 -0
  150. package/dist/meter/root/meter-root.js +33 -7
  151. package/dist/navigation/root/navigation-root.js +5 -8
  152. package/dist/notifications/group/notification-group.d.ts +8 -1
  153. package/dist/notifications/group/notification-group.js +12 -6
  154. package/dist/notifications/notification/notification.d.ts +12 -1
  155. package/dist/notifications/notification/notification.js +3 -1
  156. package/dist/notifications/root/notifications-root.d.ts +48 -3
  157. package/dist/notifications/root/notifications-root.js +36 -6
  158. package/dist/number-input/grain.d.ts +28 -0
  159. package/dist/number-input/grain.js +14 -0
  160. package/dist/number-input/root/number-input-root.d.ts +17 -1
  161. package/dist/number-input/root/number-input-root.js +29 -18
  162. package/dist/object-inspector/root/object-inspector-root.d.ts +17 -6
  163. package/dist/object-inspector/root/object-inspector-root.js +31 -20
  164. package/dist/option/index.d.ts +1 -0
  165. package/dist/option/index.js +1 -0
  166. package/dist/option/separator/option-separator.d.ts +30 -0
  167. package/dist/option/separator/option-separator.js +21 -0
  168. package/dist/outline/root/outline-root.d.ts +12 -0
  169. package/dist/outline/root/outline-root.js +5 -19
  170. package/dist/pagination/size/pagination-size.d.ts +6 -0
  171. package/dist/pagination/size/pagination-size.js +5 -3
  172. package/dist/pin-input/code.d.ts +51 -1
  173. package/dist/pin-input/code.js +37 -0
  174. package/dist/pin-input/root/pin-input-root.d.ts +98 -121
  175. package/dist/pin-input/root/pin-input-root.js +151 -156
  176. package/dist/popover/hide-outside.js +2 -9
  177. package/dist/popover/root/popover-root.d.ts +7 -1
  178. package/dist/popover/root/popover-root.js +9 -1
  179. package/dist/progress/root/progress-root.d.ts +27 -0
  180. package/dist/progress/root/progress-root.js +25 -7
  181. package/dist/qr-code/export.d.ts +19 -0
  182. package/dist/qr-code/export.js +14 -0
  183. package/dist/qr-code/name.d.ts +11 -0
  184. package/dist/qr-code/name.js +7 -0
  185. package/dist/qr-code/root/qr-code-root.d.ts +31 -9
  186. package/dist/qr-code/root/qr-code-root.js +29 -17
  187. package/dist/radio/group/radio-group.d.ts +15 -0
  188. package/dist/radio/root/radio-root.js +2 -2
  189. package/dist/rating/root/rating-root.d.ts +22 -9
  190. package/dist/rating/root/rating-root.js +15 -39
  191. package/dist/scatter-chart/measures.d.ts +22 -0
  192. package/dist/scatter-chart/measures.js +11 -0
  193. package/dist/scatter-chart/root/scatter-chart-root.d.ts +26 -0
  194. package/dist/scatter-chart/root/scatter-chart-root.js +21 -7
  195. package/dist/scroll-area/scrollbar.d.ts +0 -8
  196. package/dist/scroll-area/scrollbar.js +1 -1
  197. package/dist/select/align.d.ts +47 -0
  198. package/dist/select/align.js +7 -0
  199. package/dist/select/root/select-root.d.ts +115 -11
  200. package/dist/select/root/select-root.js +104 -20
  201. package/dist/slider/root/slider-root.d.ts +18 -1
  202. package/dist/slider/root/slider-root.js +22 -0
  203. package/dist/sparkline/readings.d.ts +30 -12
  204. package/dist/sparkline/readings.js +32 -15
  205. package/dist/sparkline/root/sparkline-root.d.ts +46 -3
  206. package/dist/sparkline/root/sparkline-root.js +60 -21
  207. package/dist/speed-dial/group/speed-dial-group.d.ts +48 -0
  208. package/dist/speed-dial/group/speed-dial-group.js +43 -0
  209. package/dist/speed-dial/index.d.ts +1 -0
  210. package/dist/speed-dial/index.js +1 -0
  211. package/dist/speed-dial/item/speed-dial-item.d.ts +27 -2
  212. package/dist/speed-dial/item/speed-dial-item.js +43 -5
  213. package/dist/speed-dial/long-press.d.ts +50 -0
  214. package/dist/speed-dial/long-press.js +48 -0
  215. package/dist/speed-dial/root/speed-dial-root.d.ts +86 -1
  216. package/dist/speed-dial/root/speed-dial-root.js +123 -6
  217. package/dist/splitter/root/splitter-root.d.ts +9 -2
  218. package/dist/splitter/root/splitter-root.js +18 -3
  219. package/dist/steps/root/steps-root.d.ts +8 -1
  220. package/dist/steps/root/steps-root.js +6 -1
  221. package/dist/steps/step/step.d.ts +1 -0
  222. package/dist/steps/step/step.js +1 -1
  223. package/dist/table/cell/table-cell.d.ts +8 -1
  224. package/dist/table/cell/table-cell.js +6 -2
  225. package/dist/table/column/table-column.d.ts +6 -0
  226. package/dist/table/column/table-column.js +5 -1
  227. package/dist/table/grid-controller.d.ts +117 -0
  228. package/dist/table/grid-controller.js +293 -0
  229. package/dist/table/root/table-root.d.ts +47 -3
  230. package/dist/table/root/table-root.js +93 -29
  231. package/dist/table/row/table-row.d.ts +36 -0
  232. package/dist/table/row/table-row.js +52 -2
  233. package/dist/tabs/root/tabs-root.d.ts +5 -3
  234. package/dist/tag/group/tag-group.d.ts +16 -4
  235. package/dist/tag/group/tag-group.js +23 -20
  236. package/dist/textarea/root/textarea-root.d.ts +14 -2
  237. package/dist/textarea/root/textarea-root.js +6 -7
  238. package/dist/time-input/root/time-input-root.d.ts +5 -1
  239. package/dist/time-input/root/time-input-root.js +10 -7
  240. package/dist/time-picker/root/time-picker-root.d.ts +12 -14
  241. package/dist/time-picker/root/time-picker-root.js +18 -23
  242. package/dist/toast/item/toast-item.d.ts +9 -6
  243. package/dist/toast/item/toast-item.js +1 -1
  244. package/dist/toast/root/toast-root.d.ts +40 -4
  245. package/dist/toast/root/toast-root.js +68 -16
  246. package/dist/toast/words.d.ts +12 -0
  247. package/dist/toast/words.js +25 -0
  248. package/dist/toggle/group/toggle-group.d.ts +29 -5
  249. package/dist/toggle/group/toggle-group.js +26 -43
  250. package/dist/toggle/root/toggle-root.js +2 -4
  251. package/dist/toolbar/root/toolbar-root.d.ts +52 -5
  252. package/dist/toolbar/root/toolbar-root.js +88 -13
  253. package/dist/tooltip/root/tooltip-root.d.ts +7 -1
  254. package/dist/tooltip/root/tooltip-root.js +10 -2
  255. package/dist/tour/root/tour-root.d.ts +15 -0
  256. package/dist/tour/root/tour-root.js +20 -1
  257. package/dist/tour/stop/tour-stop.d.ts +3 -0
  258. package/dist/tour/stop/tour-stop.js +3 -3
  259. package/dist/tree/root/tree-root.js +2 -2
  260. package/dist/trend/figure.d.ts +16 -0
  261. package/dist/trend/figure.js +8 -0
  262. package/dist/trend/root/trend-root.d.ts +41 -2
  263. package/dist/trend/root/trend-root.js +29 -3
  264. package/dist/video/root/extra-controls.d.ts +12 -0
  265. package/dist/video/root/extra-controls.js +6 -2
  266. package/dist/video/root/video-root.d.ts +16 -1
  267. package/dist/video/root/video-root.js +15 -1
  268. package/dist/video-playlist/root/video-playlist-root.d.ts +11 -1
  269. package/dist/video-playlist/root/video-playlist-root.js +3 -0
  270. package/dist/virtual-list/fill.d.ts +11 -0
  271. package/dist/virtual-list/fill.js +9 -0
  272. package/dist/virtual-list/keys.d.ts +16 -7
  273. package/dist/virtual-list/keys.js +17 -6
  274. package/dist/virtual-list/plan.d.ts +17 -1
  275. package/dist/virtual-list/plan.js +13 -2
  276. package/dist/virtual-list/root/virtual-list-root.d.ts +25 -3
  277. package/dist/virtual-list/root/virtual-list-root.js +53 -41
  278. package/lint.json +1 -1
  279. package/package.json +5 -5
  280. package/types/jsx.d.ts +4934 -954
  281. package/types/svelte.d.ts +2 -0
  282. package/types/vue.d.ts +946 -253
  283. package/dist/color-picker/swatches.d.ts +0 -12
  284. package/dist/color-picker/swatches.js +0 -26
  285. package/dist/rating/steps.d.ts +0 -7
  286. package/dist/rating/steps.js +0 -9
  287. package/dist/tooltip/focus-target.d.ts +0 -6
  288. package/dist/tooltip/focus-target.js +0 -9
@@ -4,33 +4,110 @@ 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, markRegion } from '@fusion.dev/helix-core';
8
- import { html, nothing } from 'lit';
7
+ import { customElement, HelixElement, markRegion, reportRuntime } from '@fusion.dev/helix-core';
8
+ import { css, html, nothing, } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
+ import { typedMarks } from '../../quote/marks.js';
10
11
  let HelixHeadlessBlockquote = class HelixHeadlessBlockquote extends HelixElement {
11
12
  constructor() {
12
13
  super(...arguments);
14
+ this.attribution = '';
13
15
  this.cite = '';
16
+ this.marks = false;
17
+ this.warned = false;
18
+ this.citeWarned = false;
19
+ this.readPassage = () => {
20
+ if (!this.marks || this.warned) {
21
+ return;
22
+ }
23
+ const text = [...this.childNodes]
24
+ .filter((node) => !(node instanceof Element) || !node.slot)
25
+ .map((node) => node.textContent ?? '')
26
+ .join('')
27
+ .trim();
28
+ if (!typedMarks(text.charAt(0))) {
29
+ return;
30
+ }
31
+ this.warned = true;
32
+ reportRuntime({
33
+ kind: 'typed-marks',
34
+ message: `<${this.localName} marks> starts with a typed quotation mark, and the element draws its own, so the mark shows twice. Take the typed mark out.`,
35
+ tag: this.localName,
36
+ });
37
+ };
14
38
  }
39
+ static { this.styles = css `
40
+ :host([marks]) [part='blockquote']::before {
41
+ content: open-quote / '';
42
+ }
43
+
44
+ :host([marks]) [part='blockquote']::after {
45
+ content: no-close-quote;
46
+ }
47
+ `; }
15
48
  markEmpty() {
16
- markRegion(this.renderRoot, 'attribution', ['attribution']);
49
+ markRegion(this.renderRoot, 'attribution', ['attribution'], {
50
+ occupied: 'slot[name="attribution"] > *',
51
+ });
52
+ const named = !this.partNamed('attribution')?.hasAttribute('data-empty');
53
+ const figure = this.partNamed('figure');
54
+ if (named) {
55
+ figure?.setAttribute('role', 'figure');
56
+ figure?.setAttribute('aria-labelledby', 'attribution');
57
+ }
58
+ else {
59
+ figure?.removeAttribute('role');
60
+ figure?.removeAttribute('aria-labelledby');
61
+ }
62
+ }
63
+ readCite() {
64
+ if (this.citeWarned || !/\S\s+\S/.test(this.cite || '')) {
65
+ return;
66
+ }
67
+ this.citeWarned = true;
68
+ reportRuntime({
69
+ kind: 'cite-not-url',
70
+ message: `<${this.localName} cite="${this.cite}"> holds words, and \`cite\` takes the URL of the source. Put who said it in \`attribution\`.`,
71
+ tag: this.localName,
72
+ });
73
+ }
74
+ firstUpdated(changed) {
75
+ super.firstUpdated(changed);
76
+ this.renderRoot.addEventListener('slotchange', this.readPassage);
17
77
  }
18
78
  updated(changed) {
19
79
  super.updated(changed);
20
80
  this.markEmpty();
81
+ if (changed.has('marks')) {
82
+ this.readPassage();
83
+ }
84
+ if (changed.has('cite')) {
85
+ this.readCite();
86
+ }
21
87
  }
22
88
  render() {
23
- return html `<figure aria-labelledby="attribution" part="figure">
89
+ return html `<div aria-labelledby="attribution" part="figure" role="figure">
24
90
  <blockquote part="blockquote" cite=${this.cite || nothing}><slot></slot></blockquote>
25
- <figcaption id="attribution" part="attribution"
26
- ><slot name="attribution" @slotchange=${() => this.markEmpty()}></slot
27
- ></figcaption>
28
- </figure>`;
91
+ ${this.renderAttribution()}
92
+ </div>`;
93
+ }
94
+ renderAttribution() {
95
+ return html `<div id="attribution" part="attribution"
96
+ ><slot name="attribution" @slotchange=${() => this.markEmpty()}
97
+ >${this.attribution ? html `<span>${this.attribution}</span>` : nothing}</slot
98
+ ></div
99
+ >`;
29
100
  }
30
101
  };
102
+ __decorate([
103
+ property()
104
+ ], HelixHeadlessBlockquote.prototype, "attribution", void 0);
31
105
  __decorate([
32
106
  property()
33
107
  ], HelixHeadlessBlockquote.prototype, "cite", void 0);
108
+ __decorate([
109
+ property({ reflect: true, type: Boolean })
110
+ ], HelixHeadlessBlockquote.prototype, "marks", void 0);
34
111
  HelixHeadlessBlockquote = __decorate([
35
112
  customElement('helix-headless-blockquote')
36
113
  ], HelixHeadlessBlockquote);
@@ -37,6 +37,9 @@ import { type PropertyValues, type TemplateResult } from 'lit';
37
37
  * @csspart link - The crumb itself, which is an `<a>` where `href` is set.
38
38
  * @csspart separator - The separator preceding the item.
39
39
  * @csspart separator-mark - The chevron between crumbs, shown when no separator is slotted.
40
+ *
41
+ * @cssstate end - While the `end` slot holds something, so a stylesheet can join
42
+ * the label and what follows it into one control.
40
43
  */
41
44
  export declare class HelixHeadlessBreadcrumb extends HelixElement {
42
45
  /**
@@ -74,6 +77,17 @@ export declare class HelixHeadlessBreadcrumb extends HelixElement {
74
77
  */
75
78
  target: string;
76
79
  protected willUpdate(changed: PropertyValues): void;
80
+ protected updated(changed: PropertyValues): void;
81
+ /**
82
+ * Says whether anything sits after the label.
83
+ *
84
+ * A switcher's trigger in `end` makes the crumb a split control, and its
85
+ * label is drawn as the first half of it. No selector can ask a slot what was
86
+ * assigned to it, so this is script. Answered from a DOM, so the server
87
+ * writes the resting answer and the first client update corrects it.
88
+ */
89
+ private markEnd;
90
+ private readonly handleEnd;
77
91
  /**
78
92
  * The mark between one crumb and the next.
79
93
  *
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, hostAria, slotFilled, slotNamed, } from '@fusion.dev/helix-core';
8
8
  import { caretRight } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property } from 'lit/decorators.js';
@@ -15,12 +15,22 @@ let HelixHeadlessBreadcrumb = class HelixHeadlessBreadcrumb extends HelixElement
15
15
  this.current = false;
16
16
  this.rel = '';
17
17
  this.target = '';
18
+ this.handleEnd = () => {
19
+ this.markEnd();
20
+ };
18
21
  }
19
22
  willUpdate(changed) {
20
23
  super.willUpdate(changed);
21
24
  hostAria(this.internals, { role: 'listitem' });
22
25
  this.internals.ariaCurrent = this.current ? 'page' : null;
23
26
  }
27
+ updated(changed) {
28
+ super.updated(changed);
29
+ this.markEnd();
30
+ }
31
+ markEnd() {
32
+ this.toggleState('end', slotFilled(slotNamed(this.renderRoot, 'end')));
33
+ }
24
34
  renderSeparator() {
25
35
  return html `<slot name="separator"
26
36
  ><svg part="separator-mark" data-mirror fill="currentColor" viewBox=${caretRight.viewBox}>
@@ -41,7 +51,7 @@ let HelixHeadlessBreadcrumb = class HelixHeadlessBreadcrumb extends HelixElement
41
51
  rel=${this.rel || nothing}
42
52
  >${label}</a
43
53
  >`
44
- : html `<span part="link">${label}</span>`}<slot name="end"></slot>`;
54
+ : html `<span part="link">${label}</span>`}<slot name="end" @slotchange=${this.handleEnd}></slot>`;
45
55
  }
46
56
  };
47
57
  __decorate([
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
7
+ import { customElement, fillTemplate, HelixElement, hostAria } from '@fusion.dev/helix-core';
8
8
  import { nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import { repeat } from 'lit/directives/repeat.js';
@@ -55,7 +55,7 @@ let HelixHeadlessBreadcrumbs = class HelixHeadlessBreadcrumbs extends HelixEleme
55
55
  ><button
56
56
  part="expand"
57
57
  type="button"
58
- aria-label=${this.expandLabel.replace('{count}', String(folded))}
58
+ aria-label=${fillTemplate(this.expandLabel, { count: String(folded) })}
59
59
  @click=${this.expand}
60
60
  >
61
61
  …
@@ -18,13 +18,12 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
18
18
  *
19
19
  * The inner control is always `type="button"`, because a
20
20
  * `<button type="submit">` inside a shadow root cannot submit a form outside
21
- * it. To submit, the button calls `requestSubmit()` on its form, which runs
22
- * validation, unlike `submit()`.
23
- *
24
- * A `submit` listener reads `event.submitter` as `null`, because the platform
25
- * accepts only a native submit button there. The button's `name` and `value`
26
- * are in the form data, so read them from `new FormData(form)` to tell two
27
- * submit buttons apart.
21
+ * it. To submit, the button puts a hidden native submit button carrying its
22
+ * `name`, `value` and `form*` overrides into the form, calls
23
+ * `requestSubmit()` with it, which runs validation, unlike `submit()`, and
24
+ * takes it out again, as Shoelace and Web Awesome do. A `submit` listener
25
+ * reads that button as `event.submitter`, so `event.submitter.value` and
26
+ * `new FormData(form, event.submitter)` tell two submit buttons apart.
28
27
  *
29
28
  * As the first submit button in its form, it is the form's default button:
30
29
  * Enter in a text field of the form presses it, as it presses a native one.
@@ -54,6 +53,7 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
54
53
  *
55
54
  * @csspart button - The inner control.
56
55
  * @csspart live - The live region that says `pending-label` when the button becomes pending under focus. It is rendered from the first time the button is pending, and the styled button clips it out of view.
56
+ * @csspart spinner - The ring a pending button turns, an empty element the styled button draws over the words. It is rendered only while the button is pending.
57
57
  * @csspart text - The button's words, which the styled button trims to their capitals and baseline so the letters sit at the middle in any font.
58
58
  *
59
59
  * @cssstate pending - The button is pending: `pending` is set.
@@ -135,18 +135,13 @@ export declare class HelixHeadlessButton extends HelixFormElement {
135
135
  /**
136
136
  * Where this button's submission goes, overriding the form's own.
137
137
  *
138
- * **The five `form*` overrides work by writing the form and putting it back**,
139
- * which is the only mechanism available and is closer to the platform than it
140
- * sounds. `requestSubmit(submitter)` is how a native submitter carries these,
141
- * and it refuses ours twice over: the inner `<button>` is `type="button"` so
142
- * it is not a submit button, and it sits in this shadow root so its form owner
143
- * is not the consumer's form. Both throw. So the submission is started with no
144
- * submitter and the overrides are applied to the form for the duration of it.
145
- *
146
- * The whole submit algorithm runs synchronously inside `requestSubmit()`, so
147
- * the window is one call and a `finally` closes it whether or not a listener
148
- * refused. What a `submit` handler observes during that window is the form a
149
- * native submitter would have given it, which is the point.
138
+ * **The five `form*` overrides ride on a native submit button that stands in
139
+ * for this one**, because `requestSubmit(submitter)` refuses the inner
140
+ * `<button>` twice over: it is `type="button"`, and it sits in this shadow
141
+ * root, so its form owner is not the consumer's form. The stand-in is a
142
+ * hidden `<button type="submit">` in the form for the one synchronous call,
143
+ * so the platform applies the overrides to that submission alone and leaves
144
+ * the form as it was. See `submitAs`.
150
145
  */
151
146
  formAction: string;
152
147
  /**
@@ -249,13 +244,10 @@ export declare class HelixHeadlessButton extends HelixFormElement {
249
244
  * The value this button submits under its own `name`, as a native submit
250
245
  * button does.
251
246
  *
252
- * **Reported for exactly the submission this button starts and not otherwise.**
247
+ * **Sent with exactly the submission this button starts and not otherwise.**
253
248
  * A native submit button contributes `name=value` only when it is the one
254
- * that submitted, and every other button on the form contributes nothing. A
255
- * form-associated element reporting a value continuously would contribute on
256
- * every submission whoever started it, which is a different feature wearing
257
- * the same name, so `submit` below sets it, submits, and clears it inside one
258
- * synchronous call.
249
+ * that submitted, and every other button on the form contributes nothing,
250
+ * which is what the native button standing in for this one at submit does.
259
251
  *
260
252
  * `CopyButton` has a `value` of its own meaning the text to put on the
261
253
  * clipboard, which is not this and is why it is declared there rather than
@@ -263,19 +255,8 @@ export declare class HelixHeadlessButton extends HelixFormElement {
263
255
  */
264
256
  value: string;
265
257
  /**
266
- * Submits the form as this button, for the length of one call.
267
- *
268
- * Everything a native submitter carries is written onto the form, the
269
- * submission is started, and everything is put back. See `formAction` for why
270
- * `requestSubmit(submitter)` is not available to a control inside a shadow
271
- * root.
272
- *
273
- * `novalidate` is in the same list rather than reached by `form.submit()`,
274
- * which looks like the shortcut and is not: `submit()` skips the `submit`
275
- * event as well as the validation, so a consumer's handler would stop running
276
- * the moment somebody wrote `formnovalidate`. Turning the flag on and calling
277
- * `requestSubmit()` skips the validation alone, which is what the attribute
278
- * means.
258
+ * Submits the form as this button, through a native submit button carrying
259
+ * its name, value and overrides. See `formAction`.
279
260
  */
280
261
  private submit;
281
262
  /**
@@ -346,6 +327,11 @@ export declare class HelixHeadlessButton extends HelixFormElement {
346
327
  /** Called by the browser when the button's form changes. */
347
328
  formAssociatedCallback(form: HTMLFormElement | null): void;
348
329
  protected render(): TemplateResult;
330
+ /**
331
+ * The ring a pending button turns, behind a seam a tier can move: an empty
332
+ * element, hidden from a screen reader, which hears `pending-label` instead.
333
+ */
334
+ protected renderSpinner(): TemplateResult | typeof nothing;
349
335
  }
350
336
  declare global {
351
337
  interface HTMLElementTagNameMap {
@@ -7,6 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { AriaForwardController, customElement, defaultButton, drawStatus, elementById, HelixFallbackCommandEvent, HelixFormElement, HelixPressEvent, pressType, StatusController, submitsOnEnter, supportsInvokers, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
+ import { submitAs } from '../submitter.js';
10
11
  let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
11
12
  constructor() {
12
13
  super(...arguments);
@@ -103,42 +104,7 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
103
104
  }
104
105
  }
105
106
  submit(form) {
106
- const undo = [];
107
- const override = (attribute, value) => {
108
- if (value === null) {
109
- return;
110
- }
111
- const had = form.getAttribute(attribute);
112
- form.setAttribute(attribute, value);
113
- undo.push(() => {
114
- if (had === null) {
115
- form.removeAttribute(attribute);
116
- }
117
- else {
118
- form.setAttribute(attribute, had);
119
- }
120
- });
121
- };
122
- override('action', this.formAction || null);
123
- override('enctype', this.formEnctype || null);
124
- override('method', this.formMethod || null);
125
- override('novalidate', this.formNoValidate ? '' : null);
126
- override('target', this.formTarget || null);
127
- const reports = this.name !== '';
128
- if (reports) {
129
- this.internals.setFormValue(this.value);
130
- }
131
- try {
132
- form.requestSubmit();
133
- }
134
- finally {
135
- for (const put of undo) {
136
- put();
137
- }
138
- if (reports) {
139
- this.internals.setFormValue(null);
140
- }
141
- }
107
+ submitAs(form, this);
142
108
  }
143
109
  get commandForElement() {
144
110
  const button = this.renderRoot?.querySelector('button');
@@ -222,9 +188,12 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
222
188
  @keydown=${this.notePress}
223
189
  @pointerdown=${this.notePress}
224
190
  >
225
- ${label}
191
+ ${label}${this.renderSpinner()}
226
192
  </button>${this.renderLive()}`;
227
193
  }
194
+ renderSpinner() {
195
+ return this.pending ? html `<span part="spinner" aria-hidden="true"></span>` : nothing;
196
+ }
228
197
  };
229
198
  __decorate([
230
199
  property()
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Submitting a form through a native submit button that stands in for one in a
3
+ * shadow root.
4
+ *
5
+ * `requestSubmit(submitter)` takes only a native submit button whose form is
6
+ * the form, and a button inside a shadow root is neither. A hidden native
7
+ * `<button type="submit">` carrying the same name, value and `form*`
8
+ * overrides is put into the form for the one synchronous call, as Shoelace and
9
+ * Web Awesome do, so the platform does the rest: `event.submitter` is a
10
+ * button with the name and value, `new FormData(form, event.submitter)`
11
+ * includes them, and the overrides apply to that submission alone.
12
+ */
13
+ /** What a submit button carries into the submission it starts. */
14
+ export interface Carried {
15
+ formAction: string;
16
+ formEnctype: string;
17
+ formMethod: string;
18
+ formNoValidate: boolean;
19
+ formTarget: string;
20
+ name: string;
21
+ value: string;
22
+ }
23
+ /**
24
+ * Submits `form` as a native submit button carrying `carried` would, and takes
25
+ * that button out again whether or not a listener refused the submission.
26
+ */
27
+ export declare const submitAs: (form: HTMLFormElement, carried: Carried) => void;
@@ -0,0 +1,25 @@
1
+ export const submitAs = (form, carried) => {
2
+ const stand = form.ownerDocument.createElement('button');
3
+ stand.type = 'submit';
4
+ stand.hidden = true;
5
+ for (const [attribute, value] of [
6
+ ['formaction', carried.formAction],
7
+ ['formenctype', carried.formEnctype],
8
+ ['formmethod', carried.formMethod],
9
+ ['formtarget', carried.formTarget],
10
+ ['name', carried.name],
11
+ ]) {
12
+ if (value !== '') {
13
+ stand.setAttribute(attribute, value);
14
+ }
15
+ }
16
+ stand.value = carried.value;
17
+ stand.formNoValidate = carried.formNoValidate;
18
+ form.append(stand);
19
+ try {
20
+ form.requestSubmit(stand);
21
+ }
22
+ finally {
23
+ stand.remove();
24
+ }
25
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The index a key moves focus to, or `-1` for a key that moves nothing.
3
+ *
4
+ * Down and Up step forward and back, and Left and Right step by `step`, which
5
+ * core's `inlineStep` turns on a right-to-left page. Home and End go to the
6
+ * ends. A step wraps from the last button round to the first, as a toolbar's
7
+ * does.
8
+ *
9
+ * @param key The key pressed.
10
+ * @param step `1` or `-1` for Left and Right, from `inlineStep`, and `0` otherwise.
11
+ * @param from The index focus is on, or `-1` where it is on none of them.
12
+ * @param count How many buttons focus can move between.
13
+ */
14
+ export declare const arrowTarget: (key: string, step: number, from: number, count: number) => number;
@@ -0,0 +1,10 @@
1
+ export const arrowTarget = (key, step, from, count) => {
2
+ const by = key === 'ArrowDown' ? 1 : key === 'ArrowUp' ? -1 : step;
3
+ if (from < 0 || count === 0) {
4
+ return -1;
5
+ }
6
+ if (key === 'Home' || key === 'End') {
7
+ return key === 'Home' ? 0 : count - 1;
8
+ }
9
+ return by === 0 ? -1 : (from + by + count) % count;
10
+ };
@@ -9,19 +9,62 @@ import { type PropertyValues, type TemplateResult } from 'lit';
9
9
  * controls, and a development build warns once with the kind `missing-name`.
10
10
  *
11
11
  * The group does not manage focus, so each button keeps its own tab stop. For
12
- * one tab stop with arrow keys between the buttons, the APG toolbar pattern,
13
- * use Toolbar.
12
+ * one tab stop with arrow keys between a mix of controls, the APG toolbar
13
+ * pattern, use Toolbar.
14
14
  *
15
15
  * Buttons are slotted, so they stay your own elements and keep their own
16
16
  * appearance and size.
17
17
  *
18
+ * **Buttons that stay pressed.** With `selection-mode`, each button with a
19
+ * `value` is a toggle button: a press presses or releases it, and it says so
20
+ * with `aria-pressed`. `single` keeps one pressed at a time, and a press on the
21
+ * pressed one releases it. `multiple` lets any number be pressed. `value` is
22
+ * the pressed values, comma separated. A press sends a cancelable
23
+ * `helix-before-change` with the value it would leave, then `change`.
24
+ *
25
+ * The pressed state is written on the control inside each button's shadow
26
+ * root, so the markup you rendered is never changed and a framework hydrating
27
+ * it sees what its server sent. That is also why only Helix buttons are
28
+ * toggles: a native `<button>` or a button without a `value` stays a plain
29
+ * action button in the group, and a native one without its own
30
+ * `aria-pressed` warns once, with the runtime kind `untold-toggle`. The inner
31
+ * control also carries
32
+ * `--button-group-pressed`, `1` or `0`, for a `::part(button)` rule of yours.
33
+ *
34
+ * Use Toggle Group instead for a choice that belongs in a form, or one where
35
+ * the arrow keys choose as they move: it is a `radiogroup` of options with a
36
+ * form value, `required` and validation. A Button Group in a selection mode is
37
+ * a set of actions that stay on, such as bold and italic, or a view that a
38
+ * press switches, and it submits nothing.
39
+ *
40
+ * **Keyboard.** In a selection mode the group is one tab stop: the button
41
+ * focus was last on, then the first pressed one, then the first one.
42
+ *
43
+ * | Key | What it does |
44
+ * |---|---|
45
+ * | Right, Down | Moves to the next button, from the last back to the first. |
46
+ * | Left, Up | Moves to the button before, from the first round to the last. |
47
+ * | Home, End | Moves to the first or the last button. |
48
+ * | Space, Enter | Presses or releases the focused button. |
49
+ *
50
+ * Left and Right swap on a right-to-left page, and a disabled button is
51
+ * passed over. In a selection mode the group is a `toolbar`, with
52
+ * `aria-orientation` under `orientation="vertical"`. Inside a toolbar it stays
53
+ * a named `group`, and the toolbar's own arrow keys walk its buttons, so a
54
+ * reader meets one toolbar.
55
+ *
18
56
  * @since 0.1.0
19
57
  * @status stable
20
58
  * @category actions
21
- * @aliases Segmented buttons, Split button
59
+ * @aliases Segmented buttons, Split button, Toggle buttons
22
60
  * @documentation https://fusion.dev/helix/components/button-group
23
61
  *
24
62
  * @slot - The buttons in the group.
63
+ *
64
+ * @event {Event} change - Fired after a press changes `value`, in a selection mode.
65
+ * @event {HelixBeforeChangeEvent} helix-before-change - Fired before a press changes `value`, with the value it would leave in `detail.value`. Cancelable: call `preventDefault()` to keep the buttons as they are.
66
+ *
67
+ * @cssproperty --button-group-pressed - Written on the control inside each toggle button of a group with `selection-mode`: `1` while it is pressed and `0` while it is not. Read it in a `::part(button)` rule to draw the pressed look.
25
68
  */
26
69
  export declare class HelixHeadlessButtonGroup extends HelixElement {
27
70
  /**
@@ -51,8 +94,103 @@ export declare class HelixHeadlessButtonGroup extends HelixElement {
51
94
  * column.
52
95
  */
53
96
  orientation?: 'horizontal' | 'vertical';
97
+ /**
98
+ * Whether the buttons stay pressed: `single` keeps one pressed at a time and
99
+ * `multiple` any number. Unset, they are action buttons, each its own tab
100
+ * stop, as before.
101
+ *
102
+ * Optional, so the attribute is absent until a consumer writes it.
103
+ */
104
+ selectionMode?: 'multiple' | 'single';
105
+ /**
106
+ * The values of the pressed buttons, comma separated, in a selection mode.
107
+ * A button's own `value` names it, so a value holds no comma.
108
+ *
109
+ * The attribute is the seed, and a press changes the property, which does
110
+ * not reflect, as a native control's `value` does not.
111
+ */
112
+ value: string;
113
+ /**
114
+ * The Helix buttons of a group in a selection mode, in the order they are
115
+ * written, and none otherwise. Empty on a server, which has no tree.
116
+ */
117
+ private get buttons();
118
+ /**
119
+ * Whether the group holds the tab stop and the arrow keys: in a selection
120
+ * mode, and not inside a toolbar, whose own keys walk the buttons.
121
+ */
122
+ private get roving();
123
+ /** Whether a button is pressed: its value is among the group's. */
124
+ private pressed;
125
+ /** The button focus was last on, which holds the tab stop. */
126
+ private lastFocused;
127
+ /**
128
+ * The buttons this group has told something, each with whether it moved
129
+ * their tab stop, so a button that leaves the group, or a group that leaves
130
+ * its selection mode, is put back as it was.
131
+ */
132
+ private readonly told;
133
+ private watcher;
134
+ /**
135
+ * Tells every button whether it is pressed and whether it holds the stop,
136
+ * and puts back the ones that left.
137
+ */
138
+ private sync;
139
+ /** Whether this group has said a native button in it cannot be a toggle, so it says so once. */
140
+ private warned;
141
+ /**
142
+ * Says once that a native `<button>` in a selection mode stays an action
143
+ * button, since its pressed state would be an attribute on the page's markup.
144
+ * One with its own `aria-pressed` is the page's to say, and is not reported.
145
+ */
146
+ private warn;
147
+ /**
148
+ * Writes a button's pressed state onto its control, once the button has
149
+ * rendered it, and takes it off a button the group no longer holds.
150
+ *
151
+ * **On the control inside the button's shadow root, never on the button.**
152
+ * An attribute written on the consumer's element is a hydration mismatch a
153
+ * React consumer's hydration reports and does not patch up, which is why
154
+ * `tellDisclosure` hands a trigger `expanded` as a property. Inside the
155
+ * button's own shadow root no framework compares the attribute, and the
156
+ * reader is on that control. Read at the moment of the write, so the last
157
+ * press wins whatever order renders finish in.
158
+ *
159
+ * The control is the `<button>` a Helix button renders, which is where its
160
+ * `delegatesFocus` sends focus and what a reader is on.
161
+ *
162
+ * A button that carries `aria-pressed` itself is the consumer's to say, and
163
+ * its own forwarding carries that down instead.
164
+ */
165
+ private mark;
166
+ /**
167
+ * Says on a button's control whether it is pressed, or that it is no toggle
168
+ * at all where `pressed` is undefined. A styled tier draws the look here.
169
+ *
170
+ * @param control The focusable control inside the button's shadow root.
171
+ * @param pressed Whether the button is pressed, or undefined where it is not a toggle.
172
+ */
173
+ protected markPressed(control: HTMLElement, pressed: boolean | undefined): void;
174
+ /**
175
+ * Presses or releases the button a press acted on. `helix-press` rather than
176
+ * `click`, since a button sends it only for a press that acted: never while
177
+ * it is disabled or pending.
178
+ */
179
+ private readonly handlePress;
180
+ /**
181
+ * Moves focus between the buttons without pressing them, a toolbar's
182
+ * reading: a press is its own act, on Space or Enter, which the button
183
+ * answers natively. Both axes, as every set here answers both, and the
184
+ * horizontal pair turns on a right-to-left page.
185
+ */
186
+ private readonly handleKeydown;
187
+ /** Moves the tab stop to the button focus arrived on. */
188
+ private readonly noteFocus;
189
+ connectedCallback(): void;
190
+ disconnectedCallback(): void;
54
191
  protected willUpdate(changed: PropertyValues): void;
55
192
  protected firstUpdated(changed: PropertyValues): void;
193
+ protected updated(changed: PropertyValues): void;
56
194
  protected render(): TemplateResult;
57
195
  }
58
196
  declare global {