@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
@@ -1,3 +1,17 @@
1
+ export const boxIn = (rect, area, box) => {
2
+ const scale = box / area.width;
3
+ return [
4
+ (rect.left - area.left) * scale,
5
+ (rect.top - area.top) * scale,
6
+ rect.width * scale,
7
+ rect.height * scale,
8
+ ];
9
+ };
10
+ export const logoBox = (rect, area, box) => {
11
+ const frame = boxIn(rect, area, box);
12
+ const [x, y, width, height] = frame;
13
+ return x < 0 || y < 0 || x + width > box || y + height > box ? undefined : frame;
14
+ };
1
15
  const attribute = (value) => value.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
2
16
  const unit = (value) => String(Math.round(value * 100) / 100);
3
17
  const placed = ([x, y, width, height]) => `x="${unit(x)}" y="${unit(y)}" width="${unit(width)}" height="${unit(height)}"`;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The name a code is read by: its `label`, or with none, the word for a code,
3
+ * followed by the value where the value is a web address.
4
+ *
5
+ * **A value that is no web address is never read out.** A code that sets up
6
+ * an authenticator (`otpauth://…?secret=…`) or joins a network
7
+ * (`WIFI:…;P:…;;`) carries a secret in its value, and the value as the name
8
+ * read that secret aloud to anyone in earshot. A web address is what a
9
+ * sighted reader gets by scanning, so it is said after the word.
10
+ */
11
+ export declare const qrName: (label: string, value: string, word: string) => string;
@@ -0,0 +1,7 @@
1
+ export const qrName = (label, value, word) => {
2
+ if (label.trim() !== '') {
3
+ return label;
4
+ }
5
+ const address = value.trim();
6
+ return /^https?:\/\/\S+$/iu.test(address) ? `${word}, ${address}` : word;
7
+ };
@@ -13,9 +13,12 @@ export type QrStyle = 'dots' | 'rounded' | 'square';
13
13
  * thousand modules, and an element for each would be a thousand nodes for one
14
14
  * image.
15
15
  *
16
- * The element has `role="img"` and an accessible name. Set the name to what the
17
- * code is for. When you pass no name, the value is the name. Most codes hold a
18
- * URL, and a URL read aloud gives the destination.
16
+ * The element has `role="img"` and an accessible name. Set `label` to what
17
+ * the code is for, such as "Open the menu". With no `label`, a code is named
18
+ * by `code-label`, "QR code", followed by its value where the value is a web
19
+ * address, which a URL read aloud makes a destination. Any other value is
20
+ * never read out: a code that sets up an authenticator or joins a network
21
+ * carries a secret in it.
19
22
  *
20
23
  * The quiet zone of four modules is required by the specification, and a
21
24
  * scanner rejects a code cropped to its edge. It is drawn inside the `viewBox`,
@@ -56,6 +59,11 @@ export type QrStyle = 'dots' | 'rounded' | 'square';
56
59
  * @cssstate overflow - The value is too long for a code at the correction level, so the fallback shows.
57
60
  */
58
61
  export declare class HelixHeadlessQrCode extends HelixElement {
62
+ /**
63
+ * The words a code with no `label` is named by, followed by its value where
64
+ * the value is a web address.
65
+ */
66
+ codeLabel: string;
59
67
  /**
60
68
  * The error correction level, which sets how much of the code can be lost
61
69
  * and still scan.
@@ -68,10 +76,13 @@ export declare class HelixHeadlessQrCode extends HelixElement {
68
76
  */
69
77
  correction?: QrCorrection;
70
78
  /**
71
- * The image's accessible name.
79
+ * The image's accessible name: what the code is for, such as "Open the
80
+ * menu".
72
81
  *
73
- * Falling back to the value is deliberate: a code almost always holds a URL,
74
- * and a URL is a better name than "QR code" for a reader who cannot see it.
82
+ * Absent names it by `code-label` and, for a web address, the address,
83
+ * which is what a reader who cannot scan it needs. Any other value is left
84
+ * out of the name, since it may hold a secret, such as an authenticator's
85
+ * key or a network's password.
75
86
  */
76
87
  label: string;
77
88
  /**
@@ -8,7 +8,8 @@ import { customElement, HelixElement, rgbOf } from '@fusion.dev/helix-core';
8
8
  import { html, nothing, svg, } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { encode } from 'uqr';
11
- import { qrSvg } from '../export.js';
11
+ import { boxIn, logoBox, qrSvg } from '../export.js';
12
+ import { qrName } from '../name.js';
12
13
  const QUIET = 4;
13
14
  const pixelsOf = (image) => {
14
15
  try {
@@ -76,6 +77,7 @@ const pathFor = (matrix, style, only = 'all') => {
76
77
  let HelixHeadlessQrCode = class HelixHeadlessQrCode extends HelixElement {
77
78
  constructor() {
78
79
  super(...arguments);
80
+ this.codeLabel = 'QR code';
79
81
  this.label = '';
80
82
  this.value = '';
81
83
  this.logo = false;
@@ -150,15 +152,8 @@ let HelixHeadlessQrCode = class HelixHeadlessQrCode extends HelixElement {
150
152
  if (!holder || holder.hidden || !mark || area.width === 0) {
151
153
  return undefined;
152
154
  }
153
- const scale = box / area.width;
154
- const at = (rect) => [
155
- (rect.left - area.left) * scale,
156
- (rect.top - area.top) * scale,
157
- rect.width * scale,
158
- rect.height * scale,
159
- ];
160
- const frame = at(holder.getBoundingClientRect());
161
- if (frame[0] < 0 || frame[1] < 0 || frame[0] + frame[2] > box || frame[1] + frame[3] > box) {
155
+ const frame = logoBox(holder.getBoundingClientRect(), area, box);
156
+ if (!frame) {
162
157
  return undefined;
163
158
  }
164
159
  const fill = getComputedStyle(holder).backgroundColor;
@@ -166,7 +161,7 @@ let HelixHeadlessQrCode = class HelixHeadlessQrCode extends HelixElement {
166
161
  const logo = {
167
162
  frame,
168
163
  ground: ground ? background : rgbOf(fill),
169
- mark: at(mark.getBoundingClientRect()),
164
+ mark: boxIn(mark.getBoundingClientRect(), area, box),
170
165
  };
171
166
  if (mark instanceof HTMLImageElement) {
172
167
  return { ...logo, href: pixelsOf(mark) };
@@ -215,7 +210,7 @@ let HelixHeadlessQrCode = class HelixHeadlessQrCode extends HelixElement {
215
210
  viewBox="0 0 ${box} ${box}"
216
211
  xmlns="http://www.w3.org/2000/svg"
217
212
  role="img"
218
- aria-label=${this.label || this.value}
213
+ aria-label=${qrName(this.label, this.value, this.codeLabel)}
219
214
  shape-rendering="crispEdges"
220
215
  >
221
216
  <path
@@ -250,6 +245,9 @@ let HelixHeadlessQrCode = class HelixHeadlessQrCode extends HelixElement {
250
245
  </span>`;
251
246
  }
252
247
  };
248
+ __decorate([
249
+ property({ attribute: 'code-label' })
250
+ ], HelixHeadlessQrCode.prototype, "codeLabel", void 0);
253
251
  __decorate([
254
252
  property({ attribute: 'correction' })
255
253
  ], HelixHeadlessQrCode.prototype, "correction", void 0);
@@ -30,6 +30,21 @@ declare const HelixHeadlessRadioGroup_base: (new (...args: any[]) => import("@fu
30
30
  * `helix-select`. A native radio sends `input` and `change` in the same task as
31
31
  * the click.
32
32
  *
33
+ * **Keyboard.** The group is one tab stop: the chosen radio, or the first that
34
+ * can be chosen.
35
+ *
36
+ * | Key | What it does |
37
+ * |---|---|
38
+ * | Right, Down | Chooses the next radio, from the last back to the first. |
39
+ * | Left, Up | Chooses the radio before, from the first round to the last. |
40
+ * | Home, End | Chooses the first or the last radio. |
41
+ * | Space, Enter | Chooses the focused radio. |
42
+ *
43
+ * Left and Right swap on a right-to-left page, and a disabled radio is passed
44
+ * over. Under `readonly` the arrows move focus and choose nothing. The group
45
+ * is a `radiogroup` named by `label`, and each radio is a `role="radio"`
46
+ * button with `aria-checked`.
47
+ *
33
48
  * @since 0.1.0
34
49
  * @status stable
35
50
  * @aliases RadioButtons, Choice group
@@ -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, HostClickController, MixinSelectableItem, } from '@fusion.dev/helix-core';
7
+ import { customElement, HelixElement, HostClickController, MixinSelectableItem, slotFilled, } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  let HelixHeadlessRadio = class HelixHeadlessRadio extends MixinSelectableItem(HelixElement) {
@@ -35,7 +35,7 @@ let HelixHeadlessRadio = class HelixHeadlessRadio extends MixinSelectableItem(He
35
35
  <span part="box" aria-hidden="true"
36
36
  ><slot
37
37
  name="indicator"
38
- @slotchange=${(event) => this.toggleState('has-indicator', event.target.assignedNodes().length > 0)}
38
+ @slotchange=${(event) => this.toggleState('has-indicator', slotFilled(event.target))}
39
39
  ></slot
40
40
  ></span>
41
41
  <slot name="icon"></slot>
@@ -18,6 +18,20 @@ declare const HelixHeadlessRating_base: (new (...args: any[]) => import("@fusion
18
18
  * With `clearable`, pressing the chosen score again, or Backspace or Delete on
19
19
  * the marks, takes the rating back to unrated and sends `helix-clear`.
20
20
  *
21
+ * **Keyboard.** The marks are one tab stop, on the chosen score or the first.
22
+ *
23
+ * | Key | What it does |
24
+ * |---|---|
25
+ * | Right, Down | Chooses the next score up, and stops at the highest. |
26
+ * | Left, Up | Chooses the next score down, and stops at the lowest. |
27
+ * | Home, End | Chooses the lowest or the highest score. |
28
+ * | Space, Enter | Chooses the focused score, or clears it under `clearable` when it is already chosen. |
29
+ * | Backspace, Delete | Clears the rating, under `clearable`. |
30
+ *
31
+ * Left and Right swap on a right-to-left page. The marks are a `radiogroup`
32
+ * named by `label`, and each score is a `radio` named by `option-label`, such
33
+ * as "3 out of 5". A read-only rating is one `img` named by its score.
34
+ *
21
35
  * @since 0.1.0
22
36
  * @status stable
23
37
  * @category forms
@@ -172,8 +186,8 @@ export declare class HelixHeadlessRating extends HelixHeadlessRating_base {
172
186
  * each of its own options. Anything past the first slotted element is
173
187
  * ignored.
174
188
  */
175
- private markSource;
176
- /** Whether `markSource` has had its first answer from the children. */
189
+ private readonly mark;
190
+ /** Whether `mark` has had its first answer from the children. */
177
191
  private markRead;
178
192
  /**
179
193
  * What the pointer is over, which is shown instead of the score while it is.
@@ -215,18 +229,17 @@ export declare class HelixHeadlessRating extends HelixHeadlessRating_base {
215
229
  /** The pointer drawing a score, while one is. */
216
230
  private dragging;
217
231
  /**
218
- * One clone per place a mark goes, kept so a render does not remake them.
232
+ * The shape for one place, or nothing when the stylesheet is drawing it.
219
233
  *
220
- * A drag renders on every pointer move, and handing Lit a fresh node each
221
- * time would swap every mark in the row on every frame of a gesture whose
222
- * whole point is that only the fill moves.
234
+ * One copy per place, kept so a render does not remake them: a drag renders
235
+ * on every pointer move, and handing Lit a fresh node each time would swap
236
+ * every mark in the row on every frame of a gesture whose whole point is
237
+ * that only the fill moves.
223
238
  */
224
- private readonly clones;
225
- /** The shape for one place, or nothing when the stylesheet is drawing it. */
226
239
  private markFor;
227
240
  private readonly handleMarkSlot;
228
241
  /** The element slotted into `empty-mark`, which the track copies. */
229
- private emptySource;
242
+ private readonly empty;
230
243
  /**
231
244
  * The step under a point on the row, or the nearest one to it.
232
245
  *
@@ -4,10 +4,9 @@ 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, valueAsNumber as asNumber, customElement, fillTemplate, numberAsValue as fromNumber, HelixClearEvent, HelixFormElement, HelixPreviewEvent, KeyboardNavigationController, MixinControllerValue, MixinField, reportRuntime, } from '@fusion.dev/helix-core';
7
+ import { AriaForwardController, valueAsNumber as asNumber, customElement, fillTemplate, numberAsValue as fromNumber, HelixClearEvent, HelixFormElement, HelixPreviewEvent, KeyboardNavigationController, MixinControllerValue, MixinField, nearest, reportRuntime, SlottedGlyph, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
- import { nearestStep } from '../steps.js';
11
10
  let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue(MixinField(HelixFormElement)) {
12
11
  constructor() {
13
12
  super(...arguments);
@@ -23,7 +22,7 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
23
22
  this.aria = new AriaForwardController(this, {
24
23
  target: () => this.partNamed('marks'),
25
24
  });
26
- this.markSource = undefined;
25
+ this.mark = new SlottedGlyph();
27
26
  this.markRead = false;
28
27
  this.previewing = undefined;
29
28
  this.keys = new KeyboardNavigationController(this, {
@@ -32,19 +31,12 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
32
31
  wrap: false,
33
32
  });
34
33
  this.dragging = undefined;
35
- this.clones = new Map();
36
34
  this.handleMarkSlot = (event) => {
37
35
  const slot = event.target;
38
- const source = slot.assignedElements({ flatten: true })[0];
39
- this.clones.clear();
40
- if (slot.name === 'empty-mark') {
41
- this.emptySource = source;
42
- }
43
- else {
44
- this.markSource = source;
45
- }
36
+ (slot.name === 'empty-mark' ? this.empty : this.mark).read(slot);
37
+ this.requestUpdate();
46
38
  };
47
- this.emptySource = undefined;
39
+ this.empty = new SlottedGlyph();
48
40
  this.handleDragStart = (event) => {
49
41
  if (this.isDisabled || this.readOnly || event.button !== 0) {
50
42
  return;
@@ -167,21 +159,11 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
167
159
  }));
168
160
  }
169
161
  markFor(layer, index) {
170
- const source = layer === 'track' ? (this.emptySource ?? this.markSource) : this.markSource;
171
- if (!source) {
172
- return undefined;
173
- }
174
- const key = `${layer}:${index}`;
175
- const held = this.clones.get(key);
176
- if (held) {
177
- return held;
178
- }
179
- const clone = source.cloneNode(true);
180
- this.clones.set(key, clone);
181
- return clone;
162
+ const glyph = layer === 'track' && this.empty.slotted ? this.empty : this.mark;
163
+ return glyph.copyFor(`${layer}:${index}`);
182
164
  }
183
165
  scoreAt(clientX) {
184
- let nearest;
166
+ let closest;
185
167
  let best = Number.POSITIVE_INFINITY;
186
168
  for (const option of this.renderRoot?.querySelectorAll('[part="option"]') ?? []) {
187
169
  const box = option.getBoundingClientRect();
@@ -191,10 +173,10 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
191
173
  const away = clientX < box.left ? box.left - clientX : clientX - box.right;
192
174
  if (away < best) {
193
175
  best = away;
194
- nearest = option.dataset.score;
176
+ closest = option.dataset.score;
195
177
  }
196
178
  }
197
- return nearest;
179
+ return closest;
198
180
  }
199
181
  clearsAt(score) {
200
182
  return this.clearable && this.value !== '' && Number(score) === Number(this.value);
@@ -239,8 +221,8 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
239
221
  super.willUpdate(changed);
240
222
  if (!this.markRead && this.childrenKnown) {
241
223
  this.markRead = true;
242
- this.markSource = this.knownChildren.find((child) => child.slot === 'mark');
243
- this.emptySource = this.knownChildren.find((child) => child.slot === 'empty-mark');
224
+ this.mark.take(this.knownChildren.find((child) => child.slot === 'mark'));
225
+ this.empty.take(this.knownChildren.find((child) => child.slot === 'empty-mark'));
244
226
  }
245
227
  }
246
228
  firstUpdated(changed) {
@@ -255,7 +237,7 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
255
237
  if (!answered) {
256
238
  return first;
257
239
  }
258
- return nearestStep(this.steps, this.score) ?? first;
240
+ return nearest(this.steps, this.score) ?? first;
259
241
  }
260
242
  render() {
261
243
  const showing = this.previewing ?? this.score;
@@ -276,7 +258,7 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
276
258
  aria-describedby=${this.describedBy ?? nothing}
277
259
  aria-invalid=${this.invalidity ?? nothing}
278
260
  aria-required=${this.required && !this.readOnly ? 'true' : nothing}
279
- data-slotted=${this.markSource ? '' : nothing}
261
+ data-slotted=${this.mark.slotted ? '' : nothing}
280
262
  style=${`--rating-full: ${Math.floor(showing)}; --rating-part: ${showing - Math.floor(showing)}; --rating-steps: ${this.positions.length}`}
281
263
  @pointercancel=${this.handleDragCancel}
282
264
  @keydown=${this.handleClearKey}
@@ -299,7 +281,7 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
299
281
  <span
300
282
  part="track"
301
283
  aria-hidden="true"
302
- data-slotted=${this.emptySource ? '' : nothing}
284
+ data-slotted=${this.empty.slotted ? '' : nothing}
303
285
  >${marksIn('track')}</span
304
286
  >
305
287
  <span
@@ -367,15 +349,9 @@ __decorate([
367
349
  __decorate([
368
350
  property({ attribute: 'value-text' })
369
351
  ], HelixHeadlessRating.prototype, "valueText", void 0);
370
- __decorate([
371
- state()
372
- ], HelixHeadlessRating.prototype, "markSource", void 0);
373
352
  __decorate([
374
353
  state()
375
354
  ], HelixHeadlessRating.prototype, "previewing", void 0);
376
- __decorate([
377
- state()
378
- ], HelixHeadlessRating.prototype, "emptySource", void 0);
379
355
  HelixHeadlessRating = __decorate([
380
356
  customElement('helix-headless-rating')
381
357
  ], HelixHeadlessRating);
@@ -5,6 +5,11 @@ import '../../tooltip/index.js';
5
5
  export interface ScatterPoint {
6
6
  depth: number;
7
7
  row: number;
8
+ /**
9
+ * How large the point is drawn under `size-by`, from 0 to 1: the square root
10
+ * of its share of the largest size, so its area follows the measure.
11
+ */
12
+ weight?: number | undefined;
8
13
  x: number;
9
14
  y: number;
10
15
  }
@@ -35,6 +40,11 @@ declare const HelixHeadlessScatterChart_base: typeof HelixElement & (new (...arg
35
40
  * `trend` draws each series' line of best fit, by least squares, across the
36
41
  * plot in the series' color.
37
42
  *
43
+ * **`size-by` names a third measure, drawn as each point's size**, as a bubble
44
+ * chart does: `size-by="Population"` takes that column out of the series and
45
+ * gives each point an area in proportion to its row's number there. The
46
+ * tooltip and the table still say the number.
47
+ *
38
48
  * The summary gives each series' correlation coefficient and its number of
39
49
  * points. The table has every number, and the drawing is hidden from screen
40
50
  * readers.
@@ -94,6 +104,7 @@ declare const HelixHeadlessScatterChart_base: typeof HelixElement & (new (...arg
94
104
  * @csspart y-name - The name of the vertical measure.
95
105
  * @csspart y-tick - One labeled value on the vertical axis.
96
106
  *
107
+ * @cssproperty --chart-point-weight - How large a point is drawn under `size-by`, from 0 to 1, by area. The component writes it, and the stylesheet that sizes the point reads it.
97
108
  * @cssproperty --chart-point-x - A point's horizontal position, as a percentage. The component writes it.
98
109
  * @cssproperty --chart-point-y - A point's vertical position, as a percentage from the top. The component writes it.
99
110
  * @cssproperty --chart-tick - A tick's position along its axis, as a percentage. The component writes it.
@@ -116,6 +127,15 @@ export declare class HelixHeadlessScatterChart extends HelixHeadlessScatterChart
116
127
  * The word the summary uses for a row when it counts them.
117
128
  */
118
129
  pointsLabel: string;
130
+ /**
131
+ * The heading of a column whose numbers size the points, such as
132
+ * `Population`, which makes the chart a bubble chart.
133
+ *
134
+ * That column is then a measure and no series: it draws no points of its
135
+ * own, and each point's area follows its row's number in it. Absent, every
136
+ * point is one size.
137
+ */
138
+ sizeBy: string;
119
139
  /**
120
140
  * Draws each series' line of best fit across the plot, by least squares, in
121
141
  * the series' color: the regression line a reader would otherwise draw by
@@ -148,6 +168,16 @@ export declare class HelixHeadlessScatterChart extends HelixHeadlessScatterChart
148
168
  * which is where a reader is told what it is.
149
169
  */
150
170
  seriesOf(cell: number): number | undefined;
171
+ /**
172
+ * The numeric columns by what they measure: the first across, the column
173
+ * `size-by` names as the size, and every other one up, as a series.
174
+ */
175
+ protected get measures(): {
176
+ across: number | undefined;
177
+ size: number | undefined;
178
+ ups: number[];
179
+ };
180
+ showsColumn(cell: number): boolean;
151
181
  /** Whether the levels across are ruled. */
152
182
  protected get rulesX(): boolean;
153
183
  /** Whether the levels up are ruled. */
@@ -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 { CHART_PLOT, customElement, drawTooltip, extent, formatNumbers, HelixElement, isRtl, linear, MixinChartSource, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, ticks, } from '@fusion.dev/helix-core';
7
+ import { CHART_PLOT, customElement, drawTooltip, extent, formatNumbers, HelixElement, isRtl, linear, MixinChartSource, PLOT_HEIGHT, PLOT_WIDTH, seriesIndex, seriesKey, ticks, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing, svg } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import '../../tooltip/index.js';
@@ -37,6 +37,7 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
37
37
  super(...arguments);
38
38
  this.correlationLabel = 'correlation';
39
39
  this.pointsLabel = 'points';
40
+ this.sizeBy = '';
40
41
  this.trend = false;
41
42
  this.drawn = [];
42
43
  this.nearest = undefined;
@@ -82,8 +83,20 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
82
83
  this.removeEventListener('pointerleave', this.handleLeave);
83
84
  }
84
85
  seriesOf(cell) {
85
- const depth = this.plottedColumns.indexOf(cell);
86
- return depth < 1 ? undefined : seriesIndex(depth - 1);
86
+ const depth = this.measures.ups.indexOf(cell);
87
+ return depth < 0 ? undefined : seriesIndex(depth);
88
+ }
89
+ get measures() {
90
+ const rows = this.sourceRows;
91
+ const key = seriesKey(this.sizeBy);
92
+ const size = key === ''
93
+ ? undefined
94
+ : this.plottedColumns.find((column) => seriesKey(rows.find((row) => row.cells[column])?.cells[column]?.name ?? '') === key);
95
+ const [across, ...ups] = this.plottedColumns.filter((column) => column !== size);
96
+ return { across, size, ups };
97
+ }
98
+ showsColumn(cell) {
99
+ return cell !== this.measures.size && super.showsColumn(cell);
87
100
  }
88
101
  get rulesX() {
89
102
  return this.gridlines !== undefined && this.gridlines !== 'y';
@@ -93,8 +106,7 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
93
106
  }
94
107
  get field() {
95
108
  const rows = this.sourceRows;
96
- const columns = this.plottedColumns;
97
- const [across, ...ups] = columns;
109
+ const { across, size, ups } = this.measures;
98
110
  const name = (column) => column === undefined ? '' : (rows.find((row) => row.cells[column])?.cells[column]?.name ?? '');
99
111
  const pairs = ups.map((column) => rows.flatMap((row, index) => {
100
112
  const x = across === undefined ? undefined : row.cells[across]?.value;
@@ -107,10 +119,22 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
107
119
  const y = nice(extent(all.length > 0 ? all.map(([, one]) => one) : [0]));
108
120
  const along = linear(x, [0, PLOT_WIDTH]);
109
121
  const up = linear(y, [PLOT_HEIGHT, 0]);
122
+ const sizes = rows.map((row) => (size === undefined ? undefined : row.cells[size]?.value));
123
+ const largest = Math.max(0, ...sizes.map((one) => one ?? 0));
124
+ const weight = (row) => {
125
+ const one = sizes[row];
126
+ return one === undefined || largest <= 0 ? undefined : Math.sqrt(Math.max(0, one) / largest);
127
+ };
110
128
  return {
111
129
  pairs: pairs.map((series) => series.map(([one, two]) => [one, two])),
112
130
  points: pairs.flatMap((series, depth) => shown[depth]
113
- ? series.map(([one, two, row]) => ({ depth, row, x: along(one), y: up(two) }))
131
+ ? series.map(([one, two, row]) => ({
132
+ depth,
133
+ row,
134
+ weight: weight(row),
135
+ x: along(one),
136
+ y: up(two),
137
+ }))
114
138
  : []),
115
139
  x,
116
140
  xName: name(across),
@@ -174,7 +198,7 @@ let HelixHeadlessScatterChart = class HelixHeadlessScatterChart extends MixinCha
174
198
  data-series=${index}
175
199
  ?data-active=${point.row === at}
176
200
  part="point${point.row === at ? ' point-active' : ''}"
177
- style=${`--chart-series: var(--chart-series-${index}); --chart-point-x: ${point.x}%; --chart-point-y: ${point.y}%`}
201
+ style=${`--chart-series: var(--chart-series-${index}); --chart-point-x: ${point.x}%; --chart-point-y: ${point.y}%${point.weight === undefined ? '' : `; --chart-point-weight: ${point.weight}`}`}
178
202
  ></span>`;
179
203
  })}
180
204
  ${this.renderTooltip()}
@@ -243,6 +267,9 @@ __decorate([
243
267
  __decorate([
244
268
  property({ attribute: 'points-label' })
245
269
  ], HelixHeadlessScatterChart.prototype, "pointsLabel", void 0);
270
+ __decorate([
271
+ property({ attribute: 'size-by' })
272
+ ], HelixHeadlessScatterChart.prototype, "sizeBy", void 0);
246
273
  __decorate([
247
274
  property({ reflect: true, type: Boolean })
248
275
  ], HelixHeadlessScatterChart.prototype, "trend", void 0);
@@ -1,11 +1,3 @@
1
- /**
2
- * The arithmetic behind the scrollbars a scroll area draws under `scrollbars`.
3
- *
4
- * Every length here is along one axis and counted from that axis' start, which
5
- * is the right-hand edge on a right-to-left page for the horizontal one. The
6
- * component turns a pointer and a `scrollLeft` into those counts, so nothing
7
- * below has to know which way the line runs.
8
- */
9
1
  /** Which scrollbars are drawn, and when. Absent keeps the browser's own. */
10
2
  export type Scrollbars = 'always' | 'auto' | 'hover' | 'scroll';
11
3
  /** The two axes a scrollbar runs along. */
@@ -1,4 +1,4 @@
1
- const clamp = (value, low, high) => Math.min(Math.max(value, low), high);
1
+ import { clamp } from '@fusion.dev/helix-core';
2
2
  export const thumbFraction = ({ client, scroll }) => scroll <= 0 ? 1 : clamp(client / scroll, 0, 1);
3
3
  export const scrollFraction = ({ client, offset, scroll }) => {
4
4
  const room = scroll - client;
@@ -0,0 +1,47 @@
1
+ /** A box on the screen, in viewport pixels. */
2
+ export interface Box {
3
+ height: number;
4
+ left: number;
5
+ top: number;
6
+ width: number;
7
+ }
8
+ /** What places a list over its trigger. */
9
+ export interface Over {
10
+ /** How far inside the screen's edges the list stays, in pixels. */
11
+ margin: number;
12
+ /** The option to sit on the trigger: its top within the list, and its height. */
13
+ option: {
14
+ height: number;
15
+ top: number;
16
+ };
17
+ /** The list's own size. */
18
+ panel: {
19
+ height: number;
20
+ width: number;
21
+ };
22
+ /** Whether the line of text runs right to left, which aligns the end edges. */
23
+ rtl: boolean;
24
+ /** The trigger. */
25
+ trigger: Box;
26
+ /** The screen. */
27
+ viewport: {
28
+ height: number;
29
+ width: number;
30
+ };
31
+ }
32
+ /**
33
+ * Where a list opened over its trigger goes, so the option it opens on sits
34
+ * where the trigger is: the native select's place on macOS, and Radix's
35
+ * `item-aligned`.
36
+ *
37
+ * The option's middle on the trigger's middle, then kept on the screen: a
38
+ * list that would run off the bottom moves up, and one that would run off the
39
+ * top moves down, so near an edge the option is beside the trigger rather than
40
+ * on it. A list taller than the screen starts at its top edge. The start edges
41
+ * line up, the left in English and the right in Arabic, kept on the screen the
42
+ * same way.
43
+ */
44
+ export declare const over: ({ margin, option, panel, rtl, trigger, viewport, }: Over) => {
45
+ left: number;
46
+ top: number;
47
+ };
@@ -0,0 +1,7 @@
1
+ export const over = ({ margin, option, panel, rtl, trigger, viewport, }) => {
2
+ const centered = trigger.top + trigger.height / 2 - (option.top + option.height / 2);
3
+ const top = Math.max(margin, Math.min(centered, viewport.height - margin - panel.height));
4
+ const start = rtl ? trigger.left + trigger.width - panel.width : trigger.left;
5
+ const left = Math.max(margin, Math.min(start, viewport.width - margin - panel.width));
6
+ return { left: Math.round(left), top: Math.round(top) };
7
+ };