@expressivecss/expressive 0.1.0 → 0.3.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 (40) hide show
  1. package/README.md +8 -8
  2. package/dist/css/expressive.css +1088 -356
  3. package/dist/css/expressive.css.map +1 -1
  4. package/dist/css/expressive.min.css +1 -1
  5. package/dist/css/expressive.min.css.map +1 -1
  6. package/dist/js/expressive.cjs +778 -746
  7. package/dist/js/expressive.cjs.map +4 -4
  8. package/dist/js/expressive.js +778 -746
  9. package/dist/js/expressive.js.map +4 -4
  10. package/dist/js/expressive.min.js +10 -10
  11. package/dist/js/expressive.min.js.map +4 -4
  12. package/dist/js/expressive.mjs +778 -746
  13. package/dist/js/expressive.mjs.map +4 -4
  14. package/dist/types/behaviors/dialogs.d.ts +25 -0
  15. package/dist/types/components/buttons.d.ts +17 -41
  16. package/dist/types/components/carousel.d.ts +40 -46
  17. package/dist/types/components/chips.d.ts +2 -2
  18. package/dist/types/components/collapsible.d.ts +35 -13
  19. package/dist/types/components/parallax.d.ts +7 -31
  20. package/dist/types/components/tapTarget.d.ts +23 -24
  21. package/dist/types/index.d.ts +2 -1
  22. package/package.json +1 -1
  23. package/src/sass/README.md +4 -3
  24. package/src/sass/components/_badges.scss +5 -4
  25. package/src/sass/components/_breadcrumb.scss +93 -16
  26. package/src/sass/components/_buttons.scss +195 -96
  27. package/src/sass/components/_carousel.scss +130 -63
  28. package/src/sass/components/_chips.scss +1 -1
  29. package/src/sass/components/_collapsible.scss +160 -60
  30. package/src/sass/components/_collection.scss +161 -77
  31. package/src/sass/components/_icons-material-design.scss +77 -4
  32. package/src/sass/components/_lightbox.scss +4 -0
  33. package/src/sass/components/_modal.scss +67 -3
  34. package/src/sass/components/_navbar.scss +15 -0
  35. package/src/sass/components/_pagination.scss +131 -25
  36. package/src/sass/components/_parallax.scss +116 -20
  37. package/src/sass/components/_sidenav.scss +10 -5
  38. package/src/sass/components/_slider.scss +8 -0
  39. package/src/sass/components/_tapTarget.scss +134 -47
  40. package/src/sass/utilities/_helpers.scss +10 -0
@@ -1425,7 +1425,7 @@ var _defaults4 = {
1425
1425
  data: [],
1426
1426
  placeholder: "",
1427
1427
  secondaryPlaceholder: "",
1428
- closeIconClass: "material-icons",
1428
+ closeIconClass: "material-symbols",
1429
1429
  autocompleteOptions: {},
1430
1430
  autocompleteOnly: false,
1431
1431
  limit: Infinity,
@@ -1842,6 +1842,74 @@ var Range = class _Range extends Component {
1842
1842
  }
1843
1843
  };
1844
1844
 
1845
+ // src/ts/behaviors/dialogs.ts
1846
+ var _Dialogs = class _Dialogs {
1847
+ static Init() {
1848
+ Utils.onDocumentReady(() => {
1849
+ document.addEventListener("pointerdown", _Dialogs._onPointerDown, { passive: true });
1850
+ document.addEventListener("pointerup", _Dialogs._onPointerUp, { passive: true });
1851
+ document.addEventListener("pointercancel", _Dialogs._onPointerCancel, { passive: true });
1852
+ });
1853
+ }
1854
+ static _onPointerDown(event) {
1855
+ if (!_Dialogs._isPrimaryPress(event)) {
1856
+ _Dialogs._down = null;
1857
+ return;
1858
+ }
1859
+ const dialog = _Dialogs._backdropDialog(event);
1860
+ if (!dialog || !_Dialogs._lightDismissible(dialog) || !_Dialogs._outside(event, dialog)) {
1861
+ _Dialogs._down = null;
1862
+ return;
1863
+ }
1864
+ _Dialogs._down = { dialog, pointerId: event.pointerId };
1865
+ }
1866
+ static _onPointerUp(event) {
1867
+ const started = _Dialogs._down;
1868
+ _Dialogs._down = null;
1869
+ if (!started || started.pointerId !== event.pointerId) return;
1870
+ if (!event.isPrimary) return;
1871
+ if (!started.dialog.isConnected || !started.dialog.open) return;
1872
+ if (!_Dialogs._lightDismissible(started.dialog)) return;
1873
+ if (_Dialogs._backdropDialog(event) !== started.dialog) return;
1874
+ if (_Dialogs._outside(event, started.dialog)) {
1875
+ started.dialog.close();
1876
+ }
1877
+ }
1878
+ static _onPointerCancel() {
1879
+ _Dialogs._down = null;
1880
+ }
1881
+ /** Backdrop hits retarget to the dialog itself, never a child. */
1882
+ static _backdropDialog(event) {
1883
+ const target = event.target;
1884
+ if (!(target instanceof HTMLDialogElement) || !target.open) return null;
1885
+ return target;
1886
+ }
1887
+ static _outside(event, dialog) {
1888
+ const rect = dialog.getBoundingClientRect();
1889
+ return event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom;
1890
+ }
1891
+ static _isPrimaryPress(event) {
1892
+ if (!event.isPrimary) return false;
1893
+ return event.pointerType !== "mouse" || event.button === 0;
1894
+ }
1895
+ static _lightDismissible(dialog) {
1896
+ const value = _Dialogs._closedBy(dialog);
1897
+ if (value === "none" || value === "closerequest") return false;
1898
+ if (value === "any" && "closedBy" in HTMLDialogElement.prototype) return false;
1899
+ return true;
1900
+ }
1901
+ // Read the attribute only. The IDL default for an unset closedby is
1902
+ // "closerequest" (Escape, no light dismiss) — treating that as an
1903
+ // author opt-out made every dialog undismissable from the scrim.
1904
+ static _closedBy(dialog) {
1905
+ const attr = dialog.getAttribute("closedby");
1906
+ if (attr === "any" || attr === "closerequest" || attr === "none") return attr;
1907
+ return null;
1908
+ }
1909
+ };
1910
+ _Dialogs._down = null;
1911
+ var Dialogs = _Dialogs;
1912
+
1845
1913
  // src/ts/behaviors/forms.ts
1846
1914
  var Forms = class _Forms {
1847
1915
  /**
@@ -1996,83 +2064,76 @@ var _defaults6 = {
1996
2064
  var FloatingActionButton = class _FloatingActionButton extends Component {
1997
2065
  constructor(el, options) {
1998
2066
  super(el, options, _FloatingActionButton);
1999
- /** Toolbar-mode backdrop, kept so closing can take it back out. */
2000
- this._backdrop = null;
2001
- this._handleFABClick = () => {
2002
- this._handleFABToggle();
2003
- };
2004
- this._handleFABKeyPress = (e) => {
2005
- if (Utils.keys.ENTER.includes(e.key)) {
2006
- this._handleFABToggle();
2007
- }
2008
- };
2009
- this._handleFABToggle = () => {
2010
- if (this.isOpen) {
2011
- this.close();
2012
- } else {
2013
- this.open();
2014
- }
2015
- };
2016
- this._handleDocumentClick = (e) => {
2017
- const elem = e.target;
2018
- if (elem !== this._menu) this.close();
2019
- };
2020
2067
  /**
2021
2068
  * Open FAB.
2022
2069
  */
2023
2070
  this.open = () => {
2024
2071
  if (this.isOpen) return;
2025
- if (this.options.toolbarEnabled) this._animateInToolbar();
2026
- else this._animateInFAB();
2027
2072
  this.isOpen = true;
2073
+ this.el.classList.add("active");
2074
+ this._anchor?.setAttribute("aria-expanded", "true");
2075
+ document.addEventListener("click", this._handleDocumentClick);
2076
+ document.addEventListener("keydown", this._handleDocumentKeydown);
2028
2077
  };
2029
2078
  /**
2030
2079
  * Close FAB.
2031
2080
  */
2032
2081
  this.close = () => {
2033
2082
  if (!this.isOpen) return;
2034
- if (this.options.toolbarEnabled) {
2035
- window.removeEventListener("scroll", this.close, true);
2036
- document.body.removeEventListener("click", this._handleDocumentClick, true);
2037
- this._animateOutToolbar();
2038
- } else {
2039
- this._animateOutFAB();
2040
- }
2041
2083
  this.isOpen = false;
2084
+ this.el.classList.remove("active");
2085
+ this._anchor?.setAttribute("aria-expanded", "false");
2086
+ document.removeEventListener("click", this._handleDocumentClick);
2087
+ document.removeEventListener("keydown", this._handleDocumentKeydown);
2088
+ };
2089
+ this._handleFABClick = (e) => {
2090
+ e.stopPropagation();
2091
+ this._toggle();
2092
+ };
2093
+ this._handleAnchorKeydown = (e) => {
2094
+ if (Utils.keys.ENTER.includes(e.key) || e.key === " ") {
2095
+ e.preventDefault();
2096
+ this._toggle();
2097
+ }
2098
+ };
2099
+ this._handleDocumentKeydown = (e) => {
2100
+ if (Utils.keys.ESC.includes(e.key)) this.close();
2101
+ };
2102
+ this._handleDocumentClick = (e) => {
2103
+ const target = e.target;
2104
+ if (!(target instanceof Node) || this.el.contains(target)) return;
2105
+ this.close();
2042
2106
  };
2043
2107
  this.el["Expressive_FloatingActionButton"] = this;
2108
+ const fromMarkup = this._directionFromClass();
2109
+ const toolbar = el.classList.contains("toolbar") || options.toolbarEnabled === true;
2110
+ const clickToToggle = el.classList.contains("click-to-toggle") || options.hoverEnabled === false;
2044
2111
  this.options = {
2045
2112
  ..._FloatingActionButton.defaults,
2046
- ...options
2113
+ ...options,
2114
+ direction: options.direction ?? fromMarkup ?? _FloatingActionButton.defaults.direction,
2115
+ hoverEnabled: !clickToToggle,
2116
+ toolbarEnabled: toolbar
2047
2117
  };
2048
2118
  this.isOpen = false;
2049
- this._anchor = this.el.querySelector("a, button");
2050
- this._menu = this.el.querySelector("ul");
2051
- this._floatingBtns = Array.from(this.el.querySelectorAll("ul .btn-floating"));
2052
- this._floatingBtnsReverse = this._floatingBtns.reverse();
2053
- this.offsetY = 0;
2054
- this.offsetX = 0;
2119
+ this._anchor = this.el.querySelector(":scope > a, :scope > button");
2120
+ this._menu = this.el.querySelector("ul, menu");
2055
2121
  this.el.classList.add(`direction-${this.options.direction}`);
2056
- if (this._anchor) this._anchor.tabIndex = 0;
2057
- if (this._menu) this._menu.ariaExpanded = "false";
2058
- if (this.options.direction === "top")
2059
- this.offsetY = 40;
2060
- else if (this.options.direction === "right")
2061
- this.offsetX = -40;
2062
- else if (this.options.direction === "bottom")
2063
- this.offsetY = -40;
2064
- else
2065
- this.offsetX = 40;
2122
+ if (clickToToggle) this.el.classList.add("click-to-toggle");
2123
+ if (toolbar) this.el.classList.add("toolbar");
2124
+ if (this._anchor) {
2125
+ this._anchor.setAttribute("aria-haspopup", "menu");
2126
+ this._anchor.setAttribute("aria-expanded", "false");
2127
+ if (this._anchor instanceof HTMLAnchorElement && !this._anchor.hasAttribute("href") && this._anchor.tabIndex < 0) {
2128
+ this._anchor.tabIndex = 0;
2129
+ }
2130
+ }
2131
+ if (this._menu) this._menu.setAttribute("role", "menu");
2066
2132
  this._setupEventHandlers();
2067
2133
  }
2068
2134
  static get defaults() {
2069
2135
  return _defaults6;
2070
2136
  }
2071
- /**
2072
- * Initializes instances of FloatingActionButton.
2073
- * @param els HTML elements.
2074
- * @param options Component options.
2075
- */
2076
2137
  static init(els, options = {}) {
2077
2138
  return super.init(els, options, _FloatingActionButton);
2078
2139
  }
@@ -2080,229 +2141,109 @@ var FloatingActionButton = class _FloatingActionButton extends Component {
2080
2141
  return el["Expressive_FloatingActionButton"];
2081
2142
  }
2082
2143
  destroy() {
2083
- this._removeEventHandlers();
2084
2144
  this.close();
2145
+ this._removeEventHandlers();
2085
2146
  this.el["Expressive_FloatingActionButton"] = void 0;
2086
2147
  }
2148
+ _directionFromClass() {
2149
+ const match = [...this.el.classList].find((c) => c.startsWith("direction-"));
2150
+ const value = match?.slice("direction-".length);
2151
+ if (value === "top" || value === "right" || value === "bottom" || value === "left") {
2152
+ return value;
2153
+ }
2154
+ return null;
2155
+ }
2087
2156
  _setupEventHandlers() {
2088
- if (this.options.hoverEnabled && !this.options.toolbarEnabled) {
2089
- this.el.addEventListener("mouseenter", this.open);
2090
- this.el.addEventListener("mouseleave", this.close);
2091
- } else {
2092
- this.el.addEventListener("click", this._handleFABClick);
2157
+ this.el.addEventListener("click", this._handleFABClick);
2158
+ if (this._anchor instanceof HTMLAnchorElement && !this._anchor.hasAttribute("href")) {
2159
+ this._anchor.addEventListener("keydown", this._handleAnchorKeydown);
2093
2160
  }
2094
- this.el.addEventListener("keypress", this._handleFABKeyPress);
2095
2161
  }
2096
2162
  _removeEventHandlers() {
2097
- if (this.options.hoverEnabled && !this.options.toolbarEnabled) {
2098
- this.el.removeEventListener("mouseenter", this.open);
2099
- this.el.removeEventListener("mouseleave", this.close);
2100
- } else {
2101
- this.el.removeEventListener("click", this._handleFABClick);
2102
- }
2103
- this.el.removeEventListener("keypress", this._handleFABKeyPress);
2104
- }
2105
- _animateInFAB() {
2106
- this.el.classList.add("active");
2107
- if (this._menu) this._menu.ariaExpanded = "true";
2108
- const delayIncrement = 40;
2109
- const duration = 275;
2110
- this._floatingBtnsReverse.forEach((el, index) => {
2111
- const delay = delayIncrement * index;
2112
- el.style.transition = "none";
2113
- el.style.opacity = "0";
2114
- el.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px) scale(0.4)`;
2115
- setTimeout(() => {
2116
- el.style.opacity = "0.4";
2117
- setTimeout(() => {
2118
- el.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`;
2119
- el.style.opacity = "1";
2120
- el.style.transform = "translate(0, 0) scale(1)";
2121
- el.tabIndex = 0;
2122
- }, 1);
2123
- }, delay);
2124
- });
2125
- }
2126
- _animateOutFAB() {
2127
- const duration = 175;
2128
- setTimeout(() => {
2129
- this.el.classList.remove("active");
2130
- if (this._menu) this._menu.ariaExpanded = "false";
2131
- }, duration);
2132
- this._floatingBtnsReverse.forEach((el) => {
2133
- el.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`;
2134
- el.style.opacity = "0";
2135
- el.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px) scale(0.4)`;
2136
- el.tabIndex = -1;
2137
- });
2138
- }
2139
- _animateInToolbar() {
2140
- const windowWidth = window.innerWidth;
2141
- const windowHeight = window.innerHeight;
2142
- const btnRect = this.el.getBoundingClientRect();
2143
- const fabColor = getComputedStyle(this._anchor).backgroundColor;
2144
- const backdrop = document.createElement("div");
2145
- backdrop.classList.add("fab-backdrop");
2146
- backdrop.style.backgroundColor = fabColor;
2147
- this._anchor.append(backdrop);
2148
- this._backdrop = backdrop;
2149
- const backdropWidth = backdrop.clientWidth;
2150
- const scaleFactor = backdropWidth > 0 ? windowWidth / backdropWidth : 1;
2151
- this.offsetX = btnRect.left - windowWidth / 2 + btnRect.width / 2;
2152
- this.offsetY = windowHeight - btnRect.bottom;
2153
- this.btnBottom = btnRect.bottom;
2154
- this.btnLeft = btnRect.left;
2155
- this.btnWidth = btnRect.width;
2156
- this.el.classList.add("active");
2157
- this.el.style.textAlign = "center";
2158
- this.el.style.width = "100%";
2159
- this.el.style.bottom = "0";
2160
- this.el.style.left = "0";
2161
- this.el.style.transform = "translateX(" + this.offsetX + "px)";
2162
- this.el.style.transition = "none";
2163
- if (this._menu) this._menu.ariaExpanded = "true";
2164
- this._anchor.style.transform = `translateY(${this.offsetY}px)`;
2165
- this._anchor.style.transition = "none";
2166
- setTimeout(() => {
2167
- this.el.style.transform = "";
2168
- this.el.style.transition = "transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s";
2169
- this._anchor.style.overflow = "visible";
2170
- this._anchor.style.transform = "";
2171
- this._anchor.style.transition = "transform .2s";
2172
- setTimeout(() => {
2173
- this.el.style.overflow = "hidden";
2174
- this.el.style.backgroundColor = fabColor;
2175
- backdrop.style.transform = "scale(" + scaleFactor + ")";
2176
- backdrop.style.transition = "transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)";
2177
- this._menu.querySelectorAll("li > a").forEach((a) => {
2178
- a.style.opacity = "1";
2179
- a.tabIndex = 0;
2180
- });
2181
- window.addEventListener("scroll", this.close, true);
2182
- document.body.addEventListener("click", this._handleDocumentClick, true);
2183
- }, 100);
2184
- }, 0);
2163
+ this.el.removeEventListener("click", this._handleFABClick);
2164
+ this._anchor?.removeEventListener("keydown", this._handleAnchorKeydown);
2185
2165
  }
2186
- /**
2187
- * Reverse {@link _animateInToolbar}.
2188
- *
2189
- * Closing used to only unbind the listeners, leaving the FAB stretched
2190
- * across the viewport with its backdrop still in the DOM - and a fresh
2191
- * backdrop appended on every subsequent open.
2192
- */
2193
- _animateOutToolbar() {
2194
- const backdrop = this._backdrop;
2195
- this._backdrop = null;
2196
- this._menu?.querySelectorAll("li > a").forEach((a) => {
2197
- a.style.opacity = "";
2198
- a.tabIndex = -1;
2199
- });
2200
- if (backdrop) {
2201
- backdrop.style.transition = "transform .2s";
2202
- backdrop.style.transform = "scale(0)";
2203
- }
2204
- this.el.style.overflow = "";
2205
- this.el.style.backgroundColor = "";
2206
- this.el.style.transition = "transform .2s";
2207
- this.el.style.transform = `translateX(${this.offsetX}px)`;
2208
- this._anchor.style.transition = "transform .2s";
2209
- this._anchor.style.transform = `translateY(${this.offsetY}px)`;
2210
- setTimeout(() => {
2211
- backdrop?.remove();
2212
- this.el.classList.remove("active");
2213
- if (this._menu) this._menu.ariaExpanded = "false";
2214
- this.el.style.textAlign = "";
2215
- this.el.style.width = "";
2216
- this.el.style.bottom = "";
2217
- this.el.style.left = "";
2218
- this.el.style.transform = "";
2219
- this.el.style.transition = "";
2220
- this._anchor.style.overflow = "";
2221
- this._anchor.style.transform = "";
2222
- this._anchor.style.transition = "";
2223
- }, 200);
2166
+ _toggle() {
2167
+ if (this.isOpen) this.close();
2168
+ else this.open();
2224
2169
  }
2225
2170
  };
2226
2171
 
2227
2172
  // src/ts/components/carousel.ts
2228
2173
  var _defaults7 = {
2229
2174
  duration: 200,
2230
- // ms
2231
2175
  dist: -100,
2232
- // zoom scale TODO: make this more intuitive as an option
2233
2176
  shift: 0,
2234
- // spacing for center image
2235
2177
  padding: 0,
2236
- // Padding between non center items
2237
2178
  numVisible: 5,
2238
- // Number of visible items in carousel
2239
2179
  fullWidth: false,
2240
- // Change to full width styles
2241
2180
  indicators: false,
2242
- // Toggle indicators
2243
2181
  noWrap: false,
2244
- // Don't wrap around and cycle through items.
2245
2182
  onCycleTo: null
2246
- // Callback for when a new slide is cycled to.
2247
2183
  };
2248
- var Carousel = class _Carousel extends Component {
2184
+ var _Carousel = class _Carousel extends Component {
2249
2185
  constructor(el, options) {
2250
2186
  super(el, options, _Carousel);
2251
- /** The index of the center carousel item. */
2252
2187
  this.center = 0;
2253
- // Built once per instance. Creating the throttled function inside the
2254
- // handler - as this used to - produced a fresh closure per resize event and
2255
- // never actually called it, so resizing did nothing at all.
2188
+ this._ownIndicators = false;
2189
+ this._trackEl = null;
2190
+ this._ignoreScroll = false;
2191
+ this._trackRaf = null;
2192
+ this._started = false;
2256
2193
  this._handleThrottledResize = Utils.throttle(() => this._handleResize(), 200);
2257
2194
  this._handleCarouselTap = (e) => {
2258
- if (e.type === "mousedown" && e.target.tagName === "IMG") {
2195
+ if (e.pointerType === "mouse" && e.button !== 0) return;
2196
+ if (e.target instanceof HTMLElement && e.target.tagName === "IMG") {
2259
2197
  e.preventDefault();
2260
2198
  }
2261
2199
  this.pressed = true;
2262
2200
  this.dragged = false;
2263
2201
  this.verticalDragged = false;
2264
- this.reference = this._xpos(e);
2265
- this.referenceY = this._ypos(e);
2202
+ this.reference = e.clientX;
2203
+ this.referenceY = e.clientY;
2266
2204
  this.velocity = this.amplitude = 0;
2267
2205
  this.frame = this.offset;
2268
2206
  this.timestamp = Date.now();
2269
- clearInterval(this.ticker);
2270
- this.ticker = setInterval(this._track, 100);
2207
+ try {
2208
+ this.el.setPointerCapture(e.pointerId);
2209
+ } catch {
2210
+ }
2211
+ if (this._trackRaf !== null) cancelAnimationFrame(this._trackRaf);
2212
+ const loop = () => {
2213
+ this._track();
2214
+ if (this.pressed) this._trackRaf = requestAnimationFrame(loop);
2215
+ else this._trackRaf = null;
2216
+ };
2217
+ this._trackRaf = requestAnimationFrame(loop);
2271
2218
  };
2272
2219
  this._handleCarouselDrag = (e) => {
2273
- let x, y, delta, deltaY;
2274
- if (this.pressed) {
2275
- x = this._xpos(e);
2276
- y = this._ypos(e);
2277
- delta = this.reference - x;
2278
- deltaY = Math.abs(this.referenceY - y);
2279
- if (deltaY < 30 && !this.verticalDragged) {
2280
- if (delta > 2 || delta < -2) {
2281
- this.dragged = true;
2282
- this.reference = x;
2283
- this._scroll(this.offset + delta);
2284
- }
2285
- } else if (this.dragged) {
2286
- e.preventDefault();
2287
- e.stopPropagation();
2288
- return false;
2289
- } else {
2290
- this.verticalDragged = true;
2220
+ if (!this.pressed) return;
2221
+ const delta = this.reference - e.clientX;
2222
+ const deltaY = Math.abs(this.referenceY - e.clientY);
2223
+ if (deltaY < 30 && !this.verticalDragged) {
2224
+ if (delta > 2 || delta < -2) {
2225
+ this.dragged = true;
2226
+ this.reference = e.clientX;
2227
+ this._scroll(this.offset + delta);
2291
2228
  }
2292
- }
2293
- if (this.dragged) {
2229
+ } else if (this.dragged) {
2294
2230
  e.preventDefault();
2295
- e.stopPropagation();
2296
- return false;
2231
+ } else {
2232
+ this.verticalDragged = true;
2297
2233
  }
2234
+ if (this.dragged) e.preventDefault();
2298
2235
  };
2299
2236
  this._handleCarouselRelease = (e) => {
2300
- if (this.pressed) {
2301
- this.pressed = false;
2302
- } else {
2303
- return;
2237
+ if (!this.pressed) return;
2238
+ this.pressed = false;
2239
+ if (this._trackRaf !== null) {
2240
+ cancelAnimationFrame(this._trackRaf);
2241
+ this._trackRaf = null;
2242
+ }
2243
+ try {
2244
+ this.el.releasePointerCapture(e.pointerId);
2245
+ } catch {
2304
2246
  }
2305
- clearInterval(this.ticker);
2306
2247
  this.target = this.offset;
2307
2248
  if (this.velocity > 10 || this.velocity < -10) {
2308
2249
  this.amplitude = 0.9 * this.velocity;
@@ -2319,86 +2260,91 @@ var Carousel = class _Carousel extends Component {
2319
2260
  this.amplitude = this.target - this.offset;
2320
2261
  this.timestamp = Date.now();
2321
2262
  requestAnimationFrame(this._autoScroll);
2263
+ if (this.dragged) e.preventDefault();
2264
+ };
2265
+ this._handleCarouselClick = (e) => {
2322
2266
  if (this.dragged) {
2323
2267
  e.preventDefault();
2324
2268
  e.stopPropagation();
2269
+ return;
2325
2270
  }
2326
- return false;
2327
- };
2328
- this._handleCarouselClick = (e) => {
2329
- if (this.dragged) {
2271
+ const clickedElem = e.target.closest(".carousel-item");
2272
+ if (!clickedElem || !this.el.contains(clickedElem)) return;
2273
+ const clickedIndex = this.images.indexOf(clickedElem);
2274
+ const diff = this._wrap(this.center) - clickedIndex;
2275
+ if (diff !== 0) {
2330
2276
  e.preventDefault();
2331
2277
  e.stopPropagation();
2332
- return false;
2333
- } else if (!this.options.fullWidth) {
2334
- const clickedElem = e.target.closest(".carousel-item");
2335
- if (!clickedElem) return;
2336
- const clickedIndex = [...clickedElem.parentNode.children].indexOf(clickedElem);
2337
- const diff = this._wrap(this.center) - clickedIndex;
2338
- if (diff !== 0) {
2339
- e.preventDefault();
2340
- e.stopPropagation();
2341
- }
2342
- if (clickedIndex < 0) {
2343
- if (e.clientX - e.target.getBoundingClientRect().left > this.el.clientWidth / 2) {
2344
- this.next();
2345
- } else {
2346
- this.prev();
2347
- }
2278
+ }
2279
+ if (clickedIndex < 0) {
2280
+ if (e.clientX - e.target.getBoundingClientRect().left > this.el.clientWidth / 2) {
2281
+ this.next();
2348
2282
  } else {
2349
- this._cycleTo(clickedIndex);
2283
+ this.prev();
2350
2284
  }
2285
+ } else {
2286
+ this._cycleTo(clickedIndex);
2351
2287
  }
2352
2288
  };
2353
2289
  this._handleIndicatorClick = (e) => {
2354
2290
  e.stopPropagation();
2355
- this._handleIndicatorInteraction(e);
2291
+ const indicator = e.target.closest(".indicator-item");
2292
+ if (!indicator || !this._indicators.contains(indicator)) return;
2293
+ const index = [...this._indicators.children].indexOf(indicator);
2294
+ if (index < 0) return;
2295
+ this._cycleTo(index);
2356
2296
  };
2357
- this._handleIndicatorKeyPress = (e) => {
2358
- e.stopPropagation();
2359
- if (Utils.keys.ENTER.includes(e.key)) {
2360
- this._handleIndicatorInteraction(e);
2297
+ this._handleKeydown = (e) => {
2298
+ const target = e.target;
2299
+ if (target.closest('input, textarea, select, [contenteditable="true"]')) return;
2300
+ if (Utils.keys.ARROW_RIGHT.includes(e.key)) {
2301
+ e.preventDefault();
2302
+ this.next();
2303
+ } else if (Utils.keys.ARROW_LEFT.includes(e.key)) {
2304
+ e.preventDefault();
2305
+ this.prev();
2361
2306
  }
2362
2307
  };
2363
- this._handleIndicatorInteraction = (e) => {
2364
- const indicator = e.target.closest(".indicator-item");
2365
- if (indicator) {
2366
- const index = [...indicator.parentNode.children].indexOf(indicator);
2367
- this._cycleTo(index);
2368
- }
2308
+ this._handleFlatScroll = () => {
2309
+ if (this._ignoreScroll) return;
2310
+ const index = this._nearestIndex();
2311
+ if (index !== this.center) this._syncActive(index, this.dragged);
2369
2312
  };
2370
2313
  this._handleResize = () => {
2314
+ if (!this.images.length) return;
2315
+ if (this._flat) {
2316
+ this._scrollToIndex(this.center, false);
2317
+ return;
2318
+ }
2371
2319
  if (this.options.fullWidth) {
2372
- const firstItem = this.el.querySelector(".carousel-item");
2373
- const activeItem = this.el.querySelector(".carousel-item.active");
2374
- if (!firstItem || !activeItem) return;
2320
+ const firstItem = this.images[0];
2321
+ if (!firstItem) return;
2375
2322
  this.itemWidth = firstItem.clientWidth;
2376
- this.imageHeight = activeItem.clientHeight;
2377
2323
  this.dim = this.itemWidth * 2 + this.options.padding;
2378
2324
  this.offset = this.center * 2 * this.itemWidth;
2379
2325
  this.target = this.offset;
2380
- this._setCarouselHeight(true);
2381
2326
  } else {
2382
2327
  this._scroll();
2383
2328
  }
2384
2329
  };
2385
2330
  this._track = () => {
2386
- const now = Date.now(), elapsed = now - this.timestamp, delta = this.offset - this.frame, v = 1e3 * delta / (1 + elapsed);
2331
+ const now = Date.now();
2332
+ const elapsed = now - this.timestamp;
2333
+ const delta = this.offset - this.frame;
2334
+ const v = 1e3 * delta / (1 + elapsed);
2387
2335
  this.timestamp = now;
2388
2336
  this.frame = this.offset;
2389
2337
  this.velocity = 0.8 * v + 0.2 * this.velocity;
2390
2338
  };
2391
2339
  this._autoScroll = () => {
2392
- let elapsed, delta;
2393
- if (this.amplitude) {
2394
- elapsed = Date.now() - this.timestamp;
2395
- delta = this.amplitude * Math.exp(-elapsed / this.options.duration);
2396
- if (delta > 2 || delta < -2) {
2397
- this._scroll(this.target - delta);
2398
- requestAnimationFrame(this._autoScroll);
2399
- } else {
2400
- this._scroll(this.target);
2401
- }
2340
+ if (!this.amplitude) return;
2341
+ const elapsed = Date.now() - this.timestamp;
2342
+ const delta = this.amplitude * Math.exp(-elapsed / this.options.duration);
2343
+ if (delta > 2 || delta < -2) {
2344
+ this._scroll(this.target - delta);
2345
+ requestAnimationFrame(this._autoScroll);
2346
+ } else {
2347
+ this._scroll(this.target);
2402
2348
  }
2403
2349
  };
2404
2350
  this.el["Expressive_Carousel"] = this;
@@ -2406,56 +2352,58 @@ var Carousel = class _Carousel extends Component {
2406
2352
  ..._Carousel.defaults,
2407
2353
  ...options
2408
2354
  };
2409
- this.hasMultipleSlides = this.el.querySelectorAll(".carousel-item").length > 1;
2410
- this.showIndicators = this.options.indicators && this.hasMultipleSlides;
2411
- this.noWrap = this.options.noWrap || !this.hasMultipleSlides;
2355
+ this._flat = this.options.fullWidth || this.el.classList.contains("flat") || this.el.classList.contains("carousel-slider");
2356
+ if (this._flat) {
2357
+ this.el.classList.add("flat");
2358
+ this.options.fullWidth = true;
2359
+ this.options.dist = 0;
2360
+ }
2361
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
2362
+ this.options.duration = 1;
2363
+ if (!this._flat) this.options.dist = 0;
2364
+ }
2412
2365
  this.pressed = false;
2413
2366
  this.dragged = false;
2414
2367
  this.offset = this.target = 0;
2415
- this.images = [];
2416
- const firstItem = this.el.querySelector(".carousel-item");
2417
- if (!firstItem) {
2368
+ this.images = this._collectItems();
2369
+ if (!this.images.length) {
2418
2370
  console.error("Carousel: no .carousel-item elements to show");
2419
2371
  return;
2420
2372
  }
2421
- this.itemWidth = firstItem.clientWidth;
2422
- this.itemHeight = firstItem.clientHeight;
2423
- this.dim = this.itemWidth * 2 + this.options.padding || 1;
2424
- if (this.options.fullWidth) {
2425
- this.options.dist = 0;
2426
- this._setCarouselHeight();
2427
- if (this.showIndicators) {
2428
- this.el.querySelector(".carousel-fixed-item")?.classList.add("with-indicators");
2429
- }
2430
- }
2431
- this._indicators = document.createElement("ul");
2432
- this._indicators.classList.add("indicators");
2433
- this.el.querySelectorAll(".carousel-item").forEach((item, i) => {
2434
- this.images.push(item);
2435
- if (this.showIndicators) {
2436
- const indicator = document.createElement("li");
2437
- indicator.classList.add("indicator-item");
2438
- indicator.tabIndex = 0;
2439
- if (i === 0) {
2440
- indicator.classList.add("active");
2441
- }
2442
- this._indicators.appendChild(indicator);
2443
- }
2444
- });
2445
- if (this.showIndicators) this.el.appendChild(this._indicators);
2373
+ this.hasMultipleSlides = this.images.length > 1;
2374
+ this.showIndicators = this.options.indicators && this.hasMultipleSlides;
2375
+ this.noWrap = this.options.noWrap || !this.hasMultipleSlides || this._flat;
2446
2376
  this.count = this.images.length;
2447
2377
  this.options.numVisible = Math.min(this.count, this.options.numVisible);
2378
+ this.images.forEach((item) => item.classList.add("carousel-item"));
2379
+ if (this._flat) this._wrapTrack();
2380
+ const firstItem = this.images[0];
2381
+ this.itemWidth = firstItem.clientWidth || this.el.clientWidth || 1;
2382
+ this.itemHeight = firstItem.clientHeight || this.el.clientHeight || 1;
2383
+ this.dim = this.itemWidth * 2 + this.options.padding || 1;
2384
+ if (this.showIndicators) this._setupIndicators();
2385
+ this.el.tabIndex = this.el.tabIndex >= 0 ? this.el.tabIndex : 0;
2386
+ this.el.setAttribute("aria-roledescription", "carousel");
2448
2387
  this._setupEventHandlers();
2449
- this._scroll(this.offset);
2388
+ this._started = true;
2389
+ if (this._flat) {
2390
+ const start = Math.max(
2391
+ 0,
2392
+ this.images.findIndex((el2) => el2.classList.contains("active"))
2393
+ );
2394
+ this.center = start;
2395
+ this._syncActive(start, false);
2396
+ this._scrollToIndex(start, false);
2397
+ } else {
2398
+ this._scroll(this.offset);
2399
+ }
2400
+ }
2401
+ get _scroller() {
2402
+ return this._trackEl ?? this.el;
2450
2403
  }
2451
2404
  static get defaults() {
2452
2405
  return _defaults7;
2453
2406
  }
2454
- /**
2455
- * Initializes instances of Carousel.
2456
- * @param els HTML elements.
2457
- * @param options Component options.
2458
- */
2459
2407
  static init(els, options = {}) {
2460
2408
  return super.init(els, options, _Carousel);
2461
2409
  }
@@ -2463,81 +2411,104 @@ var Carousel = class _Carousel extends Component {
2463
2411
  return el["Expressive_Carousel"];
2464
2412
  }
2465
2413
  destroy() {
2466
- this._removeEventHandlers();
2414
+ if (this._started) this._teardown();
2467
2415
  this.el["Expressive_Carousel"] = void 0;
2468
2416
  }
2417
+ _collectItems() {
2418
+ const explicit = Array.from(this.el.children).filter(
2419
+ (el) => el instanceof HTMLElement && el.classList.contains("carousel-item")
2420
+ );
2421
+ if (explicit.length) return explicit;
2422
+ return Array.from(this.el.children).filter((el) => {
2423
+ if (!(el instanceof HTMLElement)) return false;
2424
+ if (el.classList.contains("indicators") || el.classList.contains("carousel-fixed-item")) {
2425
+ return false;
2426
+ }
2427
+ return el.tagName !== "NAV";
2428
+ });
2429
+ }
2430
+ _setupIndicators() {
2431
+ const existing = Array.from(this.el.children).find(
2432
+ (el) => el instanceof HTMLElement && el.classList.contains("indicators")
2433
+ );
2434
+ this._indicators = existing ?? document.createElement("nav");
2435
+ this._ownIndicators = !existing;
2436
+ this._indicators.classList.add("indicators");
2437
+ this._indicators.setAttribute("aria-label", "Slides");
2438
+ this._indicators.replaceChildren();
2439
+ this.images.forEach((_, i) => {
2440
+ const button = document.createElement("button");
2441
+ button.type = "button";
2442
+ button.classList.add("indicator-item");
2443
+ if (i === 0) {
2444
+ button.classList.add("active");
2445
+ button.setAttribute("aria-current", "true");
2446
+ }
2447
+ button.setAttribute("aria-label", `Slide ${i + 1}`);
2448
+ this._indicators.appendChild(button);
2449
+ });
2450
+ if (this._ownIndicators) this.el.appendChild(this._indicators);
2451
+ if (this._flat) {
2452
+ this.el.querySelector(".carousel-fixed-item")?.classList.add("with-indicators");
2453
+ }
2454
+ }
2469
2455
  _setupEventHandlers() {
2470
- if (typeof window.ontouchstart !== "undefined") {
2471
- this.el.addEventListener("touchstart", this._handleCarouselTap);
2472
- this.el.addEventListener("touchmove", this._handleCarouselDrag);
2473
- this.el.addEventListener("touchend", this._handleCarouselRelease);
2474
- }
2475
- this.el.addEventListener("mousedown", this._handleCarouselTap);
2476
- this.el.addEventListener("mousemove", this._handleCarouselDrag);
2477
- this.el.addEventListener("mouseup", this._handleCarouselRelease);
2478
- this.el.addEventListener("mouseleave", this._handleCarouselRelease);
2479
- this.el.addEventListener("click", this._handleCarouselClick);
2456
+ this.el.addEventListener("keydown", this._handleKeydown);
2480
2457
  if (this.showIndicators && this._indicators) {
2481
- this._indicators.querySelectorAll(".indicator-item").forEach((el) => {
2482
- el.addEventListener("click", this._handleIndicatorClick);
2483
- el.addEventListener("keypress", this._handleIndicatorKeyPress);
2484
- });
2458
+ this._indicators.addEventListener("click", this._handleIndicatorClick);
2459
+ }
2460
+ _Carousel._live.add(this);
2461
+ if (!_Carousel._resizeListening) {
2462
+ window.addEventListener("resize", _Carousel._onResize, { passive: true });
2463
+ _Carousel._resizeListening = true;
2485
2464
  }
2486
- window.addEventListener("resize", this._handleThrottledResize);
2465
+ if (this._flat) {
2466
+ this._scroller.addEventListener("scroll", this._handleFlatScroll, { passive: true });
2467
+ return;
2468
+ }
2469
+ this.el.addEventListener("pointerdown", this._handleCarouselTap);
2470
+ this.el.addEventListener("pointermove", this._handleCarouselDrag);
2471
+ this.el.addEventListener("pointerup", this._handleCarouselRelease);
2472
+ this.el.addEventListener("pointercancel", this._handleCarouselRelease);
2473
+ this.el.addEventListener("click", this._handleCarouselClick);
2487
2474
  }
2488
2475
  _removeEventHandlers() {
2489
- if (typeof window.ontouchstart !== "undefined") {
2490
- this.el.removeEventListener("touchstart", this._handleCarouselTap);
2491
- this.el.removeEventListener("touchmove", this._handleCarouselDrag);
2492
- this.el.removeEventListener("touchend", this._handleCarouselRelease);
2493
- }
2494
- this.el.removeEventListener("mousedown", this._handleCarouselTap);
2495
- this.el.removeEventListener("mousemove", this._handleCarouselDrag);
2496
- this.el.removeEventListener("mouseup", this._handleCarouselRelease);
2497
- this.el.removeEventListener("mouseleave", this._handleCarouselRelease);
2476
+ this.el.removeEventListener("keydown", this._handleKeydown);
2477
+ if (this._indicators) {
2478
+ this._indicators.removeEventListener("click", this._handleIndicatorClick);
2479
+ }
2480
+ this._scroller.removeEventListener("scroll", this._handleFlatScroll);
2481
+ this.el.removeEventListener("pointerdown", this._handleCarouselTap);
2482
+ this.el.removeEventListener("pointermove", this._handleCarouselDrag);
2483
+ this.el.removeEventListener("pointerup", this._handleCarouselRelease);
2484
+ this.el.removeEventListener("pointercancel", this._handleCarouselRelease);
2498
2485
  this.el.removeEventListener("click", this._handleCarouselClick);
2499
- if (this.showIndicators && this._indicators) {
2500
- this._indicators.querySelectorAll(".indicator-item").forEach((el) => {
2501
- el.removeEventListener("click", this._handleIndicatorClick);
2502
- });
2486
+ _Carousel._live.delete(this);
2487
+ if (_Carousel._live.size === 0 && _Carousel._resizeListening) {
2488
+ window.removeEventListener("resize", _Carousel._onResize);
2489
+ _Carousel._resizeListening = false;
2503
2490
  }
2504
- window.removeEventListener("resize", this._handleThrottledResize);
2505
2491
  }
2506
- _setCarouselHeight(imageOnly = false) {
2507
- const firstSlide = this.el.querySelector(".carousel-item.active") ? this.el.querySelector(".carousel-item.active") : this.el.querySelector(".carousel-item");
2508
- const firstImage = firstSlide.querySelector("img");
2509
- if (firstImage) {
2510
- if (firstImage.complete) {
2511
- const imageHeight = firstImage.clientHeight;
2512
- if (imageHeight > 0) {
2513
- this.el.style.height = imageHeight + "px";
2514
- } else {
2515
- const naturalWidth = firstImage.naturalWidth;
2516
- const naturalHeight = firstImage.naturalHeight;
2517
- const adjustedHeight = this.el.clientWidth / naturalWidth * naturalHeight;
2518
- this.el.style.height = adjustedHeight + "px";
2519
- }
2520
- } else {
2521
- firstImage.addEventListener("load", () => {
2522
- this.el.style.height = firstImage.offsetHeight + "px";
2523
- });
2524
- }
2525
- } else if (!imageOnly) {
2526
- const slideHeight = firstSlide.clientHeight;
2527
- this.el.style.height = slideHeight + "px";
2528
- }
2492
+ _teardown() {
2493
+ this._removeEventHandlers();
2494
+ if (this._trackRaf !== null) cancelAnimationFrame(this._trackRaf);
2495
+ window.clearTimeout(this.scrollingTimeout);
2496
+ if (this._ownIndicators) this._indicators?.remove();
2497
+ this._unwrapTrack();
2498
+ this.images.forEach((el) => {
2499
+ el.style.transform = "";
2500
+ el.style.zIndex = "";
2501
+ el.style.opacity = "";
2502
+ el.style.visibility = "";
2503
+ });
2529
2504
  }
2530
2505
  _xpos(e) {
2531
- if (e.type.startsWith("touch") && e.targetTouches.length >= 1) {
2532
- return e.targetTouches[0].clientX;
2533
- }
2534
- return e.clientX;
2506
+ if ("clientX" in e) return e.clientX;
2507
+ return 0;
2535
2508
  }
2536
2509
  _ypos(e) {
2537
- if (e.type.startsWith("touch") && e.targetTouches.length >= 1) {
2538
- return e.targetTouches[0].clientY;
2539
- }
2540
- return e.clientY;
2510
+ if ("clientY" in e) return e.clientY;
2511
+ return 0;
2541
2512
  }
2542
2513
  _wrap(x) {
2543
2514
  return x >= this.count ? x % this.count : x < 0 ? this._wrap(this.count + x % this.count) : x;
@@ -2546,18 +2517,20 @@ var Carousel = class _Carousel extends Component {
2546
2517
  if (!this.el.classList.contains("scrolling")) {
2547
2518
  this.el.classList.add("scrolling");
2548
2519
  }
2549
- if (this.scrollingTimeout != null) {
2550
- clearTimeout(this.scrollingTimeout);
2551
- }
2520
+ window.clearTimeout(this.scrollingTimeout);
2552
2521
  this.scrollingTimeout = setTimeout(() => {
2553
2522
  this.el.classList.remove("scrolling");
2554
2523
  }, this.options.duration);
2555
2524
  this.offset = typeof x === "number" ? x : this.offset;
2556
2525
  this.center = Math.floor((this.offset + this.dim / 2) / this.dim);
2557
- const half = this.count >> 1, delta = this.offset - this.center * this.dim, dir = delta < 0 ? 1 : -1, tween = -dir * delta * 2 / this.dim;
2558
- let i, el, alignment, zTranslation, tweenedOpacity, centerTweenedOpacity;
2526
+ const half = this.count >> 1;
2527
+ const delta = this.offset - this.center * this.dim;
2528
+ const dir = delta < 0 ? 1 : -1;
2529
+ const tween = -dir * delta * 2 / this.dim;
2559
2530
  const lastCenter = this.center;
2560
2531
  const numVisibleOffset = 1 / this.options.numVisible;
2532
+ let alignment;
2533
+ let centerTweenedOpacity;
2561
2534
  if (this.options.fullWidth) {
2562
2535
  alignment = "translateX(0)";
2563
2536
  centerTweenedOpacity = 1;
@@ -2566,26 +2539,17 @@ var Carousel = class _Carousel extends Component {
2566
2539
  alignment += "translateY(" + (this.el.clientHeight - this.itemHeight) / 2 + "px)";
2567
2540
  centerTweenedOpacity = 1 - numVisibleOffset * tween;
2568
2541
  }
2569
- if (this.showIndicators) {
2570
- const diff = this.center % this.count;
2571
- const activeIndicator = this._indicators.querySelector(".indicator-item.active");
2572
- const activeIndicatorIndex = [...activeIndicator.parentNode.children].indexOf(
2573
- activeIndicator
2574
- );
2575
- if (activeIndicatorIndex !== diff) {
2576
- activeIndicator.classList.remove("active");
2577
- const pos = diff < 0 ? this.count + diff : diff;
2578
- this._indicators.querySelectorAll(".indicator-item")[pos].classList.add("active");
2579
- }
2580
- }
2542
+ this._paintIndicators(this.center);
2581
2543
  if (!this.noWrap || this.center >= 0 && this.center < this.count) {
2582
- el = this.images[this._wrap(this.center)];
2583
- if (!el.classList.contains("active")) {
2544
+ const el = this.images[this._wrap(this.center)];
2545
+ if (el && !el.classList.contains("active")) {
2584
2546
  this.el.querySelector(".carousel-item.active")?.classList.remove("active");
2585
2547
  el.classList.add("active");
2586
2548
  }
2587
2549
  }
2588
- for (i = 1; i <= half; ++i) {
2550
+ for (let i = 1; i <= half; ++i) {
2551
+ let zTranslation;
2552
+ let tweenedOpacity;
2589
2553
  if (this.options.fullWidth) {
2590
2554
  zTranslation = this.options.dist;
2591
2555
  tweenedOpacity = i === half && delta < 0 ? 1 - tween : 1;
@@ -2594,7 +2558,7 @@ var Carousel = class _Carousel extends Component {
2594
2558
  tweenedOpacity = 1 - numVisibleOffset * (i * 2 + tween * dir);
2595
2559
  }
2596
2560
  if (!this.noWrap || this.center + i < this.count) {
2597
- el = this.images[this._wrap(this.center + i)];
2561
+ const el = this.images[this._wrap(this.center + i)];
2598
2562
  const transformString = `${alignment} translateX(${this.options.shift + (this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`;
2599
2563
  this._updateItemStyle(el, tweenedOpacity, -i, transformString);
2600
2564
  }
@@ -2606,32 +2570,113 @@ var Carousel = class _Carousel extends Component {
2606
2570
  tweenedOpacity = 1 - numVisibleOffset * (i * 2 - tween * dir);
2607
2571
  }
2608
2572
  if (!this.noWrap || this.center - i >= 0) {
2609
- el = this.images[this._wrap(this.center - i)];
2573
+ const el = this.images[this._wrap(this.center - i)];
2610
2574
  const transformString = `${alignment} translateX(${-this.options.shift + (-this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`;
2611
2575
  this._updateItemStyle(el, tweenedOpacity, -i, transformString);
2612
2576
  }
2613
2577
  }
2614
2578
  if (!this.noWrap || this.center >= 0 && this.center < this.count) {
2615
- el = this.images[this._wrap(this.center)];
2579
+ const el = this.images[this._wrap(this.center)];
2616
2580
  const transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir * this.options.shift * tween}px) translateZ(${this.options.dist * tween}px)`;
2617
2581
  this._updateItemStyle(el, centerTweenedOpacity, 0, transformString);
2618
2582
  }
2619
- const _currItem = this.el.querySelectorAll(".carousel-item")[this._wrap(this.center)];
2583
+ const currItem = this.images[this._wrap(this.center)];
2584
+ this._syncA11y(this._wrap(this.center));
2620
2585
  if (lastCenter !== this.center && typeof this.options.onCycleTo === "function") {
2621
- this.options.onCycleTo.call(this, _currItem, this.dragged);
2586
+ this.options.onCycleTo.call(this, currItem, this.dragged);
2622
2587
  }
2623
2588
  if (typeof this.oneTimeCallback === "function") {
2624
- this.oneTimeCallback.call(this, _currItem, this.dragged);
2589
+ this.oneTimeCallback.call(this, currItem, this.dragged);
2625
2590
  this.oneTimeCallback = null;
2626
2591
  }
2627
2592
  }
2628
2593
  _updateItemStyle(el, opacity, zIndex, transform) {
2594
+ if (!el) return;
2629
2595
  el.style.transform = transform;
2630
2596
  el.style.zIndex = zIndex.toString();
2631
2597
  el.style.opacity = opacity.toString();
2632
2598
  el.style.visibility = "visible";
2633
2599
  }
2600
+ _paintIndicators(center) {
2601
+ if (!this.showIndicators || !this._indicators) return;
2602
+ const pos = (center % this.count + this.count) % this.count;
2603
+ this._indicators.querySelectorAll(".indicator-item").forEach((el, i) => {
2604
+ const on = i === pos;
2605
+ el.classList.toggle("active", on);
2606
+ if (on) el.setAttribute("aria-current", "true");
2607
+ else el.removeAttribute("aria-current");
2608
+ });
2609
+ }
2610
+ _syncA11y(index) {
2611
+ this.images.forEach((el, i) => {
2612
+ if (i === index) el.removeAttribute("aria-hidden");
2613
+ else el.setAttribute("aria-hidden", "true");
2614
+ });
2615
+ }
2616
+ _syncActive(index, dragged) {
2617
+ const prev = this.center;
2618
+ this.center = index;
2619
+ this.images.forEach((el, i) => el.classList.toggle("active", i === index));
2620
+ this._paintIndicators(index);
2621
+ this._syncA11y(index);
2622
+ const curr = this.images[index];
2623
+ if (prev !== index && typeof this.options.onCycleTo === "function") {
2624
+ this.options.onCycleTo.call(this, curr, dragged);
2625
+ }
2626
+ if (typeof this.oneTimeCallback === "function") {
2627
+ this.oneTimeCallback.call(this, curr, dragged);
2628
+ this.oneTimeCallback = null;
2629
+ }
2630
+ }
2631
+ _wrapTrack() {
2632
+ const track = document.createElement("div");
2633
+ track.className = "carousel-track";
2634
+ this.images[0].before(track);
2635
+ this.images.forEach((el) => track.appendChild(el));
2636
+ this._trackEl = track;
2637
+ }
2638
+ _unwrapTrack() {
2639
+ if (!this._trackEl) return;
2640
+ this.images.forEach((el) => this.el.insertBefore(el, this._trackEl));
2641
+ this._trackEl.remove();
2642
+ this._trackEl = null;
2643
+ }
2644
+ _nearestIndex() {
2645
+ const left = this._scroller.scrollLeft;
2646
+ let best = 0;
2647
+ let bestDist = Infinity;
2648
+ this.images.forEach((el, i) => {
2649
+ const dist = Math.abs(el.offsetLeft - left);
2650
+ if (dist < bestDist) {
2651
+ bestDist = dist;
2652
+ best = i;
2653
+ }
2654
+ });
2655
+ return best;
2656
+ }
2657
+ _scrollToIndex(n, smooth = true) {
2658
+ const el = this.images[n];
2659
+ if (!el) return;
2660
+ const left = el.offsetLeft;
2661
+ if (Math.abs(this._scroller.scrollLeft - left) < 1) {
2662
+ this._syncActive(n, false);
2663
+ return;
2664
+ }
2665
+ this._ignoreScroll = true;
2666
+ this._scroller.scrollTo({ left, behavior: smooth ? "smooth" : "auto" });
2667
+ const done = () => {
2668
+ this._ignoreScroll = false;
2669
+ };
2670
+ this._scroller.addEventListener("scrollend", done, { once: true });
2671
+ window.setTimeout(done, this.options.duration + 350);
2672
+ }
2634
2673
  _cycleTo(n, callback = null) {
2674
+ if (this._flat) {
2675
+ if (typeof callback === "function") this.oneTimeCallback = callback;
2676
+ this._syncActive(n, false);
2677
+ this._scrollToIndex(n);
2678
+ return;
2679
+ }
2635
2680
  let diff = this.center % this.count - n;
2636
2681
  if (!this.noWrap) {
2637
2682
  if (diff < 0) {
@@ -2659,14 +2704,8 @@ var Carousel = class _Carousel extends Component {
2659
2704
  requestAnimationFrame(this._autoScroll);
2660
2705
  }
2661
2706
  }
2662
- /**
2663
- * Move carousel to next slide or go forward a given amount of slides.
2664
- * @param n How many times the carousel slides.
2665
- */
2666
2707
  next(n = 1) {
2667
- if (n === void 0 || isNaN(n)) {
2668
- n = 1;
2669
- }
2708
+ if (n === void 0 || isNaN(n)) n = 1;
2670
2709
  let index = this.center + n;
2671
2710
  if (index >= this.count || index < 0) {
2672
2711
  if (this.noWrap) return;
@@ -2674,14 +2713,8 @@ var Carousel = class _Carousel extends Component {
2674
2713
  }
2675
2714
  this._cycleTo(index);
2676
2715
  }
2677
- /**
2678
- * Move carousel to previous slide or go back a given amount of slides.
2679
- * @param n How many times the carousel slides.
2680
- */
2681
2716
  prev(n = 1) {
2682
- if (n === void 0 || isNaN(n)) {
2683
- n = 1;
2684
- }
2717
+ if (n === void 0 || isNaN(n)) n = 1;
2685
2718
  let index = this.center - n;
2686
2719
  if (index >= this.count || index < 0) {
2687
2720
  if (this.noWrap) return;
@@ -2689,15 +2722,8 @@ var Carousel = class _Carousel extends Component {
2689
2722
  }
2690
2723
  this._cycleTo(index);
2691
2724
  }
2692
- /**
2693
- * Move carousel to nth slide.
2694
- * @param n Index of slide.
2695
- * @param callback "onCycleTo" optional callback.
2696
- */
2697
2725
  set(n, callback) {
2698
- if (n === void 0 || isNaN(n)) {
2699
- n = 0;
2700
- }
2726
+ if (n === void 0 || isNaN(n)) n = 0;
2701
2727
  if (n > this.count || n < 0) {
2702
2728
  if (this.noWrap) return;
2703
2729
  n = this._wrap(n);
@@ -2705,6 +2731,12 @@ var Carousel = class _Carousel extends Component {
2705
2731
  this._cycleTo(n, callback);
2706
2732
  }
2707
2733
  };
2734
+ _Carousel._live = /* @__PURE__ */ new Set();
2735
+ _Carousel._resizeListening = false;
2736
+ _Carousel._onResize = Utils.throttle(() => {
2737
+ _Carousel._live.forEach((c) => c._handleResize());
2738
+ }, 200);
2739
+ var Carousel = _Carousel;
2708
2740
 
2709
2741
  // src/ts/components/characterCounter.ts
2710
2742
  var _defaults8 = Object.freeze({});
@@ -2794,77 +2826,75 @@ var _defaults9 = {
2794
2826
  var Collapsible = class _Collapsible extends Component {
2795
2827
  constructor(el, options) {
2796
2828
  super(el, options, _Collapsible);
2797
- this._handleCollapsibleClick = (e) => {
2798
- const header = e.target.closest(".collapsible-header");
2799
- if (e.target && header) {
2800
- const collapsible = header.closest(".collapsible");
2801
- if (collapsible !== this.el) return;
2802
- const li = header.closest("li");
2803
- const isActive = li.classList.contains("active");
2804
- const index = [...li.parentNode.children].indexOf(li);
2805
- if (isActive) this.close(index);
2806
- else this.open(index);
2807
- }
2808
- };
2809
- this._handleCollapsibleKeydown = (e) => {
2810
- if (Utils.keys.ENTER.includes(e.key)) {
2811
- this._handleCollapsibleClick(e);
2812
- }
2813
- };
2829
+ this._items = [];
2830
+ this._endTimers = [];
2831
+ this._wrappedBodies = [];
2814
2832
  /**
2815
2833
  * Open collapsible section.
2816
- * @param n Nth section to open.
2834
+ * @param index 0-based index of the section to open.
2817
2835
  */
2818
2836
  this.open = (index) => {
2819
- const listItems = Array.from(this.el.children).filter((c) => c.tagName === "LI");
2820
- const li = listItems[index];
2821
- if (li && !li.classList.contains("active")) {
2822
- if (typeof this.options.onOpenStart === "function") {
2823
- this.options.onOpenStart.call(this, li);
2824
- }
2825
- if (this.options.accordion) {
2826
- const activeLis = listItems.filter((li2) => li2.classList.contains("active"));
2827
- activeLis.forEach((activeLi) => {
2828
- const index2 = listItems.indexOf(activeLi);
2829
- this.close(index2);
2830
- });
2831
- }
2832
- li.classList.add("active");
2833
- this._animateIn(index);
2837
+ const item = this._items[index];
2838
+ if (!item || this._isOpen(item)) return;
2839
+ if (this.options.accordion) {
2840
+ this._items.forEach((other, i) => {
2841
+ if (i !== index && this._isOpen(other)) this.close(i);
2842
+ });
2834
2843
  }
2844
+ this._setOpen(item, true);
2835
2845
  };
2836
2846
  /**
2837
2847
  * Close collapsible section.
2838
- * @param n Nth section to close.
2848
+ * @param index 0-based index of the section to close.
2839
2849
  */
2840
2850
  this.close = (index) => {
2841
- const li = Array.from(this.el.children).filter((c) => c.tagName === "LI")[index];
2842
- if (li && li.classList.contains("active")) {
2843
- if (typeof this.options.onCloseStart === "function") {
2844
- this.options.onCloseStart.call(this, li);
2845
- }
2846
- li.classList.remove("active");
2847
- this._animateOut(index);
2848
- }
2851
+ const item = this._items[index];
2852
+ if (!item || !this._isOpen(item)) return;
2853
+ this._setOpen(item, false);
2854
+ };
2855
+ this._handleClick = (e) => {
2856
+ const header = e.target.closest(".collapsible-header");
2857
+ if (!header || header.closest(".collapsible") !== this.el) return;
2858
+ if (header.closest("details")) return;
2859
+ const item = header.closest("li");
2860
+ if (!item) return;
2861
+ const index = this._items.indexOf(item);
2862
+ if (index < 0) return;
2863
+ if (this._isOpen(item)) this.close(index);
2864
+ else this.open(index);
2865
+ };
2866
+ this._handleKeydown = (e) => {
2867
+ if (!Utils.keys.ENTER.includes(e.key) && e.key !== " ") return;
2868
+ const header = e.target.closest(".collapsible-header");
2869
+ if (!header || header.closest(".collapsible") !== this.el) return;
2870
+ if (header.closest("details")) return;
2871
+ if (header.matches("a, button")) return;
2872
+ e.preventDefault();
2873
+ const item = header.closest("li");
2874
+ if (!item) return;
2875
+ const index = this._items.indexOf(item);
2876
+ if (index < 0) return;
2877
+ if (this._isOpen(item)) this.close(index);
2878
+ else this.open(index);
2879
+ };
2880
+ this._handleToggle = (e) => {
2881
+ const item = e.target;
2882
+ if (!(item instanceof HTMLDetailsElement)) return;
2883
+ if (!this._items.includes(item)) return;
2884
+ this._notify(item, item.open);
2849
2885
  };
2850
2886
  this.el["Expressive_Collapsible"] = this;
2887
+ const accordion = options.accordion !== void 0 ? options.accordion : !el.classList.contains("expandable");
2851
2888
  this.options = {
2852
2889
  ..._Collapsible.defaults,
2853
- ...options
2890
+ ...options,
2891
+ accordion
2854
2892
  };
2855
- this._headers = Array.from(this.el.querySelectorAll("li > .collapsible-header"));
2856
- this._headers.forEach((el2) => el2.tabIndex = 0);
2893
+ this._groupName = el.id ? `collapsible-${el.id}` : `collapsible-${Utils.guid()}`;
2894
+ this._items = this._collectItems();
2895
+ this._wrapDetailsBodies();
2896
+ this._setupItems();
2857
2897
  this._setupEventHandlers();
2858
- const activeBodies = Array.from(
2859
- this.el.querySelectorAll("li.active > .collapsible-body")
2860
- );
2861
- if (this.options.accordion) {
2862
- if (activeBodies.length > 0) {
2863
- this._setExpanded(activeBodies[0]);
2864
- }
2865
- } else {
2866
- activeBodies.forEach((el2) => this._setExpanded(el2));
2867
- }
2868
2898
  }
2869
2899
  static get defaults() {
2870
2900
  return _defaults9;
@@ -2882,48 +2912,130 @@ var Collapsible = class _Collapsible extends Component {
2882
2912
  }
2883
2913
  destroy() {
2884
2914
  this._removeEventHandlers();
2915
+ this._endTimers.forEach((id) => clearTimeout(id));
2916
+ this._endTimers = [];
2917
+ this._unwrapDetailsBodies();
2885
2918
  this.el["Expressive_Collapsible"] = void 0;
2886
2919
  }
2887
- _setupEventHandlers() {
2888
- this.el.addEventListener("click", this._handleCollapsibleClick);
2889
- this._headers.forEach(
2890
- (header) => header.addEventListener("keydown", this._handleCollapsibleKeydown)
2891
- );
2920
+ _collectItems() {
2921
+ const details = Array.from(this.el.querySelectorAll(":scope > details"));
2922
+ if (details.length) return details;
2923
+ return Array.from(this.el.children).filter((c) => c.tagName === "LI");
2892
2924
  }
2893
- _removeEventHandlers() {
2894
- this.el.removeEventListener("click", this._handleCollapsibleClick);
2895
- this._headers.forEach(
2896
- (header) => header.removeEventListener("keydown", this._handleCollapsibleKeydown)
2897
- );
2925
+ _setupItems() {
2926
+ if (this.options.accordion) {
2927
+ this._items.forEach((item) => {
2928
+ if (item instanceof HTMLDetailsElement && !item.getAttribute("name")) {
2929
+ item.setAttribute("name", this._groupName);
2930
+ }
2931
+ });
2932
+ }
2933
+ const opened = this._items.filter((item) => this._isOpen(item));
2934
+ if (this.options.accordion && opened.length > 1) {
2935
+ opened.slice(1).forEach((item) => {
2936
+ if (item instanceof HTMLDetailsElement) item.open = false;
2937
+ else item.classList.remove("active");
2938
+ });
2939
+ }
2940
+ this._items.forEach((item) => this._syncAria(item));
2898
2941
  }
2899
- _setExpanded(li) {
2900
- li.style.maxHeight = li.scrollHeight + "px";
2942
+ _isOpen(item) {
2943
+ if (item instanceof HTMLDetailsElement) return item.open;
2944
+ return item.classList.contains("active");
2901
2945
  }
2902
- _animateIn(index) {
2903
- const li = this.el.children[index];
2904
- if (!li) return;
2905
- const body = li.querySelector(".collapsible-body");
2906
- const duration = this.options.inDuration;
2907
- body.style.transition = `max-height ${duration}ms ease-out`;
2908
- this._setExpanded(body);
2909
- setTimeout(() => {
2910
- if (typeof this.options.onOpenEnd === "function") {
2911
- this.options.onOpenEnd.call(this, li);
2912
- }
2913
- }, duration);
2946
+ _setOpen(item, open) {
2947
+ if (item instanceof HTMLDetailsElement) {
2948
+ item.open = open;
2949
+ return;
2950
+ }
2951
+ item.classList.toggle("active", open);
2952
+ this._syncAria(item);
2953
+ this._notify(item, open);
2954
+ }
2955
+ _headerOf(item) {
2956
+ if (item instanceof HTMLDetailsElement) {
2957
+ return item.querySelector(":scope > summary");
2958
+ }
2959
+ return item.querySelector(":scope > .collapsible-header");
2960
+ }
2961
+ _wrapDetailsBodies() {
2962
+ this._items.forEach((item) => {
2963
+ if (!(item instanceof HTMLDetailsElement)) return;
2964
+ if (item.querySelector(":scope > .collapsible-body")) return;
2965
+ const body = document.createElement("div");
2966
+ body.className = "collapsible-body";
2967
+ Array.from(item.childNodes).forEach((node) => {
2968
+ if (node instanceof HTMLElement && node.tagName === "SUMMARY") return;
2969
+ body.appendChild(node);
2970
+ });
2971
+ item.appendChild(body);
2972
+ this._wrappedBodies.push(body);
2973
+ });
2914
2974
  }
2915
- _animateOut(index) {
2916
- const li = this.el.children[index];
2917
- if (!li) return;
2918
- const body = li.querySelector(".collapsible-body");
2919
- const duration = this.options.outDuration;
2920
- body.style.transition = `max-height ${duration}ms ease-out`;
2921
- body.style.maxHeight = "0";
2922
- setTimeout(() => {
2923
- if (typeof this.options.onCloseEnd === "function") {
2924
- this.options.onCloseEnd.call(this, li);
2925
- }
2926
- }, duration);
2975
+ _unwrapDetailsBodies() {
2976
+ this._wrappedBodies.forEach((body) => {
2977
+ const parent = body.parentNode;
2978
+ if (!parent) return;
2979
+ while (body.firstChild) parent.insertBefore(body.firstChild, body);
2980
+ body.remove();
2981
+ });
2982
+ this._wrappedBodies = [];
2983
+ }
2984
+ _bodyOf(item) {
2985
+ return item.querySelector(":scope > .collapsible-body");
2986
+ }
2987
+ _syncAria(item) {
2988
+ if (item instanceof HTMLDetailsElement) return;
2989
+ const header = this._headerOf(item);
2990
+ const body = this._bodyOf(item);
2991
+ if (!header) return;
2992
+ header.setAttribute("aria-expanded", String(this._isOpen(item)));
2993
+ if (body) {
2994
+ if (!body.id) body.id = `collapsible-body-${Utils.guid()}`;
2995
+ header.setAttribute("aria-controls", body.id);
2996
+ }
2997
+ if (!header.matches("a, button, summary")) {
2998
+ header.setAttribute("role", "button");
2999
+ if (header.tabIndex < 0) header.tabIndex = 0;
3000
+ }
3001
+ }
3002
+ _setupEventHandlers() {
3003
+ this.el.addEventListener("click", this._handleClick);
3004
+ this.el.addEventListener("keydown", this._handleKeydown);
3005
+ this.el.addEventListener("toggle", this._handleToggle, true);
3006
+ }
3007
+ _removeEventHandlers() {
3008
+ this.el.removeEventListener("click", this._handleClick);
3009
+ this.el.removeEventListener("keydown", this._handleKeydown);
3010
+ this.el.removeEventListener("toggle", this._handleToggle, true);
3011
+ }
3012
+ _notify(item, opening) {
3013
+ const start = opening ? this.options.onOpenStart : this.options.onCloseStart;
3014
+ if (typeof start === "function") start.call(this, item);
3015
+ const end = opening ? this.options.onOpenEnd : this.options.onCloseEnd;
3016
+ if (typeof end !== "function") return;
3017
+ const target = this._bodyOf(item) ?? item;
3018
+ let done = false;
3019
+ const finish = () => {
3020
+ if (done) return;
3021
+ done = true;
3022
+ target.removeEventListener("transitionend", onEnd);
3023
+ end.call(this, item);
3024
+ };
3025
+ const onEnd = (ev) => {
3026
+ if (ev.target !== target) return;
3027
+ finish();
3028
+ };
3029
+ target.addEventListener("transitionend", onEnd);
3030
+ this._endTimers.push(window.setTimeout(finish, this._durationMs() + 50));
3031
+ }
3032
+ _durationMs() {
3033
+ const raw = getComputedStyle(this.el).getPropertyValue("--md-comp-collapsible-duration").trim();
3034
+ if (!raw) return 500;
3035
+ if (raw.endsWith("ms")) return parseFloat(raw);
3036
+ if (raw.endsWith("s")) return parseFloat(raw) * 1e3;
3037
+ const n = parseFloat(raw);
3038
+ return Number.isFinite(n) ? n : 500;
2927
3039
  }
2928
3040
  };
2929
3041
 
@@ -4723,38 +4835,22 @@ var Modal = _Modal;
4723
4835
  // src/ts/components/parallax.ts
4724
4836
  var _defaults15 = {
4725
4837
  responsiveThreshold: 0
4726
- // breakpoint for swipeable
4727
4838
  };
4728
- var _Parallax = class _Parallax extends Component {
4839
+ var Parallax = class _Parallax extends Component {
4729
4840
  constructor(el, options) {
4730
4841
  super(el, options, _Parallax);
4731
- this._handleImageLoad = () => {
4732
- this._updateParallax();
4733
- };
4734
4842
  this.el["Expressive_Parallax"] = this;
4735
4843
  this.options = {
4736
4844
  ..._Parallax.defaults,
4737
4845
  ...options
4738
4846
  };
4739
- this._enabled = window.innerWidth > this.options.responsiveThreshold;
4740
- this._img = this.el.querySelector("img");
4741
- if (!this._img) {
4742
- console.error("Parallax: no <img> to move inside the .parallax element");
4743
- return;
4847
+ if (!this.el.querySelector("img, video")) {
4848
+ console.error("Parallax: no <img> or <video> to move inside the .parallax element");
4744
4849
  }
4745
- this._updateParallax();
4746
- this._setupEventHandlers();
4747
- this._setupStyles();
4748
- _Parallax._parallaxes.push(this);
4749
4850
  }
4750
4851
  static get defaults() {
4751
4852
  return _defaults15;
4752
4853
  }
4753
- /**
4754
- * Initializes instances of Parallax.
4755
- * @param els HTML elements.
4756
- * @param options Component options.
4757
- */
4758
4854
  static init(els, options = {}) {
4759
4855
  return super.init(els, options, _Parallax);
4760
4856
  }
@@ -4762,84 +4858,9 @@ var _Parallax = class _Parallax extends Component {
4762
4858
  return el["Expressive_Parallax"];
4763
4859
  }
4764
4860
  destroy() {
4765
- const index = _Parallax._parallaxes.indexOf(this);
4766
- if (index >= 0) _Parallax._parallaxes.splice(index, 1);
4767
- if (this._img) this._img.style.transform = "";
4768
- this._removeEventHandlers();
4769
4861
  this.el["Expressive_Parallax"] = void 0;
4770
4862
  }
4771
- static _handleScroll() {
4772
- for (let i = 0; i < _Parallax._parallaxes.length; i++) {
4773
- const parallaxInstance = _Parallax._parallaxes[i];
4774
- parallaxInstance._updateParallax.call(parallaxInstance);
4775
- }
4776
- }
4777
- static _handleWindowResize() {
4778
- for (let i = 0; i < _Parallax._parallaxes.length; i++) {
4779
- const parallaxInstance = _Parallax._parallaxes[i];
4780
- parallaxInstance._enabled = window.innerWidth > parallaxInstance.options.responsiveThreshold;
4781
- }
4782
- }
4783
- _setupEventHandlers() {
4784
- this._img?.addEventListener("load", this._handleImageLoad);
4785
- if (_Parallax._parallaxes.length === 0) {
4786
- if (!_Parallax._handleWindowResizeThrottled) {
4787
- _Parallax._handleWindowResizeThrottled = Utils.throttle(_Parallax._handleWindowResize, 100);
4788
- }
4789
- window.addEventListener("scroll", _Parallax._requestScrollUpdate, { passive: true });
4790
- window.addEventListener("resize", _Parallax._handleWindowResizeThrottled, { passive: true });
4791
- }
4792
- }
4793
- _removeEventHandlers() {
4794
- this._img?.removeEventListener("load", this._handleImageLoad);
4795
- if (_Parallax._parallaxes.length === 0) {
4796
- window.removeEventListener("scroll", _Parallax._requestScrollUpdate);
4797
- window.removeEventListener("resize", _Parallax._handleWindowResizeThrottled);
4798
- if (_Parallax._scrollFrame !== null) {
4799
- cancelAnimationFrame(_Parallax._scrollFrame);
4800
- _Parallax._scrollFrame = null;
4801
- }
4802
- }
4803
- }
4804
- _setupStyles() {
4805
- if (this._img) this._img.style.opacity = "1";
4806
- }
4807
- _updateParallax() {
4808
- const rect = this.el.getBoundingClientRect();
4809
- const containerHeight = rect.height > 0 ? this.el.parentElement.offsetHeight : 500;
4810
- const imgHeight = this._img.offsetHeight;
4811
- const parallaxDist = imgHeight - containerHeight;
4812
- const top = rect.top + window.scrollY - document.documentElement.clientTop;
4813
- const bottom = top + containerHeight;
4814
- const scrollTop = Utils.getDocumentScrollTop();
4815
- const windowHeight = window.innerHeight;
4816
- const windowBottom = scrollTop + windowHeight;
4817
- const percentScrolled = (windowBottom - top) / (containerHeight + windowHeight);
4818
- const parallax = parallaxDist * percentScrolled;
4819
- if (!this._enabled) {
4820
- this._img.style.transform = "";
4821
- } else if (bottom > scrollTop && top < scrollTop + windowHeight) {
4822
- this._img.style.transform = `translate3D(-50%, ${parallax}px, 0)`;
4823
- }
4824
- }
4825
4863
  };
4826
- _Parallax._parallaxes = [];
4827
- _Parallax._scrollFrame = null;
4828
- /**
4829
- * Coalesce scroll events onto animation frames.
4830
- *
4831
- * The handler reads layout and then writes a transform, so running it more
4832
- * than once per frame is wasted work - and the 5ms throttle it replaces was
4833
- * short enough to let several run per frame anyway.
4834
- */
4835
- _Parallax._requestScrollUpdate = () => {
4836
- if (_Parallax._scrollFrame !== null) return;
4837
- _Parallax._scrollFrame = requestAnimationFrame(() => {
4838
- _Parallax._scrollFrame = null;
4839
- _Parallax._handleScroll();
4840
- });
4841
- };
4842
- var Parallax = _Parallax;
4843
4864
 
4844
4865
  // src/ts/components/pushpin.ts
4845
4866
  var _defaults16 = {
@@ -6079,60 +6100,96 @@ var _defaults21 = {
6079
6100
  var _TapTarget = class _TapTarget extends Component {
6080
6101
  constructor(el, options) {
6081
6102
  super(el, options, _TapTarget);
6082
- // Built once per instance; see the note in carousel.ts - the previous form
6083
- // returned a new throttled function on every resize and never invoked it.
6084
- this._handleThrottledResize = Utils.throttle(() => this._handleResize(), 200);
6085
- this._handleKeyboardInteraction = (e) => {
6086
- if (Utils.keys.ENTER.includes(e.key)) {
6087
- this._handleTargetToggle();
6088
- }
6089
- };
6090
- this._handleTargetToggle = () => {
6091
- if (!this.isOpen) this.open();
6092
- else this.close();
6093
- };
6094
- /*_handleOriginClick = () => {
6095
- this.close();
6096
- }*/
6097
- this._handleResize = () => {
6098
- this._calculatePositioning();
6099
- };
6100
- this._handleDocumentClick = (e) => {
6101
- const target = e.target;
6102
- if (!(target instanceof Element)) return;
6103
- if (!this.originEl.contains(target) && !target.closest(".tap-target-wrapper")) {
6104
- this.close();
6105
- }
6106
- };
6103
+ this.wrapper = null;
6104
+ this.waveEl = null;
6105
+ this.contentEl = null;
6106
+ this._createdWrapper = false;
6107
+ this._createdWave = false;
6108
+ this._usePopover = false;
6109
+ this._liftedStatic = false;
6110
+ this._opening = false;
6107
6111
  /**
6108
6112
  * Open Tap Target.
6109
6113
  */
6110
6114
  this.open = () => {
6111
- if (this.isOpen) return;
6115
+ if (this.isOpen || !this.originEl || !this.wrapper) return;
6116
+ _TapTarget._taptargets.forEach((other) => {
6117
+ if (other !== this) other.close();
6118
+ });
6119
+ this._calculatePositioning();
6112
6120
  if (typeof this.options.onOpen === "function") {
6113
6121
  this.options.onOpen.call(this, this.originEl);
6114
6122
  }
6115
6123
  this.isOpen = true;
6116
6124
  this.wrapper.classList.add("open");
6117
- this.wrapper.ariaExpanded = "true";
6118
- document.body.addEventListener("click", this._handleDocumentClick, true);
6119
- document.body.addEventListener("keypress", this._handleDocumentClick, true);
6120
- document.body.addEventListener("touchend", this._handleDocumentClick);
6125
+ this.originEl.setAttribute("aria-expanded", "true");
6126
+ if (!this._usePopover) {
6127
+ this.originEl.classList.add("tap-target-origin");
6128
+ if (getComputedStyle(this.originEl).position === "static") {
6129
+ this.originEl.style.position = "relative";
6130
+ this._liftedStatic = true;
6131
+ }
6132
+ }
6133
+ if (this._usePopover) {
6134
+ this._opening = true;
6135
+ try {
6136
+ this.wrapper.showPopover();
6137
+ } catch {
6138
+ }
6139
+ requestAnimationFrame(() => {
6140
+ this._opening = false;
6141
+ });
6142
+ }
6121
6143
  };
6122
6144
  /**
6123
6145
  * Close Tap Target.
6124
6146
  */
6125
6147
  this.close = () => {
6126
6148
  if (!this.isOpen) return;
6127
- if (typeof this.options.onClose === "function") {
6149
+ if (typeof this.options.onClose === "function" && this.originEl) {
6128
6150
  this.options.onClose.call(this, this.originEl);
6129
6151
  }
6130
6152
  this.isOpen = false;
6131
- this.wrapper.classList.remove("open");
6132
- this.wrapper.ariaExpanded = "false";
6133
- document.body.removeEventListener("click", this._handleDocumentClick, true);
6134
- document.body.removeEventListener("keypress", this._handleDocumentClick, true);
6135
- document.body.removeEventListener("touchend", this._handleDocumentClick);
6153
+ this.wrapper?.classList.remove("open");
6154
+ this.originEl?.setAttribute("aria-expanded", "false");
6155
+ this.originEl?.classList.remove("tap-target-origin");
6156
+ if (this._liftedStatic && this.originEl) {
6157
+ this.originEl.style.position = "";
6158
+ this._liftedStatic = false;
6159
+ }
6160
+ if (this._usePopover && this.wrapper) {
6161
+ try {
6162
+ this.wrapper.hidePopover();
6163
+ } catch {
6164
+ }
6165
+ }
6166
+ };
6167
+ // Built once per instance; a fresh throttle per event never fires.
6168
+ this._handleThrottledResize = Utils.throttle(() => this._handleResize(), 200);
6169
+ this._handleOriginKeydown = (e) => {
6170
+ if (Utils.keys.ENTER.includes(e.key) || e.key === " ") {
6171
+ e.preventDefault();
6172
+ this._handleTargetToggle();
6173
+ }
6174
+ };
6175
+ this._handleTargetToggle = () => {
6176
+ if (!this.isOpen) this.open();
6177
+ else this.close();
6178
+ };
6179
+ this._handleResize = () => {
6180
+ if (this.isOpen) this._calculatePositioning();
6181
+ };
6182
+ this._handlePopoverToggle = (e) => {
6183
+ const state = "newState" in e ? String(e.newState) : "";
6184
+ if (state === "open") return;
6185
+ if (this._opening) {
6186
+ try {
6187
+ this.wrapper?.showPopover();
6188
+ } catch {
6189
+ }
6190
+ return;
6191
+ }
6192
+ if (this.isOpen) this.close();
6136
6193
  };
6137
6194
  this.el["Expressive_TapTarget"] = this;
6138
6195
  this.options = {
@@ -6145,7 +6202,6 @@ var _TapTarget = class _TapTarget extends Component {
6145
6202
  console.error(`TapTarget: no element with id "${el.dataset.target ?? ""}" to attach to`);
6146
6203
  return;
6147
6204
  }
6148
- this.originEl.tabIndex = 0;
6149
6205
  this._setup();
6150
6206
  this._calculatePositioning();
6151
6207
  this._setupEventHandlers();
@@ -6154,11 +6210,6 @@ var _TapTarget = class _TapTarget extends Component {
6154
6210
  static get defaults() {
6155
6211
  return _defaults21;
6156
6212
  }
6157
- /**
6158
- * Initializes instances of TapTarget.
6159
- * @param els HTML elements.
6160
- * @param options Component options.
6161
- */
6162
6213
  static init(els, options = {}) {
6163
6214
  return super.init(els, options, _TapTarget);
6164
6215
  }
@@ -6168,116 +6219,95 @@ var _TapTarget = class _TapTarget extends Component {
6168
6219
  destroy() {
6169
6220
  this.close();
6170
6221
  this._removeEventHandlers();
6222
+ this._teardownDom();
6171
6223
  this.el["Expressive_TapTarget"] = void 0;
6172
6224
  const index = _TapTarget._taptargets.indexOf(this);
6173
- if (index >= 0) {
6174
- _TapTarget._taptargets.splice(index, 1);
6175
- }
6176
- }
6177
- _setupEventHandlers() {
6178
- if (!this.originEl) return;
6179
- this.originEl.addEventListener("click", this._handleTargetToggle);
6180
- this.originEl.addEventListener("keypress", this._handleKeyboardInteraction, true);
6181
- window.addEventListener("resize", this._handleThrottledResize);
6182
- }
6183
- _removeEventHandlers() {
6184
- if (!this.originEl) return;
6185
- this.originEl.removeEventListener("click", this._handleTargetToggle);
6186
- this.originEl.removeEventListener("keypress", this._handleKeyboardInteraction, true);
6187
- window.removeEventListener("resize", this._handleThrottledResize);
6225
+ if (index >= 0) _TapTarget._taptargets.splice(index, 1);
6188
6226
  }
6189
6227
  _setup() {
6190
- this.wrapper = this.el.parentElement;
6191
- this.waveEl = this.wrapper.querySelector(".tap-target-wave");
6192
- this.el.parentElement.ariaExpanded = "false";
6193
- this.originEl.style.zIndex = "1002";
6194
- this.contentEl = this.el.querySelector(".tap-target-content");
6195
- if (!this.wrapper.classList.contains(".tap-target-wrapper")) {
6228
+ const parent = this.el.parentElement;
6229
+ if (parent?.classList.contains("tap-target-wrapper")) {
6230
+ this.wrapper = parent;
6231
+ } else {
6196
6232
  this.wrapper = document.createElement("div");
6197
6233
  this.wrapper.classList.add("tap-target-wrapper");
6198
6234
  this.el.before(this.wrapper);
6199
6235
  this.wrapper.append(this.el);
6236
+ this._createdWrapper = true;
6200
6237
  }
6238
+ this.contentEl = this.el.querySelector(".tap-target-content");
6201
6239
  if (!this.contentEl) {
6202
6240
  this.contentEl = document.createElement("div");
6203
- this.contentEl.classList.add("tap-target-content");
6241
+ this.contentEl.className = "tap-target-content";
6242
+ while (this.el.firstChild) this.contentEl.append(this.el.firstChild);
6204
6243
  this.el.append(this.contentEl);
6205
6244
  }
6245
+ this.waveEl = this.el.querySelector(".tap-target-wave") ?? this.wrapper.querySelector(".tap-target-wave");
6206
6246
  if (!this.waveEl) {
6207
6247
  this.waveEl = document.createElement("div");
6208
- this.waveEl.classList.add("tap-target-wave");
6209
- this.wrapper.append(this.waveEl);
6248
+ this.waveEl.className = "tap-target-wave";
6249
+ this._createdWave = true;
6250
+ }
6251
+ this.el.prepend(this.waveEl);
6252
+ this.el.setAttribute("role", "dialog");
6253
+ this._labelFromHeading();
6254
+ const origin = this.originEl;
6255
+ origin.setAttribute("aria-expanded", "false");
6256
+ origin.setAttribute("aria-controls", this.el.id || this._ensureId());
6257
+ this._usePopover = typeof this.wrapper.showPopover === "function";
6258
+ if (this._usePopover) {
6259
+ this.wrapper.setAttribute("popover", "auto");
6260
+ }
6261
+ }
6262
+ _ensureId() {
6263
+ if (!this.el.id) this.el.id = `tap-target-${Utils.guid()}`;
6264
+ return this.el.id;
6265
+ }
6266
+ _labelFromHeading() {
6267
+ const heading = this.contentEl?.querySelector("h1, h2, h3, h4, h5, h6");
6268
+ if (!heading) return;
6269
+ if (!heading.id) heading.id = `tap-target-title-${Utils.guid()}`;
6270
+ this.el.setAttribute("aria-labelledby", heading.id);
6271
+ }
6272
+ _teardownDom() {
6273
+ if (this._createdWave) this.waveEl?.remove();
6274
+ if (this._createdWrapper && this.wrapper) {
6275
+ this.wrapper.before(this.el);
6276
+ this.wrapper.remove();
6277
+ }
6278
+ this.wrapper = null;
6279
+ this.waveEl = null;
6280
+ }
6281
+ _setupEventHandlers() {
6282
+ if (!this.originEl) return;
6283
+ this.originEl.addEventListener("click", this._handleTargetToggle);
6284
+ if (!this.originEl.matches('button, a, [tabindex], .btn, [role="button"]')) {
6285
+ this.originEl.tabIndex = 0;
6286
+ this.originEl.addEventListener("keydown", this._handleOriginKeydown);
6210
6287
  }
6288
+ this.wrapper?.addEventListener("toggle", this._handlePopoverToggle);
6289
+ window.addEventListener("resize", this._handleThrottledResize, { passive: true });
6211
6290
  }
6212
- _offset(el) {
6213
- const box = el.getBoundingClientRect();
6214
- const docElem = document.documentElement;
6215
- return {
6216
- top: box.top + window.pageYOffset - docElem.clientTop,
6217
- left: box.left + window.pageXOffset - docElem.clientLeft
6218
- };
6291
+ _removeEventHandlers() {
6292
+ if (!this.originEl) return;
6293
+ this.originEl.removeEventListener("click", this._handleTargetToggle);
6294
+ this.originEl.removeEventListener("keydown", this._handleOriginKeydown);
6295
+ this.wrapper?.removeEventListener("toggle", this._handlePopoverToggle);
6296
+ window.removeEventListener("resize", this._handleThrottledResize);
6219
6297
  }
6220
6298
  _calculatePositioning() {
6221
- let isFixed = getComputedStyle(this.originEl).position === "fixed";
6222
- if (!isFixed) {
6223
- let currentElem = this.originEl;
6224
- const parents = [];
6225
- while ((currentElem = currentElem.parentNode) && currentElem !== document)
6226
- parents.push(currentElem);
6227
- for (let i = 0; i < parents.length; i++) {
6228
- isFixed = getComputedStyle(parents[i]).position === "fixed";
6229
- if (isFixed) break;
6230
- }
6231
- }
6232
- const originWidth = this.originEl.offsetWidth;
6233
- const originHeight = this.originEl.offsetHeight;
6234
- const originOffset = this._offset(this.originEl);
6235
- const originTop = isFixed ? originOffset.top - Utils.getDocumentScrollTop() : originOffset.top;
6236
- const originLeft = isFixed ? originOffset.left - Utils.getDocumentScrollLeft() : originOffset.left;
6237
- const windowWidth = window.innerWidth;
6238
- const windowHeight = window.innerHeight;
6239
- const scrollBarWidth = windowWidth - document.documentElement.clientWidth;
6240
- const centerX = windowWidth / 2;
6241
- const centerY = windowHeight / 2;
6242
- const isLeft = originLeft <= centerX;
6243
- const isRight = originLeft > centerX;
6244
- const isTop = originTop <= centerY;
6245
- const isBottom = originTop > centerY;
6246
- const isCenterX = originLeft >= windowWidth * 0.25 && originLeft <= windowWidth * 0.75;
6247
- const tapTargetWidth = this.el.offsetWidth;
6248
- const tapTargetHeight = this.el.offsetHeight;
6249
- const tapTargetTop = originTop + originHeight / 2 - tapTargetHeight / 2;
6250
- const tapTargetLeft = originLeft + originWidth / 2 - tapTargetWidth / 2;
6251
- const tapTargetPosition = isFixed ? "fixed" : "absolute";
6252
- const tapTargetTextWidth = isCenterX ? tapTargetWidth : tapTargetWidth / 2 + originWidth;
6253
- const tapTargetTextHeight = tapTargetHeight / 2;
6254
- const tapTargetTextTop = isTop ? tapTargetHeight / 2 : 0;
6255
- const tapTargetTextBottom = 0;
6256
- const tapTargetTextLeft = isLeft && !isCenterX ? tapTargetWidth / 2 - originWidth : 0;
6257
- const tapTargetTextRight = 0;
6258
- const tapTargetTextPadding = originWidth;
6259
- const tapTargetTextAlign = isBottom ? "bottom" : "top";
6260
- const tapTargetWaveWidth = originWidth > originHeight ? originWidth * 2 : originWidth * 2;
6261
- const tapTargetWaveHeight = tapTargetWaveWidth;
6262
- const tapTargetWaveTop = tapTargetHeight / 2 - tapTargetWaveHeight / 2;
6263
- const tapTargetWaveLeft = tapTargetWidth / 2 - tapTargetWaveWidth / 2;
6264
- this.wrapper.style.top = isTop ? tapTargetTop + "px" : "";
6265
- this.wrapper.style.right = isRight ? windowWidth - tapTargetLeft - tapTargetWidth - scrollBarWidth + "px" : "";
6266
- this.wrapper.style.bottom = isBottom ? windowHeight - tapTargetTop - tapTargetHeight + "px" : "";
6267
- this.wrapper.style.left = isLeft ? tapTargetLeft + "px" : "";
6268
- this.wrapper.style.position = tapTargetPosition;
6269
- this.contentEl.style.width = tapTargetTextWidth + "px";
6270
- this.contentEl.style.height = tapTargetTextHeight + "px";
6271
- this.contentEl.style.top = tapTargetTextTop + "px";
6272
- this.contentEl.style.right = tapTargetTextRight + "px";
6273
- this.contentEl.style.bottom = tapTargetTextBottom + "px";
6274
- this.contentEl.style.left = tapTargetTextLeft + "px";
6275
- this.contentEl.style.padding = tapTargetTextPadding + "px";
6276
- this.contentEl.style.verticalAlign = tapTargetTextAlign;
6277
- this.waveEl.style.top = tapTargetWaveTop + "px";
6278
- this.waveEl.style.left = tapTargetWaveLeft + "px";
6279
- this.waveEl.style.width = tapTargetWaveWidth + "px";
6280
- this.waveEl.style.height = tapTargetWaveHeight + "px";
6299
+ if (!this.originEl || !this.wrapper) return;
6300
+ const rect = this.originEl.getBoundingClientRect();
6301
+ const x = rect.left + rect.width / 2;
6302
+ const y = rect.top + rect.height / 2;
6303
+ const size = Math.max(rect.width, rect.height, 1);
6304
+ this.wrapper.style.setProperty("--md-comp-tap-target-x", `${x}px`);
6305
+ this.wrapper.style.setProperty("--md-comp-tap-target-y", `${y}px`);
6306
+ this.wrapper.style.setProperty("--md-comp-tap-target-origin-size", `${size}px`);
6307
+ const vertical = y <= window.innerHeight / 2 ? "top" : "bottom";
6308
+ const onCenter = x >= window.innerWidth * 0.25 && x <= window.innerWidth * 0.75;
6309
+ const horizontal = onCenter ? "center" : x <= window.innerWidth / 2 ? "left" : "right";
6310
+ this.wrapper.dataset.edge = `${vertical}-${horizontal}`;
6281
6311
  }
6282
6312
  };
6283
6313
  _TapTarget._taptargets = [];
@@ -7021,7 +7051,7 @@ var _Toast = class _Toast {
7021
7051
  close.className = "circle";
7022
7052
  close.setAttribute("aria-label", "Dismiss");
7023
7053
  const icon = document.createElement("i");
7024
- icon.className = "material-icons";
7054
+ icon.className = "material-symbols";
7025
7055
  icon.setAttribute("aria-hidden", "true");
7026
7056
  icon.textContent = "close";
7027
7057
  close.appendChild(icon);
@@ -7386,7 +7416,7 @@ function AutoInit(context = document.body, options) {
7386
7416
  }
7387
7417
 
7388
7418
  // src/ts/index.ts
7389
- var version = "0.1.0";
7419
+ var version = "0.3.0";
7390
7420
  if (typeof document !== "undefined") {
7391
7421
  document.addEventListener("keydown", Utils.docHandleKeydown, true);
7392
7422
  document.addEventListener("keyup", Utils.docHandleKeyup, true);
@@ -7398,6 +7428,7 @@ Chips.Init();
7398
7428
  Waves.Init();
7399
7429
  Range.Init();
7400
7430
  Cards.Init();
7431
+ Dialogs.Init();
7401
7432
  export {
7402
7433
  AutoInit,
7403
7434
  Autocomplete,
@@ -7407,6 +7438,7 @@ export {
7407
7438
  Chips,
7408
7439
  Collapsible,
7409
7440
  Datepicker,
7441
+ Dialogs,
7410
7442
  Dropdown,
7411
7443
  FloatingActionButton,
7412
7444
  FormSelect,