@fusion.dev/helix-headless 0.142.1 → 0.144.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 (285) hide show
  1. package/custom-elements.json +25381 -1202
  2. package/dist/accordion/item/accordion-item.d.ts +38 -2
  3. package/dist/accordion/item/accordion-item.js +38 -6
  4. package/dist/accordion/root/accordion-root.d.ts +23 -1
  5. package/dist/accordion/root/accordion-root.js +39 -3
  6. package/dist/announcement/root/announcement-root.d.ts +11 -7
  7. package/dist/announcement/root/announcement-root.js +1 -1
  8. package/dist/app-header/root/app-header-root.d.ts +21 -0
  9. package/dist/app-header/root/app-header-root.js +31 -5
  10. package/dist/area-chart/root/area-chart-root.d.ts +12 -4
  11. package/dist/area-chart/root/area-chart-root.js +39 -11
  12. package/dist/avatar/group/avatar-group.d.ts +24 -3
  13. package/dist/avatar/group/avatar-group.js +34 -2
  14. package/dist/avatar/root/avatar-root.d.ts +17 -1
  15. package/dist/avatar/root/avatar-root.js +31 -10
  16. package/dist/badge/root/badge-root.d.ts +12 -0
  17. package/dist/badge/root/badge-root.js +1 -1
  18. package/dist/bar-chart/root/bar-chart-root.d.ts +30 -5
  19. package/dist/bar-chart/root/bar-chart-root.js +58 -21
  20. package/dist/blockquote/root/blockquote-root.d.ts +12 -7
  21. package/dist/blockquote/root/blockquote-root.js +6 -4
  22. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +9 -3
  23. package/dist/breadcrumbs/root/breadcrumbs-root.js +9 -1
  24. package/dist/button/root/button-root.d.ts +84 -1
  25. package/dist/button/root/button-root.js +81 -3
  26. package/dist/callout/root/callout-root.d.ts +32 -6
  27. package/dist/callout/root/callout-root.js +26 -7
  28. package/dist/canvas/root/canvas-root.d.ts +1 -0
  29. package/dist/canvas/root/canvas-root.js +10 -1
  30. package/dist/card/root/card-root.d.ts +30 -1
  31. package/dist/card/root/card-root.js +38 -5
  32. package/dist/carousel/root/carousel-root.d.ts +47 -5
  33. package/dist/carousel/root/carousel-root.js +53 -21
  34. package/dist/chat/composer/chat-composer.d.ts +13 -11
  35. package/dist/chat/composer/chat-composer.js +6 -12
  36. package/dist/chat/message/chat-message.js +8 -1
  37. package/dist/chat/root/chat-root.d.ts +1 -0
  38. package/dist/chat/root/chat-root.js +2 -1
  39. package/dist/checkbox/root/checkbox-root.d.ts +7 -0
  40. package/dist/checkbox/root/checkbox-root.js +9 -3
  41. package/dist/checkbox-group/root/checkbox-group-root.d.ts +116 -33
  42. package/dist/checkbox-group/root/checkbox-group-root.js +141 -44
  43. package/dist/code/root/code-root.d.ts +1 -1
  44. package/dist/code-block/lines.d.ts +13 -0
  45. package/dist/code-block/lines.js +2 -0
  46. package/dist/code-block/root/code-block-root.d.ts +49 -0
  47. package/dist/code-block/root/code-block-root.js +44 -3
  48. package/dist/color-input/root/color-input-root.d.ts +60 -2
  49. package/dist/color-input/root/color-input-root.js +55 -7
  50. package/dist/color-picker/channels.d.ts +26 -0
  51. package/dist/color-picker/channels.js +84 -0
  52. package/dist/color-picker/root/color-picker-root.d.ts +118 -12
  53. package/dist/color-picker/root/color-picker-root.js +183 -13
  54. package/dist/combobox/root/combobox-root.d.ts +126 -3
  55. package/dist/combobox/root/combobox-root.js +224 -21
  56. package/dist/command/root/command-root.d.ts +47 -10
  57. package/dist/command/root/command-root.js +45 -4
  58. package/dist/copy-button/from.d.ts +22 -0
  59. package/dist/copy-button/from.js +28 -0
  60. package/dist/copy-button/root/copy-button-root.d.ts +48 -7
  61. package/dist/copy-button/root/copy-button-root.js +54 -8
  62. package/dist/data-table/root/data-table-root.d.ts +164 -10
  63. package/dist/data-table/root/data-table-root.js +488 -57
  64. package/dist/date-input/root/date-input-root.d.ts +123 -9
  65. package/dist/date-input/root/date-input-root.js +147 -45
  66. package/dist/date-picker/root/date-picker-root.d.ts +229 -18
  67. package/dist/date-picker/root/date-picker-root.js +342 -46
  68. package/dist/dialog/description.d.ts +9 -0
  69. package/dist/dialog/description.js +14 -0
  70. package/dist/dialog/root/dialog-root.d.ts +101 -10
  71. package/dist/dialog/root/dialog-root.js +83 -4
  72. package/dist/donut-chart/root/donut-chart-root.d.ts +61 -16
  73. package/dist/donut-chart/root/donut-chart-root.js +48 -13
  74. package/dist/drawer/root/drawer-root.d.ts +93 -7
  75. package/dist/drawer/root/drawer-root.js +126 -5
  76. package/dist/drawer/swipe-start.d.ts +9 -0
  77. package/dist/drawer/swipe-start.js +12 -0
  78. package/dist/drilldown/item/drilldown-item.d.ts +16 -13
  79. package/dist/drilldown/item/drilldown-item.js +6 -2
  80. package/dist/drilldown/root/drilldown-root.d.ts +21 -5
  81. package/dist/drilldown/root/drilldown-root.js +26 -10
  82. package/dist/element-events.generated.js +42 -13
  83. package/dist/empty/root/empty-root.d.ts +1 -1
  84. package/dist/file-input/root/file-input-root.d.ts +65 -1
  85. package/dist/file-input/root/file-input-root.js +76 -12
  86. package/dist/file-input/screen.d.ts +35 -0
  87. package/dist/file-input/screen.js +49 -0
  88. package/dist/filter-input/menu/filter-menu.d.ts +9 -0
  89. package/dist/filter-input/menu/filter-menu.js +8 -0
  90. package/dist/filter-input/root/filter-input-root.d.ts +43 -6
  91. package/dist/filter-input/root/filter-input-root.js +89 -10
  92. package/dist/filter-input/suggest.d.ts +17 -0
  93. package/dist/filter-input/suggest.js +23 -0
  94. package/dist/format-date/options.d.ts +33 -0
  95. package/dist/format-date/options.js +42 -0
  96. package/dist/format-date/root/format-date-root.d.ts +62 -3
  97. package/dist/format-date/root/format-date-root.js +78 -37
  98. package/dist/graph/root/graph-root.d.ts +24 -3
  99. package/dist/graph/root/graph-root.js +66 -12
  100. package/dist/heatmap/root/buckets.d.ts +2 -1
  101. package/dist/heatmap/root/buckets.js +5 -1
  102. package/dist/heatmap/root/heatmap-root.d.ts +47 -9
  103. package/dist/heatmap/root/heatmap-root.js +46 -18
  104. package/dist/icon/index.d.ts +2 -0
  105. package/dist/icon/index.js +2 -0
  106. package/dist/icon/registry.d.ts +37 -0
  107. package/dist/icon/registry.js +83 -0
  108. package/dist/icon/root/icon-root.d.ts +53 -6
  109. package/dist/icon/root/icon-root.js +72 -4
  110. package/dist/icon/root/index.d.ts +2 -0
  111. package/dist/icon/root/index.js +2 -0
  112. package/dist/icon/set.d.ts +1 -0
  113. package/dist/icon/set.js +3 -0
  114. package/dist/icon-button/root/icon-button-root.d.ts +1 -1
  115. package/dist/image/root/image-root.d.ts +23 -2
  116. package/dist/image/root/image-root.js +23 -4
  117. package/dist/input/root/input-root.d.ts +62 -9
  118. package/dist/input/root/input-root.js +61 -5
  119. package/dist/kbd/root/kbd-root.d.ts +26 -11
  120. package/dist/kbd/root/kbd-root.js +35 -38
  121. package/dist/kbd/shortcut.d.ts +18 -0
  122. package/dist/kbd/shortcut.js +35 -0
  123. package/dist/legend/hidden-series.d.ts +8 -0
  124. package/dist/legend/hidden-series.js +13 -0
  125. package/dist/legend/root/legend-root.d.ts +63 -0
  126. package/dist/legend/root/legend-root.js +61 -7
  127. package/dist/lightbox/item/lightbox-item.d.ts +17 -1
  128. package/dist/lightbox/item/lightbox-item.js +10 -1
  129. package/dist/lightbox/root/lightbox-root.d.ts +77 -6
  130. package/dist/lightbox/root/lightbox-root.js +116 -5
  131. package/dist/line-chart/root/line-chart-root.d.ts +29 -4
  132. package/dist/line-chart/root/line-chart-root.js +58 -17
  133. package/dist/list/item/list-item.d.ts +11 -5
  134. package/dist/list/item/list-item.js +5 -6
  135. package/dist/listbox/range.d.ts +17 -0
  136. package/dist/listbox/range.js +5 -0
  137. package/dist/listbox/root/listbox-root.d.ts +184 -4
  138. package/dist/listbox/root/listbox-root.js +229 -23
  139. package/dist/loader/busy.d.ts +17 -0
  140. package/dist/loader/busy.js +24 -0
  141. package/dist/loader/root/loader-root.d.ts +37 -0
  142. package/dist/loader/root/loader-root.js +52 -3
  143. package/dist/map/root/map-root.d.ts +19 -4
  144. package/dist/map/root/map-root.js +36 -7
  145. package/dist/megamenu/root/megamenu-root.d.ts +10 -1
  146. package/dist/megamenu/root/megamenu-root.js +10 -1
  147. package/dist/menu/item/menu-item.d.ts +41 -12
  148. package/dist/menu/item/menu-item.js +46 -3
  149. package/dist/menu/root/menu-root.d.ts +32 -2
  150. package/dist/menu/root/menu-root.js +48 -8
  151. package/dist/meter/root/meter-root.d.ts +13 -0
  152. package/dist/meter/root/meter-root.js +24 -5
  153. package/dist/navigation/root/navigation-root.d.ts +10 -2
  154. package/dist/navigation/root/navigation-root.js +13 -2
  155. package/dist/notifications/feed-keys.d.ts +10 -0
  156. package/dist/notifications/feed-keys.js +14 -0
  157. package/dist/notifications/group/notification-group.d.ts +1 -0
  158. package/dist/notifications/notification/notification.d.ts +29 -0
  159. package/dist/notifications/notification/notification.js +51 -3
  160. package/dist/notifications/root/notifications-root.d.ts +49 -0
  161. package/dist/notifications/root/notifications-root.js +76 -2
  162. package/dist/number-input/root/number-input-root.d.ts +109 -7
  163. package/dist/number-input/root/number-input-root.js +109 -21
  164. package/dist/object-inspector/root/object-inspector-root.d.ts +49 -5
  165. package/dist/object-inspector/root/object-inspector-root.js +92 -12
  166. package/dist/option/root/option-root.d.ts +58 -6
  167. package/dist/option/root/option-root.js +35 -5
  168. package/dist/outline/root/outline-root.d.ts +13 -2
  169. package/dist/outline/root/outline-root.js +4 -2
  170. package/dist/pagination/root/pagination-root.d.ts +19 -1
  171. package/dist/pagination/root/pagination-root.js +49 -9
  172. package/dist/pagination/size/pagination-size.d.ts +13 -3
  173. package/dist/pagination/size/pagination-size.js +7 -0
  174. package/dist/pin-input/format.d.ts +7 -0
  175. package/dist/pin-input/format.js +16 -0
  176. package/dist/pin-input/root/pin-input-root.d.ts +37 -0
  177. package/dist/pin-input/root/pin-input-root.js +48 -7
  178. package/dist/popover/root/popover-root.d.ts +78 -3
  179. package/dist/popover/root/popover-root.js +74 -5
  180. package/dist/progress/root/progress-root.d.ts +23 -1
  181. package/dist/progress/root/progress-root.js +40 -10
  182. package/dist/qr-code/export.d.ts +31 -0
  183. package/dist/qr-code/export.js +10 -0
  184. package/dist/qr-code/root/qr-code-root.d.ts +45 -0
  185. package/dist/qr-code/root/qr-code-root.js +78 -3
  186. package/dist/radio/group/radio-group.d.ts +64 -0
  187. package/dist/radio/group/radio-group.js +66 -12
  188. package/dist/radio/root/radio-root.d.ts +8 -5
  189. package/dist/radio/root/radio-root.js +1 -2
  190. package/dist/rating/root/rating-root.d.ts +49 -1
  191. package/dist/rating/root/rating-root.js +69 -10
  192. package/dist/scatter-chart/root/scatter-chart-root.d.ts +17 -3
  193. package/dist/scatter-chart/root/scatter-chart-root.js +40 -14
  194. package/dist/select/root/select-root.d.ts +93 -3
  195. package/dist/select/root/select-root.js +111 -13
  196. package/dist/skeleton/root/skeleton-root.d.ts +39 -3
  197. package/dist/skeleton/root/skeleton-root.js +33 -4
  198. package/dist/skip-link/root/skip-link-root.d.ts +10 -0
  199. package/dist/skip-link/root/skip-link-root.js +15 -1
  200. package/dist/slider/root/slider-root.d.ts +59 -2
  201. package/dist/slider/root/slider-root.js +82 -8
  202. package/dist/sparkline/root/sparkline-root.d.ts +9 -10
  203. package/dist/speed-dial/item/speed-dial-item.d.ts +11 -5
  204. package/dist/speed-dial/item/speed-dial-item.js +7 -2
  205. package/dist/speed-dial/root/speed-dial-root.d.ts +15 -4
  206. package/dist/speed-dial/root/speed-dial-root.js +19 -5
  207. package/dist/splitter/root/splitter-root.d.ts +28 -6
  208. package/dist/splitter/root/splitter-root.js +35 -1
  209. package/dist/stat/root/stat-root.d.ts +9 -1
  210. package/dist/stat/root/stat-root.js +15 -2
  211. package/dist/steps/root/steps-root.js +1 -1
  212. package/dist/steps/step/step.d.ts +1 -0
  213. package/dist/steps/step/step.js +1 -1
  214. package/dist/switch/root/switch-root.d.ts +14 -2
  215. package/dist/switch/root/switch-root.js +7 -1
  216. package/dist/table/cell/table-cell.d.ts +24 -4
  217. package/dist/table/cell/table-cell.js +21 -1
  218. package/dist/table/column/table-column.d.ts +32 -6
  219. package/dist/table/column/table-column.js +36 -6
  220. package/dist/table/root/table-root.d.ts +94 -3
  221. package/dist/table/root/table-root.js +128 -5
  222. package/dist/table/row/table-row.d.ts +7 -0
  223. package/dist/table/row/table-row.js +9 -1
  224. package/dist/tabs/root/tabs-root.d.ts +6 -1
  225. package/dist/tabs/root/tabs-root.js +6 -2
  226. package/dist/tag/group/tag-group.d.ts +107 -0
  227. package/dist/tag/group/tag-group.js +157 -0
  228. package/dist/tag/index.d.ts +1 -0
  229. package/dist/tag/index.js +1 -0
  230. package/dist/tag/root/tag-root.d.ts +83 -1
  231. package/dist/tag/root/tag-root.js +87 -17
  232. package/dist/tag/selection.d.ts +10 -0
  233. package/dist/tag/selection.js +12 -0
  234. package/dist/textarea/grow.d.ts +22 -0
  235. package/dist/textarea/grow.js +20 -0
  236. package/dist/textarea/root/textarea-root.d.ts +13 -0
  237. package/dist/textarea/root/textarea-root.js +10 -2
  238. package/dist/time-input/root/time-input-root.d.ts +71 -6
  239. package/dist/time-input/root/time-input-root.js +110 -44
  240. package/dist/time-picker/root/time-picker-root.d.ts +155 -26
  241. package/dist/time-picker/root/time-picker-root.js +192 -63
  242. package/dist/toast/hotkey.d.ts +22 -0
  243. package/dist/toast/hotkey.js +23 -0
  244. package/dist/toast/item/toast-item.d.ts +16 -1
  245. package/dist/toast/item/toast-item.js +41 -13
  246. package/dist/toast/root/toast-root.d.ts +105 -8
  247. package/dist/toast/root/toast-root.js +209 -4
  248. package/dist/toggle/group/toggle-group.d.ts +95 -16
  249. package/dist/toggle/group/toggle-group.js +99 -13
  250. package/dist/toggle/root/toggle-root.d.ts +26 -11
  251. package/dist/toggle/root/toggle-root.js +14 -6
  252. package/dist/toolbar/root/toolbar-root.d.ts +55 -15
  253. package/dist/toolbar/root/toolbar-root.js +99 -16
  254. package/dist/tooltip/focus-target.d.ts +6 -0
  255. package/dist/tooltip/focus-target.js +9 -0
  256. package/dist/tooltip/root/tooltip-root.d.ts +73 -15
  257. package/dist/tooltip/root/tooltip-root.js +96 -7
  258. package/dist/tour/naming.d.ts +30 -0
  259. package/dist/tour/naming.js +10 -0
  260. package/dist/tour/root/tour-root.d.ts +67 -3
  261. package/dist/tour/root/tour-root.js +105 -17
  262. package/dist/tour/stop/tour-stop.d.ts +65 -3
  263. package/dist/tour/stop/tour-stop.js +61 -11
  264. package/dist/tour/wait.d.ts +11 -0
  265. package/dist/tour/wait.js +19 -0
  266. package/dist/tree/item/tree-item.d.ts +21 -5
  267. package/dist/tree/item/tree-item.js +14 -4
  268. package/dist/tree/root/tree-root.d.ts +29 -5
  269. package/dist/tree/root/tree-root.js +64 -8
  270. package/dist/trend/root/trend-root.d.ts +36 -1
  271. package/dist/trend/root/trend-root.js +41 -15
  272. package/dist/video/root/keys.d.ts +5 -0
  273. package/dist/video/root/keys.js +13 -1
  274. package/dist/video/root/video-root.d.ts +19 -0
  275. package/dist/video/root/video-root.js +48 -4
  276. package/dist/video-playlist/menu/video-playlist-menu.d.ts +20 -2
  277. package/dist/video-playlist/menu/video-playlist-menu.js +27 -5
  278. package/dist/video-playlist/root/video-playlist-root.js +6 -3
  279. package/dist/virtual-list/root/virtual-list-root.d.ts +52 -4
  280. package/dist/virtual-list/root/virtual-list-root.js +103 -21
  281. package/lint.json +1 -1
  282. package/package.json +5 -5
  283. package/types/jsx.d.ts +9140 -1149
  284. package/types/svelte.d.ts +1 -0
  285. package/types/vue.d.ts +2089 -267
@@ -1,4 +1,4 @@
1
- import { CommandController, HelixElement, type Placement } from '@fusion.dev/helix-core';
1
+ import { AriaForwardController, CommandController, HelixElement, type Placement } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  declare const HelixHeadlessPopover_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
4
4
  /**
@@ -21,6 +21,29 @@ declare const HelixHeadlessPopover_base: (new (...args: any[]) => import("@fusio
21
21
  * commands work as they do on `Dialog` and `Drawer`, so a button anywhere on
22
22
  * the page can open the popover with no script.
23
23
  *
24
+ * **Opening moves focus into the panel**, to the first control in it, or to the
25
+ * panel itself where it holds none, and closing from inside gives it back to
26
+ * the trigger. It closes on Escape, on a press outside it, and when focus moves
27
+ * out of the trigger and the panel to somewhere else on the page.
28
+ *
29
+ * `modal` keeps `Tab` inside the panel while it is open and says so with
30
+ * `aria-modal`, for a panel that has to be answered before the page goes on.
31
+ * Without it the popover does not trap focus.
32
+ *
33
+ * The panel's name is `label` when you set it. Otherwise it is the first
34
+ * heading in the panel, or the elements an `aria-labelledby` on the host names.
35
+ *
36
+ * `arrow` draws a point on the panel's edge facing the trigger. The side the
37
+ * panel landed on is on the host as `:state(placed-top)` and its kin, and the
38
+ * trigger's width is on the panel as `--floating-anchor-inline-size`, for a
39
+ * panel as wide as its trigger:
40
+ *
41
+ * ```css
42
+ * helix-popover::part(popover) {
43
+ * min-inline-size: var(--floating-anchor-inline-size);
44
+ * }
45
+ * ```
46
+ *
24
47
  * @since 0.1.0
25
48
  * @status stable
26
49
  * @category overlays
@@ -32,18 +55,41 @@ declare const HelixHeadlessPopover_base: (new (...args: any[]) => import("@fusio
32
55
  *
33
56
  * @event {HelixBeforeCloseEvent} helix-before-close - Fired before it closes, and cancelable.
34
57
  * @event {HelixBeforeOpenEvent} helix-before-open - Fired before it opens, and cancelable.
35
- * @event {Event} toggle - Fired after the panel opens or closes, with `newState` and `oldState`.
58
+ * @event {ToggleEvent} toggle - Fired after the panel opens or closes, with `newState` and `oldState`.
36
59
  *
60
+ * @csspart arrow - The point on the panel's edge facing the trigger, drawn with `arrow`.
61
+ * @csspart arrow-bottom - Added to `arrow` while the panel is below its trigger, with the point on the panel's top edge.
62
+ * @csspart arrow-left - Added to `arrow` while the panel is left of its trigger, with the point on the panel's right edge.
63
+ * @csspart arrow-right - Added to `arrow` while the panel is right of its trigger, with the point on the panel's left edge.
64
+ * @csspart arrow-top - Added to `arrow` while the panel is above its trigger, with the point on the panel's bottom edge.
37
65
  * @csspart content - The scrolling region inside the panel.
38
66
  * @csspart popover - The panel.
39
67
  *
40
68
  * @cssstate open - While the popover is open.
69
+ * @cssstate placed-bottom - While the panel sits below its trigger.
70
+ * @cssstate placed-left - While the panel sits to the left of its trigger.
71
+ * @cssstate placed-right - While the panel sits to the right of its trigger.
72
+ * @cssstate placed-top - While the panel sits above its trigger.
41
73
  */
42
74
  export declare class HelixHeadlessPopover extends HelixHeadlessPopover_base {
43
75
  /**
44
- * The panel's name, read by screen readers.
76
+ * Draws a point on the panel's edge facing the trigger.
77
+ *
78
+ * Off by default, which is the house style: a panel that opens from its
79
+ * trigger already reads as belonging to it.
80
+ */
81
+ arrow: boolean;
82
+ /**
83
+ * The panel's name, read by screen readers. Left at `Popover`, the first
84
+ * heading in the panel names it instead.
45
85
  */
46
86
  label: string;
87
+ /**
88
+ * Keeps `Tab` inside the panel while it is open, and marks the panel
89
+ * `aria-modal`. A press outside and Escape still close it.
90
+ */
91
+ modal: boolean;
92
+ protected readonly aria: AriaForwardController;
47
93
  /**
48
94
  * Space between the trigger and the panel, in pixels.
49
95
  */
@@ -55,6 +101,23 @@ export declare class HelixHeadlessPopover extends HelixHeadlessPopover_base {
55
101
  */
56
102
  placement: Placement;
57
103
  private readonly panel;
104
+ /**
105
+ * The side of its trigger the panel last landed on, which names the arrow's
106
+ * second part. Undefined until the first placement, which the server never
107
+ * makes.
108
+ */
109
+ private side?;
110
+ private readonly scope;
111
+ /** What is assigned to the panel's slot. */
112
+ private get slotted();
113
+ /** Whether the first heading in the panel is what names it. */
114
+ private namedByHeading;
115
+ /**
116
+ * Names the panel by its first heading, where `label` was left alone and no
117
+ * `aria-labelledby` on the host names it. An element reference, for the
118
+ * reason `Dialog` gives.
119
+ */
120
+ private nameByHeading;
58
121
  /**
59
122
  * The invoker that last opened the panel, when a command did.
60
123
  *
@@ -75,6 +138,18 @@ export declare class HelixHeadlessPopover extends HelixHeadlessPopover_base {
75
138
  show(): void;
76
139
  /** Closes the panel and puts focus back on the trigger. */
77
140
  hide(): void;
141
+ /**
142
+ * Focus leaving the control closes the panel.
143
+ *
144
+ * A popover does not trap focus, so `Tab` past its last control walks on
145
+ * into the page, and a panel left open there covers what the reader moved
146
+ * to. Focus moving between the trigger, the panel and a command's invoker
147
+ * stays inside the control. With no `relatedTarget`, focus left the
148
+ * document or went nowhere, and a press outside is already its own signal.
149
+ */
150
+ private readonly handleFocusOut;
151
+ connectedCallback(): void;
152
+ disconnectedCallback(): void;
78
153
  private readonly handleTriggerClick;
79
154
  /** Tells a slotted trigger the disclosure state, by property. See `tellDisclosure`. */
80
155
  private tellTrigger;
@@ -4,24 +4,44 @@ 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 { anchorElement, CommandController, customElement, deepActiveElement, HelixElement, MixinControllerOpen, PopoverController, tellDisclosure, } from '@fusion.dev/helix-core';
8
- import { html } from 'lit';
9
- import { property } from 'lit/decorators.js';
7
+ import { AriaForwardController, anchorElement, arrowReach, CommandController, customElement, deepActiveElement, FocusScopeController, HelixElement, headingIn, MixinControllerOpen, PopoverController, reflectElements, sideOf, tellDisclosure, } from '@fusion.dev/helix-core';
8
+ import { html, nothing } from 'lit';
9
+ import { property, state } from 'lit/decorators.js';
10
10
  let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpen(HelixElement) {
11
11
  constructor() {
12
12
  super(...arguments);
13
+ this.arrow = false;
13
14
  this.label = 'Popover';
15
+ this.modal = false;
16
+ this.aria = new AriaForwardController(this, {
17
+ target: () => this.partNamed('popover'),
18
+ });
14
19
  this.offset = 4;
15
20
  this.placement = 'bottom-start';
16
21
  this.panel = new PopoverController(this, {
17
22
  anchor: () => this.trigger,
23
+ arrow: () => this.partNamed('arrow'),
18
24
  boundary: () => [this, this.invoker],
19
- offset: () => this.offset,
25
+ offset: () => this.offset + arrowReach(this.partNamed('arrow')),
20
26
  onDismiss: () => this.hide(),
27
+ onPlace: (placement) => {
28
+ const side = sideOf(placement);
29
+ this.toggleState('placed-bottom', side === 'bottom');
30
+ this.toggleState('placed-left', side === 'left');
31
+ this.toggleState('placed-right', side === 'right');
32
+ this.toggleState('placed-top', side === 'top');
33
+ this.side = side;
34
+ },
21
35
  open: () => this.open,
22
36
  placement: () => this.placement,
23
37
  surface: () => this.partNamed('popover'),
24
38
  });
39
+ this.scope = new FocusScopeController(this, {
40
+ contain: () => this.modal && this.open,
41
+ contents: () => this.slotted,
42
+ surface: () => this.partNamed('popover'),
43
+ });
44
+ this.namedByHeading = false;
25
45
  this.commands = new CommandController(this, {
26
46
  commands: {
27
47
  '--close': () => this.hide(),
@@ -40,6 +60,16 @@ let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpe
40
60
  },
41
61
  },
42
62
  });
63
+ this.handleFocusOut = (event) => {
64
+ const next = event.relatedTarget;
65
+ if (!this.open || !(next instanceof Node)) {
66
+ return;
67
+ }
68
+ if (this.contains(next) || this.renderRoot.contains(next) || this.invoker?.contains(next)) {
69
+ return;
70
+ }
71
+ this.hide();
72
+ };
43
73
  this.handleTriggerClick = () => {
44
74
  if (this.open) {
45
75
  this.hide();
@@ -49,6 +79,16 @@ let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpe
49
79
  }
50
80
  };
51
81
  }
82
+ get slotted() {
83
+ return (this.renderRoot
84
+ ?.querySelector('slot:not([name])')
85
+ ?.assignedElements({ flatten: true }) ?? []);
86
+ }
87
+ nameByHeading() {
88
+ const asked = this.label !== 'Popover' || this.hasAttribute('aria-labelledby');
89
+ const heading = asked ? null : headingIn(this.slotted);
90
+ this.namedByHeading = reflectElements(this.partNamed('popover'), 'ariaLabelledByElements', heading ? [heading] : [], this.namedByHeading);
91
+ }
52
92
  get trigger() {
53
93
  return (this.invoker ??
54
94
  anchorElement(this.renderRoot?.querySelector('slot[name="trigger"]'), this));
@@ -67,6 +107,14 @@ let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpe
67
107
  this.trigger.focus();
68
108
  }
69
109
  }
110
+ connectedCallback() {
111
+ super.connectedCallback();
112
+ this.addEventListener('focusout', this.handleFocusOut);
113
+ }
114
+ disconnectedCallback() {
115
+ super.disconnectedCallback();
116
+ this.removeEventListener('focusout', this.handleFocusOut);
117
+ }
70
118
  tellTrigger() {
71
119
  const slot = this.renderRoot?.querySelector('slot[name="trigger"]');
72
120
  tellDisclosure(slot?.assignedElements({ flatten: false })[0], this.open);
@@ -74,6 +122,10 @@ let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpe
74
122
  updated(changed) {
75
123
  super.updated(changed);
76
124
  this.tellTrigger();
125
+ this.nameByHeading();
126
+ if (changed.has('open') && this.open && changed.get('open') === false) {
127
+ this.scope.enter();
128
+ }
77
129
  }
78
130
  render() {
79
131
  return html `<slot name="trigger" @click=${this.handleTriggerClick}></slot>
@@ -82,21 +134,38 @@ let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpe
82
134
  popover="manual"
83
135
  role="dialog"
84
136
  aria-label=${this.label}
137
+ aria-modal=${this.modal ? 'true' : nothing}
138
+ tabindex="-1"
85
139
  ?hidden=${!this.open}
86
140
  >
87
- <div part="content"><slot></slot></div>
141
+ ${this.arrow
142
+ ? html `<div
143
+ part="arrow${this.side ? { bottom: ' arrow-bottom', left: ' arrow-left', right: ' arrow-right', top: ' arrow-top' }[this.side] : ''}"
144
+ aria-hidden="true"
145
+ ></div>`
146
+ : nothing}
147
+ <div part="content"><slot @slotchange=${this.nameByHeading}></slot></div>
88
148
  </div>`;
89
149
  }
90
150
  };
151
+ __decorate([
152
+ property({ type: Boolean })
153
+ ], HelixHeadlessPopover.prototype, "arrow", void 0);
91
154
  __decorate([
92
155
  property()
93
156
  ], HelixHeadlessPopover.prototype, "label", void 0);
157
+ __decorate([
158
+ property({ type: Boolean })
159
+ ], HelixHeadlessPopover.prototype, "modal", void 0);
94
160
  __decorate([
95
161
  property({ type: Number })
96
162
  ], HelixHeadlessPopover.prototype, "offset", void 0);
97
163
  __decorate([
98
164
  property()
99
165
  ], HelixHeadlessPopover.prototype, "placement", void 0);
166
+ __decorate([
167
+ state()
168
+ ], HelixHeadlessPopover.prototype, "side", void 0);
100
169
  HelixHeadlessPopover = __decorate([
101
170
  customElement('helix-headless-popover')
102
171
  ], HelixHeadlessPopover);
@@ -21,6 +21,8 @@ import { type PropertyValues, type TemplateResult } from 'lit';
21
21
  * @aliases Progress bar, Loading bar
22
22
  * @documentation https://fusion.dev/helix/components/progress
23
23
  *
24
+ * @slot label - A visible label. Its words name the progress bar in place of `label`.
25
+ *
24
26
  * @csspart groove - The unfilled circle under the ring's filled arc.
25
27
  * @csspart indicator - The filled part that shows the work done.
26
28
  * @csspart track - The full length the work is measured against.
@@ -46,12 +48,28 @@ export declare class HelixHeadlessProgress extends HelixElement {
46
48
  indeterminate: boolean;
47
49
  /**
48
50
  * Accessible name for what is in progress.
51
+ *
52
+ * A visible label in the `label` slot names it instead, with the words it
53
+ * shows, so the name and the label cannot disagree.
49
54
  */
50
55
  label: string;
56
+ /**
57
+ * The locale the percentage is written in, such as `fr` for `72 %`.
58
+ *
59
+ * Absent uses the runtime's own.
60
+ */
61
+ locale: string;
51
62
  /**
52
63
  * The total the work is measured against.
53
64
  */
54
65
  max: number;
66
+ /**
67
+ * Where the work starts, in the same units as `max`. Defaults to zero.
68
+ *
69
+ * For a measure that does not start at nothing, such as a migration from
70
+ * version 12 to version 20, where 12 is the empty bar.
71
+ */
72
+ min: number;
55
73
  /**
56
74
  * Whether the value is drawn as a bar or as a ring.
57
75
  *
@@ -82,7 +100,7 @@ export declare class HelixHeadlessProgress extends HelixElement {
82
100
  /**
83
101
  * What has been done so far, in the same units as `max`.
84
102
  *
85
- * Clamped between zero and `max` for both the fill and the announcement, so
103
+ * Clamped between `min` and `max` for both the fill and the announcement, so
86
104
  * the two can never disagree about what is on screen.
87
105
  */
88
106
  value: number;
@@ -96,9 +114,13 @@ export declare class HelixHeadlessProgress extends HelixElement {
96
114
  * report progress.
97
115
  */
98
116
  valueText: string;
117
+ /** The range the value is measured in, with a top above its bottom. */
118
+ private get range();
99
119
  /** What is done, as a fraction of the whole, between 0 and 1. */
100
120
  get fraction(): number;
101
121
  protected willUpdate(changed: PropertyValues): void;
122
+ /** A label's words changed, so the name is written again. */
123
+ private readonly renamed;
102
124
  protected render(): TemplateResult;
103
125
  }
104
126
  declare global {
@@ -4,32 +4,56 @@ 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, slottedWords } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
+ const percents = new Map();
11
+ const percent = (share, locale) => {
12
+ let format = percents.get(locale);
13
+ if (!format) {
14
+ format = new Intl.NumberFormat(locale || undefined, {
15
+ maximumFractionDigits: 0,
16
+ style: 'percent',
17
+ });
18
+ percents.set(locale, format);
19
+ }
20
+ return format.format(share);
21
+ };
10
22
  let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
11
23
  constructor() {
12
24
  super(...arguments);
13
25
  this.indeterminate = false;
14
26
  this.label = 'Progress';
27
+ this.locale = '';
15
28
  this.max = 100;
29
+ this.min = 0;
16
30
  this.showValue = false;
17
31
  this.value = 0;
18
32
  this.valueText = '';
33
+ this.renamed = () => {
34
+ this.requestUpdate();
35
+ };
36
+ }
37
+ get range() {
38
+ const min = Number.isFinite(this.min) ? this.min : 0;
39
+ return { max: this.max > min ? this.max : min + 100, min };
19
40
  }
20
41
  get fraction() {
21
- const max = this.max > 0 ? this.max : 100;
22
- return Math.min(Math.max(this.value / max, 0), 1) || 0;
42
+ const { max, min } = this.range;
43
+ return Math.min(Math.max((this.value - min) / (max - min), 0), 1) || 0;
23
44
  }
24
45
  willUpdate(changed) {
25
46
  super.willUpdate(changed);
26
- hostAria(this.internals, { label: this.label, role: 'progressbar' });
27
- this.internals.ariaValueMin = '0';
28
- this.internals.ariaValueMax = String(this.max > 0 ? this.max : 100);
29
- const ceiling = this.max > 0 ? this.max : 100;
47
+ hostAria(this.internals, {
48
+ label: slottedWords(this, ['label']) || this.label,
49
+ role: 'progressbar',
50
+ });
51
+ const { max, min } = this.range;
52
+ this.internals.ariaValueMin = String(min);
53
+ this.internals.ariaValueMax = String(max);
30
54
  this.internals.ariaValueNow = this.indeterminate
31
55
  ? null
32
- : String(Math.min(Math.max(this.value || 0, 0), ceiling));
56
+ : String(Math.min(Math.max(this.value || min, min), max));
33
57
  this.internals.ariaValueText = this.valueText || null;
34
58
  }
35
59
  render() {
@@ -38,14 +62,14 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
38
62
  <circle part="groove" />
39
63
  <circle part="indicator" pathLength="100" style="--progress-fraction: ${fraction}" />
40
64
  </svg>`;
41
- return html `${this.shape === 'ring'
65
+ return html `<slot name="label" @slotchange=${this.renamed}></slot>${this.shape === 'ring'
42
66
  ? ring
43
67
  : html `<div part="track">
44
68
  <div part="indicator" style="--progress-fraction: ${fraction}"></div>
45
69
  </div>`}
46
70
  ${this.showValue && !this.indeterminate
47
71
  ? html `<span part="value" aria-hidden="true"
48
- >${this.valueText || `${Math.round(this.fraction * 100)}%`}</span
72
+ >${this.valueText || percent(this.fraction, this.locale)}</span
49
73
  >`
50
74
  : ''}`;
51
75
  }
@@ -56,9 +80,15 @@ __decorate([
56
80
  __decorate([
57
81
  property()
58
82
  ], HelixHeadlessProgress.prototype, "label", void 0);
83
+ __decorate([
84
+ property()
85
+ ], HelixHeadlessProgress.prototype, "locale", void 0);
59
86
  __decorate([
60
87
  property({ type: Number })
61
88
  ], HelixHeadlessProgress.prototype, "max", void 0);
89
+ __decorate([
90
+ property({ type: Number })
91
+ ], HelixHeadlessProgress.prototype, "min", void 0);
62
92
  __decorate([
63
93
  property({ reflect: true })
64
94
  ], HelixHeadlessProgress.prototype, "shape", void 0);
@@ -0,0 +1,31 @@
1
+ /**
2
+ * A drawn code as a file of its own: an SVG with its colors written in, which
3
+ * an image can load and a canvas can turn into a PNG.
4
+ *
5
+ * Beside the family's `index.ts`, because writing a file is not the element's
6
+ * template, and the markup is a pure function of what the element drew.
7
+ */
8
+ /** What a code is drawn with, read from the element once it has rendered. */
9
+ export interface QrDrawing {
10
+ /** The fill behind the code, quiet zone included. */
11
+ background: string;
12
+ /** The side of the `viewBox`, quiet zone included. */
13
+ box: number;
14
+ /** The color of the corner squares. */
15
+ finderColor: string;
16
+ /** The path of the corner squares. */
17
+ finders: string;
18
+ /** The color of every other module. */
19
+ moduleColor: string;
20
+ /** The path of every other module. */
21
+ modules: string;
22
+ /** The quiet zone, in modules, that both paths are moved in by. */
23
+ quiet: number;
24
+ }
25
+ /**
26
+ * The code as standalone SVG markup, sized `size` pixels square.
27
+ *
28
+ * The background is a rectangle under the code, since a scanner needs the
29
+ * light quiet zone and a transparent file put on a dark page has none.
30
+ */
31
+ export declare const qrSvg: (drawing: QrDrawing, size: number) => string;
@@ -0,0 +1,10 @@
1
+ const attribute = (value) => value.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
2
+ export const qrSvg = (drawing, size) => {
3
+ const shift = `translate(${drawing.quiet} ${drawing.quiet})`;
4
+ return (`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${drawing.box} ${drawing.box}"` +
5
+ ` width="${size}" height="${size}" shape-rendering="crispEdges">` +
6
+ `<rect width="${drawing.box}" height="${drawing.box}" fill="${attribute(drawing.background)}"/>` +
7
+ `<path d="${attribute(drawing.modules)}" transform="${shift}" fill="${attribute(drawing.moduleColor)}"/>` +
8
+ `<path d="${attribute(drawing.finders)}" transform="${shift}" fill="${attribute(drawing.finderColor)}"/>` +
9
+ '</svg>');
10
+ };
@@ -26,6 +26,14 @@ export type QrStyle = 'dots' | 'rounded' | 'square';
26
26
  * the same mark makes the code unscannable. The default is `M`, and `H` when the
27
27
  * `logo` slot is filled.
28
28
  *
29
+ * A higher level holds fewer characters, so a value that fits at `M` can be too
30
+ * long once a logo raises the level to `H`. A value too long for the level
31
+ * draws no code, and the element fires `error`.
32
+ *
33
+ * `toSvg()`, `toPng()` and `download()` give the drawn code as a file of its
34
+ * own, in the colors it is drawn in, for a page that offers it to save or
35
+ * print, as Zag's download trigger does.
36
+ *
29
37
  * @since 0.1.0
30
38
  * @status stable
31
39
  * @category media
@@ -34,6 +42,8 @@ export type QrStyle = 'dots' | 'rounded' | 'square';
34
42
  *
35
43
  * @slot logo - A logo for the center. A code with a logo scans only at a high correction level.
36
44
  *
45
+ * @event {Event} error - The value is too long for a code at the correction level, so no code is drawn.
46
+ *
37
47
  * @csspart code - The square code image.
38
48
  * @csspart finders - The three corner squares, which can take their own color.
39
49
  * @csspart logo - The box that holds the center logo.
@@ -84,6 +94,41 @@ export declare class HelixHeadlessQrCode extends HelixElement {
84
94
  protected firstUpdated(changed: PropertyValues): void;
85
95
  /** The level actually used, which the slot can raise. */
86
96
  private get level();
97
+ /** The value and level that did not fit, so `error` fires once for each. */
98
+ private overflowed;
99
+ /**
100
+ * The code for the value at the level, or undefined where the value is too
101
+ * long for it.
102
+ *
103
+ * `encode` throws for a value past the level's capacity, and it threw inside
104
+ * the render: the update rejected and the last code stayed on screen. A
105
+ * logo raising the level to `H` was the easy way to get there, since `H`
106
+ * holds the fewest characters.
107
+ */
108
+ private encoded;
109
+ protected updated(changed: PropertyValues): void;
110
+ /**
111
+ * The code as an SVG file of its own, `size` pixels square, in the colors it
112
+ * is drawn in, with its background under the quiet zone.
113
+ *
114
+ * `''` where no code is drawn, before the first render or for a value too
115
+ * long for the level. A slotted logo is not in the file, being the page's
116
+ * own markup; save the file at `H` and the code still scans without it.
117
+ */
118
+ toSvg(size?: number): string;
119
+ /**
120
+ * The code as a PNG, `size` pixels square, drawn from `toSvg()`.
121
+ *
122
+ * `null` where no code is drawn.
123
+ */
124
+ toPng(size?: number): Promise<Blob | null>;
125
+ /**
126
+ * Saves the code as a PNG named `filename`, `size` pixels square.
127
+ *
128
+ * Call it from a press, since a browser lets a page start a download only
129
+ * in answer to one. Does nothing where no code is drawn.
130
+ */
131
+ download(filename?: string, size?: number): Promise<void>;
87
132
  private renderCode;
88
133
  protected render(): TemplateResult;
89
134
  }
@@ -4,10 +4,12 @@ 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 } from '@fusion.dev/helix-core';
7
+ 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';
12
+ const QUIET = 4;
11
13
  const isFinder = (x, y, size) => {
12
14
  const near = (a, b) => a < 8 && b < 8;
13
15
  return near(x, y) || near(size - 1 - x, y) || near(x, size - 1 - y);
@@ -65,6 +67,7 @@ let HelixHeadlessQrCode = class HelixHeadlessQrCode extends HelixElement {
65
67
  this.label = '';
66
68
  this.value = '';
67
69
  this.logo = false;
70
+ this.overflowed = '';
68
71
  }
69
72
  willUpdate(changed) {
70
73
  super.willUpdate(changed);
@@ -79,13 +82,85 @@ let HelixHeadlessQrCode = class HelixHeadlessQrCode extends HelixElement {
79
82
  get level() {
80
83
  return this.correction ?? (this.logo ? 'H' : 'M');
81
84
  }
85
+ encoded() {
86
+ try {
87
+ return encode(this.value, { border: 0, ecc: this.level });
88
+ }
89
+ catch (error) {
90
+ if (error instanceof RangeError) {
91
+ return undefined;
92
+ }
93
+ throw error;
94
+ }
95
+ }
96
+ updated(changed) {
97
+ super.updated(changed);
98
+ const key = `${this.level}:${this.value}`;
99
+ if (this.value !== '' && this.encoded() === undefined) {
100
+ if (this.overflowed !== key) {
101
+ this.overflowed = key;
102
+ this.dispatchEvent(new Event('error', { bubbles: false, cancelable: false, composed: false }));
103
+ }
104
+ }
105
+ else {
106
+ this.overflowed = '';
107
+ }
108
+ }
109
+ toSvg(size = 512) {
110
+ const code = this.renderRoot?.querySelector('svg[part="code"]');
111
+ const modules = code?.querySelector('[part="modules"]');
112
+ const finders = code?.querySelector('[part="finders"]');
113
+ if (!code || !modules || !finders) {
114
+ return '';
115
+ }
116
+ const ground = getComputedStyle(this).backgroundColor;
117
+ const clear = ground === '' || ground === 'transparent' || /^rgba\(.*,\s*0\)$/.test(ground);
118
+ return qrSvg({
119
+ background: clear ? '#ffffff' : rgbOf(ground),
120
+ box: Number(code.getAttribute('viewBox')?.split(' ')[2] ?? 0),
121
+ finderColor: rgbOf(getComputedStyle(finders).fill || '#000000'),
122
+ finders: finders.getAttribute('d') ?? '',
123
+ moduleColor: rgbOf(getComputedStyle(modules).fill || '#000000'),
124
+ modules: modules.getAttribute('d') ?? '',
125
+ quiet: QUIET,
126
+ }, size);
127
+ }
128
+ async toPng(size = 512) {
129
+ const markup = this.toSvg(size);
130
+ if (markup === '') {
131
+ return null;
132
+ }
133
+ const image = new Image();
134
+ image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(markup)}`;
135
+ await image.decode();
136
+ const canvas = document.createElement('canvas');
137
+ canvas.width = size;
138
+ canvas.height = size;
139
+ canvas.getContext('2d')?.drawImage(image, 0, 0, size, size);
140
+ return new Promise((resolve) => canvas.toBlob(resolve, 'image/png'));
141
+ }
142
+ async download(filename = 'qr-code.png', size = 512) {
143
+ const png = await this.toPng(size);
144
+ if (!png) {
145
+ return;
146
+ }
147
+ const url = URL.createObjectURL(png);
148
+ const link = document.createElement('a');
149
+ link.href = url;
150
+ link.download = filename;
151
+ link.click();
152
+ setTimeout(() => URL.revokeObjectURL(url));
153
+ }
82
154
  renderCode() {
83
155
  if (this.value === '') {
84
156
  return nothing;
85
157
  }
86
- const result = encode(this.value, { border: 0, ecc: this.level });
158
+ const result = this.encoded();
159
+ if (result === undefined) {
160
+ return nothing;
161
+ }
87
162
  const size = result.size;
88
- const quiet = 4;
163
+ const quiet = QUIET;
89
164
  const box = size + quiet * 2;
90
165
  const finders = pathFor(result.data, 'square', 'finders');
91
166
  return svg `<svg