@expressivecss/expressive 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/CHANGELOG.md +1313 -1
  2. package/README.md +76 -30
  3. package/dist/css/expressive.css +4896 -1777
  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/LICENSE-material-symbols +93 -0
  8. package/dist/fonts/LICENSE-noto-sans +93 -0
  9. package/dist/fonts/LICENSE-roboto +93 -0
  10. package/dist/fonts/material-symbols-outlined.woff2 +0 -0
  11. package/dist/fonts/material-symbols-rounded.woff2 +0 -0
  12. package/dist/fonts/material-symbols-sharp.woff2 +0 -0
  13. package/dist/fonts/noto-sans-latin-400.woff2 +0 -0
  14. package/dist/fonts/noto-sans-latin-500.woff2 +0 -0
  15. package/dist/fonts/roboto-latin-400.woff2 +0 -0
  16. package/dist/fonts/roboto-latin-500.woff2 +0 -0
  17. package/dist/js/expressive.cjs +1673 -1338
  18. package/dist/js/expressive.cjs.map +4 -4
  19. package/dist/js/expressive.js +1673 -1338
  20. package/dist/js/expressive.js.map +4 -4
  21. package/dist/js/expressive.min.js +13 -13
  22. package/dist/js/expressive.min.js.map +4 -4
  23. package/dist/js/expressive.mjs +1673 -1338
  24. package/dist/js/expressive.mjs.map +4 -4
  25. package/dist/types/behaviors/bottomSheets.d.ts +19 -11
  26. package/dist/types/behaviors/sheetDrag.d.ts +40 -0
  27. package/dist/types/behaviors/sideSheets.d.ts +2 -9
  28. package/dist/types/components/appBar.d.ts +26 -0
  29. package/dist/types/components/autocomplete.d.ts +15 -3
  30. package/dist/types/components/buttonGroup.d.ts +39 -0
  31. package/dist/types/components/buttons.d.ts +5 -1
  32. package/dist/types/components/cards.d.ts +11 -13
  33. package/dist/types/components/carousel.d.ts +87 -53
  34. package/dist/types/components/chips.d.ts +21 -1
  35. package/dist/types/components/expandingCard.d.ts +31 -0
  36. package/dist/types/components/index.d.ts +23 -20
  37. package/dist/types/components/lightbox.d.ts +1 -0
  38. package/dist/types/components/menu.d.ts +11 -1
  39. package/dist/types/components/{sidenav.d.ts → navigationDrawer.d.ts} +16 -16
  40. package/dist/types/components/registry.d.ts +18 -8
  41. package/dist/types/components/slider.d.ts +26 -85
  42. package/dist/types/components/snackbar.d.ts +20 -5
  43. package/dist/types/components/tabs.d.ts +12 -0
  44. package/dist/types/components/timepicker.d.ts +12 -10
  45. package/dist/types/components/tooltip.d.ts +1 -0
  46. package/dist/types/core/utils.d.ts +64 -53
  47. package/dist/types/index.d.ts +8 -9
  48. package/package.json +39 -3
  49. package/src/sass/README.md +61 -11
  50. package/src/sass/abstracts/_breakpoints.scss +17 -19
  51. package/src/sass/abstracts/_mixins.scss +155 -21
  52. package/src/sass/abstracts/_variables.scss +69 -5
  53. package/src/sass/base/_fonts.scss +63 -0
  54. package/src/sass/base/_global.scss +6 -24
  55. package/src/sass/base/_grid.scss +15 -12
  56. package/src/sass/base/_normalize.scss +34 -38
  57. package/src/sass/base/_typography.scss +4 -18
  58. package/src/sass/components/_badges.scss +3 -5
  59. package/src/sass/components/_banners.scss +298 -0
  60. package/src/sass/components/_bottom-app-bar.scss +180 -0
  61. package/src/sass/components/_bottom-sheet.scss +12 -4
  62. package/src/sass/components/_button-groups.scss +358 -0
  63. package/src/sass/components/_buttons.scss +245 -92
  64. package/src/sass/components/_cards.scss +514 -78
  65. package/src/sass/components/_carousel.scss +562 -68
  66. package/src/sass/components/_chips.scss +64 -5
  67. package/src/sass/components/_datepicker.scss +24 -24
  68. package/src/sass/components/_dialog.scss +17 -11
  69. package/src/sass/components/_drag-handle.scss +130 -0
  70. package/src/sass/components/_expanding-card.scss +226 -0
  71. package/src/sass/components/_fab-menu.scss +232 -0
  72. package/src/sass/components/_floating-sheet.scss +86 -0
  73. package/src/sass/components/_icon-buttons.scss +172 -0
  74. package/src/sass/components/_icons-material-design.scss +22 -10
  75. package/src/sass/components/_index.scss +22 -6
  76. package/src/sass/components/_list.scss +16 -13
  77. package/src/sass/components/_loading-indicator.scss +106 -0
  78. package/src/sass/components/_menu.scss +335 -61
  79. package/src/sass/components/_navbar.scss +245 -45
  80. package/src/sass/components/_navigation-bar.scss +22 -11
  81. package/src/sass/components/{_sidenav.scss → _navigation-drawer.scss} +123 -33
  82. package/src/sass/components/_navigation-rail.scss +31 -23
  83. package/src/sass/components/_page-footer.scss +19 -16
  84. package/src/sass/components/_pagination.scss +24 -6
  85. package/src/sass/components/_panes.scss +52 -11
  86. package/src/sass/components/{_preloader.scss → _progress.scss} +11 -3
  87. package/src/sass/components/_scrim.scss +37 -0
  88. package/src/sass/components/_search.scss +259 -0
  89. package/src/sass/components/_segmented-buttons.scss +188 -0
  90. package/src/sass/components/_side-sheet.scss +2 -2
  91. package/src/sass/components/_snackbar.scss +20 -15
  92. package/src/sass/components/_split-button.scss +196 -0
  93. package/src/sass/components/_tabs.scss +7 -8
  94. package/src/sass/components/_timepicker.scss +67 -58
  95. package/src/sass/components/_toolbar.scss +206 -80
  96. package/src/sass/components/_transitions.scss +8 -1
  97. package/src/sass/components/forms/_checkboxes.scss +49 -12
  98. package/src/sass/components/forms/_fieldset.scss +4 -4
  99. package/src/sass/components/forms/_file-input.scss +8 -1
  100. package/src/sass/components/forms/_index.scss +1 -1
  101. package/src/sass/components/forms/_input-fields.scss +50 -44
  102. package/src/sass/components/forms/_radio-buttons.scss +42 -10
  103. package/src/sass/components/forms/_select.scss +38 -1
  104. package/src/sass/components/forms/{_range.scss → _slider.scss} +8 -9
  105. package/src/sass/components/forms/_switches.scss +6 -7
  106. package/src/sass/expressive.scss +5 -0
  107. package/src/sass/tokens/_index.scss +4 -1
  108. package/src/sass/tokens/_reference.scss +70 -61
  109. package/src/sass/tokens/_state.scss +24 -0
  110. package/src/sass/tokens/_theme.scss +6 -3
  111. package/src/sass/tokens/_vibrant.scss +76 -0
  112. package/src/sass/utilities/_colors.scss +14 -1
  113. package/src/sass/utilities/_helpers.scss +5 -5
  114. package/src/sass/utilities/_visibility.scss +48 -26
  115. package/dist/types/behaviors/waves.d.ts +0 -15
  116. package/dist/types/components/parallax.d.ts +0 -16
  117. package/dist/types/components/range.d.ts +0 -43
  118. package/dist/types/core/bounding.d.ts +0 -6
  119. package/dist/types/core/edges.d.ts +0 -6
  120. package/src/sass/components/_parallax.scss +0 -104
  121. package/src/sass/components/_pulse.scss +0 -37
  122. package/src/sass/components/_slider.scss +0 -129
@@ -20,77 +20,39 @@ var Expressive = (() => {
20
20
  // src/ts/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ AppBar: () => AppBar,
23
24
  AutoInit: () => AutoInit,
24
25
  Autocomplete: () => Autocomplete,
25
26
  BottomSheets: () => BottomSheets,
27
+ ButtonGroup: () => ButtonGroup,
26
28
  Cards: () => Cards,
27
29
  Carousel: () => Carousel,
28
30
  CharacterCounter: () => CharacterCounter,
29
31
  Chips: () => Chips,
30
32
  Datepicker: () => Datepicker,
31
33
  Dialogs: () => Dialogs,
34
+ ExpandingCard: () => ExpandingCard,
32
35
  FloatingActionButton: () => FloatingActionButton,
33
36
  FormSelect: () => FormSelect,
34
37
  Forms: () => Forms,
35
38
  Lightbox: () => Lightbox,
36
39
  Menu: () => Menu,
40
+ NavigationDrawer: () => NavigationDrawer,
37
41
  NavigationRail: () => NavigationRail,
38
- Parallax: () => Parallax,
39
- Range: () => Range,
42
+ Range: () => Slider,
40
43
  ScrollSpy: () => ScrollSpy,
41
44
  SideSheets: () => SideSheets,
42
- Sidenav: () => Sidenav,
45
+ Sidenav: () => NavigationDrawer,
43
46
  Slider: () => Slider,
44
47
  Snackbar: () => Snackbar,
45
48
  Tabs: () => Tabs,
46
49
  Timepicker: () => Timepicker,
47
50
  Tooltip: () => Tooltip,
48
- Waves: () => Waves,
49
51
  version: () => version
50
52
  });
51
53
 
52
54
  // src/ts/core/utils.ts
53
55
  var _Utils = class _Utils {
54
- /**
55
- * Detects when a key is pressed.
56
- * @param e Event instance.
57
- */
58
- static docHandleKeydown(e) {
59
- _Utils.keyDown = true;
60
- if (_Utils._navigationKeys.has(e.key)) {
61
- _Utils.tabPressed = true;
62
- }
63
- }
64
- /**
65
- * Detects when a key is released.
66
- * @param e Event instance.
67
- */
68
- static docHandleKeyup(e) {
69
- _Utils.keyDown = false;
70
- if (_Utils._navigationKeys.has(e.key)) {
71
- _Utils.tabPressed = false;
72
- }
73
- }
74
- /**
75
- * Detects when document is focused.
76
- * @param e Event instance.
77
- */
78
- /* eslint-disabled as of required event type condition check */
79
- /* eslint-disable-next-line */
80
- static docHandleFocus(e) {
81
- if (_Utils.keyDown) {
82
- document.body.classList.add("keyboard-focused");
83
- }
84
- }
85
- /**
86
- * Detects when document is not focused.
87
- * @param e Event instance.
88
- */
89
- /* eslint-disabled as of required event type condition check */
90
- /* eslint-disable-next-line */
91
- static docHandleBlur(e) {
92
- document.body.classList.remove("keyboard-focused");
93
- }
94
56
  /**
95
57
  * Run `callback` once the document is parsed.
96
58
  *
@@ -108,13 +70,46 @@ var Expressive = (() => {
108
70
  }
109
71
  }
110
72
  /**
111
- * Generates a unique string identifier.
73
+ * Generates a unique string identifier. Every caller prefixes the result and
74
+ * uses it as an element id, and a UUID is valid in that position.
75
+ *
76
+ * `randomUUID` is exposed only in secure contexts, and a dev server on a
77
+ * plain-HTTP LAN address - the usual way a page is opened on a phone - is
78
+ * not one. Without the fallback every component that stamps an id would
79
+ * throw there. The ids only have to be unique within one document, so the
80
+ * fallback does not need to be cryptographic.
112
81
  */
113
82
  static guid() {
114
- const s4 = () => {
115
- return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
116
- };
117
- return s4() + s4() + "-" + s4() + "-" + s4() + "-" + s4() + "-" + s4() + s4() + s4();
83
+ return globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
84
+ }
85
+ /**
86
+ * Where an element created at runtime has to be appended to stay in the same
87
+ * tree as `el`. A stylesheet adopted into a shadow root cannot match a node
88
+ * in the document, so a portal that lands on `document.body` is unstyled
89
+ * whenever its origin lives in a shadow root (adr/0002).
90
+ *
91
+ * Light-DOM elements still get `document.body`, which is what escapes an
92
+ * ancestor's `overflow: hidden` and stacking context. Inside a shadow root
93
+ * both hazards come back: the host's own ancestors still form the containing
94
+ * block, so a transformed ancestor re-anchors `position: fixed` and a
95
+ * positioned one re-anchors `position: absolute`.
96
+ */
97
+ static portalRoot(el) {
98
+ const root = el.getRootNode();
99
+ return root.host ? root : document.body;
100
+ }
101
+ /**
102
+ * `getElementById` against the tree `el` lives in. The mirror of
103
+ * `portalRoot`: an id in a shadow root is invisible to
104
+ * `document.getElementById`, so a trigger and its target inside the same
105
+ * root could not find each other at all.
106
+ *
107
+ * Falls back to the document for a detached `el`, whose root is a plain
108
+ * element with no lookup of its own.
109
+ */
110
+ static getElementById(el, id) {
111
+ const root = el.getRootNode();
112
+ return root.getElementById ? root.getElementById(id) : document.getElementById(id);
118
113
  }
119
114
  /**
120
115
  * Checks for exceeded edges
@@ -211,43 +206,50 @@ var Expressive = (() => {
211
206
  * Retrieves document scroll postion from top.
212
207
  */
213
208
  static getDocumentScrollTop() {
214
- return window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0;
209
+ return window.scrollY;
215
210
  }
216
211
  /**
217
212
  * Retrieves document scroll postion from left.
218
213
  */
219
214
  static getDocumentScrollLeft() {
220
- return window.scrollX || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
215
+ return window.scrollX;
221
216
  }
222
217
  /**
223
- * Fires the given function after a certain ammount of time.
224
- * @param func Function to be fired.
225
- * @param wait Wait time.
226
- * @param options Additional options.
218
+ * Trailing-edge throttle: run `func` now, then at most once per `wait` ms
219
+ * with the most recent arguments.
220
+ *
221
+ * This used to carry its underscore ancestor's whole configuration - leading
222
+ * and trailing switches, a result passthrough - and both call sites are
223
+ * resize handlers passing nothing but a function and a delay.
224
+ *
225
+ * Assign the return value once. `x = Utils.throttle(fn, 200)` is right;
226
+ * wrapping it in an arrow builds a fresh closure per event and never calls
227
+ * it, which is how resize handling was dead in three components.
227
228
  */
228
- static throttle(func, wait, options = {}) {
229
- let lastArgs = null, result, timeout = null, previous = 0;
230
- const later = () => {
231
- previous = options?.leading === false ? 0 : Date.now();
232
- timeout = null;
233
- result = func(...lastArgs ?? []);
234
- lastArgs = null;
235
- };
229
+ static throttle(func, wait) {
230
+ let timeout = null;
231
+ let lastArgs = null;
232
+ let previous = 0;
236
233
  return (...args) => {
237
234
  const now = Date.now();
238
- if (!previous && options?.leading === false) previous = now;
239
235
  const remaining = wait - (now - previous);
240
- lastArgs = args;
241
236
  if (remaining <= 0) {
242
237
  clearTimeout(timeout);
243
238
  timeout = null;
244
239
  previous = now;
245
240
  lastArgs = null;
246
- result = func(...args);
247
- } else if (!timeout && options?.trailing !== false) {
248
- timeout = setTimeout(later, remaining);
241
+ func(...args);
242
+ } else if (!timeout) {
243
+ lastArgs = args;
244
+ timeout = setTimeout(() => {
245
+ previous = Date.now();
246
+ timeout = null;
247
+ func(...lastArgs ?? []);
248
+ lastArgs = null;
249
+ }, remaining);
250
+ } else {
251
+ lastArgs = args;
249
252
  }
250
- return result;
251
253
  };
252
254
  }
253
255
  /**
@@ -264,7 +266,7 @@ var Expressive = (() => {
264
266
  * Renders a button with optional callback function
265
267
  */
266
268
  static createButton(container, text, className = [], visibility = true, callback = null) {
267
- className = className.concat(["waves-effect", "text"]);
269
+ className = className.concat(["text"]);
268
270
  const button = document.createElement("button");
269
271
  button.className = className.join(" ");
270
272
  button.style.visibility = visibility ? "visible" : "hidden";
@@ -274,7 +276,7 @@ var Expressive = (() => {
274
276
  if (typeof callback === "function") {
275
277
  button.addEventListener("click", callback);
276
278
  button.addEventListener("keypress", (e) => {
277
- if (_Utils.keys.ENTER.includes(e.key)) callback(e);
279
+ if (e.key === _Utils.keys.ENTER) callback(e);
278
280
  });
279
281
  }
280
282
  container.append(button);
@@ -353,36 +355,22 @@ var Expressive = (() => {
353
355
  };
354
356
  }
355
357
  };
356
- /** Specifies wether tab is pressed or not. */
357
- _Utils.tabPressed = false;
358
- /** Specifies wether there is a key pressed. */
359
- _Utils.keyDown = false;
360
358
  /**
361
- * Key maps.
359
+ * Key maps. One `KeyboardEvent.key` value each: the second entries these used
360
+ * to carry (`Esc`, `Up`, `Del`, ...) were IE and Edge Legacy spellings, and
361
+ * the support baseline excludes both.
362
362
  */
363
363
  _Utils.keys = {
364
- TAB: ["Tab"],
365
- ENTER: ["Enter"],
366
- ESC: ["Escape", "Esc"],
367
- BACKSPACE: ["Backspace"],
368
- ARROW_UP: ["ArrowUp", "Up"],
369
- ARROW_DOWN: ["ArrowDown", "Down"],
370
- ARROW_LEFT: ["ArrowLeft", "Left"],
371
- ARROW_RIGHT: ["ArrowRight", "Right"],
372
- DELETE: ["Delete", "Del"]
364
+ TAB: "Tab",
365
+ ENTER: "Enter",
366
+ ESC: "Escape",
367
+ BACKSPACE: "Backspace",
368
+ ARROW_UP: "ArrowUp",
369
+ ARROW_DOWN: "ArrowDown",
370
+ ARROW_LEFT: "ArrowLeft",
371
+ ARROW_RIGHT: "ArrowRight",
372
+ DELETE: "Delete"
373
373
  };
374
- /**
375
- * Keys that count as keyboard navigation, precomputed.
376
- *
377
- * These handlers run at capture phase on the document for every keystroke on
378
- * the page, and used to rebuild three arrays and spread them into a fourth
379
- * on each one.
380
- */
381
- _Utils._navigationKeys = /* @__PURE__ */ new Set([
382
- ..._Utils.keys.TAB,
383
- ..._Utils.keys.ARROW_DOWN,
384
- ..._Utils.keys.ARROW_UP
385
- ]);
386
374
  var Utils = _Utils;
387
375
 
388
376
  // src/ts/core/component.ts
@@ -445,47 +433,63 @@ var Expressive = (() => {
445
433
  onOpen: null,
446
434
  onClose: null
447
435
  };
448
- var CARDS_SELECTOR = "article:has(> aside)";
436
+ var CARDS_SELECTOR = 'article:has(> aside[id]:not([id=""])):not(:has(> aside ~ aside)):has(> button.card-reveal-trigger[type="button"], > :not(aside) button.card-reveal-trigger[type="button"])';
437
+ var REVEAL_INITIALIZED_CLASS = "card-reveal-initialized";
438
+ function hasAccessibleName(trigger) {
439
+ const label = trigger.getAttribute("aria-label");
440
+ if (label?.trim()) return true;
441
+ const labelledBy = trigger.getAttribute("aria-labelledby");
442
+ if (labelledBy) {
443
+ const referencedText = labelledBy.trim().split(/\s+/).map((id) => trigger.ownerDocument.getElementById(id)?.textContent ?? "").join(" ").trim();
444
+ if (referencedText) return true;
445
+ }
446
+ const content = trigger.cloneNode(true);
447
+ content.querySelectorAll('[aria-hidden="true"]').forEach((node) => node.remove());
448
+ return Boolean(content.textContent?.trim());
449
+ }
450
+ function isUsableCardTrigger(trigger, card, panel) {
451
+ return trigger.closest("article") === card && !panel.contains(trigger) && !trigger.disabled && trigger.getAttribute("aria-disabled") !== "true" && hasAccessibleName(trigger);
452
+ }
453
+ function isCardReveal(el) {
454
+ if (el.tagName !== "ARTICLE") return false;
455
+ const panels = el.querySelectorAll(":scope > aside");
456
+ if (panels.length !== 1 || !panels[0].id) return false;
457
+ return Array.from(
458
+ el.querySelectorAll('button.card-reveal-trigger[type="button"]')
459
+ ).some((trigger) => isUsableCardTrigger(trigger, el, panels[0]));
460
+ }
449
461
  var Cards = class _Cards extends Component {
450
- constructor(el, options) {
462
+ constructor(el, options = {}) {
451
463
  super(el, options, _Cards);
452
464
  this.isOpen = false;
465
+ this._ownsRevealInitializedClass = false;
466
+ this._initialAriaControls = /* @__PURE__ */ new Map();
467
+ this._lastActivator = null;
453
468
  this._setupEventHandlers = () => {
454
- this._activators.forEach((el) => {
455
- el.addEventListener("click", this._handleClickInteraction);
456
- el.addEventListener("keypress", this._handleKeypressEvent);
469
+ this._activators.forEach((activator) => {
470
+ activator.addEventListener("click", this._handleClickInteraction);
457
471
  });
472
+ this.el.addEventListener("keydown", this._handleKeydownEvent);
458
473
  };
459
474
  this._removeEventHandlers = () => {
460
- this._activators.forEach((el) => {
461
- el.removeEventListener("click", this._handleClickInteraction);
462
- el.removeEventListener("keypress", this._handleKeypressEvent);
475
+ this._activators.forEach((activator) => {
476
+ activator.removeEventListener("click", this._handleClickInteraction);
463
477
  });
478
+ this.el.removeEventListener("keydown", this._handleKeydownEvent);
464
479
  };
465
- this._handleClickInteraction = () => {
480
+ this._handleClickInteraction = (event) => {
481
+ this._lastActivator = event.currentTarget;
466
482
  this._handleRevealEvent();
467
483
  };
468
- this._handleKeypressEvent = (e) => {
469
- if (Utils.keys.ENTER.includes(e.key)) {
470
- this._handleRevealEvent();
471
- }
484
+ this._handleKeydownEvent = (event) => {
485
+ const target = event.target instanceof Element ? event.target.closest("article") : null;
486
+ if (target !== this.el || event.key !== "Escape" || !this.isOpen) return;
487
+ event.preventDefault();
488
+ this.close();
472
489
  };
473
490
  this._handleRevealEvent = () => {
474
- this._activators.forEach((el) => el.tabIndex = -1);
475
- this.open();
476
- };
477
- this._setupRevealCloseEventHandlers = () => {
478
- this.cardRevealClose.addEventListener("click", this.close);
479
- this.cardRevealClose.addEventListener("keypress", this._handleKeypressCloseEvent);
480
- };
481
- this._removeRevealCloseEventHandlers = () => {
482
- this.cardRevealClose.removeEventListener("click", this.close);
483
- this.cardRevealClose.removeEventListener("keypress", this._handleKeypressCloseEvent);
484
- };
485
- this._handleKeypressCloseEvent = (e) => {
486
- if (Utils.keys.ENTER.includes(e.key)) {
487
- this.close();
488
- }
491
+ if (this.isOpen) this.close();
492
+ else this.open();
489
493
  };
490
494
  /**
491
495
  * Show card reveal.
@@ -493,44 +497,56 @@ var Expressive = (() => {
493
497
  this.open = () => {
494
498
  if (this.isOpen || !this.cardReveal) return;
495
499
  this.isOpen = true;
496
- this.cardReveal.ariaExpanded = "true";
497
- if (this.cardRevealClose) this.cardRevealClose.tabIndex = 0;
500
+ this.cardReveal.classList.add("open");
501
+ this.cardReveal.inert = false;
502
+ this._activators.forEach((activator) => activator.ariaExpanded = "true");
498
503
  if (typeof this.options.onOpen === "function") {
499
- this.options.onOpen.call(this);
504
+ this.options.onOpen.call(this, this.el);
500
505
  }
501
- if (this.cardRevealClose) this._setupRevealCloseEventHandlers();
502
506
  };
503
507
  /**
504
508
  * Hide card reveal.
505
509
  */
506
510
  this.close = () => {
507
511
  if (!this.isOpen || !this.cardReveal) return;
512
+ const focusWasInside = this.cardReveal.contains(document.activeElement);
508
513
  this.isOpen = false;
509
- this.cardReveal.ariaExpanded = "false";
510
- this._activators.forEach((el) => el.tabIndex = 0);
511
- if (this.cardRevealClose) this.cardRevealClose.tabIndex = -1;
514
+ this.cardReveal.classList.remove("open");
515
+ this.cardReveal.inert = true;
516
+ this._activators.forEach((activator) => activator.ariaExpanded = "false");
512
517
  if (typeof this.options.onClose === "function") {
513
- this.options.onClose.call(this);
518
+ this.options.onClose.call(this, this.el);
519
+ }
520
+ if (focusWasInside) {
521
+ (this._lastActivator ?? this._activators[0])?.focus();
514
522
  }
515
- if (this.cardRevealClose) this._removeRevealCloseEventHandlers();
516
523
  };
517
524
  this.el["Expressive_Cards"] = this;
518
525
  this.options = {
519
526
  ..._Cards.defaults,
520
527
  ...options
521
528
  };
522
- this._activators = [];
523
- this.cardReveal = this.el.querySelector(":scope > aside");
529
+ const cardReveals = this.el.querySelectorAll(":scope > aside");
530
+ const cardReveal = cardReveals.length === 1 ? cardReveals[0] : null;
531
+ const activators = cardReveal ? Array.from(
532
+ this.el.querySelectorAll(
533
+ 'button.card-reveal-trigger[type="button"]'
534
+ )
535
+ ).filter(
536
+ (activator) => isUsableCardTrigger(activator, this.el, cardReveal)
537
+ ) : [];
538
+ this.cardReveal = cardReveal?.id && activators.length > 0 ? cardReveal : null;
539
+ this._activators = this.cardReveal ? activators : [];
524
540
  if (this.cardReveal) {
525
- this._activators = Array.from(this.el.querySelectorAll(".activator"));
526
- this._activators.forEach((el2) => {
527
- if (el2) el2.tabIndex = 0;
541
+ this._ownsRevealInitializedClass = !this.el.classList.contains(REVEAL_INITIALIZED_CLASS);
542
+ this.el.classList.add(REVEAL_INITIALIZED_CLASS);
543
+ this._activators.forEach((activator) => {
544
+ this._initialAriaControls.set(activator, activator.getAttribute("aria-controls"));
545
+ activator.ariaExpanded = "false";
546
+ activator.setAttribute("aria-controls", this.cardReveal.id);
528
547
  });
529
- this.cardRevealClose = this.cardReveal.querySelector(
530
- ":scope > :is(h1, h2, h3, h4, h5, h6)"
531
- );
532
- if (this.cardRevealClose) this.cardRevealClose.tabIndex = -1;
533
- this.cardReveal.ariaExpanded = "false";
548
+ this.cardReveal.classList.remove("open");
549
+ this.cardReveal.inert = true;
534
550
  this._setupEventHandlers();
535
551
  }
536
552
  }
@@ -553,13 +569,28 @@ var Expressive = (() => {
553
569
  */
554
570
  destroy() {
555
571
  this._removeEventHandlers();
556
- if (this.cardRevealClose) this._removeRevealCloseEventHandlers();
572
+ if (this.cardReveal) {
573
+ this.cardReveal.classList.remove("open");
574
+ this.cardReveal.inert = false;
575
+ if (this._ownsRevealInitializedClass) this.el.classList.remove(REVEAL_INITIALIZED_CLASS);
576
+ }
577
+ this._activators.forEach((activator) => {
578
+ activator.removeAttribute("aria-expanded");
579
+ const initialAriaControls = this._initialAriaControls.get(activator);
580
+ if (initialAriaControls === null) activator.removeAttribute("aria-controls");
581
+ else if (initialAriaControls !== void 0) {
582
+ activator.setAttribute("aria-controls", initialAriaControls);
583
+ }
584
+ });
585
+ this.isOpen = false;
557
586
  this._activators = [];
587
+ this._initialAriaControls.clear();
588
+ this._lastActivator = null;
558
589
  this.el["Expressive_Cards"] = void 0;
559
590
  }
560
591
  static Init() {
561
592
  Utils.onDocumentReady(() => {
562
- const cards = document.querySelectorAll(CARDS_SELECTOR);
593
+ const cards = Array.from(document.querySelectorAll(CARDS_SELECTOR)).filter(isCardReveal);
563
594
  cards.forEach((el) => {
564
595
  if (el && el["Expressive_Cards"] == void 0) this.init(el);
565
596
  });
@@ -567,8 +598,173 @@ var Expressive = (() => {
567
598
  }
568
599
  };
569
600
 
570
- // src/ts/components/menu.ts
601
+ // src/ts/components/expandingCard.ts
571
602
  var _defaults2 = {
603
+ onOpen: null,
604
+ onClose: null
605
+ };
606
+ var EXPANDING_CARD_SELECTOR = "article.expanding-card:has(> dialog.expanding-card-dialog)";
607
+ var ExpandingCard = class _ExpandingCard extends Component {
608
+ constructor(el, options) {
609
+ super(el, options, _ExpandingCard);
610
+ this.isOpen = false;
611
+ this.closeGeneration = 0;
612
+ this._setupEventHandlers = () => {
613
+ this.trigger?.addEventListener("click", this.open);
614
+ this.trigger?.addEventListener("keypress", this._handleTriggerKeypress);
615
+ this.closeButton?.addEventListener("click", this.close);
616
+ this.dialog?.addEventListener("cancel", this._handleCancel);
617
+ this.dialog?.addEventListener("close", this._handleNativeClose);
618
+ };
619
+ this._removeEventHandlers = () => {
620
+ this.trigger?.removeEventListener("click", this.open);
621
+ this.trigger?.removeEventListener("keypress", this._handleTriggerKeypress);
622
+ this.closeButton?.removeEventListener("click", this.close);
623
+ this.dialog?.removeEventListener("cancel", this._handleCancel);
624
+ this.dialog?.removeEventListener("close", this._handleNativeClose);
625
+ };
626
+ this._handleTriggerKeypress = (event) => {
627
+ if (event.key === Utils.keys.ENTER || event.key === " ") {
628
+ event.preventDefault();
629
+ this.open();
630
+ }
631
+ };
632
+ this._handleCancel = (event) => {
633
+ event.preventDefault();
634
+ this.close();
635
+ };
636
+ this._handleNativeClose = () => {
637
+ if (this.dialog.open) return;
638
+ this.closeGeneration++;
639
+ this.isOpen = false;
640
+ this.trigger.ariaExpanded = "false";
641
+ this.dialog.ariaExpanded = "false";
642
+ this.dialog.classList.remove("expanded");
643
+ };
644
+ this._setTransitionOrigin = () => {
645
+ if (!this.dialog) return;
646
+ const card = this.el.getBoundingClientRect();
647
+ const media = this.el.querySelector(
648
+ ":scope > figure, :scope > img"
649
+ );
650
+ const mediaRect = media?.getBoundingClientRect();
651
+ this.dialog.style.setProperty(
652
+ "--md-comp-expanding-card-from-top",
653
+ `${Math.max(0, card.top)}px`
654
+ );
655
+ this.dialog.style.setProperty(
656
+ "--md-comp-expanding-card-from-right",
657
+ `${Math.max(0, window.innerWidth - card.right)}px`
658
+ );
659
+ this.dialog.style.setProperty(
660
+ "--md-comp-expanding-card-from-bottom",
661
+ `${Math.max(0, window.innerHeight - card.bottom)}px`
662
+ );
663
+ this.dialog.style.setProperty(
664
+ "--md-comp-expanding-card-from-left",
665
+ `${Math.max(0, card.left)}px`
666
+ );
667
+ this.dialog.style.setProperty(
668
+ "--md-comp-expanding-card-from-media-height",
669
+ `${Math.max(0, mediaRect?.height ?? 0)}px`
670
+ );
671
+ };
672
+ this.open = () => {
673
+ if (this.isOpen || !this.dialog || !this.trigger) return;
674
+ this.closeGeneration++;
675
+ this._setTransitionOrigin();
676
+ this.isOpen = true;
677
+ this.trigger.ariaExpanded = "true";
678
+ this.dialog.ariaExpanded = "true";
679
+ this.dialog.showModal();
680
+ void this.dialog.offsetWidth;
681
+ this.dialog.classList.add("expanded");
682
+ this.closeButton?.focus();
683
+ if (typeof this.options.onOpen === "function") {
684
+ this.options.onOpen.call(this, this.el);
685
+ }
686
+ };
687
+ this.close = () => {
688
+ if (!this.isOpen || !this.dialog || !this.trigger) return;
689
+ const generation = ++this.closeGeneration;
690
+ this._setTransitionOrigin();
691
+ this.isOpen = false;
692
+ this.trigger.ariaExpanded = "false";
693
+ this.dialog.ariaExpanded = "false";
694
+ this.dialog.classList.remove("expanded");
695
+ if (typeof this.options.onClose === "function") {
696
+ this.options.onClose.call(this, this.el);
697
+ }
698
+ const finish = () => {
699
+ if (generation !== this.closeGeneration || this.isOpen || !this.dialog.open) return;
700
+ this.dialog.close();
701
+ if (this.dialog.isConnected && this.trigger.isConnected) this.trigger.focus();
702
+ };
703
+ const reducedMotion = window.matchMedia(
704
+ "(prefers-reduced-motion: reduce)"
705
+ ).matches;
706
+ const transitions = reducedMotion ? [] : (this.dialog.getAnimations?.() ?? []).filter((animation) => animation.transitionProperty === "clip-path");
707
+ if (transitions.length === 0) finish();
708
+ else void Promise.allSettled(transitions.map((animation) => animation.finished)).then(finish);
709
+ };
710
+ this.el["Expressive_ExpandingCard"] = this;
711
+ this.options = {
712
+ ..._ExpandingCard.defaults,
713
+ ...options
714
+ };
715
+ this.dialog = this.el.querySelector(
716
+ ":scope > dialog.expanding-card-dialog"
717
+ );
718
+ this.trigger = this.el.querySelector(
719
+ ":scope > .expanding-card-trigger, :scope > figure > .expanding-card-trigger"
720
+ );
721
+ this.closeButton = this.dialog?.querySelector(".expanding-card-close") ?? null;
722
+ if (this.trigger && this.dialog) {
723
+ this.trigger.ariaExpanded = "false";
724
+ if (this.dialog.id)
725
+ this.trigger.setAttribute("aria-controls", this.dialog.id);
726
+ if (!this.trigger.matches("a[href], button, input, select, textarea")) {
727
+ this.trigger.tabIndex = 0;
728
+ this.trigger.setAttribute("role", "button");
729
+ }
730
+ this.dialog.ariaExpanded = "false";
731
+ this._setupEventHandlers();
732
+ }
733
+ }
734
+ static get defaults() {
735
+ return _defaults2;
736
+ }
737
+ static init(els, options) {
738
+ return super.init(els, options, _ExpandingCard);
739
+ }
740
+ static getInstance(el) {
741
+ return el["Expressive_ExpandingCard"];
742
+ }
743
+ destroy() {
744
+ this._removeEventHandlers();
745
+ this.closeGeneration++;
746
+ this.isOpen = false;
747
+ if (this.trigger) this.trigger.ariaExpanded = "false";
748
+ if (this.dialog) this.dialog.ariaExpanded = "false";
749
+ this.dialog?.classList.remove("expanded");
750
+ if (this.dialog?.open) this.dialog.close();
751
+ this.el["Expressive_ExpandingCard"] = void 0;
752
+ }
753
+ static Init() {
754
+ Utils.onDocumentReady(() => {
755
+ document.querySelectorAll(EXPANDING_CARD_SELECTOR).forEach((el) => {
756
+ if (el && el["Expressive_ExpandingCard"] == void 0) {
757
+ this.init(el);
758
+ }
759
+ });
760
+ });
761
+ }
762
+ };
763
+
764
+ // src/ts/components/menu.ts
765
+ var MIN_SURFACE_WIDTH = 112;
766
+ var MAX_SURFACE_WIDTH = 280;
767
+ var _defaults3 = {
572
768
  alignment: "left",
573
769
  autoFocus: true,
574
770
  constrainWidth: true,
@@ -584,9 +780,12 @@ var Expressive = (() => {
584
780
  onCloseEnd: null,
585
781
  onItemClick: null
586
782
  };
783
+ var SPACE = " ";
587
784
  var _Menu = class _Menu extends Component {
588
785
  constructor(el, options) {
589
786
  super(el, options, _Menu);
787
+ this._pendingTimers = /* @__PURE__ */ new Set();
788
+ this._transition = 0;
590
789
  this._handleClick = (e) => {
591
790
  e.preventDefault();
592
791
  if (this.isOpen) {
@@ -620,7 +819,7 @@ var Expressive = (() => {
620
819
  if (this.options.closeOnClick && this.menuEl?.contains(target) && !this.isTouchMoving) {
621
820
  this.close();
622
821
  } else if (!this.menuEl?.contains(target)) {
623
- setTimeout(() => {
822
+ this._schedule(() => {
624
823
  if (this.isOpen) {
625
824
  this.close();
626
825
  }
@@ -629,8 +828,8 @@ var Expressive = (() => {
629
828
  this.isTouchMoving = false;
630
829
  };
631
830
  this._handleTriggerKeydown = (e) => {
632
- const arrowDownOrEnter = Utils.keys.ARROW_DOWN.includes(e.key) || Utils.keys.ENTER.includes(e.key);
633
- if (arrowDownOrEnter && !this.isOpen) {
831
+ const opensMenu = e.key === SPACE || e.key === Utils.keys.ENTER || e.key === Utils.keys.ARROW_DOWN || e.key === Utils.keys.ARROW_UP;
832
+ if (opensMenu && !this.isOpen) {
634
833
  e.preventDefault();
635
834
  this.open();
636
835
  }
@@ -644,13 +843,18 @@ var Expressive = (() => {
644
843
  this._handleMenuClick = (e) => {
645
844
  const target = e.target;
646
845
  const li = target.closest("li");
846
+ if (li?.matches('.disabled, [aria-disabled="true"]')) {
847
+ e.preventDefault();
848
+ e.stopPropagation();
849
+ return;
850
+ }
647
851
  if (li && this._isSubmenuTriggerClick(target)) {
648
852
  e.preventDefault();
649
853
  e.stopPropagation();
650
854
  this._toggleSubmenu(li);
651
855
  return;
652
856
  }
653
- if (typeof this.options.onItemClick === "function") {
857
+ if (li && typeof this.options.onItemClick === "function") {
654
858
  this.options.onItemClick.call(this, li);
655
859
  }
656
860
  };
@@ -663,11 +867,11 @@ var Expressive = (() => {
663
867
  }
664
868
  };
665
869
  this._handleMenuKeydown = (e) => {
666
- const arrowUpOrDown = Utils.keys.ARROW_DOWN.includes(e.key) || Utils.keys.ARROW_UP.includes(e.key);
667
- if (Utils.keys.TAB.includes(e.key)) {
870
+ const arrowUpOrDown = e.key === Utils.keys.ARROW_DOWN || e.key === Utils.keys.ARROW_UP;
871
+ if (e.key === Utils.keys.TAB) {
668
872
  e.preventDefault();
669
873
  this.close();
670
- } else if (Utils.keys.ARROW_RIGHT.includes(e.key) && this.isOpen) {
874
+ } else if (e.key === Utils.keys.ARROW_RIGHT && this.isOpen) {
671
875
  const li = this._focusedRow(e);
672
876
  if (li?.querySelector(":scope > menu")) {
673
877
  e.preventDefault();
@@ -677,7 +881,7 @@ var Expressive = (() => {
677
881
  const first = li.querySelector(":scope > menu > li");
678
882
  first?.focus();
679
883
  }
680
- } else if (Utils.keys.ARROW_LEFT.includes(e.key) && this.isOpen) {
884
+ } else if (e.key === Utils.keys.ARROW_LEFT && this.isOpen) {
681
885
  const submenu = this._containingSubmenu(e);
682
886
  if (submenu) {
683
887
  e.preventDefault();
@@ -688,7 +892,7 @@ var Expressive = (() => {
688
892
  }
689
893
  } else if (arrowUpOrDown && this.isOpen) {
690
894
  e.preventDefault();
691
- const direction = Utils.keys.ARROW_DOWN.includes(e.key) ? 1 : -1;
895
+ const direction = e.key === Utils.keys.ARROW_DOWN ? 1 : -1;
692
896
  const list = this._containingSubmenu(e) ?? this.menuEl;
693
897
  let newFocusedIndex = this.focusedIndex;
694
898
  let hasFoundNewIndex = false;
@@ -700,17 +904,17 @@ var Expressive = (() => {
700
904
  }
701
905
  } while (newFocusedIndex < list.children.length && newFocusedIndex >= 0);
702
906
  if (hasFoundNewIndex) {
703
- if (this.focusedIndex >= 0)
704
- list.children[this.focusedIndex]?.classList.remove("active");
907
+ if (this.focusedIndex >= 0) list.children[this.focusedIndex]?.classList.remove("active");
705
908
  this.focusedIndex = newFocusedIndex;
706
909
  const item = list.children[this.focusedIndex];
707
910
  item.classList.add("active");
708
911
  item.focus({ preventScroll: true });
709
912
  }
710
- } else if (Utils.keys.ENTER.includes(e.key) && this.isOpen) {
913
+ } else if ((e.key === Utils.keys.ENTER || e.key === SPACE) && this.isOpen) {
711
914
  const li = this._focusedRow(e);
915
+ e.preventDefault();
916
+ if (li?.matches('.disabled, [aria-disabled="true"]')) return;
712
917
  if (li?.querySelector(":scope > menu")) {
713
- e.preventDefault();
714
918
  this._toggleSubmenu(li);
715
919
  return;
716
920
  }
@@ -721,7 +925,7 @@ var Expressive = (() => {
721
925
  } else if (focusedElement instanceof HTMLElement) {
722
926
  focusedElement.click();
723
927
  }
724
- } else if (Utils.keys.ESC.includes(e.key) && this.isOpen) {
928
+ } else if (e.key === Utils.keys.ESC && this.isOpen) {
725
929
  e.preventDefault();
726
930
  const openSub = this.menuEl.querySelector("li.open");
727
931
  if (openSub) {
@@ -735,11 +939,12 @@ var Expressive = (() => {
735
939
  const keyText = e.key.toLowerCase();
736
940
  const isLetter = /[a-zA-Z0-9-_]/.test(keyText);
737
941
  const specialKeys = [
738
- ...Utils.keys.ARROW_DOWN,
739
- ...Utils.keys.ARROW_UP,
740
- ...Utils.keys.ENTER,
741
- ...Utils.keys.ESC,
742
- ...Utils.keys.TAB
942
+ Utils.keys.ARROW_DOWN,
943
+ Utils.keys.ARROW_UP,
944
+ Utils.keys.ENTER,
945
+ Utils.keys.ESC,
946
+ Utils.keys.TAB,
947
+ SPACE
743
948
  ];
744
949
  if (isLetter && !specialKeys.includes(e.key)) {
745
950
  this.filterQuery.push(keyText);
@@ -768,16 +973,18 @@ var Expressive = (() => {
768
973
  * Open menu.
769
974
  */
770
975
  this.open = () => {
771
- if (this.isOpen) return;
976
+ if (this.isOpen || this.el["Expressive_Menu"] !== this) return;
977
+ const transition = this._clearPendingTimers();
772
978
  this.isOpen = true;
773
979
  if (typeof this.options.onOpenStart === "function") {
774
980
  this.options.onOpenStart.call(this, this.el);
775
981
  }
982
+ if (transition !== this._transition) return;
776
983
  this._resetMenuStyles();
777
984
  this.menuEl.style.display = "block";
778
985
  this._placeMenu();
986
+ this._schedule(() => this._setupTemporaryEventHandlers(), 0);
779
987
  this._animateIn();
780
- setTimeout(() => this._setupTemporaryEventHandlers(), 0);
781
988
  this.el.ariaExpanded = "true";
782
989
  };
783
990
  /**
@@ -785,18 +992,20 @@ var Expressive = (() => {
785
992
  */
786
993
  this.close = () => {
787
994
  if (!this.isOpen) return;
995
+ const transition = this._clearPendingTimers();
788
996
  this.isOpen = false;
789
997
  this.focusedIndex = -1;
790
998
  this._closeSubmenus();
791
999
  if (typeof this.options.onCloseStart === "function") {
792
1000
  this.options.onCloseStart.call(this, this.el);
793
1001
  }
1002
+ if (transition !== this._transition) return;
794
1003
  this._animateOut();
795
1004
  this._removeTemporaryEventHandlers();
1005
+ this.el.ariaExpanded = "false";
796
1006
  if (this.options.autoFocus) {
797
1007
  this.el.focus();
798
1008
  }
799
- this.el.ariaExpanded = "false";
800
1009
  };
801
1010
  /**
802
1011
  * While menu is open, you can recalculate its dimensions if its contents have changed.
@@ -810,7 +1019,7 @@ var Expressive = (() => {
810
1019
  this.el["Expressive_Menu"] = this;
811
1020
  _Menu._menus.push(this);
812
1021
  this.id = Utils.getIdFromTrigger(el);
813
- this.menuEl = document.getElementById(this.id);
1022
+ this.menuEl = Utils.getElementById(el, this.id);
814
1023
  this.options = {
815
1024
  ..._Menu.defaults,
816
1025
  ...options
@@ -821,13 +1030,17 @@ var Expressive = (() => {
821
1030
  this.focusedIndex = -1;
822
1031
  this.filterQuery = [];
823
1032
  this.el.ariaExpanded = "false";
1033
+ if (!this.el.hasAttribute("aria-haspopup")) this.el.setAttribute("aria-haspopup", "menu");
1034
+ if (this.id && !this.el.hasAttribute("aria-controls"))
1035
+ this.el.setAttribute("aria-controls", this.id);
824
1036
  this._moveMenuToElement();
1037
+ this._setupAccessibility();
825
1038
  this._makeMenuFocusable();
826
1039
  this._setupSubmenus();
827
1040
  this._setupEventHandlers();
828
1041
  }
829
1042
  static get defaults() {
830
- return _defaults2;
1043
+ return _defaults3;
831
1044
  }
832
1045
  /**
833
1046
  * Initializes instances of Menu.
@@ -841,6 +1054,9 @@ var Expressive = (() => {
841
1054
  return el["Expressive_Menu"];
842
1055
  }
843
1056
  destroy() {
1057
+ this._clearPendingTimers();
1058
+ this.isOpen = false;
1059
+ this.el.ariaExpanded = "false";
844
1060
  clearTimeout(this.filterTimeout);
845
1061
  this._resetMenuStyles();
846
1062
  this._removeEventHandlers();
@@ -877,7 +1093,9 @@ var Expressive = (() => {
877
1093
  }
878
1094
  _setupTemporaryEventHandlers() {
879
1095
  document.body.addEventListener("click", this._handleDocumentClick);
880
- document.body.addEventListener("touchmove", this._handleDocumentTouchmove, { passive: true });
1096
+ document.body.addEventListener("touchmove", this._handleDocumentTouchmove, {
1097
+ passive: true
1098
+ });
881
1099
  this.menuEl?.addEventListener("keydown", this._handleMenuKeydown);
882
1100
  window.addEventListener("resize", this._handleWindowResize);
883
1101
  }
@@ -887,6 +1105,33 @@ var Expressive = (() => {
887
1105
  this.menuEl?.removeEventListener("keydown", this._handleMenuKeydown);
888
1106
  window.removeEventListener("resize", this._handleWindowResize);
889
1107
  }
1108
+ _setupAccessibility() {
1109
+ if (!this.menuEl) return;
1110
+ if (!this.menuEl.hasAttribute("role")) this.menuEl.setAttribute("role", "menu");
1111
+ const menus = [this.menuEl, ...this.menuEl.querySelectorAll("menu")];
1112
+ menus.forEach((menu) => {
1113
+ if (!menu.hasAttribute("role")) menu.setAttribute("role", "menu");
1114
+ if (menu.getAttribute("role") !== "menu") return;
1115
+ menu.querySelectorAll(":scope > li").forEach((li) => {
1116
+ if (li.classList.contains("divider")) {
1117
+ if (!li.hasAttribute("role")) li.setAttribute("role", "separator");
1118
+ return;
1119
+ }
1120
+ if (li.classList.contains("gap")) {
1121
+ li.setAttribute("aria-hidden", "true");
1122
+ return;
1123
+ }
1124
+ if (li.classList.contains("label") || li.classList.contains("optgroup")) {
1125
+ if (!li.hasAttribute("role")) li.setAttribute("role", "presentation");
1126
+ return;
1127
+ }
1128
+ if (!li.hasAttribute("role")) li.setAttribute("role", "menuitem");
1129
+ if (li.classList.contains("disabled") && !li.hasAttribute("aria-disabled")) {
1130
+ li.setAttribute("aria-disabled", "true");
1131
+ }
1132
+ });
1133
+ });
1134
+ }
890
1135
  _isSubmenuTriggerClick(target) {
891
1136
  if (!this.menuEl) return false;
892
1137
  const li = target.closest("li");
@@ -973,7 +1218,7 @@ var Expressive = (() => {
973
1218
  _makeMenuFocusable() {
974
1219
  if (!this.menuEl) return;
975
1220
  this.menuEl.popover = "";
976
- this.menuEl.tabIndex = 0;
1221
+ this.menuEl.tabIndex = -1;
977
1222
  this.menuEl.querySelectorAll(":scope li, :scope > hr").forEach((el) => {
978
1223
  if (el instanceof HTMLHRElement || el.classList.contains("divider") || el.classList.contains("gap") || el.classList.contains("label") || el.classList.contains("optgroup")) {
979
1224
  el.setAttribute("tabindex", "-1");
@@ -982,6 +1227,15 @@ var Expressive = (() => {
982
1227
  if (!el.getAttribute("tabindex")) el.setAttribute("tabindex", "0");
983
1228
  });
984
1229
  }
1230
+ _focusInitialItem() {
1231
+ if (!this.menuEl || !this.options.autoFocus) return;
1232
+ const first = Array.from(this.menuEl.children).find(
1233
+ (child) => child instanceof HTMLElement && child.tabIndex !== -1
1234
+ );
1235
+ if (!first) return;
1236
+ this.focusedIndex = Array.from(this.menuEl.children).indexOf(first);
1237
+ first.focus({ preventScroll: true });
1238
+ }
985
1239
  _focusFocusedItem() {
986
1240
  if (this.focusedIndex >= 0 && this.focusedIndex < this.menuEl.children.length && this.options.autoFocus) {
987
1241
  this.menuEl.children[this.focusedIndex].focus({
@@ -1065,18 +1319,32 @@ var Expressive = (() => {
1065
1319
  width: idealWidth
1066
1320
  };
1067
1321
  }
1322
+ _schedule(callback, delay) {
1323
+ const timer = setTimeout(() => {
1324
+ this._pendingTimers.delete(timer);
1325
+ callback();
1326
+ }, delay);
1327
+ this._pendingTimers.add(timer);
1328
+ }
1329
+ _clearPendingTimers() {
1330
+ for (const timer of this._pendingTimers) clearTimeout(timer);
1331
+ this._pendingTimers.clear();
1332
+ return ++this._transition;
1333
+ }
1068
1334
  _animateIn() {
1335
+ const transition = this._transition;
1069
1336
  const duration = this.options.inDuration;
1070
1337
  this.menuEl.style.transition = "none";
1071
1338
  this.menuEl.style.opacity = "0";
1072
1339
  this.menuEl.style.transform = "scale(0.85)";
1073
- setTimeout(() => {
1340
+ this._schedule(() => {
1074
1341
  this.menuEl.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`;
1075
1342
  this.menuEl.style.opacity = "1";
1076
1343
  this.menuEl.style.transform = "scale(1)";
1077
1344
  }, 1);
1078
- setTimeout(() => {
1079
- if (this.options.autoFocus) this.menuEl.focus();
1345
+ this._schedule(() => {
1346
+ this._focusInitialItem();
1347
+ if (transition !== this._transition) return;
1080
1348
  if (typeof this.options.onOpenEnd === "function") this.options.onOpenEnd.call(this, this.el);
1081
1349
  }, duration);
1082
1350
  }
@@ -1085,7 +1353,7 @@ var Expressive = (() => {
1085
1353
  this.menuEl.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`;
1086
1354
  this.menuEl.style.opacity = "0";
1087
1355
  this.menuEl.style.transform = "scale(0.85)";
1088
- setTimeout(() => {
1356
+ this._schedule(() => {
1089
1357
  this._resetMenuStyles();
1090
1358
  if (typeof this.options.onCloseEnd === "function")
1091
1359
  this.options.onCloseEnd.call(this, this.el);
@@ -1093,11 +1361,11 @@ var Expressive = (() => {
1093
1361
  }
1094
1362
  _getClosestAncestor(el, condition) {
1095
1363
  let ancestor = el.parentNode;
1096
- while (ancestor !== null && ancestor !== document) {
1097
- if (condition(ancestor)) {
1364
+ while (ancestor !== null && ancestor !== void 0 && ancestor !== document) {
1365
+ if (ancestor.nodeType === Node.ELEMENT_NODE && condition(ancestor)) {
1098
1366
  return ancestor;
1099
1367
  }
1100
- ancestor = ancestor.parentElement;
1368
+ ancestor = ancestor.parentNode ?? ancestor.host;
1101
1369
  }
1102
1370
  return null;
1103
1371
  }
@@ -1109,12 +1377,14 @@ var Expressive = (() => {
1109
1377
  }
1110
1378
  );
1111
1379
  if (!closestOverflowParent) {
1112
- closestOverflowParent = !!this.menuEl.offsetParent ? this.menuEl.offsetParent : this.menuEl.parentNode;
1380
+ const parent = !!this.menuEl.offsetParent ? this.menuEl.offsetParent : this.menuEl.parentNode;
1381
+ closestOverflowParent = parent.host ?? parent;
1113
1382
  }
1114
1383
  if (getComputedStyle(closestOverflowParent).position === "static")
1115
1384
  closestOverflowParent.style.position = "relative";
1116
1385
  const natural = this.menuEl.getBoundingClientRect().width;
1117
- const idealWidth = this.options.constrainWidth ? this.el.getBoundingClientRect().width : Math.min(280, Math.max(112, natural));
1386
+ const triggerWidth = this.el.getBoundingClientRect().width;
1387
+ const idealWidth = this.options.constrainWidth && triggerWidth >= MIN_SURFACE_WIDTH ? triggerWidth : Math.min(MAX_SURFACE_WIDTH, Math.max(MIN_SURFACE_WIDTH, natural));
1118
1388
  this.menuEl.style.width = idealWidth + "px";
1119
1389
  const positionInfo = this._getMenuPosition(closestOverflowParent);
1120
1390
  this.menuEl.style.left = positionInfo.x + "px";
@@ -1128,7 +1398,7 @@ var Expressive = (() => {
1128
1398
  var Menu = _Menu;
1129
1399
 
1130
1400
  // src/ts/components/autocomplete.ts
1131
- var _defaults3 = {
1401
+ var _defaults4 = {
1132
1402
  data: [],
1133
1403
  // Autocomplete data set
1134
1404
  onAutocomplete: null,
@@ -1158,7 +1428,7 @@ var Expressive = (() => {
1158
1428
  constructor(el, options) {
1159
1429
  super(el, options, _Autocomplete);
1160
1430
  this._handleInputBlur = () => {
1161
- if (!this._mousedown) {
1431
+ if (!this._pointerDown) {
1162
1432
  this.close();
1163
1433
  this._resetAutocomplete();
1164
1434
  }
@@ -1167,9 +1437,9 @@ var Expressive = (() => {
1167
1437
  if (e.type === "keyup") _Autocomplete._keydown = false;
1168
1438
  this.count = 0;
1169
1439
  const actualValue = this.el.value.toLocaleLowerCase();
1170
- if (Utils.keys.ENTER.includes(e.key) || Utils.keys.ARROW_UP.includes(e.key) || Utils.keys.ARROW_DOWN.includes(e.key))
1440
+ if (e.key === Utils.keys.ENTER || e.key === Utils.keys.ARROW_UP || e.key === Utils.keys.ARROW_DOWN)
1171
1441
  return;
1172
- if (this.oldVal !== actualValue && Utils.tabPressed) {
1442
+ if (this.oldVal !== actualValue && this.el.matches(":focus-visible")) {
1173
1443
  this.open();
1174
1444
  }
1175
1445
  this._inputChangeDetection(actualValue);
@@ -1193,7 +1463,7 @@ var Expressive = (() => {
1193
1463
  this._handleInputKeydown = (e) => {
1194
1464
  _Autocomplete._keydown = true;
1195
1465
  const numItems = this.container.querySelectorAll("li").length;
1196
- if (Utils.keys.ENTER.includes(e.key) && this.activeIndex >= 0) {
1466
+ if (e.key === Utils.keys.ENTER && this.activeIndex >= 0) {
1197
1467
  const liElement = this.container.querySelectorAll("li")[this.activeIndex];
1198
1468
  if (liElement) {
1199
1469
  this.selectOption(liElement.getAttribute("data-id"));
@@ -1201,15 +1471,14 @@ var Expressive = (() => {
1201
1471
  }
1202
1472
  return;
1203
1473
  }
1204
- if (Utils.keys.ARROW_UP.includes(e.key) || Utils.keys.ARROW_DOWN.includes(e.key)) {
1474
+ if (e.key === Utils.keys.ARROW_UP || e.key === Utils.keys.ARROW_DOWN) {
1205
1475
  e.preventDefault();
1206
- if (Utils.keys.ARROW_UP.includes(e.key) && this.activeIndex > 0) this.activeIndex--;
1207
- if (Utils.keys.ARROW_DOWN.includes(e.key) && this.activeIndex < numItems - 1)
1476
+ if (e.key === Utils.keys.ARROW_UP && this.activeIndex > 0) this.activeIndex--;
1477
+ if (e.key === Utils.keys.ARROW_DOWN && this.activeIndex < numItems - 1)
1208
1478
  this.activeIndex++;
1209
- this.$active?.classList.remove("active");
1479
+ this._setActive(null);
1210
1480
  if (this.activeIndex >= 0) {
1211
- this.$active = this.container.querySelectorAll("li")[this.activeIndex];
1212
- this.$active?.classList.add("active");
1481
+ this._setActive(this.container.querySelectorAll("li")[this.activeIndex]);
1213
1482
  this.container.children[this.activeIndex].scrollIntoView({
1214
1483
  behavior: "smooth",
1215
1484
  block: "nearest",
@@ -1221,11 +1490,17 @@ var Expressive = (() => {
1221
1490
  this._handleInputClick = () => {
1222
1491
  this.open();
1223
1492
  };
1224
- this._handleContainerMousedownAndTouchstart = () => {
1225
- this._mousedown = true;
1493
+ // The flag says "a press is in flight inside the option list", so that a blur
1494
+ // caused by that press does not close the list out from under it. Without the
1495
+ // cancel half, a cancelled gesture left the flag set and the next blur could
1496
+ // not close the list at all.
1497
+ this._handleContainerPointerDown = (e) => {
1498
+ if (!e.isPrimary) return;
1499
+ if (e.pointerType === "mouse" && e.button !== 0) return;
1500
+ this._pointerDown = true;
1226
1501
  };
1227
- this._handleContainerMouseupAndTouchend = () => {
1228
- this._mousedown = false;
1502
+ this._handleContainerPointerUp = () => {
1503
+ this._pointerDown = false;
1229
1504
  };
1230
1505
  /**
1231
1506
  * Show autocomplete.
@@ -1238,7 +1513,9 @@ var Expressive = (() => {
1238
1513
  this._renderMenu();
1239
1514
  }
1240
1515
  if (!this.menu.isOpen) {
1241
- setTimeout(() => {
1516
+ if (this._openTimer !== void 0) clearTimeout(this._openTimer);
1517
+ this._openTimer = setTimeout(() => {
1518
+ this._openTimer = void 0;
1242
1519
  this.menu.open();
1243
1520
  }, 0);
1244
1521
  } else this.menu.recalculateDimensions();
@@ -1261,12 +1538,32 @@ var Expressive = (() => {
1261
1538
  this.selectedValues = this.selectedValues || this.options.selected.map((value) => ({ id: value })) || [];
1262
1539
  this.menuItems = this.options.data || [];
1263
1540
  this.$active = null;
1264
- this._mousedown = false;
1541
+ this._pointerDown = false;
1265
1542
  this._setupMenu();
1266
1543
  this._setupEventHandlers();
1267
1544
  }
1545
+ /**
1546
+ * The active entry, for both the eye and the screen reader. The class alone
1547
+ * was invisible to assistive technology; aria-activedescendant on the input
1548
+ * is what reports the move.
1549
+ *
1550
+ * Deliberately does not touch aria-selected. Active and selected are two
1551
+ * different things - in a multi-select the checkboxes track real choices,
1552
+ * and folding the highlight into aria-selected would announce a committed
1553
+ * choice as unselected the moment the user arrowed past it.
1554
+ */
1555
+ _setActive(item) {
1556
+ this.$active?.classList.remove("active");
1557
+ this.$active = item;
1558
+ if (item) {
1559
+ item.classList.add("active");
1560
+ this.el.setAttribute("aria-activedescendant", item.id);
1561
+ } else {
1562
+ this.el.removeAttribute("aria-activedescendant");
1563
+ }
1564
+ }
1268
1565
  static get defaults() {
1269
- return _defaults3;
1566
+ return _defaults4;
1270
1567
  }
1271
1568
  /**
1272
1569
  * Initializes instances of Autocomplete.
@@ -1292,6 +1589,10 @@ var Expressive = (() => {
1292
1589
  return _Autocomplete._loadingTemplate.cloneNode(true);
1293
1590
  }
1294
1591
  destroy() {
1592
+ if (this._openTimer !== void 0) {
1593
+ clearTimeout(this._openTimer);
1594
+ this._openTimer = void 0;
1595
+ }
1295
1596
  this._removeEventHandlers();
1296
1597
  this._removeMenu();
1297
1598
  this.el["Expressive_Autocomplete"] = void 0;
@@ -1302,12 +1603,9 @@ var Expressive = (() => {
1302
1603
  this.el.addEventListener("focus", this._handleInputFocus);
1303
1604
  this.el.addEventListener("keydown", this._handleInputKeydown);
1304
1605
  this.el.addEventListener("click", this._handleInputClick);
1305
- this.container.addEventListener("mousedown", this._handleContainerMousedownAndTouchstart);
1306
- this.container.addEventListener("mouseup", this._handleContainerMouseupAndTouchend);
1307
- if (typeof window.ontouchstart !== "undefined") {
1308
- this.container.addEventListener("touchstart", this._handleContainerMousedownAndTouchstart);
1309
- this.container.addEventListener("touchend", this._handleContainerMouseupAndTouchend);
1310
- }
1606
+ this.container.addEventListener("pointerdown", this._handleContainerPointerDown);
1607
+ this.container.addEventListener("pointerup", this._handleContainerPointerUp);
1608
+ this.container.addEventListener("pointercancel", this._handleContainerPointerUp);
1311
1609
  }
1312
1610
  _removeEventHandlers() {
1313
1611
  this.el.removeEventListener("blur", this._handleInputBlur);
@@ -1315,12 +1613,9 @@ var Expressive = (() => {
1315
1613
  this.el.removeEventListener("focus", this._handleInputFocus);
1316
1614
  this.el.removeEventListener("keydown", this._handleInputKeydown);
1317
1615
  this.el.removeEventListener("click", this._handleInputClick);
1318
- this.container.removeEventListener("mousedown", this._handleContainerMousedownAndTouchstart);
1319
- this.container.removeEventListener("mouseup", this._handleContainerMouseupAndTouchend);
1320
- if (typeof window.ontouchstart !== "undefined") {
1321
- this.container.removeEventListener("touchstart", this._handleContainerMousedownAndTouchstart);
1322
- this.container.removeEventListener("touchend", this._handleContainerMouseupAndTouchend);
1323
- }
1616
+ this.container.removeEventListener("pointerdown", this._handleContainerPointerDown);
1617
+ this.container.removeEventListener("pointerup", this._handleContainerPointerUp);
1618
+ this.container.removeEventListener("pointercancel", this._handleContainerPointerUp);
1324
1619
  }
1325
1620
  _setupMenu() {
1326
1621
  this.container = document.createElement("menu");
@@ -1328,6 +1623,10 @@ var Expressive = (() => {
1328
1623
  this.container.id = `autocomplete-options-${Utils.guid()}`;
1329
1624
  this.container.classList.add("autocomplete-content");
1330
1625
  this.container.ariaExpanded = "true";
1626
+ this.container.setAttribute("role", "listbox");
1627
+ this.el.setAttribute("role", "combobox");
1628
+ this.el.setAttribute("aria-autocomplete", "list");
1629
+ this.el.setAttribute("aria-controls", this.container.id);
1331
1630
  this.el.setAttribute("data-target", this.container.id);
1332
1631
  this.menuItems.forEach((menuItem) => {
1333
1632
  const itemElement = this._createMenuItem(menuItem);
@@ -1366,14 +1665,14 @@ var Expressive = (() => {
1366
1665
  }
1367
1666
  _resetCurrentElementPosition() {
1368
1667
  this.activeIndex = -1;
1369
- this.$active?.classList.remove("active");
1668
+ this._setActive(null);
1370
1669
  }
1371
1670
  _resetAutocomplete() {
1372
1671
  this.container.replaceChildren();
1373
1672
  this._resetCurrentElementPosition();
1374
1673
  this.oldVal = null;
1375
1674
  this.isOpen = false;
1376
- this._mousedown = false;
1675
+ this._pointerDown = false;
1377
1676
  }
1378
1677
  _highlightPartialText(input, label) {
1379
1678
  const start = label.toLocaleLowerCase().indexOf("" + input.toLocaleLowerCase());
@@ -1385,6 +1684,10 @@ var Expressive = (() => {
1385
1684
  }
1386
1685
  _createMenuItem(entry) {
1387
1686
  const item = document.createElement("li");
1687
+ item.id = `autocomplete-option-${Utils.guid()}`;
1688
+ item.setAttribute("role", "option");
1689
+ const isSelected = this.selectedValues.some((sel) => sel.id === entry.id);
1690
+ item.setAttribute("aria-selected", isSelected ? "true" : "false");
1388
1691
  item.setAttribute("data-id", entry.id);
1389
1692
  item.setAttribute(
1390
1693
  "style",
@@ -1397,7 +1700,7 @@ var Expressive = (() => {
1397
1700
  selection.style.textAlign = "center";
1398
1701
  const checkbox = document.createElement("input");
1399
1702
  checkbox.type = "checkbox";
1400
- checkbox.checked = this.selectedValues.some((sel) => sel.id === entry.id);
1703
+ checkbox.checked = isSelected;
1401
1704
  const spacer = document.createElement("span");
1402
1705
  spacer.style.paddingLeft = "21px";
1403
1706
  selection.append(checkbox, spacer);
@@ -1543,7 +1846,7 @@ var Expressive = (() => {
1543
1846
  var Autocomplete = _Autocomplete;
1544
1847
 
1545
1848
  // src/ts/components/chips.ts
1546
- var _defaults4 = {
1849
+ var _defaults5 = {
1547
1850
  data: [],
1548
1851
  placeholder: "",
1549
1852
  secondaryPlaceholder: "",
@@ -1552,6 +1855,7 @@ var Expressive = (() => {
1552
1855
  autocompleteOnly: false,
1553
1856
  limit: Infinity,
1554
1857
  allowUserInput: false,
1858
+ i18n: { remove: "Remove" },
1555
1859
  onChipAdd: null,
1556
1860
  onChipSelect: null,
1557
1861
  onChipDelete: null
@@ -1561,7 +1865,7 @@ var Expressive = (() => {
1561
1865
  super(el, options, _Chips);
1562
1866
  this._handleChipClick = (e) => {
1563
1867
  const _chip = e.target.closest(".chip");
1564
- const clickedClose = e.target.classList.contains("close");
1868
+ const clickedClose = !!e.target.closest(".close");
1565
1869
  if (_chip) {
1566
1870
  const index = this._chips.indexOf(_chip);
1567
1871
  if (index < 0) return;
@@ -1583,7 +1887,7 @@ var Expressive = (() => {
1583
1887
  };
1584
1888
  this._handleInputKeydown = (e) => {
1585
1889
  _Chips._keydown = true;
1586
- if (Utils.keys.ENTER.includes(e.key)) {
1890
+ if (e.key === Utils.keys.ENTER) {
1587
1891
  if (this.hasAutocomplete && this.autocomplete && this.autocomplete.isOpen) {
1588
1892
  return;
1589
1893
  }
@@ -1592,7 +1896,7 @@ var Expressive = (() => {
1592
1896
  this.addChip({ id: this._input.value });
1593
1897
  }
1594
1898
  this._input.value = "";
1595
- } else if ((Utils.keys.BACKSPACE.includes(e.key) || Utils.keys.ARROW_LEFT.includes(e.key)) && this._input.value === "" && this.chipsData.length) {
1899
+ } else if ((e.key === Utils.keys.BACKSPACE || e.key === Utils.keys.ARROW_LEFT) && this._input.value === "" && this.chipsData.length) {
1596
1900
  e.preventDefault();
1597
1901
  this.selectChip(this.chipsData.length - 1);
1598
1902
  }
@@ -1602,6 +1906,7 @@ var Expressive = (() => {
1602
1906
  ..._Chips.defaults,
1603
1907
  ...options
1604
1908
  };
1909
+ if (options?.i18n) this.options.i18n = { ..._Chips.defaults.i18n, ...options.i18n };
1605
1910
  this.el.classList.add("chips");
1606
1911
  this.chipsData = [];
1607
1912
  this._chips = [];
@@ -1617,8 +1922,22 @@ var Expressive = (() => {
1617
1922
  }
1618
1923
  this._setupLabel();
1619
1924
  }
1925
+ /**
1926
+ * Selection is drawn by a class now, not by `:focus`, so it does not clear
1927
+ * itself when focus moves - every path that changes the selection has to go
1928
+ * through here or the class and the field drift apart.
1929
+ *
1930
+ * Deliberately no `aria-selected`: the chip is not an option in a listbox,
1931
+ * and putting the attribute on an element with no such role is the same
1932
+ * defect the semantics standard exists to remove.
1933
+ */
1934
+ _setSelected(chip) {
1935
+ this._selectedChip?.classList.remove("selected");
1936
+ this._selectedChip = chip;
1937
+ chip?.classList.add("selected");
1938
+ }
1620
1939
  static get defaults() {
1621
- return _defaults4;
1940
+ return _defaults5;
1622
1941
  }
1623
1942
  /**
1624
1943
  * Initializes instances of Chips.
@@ -1668,28 +1987,30 @@ var Expressive = (() => {
1668
1987
  if (tag === "INPUT" || tag === "TEXTAREA" || !chipsKeydown) return;
1669
1988
  const currChips = chips["Expressive_Chips"];
1670
1989
  if (!currChips) return;
1671
- if (Utils.keys.BACKSPACE.includes(e.key) || Utils.keys.DELETE.includes(e.key)) {
1990
+ if (e.key === Utils.keys.BACKSPACE || e.key === Utils.keys.DELETE) {
1672
1991
  e.preventDefault();
1673
1992
  let selectIndex = currChips.chipsData.length;
1674
1993
  if (currChips._selectedChip) {
1675
1994
  const index = currChips._chips.indexOf(currChips._selectedChip);
1676
1995
  currChips.deleteChip(index);
1677
- currChips._selectedChip = null;
1996
+ currChips._setSelected(null);
1678
1997
  selectIndex = Math.max(index - 1, 0);
1679
1998
  }
1680
1999
  if (currChips.chipsData.length) currChips.selectChip(selectIndex);
1681
2000
  else currChips._input.focus();
1682
- } else if (Utils.keys.ARROW_LEFT.includes(e.key)) {
2001
+ } else if (e.key === Utils.keys.ARROW_LEFT) {
1683
2002
  if (currChips._selectedChip) {
1684
2003
  const selectIndex = currChips._chips.indexOf(currChips._selectedChip) - 1;
1685
2004
  if (selectIndex < 0) return;
1686
2005
  currChips.selectChip(selectIndex);
1687
2006
  }
1688
- } else if (Utils.keys.ARROW_RIGHT.includes(e.key)) {
2007
+ } else if (e.key === Utils.keys.ARROW_RIGHT) {
1689
2008
  if (currChips._selectedChip) {
1690
2009
  const selectIndex = currChips._chips.indexOf(currChips._selectedChip) + 1;
1691
- if (selectIndex >= currChips.chipsData.length) currChips._input.focus();
1692
- else currChips.selectChip(selectIndex);
2010
+ if (selectIndex >= currChips.chipsData.length) {
2011
+ currChips._setSelected(null);
2012
+ currChips._input.focus();
2013
+ } else currChips.selectChip(selectIndex);
1693
2014
  }
1694
2015
  }
1695
2016
  }
@@ -1700,25 +2021,31 @@ var Expressive = (() => {
1700
2021
  if (!_Chips._keydown && document.hidden) {
1701
2022
  const chips = e.target.closest(".chips");
1702
2023
  const currChips = chips?.["Expressive_Chips"];
1703
- if (currChips) currChips._selectedChip = null;
2024
+ if (currChips) currChips._setSelected(null);
1704
2025
  }
1705
2026
  }
1706
2027
  _renderChip(chip) {
1707
2028
  if (!chip.id) return;
1708
- const renderedChip = document.createElement("div");
2029
+ const label = chip.text || chip.id;
2030
+ const renderedChip = document.createElement("span");
1709
2031
  renderedChip.classList.add("chip");
1710
- renderedChip.innerText = chip.text || chip.id;
2032
+ renderedChip.innerText = label;
1711
2033
  if (chip.image) {
1712
2034
  const img = document.createElement("img");
1713
2035
  img.setAttribute("src", chip.image);
1714
2036
  renderedChip.insertBefore(img, renderedChip.firstChild);
1715
2037
  }
1716
2038
  if (this.options.allowUserInput) {
1717
- renderedChip.setAttribute("tabindex", "0");
1718
- const closeIcon = document.createElement("i");
1719
- closeIcon.classList.add(this.options.closeIconClass, "close");
2039
+ const closeButton = document.createElement("button");
2040
+ closeButton.type = "button";
2041
+ closeButton.classList.add("close");
2042
+ closeButton.setAttribute("aria-label", `${this.options.i18n.remove} ${label}`);
2043
+ const closeIcon = document.createElement("span");
2044
+ closeIcon.classList.add(this.options.closeIconClass);
2045
+ closeIcon.setAttribute("aria-hidden", "true");
1720
2046
  closeIcon.innerText = "close";
1721
- renderedChip.appendChild(closeIcon);
2047
+ closeButton.appendChild(closeIcon);
2048
+ renderedChip.appendChild(closeButton);
1722
2049
  }
1723
2050
  return renderedChip;
1724
2051
  }
@@ -1793,6 +2120,7 @@ var Expressive = (() => {
1793
2120
  deleteChip(chipIndex) {
1794
2121
  const chip = this._chips[chipIndex];
1795
2122
  if (!chip) return;
2123
+ if (this._selectedChip === chip) this._setSelected(null);
1796
2124
  chip.remove();
1797
2125
  this._chips.splice(chipIndex, 1);
1798
2126
  this.chipsData.splice(chipIndex, 1);
@@ -1808,8 +2136,8 @@ var Expressive = (() => {
1808
2136
  selectChip(chipIndex) {
1809
2137
  const chip = this._chips[chipIndex];
1810
2138
  if (!chip) return;
1811
- this._selectedChip = chip;
1812
- chip.focus();
2139
+ this._setSelected(chip);
2140
+ chip.querySelector(".close")?.focus();
1813
2141
  if (typeof this.options.onChipSelect === "function") {
1814
2142
  this.options.onChipSelect(this.el, chip);
1815
2143
  }
@@ -1820,7 +2148,7 @@ var Expressive = (() => {
1820
2148
  chips.forEach((el) => {
1821
2149
  el.addEventListener("click", (e) => {
1822
2150
  if (el["Expressive_Chips"]) return;
1823
- if (e.target.classList.contains("close")) {
2151
+ if (e.target.closest(".close")) {
1824
2152
  const chip = e.target.closest(".chip");
1825
2153
  if (chip) chip.remove();
1826
2154
  }
@@ -1832,97 +2160,93 @@ var Expressive = (() => {
1832
2160
  _Chips._keydown = false;
1833
2161
  var Chips = _Chips;
1834
2162
 
1835
- // src/ts/components/range.ts
1836
- var _defaults5 = {};
1837
- var Range = class _Range extends Component {
2163
+ // src/ts/components/slider.ts
2164
+ var _defaults6 = {};
2165
+ var Slider = class _Slider extends Component {
1838
2166
  constructor(el, options) {
1839
- super(el, options, _Range);
2167
+ super(el, options, _Slider);
1840
2168
  this._handleRangeChange = () => {
1841
2169
  this._clampDual();
1842
2170
  this._sync();
1843
2171
  this.thumb.classList.add("active");
1844
2172
  };
1845
- this._handleRangeMousedownTouchstart = (e) => {
1846
- this._mousedown = true;
2173
+ this._handleRangePointerDown = (e) => {
2174
+ if (!e.isPrimary) return;
2175
+ if (e.pointerType === "mouse" && e.button !== 0) return;
2176
+ this._pointerDown = true;
1847
2177
  this.el.classList.add("active");
1848
2178
  this._clampDual();
1849
2179
  this._sync();
1850
- if (e.type !== "input") {
1851
- this.thumb.classList.add("active");
1852
- }
2180
+ this.thumb.classList.add("active");
1853
2181
  };
1854
- this._handleRangeInputMousemoveTouchmove = () => {
1855
- if (this._mousedown) {
2182
+ this._handleRangeInputOrMove = () => {
2183
+ if (this._pointerDown) {
1856
2184
  this._clampDual();
1857
2185
  this._sync();
1858
2186
  this.thumb.classList.add("active");
1859
2187
  }
1860
2188
  };
1861
- this._handleRangeMouseupTouchend = () => {
1862
- this._mousedown = false;
2189
+ this._handleRangePointerUp = (e) => {
2190
+ if (!e.isPrimary) return;
2191
+ this._pointerDown = false;
1863
2192
  this.el.classList.remove("active");
1864
2193
  };
1865
- this._handleRangeBlurMouseoutTouchleave = () => {
1866
- if (!this._mousedown) {
2194
+ this._handleRangeRelease = () => {
2195
+ if (!this._pointerDown) {
1867
2196
  this.thumb.classList.remove("active");
1868
2197
  }
1869
2198
  };
1870
- this.el["Expressive_Range"] = this;
2199
+ this.el["Expressive_Slider"] = this;
1871
2200
  this.options = {
1872
- ..._Range.defaults,
2201
+ ..._Slider.defaults,
1873
2202
  ...options
1874
2203
  };
1875
- this._mousedown = false;
2204
+ this._pointerDown = false;
1876
2205
  this._setupThumb();
1877
2206
  this._sync();
1878
2207
  this._setupEventHandlers();
1879
2208
  }
1880
2209
  static get defaults() {
1881
- return _defaults5;
2210
+ return _defaults6;
1882
2211
  }
1883
2212
  /**
1884
- * Initializes instances of Range.
2213
+ * Initializes instances of Slider.
1885
2214
  * @param els HTML elements.
1886
2215
  * @param options Component options.
1887
2216
  */
1888
2217
  static init(els, options = {}) {
1889
- return super.init(els, options, _Range);
2218
+ return super.init(els, options, _Slider);
1890
2219
  }
1891
2220
  static getInstance(el) {
1892
- return el["Expressive_Range"];
2221
+ return el["Expressive_Slider"];
1893
2222
  }
1894
2223
  destroy() {
1895
2224
  this._removeEventHandlers();
1896
2225
  this._removeThumb();
1897
- this.el["Expressive_Range"] = void 0;
2226
+ this.el["Expressive_Slider"] = void 0;
1898
2227
  }
2228
+ // Eleven listeners used to cover four stages, because each one was written
2229
+ // twice - once for mouse, once for touch. One of the touch spellings,
2230
+ // `touchleave`, was never implemented by any browser at all.
1899
2231
  _setupEventHandlers() {
1900
2232
  this.el.addEventListener("change", this._handleRangeChange);
1901
- this.el.addEventListener("mousedown", this._handleRangeMousedownTouchstart);
1902
- this.el.addEventListener("touchstart", this._handleRangeMousedownTouchstart);
1903
- this.el.addEventListener("input", this._handleRangeInputMousemoveTouchmove);
1904
- this.el.addEventListener("mousemove", this._handleRangeInputMousemoveTouchmove);
1905
- this.el.addEventListener("touchmove", this._handleRangeInputMousemoveTouchmove, {
1906
- passive: true
1907
- });
1908
- this.el.addEventListener("mouseup", this._handleRangeMouseupTouchend);
1909
- this.el.addEventListener("touchend", this._handleRangeMouseupTouchend);
1910
- this.el.addEventListener("blur", this._handleRangeBlurMouseoutTouchleave);
1911
- this.el.addEventListener("mouseout", this._handleRangeBlurMouseoutTouchleave);
1912
- this.el.addEventListener("touchleave", this._handleRangeBlurMouseoutTouchleave);
2233
+ this.el.addEventListener("pointerdown", this._handleRangePointerDown);
2234
+ this.el.addEventListener("input", this._handleRangeInputOrMove);
2235
+ this.el.addEventListener("pointermove", this._handleRangeInputOrMove, { passive: true });
2236
+ this.el.addEventListener("pointerup", this._handleRangePointerUp);
2237
+ this.el.addEventListener("pointercancel", this._handleRangePointerUp);
2238
+ this.el.addEventListener("blur", this._handleRangeRelease);
2239
+ this.el.addEventListener("mouseout", this._handleRangeRelease);
1913
2240
  }
1914
2241
  _removeEventHandlers() {
1915
2242
  this.el.removeEventListener("change", this._handleRangeChange);
1916
- this.el.removeEventListener("mousedown", this._handleRangeMousedownTouchstart);
1917
- this.el.removeEventListener("touchstart", this._handleRangeMousedownTouchstart);
1918
- this.el.removeEventListener("input", this._handleRangeInputMousemoveTouchmove);
1919
- this.el.removeEventListener("mousemove", this._handleRangeInputMousemoveTouchmove);
1920
- this.el.removeEventListener("touchmove", this._handleRangeInputMousemoveTouchmove);
1921
- this.el.removeEventListener("mouseup", this._handleRangeMouseupTouchend);
1922
- this.el.removeEventListener("touchend", this._handleRangeMouseupTouchend);
1923
- this.el.removeEventListener("blur", this._handleRangeBlurMouseoutTouchleave);
1924
- this.el.removeEventListener("mouseout", this._handleRangeBlurMouseoutTouchleave);
1925
- this.el.removeEventListener("touchleave", this._handleRangeBlurMouseoutTouchleave);
2243
+ this.el.removeEventListener("pointerdown", this._handleRangePointerDown);
2244
+ this.el.removeEventListener("input", this._handleRangeInputOrMove);
2245
+ this.el.removeEventListener("pointermove", this._handleRangeInputOrMove);
2246
+ this.el.removeEventListener("pointerup", this._handleRangePointerUp);
2247
+ this.el.removeEventListener("pointercancel", this._handleRangePointerUp);
2248
+ this.el.removeEventListener("blur", this._handleRangeRelease);
2249
+ this.el.removeEventListener("mouseout", this._handleRangeRelease);
1926
2250
  }
1927
2251
  _setupThumb() {
1928
2252
  this.thumb = document.createElement("span");
@@ -1939,7 +2263,7 @@ var Expressive = (() => {
1939
2263
  return !!this.el.closest(".vertical");
1940
2264
  }
1941
2265
  _host() {
1942
- return this.el.closest(".range, .range-field, label");
2266
+ return this.el.closest(".slider, .range, .range-field, label");
1943
2267
  }
1944
2268
  _clampDual() {
1945
2269
  const host = this._host();
@@ -1994,7 +2318,7 @@ var Expressive = (() => {
1994
2318
  */
1995
2319
  static Init() {
1996
2320
  Utils.onDocumentReady(() => {
1997
- _Range.init(document.querySelectorAll("input[type=range]"), {});
2321
+ _Slider.init(document.querySelectorAll("input[type=range]"), {});
1998
2322
  });
1999
2323
  }
2000
2324
  };
@@ -2067,169 +2391,144 @@ var Expressive = (() => {
2067
2391
  _Dialogs._down = null;
2068
2392
  var Dialogs = _Dialogs;
2069
2393
 
2070
- // src/ts/behaviors/bottomSheets.ts
2071
- var _BottomSheets = class _BottomSheets {
2072
- static Init() {
2073
- Utils.onDocumentReady(() => {
2074
- document.addEventListener("pointerdown", _BottomSheets._onDown, { passive: true });
2075
- document.addEventListener("pointermove", _BottomSheets._onMove, { passive: true });
2076
- document.addEventListener("pointerup", _BottomSheets._onUp, { passive: true });
2077
- document.addEventListener("pointercancel", _BottomSheets._onCancel, { passive: true });
2078
- });
2079
- }
2080
- static _onDown(event) {
2394
+ // src/ts/behaviors/sheetDrag.ts
2395
+ var DISMISS_DISTANCE = 96;
2396
+ var DISMISS_VELOCITY = 0.5;
2397
+ var DRAG_SLOP = 4;
2398
+ function installSheetDrag(config) {
2399
+ const coord = (event) => config.axis === "x" ? event.clientX : event.clientY;
2400
+ let drag = null;
2401
+ const sheetFor = (target) => {
2402
+ if (!(target instanceof Element)) return null;
2403
+ const dialog = target.closest(config.selector);
2404
+ return dialog instanceof HTMLDialogElement ? dialog : null;
2405
+ };
2406
+ const reset = (dialog) => {
2407
+ dialog.style.transition = "";
2408
+ dialog.style.setProperty(config.property, "0px");
2409
+ };
2410
+ const onDown = (event) => {
2411
+ config.onPress?.();
2081
2412
  if (!event.isPrimary) return;
2082
2413
  if (event.pointerType === "mouse" && event.button !== 0) return;
2083
- const dialog = _BottomSheets._sheet(event.target);
2414
+ const dialog = sheetFor(event.target);
2084
2415
  if (!dialog?.open) {
2085
- _BottomSheets._drag = null;
2416
+ drag = null;
2086
2417
  return;
2087
2418
  }
2088
- if (!_BottomSheets._inHandle(event, dialog)) {
2089
- _BottomSheets._drag = null;
2419
+ if (!config.inHandle(event, dialog)) {
2420
+ drag = null;
2090
2421
  return;
2091
2422
  }
2092
2423
  dialog.style.transition = "none";
2093
- _BottomSheets._drag = {
2424
+ drag = {
2094
2425
  dialog,
2095
2426
  pointerId: event.pointerId,
2096
- startY: event.clientY,
2097
- lastY: event.clientY,
2427
+ direction: config.direction(dialog),
2428
+ start: coord(event),
2429
+ last: coord(event),
2098
2430
  lastT: Date.now(),
2099
2431
  shift: 0
2100
2432
  };
2101
- }
2102
- static _onMove(event) {
2103
- const drag = _BottomSheets._drag;
2433
+ };
2434
+ const onMove = (event) => {
2104
2435
  if (!drag || drag.pointerId !== event.pointerId) return;
2105
- const shift = Math.max(0, event.clientY - drag.startY);
2106
- drag.shift = shift;
2107
- drag.lastY = event.clientY;
2436
+ const delta = coord(event) - drag.start;
2437
+ drag.shift = drag.direction === 1 ? Math.max(0, delta) : Math.min(0, delta);
2438
+ drag.last = coord(event);
2108
2439
  drag.lastT = Date.now();
2109
- drag.dialog.style.setProperty("--md-comp-bottom-sheet-shift", `${shift}px`);
2110
- }
2111
- static _onUp(event) {
2112
- const drag = _BottomSheets._drag;
2113
- _BottomSheets._drag = null;
2114
- if (!drag || drag.pointerId !== event.pointerId) return;
2115
- const dialog = drag.dialog;
2116
- dialog.style.transition = "";
2117
- const dt = Math.max(1, Date.now() - drag.lastT);
2118
- const velocity = (event.clientY - drag.lastY) / dt;
2119
- const dismiss = drag.shift > 96 || velocity > 0.5;
2120
- if (dismiss && dialog.open) {
2121
- dialog.close();
2122
- }
2123
- dialog.style.setProperty("--md-comp-bottom-sheet-shift", "0px");
2124
- }
2125
- static _onCancel() {
2126
- const drag = _BottomSheets._drag;
2127
- _BottomSheets._drag = null;
2128
- if (!drag) return;
2129
- drag.dialog.style.transition = "";
2130
- drag.dialog.style.setProperty("--md-comp-bottom-sheet-shift", "0px");
2131
- }
2132
- static _sheet(target) {
2133
- if (!(target instanceof Element)) return null;
2134
- const dialog = target.closest("dialog.bottom-sheet, dialog.bottom");
2135
- return dialog instanceof HTMLDialogElement ? dialog : null;
2440
+ drag.dialog.style.setProperty(config.property, `${drag.shift}px`);
2441
+ };
2442
+ const onUp = (event) => {
2443
+ const held = drag;
2444
+ if (!held || held.pointerId !== event.pointerId) return;
2445
+ drag = null;
2446
+ held.dialog.style.transition = "";
2447
+ const dt = Math.max(1, Date.now() - held.lastT);
2448
+ const velocity = (coord(event) - held.last) / dt;
2449
+ config.onRelease?.(Math.abs(held.shift) > DRAG_SLOP);
2450
+ const dismiss = held.direction === 1 ? held.shift > DISMISS_DISTANCE || velocity > DISMISS_VELOCITY : held.shift < -DISMISS_DISTANCE || velocity < -DISMISS_VELOCITY;
2451
+ if (dismiss && held.dialog.open) held.dialog.close();
2452
+ held.dialog.style.setProperty(config.property, "0px");
2453
+ };
2454
+ const onCancel = () => {
2455
+ config.onRelease?.(false);
2456
+ const held = drag;
2457
+ drag = null;
2458
+ if (held) reset(held.dialog);
2459
+ };
2460
+ Utils.onDocumentReady(() => {
2461
+ document.addEventListener("pointerdown", onDown, { passive: true });
2462
+ document.addEventListener("pointermove", onMove, { passive: true });
2463
+ document.addEventListener("pointerup", onUp, { passive: true });
2464
+ document.addEventListener("pointercancel", onCancel, { passive: true });
2465
+ });
2466
+ }
2467
+
2468
+ // src/ts/behaviors/bottomSheets.ts
2469
+ var SHEET_SELECTOR = "dialog.bottom-sheet, dialog.bottom";
2470
+ var _BottomSheets = class _BottomSheets {
2471
+ static Init() {
2472
+ installSheetDrag({
2473
+ selector: SHEET_SELECTOR,
2474
+ axis: "y",
2475
+ property: "--md-comp-bottom-sheet-shift",
2476
+ direction: () => 1,
2477
+ inHandle: (event, dialog) => {
2478
+ const target = event.target;
2479
+ if (target instanceof Element && target.closest(".handle")) return true;
2480
+ const rect = dialog.getBoundingClientRect();
2481
+ return event.clientY >= rect.top && event.clientY <= rect.top + 48;
2482
+ },
2483
+ onPress: () => {
2484
+ _BottomSheets._dragged = false;
2485
+ },
2486
+ onRelease: (dragged) => {
2487
+ _BottomSheets._dragged = dragged;
2488
+ }
2489
+ });
2490
+ Utils.onDocumentReady(() => {
2491
+ document.addEventListener("click", _BottomSheets._onClick);
2492
+ });
2136
2493
  }
2137
- static _inHandle(event, dialog) {
2494
+ static _onClick(event) {
2138
2495
  const target = event.target;
2139
- if (target instanceof Element && target.closest(".handle")) return true;
2140
- const rect = dialog.getBoundingClientRect();
2141
- return event.clientY >= rect.top && event.clientY <= rect.top + 48;
2496
+ if (!(target instanceof Element)) return;
2497
+ const handle = target.closest(".handle, .drag-handle");
2498
+ if (!(handle instanceof HTMLButtonElement)) return;
2499
+ if (event.detail !== 0 && _BottomSheets._dragged) return;
2500
+ const dialog = handle.closest(SHEET_SELECTOR);
2501
+ if (dialog instanceof HTMLDialogElement && dialog.open) dialog.close();
2142
2502
  }
2143
2503
  };
2144
- _BottomSheets._drag = null;
2504
+ /**
2505
+ * Whether the last pointer sequence moved. A drag ends with a `click` on the
2506
+ * element it started on, so without this the tail of a drag that snapped back
2507
+ * would dismiss the sheet the drag had just declined to dismiss.
2508
+ */
2509
+ _BottomSheets._dragged = false;
2145
2510
  var BottomSheets = _BottomSheets;
2146
2511
 
2147
2512
  // src/ts/behaviors/sideSheets.ts
2148
- var _SideSheets = class _SideSheets {
2513
+ var startDocked = (dialog) => dialog.classList.contains("left") || dialog.classList.contains("left-sheet") || dialog.classList.contains("start");
2514
+ var SideSheets = class {
2149
2515
  static Init() {
2150
- Utils.onDocumentReady(() => {
2151
- document.addEventListener("pointerdown", _SideSheets._onDown, { passive: true });
2152
- document.addEventListener("pointermove", _SideSheets._onMove, { passive: true });
2153
- document.addEventListener("pointerup", _SideSheets._onUp, { passive: true });
2154
- document.addEventListener("pointercancel", _SideSheets._onCancel, { passive: true });
2516
+ installSheetDrag({
2517
+ selector: "dialog.side-sheet, dialog.right, dialog.right-sheet, dialog.left, dialog.left-sheet",
2518
+ axis: "x",
2519
+ property: "--md-comp-side-sheet-shift",
2520
+ // A start-docked sheet leaves toward the start edge, so its dismissal
2521
+ // direction is the negative one.
2522
+ direction: (dialog) => startDocked(dialog) ? -1 : 1,
2523
+ inHandle: (event, dialog) => {
2524
+ const target = event.target;
2525
+ if (target instanceof Element && target.closest("header")) return true;
2526
+ const rect = dialog.getBoundingClientRect();
2527
+ return startDocked(dialog) ? event.clientX >= rect.right - 24 && event.clientX <= rect.right : event.clientX >= rect.left && event.clientX <= rect.left + 24;
2528
+ }
2155
2529
  });
2156
2530
  }
2157
- static _onDown(event) {
2158
- if (!event.isPrimary) return;
2159
- if (event.pointerType === "mouse" && event.button !== 0) return;
2160
- const dialog = _SideSheets._sheet(event.target);
2161
- if (!dialog?.open) {
2162
- _SideSheets._drag = null;
2163
- return;
2164
- }
2165
- if (!_SideSheets._inHandle(event, dialog)) {
2166
- _SideSheets._drag = null;
2167
- return;
2168
- }
2169
- dialog.style.transition = "none";
2170
- _SideSheets._drag = {
2171
- dialog,
2172
- pointerId: event.pointerId,
2173
- startX: event.clientX,
2174
- lastX: event.clientX,
2175
- lastT: Date.now(),
2176
- shift: 0,
2177
- startDocked: _SideSheets._startDocked(dialog)
2178
- };
2179
- }
2180
- static _onMove(event) {
2181
- const drag = _SideSheets._drag;
2182
- if (!drag || drag.pointerId !== event.pointerId) return;
2183
- const delta = event.clientX - drag.startX;
2184
- const shift = drag.startDocked ? Math.min(0, delta) : Math.max(0, delta);
2185
- drag.shift = shift;
2186
- drag.lastX = event.clientX;
2187
- drag.lastT = Date.now();
2188
- drag.dialog.style.setProperty("--md-comp-side-sheet-shift", `${shift}px`);
2189
- }
2190
- static _onUp(event) {
2191
- const drag = _SideSheets._drag;
2192
- _SideSheets._drag = null;
2193
- if (!drag || drag.pointerId !== event.pointerId) return;
2194
- const dialog = drag.dialog;
2195
- dialog.style.transition = "";
2196
- const dt = Math.max(1, Date.now() - drag.lastT);
2197
- const velocity = (event.clientX - drag.lastX) / dt;
2198
- const dismiss = drag.startDocked ? drag.shift < -96 || velocity < -0.5 : drag.shift > 96 || velocity > 0.5;
2199
- if (dismiss && dialog.open) {
2200
- dialog.close();
2201
- }
2202
- dialog.style.setProperty("--md-comp-side-sheet-shift", "0px");
2203
- }
2204
- static _onCancel() {
2205
- const drag = _SideSheets._drag;
2206
- _SideSheets._drag = null;
2207
- if (!drag) return;
2208
- drag.dialog.style.transition = "";
2209
- drag.dialog.style.setProperty("--md-comp-side-sheet-shift", "0px");
2210
- }
2211
- static _sheet(target) {
2212
- if (!(target instanceof Element)) return null;
2213
- const dialog = target.closest(
2214
- "dialog.side-sheet, dialog.right, dialog.right-sheet, dialog.left, dialog.left-sheet"
2215
- );
2216
- return dialog instanceof HTMLDialogElement ? dialog : null;
2217
- }
2218
- static _startDocked(dialog) {
2219
- return dialog.classList.contains("left") || dialog.classList.contains("left-sheet") || dialog.classList.contains("start");
2220
- }
2221
- static _inHandle(event, dialog) {
2222
- const target = event.target;
2223
- if (target instanceof Element && target.closest("header")) return true;
2224
- const rect = dialog.getBoundingClientRect();
2225
- if (_SideSheets._startDocked(dialog)) {
2226
- return event.clientX >= rect.right - 24 && event.clientX <= rect.right;
2227
- }
2228
- return event.clientX >= rect.left && event.clientX <= rect.left + 24;
2229
- }
2230
2531
  };
2231
- _SideSheets._drag = null;
2232
- var SideSheets = _SideSheets;
2233
2532
 
2234
2533
  // src/ts/behaviors/forms.ts
2235
2534
  var Forms = class _Forms {
@@ -2285,7 +2584,7 @@ var Expressive = (() => {
2285
2584
  document.addEventListener("keyup", (e) => {
2286
2585
  const target = e.target;
2287
2586
  if (target instanceof HTMLInputElement && ["radio", "checkbox"].includes(target.type)) {
2288
- if (Utils.keys.TAB.includes(e.key)) {
2587
+ if (e.key === Utils.keys.TAB) {
2289
2588
  target.classList.add("tabbed");
2290
2589
  target.addEventListener("blur", () => target.classList.remove("tabbed"), {
2291
2590
  once: true
@@ -2323,61 +2622,89 @@ var Expressive = (() => {
2323
2622
  }
2324
2623
  };
2325
2624
 
2326
- // src/ts/behaviors/waves.ts
2327
- var Waves = class _Waves {
2328
- static _offset(el) {
2329
- const box = el.getBoundingClientRect();
2330
- const docElem = document.documentElement;
2331
- return {
2332
- top: box.top + window.pageYOffset - docElem.clientTop,
2333
- left: box.left + window.pageXOffset - docElem.clientLeft
2334
- };
2335
- }
2336
- // https://phoenix-dx.com/css-techniques-for-material-ripple-effect/
2337
- static renderWaveEffect(targetElement, position = null, color = null) {
2338
- const isCentered = position === null;
2339
- const duration = 500;
2340
- let animationFrame, animationStart;
2341
- const animationStep = function(timestamp) {
2342
- if (!animationStart) {
2343
- animationStart = timestamp;
2344
- }
2345
- const frame = timestamp - animationStart;
2346
- if (frame < duration) {
2347
- const easing = frame / duration * (2 - frame / duration);
2348
- const circle = isCentered ? "circle at 50% 50%" : `circle at ${position.x}px ${position.y}px`;
2349
- const waveColor = `rgba(${color?.r || 0}, ${color?.g || 0}, ${color?.b || 0}, ${0.3 * (1 - easing)})`;
2350
- const stop = 90 * easing + "%";
2351
- targetElement.style.backgroundImage = "radial-gradient(" + circle + ", " + waveColor + " " + stop + ", transparent " + stop + ")";
2352
- animationFrame = window.requestAnimationFrame(animationStep);
2353
- } else {
2354
- targetElement.style.backgroundImage = "none";
2355
- window.cancelAnimationFrame(animationFrame);
2625
+ // src/ts/components/appBar.ts
2626
+ var _defaults7 = {};
2627
+ var AppBar = class _AppBar extends Component {
2628
+ constructor(el, options) {
2629
+ super(el, options, _AppBar);
2630
+ this._observer = null;
2631
+ this._sentinel = null;
2632
+ this._input = null;
2633
+ this._view = null;
2634
+ this._suppressSearchOpen = false;
2635
+ this._onIntersect = (entries) => {
2636
+ const entry = entries[0];
2637
+ if (!entry) return;
2638
+ this.el.classList.toggle("collapsed", !entry.isIntersecting);
2639
+ };
2640
+ this._onSearchViewClose = () => {
2641
+ this._suppressSearchOpen = true;
2642
+ };
2643
+ this._onSearchFocus = () => {
2644
+ if (!this._view) return;
2645
+ if (this._suppressSearchOpen) {
2646
+ this._suppressSearchOpen = false;
2647
+ return;
2356
2648
  }
2649
+ if (this._view instanceof HTMLDialogElement) {
2650
+ if (!this._view.open) this._view.showModal();
2651
+ return;
2652
+ }
2653
+ this._view.hidden = false;
2654
+ this._input?.setAttribute("aria-expanded", "true");
2357
2655
  };
2358
- animationFrame = window.requestAnimationFrame(animationStep);
2656
+ this.el["Expressive_AppBar"] = this;
2657
+ this.options = { ..._AppBar.defaults, ...options };
2658
+ this._setupCollapse();
2659
+ this._setupSearch();
2359
2660
  }
2360
- static Init() {
2361
- Utils.onDocumentReady(() => {
2362
- document.body.addEventListener("click", (e) => {
2363
- const trigger = e.target;
2364
- const el = trigger.closest(".waves-effect");
2365
- if (el && el.contains(trigger)) {
2366
- const isCircular = el.classList.contains("waves-circle");
2367
- const offset = _Waves._offset(el);
2368
- const x = e.pageX - offset.left;
2369
- const y = e.pageY - offset.top;
2370
- let color = null;
2371
- if (el.classList.contains("waves-light")) color = { r: 255, g: 255, b: 255 };
2372
- _Waves.renderWaveEffect(el, isCircular ? null : { x, y }, color);
2373
- }
2374
- });
2375
- });
2661
+ static get defaults() {
2662
+ return _defaults7;
2663
+ }
2664
+ static init(els, options = {}) {
2665
+ return super.init(els, options, _AppBar);
2666
+ }
2667
+ static getInstance(el) {
2668
+ return el["Expressive_AppBar"];
2669
+ }
2670
+ destroy() {
2671
+ this._observer?.disconnect();
2672
+ this._observer = null;
2673
+ this._sentinel?.remove();
2674
+ this._sentinel = null;
2675
+ this._input?.removeEventListener("focus", this._onSearchFocus);
2676
+ this._view?.removeEventListener("close", this._onSearchViewClose);
2677
+ this.el.classList.remove("collapsed");
2678
+ this.el["Expressive_AppBar"] = void 0;
2679
+ }
2680
+ _setupCollapse() {
2681
+ if (!this.el.classList.contains("medium") && !this.el.classList.contains("large")) {
2682
+ return;
2683
+ }
2684
+ if (typeof IntersectionObserver === "undefined") return;
2685
+ this._sentinel = document.createElement("span");
2686
+ this._sentinel.setAttribute("aria-hidden", "true");
2687
+ this._sentinel.style.cssText = "display:block;width:100%;height:1px;margin-top:-1px;pointer-events:none;visibility:hidden";
2688
+ this.el.insertAdjacentElement("beforebegin", this._sentinel);
2689
+ this._observer = new IntersectionObserver(this._onIntersect);
2690
+ this._observer.observe(this._sentinel);
2691
+ }
2692
+ _setupSearch() {
2693
+ this._input = this.el.querySelector('input[type="search"]');
2694
+ if (!this._input) return;
2695
+ const id = this._input.getAttribute("aria-controls");
2696
+ if (id) this._view = document.getElementById(id);
2697
+ if (!this._view) this._view = this.el.querySelector(".search-view");
2698
+ if (!this._view) return;
2699
+ this._input.addEventListener("focus", this._onSearchFocus);
2700
+ if (this._view instanceof HTMLDialogElement) {
2701
+ this._view.addEventListener("close", this._onSearchViewClose);
2702
+ }
2376
2703
  }
2377
2704
  };
2378
2705
 
2379
2706
  // src/ts/components/buttons.ts
2380
- var _defaults6 = {
2707
+ var _defaults8 = {
2381
2708
  direction: "top",
2382
2709
  hoverEnabled: true,
2383
2710
  toolbarEnabled: false
@@ -2412,13 +2739,13 @@ var Expressive = (() => {
2412
2739
  this._toggle();
2413
2740
  };
2414
2741
  this._handleAnchorKeydown = (e) => {
2415
- if (Utils.keys.ENTER.includes(e.key) || e.key === " ") {
2742
+ if (e.key === Utils.keys.ENTER || e.key === " ") {
2416
2743
  e.preventDefault();
2417
2744
  this._toggle();
2418
2745
  }
2419
2746
  };
2420
2747
  this._handleDocumentKeydown = (e) => {
2421
- if (Utils.keys.ESC.includes(e.key)) this.close();
2748
+ if (e.key === Utils.keys.ESC) this.close();
2422
2749
  };
2423
2750
  this._handleDocumentClick = (e) => {
2424
2751
  const target = e.target;
@@ -2438,22 +2765,19 @@ var Expressive = (() => {
2438
2765
  };
2439
2766
  this.isOpen = false;
2440
2767
  this._anchor = this.el.querySelector(":scope > a, :scope > button");
2441
- this._menu = this.el.querySelector("ul, menu");
2442
2768
  this.el.classList.add(`direction-${this.options.direction}`);
2443
2769
  if (clickToToggle) this.el.classList.add("click-to-toggle");
2444
2770
  if (toolbar) this.el.classList.add("toolbar");
2445
2771
  if (this._anchor) {
2446
- this._anchor.setAttribute("aria-haspopup", "menu");
2447
2772
  this._anchor.setAttribute("aria-expanded", "false");
2448
2773
  if (this._anchor instanceof HTMLAnchorElement && !this._anchor.hasAttribute("href") && this._anchor.tabIndex < 0) {
2449
2774
  this._anchor.tabIndex = 0;
2450
2775
  }
2451
2776
  }
2452
- if (this._menu) this._menu.setAttribute("role", "menu");
2453
2777
  this._setupEventHandlers();
2454
2778
  }
2455
2779
  static get defaults() {
2456
- return _defaults6;
2780
+ return _defaults8;
2457
2781
  }
2458
2782
  static init(els, options = {}) {
2459
2783
  return super.init(els, options, _FloatingActionButton);
@@ -2490,17 +2814,240 @@ var Expressive = (() => {
2490
2814
  }
2491
2815
  };
2492
2816
 
2817
+ // src/ts/components/buttonGroup.ts
2818
+ var _defaults9 = {};
2819
+ var VALID_SELECTION_MODES = /* @__PURE__ */ new Set(["single", "multiple"]);
2820
+ var PRESSED_CLASS = "button-group-pressed";
2821
+ var MAX_ICON_COMPRESSION = 0.15;
2822
+ var ButtonGroup = class _ButtonGroup extends Component {
2823
+ constructor(el, options = {}) {
2824
+ super(el, options, _ButtonGroup);
2825
+ this._pressedStyles = /* @__PURE__ */ new Map();
2826
+ this._releaseStyles = /* @__PURE__ */ new Map();
2827
+ this._releaseTimer = null;
2828
+ this._originalPressed = /* @__PURE__ */ new Map();
2829
+ this._activeItem = null;
2830
+ this._handleClick = (event) => {
2831
+ if (!this._selectionMode) return;
2832
+ const item = this._itemFromEvent(event);
2833
+ if (!(item instanceof HTMLButtonElement) || this._isDisabled(item)) return;
2834
+ this._rememberPressed(item);
2835
+ const willSelect = item.getAttribute("aria-pressed") !== "true";
2836
+ const selectedCount = this._toggleItems.filter(
2837
+ (button) => button.getAttribute("aria-pressed") === "true"
2838
+ ).length;
2839
+ if (!willSelect && this.el.hasAttribute("data-selection-required") && selectedCount === 1) {
2840
+ return;
2841
+ }
2842
+ if (this._selectionMode === "single" && willSelect) {
2843
+ this._toggleItems.forEach((button) => {
2844
+ this._rememberPressed(button);
2845
+ button.setAttribute("aria-pressed", "false");
2846
+ });
2847
+ }
2848
+ item.setAttribute("aria-pressed", willSelect ? "true" : "false");
2849
+ };
2850
+ this._handlePointerDown = (event) => {
2851
+ if (event.button !== 0) return;
2852
+ const item = this._itemFromEvent(event);
2853
+ if (item && !this._isDisabled(item)) this._startPress(item);
2854
+ };
2855
+ this._handleKeyDown = (event) => {
2856
+ if (event.repeat || event.key !== " " && event.key !== "Enter") return;
2857
+ const item = this._itemFromEvent(event);
2858
+ if (item && !this._isDisabled(item)) this._startPress(item);
2859
+ };
2860
+ this._handleKeyUp = (event) => {
2861
+ if (event.key === " " || event.key === "Enter") this._resetPress();
2862
+ };
2863
+ this._handleFocusOut = (event) => {
2864
+ if (event.target === this._activeItem) this._resetPress();
2865
+ };
2866
+ this._resetPress = () => {
2867
+ this._activeItem?.classList.remove(PRESSED_CLASS);
2868
+ this._activeItem = null;
2869
+ if (!this._pressedStyles.size) return;
2870
+ this._finishRelease();
2871
+ this._releaseStyles = new Map(this._pressedStyles);
2872
+ for (const [item, style] of this._releaseStyles) {
2873
+ item.style.width = `${style.baseWidth}px`;
2874
+ }
2875
+ this._pressedStyles.clear();
2876
+ this._releaseTimer = window.setTimeout(this._finishRelease, 200);
2877
+ };
2878
+ this._finishRelease = () => {
2879
+ if (this._releaseTimer !== null) window.clearTimeout(this._releaseTimer);
2880
+ this._releaseTimer = null;
2881
+ for (const [item, style] of this._releaseStyles) item.style.width = style.width;
2882
+ this._releaseStyles.clear();
2883
+ };
2884
+ this.el["Expressive_ButtonGroup"] = this;
2885
+ this.options = { ..._ButtonGroup.defaults, ...options };
2886
+ if (this._selectionMode) {
2887
+ this._normalizeSelection();
2888
+ this.el.addEventListener("click", this._handleClick, true);
2889
+ }
2890
+ this.el.addEventListener("pointerdown", this._handlePointerDown);
2891
+ this.el.addEventListener("keydown", this._handleKeyDown);
2892
+ this.el.addEventListener("focusout", this._handleFocusOut);
2893
+ document.addEventListener("pointerup", this._resetPress);
2894
+ document.addEventListener("pointercancel", this._resetPress);
2895
+ document.addEventListener("keyup", this._handleKeyUp);
2896
+ window.addEventListener("blur", this._resetPress);
2897
+ }
2898
+ static get defaults() {
2899
+ return _defaults9;
2900
+ }
2901
+ static init(els, options = {}) {
2902
+ return super.init(els, options, _ButtonGroup);
2903
+ }
2904
+ static getInstance(el) {
2905
+ return el["Expressive_ButtonGroup"];
2906
+ }
2907
+ destroy() {
2908
+ this._resetPress();
2909
+ this._finishRelease();
2910
+ this.el.removeEventListener("click", this._handleClick, true);
2911
+ this.el.removeEventListener("pointerdown", this._handlePointerDown);
2912
+ this.el.removeEventListener("keydown", this._handleKeyDown);
2913
+ this.el.removeEventListener("focusout", this._handleFocusOut);
2914
+ document.removeEventListener("pointerup", this._resetPress);
2915
+ document.removeEventListener("pointercancel", this._resetPress);
2916
+ document.removeEventListener("keyup", this._handleKeyUp);
2917
+ window.removeEventListener("blur", this._resetPress);
2918
+ for (const [item, value] of this._originalPressed) {
2919
+ if (value === null) item.removeAttribute("aria-pressed");
2920
+ else item.setAttribute("aria-pressed", value);
2921
+ }
2922
+ this._originalPressed.clear();
2923
+ this.el["Expressive_ButtonGroup"] = void 0;
2924
+ }
2925
+ get _selectionMode() {
2926
+ const mode = this.el.dataset.selection;
2927
+ return VALID_SELECTION_MODES.has(mode) ? mode : null;
2928
+ }
2929
+ get _items() {
2930
+ return Array.from(this.el.children).filter(
2931
+ (child) => child instanceof HTMLButtonElement || child instanceof HTMLAnchorElement && child.classList.contains("button")
2932
+ );
2933
+ }
2934
+ get _toggleItems() {
2935
+ return this._items.filter(
2936
+ (item) => item instanceof HTMLButtonElement
2937
+ );
2938
+ }
2939
+ _normalizeSelection() {
2940
+ const items = this._toggleItems;
2941
+ items.forEach((item) => {
2942
+ this._rememberPressed(item);
2943
+ if (!["true", "false"].includes(item.getAttribute("aria-pressed") || "")) {
2944
+ item.setAttribute("aria-pressed", "false");
2945
+ }
2946
+ });
2947
+ if (this._selectionMode === "single") {
2948
+ const selected = items.filter((item) => item.getAttribute("aria-pressed") === "true");
2949
+ selected.slice(1).forEach((item) => item.setAttribute("aria-pressed", "false"));
2950
+ }
2951
+ if (this.el.hasAttribute("data-selection-required") && !items.some((item) => item.getAttribute("aria-pressed") === "true")) {
2952
+ items.find((item) => !this._isDisabled(item))?.setAttribute("aria-pressed", "true");
2953
+ }
2954
+ }
2955
+ _itemFromEvent(event) {
2956
+ if (!(event.target instanceof Element)) return null;
2957
+ const item = event.target.closest("button, a.button");
2958
+ return item instanceof HTMLElement && item.parentElement === this.el ? item : null;
2959
+ }
2960
+ _isDisabled(item) {
2961
+ return item instanceof HTMLButtonElement && item.disabled || item.getAttribute("aria-disabled") === "true" || item.classList.contains("disabled");
2962
+ }
2963
+ _rememberPressed(item) {
2964
+ if (!this._originalPressed.has(item)) {
2965
+ this._originalPressed.set(item, item.getAttribute("aria-pressed"));
2966
+ }
2967
+ }
2968
+ _startPress(item) {
2969
+ if (this._activeItem) return;
2970
+ this._finishRelease();
2971
+ const items = this._items;
2972
+ const activeIndex = items.indexOf(item);
2973
+ if (activeIndex < 0) return;
2974
+ this._activeItem = item;
2975
+ item.classList.add(PRESSED_CLASS);
2976
+ if (this.el.classList.contains("connected") || items.length < 2) return;
2977
+ const rects = items.map((candidate) => candidate.getBoundingClientRect());
2978
+ const activeWidth = rects[activeIndex].width;
2979
+ if (activeWidth <= 0) return;
2980
+ const parsedMultiplier = Number.parseFloat(
2981
+ getComputedStyle(this.el).getPropertyValue(
2982
+ "--md-comp-button-group-pressed-item-width-multiplier"
2983
+ )
2984
+ );
2985
+ const multiplier = Number.isFinite(parsedMultiplier) ? parsedMultiplier : 0.15;
2986
+ const desiredGrowth = activeWidth * multiplier;
2987
+ const neighborIndexes = [activeIndex - 1, activeIndex + 1].filter(
2988
+ (index) => index >= 0 && index < items.length
2989
+ );
2990
+ const compression = /* @__PURE__ */ new Map();
2991
+ if (neighborIndexes.length === 1) {
2992
+ const index = neighborIndexes[0];
2993
+ compression.set(
2994
+ index,
2995
+ Math.min(desiredGrowth, this._compressionCapacity(items[index], rects[index]))
2996
+ );
2997
+ } else {
2998
+ const half = desiredGrowth / 2;
2999
+ for (const index of neighborIndexes) {
3000
+ compression.set(
3001
+ index,
3002
+ Math.min(half, this._compressionCapacity(items[index], rects[index]))
3003
+ );
3004
+ }
3005
+ let remaining = desiredGrowth - [...compression.values()].reduce((sum, value) => sum + value, 0);
3006
+ for (const index of neighborIndexes) {
3007
+ if (remaining <= 0) break;
3008
+ const capacity = this._compressionCapacity(items[index], rects[index]) - compression.get(index);
3009
+ const extra = Math.min(remaining, capacity);
3010
+ compression.set(index, compression.get(index) + extra);
3011
+ remaining -= extra;
3012
+ }
3013
+ }
3014
+ const actualGrowth = [...compression.values()].reduce((sum, value) => sum + value, 0);
3015
+ if (actualGrowth <= 0) return;
3016
+ items.forEach((candidate, index) => {
3017
+ this._pressedStyles.set(candidate, {
3018
+ width: candidate.style.width,
3019
+ baseWidth: rects[index].width
3020
+ });
3021
+ candidate.style.width = `${rects[index].width}px`;
3022
+ });
3023
+ void this.el.offsetWidth;
3024
+ items.forEach((candidate, index) => {
3025
+ const adjustment = index === activeIndex ? actualGrowth : -(compression.get(index) || 0);
3026
+ candidate.style.width = `${rects[index].width + adjustment}px`;
3027
+ });
3028
+ }
3029
+ _compressionCapacity(item, rect) {
3030
+ const minimumWidth = item.classList.contains("icon-button") ? rect.width * (1 - MAX_ICON_COMPRESSION) : rect.height;
3031
+ return Math.max(0, rect.width - minimumWidth);
3032
+ }
3033
+ };
3034
+
2493
3035
  // src/ts/components/carousel.ts
2494
- var _defaults7 = {
3036
+ var _defaults10 = {
2495
3037
  duration: 200,
2496
- dist: -100,
2497
- shift: 0,
2498
- padding: 0,
2499
- numVisible: 5,
2500
3038
  fullWidth: false,
2501
3039
  indicators: false,
2502
3040
  noWrap: false,
2503
- onCycleTo: null
3041
+ interval: 0,
3042
+ height: null,
3043
+ onCycleTo: null,
3044
+ i18n: {
3045
+ carousel: "Carousel",
3046
+ item: "Item",
3047
+ of: "of",
3048
+ indicators: "Slides",
3049
+ slide: "Slide"
3050
+ }
2504
3051
  };
2505
3052
  var _Carousel = class _Carousel extends Component {
2506
3053
  constructor(el, options) {
@@ -2511,101 +3058,112 @@ var Expressive = (() => {
2511
3058
  this._ignoreScroll = false;
2512
3059
  this._trackRaf = null;
2513
3060
  this._started = false;
3061
+ this._vertical = false;
3062
+ this._resizeObserver = null;
3063
+ this._usesWindowResize = false;
3064
+ this._dragPointerId = null;
3065
+ this._dragStartX = 0;
3066
+ this._dragStartY = 0;
3067
+ this._dragStartScroll = 0;
3068
+ this._suppressClick = false;
3069
+ this._suppressClickTimeout = null;
3070
+ this._generatedContainerRole = false;
3071
+ this._generatedContainerLabel = false;
3072
+ this._generatedContainerDescription = false;
3073
+ this._generatedWideLayout = false;
3074
+ this._generatedFixedHeight = false;
3075
+ this._authoredInlineHeight = null;
3076
+ this._autoAdvanceTimer = null;
3077
+ this._autoAdvances = false;
3078
+ this._autoPaused = false;
3079
+ this._hovered = false;
3080
+ this._focused = false;
3081
+ this._generatedItemTabIndexes = /* @__PURE__ */ new Set();
3082
+ this._generatedItemLabels = /* @__PURE__ */ new Set();
3083
+ this._generatedItemDescriptions = /* @__PURE__ */ new Set();
3084
+ this._generatedItemSizes = /* @__PURE__ */ new Set();
3085
+ this._authoredItemSizes = /* @__PURE__ */ new Set();
3086
+ this._itemA11yOriginal = /* @__PURE__ */ new Map();
2514
3087
  this._handleThrottledResize = Utils.throttle(() => this._handleResize(), 200);
2515
- this._handleCarouselTap = (e) => {
2516
- if (e.pointerType === "mouse" && e.button !== 0) return;
2517
- if (e.target instanceof HTMLElement && e.target.tagName === "IMG") {
2518
- e.preventDefault();
3088
+ this._handleAutoAdvanceState = (e) => {
3089
+ if (e.type === "mouseenter") this._hovered = true;
3090
+ else if (e.type === "mouseleave") this._hovered = false;
3091
+ else if (e.type === "focusin") this._focused = true;
3092
+ else if (e.type === "focusout") this._focused = false;
3093
+ this._syncAutoAdvance();
3094
+ };
3095
+ this._tick = () => {
3096
+ this._autoAdvanceTimer = null;
3097
+ this._advance();
3098
+ this._syncAutoAdvance();
3099
+ };
3100
+ this._advance = () => {
3101
+ const next = this.center + 1;
3102
+ if (next < this.count) {
3103
+ this.set(next);
3104
+ return;
3105
+ }
3106
+ if (this.options.noWrap) {
3107
+ this._autoAdvances = false;
3108
+ this._syncAutoAdvance();
3109
+ return;
2519
3110
  }
3111
+ this.set(0);
3112
+ };
3113
+ this._handleTrackPointerDown = (e) => {
3114
+ if (e.pointerType !== "mouse" || e.button !== 0) return;
2520
3115
  this.pressed = true;
2521
3116
  this.dragged = false;
2522
- this.verticalDragged = false;
2523
- this.reference = e.clientX;
2524
- this.referenceY = e.clientY;
2525
- this.velocity = this.amplitude = 0;
2526
- this.frame = this.offset;
2527
- this.timestamp = Date.now();
3117
+ this._suppressClick = false;
3118
+ this._dragPointerId = e.pointerId;
3119
+ this._dragStartX = e.clientX;
3120
+ this._dragStartY = e.clientY;
3121
+ this._dragStartScroll = this._vertical ? this._scroller.scrollTop : this._scroller.scrollLeft;
2528
3122
  try {
2529
- this.el.setPointerCapture(e.pointerId);
3123
+ this._scroller.setPointerCapture(e.pointerId);
2530
3124
  } catch {
2531
3125
  }
2532
- if (this._trackRaf !== null) cancelAnimationFrame(this._trackRaf);
2533
- const loop = () => {
2534
- this._track();
2535
- if (this.pressed) this._trackRaf = requestAnimationFrame(loop);
2536
- else this._trackRaf = null;
2537
- };
2538
- this._trackRaf = requestAnimationFrame(loop);
2539
- };
2540
- this._handleCarouselDrag = (e) => {
2541
- if (!this.pressed) return;
2542
- const delta = this.reference - e.clientX;
2543
- const deltaY = Math.abs(this.referenceY - e.clientY);
2544
- if (deltaY < 30 && !this.verticalDragged) {
2545
- if (delta > 2 || delta < -2) {
2546
- this.dragged = true;
2547
- this.reference = e.clientX;
2548
- this._scroll(this.offset + delta);
2549
- }
2550
- } else if (this.dragged) {
2551
- e.preventDefault();
2552
- } else {
2553
- this.verticalDragged = true;
3126
+ };
3127
+ this._handleTrackPointerMove = (e) => {
3128
+ if (!this.pressed || e.pointerId !== this._dragPointerId) return;
3129
+ const deltaX = e.clientX - this._dragStartX;
3130
+ const deltaY = e.clientY - this._dragStartY;
3131
+ const primaryDelta = this._vertical ? deltaY : deltaX;
3132
+ const crossDelta = this._vertical ? deltaX : deltaY;
3133
+ if (!this.dragged) {
3134
+ if (Math.abs(primaryDelta) < 6 || Math.abs(primaryDelta) <= Math.abs(crossDelta)) return;
3135
+ this.dragged = true;
3136
+ this.el.classList.add("dragging");
2554
3137
  }
2555
- if (this.dragged) e.preventDefault();
3138
+ if (this._vertical) this._scroller.scrollTop = this._dragStartScroll - deltaY;
3139
+ else this._scroller.scrollLeft = this._dragStartScroll - deltaX;
3140
+ e.preventDefault();
2556
3141
  };
2557
- this._handleCarouselRelease = (e) => {
2558
- if (!this.pressed) return;
3142
+ this._handleTrackPointerUp = (e) => {
3143
+ if (!this.pressed || e.pointerId !== this._dragPointerId) return;
2559
3144
  this.pressed = false;
2560
- if (this._trackRaf !== null) {
2561
- cancelAnimationFrame(this._trackRaf);
2562
- this._trackRaf = null;
2563
- }
3145
+ this._dragPointerId = null;
3146
+ this.el.classList.remove("dragging");
2564
3147
  try {
2565
- this.el.releasePointerCapture(e.pointerId);
3148
+ this._scroller.releasePointerCapture(e.pointerId);
2566
3149
  } catch {
2567
3150
  }
2568
- this.target = this.offset;
2569
- if (this.velocity > 10 || this.velocity < -10) {
2570
- this.amplitude = 0.9 * this.velocity;
2571
- this.target = this.offset + this.amplitude;
2572
- }
2573
- this.target = Math.round(this.target / this.dim) * this.dim;
2574
- if (this.noWrap) {
2575
- if (this.target >= this.dim * (this.count - 1)) {
2576
- this.target = this.dim * (this.count - 1);
2577
- } else if (this.target < 0) {
2578
- this.target = 0;
2579
- }
2580
- }
2581
- this.amplitude = this.target - this.offset;
2582
- this.timestamp = Date.now();
2583
- requestAnimationFrame(this._autoScroll);
2584
- if (this.dragged) e.preventDefault();
3151
+ if (!this.dragged) return;
3152
+ this._suppressClick = true;
3153
+ window.clearTimeout(this._suppressClickTimeout);
3154
+ this._suppressClickTimeout = setTimeout(() => {
3155
+ this._suppressClick = false;
3156
+ }, 0);
3157
+ this._syncActive(this._nearestIndex(), true);
3158
+ };
3159
+ this._handleTrackClick = (e) => {
3160
+ if (!this._suppressClick) return;
3161
+ this._suppressClick = false;
3162
+ e.preventDefault();
3163
+ e.stopImmediatePropagation();
2585
3164
  };
2586
- this._handleCarouselClick = (e) => {
2587
- if (this.dragged) {
2588
- e.preventDefault();
2589
- e.stopPropagation();
2590
- return;
2591
- }
2592
- const clickedElem = e.target.closest(".carousel-item");
2593
- if (!clickedElem || !this.el.contains(clickedElem)) return;
2594
- const clickedIndex = this.images.indexOf(clickedElem);
2595
- const diff = this._wrap(this.center) - clickedIndex;
2596
- if (diff !== 0) {
2597
- e.preventDefault();
2598
- e.stopPropagation();
2599
- }
2600
- if (clickedIndex < 0) {
2601
- if (e.clientX - e.target.getBoundingClientRect().left > this.el.clientWidth / 2) {
2602
- this.next();
2603
- } else {
2604
- this.prev();
2605
- }
2606
- } else {
2607
- this._cycleTo(clickedIndex);
2608
- }
3165
+ this._handleTrackDragStart = (e) => {
3166
+ if (e.target.closest("img, picture, video")) e.preventDefault();
2609
3167
  };
2610
3168
  this._handleIndicatorClick = (e) => {
2611
3169
  e.stopPropagation();
@@ -2618,112 +3176,107 @@ var Expressive = (() => {
2618
3176
  this._handleKeydown = (e) => {
2619
3177
  const target = e.target;
2620
3178
  if (target.closest('input, textarea, select, [contenteditable="true"]')) return;
2621
- if (Utils.keys.ARROW_RIGHT.includes(e.key)) {
3179
+ const forward = this._vertical ? e.key === "ArrowDown" : e.key === Utils.keys.ARROW_RIGHT;
3180
+ const backward = this._vertical ? e.key === "ArrowUp" : e.key === Utils.keys.ARROW_LEFT;
3181
+ if (e.key === "Home") {
3182
+ e.preventDefault();
3183
+ this.set(0);
3184
+ this._focusItem(0);
3185
+ } else if (e.key === "End") {
3186
+ e.preventDefault();
3187
+ this.set(this.count - 1);
3188
+ this._focusItem(this.count - 1);
3189
+ } else if (forward) {
2622
3190
  e.preventDefault();
2623
3191
  this.next();
2624
- } else if (Utils.keys.ARROW_LEFT.includes(e.key)) {
3192
+ this._focusItem(this.center);
3193
+ } else if (backward) {
2625
3194
  e.preventDefault();
2626
3195
  this.prev();
3196
+ this._focusItem(this.center);
2627
3197
  }
2628
3198
  };
2629
- this._handleFlatScroll = () => {
2630
- if (this._ignoreScroll) return;
2631
- const index = this._nearestIndex();
2632
- if (index !== this.center) this._syncActive(index, this.dragged);
3199
+ this._handleTrackScroll = () => {
3200
+ if (!this.el.classList.contains("scrolling")) this.el.classList.add("scrolling");
3201
+ window.clearTimeout(this.scrollingTimeout);
3202
+ this.scrollingTimeout = setTimeout(() => this.el.classList.remove("scrolling"), 120);
3203
+ if (this._trackRaf !== null) return;
3204
+ this._trackRaf = requestAnimationFrame(() => {
3205
+ this._trackRaf = null;
3206
+ this._updateParallax();
3207
+ if (this._ignoreScroll) return;
3208
+ const index = this._nearestIndex();
3209
+ if (index !== this.center) this._syncActive(index, this.dragged);
3210
+ });
2633
3211
  };
2634
3212
  this._handleResize = () => {
2635
3213
  if (!this.images.length) return;
2636
- if (this._flat) {
2637
- this._scrollToIndex(this.center, false);
2638
- return;
2639
- }
2640
- if (this.options.fullWidth) {
2641
- const firstItem = this.images[0];
2642
- if (!firstItem) return;
2643
- this.itemWidth = firstItem.clientWidth;
2644
- this.dim = this.itemWidth * 2 + this.options.padding;
2645
- this.offset = this.center * 2 * this.itemWidth;
2646
- this.target = this.offset;
2647
- } else {
2648
- this._scroll();
2649
- }
2650
- };
2651
- this._track = () => {
2652
- const now = Date.now();
2653
- const elapsed = now - this.timestamp;
2654
- const delta = this.offset - this.frame;
2655
- const v = 1e3 * delta / (1 + elapsed);
2656
- this.timestamp = now;
2657
- this.frame = this.offset;
2658
- this.velocity = 0.8 * v + 0.2 * this.velocity;
2659
- };
2660
- this._autoScroll = () => {
2661
- if (!this.amplitude) return;
2662
- const elapsed = Date.now() - this.timestamp;
2663
- const delta = this.amplitude * Math.exp(-elapsed / this.options.duration);
2664
- if (delta > 2 || delta < -2) {
2665
- this._scroll(this.target - delta);
2666
- requestAnimationFrame(this._autoScroll);
2667
- } else {
2668
- this._scroll(this.target);
2669
- }
3214
+ this._syncAdaptiveMode();
3215
+ this._syncLayoutRoles(this.center);
3216
+ this._scrollToIndex(this.center, false);
3217
+ this._updateParallax();
2670
3218
  };
2671
3219
  this.el["Expressive_Carousel"] = this;
2672
3220
  this.options = {
2673
3221
  ..._Carousel.defaults,
2674
3222
  ...options
2675
3223
  };
2676
- this._flat = this.options.fullWidth || this.el.classList.contains("flat");
2677
- if (this._flat) {
3224
+ if (options?.i18n) {
3225
+ this.options.i18n = { ..._Carousel.defaults.i18n, ...options.i18n };
3226
+ }
3227
+ if (this.options.fullWidth || this.el.classList.contains("flat")) {
2678
3228
  this.el.classList.add("flat");
2679
3229
  this.options.fullWidth = true;
2680
- this.options.dist = 0;
2681
- }
2682
- if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
2683
- this.options.duration = 1;
2684
- if (!this._flat) this.options.dist = 0;
2685
3230
  }
3231
+ this._vertical = this.el.classList.contains("full-screen");
3232
+ const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
3233
+ if (reducedMotion) this.options.duration = 1;
2686
3234
  this.pressed = false;
2687
3235
  this.dragged = false;
2688
- this.offset = this.target = 0;
2689
3236
  this.images = this._collectItems();
2690
3237
  if (!this.images.length) {
2691
3238
  console.error("Carousel: no .carousel-item elements to show");
2692
3239
  return;
2693
3240
  }
3241
+ this.images.forEach((item) => {
3242
+ this._itemA11yOriginal.set(item, {
3243
+ ariaHidden: item.getAttribute("aria-hidden"),
3244
+ tabIndex: item.getAttribute("tabindex")
3245
+ });
3246
+ if (item.hasAttribute("data-carousel-size")) this._authoredItemSizes.add(item);
3247
+ });
2694
3248
  this.hasMultipleSlides = this.images.length > 1;
2695
3249
  this.showIndicators = this.options.indicators && this.hasMultipleSlides;
2696
- this.noWrap = this.options.noWrap || !this.hasMultipleSlides || this._flat;
2697
3250
  this.count = this.images.length;
2698
- this.options.numVisible = Math.min(this.count, this.options.numVisible);
3251
+ this._autoAdvances = this.options.interval > 0 && this.hasMultipleSlides && !reducedMotion;
2699
3252
  this.images.forEach((item) => item.classList.add("carousel-item"));
2700
- if (this._flat) this._wrapTrack();
2701
- const firstItem = this.images[0];
2702
- this.itemWidth = firstItem.clientWidth || this.el.clientWidth || 1;
2703
- this.itemHeight = firstItem.clientHeight || this.el.clientHeight || 1;
2704
- this.dim = this.itemWidth * 2 + this.options.padding || 1;
3253
+ if (this.options.height !== null) {
3254
+ this._generatedFixedHeight = !this.el.classList.contains("fixed-height");
3255
+ this._authoredInlineHeight = this.el.style.getPropertyValue("--carousel-height") || null;
3256
+ this.el.classList.add("fixed-height");
3257
+ this.el.style.setProperty("--carousel-height", `${this.options.height}px`);
3258
+ }
3259
+ this._wrapTrack();
3260
+ this._syncAdaptiveMode();
2705
3261
  if (this.showIndicators) this._setupIndicators();
2706
- this.el.tabIndex = this.el.tabIndex >= 0 ? this.el.tabIndex : 0;
2707
- this.el.setAttribute("aria-roledescription", "carousel");
3262
+ this._setupAccessibility();
2708
3263
  this._setupEventHandlers();
2709
3264
  this._started = true;
2710
- if (this._flat) {
2711
- const start = Math.max(
2712
- 0,
2713
- this.images.findIndex((el2) => el2.classList.contains("active"))
2714
- );
2715
- this.center = start;
2716
- this._syncActive(start, false);
2717
- this._scrollToIndex(start, false);
2718
- } else {
2719
- this._scroll(this.offset);
2720
- }
3265
+ const start = Math.max(
3266
+ 0,
3267
+ this.images.findIndex((el2) => el2.classList.contains("active"))
3268
+ );
3269
+ this.center = start;
3270
+ this._syncActive(start, false);
3271
+ this._updateParallax();
3272
+ this._scrollToIndex(start, false);
3273
+ this._syncAutoAdvance();
2721
3274
  }
2722
3275
  get _scroller() {
2723
3276
  return this._trackEl ?? this.el;
2724
3277
  }
2725
3278
  static get defaults() {
2726
- return _defaults7;
3279
+ return _defaults10;
2727
3280
  }
2728
3281
  static init(els, options = {}) {
2729
3282
  return super.init(els, options, _Carousel);
@@ -2748,6 +3301,39 @@ var Expressive = (() => {
2748
3301
  return el.tagName !== "NAV";
2749
3302
  });
2750
3303
  }
3304
+ _setupAccessibility() {
3305
+ if (!this.el.hasAttribute("role")) {
3306
+ this.el.setAttribute("role", "region");
3307
+ this._generatedContainerRole = true;
3308
+ }
3309
+ if (!this.el.hasAttribute("aria-label") && !this.el.hasAttribute("aria-labelledby")) {
3310
+ this.el.setAttribute("aria-label", this.options.i18n.carousel);
3311
+ this._generatedContainerLabel = true;
3312
+ }
3313
+ if (!this.el.hasAttribute("aria-roledescription")) {
3314
+ this.el.setAttribute("aria-roledescription", "carousel");
3315
+ this._generatedContainerDescription = true;
3316
+ }
3317
+ const naturallyFocusable = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
3318
+ this.images.forEach((item, index) => {
3319
+ if (!item.hasAttribute("aria-roledescription")) {
3320
+ item.setAttribute("aria-roledescription", "slide");
3321
+ this._generatedItemDescriptions.add(item);
3322
+ }
3323
+ if (!item.hasAttribute("aria-label") && !item.hasAttribute("aria-labelledby")) {
3324
+ const mediaName = item.querySelector("img[alt]")?.getAttribute("alt")?.trim();
3325
+ const textName = item.textContent?.replace(/\s+/g, " ").trim();
3326
+ const name = mediaName || textName;
3327
+ const position = `${this.options.i18n.item} ${index + 1} ${this.options.i18n.of} ${this.count}`;
3328
+ item.setAttribute("aria-label", name ? `${name}, ${position}` : position);
3329
+ this._generatedItemLabels.add(item);
3330
+ }
3331
+ if (!item.matches(naturallyFocusable) && !item.querySelector(naturallyFocusable)) {
3332
+ item.tabIndex = 0;
3333
+ this._generatedItemTabIndexes.add(item);
3334
+ }
3335
+ });
3336
+ }
2751
3337
  _setupIndicators() {
2752
3338
  const existing = Array.from(this.el.children).find(
2753
3339
  (el) => el instanceof HTMLElement && el.classList.contains("indicators")
@@ -2755,7 +3341,7 @@ var Expressive = (() => {
2755
3341
  this._indicators = existing ?? document.createElement("nav");
2756
3342
  this._ownIndicators = !existing;
2757
3343
  this._indicators.classList.add("indicators");
2758
- this._indicators.setAttribute("aria-label", "Slides");
3344
+ this._indicators.setAttribute("aria-label", this.options.i18n.indicators);
2759
3345
  this._indicators.replaceChildren();
2760
3346
  this.images.forEach((_, i) => {
2761
3347
  const button = document.createElement("button");
@@ -2765,158 +3351,124 @@ var Expressive = (() => {
2765
3351
  button.classList.add("active");
2766
3352
  button.setAttribute("aria-current", "true");
2767
3353
  }
2768
- button.setAttribute("aria-label", `Slide ${i + 1}`);
3354
+ button.setAttribute("aria-label", `${this.options.i18n.slide} ${i + 1}`);
2769
3355
  this._indicators.appendChild(button);
2770
3356
  });
2771
3357
  if (this._ownIndicators) this.el.appendChild(this._indicators);
2772
- if (this._flat) {
2773
- this.el.querySelector(".carousel-fixed-item")?.classList.add("with-indicators");
2774
- }
3358
+ this.el.querySelector(".carousel-fixed-item")?.classList.add("with-indicators");
2775
3359
  }
2776
3360
  _setupEventHandlers() {
2777
3361
  this.el.addEventListener("keydown", this._handleKeydown);
3362
+ if (this._autoAdvances) {
3363
+ this.el.addEventListener("mouseenter", this._handleAutoAdvanceState);
3364
+ this.el.addEventListener("mouseleave", this._handleAutoAdvanceState);
3365
+ this.el.addEventListener("focusin", this._handleAutoAdvanceState);
3366
+ this.el.addEventListener("focusout", this._handleAutoAdvanceState);
3367
+ document.addEventListener("visibilitychange", this._handleAutoAdvanceState);
3368
+ }
2778
3369
  if (this.showIndicators && this._indicators) {
2779
3370
  this._indicators.addEventListener("click", this._handleIndicatorClick);
2780
3371
  }
2781
- _Carousel._live.add(this);
2782
- if (!_Carousel._resizeListening) {
2783
- window.addEventListener("resize", _Carousel._onResize, { passive: true });
2784
- _Carousel._resizeListening = true;
3372
+ if (typeof ResizeObserver !== "undefined") {
3373
+ this._resizeObserver = new ResizeObserver(() => this._handleThrottledResize());
3374
+ this._resizeObserver.observe(this.el);
2785
3375
  }
2786
- if (this._flat) {
2787
- this._scroller.addEventListener("scroll", this._handleFlatScroll, { passive: true });
2788
- return;
3376
+ this._usesWindowResize = !this._resizeObserver || this.el.classList.contains("full-screen");
3377
+ if (this._usesWindowResize) {
3378
+ _Carousel._live.add(this);
3379
+ if (!_Carousel._resizeListening) {
3380
+ window.addEventListener("resize", _Carousel._onResize, {
3381
+ passive: true
3382
+ });
3383
+ _Carousel._resizeListening = true;
3384
+ }
2789
3385
  }
2790
- this.el.addEventListener("pointerdown", this._handleCarouselTap);
2791
- this.el.addEventListener("pointermove", this._handleCarouselDrag);
2792
- this.el.addEventListener("pointerup", this._handleCarouselRelease);
2793
- this.el.addEventListener("pointercancel", this._handleCarouselRelease);
2794
- this.el.addEventListener("click", this._handleCarouselClick);
3386
+ this._scroller.addEventListener("scroll", this._handleTrackScroll, {
3387
+ passive: true
3388
+ });
3389
+ this._scroller.addEventListener("pointerdown", this._handleTrackPointerDown);
3390
+ this._scroller.addEventListener("pointermove", this._handleTrackPointerMove);
3391
+ this._scroller.addEventListener("pointerup", this._handleTrackPointerUp);
3392
+ this._scroller.addEventListener("pointercancel", this._handleTrackPointerUp);
3393
+ this._scroller.addEventListener("click", this._handleTrackClick, true);
3394
+ this._scroller.addEventListener("dragstart", this._handleTrackDragStart);
2795
3395
  }
2796
3396
  _removeEventHandlers() {
2797
3397
  this.el.removeEventListener("keydown", this._handleKeydown);
3398
+ this.el.removeEventListener("mouseenter", this._handleAutoAdvanceState);
3399
+ this.el.removeEventListener("mouseleave", this._handleAutoAdvanceState);
3400
+ this.el.removeEventListener("focusin", this._handleAutoAdvanceState);
3401
+ this.el.removeEventListener("focusout", this._handleAutoAdvanceState);
3402
+ document.removeEventListener("visibilitychange", this._handleAutoAdvanceState);
2798
3403
  if (this._indicators) {
2799
3404
  this._indicators.removeEventListener("click", this._handleIndicatorClick);
2800
3405
  }
2801
- this._scroller.removeEventListener("scroll", this._handleFlatScroll);
2802
- this.el.removeEventListener("pointerdown", this._handleCarouselTap);
2803
- this.el.removeEventListener("pointermove", this._handleCarouselDrag);
2804
- this.el.removeEventListener("pointerup", this._handleCarouselRelease);
2805
- this.el.removeEventListener("pointercancel", this._handleCarouselRelease);
2806
- this.el.removeEventListener("click", this._handleCarouselClick);
2807
- _Carousel._live.delete(this);
2808
- if (_Carousel._live.size === 0 && _Carousel._resizeListening) {
2809
- window.removeEventListener("resize", _Carousel._onResize);
2810
- _Carousel._resizeListening = false;
3406
+ this._scroller.removeEventListener("scroll", this._handleTrackScroll);
3407
+ this._scroller.removeEventListener("pointerdown", this._handleTrackPointerDown);
3408
+ this._scroller.removeEventListener("pointermove", this._handleTrackPointerMove);
3409
+ this._scroller.removeEventListener("pointerup", this._handleTrackPointerUp);
3410
+ this._scroller.removeEventListener("pointercancel", this._handleTrackPointerUp);
3411
+ this._scroller.removeEventListener("click", this._handleTrackClick, true);
3412
+ this._scroller.removeEventListener("dragstart", this._handleTrackDragStart);
3413
+ this._resizeObserver?.disconnect();
3414
+ this._resizeObserver = null;
3415
+ if (this._usesWindowResize) {
3416
+ _Carousel._live.delete(this);
3417
+ if (_Carousel._live.size === 0 && _Carousel._resizeListening) {
3418
+ window.removeEventListener("resize", _Carousel._onResize);
3419
+ _Carousel._resizeListening = false;
3420
+ }
2811
3421
  }
2812
3422
  }
2813
3423
  _teardown() {
2814
3424
  this._removeEventHandlers();
3425
+ this._autoAdvances = false;
3426
+ this._syncAutoAdvance();
2815
3427
  if (this._trackRaf !== null) cancelAnimationFrame(this._trackRaf);
2816
3428
  window.clearTimeout(this.scrollingTimeout);
3429
+ window.clearTimeout(this._suppressClickTimeout);
2817
3430
  if (this._ownIndicators) this._indicators?.remove();
2818
3431
  this._unwrapTrack();
2819
3432
  this.images.forEach((el) => {
2820
- el.style.transform = "";
2821
- el.style.zIndex = "";
2822
- el.style.opacity = "";
2823
- el.style.visibility = "";
3433
+ el.style.removeProperty("--md-comp-carousel-item-parallax");
3434
+ if (this._generatedItemLabels.has(el)) el.removeAttribute("aria-label");
3435
+ if (this._generatedItemDescriptions.has(el)) el.removeAttribute("aria-roledescription");
3436
+ if (this._generatedItemSizes.has(el)) el.removeAttribute("data-carousel-size");
3437
+ const original = this._itemA11yOriginal.get(el);
3438
+ if (original?.ariaHidden === null) el.removeAttribute("aria-hidden");
3439
+ else if (original) el.setAttribute("aria-hidden", original.ariaHidden);
3440
+ if (original?.tabIndex === null) el.removeAttribute("tabindex");
3441
+ else if (original) el.setAttribute("tabindex", original.tabIndex);
2824
3442
  });
2825
- }
2826
- _xpos(e) {
2827
- if ("clientX" in e) return e.clientX;
2828
- return 0;
2829
- }
2830
- _ypos(e) {
2831
- if ("clientY" in e) return e.clientY;
2832
- return 0;
2833
- }
2834
- _wrap(x) {
2835
- return x >= this.count ? x % this.count : x < 0 ? this._wrap(this.count + x % this.count) : x;
2836
- }
2837
- _scroll(x = 0) {
2838
- if (!this.el.classList.contains("scrolling")) {
2839
- this.el.classList.add("scrolling");
2840
- }
2841
- window.clearTimeout(this.scrollingTimeout);
2842
- this.scrollingTimeout = setTimeout(() => {
2843
- this.el.classList.remove("scrolling");
2844
- }, this.options.duration);
2845
- this.offset = typeof x === "number" ? x : this.offset;
2846
- this.center = Math.floor((this.offset + this.dim / 2) / this.dim);
2847
- const half = this.count >> 1;
2848
- const delta = this.offset - this.center * this.dim;
2849
- const dir = delta < 0 ? 1 : -1;
2850
- const tween = -dir * delta * 2 / this.dim;
2851
- const lastCenter = this.center;
2852
- const numVisibleOffset = 1 / this.options.numVisible;
2853
- let alignment;
2854
- let centerTweenedOpacity;
2855
- if (this.options.fullWidth) {
2856
- alignment = "translateX(0)";
2857
- centerTweenedOpacity = 1;
2858
- } else {
2859
- alignment = "translateX(" + (this.el.clientWidth - this.itemWidth) / 2 + "px) ";
2860
- alignment += "translateY(" + (this.el.clientHeight - this.itemHeight) / 2 + "px)";
2861
- centerTweenedOpacity = 1 - numVisibleOffset * tween;
2862
- }
2863
- this._paintIndicators(this.center);
2864
- if (!this.noWrap || this.center >= 0 && this.center < this.count) {
2865
- const el = this.images[this._wrap(this.center)];
2866
- if (el && !el.classList.contains("active")) {
2867
- this.el.querySelector(".carousel-item.active")?.classList.remove("active");
2868
- el.classList.add("active");
2869
- }
2870
- }
2871
- for (let i = 1; i <= half; ++i) {
2872
- let zTranslation;
2873
- let tweenedOpacity;
2874
- if (this.options.fullWidth) {
2875
- zTranslation = this.options.dist;
2876
- tweenedOpacity = i === half && delta < 0 ? 1 - tween : 1;
2877
- } else {
2878
- zTranslation = this.options.dist * (i * 2 + tween * dir);
2879
- tweenedOpacity = 1 - numVisibleOffset * (i * 2 + tween * dir);
2880
- }
2881
- if (!this.noWrap || this.center + i < this.count) {
2882
- const el = this.images[this._wrap(this.center + i)];
2883
- const transformString = `${alignment} translateX(${this.options.shift + (this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`;
2884
- this._updateItemStyle(el, tweenedOpacity, -i, transformString);
2885
- }
2886
- if (this.options.fullWidth) {
2887
- zTranslation = this.options.dist;
2888
- tweenedOpacity = i === half && delta > 0 ? 1 - tween : 1;
2889
- } else {
2890
- zTranslation = this.options.dist * (i * 2 - tween * dir);
2891
- tweenedOpacity = 1 - numVisibleOffset * (i * 2 - tween * dir);
2892
- }
2893
- if (!this.noWrap || this.center - i >= 0) {
2894
- const el = this.images[this._wrap(this.center - i)];
2895
- const transformString = `${alignment} translateX(${-this.options.shift + (-this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`;
2896
- this._updateItemStyle(el, tweenedOpacity, -i, transformString);
2897
- }
2898
- }
2899
- if (!this.noWrap || this.center >= 0 && this.center < this.count) {
2900
- const el = this.images[this._wrap(this.center)];
2901
- const transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir * this.options.shift * tween}px) translateZ(${this.options.dist * tween}px)`;
2902
- this._updateItemStyle(el, centerTweenedOpacity, 0, transformString);
2903
- }
2904
- const currItem = this.images[this._wrap(this.center)];
2905
- this._syncA11y(this._wrap(this.center));
2906
- if (lastCenter !== this.center && typeof this.options.onCycleTo === "function") {
2907
- this.options.onCycleTo.call(this, currItem, this.dragged);
2908
- }
2909
- if (typeof this.oneTimeCallback === "function") {
2910
- this.oneTimeCallback.call(this, currItem, this.dragged);
2911
- this.oneTimeCallback = null;
3443
+ if (this._generatedContainerRole) this.el.removeAttribute("role");
3444
+ if (this._generatedContainerLabel) this.el.removeAttribute("aria-label");
3445
+ if (this._generatedContainerDescription) this.el.removeAttribute("aria-roledescription");
3446
+ if (this._generatedFixedHeight) this.el.classList.remove("fixed-height");
3447
+ if (this.options.height !== null) {
3448
+ if (this._authoredInlineHeight === null) this.el.style.removeProperty("--carousel-height");
3449
+ else this.el.style.setProperty("--carousel-height", this._authoredInlineHeight);
3450
+ }
3451
+ if (this._generatedWideLayout) {
3452
+ this.el.classList.remove(
3453
+ "multi-wide",
3454
+ "multi-large",
3455
+ "multi-extra-large",
3456
+ "full-screen-horizontal"
3457
+ );
2912
3458
  }
2913
3459
  }
2914
- _updateItemStyle(el, opacity, zIndex, transform) {
2915
- if (!el) return;
2916
- el.style.transform = transform;
2917
- el.style.zIndex = zIndex.toString();
2918
- el.style.opacity = opacity.toString();
2919
- el.style.visibility = "visible";
3460
+ /** Start or stop the timer to match the current pause state. */
3461
+ _syncAutoAdvance() {
3462
+ const run = this._autoAdvances && !this._autoPaused && !this._hovered && !this._focused && !document.hidden;
3463
+ if (run && this._autoAdvanceTimer === null) {
3464
+ this._autoAdvanceTimer = setTimeout(
3465
+ this._tick,
3466
+ this.options.duration + this.options.interval
3467
+ );
3468
+ } else if (!run && this._autoAdvanceTimer !== null) {
3469
+ clearTimeout(this._autoAdvanceTimer);
3470
+ this._autoAdvanceTimer = null;
3471
+ }
2920
3472
  }
2921
3473
  _paintIndicators(center) {
2922
3474
  if (!this.showIndicators || !this._indicators) return;
@@ -2928,18 +3480,12 @@ var Expressive = (() => {
2928
3480
  else el.removeAttribute("aria-current");
2929
3481
  });
2930
3482
  }
2931
- _syncA11y(index) {
2932
- this.images.forEach((el, i) => {
2933
- if (i === index) el.removeAttribute("aria-hidden");
2934
- else el.setAttribute("aria-hidden", "true");
2935
- });
2936
- }
2937
3483
  _syncActive(index, dragged) {
2938
3484
  const prev = this.center;
2939
3485
  this.center = index;
2940
3486
  this.images.forEach((el, i) => el.classList.toggle("active", i === index));
2941
3487
  this._paintIndicators(index);
2942
- this._syncA11y(index);
3488
+ this._syncLayoutRoles(index);
2943
3489
  const curr = this.images[index];
2944
3490
  if (prev !== index && typeof this.options.onCycleTo === "function") {
2945
3491
  this.options.onCycleTo.call(this, curr, dragged);
@@ -2949,6 +3495,81 @@ var Expressive = (() => {
2949
3495
  this.oneTimeCallback = null;
2950
3496
  }
2951
3497
  }
3498
+ _syncLayoutRoles(index) {
3499
+ const setSize = (item, size) => {
3500
+ if (this._authoredItemSizes.has(item)) return;
3501
+ item.setAttribute("data-carousel-size", size);
3502
+ this._generatedItemSizes.add(item);
3503
+ };
3504
+ this.images.forEach((item) => setSize(item, "large"));
3505
+ if (this.el.classList.contains("flat") || this.el.classList.contains("uncontained") || this.el.classList.contains("full-screen") && this._vertical) {
3506
+ return;
3507
+ }
3508
+ if (this.el.classList.contains("hero") || this.el.classList.contains("full-screen-horizontal")) {
3509
+ const next = this.images[index + 1];
3510
+ if (next) setSize(next, "small");
3511
+ if (this.el.classList.contains("center-aligned")) {
3512
+ const previous = this.images[index - 1];
3513
+ if (previous) setSize(previous, "small");
3514
+ }
3515
+ return;
3516
+ }
3517
+ const width = this.el.clientWidth;
3518
+ const desiredLargeCount = width >= 1600 ? 4 : width >= 1200 ? 3 : width >= 600 ? 2 : 1;
3519
+ const largeCount = Math.min(desiredLargeCount, Math.max(1, this.count - 2));
3520
+ this.el.classList.remove("multi-wide", "multi-large", "multi-extra-large");
3521
+ if (largeCount === 2) this.el.classList.add("multi-wide");
3522
+ else if (largeCount === 3) this.el.classList.add("multi-large");
3523
+ else if (largeCount >= 4) this.el.classList.add("multi-extra-large");
3524
+ this._generatedWideLayout = this._generatedWideLayout || largeCount > 1;
3525
+ for (let offset = 1; offset < largeCount; offset += 1) {
3526
+ const item = this.images[index + offset];
3527
+ if (item) setSize(item, "large");
3528
+ }
3529
+ const medium = this.images[index + largeCount];
3530
+ const small = this.images[index + largeCount + 1];
3531
+ if (medium) setSize(medium, "medium");
3532
+ if (small) setSize(small, "small");
3533
+ }
3534
+ _syncAdaptiveMode() {
3535
+ if (!this.el.classList.contains("full-screen")) return;
3536
+ const landscape = window.matchMedia("(orientation: landscape)").matches;
3537
+ const horizontal = this.el.clientWidth >= 840 || landscape;
3538
+ const wasVertical = this._vertical;
3539
+ this._vertical = !horizontal;
3540
+ this.el.classList.toggle("full-screen-horizontal", horizontal);
3541
+ this._generatedWideLayout = this._generatedWideLayout || horizontal;
3542
+ if (this._started && wasVertical !== this._vertical) {
3543
+ this._scroller.scrollTo({ left: 0, top: 0, behavior: "auto" });
3544
+ }
3545
+ }
3546
+ _focusItem(index) {
3547
+ const item = this.images[index];
3548
+ if (!item) return;
3549
+ const focusable = item.matches(
3550
+ 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
3551
+ ) ? item : item.querySelector(
3552
+ 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
3553
+ );
3554
+ (focusable ?? item).focus({ preventScroll: true });
3555
+ }
3556
+ _updateParallax() {
3557
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
3558
+ this.images.forEach(
3559
+ (item) => item.style.setProperty("--md-comp-carousel-item-parallax", "0px")
3560
+ );
3561
+ return;
3562
+ }
3563
+ const scrollerRect = this._scroller.getBoundingClientRect();
3564
+ const viewportCenter = this._vertical ? scrollerRect.top + scrollerRect.height / 2 : scrollerRect.left + scrollerRect.width / 2;
3565
+ const viewportSize = Math.max(1, this._vertical ? scrollerRect.height : scrollerRect.width);
3566
+ this.images.forEach((item) => {
3567
+ const rect = item.getBoundingClientRect();
3568
+ const itemCenter = this._vertical ? rect.top + rect.height / 2 : rect.left + rect.width / 2;
3569
+ const progress = Math.max(-1, Math.min(1, (itemCenter - viewportCenter) / viewportSize));
3570
+ item.style.setProperty("--md-comp-carousel-item-parallax", `${progress * -24}px`);
3571
+ });
3572
+ }
2952
3573
  _wrapTrack() {
2953
3574
  const track = document.createElement("div");
2954
3575
  track.className = "carousel-track";
@@ -2963,11 +3584,16 @@ var Expressive = (() => {
2963
3584
  this._trackEl = null;
2964
3585
  }
2965
3586
  _nearestIndex() {
2966
- const left = this._scroller.scrollLeft;
3587
+ const centered = this.el.classList.contains("center-aligned");
3588
+ const position = this._vertical ? this._scroller.scrollTop : this._scroller.scrollLeft;
3589
+ const viewportCenter = position + (this._vertical ? this._scroller.clientHeight : this._scroller.clientWidth) / 2;
2967
3590
  let best = 0;
2968
3591
  let bestDist = Infinity;
2969
3592
  this.images.forEach((el, i) => {
2970
- const dist = Math.abs(el.offsetLeft - left);
3593
+ const start = this._vertical ? el.offsetTop : el.offsetLeft;
3594
+ const size = this._vertical ? el.offsetHeight : el.offsetWidth;
3595
+ const target = centered ? start + size / 2 : start;
3596
+ const dist = Math.abs(target - (centered ? viewportCenter : position));
2971
3597
  if (dist < bestDist) {
2972
3598
  bestDist = dist;
2973
3599
  best = i;
@@ -2978,13 +3604,24 @@ var Expressive = (() => {
2978
3604
  _scrollToIndex(n, smooth = true) {
2979
3605
  const el = this.images[n];
2980
3606
  if (!el) return;
2981
- const left = el.offsetLeft;
2982
- if (Math.abs(this._scroller.scrollLeft - left) < 1) {
3607
+ const centered = this.el.classList.contains("center-aligned");
3608
+ const style = getComputedStyle(this._scroller);
3609
+ const inlinePadding = Number.parseFloat(style.paddingInlineStart) || 0;
3610
+ const blockPadding = Number.parseFloat(style.paddingBlockStart) || 0;
3611
+ const left = centered ? el.offsetLeft - (this._scroller.clientWidth - el.offsetWidth) / 2 : el.offsetLeft - inlinePadding;
3612
+ const top = el.offsetTop - blockPadding;
3613
+ const current = this._vertical ? this._scroller.scrollTop : this._scroller.scrollLeft;
3614
+ const target = this._vertical ? top : left;
3615
+ if (Math.abs(current - target) < 1) {
2983
3616
  this._syncActive(n, false);
2984
3617
  return;
2985
3618
  }
2986
3619
  this._ignoreScroll = true;
2987
- this._scroller.scrollTo({ left, behavior: smooth ? "smooth" : "auto" });
3620
+ this._scroller.scrollTo({
3621
+ left: this._vertical ? 0 : left,
3622
+ top: this._vertical ? top : 0,
3623
+ behavior: smooth ? "smooth" : "auto"
3624
+ });
2988
3625
  const done = () => {
2989
3626
  this._ignoreScroll = false;
2990
3627
  };
@@ -2992,63 +3629,38 @@ var Expressive = (() => {
2992
3629
  window.setTimeout(done, this.options.duration + 350);
2993
3630
  }
2994
3631
  _cycleTo(n, callback = null) {
2995
- if (this._flat) {
2996
- if (typeof callback === "function") this.oneTimeCallback = callback;
2997
- this._syncActive(n, false);
2998
- this._scrollToIndex(n);
2999
- return;
3000
- }
3001
- let diff = this.center % this.count - n;
3002
- if (!this.noWrap) {
3003
- if (diff < 0) {
3004
- if (Math.abs(diff + this.count) < Math.abs(diff)) {
3005
- diff += this.count;
3006
- }
3007
- } else if (diff > 0) {
3008
- if (Math.abs(diff - this.count) < diff) {
3009
- diff -= this.count;
3010
- }
3011
- }
3012
- }
3013
- this.target = this.dim * Math.round(this.offset / this.dim);
3014
- if (diff < 0) {
3015
- this.target += this.dim * Math.abs(diff);
3016
- } else if (diff > 0) {
3017
- this.target -= this.dim * diff;
3018
- }
3019
- if (typeof callback === "function") {
3020
- this.oneTimeCallback = callback;
3021
- }
3022
- if (this.offset !== this.target) {
3023
- this.amplitude = this.target - this.offset;
3024
- this.timestamp = Date.now();
3025
- requestAnimationFrame(this._autoScroll);
3026
- }
3632
+ if (typeof callback === "function") this.oneTimeCallback = callback;
3633
+ this._syncActive(n, false);
3634
+ this._scrollToIndex(n);
3635
+ }
3636
+ /**
3637
+ * Stop auto-advance until start() is called. Without an `interval` there is
3638
+ * nothing to stop and this does nothing.
3639
+ */
3640
+ pause() {
3641
+ this._autoPaused = true;
3642
+ this._syncAutoAdvance();
3643
+ }
3644
+ /** Resume auto-advance after pause(). */
3645
+ start() {
3646
+ this._autoPaused = false;
3647
+ this._syncAutoAdvance();
3027
3648
  }
3028
3649
  next(n = 1) {
3029
3650
  if (n === void 0 || isNaN(n)) n = 1;
3030
- let index = this.center + n;
3031
- if (index >= this.count || index < 0) {
3032
- if (this.noWrap) return;
3033
- index = this._wrap(index);
3034
- }
3651
+ const index = this.center + n;
3652
+ if (index >= this.count || index < 0) return;
3035
3653
  this._cycleTo(index);
3036
3654
  }
3037
3655
  prev(n = 1) {
3038
3656
  if (n === void 0 || isNaN(n)) n = 1;
3039
- let index = this.center - n;
3040
- if (index >= this.count || index < 0) {
3041
- if (this.noWrap) return;
3042
- index = this._wrap(index);
3043
- }
3657
+ const index = this.center - n;
3658
+ if (index >= this.count || index < 0) return;
3044
3659
  this._cycleTo(index);
3045
3660
  }
3046
3661
  set(n, callback) {
3047
3662
  if (n === void 0 || isNaN(n)) n = 0;
3048
- if (n > this.count || n < 0) {
3049
- if (this.noWrap) return;
3050
- n = this._wrap(n);
3051
- }
3663
+ if (n >= this.count || n < 0) return;
3052
3664
  this._cycleTo(n, callback);
3053
3665
  }
3054
3666
  };
@@ -3060,7 +3672,7 @@ var Expressive = (() => {
3060
3672
  var Carousel = _Carousel;
3061
3673
 
3062
3674
  // src/ts/components/characterCounter.ts
3063
- var _defaults8 = Object.freeze({});
3675
+ var _defaults11 = Object.freeze({});
3064
3676
  var CharacterCounter = class _CharacterCounter extends Component {
3065
3677
  constructor(el, options) {
3066
3678
  super(el, {}, _CharacterCounter);
@@ -3085,7 +3697,7 @@ var Expressive = (() => {
3085
3697
  this._setupEventHandlers();
3086
3698
  }
3087
3699
  static get defaults() {
3088
- return _defaults8;
3700
+ return _defaults11;
3089
3701
  }
3090
3702
  /**
3091
3703
  * Initializes instances of CharacterCounter.
@@ -3114,6 +3726,8 @@ var Expressive = (() => {
3114
3726
  _setupCounter() {
3115
3727
  this.counterEl = document.createElement("span");
3116
3728
  this.counterEl.classList.add("character-counter");
3729
+ this.counterEl.setAttribute("aria-live", "polite");
3730
+ this.counterEl.setAttribute("aria-atomic", "true");
3117
3731
  this.counterEl.style.float = "right";
3118
3732
  this.counterEl.style.fontSize = "12px";
3119
3733
  this.counterEl.style.height = "1";
@@ -3135,7 +3749,7 @@ var Expressive = (() => {
3135
3749
  };
3136
3750
 
3137
3751
  // src/ts/components/select.ts
3138
- var _defaults9 = {
3752
+ var _defaults12 = {
3139
3753
  classes: "",
3140
3754
  menuOptions: {}
3141
3755
  };
@@ -3184,7 +3798,7 @@ var Expressive = (() => {
3184
3798
  this._setupEventHandlers();
3185
3799
  }
3186
3800
  static get defaults() {
3187
- return _defaults9;
3801
+ return _defaults12;
3188
3802
  }
3189
3803
  /**
3190
3804
  * Initializes instances of FormSelect.
@@ -3315,7 +3929,7 @@ var Expressive = (() => {
3315
3929
  _buildInput() {
3316
3930
  this.input = document.createElement("input");
3317
3931
  this.input.id = "select-input-" + Utils.guid();
3318
- this.input.classList.add("menu-trigger");
3932
+ this.input.classList.add("menu-trigger", "no-autoinit");
3319
3933
  this.input.type = "text";
3320
3934
  this.input.readOnly = true;
3321
3935
  this.input.setAttribute("data-target", this.menuEl.id);
@@ -3355,10 +3969,8 @@ var Expressive = (() => {
3355
3969
  menuOptions.onOpenEnd = () => {
3356
3970
  const selectedOption = this.menuEl.querySelector(".selected");
3357
3971
  if (selectedOption) {
3358
- Utils.keyDown = true;
3359
3972
  this.menu.focusedIndex = [...selectedOption.parentNode.children].indexOf(selectedOption);
3360
3973
  this.menu._focusFocusedItem();
3361
- Utils.keyDown = false;
3362
3974
  if (this.menu.isScrollable) {
3363
3975
  let scrollOffset = selectedOption.getBoundingClientRect().top - this.menuEl.getBoundingClientRect().top;
3364
3976
  scrollOffset -= this.menuEl.clientHeight / 2;
@@ -3558,7 +4170,7 @@ var Expressive = (() => {
3558
4170
  };
3559
4171
 
3560
4172
  // src/ts/plugins/dockedDisplayPlugin.ts
3561
- var _defaults10 = {
4173
+ var _defaults13 = {
3562
4174
  margin: 5,
3563
4175
  transition: 10,
3564
4176
  duration: 250,
@@ -3607,7 +4219,7 @@ var Expressive = (() => {
3607
4219
  };
3608
4220
  this.el = el;
3609
4221
  this.options = {
3610
- ..._defaults10,
4222
+ ..._defaults13,
3611
4223
  ...options
3612
4224
  };
3613
4225
  this.container = document.createElement("div");
@@ -3628,7 +4240,7 @@ var Expressive = (() => {
3628
4240
  };
3629
4241
 
3630
4242
  // src/ts/components/datepicker.ts
3631
- var _defaults11 = {
4243
+ var _defaults14 = {
3632
4244
  // the default output format for the input field value
3633
4245
  format: "mmm dd, yyyy",
3634
4246
  // Used to create date object from current input string
@@ -3753,7 +4365,7 @@ var Expressive = (() => {
3753
4365
  if (this.options.onInputInteraction) this.options.onInputInteraction.call(this);
3754
4366
  };
3755
4367
  this._handleInputKeydown = (e) => {
3756
- if (Utils.keys.ENTER.includes(e.key)) {
4368
+ if (e.key === Utils.keys.ENTER) {
3757
4369
  e.preventDefault();
3758
4370
  this._batchDraws(() => {
3759
4371
  this.setDateFromInput(e.target);
@@ -3893,7 +4505,7 @@ var Expressive = (() => {
3893
4505
  }
3894
4506
  }
3895
4507
  static get defaults() {
3896
- return _defaults11;
4508
+ return _defaults14;
3897
4509
  }
3898
4510
  /**
3899
4511
  * Initializes instances of Datepicker.
@@ -4476,11 +5088,11 @@ var Expressive = (() => {
4476
5088
  const monthSelect = this.calendarEl.querySelector(".orig-select-month");
4477
5089
  FormSelect.init(yearSelect, {
4478
5090
  classes: "select-year",
4479
- menuOptions: { container: document.body, constrainWidth: false }
5091
+ menuOptions: { container: Utils.portalRoot(this.el), constrainWidth: false }
4480
5092
  });
4481
5093
  FormSelect.init(monthSelect, {
4482
5094
  classes: "select-month",
4483
- menuOptions: { container: document.body, constrainWidth: false }
5095
+ menuOptions: { container: Utils.portalRoot(this.el), constrainWidth: false }
4484
5096
  });
4485
5097
  yearSelect.addEventListener("change", this._handleYearChange);
4486
5098
  monthSelect.addEventListener("change", this._handleMonthChange);
@@ -4611,7 +5223,7 @@ var Expressive = (() => {
4611
5223
  var Datepicker = _Datepicker;
4612
5224
 
4613
5225
  // src/ts/components/lightbox.ts
4614
- var _defaults12 = {
5226
+ var _defaults15 = {
4615
5227
  inDuration: 275,
4616
5228
  outDuration: 200,
4617
5229
  onOpenStart: null,
@@ -4626,7 +5238,7 @@ var Expressive = (() => {
4626
5238
  this._handleLightboxToggle();
4627
5239
  };
4628
5240
  this._handleLightboxKeypress = (e) => {
4629
- if (Utils.keys.ENTER.includes(e.key)) {
5241
+ if (e.key === Utils.keys.ENTER) {
4630
5242
  this._handleLightboxToggle();
4631
5243
  }
4632
5244
  };
@@ -4641,7 +5253,7 @@ var Expressive = (() => {
4641
5253
  if (this.overlayActive) this.close();
4642
5254
  };
4643
5255
  this._handleWindowEscape = (e) => {
4644
- if (Utils.keys.ESC.includes(e.key) && this.doneAnimating && this.overlayActive) this.close();
5256
+ if (e.key === Utils.keys.ESC && this.doneAnimating && this.overlayActive) this.close();
4645
5257
  };
4646
5258
  /**
4647
5259
  * Open lightbox.
@@ -4730,7 +5342,7 @@ var Expressive = (() => {
4730
5342
  this._setupEventHandlers();
4731
5343
  }
4732
5344
  static get defaults() {
4733
- return _defaults12;
5345
+ return _defaults15;
4734
5346
  }
4735
5347
  /**
4736
5348
  * Initializes instances of Lightbox.
@@ -4760,13 +5372,13 @@ var Expressive = (() => {
4760
5372
  _makeAncestorsOverflowVisible() {
4761
5373
  this._changedAncestorList = [];
4762
5374
  let ancestor = this.placeholder.parentNode;
4763
- while (ancestor !== null && ancestor !== document) {
5375
+ while (ancestor !== null && ancestor !== void 0 && ancestor !== document) {
4764
5376
  const curr = ancestor;
4765
- if (curr.style.overflow !== "visible") {
5377
+ if (curr.style && curr.style.overflow !== "visible") {
5378
+ this._changedAncestorList.push([curr, curr.style.overflow]);
4766
5379
  curr.style.overflow = "visible";
4767
- this._changedAncestorList.push(curr);
4768
5380
  }
4769
- ancestor = ancestor.parentNode;
5381
+ ancestor = ancestor.parentNode ?? ancestor.host;
4770
5382
  }
4771
5383
  }
4772
5384
  _offset(el) {
@@ -4830,7 +5442,7 @@ var Expressive = (() => {
4830
5442
  if (this.originInlineStyles) this.el.setAttribute("style", this.originInlineStyles);
4831
5443
  this.el.classList.remove("active");
4832
5444
  this.doneAnimating = true;
4833
- this._changedAncestorList.forEach((anchestor) => anchestor.style.overflow = "");
5445
+ this._changedAncestorList.forEach(([ancestor, overflow]) => ancestor.style.overflow = overflow);
4834
5446
  if (typeof this.options.onCloseEnd === "function")
4835
5447
  this.options.onCloseEnd.call(this, this.el);
4836
5448
  }, duration);
@@ -4840,7 +5452,7 @@ var Expressive = (() => {
4840
5452
  this._photoCaption = document.createElement("div");
4841
5453
  this._photoCaption.classList.add("lightbox-caption");
4842
5454
  this._photoCaption.innerText = this.caption;
4843
- document.body.append(this._photoCaption);
5455
+ Utils.portalRoot(this.el).append(this._photoCaption);
4844
5456
  this._photoCaption.style.display = "inline";
4845
5457
  this._photoCaption.style.transition = "none";
4846
5458
  this._photoCaption.style.opacity = "0";
@@ -4894,37 +5506,9 @@ var Expressive = (() => {
4894
5506
  }
4895
5507
  };
4896
5508
 
4897
- // src/ts/components/parallax.ts
4898
- var _defaults13 = {};
4899
- var Parallax = class _Parallax extends Component {
4900
- constructor(el, options) {
4901
- super(el, options, _Parallax);
4902
- this.el["Expressive_Parallax"] = this;
4903
- this.options = {
4904
- ..._Parallax.defaults,
4905
- ...options
4906
- };
4907
- if (!this.el.querySelector("img, video")) {
4908
- console.error("Parallax: no <img> or <video> to move inside the .parallax element");
4909
- }
4910
- }
4911
- static get defaults() {
4912
- return _defaults13;
4913
- }
4914
- static init(els, options = {}) {
4915
- return super.init(els, options, _Parallax);
4916
- }
4917
- static getInstance(el) {
4918
- return el["Expressive_Parallax"];
4919
- }
4920
- destroy() {
4921
- this.el["Expressive_Parallax"] = void 0;
4922
- }
4923
- };
4924
-
4925
5509
  // src/ts/components/scrollspy.ts
4926
5510
  var _defaultGetActiveElement = (id) => `a[href="#${id}"]`;
4927
- var _defaults14 = {
5511
+ var _defaults16 = {
4928
5512
  scrollOffset: 200,
4929
5513
  activeClass: "active",
4930
5514
  getActiveElement: _defaultGetActiveElement,
@@ -4947,7 +5531,7 @@ var Expressive = (() => {
4947
5531
  _ScrollSpy._observer?.observe(this.el);
4948
5532
  }
4949
5533
  static get defaults() {
4950
- return _defaults14;
5534
+ return _defaults16;
4951
5535
  }
4952
5536
  static init(els, options = {}) {
4953
5537
  return super.init(els, options, _ScrollSpy);
@@ -5068,8 +5652,8 @@ var Expressive = (() => {
5068
5652
  };
5069
5653
  var ScrollSpy = _ScrollSpy;
5070
5654
 
5071
- // src/ts/components/sidenav.ts
5072
- var _defaults15 = {
5655
+ // src/ts/components/navigationDrawer.ts
5656
+ var _defaults17 = {
5073
5657
  edge: "left",
5074
5658
  draggable: true,
5075
5659
  dragTargetWidth: "10px",
@@ -5078,17 +5662,17 @@ var Expressive = (() => {
5078
5662
  onCloseStart: null,
5079
5663
  onCloseEnd: null
5080
5664
  };
5081
- var LARGE_UP = "(width >= 993px)";
5082
- var _Sidenav = class _Sidenav extends Component {
5665
+ var EXPANDED_UP = "(width >= 840px)";
5666
+ var _NavigationDrawer = class _NavigationDrawer extends Component {
5083
5667
  constructor(el, options) {
5084
- super(el, options, _Sidenav);
5668
+ super(el, options, _NavigationDrawer);
5085
5669
  /**
5086
5670
  * Opens the overlay drawer. No-op while the sidenav is docked
5087
- * (sidenav-fixed at the large breakpoint).
5671
+ * (navigation-drawer-fixed at the Expanded breakpoint).
5088
5672
  */
5089
5673
  this.open = () => {
5090
5674
  if (this._isCurrentlyFixed() || this.isOpen || !this._dialog) return;
5091
- _Sidenav._sidenavs.forEach((other) => {
5675
+ _NavigationDrawer._sidenavs.forEach((other) => {
5092
5676
  if (other !== this && other.isOpen) other.close();
5093
5677
  });
5094
5678
  if (typeof this.options.onOpenStart === "function") {
@@ -5140,7 +5724,7 @@ var Expressive = (() => {
5140
5724
  this._handleCloseTriggerClick = (e) => {
5141
5725
  const target = e.target;
5142
5726
  if (!(target instanceof Element)) return;
5143
- const closeTrigger = target.closest(".sidenav-close");
5727
+ const closeTrigger = target.closest(".sidenav-close, .navigation-drawer-close");
5144
5728
  if (closeTrigger && !this._isCurrentlyFixed()) {
5145
5729
  this.close();
5146
5730
  }
@@ -5184,14 +5768,14 @@ var Expressive = (() => {
5184
5768
  this.isDragged = false;
5185
5769
  this._verticallyScrolling = false;
5186
5770
  };
5187
- this.el["Expressive_Sidenav"] = this;
5771
+ this.el["Expressive_NavigationDrawer"] = this;
5188
5772
  this.options = {
5189
- ..._Sidenav.defaults,
5773
+ ..._NavigationDrawer.defaults,
5190
5774
  ...options
5191
5775
  };
5192
5776
  this.id = this.el.id;
5193
5777
  this.isOpen = false;
5194
- this.isFixed = this.el.classList.contains("sidenav-fixed");
5778
+ this.isFixed = this.el.classList.contains("navigation-drawer-fixed") || this.el.classList.contains("sidenav-fixed");
5195
5779
  this.isDragged = false;
5196
5780
  this.dragTarget = null;
5197
5781
  this._dialog = null;
@@ -5203,27 +5787,27 @@ var Expressive = (() => {
5203
5787
  this._width = 0;
5204
5788
  this._initialScrollTop = 0;
5205
5789
  this._verticallyScrolling = false;
5206
- this._mql = window.matchMedia(LARGE_UP);
5790
+ this._mql = window.matchMedia(EXPANDED_UP);
5207
5791
  this._setupDialog();
5208
5792
  this._createDragTarget();
5209
5793
  this._setupClasses();
5210
5794
  this._setupEventHandlers();
5211
5795
  this._syncTriggers();
5212
- _Sidenav._sidenavs.push(this);
5796
+ _NavigationDrawer._sidenavs.push(this);
5213
5797
  }
5214
5798
  static get defaults() {
5215
- return _defaults15;
5799
+ return _defaults17;
5216
5800
  }
5217
5801
  /**
5218
- * Initializes instances of Sidenav.
5802
+ * Initializes instances of NavigationDrawer.
5219
5803
  * @param els HTML elements.
5220
5804
  * @param options Component options.
5221
5805
  */
5222
5806
  static init(els, options = {}) {
5223
- return super.init(els, options, _Sidenav);
5807
+ return super.init(els, options, _NavigationDrawer);
5224
5808
  }
5225
5809
  static getInstance(el) {
5226
- return el["Expressive_Sidenav"];
5810
+ return el["Expressive_NavigationDrawer"];
5227
5811
  }
5228
5812
  destroy() {
5229
5813
  this._removeEventHandlers();
@@ -5236,10 +5820,10 @@ var Expressive = (() => {
5236
5820
  this._clearShift();
5237
5821
  this._unwrapDialog();
5238
5822
  this._removeDragTarget();
5239
- this.el["Expressive_Sidenav"] = void 0;
5240
- const index = _Sidenav._sidenavs.indexOf(this);
5823
+ this.el["Expressive_NavigationDrawer"] = void 0;
5824
+ const index = _NavigationDrawer._sidenavs.indexOf(this);
5241
5825
  if (index >= 0) {
5242
- _Sidenav._sidenavs.splice(index, 1);
5826
+ _NavigationDrawer._sidenavs.splice(index, 1);
5243
5827
  }
5244
5828
  }
5245
5829
  _beginClose() {
@@ -5270,6 +5854,11 @@ var Expressive = (() => {
5270
5854
  }
5271
5855
  const dialog = document.createElement("dialog");
5272
5856
  dialog.classList.add("sidenav-overlay");
5857
+ const named = this.el.closest("[aria-label], [aria-labelledby]") ?? this.el;
5858
+ const label = named.getAttribute("aria-label");
5859
+ const labelledBy = named.getAttribute("aria-labelledby");
5860
+ if (label) dialog.setAttribute("aria-label", label);
5861
+ else if (labelledBy) dialog.setAttribute("aria-labelledby", labelledBy);
5273
5862
  this.el.replaceWith(dialog);
5274
5863
  dialog.appendChild(this.el);
5275
5864
  this._dialog = dialog;
@@ -5287,7 +5876,7 @@ var Expressive = (() => {
5287
5876
  dragTarget.classList.add("drag-target");
5288
5877
  dragTarget.style.width = this.options.dragTargetWidth;
5289
5878
  if (this.isFixed) dragTarget.dataset.fixed = "";
5290
- document.body.appendChild(dragTarget);
5879
+ Utils.portalRoot(this.el).appendChild(dragTarget);
5291
5880
  this.dragTarget = dragTarget;
5292
5881
  }
5293
5882
  _removeDragTarget() {
@@ -5302,8 +5891,8 @@ var Expressive = (() => {
5302
5891
  }
5303
5892
  }
5304
5893
  _setupEventHandlers() {
5305
- if (_Sidenav._sidenavs.length === 0) {
5306
- document.body.addEventListener("click", _Sidenav._handleTriggerClick);
5894
+ if (_NavigationDrawer._sidenavs.length === 0) {
5895
+ document.body.addEventListener("click", _NavigationDrawer._handleTriggerClick);
5307
5896
  }
5308
5897
  const passive = { passive: true };
5309
5898
  this.dragTarget?.addEventListener("touchmove", this._handleDragTargetDrag, passive);
@@ -5315,8 +5904,8 @@ var Expressive = (() => {
5315
5904
  this._mql.addEventListener("change", this._onBreakpointChange);
5316
5905
  }
5317
5906
  _removeEventHandlers() {
5318
- if (_Sidenav._sidenavs.length === 1) {
5319
- document.body.removeEventListener("click", _Sidenav._handleTriggerClick);
5907
+ if (_NavigationDrawer._sidenavs.length === 1) {
5908
+ document.body.removeEventListener("click", _NavigationDrawer._handleTriggerClick);
5320
5909
  }
5321
5910
  this.dragTarget?.removeEventListener("touchmove", this._handleDragTargetDrag);
5322
5911
  this.dragTarget?.removeEventListener("touchend", this._handleDragTargetRelease);
@@ -5329,10 +5918,10 @@ var Expressive = (() => {
5329
5918
  static _handleTriggerClick(e) {
5330
5919
  const target = e.target;
5331
5920
  if (!(target instanceof Element)) return;
5332
- const trigger = target.closest(".sidenav-trigger");
5921
+ const trigger = target.closest(".sidenav-trigger, .navigation-drawer-trigger");
5333
5922
  if (!trigger || !(trigger instanceof HTMLElement)) return;
5334
5923
  const sidenavId = Utils.getIdFromTrigger(trigger);
5335
- const sidenavInstance = document.getElementById(sidenavId)?.["Expressive_Sidenav"];
5924
+ const sidenavInstance = Utils.getElementById(trigger, sidenavId)?.["Expressive_NavigationDrawer"];
5336
5925
  if (sidenavInstance) {
5337
5926
  sidenavInstance.open();
5338
5927
  e.preventDefault();
@@ -5386,7 +5975,7 @@ var Expressive = (() => {
5386
5975
  }
5387
5976
  _syncTriggers() {
5388
5977
  if (!this.id) return;
5389
- document.querySelectorAll(".sidenav-trigger").forEach((trigger) => {
5978
+ document.querySelectorAll(".sidenav-trigger, .navigation-drawer-trigger").forEach((trigger) => {
5390
5979
  if (!(trigger instanceof HTMLElement)) return;
5391
5980
  if (Utils.getIdFromTrigger(trigger) !== this.id) return;
5392
5981
  trigger.setAttribute("aria-expanded", this.isOpen ? "true" : "false");
@@ -5394,17 +5983,17 @@ var Expressive = (() => {
5394
5983
  });
5395
5984
  }
5396
5985
  };
5397
- _Sidenav._sidenavs = [];
5398
- var Sidenav = _Sidenav;
5986
+ _NavigationDrawer._sidenavs = [];
5987
+ var NavigationDrawer = _NavigationDrawer;
5399
5988
 
5400
5989
  // src/ts/components/navigationRail.ts
5401
- var _defaults16 = {
5990
+ var _defaults18 = {
5402
5991
  onOpenStart: null,
5403
5992
  onOpenEnd: null,
5404
5993
  onCloseStart: null,
5405
5994
  onCloseEnd: null
5406
5995
  };
5407
- var COMPACT = "(width < 601px)";
5996
+ var COMPACT = "(width < 600px)";
5408
5997
  var NavigationRail = class _NavigationRail extends Component {
5409
5998
  constructor(el, options) {
5410
5999
  super(el, options, _NavigationRail);
@@ -5474,7 +6063,7 @@ var Expressive = (() => {
5474
6063
  document.addEventListener("keydown", this._onKeyDown);
5475
6064
  }
5476
6065
  static get defaults() {
5477
- return _defaults16;
6066
+ return _defaults18;
5478
6067
  }
5479
6068
  static init(els, options = {}) {
5480
6069
  return super.init(els, options, _NavigationRail);
@@ -5503,309 +6092,8 @@ var Expressive = (() => {
5503
6092
  }
5504
6093
  };
5505
6094
 
5506
- // src/ts/components/slider.ts
5507
- var _defaults17 = {
5508
- indicators: true,
5509
- height: 400,
5510
- duration: 500,
5511
- interval: 6e3,
5512
- pauseOnFocus: true,
5513
- pauseOnHover: true,
5514
- indicatorLabelFunc: null
5515
- // Function which will generate a label for the indicators (ARIA)
5516
- };
5517
- var Slider = class _Slider extends Component {
5518
- constructor(el, options) {
5519
- super(el, options, _Slider);
5520
- // Nobody is watching a backgrounded tab. Browsers clamp the timer rather
5521
- // than stopping it, so without this the slider keeps cycling slides - and
5522
- // running their transitions - out of sight.
5523
- this._handleVisibilityChange = () => {
5524
- if (document.hidden) {
5525
- if (this.interval != null) this._pause(true);
5526
- } else if (this.eventPause && !(this.options.pauseOnHover && this._hovered) && !(this.options.pauseOnFocus && this._focused)) {
5527
- this.start();
5528
- }
5529
- };
5530
- this._handleIndicatorClick = (e) => {
5531
- const el = e.target.parentElement;
5532
- const currIndex = [...el.parentNode.children].indexOf(el);
5533
- this._focusCurrent = true;
5534
- this.set(currIndex);
5535
- };
5536
- this._handleAutoPauseHover = () => {
5537
- this._hovered = true;
5538
- if (this.interval != null) {
5539
- this._pause(true);
5540
- }
5541
- };
5542
- this._handleAutoPauseFocus = () => {
5543
- this._focused = true;
5544
- if (this.interval != null) {
5545
- this._pause(true);
5546
- }
5547
- };
5548
- this._handleAutoStartHover = () => {
5549
- this._hovered = false;
5550
- if (!(this.options.pauseOnFocus && this._focused) && this.eventPause) {
5551
- this.start();
5552
- }
5553
- };
5554
- this._handleAutoStartFocus = () => {
5555
- this._focused = false;
5556
- if (!(this.options.pauseOnHover && this._hovered) && this.eventPause) {
5557
- this.start();
5558
- }
5559
- };
5560
- this._handleInterval = () => {
5561
- const activeElem = this._slider.querySelector(".active");
5562
- let newActiveIndex = [...activeElem.parentNode.children].indexOf(activeElem);
5563
- if (this._slides.length === newActiveIndex + 1)
5564
- newActiveIndex = 0;
5565
- else newActiveIndex += 1;
5566
- this.set(newActiveIndex);
5567
- };
5568
- /**
5569
- * Pause slider autoslide.
5570
- */
5571
- this.pause = () => {
5572
- this._pause(false);
5573
- };
5574
- /**
5575
- * Start slider autoslide.
5576
- */
5577
- this.start = () => {
5578
- clearInterval(this.interval);
5579
- this.interval = setInterval(
5580
- this._handleInterval,
5581
- this.options.duration + this.options.interval
5582
- );
5583
- this.eventPause = false;
5584
- };
5585
- /**
5586
- * Move to next slider.
5587
- */
5588
- this.next = () => {
5589
- let newIndex = this.activeIndex + 1;
5590
- if (newIndex >= this._slides.length) newIndex = 0;
5591
- else if (newIndex < 0) newIndex = this._slides.length - 1;
5592
- this.set(newIndex);
5593
- };
5594
- /**
5595
- * Move to prev slider.
5596
- */
5597
- this.prev = () => {
5598
- let newIndex = this.activeIndex - 1;
5599
- if (newIndex >= this._slides.length) newIndex = 0;
5600
- else if (newIndex < 0) newIndex = this._slides.length - 1;
5601
- this.set(newIndex);
5602
- };
5603
- this.el["Expressive_Slider"] = this;
5604
- this.options = {
5605
- ..._Slider.defaults,
5606
- ...options
5607
- };
5608
- this.interval = null;
5609
- this.eventPause = false;
5610
- this._hovered = false;
5611
- this._focused = false;
5612
- this._focusCurrent = false;
5613
- this._slider = this.el.querySelector(".slides");
5614
- this._slides = Array.from(this._slider.querySelectorAll("li"));
5615
- this.activeIndex = this._slides.findIndex((li) => li.classList.contains("active"));
5616
- if (this.activeIndex !== -1) {
5617
- this._activeSlide = this._slides[this.activeIndex];
5618
- }
5619
- this._setSliderHeight();
5620
- if (this._slider.hasAttribute("id")) this._sliderId = this._slider.getAttribute("id");
5621
- else {
5622
- this._sliderId = "slider-" + Utils.guid();
5623
- this._slider.setAttribute("id", this._sliderId);
5624
- }
5625
- const placeholderBase64 = "data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
5626
- this._slides.forEach((slide) => {
5627
- const img = slide.querySelector("img");
5628
- if (img) {
5629
- if (img.src !== placeholderBase64) {
5630
- img.style.backgroundImage = "url(" + img.src + ")";
5631
- img.src = placeholderBase64;
5632
- }
5633
- }
5634
- if (!slide.hasAttribute("tabindex")) slide.setAttribute("tabindex", "-1");
5635
- slide.style.visibility = "hidden";
5636
- });
5637
- this._setupIndicators();
5638
- if (this._activeSlide) {
5639
- this._activeSlide.style.display = "block";
5640
- this._activeSlide.style.visibility = "visible";
5641
- } else {
5642
- this.activeIndex = 0;
5643
- this._slides[0].classList.add("active");
5644
- this._slides[0].style.visibility = "visible";
5645
- this._activeSlide = this._slides[0];
5646
- this._animateSlide(this._slides[0], true);
5647
- if (this.options.indicators) {
5648
- this._indicators[this.activeIndex].children[0].classList.add("active");
5649
- }
5650
- }
5651
- this._setupEventHandlers();
5652
- this.start();
5653
- }
5654
- static get defaults() {
5655
- return _defaults17;
5656
- }
5657
- /**
5658
- * Initializes instances of Slider.
5659
- * @param els HTML elements.
5660
- * @param options Component options.
5661
- */
5662
- static init(els, options = {}) {
5663
- return super.init(els, options, _Slider);
5664
- }
5665
- static getInstance(el) {
5666
- return el["Expressive_Slider"];
5667
- }
5668
- destroy() {
5669
- this.pause();
5670
- this._removeIndicators();
5671
- this._removeEventHandlers();
5672
- this.el["Expressive_Slider"] = void 0;
5673
- }
5674
- _setupEventHandlers() {
5675
- if (this.options.pauseOnFocus) {
5676
- this.el.addEventListener("focusin", this._handleAutoPauseFocus);
5677
- this.el.addEventListener("focusout", this._handleAutoStartFocus);
5678
- }
5679
- if (this.options.pauseOnHover) {
5680
- this.el.addEventListener("mouseenter", this._handleAutoPauseHover);
5681
- this.el.addEventListener("mouseleave", this._handleAutoStartHover);
5682
- }
5683
- if (this.options.indicators) {
5684
- this._indicators.forEach((el) => {
5685
- el.addEventListener("click", this._handleIndicatorClick);
5686
- });
5687
- }
5688
- document.addEventListener("visibilitychange", this._handleVisibilityChange);
5689
- }
5690
- _removeEventHandlers() {
5691
- if (this.options.pauseOnFocus) {
5692
- this.el.removeEventListener("focusin", this._handleAutoPauseFocus);
5693
- this.el.removeEventListener("focusout", this._handleAutoStartFocus);
5694
- }
5695
- if (this.options.pauseOnHover) {
5696
- this.el.removeEventListener("mouseenter", this._handleAutoPauseHover);
5697
- this.el.removeEventListener("mouseleave", this._handleAutoStartHover);
5698
- }
5699
- if (this.options.indicators) {
5700
- this._indicators.forEach((el) => {
5701
- el.removeEventListener("click", this._handleIndicatorClick);
5702
- });
5703
- }
5704
- document.removeEventListener("visibilitychange", this._handleVisibilityChange);
5705
- }
5706
- _animateSlide(slide, isDirectionIn) {
5707
- let dx = 0, dy = 0;
5708
- slide.style.opacity = isDirectionIn ? "0" : "1";
5709
- setTimeout(() => {
5710
- slide.style.transition = `opacity ${this.options.duration}ms ease`;
5711
- slide.style.opacity = isDirectionIn ? "1" : "0";
5712
- }, 1);
5713
- const caption = slide.querySelector(".caption");
5714
- if (!caption) return;
5715
- if (caption.classList.contains("center-align")) dy = -100;
5716
- else if (caption.classList.contains("right-align")) dx = 100;
5717
- else if (caption.classList.contains("left-align")) dx = -100;
5718
- caption.style.opacity = isDirectionIn ? "0" : "1";
5719
- caption.style.transform = isDirectionIn ? `translate(${dx}px, ${dy}px)` : `translate(0, 0)`;
5720
- setTimeout(() => {
5721
- caption.style.transition = `opacity ${this.options.duration}ms ease, transform ${this.options.duration}ms ease`;
5722
- caption.style.opacity = isDirectionIn ? "1" : "0";
5723
- caption.style.transform = isDirectionIn ? `translate(0, 0)` : `translate(${dx}px, ${dy}px)`;
5724
- }, this.options.duration);
5725
- }
5726
- _setSliderHeight() {
5727
- if (!this.el.classList.contains("fullscreen")) {
5728
- if (this.options.indicators) {
5729
- this.el.style.height = this.options.height + 40 + "px";
5730
- } else {
5731
- this.el.style.height = this.options.height + "px";
5732
- }
5733
- this._slider.style.height = this.options.height + "px";
5734
- }
5735
- }
5736
- _setupIndicators() {
5737
- if (this.options.indicators) {
5738
- const ul = document.createElement("ul");
5739
- ul.classList.add("indicators");
5740
- const arrLi = [];
5741
- this._slides.forEach((el, i) => {
5742
- const label = this.options.indicatorLabelFunc ? this.options.indicatorLabelFunc.call(this, i + 1, i === 0) : `${i + 1}`;
5743
- const li = document.createElement("li");
5744
- li.classList.add("indicator-item");
5745
- const button = document.createElement("button");
5746
- button.type = "button";
5747
- button.classList.add("indicator-item-btn");
5748
- button.setAttribute("aria-label", label);
5749
- button.setAttribute("aria-controls", this._sliderId);
5750
- li.appendChild(button);
5751
- arrLi.push(li);
5752
- ul.append(li);
5753
- });
5754
- this.el.append(ul);
5755
- this._indicators = arrLi;
5756
- }
5757
- }
5758
- _removeIndicators() {
5759
- this.el.querySelector("ul.indicators").remove();
5760
- }
5761
- set(index) {
5762
- if (index >= this._slides.length) index = 0;
5763
- else if (index < 0) index = this._slides.length - 1;
5764
- if (this.activeIndex === index) return;
5765
- this._activeSlide = this._slides[this.activeIndex];
5766
- const _caption = this._activeSlide.querySelector(".caption");
5767
- this._activeSlide.classList.remove("active");
5768
- this._slides.forEach((slide) => slide.style.visibility = "visible");
5769
- this._activeSlide.style.opacity = "0";
5770
- setTimeout(() => {
5771
- this._slides.forEach((slide) => {
5772
- if (slide.classList.contains("active")) return;
5773
- slide.style.opacity = "0";
5774
- slide.style.transform = "translate(0, 0)";
5775
- slide.style.visibility = "hidden";
5776
- });
5777
- }, this.options.duration);
5778
- if (_caption) _caption.style.opacity = "0";
5779
- if (this.options.indicators) {
5780
- const activeIndicator = this._indicators[this.activeIndex].children[0];
5781
- const nextIndicator = this._indicators[index].children[0];
5782
- activeIndicator.classList.remove("active");
5783
- nextIndicator.classList.add("active");
5784
- if (typeof this.options.indicatorLabelFunc === "function") {
5785
- activeIndicator.ariaLabel = this.options.indicatorLabelFunc.call(
5786
- this,
5787
- this.activeIndex,
5788
- false
5789
- );
5790
- nextIndicator.ariaLabel = this.options.indicatorLabelFunc.call(this, index, true);
5791
- }
5792
- }
5793
- this._animateSlide(this._slides[index], true);
5794
- this._slides[index].classList.add("active");
5795
- this.activeIndex = index;
5796
- if (this.interval != null) {
5797
- this.start();
5798
- }
5799
- }
5800
- _pause(fromEvent) {
5801
- clearInterval(this.interval);
5802
- this.eventPause = fromEvent;
5803
- this.interval = null;
5804
- }
5805
- };
5806
-
5807
6095
  // src/ts/components/tabs.ts
5808
- var _defaults18 = {
6096
+ var _defaults19 = {
5809
6097
  duration: 300,
5810
6098
  onShow: null,
5811
6099
  swipeable: false,
@@ -5834,12 +6122,10 @@ var Expressive = (() => {
5834
6122
  return;
5835
6123
  }
5836
6124
  if (tabLink.hasAttribute("target")) return;
5837
- this._activeTabLink.classList.remove("active");
5838
6125
  const _oldContent = this._content;
5839
- this._activeTabLink = tabLink;
5840
6126
  if (tabLink.hash) this._content = document.querySelector(tabLink.hash);
5841
6127
  this._tabLinks = this._queryTabLinks();
5842
- this._activeTabLink.classList.add("active");
6128
+ this._setActiveTabLink(tabLink);
5843
6129
  const prevIndex = this._index;
5844
6130
  this._index = Math.max(Array.from(this._tabLinks).indexOf(tabLink), 0);
5845
6131
  if (this.options.swipeable) {
@@ -5883,7 +6169,7 @@ var Expressive = (() => {
5883
6169
  this._setupEventHandlers();
5884
6170
  }
5885
6171
  static get defaults() {
5886
- return _defaults18;
6172
+ return _defaults19;
5887
6173
  }
5888
6174
  /**
5889
6175
  * Initializes instances of Tabs.
@@ -5934,6 +6220,26 @@ var Expressive = (() => {
5934
6220
  this._indicator.style.left = this._calcLeftPos(this._activeTabLink) + "px";
5935
6221
  this._indicator.style.right = this._calcRightPos(this._activeTabLink) + "px";
5936
6222
  }
6223
+ /**
6224
+ * Which tab is current, for the eye and for assistive technology.
6225
+ *
6226
+ * The class alone is a colour. `aria-current` is the part a screen reader
6227
+ * reads, and because tabs are navigation rather than a tablist (rule 2 -
6228
+ * there is no keyboard model here to back a tablist), "current" is exactly
6229
+ * the right word for it. Its *value* changes as the user clicks, which makes
6230
+ * maintaining it the component's job, not the author's - see CONTEXT.md on
6231
+ * static semantics versus dynamic state. Writing it once in the markup and
6232
+ * never moving it left the old tab announcing itself as current.
6233
+ */
6234
+ _setActiveTabLink(tabLink) {
6235
+ Array.from(this._tabLinks).forEach((a) => {
6236
+ a.classList.remove("active");
6237
+ a.removeAttribute("aria-current");
6238
+ });
6239
+ this._activeTabLink = tabLink;
6240
+ tabLink.classList.add("active");
6241
+ tabLink.setAttribute("aria-current", "page");
6242
+ }
5937
6243
  _setupActiveTabLink() {
5938
6244
  this._activeTabLink = Array.from(this._tabLinks).find(
5939
6245
  (a) => a.getAttribute("href") === location.hash
@@ -5945,8 +6251,7 @@ var Expressive = (() => {
5945
6251
  }
5946
6252
  this._activeTabLink = activeTabLink;
5947
6253
  }
5948
- Array.from(this._tabLinks).forEach((a) => a.classList.remove("active"));
5949
- this._activeTabLink.classList.add("active");
6254
+ this._setActiveTabLink(this._activeTabLink);
5950
6255
  this._index = Math.max(Array.from(this._tabLinks).indexOf(this._activeTabLink), 0);
5951
6256
  if (this._activeTabLink && this._activeTabLink.hash) {
5952
6257
  this._content = document.querySelector(this._activeTabLink.hash);
@@ -6051,7 +6356,7 @@ var Expressive = (() => {
6051
6356
  };
6052
6357
 
6053
6358
  // src/ts/components/timepicker.ts
6054
- var _defaults19 = {
6359
+ var _defaults20 = {
6055
6360
  dialRadius: 135,
6056
6361
  outerRadius: 105,
6057
6362
  innerRadius: 70,
@@ -6085,6 +6390,8 @@ var Expressive = (() => {
6085
6390
  var _Timepicker = class _Timepicker extends Component {
6086
6391
  constructor(el, options) {
6087
6392
  super(el, options, _Timepicker);
6393
+ /** Which pointer is dragging the clock hand; a second finger is ignored. */
6394
+ this._dragPointerId = null;
6088
6395
  /** Whether the dial (ticks + SVG + AM/PM buttons) has been built yet. */
6089
6396
  this._clockBuilt = false;
6090
6397
  this._handleInputClick = () => {
@@ -6094,7 +6401,7 @@ var Expressive = (() => {
6094
6401
  if (this.displayPlugin) this.displayPlugin.show();
6095
6402
  };
6096
6403
  this._handleInputKeydown = (e) => {
6097
- if (Utils.keys.ENTER.includes(e.key)) {
6404
+ if (e.key === Utils.keys.ENTER) {
6098
6405
  e.preventDefault();
6099
6406
  this._ensureClockBuilt();
6100
6407
  this.inputHours.focus();
@@ -6102,13 +6409,9 @@ var Expressive = (() => {
6102
6409
  if (this.displayPlugin) this.displayPlugin.show();
6103
6410
  }
6104
6411
  };
6105
- this._handleTimeInputEnterKey = (e) => {
6106
- if (Utils.keys.ENTER.includes(e.key)) {
6107
- e.preventDefault();
6108
- this._inputFromTextField();
6109
- }
6110
- };
6111
6412
  this._handleClockClickStart = (e) => {
6413
+ if (!e.isPrimary) return;
6414
+ if (e.pointerType === "mouse" && e.button !== 0) return;
6112
6415
  e.preventDefault();
6113
6416
  this._ensureClockBuilt();
6114
6417
  const clockPlateBR = this.plate.getBoundingClientRect();
@@ -6116,30 +6419,30 @@ var Expressive = (() => {
6116
6419
  this.x0 = offset.x + this.options.dialRadius;
6117
6420
  this.y0 = offset.y + this.options.dialRadius;
6118
6421
  this.moved = false;
6119
- const clickPos = _Timepicker._Pos(e);
6120
- this.dx = clickPos.x - this.x0;
6121
- this.dy = clickPos.y - this.y0;
6422
+ this._dragPointerId = e.pointerId;
6423
+ this.dx = e.clientX - this.x0;
6424
+ this.dy = e.clientY - this.y0;
6122
6425
  this.setHand(this.dx, this.dy, false);
6123
- document.addEventListener("mousemove", this._handleDocumentClickMove);
6124
- document.addEventListener("touchmove", this._handleDocumentClickMove);
6125
- document.addEventListener("mouseup", this._handleDocumentClickEnd);
6126
- document.addEventListener("touchend", this._handleDocumentClickEnd);
6426
+ document.addEventListener("pointermove", this._handleDocumentClickMove);
6427
+ document.addEventListener("pointerup", this._handleDocumentClickEnd);
6428
+ document.addEventListener("pointercancel", this._handleDocumentClickCancel);
6127
6429
  };
6128
6430
  this._handleDocumentClickMove = (e) => {
6431
+ if (e.pointerId !== this._dragPointerId) return;
6129
6432
  e.preventDefault();
6130
- const clickPos = _Timepicker._Pos(e);
6131
- const x = clickPos.x - this.x0;
6132
- const y = clickPos.y - this.y0;
6433
+ const x = e.clientX - this.x0;
6434
+ const y = e.clientY - this.y0;
6133
6435
  this.moved = true;
6134
6436
  this.setHand(x, y, false);
6135
6437
  };
6136
6438
  this._handleDocumentClickEnd = (e) => {
6439
+ if (e.pointerId !== this._dragPointerId) return;
6440
+ this._dragPointerId = null;
6137
6441
  e.preventDefault();
6138
- document.removeEventListener("mouseup", this._handleDocumentClickEnd);
6139
- document.removeEventListener("touchend", this._handleDocumentClickEnd);
6140
- const clickPos = _Timepicker._Pos(e);
6141
- const x = clickPos.x - this.x0;
6142
- const y = clickPos.y - this.y0;
6442
+ document.removeEventListener("pointerup", this._handleDocumentClickEnd);
6443
+ document.removeEventListener("pointercancel", this._handleDocumentClickCancel);
6444
+ const x = e.clientX - this.x0;
6445
+ const y = e.clientY - this.y0;
6143
6446
  if (this.moved && x === this.dx && y === this.dy) {
6144
6447
  this.setHand(x, y);
6145
6448
  }
@@ -6154,14 +6457,27 @@ var Expressive = (() => {
6154
6457
  if (typeof this.options.onSelect === "function") {
6155
6458
  this.options.onSelect.call(this, this.hours, this.minutes);
6156
6459
  }
6157
- document.removeEventListener("mousemove", this._handleDocumentClickMove);
6158
- document.removeEventListener("touchmove", this._handleDocumentClickMove);
6460
+ document.removeEventListener("pointermove", this._handleDocumentClickMove);
6461
+ };
6462
+ /**
6463
+ * A cancelled pointer is not a completed selection. The browser or the OS
6464
+ * took the gesture over, so this only lets go of the drag - it must not run
6465
+ * the pointer-up path, which advances hours to minutes, fires `onSelect`,
6466
+ * and under `autoSubmit` closes the picker on a time the user never chose.
6467
+ */
6468
+ this._handleDocumentClickCancel = (e) => {
6469
+ if (e.pointerId !== this._dragPointerId) return;
6470
+ this._dragPointerId = null;
6471
+ this.moved = false;
6472
+ document.removeEventListener("pointermove", this._handleDocumentClickMove);
6473
+ document.removeEventListener("pointerup", this._handleDocumentClickEnd);
6474
+ document.removeEventListener("pointercancel", this._handleDocumentClickCancel);
6159
6475
  };
6160
6476
  this._handleAmPmClick = (e) => {
6161
6477
  this._handleAmPmInteraction(e.target);
6162
6478
  };
6163
6479
  this._handleAmPmKeypress = (e) => {
6164
- if (Utils.keys.ENTER.includes(e.key)) {
6480
+ if (e.key === Utils.keys.ENTER) {
6165
6481
  this._handleAmPmInteraction(e.target);
6166
6482
  }
6167
6483
  };
@@ -6176,8 +6492,6 @@ var Expressive = (() => {
6176
6492
  */
6177
6493
  this.showView = (view, delay = null) => {
6178
6494
  this._ensureClockBuilt();
6179
- if (view === "minutes" && getComputedStyle(this.hoursView).visibility === "visible") {
6180
- }
6181
6495
  const isHours = view === "hours", nextView = isHours ? this.hoursView : this.minutesView, hideView = isHours ? this.minutesView : this.hoursView;
6182
6496
  this.currentView = view;
6183
6497
  hideView.classList.add("timepicker-dial-out");
@@ -6249,7 +6563,7 @@ var Expressive = (() => {
6249
6563
  }
6250
6564
  }
6251
6565
  static get defaults() {
6252
- return _defaults19;
6566
+ return _defaults20;
6253
6567
  }
6254
6568
  /**
6255
6569
  * Initializes instances of Timepicker.
@@ -6266,15 +6580,6 @@ var Expressive = (() => {
6266
6580
  const svgNS = "http://www.w3.org/2000/svg";
6267
6581
  return document.createElementNS(svgNS, name);
6268
6582
  }
6269
- static _Pos(e) {
6270
- if (e.type.startsWith("touch") && e.targetTouches.length >= 1) {
6271
- return {
6272
- x: e.targetTouches[0].clientX,
6273
- y: e.targetTouches[0].clientY
6274
- };
6275
- }
6276
- return { x: e.clientX, y: e.clientY };
6277
- }
6278
6583
  static getInstance(el) {
6279
6584
  return el["Expressive_Timepicker"];
6280
6585
  }
@@ -6287,8 +6592,7 @@ var Expressive = (() => {
6287
6592
  _setupEventHandlers() {
6288
6593
  this.el.addEventListener("click", this._handleInputClick);
6289
6594
  this.el.addEventListener("keydown", this._handleInputKeydown);
6290
- this.plate.addEventListener("mousedown", this._handleClockClickStart);
6291
- this.plate.addEventListener("touchstart", this._handleClockClickStart);
6595
+ this.plate.addEventListener("pointerdown", this._handleClockClickStart);
6292
6596
  this.digitalClock.addEventListener("keyup", this._inputFromTextField);
6293
6597
  this.inputHours.addEventListener("focus", () => this.showView("hours"));
6294
6598
  this.inputHours.addEventListener("focusout", () => this.formatHours());
@@ -6298,10 +6602,9 @@ var Expressive = (() => {
6298
6602
  _removeEventHandlers() {
6299
6603
  this.el.removeEventListener("click", this._handleInputClick);
6300
6604
  this.el.removeEventListener("keydown", this._handleInputKeydown);
6301
- document.removeEventListener("mousemove", this._handleDocumentClickMove);
6302
- document.removeEventListener("touchmove", this._handleDocumentClickMove);
6303
- document.removeEventListener("mouseup", this._handleDocumentClickEnd);
6304
- document.removeEventListener("touchend", this._handleDocumentClickEnd);
6605
+ document.removeEventListener("pointermove", this._handleDocumentClickMove);
6606
+ document.removeEventListener("pointerup", this._handleDocumentClickEnd);
6607
+ document.removeEventListener("pointercancel", this._handleDocumentClickCancel);
6305
6608
  }
6306
6609
  _insertHTMLIntoDOM() {
6307
6610
  const template = document.createElement("template");
@@ -6330,15 +6633,6 @@ var Expressive = (() => {
6330
6633
  this.footer = this.containerEl.querySelector(".timepicker-footer");
6331
6634
  this.amOrPm = "PM";
6332
6635
  }
6333
- /*private _createButton(text: string, visibility: string): HTMLButtonElement {
6334
- const button = document.createElement('button');
6335
- button.classList.add('waves-effect', 'text');
6336
- button.style.visibility = visibility;
6337
- button.type = 'button';
6338
- button.tabIndex = -1;
6339
- button.innerText = text;
6340
- return button;
6341
- }*/
6342
6636
  _pickerSetup() {
6343
6637
  Utils.createButton(
6344
6638
  this.footer,
@@ -6619,7 +6913,7 @@ var Expressive = (() => {
6619
6913
  var Timepicker = _Timepicker;
6620
6914
 
6621
6915
  // src/ts/components/snackbar.ts
6622
- var _defaults20 = {
6916
+ var _defaults21 = {
6623
6917
  text: "",
6624
6918
  action: "",
6625
6919
  onAction: null,
@@ -6629,7 +6923,8 @@ var Expressive = (() => {
6629
6923
  outDuration: 375,
6630
6924
  classes: "",
6631
6925
  completeCallback: null,
6632
- activationPercent: 0.8
6926
+ activationPercent: 0.8,
6927
+ root: null
6633
6928
  };
6634
6929
  var _Snackbar = class _Snackbar {
6635
6930
  constructor(options = {}) {
@@ -6652,8 +6947,10 @@ var Expressive = (() => {
6652
6947
  }
6653
6948
  _Snackbar._snackbars.length = 0;
6654
6949
  }
6655
- if (!_Snackbar._container || !_Snackbar._container.isConnected) {
6656
- _Snackbar._createContainer();
6950
+ const root = Utils.portalRoot(this.options.root ?? document.body);
6951
+ if (!_Snackbar._container || !_Snackbar._container.isConnected || _Snackbar._container.parentNode !== root) {
6952
+ if (_Snackbar._container) _Snackbar._removeContainer();
6953
+ _Snackbar._createContainer(root);
6657
6954
  }
6658
6955
  _Snackbar._snackbars.push(this);
6659
6956
  const snackbarElement = this._createSnackbar();
@@ -6663,30 +6960,31 @@ var Expressive = (() => {
6663
6960
  this._setTimer();
6664
6961
  }
6665
6962
  static get defaults() {
6666
- return _defaults20;
6963
+ return _defaults21;
6667
6964
  }
6668
6965
  static getInstance(el) {
6669
6966
  return el["Expressive_Snackbar"];
6670
6967
  }
6671
- static _createContainer() {
6968
+ static _createContainer(root = document.body) {
6672
6969
  const container = document.createElement("div");
6673
6970
  container.setAttribute("id", "snackbar-container");
6674
- container.addEventListener("touchstart", _Snackbar._onDragStart);
6675
- container.addEventListener("touchmove", _Snackbar._onDragMove);
6676
- container.addEventListener("touchend", _Snackbar._onDragEnd);
6677
- container.addEventListener("mousedown", _Snackbar._onDragStart);
6678
- document.addEventListener("mousemove", _Snackbar._onDragMove);
6679
- document.addEventListener("mouseup", _Snackbar._onDragEnd);
6680
- document.body.appendChild(container);
6971
+ container.addEventListener("pointerdown", _Snackbar._onDragStart);
6972
+ document.addEventListener("pointermove", _Snackbar._onDragMove);
6973
+ document.addEventListener("pointerup", _Snackbar._onDragEnd);
6974
+ document.addEventListener("pointercancel", _Snackbar._onDragCancel);
6975
+ root.appendChild(container);
6681
6976
  _Snackbar._container = container;
6682
6977
  }
6683
6978
  static _removeContainer() {
6684
- document.removeEventListener("mousemove", _Snackbar._onDragMove);
6685
- document.removeEventListener("mouseup", _Snackbar._onDragEnd);
6979
+ document.removeEventListener("pointermove", _Snackbar._onDragMove);
6980
+ document.removeEventListener("pointerup", _Snackbar._onDragEnd);
6981
+ document.removeEventListener("pointercancel", _Snackbar._onDragCancel);
6686
6982
  _Snackbar._container.remove();
6687
6983
  _Snackbar._container = null;
6688
6984
  }
6689
6985
  static _onDragStart(e) {
6986
+ if (!e.isPrimary) return;
6987
+ if (e.pointerType === "mouse" && e.button !== 0) return;
6690
6988
  const target = e.target;
6691
6989
  if (target && !target.closest("button, a") && target.closest(".snackbar")) {
6692
6990
  const snackbarElem = target.closest(".snackbar");
@@ -6697,17 +6995,19 @@ var Expressive = (() => {
6697
6995
  _Snackbar._draggedSnackbar = snackbar;
6698
6996
  snackbar.el.classList.add("panning");
6699
6997
  snackbar.el.style.transition = "";
6700
- snackbar.startingXPos = _Snackbar._xPos(e);
6998
+ _Snackbar._dragPointerId = e.pointerId;
6999
+ snackbar.startingXPos = e.clientX;
6701
7000
  snackbar.time = Date.now();
6702
- snackbar.xPos = _Snackbar._xPos(e);
7001
+ snackbar.xPos = e.clientX;
6703
7002
  }
6704
7003
  }
6705
7004
  static _onDragMove(e) {
7005
+ if (_Snackbar._dragPointerId !== e.pointerId) return;
6706
7006
  if (!!_Snackbar._draggedSnackbar) {
6707
7007
  e.preventDefault();
6708
7008
  const snackbar = _Snackbar._draggedSnackbar;
6709
- snackbar.deltaX = Math.abs(snackbar.xPos - _Snackbar._xPos(e));
6710
- snackbar.xPos = _Snackbar._xPos(e);
7009
+ snackbar.deltaX = Math.abs(snackbar.xPos - e.clientX);
7010
+ snackbar.xPos = e.clientX;
6711
7011
  snackbar.velocityX = snackbar.deltaX / (Date.now() - snackbar.time);
6712
7012
  snackbar.time = Date.now();
6713
7013
  const totalDeltaX = snackbar.xPos - snackbar.startingXPos;
@@ -6716,7 +7016,9 @@ var Expressive = (() => {
6716
7016
  snackbar.el.style.opacity = (1 - Math.abs(totalDeltaX / activationDistance)).toString();
6717
7017
  }
6718
7018
  }
6719
- static _onDragEnd() {
7019
+ static _onDragEnd(e) {
7020
+ if (_Snackbar._dragPointerId !== e.pointerId) return;
7021
+ _Snackbar._dragPointerId = null;
6720
7022
  if (!!_Snackbar._draggedSnackbar) {
6721
7023
  const snackbar = _Snackbar._draggedSnackbar;
6722
7024
  snackbar.panning = false;
@@ -6736,11 +7038,23 @@ var Expressive = (() => {
6736
7038
  _Snackbar._draggedSnackbar = null;
6737
7039
  }
6738
7040
  }
6739
- static _xPos(e) {
6740
- if (e.type.startsWith("touch") && e.targetTouches.length >= 1) {
6741
- return e.targetTouches[0].clientX;
6742
- }
6743
- return e.clientX;
7041
+ /**
7042
+ * A cancelled pointer is not a short swipe. Snap back and resume the timer
7043
+ * without consulting the distance travelled - the browser took the gesture
7044
+ * over, so the movement so far says nothing about what the user wanted.
7045
+ */
7046
+ static _onDragCancel(e) {
7047
+ if (_Snackbar._dragPointerId !== e.pointerId) return;
7048
+ _Snackbar._dragPointerId = null;
7049
+ const snackbar = _Snackbar._draggedSnackbar;
7050
+ if (!snackbar) return;
7051
+ snackbar.panning = false;
7052
+ snackbar.el.classList.remove("panning");
7053
+ snackbar.el.style.transition = "transform .2s, opacity .2s";
7054
+ snackbar.el.style.transform = "";
7055
+ snackbar.el.style.opacity = "";
7056
+ snackbar._resumeTimer();
7057
+ _Snackbar._draggedSnackbar = null;
6744
7058
  }
6745
7059
  /**
6746
7060
  * dismiss all snackbars.
@@ -6751,7 +7065,7 @@ var Expressive = (() => {
6751
7065
  }
6752
7066
  }
6753
7067
  _createSnackbar() {
6754
- let snackbar = this.options.snackbarId ? document.getElementById(this.options.snackbarId) : document.createElement("div");
7068
+ let snackbar = this.options.snackbarId ? Utils.getElementById(this.options.root ?? document.body, this.options.snackbarId) : document.createElement("div");
6755
7069
  if (snackbar instanceof HTMLTemplateElement) {
6756
7070
  const node = snackbar.content.cloneNode(true);
6757
7071
  snackbar = node.firstElementChild;
@@ -6873,13 +7187,15 @@ var Expressive = (() => {
6873
7187
  }, this.options.outDuration);
6874
7188
  }
6875
7189
  };
7190
+ /** Which pointer owns the drag in flight; a second finger is ignored. */
7191
+ _Snackbar._dragPointerId = null;
6876
7192
  _Snackbar._snackbars = [];
6877
7193
  _Snackbar._container = null;
6878
7194
  _Snackbar._draggedSnackbar = null;
6879
7195
  var Snackbar = _Snackbar;
6880
7196
 
6881
7197
  // src/ts/components/tooltip.ts
6882
- var _defaults21 = {
7198
+ var _defaults22 = {
6883
7199
  exitDelay: 200,
6884
7200
  enterDelay: 0,
6885
7201
  text: "",
@@ -6924,8 +7240,10 @@ var Expressive = (() => {
6924
7240
  this.isFocused = false;
6925
7241
  this.close();
6926
7242
  };
7243
+ // Focus opens the tooltip only when it arrived by keyboard: a pointer press
7244
+ // on the trigger also focuses it, and _handleMouseEnter already covers that.
6927
7245
  this._handleFocus = () => {
6928
- if (Utils.tabPressed) {
7246
+ if (this.el.matches(":focus-visible")) {
6929
7247
  this.isFocused = true;
6930
7248
  this.open(false);
6931
7249
  }
@@ -6947,7 +7265,7 @@ var Expressive = (() => {
6947
7265
  this._setupEventHandlers();
6948
7266
  }
6949
7267
  static get defaults() {
6950
- return _defaults21;
7268
+ return _defaults22;
6951
7269
  }
6952
7270
  /**
6953
7271
  * Initializes instances of Tooltip.
@@ -6961,6 +7279,12 @@ var Expressive = (() => {
6961
7279
  return el["Expressive_Tooltip"];
6962
7280
  }
6963
7281
  destroy() {
7282
+ clearTimeout(this._enterDelayTimeout);
7283
+ clearTimeout(this._exitDelayTimeout);
7284
+ clearTimeout(this._animationTimeout);
7285
+ this.isOpen = false;
7286
+ this.isHovered = false;
7287
+ this.isFocused = false;
6964
7288
  if (this.el.getAttribute("aria-describedby") === this.tooltipEl.id) {
6965
7289
  this.el.removeAttribute("aria-describedby");
6966
7290
  }
@@ -6974,7 +7298,7 @@ var Expressive = (() => {
6974
7298
  this.tooltipEl.id = `tooltip-${Utils.guid()}`;
6975
7299
  this.tooltipEl.setAttribute("role", "tooltip");
6976
7300
  this.el.setAttribute("aria-describedby", this.tooltipEl.id);
6977
- const tooltipContentEl = this.options.tooltipId ? document.getElementById(this.options.tooltipId) : document.createElement("div");
7301
+ const tooltipContentEl = this.options.tooltipId ? Utils.getElementById(this.el, this.options.tooltipId) : document.createElement("div");
6978
7302
  if (this.options.tooltipId) {
6979
7303
  this.tooltipEl.classList.add("rich");
6980
7304
  }
@@ -6982,7 +7306,7 @@ var Expressive = (() => {
6982
7306
  tooltipContentEl.classList.add("tooltip-content");
6983
7307
  this._setTooltipContent(tooltipContentEl);
6984
7308
  this.tooltipEl.appendChild(tooltipContentEl);
6985
- document.body.appendChild(this.tooltipEl);
7309
+ Utils.portalRoot(this.el).appendChild(this.tooltipEl);
6986
7310
  }
6987
7311
  _setTooltipContent(tooltipContentEl) {
6988
7312
  if (this.options.tooltipId) return;
@@ -7052,32 +7376,15 @@ var Expressive = (() => {
7052
7376
  tooltip.style.left = newCoordinates.x + "px";
7053
7377
  }
7054
7378
  _repositionWithinScreen(x, y, width, height) {
7055
- const scrollLeft = Utils.getDocumentScrollLeft();
7056
- const scrollTop = Utils.getDocumentScrollTop();
7057
- let newX = x - scrollLeft;
7058
- let newY = y - scrollTop;
7059
- const bounding = {
7060
- left: newX,
7061
- top: newY,
7379
+ return Utils._repositionWithinScreen(
7380
+ x,
7381
+ y,
7062
7382
  width,
7063
- height
7064
- };
7065
- const offset = this.options.margin + this.options.transitionMovement;
7066
- const edges = Utils.checkWithinContainer(document.body, bounding, offset);
7067
- if (edges.left) {
7068
- newX = offset;
7069
- } else if (edges.right) {
7070
- newX -= newX + width - window.innerWidth;
7071
- }
7072
- if (edges.top) {
7073
- newY = offset;
7074
- } else if (edges.bottom) {
7075
- newY -= newY + height - window.innerHeight;
7076
- }
7077
- return {
7078
- x: newX + scrollLeft,
7079
- y: newY + scrollTop
7080
- };
7383
+ height,
7384
+ this.options.margin,
7385
+ this.options.transitionMovement,
7386
+ "center"
7387
+ );
7081
7388
  }
7082
7389
  _animateIn() {
7083
7390
  this._positionTooltip();
@@ -7086,7 +7393,8 @@ var Expressive = (() => {
7086
7393
  this.tooltipEl.style.transition = `
7087
7394
  transform ${duration}ms ease-out,
7088
7395
  opacity ${duration}ms ease-out`;
7089
- setTimeout(() => {
7396
+ clearTimeout(this._animationTimeout);
7397
+ this._animationTimeout = setTimeout(() => {
7090
7398
  this.tooltipEl.style.transform = `translateX(${this.xMovement}px) translateY(${this.yMovement}px)`;
7091
7399
  this.tooltipEl.style.opacity = (this.options.opacity || 1).toString();
7092
7400
  }, 1);
@@ -7096,7 +7404,8 @@ var Expressive = (() => {
7096
7404
  this.tooltipEl.style.transition = `
7097
7405
  transform ${duration}ms ease-out,
7098
7406
  opacity ${duration}ms ease-out`;
7099
- setTimeout(() => {
7407
+ clearTimeout(this._animationTimeout);
7408
+ this._animationTimeout = setTimeout(() => {
7100
7409
  this.tooltipEl.style.transform = `translateX(0px) translateY(0px)`;
7101
7410
  this.tooltipEl.style.opacity = "0";
7102
7411
  }, 1);
@@ -7121,49 +7430,75 @@ var Expressive = (() => {
7121
7430
 
7122
7431
  // src/ts/components/registry.ts
7123
7432
  var AUTO_INIT_COMPONENTS = {
7124
- Autocomplete: { component: Autocomplete, selector: ".autocomplete" },
7433
+ AppBar: {
7434
+ component: AppBar,
7435
+ selector: "header.medium, header.large, header:has(> :is(nav:not(.tabs, .navigation-bar, .navigation-rail), .bar) > :is(.search-bar, .searchbar))"
7436
+ },
7437
+ Autocomplete: {
7438
+ component: Autocomplete,
7439
+ selector: ".autocomplete"
7440
+ },
7441
+ ButtonGroup: {
7442
+ component: ButtonGroup,
7443
+ selector: ".button-group"
7444
+ },
7125
7445
  // Shared with Cards.Init() - see CARDS_SELECTOR. This entry used to read
7126
7446
  // '.cards', which no card markup uses.
7127
- Cards: { component: Cards, selector: CARDS_SELECTOR },
7447
+ Cards: { component: Cards, selector: CARDS_SELECTOR, filter: isCardReveal },
7448
+ ExpandingCard: {
7449
+ component: ExpandingCard,
7450
+ selector: EXPANDING_CARD_SELECTOR
7451
+ },
7128
7452
  Carousel: { component: Carousel, selector: ".carousel" },
7129
7453
  Chips: { component: Chips, selector: ".chips" },
7130
- Datepicker: { component: Datepicker, selector: ".datepicker" },
7454
+ Datepicker: {
7455
+ component: Datepicker,
7456
+ selector: ".datepicker, .date-picker"
7457
+ },
7131
7458
  Menu: { component: Menu, selector: ".menu-trigger" },
7132
7459
  Lightbox: { component: Lightbox, selector: ".lightboxed" },
7133
- Parallax: { component: Parallax, selector: ".parallax" },
7134
7460
  ScrollSpy: { component: ScrollSpy, selector: ".scrollspy" },
7135
7461
  FormSelect: { component: FormSelect, selector: "select" },
7136
- Sidenav: { component: Sidenav, selector: ".sidenav" },
7137
- NavigationRail: { component: NavigationRail, selector: ".navigation-rail" },
7462
+ NavigationDrawer: {
7463
+ component: NavigationDrawer,
7464
+ selector: ".sidenav, .navigation-drawer"
7465
+ },
7466
+ NavigationRail: {
7467
+ component: NavigationRail,
7468
+ selector: ".navigation-rail"
7469
+ },
7138
7470
  Tabs: { component: Tabs, selector: ".tabs" },
7139
- Timepicker: { component: Timepicker, selector: ".timepicker" },
7471
+ Timepicker: {
7472
+ component: Timepicker,
7473
+ selector: ".timepicker, .time-picker"
7474
+ },
7140
7475
  Tooltip: { component: Tooltip, selector: ".tooltipped" },
7141
7476
  FloatingActionButton: {
7142
7477
  component: FloatingActionButton,
7143
- selector: ".fixed-action-btn"
7478
+ selector: ".fab, .fixed-action-btn, .fab-menu"
7144
7479
  }
7145
7480
  };
7146
7481
  function AutoInit(context = document.body, options) {
7147
7482
  for (const name of Object.keys(AUTO_INIT_COMPONENTS)) {
7148
- const { component, selector } = AUTO_INIT_COMPONENTS[name];
7483
+ const { component, selector, filter } = AUTO_INIT_COMPONENTS[name];
7149
7484
  const els = context.querySelectorAll(`:is(${selector}):not(.no-autoinit)`);
7150
- component.init(els, options?.[name] ?? {});
7485
+ if (filter) {
7486
+ els.forEach((el) => {
7487
+ if (filter(el)) component.init(el, options?.[name] ?? {});
7488
+ });
7489
+ } else {
7490
+ component.init(els, options?.[name] ?? {});
7491
+ }
7151
7492
  }
7152
7493
  }
7153
7494
 
7154
7495
  // src/ts/index.ts
7155
- var version = "0.7.0";
7156
- if (typeof document !== "undefined") {
7157
- document.addEventListener("keydown", Utils.docHandleKeydown, true);
7158
- document.addEventListener("keyup", Utils.docHandleKeyup, true);
7159
- document.addEventListener("focus", Utils.docHandleFocus, true);
7160
- document.addEventListener("blur", Utils.docHandleBlur, true);
7161
- }
7496
+ var version = "0.9.0";
7162
7497
  Forms.Init();
7163
7498
  Chips.Init();
7164
- Waves.Init();
7165
- Range.Init();
7499
+ Slider.Init();
7166
7500
  Cards.Init();
7501
+ ExpandingCard.Init();
7167
7502
  Dialogs.Init();
7168
7503
  BottomSheets.Init();
7169
7504
  SideSheets.Init();