@expressivecss/expressive 0.11.0 → 0.13.1

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 (265) hide show
  1. package/CHANGELOG.md +392 -7
  2. package/README.md +37 -1
  3. package/dist/css/expressive.css +5440 -234
  4. package/dist/css/expressive.css.map +1 -1
  5. package/dist/css/expressive.min.css +1 -1
  6. package/dist/css/expressive.min.css.map +1 -1
  7. package/dist/fonts/material-symbols-outlined.woff2 +0 -0
  8. package/dist/fonts/material-symbols-rounded.woff2 +0 -0
  9. package/dist/fonts/material-symbols-sharp.woff2 +0 -0
  10. package/dist/js/chunks/{chunk-AYYYWUJ4.mjs → chunk-3CLXLPYS.mjs} +17 -11
  11. package/dist/js/chunks/chunk-3CLXLPYS.mjs.map +7 -0
  12. package/dist/js/chunks/chunk-3DHK5CZU.mjs +105 -0
  13. package/dist/js/chunks/chunk-3DHK5CZU.mjs.map +7 -0
  14. package/dist/js/chunks/{chunk-IMUPRRI6.mjs → chunk-3VOK6J7U.mjs} +3 -3
  15. package/dist/js/chunks/chunk-4PWAPV6F.mjs +7 -0
  16. package/dist/js/chunks/chunk-4PWAPV6F.mjs.map +7 -0
  17. package/dist/js/chunks/{chunk-XFJ452AZ.mjs → chunk-55IA2XUC.mjs} +5 -6
  18. package/dist/js/chunks/{chunk-XFJ452AZ.mjs.map → chunk-55IA2XUC.mjs.map} +2 -2
  19. package/dist/js/chunks/{chunk-XDB3G6RL.mjs → chunk-6N2XAEFK.mjs} +15 -4
  20. package/dist/js/chunks/{chunk-XDB3G6RL.mjs.map → chunk-6N2XAEFK.mjs.map} +2 -2
  21. package/dist/js/chunks/{chunk-MISGHDUU.mjs → chunk-6OMRGML6.mjs} +9 -7
  22. package/dist/js/chunks/{chunk-MISGHDUU.mjs.map → chunk-6OMRGML6.mjs.map} +2 -2
  23. package/dist/js/chunks/{chunk-A3BJA7V4.mjs → chunk-6WLSDZ4O.mjs} +2 -2
  24. package/dist/js/chunks/chunk-CCWDV5CR.mjs +292 -0
  25. package/dist/js/chunks/chunk-CCWDV5CR.mjs.map +7 -0
  26. package/dist/js/chunks/{chunk-JODJAGFS.mjs → chunk-CKQP6P76.mjs} +31 -8
  27. package/dist/js/chunks/chunk-CKQP6P76.mjs.map +7 -0
  28. package/dist/js/chunks/{chunk-KR44BSYQ.mjs → chunk-DLWYKFEI.mjs} +3 -3
  29. package/dist/js/chunks/chunk-DTKR54I6.mjs +270 -0
  30. package/dist/js/chunks/chunk-DTKR54I6.mjs.map +7 -0
  31. package/dist/js/chunks/{chunk-SLFNPY5M.mjs → chunk-EC63CHXU.mjs} +2 -2
  32. package/dist/js/chunks/chunk-GVGT6MIU.mjs +47 -0
  33. package/dist/js/chunks/chunk-GVGT6MIU.mjs.map +7 -0
  34. package/dist/js/chunks/{chunk-LZVBJ33S.mjs → chunk-GZWPXXY6.mjs} +15 -8
  35. package/dist/js/chunks/chunk-GZWPXXY6.mjs.map +7 -0
  36. package/dist/js/chunks/chunk-HKT2S7VM.mjs +48 -0
  37. package/dist/js/chunks/chunk-HKT2S7VM.mjs.map +7 -0
  38. package/dist/js/chunks/{chunk-D4UZ4HFN.mjs → chunk-IFXVDDDH.mjs} +23 -11
  39. package/dist/js/chunks/chunk-IFXVDDDH.mjs.map +7 -0
  40. package/dist/js/chunks/chunk-K4CJCRGE.mjs +262 -0
  41. package/dist/js/chunks/chunk-K4CJCRGE.mjs.map +7 -0
  42. package/dist/js/chunks/chunk-LNCCYGPC.mjs +163 -0
  43. package/dist/js/chunks/chunk-LNCCYGPC.mjs.map +7 -0
  44. package/dist/js/chunks/{chunk-QSQ24FYT.mjs → chunk-LTWPGFBR.mjs} +2 -2
  45. package/dist/js/chunks/{chunk-QSQ24FYT.mjs.map → chunk-LTWPGFBR.mjs.map} +2 -2
  46. package/dist/js/chunks/{chunk-I5KR22BY.mjs → chunk-LWBQIX5J.mjs} +15 -15
  47. package/dist/js/chunks/chunk-LWBQIX5J.mjs.map +7 -0
  48. package/dist/js/chunks/{chunk-GY2Z5CJ2.mjs → chunk-NNYP33NB.mjs} +67 -32
  49. package/dist/js/chunks/chunk-NNYP33NB.mjs.map +7 -0
  50. package/dist/js/chunks/chunk-NQLC3YMW.mjs +1 -0
  51. package/dist/js/chunks/chunk-O5XPWXIQ.mjs +406 -0
  52. package/dist/js/chunks/chunk-O5XPWXIQ.mjs.map +7 -0
  53. package/dist/js/chunks/chunk-P3G3QHII.mjs +166 -0
  54. package/dist/js/chunks/chunk-P3G3QHII.mjs.map +7 -0
  55. package/dist/js/chunks/{chunk-VAS6JQ6P.mjs → chunk-PIJ2TAR3.mjs} +3 -3
  56. package/dist/js/chunks/{chunk-74N6U5JJ.mjs → chunk-PLYXLSH3.mjs} +38 -37
  57. package/dist/js/chunks/chunk-PLYXLSH3.mjs.map +7 -0
  58. package/dist/js/chunks/{chunk-PIIAEYAJ.mjs → chunk-QDAL4IYN.mjs} +2 -2
  59. package/dist/js/chunks/chunk-QKWPQK5N.mjs +179 -0
  60. package/dist/js/chunks/chunk-QKWPQK5N.mjs.map +7 -0
  61. package/dist/js/chunks/{chunk-FRB7TOCS.mjs → chunk-QV2JAWIZ.mjs} +2 -2
  62. package/dist/js/chunks/{chunk-N3UHGN3N.mjs → chunk-QVVEWBOF.mjs} +3 -3
  63. package/dist/js/chunks/chunk-RHRQKTTX.mjs +139 -0
  64. package/dist/js/chunks/chunk-RHRQKTTX.mjs.map +7 -0
  65. package/dist/js/chunks/chunk-SPETGK25.mjs +59 -0
  66. package/dist/js/chunks/chunk-SPETGK25.mjs.map +7 -0
  67. package/dist/js/chunks/{chunk-TK6PX2GC.mjs → chunk-U2OTHOTI.mjs} +131 -19
  68. package/dist/js/chunks/chunk-U2OTHOTI.mjs.map +7 -0
  69. package/dist/js/chunks/{chunk-TYM6U5DY.mjs → chunk-UAEAQBRC.mjs} +2 -2
  70. package/dist/js/chunks/{chunk-7UXHSYIA.mjs → chunk-UBLRRJ7N.mjs} +3 -3
  71. package/dist/js/chunks/{chunk-EP3CVKB5.mjs → chunk-UEZUJQ66.mjs} +8 -5
  72. package/dist/js/chunks/chunk-UEZUJQ66.mjs.map +7 -0
  73. package/dist/js/chunks/{chunk-JNPIWFJT.mjs → chunk-UNEMYWEW.mjs} +3 -3
  74. package/dist/js/chunks/chunk-VTLBW7QS.mjs +124 -0
  75. package/dist/js/chunks/chunk-VTLBW7QS.mjs.map +7 -0
  76. package/dist/js/chunks/{chunk-JKNPQQWY.mjs → chunk-WJXDBPXQ.mjs} +28 -12
  77. package/dist/js/chunks/chunk-WJXDBPXQ.mjs.map +7 -0
  78. package/dist/js/chunks/{chunk-CDW6WIRT.mjs → chunk-XGHORT7J.mjs} +18 -11
  79. package/dist/js/chunks/chunk-XGHORT7J.mjs.map +7 -0
  80. package/dist/js/chunks/{chunk-7GKXUG4B.mjs → chunk-XH6BEEMY.mjs} +78 -25
  81. package/dist/js/chunks/chunk-XH6BEEMY.mjs.map +7 -0
  82. package/dist/js/chunks/{chunk-XO5WX3TK.mjs → chunk-YDCS5S3J.mjs} +2 -2
  83. package/dist/js/expressive.cjs +2540 -233
  84. package/dist/js/expressive.cjs.map +4 -4
  85. package/dist/js/expressive.js +2540 -233
  86. package/dist/js/expressive.js.map +4 -4
  87. package/dist/js/expressive.min.js +10 -10
  88. package/dist/js/expressive.min.js.map +4 -4
  89. package/dist/js/expressive.mjs +86 -38
  90. package/dist/js/expressive.mjs.map +1 -1
  91. package/dist/js/internal/behaviors/bottomSheets.mjs +3 -3
  92. package/dist/js/internal/behaviors/dialogs.mjs +2 -2
  93. package/dist/js/internal/behaviors/forms.mjs +2 -2
  94. package/dist/js/internal/behaviors/sheetDrag.mjs +2 -2
  95. package/dist/js/internal/behaviors/sideSheets.mjs +3 -3
  96. package/dist/js/internal/components/appBar.mjs +2 -2
  97. package/dist/js/internal/components/autocomplete.mjs +4 -4
  98. package/dist/js/internal/components/barChart.mjs +10 -0
  99. package/dist/js/internal/components/barChart.mjs.map +7 -0
  100. package/dist/js/internal/components/buttonGroup.mjs +2 -2
  101. package/dist/js/internal/components/buttons.mjs +3 -3
  102. package/dist/js/internal/components/cards.mjs +3 -3
  103. package/dist/js/internal/components/carousel.mjs +3 -3
  104. package/dist/js/internal/components/characterCounter.mjs +2 -2
  105. package/dist/js/internal/components/chips.mjs +5 -5
  106. package/dist/js/internal/components/columnChart.mjs +9 -0
  107. package/dist/js/internal/components/columnChart.mjs.map +7 -0
  108. package/dist/js/internal/components/commandPalette.mjs +9 -0
  109. package/dist/js/internal/components/commandPalette.mjs.map +7 -0
  110. package/dist/js/internal/components/datepicker.mjs +6 -6
  111. package/dist/js/internal/components/expandingCard.mjs +3 -3
  112. package/dist/js/internal/components/heatmapChart.mjs +9 -0
  113. package/dist/js/internal/components/heatmapChart.mjs.map +7 -0
  114. package/dist/js/internal/components/index.mjs +79 -31
  115. package/dist/js/internal/components/lightbox.mjs +3 -3
  116. package/dist/js/internal/components/lineChart.mjs +12 -0
  117. package/dist/js/internal/components/lineChart.mjs.map +7 -0
  118. package/dist/js/internal/components/menu.mjs +3 -3
  119. package/dist/js/internal/components/messageScroller.mjs +9 -0
  120. package/dist/js/internal/components/messageScroller.mjs.map +7 -0
  121. package/dist/js/internal/components/mixedChart.mjs +10 -0
  122. package/dist/js/internal/components/mixedChart.mjs.map +7 -0
  123. package/dist/js/internal/components/navigationRail.mjs +2 -2
  124. package/dist/js/internal/components/pieChart.mjs +9 -0
  125. package/dist/js/internal/components/pieChart.mjs.map +7 -0
  126. package/dist/js/internal/components/questionnaire.mjs +9 -0
  127. package/dist/js/internal/components/questionnaire.mjs.map +7 -0
  128. package/dist/js/internal/components/radarChart.mjs +9 -0
  129. package/dist/js/internal/components/radarChart.mjs.map +7 -0
  130. package/dist/js/internal/components/radialChart.mjs +9 -0
  131. package/dist/js/internal/components/radialChart.mjs.map +7 -0
  132. package/dist/js/internal/components/registry.mjs +37 -25
  133. package/dist/js/internal/components/richTextEditor.mjs +9 -0
  134. package/dist/js/internal/components/richTextEditor.mjs.map +7 -0
  135. package/dist/js/internal/components/scrollspy.mjs +2 -2
  136. package/dist/js/internal/components/select.mjs +4 -4
  137. package/dist/js/internal/components/slider.mjs +3 -3
  138. package/dist/js/internal/components/snackbar.mjs +2 -2
  139. package/dist/js/internal/components/tabs.mjs +4 -4
  140. package/dist/js/internal/components/timepicker.mjs +4 -4
  141. package/dist/js/internal/components/tooltip.mjs +3 -3
  142. package/dist/js/internal/core/component.mjs +1 -1
  143. package/dist/js/internal/core/utils.mjs +1 -1
  144. package/dist/js/internal/plugins/dockedDisplayPlugin.mjs +2 -2
  145. package/dist/js/modular.mjs +86 -38
  146. package/dist/types/behaviors/forms.d.ts +22 -0
  147. package/dist/types/components/appBar.d.ts +1 -1
  148. package/dist/types/components/barChart.d.ts +24 -0
  149. package/dist/types/components/columnChart.d.ts +21 -0
  150. package/dist/types/components/commandPalette.d.ts +96 -0
  151. package/dist/types/components/datepicker.d.ts +5 -2
  152. package/dist/types/components/heatmapChart.d.ts +49 -0
  153. package/dist/types/components/index.d.ts +12 -0
  154. package/dist/types/components/lineChart.d.ts +134 -0
  155. package/dist/types/components/menu.d.ts +32 -0
  156. package/dist/types/components/messageScroller.d.ts +111 -0
  157. package/dist/types/components/mixedChart.d.ts +25 -0
  158. package/dist/types/components/pieChart.d.ts +40 -0
  159. package/dist/types/components/questionnaire.d.ts +96 -0
  160. package/dist/types/components/radarChart.d.ts +39 -0
  161. package/dist/types/components/radialChart.d.ts +45 -0
  162. package/dist/types/components/registry.d.ts +44 -0
  163. package/dist/types/components/richTextEditor.d.ts +71 -0
  164. package/dist/types/components/slider.d.ts +1 -0
  165. package/dist/types/components/snackbar.d.ts +6 -0
  166. package/dist/types/components/timepicker.d.ts +4 -1
  167. package/dist/types/components/tooltip.d.ts +2 -1
  168. package/dist/types/modular.d.ts +10 -1
  169. package/package.json +4 -1
  170. package/src/sass/README.md +3 -6
  171. package/src/sass/abstracts/_index.scss +1 -1
  172. package/src/sass/abstracts/_mixins.scss +226 -0
  173. package/src/sass/abstracts/_variables.scss +14 -0
  174. package/src/sass/base/_typography.scss +45 -0
  175. package/src/sass/components/_accordion.scss +161 -0
  176. package/src/sass/components/_aura.scss +119 -0
  177. package/src/sass/components/_avatar.scss +86 -0
  178. package/src/sass/components/_bar-chart.scss +285 -0
  179. package/src/sass/components/_bottom-sheet.scss +1 -0
  180. package/src/sass/components/_button-groups.scss +13 -3
  181. package/src/sass/components/_cards.scss +16 -13
  182. package/src/sass/components/_carousel.scss +9 -7
  183. package/src/sass/components/_column-chart.scss +134 -0
  184. package/src/sass/components/_command-palette.scss +140 -0
  185. package/src/sass/components/_countdown.scss +89 -0
  186. package/src/sass/components/_data-table.scss +188 -0
  187. package/src/sass/components/_dialog.scss +1 -0
  188. package/src/sass/components/_empty-state.scss +78 -0
  189. package/src/sass/components/_expanding-card.scss +3 -0
  190. package/src/sass/components/_heatmap-chart.scss +156 -0
  191. package/src/sass/components/_index.scss +27 -0
  192. package/src/sass/components/_kpi.scss +162 -0
  193. package/src/sass/components/_line-chart.scss +102 -0
  194. package/src/sass/components/_marquee.scss +111 -0
  195. package/src/sass/components/_menu.scss +10 -0
  196. package/src/sass/components/_message-scroller.scss +66 -0
  197. package/src/sass/components/_message.scss +202 -0
  198. package/src/sass/components/_mixed-chart.scss +157 -0
  199. package/src/sass/components/_navbar.scss +11 -6
  200. package/src/sass/components/_navigation-bar.scss +1 -0
  201. package/src/sass/components/_navigation-rail.scss +9 -0
  202. package/src/sass/components/_page-footer.scss +0 -11
  203. package/src/sass/components/_pie-chart.scss +203 -0
  204. package/src/sass/components/_popover.scss +112 -0
  205. package/src/sass/components/_progress.scss +25 -6
  206. package/src/sass/components/_questionnaire.scss +144 -0
  207. package/src/sass/components/_radar-chart.scss +205 -0
  208. package/src/sass/components/_radial-chart.scss +174 -0
  209. package/src/sass/components/_rich-text-editor.scss +99 -0
  210. package/src/sass/components/_search.scss +1 -0
  211. package/src/sass/components/_side-sheet.scss +1 -0
  212. package/src/sass/components/_skeleton.scss +81 -0
  213. package/src/sass/components/_snackbar.scss +2 -1
  214. package/src/sass/components/_stat.scss +86 -0
  215. package/src/sass/components/_stepper.scss +190 -0
  216. package/src/sass/components/_timeline.scss +114 -0
  217. package/src/sass/components/_timepicker.scss +2 -2
  218. package/src/sass/components/_toolbar.scss +2 -1
  219. package/src/sass/components/_tooltip.scss +2 -0
  220. package/src/sass/components/_tree.scss +139 -0
  221. package/src/sass/components/forms/_checkboxes.scss +2 -2
  222. package/src/sass/components/forms/_drop-zone.scss +128 -0
  223. package/src/sass/components/forms/_index.scss +2 -0
  224. package/src/sass/components/forms/_input-fields.scss +166 -15
  225. package/src/sass/components/forms/_radio-buttons.scss +2 -2
  226. package/src/sass/components/forms/_rating.scss +165 -0
  227. package/src/sass/components/forms/_slider.scss +51 -29
  228. package/src/sass/components/forms/_switches.scss +3 -3
  229. package/src/sass/tokens/_reference.scss +3 -15
  230. package/src/sass/utilities/_helpers.scss +14 -0
  231. package/src/sass/utilities/_index.scss +2 -0
  232. package/src/sass/utilities/_scroll-area.scss +35 -0
  233. package/src/sass/utilities/_shapes.scss +164 -0
  234. package/src/sass/utilities/_spacing.scss +29 -23
  235. package/src/sass/utilities/_visibility.scss +3 -2
  236. package/dist/js/chunks/chunk-74N6U5JJ.mjs.map +0 -7
  237. package/dist/js/chunks/chunk-7GKXUG4B.mjs.map +0 -7
  238. package/dist/js/chunks/chunk-AYYYWUJ4.mjs.map +0 -7
  239. package/dist/js/chunks/chunk-CDW6WIRT.mjs.map +0 -7
  240. package/dist/js/chunks/chunk-D4UZ4HFN.mjs.map +0 -7
  241. package/dist/js/chunks/chunk-EP3CVKB5.mjs.map +0 -7
  242. package/dist/js/chunks/chunk-GY2Z5CJ2.mjs.map +0 -7
  243. package/dist/js/chunks/chunk-I5KR22BY.mjs.map +0 -7
  244. package/dist/js/chunks/chunk-JKNPQQWY.mjs.map +0 -7
  245. package/dist/js/chunks/chunk-JODJAGFS.mjs.map +0 -7
  246. package/dist/js/chunks/chunk-JR24UB4X.mjs +0 -100
  247. package/dist/js/chunks/chunk-JR24UB4X.mjs.map +0 -7
  248. package/dist/js/chunks/chunk-LZVBJ33S.mjs.map +0 -7
  249. package/dist/js/chunks/chunk-PMBPAFAS.mjs +0 -7
  250. package/dist/js/chunks/chunk-PMBPAFAS.mjs.map +0 -7
  251. package/dist/js/chunks/chunk-TK6PX2GC.mjs.map +0 -7
  252. package/dist/js/chunks/chunk-WWPAPHMA.mjs +0 -1
  253. /package/dist/js/chunks/{chunk-IMUPRRI6.mjs.map → chunk-3VOK6J7U.mjs.map} +0 -0
  254. /package/dist/js/chunks/{chunk-A3BJA7V4.mjs.map → chunk-6WLSDZ4O.mjs.map} +0 -0
  255. /package/dist/js/chunks/{chunk-KR44BSYQ.mjs.map → chunk-DLWYKFEI.mjs.map} +0 -0
  256. /package/dist/js/chunks/{chunk-SLFNPY5M.mjs.map → chunk-EC63CHXU.mjs.map} +0 -0
  257. /package/dist/js/chunks/{chunk-WWPAPHMA.mjs.map → chunk-NQLC3YMW.mjs.map} +0 -0
  258. /package/dist/js/chunks/{chunk-VAS6JQ6P.mjs.map → chunk-PIJ2TAR3.mjs.map} +0 -0
  259. /package/dist/js/chunks/{chunk-PIIAEYAJ.mjs.map → chunk-QDAL4IYN.mjs.map} +0 -0
  260. /package/dist/js/chunks/{chunk-FRB7TOCS.mjs.map → chunk-QV2JAWIZ.mjs.map} +0 -0
  261. /package/dist/js/chunks/{chunk-N3UHGN3N.mjs.map → chunk-QVVEWBOF.mjs.map} +0 -0
  262. /package/dist/js/chunks/{chunk-TYM6U5DY.mjs.map → chunk-UAEAQBRC.mjs.map} +0 -0
  263. /package/dist/js/chunks/{chunk-7UXHSYIA.mjs.map → chunk-UBLRRJ7N.mjs.map} +0 -0
  264. /package/dist/js/chunks/{chunk-JNPIWFJT.mjs.map → chunk-UNEMYWEW.mjs.map} +0 -0
  265. /package/dist/js/chunks/{chunk-XO5WX3TK.mjs.map → chunk-YDCS5S3J.mjs.map} +0 -0
@@ -22,22 +22,34 @@ __export(index_exports, {
22
22
  AppBar: () => AppBar,
23
23
  AutoInit: () => AutoInit,
24
24
  Autocomplete: () => Autocomplete,
25
+ BarChart: () => BarChart,
25
26
  BottomSheets: () => BottomSheets,
26
27
  ButtonGroup: () => ButtonGroup,
27
28
  Cards: () => Cards,
28
29
  Carousel: () => Carousel,
29
30
  CharacterCounter: () => CharacterCounter,
30
31
  Chips: () => Chips,
32
+ ColumnChart: () => ColumnChart,
33
+ CommandPalette: () => CommandPalette,
31
34
  Datepicker: () => Datepicker,
32
35
  Dialogs: () => Dialogs,
33
36
  ExpandingCard: () => ExpandingCard,
34
37
  FloatingActionButton: () => FloatingActionButton,
35
38
  FormSelect: () => FormSelect,
36
39
  Forms: () => Forms,
40
+ HeatmapChart: () => HeatmapChart,
37
41
  Lightbox: () => Lightbox,
42
+ LineChart: () => LineChart,
38
43
  Menu: () => Menu,
44
+ MessageScroller: () => MessageScroller,
45
+ MixedChart: () => MixedChart,
39
46
  NavigationRail: () => NavigationRail,
47
+ PieChart: () => PieChart,
48
+ Questionnaire: () => Questionnaire,
49
+ RadarChart: () => RadarChart,
50
+ RadialChart: () => RadialChart,
40
51
  Range: () => Slider,
52
+ RichTextEditor: () => RichTextEditor,
41
53
  ScrollSpy: () => ScrollSpy,
42
54
  SideSheets: () => SideSheets,
43
55
  Slider: () => Slider,
@@ -271,12 +283,7 @@ var _Utils = class _Utils {
271
283
  button.type = "button";
272
284
  button.tabIndex = !!visibility ? 0 : -1;
273
285
  button.innerText = text;
274
- if (typeof callback === "function") {
275
- button.addEventListener("click", callback);
276
- button.addEventListener("keypress", (e) => {
277
- if (e.key === _Utils.keys.ENTER) callback(e);
278
- });
279
- }
286
+ if (typeof callback === "function") button.addEventListener("click", callback);
280
287
  container.append(button);
281
288
  }
282
289
  static _setAbsolutePosition(origin, container, position, margin, transitionMovement, align = "center") {
@@ -315,6 +322,13 @@ var _Utils = class _Utils {
315
322
  transitionMovement,
316
323
  align
317
324
  );
325
+ const parent = container.offsetParent;
326
+ if (parent && (parent !== document.body || getComputedStyle(parent).position !== "static")) {
327
+ const rect = parent.getBoundingClientRect();
328
+ const root = parent === document.scrollingElement;
329
+ newCoordinates.x -= rect.left + _Utils.getDocumentScrollLeft() + parent.clientLeft - (root ? 0 : parent.scrollLeft);
330
+ newCoordinates.y -= rect.top + _Utils.getDocumentScrollTop() + parent.clientTop - (root ? 0 : parent.scrollTop);
331
+ }
318
332
  container.style.top = newCoordinates.y + "px";
319
333
  container.style.left = newCoordinates.x + "px";
320
334
  return { x: xMovement, y: yMovement };
@@ -396,7 +410,7 @@ var Component = class {
396
410
  let instances = null;
397
411
  if (els instanceof Element) {
398
412
  instances = new classDef(els, options);
399
- } else if (!!els && els.length) {
413
+ } else if (els) {
400
414
  instances = [];
401
415
  for (let i = 0; i < els.length; i++) {
402
416
  instances.push(new classDef(els[i], options));
@@ -770,6 +784,7 @@ var _defaults3 = {
770
784
  coverTrigger: false,
771
785
  closeOnClick: true,
772
786
  hover: false,
787
+ contextMenu: false,
773
788
  inDuration: 150,
774
789
  outDuration: 250,
775
790
  onOpenStart: null,
@@ -784,6 +799,41 @@ var _Menu = class _Menu extends Component {
784
799
  super(el, options, _Menu);
785
800
  this._pendingTimers = /* @__PURE__ */ new Set();
786
801
  this._transition = 0;
802
+ this._menuClickTargets = /* @__PURE__ */ new WeakMap();
803
+ /**
804
+ * Viewport point the open context menu belongs to. Null outside a
805
+ * right-click, so a keyboard or programmatic open uses the region's corner
806
+ * rather than an old pointer position.
807
+ */
808
+ this._contextPoint = null;
809
+ /** What had focus when a context menu opened; focus goes back there. */
810
+ this._contextReturn = null;
811
+ this._handleContextMenu = (e) => {
812
+ if (e.defaultPrevented) {
813
+ this.close();
814
+ return;
815
+ }
816
+ const path = e.composedPath();
817
+ if (_Menu._menus.some((menu) => menu !== this && menu.isOpen && path.includes(menu.menuEl))) return;
818
+ e.preventDefault();
819
+ if (!this.isOpen) {
820
+ const root = this.el.getRootNode();
821
+ this._contextReturn = root.activeElement instanceof HTMLElement ? root.activeElement : null;
822
+ }
823
+ const rect = this.el.getBoundingClientRect();
824
+ const fromPointer = (e.clientX !== 0 || e.clientY !== 0) && e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
825
+ this._contextPoint = fromPointer ? { x: e.clientX, y: e.clientY } : null;
826
+ if (this.isOpen) this.recalculateDimensions();
827
+ else this.open();
828
+ };
829
+ this._handleDocumentContextMenu = (e) => {
830
+ const path = e.composedPath();
831
+ if (path.includes(this.menuEl)) {
832
+ e.preventDefault();
833
+ return;
834
+ }
835
+ if (!path.includes(this.el)) this.close();
836
+ };
787
837
  this._handleClick = (e) => {
788
838
  e.preventDefault();
789
839
  if (this.isOpen) {
@@ -812,11 +862,15 @@ var _Menu = class _Menu extends Component {
812
862
  }
813
863
  };
814
864
  this._handleDocumentClick = (e) => {
815
- const target = e.target;
865
+ const path = e.composedPath();
866
+ const menuTarget = this._menuClickTargets.get(e);
867
+ this._menuClickTargets.delete(e);
868
+ const target = menuTarget ?? (path[0] || e.target);
869
+ const inside = !!menuTarget || path.includes(this.menuEl) || this.menuEl?.contains(target);
816
870
  if (this._isSubmenuTriggerClick(target)) return;
817
- if (this.options.closeOnClick && this.menuEl?.contains(target) && !this.isTouchMoving) {
871
+ if (this.options.closeOnClick && inside && !this.isTouchMoving) {
818
872
  this.close();
819
- } else if (!this.menuEl?.contains(target)) {
873
+ } else if (!inside) {
820
874
  this._schedule(() => {
821
875
  if (this.isOpen) {
822
876
  this.close();
@@ -826,6 +880,7 @@ var _Menu = class _Menu extends Component {
826
880
  this.isTouchMoving = false;
827
881
  };
828
882
  this._handleTriggerKeydown = (e) => {
883
+ if (this.options.contextMenu) return;
829
884
  const opensMenu = e.key === SPACE || e.key === Utils.keys.ENTER || e.key === Utils.keys.ARROW_DOWN || e.key === Utils.keys.ARROW_UP;
830
885
  if (opensMenu && !this.isOpen) {
831
886
  e.preventDefault();
@@ -834,12 +889,13 @@ var _Menu = class _Menu extends Component {
834
889
  };
835
890
  this._handleDocumentTouchmove = (e) => {
836
891
  const target = e.target;
837
- if (this.menuEl?.contains(target)) {
892
+ if (e.composedPath().includes(this.menuEl) || this.menuEl?.contains(target)) {
838
893
  this.isTouchMoving = true;
839
894
  }
840
895
  };
841
896
  this._handleMenuClick = (e) => {
842
897
  const target = e.target;
898
+ this._menuClickTargets.set(e, target);
843
899
  const li = target.closest("li");
844
900
  if (li?.matches('.disabled, [aria-disabled="true"]')) {
845
901
  e.preventDefault();
@@ -983,7 +1039,7 @@ var _Menu = class _Menu extends Component {
983
1039
  this._placeMenu();
984
1040
  this._schedule(() => this._setupTemporaryEventHandlers(), 0);
985
1041
  this._animateIn();
986
- this.el.ariaExpanded = "true";
1042
+ this._setExpanded(true);
987
1043
  };
988
1044
  /**
989
1045
  * Close menu.
@@ -1005,10 +1061,13 @@ var _Menu = class _Menu extends Component {
1005
1061
  if (transition !== this._transition) return;
1006
1062
  this._animateOut();
1007
1063
  this._removeTemporaryEventHandlers();
1008
- this.el.ariaExpanded = "false";
1009
- if (this.options.autoFocus && (root.activeElement === focused || !root.activeElement || root.activeElement === document.body)) {
1010
- this.el.focus();
1064
+ this._setExpanded(false);
1065
+ if (this.options.autoFocus && this.menuEl.contains(focused) && (root.activeElement === focused || this.el.ownerDocument.activeElement === this.el.ownerDocument.body)) {
1066
+ const back = this._contextReturn;
1067
+ (back?.isConnected && back !== this.el.ownerDocument.body ? back : this.el).focus();
1011
1068
  }
1069
+ this._contextReturn = null;
1070
+ this._contextPoint = null;
1012
1071
  };
1013
1072
  /**
1014
1073
  * While menu is open, you can recalculate its dimensions if its contents have changed.
@@ -1026,6 +1085,7 @@ var _Menu = class _Menu extends Component {
1026
1085
  this._originalInert = this.menuEl?.getAttribute("inert") ?? null;
1027
1086
  this.options = {
1028
1087
  ..._Menu.defaults,
1088
+ contextMenu: el.hasAttribute("data-context-menu"),
1029
1089
  ...options
1030
1090
  };
1031
1091
  this.isOpen = false;
@@ -1033,10 +1093,12 @@ var _Menu = class _Menu extends Component {
1033
1093
  this.isTouchMoving = false;
1034
1094
  this.focusedIndex = -1;
1035
1095
  this.filterQuery = [];
1036
- this.el.ariaExpanded = "false";
1037
- if (!this.el.hasAttribute("aria-haspopup")) this.el.setAttribute("aria-haspopup", "menu");
1038
- if (this.id && !this.el.hasAttribute("aria-controls"))
1039
- this.el.setAttribute("aria-controls", this.id);
1096
+ if (!this.options.contextMenu) {
1097
+ this.el.ariaExpanded = "false";
1098
+ if (!this.el.hasAttribute("aria-haspopup")) this.el.setAttribute("aria-haspopup", "menu");
1099
+ if (this.id && !this.el.hasAttribute("aria-controls"))
1100
+ this.el.setAttribute("aria-controls", this.id);
1101
+ }
1040
1102
  this._moveMenuToElement();
1041
1103
  this._setupAccessibility();
1042
1104
  this._makeMenuFocusable();
@@ -1060,7 +1122,7 @@ var _Menu = class _Menu extends Component {
1060
1122
  destroy() {
1061
1123
  this._clearPendingTimers();
1062
1124
  this.isOpen = false;
1063
- this.el.ariaExpanded = "false";
1125
+ this._setExpanded(false);
1064
1126
  clearTimeout(this.filterTimeout);
1065
1127
  this._resetMenuStyles();
1066
1128
  this._restoreInert();
@@ -1076,7 +1138,9 @@ var _Menu = class _Menu extends Component {
1076
1138
  if (this.menuEl?.querySelector(":scope > li > menu")) {
1077
1139
  this.menuEl.addEventListener("mouseover", this._handleSubmenuAlign);
1078
1140
  }
1079
- if (this.options.hover) {
1141
+ if (this.options.contextMenu) {
1142
+ this.el.addEventListener("contextmenu", this._handleContextMenu);
1143
+ } else if (this.options.hover) {
1080
1144
  this.el.addEventListener("mouseenter", this._handleMouseEnter);
1081
1145
  this.el.addEventListener("mouseleave", this._handleMouseLeave);
1082
1146
  this.menuEl.addEventListener("mouseleave", this._handleMouseLeave);
@@ -1088,7 +1152,9 @@ var _Menu = class _Menu extends Component {
1088
1152
  this.el.removeEventListener("keydown", this._handleTriggerKeydown);
1089
1153
  this.menuEl?.removeEventListener("click", this._handleMenuClick);
1090
1154
  this.menuEl?.removeEventListener("mouseover", this._handleSubmenuAlign);
1091
- if (this.options.hover) {
1155
+ if (this.options.contextMenu) {
1156
+ this.el.removeEventListener("contextmenu", this._handleContextMenu);
1157
+ } else if (this.options.hover) {
1092
1158
  this.el.removeEventListener("mouseenter", this._handleMouseEnter);
1093
1159
  this.el.removeEventListener("mouseleave", this._handleMouseLeave);
1094
1160
  this.menuEl?.removeEventListener("mouseleave", this._handleMouseLeave);
@@ -1098,17 +1164,21 @@ var _Menu = class _Menu extends Component {
1098
1164
  }
1099
1165
  _setupTemporaryEventHandlers() {
1100
1166
  document.body.addEventListener("click", this._handleDocumentClick);
1167
+ this.menuEl?.addEventListener("touchmove", this._handleDocumentTouchmove, { passive: true });
1101
1168
  document.body.addEventListener("touchmove", this._handleDocumentTouchmove, {
1102
1169
  passive: true
1103
1170
  });
1104
1171
  this.menuEl?.addEventListener("keydown", this._handleMenuKeydown);
1105
1172
  window.addEventListener("resize", this._handleWindowResize);
1173
+ if (this.options.contextMenu) document.addEventListener("contextmenu", this._handleDocumentContextMenu);
1106
1174
  }
1107
1175
  _removeTemporaryEventHandlers() {
1108
1176
  document.body.removeEventListener("click", this._handleDocumentClick);
1177
+ this.menuEl?.removeEventListener("touchmove", this._handleDocumentTouchmove);
1109
1178
  document.body.removeEventListener("touchmove", this._handleDocumentTouchmove);
1110
1179
  this.menuEl?.removeEventListener("keydown", this._handleMenuKeydown);
1111
1180
  window.removeEventListener("resize", this._handleWindowResize);
1181
+ document.removeEventListener("contextmenu", this._handleDocumentContextMenu);
1112
1182
  }
1113
1183
  _setupAccessibility() {
1114
1184
  if (!this.menuEl) return;
@@ -1398,8 +1468,12 @@ var _Menu = class _Menu extends Component {
1398
1468
  closestOverflowParent.style.position = "relative";
1399
1469
  const natural = this.menuEl.getBoundingClientRect().width;
1400
1470
  const triggerWidth = this.el.getBoundingClientRect().width;
1401
- const idealWidth = this.options.constrainWidth && triggerWidth >= MIN_SURFACE_WIDTH ? triggerWidth : Math.min(MAX_SURFACE_WIDTH, Math.max(MIN_SURFACE_WIDTH, natural));
1471
+ const idealWidth = this.options.constrainWidth && !this.options.contextMenu && triggerWidth >= MIN_SURFACE_WIDTH ? triggerWidth : Math.min(MAX_SURFACE_WIDTH, Math.max(MIN_SURFACE_WIDTH, natural));
1402
1472
  this.menuEl.style.width = idealWidth + "px";
1473
+ if (this.options.contextMenu) {
1474
+ this._placeMenuAtPoint(this._contextPoint ?? this._regionCorner(), closestOverflowParent);
1475
+ return;
1476
+ }
1403
1477
  const positionInfo = this._getMenuPosition(closestOverflowParent);
1404
1478
  this.menuEl.style.left = positionInfo.x + "px";
1405
1479
  this.menuEl.style.top = positionInfo.y + "px";
@@ -1407,6 +1481,58 @@ var _Menu = class _Menu extends Component {
1407
1481
  this.menuEl.style.width = positionInfo.width + "px";
1408
1482
  this.menuEl.style.transformOrigin = `${positionInfo.horizontalAlignment === "left" ? "0" : "100%"} ${positionInfo.verticalAlignment === "top" ? "0" : "100%"}`;
1409
1483
  }
1484
+ /** Where a context menu opens without a pointer: the region's start corner. */
1485
+ _regionCorner() {
1486
+ const rect = this.el.getBoundingClientRect();
1487
+ const rtl = getComputedStyle(this.el).direction === "rtl";
1488
+ return { x: rtl ? rect.right : rect.left, y: rect.bottom };
1489
+ }
1490
+ /**
1491
+ * Places a context menu with its start corner on the point, flipping to
1492
+ * the other side of the point where it would not fit. The bounds are the
1493
+ * viewport, narrowed to a scrolling or clipping ancestor the menu sits in;
1494
+ * a menu taller than them gets their height and scrolls.
1495
+ *
1496
+ * The menu rests at left/top 0 of its containing block, so its rect is the
1497
+ * offset to subtract - the same trick _getMenuPosition uses. It is measured
1498
+ * without the entry animation's scale, which a second right-click can catch.
1499
+ */
1500
+ _placeMenuAtPoint({ x, y }, container) {
1501
+ const { transform, transition } = this.menuEl.style;
1502
+ this.menuEl.style.transition = "none";
1503
+ this.menuEl.style.transform = "none";
1504
+ const menu = this.menuEl.getBoundingClientRect();
1505
+ this.menuEl.style.transform = transform;
1506
+ this.menuEl.style.transition = transition;
1507
+ let bounds = { left: 0, top: 0, right: window.innerWidth, bottom: window.innerHeight };
1508
+ if (container && getComputedStyle(container).overflow !== "visible") {
1509
+ const clip = container.getBoundingClientRect();
1510
+ bounds = {
1511
+ left: Math.max(bounds.left, clip.left),
1512
+ top: Math.max(bounds.top, clip.top),
1513
+ right: Math.min(bounds.right, clip.right),
1514
+ bottom: Math.min(bounds.bottom, clip.bottom)
1515
+ };
1516
+ }
1517
+ const width = menu.width;
1518
+ const height = Math.min(menu.height, bounds.bottom - bounds.top);
1519
+ this.isScrollable = height < menu.height;
1520
+ if (this.isScrollable) this.menuEl.style.height = height + "px";
1521
+ const rtl = getComputedStyle(this.el).direction === "rtl";
1522
+ const fitsRight = x + width <= bounds.right;
1523
+ const fitsLeft = x - width >= bounds.left;
1524
+ const leftward = rtl ? fitsLeft || !fitsRight : !fitsRight && fitsLeft;
1525
+ const upward = y + height > bounds.bottom && y - height >= bounds.top;
1526
+ const clamp = (value, min, max) => Math.max(min, Math.min(value, max));
1527
+ const left = clamp(leftward ? x - width : x, bounds.left, bounds.right - width);
1528
+ const top = clamp(upward ? y - height : y, bounds.top, bounds.bottom - height);
1529
+ this.menuEl.style.left = left - menu.left + "px";
1530
+ this.menuEl.style.top = top - menu.top + "px";
1531
+ this.menuEl.style.transformOrigin = `${leftward ? "100%" : "0"} ${upward ? "100%" : "0"}`;
1532
+ }
1533
+ _setExpanded(expanded) {
1534
+ if (!this.options.contextMenu) this.el.ariaExpanded = String(expanded);
1535
+ }
1410
1536
  };
1411
1537
  _Menu._menus = [];
1412
1538
  var Menu = _Menu;
@@ -1539,7 +1665,6 @@ var _Autocomplete = class _Autocomplete extends Component {
1539
1665
  const inputText = this.el.value.toLocaleLowerCase();
1540
1666
  this._resetAutocomplete();
1541
1667
  if (inputText.length >= this.options.minLength) {
1542
- this.isOpen = true;
1543
1668
  this._renderMenu();
1544
1669
  this._announce(this.menuItems.length ? this.options.i18n.results(this.menuItems.length) : this.options.i18n.noResults);
1545
1670
  }
@@ -1633,6 +1758,7 @@ var _Autocomplete = class _Autocomplete extends Component {
1633
1758
  }
1634
1759
  this._removeEventHandlers();
1635
1760
  this.menu.destroy();
1761
+ this.isOpen = false;
1636
1762
  this._removeMenu();
1637
1763
  this._statusInfo.remove();
1638
1764
  this._announcer.remove();
@@ -1680,6 +1806,16 @@ var _Autocomplete = class _Autocomplete extends Component {
1680
1806
  ..._Autocomplete.defaults.menuOptions,
1681
1807
  ...this.options.menuOptions
1682
1808
  };
1809
+ const userOnOpenStart = menuOptions.onOpenStart;
1810
+ const userOnCloseStart = menuOptions.onCloseStart;
1811
+ menuOptions.onOpenStart = (el) => {
1812
+ this.isOpen = true;
1813
+ if (typeof userOnOpenStart === "function") userOnOpenStart.call(this.menu, el);
1814
+ };
1815
+ menuOptions.onCloseStart = (el) => {
1816
+ this.isOpen = false;
1817
+ if (typeof userOnCloseStart === "function") userOnCloseStart.call(this.menu, el);
1818
+ };
1683
1819
  const userOnItemClick = menuOptions.onItemClick;
1684
1820
  menuOptions.onItemClick = (li) => {
1685
1821
  if (!li) return;
@@ -1692,10 +1828,7 @@ var _Autocomplete = class _Autocomplete extends Component {
1692
1828
  const label = this.el.parentElement.querySelector("label");
1693
1829
  if (label) this.el.after(label);
1694
1830
  this.el.removeEventListener("click", this.menu._handleClick);
1695
- if (!this.options.isMultiSelect && !(this.options.selected.length === 0)) {
1696
- const selectedValue = this.menuItems.filter((value) => value.id === this.selectedValues[0].id);
1697
- this.el.value = selectedValue[0].text;
1698
- }
1831
+ if (!this.options.isMultiSelect) this._refreshInputText();
1699
1832
  if (this.el.value) this.selectOption(this.el.value);
1700
1833
  const div = document.createElement("div");
1701
1834
  div.classList.add("status-info");
@@ -1723,7 +1856,6 @@ var _Autocomplete = class _Autocomplete extends Component {
1723
1856
  this.container.replaceChildren();
1724
1857
  this._resetCurrentElementPosition();
1725
1858
  this.oldVal = null;
1726
- this.isOpen = false;
1727
1859
  this._pointerDown = false;
1728
1860
  }
1729
1861
  _highlightPartialText(input, label) {
@@ -1841,7 +1973,7 @@ var _Autocomplete = class _Autocomplete extends Component {
1841
1973
  }
1842
1974
  }
1843
1975
  _triggerChanged() {
1844
- this.el.dispatchEvent(new Event("change"));
1976
+ this.el.dispatchEvent(new Event("change", { bubbles: true, cancelable: true, composed: true }));
1845
1977
  if (typeof this.options.onAutocomplete === "function")
1846
1978
  this.options.onAutocomplete.call(this, this.selectedValues);
1847
1979
  }
@@ -1950,7 +2082,7 @@ var _Chips = class _Chips extends Component {
1950
2082
  this._handleInputKeydown = (e) => {
1951
2083
  _Chips._keydown = true;
1952
2084
  if (e.key === Utils.keys.ENTER) {
1953
- if (this.hasAutocomplete && this.autocomplete && this.autocomplete.isOpen) {
2085
+ if (this.hasAutocomplete && this.autocomplete?.isOpen && this.autocomplete.container.querySelector('[role="option"]')) {
1954
2086
  return;
1955
2087
  }
1956
2088
  e.preventDefault();
@@ -2016,6 +2148,9 @@ var _Chips = class _Chips extends Component {
2016
2148
  return this.chipsData;
2017
2149
  }
2018
2150
  destroy() {
2151
+ if (this.autocomplete && Autocomplete.getInstance(this._input) === this.autocomplete) {
2152
+ this.autocomplete.destroy();
2153
+ }
2019
2154
  if (this.options.allowUserInput) {
2020
2155
  this._removeEventHandlers();
2021
2156
  }
@@ -2081,7 +2216,7 @@ var _Chips = class _Chips extends Component {
2081
2216
  }
2082
2217
  }
2083
2218
  _renderChip(chip) {
2084
- if (!chip.id) return;
2219
+ if (!chip.id && chip.id !== 0) return;
2085
2220
  const label = chip.text || chip.id;
2086
2221
  const renderedChip = document.createElement("span");
2087
2222
  renderedChip.classList.add("chip");
@@ -2115,14 +2250,16 @@ var _Chips = class _Chips extends Component {
2115
2250
  }
2116
2251
  _setupAutocomplete() {
2117
2252
  this.options.autocompleteOptions.onAutocomplete = (items) => {
2118
- if (items.length > 0)
2253
+ if (_Chips.getInstance(this.el) !== this) return;
2254
+ if (items.length > 0) {
2119
2255
  this.addChip({
2120
2256
  id: items[0].id,
2121
2257
  text: items[0].text,
2122
2258
  image: items[0].image
2123
2259
  });
2124
- this._input.value = "";
2125
- this._input.focus();
2260
+ this._input.value = "";
2261
+ this._input.focus();
2262
+ }
2126
2263
  };
2127
2264
  this.autocomplete = Autocomplete.init(this._input, this.options.autocompleteOptions);
2128
2265
  }
@@ -2143,6 +2280,7 @@ var _Chips = class _Chips extends Component {
2143
2280
  if (this._label && this._input) this._label.setAttribute("for", this._input.getAttribute("id"));
2144
2281
  }
2145
2282
  _setPlaceholder() {
2283
+ if (!this._input) return;
2146
2284
  if (this.chipsData !== void 0 && !this.chipsData.length && this.options.placeholder) {
2147
2285
  this._input.placeholder = this.options.placeholder;
2148
2286
  } else if ((this.chipsData === void 0 || !!this.chipsData.length) && this.options.secondaryPlaceholder) {
@@ -2150,7 +2288,7 @@ var _Chips = class _Chips extends Component {
2150
2288
  }
2151
2289
  }
2152
2290
  _isValidAndNotExist(chip) {
2153
- const isValid = !!chip.id;
2291
+ const isValid = !!chip.id || chip.id === 0;
2154
2292
  const doesNotExist = !this.chipsData.some((item) => item.id == chip.id);
2155
2293
  return isValid && doesNotExist;
2156
2294
  }
@@ -2163,7 +2301,8 @@ var _Chips = class _Chips extends Component {
2163
2301
  const renderedChip = this._renderChip(chip);
2164
2302
  this._chips.push(renderedChip);
2165
2303
  this.chipsData.push(chip);
2166
- this._input.before(renderedChip);
2304
+ if (this._input) this._input.before(renderedChip);
2305
+ else this.el.append(renderedChip);
2167
2306
  this._setPlaceholder();
2168
2307
  if (typeof this.options.onChipAdd === "function") {
2169
2308
  this.options.onChipAdd(this.el, renderedChip);
@@ -2234,6 +2373,7 @@ var Chips = _Chips;
2234
2373
 
2235
2374
  // src/ts/components/slider.ts
2236
2375
  var _defaults6 = {};
2376
+ var _authored = /* @__PURE__ */ new WeakMap();
2237
2377
  var Slider = class _Slider extends Component {
2238
2378
  constructor(el, options) {
2239
2379
  super(el, options, _Slider);
@@ -2302,6 +2442,18 @@ var Slider = class _Slider extends Component {
2302
2442
  this._removeEventHandlers();
2303
2443
  this._removeThumb();
2304
2444
  this.el["Expressive_Slider"] = void 0;
2445
+ const host = this._host();
2446
+ for (const el of [this.el, host]) {
2447
+ for (const name in _authored.get(el)) el.style.setProperty(name, _authored.get(el)[name]);
2448
+ _authored.delete(el);
2449
+ }
2450
+ host?.querySelectorAll('input[type="range"]').forEach((el) => el["Expressive_Slider"]?._sync());
2451
+ }
2452
+ _set(el, name, value) {
2453
+ const authored = _authored.get(el) ?? {};
2454
+ _authored.set(el, authored);
2455
+ authored[name] ?? (authored[name] = el.style.getPropertyValue(name));
2456
+ el.style.setProperty(name, value);
2305
2457
  }
2306
2458
  // Eleven listeners used to cover four stages, because each one was written
2307
2459
  // twice - once for mouse, once for touch. One of the touch spellings,
@@ -2372,25 +2524,28 @@ var Slider = class _Slider extends Component {
2372
2524
  const handle = parseFloat(style.getPropertyValue("--md-comp-slider-handle-width")) || 4;
2373
2525
  const length = vertical ? height : width;
2374
2526
  const position = handle / 2 + percent * (length - handle);
2375
- this.el.style.setProperty("--md-comp-slider-active-fraction", fraction);
2376
- this.el.style.setProperty("--md-comp-slider-active-position", `${position}px`);
2527
+ this._set(this.el, "--md-comp-slider-active-fraction", fraction);
2528
+ this._set(this.el, "--md-comp-slider-active-position", `${position}px`);
2377
2529
  const host = this._host();
2378
2530
  const peers = host?.querySelectorAll('input[type="range"]');
2379
2531
  if (host && peers && peers.length === 2) {
2380
2532
  const nums = Array.from(peers).map((el) => this._fraction(el));
2381
- host.style.setProperty("--md-comp-slider-start-fraction", `${Math.min(...nums) * 100}%`);
2382
- host.style.setProperty("--md-comp-slider-end-fraction", `${Math.max(...nums) * 100}%`);
2383
- host.style.setProperty("--md-comp-slider-start-position", `${handle / 2 + Math.min(...nums) * (length - handle)}px`);
2384
- host.style.setProperty("--md-comp-slider-end-position", `${handle / 2 + Math.max(...nums) * (length - handle)}px`);
2533
+ this._set(host, "--md-comp-slider-start-fraction", `${Math.min(...nums) * 100}%`);
2534
+ this._set(host, "--md-comp-slider-end-fraction", `${Math.max(...nums) * 100}%`);
2535
+ this._set(host, "--md-comp-slider-start-position", `${handle / 2 + Math.min(...nums) * (length - handle)}px`);
2536
+ this._set(host, "--md-comp-slider-end-position", `${handle / 2 + Math.max(...nums) * (length - handle)}px`);
2385
2537
  }
2386
2538
  if (host?.classList.contains("stops")) {
2387
2539
  const parsedMax = parseFloat(this.el.max);
2388
2540
  const max = Number.isNaN(parsedMax) ? 100 : parsedMax;
2389
2541
  const min = parseFloat(this.el.min) || 0;
2390
2542
  const step = parseFloat(this.el.step);
2391
- if (step > 0 && Number.isFinite(step)) {
2392
- const n = Math.round((max - min) / step) + 1;
2393
- host.style.setProperty("--md-comp-slider-stop-count", String(Math.max(2, n)));
2543
+ const stops = step > 0 && Number.isFinite(step) && max > min;
2544
+ this._set(host, "--md-comp-slider-stop-count", stops ? String(Math.floor((max - min) / step + 1e-9) + 1) : "");
2545
+ this._set(host, "--md-comp-slider-stop-spacing", stops ? `${(length - handle) * step / (max - min)}px` : "");
2546
+ if (peers?.length === 1) {
2547
+ this._set(host, "--md-comp-slider-active-position", `${position}px`);
2548
+ this._set(host, "--md-comp-slider-track-center", `${top + height / 2}px`);
2394
2549
  }
2395
2550
  }
2396
2551
  this.value.textContent = this.el.value;
@@ -2483,6 +2638,7 @@ var Dialogs = _Dialogs;
2483
2638
  // src/ts/behaviors/sheetDrag.ts
2484
2639
  var DISMISS_DISTANCE = 96;
2485
2640
  var DISMISS_VELOCITY = 0.5;
2641
+ var FLICK_TIMEOUT = 100;
2486
2642
  var DRAG_SLOP = 4;
2487
2643
  function installSheetDrag(config) {
2488
2644
  const coord = (event) => config.axis === "x" ? event.clientX : event.clientY;
@@ -2519,6 +2675,7 @@ function installSheetDrag(config) {
2519
2675
  start: coord(event),
2520
2676
  last: coord(event),
2521
2677
  lastT: Date.now(),
2678
+ velocity: 0,
2522
2679
  shift: 0
2523
2680
  };
2524
2681
  observer.observe(document, { childList: true, subtree: true, attributes: true, attributeFilter: ["open"], attributeOldValue: true });
@@ -2531,8 +2688,12 @@ function installSheetDrag(config) {
2531
2688
  }
2532
2689
  const delta = coord(event) - drag.start;
2533
2690
  drag.shift = drag.direction === 1 ? Math.max(0, delta) : Math.min(0, delta);
2534
- drag.last = coord(event);
2535
- drag.lastT = Date.now();
2691
+ if (coord(event) !== drag.last) {
2692
+ const now = Date.now();
2693
+ drag.velocity = (coord(event) - drag.last) / Math.max(1, now - drag.lastT);
2694
+ drag.last = coord(event);
2695
+ drag.lastT = now;
2696
+ }
2536
2697
  drag.dialog.style.setProperty(config.property, `${drag.shift}px`);
2537
2698
  };
2538
2699
  const onUp = (event) => {
@@ -2542,13 +2703,14 @@ function installSheetDrag(config) {
2542
2703
  onCancel();
2543
2704
  return;
2544
2705
  }
2706
+ onMove(event);
2545
2707
  drag = null;
2546
2708
  observer.disconnect();
2547
2709
  held.dialog.style.transition = "";
2548
- const dt = Math.max(1, Date.now() - held.lastT);
2549
- const velocity = (coord(event) - held.last) / dt;
2550
- config.onRelease?.(Math.abs(held.shift) > DRAG_SLOP);
2551
- const dismiss = held.direction === 1 ? held.shift > DISMISS_DISTANCE || velocity > DISMISS_VELOCITY : held.shift < -DISMISS_DISTANCE || velocity < -DISMISS_VELOCITY;
2710
+ const velocity = Date.now() - held.lastT < FLICK_TIMEOUT ? held.velocity : 0;
2711
+ const dragged = Math.abs(held.shift) > DRAG_SLOP;
2712
+ config.onRelease?.(dragged);
2713
+ const dismiss = dragged && (held.direction === 1 ? held.shift > DISMISS_DISTANCE || velocity > DISMISS_VELOCITY : held.shift < -DISMISS_DISTANCE || velocity < -DISMISS_VELOCITY);
2552
2714
  if (dismiss && held.dialog.open) held.dialog.close();
2553
2715
  held.dialog.style.setProperty(config.property, "0px");
2554
2716
  };
@@ -2685,6 +2847,10 @@ var Forms = class _Forms {
2685
2847
  Utils.onDocumentReady(() => {
2686
2848
  document.addEventListener("change", (e) => {
2687
2849
  const target = e.target;
2850
+ if (target instanceof HTMLInputElement && target.type === "file") {
2851
+ const zone = target.closest(".drop-zone");
2852
+ if (zone) _Forms.listDropZoneFiles(zone, target);
2853
+ }
2688
2854
  if (target instanceof HTMLInputElement) {
2689
2855
  _Forms.validateField(target);
2690
2856
  }
@@ -2700,6 +2866,12 @@ var Forms = class _Forms {
2700
2866
  }
2701
2867
  }
2702
2868
  });
2869
+ document.addEventListener("click", _Forms.togglePassword);
2870
+ document.addEventListener("submit", _Forms.resetFormExtras, true);
2871
+ document.addEventListener("reset", _Forms.resetFormExtras, true);
2872
+ for (const type of ["dragenter", "dragover", "dragleave", "drop"]) {
2873
+ document.addEventListener(type, _Forms.markDropZone);
2874
+ }
2703
2875
  document.querySelectorAll(".expressive-textarea").forEach((textArea) => {
2704
2876
  _Forms.InitTextarea(textArea);
2705
2877
  });
@@ -2708,6 +2880,75 @@ var Forms = class _Forms {
2708
2880
  });
2709
2881
  });
2710
2882
  }
2883
+ /**
2884
+ * A .password-toggle button shows or hides the password in its field. The
2885
+ * button's own name stays put ("Show password"); aria-pressed carries the
2886
+ * state, so there is no generated text to translate.
2887
+ */
2888
+ static togglePassword(e) {
2889
+ const button = e.target.closest?.(".password-toggle");
2890
+ const input = button?.closest(".field")?.querySelector(
2891
+ 'input[type="password"], input[data-password]'
2892
+ );
2893
+ if (!input) return;
2894
+ const show = input.type === "password";
2895
+ input.toggleAttribute("data-password", show);
2896
+ input.type = show ? "text" : "password";
2897
+ button.setAttribute("aria-pressed", String(show));
2898
+ }
2899
+ /**
2900
+ * A revealed password goes back to type="password" when its form is
2901
+ * submitted or reset, so it is never sent or autofilled as plain text and
2902
+ * a reset field does not stay revealed. A reset form also drops the file
2903
+ * lists it would otherwise leave showing: reset fires no change event.
2904
+ */
2905
+ static resetFormExtras(e) {
2906
+ const form = e.target;
2907
+ if (!(form instanceof HTMLFormElement)) return;
2908
+ form.querySelectorAll("input[data-password]").forEach((input) => {
2909
+ input.removeAttribute("data-password");
2910
+ input.type = "password";
2911
+ input.closest(".field")?.querySelector(".password-toggle")?.setAttribute("aria-pressed", "false");
2912
+ });
2913
+ if (e.type === "reset") {
2914
+ form.querySelectorAll(".drop-zone-files").forEach((list) => list.replaceChildren());
2915
+ form.querySelectorAll(".drop-zone.dragover").forEach((zone) => zone.classList.remove("dragover"));
2916
+ }
2917
+ }
2918
+ /**
2919
+ * The native file input covers its .drop-zone, so the browser already takes
2920
+ * a dropped file. This only shows the drag: the input is the topmost
2921
+ * element, so every drag event over the zone targets it and leaving it is
2922
+ * leaving the zone.
2923
+ */
2924
+ static markDropZone(e) {
2925
+ const input = e.target;
2926
+ if (!(input instanceof HTMLInputElement) || input.type !== "file") return;
2927
+ const zone = input.closest(".drop-zone");
2928
+ if (!zone) return;
2929
+ const over = e.type === "dragenter" || e.type === "dragover";
2930
+ zone.classList.toggle("dragover", over && !input.disabled);
2931
+ }
2932
+ /** Lists the chosen files, name and size, in the zone's .drop-zone-files. */
2933
+ static listDropZoneFiles(zone, input) {
2934
+ const list = zone.querySelector(".drop-zone-files");
2935
+ if (!list) return;
2936
+ const size = (bytes) => {
2937
+ const [value, unit] = bytes >= 1e6 ? [bytes / 1e6, "megabyte"] : bytes >= 1e3 ? [bytes / 1e3, "kilobyte"] : [bytes, "byte"];
2938
+ return new Intl.NumberFormat(void 0, { style: "unit", unit, maximumFractionDigits: 1 }).format(value);
2939
+ };
2940
+ list.replaceChildren(
2941
+ ...Array.from(input.files ?? [], (file) => {
2942
+ const item = document.createElement("li");
2943
+ const name = document.createElement("span");
2944
+ const meta = document.createElement("span");
2945
+ name.textContent = file.name;
2946
+ meta.textContent = size(file.size);
2947
+ item.append(name, meta);
2948
+ return item;
2949
+ })
2950
+ );
2951
+ }
2711
2952
  static InitTextarea(textarea) {
2712
2953
  textarea.setAttribute("original-height", textarea.getBoundingClientRect().height.toString());
2713
2954
  _Forms.textareaAutoResize(textarea);
@@ -2743,7 +2984,10 @@ var AppBar = class _AppBar extends Component {
2743
2984
  this._onIntersect = (entries) => {
2744
2985
  const entry = entries[0];
2745
2986
  if (!entry) return;
2746
- this.el.classList.toggle("collapsed", !entry.isIntersecting);
2987
+ this.el.classList.toggle(
2988
+ "collapsed",
2989
+ !entry.isIntersecting && entry.boundingClientRect.top < this.el.getBoundingClientRect().top
2990
+ );
2747
2991
  };
2748
2992
  this._onSearchViewClose = () => {
2749
2993
  this._searchOpen = this._view.open;
@@ -2790,8 +3034,8 @@ var AppBar = class _AppBar extends Component {
2790
3034
  if (typeof IntersectionObserver === "undefined") return;
2791
3035
  this._sentinel = document.createElement("span");
2792
3036
  this._sentinel.setAttribute("aria-hidden", "true");
2793
- this._sentinel.style.cssText = "display:block;width:100%;height:1px;margin-top:-1px;pointer-events:none;visibility:hidden";
2794
- this.el.insertAdjacentElement("beforebegin", this._sentinel);
3037
+ this._sentinel.style.cssText = "display:block;height:0;position:relative;top:-64px;pointer-events:none;visibility:hidden";
3038
+ this.el.insertAdjacentElement("afterend", this._sentinel);
2795
3039
  this._observer = new IntersectionObserver(this._onIntersect);
2796
3040
  this._observer.observe(this._sentinel);
2797
3041
  }
@@ -3218,10 +3462,6 @@ var _Carousel = class _Carousel extends Component {
3218
3462
  this._dragStartX = e.clientX;
3219
3463
  this._dragStartY = e.clientY;
3220
3464
  this._dragStartScroll = this._vertical ? this._scroller.scrollTop : this._scroller.scrollLeft;
3221
- try {
3222
- this._scroller.setPointerCapture(e.pointerId);
3223
- } catch {
3224
- }
3225
3465
  };
3226
3466
  this._handleTrackPointerMove = (e) => {
3227
3467
  if (!this.pressed || e.pointerId !== this._dragPointerId) return;
@@ -3233,6 +3473,10 @@ var _Carousel = class _Carousel extends Component {
3233
3473
  if (Math.abs(primaryDelta) < 6 || Math.abs(primaryDelta) <= Math.abs(crossDelta)) return;
3234
3474
  this.dragged = true;
3235
3475
  this.el.classList.add("dragging");
3476
+ try {
3477
+ this._scroller.setPointerCapture(e.pointerId);
3478
+ } catch {
3479
+ }
3236
3480
  }
3237
3481
  if (this._vertical) this._scroller.scrollTop = this._dragStartScroll - deltaY;
3238
3482
  else this._scroller.scrollLeft = this._dragStartScroll - deltaX;
@@ -3262,7 +3506,7 @@ var _Carousel = class _Carousel extends Component {
3262
3506
  e.stopImmediatePropagation();
3263
3507
  };
3264
3508
  this._handleTrackDragStart = (e) => {
3265
- if (e.target.closest("img, picture, video")) e.preventDefault();
3509
+ if (e.target.closest("a, img, picture, video")) e.preventDefault();
3266
3510
  };
3267
3511
  this._handleItemResize = (e) => {
3268
3512
  if (this._alignAfterResize && !this.pressed && e.propertyName === "flex-basis" && this.images.includes(e.target)) {
@@ -3318,7 +3562,7 @@ var _Carousel = class _Carousel extends Component {
3318
3562
  if (!this._started || !this.images.length) return;
3319
3563
  this._syncAdaptiveMode();
3320
3564
  this._syncLayoutRoles(this.center);
3321
- this._scrollToIndex(this.center, false);
3565
+ if (!this.pressed) this._scrollToIndex(this.center, false);
3322
3566
  this._updateParallax();
3323
3567
  };
3324
3568
  this._finishScroll = () => {
@@ -3502,9 +3746,9 @@ var _Carousel = class _Carousel extends Component {
3502
3746
  });
3503
3747
  this._scroller.addEventListener("pointerdown", this._handleTrackPointerDown);
3504
3748
  this._scroller.addEventListener("wheel", this._handleNativeScroll, { passive: true });
3505
- this._scroller.addEventListener("pointermove", this._handleTrackPointerMove);
3506
- this._scroller.addEventListener("pointerup", this._handleTrackPointerUp);
3507
- this._scroller.addEventListener("pointercancel", this._handleTrackPointerUp);
3749
+ document.addEventListener("pointermove", this._handleTrackPointerMove);
3750
+ document.addEventListener("pointerup", this._handleTrackPointerUp);
3751
+ document.addEventListener("pointercancel", this._handleTrackPointerUp);
3508
3752
  this._scroller.addEventListener("click", this._handleTrackClick, true);
3509
3753
  this._scroller.addEventListener("dragstart", this._handleTrackDragStart);
3510
3754
  this._scroller.addEventListener("transitionend", this._handleItemResize);
@@ -3523,9 +3767,9 @@ var _Carousel = class _Carousel extends Component {
3523
3767
  this._scroller.removeEventListener("scroll", this._handleTrackScroll);
3524
3768
  this._scroller.removeEventListener("pointerdown", this._handleTrackPointerDown);
3525
3769
  this._scroller.removeEventListener("wheel", this._handleNativeScroll);
3526
- this._scroller.removeEventListener("pointermove", this._handleTrackPointerMove);
3527
- this._scroller.removeEventListener("pointerup", this._handleTrackPointerUp);
3528
- this._scroller.removeEventListener("pointercancel", this._handleTrackPointerUp);
3770
+ document.removeEventListener("pointermove", this._handleTrackPointerMove);
3771
+ document.removeEventListener("pointerup", this._handleTrackPointerUp);
3772
+ document.removeEventListener("pointercancel", this._handleTrackPointerUp);
3529
3773
  this._scroller.removeEventListener("click", this._handleTrackClick, true);
3530
3774
  this._scroller.removeEventListener("dragstart", this._handleTrackDragStart);
3531
3775
  this._scroller.removeEventListener("transitionend", this._handleItemResize);
@@ -3784,92 +4028,1660 @@ var _Carousel = class _Carousel extends Component {
3784
4028
  this._cycleTo(n, callback);
3785
4029
  }
3786
4030
  };
3787
- _Carousel._live = /* @__PURE__ */ new Set();
3788
- _Carousel._resizeListening = false;
3789
- _Carousel._onResize = Utils.throttle(() => {
3790
- _Carousel._live.forEach((c) => c._handleResize());
3791
- }, 200);
3792
- var Carousel = _Carousel;
4031
+ _Carousel._live = /* @__PURE__ */ new Set();
4032
+ _Carousel._resizeListening = false;
4033
+ _Carousel._onResize = Utils.throttle(() => {
4034
+ _Carousel._live.forEach((c) => c._handleResize());
4035
+ }, 200);
4036
+ var Carousel = _Carousel;
4037
+
4038
+ // src/ts/components/characterCounter.ts
4039
+ var _defaults11 = Object.freeze({});
4040
+ var CharacterCounter = class _CharacterCounter extends Component {
4041
+ constructor(el, options) {
4042
+ super(el, {}, _CharacterCounter);
4043
+ this.updateCounter = () => {
4044
+ const maxLength = parseInt(this.el.getAttribute("maxlength")), actualLength = this.el.value.length;
4045
+ this.isValidLength = actualLength <= maxLength;
4046
+ let counterString = actualLength.toString();
4047
+ if (maxLength) {
4048
+ counterString += "/" + maxLength;
4049
+ this._validateInput();
4050
+ }
4051
+ this.counterEl.textContent = counterString;
4052
+ };
4053
+ this.el["Expressive_CharacterCounter"] = this;
4054
+ this.options = {
4055
+ ..._CharacterCounter.defaults,
4056
+ ...options
4057
+ };
4058
+ this.isInvalid = false;
4059
+ this.isValidLength = false;
4060
+ this._setupCounter();
4061
+ this._setupEventHandlers();
4062
+ }
4063
+ static get defaults() {
4064
+ return _defaults11;
4065
+ }
4066
+ /**
4067
+ * Initializes instances of CharacterCounter.
4068
+ * @param els HTML elements.
4069
+ * @param options Component options.
4070
+ */
4071
+ static init(els, options = {}) {
4072
+ return super.init(els, options, _CharacterCounter);
4073
+ }
4074
+ static getInstance(el) {
4075
+ return el["Expressive_CharacterCounter"];
4076
+ }
4077
+ destroy() {
4078
+ this._removeEventHandlers();
4079
+ this.el["Expressive_CharacterCounter"] = void 0;
4080
+ this._removeCounter();
4081
+ }
4082
+ _setupEventHandlers() {
4083
+ this.el.addEventListener("focus", this.updateCounter, true);
4084
+ this.el.addEventListener("input", this.updateCounter, true);
4085
+ }
4086
+ _removeEventHandlers() {
4087
+ this.el.removeEventListener("focus", this.updateCounter, true);
4088
+ this.el.removeEventListener("input", this.updateCounter, true);
4089
+ }
4090
+ _setupCounter() {
4091
+ this.counterEl = document.createElement("span");
4092
+ this.counterEl.classList.add("character-counter");
4093
+ this.counterEl.setAttribute("aria-live", "polite");
4094
+ this.counterEl.setAttribute("aria-atomic", "true");
4095
+ this.counterEl.style.float = "right";
4096
+ this.counterEl.style.fontSize = "12px";
4097
+ this.counterEl.style.height = "1";
4098
+ this.el.parentElement.appendChild(this.counterEl);
4099
+ }
4100
+ _removeCounter() {
4101
+ this.counterEl.remove();
4102
+ }
4103
+ _validateInput() {
4104
+ if (this.isValidLength && this.isInvalid) {
4105
+ this.isInvalid = false;
4106
+ this.el.classList.remove("invalid");
4107
+ } else if (!this.isValidLength && !this.isInvalid) {
4108
+ this.isInvalid = true;
4109
+ this.el.classList.remove("valid");
4110
+ this.el.classList.add("invalid");
4111
+ }
4112
+ }
4113
+ };
4114
+
4115
+ // src/ts/components/commandPalette.ts
4116
+ var _defaults12 = {
4117
+ shortcut: "k",
4118
+ closeOnRun: true
4119
+ };
4120
+ var APPLE = typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/.test(navigator.platform);
4121
+ var fold = (text) => text.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase();
4122
+ var CommandPalette = class _CommandPalette extends Component {
4123
+ constructor(el, options) {
4124
+ super(el, options, _CommandPalette);
4125
+ this._active = null;
4126
+ this._generatedIds = [];
4127
+ /** Wires and refilters commands the page adds or removes later. */
4128
+ this._observer = null;
4129
+ /** Opens the palette as a modal dialog with the search input focused. */
4130
+ this.open = () => {
4131
+ if (!this.el.open) this.el.showModal();
4132
+ this.input?.focus();
4133
+ };
4134
+ /** Closes the palette. The dialog returns focus to where it was. */
4135
+ this.close = () => {
4136
+ if (this.el.open) this.el.close();
4137
+ };
4138
+ this._handleInput = () => this._filter();
4139
+ this._handleKeydown = (e) => {
4140
+ if (e.key === Utils.keys.ARROW_DOWN || e.key === Utils.keys.ARROW_UP) {
4141
+ e.preventDefault();
4142
+ this._move(e.key === Utils.keys.ARROW_DOWN ? 1 : -1);
4143
+ } else if (e.key === Utils.keys.ENTER && !e.isComposing && e.keyCode !== 229 && this._active) {
4144
+ e.preventDefault();
4145
+ this._run(this._active);
4146
+ }
4147
+ };
4148
+ this._handleListClick = (e) => {
4149
+ const item = e.target.closest('[role="option"]');
4150
+ if (!item || !this.list.contains(item) || item.getAttribute("aria-disabled") === "true") return;
4151
+ const action = this._action(item);
4152
+ if (action && !action.contains(e.target)) {
4153
+ action.click();
4154
+ return;
4155
+ }
4156
+ if (this.options.closeOnRun) this.close();
4157
+ };
4158
+ this._handlePointerMove = (e) => {
4159
+ const item = e.target.closest('[role="option"]');
4160
+ if (item && this.list.contains(item) && item.getAttribute("aria-disabled") !== "true") this._setActive(item);
4161
+ };
4162
+ /**
4163
+ * Each opening starts from the full list. The close event is queued, so a
4164
+ * palette reopened straight away may already be open again; its new search
4165
+ * is left alone.
4166
+ */
4167
+ this._handleClose = () => {
4168
+ if (this.el.open) return;
4169
+ this.input.value = "";
4170
+ this._filter();
4171
+ };
4172
+ this._handleShortcut = (e) => {
4173
+ const { shortcut } = this.options;
4174
+ if (!shortcut || e.defaultPrevented || e.altKey || e.shiftKey) return;
4175
+ if (!(APPLE ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey)) return;
4176
+ const letter = shortcut.toLowerCase();
4177
+ const latin = /^[a-z]$/i.test(e.key);
4178
+ const hit = e.key.toLowerCase() === letter || !latin && /^[a-z]$/.test(letter) && e.code === `Key${letter.toUpperCase()}`;
4179
+ if (!hit) return;
4180
+ e.preventDefault();
4181
+ if (this.el.open) this.close();
4182
+ else this.open();
4183
+ };
4184
+ this.el["Expressive_CommandPalette"] = this;
4185
+ const shortcut = el.dataset.shortcut;
4186
+ this.options = {
4187
+ ..._CommandPalette.defaults,
4188
+ ...shortcut !== void 0 ? { shortcut: shortcut || null } : {},
4189
+ ...options
4190
+ };
4191
+ this.input = el.querySelector(":scope > input");
4192
+ this.list = el.querySelector(":scope > :is(ul, ol)");
4193
+ if (!this.input || !this.list) {
4194
+ console.error("CommandPalette needs an <input> and a <ul> as children of the dialog.");
4195
+ return;
4196
+ }
4197
+ this._setupAccessibility();
4198
+ this._filter();
4199
+ this._observer = new MutationObserver(() => this._filter(true));
4200
+ this._observer.observe(this.list, {
4201
+ childList: true,
4202
+ subtree: true,
4203
+ attributes: true,
4204
+ attributeFilter: ["hidden", "disabled"]
4205
+ });
4206
+ this.input.addEventListener("input", this._handleInput);
4207
+ this.input.addEventListener("keydown", this._handleKeydown);
4208
+ this.list.addEventListener("click", this._handleListClick);
4209
+ this.list.addEventListener("pointermove", this._handlePointerMove);
4210
+ this.el.addEventListener("close", this._handleClose);
4211
+ document.addEventListener("keydown", this._handleShortcut);
4212
+ }
4213
+ static get defaults() {
4214
+ return _defaults12;
4215
+ }
4216
+ /**
4217
+ * Initializes instances of CommandPalette.
4218
+ * @param els HTML elements.
4219
+ * @param options Component options.
4220
+ */
4221
+ static init(els, options = {}) {
4222
+ return super.init(els, options, _CommandPalette);
4223
+ }
4224
+ static getInstance(el) {
4225
+ return el["Expressive_CommandPalette"];
4226
+ }
4227
+ /** If the palette is open. */
4228
+ get isOpen() {
4229
+ return this.el.open;
4230
+ }
4231
+ destroy() {
4232
+ this._observer?.disconnect();
4233
+ document.removeEventListener("keydown", this._handleShortcut);
4234
+ this.el.removeEventListener("close", this._handleClose);
4235
+ if (this.input && this.list) {
4236
+ this.input.removeEventListener("input", this._handleInput);
4237
+ this.input.removeEventListener("keydown", this._handleKeydown);
4238
+ this.list.removeEventListener("click", this._handleListClick);
4239
+ this.list.removeEventListener("pointermove", this._handlePointerMove);
4240
+ for (const name of ["role", "aria-controls", "aria-expanded", "aria-autocomplete", "aria-activedescendant"]) {
4241
+ this.input.removeAttribute(name);
4242
+ }
4243
+ this.list.removeAttribute("role");
4244
+ for (const item of this._items()) {
4245
+ for (const name of ["role", "aria-selected", "aria-disabled", "data-filtered"]) item.removeAttribute(name);
4246
+ item.classList.remove("active");
4247
+ this._action(item)?.removeAttribute("tabindex");
4248
+ }
4249
+ for (const label of this._labels()) {
4250
+ label.removeAttribute("role");
4251
+ label.removeAttribute("data-filtered");
4252
+ }
4253
+ for (const el of this._generatedIds) el.removeAttribute("id");
4254
+ const empty = this._empty();
4255
+ if (empty) empty.hidden = false;
4256
+ }
4257
+ this.el["Expressive_CommandPalette"] = void 0;
4258
+ }
4259
+ _items() {
4260
+ return Array.from(this.list.children).filter(
4261
+ (child) => child instanceof HTMLElement && !child.classList.contains("label")
4262
+ );
4263
+ }
4264
+ _labels() {
4265
+ return Array.from(this.list.querySelectorAll(":scope > .label"));
4266
+ }
4267
+ _empty() {
4268
+ return this.el.querySelector(":scope > .command-palette-empty");
4269
+ }
4270
+ _action(item) {
4271
+ return item.querySelector(":scope > :is(a[href], button)");
4272
+ }
4273
+ _ensureId(el, prefix) {
4274
+ if (!el.id) {
4275
+ el.id = `${prefix}-${Utils.guid()}`;
4276
+ this._generatedIds.push(el);
4277
+ }
4278
+ return el.id;
4279
+ }
4280
+ _setupAccessibility() {
4281
+ this.list.setAttribute("role", "listbox");
4282
+ this.input.setAttribute("role", "combobox");
4283
+ this.input.setAttribute("aria-controls", this._ensureId(this.list, "command-palette-list"));
4284
+ this.input.setAttribute("aria-expanded", "true");
4285
+ this.input.setAttribute("aria-autocomplete", "list");
4286
+ }
4287
+ /** Every command, including ones added since the last pass, is an option. */
4288
+ _wireItems() {
4289
+ for (const item of this._items()) {
4290
+ if (item.getAttribute("role") === "option") continue;
4291
+ item.setAttribute("role", "option");
4292
+ this._ensureId(item, "command-palette-option");
4293
+ this._action(item)?.setAttribute("tabindex", "-1");
4294
+ }
4295
+ for (const label of this._labels()) label.setAttribute("role", "none");
4296
+ }
4297
+ /** Commands on screen: neither hidden by the page nor filtered out. */
4298
+ _shown() {
4299
+ return this._items().filter((item) => !item.hidden && !item.hasAttribute("data-filtered"));
4300
+ }
4301
+ /** Shown commands the arrows and Enter can reach: disabled ones are skipped. */
4302
+ _visible() {
4303
+ return this._shown().filter((item) => item.getAttribute("aria-disabled") !== "true");
4304
+ }
4305
+ /** A disabled link or button cannot run, so its option says so. */
4306
+ _isDisabled(item) {
4307
+ const action = this._action(item);
4308
+ return !!action && (action.matches(":disabled") || action.getAttribute("aria-disabled") === "true");
4309
+ }
4310
+ _setActive(item) {
4311
+ if (this._active === item) return;
4312
+ this._active?.classList.remove("active");
4313
+ this._active?.removeAttribute("aria-selected");
4314
+ this._active = item;
4315
+ if (item) {
4316
+ item.classList.add("active");
4317
+ item.setAttribute("aria-selected", "true");
4318
+ this.input.setAttribute("aria-activedescendant", item.id);
4319
+ item.scrollIntoView({ block: "nearest" });
4320
+ } else {
4321
+ this.input.removeAttribute("aria-activedescendant");
4322
+ }
4323
+ }
4324
+ /**
4325
+ * Shows the commands matching the query. Filtering marks rows with
4326
+ * data-filtered rather than hidden, so a command the page hides stays
4327
+ * hidden. `keepActive` holds the active command while it is still
4328
+ * reachable, for list changes the user did not make.
4329
+ */
4330
+ _filter(keepActive = false) {
4331
+ this._wireItems();
4332
+ const terms = fold(this.input.value).split(/\s+/).filter(Boolean);
4333
+ for (const item of this._items()) {
4334
+ const keywords = item.dataset.keywords ?? this._action(item)?.dataset.keywords ?? "";
4335
+ const text = fold(`${item.textContent} ${keywords}`);
4336
+ item.toggleAttribute("data-filtered", !terms.every((term) => text.includes(term)));
4337
+ if (this._isDisabled(item)) item.setAttribute("aria-disabled", "true");
4338
+ else item.removeAttribute("aria-disabled");
4339
+ }
4340
+ for (const label of this._labels()) {
4341
+ let next = label.nextElementSibling;
4342
+ let any = false;
4343
+ while (next && !next.classList.contains("label")) {
4344
+ if (!next.hidden && !next.hasAttribute("data-filtered")) any = true;
4345
+ next = next.nextElementSibling;
4346
+ }
4347
+ label.toggleAttribute("data-filtered", !any);
4348
+ }
4349
+ const visible = this._visible();
4350
+ const empty = this._empty();
4351
+ if (empty) empty.hidden = this._shown().length > 0;
4352
+ this._setActive(keepActive && visible.includes(this._active) ? this._active : visible[0] ?? null);
4353
+ }
4354
+ _move(step) {
4355
+ const visible = this._visible();
4356
+ if (!visible.length) return;
4357
+ const index = visible.indexOf(this._active);
4358
+ this._setActive(visible[(index + step + visible.length) % visible.length]);
4359
+ }
4360
+ /** Runs a command by clicking its link or button; the click handler closes. */
4361
+ _run(item) {
4362
+ (this._action(item) ?? item).click();
4363
+ }
4364
+ };
4365
+
4366
+ // src/ts/components/questionnaire.ts
4367
+ var _defaults13 = {
4368
+ progressLabel: "Question {current} of {total}",
4369
+ requiredMessage: "Choose an answer to continue."
4370
+ };
4371
+ var NAVIGATION = ["previous", "skip", "next"];
4372
+ var Questionnaire = class _Questionnaire extends Component {
4373
+ constructor(el, options) {
4374
+ super(el, options, _Questionnaire);
4375
+ this._active = null;
4376
+ this._generated = [];
4377
+ this._generatedIds = [];
4378
+ /** Set while one submission's invalid events arrive, so only the first opens. */
4379
+ this._reporting = false;
4380
+ /** Shows the question at `index` without checking the current one. */
4381
+ this.goTo = (index) => {
4382
+ const item = this.items[index];
4383
+ if (!item) return;
4384
+ this._active = item;
4385
+ this._render();
4386
+ const controls = this._controls(item).filter((control) => !control.disabled);
4387
+ (controls.find((control) => control.checked) ?? controls[0])?.focus();
4388
+ };
4389
+ /** Checks the shown question and moves to the next one if it is answered. */
4390
+ this.next = () => {
4391
+ if (this._validate(this._active)) this.goTo(this.index + 1);
4392
+ };
4393
+ /** Moves to the previous question. */
4394
+ this.previous = () => this.goTo(this.index - 1);
4395
+ /** Clears the answer to the shown question and moves to the next one. */
4396
+ this.skip = () => {
4397
+ if (!this._active) return;
4398
+ for (const control of this._controls(this._active)) {
4399
+ const isChoice = control instanceof HTMLInputElement && (control.type === "radio" || control.type === "checkbox");
4400
+ if (isChoice ? !control.checked : !control.value) continue;
4401
+ if (isChoice) control.checked = false;
4402
+ else control.value = "";
4403
+ control.dispatchEvent(new Event("change", { bubbles: true }));
4404
+ }
4405
+ this.goTo(this.index + 1);
4406
+ };
4407
+ this._handleClick = (e) => {
4408
+ const button = e.target.closest?.("button");
4409
+ if (!button || !this._buttons().includes(button)) return;
4410
+ if (NAVIGATION.includes(button.value)) {
4411
+ e.preventDefault();
4412
+ this[button.value]();
4413
+ return;
4414
+ }
4415
+ this._syncRequired();
4416
+ };
4417
+ // Enter in a field moves on instead of submitting the form early.
4418
+ this._handleKeydown = (e) => {
4419
+ if (e.key !== "Enter" || e.defaultPrevented || e.isComposing) return;
4420
+ if (!(e.target instanceof HTMLInputElement) || this.index >= this.items.length - 1) return;
4421
+ e.preventDefault();
4422
+ this.next();
4423
+ };
4424
+ // A submission found an unanswered question, possibly a hidden one. Open the
4425
+ // first instead of letting the browser report a control it cannot show.
4426
+ // One submission fires its invalid events in a single task.
4427
+ this._handleInvalid = (e) => {
4428
+ const item = this.items.find((item2) => item2.contains(e.target));
4429
+ if (!item) return;
4430
+ e.preventDefault();
4431
+ if (this._reporting) return;
4432
+ this._reporting = true;
4433
+ setTimeout(() => this._reporting = false);
4434
+ this._active = item;
4435
+ this._render();
4436
+ this._validate(item);
4437
+ };
4438
+ // An answer clears the question's error.
4439
+ this._handleInput = (e) => {
4440
+ this._syncRequired();
4441
+ const item = e.target.closest?.("fieldset");
4442
+ if (item instanceof HTMLFieldSetElement && item.classList.contains("invalid")) this._setInvalid(item, false);
4443
+ };
4444
+ this.el["Expressive_Questionnaire"] = this;
4445
+ const progressLabel = el.dataset.progressLabel;
4446
+ this.options = {
4447
+ ..._Questionnaire.defaults,
4448
+ ...progressLabel ? { progressLabel } : {},
4449
+ ...options
4450
+ };
4451
+ this._progress = document.createElement("div");
4452
+ this._progress.className = "questionnaire-progress";
4453
+ this._progressText = document.createElement("span");
4454
+ this._progressText.id = `questionnaire-progress-${Utils.guid()}`;
4455
+ this._progressBar = document.createElement("progress");
4456
+ this._progressBar.className = "progress";
4457
+ this._progressBar.setAttribute("aria-labelledby", this._progressText.id);
4458
+ this._progress.append(this._progressText, this._progressBar);
4459
+ this.el.prepend(this._progress);
4460
+ for (const item of this._fieldsets()) {
4461
+ const ids = Array.from(item.querySelectorAll(":scope > p:not(.questionnaire-error)")).map((p) => this._ensureId(p, "questionnaire-description"));
4462
+ if (ids.length && !item.hasAttribute("aria-describedby")) {
4463
+ item.setAttribute("aria-describedby", ids.join(" "));
4464
+ this._generatedIds.push(item);
4465
+ }
4466
+ }
4467
+ this._active = this.items[0] ?? null;
4468
+ this._syncRequired();
4469
+ this._render();
4470
+ this._observer = new MutationObserver(() => {
4471
+ this._syncRequired();
4472
+ this._render();
4473
+ });
4474
+ this._observer.observe(this.el, { attributes: true, subtree: true, attributeFilter: ["disabled"] });
4475
+ this.el.addEventListener("click", this._handleClick);
4476
+ this.el.addEventListener("keydown", this._handleKeydown);
4477
+ this.el.addEventListener("input", this._handleInput);
4478
+ this.el.addEventListener("invalid", this._handleInvalid, true);
4479
+ }
4480
+ static get defaults() {
4481
+ return _defaults13;
4482
+ }
4483
+ /**
4484
+ * Initializes instances of Questionnaire.
4485
+ * @param els HTML elements.
4486
+ * @param options Component options.
4487
+ */
4488
+ static init(els, options = {}) {
4489
+ return super.init(els, options, _Questionnaire);
4490
+ }
4491
+ static getInstance(el) {
4492
+ return el["Expressive_Questionnaire"];
4493
+ }
4494
+ /** The questions that apply: every fieldset child that is not disabled. */
4495
+ get items() {
4496
+ return this._fieldsets().filter((item) => !item.disabled);
4497
+ }
4498
+ /** Position of the shown question in `items`. */
4499
+ get index() {
4500
+ return this.items.indexOf(this._active);
4501
+ }
4502
+ destroy() {
4503
+ this._observer.disconnect();
4504
+ this.el.removeEventListener("click", this._handleClick);
4505
+ this.el.removeEventListener("keydown", this._handleKeydown);
4506
+ this.el.removeEventListener("input", this._handleInput);
4507
+ this.el.removeEventListener("invalid", this._handleInvalid, true);
4508
+ for (const item of this._fieldsets()) {
4509
+ if (item.hasAttribute("data-required")) for (const control of this._controls(item)) control.setCustomValidity("");
4510
+ item.hidden = false;
4511
+ this._setInvalid(item, false);
4512
+ }
4513
+ for (const button of this._buttons()) button.hidden = false;
4514
+ this._progress.remove();
4515
+ for (const el of this._generated) el.remove();
4516
+ for (const el of this._generatedIds) {
4517
+ el.removeAttribute(el instanceof HTMLFieldSetElement ? "aria-describedby" : "id");
4518
+ }
4519
+ this.el["Expressive_Questionnaire"] = void 0;
4520
+ }
4521
+ _fieldsets() {
4522
+ return Array.from(this.el.querySelectorAll(":scope > fieldset"));
4523
+ }
4524
+ /** The footer's navigation and submit buttons. */
4525
+ _buttons() {
4526
+ return Array.from(this.el.querySelectorAll(":scope > footer > button")).filter(
4527
+ (button) => NAVIGATION.includes(button.value) || button.type === "submit"
4528
+ );
4529
+ }
4530
+ _controls(item) {
4531
+ return Array.from(item.elements).filter(
4532
+ (el) => el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement || el instanceof HTMLInputElement && el.type !== "button" && el.type !== "submit"
4533
+ );
4534
+ }
4535
+ _required(item) {
4536
+ return item.hasAttribute("data-required") || this._controls(item).some((control) => control.required);
4537
+ }
4538
+ _ensureId(el, prefix) {
4539
+ if (!el.id) {
4540
+ el.id = `${prefix}-${Utils.guid()}`;
4541
+ this._generatedIds.push(el);
4542
+ }
4543
+ return el.id;
4544
+ }
4545
+ _render() {
4546
+ const items = this.items;
4547
+ if (!items.includes(this._active)) {
4548
+ const all = this._fieldsets();
4549
+ const after = all.slice(all.indexOf(this._active) + 1).find((item) => !item.disabled);
4550
+ this._active = after ?? items[items.length - 1] ?? null;
4551
+ }
4552
+ const index = items.indexOf(this._active);
4553
+ const last = index === items.length - 1;
4554
+ for (const item of this._fieldsets()) item.hidden = item !== this._active;
4555
+ for (const button of this._buttons()) {
4556
+ if (button.value === "previous") button.hidden = index <= 0;
4557
+ else if (button.value === "skip") button.hidden = last || !this._active || this._required(this._active);
4558
+ else if (button.value === "next") button.hidden = last;
4559
+ else button.hidden = !last;
4560
+ }
4561
+ this._progressText.textContent = this.options.progressLabel.replace("{current}", String(index + 1)).replace("{total}", String(items.length));
4562
+ this._progressBar.max = Math.max(items.length, 1);
4563
+ this._progressBar.value = index + 1;
4564
+ }
4565
+ /**
4566
+ * Puts "choose at least one" on the first enabled checkbox of each
4567
+ * data-required group with none checked, so the browser's validation, and
4568
+ * every way of submitting, sees it. A disabled checkbox is not sent, so it
4569
+ * does not count.
4570
+ */
4571
+ _syncRequired() {
4572
+ for (const item of this._fieldsets()) {
4573
+ if (!item.hasAttribute("data-required")) continue;
4574
+ const boxes = this._controls(item).filter(
4575
+ (control) => control instanceof HTMLInputElement && control.type === "checkbox"
4576
+ );
4577
+ const enabled = boxes.filter((box) => !box.disabled);
4578
+ for (const box of boxes) box.setCustomValidity("");
4579
+ if (enabled.length && !enabled.some((box) => box.checked)) {
4580
+ const error = item.querySelector(":scope > .questionnaire-error:not([data-generated])");
4581
+ enabled[0].setCustomValidity(error?.textContent.trim() || this.options.requiredMessage);
4582
+ }
4583
+ }
4584
+ }
4585
+ /** The first control in the question that still needs an answer. */
4586
+ _invalidControl(item) {
4587
+ return this._controls(item).find((control) => control.willValidate && !control.validity.valid);
4588
+ }
4589
+ /** Shows the question's error and returns false when it needs an answer. */
4590
+ _validate(item) {
4591
+ this._syncRequired();
4592
+ const invalid = item && this._invalidControl(item);
4593
+ if (!invalid) return true;
4594
+ let error = item.querySelector(":scope > .questionnaire-error");
4595
+ if (!error) {
4596
+ error = document.createElement("p");
4597
+ error.className = "questionnaire-error";
4598
+ error.dataset.generated = "";
4599
+ error.textContent = invalid.validationMessage || this.options.requiredMessage;
4600
+ item.append(error);
4601
+ this._generated.push(error);
4602
+ }
4603
+ this._setInvalid(item, true);
4604
+ invalid.focus();
4605
+ return false;
4606
+ }
4607
+ _setInvalid(item, invalid) {
4608
+ item.classList.toggle("invalid", invalid);
4609
+ const error = item.querySelector(":scope > .questionnaire-error");
4610
+ const described = (item.getAttribute("aria-describedby") ?? "").split(" ").filter((id) => id && id !== error?.id);
4611
+ if (invalid && error) described.push(this._ensureId(error, "questionnaire-error"));
4612
+ if (described.length) item.setAttribute("aria-describedby", described.join(" "));
4613
+ else item.removeAttribute("aria-describedby");
4614
+ for (const control of this._controls(item)) {
4615
+ if (invalid) control.setAttribute("aria-invalid", "true");
4616
+ else control.removeAttribute("aria-invalid");
4617
+ }
4618
+ if (!invalid && this._generated.includes(error)) {
4619
+ error.remove();
4620
+ this._generated.splice(this._generated.indexOf(error), 1);
4621
+ }
4622
+ }
4623
+ };
4624
+
4625
+ // src/ts/components/lineChart.ts
4626
+ var _defaults14 = {
4627
+ min: null,
4628
+ max: null
4629
+ };
4630
+ var SVG = "http://www.w3.org/2000/svg";
4631
+ var COLORS = 4;
4632
+ function svgNode(name, attributes = {}) {
4633
+ const element = document.createElementNS(SVG, name);
4634
+ for (const key in attributes) element.setAttribute(key, attributes[key]);
4635
+ return element;
4636
+ }
4637
+ var _gradientId = 0;
4638
+ function cellValue(cell) {
4639
+ if (!cell) return null;
4640
+ const raw = cell.dataset.value ?? cell.textContent;
4641
+ const text = raw.replace(/\u2212/g, "-").replace(/[\s%\p{Sc}]/gu, "");
4642
+ if (!/^[+-]?(\d{1,3}(,\d{3})+|\d+)?(\.\d+)?(e[+-]?\d+)?$/i.test(text) || !/\d/.test(text)) return null;
4643
+ return parseFloat(text.replace(/,/g, ""));
4644
+ }
4645
+ function monotonePath(points) {
4646
+ const n = points.length;
4647
+ if (n === 0) return "";
4648
+ if (n === 1) return `M${points[0][0]},${points[0][1]}h0`;
4649
+ const slopes = [];
4650
+ for (let i = 0; i < n - 1; i++) {
4651
+ slopes.push((points[i + 1][1] - points[i][1]) / (points[i + 1][0] - points[i][0]));
4652
+ }
4653
+ const tangents = points.map((_, i) => {
4654
+ if (i === 0) return slopes[0];
4655
+ if (i === n - 1) return slopes[n - 2];
4656
+ const [a, b] = [slopes[i - 1], slopes[i]];
4657
+ return a * b <= 0 ? 0 : 2 / (1 / a + 1 / b);
4658
+ });
4659
+ let d = `M${points[0][0]},${points[0][1]}`;
4660
+ for (let i = 0; i < n - 1; i++) {
4661
+ const [[x0, y0], [x1, y1]] = [points[i], points[i + 1]];
4662
+ const h = (x1 - x0) / 3;
4663
+ d += `C${x0 + h},${y0 + tangents[i] * h} ${x1 - h},${y1 - tangents[i + 1] * h} ${x1},${y1}`;
4664
+ }
4665
+ return d;
4666
+ }
4667
+ var LineChart = class extends Component {
4668
+ constructor(el, options) {
4669
+ super(el, options, new.target);
4670
+ /** The values' x positions and series, read from the table. */
4671
+ this.labels = [];
4672
+ this.series = [];
4673
+ /** The highlighted row, or -1. */
4674
+ this.activeIndex = -1;
4675
+ this._generated = [];
4676
+ /** The table's header cells and body rows, as read. */
4677
+ this._header = [];
4678
+ this._rows = [];
4679
+ /**
4680
+ * Whether the rows run right to left: a horizontal row axis in a
4681
+ * right-to-left context, like the labels. Bar chart rows keep their order.
4682
+ */
4683
+ this._reversed = false;
4684
+ this._onPointerMove = (e) => {
4685
+ this.show(this._indexAt(e));
4686
+ };
4687
+ this._onFocus = () => {
4688
+ if (this.activeIndex < 0 && this._plot.matches(":focus-visible")) this.show(0);
4689
+ };
4690
+ this._onLeave = (e) => {
4691
+ if (e.pointerType === "touch" || this._plot.matches(":focus-visible")) return;
4692
+ this.show(-1);
4693
+ };
4694
+ /** The browser took the touch to scroll the page. */
4695
+ this._onCancel = () => {
4696
+ this.show(-1);
4697
+ };
4698
+ this._onKeyDown = (e) => {
4699
+ const next = this._keyTarget(e.key);
4700
+ if (next === void 0) return;
4701
+ e.preventDefault();
4702
+ this.show(next);
4703
+ };
4704
+ this.el[this._key] = this;
4705
+ this.options = {
4706
+ ...new.target.defaults,
4707
+ ...options
4708
+ };
4709
+ this._read();
4710
+ if (!this.labels.length || !this.series.length) return;
4711
+ this._reversed = !this._horizontal && getComputedStyle(this.el).direction === "rtl";
4712
+ this._render();
4713
+ if (!this.el.classList.contains("sparkline")) {
4714
+ this._plot.tabIndex = 0;
4715
+ this._plot.addEventListener("pointerdown", this._onPointerMove);
4716
+ this._plot.addEventListener("pointermove", this._onPointerMove);
4717
+ this._plot.addEventListener("pointerleave", this._onLeave);
4718
+ this._plot.addEventListener("pointercancel", this._onCancel);
4719
+ this._plot.addEventListener("keydown", this._onKeyDown);
4720
+ this._plot.addEventListener("focus", this._onFocus);
4721
+ this._plot.addEventListener("blur", this._onLeave);
4722
+ }
4723
+ }
4724
+ /** The class prefix of the generated parts; ColumnChart reuses this class. */
4725
+ get _type() {
4726
+ return "line-chart";
4727
+ }
4728
+ /** Whether the y axis always includes zero, as columns grow from it. */
4729
+ get _fromZero() {
4730
+ return false;
4731
+ }
4732
+ /**
4733
+ * Whether the rows run down the plot and the values across it, as in a
4734
+ * bar chart. The rows then keep their order in a right-to-left context.
4735
+ */
4736
+ get _horizontal() {
4737
+ return false;
4738
+ }
4739
+ /**
4740
+ * Whether the rows go round a circle, as in a pie or radar chart. The
4741
+ * arrows then step round it, wrapping from the last row to the first.
4742
+ */
4743
+ get _circular() {
4744
+ return false;
4745
+ }
4746
+ /** Whether the cursor marks each series with a point. */
4747
+ get _points() {
4748
+ return true;
4749
+ }
4750
+ get _key() {
4751
+ return "Expressive_LineChart";
4752
+ }
4753
+ static get defaults() {
4754
+ return _defaults14;
4755
+ }
4756
+ static init(els, options = {}) {
4757
+ return super.init(els, options, this);
4758
+ }
4759
+ static getInstance(el) {
4760
+ return el["Expressive_LineChart"];
4761
+ }
4762
+ destroy() {
4763
+ this._generated.forEach((node) => node.remove());
4764
+ this._generated = [];
4765
+ this.el[this._key] = void 0;
4766
+ }
4767
+ /**
4768
+ * Highlights one row: moves the cursor and fills the tooltip.
4769
+ * Pass -1 to hide them. Does nothing for a chart without data.
4770
+ */
4771
+ show(index) {
4772
+ if (!this._tooltip || index === this.activeIndex) return;
4773
+ this.activeIndex = index;
4774
+ const hidden = index < 0 || index >= this.labels.length;
4775
+ this._cursor.hidden = this._tooltip.hidden = hidden;
4776
+ if (hidden) return;
4777
+ this._cursor.replaceChildren();
4778
+ const title = document.createElement("strong");
4779
+ title.textContent = this.labels[index];
4780
+ this._tooltip.replaceChildren(title);
4781
+ this.series.forEach((series, i) => {
4782
+ const value = series.values[index];
4783
+ if (value === null) return;
4784
+ const color = String(i % COLORS + 1);
4785
+ const top = series.tops[index];
4786
+ if (top !== null && this._points) {
4787
+ const dot = document.createElement("span");
4788
+ dot.className = series.className;
4789
+ dot.dataset.series = color;
4790
+ dot.style.top = `${series.y(top)}%`;
4791
+ this._cursor.append(dot);
4792
+ }
4793
+ const name = document.createElement("span");
4794
+ name.dataset.series = color;
4795
+ name.textContent = series.name;
4796
+ const text = document.createElement("span");
4797
+ text.textContent = series.text[index];
4798
+ this._tooltip.append(name, text);
4799
+ });
4800
+ this._place(index);
4801
+ }
4802
+ /**
4803
+ * Moves the filled cursor and tooltip to a row: beside its band, with the
4804
+ * tooltip opening back over the plot past the middle.
4805
+ */
4806
+ _place(index) {
4807
+ const x = this._x(index);
4808
+ const side = this._horizontal ? "top" : "left";
4809
+ this._cursor.style[side] = this._tooltip.style[side] = `${x}%`;
4810
+ this._tooltip.classList.toggle("end", x > 50);
4811
+ }
4812
+ _read() {
4813
+ const table = this.el.querySelector(":scope > table");
4814
+ if (!table) return;
4815
+ const headerRow = table.tHead?.rows[0] ?? table.rows[0];
4816
+ const header = Array.from(headerRow?.cells ?? []);
4817
+ const rows = Array.from(table.tBodies).flatMap((body) => Array.from(body.rows)).filter((row) => row !== headerRow);
4818
+ [this._header, this._rows] = [header, rows];
4819
+ this.labels = rows.map((row) => row.cells[0]?.textContent.trim() ?? "");
4820
+ this.series = header.slice(1).map((cell, i) => {
4821
+ const values = rows.map((row) => cellValue(row.cells[i + 1]));
4822
+ return {
4823
+ name: cell.textContent.trim(),
4824
+ className: cell.className,
4825
+ text: rows.map((row) => row.cells[i + 1]?.textContent.trim() ?? ""),
4826
+ values,
4827
+ tops: values,
4828
+ y: void 0
4829
+ };
4830
+ });
4831
+ const stacked = this.el.classList.contains("stacked");
4832
+ if (stacked) {
4833
+ const totals = this.labels.map(() => 0);
4834
+ for (const series of this.series) {
4835
+ series.base = [...totals];
4836
+ series.tops = series.values.map((value, i) => totals[i] = value === null || totals[i] === null ? null : totals[i] + value);
4837
+ }
4838
+ }
4839
+ if (this.series.every((series) => series.tops.every((value) => value === null))) this.series = [];
4840
+ const option = (name) => this._number(name) ?? this.options[name];
4841
+ this._y = this._scale(this.series, option("min"), option("max"));
4842
+ }
4843
+ /** A number from one of the chart's data attributes, or null. */
4844
+ _number(name) {
4845
+ const value = parseFloat(this.el.dataset[name] ?? "");
4846
+ return Number.isFinite(value) ? value : null;
4847
+ }
4848
+ /**
4849
+ * Fits a y scale to the series' tops, between `min` and `max` where they
4850
+ * are set, and gives it to each series.
4851
+ */
4852
+ _scale(series, min, max, fromZero = this._fromZero) {
4853
+ const stacked = this.el.classList.contains("stacked");
4854
+ let [low, high] = [Infinity, -Infinity];
4855
+ for (const { tops } of series) {
4856
+ for (const value of tops) {
4857
+ if (value === null) continue;
4858
+ low = Math.min(low, value);
4859
+ high = Math.max(high, value);
4860
+ }
4861
+ }
4862
+ if (low > high) low = high = 0;
4863
+ if (fromZero) {
4864
+ const [bottom, top] = [Math.min(0, low), Math.max(0, high)];
4865
+ const room = (top - (min ?? bottom)) * 0.1 || 1;
4866
+ min ?? (min = bottom < 0 ? bottom - room : 0);
4867
+ max ?? (max = top + room);
4868
+ } else {
4869
+ const pad = (high - low) * 0.1 || 1;
4870
+ min ?? (min = stacked ? Math.min(0, low) : low - pad);
4871
+ max ?? (max = high + (stacked ? (high - min) * 0.1 || 1 : pad));
4872
+ }
4873
+ const y = (value) => 100 - (value - min) / (max - min || 1) * 100;
4874
+ for (const each of series) each.y = y;
4875
+ return y;
4876
+ }
4877
+ /** Each row sits in the middle of an equal band, beside its label. */
4878
+ _x(index) {
4879
+ const x = (index + 0.5) / this.labels.length * 100;
4880
+ return this._reversed ? 100 - x : x;
4881
+ }
4882
+ _render() {
4883
+ const type = this._type;
4884
+ this._plot = document.createElement("div");
4885
+ this._plot.className = `${type}-plot`;
4886
+ const caption = this.el.querySelector(":scope > figcaption")?.textContent.trim();
4887
+ this._plot.setAttribute("role", "group");
4888
+ this._plot.setAttribute("aria-roledescription", type.replace(/-/g, " "));
4889
+ if (caption) this._plot.setAttribute("aria-label", caption);
4890
+ this._cursor = document.createElement("div");
4891
+ this._cursor.className = `${type}-cursor`;
4892
+ this._cursor.hidden = true;
4893
+ this._tooltip = document.createElement("div");
4894
+ this._tooltip.className = `${type}-tooltip`;
4895
+ this._tooltip.hidden = true;
4896
+ this._tooltip.setAttribute("aria-live", "polite");
4897
+ this._plot.append(this._draw(), this._cursor, this._tooltip);
4898
+ this._generated = [this._plot, ...this._guides()];
4899
+ this.el.append(...this._generated);
4900
+ }
4901
+ /**
4902
+ * The <tfoot> row as a total to show over the plot, its value over its
4903
+ * label, or null without one.
4904
+ */
4905
+ _total(className) {
4906
+ const footer = this.el.querySelector(":scope > table")?.tFoot?.rows[0];
4907
+ if (!footer) return null;
4908
+ const total = document.createElement("div");
4909
+ total.className = className;
4910
+ const [label, value] = [document.createElement("span"), document.createElement("strong")];
4911
+ label.textContent = footer.cells[0]?.textContent.trim() ?? "";
4912
+ value.textContent = footer.cells[1]?.textContent.trim() ?? "";
4913
+ total.append(value, label);
4914
+ return total;
4915
+ }
4916
+ /** The parts below the plot: the row labels and the legend. */
4917
+ _guides() {
4918
+ const labels = document.createElement("ol");
4919
+ labels.className = `${this._type}-labels`;
4920
+ labels.setAttribute("aria-hidden", "true");
4921
+ for (const text of this.labels) {
4922
+ const li = document.createElement("li");
4923
+ li.textContent = text;
4924
+ labels.append(li);
4925
+ }
4926
+ return [labels, ...this._legend()];
4927
+ }
4928
+ /** The series legend, for more than one series. */
4929
+ _legend() {
4930
+ if (this.series.length < 2) return [];
4931
+ const legend = document.createElement("ul");
4932
+ legend.className = `${this._type}-legend`;
4933
+ legend.setAttribute("aria-hidden", "true");
4934
+ legend.append(...this.series.map((series, i) => {
4935
+ const li = document.createElement("li");
4936
+ li.dataset.series = String(i % COLORS + 1);
4937
+ li.textContent = series.name;
4938
+ return li;
4939
+ }));
4940
+ return [legend];
4941
+ }
4942
+ /** Draws the series, which fill the plot. */
4943
+ _draw(drawn = this.series) {
4944
+ const svg = document.createElementNS(SVG, "svg");
4945
+ svg.setAttribute("viewBox", "0 0 100 100");
4946
+ svg.setAttribute("preserveAspectRatio", "none");
4947
+ svg.setAttribute("aria-hidden", "true");
4948
+ svg.setAttribute("focusable", "false");
4949
+ for (const series of drawn) {
4950
+ const group = document.createElementNS(SVG, "g");
4951
+ group.setAttribute("class", `${this._type}-series ${series.className}`.trim());
4952
+ group.dataset.series = String(this.series.indexOf(series) % COLORS + 1);
4953
+ const runs = [[]];
4954
+ series.tops.forEach((value, index) => {
4955
+ if (value === null) runs.push([]);
4956
+ else runs[runs.length - 1].push(index);
4957
+ });
4958
+ const filled = runs.filter((run) => run.length);
4959
+ const points = (run, values) => run.map((index) => [this._x(index), series.y(values[index])]);
4960
+ const tops = filled.map((run) => monotonePath(points(run, series.tops)));
4961
+ const id = `line-chart-gradient-${++_gradientId}`;
4962
+ const gradient = document.createElementNS(SVG, "linearGradient");
4963
+ gradient.id = id;
4964
+ gradient.setAttribute("x2", "0");
4965
+ gradient.setAttribute("y2", "1");
4966
+ gradient.append(document.createElementNS(SVG, "stop"), document.createElementNS(SVG, "stop"));
4967
+ gradient.lastElementChild.setAttribute("offset", "1");
4968
+ const area = document.createElementNS(SVG, "path");
4969
+ area.setAttribute("class", `${this._type}-area`);
4970
+ area.setAttribute("fill", `url(#${id})`);
4971
+ area.setAttribute("d", filled.map((run, r) => {
4972
+ if (!series.base) return `${tops[r]}V100H${this._x(run[0])}Z`;
4973
+ const base = points(run, series.base).map(([x, y]) => [x, Math.min(100, y)]);
4974
+ return `${tops[r]}L${monotonePath(base.reverse()).slice(1)}Z`;
4975
+ }).join(""));
4976
+ const line = document.createElementNS(SVG, "path");
4977
+ line.setAttribute("class", `${this._type}-line`);
4978
+ line.setAttribute("d", tops.join(""));
4979
+ group.append(gradient, area, line);
4980
+ svg.append(group);
4981
+ }
4982
+ return svg;
4983
+ }
4984
+ /** The row under the pointer, or -1. */
4985
+ _indexAt(e) {
4986
+ const box = this._plot.getBoundingClientRect();
4987
+ const fraction = this._horizontal ? (e.clientY - box.top) / box.height : (e.clientX - box.left) / box.width;
4988
+ const index = Math.floor((this._reversed ? 1 - fraction : fraction) * this.labels.length);
4989
+ return Math.min(this.labels.length - 1, Math.max(0, index));
4990
+ }
4991
+ /**
4992
+ * For a circular chart, the pointer's position from the plot's middle: a
4993
+ * fraction of a turn clockwise from the top, and its distance as a share
4994
+ * of the plot's width.
4995
+ */
4996
+ _polarAt(e) {
4997
+ const box = this._plot.getBoundingClientRect();
4998
+ const [x, y] = [e.clientX - box.left - box.width / 2, e.clientY - box.top - box.height / 2];
4999
+ return [(Math.atan2(x, -y) / (2 * Math.PI) + 1) % 1, Math.hypot(x, y) / box.width];
5000
+ }
5001
+ /** The keys that move to the next row and to the one before. */
5002
+ get _arrows() {
5003
+ if (this._circular) return [["ArrowRight", "ArrowDown"], ["ArrowLeft", "ArrowUp"]];
5004
+ if (this._horizontal) return [["ArrowDown"], ["ArrowUp"]];
5005
+ return this._reversed ? [["ArrowLeft"], ["ArrowRight"]] : [["ArrowRight"], ["ArrowLeft"]];
5006
+ }
5007
+ /** The index a key moves to, or undefined for a key the chart ignores. */
5008
+ _keyTarget(key) {
5009
+ const last = this.labels.length - 1;
5010
+ const [forward, back] = this._arrows;
5011
+ const step = (by) => {
5012
+ if (!this._circular) return Math.min(last, Math.max(0, this.activeIndex + by));
5013
+ if (this.activeIndex < 0) return by > 0 ? 0 : last;
5014
+ return (this.activeIndex + by + last + 1) % (last + 1);
5015
+ };
5016
+ if (key === "Escape") return this.activeIndex < 0 ? void 0 : -1;
5017
+ return forward.includes(key) ? step(1) : back.includes(key) ? step(-1) : { Home: 0, End: last }[key];
5018
+ }
5019
+ };
5020
+
5021
+ // src/ts/components/columnChart.ts
5022
+ var ColumnChart = class extends LineChart {
5023
+ get _type() {
5024
+ return "column-chart";
5025
+ }
5026
+ get _key() {
5027
+ return "Expressive_ColumnChart";
5028
+ }
5029
+ get _fromZero() {
5030
+ return true;
5031
+ }
5032
+ get _points() {
5033
+ return false;
5034
+ }
5035
+ static init(els, options = {}) {
5036
+ return super.init(els, options);
5037
+ }
5038
+ static getInstance(el) {
5039
+ return el["Expressive_ColumnChart"];
5040
+ }
5041
+ _draw(drawn = this.series) {
5042
+ const columns = document.createElement("div");
5043
+ columns.className = "column-chart-columns";
5044
+ columns.setAttribute("aria-hidden", "true");
5045
+ const clamp = (value, y = drawn[0]?.y ?? this._y) => Math.min(100, Math.max(0, y(value)));
5046
+ columns.style.setProperty("--zero", `${clamp(0)}%`);
5047
+ this._plot.style.setProperty("--rows", String(this.labels.length));
5048
+ this.labels.forEach((_, index) => {
5049
+ const row = document.createElement("div");
5050
+ drawn.forEach((series) => {
5051
+ const to = series.tops[index];
5052
+ const from = series.base?.[index] ?? 0;
5053
+ if (series.base && (to === null || from === null)) return;
5054
+ const column = document.createElement("span");
5055
+ if (to !== null) {
5056
+ column.className = series.className;
5057
+ column.dataset.series = String(this.series.indexOf(series) % COLORS + 1);
5058
+ column.classList.toggle("negative", !series.base && to < from);
5059
+ column.style.setProperty("--top", `${clamp(Math.max(from, to), series.y)}%`);
5060
+ column.style.setProperty("--bottom", `${100 - clamp(Math.min(from, to), series.y)}%`);
5061
+ if (this._horizontal) column.dataset.text = series.text[index];
5062
+ }
5063
+ row.append(column);
5064
+ });
5065
+ columns.append(row);
5066
+ });
5067
+ return columns;
5068
+ }
5069
+ };
5070
+
5071
+ // src/ts/components/barChart.ts
5072
+ var BarChart = class extends ColumnChart {
5073
+ get _type() {
5074
+ return "bar-chart";
5075
+ }
5076
+ get _key() {
5077
+ return "Expressive_BarChart";
5078
+ }
5079
+ get _horizontal() {
5080
+ return true;
5081
+ }
5082
+ static init(els, options = {}) {
5083
+ return super.init(els, options);
5084
+ }
5085
+ static getInstance(el) {
5086
+ return el["Expressive_BarChart"];
5087
+ }
5088
+ /**
5089
+ * The column chart's spans; CSS reads their --top and --bottom across the
5090
+ * plot. With .values, a copy of the bars without their clipping holds each
5091
+ * cell's text, so text longer than a short bar can run past its end.
5092
+ */
5093
+ _draw() {
5094
+ const bars = super._draw();
5095
+ bars.className = "bar-chart-bars";
5096
+ bars.classList.toggle("rtl", getComputedStyle(this.el).direction === "rtl");
5097
+ if (!this.el.classList.contains("values")) return bars;
5098
+ const values = bars.cloneNode(true);
5099
+ values.classList.replace("bar-chart-bars", "bar-chart-values");
5100
+ values.querySelectorAll("span[data-text]").forEach((span) => {
5101
+ span.textContent = span.dataset.text;
5102
+ });
5103
+ const layers = document.createElement("div");
5104
+ layers.className = "bar-chart-layers";
5105
+ layers.append(bars, values);
5106
+ return layers;
5107
+ }
5108
+ };
5109
+
5110
+ // src/ts/components/pieChart.ts
5111
+ var COLORS2 = 6;
5112
+ var RADIUS = 46;
5113
+ var GROWN = 1.06;
5114
+ var LABEL_SHARE = 0.05;
5115
+ var _maskId = 0;
5116
+ var PieChart = class extends LineChart {
5117
+ get _type() {
5118
+ return "pie-chart";
5119
+ }
5120
+ get _key() {
5121
+ return "Expressive_PieChart";
5122
+ }
5123
+ get _points() {
5124
+ return false;
5125
+ }
5126
+ get _circular() {
5127
+ return true;
5128
+ }
5129
+ static init(els, options = {}) {
5130
+ return super.init(els, options);
5131
+ }
5132
+ static getInstance(el) {
5133
+ return el["Expressive_PieChart"];
5134
+ }
5135
+ show(index) {
5136
+ if (!this._tooltip || index === this.activeIndex) return;
5137
+ super.show(index);
5138
+ this._slices.forEach((slice, i) => slice?.classList.toggle("active", i === index));
5139
+ if (this._tooltip.hidden) return;
5140
+ const color = this._colors[index];
5141
+ this._tooltip.querySelectorAll("[data-series]").forEach((span) => {
5142
+ if (color) span.dataset.series = color;
5143
+ else delete span.dataset.series;
5144
+ });
5145
+ const share = this._share(index);
5146
+ const value = this._tooltip.querySelector("span:nth-of-type(2)");
5147
+ if (share && value) value.textContent += ` (${share})`;
5148
+ }
5149
+ /** CSS places the tooltip on the slice's middle from --turn. */
5150
+ _place(index) {
5151
+ const middle = (this._turns[index][0] + this._turns[index][1]) / 2;
5152
+ this._tooltip.style.setProperty("--turn", String(middle));
5153
+ this._tooltip.classList.toggle("end", middle < 0.5);
5154
+ }
5155
+ /** The row's share of the whole as a percentage, or '' for none. */
5156
+ _share(index) {
5157
+ const [start, end] = this._turns[index];
5158
+ if (end <= start) return "";
5159
+ const share = end - start;
5160
+ const options = { style: "percent", maximumFractionDigits: share < 0.1 ? 1 : 0 };
5161
+ try {
5162
+ return share.toLocaleString(this.el.closest("[lang]")?.lang || void 0, options);
5163
+ } catch {
5164
+ return share.toLocaleString(void 0, options);
5165
+ }
5166
+ }
5167
+ /** The legend: every row with its color, its cell and its share. */
5168
+ _guides() {
5169
+ const legend = document.createElement("ul");
5170
+ legend.className = "pie-chart-legend";
5171
+ legend.setAttribute("aria-hidden", "true");
5172
+ const text = this.series[0].text;
5173
+ legend.append(...this.labels.map((label, i) => {
5174
+ const li = document.createElement("li");
5175
+ if (this._colors[i]) li.dataset.series = this._colors[i];
5176
+ const [name, value, share] = [label, text[i], this._share(i)].map((content) => {
5177
+ const span = document.createElement("span");
5178
+ span.textContent = content;
5179
+ return span;
5180
+ });
5181
+ li.append(name, value, share);
5182
+ return li;
5183
+ }));
5184
+ return [legend];
5185
+ }
5186
+ /** The row under the pointer by its angle from the middle, or -1 off the slices. */
5187
+ _indexAt(e) {
5188
+ const [turn, share] = this._polarAt(e);
5189
+ const distance = share * 100;
5190
+ const hole = this.el.classList.contains("donut") ? parseFloat(getComputedStyle(this.el).getPropertyValue("--md-comp-pie-chart-hole")) || 0 : 0;
5191
+ if (distance < RADIUS * hole) return -1;
5192
+ const index = this._turns.findIndex(([start, end]) => start <= turn && turn < end);
5193
+ return distance > RADIUS * (index === this.activeIndex ? GROWN : 1) ? -1 : index;
5194
+ }
5195
+ /**
5196
+ * An SVG of slices in a -50 to 50 box, under a mask that cuts the gaps
5197
+ * between them and the donut's hole, which CSS sizes. The values and the
5198
+ * total are HTML over it.
5199
+ */
5200
+ _draw() {
5201
+ const values = this.series[0].values.map((value) => Math.max(0, value ?? 0));
5202
+ const total = values.reduce((sum, value) => sum + value, 0);
5203
+ let turn = 0;
5204
+ this._turns = values.map((value) => [turn, turn += total ? value / total : 0]);
5205
+ const drawn = this._turns.filter(([start, end]) => end > start).length;
5206
+ let slice = 0;
5207
+ this._colors = this._turns.map(([start, end]) => {
5208
+ if (end <= start) return void 0;
5209
+ const index = slice++;
5210
+ return String(index === drawn - 1 && index > 0 && index % COLORS2 === 0 ? 2 : index % COLORS2 + 1);
5211
+ });
5212
+ const at = (t, r = RADIUS) => {
5213
+ const angle = t * 2 * Math.PI;
5214
+ return `${(r * Math.sin(angle)).toFixed(3)},${(-r * Math.cos(angle)).toFixed(3)}`;
5215
+ };
5216
+ const pie = document.createElement("div");
5217
+ pie.className = "pie-chart-pie";
5218
+ pie.setAttribute("aria-hidden", "true");
5219
+ const svg = svgNode("svg", { viewBox: "-50 -50 100 100", focusable: "false" });
5220
+ const id = `pie-chart-mask-${++_maskId}`;
5221
+ const area = { x: "-50", y: "-50", width: "100", height: "100" };
5222
+ const mask = svgNode("mask", { id, maskUnits: "userSpaceOnUse", ...area });
5223
+ const edges = this._turns.filter(([start, end]) => end > start);
5224
+ mask.append(
5225
+ svgNode("rect", { ...area, fill: "white" }),
5226
+ svgNode("path", {
5227
+ class: "pie-chart-gaps",
5228
+ stroke: "black",
5229
+ d: edges.length > 1 ? edges.map(([start]) => `M0,0L${at(start, 50)}`).join("") : ""
5230
+ }),
5231
+ svgNode("circle", { class: "pie-chart-hole", r: String(RADIUS), fill: "black" })
5232
+ );
5233
+ const slices = svgNode("g", { mask: `url(#${id})` });
5234
+ const arc = `A${RADIUS},${RADIUS} 0`;
5235
+ this._slices = this._turns.map(([start, end], i) => {
5236
+ if (end <= start) return null;
5237
+ const slice2 = svgNode("path", {
5238
+ class: "pie-chart-slice",
5239
+ "data-series": this._colors[i],
5240
+ // A whole circle is two half arcs; one arc cannot end where it starts.
5241
+ d: end - start >= 1 ? `M0,${-RADIUS}${arc} 1 1 0,${RADIUS}${arc} 1 1 0,${-RADIUS}Z` : `M0,0L${at(start)}${arc} ${end - start > 0.5 ? 1 : 0} 1 ${at(end)}Z`
5242
+ });
5243
+ slices.append(slice2);
5244
+ return slice2;
5245
+ });
5246
+ svg.append(mask, slices);
5247
+ pie.append(svg);
5248
+ if (this.el.classList.contains("values")) {
5249
+ const labels = document.createElement("div");
5250
+ labels.className = "pie-chart-values";
5251
+ this._turns.forEach(([start, end], i) => {
5252
+ if (end - start < LABEL_SHARE) return;
5253
+ const label = document.createElement("span");
5254
+ label.dataset.series = this._colors[i];
5255
+ label.style.setProperty("--turn", String((start + end) / 2));
5256
+ label.textContent = this._share(i);
5257
+ labels.append(label);
5258
+ });
5259
+ pie.append(labels);
5260
+ }
5261
+ const middle = this._total("pie-chart-total");
5262
+ if (middle) pie.append(middle);
5263
+ return pie;
5264
+ }
5265
+ };
5266
+
5267
+ // src/ts/components/heatmapChart.ts
5268
+ var HeatmapChart = class extends LineChart {
5269
+ get _type() {
5270
+ return "heatmap-chart";
5271
+ }
5272
+ get _key() {
5273
+ return "Expressive_HeatmapChart";
5274
+ }
5275
+ static init(els, options = {}) {
5276
+ return super.init(els, options);
5277
+ }
5278
+ static getInstance(el) {
5279
+ return el["Expressive_HeatmapChart"];
5280
+ }
5281
+ /** Highlights one cell, numbered row by row. Pass -1 to hide the tooltip. */
5282
+ show(index) {
5283
+ if (!this._tooltip || index === this.activeIndex) return;
5284
+ this._cells[this.activeIndex]?.classList.remove("active");
5285
+ this.activeIndex = index;
5286
+ const cell = this._cells[index];
5287
+ this._tooltip.hidden = !cell;
5288
+ if (!cell) return;
5289
+ cell.classList.add("active");
5290
+ const columns = this.series.length;
5291
+ const [row, series] = [Math.floor(index / columns), this.series[index % columns]];
5292
+ const title = document.createElement("strong");
5293
+ title.textContent = this.labels[row];
5294
+ const [name, text] = [document.createElement("span"), document.createElement("span")];
5295
+ name.textContent = series.name;
5296
+ if (this._levels[index] === null) name.className = "gap";
5297
+ else name.style.setProperty("--level", String(this._levels[index]));
5298
+ text.textContent = series.text[row];
5299
+ this._tooltip.replaceChildren(title, name, text);
5300
+ const end = cell.offsetLeft + cell.offsetWidth / 2 > this._plot.clientWidth / 2;
5301
+ this._tooltip.classList.toggle("end", end);
5302
+ this._tooltip.style.left = `${cell.offsetLeft + (end ? 0 : cell.offsetWidth)}px`;
5303
+ this._tooltip.style.top = `${cell.offsetTop}px`;
5304
+ }
5305
+ /** Arrows move the way they point, so left and right swap in RTL. */
5306
+ _keyTarget(key) {
5307
+ if (key === "Escape") return this.activeIndex < 0 ? void 0 : -1;
5308
+ const [columns, last] = [this.series.length, this.labels.length - 1];
5309
+ const index = Math.max(0, this.activeIndex);
5310
+ const [row, column] = [Math.floor(index / columns), index % columns];
5311
+ const [next, back] = this._reversed ? ["ArrowLeft", "ArrowRight"] : ["ArrowRight", "ArrowLeft"];
5312
+ const to = {
5313
+ [next]: [row, column + 1],
5314
+ [back]: [row, column - 1],
5315
+ ArrowDown: [row + 1, column],
5316
+ ArrowUp: [row - 1, column],
5317
+ Home: [row, 0],
5318
+ End: [row, columns - 1]
5319
+ }[key];
5320
+ if (!to) return void 0;
5321
+ if (this.activeIndex < 0) return 0;
5322
+ const clamp = (value, max) => Math.min(max, Math.max(0, value));
5323
+ return clamp(to[0], last) * columns + clamp(to[1], columns - 1);
5324
+ }
5325
+ /**
5326
+ * The cell under the pointer by its position, or -1 off the grid. A touch
5327
+ * keeps its first target while it drags, so the target cannot be used. The
5328
+ * cells and gaps are even, so each band's edge falls in a gap and a gap
5329
+ * counts as the nearer cell.
5330
+ */
5331
+ _indexAt(e) {
5332
+ const [columns, rows] = [this.series.length, this.labels.length];
5333
+ const first = this._cells[0].getBoundingClientRect();
5334
+ const last = this._cells[this._cells.length - 1].getBoundingClientRect();
5335
+ const [left, right] = [Math.min(first.left, last.left), Math.max(first.right, last.right)];
5336
+ const [x, y] = [e.clientX, e.clientY];
5337
+ if (x < left || x > right || y < first.top || y > last.bottom) return -1;
5338
+ const band = (offset, size, count) => Math.min(count - 1, Math.floor(offset / (size || 1) * count));
5339
+ const column = band(x - left, right - left, columns);
5340
+ return band(y - first.top, last.bottom - first.top, rows) * columns + (this._reversed ? columns - 1 - column : column);
5341
+ }
5342
+ /**
5343
+ * The legend: a gradient from the lowest cell's shade to the highest's,
5344
+ * labelled with their text. A fixed scale wider than the data shows only
5345
+ * the part the data uses.
5346
+ */
5347
+ _guides() {
5348
+ const legend = document.createElement("div");
5349
+ legend.className = "heatmap-chart-legend";
5350
+ legend.setAttribute("aria-hidden", "true");
5351
+ const [low, scale, high] = ["span", "span", "span"].map((tag) => document.createElement(tag));
5352
+ [low.textContent, high.textContent] = this._ends;
5353
+ scale.className = "heatmap-chart-scale";
5354
+ if (this._reversed) scale.style.setProperty("--heatmap-chart-to", "left");
5355
+ scale.style.setProperty("--low", String(this._endLevels[0]));
5356
+ scale.style.setProperty("--high", String(this._endLevels[1]));
5357
+ legend.append(low, scale, high);
5358
+ return [legend];
5359
+ }
5360
+ /**
5361
+ * A grid of the row headers and cells, row by row, with the column headers
5362
+ * below. Each cell's --level shades it in CSS.
5363
+ */
5364
+ _draw() {
5365
+ const columns = this.series.length;
5366
+ let [low, high] = [Infinity, -Infinity];
5367
+ this._ends = ["", ""];
5368
+ this.series.forEach((series) => series.values.forEach((value, row) => {
5369
+ if (value === null) return;
5370
+ if (value < low) [low, this._ends[0]] = [value, series.text[row]];
5371
+ if (value > high) [high, this._ends[1]] = [value, series.text[row]];
5372
+ }));
5373
+ const option = (name) => {
5374
+ const value = parseFloat(this.el.dataset[name] ?? "");
5375
+ return Number.isFinite(value) ? value : this.options[name];
5376
+ };
5377
+ const [min, max] = [option("min") ?? low, option("max") ?? high];
5378
+ const shade = (value) => Math.min(1, Math.max(0, (value - min) / (max - min || 1)));
5379
+ this._endLevels = [shade(low), shade(high)];
5380
+ const grid = document.createElement("div");
5381
+ grid.className = "heatmap-chart-grid";
5382
+ grid.setAttribute("aria-hidden", "true");
5383
+ grid.style.setProperty("--columns", String(columns));
5384
+ grid.style.setProperty("--rows", String(this.labels.length));
5385
+ const span = (className, text = "") => {
5386
+ const element = document.createElement("span");
5387
+ element.className = className;
5388
+ element.textContent = text;
5389
+ return element;
5390
+ };
5391
+ const label = (cell) => cell?.dataset.label ?? cell?.textContent.trim() ?? "";
5392
+ const values = this.el.classList.contains("values");
5393
+ this._cells = [];
5394
+ this._levels = [];
5395
+ this.labels.forEach((_, row) => {
5396
+ grid.append(span("heatmap-chart-row", label(this._rows[row].cells[0])));
5397
+ this.series.forEach((series) => {
5398
+ const value = series.values[row];
5399
+ const level = value === null ? null : shade(value);
5400
+ const cell = span("heatmap-chart-cell", values ? series.text[row] : "");
5401
+ if (level === null) cell.classList.add("gap");
5402
+ else cell.style.setProperty("--level", String(level));
5403
+ this._cells.push(cell);
5404
+ this._levels.push(level);
5405
+ grid.append(cell);
5406
+ });
5407
+ });
5408
+ const labels = this._header.slice(1, columns + 1).map(label);
5409
+ labels.forEach((text, column) => {
5410
+ if (!text) return;
5411
+ let end = column + 1;
5412
+ while (end < columns && !labels[end]) end++;
5413
+ const element = span("heatmap-chart-column", text);
5414
+ element.classList.toggle("run", end > column + 1);
5415
+ element.style.gridArea = `${this.labels.length + 1} / ${column + 2} / auto / ${end + 2}`;
5416
+ grid.append(element);
5417
+ });
5418
+ return grid;
5419
+ }
5420
+ };
5421
+
5422
+ // src/ts/components/radarChart.ts
5423
+ var RINGS = 4;
5424
+ var RadarChart = class extends LineChart {
5425
+ get _type() {
5426
+ return "radar-chart";
5427
+ }
5428
+ get _key() {
5429
+ return "Expressive_RadarChart";
5430
+ }
5431
+ /** The scale starts at zero, the middle, so shapes compare by size. */
5432
+ get _fromZero() {
5433
+ return true;
5434
+ }
5435
+ get _circular() {
5436
+ return true;
5437
+ }
5438
+ static init(els, options = {}) {
5439
+ return super.init(els, options);
5440
+ }
5441
+ static getInstance(el) {
5442
+ return el["Expressive_RadarChart"];
5443
+ }
5444
+ /**
5445
+ * The cursor is a spoke turned by --turn, and the tooltip sits past its
5446
+ * tip. LineChart's points on the spoke keep their top, a share of the
5447
+ * radius, which stops at the rim and the middle like the shapes.
5448
+ */
5449
+ _place(index) {
5450
+ for (const dot of this._cursor.children) {
5451
+ dot.style.top = `${Math.min(100, Math.max(0, parseFloat(dot.style.top)))}%`;
5452
+ }
5453
+ const turn = this._turn(index);
5454
+ this._cursor.style.setProperty("--turn", String(turn));
5455
+ this._tooltip.style.setProperty("--turn", String(turn));
5456
+ this._tooltip.classList.toggle("end", turn > 0 && turn < 0.5);
5457
+ }
5458
+ /** A row's spoke as a fraction of a turn clockwise from the top. */
5459
+ _turn(index) {
5460
+ return index / this.labels.length;
5461
+ }
5462
+ /** Only the legend; the labels go round the rim in _draw. */
5463
+ _guides() {
5464
+ return this._legend();
5465
+ }
5466
+ /** The nearest spoke to the pointer, by its angle from the middle. */
5467
+ _indexAt(e) {
5468
+ const [turn] = this._polarAt(e);
5469
+ return Math.round(turn * this.labels.length) % this.labels.length;
5470
+ }
5471
+ /**
5472
+ * An SVG in a -50 to 50 box: the rings and spokes of the grid, then each
5473
+ * series' shape, with its points for .points. The rim is the box's edge;
5474
+ * the labels round it are HTML.
5475
+ */
5476
+ _draw() {
5477
+ const at = (index, share2) => {
5478
+ const angle = this._turn(index) * 2 * Math.PI;
5479
+ return `${(50 * share2 * Math.sin(angle)).toFixed(3)},${(-50 * share2 * Math.cos(angle)).toFixed(3)}`;
5480
+ };
5481
+ const share = (value) => Math.min(1, Math.max(0, 1 - this._y(value) / 100));
5482
+ const rows = this.labels.map((_, i) => i);
5483
+ const ring = (r) => `M${rows.map((i) => at(i, r)).join("L")}Z`;
5484
+ const svg = svgNode("svg", { viewBox: "-50 -50 100 100", "aria-hidden": "true", focusable: "false" });
5485
+ svg.append(svgNode("path", {
5486
+ class: "radar-chart-grid",
5487
+ d: Array.from({ length: RINGS }, (_, r) => ring((r + 1) / RINGS)).join("") + rows.map((i) => `M0,0L${at(i, 1)}`).join("")
5488
+ }));
5489
+ const points = this.el.classList.contains("points");
5490
+ this.series.forEach((series, s) => {
5491
+ const group = svgNode("g", {
5492
+ class: `radar-chart-series ${series.className}`.trim(),
5493
+ "data-series": String(s % COLORS + 1)
5494
+ });
5495
+ const drawn = rows.filter((i) => series.values[i] !== null);
5496
+ const corners = drawn.map((i) => at(i, share(series.values[i])));
5497
+ group.append(svgNode("path", { class: "radar-chart-shape", d: corners.length ? `M${corners.join("L")}Z` : "" }));
5498
+ if (points) group.append(svgNode("path", { class: "radar-chart-points", d: corners.map((c) => `M${c}h0`).join("") }));
5499
+ svg.append(group);
5500
+ });
5501
+ const labels = document.createElement("ol");
5502
+ labels.className = "radar-chart-axes";
5503
+ labels.setAttribute("aria-hidden", "true");
5504
+ this.labels.forEach((text, i) => {
5505
+ const li = document.createElement("li");
5506
+ li.textContent = text;
5507
+ li.style.setProperty("--turn", String(this._turn(i)));
5508
+ labels.append(li);
5509
+ });
5510
+ const web = document.createElement("div");
5511
+ web.className = "radar-chart-web";
5512
+ web.append(svg, labels);
5513
+ return web;
5514
+ }
5515
+ };
3793
5516
 
3794
- // src/ts/components/characterCounter.ts
3795
- var _defaults11 = Object.freeze({});
3796
- var CharacterCounter = class _CharacterCounter extends Component {
3797
- constructor(el, options) {
3798
- super(el, {}, _CharacterCounter);
3799
- this.updateCounter = () => {
3800
- const maxLength = parseInt(this.el.getAttribute("maxlength")), actualLength = this.el.value.length;
3801
- this.isValidLength = actualLength <= maxLength;
3802
- let counterString = actualLength.toString();
3803
- if (maxLength) {
3804
- counterString += "/" + maxLength;
3805
- this._validateInput();
3806
- }
3807
- this.counterEl.textContent = counterString;
3808
- };
3809
- this.el["Expressive_CharacterCounter"] = this;
3810
- this.options = {
3811
- ..._CharacterCounter.defaults,
3812
- ...options
3813
- };
3814
- this.isInvalid = false;
3815
- this.isValidLength = false;
3816
- this._setupCounter();
3817
- this._setupEventHandlers();
5517
+ // src/ts/components/radialChart.ts
5518
+ var RADIUS2 = 46;
5519
+ var FILL = 0.8;
5520
+ var RadialChart = class extends LineChart {
5521
+ get _type() {
5522
+ return "radial-chart";
3818
5523
  }
3819
- static get defaults() {
3820
- return _defaults11;
5524
+ get _key() {
5525
+ return "Expressive_RadialChart";
5526
+ }
5527
+ get _points() {
5528
+ return false;
5529
+ }
5530
+ get _circular() {
5531
+ return true;
5532
+ }
5533
+ get _gauge() {
5534
+ return this.el.classList.contains("gauge");
3821
5535
  }
3822
- /**
3823
- * Initializes instances of CharacterCounter.
3824
- * @param els HTML elements.
3825
- * @param options Component options.
3826
- */
3827
5536
  static init(els, options = {}) {
3828
- return super.init(els, options, _CharacterCounter);
5537
+ return super.init(els, options);
3829
5538
  }
3830
5539
  static getInstance(el) {
3831
- return el["Expressive_CharacterCounter"];
5540
+ return el["Expressive_RadialChart"];
5541
+ }
5542
+ show(index) {
5543
+ if (!this._tooltip || index === this.activeIndex) return;
5544
+ super.show(index);
5545
+ this._rings.forEach((ring, i) => ring.classList.toggle("active", i === index));
5546
+ if (this._tooltip.hidden) return;
5547
+ const color = this._color(index);
5548
+ this._tooltip.querySelectorAll("[data-series]").forEach((span) => {
5549
+ span.dataset.series = color;
5550
+ });
3832
5551
  }
3833
- destroy() {
3834
- this._removeEventHandlers();
3835
- this.el["Expressive_CharacterCounter"] = void 0;
3836
- this._removeCounter();
5552
+ /** The tooltip sits where the ring's fill ends. */
5553
+ _place(index) {
5554
+ const turn = this._turn(this._fractions[index]);
5555
+ const r = this._radii[index];
5556
+ const [x, y] = [r * Math.sin(turn * 2 * Math.PI), -r * Math.cos(turn * 2 * Math.PI)];
5557
+ this._tooltip.style.left = `${50 + x}%`;
5558
+ this._tooltip.style.top = `${(50 + y) / (this._gauge ? 50 : 100) * 100}%`;
5559
+ this._tooltip.classList.toggle("end", x > 0);
5560
+ }
5561
+ _color(index) {
5562
+ return String(index % COLORS + 1);
5563
+ }
5564
+ /** A fraction of the ring as a turn clockwise from the top. */
5565
+ _turn(fraction) {
5566
+ return this._gauge ? fraction / 2 - 0.25 : fraction;
5567
+ }
5568
+ /** For more than one row, a legend of every row with its color and cell. */
5569
+ _guides() {
5570
+ if (this.labels.length < 2) return [];
5571
+ const legend = document.createElement("ul");
5572
+ legend.className = "radial-chart-legend";
5573
+ legend.setAttribute("aria-hidden", "true");
5574
+ const text = this.series[0].text;
5575
+ legend.append(...this.labels.map((label, i) => {
5576
+ const li = document.createElement("li");
5577
+ li.dataset.series = this._color(i);
5578
+ const [name, value] = [label, text[i]].map((content) => {
5579
+ const span = document.createElement("span");
5580
+ span.textContent = content;
5581
+ return span;
5582
+ });
5583
+ li.append(name, value);
5584
+ return li;
5585
+ }));
5586
+ return [legend];
5587
+ }
5588
+ /** The ring under the pointer by its distance from the middle, or -1. */
5589
+ _indexAt(e) {
5590
+ const box = this._plot.getBoundingClientRect();
5591
+ const [x, y] = [e.clientX - box.left - box.width / 2, e.clientY - box.top - box.width / 2];
5592
+ if (this._gauge && y > 0) return -1;
5593
+ const distance = Math.hypot(x, y) / box.width * 100;
5594
+ const step = this._radii.length > 1 ? this._radii[0] - this._radii[1] : this._width;
5595
+ return this._radii.findIndex((r) => Math.abs(distance - r) <= step / 2);
3837
5596
  }
3838
- _setupEventHandlers() {
3839
- this.el.addEventListener("focus", this.updateCounter, true);
3840
- this.el.addEventListener("input", this.updateCounter, true);
5597
+ /**
5598
+ * An SVG of rings in a -50 to 50 box, the top half for a gauge. Each row
5599
+ * is a track circle and a fill circle whose dash is its share; CSS draws
5600
+ * them from --radial-chart-dash. The total is HTML over it.
5601
+ */
5602
+ _draw() {
5603
+ const style = getComputedStyle(this.el);
5604
+ const token = (name, fallback) => {
5605
+ const value = parseFloat(style.getPropertyValue(`--md-comp-radial-chart-${name}`));
5606
+ return Number.isFinite(value) ? value : fallback;
5607
+ };
5608
+ const count = this.labels.length;
5609
+ let [width, gap] = [Math.max(0, token("thickness", 8)), Math.max(0, token("gap", 2))];
5610
+ const scale = Math.min(1, RADIUS2 * FILL / (count * width + (count - 1) * gap || 1));
5611
+ [width, gap] = [width * scale, gap * scale];
5612
+ this._width = width;
5613
+ this._radii = this.labels.map((_, i) => RADIUS2 - width / 2 - i * (width + gap));
5614
+ const max = [parseFloat(this.el.dataset.max ?? ""), this.options.max ?? NaN, 100].find((value) => value > 0);
5615
+ this._fractions = this.series[0].values.map((value) => Math.min(1, Math.max(0, (value ?? 0) / max)));
5616
+ const gauge = this._gauge;
5617
+ const svg = svgNode("svg", { viewBox: gauge ? "-50 -50 100 50" : "-50 -50 100 100", focusable: "false" });
5618
+ const transform = gauge ? "rotate(180)" : "rotate(-90)";
5619
+ const span = gauge ? 0.5 : 1;
5620
+ this._rings = this._radii.map((r, i) => {
5621
+ const length = 2 * Math.PI * r;
5622
+ const circle = (name, fraction, capped) => {
5623
+ const ring2 = svgNode("circle", { class: name, r: r.toFixed(3), transform, "stroke-width": width.toFixed(3) });
5624
+ const dash = Math.max(1e-3, fraction * span * length - (capped ? width : 0));
5625
+ ring2.style.setProperty("--radial-chart-dash", `${dash.toFixed(3)} ${length.toFixed(3)}`);
5626
+ if (capped) ring2.style.setProperty("--radial-chart-offset", (-width / 2).toFixed(3));
5627
+ return ring2;
5628
+ };
5629
+ const ring = circle("radial-chart-ring", this._fractions[i], true);
5630
+ ring.dataset.series = this._color(i);
5631
+ if (!this._fractions[i]) ring.classList.add("empty");
5632
+ svg.append(circle("radial-chart-track", 1, gauge), ring);
5633
+ return ring;
5634
+ });
5635
+ const rings = document.createElement("div");
5636
+ rings.className = "radial-chart-rings";
5637
+ rings.setAttribute("aria-hidden", "true");
5638
+ rings.style.setProperty("--radial-chart-middle", `${(this._radii[count - 1] - this._width / 2) * 2}%`);
5639
+ rings.append(svg);
5640
+ const total = this._total("radial-chart-total");
5641
+ if (total) rings.append(total);
5642
+ return rings;
3841
5643
  }
3842
- _removeEventHandlers() {
3843
- this.el.removeEventListener("focus", this.updateCounter, true);
3844
- this.el.removeEventListener("input", this.updateCounter, true);
5644
+ };
5645
+
5646
+ // src/ts/components/mixedChart.ts
5647
+ var has = (series, name) => series.className.split(/\s+/).includes(name);
5648
+ var MixedChart = class extends ColumnChart {
5649
+ get _type() {
5650
+ return "mixed-chart";
3845
5651
  }
3846
- _setupCounter() {
3847
- this.counterEl = document.createElement("span");
3848
- this.counterEl.classList.add("character-counter");
3849
- this.counterEl.setAttribute("aria-live", "polite");
3850
- this.counterEl.setAttribute("aria-atomic", "true");
3851
- this.counterEl.style.float = "right";
3852
- this.counterEl.style.fontSize = "12px";
3853
- this.counterEl.style.height = "1";
3854
- this.el.parentElement.appendChild(this.counterEl);
5652
+ get _key() {
5653
+ return "Expressive_MixedChart";
3855
5654
  }
3856
- _removeCounter() {
3857
- this.counterEl.remove();
5655
+ get _points() {
5656
+ return true;
3858
5657
  }
3859
- _validateInput() {
3860
- if (this.isValidLength && this.isInvalid) {
3861
- this.isInvalid = false;
3862
- this.el.classList.remove("invalid");
3863
- } else if (!this.isValidLength && !this.isInvalid) {
3864
- this.isInvalid = true;
3865
- this.el.classList.remove("valid");
3866
- this.el.classList.add("invalid");
3867
- }
5658
+ static init(els, options = {}) {
5659
+ return super.init(els, options);
5660
+ }
5661
+ static getInstance(el) {
5662
+ return el["Expressive_MixedChart"];
5663
+ }
5664
+ /** The start scale, then the end scale; each includes zero if it has columns. */
5665
+ _scale(series, min, max) {
5666
+ const end = series.filter((each) => has(each, "end"));
5667
+ const start = series.filter((each) => !end.includes(each));
5668
+ const columns = (list) => list.some((each) => has(each, "column"));
5669
+ super._scale(end, this._number("endMin"), this._number("endMax"), columns(end));
5670
+ return super._scale(start, min, max, columns(start));
5671
+ }
5672
+ /** Columns behind, then the lines and areas over them. */
5673
+ _draw() {
5674
+ const marks = document.createElement("div");
5675
+ marks.className = "mixed-chart-marks";
5676
+ const columns = super._draw(this.series.filter((series) => has(series, "column")));
5677
+ columns.className = "mixed-chart-columns";
5678
+ marks.append(columns, LineChart.prototype["_draw"].call(this, this.series.filter((series) => !has(series, "column"))));
5679
+ return marks;
3868
5680
  }
3869
5681
  };
3870
5682
 
3871
5683
  // src/ts/components/select.ts
3872
- var _defaults12 = {
5684
+ var _defaults15 = {
3873
5685
  classes: "",
3874
5686
  menuOptions: {}
3875
5687
  };
@@ -3951,7 +5763,7 @@ var FormSelect = class _FormSelect extends Component {
3951
5763
  this._setupEventHandlers();
3952
5764
  }
3953
5765
  static get defaults() {
3954
- return _defaults12;
5766
+ return _defaults15;
3955
5767
  }
3956
5768
  /**
3957
5769
  * Initializes instances of FormSelect.
@@ -4335,8 +6147,7 @@ var FormSelect = class _FormSelect extends Component {
4335
6147
  this._values.forEach((value) => this._deselectValue(value));
4336
6148
  }
4337
6149
  _isValueSelected(value) {
4338
- const realValues = this.getSelectedValues();
4339
- return realValues.some((realValue) => realValue === value.el.value);
6150
+ return value.el.selected;
4340
6151
  }
4341
6152
  _toggleEntryFromArray(value) {
4342
6153
  if (this._isValueSelected(value)) this._deselectValue(value);
@@ -4377,7 +6188,7 @@ var FormSelect = class _FormSelect extends Component {
4377
6188
  };
4378
6189
 
4379
6190
  // src/ts/plugins/dockedDisplayPlugin.ts
4380
- var _defaults13 = {
6191
+ var _defaults16 = {
4381
6192
  margin: 5,
4382
6193
  transition: 10,
4383
6194
  duration: 250,
@@ -4426,7 +6237,7 @@ var DockedDisplayPlugin = class _DockedDisplayPlugin {
4426
6237
  };
4427
6238
  this.el = el;
4428
6239
  this.options = {
4429
- ..._defaults13,
6240
+ ..._defaults16,
4430
6241
  ...options
4431
6242
  };
4432
6243
  this.container = document.createElement("div");
@@ -4447,7 +6258,7 @@ var DockedDisplayPlugin = class _DockedDisplayPlugin {
4447
6258
  };
4448
6259
 
4449
6260
  // src/ts/components/datepicker.ts
4450
- var _defaults14 = {
6261
+ var _defaults17 = {
4451
6262
  // the default output format for the input field value
4452
6263
  format: "mmm dd, yyyy",
4453
6264
  // Used to create date object from current input string
@@ -4561,9 +6372,7 @@ var _Datepicker = class _Datepicker extends Component {
4561
6372
  this._randId = null;
4562
6373
  this._focusedDate = null;
4563
6374
  this._handleInputClick = (e) => {
4564
- if (e.type == "date") {
4565
- e.preventDefault();
4566
- }
6375
+ if (e.target.type == "date" && (this.options.openByDefault || this.options.container)) e.preventDefault();
4567
6376
  this._batchDraws(() => {
4568
6377
  this.setDateFromInput(e.target);
4569
6378
  this.draw();
@@ -4673,26 +6482,8 @@ var _Datepicker = class _Datepicker extends Component {
4673
6482
  this.gotoYear(e.target.value);
4674
6483
  };
4675
6484
  this._handleInputChange = (e) => {
4676
- let date;
4677
- const el = e.target;
4678
6485
  if (e["detail"]?.firedBy === this) return;
4679
- if (el == this.endDateEl && !this.date) return;
4680
- if (this.options.parse) {
4681
- date = this.options.parse(
4682
- e.target.value,
4683
- typeof this.options.format === "function" ? this.options.format(new Date(this.el.value)) : this.options.format
4684
- );
4685
- } else {
4686
- date = _Datepicker._parseDate(e.target.value);
4687
- }
4688
- if (_Datepicker._isDate(date)) {
4689
- if (el == this.endDateEl && date < this.date) return this.setInputValue(el, this.endDate);
4690
- this.setDate(date, false, el == this.endDateEl, true);
4691
- if (e.type == "date") {
4692
- this.setDataDate(e, date);
4693
- this.setInputValues();
4694
- }
4695
- }
6486
+ this.setDateFromInput(e.target);
4696
6487
  };
4697
6488
  this._finishSelection = () => {
4698
6489
  this.setInputValues();
@@ -4724,7 +6515,7 @@ var _Datepicker = class _Datepicker extends Component {
4724
6515
  this._insertHTMLIntoDOM();
4725
6516
  this._setupEventHandlers();
4726
6517
  if (!this.options.defaultDate) {
4727
- this.options.defaultDate = _Datepicker._parseDate(this.el.value);
6518
+ this.options.defaultDate = this._parseInput(this.el.value);
4728
6519
  }
4729
6520
  const defDate = this.options.defaultDate;
4730
6521
  if (_Datepicker._isDate(defDate)) {
@@ -4752,7 +6543,7 @@ var _Datepicker = class _Datepicker extends Component {
4752
6543
  }
4753
6544
  }
4754
6545
  static get defaults() {
4755
- return _defaults14;
6546
+ return _defaults17;
4756
6547
  }
4757
6548
  /**
4758
6549
  * Initializes instances of Datepicker.
@@ -4783,6 +6574,13 @@ var _Datepicker = class _Datepicker extends Component {
4783
6574
  const date = /* @__PURE__ */ new Date(value + "T00:00:00");
4784
6575
  return date.getDate() === Number(value.slice(-2)) ? date : /* @__PURE__ */ new Date(NaN);
4785
6576
  }
6577
+ _parseInput(value) {
6578
+ if (!value.trim() || !this.options.parse) return _Datepicker._parseDate(value);
6579
+ return this.options.parse(
6580
+ value,
6581
+ typeof this.options.format === "string" ? this.options.format : ""
6582
+ );
6583
+ }
4786
6584
  static _isWeekend(date) {
4787
6585
  const day = date.getDay();
4788
6586
  return day === 0 || day === 6;
@@ -4892,8 +6690,22 @@ var _Datepicker = class _Datepicker extends Component {
4892
6690
  * Sets date from input field.
4893
6691
  */
4894
6692
  setDateFromInput(el) {
4895
- const date = _Datepicker._parseDate(el.value);
4896
- this.setDate(date, false, el == this.endDateEl, true);
6693
+ if (el.type === "date") this.setDataDate(el, _Datepicker._parseDate(el.value));
6694
+ if ((this.options.isDateRange || !this.options.isMultipleSelection) && !el.value.trim()) {
6695
+ this.setSingleDate(null, el === this.endDateEl);
6696
+ this.draw();
6697
+ return;
6698
+ }
6699
+ const parsedDate = this._parseInput(el.value);
6700
+ if (!_Datepicker._isDate(parsedDate)) return;
6701
+ const date = new Date(parsedDate.getTime());
6702
+ _Datepicker._setToStartOfDay(date);
6703
+ const isEndDate = el === this.endDateEl;
6704
+ if (this.options.isDateRange && (isEndDate ? this.date && date < this.date : this.endDate && date > this.endDate)) {
6705
+ this.setInputValue(el, isEndDate ? this.endDate : this.date);
6706
+ return;
6707
+ }
6708
+ this.setDate(date, false, isEndDate, true);
4897
6709
  }
4898
6710
  /**
4899
6711
  * Set a date on the datepicker.
@@ -4965,7 +6777,7 @@ var _Datepicker = class _Datepicker extends Component {
4965
6777
  * Sets the data-date attribute on the date input field
4966
6778
  */
4967
6779
  setDataDate(el, date) {
4968
- el.setAttribute("data-date", this.toString(date));
6780
+ el.setAttribute("data-date", _Datepicker._isDate(date) ? this.toString(date) : "");
4969
6781
  }
4970
6782
  /**
4971
6783
  * Sets dates on the input values.
@@ -4991,16 +6803,15 @@ var _Datepicker = class _Datepicker extends Component {
4991
6803
  */
4992
6804
  setInputValue(el, date) {
4993
6805
  if (!el) return;
6806
+ if (el.type == "date") this.setDataDate(el, date);
4994
6807
  if (!_Datepicker._isDate(date)) {
4995
6808
  el.value = "";
4996
- if (el.type === "date") el.setAttribute("data-date", "");
4997
6809
  } else if (el.type == "date") {
4998
- this.setDataDate(el, date);
4999
6810
  el.value = this.formatDate(date, "yyyy-mm-dd");
5000
6811
  } else {
5001
6812
  el.value = this.toString(date);
5002
6813
  }
5003
- this.el.dispatchEvent(
6814
+ el.dispatchEvent(
5004
6815
  new CustomEvent("change", {
5005
6816
  bubbles: true,
5006
6817
  cancelable: true,
@@ -5091,7 +6902,7 @@ var _Datepicker = class _Datepicker extends Component {
5091
6902
  cells += 7 - after;
5092
6903
  let isWeekSelected = false;
5093
6904
  for (let i = 0, r = 0; i < cells; i++) {
5094
- const day = new Date(year, month, 1 + (i - before)), isToday = _Datepicker._compareDates(day, now), hasEvent = this.options.events.indexOf(day.toDateString()) !== -1, isEmpty = i < before || i >= days + before, isStartRange = this.options.startRange && _Datepicker._compareDates(this.options.startRange, day), isEndRange = this.options.endRange && _Datepicker._compareDates(this.options.endRange, day), isInRange = this.options.startRange && this.options.endRange && this.options.startRange < day && day < this.options.endRange, isDisabled = this.options.minDate && day < this.options.minDate || this.options.maxDate && day > this.options.maxDate || this.options.disableWeekends && _Datepicker._isWeekend(day) || this.options.disableDayFn && this.options.disableDayFn(day), isDateRangeStart = this.options.isDateRange && this.date && this.endDate && _Datepicker._compareDates(this.date, day), isDateRangeEnd = this.options.isDateRange && this.endDate && _Datepicker._compareDates(this.endDate, day), isDateRange = this.options.isDateRange && _Datepicker._isDate(this.endDate) && _Datepicker._compareWithinRange(day, this.date, this.endDate);
6905
+ const day = new Date(year, month, 1 + (i - before)), isToday = _Datepicker._compareDates(day, now), hasEvent = this.options.events.indexOf(day.toDateString()) !== -1, isEmpty = i < before || i >= days + before, isStartRange = this.options.startRange && _Datepicker._compareDates(this.options.startRange, day), isEndRange = this.options.endRange && _Datepicker._compareDates(this.options.endRange, day), isInRange = this.options.startRange && this.options.endRange && this.options.startRange < day && day < this.options.endRange, isDisabled = this.options.minDate && day < this.options.minDate || this.options.maxDate && day > this.options.maxDate || this.options.disableWeekends && _Datepicker._isWeekend(day) || this.options.disableDayFn && this.options.disableDayFn(day), isDateRangeStart = this.options.isDateRange && this.date && this.endDate && _Datepicker._compareDates(this.date, day), isDateRangeEnd = this.options.isDateRange && this.endDate && _Datepicker._compareDates(this.endDate, day), isDateRange = this.options.isDateRange && _Datepicker._isDate(this.date) && _Datepicker._isDate(this.endDate) && _Datepicker._compareWithinRange(day, this.date, this.endDate);
5095
6906
  let dayNumber = 1 + (i - before), monthNumber = month, yearNumber = year;
5096
6907
  let isSelected = false;
5097
6908
  if (_Datepicker._isDate(this.date)) {
@@ -5526,6 +7337,7 @@ var _Datepicker = class _Datepicker extends Component {
5526
7337
  createDateInput() {
5527
7338
  const dateInput = this.el.cloneNode(true);
5528
7339
  dateInput.removeAttribute("id");
7340
+ dateInput.classList.add("no-autoinit");
5529
7341
  dateInput.addEventListener("click", this._handleInputClick);
5530
7342
  dateInput.addEventListener("keydown", this._handleInputKeydown);
5531
7343
  dateInput.addEventListener("change", this._handleInputChange);
@@ -5546,8 +7358,402 @@ _Datepicker._template = `
5546
7358
  </div>`;
5547
7359
  var Datepicker = _Datepicker;
5548
7360
 
7361
+ // src/ts/components/messageScroller.ts
7362
+ var _defaults18 = {
7363
+ autoScroll: true,
7364
+ defaultScrollPosition: "end",
7365
+ peek: 64
7366
+ };
7367
+ var EDGE = 2;
7368
+ var UP_KEYS = /* @__PURE__ */ new Set(["ArrowUp", "PageUp", "Home"]);
7369
+ var RELEASE_EVENTS = ["pointerup", "pointercancel", "touchend", "touchcancel"];
7370
+ var MessageScroller = class _MessageScroller extends Component {
7371
+ constructor(el, options) {
7372
+ super(el, options, _MessageScroller);
7373
+ this._opened = false;
7374
+ this._following = false;
7375
+ /** A press is down: it may be a tap or a scrollbar drag. */
7376
+ this._holding = false;
7377
+ this._lastTop = 0;
7378
+ /** Where a programmatic scroll is heading; null when none is running. */
7379
+ this._target = null;
7380
+ /** Distance left to the target at the last scroll event. */
7381
+ this._distance = Infinity;
7382
+ /** The first visible message and its offset from the viewport top. */
7383
+ this._ref = null;
7384
+ this._refOffset = 0;
7385
+ /** The latest anchored turn, which the spacer lets reach the top. */
7386
+ this._anchor = null;
7387
+ this._spacer = 0;
7388
+ this._last = null;
7389
+ this._resizeObserver = null;
7390
+ this._mutationObserver = null;
7391
+ // A programmatic scroll ends when it arrives, or when a scroll moves away
7392
+ // from its target because something else took over.
7393
+ this._handleScroll = () => {
7394
+ if (this._target !== null) {
7395
+ const distance = Math.abs(this.viewport.scrollTop - this._resolve(this._target));
7396
+ if (distance > EDGE && distance < this._distance) {
7397
+ this._distance = distance;
7398
+ return;
7399
+ }
7400
+ this._target = null;
7401
+ }
7402
+ this._settle();
7403
+ };
7404
+ this._handleUserScroll = (e) => {
7405
+ if (e.type === "wheel" && e.deltaY >= 0) return;
7406
+ if (e.type === "keydown" && !UP_KEYS.has(e.key)) return;
7407
+ this._target = null;
7408
+ if (e.type === "pointerdown" || e.type === "touchstart") this._holding = true;
7409
+ else this._following = false;
7410
+ };
7411
+ this._handleRelease = () => {
7412
+ if (!this._holding) return;
7413
+ this._holding = false;
7414
+ if (this._following) this.viewport.scrollTop = this.viewport.scrollHeight;
7415
+ this._sync();
7416
+ };
7417
+ this._handleButtonClick = () => this.scrollToEnd();
7418
+ this._handleMutations = (records) => {
7419
+ for (const record of records) {
7420
+ record.addedNodes.forEach((node) => {
7421
+ if (node instanceof Element) this._resizeObserver?.observe(node);
7422
+ });
7423
+ record.removedNodes.forEach((node) => {
7424
+ if (node instanceof Element) this._resizeObserver?.unobserve(node);
7425
+ });
7426
+ }
7427
+ const previous = this._last;
7428
+ this._last = this.viewport.lastElementChild;
7429
+ if (!this._opened) return;
7430
+ if (!this._following && this._target === null) this._restore();
7431
+ if (!this.options.autoScroll) return;
7432
+ if (previous && previous.parentElement !== this.viewport) return;
7433
+ for (let item = this._last; item && item !== previous; item = item.previousElementSibling) {
7434
+ if (item.hasAttribute("data-scroll-anchor")) {
7435
+ this._anchor = item;
7436
+ this._updateSpacer();
7437
+ this._following = true;
7438
+ this._scrollTo(this._top(item) - this.options.peek);
7439
+ return;
7440
+ }
7441
+ }
7442
+ };
7443
+ this._handleResize = () => {
7444
+ if (!this._opened) {
7445
+ if (this.viewport.clientHeight > 0) this._open();
7446
+ return;
7447
+ }
7448
+ this._updateSpacer();
7449
+ const v = this.viewport;
7450
+ if (this._target === "end") this._scrollTo("end");
7451
+ else if (this._target === null) {
7452
+ if (this._following && !this._holding) v.scrollTop = v.scrollHeight;
7453
+ else this._restore();
7454
+ }
7455
+ this._sync();
7456
+ };
7457
+ this.el["Expressive_MessageScroller"] = this;
7458
+ this.options = {
7459
+ ..._MessageScroller.defaults,
7460
+ ...options
7461
+ };
7462
+ this.viewport = el.querySelector(":scope > .message-scroller-viewport");
7463
+ this.button = el.querySelector(":scope > .message-scroller-button");
7464
+ if (!this.viewport) {
7465
+ console.error("MessageScroller needs a .message-scroller-viewport child.");
7466
+ return;
7467
+ }
7468
+ this._last = this.viewport.lastElementChild;
7469
+ if (this.viewport.clientHeight > 0) this._open();
7470
+ if (typeof ResizeObserver !== "undefined") {
7471
+ this._resizeObserver = new ResizeObserver(this._handleResize);
7472
+ this._resizeObserver.observe(this.viewport, { box: "border-box" });
7473
+ for (const item of Array.from(this.viewport.children)) this._resizeObserver.observe(item);
7474
+ }
7475
+ this._mutationObserver = new MutationObserver(this._handleMutations);
7476
+ this._mutationObserver.observe(this.viewport, { childList: true });
7477
+ this.viewport.addEventListener("scroll", this._handleScroll, { passive: true });
7478
+ for (const type of ["wheel", "touchstart", "pointerdown", "keydown"]) {
7479
+ this.viewport.addEventListener(type, this._handleUserScroll, { passive: true });
7480
+ }
7481
+ for (const type of RELEASE_EVENTS) document.addEventListener(type, this._handleRelease);
7482
+ this.button?.addEventListener("click", this._handleButtonClick);
7483
+ }
7484
+ static get defaults() {
7485
+ return _defaults18;
7486
+ }
7487
+ /**
7488
+ * Initializes instances of MessageScroller.
7489
+ * @param els HTML elements.
7490
+ * @param options Component options.
7491
+ */
7492
+ static init(els, options = {}) {
7493
+ return super.init(els, options, _MessageScroller);
7494
+ }
7495
+ static getInstance(el) {
7496
+ return el["Expressive_MessageScroller"];
7497
+ }
7498
+ destroy() {
7499
+ this.el["Expressive_MessageScroller"] = void 0;
7500
+ if (!this.viewport) return;
7501
+ this._resizeObserver?.disconnect();
7502
+ this._mutationObserver?.disconnect();
7503
+ this.viewport.removeEventListener("scroll", this._handleScroll);
7504
+ for (const type of ["wheel", "touchstart", "pointerdown", "keydown"]) {
7505
+ this.viewport.removeEventListener(type, this._handleUserScroll);
7506
+ }
7507
+ for (const type of RELEASE_EVENTS) document.removeEventListener(type, this._handleRelease);
7508
+ this.viewport.style.removeProperty("--md-comp-message-scroller-spacer");
7509
+ if (this.button) {
7510
+ this.button.removeEventListener("click", this._handleButtonClick);
7511
+ this.button.inert = false;
7512
+ }
7513
+ }
7514
+ /** Scrolls to the latest message and resumes following. */
7515
+ scrollToEnd() {
7516
+ this._following = this.options.autoScroll;
7517
+ this._scrollTo("end");
7518
+ }
7519
+ /** Scrolls to the first message. */
7520
+ scrollToStart() {
7521
+ this._following = false;
7522
+ this._scrollTo(0);
7523
+ }
7524
+ /**
7525
+ * Scrolls the element with this id to the top of the viewport, below the
7526
+ * peek, and stops following.
7527
+ * @returns false when no such element is inside the viewport.
7528
+ */
7529
+ scrollToMessage(id) {
7530
+ const target = Utils.getElementById(this.viewport, id);
7531
+ if (!target || !this.viewport.contains(target)) return false;
7532
+ this._following = false;
7533
+ this._scrollTo(this._top(target) - this.options.peek);
7534
+ return true;
7535
+ }
7536
+ _open() {
7537
+ this._opened = true;
7538
+ const v = this.viewport;
7539
+ const position = this.options.defaultScrollPosition;
7540
+ const anchors = v.querySelectorAll(":scope > [data-scroll-anchor]");
7541
+ const anchor = position === "last-anchor" ? anchors[anchors.length - 1] : null;
7542
+ if (position === "start") v.scrollTop = 0;
7543
+ else if (anchor && this._contentEnd() - this._top(anchor) + this.options.peek > v.clientHeight) {
7544
+ v.scrollTop = this._top(anchor) - this.options.peek;
7545
+ } else v.scrollTop = v.scrollHeight;
7546
+ this._settle();
7547
+ }
7548
+ /**
7549
+ * Records where the reader is once no programmatic scroll is running.
7550
+ * Content can grow between a scroll and the resize that re-pins the end,
7551
+ * so only moving toward the start releases following.
7552
+ */
7553
+ _settle() {
7554
+ const top = this.viewport.scrollTop;
7555
+ const kept = this._following && top >= this._lastTop - EDGE;
7556
+ this._following = this.options.autoScroll && (kept || this._atEnd());
7557
+ this._lastTop = top;
7558
+ this._capture();
7559
+ this._sync();
7560
+ }
7561
+ _scrollTo(target) {
7562
+ const v = this.viewport;
7563
+ if (Math.abs(v.scrollTop - this._resolve(target)) <= EDGE) {
7564
+ this._target = null;
7565
+ this._settle();
7566
+ return;
7567
+ }
7568
+ this._target = target;
7569
+ this._distance = Infinity;
7570
+ const reduce = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
7571
+ v.scrollTo({
7572
+ top: target === "end" ? v.scrollHeight : target,
7573
+ behavior: reduce ? "auto" : "smooth"
7574
+ });
7575
+ }
7576
+ _resolve(target) {
7577
+ const max = this.viewport.scrollHeight - this.viewport.clientHeight;
7578
+ return target === "end" ? max : Math.max(0, Math.min(target, max));
7579
+ }
7580
+ _atEnd() {
7581
+ const v = this.viewport;
7582
+ return v.scrollHeight - v.scrollTop - v.clientHeight <= EDGE;
7583
+ }
7584
+ /** Offset of an element from the top of the scrolled content. */
7585
+ _top(el) {
7586
+ const v = this.viewport;
7587
+ return el.getBoundingClientRect().top - v.getBoundingClientRect().top - v.clientTop + v.scrollTop;
7588
+ }
7589
+ /** Bottom of the content without the spacer, padding included. */
7590
+ _contentEnd() {
7591
+ const last = this.viewport.lastElementChild;
7592
+ if (!last) return 0;
7593
+ const padding = parseFloat(getComputedStyle(this.viewport).paddingBlockEnd) - this._spacer;
7594
+ return this._top(last) + last.getBoundingClientRect().height + padding;
7595
+ }
7596
+ /** Remembers the first visible message so prepends can keep it in place. */
7597
+ _capture() {
7598
+ const items = this.viewport.children;
7599
+ const top = this.viewport.getBoundingClientRect().top + this.viewport.clientTop;
7600
+ let low = 0;
7601
+ let high = items.length - 1;
7602
+ while (low < high) {
7603
+ const mid = low + high >> 1;
7604
+ if (items[mid].getBoundingClientRect().bottom > top) high = mid;
7605
+ else low = mid + 1;
7606
+ }
7607
+ this._ref = items[low] ?? null;
7608
+ this._refOffset = this._ref ? this._ref.getBoundingClientRect().top - top : 0;
7609
+ }
7610
+ /** Moves the remembered message back to where the reader last saw it. */
7611
+ _restore() {
7612
+ if (this._ref?.parentElement !== this.viewport) {
7613
+ this._capture();
7614
+ return;
7615
+ }
7616
+ const top = this.viewport.getBoundingClientRect().top + this.viewport.clientTop;
7617
+ const delta = this._ref.getBoundingClientRect().top - top - this._refOffset;
7618
+ if (Math.abs(delta) >= 1) this.viewport.scrollTop += delta;
7619
+ }
7620
+ /** Sizes the blank space that lets the latest anchored turn reach the top. */
7621
+ _updateSpacer() {
7622
+ let space = 0;
7623
+ if (this._anchor?.parentElement === this.viewport) {
7624
+ const room = this._top(this._anchor) - this.options.peek + this.viewport.clientHeight;
7625
+ space = Math.max(0, Math.ceil(room - this._contentEnd()));
7626
+ } else this._anchor = null;
7627
+ if (space === this._spacer) return;
7628
+ this._spacer = space;
7629
+ if (space) this.viewport.style.setProperty("--md-comp-message-scroller-spacer", `${space}px`);
7630
+ else this.viewport.style.removeProperty("--md-comp-message-scroller-spacer");
7631
+ }
7632
+ /** The button is inert while there is nothing below. */
7633
+ _sync() {
7634
+ if (!this.button) return;
7635
+ const idle = this._atEnd();
7636
+ const root = this.button.getRootNode();
7637
+ if (idle && root.activeElement === this.button) this.viewport.focus({ preventScroll: true });
7638
+ this.button.inert = idle;
7639
+ }
7640
+ };
7641
+
7642
+ // src/ts/components/richTextEditor.ts
7643
+ var _defaults19 = {
7644
+ Editor: null,
7645
+ extensions: []
7646
+ };
7647
+ var NAMING = ["aria-label", "aria-labelledby", "aria-describedby"];
7648
+ var RichTextEditor = class _RichTextEditor extends Component {
7649
+ constructor(el, options) {
7650
+ super(el, options, _RichTextEditor);
7651
+ /** The Tiptap editor. */
7652
+ this.editor = null;
7653
+ /** The element the editor is mounted in. */
7654
+ this.content = null;
7655
+ this._actions = /* @__PURE__ */ new Map();
7656
+ this._label = null;
7657
+ this._handleClick = (e) => {
7658
+ const button = e.target.closest("button[data-command]");
7659
+ const action = this._actions.get(button);
7660
+ if (action && !button.disabled) this.editor.chain().focus()[action.command](action.attributes).run();
7661
+ };
7662
+ this._handleLabelClick = () => {
7663
+ this.editor.chain().focus().run();
7664
+ };
7665
+ this._handleUpdate = () => {
7666
+ this.textarea.value = this.editor.isEmpty ? "" : this.editor.getHTML();
7667
+ this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
7668
+ };
7669
+ this._handleTransaction = () => {
7670
+ const can = this.editor.isEditable && this.editor.can();
7671
+ for (const [button, { command, attributes, active }] of this._actions) {
7672
+ button.disabled = !can || !(command in this.editor.commands) || !can[command](attributes);
7673
+ if (!active) continue;
7674
+ const on = this.editor.isActive(active, attributes);
7675
+ button.setAttribute("aria-pressed", String(on));
7676
+ button.classList.toggle("active", on);
7677
+ }
7678
+ };
7679
+ this.el["Expressive_RichTextEditor"] = this;
7680
+ this.options = {
7681
+ ..._RichTextEditor.defaults,
7682
+ ...options
7683
+ };
7684
+ this.textarea = el.querySelector(":scope > textarea");
7685
+ if (!this.textarea || !this.options.Editor) {
7686
+ console.error("RichTextEditor needs a <textarea> child and the Tiptap Editor class in options.Editor.");
7687
+ return;
7688
+ }
7689
+ for (const button of el.querySelectorAll("button[data-command]")) {
7690
+ const command = button.dataset.command;
7691
+ const attributes2 = button.dataset.attributes ? JSON.parse(button.dataset.attributes) : void 0;
7692
+ const active = command.startsWith("toggle") ? command[6].toLowerCase() + command.slice(7) : void 0;
7693
+ this._actions.set(button, { command, attributes: attributes2, active });
7694
+ }
7695
+ const attributes = { role: "textbox", "aria-multiline": "true" };
7696
+ for (const name of NAMING) {
7697
+ const value = this.textarea.getAttribute(name);
7698
+ if (value) attributes[name] = value;
7699
+ }
7700
+ this._label = this.textarea.labels?.[0] ?? null;
7701
+ if (this._label && !attributes["aria-labelledby"]) {
7702
+ if (!this._label.id) this._label.id = `rich-text-editor-label-${Utils.guid()}`;
7703
+ attributes["aria-labelledby"] = this._label.id;
7704
+ this._label.addEventListener("click", this._handleLabelClick);
7705
+ }
7706
+ this.content = document.createElement("div");
7707
+ this.content.className = "rich-text-editor-content";
7708
+ this.textarea.after(this.content);
7709
+ this.textarea.hidden = true;
7710
+ this.editor = new this.options.Editor({
7711
+ element: this.content,
7712
+ extensions: this.options.extensions,
7713
+ content: this.textarea.value,
7714
+ editable: !(this.textarea.disabled || this.textarea.readOnly),
7715
+ editorProps: { attributes }
7716
+ });
7717
+ this.editor.on("update", this._handleUpdate);
7718
+ this.editor.on("transaction", this._handleTransaction);
7719
+ this._handleTransaction();
7720
+ el.addEventListener("click", this._handleClick);
7721
+ }
7722
+ static get defaults() {
7723
+ return _defaults19;
7724
+ }
7725
+ /**
7726
+ * Initializes instances of RichTextEditor.
7727
+ * @param els HTML elements.
7728
+ * @param options Component options.
7729
+ */
7730
+ static init(els, options = {}) {
7731
+ return super.init(els, options, _RichTextEditor);
7732
+ }
7733
+ static getInstance(el) {
7734
+ return el["Expressive_RichTextEditor"];
7735
+ }
7736
+ destroy() {
7737
+ this.el["Expressive_RichTextEditor"] = void 0;
7738
+ if (!this.editor) return;
7739
+ this.el.removeEventListener("click", this._handleClick);
7740
+ this._label?.removeEventListener("click", this._handleLabelClick);
7741
+ this.editor.off("update", this._handleUpdate);
7742
+ this.editor.off("transaction", this._handleTransaction);
7743
+ this.editor.destroy();
7744
+ this.editor = null;
7745
+ this.content.remove();
7746
+ this.textarea.hidden = false;
7747
+ for (const button of this._actions.keys()) {
7748
+ button.disabled = false;
7749
+ button.classList.remove("active");
7750
+ button.removeAttribute("aria-pressed");
7751
+ }
7752
+ }
7753
+ };
7754
+
5549
7755
  // src/ts/components/lightbox.ts
5550
- var _defaults15 = {
7756
+ var _defaults20 = {
5551
7757
  inDuration: 275,
5552
7758
  outDuration: 200,
5553
7759
  onOpenStart: null,
@@ -5696,7 +7902,7 @@ var _Lightbox = class _Lightbox extends Component {
5696
7902
  this._setupEventHandlers();
5697
7903
  }
5698
7904
  static get defaults() {
5699
- return _defaults15;
7905
+ return _defaults20;
5700
7906
  }
5701
7907
  /**
5702
7908
  * Initializes instances of Lightbox.
@@ -5902,7 +8108,7 @@ var Lightbox = _Lightbox;
5902
8108
 
5903
8109
  // src/ts/components/scrollspy.ts
5904
8110
  var _defaultGetActiveElement = (id) => `a[href="#${id}"]`;
5905
- var _defaults16 = {
8111
+ var _defaults21 = {
5906
8112
  scrollOffset: 200,
5907
8113
  activeClass: "active",
5908
8114
  getActiveElement: _defaultGetActiveElement,
@@ -5938,7 +8144,7 @@ var _ScrollSpy = class _ScrollSpy extends Component {
5938
8144
  }
5939
8145
  }
5940
8146
  static get defaults() {
5941
- return _defaults16;
8147
+ return _defaults21;
5942
8148
  }
5943
8149
  static init(els, options = {}) {
5944
8150
  return super.init(els, options, _ScrollSpy);
@@ -6028,7 +8234,7 @@ _ScrollSpy._active = /* @__PURE__ */ new Map();
6028
8234
  var ScrollSpy = _ScrollSpy;
6029
8235
 
6030
8236
  // src/ts/components/navigationRail.ts
6031
- var _defaults17 = {
8237
+ var _defaults22 = {
6032
8238
  onOpenStart: null,
6033
8239
  onOpenEnd: null,
6034
8240
  onCloseStart: null,
@@ -6088,8 +8294,19 @@ var NavigationRail = class _NavigationRail extends Component {
6088
8294
  }
6089
8295
  };
6090
8296
  this._onKeyDown = (e) => {
6091
- if (e.key !== "Escape") return;
6092
- if (this.isExpanded && this._isModal()) this.collapse();
8297
+ if (e.key !== "Escape" || e.defaultPrevented) return;
8298
+ if (this.isExpanded && this._isModal()) {
8299
+ const root = this.el.getRootNode();
8300
+ const restoreFocus = this.el.contains(root.activeElement);
8301
+ let focused = root.activeElement;
8302
+ while (focused?.shadowRoot?.activeElement) focused = focused.shadowRoot.activeElement;
8303
+ this.collapse();
8304
+ let active = root.activeElement;
8305
+ while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
8306
+ if (!this.isExpanded && restoreFocus && active === focused) {
8307
+ this._toggle?.focus();
8308
+ }
8309
+ }
6093
8310
  };
6094
8311
  this.el["Expressive_NavigationRail"] = this;
6095
8312
  this.options = {
@@ -6104,7 +8321,7 @@ var NavigationRail = class _NavigationRail extends Component {
6104
8321
  document.addEventListener("keydown", this._onKeyDown);
6105
8322
  }
6106
8323
  static get defaults() {
6107
- return _defaults17;
8324
+ return _defaults22;
6108
8325
  }
6109
8326
  static init(els, options = {}) {
6110
8327
  return super.init(els, options, _NavigationRail);
@@ -6134,7 +8351,7 @@ var NavigationRail = class _NavigationRail extends Component {
6134
8351
  };
6135
8352
 
6136
8353
  // src/ts/components/tabs.ts
6137
- var _defaults18 = {
8354
+ var _defaults23 = {
6138
8355
  duration: 300,
6139
8356
  onShow: null,
6140
8357
  swipeable: false,
@@ -6221,7 +8438,7 @@ var Tabs = class _Tabs extends Component {
6221
8438
  this._setupEventHandlers();
6222
8439
  }
6223
8440
  static get defaults() {
6224
- return _defaults18;
8441
+ return _defaults23;
6225
8442
  }
6226
8443
  /**
6227
8444
  * Initializes instances of Tabs.
@@ -6408,7 +8625,7 @@ var Tabs = class _Tabs extends Component {
6408
8625
  };
6409
8626
 
6410
8627
  // src/ts/components/timepicker.ts
6411
- var _defaults19 = {
8628
+ var _defaults24 = {
6412
8629
  dialRadius: 135,
6413
8630
  outerRadius: 105,
6414
8631
  innerRadius: 70,
@@ -6449,6 +8666,7 @@ var _Timepicker = class _Timepicker extends Component {
6449
8666
  /** Whether the dial (ticks + SVG + AM/PM buttons) has been built yet. */
6450
8667
  this._clockBuilt = false;
6451
8668
  this._handleInputClick = () => {
8669
+ this._updateTimeFromInput();
6452
8670
  this._ensureClockBuilt();
6453
8671
  if (this.displayPlugin) this.displayPlugin.show();
6454
8672
  this.inputHours.focus();
@@ -6457,10 +8675,7 @@ var _Timepicker = class _Timepicker extends Component {
6457
8675
  this._handleInputKeydown = (e) => {
6458
8676
  if (e.key === Utils.keys.ENTER) {
6459
8677
  e.preventDefault();
6460
- this._ensureClockBuilt();
6461
- if (this.displayPlugin) this.displayPlugin.show();
6462
- this.inputHours.focus();
6463
- if (typeof this.options.onInputInteraction === "function") this.options.onInputInteraction.call(this);
8678
+ this._handleInputClick();
6464
8679
  }
6465
8680
  };
6466
8681
  this._handleClockClickStart = (e) => {
@@ -6504,7 +8719,9 @@ var _Timepicker = class _Timepicker extends Component {
6504
8719
  this.inputMinutes.focus();
6505
8720
  this.showView("minutes", this.options.duration / 2);
6506
8721
  } else {
6507
- setTimeout(() => {
8722
+ clearTimeout(this._autoSubmitTimer);
8723
+ this._autoSubmitTimer = setTimeout(() => {
8724
+ this._autoSubmitTimer = null;
6508
8725
  if (this.options.autoSubmit) this.done();
6509
8726
  }, this.options.duration / 2);
6510
8727
  }
@@ -6556,6 +8773,7 @@ var _Timepicker = class _Timepicker extends Component {
6556
8773
  const input = event.target;
6557
8774
  const isHours = input === this.inputHours;
6558
8775
  if (!isHours && input !== this.inputMinutes) return;
8776
+ this._cancelClockReset();
6559
8777
  const value = Number(input.value);
6560
8778
  const min = isHours && this.options.twelveHour ? 1 : 0;
6561
8779
  const max = isHours ? this.options.twelveHour ? 12 : 23 : 59;
@@ -6603,7 +8821,7 @@ var _Timepicker = class _Timepicker extends Component {
6603
8821
  }
6604
8822
  }
6605
8823
  static get defaults() {
6606
- return _defaults19;
8824
+ return _defaults24;
6607
8825
  }
6608
8826
  /**
6609
8827
  * Initializes instances of Timepicker.
@@ -6624,6 +8842,10 @@ var _Timepicker = class _Timepicker extends Component {
6624
8842
  return el["Expressive_Timepicker"];
6625
8843
  }
6626
8844
  destroy() {
8845
+ clearTimeout(this._autoSubmitTimer);
8846
+ clearTimeout(this.toggleViewTimer);
8847
+ clearTimeout(this.vibrateTimer);
8848
+ this._cancelClockReset();
6627
8849
  this._removeEventHandlers();
6628
8850
  this.displayPlugin?.destroy();
6629
8851
  this.containerEl.remove();
@@ -6810,6 +9032,7 @@ var _Timepicker = class _Timepicker extends Component {
6810
9032
  }
6811
9033
  }
6812
9034
  _updateTimeFromInput() {
9035
+ this._cancelClockReset();
6813
9036
  let value = ((this.el.value || this.options.defaultTime || "") + "").trim().toUpperCase().split(":");
6814
9037
  const period = value[1]?.match(/AM|PM/)?.[0];
6815
9038
  value[1] = value[1]?.replace(/AM|PM/, "");
@@ -6827,12 +9050,17 @@ var _Timepicker = class _Timepicker extends Component {
6827
9050
  this.inputMinutes.value = _Timepicker._addLeadingZero(this.minutes);
6828
9051
  this._updateAmPmView();
6829
9052
  }
9053
+ _cancelClockReset() {
9054
+ clearTimeout(this._clockResetTimer);
9055
+ this._clockResetTimer = null;
9056
+ this._canvas?.classList.remove("timepicker-canvas-out");
9057
+ }
6830
9058
  resetClock(delay) {
9059
+ this._cancelClockReset();
6831
9060
  const view = this.currentView, value = this[view], isHours = view === "hours", unit = Math.PI / (isHours ? 6 : 30), radian = value * unit, radius = isHours && value > 0 && value < 13 ? this.options.innerRadius : this.options.outerRadius, x = Math.sin(radian) * radius, y = -Math.cos(radian) * radius;
6832
9061
  if (delay) {
6833
9062
  this._canvas?.classList.add("timepicker-canvas-out");
6834
- setTimeout(() => {
6835
- this._canvas?.classList.remove("timepicker-canvas-out");
9063
+ this._clockResetTimer = setTimeout(() => {
6836
9064
  this.setHand(x, y);
6837
9065
  }, delay);
6838
9066
  } else {
@@ -6851,6 +9079,7 @@ var _Timepicker = class _Timepicker extends Component {
6851
9079
  this.setClockAttributes(radian, radius);
6852
9080
  }
6853
9081
  setHand(x, y, roundBy5 = false) {
9082
+ this._cancelClockReset();
6854
9083
  const isHours = this.currentView === "hours", unit = Math.PI / (isHours || roundBy5 ? 6 : 30), z = Math.sqrt(x * x + y * y), inner = isHours && z < (this.options.outerRadius + this.options.innerRadius) / 2;
6855
9084
  let radian = Math.atan2(x, -y), radius = inner ? this.options.innerRadius : this.options.outerRadius;
6856
9085
  if (this.options.twelveHour) {
@@ -6954,7 +9183,7 @@ _Timepicker._template = `<div class="timepicker-container">
6954
9183
  var Timepicker = _Timepicker;
6955
9184
 
6956
9185
  // src/ts/components/snackbar.ts
6957
- var _defaults20 = {
9186
+ var _defaults25 = {
6958
9187
  text: "",
6959
9188
  action: "",
6960
9189
  onAction: null,
@@ -6971,6 +9200,7 @@ var _Snackbar = class _Snackbar {
6971
9200
  constructor(options = {}) {
6972
9201
  this._dismissed = false;
6973
9202
  this._returnFocus = null;
9203
+ this._restoreElement = null;
6974
9204
  this.options = {
6975
9205
  ..._Snackbar.defaults,
6976
9206
  ...options
@@ -6985,27 +9215,25 @@ var _Snackbar = class _Snackbar {
6985
9215
  while (_Snackbar._snackbars.length > 0) {
6986
9216
  const snackbar = _Snackbar._snackbars.shift();
6987
9217
  snackbar._dismissed = true;
6988
- clearTimeout(snackbar.counterTimeout);
6989
- snackbar._restoreFocus();
6990
- snackbar.el.remove();
9218
+ snackbar._restoreFocus(true);
6991
9219
  }
6992
9220
  let focused = document.activeElement;
6993
9221
  while (focused?.shadowRoot?.activeElement) focused = focused.shadowRoot.activeElement;
6994
9222
  this._returnFocus = focused instanceof HTMLElement ? focused : null;
9223
+ this.el = this._createSnackbar();
6995
9224
  const root = Utils.portalRoot(this.options.root ?? document.body);
6996
9225
  if (!_Snackbar._container || !_Snackbar._container.isConnected || _Snackbar._container.parentNode !== root) {
6997
9226
  if (_Snackbar._container) _Snackbar._removeContainer();
6998
9227
  _Snackbar._createContainer(root);
6999
9228
  }
9229
+ _Snackbar._container.appendChild(this.el);
9230
+ this.el["Expressive_Snackbar"] = this;
7000
9231
  _Snackbar._snackbars.push(this);
7001
- const snackbarElement = this._createSnackbar();
7002
- snackbarElement["Expressive_Snackbar"] = this;
7003
- this.el = snackbarElement;
7004
9232
  this._animateIn();
7005
9233
  this._setTimer();
7006
9234
  }
7007
9235
  static get defaults() {
7008
- return _defaults20;
9236
+ return _defaults25;
7009
9237
  }
7010
9238
  static getInstance(el) {
7011
9239
  return el["Expressive_Snackbar"];
@@ -7042,6 +9270,7 @@ var _Snackbar = class _Snackbar {
7042
9270
  snackbar.el.style.transition = "";
7043
9271
  _Snackbar._dragPointerId = e.pointerId;
7044
9272
  snackbar.startingXPos = e.clientX;
9273
+ snackbar.velocityX = 0;
7045
9274
  snackbar.time = Date.now();
7046
9275
  snackbar.xPos = e.clientX;
7047
9276
  }
@@ -7070,7 +9299,8 @@ var _Snackbar = class _Snackbar {
7070
9299
  snackbar.el.classList.remove("panning");
7071
9300
  const totalDeltaX = snackbar.xPos - snackbar.startingXPos;
7072
9301
  const activationDistance = snackbar.el.offsetWidth * snackbar.options.activationPercent;
7073
- const shouldBeDismissed = Math.abs(totalDeltaX) > activationDistance || snackbar.velocityX > 1;
9302
+ const velocity = Date.now() - snackbar.time < 100 ? snackbar.velocityX : 0;
9303
+ const shouldBeDismissed = Math.abs(totalDeltaX) > activationDistance || velocity > 1;
7074
9304
  if (shouldBeDismissed) {
7075
9305
  snackbar.wasSwiped = true;
7076
9306
  snackbar.dismiss();
@@ -7114,7 +9344,23 @@ var _Snackbar = class _Snackbar {
7114
9344
  if (snackbar instanceof HTMLTemplateElement) {
7115
9345
  const node = snackbar.content.cloneNode(true);
7116
9346
  snackbar = node.firstElementChild;
9347
+ } else if (snackbar && this.options.snackbarId) {
9348
+ const placeholder = document.createComment("");
9349
+ snackbar.before(placeholder);
9350
+ const children = [...snackbar.childNodes];
9351
+ const attributes = ["class", "style", "role", "aria-live", "aria-atomic", "inert"].map((name) => [name, snackbar.getAttribute(name)]);
9352
+ this._restoreElement = () => {
9353
+ snackbar.replaceChildren(...children);
9354
+ for (const [name, value] of attributes) {
9355
+ if (value === null) snackbar.removeAttribute(name);
9356
+ else snackbar.setAttribute(name, value);
9357
+ }
9358
+ placeholder.replaceWith(snackbar);
9359
+ };
7117
9360
  }
9361
+ if (!snackbar) throw new Error("Snackbar body not found");
9362
+ this._events = new (snackbar.ownerDocument.defaultView ?? window).AbortController();
9363
+ const listenerOptions = { signal: this._events.signal };
7118
9364
  snackbar.classList.add("snackbar");
7119
9365
  snackbar.setAttribute("role", "status");
7120
9366
  snackbar.setAttribute("aria-live", "polite");
@@ -7125,16 +9371,19 @@ var _Snackbar = class _Snackbar {
7125
9371
  this._returnFocus = e.relatedTarget;
7126
9372
  }
7127
9373
  this._pauseTimer();
7128
- });
9374
+ }, listenerOptions);
7129
9375
  snackbar.addEventListener("keydown", (e) => {
7130
9376
  if (e.key !== "Escape" || e.defaultPrevented || this._dismissed) return;
7131
9377
  e.preventDefault();
7132
9378
  e.stopPropagation();
7133
9379
  this.dismiss();
7134
- });
7135
- snackbar.addEventListener("focusout", () => setTimeout(() => this._resumeTimer(), 0));
7136
- snackbar.addEventListener("pointerenter", () => this._pauseTimer());
7137
- snackbar.addEventListener("pointerleave", () => this._resumeTimer());
9380
+ }, listenerOptions);
9381
+ snackbar.addEventListener("focusout", () => {
9382
+ clearTimeout(this._focusTimer);
9383
+ this._focusTimer = setTimeout(() => this._resumeTimer(), 0);
9384
+ }, listenerOptions);
9385
+ snackbar.addEventListener("pointerenter", () => this._pauseTimer(), listenerOptions);
9386
+ snackbar.addEventListener("pointerleave", () => this._resumeTimer(), listenerOptions);
7138
9387
  if (this.options.classes.length > 0) {
7139
9388
  snackbar.classList.add(...this.options.classes.split(" ").filter(Boolean));
7140
9389
  }
@@ -7155,7 +9404,7 @@ var _Snackbar = class _Snackbar {
7155
9404
  } finally {
7156
9405
  this.dismiss();
7157
9406
  }
7158
- });
9407
+ }, listenerOptions);
7159
9408
  snackbar.appendChild(action);
7160
9409
  }
7161
9410
  if (this.options.dismissible) {
@@ -7171,12 +9420,32 @@ var _Snackbar = class _Snackbar {
7171
9420
  close.addEventListener("click", (e) => {
7172
9421
  e.stopPropagation();
7173
9422
  this.dismiss();
7174
- });
9423
+ }, listenerOptions);
7175
9424
  snackbar.appendChild(close);
7176
9425
  }
7177
- _Snackbar._container.appendChild(snackbar);
7178
9426
  return snackbar;
7179
9427
  }
9428
+ _remove() {
9429
+ if (this._events.signal.aborted) return;
9430
+ this._events.abort();
9431
+ clearTimeout(this.counterTimeout);
9432
+ clearTimeout(this._transitionTimer);
9433
+ clearTimeout(this._dismissTimer);
9434
+ clearTimeout(this._focusTimer);
9435
+ this.counterTimeout = null;
9436
+ if (_Snackbar._draggedSnackbar === this) {
9437
+ _Snackbar._draggedSnackbar = null;
9438
+ _Snackbar._dragPointerId = null;
9439
+ }
9440
+ this.el["Expressive_Snackbar"] = void 0;
9441
+ if (this._restoreElement) this._restoreElement();
9442
+ else this.el.remove();
9443
+ const index = _Snackbar._snackbars.indexOf(this);
9444
+ if (index >= 0) _Snackbar._snackbars.splice(index, 1);
9445
+ if (!_Snackbar._snackbars.length && _Snackbar._container?.childElementCount === 0) {
9446
+ _Snackbar._removeContainer();
9447
+ }
9448
+ }
7180
9449
  _animateIn() {
7181
9450
  this.el.style.display = "";
7182
9451
  this.el.style.opacity = "0";
@@ -7185,7 +9454,7 @@ var _Snackbar = class _Snackbar {
7185
9454
  transform ${this.options.inDuration}ms ease,
7186
9455
  opacity ${this.options.inDuration}ms ease
7187
9456
  `;
7188
- setTimeout(() => {
9457
+ this._transitionTimer = setTimeout(() => {
7189
9458
  this.el.style.transform = "";
7190
9459
  this.el.style.opacity = "1";
7191
9460
  }, 1);
@@ -7214,12 +9483,12 @@ var _Snackbar = class _Snackbar {
7214
9483
  if (this._dismissed || this.panning || this.el.matches(":hover, :focus-within") || this.counterTimeout != null) return;
7215
9484
  this._setTimer();
7216
9485
  }
7217
- _restoreFocus() {
9486
+ _restoreFocus(remove = false) {
7218
9487
  const focused = this.el.getRootNode().activeElement;
7219
- if (focused && this.el.contains(focused) && this._returnFocus?.isConnected) {
7220
- this._returnFocus.focus({ preventScroll: true });
7221
- }
9488
+ const returnFocus = focused && this.el.contains(focused) ? this._returnFocus : null;
7222
9489
  this.el.inert = true;
9490
+ if (remove) this._remove();
9491
+ if (returnFocus?.isConnected) returnFocus.focus({ preventScroll: true });
7223
9492
  }
7224
9493
  /**
7225
9494
  * Dismiss snackbar with animation.
@@ -7230,6 +9499,7 @@ var _Snackbar = class _Snackbar {
7230
9499
  clearTimeout(this.counterTimeout);
7231
9500
  this.counterTimeout = null;
7232
9501
  this._restoreFocus();
9502
+ if (this._events.signal.aborted) return;
7233
9503
  const activationDistance = this.el.offsetWidth * this.options.activationPercent;
7234
9504
  if (this.wasSwiped) {
7235
9505
  this.el.style.transition = "transform .05s, opacity .05s";
@@ -7239,22 +9509,16 @@ var _Snackbar = class _Snackbar {
7239
9509
  this.el.style.transition = `
7240
9510
  margin ${this.options.outDuration}ms ease,
7241
9511
  opacity ${this.options.outDuration}ms ease`;
7242
- setTimeout(() => {
9512
+ clearTimeout(this._transitionTimer);
9513
+ this._transitionTimer = setTimeout(() => {
7243
9514
  this.el.style.opacity = "0";
7244
9515
  this.el.style.marginTop = "-40px";
7245
9516
  }, 1);
7246
- setTimeout(() => {
9517
+ this._dismissTimer = setTimeout(() => {
9518
+ this._remove();
7247
9519
  if (typeof this.options.completeCallback === "function") {
7248
9520
  this.options.completeCallback();
7249
9521
  }
7250
- if (this.el.id != this.options.snackbarId) {
7251
- this.el.remove();
7252
- const index = _Snackbar._snackbars.indexOf(this);
7253
- if (index >= 0) _Snackbar._snackbars.splice(index, 1);
7254
- if (_Snackbar._snackbars.length === 0 && _Snackbar._container && _Snackbar._container.childElementCount === 0) {
7255
- _Snackbar._removeContainer();
7256
- }
7257
- }
7258
9522
  }, this.options.outDuration);
7259
9523
  }
7260
9524
  };
@@ -7266,7 +9530,7 @@ _Snackbar._draggedSnackbar = null;
7266
9530
  var Snackbar = _Snackbar;
7267
9531
 
7268
9532
  // src/ts/components/tooltip.ts
7269
- var _defaults21 = {
9533
+ var _defaults26 = {
7270
9534
  exitDelay: 200,
7271
9535
  enterDelay: 0,
7272
9536
  text: "",
@@ -7283,9 +9547,8 @@ var Tooltip = class _Tooltip extends Component {
7283
9547
  /**
7284
9548
  * Show tooltip.
7285
9549
  */
7286
- this.open = (isManual) => {
9550
+ this.open = (isManual = true) => {
7287
9551
  if (this.isOpen) return;
7288
- isManual = isManual === void 0 ? true : void 0;
7289
9552
  clearTimeout(this._exitDelayTimeout);
7290
9553
  clearTimeout(this._animationTimeout);
7291
9554
  this.isOpen = true;
@@ -7349,7 +9612,7 @@ var Tooltip = class _Tooltip extends Component {
7349
9612
  this._setupEventHandlers();
7350
9613
  }
7351
9614
  static get defaults() {
7352
- return _defaults21;
9615
+ return _defaults26;
7353
9616
  }
7354
9617
  /**
7355
9618
  * Initializes instances of Tooltip.
@@ -7369,8 +9632,21 @@ var Tooltip = class _Tooltip extends Component {
7369
9632
  this.isOpen = false;
7370
9633
  this.isHovered = false;
7371
9634
  this.isFocused = false;
7372
- if (this.el.getAttribute("aria-describedby") === this.tooltipEl.id) {
7373
- this.el.removeAttribute("aria-describedby");
9635
+ const descriptions = this.el.getAttribute("aria-describedby")?.split(/\s+/);
9636
+ if (descriptions?.includes(this.tooltipEl.id)) {
9637
+ const remaining = descriptions.filter((id) => id && id !== this.tooltipEl.id).join(" ");
9638
+ if (remaining) this.el.setAttribute("aria-describedby", remaining);
9639
+ else this.el.removeAttribute("aria-describedby");
9640
+ }
9641
+ if (this._originalContent) {
9642
+ const { el, marker, display, priority, hidden, contentClass } = this._originalContent;
9643
+ el.style.setProperty("display", display, priority);
9644
+ if (hidden === null) el.removeAttribute("hidden");
9645
+ else el.setAttribute("hidden", hidden);
9646
+ el.classList.toggle("tooltip-content", contentClass);
9647
+ if (!el.className) el.removeAttribute("class");
9648
+ marker.replaceWith(el);
9649
+ this._originalContent = null;
7374
9650
  }
7375
9651
  this.tooltipEl.remove();
7376
9652
  this._removeEventHandlers();
@@ -7381,10 +9657,21 @@ var Tooltip = class _Tooltip extends Component {
7381
9657
  this.tooltipEl.classList.add("tooltip");
7382
9658
  this.tooltipEl.id = `tooltip-${Utils.guid()}`;
7383
9659
  this.tooltipEl.setAttribute("role", "tooltip");
7384
- this.el.setAttribute("aria-describedby", this.tooltipEl.id);
9660
+ this.el.setAttribute("aria-describedby", [this.el.getAttribute("aria-describedby"), this.tooltipEl.id].filter(Boolean).join(" "));
7385
9661
  const tooltipContentEl = this.options.tooltipId ? Utils.getElementById(this.el, this.options.tooltipId) : document.createElement("div");
7386
9662
  if (this.options.tooltipId) {
7387
9663
  this.tooltipEl.classList.add("rich");
9664
+ const marker = document.createComment("");
9665
+ tooltipContentEl.before(marker);
9666
+ this._originalContent = {
9667
+ el: tooltipContentEl,
9668
+ marker,
9669
+ display: tooltipContentEl.style.display,
9670
+ priority: tooltipContentEl.style.getPropertyPriority("display"),
9671
+ hidden: tooltipContentEl.getAttribute("hidden"),
9672
+ contentClass: tooltipContentEl.classList.contains("tooltip-content")
9673
+ };
9674
+ tooltipContentEl.removeAttribute("hidden");
7388
9675
  }
7389
9676
  tooltipContentEl.style.display = "";
7390
9677
  tooltipContentEl.classList.add("tooltip-content");
@@ -7544,11 +9831,31 @@ var AUTO_INIT_COMPONENTS = {
7544
9831
  },
7545
9832
  Carousel: { component: Carousel, selector: ".carousel" },
7546
9833
  Chips: { component: Chips, selector: ".chips" },
9834
+ ColumnChart: { component: ColumnChart, selector: ".column-chart" },
9835
+ BarChart: { component: BarChart, selector: ".bar-chart" },
9836
+ PieChart: { component: PieChart, selector: ".pie-chart" },
9837
+ HeatmapChart: { component: HeatmapChart, selector: ".heatmap-chart" },
9838
+ RadarChart: { component: RadarChart, selector: ".radar-chart" },
9839
+ RadialChart: { component: RadialChart, selector: ".radial-chart" },
9840
+ MixedChart: { component: MixedChart, selector: ".mixed-chart" },
9841
+ CommandPalette: {
9842
+ component: CommandPalette,
9843
+ selector: "dialog.command-palette"
9844
+ },
9845
+ Questionnaire: {
9846
+ component: Questionnaire,
9847
+ selector: "form.questionnaire"
9848
+ },
7547
9849
  Datepicker: {
7548
9850
  component: Datepicker,
7549
9851
  selector: ".datepicker, .date-picker"
7550
9852
  },
9853
+ LineChart: { component: LineChart, selector: ".line-chart" },
7551
9854
  Menu: { component: Menu, selector: ".menu-trigger" },
9855
+ MessageScroller: {
9856
+ component: MessageScroller,
9857
+ selector: ".message-scroller"
9858
+ },
7552
9859
  Lightbox: { component: Lightbox, selector: ".lightboxed" },
7553
9860
  ScrollSpy: { component: ScrollSpy, selector: ".scrollspy" },
7554
9861
  FormSelect: { component: FormSelect, selector: "select" },
@@ -7582,7 +9889,7 @@ function AutoInit(context = document.body, options) {
7582
9889
  }
7583
9890
 
7584
9891
  // src/ts/modular.ts
7585
- var version = "0.11.0";
9892
+ var version = "0.13.1";
7586
9893
 
7587
9894
  // src/ts/index.ts
7588
9895
  Forms.Init();