@fusion.dev/helix-headless 0.146.0 → 0.148.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/custom-elements.json +9287 -1084
  2. package/dist/accordion/item/accordion-item.d.ts +43 -21
  3. package/dist/accordion/item/accordion-item.js +37 -11
  4. package/dist/accordion/root/accordion-root.d.ts +8 -2
  5. package/dist/accordion/root/accordion-root.js +6 -8
  6. package/dist/announcement/root/announcement-root.d.ts +66 -6
  7. package/dist/announcement/root/announcement-root.js +144 -13
  8. package/dist/area-chart/root/area-chart-root.d.ts +18 -1
  9. package/dist/area-chart/root/area-chart-root.js +14 -7
  10. package/dist/avatar/root/avatar-root.d.ts +31 -1
  11. package/dist/avatar/root/avatar-root.js +11 -1
  12. package/dist/badge/count.d.ts +14 -0
  13. package/dist/badge/count.js +20 -0
  14. package/dist/badge/root/badge-root.d.ts +57 -6
  15. package/dist/badge/root/badge-root.js +77 -8
  16. package/dist/bar-chart/root/bar-chart-root.js +11 -20
  17. package/dist/bar-chart/stack.d.ts +10 -0
  18. package/dist/bar-chart/stack.js +8 -0
  19. package/dist/blockquote/root/blockquote-root.d.ts +66 -8
  20. package/dist/blockquote/root/blockquote-root.js +85 -8
  21. package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +14 -0
  22. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +12 -2
  23. package/dist/breadcrumbs/root/breadcrumbs-root.js +2 -2
  24. package/dist/button/root/button-root.d.ts +24 -38
  25. package/dist/button/root/button-root.js +6 -37
  26. package/dist/button/submitter.d.ts +27 -0
  27. package/dist/button/submitter.js +25 -0
  28. package/dist/button-group/arrows.d.ts +14 -0
  29. package/dist/button-group/arrows.js +10 -0
  30. package/dist/button-group/root/button-group-root.d.ts +141 -3
  31. package/dist/button-group/root/button-group-root.js +142 -2
  32. package/dist/canvas/edge-pan.d.ts +61 -0
  33. package/dist/canvas/edge-pan.js +63 -0
  34. package/dist/canvas/node/canvas-node.d.ts +12 -2
  35. package/dist/canvas/node/canvas-node.js +6 -2
  36. package/dist/canvas/root/canvas-document.d.ts +10 -0
  37. package/dist/canvas/root/canvas-document.js +1 -0
  38. package/dist/canvas/root/canvas-root.d.ts +107 -5
  39. package/dist/canvas/root/canvas-root.js +140 -36
  40. package/dist/canvas/snap.d.ts +42 -0
  41. package/dist/canvas/snap.js +32 -0
  42. package/dist/card/root/card-root.d.ts +12 -1
  43. package/dist/card/root/card-root.js +11 -4
  44. package/dist/carousel/root/carousel-root.d.ts +4 -3
  45. package/dist/carousel/root/carousel-root.js +2 -6
  46. package/dist/chat/root/chat-root.d.ts +28 -4
  47. package/dist/chat/root/chat-root.js +12 -7
  48. package/dist/checkbox/root/checkbox-root.js +5 -7
  49. package/dist/checkbox-group/root/checkbox-group-root.d.ts +13 -0
  50. package/dist/checkbox-group/root/checkbox-group-root.js +16 -7
  51. package/dist/checkbox-group/select-all.d.ts +9 -4
  52. package/dist/checkbox-group/select-all.js +1 -1
  53. package/dist/code-block/root/code-block-root.d.ts +41 -18
  54. package/dist/code-block/root/code-block-root.js +21 -6
  55. package/dist/color-input/root/color-input-root.d.ts +11 -0
  56. package/dist/color-input/root/color-input-root.js +12 -5
  57. package/dist/color-picker/channels.js +1 -2
  58. package/dist/color-picker/root/color-picker-root.d.ts +32 -24
  59. package/dist/color-picker/root/color-picker-root.js +99 -55
  60. package/dist/color-picker/wheel.d.ts +28 -0
  61. package/dist/color-picker/wheel.js +15 -0
  62. package/dist/combobox/root/combobox-root.d.ts +83 -11
  63. package/dist/combobox/root/combobox-root.js +81 -18
  64. package/dist/command/root/command-root.d.ts +7 -4
  65. package/dist/command/root/command-root.js +2 -19
  66. package/dist/copy-button/root/copy-button-root.d.ts +16 -1
  67. package/dist/copy-button/root/copy-button-root.js +7 -2
  68. package/dist/data-table/keys.d.ts +0 -18
  69. package/dist/data-table/keys.js +1 -11
  70. package/dist/data-table/root/data-table-root.js +29 -50
  71. package/dist/date-input/root/date-input-root.d.ts +8 -1
  72. package/dist/date-input/root/date-input-root.js +8 -5
  73. package/dist/date-picker/root/date-picker-root.d.ts +14 -9
  74. package/dist/date-picker/root/date-picker-root.js +21 -16
  75. package/dist/description-list/item/description-list-item.d.ts +22 -4
  76. package/dist/description-list/item/description-list-item.js +14 -4
  77. package/dist/divider/root/divider-root.d.ts +45 -16
  78. package/dist/divider/root/divider-root.js +28 -4
  79. package/dist/donut-chart/root/donut-chart-root.js +13 -6
  80. package/dist/donut-chart/slice-label.d.ts +35 -0
  81. package/dist/donut-chart/slice-label.js +11 -0
  82. package/dist/drawer/root/drawer-root.d.ts +4 -2
  83. package/dist/drawer/root/drawer-root.js +15 -3
  84. package/dist/drilldown/item/drilldown-item.d.ts +21 -1
  85. package/dist/drilldown/item/drilldown-item.js +38 -4
  86. package/dist/drilldown/root/drilldown-root.js +3 -2
  87. package/dist/element-events.generated.js +5 -1
  88. package/dist/empty/root/empty-root.d.ts +46 -2
  89. package/dist/empty/root/empty-root.js +50 -5
  90. package/dist/file-input/root/file-input-root.d.ts +34 -6
  91. package/dist/file-input/root/file-input-root.js +55 -21
  92. package/dist/file-input/status.d.ts +24 -0
  93. package/dist/file-input/status.js +11 -0
  94. package/dist/filter-input/chip/filter-chip.d.ts +23 -0
  95. package/dist/filter-input/chip/filter-chip.js +10 -2
  96. package/dist/filter-input/root/filter-input-root.d.ts +8 -0
  97. package/dist/filter-input/root/filter-input-root.js +17 -27
  98. package/dist/format-date/fields.d.ts +20 -0
  99. package/dist/format-date/fields.js +14 -0
  100. package/dist/format-date/options.d.ts +5 -5
  101. package/dist/format-date/options.js +10 -39
  102. package/dist/format-date/root/format-date-root.d.ts +53 -4
  103. package/dist/format-date/root/format-date-root.js +38 -50
  104. package/dist/graph/root/graph-root.d.ts +23 -5
  105. package/dist/graph/root/graph-root.js +17 -20
  106. package/dist/heatmap/root/buckets.d.ts +21 -0
  107. package/dist/heatmap/root/buckets.js +34 -0
  108. package/dist/heatmap/root/heatmap-root.d.ts +76 -3
  109. package/dist/heatmap/root/heatmap-root.js +105 -20
  110. package/dist/icon/index.d.ts +1 -1
  111. package/dist/icon/index.js +1 -1
  112. package/dist/icon/registry.d.ts +26 -1
  113. package/dist/icon/registry.js +12 -1
  114. package/dist/icon/root/icon-root.d.ts +13 -0
  115. package/dist/icon/root/icon-root.js +23 -2
  116. package/dist/icon/root/index.d.ts +1 -1
  117. package/dist/icon/root/index.js +1 -1
  118. package/dist/image/root/image-root.d.ts +20 -0
  119. package/dist/image/root/image-root.js +6 -1
  120. package/dist/input/root/input-root.d.ts +23 -2
  121. package/dist/input/root/input-root.js +12 -8
  122. package/dist/kbd/root/kbd-root.d.ts +20 -3
  123. package/dist/kbd/root/kbd-root.js +23 -50
  124. package/dist/legend/hidden-series.d.ts +10 -2
  125. package/dist/legend/hidden-series.js +5 -2
  126. package/dist/legend/root/legend-root.d.ts +25 -7
  127. package/dist/legend/root/legend-root.js +25 -26
  128. package/dist/lightbox/root/lightbox-root.d.ts +50 -5
  129. package/dist/lightbox/root/lightbox-root.js +76 -8
  130. package/dist/lightbox/zoom.d.ts +2 -0
  131. package/dist/lightbox/zoom.js +5 -3
  132. package/dist/line-chart/root/line-chart-root.d.ts +16 -0
  133. package/dist/line-chart/root/line-chart-root.js +10 -2
  134. package/dist/list/root/list-root.d.ts +10 -2
  135. package/dist/list/root/list-root.js +31 -24
  136. package/dist/list/selection.d.ts +17 -0
  137. package/dist/list/selection.js +16 -0
  138. package/dist/listbox/root/listbox-root.d.ts +125 -26
  139. package/dist/listbox/root/listbox-root.js +156 -166
  140. package/dist/map/marker/map-marker.d.ts +2 -1
  141. package/dist/map/root/map-root.d.ts +40 -2
  142. package/dist/map/root/map-root.js +64 -23
  143. package/dist/megamenu/hand-over.d.ts +75 -0
  144. package/dist/megamenu/hand-over.js +67 -0
  145. package/dist/megamenu/root/megamenu-root.d.ts +38 -14
  146. package/dist/megamenu/root/megamenu-root.js +29 -50
  147. package/dist/menu/item/menu-item.js +4 -4
  148. package/dist/menu/root/menu-root.d.ts +5 -2
  149. package/dist/meter/root/meter-root.d.ts +42 -0
  150. package/dist/meter/root/meter-root.js +33 -7
  151. package/dist/navigation/root/navigation-root.js +5 -8
  152. package/dist/notifications/group/notification-group.d.ts +8 -1
  153. package/dist/notifications/group/notification-group.js +12 -6
  154. package/dist/notifications/notification/notification.d.ts +12 -1
  155. package/dist/notifications/notification/notification.js +3 -1
  156. package/dist/notifications/root/notifications-root.d.ts +48 -3
  157. package/dist/notifications/root/notifications-root.js +36 -6
  158. package/dist/number-input/grain.d.ts +28 -0
  159. package/dist/number-input/grain.js +14 -0
  160. package/dist/number-input/root/number-input-root.d.ts +17 -1
  161. package/dist/number-input/root/number-input-root.js +29 -18
  162. package/dist/object-inspector/root/object-inspector-root.d.ts +17 -6
  163. package/dist/object-inspector/root/object-inspector-root.js +31 -20
  164. package/dist/option/index.d.ts +1 -0
  165. package/dist/option/index.js +1 -0
  166. package/dist/option/separator/option-separator.d.ts +30 -0
  167. package/dist/option/separator/option-separator.js +21 -0
  168. package/dist/outline/root/outline-root.d.ts +12 -0
  169. package/dist/outline/root/outline-root.js +5 -19
  170. package/dist/pagination/size/pagination-size.d.ts +6 -0
  171. package/dist/pagination/size/pagination-size.js +5 -3
  172. package/dist/pin-input/code.d.ts +51 -1
  173. package/dist/pin-input/code.js +37 -0
  174. package/dist/pin-input/root/pin-input-root.d.ts +98 -121
  175. package/dist/pin-input/root/pin-input-root.js +151 -156
  176. package/dist/popover/hide-outside.js +2 -9
  177. package/dist/popover/root/popover-root.d.ts +7 -1
  178. package/dist/popover/root/popover-root.js +9 -1
  179. package/dist/progress/root/progress-root.d.ts +27 -0
  180. package/dist/progress/root/progress-root.js +25 -7
  181. package/dist/qr-code/export.d.ts +19 -0
  182. package/dist/qr-code/export.js +14 -0
  183. package/dist/qr-code/name.d.ts +11 -0
  184. package/dist/qr-code/name.js +7 -0
  185. package/dist/qr-code/root/qr-code-root.d.ts +31 -9
  186. package/dist/qr-code/root/qr-code-root.js +29 -17
  187. package/dist/radio/group/radio-group.d.ts +15 -0
  188. package/dist/radio/root/radio-root.js +2 -2
  189. package/dist/rating/root/rating-root.d.ts +22 -9
  190. package/dist/rating/root/rating-root.js +15 -39
  191. package/dist/scatter-chart/measures.d.ts +22 -0
  192. package/dist/scatter-chart/measures.js +11 -0
  193. package/dist/scatter-chart/root/scatter-chart-root.d.ts +26 -0
  194. package/dist/scatter-chart/root/scatter-chart-root.js +21 -7
  195. package/dist/scroll-area/scrollbar.d.ts +0 -8
  196. package/dist/scroll-area/scrollbar.js +1 -1
  197. package/dist/select/align.d.ts +47 -0
  198. package/dist/select/align.js +7 -0
  199. package/dist/select/root/select-root.d.ts +115 -11
  200. package/dist/select/root/select-root.js +104 -20
  201. package/dist/slider/root/slider-root.d.ts +18 -1
  202. package/dist/slider/root/slider-root.js +22 -0
  203. package/dist/sparkline/readings.d.ts +30 -12
  204. package/dist/sparkline/readings.js +32 -15
  205. package/dist/sparkline/root/sparkline-root.d.ts +46 -3
  206. package/dist/sparkline/root/sparkline-root.js +60 -21
  207. package/dist/speed-dial/group/speed-dial-group.d.ts +48 -0
  208. package/dist/speed-dial/group/speed-dial-group.js +43 -0
  209. package/dist/speed-dial/index.d.ts +1 -0
  210. package/dist/speed-dial/index.js +1 -0
  211. package/dist/speed-dial/item/speed-dial-item.d.ts +27 -2
  212. package/dist/speed-dial/item/speed-dial-item.js +43 -5
  213. package/dist/speed-dial/long-press.d.ts +50 -0
  214. package/dist/speed-dial/long-press.js +48 -0
  215. package/dist/speed-dial/root/speed-dial-root.d.ts +86 -1
  216. package/dist/speed-dial/root/speed-dial-root.js +123 -6
  217. package/dist/splitter/root/splitter-root.d.ts +9 -2
  218. package/dist/splitter/root/splitter-root.js +18 -3
  219. package/dist/steps/root/steps-root.d.ts +8 -1
  220. package/dist/steps/root/steps-root.js +6 -1
  221. package/dist/steps/step/step.d.ts +1 -0
  222. package/dist/steps/step/step.js +1 -1
  223. package/dist/table/cell/table-cell.d.ts +8 -1
  224. package/dist/table/cell/table-cell.js +6 -2
  225. package/dist/table/column/table-column.d.ts +6 -0
  226. package/dist/table/column/table-column.js +5 -1
  227. package/dist/table/grid-controller.d.ts +117 -0
  228. package/dist/table/grid-controller.js +293 -0
  229. package/dist/table/root/table-root.d.ts +47 -3
  230. package/dist/table/root/table-root.js +93 -29
  231. package/dist/table/row/table-row.d.ts +36 -0
  232. package/dist/table/row/table-row.js +52 -2
  233. package/dist/tabs/root/tabs-root.d.ts +5 -3
  234. package/dist/tag/group/tag-group.d.ts +16 -4
  235. package/dist/tag/group/tag-group.js +23 -20
  236. package/dist/textarea/root/textarea-root.d.ts +14 -2
  237. package/dist/textarea/root/textarea-root.js +6 -7
  238. package/dist/time-input/root/time-input-root.d.ts +5 -1
  239. package/dist/time-input/root/time-input-root.js +10 -7
  240. package/dist/time-picker/root/time-picker-root.d.ts +12 -14
  241. package/dist/time-picker/root/time-picker-root.js +18 -23
  242. package/dist/toast/item/toast-item.d.ts +9 -6
  243. package/dist/toast/item/toast-item.js +1 -1
  244. package/dist/toast/root/toast-root.d.ts +40 -4
  245. package/dist/toast/root/toast-root.js +68 -16
  246. package/dist/toast/words.d.ts +12 -0
  247. package/dist/toast/words.js +25 -0
  248. package/dist/toggle/group/toggle-group.d.ts +29 -5
  249. package/dist/toggle/group/toggle-group.js +26 -43
  250. package/dist/toggle/root/toggle-root.js +2 -4
  251. package/dist/toolbar/root/toolbar-root.d.ts +52 -5
  252. package/dist/toolbar/root/toolbar-root.js +88 -13
  253. package/dist/tooltip/root/tooltip-root.d.ts +7 -1
  254. package/dist/tooltip/root/tooltip-root.js +10 -2
  255. package/dist/tour/root/tour-root.d.ts +15 -0
  256. package/dist/tour/root/tour-root.js +20 -1
  257. package/dist/tour/stop/tour-stop.d.ts +3 -0
  258. package/dist/tour/stop/tour-stop.js +3 -3
  259. package/dist/tree/root/tree-root.js +2 -2
  260. package/dist/trend/figure.d.ts +16 -0
  261. package/dist/trend/figure.js +8 -0
  262. package/dist/trend/root/trend-root.d.ts +41 -2
  263. package/dist/trend/root/trend-root.js +29 -3
  264. package/dist/video/root/extra-controls.d.ts +12 -0
  265. package/dist/video/root/extra-controls.js +6 -2
  266. package/dist/video/root/video-root.d.ts +16 -1
  267. package/dist/video/root/video-root.js +15 -1
  268. package/dist/video-playlist/root/video-playlist-root.d.ts +11 -1
  269. package/dist/video-playlist/root/video-playlist-root.js +3 -0
  270. package/dist/virtual-list/fill.d.ts +11 -0
  271. package/dist/virtual-list/fill.js +9 -0
  272. package/dist/virtual-list/keys.d.ts +16 -7
  273. package/dist/virtual-list/keys.js +17 -6
  274. package/dist/virtual-list/plan.d.ts +17 -1
  275. package/dist/virtual-list/plan.js +13 -2
  276. package/dist/virtual-list/root/virtual-list-root.d.ts +25 -3
  277. package/dist/virtual-list/root/virtual-list-root.js +53 -41
  278. package/lint.json +1 -1
  279. package/package.json +5 -5
  280. package/types/jsx.d.ts +4934 -954
  281. package/types/svelte.d.ts +2 -0
  282. package/types/vue.d.ts +946 -253
  283. package/dist/color-picker/swatches.d.ts +0 -12
  284. package/dist/color-picker/swatches.js +0 -26
  285. package/dist/rating/steps.d.ts +0 -7
  286. package/dist/rating/steps.js +0 -9
  287. package/dist/tooltip/focus-target.d.ts +0 -6
  288. package/dist/tooltip/focus-target.js +0 -9
@@ -4,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 { ariaChord, chordParts, customElement, elementById, HelixElement, HelixShortcutEvent, matchesChord, onApple, printableChord, } from '@fusion.dev/helix-core';
7
+ import { ariaChord, chordParts, customElement, elementById, HelixElement, HelixShortcutEvent, keyNames, matchesChord, onApple, printableChord, wordsConverter, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
10
  import { claimShortcut, releaseShortcut } from '../shortcut.js';
@@ -29,49 +29,12 @@ const UNTYPED = new Set([
29
29
  'reset',
30
30
  'submit',
31
31
  ]);
32
- const APPLE = {
33
- alt: '⌥',
34
- backspace: '⌫',
35
- cmd: '⌘',
36
- ctrl: '⌃',
37
- del: '⌦',
38
- down: '↓',
39
- enter: '↵',
40
- esc: 'Esc',
41
- left: '←',
42
- meta: '⌘',
43
- mod: '⌘',
44
- opt: '⌥',
45
- right: '→',
46
- shift: '⇧',
47
- space: 'Space',
48
- tab: '⇥',
49
- up: '↑',
50
- };
51
- const ELSEWHERE = {
52
- alt: 'Alt',
53
- backspace: 'Backspace',
54
- cmd: 'Win',
55
- ctrl: 'Ctrl',
56
- del: 'Del',
57
- down: '↓',
58
- enter: 'Enter',
59
- esc: 'Esc',
60
- left: '←',
61
- meta: 'Win',
62
- mod: 'Ctrl',
63
- opt: 'Alt',
64
- right: '→',
65
- shift: 'Shift',
66
- space: 'Space',
67
- tab: 'Tab',
68
- up: '↑',
69
- };
70
32
  let HelixHeadlessKbd = class HelixHeadlessKbd extends HelixElement {
71
33
  constructor() {
72
34
  super(...arguments);
73
35
  this.caps = false;
74
36
  this.for = '';
37
+ this.keyLabels = {};
75
38
  this.keys = '';
76
39
  this.handleKeydown = (event) => {
77
40
  const press = event;
@@ -88,9 +51,11 @@ let HelixHeadlessKbd = class HelixHeadlessKbd extends HelixElement {
88
51
  }
89
52
  const shortcut = new HelixShortcutEvent({ keys: this.keys });
90
53
  this.dispatchEvent(shortcut);
91
- if (!shortcut.defaultPrevented) {
92
- press.preventDefault();
54
+ if (shortcut.defaultPrevented) {
55
+ return;
93
56
  }
57
+ press.preventDefault();
58
+ this.act();
94
59
  };
95
60
  }
96
61
  connectedCallback() {
@@ -133,14 +98,7 @@ let HelixHeadlessKbd = class HelixHeadlessKbd extends HelixElement {
133
98
  }
134
99
  }
135
100
  get label() {
136
- const apple = !this.hydrating && onApple();
137
- const names = apple ? APPLE : ELSEWHERE;
138
- const own = this.keyLabels ?? {};
139
- const keys = chordParts(this.keys).map((part) => {
140
- const name = part.toLowerCase();
141
- return own[name] ?? names[name] ?? (part.length === 1 ? part.toUpperCase() : part);
142
- });
143
- return { keys, separator: apple ? '' : '+' };
101
+ return keyNames(chordParts(this.keys), !this.hydrating && onApple(), this.keyLabels ?? {});
144
102
  }
145
103
  listen() {
146
104
  if (!this.within || this.keys === '') {
@@ -157,6 +115,18 @@ let HelixHeadlessKbd = class HelixHeadlessKbd extends HelixElement {
157
115
  this.listening?.removeEventListener('keydown', this.handleKeydown);
158
116
  this.listening = undefined;
159
117
  }
118
+ act() {
119
+ const control = this.activate && this.for !== '' ? elementById(this, this.for) : null;
120
+ if (!(control instanceof HTMLElement)) {
121
+ return;
122
+ }
123
+ if (this.activate === 'click') {
124
+ control.click();
125
+ }
126
+ else {
127
+ control.focus();
128
+ }
129
+ }
160
130
  firstUpdated(changed) {
161
131
  super.firstUpdated(changed);
162
132
  this.finishHydration();
@@ -173,6 +143,9 @@ let HelixHeadlessKbd = class HelixHeadlessKbd extends HelixElement {
173
143
  return html `${keys.map((key, index) => html `${index > 0 && separator !== '' ? html `<span part="separator">${separator}</span>` : nothing}<span part="key">${key}</span>`)}`;
174
144
  }
175
145
  };
146
+ __decorate([
147
+ property()
148
+ ], HelixHeadlessKbd.prototype, "activate", void 0);
176
149
  __decorate([
177
150
  property({ reflect: true, type: Boolean })
178
151
  ], HelixHeadlessKbd.prototype, "caps", void 0);
@@ -180,7 +153,7 @@ __decorate([
180
153
  property()
181
154
  ], HelixHeadlessKbd.prototype, "for", void 0);
182
155
  __decorate([
183
- property({ attribute: 'key-labels', type: Object })
156
+ property({ attribute: 'key-labels', converter: wordsConverter })
184
157
  ], HelixHeadlessKbd.prototype, "keyLabels", void 0);
185
158
  __decorate([
186
159
  property()
@@ -1,8 +1,16 @@
1
+ /** A list as it may be given: names separated by commas, or an array of names. */
2
+ export type NameList = string | readonly string[];
3
+ /**
4
+ * The names in a list, written either way: split on commas from a string,
5
+ * trimmed and without blanks from an array, which is what a framework hands
6
+ * over for `hiddenSeries={['Direct']}`.
7
+ */
8
+ export declare const namesIn: (list: NameList) => string[];
1
9
  /** Whether a name is in the list, matched without regard to case, as a heading is. */
2
- export declare const isHidden: (list: string, name: string) => boolean;
10
+ export declare const isHidden: (list: NameList, name: string) => boolean;
3
11
  /**
4
12
  * The list a press on this name leaves: without it where it was hidden, and
5
13
  * with it at the end where it was shown. Written back with a comma and a
6
14
  * space, as a person writes a list.
7
15
  */
8
- export declare const toggledNames: (list: string, name: string) => string;
16
+ export declare const toggledNames: (list: NameList, name: string) => string;
@@ -1,11 +1,14 @@
1
1
  import { commaList, seriesKey } from '@fusion.dev/helix-core';
2
+ export const namesIn = (list) => typeof list === 'string'
3
+ ? commaList(list)
4
+ : list.map((name) => String(name).trim()).filter((name) => name !== '');
2
5
  export const isHidden = (list, name) => {
3
6
  const key = seriesKey(name);
4
- return commaList(list).some((one) => seriesKey(one) === key);
7
+ return namesIn(list).some((one) => seriesKey(one) === key);
5
8
  };
6
9
  export const toggledNames = (list, name) => {
7
10
  const key = seriesKey(name);
8
- const now = commaList(list);
11
+ const now = namesIn(list);
9
12
  const next = isHidden(list, name)
10
13
  ? now.filter((one) => seriesKey(one) !== key)
11
14
  : [...now, name.trim()];
@@ -1,5 +1,5 @@
1
1
  import { HelixElement, KeyboardNavigationController, SourceController } from '@fusion.dev/helix-core';
2
- import { type PropertyValues, type TemplateResult } from 'lit';
2
+ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  /**
4
4
  * @summary A list of the series in a chart, each name beside the color it is drawn in.
5
5
  *
@@ -21,9 +21,10 @@ import { type PropertyValues, type TemplateResult } from 'lit';
21
21
  * **`toggles` makes each entry a button that hides its series.** Every chart
22
22
  * reading the same table draws without a hidden series and fits its scale to
23
23
  * the rest, and the series keeps its color. The button says whether its
24
- * series is shown with `aria-pressed`. The buttons are one tab stop: the
25
- * arrow keys move between them, Home and End go to the first and the last,
26
- * and a typed letter goes to the next name starting with it.
24
+ * series is shown with `aria-pressed`. The buttons are a `toolbar`, named
25
+ * as the list is, and one tab stop: the arrow keys move between them, Home
26
+ * and End go to the first and the last, and a typed letter goes to the next
27
+ * name starting with it.
27
28
  * `hidden-series` holds the hidden names,
28
29
  * so a page can start with some hidden or read which are. Two legends on one
29
30
  * table show the same hidden series: the list written last wins, and the
@@ -42,7 +43,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
42
43
  * `::part(entry)::before` in your stylesheet. The entry holds its series color
43
44
  * as `--chart-series`, and this tier ships no CSS.
44
45
  * @csspart entry-hidden - An entry whose series is hidden. Set alongside `entry`.
45
- * @csspart list - The list of entries.
46
+ * @csspart list - The list of entries, a `toolbar` under `toggles`.
46
47
  * @csspart name - One series name.
47
48
  * @csspart toggle - The button around a name under `toggles`, which hides or shows its series.
48
49
  */
@@ -84,9 +85,11 @@ export declare class HelixHeadlessLegend extends HelixElement {
84
85
  *
85
86
  * Set it to start with series hidden. Every chart reading the same table
86
87
  * draws without them. Commas only, because a column's heading may hold a
87
- * space. For a ring the names are the rows.
88
+ * space. For a ring the names are the rows. An array of names set on the
89
+ * property works too, as React sets `hiddenSeries={['Direct']}`; a toggle
90
+ * writes the list back as a string.
88
91
  */
89
- hiddenSeries: string;
92
+ hiddenSeries: string | readonly string[];
90
93
  /**
91
94
  * The name of the list, which a screen reader announces before its entries.
92
95
  *
@@ -176,6 +179,21 @@ export declare class HelixHeadlessLegend extends HelixElement {
176
179
  */
177
180
  protected names(): string[];
178
181
  protected render(): TemplateResult;
182
+ /**
183
+ * What an entry shows before its name, behind a seam a tier can fill.
184
+ *
185
+ * Nothing on this tier, which ships no CSS: the styled tier and a page draw
186
+ * the swatch with `::part(entry)::before`, from the entry's `--chart-series`.
187
+ * A tier that draws its swatch as an element, or a mark per series, returns
188
+ * it here, and `index` says which palette slot the series takes.
189
+ */
190
+ protected renderSwatch(_name: string, _index: number): TemplateResult | typeof nothing;
191
+ /**
192
+ * An entry's name, behind a seam a tier can move: a value beside the name,
193
+ * or the name in other words. `index` is the series' place among the
194
+ * entries. A toggle holds the same name inside its button.
195
+ */
196
+ protected renderName(name: string, _index: number): TemplateResult;
179
197
  /**
180
198
  * The button around a name under `toggles`, behind a seam a tier can move.
181
199
  *
@@ -4,10 +4,10 @@ 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 { commaList, customElement, HelixBeforeChangeEvent, HelixElement, KeyboardNavigationController, requestChange, SourceController, seriesIndex, seriesKey, typeaheadBy, } from '@fusion.dev/helix-core';
7
+ import { customElement, focusItemOf, HelixElement, KeyboardNavigationController, requestValue, SourceController, seriesIndex, seriesKey, typeaheadBy, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
- import { isHidden, toggledNames } from '../hidden-series.js';
10
+ import { isHidden, namesIn, toggledNames } from '../hidden-series.js';
11
11
  let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
12
12
  constructor() {
13
13
  super(...arguments);
@@ -19,14 +19,7 @@ let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
19
19
  this.stop = 0;
20
20
  this.keys = new KeyboardNavigationController(this, {
21
21
  items: () => this.toggles
22
- ? [...this.renderRoot.querySelectorAll('[part="toggle"]')].map((button) => ({
23
- contains: (node) => button.contains(node),
24
- disabled: false,
25
- focusItem: () => button.focus(),
26
- select: () => { },
27
- selected: false,
28
- words: button.textContent?.trim() ?? '',
29
- }))
22
+ ? [...this.renderRoot.querySelectorAll('[part="toggle"]')].map((button) => focusItemOf(button, { words: button.textContent?.trim() ?? '' }))
30
23
  : [],
31
24
  origin: 'focus',
32
25
  selects: () => false,
@@ -41,15 +34,16 @@ let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
41
34
  }
42
35
  const table = this.source.element;
43
36
  const asked = changed.has('hiddenSeries') &&
44
- !(changed.get('hiddenSeries') === undefined && this.hiddenSeries === '');
45
- this.writes = asked || (table !== null && table !== this.wroteTo && this.hiddenSeries !== '');
37
+ !(changed.get('hiddenSeries') === undefined && namesIn(this.hiddenSeries).length === 0);
38
+ this.writes =
39
+ asked || (table !== null && table !== this.wroteTo && namesIn(this.hiddenSeries).length > 0);
46
40
  if (!this.writes && table !== null) {
47
41
  this.follow();
48
42
  }
49
43
  }
50
44
  follow() {
51
45
  const hidden = this.source.hidden;
52
- const now = commaList(this.hiddenSeries);
46
+ const now = namesIn(this.hiddenSeries);
53
47
  const next = this.names().filter((name) => hidden.has(seriesKey(name)));
54
48
  const same = next.length === now.length &&
55
49
  next.every((name) => now.some((one) => seriesKey(one) === seriesKey(name)));
@@ -61,7 +55,7 @@ let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
61
55
  super.updated(changed);
62
56
  if (this.writes) {
63
57
  this.writes = false;
64
- this.wroteTo = this.source.hide(commaList(this.hiddenSeries), this.names()) ?? this.wroteTo;
58
+ this.wroteTo = this.source.hide(namesIn(this.hiddenSeries), this.names()) ?? this.wroteTo;
65
59
  }
66
60
  }
67
61
  disconnectedCallback() {
@@ -72,14 +66,8 @@ let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
72
66
  super.disconnectedCallback();
73
67
  }
74
68
  toggle(name) {
75
- requestChange({
76
- apply: (value) => {
77
- this.hiddenSeries = value;
78
- this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
79
- },
80
- event: (value) => new HelixBeforeChangeEvent({ value }),
81
- host: this,
82
- next: toggledNames(this.hiddenSeries, name),
69
+ requestValue(this, toggledNames(this.hiddenSeries, name), (value) => {
70
+ this.hiddenSeries = value;
83
71
  });
84
72
  }
85
73
  names() {
@@ -93,20 +81,31 @@ let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
93
81
  const named = this.label || this.source.element?.querySelector('caption')?.textContent?.trim();
94
82
  const names = this.names();
95
83
  const stop = Math.min(this.stop, names.length - 1);
96
- return html `<ul aria-label=${named || nothing} part="list" role="list">
84
+ const toolbar = this.toggles;
85
+ return html `<ul
86
+ aria-label=${named || nothing}
87
+ part="list"
88
+ role=${toolbar ? 'toolbar' : 'list'}
89
+ >
97
90
  ${names.map((name, index) => {
98
91
  const off = isHidden(this.hiddenSeries, name);
99
92
  return html `<li
100
93
  data-series=${seriesIndex(index)}
101
94
  part="entry${off ? ' entry-hidden' : ''}"
102
95
  style="--chart-series: var(--chart-series-${seriesIndex(index)})"
103
- role="listitem"
96
+ role=${toolbar ? 'none' : 'listitem'}
104
97
  >
105
- ${this.toggles ? this.renderToggle(name, off, index, index === stop) : html `<span part="name">${name}</span>`}
98
+ ${this.renderSwatch(name, index)}${toolbar ? this.renderToggle(name, off, index, index === stop) : this.renderName(name, index)}
106
99
  </li>`;
107
100
  })}
108
101
  </ul>`;
109
102
  }
103
+ renderSwatch(_name, _index) {
104
+ return nothing;
105
+ }
106
+ renderName(name, _index) {
107
+ return html `<span part="name">${name}</span>`;
108
+ }
110
109
  renderToggle(name, off, index = 0, holds = true) {
111
110
  return html `<button
112
111
  aria-pressed=${off ? 'false' : 'true'}
@@ -118,7 +117,7 @@ let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
118
117
  this.stop = index;
119
118
  }}
120
119
  >
121
- <span part="name">${name}</span>
120
+ ${this.renderName(name, index)}
122
121
  </button>`;
123
122
  }
124
123
  };
@@ -30,7 +30,11 @@ declare const HelixHeadlessLightbox_base: (new (...args: any[]) => import("@fusi
30
30
  * about. `+` and `-` zoom in and out from the keyboard, and `0` puts it back.
31
31
  * The scale and the offset are on the `viewport` part as `--lightbox-zoom`,
32
32
  * `--lightbox-pan-x` and `--lightbox-pan-y`, written only while zoomed, and a
33
- * swipe moves to the next item only while the item is at its own size.
33
+ * swipe moves to the next item only while the item is at its own size. With
34
+ * `controls`, the viewer also draws zoom-out and zoom-in controls, for a
35
+ * pointer with no wheel and no second finger. Once a reader stops zooming, the
36
+ * live region says the new scale in the words of `zoom-status-label`, such as
37
+ * "Zoomed to 150%".
34
38
  *
35
39
  * Each item is a slide: a `group` with the role description in
36
40
  * `slide-role-label`, named by its `label` or by `slide-label`. A touch or a pen
@@ -55,6 +59,8 @@ declare const HelixHeadlessLightbox_base: (new (...args: any[]) => import("@fusi
55
59
  * @slot header - Content above the item, such as a title and a close button.
56
60
  * @slot next-icon - Replaces the glyph inside the next control.
57
61
  * @slot previous-icon - Replaces the glyph inside the previous control.
62
+ * @slot zoom-in-icon - Replaces the glyph inside the zoom-in control.
63
+ * @slot zoom-out-icon - Replaces the glyph inside the zoom-out control.
58
64
  *
59
65
  * @event {Event} close - Fired when the viewer closes. The component dispatches it again from the host, because the dialog's own event does not leave the shadow root.
60
66
  * @event {HelixActivateEvent} helix-activate - Fired after the shown item changes, when the element already holds the new item.
@@ -67,12 +73,18 @@ declare const HelixHeadlessLightbox_base: (new (...args: any[]) => import("@fusi
67
73
  * @csspart dialog - The native dialog element.
68
74
  * @csspart dismiss - The close control, drawn with `controls`.
69
75
  * @csspart dismiss-mark - The glyph on the close control, where none is slotted.
70
- * @csspart live - The live region that announces which item is shown.
76
+ * @csspart footer - The slot the footer is in. It draws no box until you give it one.
77
+ * @csspart header - The slot the header is in. It draws no box until you give it one.
78
+ * @csspart live - The live region that announces which item is shown, and the scale once a reader has zoomed.
71
79
  * @csspart next - The control that shows the next item, drawn with `controls`.
72
80
  * @csspart next-mark - The glyph on the next control, where none is slotted.
73
81
  * @csspart previous - The control that shows the previous item, drawn with `controls`.
74
82
  * @csspart previous-mark - The glyph on the previous control, where none is slotted.
75
83
  * @csspart viewport - The box the current item is shown in.
84
+ * @csspart zoom-in - The control that zooms in by half again, drawn with `controls` where `max-zoom` allows zooming.
85
+ * @csspart zoom-in-mark - The glyph on the zoom-in control, where none is slotted.
86
+ * @csspart zoom-out - The control that zooms back out, drawn with `controls` where `max-zoom` allows zooming.
87
+ * @csspart zoom-out-mark - The glyph on the zoom-out control, where none is slotted.
76
88
  *
77
89
  * @cssproperty --lightbox-pan-x - How far the zoomed item is moved across from the middle of the viewport, in pixels, toward the right. Written only while zoomed.
78
90
  * @cssproperty --lightbox-pan-y - How far the zoomed item is moved down from the middle of the viewport, in pixels. Written only while zoomed.
@@ -86,12 +98,14 @@ declare const HelixHeadlessLightbox_base: (new (...args: any[]) => import("@fusi
86
98
  */
87
99
  export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
88
100
  /**
89
- * Draws the viewer's own previous, next and close controls over the picture.
101
+ * Draws the viewer's own previous, next and close controls over the picture,
102
+ * and zoom-out and zoom-in controls where `max-zoom` allows zooming.
90
103
  *
91
104
  * Off by default, for a viewer whose chrome you compose in the `header` and
92
105
  * `footer` slots. The controls hide with the rest of the chrome while the
93
- * reader is idle, and the previous and next ones say they are unavailable at
94
- * either end of a set that does not `loop`.
106
+ * reader is idle, the previous and next ones say they are unavailable at
107
+ * either end of a set that does not `loop`, and the zoom ones at either end
108
+ * of the scale.
95
109
  */
96
110
  controls: boolean;
97
111
  /**
@@ -156,16 +170,36 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
156
170
  * counter in the chrome can empty this and say it themselves.
157
171
  */
158
172
  statusLabel: string;
173
+ /**
174
+ * The zoom-in control's accessible name.
175
+ */
176
+ zoomInLabel: string;
177
+ /**
178
+ * The zoom-out control's accessible name.
179
+ */
180
+ zoomOutLabel: string;
181
+ /**
182
+ * What the live region says once a reader has stopped zooming, with the
183
+ * scale as a whole `{percent}` filled in. Empty it to say nothing.
184
+ */
185
+ zoomStatusLabel: string;
159
186
  /** Whether the reader has been still long enough to hide the chrome. */
160
187
  private resting;
161
188
  /** Whether the viewer is filling the screen. */
162
189
  private filled;
163
190
  /** What the live region is currently saying. */
164
191
  private announcement;
192
+ /** The scale the shown item is drawn at, which the zoom controls read. */
193
+ private zoomScale;
194
+ private readonly zoomSaid;
195
+ /** Whether the zoom is being put back because the item changed, which says nothing. */
196
+ private quietZoom;
165
197
  protected readonly swipes: SwipeController;
166
198
  private readonly zoomer;
167
199
  /** Writes the zoom on the viewport, and nothing at the item's own size. */
168
200
  private drawZoom;
201
+ /** Says the new scale in the live region, a moment after the last change. */
202
+ private sayZoom;
169
203
  /**
170
204
  * Zooms the shown item to `scale`, held between `1` and `max-zoom`, about
171
205
  * the middle of the viewport.
@@ -243,6 +277,10 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
243
277
  private readonly handleKeydown;
244
278
  /** `+` and `-` zoom in and out by half again, and `0` puts the item back. Returns whether it was one. */
245
279
  private zoomKey;
280
+ /** Zooms in by half again, as `+` and the zoom-in control do. */
281
+ private zoomIn;
282
+ /** Zooms out by as much, as `-` and the zoom-out control do. */
283
+ private zoomOut;
246
284
  /** Home and End, which are an index rather than a step. */
247
285
  private toEnd;
248
286
  /** The item on screen: the one `active` names, or the first where it names none. */
@@ -288,6 +326,13 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
288
326
  * control that has just shown the last picture keeps focus.
289
327
  */
290
328
  protected renderControls(): TemplateResult;
329
+ /**
330
+ * Zoom out and zoom in, for a pointer with no wheel and no second finger.
331
+ *
332
+ * Out before in, so they read as a scale runs. `aria-disabled` at either
333
+ * limit, as the previous and next controls are at the ends.
334
+ */
335
+ protected renderZoomControls(): TemplateResult;
291
336
  }
292
337
  declare global {
293
338
  interface HTMLElementTagNameMap {
@@ -4,12 +4,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { CommandController, customElement, deepActiveElement, deepContains, drawDismiss, drawDismissGlyph, FullscreenController, fillTemplate, HelixElement, hostAria, IdleController, inlineStep, isRtl, keyClaimed, lockScroll, MixinControllerActive, MixinControllerOpen, resolveActiveItem, SwipeController, swipeStep, unlockScroll, whenHydrated, } from '@fusion.dev/helix-core';
8
- import { caretLeft, caretRight } from '@fusion.dev/helix-icons';
7
+ import { CommandController, customElement, DelayController, deepActiveElement, deepContains, drawDismiss, drawDismissGlyph, FullscreenController, fillTemplate, HelixElement, hostAria, IdleController, inlineStep, isRtl, keyClaimed, lockScroll, MixinControllerActive, MixinControllerOpen, resolveActiveItem, SwipeController, swipeStep, unlockScroll, whenHydrated, } from '@fusion.dev/helix-core';
8
+ import { caretLeft, caretRight, minus, plus } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
12
12
  import { HelixHeadlessLightboxItem } from '../item/lightbox-item.js';
13
+ import { percentOf } from '../zoom.js';
13
14
  import { ZoomController } from '../zoom-controller.js';
14
15
  let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerActive(MixinControllerOpen(HelixElement)) {
15
16
  constructor() {
@@ -26,9 +27,15 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
26
27
  this.slideLabel = '{index} of {count}';
27
28
  this.slideRoleLabel = 'slide';
28
29
  this.statusLabel = '{index} of {count}';
30
+ this.zoomInLabel = 'Zoom in';
31
+ this.zoomOutLabel = 'Zoom out';
32
+ this.zoomStatusLabel = 'Zoomed to {percent}%';
29
33
  this.resting = false;
30
34
  this.filled = false;
31
35
  this.announcement = '';
36
+ this.zoomScale = 1;
37
+ this.zoomSaid = new DelayController(this, { renders: false });
38
+ this.quietZoom = false;
32
39
  this.swipes = new SwipeController(this, {
33
40
  axis: () => (this.open && !this.zoomer.engaged ? 'x' : undefined),
34
41
  end: (dx, _dy, vx) => {
@@ -126,13 +133,26 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
126
133
  viewport?.style.setProperty('--lightbox-pan-y', zoomed ? `${Math.round(pan.y)}px` : '');
127
134
  this.toggleState('zoomed', zoomed);
128
135
  this.idle.stir();
136
+ if (scale !== this.zoomScale) {
137
+ this.zoomScale = scale;
138
+ this.sayZoom();
139
+ }
140
+ }
141
+ sayZoom() {
142
+ if (this.quietZoom || !this.open || this.zoomStatusLabel === '') {
143
+ this.zoomSaid.cancel();
144
+ return;
145
+ }
146
+ this.zoomSaid.start(400, () => {
147
+ this.announcement = fillTemplate(this.zoomStatusLabel, { percent: percentOf(this.zoomScale) });
148
+ });
129
149
  }
130
150
  zoomTo(scale) {
131
151
  this.zoomer.zoomTo(scale);
132
152
  }
133
153
  chromeHasFocus() {
134
154
  const active = deepActiveElement();
135
- const own = this.shadowRoot?.activeElement?.matches('[part="previous"], [part="next"], [part="dismiss"]');
155
+ const own = this.shadowRoot?.activeElement?.matches('[part="previous"], [part="next"], [part="dismiss"], [part="zoom-in"], [part="zoom-out"]');
136
156
  return (own === true ||
137
157
  this.knownChildren.some((child) => (child.slot === 'header' || child.slot === 'footer') && deepContains(child, active)));
138
158
  }
@@ -178,12 +198,11 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
178
198
  if (this.maxZoom <= 1) {
179
199
  return false;
180
200
  }
181
- const scale = this.zoomer.scale;
182
201
  if (key === '+' || key === '=') {
183
- this.zoomer.zoomTo(scale * 1.5);
202
+ this.zoomIn();
184
203
  }
185
204
  else if (key === '-') {
186
- this.zoomer.zoomTo(scale / 1.5);
205
+ this.zoomOut();
187
206
  }
188
207
  else if (key === '0') {
189
208
  this.zoomer.reset();
@@ -193,6 +212,12 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
193
212
  }
194
213
  return true;
195
214
  }
215
+ zoomIn() {
216
+ this.zoomer.zoomTo(this.zoomer.scale * 1.5);
217
+ }
218
+ zoomOut() {
219
+ this.zoomer.zoomTo(this.zoomer.scale / 1.5);
220
+ }
196
221
  toEnd(at) {
197
222
  const target = this.items[at];
198
223
  if (target && target !== this.shown) {
@@ -209,7 +234,9 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
209
234
  this.toggleState('idle', this.resting);
210
235
  this.toggleState('zoomable', this.maxZoom > 1);
211
236
  if (changed.has('active') || (changed.has('open') && !this.open)) {
237
+ this.quietZoom = true;
212
238
  this.zoomer.reset();
239
+ this.quietZoom = false;
213
240
  }
214
241
  this.say(changed);
215
242
  }
@@ -305,11 +332,11 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
305
332
  @pointerleave=${this.idle.leave}
306
333
  @pointermove=${this.idle.stir}
307
334
  >
308
- <slot name="header"></slot>
335
+ <slot name="header" part="header"></slot>
309
336
  <div part="viewport">
310
337
  <slot></slot>
311
338
  </div>
312
- <slot name="footer"></slot>
339
+ <slot name="footer" part="footer"></slot>
313
340
  ${this.controls ? this.renderControls() : nothing}
314
341
  ${this.counter ? html `<span part="counter" aria-hidden="true">${this.place()}</span>` : nothing}
315
342
  <span part="live" role="status" aria-live="polite">${this.announcement || nothing}</span>
@@ -342,12 +369,41 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
342
369
  </svg>
343
370
  </slot>
344
371
  </button>
372
+ ${this.maxZoom > 1 ? this.renderZoomControls() : nothing}
345
373
  ${drawDismiss({
346
374
  label: this.dismissLabel,
347
375
  mark: html `<slot name="dismiss-icon">${drawDismissGlyph()}</slot>`,
348
376
  onDismiss: () => this.close(),
349
377
  })}`;
350
378
  }
379
+ renderZoomControls() {
380
+ return html `<button
381
+ part="zoom-out"
382
+ type="button"
383
+ aria-label=${this.zoomOutLabel}
384
+ aria-disabled=${this.zoomScale <= 1 ? 'true' : nothing}
385
+ @click=${() => this.zoomOut()}
386
+ >
387
+ <slot name="zoom-out-icon">
388
+ <svg part="zoom-out-mark" fill="currentColor" viewBox=${minus.viewBox} aria-hidden="true">
389
+ ${unsafeSVG(minus.content)}
390
+ </svg>
391
+ </slot>
392
+ </button>
393
+ <button
394
+ part="zoom-in"
395
+ type="button"
396
+ aria-label=${this.zoomInLabel}
397
+ aria-disabled=${this.zoomScale >= this.maxZoom ? 'true' : nothing}
398
+ @click=${() => this.zoomIn()}
399
+ >
400
+ <slot name="zoom-in-icon">
401
+ <svg part="zoom-in-mark" fill="currentColor" viewBox=${plus.viewBox} aria-hidden="true">
402
+ ${unsafeSVG(plus.content)}
403
+ </svg>
404
+ </slot>
405
+ </button>`;
406
+ }
351
407
  };
352
408
  __decorate([
353
409
  property({ reflect: true, type: Boolean })
@@ -385,6 +441,15 @@ __decorate([
385
441
  __decorate([
386
442
  property({ attribute: 'status-label' })
387
443
  ], HelixHeadlessLightbox.prototype, "statusLabel", void 0);
444
+ __decorate([
445
+ property({ attribute: 'zoom-in-label' })
446
+ ], HelixHeadlessLightbox.prototype, "zoomInLabel", void 0);
447
+ __decorate([
448
+ property({ attribute: 'zoom-out-label' })
449
+ ], HelixHeadlessLightbox.prototype, "zoomOutLabel", void 0);
450
+ __decorate([
451
+ property({ attribute: 'zoom-status-label' })
452
+ ], HelixHeadlessLightbox.prototype, "zoomStatusLabel", void 0);
388
453
  __decorate([
389
454
  state()
390
455
  ], HelixHeadlessLightbox.prototype, "resting", void 0);
@@ -394,6 +459,9 @@ __decorate([
394
459
  __decorate([
395
460
  state()
396
461
  ], HelixHeadlessLightbox.prototype, "announcement", void 0);
462
+ __decorate([
463
+ state()
464
+ ], HelixHeadlessLightbox.prototype, "zoomScale", void 0);
397
465
  HelixHeadlessLightbox = __decorate([
398
466
  customElement('helix-headless-lightbox')
399
467
  ], HelixHeadlessLightbox);
@@ -20,3 +20,5 @@ export declare const zoomAt: (at: Offset, from: number, to: number, pan: Offset)
20
20
  export declare const spread: (a: Offset, b: Offset) => number;
21
21
  /** The point halfway between two fingers. */
22
22
  export declare const between: (a: Offset, b: Offset) => Offset;
23
+ /** A scale as a whole percent, which is how a reader hears it: 1.5 is 150. */
24
+ export declare const percentOf: (scale: number) => number;