@fusion.dev/helix-headless 0.145.0 → 0.146.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 (278) hide show
  1. package/custom-elements.json +7373 -3849
  2. package/dist/accordion/item/accordion-item.d.ts +14 -0
  3. package/dist/accordion/item/accordion-item.js +16 -6
  4. package/dist/accordion/root/accordion-root.d.ts +8 -5
  5. package/dist/announcement/root/announcement-root.d.ts +3 -2
  6. package/dist/announcement/root/announcement-root.js +3 -1
  7. package/dist/app-header/root/app-header-root.d.ts +7 -2
  8. package/dist/app-header/root/app-header-root.js +9 -1
  9. package/dist/app-header/sectioning.d.ts +3 -1
  10. package/dist/app-header/sectioning.js +3 -2
  11. package/dist/avatar/group/avatar-group.d.ts +20 -14
  12. package/dist/avatar/group/avatar-group.js +17 -19
  13. package/dist/badge/root/badge-root.d.ts +3 -1
  14. package/dist/badge/root/badge-root.js +2 -2
  15. package/dist/bar-chart/root/bar-chart-root.d.ts +18 -1
  16. package/dist/bar-chart/root/bar-chart-root.js +52 -15
  17. package/dist/bar-chart/stack.d.ts +15 -0
  18. package/dist/bar-chart/stack.js +19 -0
  19. package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +25 -0
  20. package/dist/breadcrumbs/root/breadcrumbs-root.js +50 -3
  21. package/dist/button/root/button-root.d.ts +26 -0
  22. package/dist/button/root/button-root.js +38 -18
  23. package/dist/button-group/root/button-group-root.d.ts +2 -1
  24. package/dist/button-group/root/button-group-root.js +5 -1
  25. package/dist/canvas/node/canvas-node.d.ts +4 -3
  26. package/dist/canvas/node/canvas-node.js +3 -2
  27. package/dist/canvas/root/canvas-root.js +2 -2
  28. package/dist/card/root/card-root.d.ts +24 -3
  29. package/dist/card/root/card-root.js +20 -8
  30. package/dist/carousel/root/carousel-root.d.ts +43 -4
  31. package/dist/carousel/root/carousel-root.js +40 -12
  32. package/dist/carousel/stops.d.ts +20 -0
  33. package/dist/carousel/stops.js +30 -0
  34. package/dist/chat/message/chat-message.js +5 -4
  35. package/dist/chat/naming.d.ts +5 -1
  36. package/dist/chat/naming.js +4 -2
  37. package/dist/chat/root/chat-root.d.ts +25 -3
  38. package/dist/chat/root/chat-root.js +22 -5
  39. package/dist/checkbox/root/checkbox-root.d.ts +20 -0
  40. package/dist/checkbox/root/checkbox-root.js +14 -1
  41. package/dist/checkbox-group/root/checkbox-group-root.d.ts +8 -2
  42. package/dist/checkbox-group/root/checkbox-group-root.js +12 -10
  43. package/dist/checkbox-group/select-all.d.ts +27 -0
  44. package/dist/checkbox-group/select-all.js +12 -0
  45. package/dist/code-block/highlight.d.ts +15 -0
  46. package/dist/code-block/highlight.js +4 -0
  47. package/dist/code-block/root/code-block-root.d.ts +49 -1
  48. package/dist/code-block/root/code-block-root.js +58 -3
  49. package/dist/color-input/root/color-input-root.d.ts +3 -0
  50. package/dist/color-input/root/color-input-root.js +6 -3
  51. package/dist/color-picker/root/color-picker-root.d.ts +33 -4
  52. package/dist/color-picker/root/color-picker-root.js +80 -21
  53. package/dist/color-picker/swatches.d.ts +12 -0
  54. package/dist/color-picker/swatches.js +26 -0
  55. package/dist/combobox/root/combobox-root.d.ts +43 -0
  56. package/dist/combobox/root/combobox-root.js +79 -15
  57. package/dist/command/root/command-root.d.ts +16 -1
  58. package/dist/command/root/command-root.js +22 -4
  59. package/dist/copy-button/root/copy-button-root.d.ts +5 -0
  60. package/dist/copy-button/root/copy-button-root.js +10 -3
  61. package/dist/data-table/index.d.ts +1 -1
  62. package/dist/data-table/index.js +1 -1
  63. package/dist/data-table/keys.d.ts +24 -0
  64. package/dist/data-table/keys.js +24 -0
  65. package/dist/data-table/root/data-table-root.d.ts +27 -18
  66. package/dist/data-table/root/data-table-root.js +20 -52
  67. package/dist/data-table/tree.d.ts +16 -1
  68. package/dist/data-table/tree.js +2 -3
  69. package/dist/date-input/root/date-input-root.d.ts +3 -1
  70. package/dist/date-input/root/date-input-root.js +18 -7
  71. package/dist/date-picker/root/date-picker-root.js +12 -6
  72. package/dist/dialog/description.js +15 -1
  73. package/dist/dialog/root/dialog-root.d.ts +8 -2
  74. package/dist/dialog/root/dialog-root.js +16 -1
  75. package/dist/divider/root/divider-root.d.ts +3 -0
  76. package/dist/divider/root/divider-root.js +2 -1
  77. package/dist/donut-chart/root/donut-chart-root.d.ts +31 -0
  78. package/dist/donut-chart/root/donut-chart-root.js +48 -1
  79. package/dist/drawer/reveal.d.ts +8 -0
  80. package/dist/drawer/reveal.js +1 -0
  81. package/dist/drawer/root/drawer-root.d.ts +59 -0
  82. package/dist/drawer/root/drawer-root.js +63 -4
  83. package/dist/drilldown/item/drilldown-item.js +1 -1
  84. package/dist/drilldown/root/drilldown-root.d.ts +21 -3
  85. package/dist/drilldown/root/drilldown-root.js +21 -3
  86. package/dist/element-events.generated.js +31 -5
  87. package/dist/empty/root/empty-root.d.ts +3 -1
  88. package/dist/empty/root/empty-root.js +3 -2
  89. package/dist/file-input/root/file-input-root.d.ts +69 -6
  90. package/dist/file-input/root/file-input-root.js +82 -19
  91. package/dist/file-input/screen.d.ts +2 -1
  92. package/dist/filter-input/menu/filter-menu.d.ts +2 -0
  93. package/dist/filter-input/menu/filter-menu.js +12 -4
  94. package/dist/filter-input/root/filter-input-root.d.ts +30 -6
  95. package/dist/filter-input/root/filter-input-root.js +1 -1
  96. package/dist/filter-input/suggest.d.ts +28 -3
  97. package/dist/filter-input/suggest.js +18 -8
  98. package/dist/format-date/root/format-date-root.d.ts +9 -16
  99. package/dist/format-date/root/format-date-root.js +10 -27
  100. package/dist/heatmap/root/heatmap-root.d.ts +24 -13
  101. package/dist/heatmap/root/heatmap-root.js +34 -12
  102. package/dist/icon/root/icon-root.d.ts +12 -1
  103. package/dist/icon/root/icon-root.js +21 -10
  104. package/dist/icon/settle.d.ts +7 -0
  105. package/dist/icon/settle.js +10 -0
  106. package/dist/icon-button/root/icon-button-root.d.ts +2 -0
  107. package/dist/image/root/image-root.d.ts +5 -1
  108. package/dist/image/root/image-root.js +1 -1
  109. package/dist/input/root/input-root.d.ts +12 -2
  110. package/dist/input/root/input-root.js +11 -2
  111. package/dist/kbd/root/kbd-root.d.ts +35 -1
  112. package/dist/kbd/root/kbd-root.js +23 -4
  113. package/dist/legend/root/legend-root.d.ts +55 -8
  114. package/dist/legend/root/legend-root.js +55 -13
  115. package/dist/lightbox/root/lightbox-root.d.ts +49 -6
  116. package/dist/lightbox/root/lightbox-root.js +86 -19
  117. package/dist/lightbox/zoom-controller.d.ts +59 -0
  118. package/dist/lightbox/zoom-controller.js +128 -0
  119. package/dist/lightbox/zoom.d.ts +22 -0
  120. package/dist/lightbox/zoom.js +16 -0
  121. package/dist/line-chart/root/line-chart-root.d.ts +22 -1
  122. package/dist/line-chart/root/line-chart-root.js +21 -1
  123. package/dist/list/item/list-item.d.ts +2 -0
  124. package/dist/list/item/list-item.js +11 -2
  125. package/dist/list/root/list-root.d.ts +62 -13
  126. package/dist/list/root/list-root.js +70 -13
  127. package/dist/list/selection.d.ts +37 -7
  128. package/dist/list/selection.js +16 -7
  129. package/dist/listbox/root/listbox-root.d.ts +37 -1
  130. package/dist/listbox/root/listbox-root.js +49 -3
  131. package/dist/loader/busy.d.ts +10 -0
  132. package/dist/loader/busy.js +11 -0
  133. package/dist/loader/root/loader-root.d.ts +9 -2
  134. package/dist/loader/root/loader-root.js +23 -3
  135. package/dist/megamenu/root/megamenu-root.d.ts +51 -10
  136. package/dist/megamenu/root/megamenu-root.js +76 -5
  137. package/dist/menu/item/menu-item.d.ts +51 -2
  138. package/dist/menu/item/menu-item.js +47 -23
  139. package/dist/menu/root/menu-root.d.ts +14 -0
  140. package/dist/menu/root/menu-root.js +2 -2
  141. package/dist/meter/root/meter-root.d.ts +20 -1
  142. package/dist/meter/root/meter-root.js +11 -15
  143. package/dist/navigation/root/navigation-root.d.ts +2 -1
  144. package/dist/navigation/root/navigation-root.js +2 -2
  145. package/dist/notifications/root/notifications-root.d.ts +10 -3
  146. package/dist/notifications/root/notifications-root.js +19 -7
  147. package/dist/number-input/root/number-input-root.d.ts +17 -8
  148. package/dist/number-input/root/number-input-root.js +2 -7
  149. package/dist/object-inspector/root/object-inspector-root.d.ts +75 -27
  150. package/dist/object-inspector/root/object-inspector-root.js +117 -76
  151. package/dist/object-inspector/rows.d.ts +18 -0
  152. package/dist/object-inspector/rows.js +33 -0
  153. package/dist/option/root/option-root.d.ts +5 -3
  154. package/dist/outline/root/outline-root.d.ts +18 -0
  155. package/dist/outline/root/outline-root.js +23 -5
  156. package/dist/pagination/size/pagination-size.d.ts +1 -0
  157. package/dist/pagination/size/pagination-size.js +1 -1
  158. package/dist/pin-input/code.d.ts +10 -0
  159. package/dist/pin-input/code.js +13 -0
  160. package/dist/pin-input/root/pin-input-root.d.ts +25 -1
  161. package/dist/pin-input/root/pin-input-root.js +10 -11
  162. package/dist/popover/hide-outside.d.ts +14 -0
  163. package/dist/popover/hide-outside.js +41 -0
  164. package/dist/popover/root/popover-root.d.ts +57 -7
  165. package/dist/popover/root/popover-root.js +58 -3
  166. package/dist/progress/root/progress-root.d.ts +20 -0
  167. package/dist/progress/root/progress-root.js +15 -15
  168. package/dist/qr-code/export.d.ts +26 -1
  169. package/dist/qr-code/export.js +14 -0
  170. package/dist/qr-code/root/qr-code-root.d.ts +19 -5
  171. package/dist/qr-code/root/qr-code-root.js +58 -9
  172. package/dist/quote/marks.d.ts +8 -0
  173. package/dist/quote/marks.js +5 -0
  174. package/dist/quote/root/quote-root.d.ts +14 -2
  175. package/dist/quote/root/quote-root.js +23 -1
  176. package/dist/radio/root/radio-root.d.ts +6 -2
  177. package/dist/radio/root/radio-root.js +6 -1
  178. package/dist/rating/root/rating-root.d.ts +9 -4
  179. package/dist/rating/root/rating-root.js +31 -7
  180. package/dist/rating/steps.d.ts +7 -0
  181. package/dist/rating/steps.js +9 -0
  182. package/dist/scatter-chart/fit.d.ts +15 -0
  183. package/dist/scatter-chart/fit.js +35 -0
  184. package/dist/scatter-chart/root/scatter-chart-root.d.ts +21 -1
  185. package/dist/scatter-chart/root/scatter-chart-root.js +30 -17
  186. package/dist/scroll-area/root/scroll-area-root.d.ts +22 -51
  187. package/dist/scroll-area/root/scroll-area-root.js +57 -173
  188. package/dist/scroll-area/scrollbar-controller.d.ts +111 -0
  189. package/dist/scroll-area/scrollbar-controller.js +172 -0
  190. package/dist/scroll-area/scrollbar.d.ts +27 -0
  191. package/dist/scroll-area/scrollbar.js +9 -0
  192. package/dist/select/root/select-root.d.ts +15 -0
  193. package/dist/select/root/select-root.js +18 -6
  194. package/dist/skeleton/root/skeleton-root.d.ts +3 -0
  195. package/dist/skeleton/root/skeleton-root.js +4 -2
  196. package/dist/skip-link/root/skip-link-root.js +2 -2
  197. package/dist/slider/root/slider-root.d.ts +41 -4
  198. package/dist/slider/root/slider-root.js +23 -3
  199. package/dist/sparkline/readings.d.ts +18 -0
  200. package/dist/sparkline/readings.js +19 -0
  201. package/dist/sparkline/root/sparkline-root.d.ts +78 -7
  202. package/dist/sparkline/root/sparkline-root.js +118 -23
  203. package/dist/speed-dial/root/speed-dial-root.js +2 -2
  204. package/dist/splitter/root/splitter-root.d.ts +7 -12
  205. package/dist/splitter/root/splitter-root.js +209 -204
  206. package/dist/stat/root/stat-root.d.ts +14 -5
  207. package/dist/stat/root/stat-root.js +21 -5
  208. package/dist/steps/reach.d.ts +5 -0
  209. package/dist/steps/reach.js +3 -0
  210. package/dist/steps/root/steps-root.d.ts +49 -3
  211. package/dist/steps/root/steps-root.js +44 -5
  212. package/dist/steps/step/step.d.ts +7 -0
  213. package/dist/steps/step/step.js +7 -2
  214. package/dist/switch/root/switch-root.d.ts +10 -0
  215. package/dist/table/column/table-column.d.ts +0 -10
  216. package/dist/table/column/table-column.js +3 -19
  217. package/dist/table/root/table-root.d.ts +31 -10
  218. package/dist/table/root/table-root.js +2 -13
  219. package/dist/tabs/panel/tab-panel.d.ts +16 -3
  220. package/dist/tabs/panel/tab-panel.js +34 -5
  221. package/dist/tabs/root/tabs-root.d.ts +12 -0
  222. package/dist/tabs/root/tabs-root.js +1 -1
  223. package/dist/tabs/tab/tab.d.ts +5 -0
  224. package/dist/tabs/tab/tab.js +5 -2
  225. package/dist/tag/group/tag-group.d.ts +31 -4
  226. package/dist/tag/group/tag-group.js +46 -7
  227. package/dist/tag/root/tag-root.js +9 -2
  228. package/dist/textarea/root/textarea-root.d.ts +28 -1
  229. package/dist/textarea/root/textarea-root.js +40 -1
  230. package/dist/time-input/root/time-input-root.d.ts +5 -1
  231. package/dist/time-input/root/time-input-root.js +16 -8
  232. package/dist/time-picker/root/time-picker-root.d.ts +18 -4
  233. package/dist/time-picker/root/time-picker-root.js +7 -10
  234. package/dist/time-picker/wheel-name.d.ts +9 -0
  235. package/dist/time-picker/wheel-name.js +15 -0
  236. package/dist/timeline/group/timeline-group.js +2 -2
  237. package/dist/toast/item/toast-item.d.ts +14 -1
  238. package/dist/toast/item/toast-item.js +22 -5
  239. package/dist/toast/root/toast-root.d.ts +6 -0
  240. package/dist/toast/root/toast-root.js +16 -1
  241. package/dist/toggle/group/toggle-group.d.ts +24 -3
  242. package/dist/toggle/group/toggle-group.js +34 -5
  243. package/dist/tooltip/root/tooltip-root.d.ts +13 -2
  244. package/dist/tooltip/root/tooltip-root.js +9 -1
  245. package/dist/tour/back.d.ts +10 -0
  246. package/dist/tour/back.js +8 -0
  247. package/dist/tour/root/tour-root.d.ts +30 -2
  248. package/dist/tour/root/tour-root.js +34 -8
  249. package/dist/tree/item/tree-item.d.ts +7 -3
  250. package/dist/tree/item/tree-item.js +3 -0
  251. package/dist/tree/root/tree-root.d.ts +24 -3
  252. package/dist/tree/root/tree-root.js +32 -13
  253. package/dist/tree/selection.js +2 -15
  254. package/dist/trend/root/trend-root.d.ts +43 -18
  255. package/dist/trend/root/trend-root.js +31 -6
  256. package/dist/video/media-events.d.ts +13 -18
  257. package/dist/video/media-events.js +1 -5
  258. package/dist/video/root/video-root.d.ts +18 -5
  259. package/dist/video/root/video-root.js +19 -10
  260. package/dist/video-playlist/menu/video-playlist-menu.d.ts +2 -0
  261. package/dist/video-playlist/menu/video-playlist-menu.js +2 -1
  262. package/dist/video-playlist/root/video-playlist-root.d.ts +7 -1
  263. package/dist/virtual-list/fill.d.ts +0 -8
  264. package/dist/virtual-list/fill.js +1 -17
  265. package/dist/virtual-list/plan.d.ts +49 -0
  266. package/dist/virtual-list/plan.js +16 -0
  267. package/dist/virtual-list/root/virtual-list-root.d.ts +114 -13
  268. package/dist/virtual-list/root/virtual-list-root.js +183 -40
  269. package/lint.json +1 -1
  270. package/package.json +8 -8
  271. package/types/jsx.d.ts +4577 -835
  272. package/types/vue.d.ts +926 -168
  273. package/dist/chat/relative-clock.d.ts +0 -39
  274. package/dist/chat/relative-clock.js +0 -31
  275. package/dist/data-table/root/order.d.ts +0 -141
  276. package/dist/data-table/root/order.js +0 -76
  277. package/dist/tag/selection.d.ts +0 -10
  278. package/dist/tag/selection.js +0 -12
@@ -4,9 +4,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { commaList, customElement, HelixBeforeChangeEvent, HelixElement, requestChange, SourceController, seriesIndex, } from '@fusion.dev/helix-core';
7
+ import { commaList, customElement, HelixBeforeChangeEvent, HelixElement, KeyboardNavigationController, requestChange, SourceController, seriesIndex, seriesKey, typeaheadBy, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
- import { property } from 'lit/decorators.js';
9
+ import { property, state } from 'lit/decorators.js';
10
10
  import { isHidden, toggledNames } from '../hidden-series.js';
11
11
  let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
12
12
  constructor() {
@@ -16,25 +16,58 @@ let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
16
16
  this.label = '';
17
17
  this.toggles = false;
18
18
  this.source = new SourceController(this);
19
- this.wrote = false;
19
+ this.stop = 0;
20
+ this.keys = new KeyboardNavigationController(this, {
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
+ }))
30
+ : [],
31
+ origin: 'focus',
32
+ selects: () => false,
33
+ typeahead: typeaheadBy((item) => item.words ?? ''),
34
+ });
35
+ this.wroteTo = null;
36
+ this.writes = false;
20
37
  }
21
38
  willUpdate(changed) {
22
39
  if (changed.has('for')) {
23
40
  this.source.watch();
24
41
  }
42
+ const table = this.source.element;
43
+ const asked = changed.has('hiddenSeries') &&
44
+ !(changed.get('hiddenSeries') === undefined && this.hiddenSeries === '');
45
+ this.writes = asked || (table !== null && table !== this.wroteTo && this.hiddenSeries !== '');
46
+ if (!this.writes && table !== null) {
47
+ this.follow();
48
+ }
49
+ }
50
+ follow() {
51
+ const hidden = this.source.hidden;
52
+ const now = commaList(this.hiddenSeries);
53
+ const next = this.names().filter((name) => hidden.has(seriesKey(name)));
54
+ const same = next.length === now.length &&
55
+ next.every((name) => now.some((one) => seriesKey(one) === seriesKey(name)));
56
+ if (!same) {
57
+ this.hiddenSeries = next.join(', ');
58
+ }
25
59
  }
26
60
  updated(changed) {
27
61
  super.updated(changed);
28
- const names = commaList(this.hiddenSeries);
29
- if (names.length > 0 || this.wrote) {
30
- this.source.hide(names);
31
- this.wrote = names.length > 0;
62
+ if (this.writes) {
63
+ this.writes = false;
64
+ this.wroteTo = this.source.hide(commaList(this.hiddenSeries), this.names()) ?? this.wroteTo;
32
65
  }
33
66
  }
34
67
  disconnectedCallback() {
35
- if (this.wrote) {
36
- this.source.hide([]);
37
- this.wrote = false;
68
+ if (this.wroteTo !== null) {
69
+ this.source.hide([], this.names());
70
+ this.wroteTo = null;
38
71
  }
39
72
  super.disconnectedCallback();
40
73
  }
@@ -58,8 +91,10 @@ let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
58
91
  }
59
92
  render() {
60
93
  const named = this.label || this.source.element?.querySelector('caption')?.textContent?.trim();
94
+ const names = this.names();
95
+ const stop = Math.min(this.stop, names.length - 1);
61
96
  return html `<ul aria-label=${named || nothing} part="list" role="list">
62
- ${this.names().map((name, index) => {
97
+ ${names.map((name, index) => {
63
98
  const off = isHidden(this.hiddenSeries, name);
64
99
  return html `<li
65
100
  data-series=${seriesIndex(index)}
@@ -67,17 +102,21 @@ let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
67
102
  style="--chart-series: var(--chart-series-${seriesIndex(index)})"
68
103
  role="listitem"
69
104
  >
70
- ${this.toggles ? this.renderToggle(name, off) : html `<span part="name">${name}</span>`}
105
+ ${this.toggles ? this.renderToggle(name, off, index, index === stop) : html `<span part="name">${name}</span>`}
71
106
  </li>`;
72
107
  })}
73
108
  </ul>`;
74
109
  }
75
- renderToggle(name, off) {
110
+ renderToggle(name, off, index = 0, holds = true) {
76
111
  return html `<button
77
112
  aria-pressed=${off ? 'false' : 'true'}
78
113
  part="toggle"
114
+ tabindex=${holds ? '0' : '-1'}
79
115
  type="button"
80
116
  @click=${() => this.toggle(name)}
117
+ @focus=${() => {
118
+ this.stop = index;
119
+ }}
81
120
  >
82
121
  <span part="name">${name}</span>
83
122
  </button>`;
@@ -98,6 +137,9 @@ __decorate([
98
137
  __decorate([
99
138
  property({ reflect: true, type: Boolean })
100
139
  ], HelixHeadlessLegend.prototype, "toggles", void 0);
140
+ __decorate([
141
+ state()
142
+ ], HelixHeadlessLegend.prototype, "stop", void 0);
101
143
  HelixHeadlessLegend = __decorate([
102
144
  customElement('helix-headless-legend')
103
145
  ], HelixHeadlessLegend);
@@ -19,9 +19,18 @@ declare const HelixHeadlessLightbox_base: (new (...args: any[]) => import("@fusi
19
19
  * focus stays in the dialog.
20
20
  *
21
21
  * Set `controls` for the viewer's own previous, next and close controls, named
22
- * by `previous-label`, `next-label` and `dismiss-label`. Without it, compose the
23
- * chrome yourself in the `header` and `footer` slots, with buttons that send
24
- * `--previous`, `--next` and `--close`.
22
+ * by `previous-label`, `next-label` and `dismiss-label`, whose glyphs you can
23
+ * replace in the `previous-icon`, `next-icon` and `dismiss-icon` slots. Without
24
+ * it, compose the chrome yourself in the `header` and `footer` slots, with
25
+ * buttons that send `--previous`, `--next` and `--close`.
26
+ *
27
+ * **Set `max-zoom` to let a reader zoom into the shown item**, such as `3`.
28
+ * Two fingers pinch it, a double click or a double tap zooms all the way in
29
+ * about the point pressed and back out, and once zoomed one pointer drags it
30
+ * about. `+` and `-` zoom in and out from the keyboard, and `0` puts it back.
31
+ * The scale and the offset are on the `viewport` part as `--lightbox-zoom`,
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.
25
34
  *
26
35
  * Each item is a slide: a `group` with the role description in
27
36
  * `slide-role-label`, named by its `label` or by `slide-label`. A touch or a pen
@@ -41,8 +50,11 @@ declare const HelixHeadlessLightbox_base: (new (...args: any[]) => import("@fusi
41
50
  * @documentation https://fusion.dev/helix/components/lightbox
42
51
  *
43
52
  * @slot - The items, which are `<helix-headless-lightbox-item>` elements.
53
+ * @slot dismiss-icon - Replaces the glyph inside the close control.
44
54
  * @slot footer - Content below the item, such as a counter or a thumbnail strip.
45
55
  * @slot header - Content above the item, such as a title and a close button.
56
+ * @slot next-icon - Replaces the glyph inside the next control.
57
+ * @slot previous-icon - Replaces the glyph inside the previous control.
46
58
  *
47
59
  * @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.
48
60
  * @event {HelixActivateEvent} helix-activate - Fired after the shown item changes, when the element already holds the new item.
@@ -51,19 +63,26 @@ declare const HelixHeadlessLightbox_base: (new (...args: any[]) => import("@fusi
51
63
  * @event {HelixBeforeOpenEvent} helix-before-open - Fired before the viewer opens. Cancelable.
52
64
  * @event {ToggleEvent} toggle - Fired when the viewer opens or closes, with `newState` and `oldState`.
53
65
  *
66
+ * @csspart counter - The place of the shown item in the set, drawn with `counter`.
54
67
  * @csspart dialog - The native dialog element.
55
68
  * @csspart dismiss - The close control, drawn with `controls`.
56
- * @csspart dismiss-mark - The glyph on the close control.
69
+ * @csspart dismiss-mark - The glyph on the close control, where none is slotted.
57
70
  * @csspart live - The live region that announces which item is shown.
58
71
  * @csspart next - The control that shows the next item, drawn with `controls`.
59
- * @csspart next-mark - The glyph on the next control.
72
+ * @csspart next-mark - The glyph on the next control, where none is slotted.
60
73
  * @csspart previous - The control that shows the previous item, drawn with `controls`.
61
- * @csspart previous-mark - The glyph on the previous control.
74
+ * @csspart previous-mark - The glyph on the previous control, where none is slotted.
62
75
  * @csspart viewport - The box the current item is shown in.
63
76
  *
77
+ * @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
+ * @cssproperty --lightbox-pan-y - How far the zoomed item is moved down from the middle of the viewport, in pixels. Written only while zoomed.
79
+ * @cssproperty --lightbox-zoom - The scale the shown item is zoomed to, such as `2`. Written only while zoomed.
80
+ *
64
81
  * @cssstate fullscreen - While the viewer fills the screen.
65
82
  * @cssstate idle - The user has been still long enough for the controls to hide.
66
83
  * @cssstate open - While the viewer is open.
84
+ * @cssstate zoomable - While `max-zoom` lets a reader zoom.
85
+ * @cssstate zoomed - While the shown item is zoomed past its own size.
67
86
  */
68
87
  export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
69
88
  /**
@@ -75,6 +94,13 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
75
94
  * either end of a set that does not `loop`.
76
95
  */
77
96
  controls: boolean;
97
+ /**
98
+ * Draws the place of the shown item in the set over the picture, in the
99
+ * words of `status-label`, such as "2 of 5". It hides with the controls while
100
+ * the reader is idle. Screen readers hear the place from the live region, so
101
+ * the drawn copy is hidden from them.
102
+ */
103
+ counter: boolean;
78
104
  /**
79
105
  * The close control's accessible name. Translate it with the rest of your
80
106
  * interface.
@@ -97,6 +123,11 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
97
123
  * Whether moving past the last item returns to the first.
98
124
  */
99
125
  loop: boolean;
126
+ /**
127
+ * How far a reader can zoom into the shown item, as a scale, such as `3`.
128
+ * `1`, the default, turns zooming off.
129
+ */
130
+ maxZoom: number;
100
131
  /**
101
132
  * The next control's accessible name.
102
133
  */
@@ -132,6 +163,14 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
132
163
  /** What the live region is currently saying. */
133
164
  private announcement;
134
165
  protected readonly swipes: SwipeController;
166
+ private readonly zoomer;
167
+ /** Writes the zoom on the viewport, and nothing at the item's own size. */
168
+ private drawZoom;
169
+ /**
170
+ * Zooms the shown item to `scale`, held between `1` and `max-zoom`, about
171
+ * the middle of the viewport.
172
+ */
173
+ zoomTo(scale: number): void;
135
174
  private readonly fullscreen;
136
175
  private readonly idle;
137
176
  /**
@@ -202,6 +241,8 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
202
241
  * a reader having no way to know which one this component thinks it is.
203
242
  */
204
243
  private readonly handleKeydown;
244
+ /** `+` and `-` zoom in and out by half again, and `0` puts the item back. Returns whether it was one. */
245
+ private zoomKey;
205
246
  /** Home and End, which are an index rather than a step. */
206
247
  private toEnd;
207
248
  /** The item on screen: the one `active` names, or the first where it names none. */
@@ -236,6 +277,8 @@ export declare class HelixHeadlessLightbox extends HelixHeadlessLightbox_base {
236
277
  * every page load.
237
278
  */
238
279
  private say;
280
+ /** The shown item's place in the set, in the words of `status-label`. */
281
+ private place;
239
282
  protected render(): TemplateResult;
240
283
  /**
241
284
  * The viewer's own controls, behind a seam a tier above can move.
@@ -4,20 +4,23 @@ 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, 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, x } from '@fusion.dev/helix-icons';
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';
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 { ZoomController } from '../zoom-controller.js';
13
14
  let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerActive(MixinControllerOpen(HelixElement)) {
14
15
  constructor() {
15
16
  super(...arguments);
16
17
  this.controls = false;
18
+ this.counter = false;
17
19
  this.dismissLabel = 'Close';
18
20
  this.idleDelay = 0;
19
21
  this.label = '';
20
22
  this.loop = false;
23
+ this.maxZoom = 1;
21
24
  this.nextLabel = 'Next';
22
25
  this.previousLabel = 'Previous';
23
26
  this.slideLabel = '{index} of {count}';
@@ -27,8 +30,11 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
27
30
  this.filled = false;
28
31
  this.announcement = '';
29
32
  this.swipes = new SwipeController(this, {
30
- axis: () => (this.open ? 'x' : undefined),
33
+ axis: () => (this.open && !this.zoomer.engaged ? 'x' : undefined),
31
34
  end: (dx, _dy, vx) => {
35
+ if (this.zoomer.pinched) {
36
+ return;
37
+ }
32
38
  const width = this.partNamed('viewport')?.getBoundingClientRect().width ?? 0;
33
39
  const step = swipeStep(dx, vx, width, isRtl(this));
34
40
  if (step !== 0) {
@@ -39,6 +45,11 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
39
45
  start: (_target, event) => event.pointerType !== 'mouse',
40
46
  surface: () => this.dialog,
41
47
  });
48
+ this.zoomer = new ZoomController(this, {
49
+ max: () => (this.open ? this.maxZoom : 1),
50
+ onChange: (scale, pan) => this.drawZoom(scale, pan),
51
+ surface: () => this.partNamed('viewport'),
52
+ });
42
53
  this.fullscreen = new FullscreenController(this, (full) => {
43
54
  this.filled = full;
44
55
  });
@@ -70,6 +81,10 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
70
81
  if (keyClaimed(event) || event.altKey || event.ctrlKey || event.metaKey) {
71
82
  return;
72
83
  }
84
+ if (this.zoomKey(event.key)) {
85
+ event.preventDefault();
86
+ return;
87
+ }
73
88
  const along = inlineStep(this, event.key);
74
89
  if (along === 1 || event.key === 'ArrowDown') {
75
90
  event.preventDefault();
@@ -103,6 +118,18 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
103
118
  this.requestOpen(false);
104
119
  };
105
120
  }
121
+ drawZoom(scale, pan) {
122
+ const viewport = this.partNamed('viewport');
123
+ const zoomed = scale > 1;
124
+ viewport?.style.setProperty('--lightbox-zoom', zoomed ? String(scale) : '');
125
+ viewport?.style.setProperty('--lightbox-pan-x', zoomed ? `${Math.round(pan.x)}px` : '');
126
+ viewport?.style.setProperty('--lightbox-pan-y', zoomed ? `${Math.round(pan.y)}px` : '');
127
+ this.toggleState('zoomed', zoomed);
128
+ this.idle.stir();
129
+ }
130
+ zoomTo(scale) {
131
+ this.zoomer.zoomTo(scale);
132
+ }
106
133
  chromeHasFocus() {
107
134
  const active = deepActiveElement();
108
135
  const own = this.shadowRoot?.activeElement?.matches('[part="previous"], [part="next"], [part="dismiss"]');
@@ -147,6 +174,25 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
147
174
  this.requestActive(target.value);
148
175
  }
149
176
  }
177
+ zoomKey(key) {
178
+ if (this.maxZoom <= 1) {
179
+ return false;
180
+ }
181
+ const scale = this.zoomer.scale;
182
+ if (key === '+' || key === '=') {
183
+ this.zoomer.zoomTo(scale * 1.5);
184
+ }
185
+ else if (key === '-') {
186
+ this.zoomer.zoomTo(scale / 1.5);
187
+ }
188
+ else if (key === '0') {
189
+ this.zoomer.reset();
190
+ }
191
+ else {
192
+ return false;
193
+ }
194
+ return true;
195
+ }
150
196
  toEnd(at) {
151
197
  const target = this.items[at];
152
198
  if (target && target !== this.shown) {
@@ -161,6 +207,10 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
161
207
  hostAria(this.internals, { label: this.label });
162
208
  this.toggleState('fullscreen', this.filled);
163
209
  this.toggleState('idle', this.resting);
210
+ this.toggleState('zoomable', this.maxZoom > 1);
211
+ if (changed.has('active') || (changed.has('open') && !this.open)) {
212
+ this.zoomer.reset();
213
+ }
164
214
  this.say(changed);
165
215
  }
166
216
  disconnectedCallback() {
@@ -230,6 +280,17 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
230
280
  label: current.label,
231
281
  });
232
282
  }
283
+ place() {
284
+ const items = this.items;
285
+ const current = resolveActiveItem(this.active, items);
286
+ return current
287
+ ? fillTemplate(this.statusLabel, {
288
+ count: items.length,
289
+ index: items.indexOf(current) + 1,
290
+ label: current.label,
291
+ })
292
+ : '';
293
+ }
233
294
  render() {
234
295
  return html `<dialog
235
296
  part="dialog"
@@ -250,6 +311,7 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
250
311
  </div>
251
312
  <slot name="footer"></slot>
252
313
  ${this.controls ? this.renderControls() : nothing}
314
+ ${this.counter ? html `<span part="counter" aria-hidden="true">${this.place()}</span>` : nothing}
253
315
  <span part="live" role="status" aria-live="polite">${this.announcement || nothing}</span>
254
316
  </dialog>`;
255
317
  }
@@ -261,9 +323,11 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
261
323
  aria-disabled=${this.atEnd(-1) ? 'true' : nothing}
262
324
  @click=${() => this.previous()}
263
325
  >
264
- <svg part="previous-mark" data-mirror fill="currentColor" viewBox=${caretLeft.viewBox} aria-hidden="true">
265
- ${unsafeSVG(caretLeft.content)}
266
- </svg>
326
+ <slot name="previous-icon">
327
+ <svg part="previous-mark" data-mirror fill="currentColor" viewBox=${caretLeft.viewBox} aria-hidden="true">
328
+ ${unsafeSVG(caretLeft.content)}
329
+ </svg>
330
+ </slot>
267
331
  </button>
268
332
  <button
269
333
  part="next"
@@ -272,25 +336,25 @@ let HelixHeadlessLightbox = class HelixHeadlessLightbox extends MixinControllerA
272
336
  aria-disabled=${this.atEnd(1) ? 'true' : nothing}
273
337
  @click=${() => this.next()}
274
338
  >
275
- <svg part="next-mark" data-mirror fill="currentColor" viewBox=${caretRight.viewBox} aria-hidden="true">
276
- ${unsafeSVG(caretRight.content)}
277
- </svg>
339
+ <slot name="next-icon">
340
+ <svg part="next-mark" data-mirror fill="currentColor" viewBox=${caretRight.viewBox} aria-hidden="true">
341
+ ${unsafeSVG(caretRight.content)}
342
+ </svg>
343
+ </slot>
278
344
  </button>
279
- <button
280
- part="dismiss"
281
- type="button"
282
- aria-label=${this.dismissLabel}
283
- @click=${() => this.close()}
284
- >
285
- <svg part="dismiss-mark" fill="currentColor" viewBox=${x.viewBox} aria-hidden="true">
286
- ${unsafeSVG(x.content)}
287
- </svg>
288
- </button>`;
345
+ ${drawDismiss({
346
+ label: this.dismissLabel,
347
+ mark: html `<slot name="dismiss-icon">${drawDismissGlyph()}</slot>`,
348
+ onDismiss: () => this.close(),
349
+ })}`;
289
350
  }
290
351
  };
291
352
  __decorate([
292
353
  property({ reflect: true, type: Boolean })
293
354
  ], HelixHeadlessLightbox.prototype, "controls", void 0);
355
+ __decorate([
356
+ property({ reflect: true, type: Boolean })
357
+ ], HelixHeadlessLightbox.prototype, "counter", void 0);
294
358
  __decorate([
295
359
  property({ attribute: 'dismiss-label' })
296
360
  ], HelixHeadlessLightbox.prototype, "dismissLabel", void 0);
@@ -303,6 +367,9 @@ __decorate([
303
367
  __decorate([
304
368
  property({ reflect: true, type: Boolean })
305
369
  ], HelixHeadlessLightbox.prototype, "loop", void 0);
370
+ __decorate([
371
+ property({ attribute: 'max-zoom', type: Number })
372
+ ], HelixHeadlessLightbox.prototype, "maxZoom", void 0);
306
373
  __decorate([
307
374
  property({ attribute: 'next-label' })
308
375
  ], HelixHeadlessLightbox.prototype, "nextLabel", void 0);
@@ -0,0 +1,59 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ import { type Offset } from './zoom.js';
3
+ export interface ZoomOptions {
4
+ /** The most the item may grow, as a scale. 1 or less turns zooming off. */
5
+ max: () => number;
6
+ /** Told every time the scale or the position changes. */
7
+ onChange: (scale: number, pan: Offset) => void;
8
+ /** The box the item is shown in, which presses must land in and the item scales from the middle of. */
9
+ surface: () => HTMLElement | null | undefined;
10
+ }
11
+ /**
12
+ * Pinch, double press and drag to zoom into the item a viewer shows, and to
13
+ * move about it once zoomed.
14
+ *
15
+ * Two fingers scale the item about the point between them. A double press, a
16
+ * double click or a double tap, zooms all the way in about the point pressed,
17
+ * and back out again. One pointer dragged across a zoomed item moves it, never
18
+ * so far that an edge comes inside the box. What it reports is a scale and an
19
+ * offset from the middle of the box; drawing them is the caller's.
20
+ */
21
+ export declare class ZoomController implements ReactiveController {
22
+ private readonly host;
23
+ private readonly options;
24
+ /** The pointers down on the box, where each one is now. */
25
+ private readonly pointers;
26
+ /** What the gesture under way started from. */
27
+ private from;
28
+ /** The scale, 1 at rest. */
29
+ scale: number;
30
+ /** How far the item is moved from the middle of the box, in pixels. */
31
+ pan: Offset;
32
+ /**
33
+ * Whether two fingers have been down since the last gesture began, so a
34
+ * swipe that started with the first of them can be told it was a pinch.
35
+ */
36
+ pinched: boolean;
37
+ constructor(host: ReactiveControllerHost & HTMLElement, options: ZoomOptions);
38
+ /** Whether the item is zoomed, or two fingers are on it: either way, a drag is not a swipe. */
39
+ get engaged(): boolean;
40
+ hostConnected(): void;
41
+ hostDisconnected(): void;
42
+ /** Zooms to `scale` about the middle of the box, held to the range. */
43
+ zoomTo(scale: number, at?: Offset): void;
44
+ /** Back to the item at its own size, in the middle. */
45
+ reset(): void;
46
+ private set;
47
+ /** A point on the page, measured from the middle of the box. */
48
+ private offset;
49
+ /** Whether a press landed in the box, through slotted content as well. */
50
+ private inside;
51
+ /** Starts over from where every pointer down is now. */
52
+ private begin;
53
+ private readonly handleDown;
54
+ private readonly handleMove;
55
+ private readonly handleUp;
56
+ /** A double press zooms all the way in about the point, or back out. */
57
+ private readonly handleDouble;
58
+ private release;
59
+ }
@@ -0,0 +1,128 @@
1
+ import { between, clampPan, clampScale, spread, zoomAt } from './zoom.js';
2
+ export class ZoomController {
3
+ constructor(host, options) {
4
+ this.pointers = new Map();
5
+ this.scale = 1;
6
+ this.pan = { x: 0, y: 0 };
7
+ this.pinched = false;
8
+ this.handleDown = (event) => {
9
+ if (this.options.max() <= 1 || event.button !== 0 || !this.inside(event)) {
10
+ return;
11
+ }
12
+ if (this.pointers.size === 0) {
13
+ this.pinched = false;
14
+ window.addEventListener('pointermove', this.handleMove);
15
+ window.addEventListener('pointerup', this.handleUp);
16
+ window.addEventListener('pointercancel', this.handleUp);
17
+ }
18
+ this.pointers.set(event.pointerId, this.offset(event));
19
+ if (this.pointers.size > 1) {
20
+ this.pinched = true;
21
+ }
22
+ this.begin();
23
+ };
24
+ this.handleMove = (event) => {
25
+ const from = this.from;
26
+ if (!from || !this.pointers.has(event.pointerId)) {
27
+ return;
28
+ }
29
+ this.pointers.set(event.pointerId, this.offset(event));
30
+ const [a, b] = [...this.pointers.values()];
31
+ if (a && b && from.spread > 0) {
32
+ const at = between(a, b);
33
+ const scale = clampScale((from.scale * spread(a, b)) / from.spread, this.options.max());
34
+ const about = zoomAt(from.at, from.scale, scale, from.pan);
35
+ this.set(scale, { x: about.x + at.x - from.at.x, y: about.y + at.y - from.at.y });
36
+ }
37
+ else if (a && this.scale > 1) {
38
+ this.set(this.scale, { x: from.pan.x + a.x - from.at.x, y: from.pan.y + a.y - from.at.y });
39
+ }
40
+ };
41
+ this.handleUp = (event) => {
42
+ if (!this.pointers.delete(event.pointerId)) {
43
+ return;
44
+ }
45
+ if (this.pointers.size === 0) {
46
+ this.release();
47
+ return;
48
+ }
49
+ this.begin();
50
+ };
51
+ this.handleDouble = (event) => {
52
+ if (this.options.max() <= 1 || !this.inside(event)) {
53
+ return;
54
+ }
55
+ if (this.scale > 1) {
56
+ this.reset();
57
+ }
58
+ else {
59
+ this.zoomTo(this.options.max(), this.offset(event));
60
+ }
61
+ };
62
+ this.host = host;
63
+ this.options = options;
64
+ host.addController(this);
65
+ }
66
+ get engaged() {
67
+ return this.scale > 1 || this.pointers.size > 1;
68
+ }
69
+ hostConnected() {
70
+ this.host.addEventListener('pointerdown', this.handleDown);
71
+ this.host.addEventListener('dblclick', this.handleDouble);
72
+ }
73
+ hostDisconnected() {
74
+ this.host.removeEventListener('pointerdown', this.handleDown);
75
+ this.host.removeEventListener('dblclick', this.handleDouble);
76
+ this.release();
77
+ }
78
+ zoomTo(scale, at = { x: 0, y: 0 }) {
79
+ const next = clampScale(scale, this.options.max());
80
+ this.set(next, zoomAt(at, this.scale, next, this.pan));
81
+ }
82
+ reset() {
83
+ if (this.scale !== 1 || this.pan.x !== 0 || this.pan.y !== 0) {
84
+ this.set(1, { x: 0, y: 0 });
85
+ }
86
+ }
87
+ set(scale, pan) {
88
+ const box = this.options.surface()?.getBoundingClientRect();
89
+ const held = clampPan(pan, scale, box?.width ?? 0, box?.height ?? 0);
90
+ if (scale === this.scale && held.x === this.pan.x && held.y === this.pan.y) {
91
+ return;
92
+ }
93
+ this.scale = scale;
94
+ this.pan = held;
95
+ this.options.onChange(scale, held);
96
+ }
97
+ offset(event) {
98
+ const box = this.options.surface()?.getBoundingClientRect();
99
+ return {
100
+ x: event.clientX - (box ? box.left + box.width / 2 : 0),
101
+ y: event.clientY - (box ? box.top + box.height / 2 : 0),
102
+ };
103
+ }
104
+ inside(event) {
105
+ const surface = this.options.surface();
106
+ return surface !== null && surface !== undefined && event.composedPath().includes(surface);
107
+ }
108
+ begin() {
109
+ const [a, b] = [...this.pointers.values()];
110
+ if (!a) {
111
+ this.from = undefined;
112
+ return;
113
+ }
114
+ this.from = {
115
+ at: b ? between(a, b) : a,
116
+ pan: { ...this.pan },
117
+ scale: this.scale,
118
+ spread: b ? spread(a, b) : 0,
119
+ };
120
+ }
121
+ release() {
122
+ window.removeEventListener('pointermove', this.handleMove);
123
+ window.removeEventListener('pointerup', this.handleUp);
124
+ window.removeEventListener('pointercancel', this.handleUp);
125
+ this.pointers.clear();
126
+ this.from = undefined;
127
+ }
128
+ }
@@ -0,0 +1,22 @@
1
+ /** A distance from the middle of the viewer, or a point measured from it, in pixels. */
2
+ export interface Offset {
3
+ x: number;
4
+ y: number;
5
+ }
6
+ /** A scale held between 1, the item at its own size, and `max`. */
7
+ export declare const clampScale: (scale: number, max: number) => number;
8
+ /**
9
+ * How far a zoomed item may move, kept so its edges never come inside the
10
+ * viewer's: at a scale of 2 in a box 400 wide, 200 either way.
11
+ */
12
+ export declare const clampPan: (pan: Offset, scale: number, width: number, height: number) => Offset;
13
+ /**
14
+ * Where the item moves to as its scale goes from `from` to `to`, so the point
15
+ * under `at` stays under it. Both are measured from the middle of the viewer,
16
+ * which is where the item scales from.
17
+ */
18
+ export declare const zoomAt: (at: Offset, from: number, to: number, pan: Offset) => Offset;
19
+ /** The distance between two fingers. */
20
+ export declare const spread: (a: Offset, b: Offset) => number;
21
+ /** The point halfway between two fingers. */
22
+ export declare const between: (a: Offset, b: Offset) => Offset;