@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,11 +4,11 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { AriaForwardController, byRow, CHART_PLOT, customElement, drawTooltip, formatNumbers, HelixElement, HelixResizeEvent, MixinChartSource, markRegion, searchableById, writtenList, writtenNumbers, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, byRow, CHART_PLOT, commaList, customElement, drawTooltip, fillTemplate, formatNumbers, HelixBeforeChangeEvent, HelixElement, HelixResizeEvent, MixinChartSource, markRegion, pressedValue, rangeAdd, requestChange, StatusController, searchableById, writtenList, writtenNumbers, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
10
  import '../../tooltip/index.js';
11
- import { fits, toneOf } from './buckets.js';
11
+ import { fits, monthPage, overviewOf, toneOf } from './buckets.js';
12
12
  let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(HelixElement) {
13
13
  constructor() {
14
14
  super(...arguments);
@@ -17,9 +17,11 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
17
17
  this.gap = 2;
18
18
  this.minCell = 8;
19
19
  this.rows = 1;
20
+ this.selectionLabel = '{count} selected';
20
21
  this.thresholds = '';
21
22
  this.tones = '';
22
23
  this.totalLabel = 'Total';
24
+ this.value = '';
23
25
  this.width = undefined;
24
26
  this.metrics = undefined;
25
27
  this.aria = new AriaForwardController(this, {
@@ -27,6 +29,35 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
27
29
  });
28
30
  this.observer = undefined;
29
31
  this.announced = undefined;
32
+ this.anchor = -1;
33
+ this.status = new StatusController(this);
34
+ this.onKeydown = (event) => {
35
+ const at = this.pointer.at;
36
+ if (!this.selectionMode || at < 0 || event.altKey || event.ctrlKey || event.metaKey) {
37
+ return;
38
+ }
39
+ if (event.key === ' ' || event.key === 'Enter') {
40
+ event.preventDefault();
41
+ this.press(at);
42
+ return;
43
+ }
44
+ const moves = /^(Arrow|Page|Home$|End$)/.test(event.key);
45
+ if (!event.shiftKey || !moves || this.selectionMode !== 'multiple') {
46
+ return;
47
+ }
48
+ const keys = this.keysOf(this.drawn);
49
+ const added = rangeAdd(commaList(this.value), keys, this.anchor < 0 ? at : this.anchor, at);
50
+ if (added) {
51
+ this.choose(added.join(', '));
52
+ }
53
+ };
54
+ this.onClick = (event) => {
55
+ const cell = event.target?.closest?.('[part~="cell"]');
56
+ const index = Number(cell?.getAttribute('data-index') ?? -1);
57
+ if (cell && Number.isInteger(index) && index >= 0) {
58
+ this.press(index);
59
+ }
60
+ };
30
61
  this.onPointerOver = (event) => {
31
62
  if (!this.pointer.accepts(event)) {
32
63
  return;
@@ -44,6 +75,35 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
44
75
  get visible() {
45
76
  return Math.min(this.points.length, this.room);
46
77
  }
78
+ keysOf(cells) {
79
+ const offset = this.points.length - cells.length;
80
+ const rows = this.sourceRows;
81
+ return cells.map((_, index) => rows[offset + index]?.label ?? '');
82
+ }
83
+ choose(next) {
84
+ if (next === this.value) {
85
+ return;
86
+ }
87
+ requestChange({
88
+ apply: (value) => {
89
+ this.value = value;
90
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
91
+ this.status.say(fillTemplate(this.selectionLabel, { count: commaList(value).length }));
92
+ },
93
+ event: (value) => new HelixBeforeChangeEvent({ value }),
94
+ host: this,
95
+ next,
96
+ });
97
+ }
98
+ press(index) {
99
+ const mode = this.selectionMode;
100
+ const key = this.keysOf(this.drawn)[index];
101
+ if (!mode || key === undefined || key === '') {
102
+ return;
103
+ }
104
+ this.anchor = index;
105
+ this.choose(pressedValue(mode, this.value, key));
106
+ }
47
107
  get points() {
48
108
  const series = this.series[0];
49
109
  if (!series) {
@@ -99,8 +159,13 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
99
159
  super.willUpdate(changed);
100
160
  this.pointer.grid = this.rowCount;
101
161
  }
162
+ pageFrom(at, forward) {
163
+ const offset = this.points.length - this.drawn.length;
164
+ return monthPage(this.sourceRows.slice(offset).map((row) => row.when), at, forward);
165
+ }
102
166
  connectedCallback() {
103
167
  super.connectedCallback();
168
+ this.addEventListener('keydown', this.onKeydown);
104
169
  if (typeof ResizeObserver !== 'undefined') {
105
170
  this.observer = new ResizeObserver((entries) => {
106
171
  const measured = entries[0]?.contentRect.width;
@@ -113,6 +178,7 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
113
178
  }
114
179
  disconnectedCallback() {
115
180
  super.disconnectedCallback();
181
+ this.removeEventListener('keydown', this.onKeydown);
116
182
  this.observer?.disconnect();
117
183
  this.observer = undefined;
118
184
  }
@@ -143,6 +209,8 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
143
209
  const palette = writtenList(this.tones);
144
210
  const cells = this.drawn;
145
211
  const readable = cells.length > 0;
212
+ const keys = this.selectionMode ? this.keysOf(cells) : [];
213
+ const chosen = new Set(this.selectionMode ? commaList(this.value) : []);
146
214
  return html `<div
147
215
  aria-describedby=${readable ? 'helix-chart-summary' : nothing}
148
216
  aria-label=${this.named || 'Heatmap'}
@@ -151,6 +219,7 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
151
219
  role=${readable ? 'application' : 'img'}
152
220
  style="--heatmap-rows: ${this.rowCount}"
153
221
  tabindex=${readable ? '0' : nothing}
222
+ @click=${this.onClick}
154
223
  @pointerover=${this.onPointerOver}
155
224
  @pointerleave=${this.onPointerLeave}
156
225
  >
@@ -159,13 +228,14 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
159
228
  : cells.map((reading, index) => {
160
229
  const tone = toneOf(reading, bounds, palette);
161
230
  const active = this.pointer.at === index;
231
+ const picked = chosen.has(keys[index] ?? '');
162
232
  return tone === undefined
163
233
  ? html `<span
164
- part="cell${active ? ' cell-active' : ''}"
234
+ part="cell${active ? ' cell-active' : ''}${picked ? ' cell-selected' : ''}"
165
235
  data-index=${index}
166
236
  ></span>`
167
237
  : html `<span
168
- part="cell${active ? ' cell-active' : ''}"
238
+ part="cell${active ? ' cell-active' : ''}${picked ? ' cell-selected' : ''}"
169
239
  data-index=${index}
170
240
  data-tone=${tone}
171
241
  style="--heatmap-tone: var(--heatmap-tone-${tone})"
@@ -178,27 +248,18 @@ let HelixHeadlessHeatmap = class HelixHeadlessHeatmap extends MixinChartSource(H
178
248
  <span part="start"><slot name="start"></slot></span>
179
249
  <span part="summary"><slot name="summary"></slot></span>
180
250
  <span part="end"><slot name="end"></slot></span>
181
- </div>${readable ? this.renderOverview(cells) : nothing}`;
251
+ </div>${readable ? this.renderOverview(cells) : nothing}${this.status.spoken
252
+ ? html `<span part="selection" role="status">${this.status.said}</span>`
253
+ : nothing}`;
182
254
  }
183
255
  renderOverview(cells) {
184
256
  const rows = this.sourceRows;
185
257
  const offset = this.points.length - cells.length;
186
258
  const column = this.plottedColumns[0] ?? -1;
187
- let busiest = -1;
188
- let total = 0;
189
- cells.forEach((cell, index) => {
190
- if (cell.value === undefined) {
191
- return;
192
- }
193
- total += cell.value;
194
- if (busiest < 0 || cell.value > (cells[busiest]?.value ?? 0)) {
195
- busiest = index;
196
- }
197
- });
259
+ const { busiest, total } = overviewOf(cells);
198
260
  const row = rows[offset + busiest];
199
- const most = row
200
- ? `${this.busiestLabel}: ${row.label} ${row.cells[column]?.text ?? ''}`.trim()
201
- : '';
261
+ const text = row?.cells[column]?.text ?? '';
262
+ const most = row ? `${this.busiestLabel}: ${row.label}${text === '' ? '' : `, ${text}`}` : '';
202
263
  const sum = `${this.totalLabel} ${formatNumbers(this.locale)(total)}`;
203
264
  return html `<p id="helix-chart-summary" part="overview">${most ? `${most}; ${sum}` : sum}.</p>`;
204
265
  }
@@ -234,6 +295,12 @@ __decorate([
234
295
  __decorate([
235
296
  property({ type: Number })
236
297
  ], HelixHeadlessHeatmap.prototype, "rows", void 0);
298
+ __decorate([
299
+ property({ attribute: 'selection-label' })
300
+ ], HelixHeadlessHeatmap.prototype, "selectionLabel", void 0);
301
+ __decorate([
302
+ property({ attribute: 'selection-mode' })
303
+ ], HelixHeadlessHeatmap.prototype, "selectionMode", void 0);
237
304
  __decorate([
238
305
  property()
239
306
  ], HelixHeadlessHeatmap.prototype, "thresholds", void 0);
@@ -243,6 +310,9 @@ __decorate([
243
310
  __decorate([
244
311
  property({ attribute: 'total-label' })
245
312
  ], HelixHeadlessHeatmap.prototype, "totalLabel", void 0);
313
+ __decorate([
314
+ property()
315
+ ], HelixHeadlessHeatmap.prototype, "value", void 0);
246
316
  __decorate([
247
317
  state()
248
318
  ], HelixHeadlessHeatmap.prototype, "width", void 0);
@@ -1,3 +1,3 @@
1
1
  import './set.js';
2
- export { type IconResolver, registerIconLibrary, registerIcons, resolveIcon } from './registry.js';
2
+ export { type IconLibraryOptions, type IconResolver, registerIconLibrary, registerIcons, resolveIcon, } from './registry.js';
3
3
  export * from './root/icon-root.js';
@@ -1,3 +1,3 @@
1
1
  import './set.js';
2
- export { registerIconLibrary, registerIcons, resolveIcon } from './registry.js';
2
+ export { registerIconLibrary, registerIcons, resolveIcon, } from './registry.js';
3
3
  export * from './root/icon-root.js';
@@ -1,6 +1,15 @@
1
1
  import type { HelixIcon } from '@fusion.dev/helix-icons';
2
2
  /** What a library gives for a name: a glyph, a glyph later, or nothing. */
3
3
  export type IconResolver = (name: string) => HelixIcon | Promise<HelixIcon | undefined> | undefined;
4
+ /** How a library's glyphs are drawn, beside the names it resolves. */
5
+ export interface IconLibraryOptions {
6
+ /**
7
+ * Changes each drawn `<svg>` of the library's glyphs, as Web Awesome's
8
+ * `mutator` does: a stroke width, a fill, an attribute the set leaves out.
9
+ * Called after every render that draws one of them.
10
+ */
11
+ mutator?: (svg: SVGSVGElement) => void;
12
+ }
4
13
  /**
5
14
  * Makes glyphs resolvable by name: a record of names to glyphs, such as the
6
15
  * whole set, or a list of glyphs, each under its own `name`.
@@ -15,14 +24,30 @@ export declare const registerIcons: (glyphs: Record<string, HelixIcon> | readonl
15
24
  * The resolver may answer at once or with a promise; an element waiting on a
16
25
  * promise draws the glyph when it arrives and fires `load`. Each written name
17
26
  * is asked once, and a prefix registered again is asked afresh.
27
+ *
28
+ * A sprite sheet is a library whose glyphs point into it, so the sheet is
29
+ * fetched once and cached:
30
+ *
31
+ * ```js
32
+ * registerIconLibrary('sprite', (name) => ({
33
+ * content: `<use href="/icons.svg#${name}"></use>`,
34
+ * name,
35
+ * viewBox: '0 0 24 24',
36
+ * }))
37
+ * ```
38
+ *
39
+ * `mutator` changes each drawn `<svg>` of the library, such as
40
+ * `(svg) => svg.setAttribute('stroke-width', '1.5')`.
18
41
  */
19
- export declare const registerIconLibrary: (prefix: string, resolve: IconResolver) => void;
42
+ export declare const registerIconLibrary: (prefix: string, resolve: IconResolver, options?: IconLibraryOptions) => void;
20
43
  /**
21
44
  * Calls `watcher` whenever a glyph or a library is registered, so an element
22
45
  * drawn before its glyph was registered can look again. Returns the function
23
46
  * that stops it.
24
47
  */
25
48
  export declare const watchIcons: (watcher: () => void) => (() => void);
49
+ /** The mutator of the library a written name asks, or `undefined`. */
50
+ export declare const iconMutator: (name: string) => ((svg: SVGSVGElement) => void) | undefined;
26
51
  /** The registered glyph of this name, or `undefined`. */
27
52
  export declare const registeredIcon: (name: string) => HelixIcon | undefined;
28
53
  /**
@@ -24,9 +24,16 @@ export const registerIcons = (glyphs) => {
24
24
  }
25
25
  changed(held);
26
26
  };
27
- export const registerIconLibrary = (prefix, resolve) => {
27
+ export const registerIconLibrary = (prefix, resolve, options = {}) => {
28
28
  const held = registry();
29
29
  held.libraries.set(prefix, resolve);
30
+ held.mutators ??= new Map();
31
+ if (options.mutator) {
32
+ held.mutators.set(prefix, options.mutator);
33
+ }
34
+ else {
35
+ held.mutators.delete(prefix);
36
+ }
30
37
  for (const name of held.answers.keys()) {
31
38
  if (name.startsWith(`${prefix}:`)) {
32
39
  held.answers.delete(name);
@@ -41,6 +48,10 @@ export const watchIcons = (watcher) => {
41
48
  held.watchers.delete(watcher);
42
49
  };
43
50
  };
51
+ export const iconMutator = (name) => {
52
+ const split = name.indexOf(':');
53
+ return split > 0 ? registry().mutators?.get(name.slice(0, split)) : undefined;
54
+ };
44
55
  export const registeredIcon = (name) => registry().glyphs.get(name);
45
56
  export const resolveIcon = (name) => {
46
57
  const registered = registeredIcon(name);
@@ -36,6 +36,11 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
36
36
  * button with no text. An `aria-label` on the element works too: the element
37
37
  * is then an image with that name.
38
38
  *
39
+ * **A decorative icon that is all a control holds is reported.** In a
40
+ * development build, a button or a link whose only content is an icon with no
41
+ * `label`, and which has no name of its own, warns `missing-name` once,
42
+ * through the runtime channel every Helix warning uses.
43
+ *
39
44
  * @since 0.1.0
40
45
  * @status stable
41
46
  * @category media
@@ -121,6 +126,14 @@ export declare class HelixHeadlessIcon extends HelixElement {
121
126
  private lookup;
122
127
  protected willUpdate(changed: PropertyValues): void;
123
128
  protected updated(changed: PropertyValues): void;
129
+ /** Hands the drawn `<svg>` to the mutator of the library `name` asks, where it has one. */
130
+ private mutate;
131
+ /**
132
+ * Asks the button or link a decorative icon sits in whether it has a name.
133
+ * Its content is this icon alone where it has none, and the audit says so
134
+ * once, in a development build only. A Helix button reports its own name.
135
+ */
136
+ private auditControl;
124
137
  /** The name a report is waiting on the page to settle for, so it is asked once. */
125
138
  private pending;
126
139
  /**
@@ -4,11 +4,11 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, HelixElement, hostAria, reportRuntime } from '@fusion.dev/helix-core';
7
+ import { auditName, customElement, HelixElement, hostAria, reportRuntime, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
11
- import { resolveIcon, watchIcons } from '../registry.js';
11
+ import { iconMutator, resolveIcon, watchIcons } from '../registry.js';
12
12
  import { afterPageSettles } from '../settle.js';
13
13
  let HelixHeadlessIcon = class HelixHeadlessIcon extends HelixElement {
14
14
  constructor() {
@@ -66,6 +66,10 @@ let HelixHeadlessIcon = class HelixHeadlessIcon extends HelixElement {
66
66
  }
67
67
  updated(changed) {
68
68
  super.updated(changed);
69
+ if (this.drawn !== undefined) {
70
+ this.mutate();
71
+ this.auditControl();
72
+ }
69
73
  if (this.arrived === this.name && this.drawn !== undefined) {
70
74
  this.dispatchEvent(new Event('load', { bubbles: false, cancelable: false, composed: false }));
71
75
  }
@@ -80,6 +84,23 @@ let HelixHeadlessIcon = class HelixHeadlessIcon extends HelixElement {
80
84
  this.missed = '';
81
85
  }
82
86
  }
87
+ mutate() {
88
+ const svg = this.renderRoot.querySelector('svg');
89
+ const mutator = this.icon === undefined ? iconMutator(this.name) : undefined;
90
+ if (svg && mutator) {
91
+ mutator(svg);
92
+ }
93
+ }
94
+ auditControl() {
95
+ if (this.label !== '' || this.hasAttribute('aria-label')) {
96
+ return;
97
+ }
98
+ const control = this.closest('a[href], button, summary, [role="button"], [role="link"]');
99
+ if (control) {
100
+ auditName(control, () => null, `Its only content is <${this.localName}>, which is decorative: give the icon a \`label\`, or the control an \`aria-label\`.`, () => (control.textContent ?? '').trim() !== '' ||
101
+ control.querySelector('[alt]:not([alt=""]), [aria-label]:not([aria-label=""])') !== null);
102
+ }
103
+ }
83
104
  reportMissing() {
84
105
  const name = this.pending;
85
106
  this.pending = '';
@@ -1,2 +1,2 @@
1
- export { type IconResolver, registerIconLibrary, registerIcons, resolveIcon } from '../registry.js';
1
+ export { type IconLibraryOptions, type IconResolver, registerIconLibrary, registerIcons, resolveIcon, } from '../registry.js';
2
2
  export * from './icon-root.js';
@@ -1,2 +1,2 @@
1
- export { registerIconLibrary, registerIcons, resolveIcon } from '../registry.js';
1
+ export { registerIconLibrary, registerIcons, resolveIcon, } from '../registry.js';
2
2
  export * from './icon-root.js';
@@ -34,6 +34,8 @@ import { type PropertyValues, type TemplateResult } from 'lit';
34
34
  *
35
35
  * @cssstate error - The image failed to load, and the fallback shows.
36
36
  * @cssstate loaded - The image loaded.
37
+ * @cssstate loading - The image is on its way, so neither `loaded` nor `error` is true yet.
38
+ * @cssstate waiting - The image is on its way and `placeholder-delay` still holds the placeholder back.
37
39
  */
38
40
  export declare class HelixHeadlessImage extends HelixElement {
39
41
  /**
@@ -121,6 +123,11 @@ export declare class HelixHeadlessImage extends HelixElement {
121
123
  *
122
124
  * `loading` writes nothing, because it is where every image starts and the
123
125
  * resting state of a mirror is the attribute's absence.
126
+ *
127
+ * **Kept beside the custom states, and what the styled tier draws from.** An
128
+ * engine without `CustomStateSet`, Safari before 17.4 and Firefox before
129
+ * 126, records no state, and a picture faded in by `:state(loaded)` alone
130
+ * would stay transparent there for good. A sheet of your own can read either.
124
131
  */
125
132
  status: 'error' | 'loaded' | 'loading';
126
133
  /**
@@ -194,6 +201,8 @@ export declare class HelixHeadlessImage extends HelixElement {
194
201
  private settle;
195
202
  protected willUpdate(changed: PropertyValues): void;
196
203
  protected updated(changed: PropertyValues): void;
204
+ /** Whether the picture is on its way and the wait still holds the placeholder back. */
205
+ private get waiting();
197
206
  private startWait;
198
207
  protected firstUpdated(changed: PropertyValues): void;
199
208
  /**
@@ -92,7 +92,12 @@ let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
92
92
  }
93
93
  this.toggleState('loaded', this.status === 'loaded');
94
94
  this.toggleState('error', this.status === 'error');
95
+ this.toggleState('loading', this.status === 'loading');
95
96
  }
97
+ this.toggleState('waiting', this.waiting);
98
+ }
99
+ get waiting() {
100
+ return this.status === 'loading' && this.placeholderDelay > 0 && !this.wait.elapsed;
96
101
  }
97
102
  startWait() {
98
103
  this.wait.start(this.placeholderDelay > 0 ? this.placeholderDelay : undefined);
@@ -142,7 +147,7 @@ let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
142
147
  <span
143
148
  part="placeholder"
144
149
  aria-hidden="true"
145
- ?hidden=${this.status !== 'loading' || (this.placeholderDelay > 0 && !this.wait.elapsed)}
150
+ ?hidden=${this.status !== 'loading' || this.waiting}
146
151
  ><slot name="placeholder"></slot
147
152
  ></span>
148
153
  <span
@@ -1,4 +1,4 @@
1
- import { AriaForwardController, HelixFormElement } from '@fusion.dev/helix-core';
1
+ import { AriaForwardController, HelixFormElement, type OwnRule } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  /** What the control accepts, which is text of one kind or another. */
4
4
  export type InputType = 'email' | 'number' | 'password' | 'search' | 'tel' | 'text' | 'url';
@@ -25,16 +25,30 @@ declare const HelixHeadlessInput_base: (new (...args: any[]) => import("@fusion.
25
25
  * Aria's TextField `validate` does. What it returns is the message the field
26
26
  * shows once the user has edited it, and the message a submit raises.
27
27
  *
28
+ * **Keyboard.** The field's own keys are the platform's. Enter submits the
29
+ * form through its default button, as in a native field. The clear button
30
+ * under `clearable` and the reveal button under `reveal` are tab stops of
31
+ * their own after the field, so a keyboard reaches them.
32
+ *
33
+ * The inner input is named by `label`, or by a host `aria-label`,
34
+ * `aria-labelledby` or `<label for>`, and is described by the hint and the
35
+ * message, with `aria-invalid` while a message shows. The clear button is
36
+ * named `clear-label`, and the reveal button `reveal-label` with
37
+ * `aria-pressed` while the password shows.
38
+ *
28
39
  * @since 0.1.0
29
40
  * @status stable
30
41
  * @category forms
31
42
  * @aliases TextField, Field, TextInput
32
43
  * @documentation https://fusion.dev/helix/components/input
33
44
  *
45
+ * @slot clear-icon - Replaces the cross on the clear button, under `clearable`.
46
+ * @slot conceal-icon - Replaces the crossed-out eye on the reveal button while the password shows, under `reveal`.
34
47
  * @slot end - After the text, for an icon or a unit.
35
48
  * @slot feedback - The message that says what is wrong, for content richer than a string. It shows while there is a message.
36
49
  * @slot hint - Help under the control, for content richer than the `hint` text.
37
50
  * @slot label - The label, for content richer than the `label` text.
51
+ * @slot reveal-icon - Replaces the eye on the reveal button while the password is hidden, under `reveal`.
38
52
  * @slot start - Before the text, for an icon or a prefix.
39
53
  *
40
54
  * @event {Event} change - Sent when the user finishes changing the value. Relayed from the native input.
@@ -290,7 +304,14 @@ export declare class HelixHeadlessInput extends HelixHeadlessInput_base {
290
304
  * nothing wrong. Return a message, or a list of them, to mark the field
291
305
  * invalid, and `true`, `null`, or nothing to accept the value.
292
306
  */
293
- validate?: (value: string) => string | string[] | true | null | undefined;
307
+ validate?: OwnRule;
308
+ /** The cross on the clear button, behind a seam a tier can move. */
309
+ protected renderClearIcon(): TemplateResult;
310
+ /**
311
+ * The eye on the reveal button, behind a seam a tier can move: open while
312
+ * the password is hidden, and crossed out while it shows.
313
+ */
314
+ protected renderRevealIcon(): TemplateResult;
294
315
  protected render(): TemplateResult;
295
316
  }
296
317
  declare global {
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { AriaForwardController, customElement, drawIcon, HelixClearEvent, HelixFormElement, holdCaret, MixinControllerValue, MixinField, restoreValue, submitImplicitly, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, applyOwnRule, customElement, drawIcon, HelixClearEvent, HelixFormElement, holdCaret, MixinControllerValue, MixinField, restoreValue, submitImplicitly, } from '@fusion.dev/helix-core';
8
8
  import { eye, eyeSlash, x } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -112,11 +112,7 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(M
112
112
  return;
113
113
  }
114
114
  this.copyValidity(control);
115
- const own = control.validity.valid ? this.validate?.(this.value) : undefined;
116
- const message = Array.isArray(own) ? own.join(' ') : typeof own === 'string' ? own : '';
117
- if (message !== '') {
118
- this.internals.setValidity({ customError: true }, message, control);
119
- }
115
+ applyOwnRule(this.internals, control, this.validate, this.value);
120
116
  this.refreshFeedback();
121
117
  }
122
118
  focus(options) {
@@ -156,6 +152,14 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(M
156
152
  this.releaseCaret = undefined;
157
153
  this.sync();
158
154
  }
155
+ renderClearIcon() {
156
+ return html `<slot name="clear-icon">${drawIcon(x)}</slot>`;
157
+ }
158
+ renderRevealIcon() {
159
+ return this.revealed
160
+ ? html `<slot name="conceal-icon">${drawIcon(eyeSlash)}</slot>`
161
+ : html `<slot name="reveal-icon">${drawIcon(eye)}</slot>`;
162
+ }
159
163
  render() {
160
164
  return html `${this.renderLabel()}
161
165
  <div part="field">
@@ -199,7 +203,7 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(M
199
203
  aria-label=${this.clearLabel}
200
204
  @click=${this.handleClear}
201
205
  >
202
- ${drawIcon(x)}
206
+ ${this.renderClearIcon()}
203
207
  </button>`
204
208
  : nothing}
205
209
  ${this.reveal && this.type === 'password'
@@ -213,7 +217,7 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(M
213
217
  this.revealed = !this.revealed;
214
218
  }}
215
219
  >
216
- ${drawIcon(this.revealed ? eyeSlash : eye)}
220
+ ${this.renderRevealIcon()}
217
221
  </button>`
218
222
  : nothing}
219
223
  </div>
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The words and glyphs a chord is drawn with, per platform.
3
+ *
4
+ * Beside the family's `index.ts`, because which label a key gets is a table
5
+ * and a rule, and not the key's template.
6
+ */
7
+ /** A chord's keys as they are shown, and what is shown between two of them. */
8
+ export interface KeyNames {
9
+ /** Each key's label, in the order the chord names them. */
10
+ keys: string[];
11
+ /** What goes between two keys: `+`, a hyphen, or nothing. */
12
+ separator: string;
13
+ }
14
+ /**
15
+ * The labels of a chord's keys on a platform, with `own` labels in place of
16
+ * the platform's, and what goes between two keys.
17
+ *
18
+ * `parts` are the chord's names as `chordParts` splits them. A name `own`
19
+ * holds takes that label, a name the platform knows takes its glyph or word,
20
+ * and any other single character is shown in capitals.
21
+ *
22
+ * **On an Apple keyboard glyphs run together and words are joined with a
23
+ * hyphen**, as Apple writes `Command-K`. A label of more than one character
24
+ * from `own` is a word, so `{"mod": "Command"}` shows `Command-K` there, where
25
+ * nothing between the two ran them into `CommandK`. The platform's own `Esc`
26
+ * and `Space` keep running on from a glyph, as `⌘Esc` always has. Everywhere
27
+ * else keys are joined with `+`.
28
+ */
29
+ export declare const keyNames: (parts: readonly string[], apple: boolean, own?: Readonly<Record<string, string>>) => KeyNames;
@@ -0,0 +1,52 @@
1
+ const APPLE = {
2
+ alt: '⌥',
3
+ backspace: '⌫',
4
+ cmd: '⌘',
5
+ ctrl: '⌃',
6
+ del: '⌦',
7
+ down: '↓',
8
+ enter: '↵',
9
+ esc: 'Esc',
10
+ left: '←',
11
+ meta: '⌘',
12
+ mod: '⌘',
13
+ opt: '⌥',
14
+ right: '→',
15
+ shift: '⇧',
16
+ space: 'Space',
17
+ tab: '⇥',
18
+ up: '↑',
19
+ };
20
+ const ELSEWHERE = {
21
+ alt: 'Alt',
22
+ backspace: 'Backspace',
23
+ cmd: 'Win',
24
+ ctrl: 'Ctrl',
25
+ del: 'Del',
26
+ down: '↓',
27
+ enter: 'Enter',
28
+ esc: 'Esc',
29
+ left: '←',
30
+ meta: 'Win',
31
+ mod: 'Ctrl',
32
+ opt: 'Alt',
33
+ right: '→',
34
+ shift: 'Shift',
35
+ space: 'Space',
36
+ tab: 'Tab',
37
+ up: '↑',
38
+ };
39
+ export const keyNames = (parts, apple, own = {}) => {
40
+ const names = apple ? APPLE : ELSEWHERE;
41
+ let worded = false;
42
+ const keys = parts.map((part) => {
43
+ const name = part.toLowerCase();
44
+ const mine = own[name];
45
+ if (mine !== undefined) {
46
+ worded ||= [...mine].length > 1;
47
+ return mine;
48
+ }
49
+ return names[name] ?? (part.length === 1 ? part.toUpperCase() : part);
50
+ });
51
+ return { keys, separator: apple ? (worded ? '-' : '') : '+' };
52
+ };