@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
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  Chips: () => Chips,
34
34
  Collapsible: () => Collapsible,
35
35
  Datepicker: () => Datepicker,
36
+ Dialogs: () => Dialogs,
36
37
  Dropdown: () => Dropdown,
37
38
  FloatingActionButton: () => FloatingActionButton,
38
39
  FormSelect: () => FormSelect,
@@ -1475,7 +1476,7 @@ var _defaults4 = {
1475
1476
  data: [],
1476
1477
  placeholder: "",
1477
1478
  secondaryPlaceholder: "",
1478
- closeIconClass: "material-icons",
1479
+ closeIconClass: "material-symbols",
1479
1480
  autocompleteOptions: {},
1480
1481
  autocompleteOnly: false,
1481
1482
  limit: Infinity,
@@ -1892,6 +1893,74 @@ var Range = class _Range extends Component {
1892
1893
  }
1893
1894
  };
1894
1895
 
1896
+ // src/ts/behaviors/dialogs.ts
1897
+ var _Dialogs = class _Dialogs {
1898
+ static Init() {
1899
+ Utils.onDocumentReady(() => {
1900
+ document.addEventListener("pointerdown", _Dialogs._onPointerDown, { passive: true });
1901
+ document.addEventListener("pointerup", _Dialogs._onPointerUp, { passive: true });
1902
+ document.addEventListener("pointercancel", _Dialogs._onPointerCancel, { passive: true });
1903
+ });
1904
+ }
1905
+ static _onPointerDown(event) {
1906
+ if (!_Dialogs._isPrimaryPress(event)) {
1907
+ _Dialogs._down = null;
1908
+ return;
1909
+ }
1910
+ const dialog = _Dialogs._backdropDialog(event);
1911
+ if (!dialog || !_Dialogs._lightDismissible(dialog) || !_Dialogs._outside(event, dialog)) {
1912
+ _Dialogs._down = null;
1913
+ return;
1914
+ }
1915
+ _Dialogs._down = { dialog, pointerId: event.pointerId };
1916
+ }
1917
+ static _onPointerUp(event) {
1918
+ const started = _Dialogs._down;
1919
+ _Dialogs._down = null;
1920
+ if (!started || started.pointerId !== event.pointerId) return;
1921
+ if (!event.isPrimary) return;
1922
+ if (!started.dialog.isConnected || !started.dialog.open) return;
1923
+ if (!_Dialogs._lightDismissible(started.dialog)) return;
1924
+ if (_Dialogs._backdropDialog(event) !== started.dialog) return;
1925
+ if (_Dialogs._outside(event, started.dialog)) {
1926
+ started.dialog.close();
1927
+ }
1928
+ }
1929
+ static _onPointerCancel() {
1930
+ _Dialogs._down = null;
1931
+ }
1932
+ /** Backdrop hits retarget to the dialog itself, never a child. */
1933
+ static _backdropDialog(event) {
1934
+ const target = event.target;
1935
+ if (!(target instanceof HTMLDialogElement) || !target.open) return null;
1936
+ return target;
1937
+ }
1938
+ static _outside(event, dialog) {
1939
+ const rect = dialog.getBoundingClientRect();
1940
+ return event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom;
1941
+ }
1942
+ static _isPrimaryPress(event) {
1943
+ if (!event.isPrimary) return false;
1944
+ return event.pointerType !== "mouse" || event.button === 0;
1945
+ }
1946
+ static _lightDismissible(dialog) {
1947
+ const value = _Dialogs._closedBy(dialog);
1948
+ if (value === "none" || value === "closerequest") return false;
1949
+ if (value === "any" && "closedBy" in HTMLDialogElement.prototype) return false;
1950
+ return true;
1951
+ }
1952
+ // Read the attribute only. The IDL default for an unset closedby is
1953
+ // "closerequest" (Escape, no light dismiss) — treating that as an
1954
+ // author opt-out made every dialog undismissable from the scrim.
1955
+ static _closedBy(dialog) {
1956
+ const attr = dialog.getAttribute("closedby");
1957
+ if (attr === "any" || attr === "closerequest" || attr === "none") return attr;
1958
+ return null;
1959
+ }
1960
+ };
1961
+ _Dialogs._down = null;
1962
+ var Dialogs = _Dialogs;
1963
+
1895
1964
  // src/ts/behaviors/forms.ts
1896
1965
  var Forms = class _Forms {
1897
1966
  /**
@@ -2046,83 +2115,76 @@ var _defaults6 = {
2046
2115
  var FloatingActionButton = class _FloatingActionButton extends Component {
2047
2116
  constructor(el, options) {
2048
2117
  super(el, options, _FloatingActionButton);
2049
- /** Toolbar-mode backdrop, kept so closing can take it back out. */
2050
- this._backdrop = null;
2051
- this._handleFABClick = () => {
2052
- this._handleFABToggle();
2053
- };
2054
- this._handleFABKeyPress = (e) => {
2055
- if (Utils.keys.ENTER.includes(e.key)) {
2056
- this._handleFABToggle();
2057
- }
2058
- };
2059
- this._handleFABToggle = () => {
2060
- if (this.isOpen) {
2061
- this.close();
2062
- } else {
2063
- this.open();
2064
- }
2065
- };
2066
- this._handleDocumentClick = (e) => {
2067
- const elem = e.target;
2068
- if (elem !== this._menu) this.close();
2069
- };
2070
2118
  /**
2071
2119
  * Open FAB.
2072
2120
  */
2073
2121
  this.open = () => {
2074
2122
  if (this.isOpen) return;
2075
- if (this.options.toolbarEnabled) this._animateInToolbar();
2076
- else this._animateInFAB();
2077
2123
  this.isOpen = true;
2124
+ this.el.classList.add("active");
2125
+ this._anchor?.setAttribute("aria-expanded", "true");
2126
+ document.addEventListener("click", this._handleDocumentClick);
2127
+ document.addEventListener("keydown", this._handleDocumentKeydown);
2078
2128
  };
2079
2129
  /**
2080
2130
  * Close FAB.
2081
2131
  */
2082
2132
  this.close = () => {
2083
2133
  if (!this.isOpen) return;
2084
- if (this.options.toolbarEnabled) {
2085
- window.removeEventListener("scroll", this.close, true);
2086
- document.body.removeEventListener("click", this._handleDocumentClick, true);
2087
- this._animateOutToolbar();
2088
- } else {
2089
- this._animateOutFAB();
2090
- }
2091
2134
  this.isOpen = false;
2135
+ this.el.classList.remove("active");
2136
+ this._anchor?.setAttribute("aria-expanded", "false");
2137
+ document.removeEventListener("click", this._handleDocumentClick);
2138
+ document.removeEventListener("keydown", this._handleDocumentKeydown);
2139
+ };
2140
+ this._handleFABClick = (e) => {
2141
+ e.stopPropagation();
2142
+ this._toggle();
2143
+ };
2144
+ this._handleAnchorKeydown = (e) => {
2145
+ if (Utils.keys.ENTER.includes(e.key) || e.key === " ") {
2146
+ e.preventDefault();
2147
+ this._toggle();
2148
+ }
2149
+ };
2150
+ this._handleDocumentKeydown = (e) => {
2151
+ if (Utils.keys.ESC.includes(e.key)) this.close();
2152
+ };
2153
+ this._handleDocumentClick = (e) => {
2154
+ const target = e.target;
2155
+ if (!(target instanceof Node) || this.el.contains(target)) return;
2156
+ this.close();
2092
2157
  };
2093
2158
  this.el["Expressive_FloatingActionButton"] = this;
2159
+ const fromMarkup = this._directionFromClass();
2160
+ const toolbar = el.classList.contains("toolbar") || options.toolbarEnabled === true;
2161
+ const clickToToggle = el.classList.contains("click-to-toggle") || options.hoverEnabled === false;
2094
2162
  this.options = {
2095
2163
  ..._FloatingActionButton.defaults,
2096
- ...options
2164
+ ...options,
2165
+ direction: options.direction ?? fromMarkup ?? _FloatingActionButton.defaults.direction,
2166
+ hoverEnabled: !clickToToggle,
2167
+ toolbarEnabled: toolbar
2097
2168
  };
2098
2169
  this.isOpen = false;
2099
- this._anchor = this.el.querySelector("a, button");
2100
- this._menu = this.el.querySelector("ul");
2101
- this._floatingBtns = Array.from(this.el.querySelectorAll("ul .btn-floating"));
2102
- this._floatingBtnsReverse = this._floatingBtns.reverse();
2103
- this.offsetY = 0;
2104
- this.offsetX = 0;
2170
+ this._anchor = this.el.querySelector(":scope > a, :scope > button");
2171
+ this._menu = this.el.querySelector("ul, menu");
2105
2172
  this.el.classList.add(`direction-${this.options.direction}`);
2106
- if (this._anchor) this._anchor.tabIndex = 0;
2107
- if (this._menu) this._menu.ariaExpanded = "false";
2108
- if (this.options.direction === "top")
2109
- this.offsetY = 40;
2110
- else if (this.options.direction === "right")
2111
- this.offsetX = -40;
2112
- else if (this.options.direction === "bottom")
2113
- this.offsetY = -40;
2114
- else
2115
- this.offsetX = 40;
2173
+ if (clickToToggle) this.el.classList.add("click-to-toggle");
2174
+ if (toolbar) this.el.classList.add("toolbar");
2175
+ if (this._anchor) {
2176
+ this._anchor.setAttribute("aria-haspopup", "menu");
2177
+ this._anchor.setAttribute("aria-expanded", "false");
2178
+ if (this._anchor instanceof HTMLAnchorElement && !this._anchor.hasAttribute("href") && this._anchor.tabIndex < 0) {
2179
+ this._anchor.tabIndex = 0;
2180
+ }
2181
+ }
2182
+ if (this._menu) this._menu.setAttribute("role", "menu");
2116
2183
  this._setupEventHandlers();
2117
2184
  }
2118
2185
  static get defaults() {
2119
2186
  return _defaults6;
2120
2187
  }
2121
- /**
2122
- * Initializes instances of FloatingActionButton.
2123
- * @param els HTML elements.
2124
- * @param options Component options.
2125
- */
2126
2188
  static init(els, options = {}) {
2127
2189
  return super.init(els, options, _FloatingActionButton);
2128
2190
  }
@@ -2130,229 +2192,109 @@ var FloatingActionButton = class _FloatingActionButton extends Component {
2130
2192
  return el["Expressive_FloatingActionButton"];
2131
2193
  }
2132
2194
  destroy() {
2133
- this._removeEventHandlers();
2134
2195
  this.close();
2196
+ this._removeEventHandlers();
2135
2197
  this.el["Expressive_FloatingActionButton"] = void 0;
2136
2198
  }
2199
+ _directionFromClass() {
2200
+ const match = [...this.el.classList].find((c) => c.startsWith("direction-"));
2201
+ const value = match?.slice("direction-".length);
2202
+ if (value === "top" || value === "right" || value === "bottom" || value === "left") {
2203
+ return value;
2204
+ }
2205
+ return null;
2206
+ }
2137
2207
  _setupEventHandlers() {
2138
- if (this.options.hoverEnabled && !this.options.toolbarEnabled) {
2139
- this.el.addEventListener("mouseenter", this.open);
2140
- this.el.addEventListener("mouseleave", this.close);
2141
- } else {
2142
- this.el.addEventListener("click", this._handleFABClick);
2208
+ this.el.addEventListener("click", this._handleFABClick);
2209
+ if (this._anchor instanceof HTMLAnchorElement && !this._anchor.hasAttribute("href")) {
2210
+ this._anchor.addEventListener("keydown", this._handleAnchorKeydown);
2143
2211
  }
2144
- this.el.addEventListener("keypress", this._handleFABKeyPress);
2145
2212
  }
2146
2213
  _removeEventHandlers() {
2147
- if (this.options.hoverEnabled && !this.options.toolbarEnabled) {
2148
- this.el.removeEventListener("mouseenter", this.open);
2149
- this.el.removeEventListener("mouseleave", this.close);
2150
- } else {
2151
- this.el.removeEventListener("click", this._handleFABClick);
2152
- }
2153
- this.el.removeEventListener("keypress", this._handleFABKeyPress);
2154
- }
2155
- _animateInFAB() {
2156
- this.el.classList.add("active");
2157
- if (this._menu) this._menu.ariaExpanded = "true";
2158
- const delayIncrement = 40;
2159
- const duration = 275;
2160
- this._floatingBtnsReverse.forEach((el, index) => {
2161
- const delay = delayIncrement * index;
2162
- el.style.transition = "none";
2163
- el.style.opacity = "0";
2164
- el.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px) scale(0.4)`;
2165
- setTimeout(() => {
2166
- el.style.opacity = "0.4";
2167
- setTimeout(() => {
2168
- el.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`;
2169
- el.style.opacity = "1";
2170
- el.style.transform = "translate(0, 0) scale(1)";
2171
- el.tabIndex = 0;
2172
- }, 1);
2173
- }, delay);
2174
- });
2175
- }
2176
- _animateOutFAB() {
2177
- const duration = 175;
2178
- setTimeout(() => {
2179
- this.el.classList.remove("active");
2180
- if (this._menu) this._menu.ariaExpanded = "false";
2181
- }, duration);
2182
- this._floatingBtnsReverse.forEach((el) => {
2183
- el.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`;
2184
- el.style.opacity = "0";
2185
- el.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px) scale(0.4)`;
2186
- el.tabIndex = -1;
2187
- });
2188
- }
2189
- _animateInToolbar() {
2190
- const windowWidth = window.innerWidth;
2191
- const windowHeight = window.innerHeight;
2192
- const btnRect = this.el.getBoundingClientRect();
2193
- const fabColor = getComputedStyle(this._anchor).backgroundColor;
2194
- const backdrop = document.createElement("div");
2195
- backdrop.classList.add("fab-backdrop");
2196
- backdrop.style.backgroundColor = fabColor;
2197
- this._anchor.append(backdrop);
2198
- this._backdrop = backdrop;
2199
- const backdropWidth = backdrop.clientWidth;
2200
- const scaleFactor = backdropWidth > 0 ? windowWidth / backdropWidth : 1;
2201
- this.offsetX = btnRect.left - windowWidth / 2 + btnRect.width / 2;
2202
- this.offsetY = windowHeight - btnRect.bottom;
2203
- this.btnBottom = btnRect.bottom;
2204
- this.btnLeft = btnRect.left;
2205
- this.btnWidth = btnRect.width;
2206
- this.el.classList.add("active");
2207
- this.el.style.textAlign = "center";
2208
- this.el.style.width = "100%";
2209
- this.el.style.bottom = "0";
2210
- this.el.style.left = "0";
2211
- this.el.style.transform = "translateX(" + this.offsetX + "px)";
2212
- this.el.style.transition = "none";
2213
- if (this._menu) this._menu.ariaExpanded = "true";
2214
- this._anchor.style.transform = `translateY(${this.offsetY}px)`;
2215
- this._anchor.style.transition = "none";
2216
- setTimeout(() => {
2217
- this.el.style.transform = "";
2218
- this.el.style.transition = "transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s";
2219
- this._anchor.style.overflow = "visible";
2220
- this._anchor.style.transform = "";
2221
- this._anchor.style.transition = "transform .2s";
2222
- setTimeout(() => {
2223
- this.el.style.overflow = "hidden";
2224
- this.el.style.backgroundColor = fabColor;
2225
- backdrop.style.transform = "scale(" + scaleFactor + ")";
2226
- backdrop.style.transition = "transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)";
2227
- this._menu.querySelectorAll("li > a").forEach((a) => {
2228
- a.style.opacity = "1";
2229
- a.tabIndex = 0;
2230
- });
2231
- window.addEventListener("scroll", this.close, true);
2232
- document.body.addEventListener("click", this._handleDocumentClick, true);
2233
- }, 100);
2234
- }, 0);
2214
+ this.el.removeEventListener("click", this._handleFABClick);
2215
+ this._anchor?.removeEventListener("keydown", this._handleAnchorKeydown);
2235
2216
  }
2236
- /**
2237
- * Reverse {@link _animateInToolbar}.
2238
- *
2239
- * Closing used to only unbind the listeners, leaving the FAB stretched
2240
- * across the viewport with its backdrop still in the DOM - and a fresh
2241
- * backdrop appended on every subsequent open.
2242
- */
2243
- _animateOutToolbar() {
2244
- const backdrop = this._backdrop;
2245
- this._backdrop = null;
2246
- this._menu?.querySelectorAll("li > a").forEach((a) => {
2247
- a.style.opacity = "";
2248
- a.tabIndex = -1;
2249
- });
2250
- if (backdrop) {
2251
- backdrop.style.transition = "transform .2s";
2252
- backdrop.style.transform = "scale(0)";
2253
- }
2254
- this.el.style.overflow = "";
2255
- this.el.style.backgroundColor = "";
2256
- this.el.style.transition = "transform .2s";
2257
- this.el.style.transform = `translateX(${this.offsetX}px)`;
2258
- this._anchor.style.transition = "transform .2s";
2259
- this._anchor.style.transform = `translateY(${this.offsetY}px)`;
2260
- setTimeout(() => {
2261
- backdrop?.remove();
2262
- this.el.classList.remove("active");
2263
- if (this._menu) this._menu.ariaExpanded = "false";
2264
- this.el.style.textAlign = "";
2265
- this.el.style.width = "";
2266
- this.el.style.bottom = "";
2267
- this.el.style.left = "";
2268
- this.el.style.transform = "";
2269
- this.el.style.transition = "";
2270
- this._anchor.style.overflow = "";
2271
- this._anchor.style.transform = "";
2272
- this._anchor.style.transition = "";
2273
- }, 200);
2217
+ _toggle() {
2218
+ if (this.isOpen) this.close();
2219
+ else this.open();
2274
2220
  }
2275
2221
  };
2276
2222
 
2277
2223
  // src/ts/components/carousel.ts
2278
2224
  var _defaults7 = {
2279
2225
  duration: 200,
2280
- // ms
2281
2226
  dist: -100,
2282
- // zoom scale TODO: make this more intuitive as an option
2283
2227
  shift: 0,
2284
- // spacing for center image
2285
2228
  padding: 0,
2286
- // Padding between non center items
2287
2229
  numVisible: 5,
2288
- // Number of visible items in carousel
2289
2230
  fullWidth: false,
2290
- // Change to full width styles
2291
2231
  indicators: false,
2292
- // Toggle indicators
2293
2232
  noWrap: false,
2294
- // Don't wrap around and cycle through items.
2295
2233
  onCycleTo: null
2296
- // Callback for when a new slide is cycled to.
2297
2234
  };
2298
- var Carousel = class _Carousel extends Component {
2235
+ var _Carousel = class _Carousel extends Component {
2299
2236
  constructor(el, options) {
2300
2237
  super(el, options, _Carousel);
2301
- /** The index of the center carousel item. */
2302
2238
  this.center = 0;
2303
- // Built once per instance. Creating the throttled function inside the
2304
- // handler - as this used to - produced a fresh closure per resize event and
2305
- // never actually called it, so resizing did nothing at all.
2239
+ this._ownIndicators = false;
2240
+ this._trackEl = null;
2241
+ this._ignoreScroll = false;
2242
+ this._trackRaf = null;
2243
+ this._started = false;
2306
2244
  this._handleThrottledResize = Utils.throttle(() => this._handleResize(), 200);
2307
2245
  this._handleCarouselTap = (e) => {
2308
- if (e.type === "mousedown" && e.target.tagName === "IMG") {
2246
+ if (e.pointerType === "mouse" && e.button !== 0) return;
2247
+ if (e.target instanceof HTMLElement && e.target.tagName === "IMG") {
2309
2248
  e.preventDefault();
2310
2249
  }
2311
2250
  this.pressed = true;
2312
2251
  this.dragged = false;
2313
2252
  this.verticalDragged = false;
2314
- this.reference = this._xpos(e);
2315
- this.referenceY = this._ypos(e);
2253
+ this.reference = e.clientX;
2254
+ this.referenceY = e.clientY;
2316
2255
  this.velocity = this.amplitude = 0;
2317
2256
  this.frame = this.offset;
2318
2257
  this.timestamp = Date.now();
2319
- clearInterval(this.ticker);
2320
- this.ticker = setInterval(this._track, 100);
2258
+ try {
2259
+ this.el.setPointerCapture(e.pointerId);
2260
+ } catch {
2261
+ }
2262
+ if (this._trackRaf !== null) cancelAnimationFrame(this._trackRaf);
2263
+ const loop = () => {
2264
+ this._track();
2265
+ if (this.pressed) this._trackRaf = requestAnimationFrame(loop);
2266
+ else this._trackRaf = null;
2267
+ };
2268
+ this._trackRaf = requestAnimationFrame(loop);
2321
2269
  };
2322
2270
  this._handleCarouselDrag = (e) => {
2323
- let x, y, delta, deltaY;
2324
- if (this.pressed) {
2325
- x = this._xpos(e);
2326
- y = this._ypos(e);
2327
- delta = this.reference - x;
2328
- deltaY = Math.abs(this.referenceY - y);
2329
- if (deltaY < 30 && !this.verticalDragged) {
2330
- if (delta > 2 || delta < -2) {
2331
- this.dragged = true;
2332
- this.reference = x;
2333
- this._scroll(this.offset + delta);
2334
- }
2335
- } else if (this.dragged) {
2336
- e.preventDefault();
2337
- e.stopPropagation();
2338
- return false;
2339
- } else {
2340
- this.verticalDragged = true;
2271
+ if (!this.pressed) return;
2272
+ const delta = this.reference - e.clientX;
2273
+ const deltaY = Math.abs(this.referenceY - e.clientY);
2274
+ if (deltaY < 30 && !this.verticalDragged) {
2275
+ if (delta > 2 || delta < -2) {
2276
+ this.dragged = true;
2277
+ this.reference = e.clientX;
2278
+ this._scroll(this.offset + delta);
2341
2279
  }
2342
- }
2343
- if (this.dragged) {
2280
+ } else if (this.dragged) {
2344
2281
  e.preventDefault();
2345
- e.stopPropagation();
2346
- return false;
2282
+ } else {
2283
+ this.verticalDragged = true;
2347
2284
  }
2285
+ if (this.dragged) e.preventDefault();
2348
2286
  };
2349
2287
  this._handleCarouselRelease = (e) => {
2350
- if (this.pressed) {
2351
- this.pressed = false;
2352
- } else {
2353
- return;
2288
+ if (!this.pressed) return;
2289
+ this.pressed = false;
2290
+ if (this._trackRaf !== null) {
2291
+ cancelAnimationFrame(this._trackRaf);
2292
+ this._trackRaf = null;
2293
+ }
2294
+ try {
2295
+ this.el.releasePointerCapture(e.pointerId);
2296
+ } catch {
2354
2297
  }
2355
- clearInterval(this.ticker);
2356
2298
  this.target = this.offset;
2357
2299
  if (this.velocity > 10 || this.velocity < -10) {
2358
2300
  this.amplitude = 0.9 * this.velocity;
@@ -2369,86 +2311,91 @@ var Carousel = class _Carousel extends Component {
2369
2311
  this.amplitude = this.target - this.offset;
2370
2312
  this.timestamp = Date.now();
2371
2313
  requestAnimationFrame(this._autoScroll);
2314
+ if (this.dragged) e.preventDefault();
2315
+ };
2316
+ this._handleCarouselClick = (e) => {
2372
2317
  if (this.dragged) {
2373
2318
  e.preventDefault();
2374
2319
  e.stopPropagation();
2320
+ return;
2375
2321
  }
2376
- return false;
2377
- };
2378
- this._handleCarouselClick = (e) => {
2379
- if (this.dragged) {
2322
+ const clickedElem = e.target.closest(".carousel-item");
2323
+ if (!clickedElem || !this.el.contains(clickedElem)) return;
2324
+ const clickedIndex = this.images.indexOf(clickedElem);
2325
+ const diff = this._wrap(this.center) - clickedIndex;
2326
+ if (diff !== 0) {
2380
2327
  e.preventDefault();
2381
2328
  e.stopPropagation();
2382
- return false;
2383
- } else if (!this.options.fullWidth) {
2384
- const clickedElem = e.target.closest(".carousel-item");
2385
- if (!clickedElem) return;
2386
- const clickedIndex = [...clickedElem.parentNode.children].indexOf(clickedElem);
2387
- const diff = this._wrap(this.center) - clickedIndex;
2388
- if (diff !== 0) {
2389
- e.preventDefault();
2390
- e.stopPropagation();
2391
- }
2392
- if (clickedIndex < 0) {
2393
- if (e.clientX - e.target.getBoundingClientRect().left > this.el.clientWidth / 2) {
2394
- this.next();
2395
- } else {
2396
- this.prev();
2397
- }
2329
+ }
2330
+ if (clickedIndex < 0) {
2331
+ if (e.clientX - e.target.getBoundingClientRect().left > this.el.clientWidth / 2) {
2332
+ this.next();
2398
2333
  } else {
2399
- this._cycleTo(clickedIndex);
2334
+ this.prev();
2400
2335
  }
2336
+ } else {
2337
+ this._cycleTo(clickedIndex);
2401
2338
  }
2402
2339
  };
2403
2340
  this._handleIndicatorClick = (e) => {
2404
2341
  e.stopPropagation();
2405
- this._handleIndicatorInteraction(e);
2342
+ const indicator = e.target.closest(".indicator-item");
2343
+ if (!indicator || !this._indicators.contains(indicator)) return;
2344
+ const index = [...this._indicators.children].indexOf(indicator);
2345
+ if (index < 0) return;
2346
+ this._cycleTo(index);
2406
2347
  };
2407
- this._handleIndicatorKeyPress = (e) => {
2408
- e.stopPropagation();
2409
- if (Utils.keys.ENTER.includes(e.key)) {
2410
- this._handleIndicatorInteraction(e);
2348
+ this._handleKeydown = (e) => {
2349
+ const target = e.target;
2350
+ if (target.closest('input, textarea, select, [contenteditable="true"]')) return;
2351
+ if (Utils.keys.ARROW_RIGHT.includes(e.key)) {
2352
+ e.preventDefault();
2353
+ this.next();
2354
+ } else if (Utils.keys.ARROW_LEFT.includes(e.key)) {
2355
+ e.preventDefault();
2356
+ this.prev();
2411
2357
  }
2412
2358
  };
2413
- this._handleIndicatorInteraction = (e) => {
2414
- const indicator = e.target.closest(".indicator-item");
2415
- if (indicator) {
2416
- const index = [...indicator.parentNode.children].indexOf(indicator);
2417
- this._cycleTo(index);
2418
- }
2359
+ this._handleFlatScroll = () => {
2360
+ if (this._ignoreScroll) return;
2361
+ const index = this._nearestIndex();
2362
+ if (index !== this.center) this._syncActive(index, this.dragged);
2419
2363
  };
2420
2364
  this._handleResize = () => {
2365
+ if (!this.images.length) return;
2366
+ if (this._flat) {
2367
+ this._scrollToIndex(this.center, false);
2368
+ return;
2369
+ }
2421
2370
  if (this.options.fullWidth) {
2422
- const firstItem = this.el.querySelector(".carousel-item");
2423
- const activeItem = this.el.querySelector(".carousel-item.active");
2424
- if (!firstItem || !activeItem) return;
2371
+ const firstItem = this.images[0];
2372
+ if (!firstItem) return;
2425
2373
  this.itemWidth = firstItem.clientWidth;
2426
- this.imageHeight = activeItem.clientHeight;
2427
2374
  this.dim = this.itemWidth * 2 + this.options.padding;
2428
2375
  this.offset = this.center * 2 * this.itemWidth;
2429
2376
  this.target = this.offset;
2430
- this._setCarouselHeight(true);
2431
2377
  } else {
2432
2378
  this._scroll();
2433
2379
  }
2434
2380
  };
2435
2381
  this._track = () => {
2436
- const now = Date.now(), elapsed = now - this.timestamp, delta = this.offset - this.frame, v = 1e3 * delta / (1 + elapsed);
2382
+ const now = Date.now();
2383
+ const elapsed = now - this.timestamp;
2384
+ const delta = this.offset - this.frame;
2385
+ const v = 1e3 * delta / (1 + elapsed);
2437
2386
  this.timestamp = now;
2438
2387
  this.frame = this.offset;
2439
2388
  this.velocity = 0.8 * v + 0.2 * this.velocity;
2440
2389
  };
2441
2390
  this._autoScroll = () => {
2442
- let elapsed, delta;
2443
- if (this.amplitude) {
2444
- elapsed = Date.now() - this.timestamp;
2445
- delta = this.amplitude * Math.exp(-elapsed / this.options.duration);
2446
- if (delta > 2 || delta < -2) {
2447
- this._scroll(this.target - delta);
2448
- requestAnimationFrame(this._autoScroll);
2449
- } else {
2450
- this._scroll(this.target);
2451
- }
2391
+ if (!this.amplitude) return;
2392
+ const elapsed = Date.now() - this.timestamp;
2393
+ const delta = this.amplitude * Math.exp(-elapsed / this.options.duration);
2394
+ if (delta > 2 || delta < -2) {
2395
+ this._scroll(this.target - delta);
2396
+ requestAnimationFrame(this._autoScroll);
2397
+ } else {
2398
+ this._scroll(this.target);
2452
2399
  }
2453
2400
  };
2454
2401
  this.el["Expressive_Carousel"] = this;
@@ -2456,56 +2403,58 @@ var Carousel = class _Carousel extends Component {
2456
2403
  ..._Carousel.defaults,
2457
2404
  ...options
2458
2405
  };
2459
- this.hasMultipleSlides = this.el.querySelectorAll(".carousel-item").length > 1;
2460
- this.showIndicators = this.options.indicators && this.hasMultipleSlides;
2461
- this.noWrap = this.options.noWrap || !this.hasMultipleSlides;
2406
+ this._flat = this.options.fullWidth || this.el.classList.contains("flat") || this.el.classList.contains("carousel-slider");
2407
+ if (this._flat) {
2408
+ this.el.classList.add("flat");
2409
+ this.options.fullWidth = true;
2410
+ this.options.dist = 0;
2411
+ }
2412
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
2413
+ this.options.duration = 1;
2414
+ if (!this._flat) this.options.dist = 0;
2415
+ }
2462
2416
  this.pressed = false;
2463
2417
  this.dragged = false;
2464
2418
  this.offset = this.target = 0;
2465
- this.images = [];
2466
- const firstItem = this.el.querySelector(".carousel-item");
2467
- if (!firstItem) {
2419
+ this.images = this._collectItems();
2420
+ if (!this.images.length) {
2468
2421
  console.error("Carousel: no .carousel-item elements to show");
2469
2422
  return;
2470
2423
  }
2471
- this.itemWidth = firstItem.clientWidth;
2472
- this.itemHeight = firstItem.clientHeight;
2473
- this.dim = this.itemWidth * 2 + this.options.padding || 1;
2474
- if (this.options.fullWidth) {
2475
- this.options.dist = 0;
2476
- this._setCarouselHeight();
2477
- if (this.showIndicators) {
2478
- this.el.querySelector(".carousel-fixed-item")?.classList.add("with-indicators");
2479
- }
2480
- }
2481
- this._indicators = document.createElement("ul");
2482
- this._indicators.classList.add("indicators");
2483
- this.el.querySelectorAll(".carousel-item").forEach((item, i) => {
2484
- this.images.push(item);
2485
- if (this.showIndicators) {
2486
- const indicator = document.createElement("li");
2487
- indicator.classList.add("indicator-item");
2488
- indicator.tabIndex = 0;
2489
- if (i === 0) {
2490
- indicator.classList.add("active");
2491
- }
2492
- this._indicators.appendChild(indicator);
2493
- }
2494
- });
2495
- if (this.showIndicators) this.el.appendChild(this._indicators);
2424
+ this.hasMultipleSlides = this.images.length > 1;
2425
+ this.showIndicators = this.options.indicators && this.hasMultipleSlides;
2426
+ this.noWrap = this.options.noWrap || !this.hasMultipleSlides || this._flat;
2496
2427
  this.count = this.images.length;
2497
2428
  this.options.numVisible = Math.min(this.count, this.options.numVisible);
2429
+ this.images.forEach((item) => item.classList.add("carousel-item"));
2430
+ if (this._flat) this._wrapTrack();
2431
+ const firstItem = this.images[0];
2432
+ this.itemWidth = firstItem.clientWidth || this.el.clientWidth || 1;
2433
+ this.itemHeight = firstItem.clientHeight || this.el.clientHeight || 1;
2434
+ this.dim = this.itemWidth * 2 + this.options.padding || 1;
2435
+ if (this.showIndicators) this._setupIndicators();
2436
+ this.el.tabIndex = this.el.tabIndex >= 0 ? this.el.tabIndex : 0;
2437
+ this.el.setAttribute("aria-roledescription", "carousel");
2498
2438
  this._setupEventHandlers();
2499
- this._scroll(this.offset);
2439
+ this._started = true;
2440
+ if (this._flat) {
2441
+ const start = Math.max(
2442
+ 0,
2443
+ this.images.findIndex((el2) => el2.classList.contains("active"))
2444
+ );
2445
+ this.center = start;
2446
+ this._syncActive(start, false);
2447
+ this._scrollToIndex(start, false);
2448
+ } else {
2449
+ this._scroll(this.offset);
2450
+ }
2451
+ }
2452
+ get _scroller() {
2453
+ return this._trackEl ?? this.el;
2500
2454
  }
2501
2455
  static get defaults() {
2502
2456
  return _defaults7;
2503
2457
  }
2504
- /**
2505
- * Initializes instances of Carousel.
2506
- * @param els HTML elements.
2507
- * @param options Component options.
2508
- */
2509
2458
  static init(els, options = {}) {
2510
2459
  return super.init(els, options, _Carousel);
2511
2460
  }
@@ -2513,81 +2462,104 @@ var Carousel = class _Carousel extends Component {
2513
2462
  return el["Expressive_Carousel"];
2514
2463
  }
2515
2464
  destroy() {
2516
- this._removeEventHandlers();
2465
+ if (this._started) this._teardown();
2517
2466
  this.el["Expressive_Carousel"] = void 0;
2518
2467
  }
2468
+ _collectItems() {
2469
+ const explicit = Array.from(this.el.children).filter(
2470
+ (el) => el instanceof HTMLElement && el.classList.contains("carousel-item")
2471
+ );
2472
+ if (explicit.length) return explicit;
2473
+ return Array.from(this.el.children).filter((el) => {
2474
+ if (!(el instanceof HTMLElement)) return false;
2475
+ if (el.classList.contains("indicators") || el.classList.contains("carousel-fixed-item")) {
2476
+ return false;
2477
+ }
2478
+ return el.tagName !== "NAV";
2479
+ });
2480
+ }
2481
+ _setupIndicators() {
2482
+ const existing = Array.from(this.el.children).find(
2483
+ (el) => el instanceof HTMLElement && el.classList.contains("indicators")
2484
+ );
2485
+ this._indicators = existing ?? document.createElement("nav");
2486
+ this._ownIndicators = !existing;
2487
+ this._indicators.classList.add("indicators");
2488
+ this._indicators.setAttribute("aria-label", "Slides");
2489
+ this._indicators.replaceChildren();
2490
+ this.images.forEach((_, i) => {
2491
+ const button = document.createElement("button");
2492
+ button.type = "button";
2493
+ button.classList.add("indicator-item");
2494
+ if (i === 0) {
2495
+ button.classList.add("active");
2496
+ button.setAttribute("aria-current", "true");
2497
+ }
2498
+ button.setAttribute("aria-label", `Slide ${i + 1}`);
2499
+ this._indicators.appendChild(button);
2500
+ });
2501
+ if (this._ownIndicators) this.el.appendChild(this._indicators);
2502
+ if (this._flat) {
2503
+ this.el.querySelector(".carousel-fixed-item")?.classList.add("with-indicators");
2504
+ }
2505
+ }
2519
2506
  _setupEventHandlers() {
2520
- if (typeof window.ontouchstart !== "undefined") {
2521
- this.el.addEventListener("touchstart", this._handleCarouselTap);
2522
- this.el.addEventListener("touchmove", this._handleCarouselDrag);
2523
- this.el.addEventListener("touchend", this._handleCarouselRelease);
2524
- }
2525
- this.el.addEventListener("mousedown", this._handleCarouselTap);
2526
- this.el.addEventListener("mousemove", this._handleCarouselDrag);
2527
- this.el.addEventListener("mouseup", this._handleCarouselRelease);
2528
- this.el.addEventListener("mouseleave", this._handleCarouselRelease);
2529
- this.el.addEventListener("click", this._handleCarouselClick);
2507
+ this.el.addEventListener("keydown", this._handleKeydown);
2530
2508
  if (this.showIndicators && this._indicators) {
2531
- this._indicators.querySelectorAll(".indicator-item").forEach((el) => {
2532
- el.addEventListener("click", this._handleIndicatorClick);
2533
- el.addEventListener("keypress", this._handleIndicatorKeyPress);
2534
- });
2509
+ this._indicators.addEventListener("click", this._handleIndicatorClick);
2510
+ }
2511
+ _Carousel._live.add(this);
2512
+ if (!_Carousel._resizeListening) {
2513
+ window.addEventListener("resize", _Carousel._onResize, { passive: true });
2514
+ _Carousel._resizeListening = true;
2535
2515
  }
2536
- window.addEventListener("resize", this._handleThrottledResize);
2516
+ if (this._flat) {
2517
+ this._scroller.addEventListener("scroll", this._handleFlatScroll, { passive: true });
2518
+ return;
2519
+ }
2520
+ this.el.addEventListener("pointerdown", this._handleCarouselTap);
2521
+ this.el.addEventListener("pointermove", this._handleCarouselDrag);
2522
+ this.el.addEventListener("pointerup", this._handleCarouselRelease);
2523
+ this.el.addEventListener("pointercancel", this._handleCarouselRelease);
2524
+ this.el.addEventListener("click", this._handleCarouselClick);
2537
2525
  }
2538
2526
  _removeEventHandlers() {
2539
- if (typeof window.ontouchstart !== "undefined") {
2540
- this.el.removeEventListener("touchstart", this._handleCarouselTap);
2541
- this.el.removeEventListener("touchmove", this._handleCarouselDrag);
2542
- this.el.removeEventListener("touchend", this._handleCarouselRelease);
2543
- }
2544
- this.el.removeEventListener("mousedown", this._handleCarouselTap);
2545
- this.el.removeEventListener("mousemove", this._handleCarouselDrag);
2546
- this.el.removeEventListener("mouseup", this._handleCarouselRelease);
2547
- this.el.removeEventListener("mouseleave", this._handleCarouselRelease);
2527
+ this.el.removeEventListener("keydown", this._handleKeydown);
2528
+ if (this._indicators) {
2529
+ this._indicators.removeEventListener("click", this._handleIndicatorClick);
2530
+ }
2531
+ this._scroller.removeEventListener("scroll", this._handleFlatScroll);
2532
+ this.el.removeEventListener("pointerdown", this._handleCarouselTap);
2533
+ this.el.removeEventListener("pointermove", this._handleCarouselDrag);
2534
+ this.el.removeEventListener("pointerup", this._handleCarouselRelease);
2535
+ this.el.removeEventListener("pointercancel", this._handleCarouselRelease);
2548
2536
  this.el.removeEventListener("click", this._handleCarouselClick);
2549
- if (this.showIndicators && this._indicators) {
2550
- this._indicators.querySelectorAll(".indicator-item").forEach((el) => {
2551
- el.removeEventListener("click", this._handleIndicatorClick);
2552
- });
2537
+ _Carousel._live.delete(this);
2538
+ if (_Carousel._live.size === 0 && _Carousel._resizeListening) {
2539
+ window.removeEventListener("resize", _Carousel._onResize);
2540
+ _Carousel._resizeListening = false;
2553
2541
  }
2554
- window.removeEventListener("resize", this._handleThrottledResize);
2555
2542
  }
2556
- _setCarouselHeight(imageOnly = false) {
2557
- const firstSlide = this.el.querySelector(".carousel-item.active") ? this.el.querySelector(".carousel-item.active") : this.el.querySelector(".carousel-item");
2558
- const firstImage = firstSlide.querySelector("img");
2559
- if (firstImage) {
2560
- if (firstImage.complete) {
2561
- const imageHeight = firstImage.clientHeight;
2562
- if (imageHeight > 0) {
2563
- this.el.style.height = imageHeight + "px";
2564
- } else {
2565
- const naturalWidth = firstImage.naturalWidth;
2566
- const naturalHeight = firstImage.naturalHeight;
2567
- const adjustedHeight = this.el.clientWidth / naturalWidth * naturalHeight;
2568
- this.el.style.height = adjustedHeight + "px";
2569
- }
2570
- } else {
2571
- firstImage.addEventListener("load", () => {
2572
- this.el.style.height = firstImage.offsetHeight + "px";
2573
- });
2574
- }
2575
- } else if (!imageOnly) {
2576
- const slideHeight = firstSlide.clientHeight;
2577
- this.el.style.height = slideHeight + "px";
2578
- }
2543
+ _teardown() {
2544
+ this._removeEventHandlers();
2545
+ if (this._trackRaf !== null) cancelAnimationFrame(this._trackRaf);
2546
+ window.clearTimeout(this.scrollingTimeout);
2547
+ if (this._ownIndicators) this._indicators?.remove();
2548
+ this._unwrapTrack();
2549
+ this.images.forEach((el) => {
2550
+ el.style.transform = "";
2551
+ el.style.zIndex = "";
2552
+ el.style.opacity = "";
2553
+ el.style.visibility = "";
2554
+ });
2579
2555
  }
2580
2556
  _xpos(e) {
2581
- if (e.type.startsWith("touch") && e.targetTouches.length >= 1) {
2582
- return e.targetTouches[0].clientX;
2583
- }
2584
- return e.clientX;
2557
+ if ("clientX" in e) return e.clientX;
2558
+ return 0;
2585
2559
  }
2586
2560
  _ypos(e) {
2587
- if (e.type.startsWith("touch") && e.targetTouches.length >= 1) {
2588
- return e.targetTouches[0].clientY;
2589
- }
2590
- return e.clientY;
2561
+ if ("clientY" in e) return e.clientY;
2562
+ return 0;
2591
2563
  }
2592
2564
  _wrap(x) {
2593
2565
  return x >= this.count ? x % this.count : x < 0 ? this._wrap(this.count + x % this.count) : x;
@@ -2596,18 +2568,20 @@ var Carousel = class _Carousel extends Component {
2596
2568
  if (!this.el.classList.contains("scrolling")) {
2597
2569
  this.el.classList.add("scrolling");
2598
2570
  }
2599
- if (this.scrollingTimeout != null) {
2600
- clearTimeout(this.scrollingTimeout);
2601
- }
2571
+ window.clearTimeout(this.scrollingTimeout);
2602
2572
  this.scrollingTimeout = setTimeout(() => {
2603
2573
  this.el.classList.remove("scrolling");
2604
2574
  }, this.options.duration);
2605
2575
  this.offset = typeof x === "number" ? x : this.offset;
2606
2576
  this.center = Math.floor((this.offset + this.dim / 2) / this.dim);
2607
- const half = this.count >> 1, delta = this.offset - this.center * this.dim, dir = delta < 0 ? 1 : -1, tween = -dir * delta * 2 / this.dim;
2608
- let i, el, alignment, zTranslation, tweenedOpacity, centerTweenedOpacity;
2577
+ const half = this.count >> 1;
2578
+ const delta = this.offset - this.center * this.dim;
2579
+ const dir = delta < 0 ? 1 : -1;
2580
+ const tween = -dir * delta * 2 / this.dim;
2609
2581
  const lastCenter = this.center;
2610
2582
  const numVisibleOffset = 1 / this.options.numVisible;
2583
+ let alignment;
2584
+ let centerTweenedOpacity;
2611
2585
  if (this.options.fullWidth) {
2612
2586
  alignment = "translateX(0)";
2613
2587
  centerTweenedOpacity = 1;
@@ -2616,26 +2590,17 @@ var Carousel = class _Carousel extends Component {
2616
2590
  alignment += "translateY(" + (this.el.clientHeight - this.itemHeight) / 2 + "px)";
2617
2591
  centerTweenedOpacity = 1 - numVisibleOffset * tween;
2618
2592
  }
2619
- if (this.showIndicators) {
2620
- const diff = this.center % this.count;
2621
- const activeIndicator = this._indicators.querySelector(".indicator-item.active");
2622
- const activeIndicatorIndex = [...activeIndicator.parentNode.children].indexOf(
2623
- activeIndicator
2624
- );
2625
- if (activeIndicatorIndex !== diff) {
2626
- activeIndicator.classList.remove("active");
2627
- const pos = diff < 0 ? this.count + diff : diff;
2628
- this._indicators.querySelectorAll(".indicator-item")[pos].classList.add("active");
2629
- }
2630
- }
2593
+ this._paintIndicators(this.center);
2631
2594
  if (!this.noWrap || this.center >= 0 && this.center < this.count) {
2632
- el = this.images[this._wrap(this.center)];
2633
- if (!el.classList.contains("active")) {
2595
+ const el = this.images[this._wrap(this.center)];
2596
+ if (el && !el.classList.contains("active")) {
2634
2597
  this.el.querySelector(".carousel-item.active")?.classList.remove("active");
2635
2598
  el.classList.add("active");
2636
2599
  }
2637
2600
  }
2638
- for (i = 1; i <= half; ++i) {
2601
+ for (let i = 1; i <= half; ++i) {
2602
+ let zTranslation;
2603
+ let tweenedOpacity;
2639
2604
  if (this.options.fullWidth) {
2640
2605
  zTranslation = this.options.dist;
2641
2606
  tweenedOpacity = i === half && delta < 0 ? 1 - tween : 1;
@@ -2644,7 +2609,7 @@ var Carousel = class _Carousel extends Component {
2644
2609
  tweenedOpacity = 1 - numVisibleOffset * (i * 2 + tween * dir);
2645
2610
  }
2646
2611
  if (!this.noWrap || this.center + i < this.count) {
2647
- el = this.images[this._wrap(this.center + i)];
2612
+ const el = this.images[this._wrap(this.center + i)];
2648
2613
  const transformString = `${alignment} translateX(${this.options.shift + (this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`;
2649
2614
  this._updateItemStyle(el, tweenedOpacity, -i, transformString);
2650
2615
  }
@@ -2656,32 +2621,113 @@ var Carousel = class _Carousel extends Component {
2656
2621
  tweenedOpacity = 1 - numVisibleOffset * (i * 2 - tween * dir);
2657
2622
  }
2658
2623
  if (!this.noWrap || this.center - i >= 0) {
2659
- el = this.images[this._wrap(this.center - i)];
2624
+ const el = this.images[this._wrap(this.center - i)];
2660
2625
  const transformString = `${alignment} translateX(${-this.options.shift + (-this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`;
2661
2626
  this._updateItemStyle(el, tweenedOpacity, -i, transformString);
2662
2627
  }
2663
2628
  }
2664
2629
  if (!this.noWrap || this.center >= 0 && this.center < this.count) {
2665
- el = this.images[this._wrap(this.center)];
2630
+ const el = this.images[this._wrap(this.center)];
2666
2631
  const transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir * this.options.shift * tween}px) translateZ(${this.options.dist * tween}px)`;
2667
2632
  this._updateItemStyle(el, centerTweenedOpacity, 0, transformString);
2668
2633
  }
2669
- const _currItem = this.el.querySelectorAll(".carousel-item")[this._wrap(this.center)];
2634
+ const currItem = this.images[this._wrap(this.center)];
2635
+ this._syncA11y(this._wrap(this.center));
2670
2636
  if (lastCenter !== this.center && typeof this.options.onCycleTo === "function") {
2671
- this.options.onCycleTo.call(this, _currItem, this.dragged);
2637
+ this.options.onCycleTo.call(this, currItem, this.dragged);
2672
2638
  }
2673
2639
  if (typeof this.oneTimeCallback === "function") {
2674
- this.oneTimeCallback.call(this, _currItem, this.dragged);
2640
+ this.oneTimeCallback.call(this, currItem, this.dragged);
2675
2641
  this.oneTimeCallback = null;
2676
2642
  }
2677
2643
  }
2678
2644
  _updateItemStyle(el, opacity, zIndex, transform) {
2645
+ if (!el) return;
2679
2646
  el.style.transform = transform;
2680
2647
  el.style.zIndex = zIndex.toString();
2681
2648
  el.style.opacity = opacity.toString();
2682
2649
  el.style.visibility = "visible";
2683
2650
  }
2651
+ _paintIndicators(center) {
2652
+ if (!this.showIndicators || !this._indicators) return;
2653
+ const pos = (center % this.count + this.count) % this.count;
2654
+ this._indicators.querySelectorAll(".indicator-item").forEach((el, i) => {
2655
+ const on = i === pos;
2656
+ el.classList.toggle("active", on);
2657
+ if (on) el.setAttribute("aria-current", "true");
2658
+ else el.removeAttribute("aria-current");
2659
+ });
2660
+ }
2661
+ _syncA11y(index) {
2662
+ this.images.forEach((el, i) => {
2663
+ if (i === index) el.removeAttribute("aria-hidden");
2664
+ else el.setAttribute("aria-hidden", "true");
2665
+ });
2666
+ }
2667
+ _syncActive(index, dragged) {
2668
+ const prev = this.center;
2669
+ this.center = index;
2670
+ this.images.forEach((el, i) => el.classList.toggle("active", i === index));
2671
+ this._paintIndicators(index);
2672
+ this._syncA11y(index);
2673
+ const curr = this.images[index];
2674
+ if (prev !== index && typeof this.options.onCycleTo === "function") {
2675
+ this.options.onCycleTo.call(this, curr, dragged);
2676
+ }
2677
+ if (typeof this.oneTimeCallback === "function") {
2678
+ this.oneTimeCallback.call(this, curr, dragged);
2679
+ this.oneTimeCallback = null;
2680
+ }
2681
+ }
2682
+ _wrapTrack() {
2683
+ const track = document.createElement("div");
2684
+ track.className = "carousel-track";
2685
+ this.images[0].before(track);
2686
+ this.images.forEach((el) => track.appendChild(el));
2687
+ this._trackEl = track;
2688
+ }
2689
+ _unwrapTrack() {
2690
+ if (!this._trackEl) return;
2691
+ this.images.forEach((el) => this.el.insertBefore(el, this._trackEl));
2692
+ this._trackEl.remove();
2693
+ this._trackEl = null;
2694
+ }
2695
+ _nearestIndex() {
2696
+ const left = this._scroller.scrollLeft;
2697
+ let best = 0;
2698
+ let bestDist = Infinity;
2699
+ this.images.forEach((el, i) => {
2700
+ const dist = Math.abs(el.offsetLeft - left);
2701
+ if (dist < bestDist) {
2702
+ bestDist = dist;
2703
+ best = i;
2704
+ }
2705
+ });
2706
+ return best;
2707
+ }
2708
+ _scrollToIndex(n, smooth = true) {
2709
+ const el = this.images[n];
2710
+ if (!el) return;
2711
+ const left = el.offsetLeft;
2712
+ if (Math.abs(this._scroller.scrollLeft - left) < 1) {
2713
+ this._syncActive(n, false);
2714
+ return;
2715
+ }
2716
+ this._ignoreScroll = true;
2717
+ this._scroller.scrollTo({ left, behavior: smooth ? "smooth" : "auto" });
2718
+ const done = () => {
2719
+ this._ignoreScroll = false;
2720
+ };
2721
+ this._scroller.addEventListener("scrollend", done, { once: true });
2722
+ window.setTimeout(done, this.options.duration + 350);
2723
+ }
2684
2724
  _cycleTo(n, callback = null) {
2725
+ if (this._flat) {
2726
+ if (typeof callback === "function") this.oneTimeCallback = callback;
2727
+ this._syncActive(n, false);
2728
+ this._scrollToIndex(n);
2729
+ return;
2730
+ }
2685
2731
  let diff = this.center % this.count - n;
2686
2732
  if (!this.noWrap) {
2687
2733
  if (diff < 0) {
@@ -2709,14 +2755,8 @@ var Carousel = class _Carousel extends Component {
2709
2755
  requestAnimationFrame(this._autoScroll);
2710
2756
  }
2711
2757
  }
2712
- /**
2713
- * Move carousel to next slide or go forward a given amount of slides.
2714
- * @param n How many times the carousel slides.
2715
- */
2716
2758
  next(n = 1) {
2717
- if (n === void 0 || isNaN(n)) {
2718
- n = 1;
2719
- }
2759
+ if (n === void 0 || isNaN(n)) n = 1;
2720
2760
  let index = this.center + n;
2721
2761
  if (index >= this.count || index < 0) {
2722
2762
  if (this.noWrap) return;
@@ -2724,14 +2764,8 @@ var Carousel = class _Carousel extends Component {
2724
2764
  }
2725
2765
  this._cycleTo(index);
2726
2766
  }
2727
- /**
2728
- * Move carousel to previous slide or go back a given amount of slides.
2729
- * @param n How many times the carousel slides.
2730
- */
2731
2767
  prev(n = 1) {
2732
- if (n === void 0 || isNaN(n)) {
2733
- n = 1;
2734
- }
2768
+ if (n === void 0 || isNaN(n)) n = 1;
2735
2769
  let index = this.center - n;
2736
2770
  if (index >= this.count || index < 0) {
2737
2771
  if (this.noWrap) return;
@@ -2739,15 +2773,8 @@ var Carousel = class _Carousel extends Component {
2739
2773
  }
2740
2774
  this._cycleTo(index);
2741
2775
  }
2742
- /**
2743
- * Move carousel to nth slide.
2744
- * @param n Index of slide.
2745
- * @param callback "onCycleTo" optional callback.
2746
- */
2747
2776
  set(n, callback) {
2748
- if (n === void 0 || isNaN(n)) {
2749
- n = 0;
2750
- }
2777
+ if (n === void 0 || isNaN(n)) n = 0;
2751
2778
  if (n > this.count || n < 0) {
2752
2779
  if (this.noWrap) return;
2753
2780
  n = this._wrap(n);
@@ -2755,6 +2782,12 @@ var Carousel = class _Carousel extends Component {
2755
2782
  this._cycleTo(n, callback);
2756
2783
  }
2757
2784
  };
2785
+ _Carousel._live = /* @__PURE__ */ new Set();
2786
+ _Carousel._resizeListening = false;
2787
+ _Carousel._onResize = Utils.throttle(() => {
2788
+ _Carousel._live.forEach((c) => c._handleResize());
2789
+ }, 200);
2790
+ var Carousel = _Carousel;
2758
2791
 
2759
2792
  // src/ts/components/characterCounter.ts
2760
2793
  var _defaults8 = Object.freeze({});
@@ -2844,77 +2877,75 @@ var _defaults9 = {
2844
2877
  var Collapsible = class _Collapsible extends Component {
2845
2878
  constructor(el, options) {
2846
2879
  super(el, options, _Collapsible);
2847
- this._handleCollapsibleClick = (e) => {
2848
- const header = e.target.closest(".collapsible-header");
2849
- if (e.target && header) {
2850
- const collapsible = header.closest(".collapsible");
2851
- if (collapsible !== this.el) return;
2852
- const li = header.closest("li");
2853
- const isActive = li.classList.contains("active");
2854
- const index = [...li.parentNode.children].indexOf(li);
2855
- if (isActive) this.close(index);
2856
- else this.open(index);
2857
- }
2858
- };
2859
- this._handleCollapsibleKeydown = (e) => {
2860
- if (Utils.keys.ENTER.includes(e.key)) {
2861
- this._handleCollapsibleClick(e);
2862
- }
2863
- };
2880
+ this._items = [];
2881
+ this._endTimers = [];
2882
+ this._wrappedBodies = [];
2864
2883
  /**
2865
2884
  * Open collapsible section.
2866
- * @param n Nth section to open.
2885
+ * @param index 0-based index of the section to open.
2867
2886
  */
2868
2887
  this.open = (index) => {
2869
- const listItems = Array.from(this.el.children).filter((c) => c.tagName === "LI");
2870
- const li = listItems[index];
2871
- if (li && !li.classList.contains("active")) {
2872
- if (typeof this.options.onOpenStart === "function") {
2873
- this.options.onOpenStart.call(this, li);
2874
- }
2875
- if (this.options.accordion) {
2876
- const activeLis = listItems.filter((li2) => li2.classList.contains("active"));
2877
- activeLis.forEach((activeLi) => {
2878
- const index2 = listItems.indexOf(activeLi);
2879
- this.close(index2);
2880
- });
2881
- }
2882
- li.classList.add("active");
2883
- this._animateIn(index);
2888
+ const item = this._items[index];
2889
+ if (!item || this._isOpen(item)) return;
2890
+ if (this.options.accordion) {
2891
+ this._items.forEach((other, i) => {
2892
+ if (i !== index && this._isOpen(other)) this.close(i);
2893
+ });
2884
2894
  }
2895
+ this._setOpen(item, true);
2885
2896
  };
2886
2897
  /**
2887
2898
  * Close collapsible section.
2888
- * @param n Nth section to close.
2899
+ * @param index 0-based index of the section to close.
2889
2900
  */
2890
2901
  this.close = (index) => {
2891
- const li = Array.from(this.el.children).filter((c) => c.tagName === "LI")[index];
2892
- if (li && li.classList.contains("active")) {
2893
- if (typeof this.options.onCloseStart === "function") {
2894
- this.options.onCloseStart.call(this, li);
2895
- }
2896
- li.classList.remove("active");
2897
- this._animateOut(index);
2898
- }
2902
+ const item = this._items[index];
2903
+ if (!item || !this._isOpen(item)) return;
2904
+ this._setOpen(item, false);
2905
+ };
2906
+ this._handleClick = (e) => {
2907
+ const header = e.target.closest(".collapsible-header");
2908
+ if (!header || header.closest(".collapsible") !== this.el) return;
2909
+ if (header.closest("details")) return;
2910
+ const item = header.closest("li");
2911
+ if (!item) return;
2912
+ const index = this._items.indexOf(item);
2913
+ if (index < 0) return;
2914
+ if (this._isOpen(item)) this.close(index);
2915
+ else this.open(index);
2916
+ };
2917
+ this._handleKeydown = (e) => {
2918
+ if (!Utils.keys.ENTER.includes(e.key) && e.key !== " ") return;
2919
+ const header = e.target.closest(".collapsible-header");
2920
+ if (!header || header.closest(".collapsible") !== this.el) return;
2921
+ if (header.closest("details")) return;
2922
+ if (header.matches("a, button")) return;
2923
+ e.preventDefault();
2924
+ const item = header.closest("li");
2925
+ if (!item) return;
2926
+ const index = this._items.indexOf(item);
2927
+ if (index < 0) return;
2928
+ if (this._isOpen(item)) this.close(index);
2929
+ else this.open(index);
2930
+ };
2931
+ this._handleToggle = (e) => {
2932
+ const item = e.target;
2933
+ if (!(item instanceof HTMLDetailsElement)) return;
2934
+ if (!this._items.includes(item)) return;
2935
+ this._notify(item, item.open);
2899
2936
  };
2900
2937
  this.el["Expressive_Collapsible"] = this;
2938
+ const accordion = options.accordion !== void 0 ? options.accordion : !el.classList.contains("expandable");
2901
2939
  this.options = {
2902
2940
  ..._Collapsible.defaults,
2903
- ...options
2941
+ ...options,
2942
+ accordion
2904
2943
  };
2905
- this._headers = Array.from(this.el.querySelectorAll("li > .collapsible-header"));
2906
- this._headers.forEach((el2) => el2.tabIndex = 0);
2944
+ this._groupName = el.id ? `collapsible-${el.id}` : `collapsible-${Utils.guid()}`;
2945
+ this._items = this._collectItems();
2946
+ this._wrapDetailsBodies();
2947
+ this._setupItems();
2907
2948
  this._setupEventHandlers();
2908
- const activeBodies = Array.from(
2909
- this.el.querySelectorAll("li.active > .collapsible-body")
2910
- );
2911
- if (this.options.accordion) {
2912
- if (activeBodies.length > 0) {
2913
- this._setExpanded(activeBodies[0]);
2914
- }
2915
- } else {
2916
- activeBodies.forEach((el2) => this._setExpanded(el2));
2917
- }
2918
2949
  }
2919
2950
  static get defaults() {
2920
2951
  return _defaults9;
@@ -2932,48 +2963,130 @@ var Collapsible = class _Collapsible extends Component {
2932
2963
  }
2933
2964
  destroy() {
2934
2965
  this._removeEventHandlers();
2966
+ this._endTimers.forEach((id) => clearTimeout(id));
2967
+ this._endTimers = [];
2968
+ this._unwrapDetailsBodies();
2935
2969
  this.el["Expressive_Collapsible"] = void 0;
2936
2970
  }
2937
- _setupEventHandlers() {
2938
- this.el.addEventListener("click", this._handleCollapsibleClick);
2939
- this._headers.forEach(
2940
- (header) => header.addEventListener("keydown", this._handleCollapsibleKeydown)
2941
- );
2971
+ _collectItems() {
2972
+ const details = Array.from(this.el.querySelectorAll(":scope > details"));
2973
+ if (details.length) return details;
2974
+ return Array.from(this.el.children).filter((c) => c.tagName === "LI");
2942
2975
  }
2943
- _removeEventHandlers() {
2944
- this.el.removeEventListener("click", this._handleCollapsibleClick);
2945
- this._headers.forEach(
2946
- (header) => header.removeEventListener("keydown", this._handleCollapsibleKeydown)
2947
- );
2976
+ _setupItems() {
2977
+ if (this.options.accordion) {
2978
+ this._items.forEach((item) => {
2979
+ if (item instanceof HTMLDetailsElement && !item.getAttribute("name")) {
2980
+ item.setAttribute("name", this._groupName);
2981
+ }
2982
+ });
2983
+ }
2984
+ const opened = this._items.filter((item) => this._isOpen(item));
2985
+ if (this.options.accordion && opened.length > 1) {
2986
+ opened.slice(1).forEach((item) => {
2987
+ if (item instanceof HTMLDetailsElement) item.open = false;
2988
+ else item.classList.remove("active");
2989
+ });
2990
+ }
2991
+ this._items.forEach((item) => this._syncAria(item));
2948
2992
  }
2949
- _setExpanded(li) {
2950
- li.style.maxHeight = li.scrollHeight + "px";
2993
+ _isOpen(item) {
2994
+ if (item instanceof HTMLDetailsElement) return item.open;
2995
+ return item.classList.contains("active");
2951
2996
  }
2952
- _animateIn(index) {
2953
- const li = this.el.children[index];
2954
- if (!li) return;
2955
- const body = li.querySelector(".collapsible-body");
2956
- const duration = this.options.inDuration;
2957
- body.style.transition = `max-height ${duration}ms ease-out`;
2958
- this._setExpanded(body);
2959
- setTimeout(() => {
2960
- if (typeof this.options.onOpenEnd === "function") {
2961
- this.options.onOpenEnd.call(this, li);
2962
- }
2963
- }, duration);
2997
+ _setOpen(item, open) {
2998
+ if (item instanceof HTMLDetailsElement) {
2999
+ item.open = open;
3000
+ return;
3001
+ }
3002
+ item.classList.toggle("active", open);
3003
+ this._syncAria(item);
3004
+ this._notify(item, open);
3005
+ }
3006
+ _headerOf(item) {
3007
+ if (item instanceof HTMLDetailsElement) {
3008
+ return item.querySelector(":scope > summary");
3009
+ }
3010
+ return item.querySelector(":scope > .collapsible-header");
3011
+ }
3012
+ _wrapDetailsBodies() {
3013
+ this._items.forEach((item) => {
3014
+ if (!(item instanceof HTMLDetailsElement)) return;
3015
+ if (item.querySelector(":scope > .collapsible-body")) return;
3016
+ const body = document.createElement("div");
3017
+ body.className = "collapsible-body";
3018
+ Array.from(item.childNodes).forEach((node) => {
3019
+ if (node instanceof HTMLElement && node.tagName === "SUMMARY") return;
3020
+ body.appendChild(node);
3021
+ });
3022
+ item.appendChild(body);
3023
+ this._wrappedBodies.push(body);
3024
+ });
2964
3025
  }
2965
- _animateOut(index) {
2966
- const li = this.el.children[index];
2967
- if (!li) return;
2968
- const body = li.querySelector(".collapsible-body");
2969
- const duration = this.options.outDuration;
2970
- body.style.transition = `max-height ${duration}ms ease-out`;
2971
- body.style.maxHeight = "0";
2972
- setTimeout(() => {
2973
- if (typeof this.options.onCloseEnd === "function") {
2974
- this.options.onCloseEnd.call(this, li);
2975
- }
2976
- }, duration);
3026
+ _unwrapDetailsBodies() {
3027
+ this._wrappedBodies.forEach((body) => {
3028
+ const parent = body.parentNode;
3029
+ if (!parent) return;
3030
+ while (body.firstChild) parent.insertBefore(body.firstChild, body);
3031
+ body.remove();
3032
+ });
3033
+ this._wrappedBodies = [];
3034
+ }
3035
+ _bodyOf(item) {
3036
+ return item.querySelector(":scope > .collapsible-body");
3037
+ }
3038
+ _syncAria(item) {
3039
+ if (item instanceof HTMLDetailsElement) return;
3040
+ const header = this._headerOf(item);
3041
+ const body = this._bodyOf(item);
3042
+ if (!header) return;
3043
+ header.setAttribute("aria-expanded", String(this._isOpen(item)));
3044
+ if (body) {
3045
+ if (!body.id) body.id = `collapsible-body-${Utils.guid()}`;
3046
+ header.setAttribute("aria-controls", body.id);
3047
+ }
3048
+ if (!header.matches("a, button, summary")) {
3049
+ header.setAttribute("role", "button");
3050
+ if (header.tabIndex < 0) header.tabIndex = 0;
3051
+ }
3052
+ }
3053
+ _setupEventHandlers() {
3054
+ this.el.addEventListener("click", this._handleClick);
3055
+ this.el.addEventListener("keydown", this._handleKeydown);
3056
+ this.el.addEventListener("toggle", this._handleToggle, true);
3057
+ }
3058
+ _removeEventHandlers() {
3059
+ this.el.removeEventListener("click", this._handleClick);
3060
+ this.el.removeEventListener("keydown", this._handleKeydown);
3061
+ this.el.removeEventListener("toggle", this._handleToggle, true);
3062
+ }
3063
+ _notify(item, opening) {
3064
+ const start = opening ? this.options.onOpenStart : this.options.onCloseStart;
3065
+ if (typeof start === "function") start.call(this, item);
3066
+ const end = opening ? this.options.onOpenEnd : this.options.onCloseEnd;
3067
+ if (typeof end !== "function") return;
3068
+ const target = this._bodyOf(item) ?? item;
3069
+ let done = false;
3070
+ const finish = () => {
3071
+ if (done) return;
3072
+ done = true;
3073
+ target.removeEventListener("transitionend", onEnd);
3074
+ end.call(this, item);
3075
+ };
3076
+ const onEnd = (ev) => {
3077
+ if (ev.target !== target) return;
3078
+ finish();
3079
+ };
3080
+ target.addEventListener("transitionend", onEnd);
3081
+ this._endTimers.push(window.setTimeout(finish, this._durationMs() + 50));
3082
+ }
3083
+ _durationMs() {
3084
+ const raw = getComputedStyle(this.el).getPropertyValue("--md-comp-collapsible-duration").trim();
3085
+ if (!raw) return 500;
3086
+ if (raw.endsWith("ms")) return parseFloat(raw);
3087
+ if (raw.endsWith("s")) return parseFloat(raw) * 1e3;
3088
+ const n = parseFloat(raw);
3089
+ return Number.isFinite(n) ? n : 500;
2977
3090
  }
2978
3091
  };
2979
3092
 
@@ -4773,38 +4886,22 @@ var Modal = _Modal;
4773
4886
  // src/ts/components/parallax.ts
4774
4887
  var _defaults15 = {
4775
4888
  responsiveThreshold: 0
4776
- // breakpoint for swipeable
4777
4889
  };
4778
- var _Parallax = class _Parallax extends Component {
4890
+ var Parallax = class _Parallax extends Component {
4779
4891
  constructor(el, options) {
4780
4892
  super(el, options, _Parallax);
4781
- this._handleImageLoad = () => {
4782
- this._updateParallax();
4783
- };
4784
4893
  this.el["Expressive_Parallax"] = this;
4785
4894
  this.options = {
4786
4895
  ..._Parallax.defaults,
4787
4896
  ...options
4788
4897
  };
4789
- this._enabled = window.innerWidth > this.options.responsiveThreshold;
4790
- this._img = this.el.querySelector("img");
4791
- if (!this._img) {
4792
- console.error("Parallax: no <img> to move inside the .parallax element");
4793
- return;
4898
+ if (!this.el.querySelector("img, video")) {
4899
+ console.error("Parallax: no <img> or <video> to move inside the .parallax element");
4794
4900
  }
4795
- this._updateParallax();
4796
- this._setupEventHandlers();
4797
- this._setupStyles();
4798
- _Parallax._parallaxes.push(this);
4799
4901
  }
4800
4902
  static get defaults() {
4801
4903
  return _defaults15;
4802
4904
  }
4803
- /**
4804
- * Initializes instances of Parallax.
4805
- * @param els HTML elements.
4806
- * @param options Component options.
4807
- */
4808
4905
  static init(els, options = {}) {
4809
4906
  return super.init(els, options, _Parallax);
4810
4907
  }
@@ -4812,84 +4909,9 @@ var _Parallax = class _Parallax extends Component {
4812
4909
  return el["Expressive_Parallax"];
4813
4910
  }
4814
4911
  destroy() {
4815
- const index = _Parallax._parallaxes.indexOf(this);
4816
- if (index >= 0) _Parallax._parallaxes.splice(index, 1);
4817
- if (this._img) this._img.style.transform = "";
4818
- this._removeEventHandlers();
4819
4912
  this.el["Expressive_Parallax"] = void 0;
4820
4913
  }
4821
- static _handleScroll() {
4822
- for (let i = 0; i < _Parallax._parallaxes.length; i++) {
4823
- const parallaxInstance = _Parallax._parallaxes[i];
4824
- parallaxInstance._updateParallax.call(parallaxInstance);
4825
- }
4826
- }
4827
- static _handleWindowResize() {
4828
- for (let i = 0; i < _Parallax._parallaxes.length; i++) {
4829
- const parallaxInstance = _Parallax._parallaxes[i];
4830
- parallaxInstance._enabled = window.innerWidth > parallaxInstance.options.responsiveThreshold;
4831
- }
4832
- }
4833
- _setupEventHandlers() {
4834
- this._img?.addEventListener("load", this._handleImageLoad);
4835
- if (_Parallax._parallaxes.length === 0) {
4836
- if (!_Parallax._handleWindowResizeThrottled) {
4837
- _Parallax._handleWindowResizeThrottled = Utils.throttle(_Parallax._handleWindowResize, 100);
4838
- }
4839
- window.addEventListener("scroll", _Parallax._requestScrollUpdate, { passive: true });
4840
- window.addEventListener("resize", _Parallax._handleWindowResizeThrottled, { passive: true });
4841
- }
4842
- }
4843
- _removeEventHandlers() {
4844
- this._img?.removeEventListener("load", this._handleImageLoad);
4845
- if (_Parallax._parallaxes.length === 0) {
4846
- window.removeEventListener("scroll", _Parallax._requestScrollUpdate);
4847
- window.removeEventListener("resize", _Parallax._handleWindowResizeThrottled);
4848
- if (_Parallax._scrollFrame !== null) {
4849
- cancelAnimationFrame(_Parallax._scrollFrame);
4850
- _Parallax._scrollFrame = null;
4851
- }
4852
- }
4853
- }
4854
- _setupStyles() {
4855
- if (this._img) this._img.style.opacity = "1";
4856
- }
4857
- _updateParallax() {
4858
- const rect = this.el.getBoundingClientRect();
4859
- const containerHeight = rect.height > 0 ? this.el.parentElement.offsetHeight : 500;
4860
- const imgHeight = this._img.offsetHeight;
4861
- const parallaxDist = imgHeight - containerHeight;
4862
- const top = rect.top + window.scrollY - document.documentElement.clientTop;
4863
- const bottom = top + containerHeight;
4864
- const scrollTop = Utils.getDocumentScrollTop();
4865
- const windowHeight = window.innerHeight;
4866
- const windowBottom = scrollTop + windowHeight;
4867
- const percentScrolled = (windowBottom - top) / (containerHeight + windowHeight);
4868
- const parallax = parallaxDist * percentScrolled;
4869
- if (!this._enabled) {
4870
- this._img.style.transform = "";
4871
- } else if (bottom > scrollTop && top < scrollTop + windowHeight) {
4872
- this._img.style.transform = `translate3D(-50%, ${parallax}px, 0)`;
4873
- }
4874
- }
4875
4914
  };
4876
- _Parallax._parallaxes = [];
4877
- _Parallax._scrollFrame = null;
4878
- /**
4879
- * Coalesce scroll events onto animation frames.
4880
- *
4881
- * The handler reads layout and then writes a transform, so running it more
4882
- * than once per frame is wasted work - and the 5ms throttle it replaces was
4883
- * short enough to let several run per frame anyway.
4884
- */
4885
- _Parallax._requestScrollUpdate = () => {
4886
- if (_Parallax._scrollFrame !== null) return;
4887
- _Parallax._scrollFrame = requestAnimationFrame(() => {
4888
- _Parallax._scrollFrame = null;
4889
- _Parallax._handleScroll();
4890
- });
4891
- };
4892
- var Parallax = _Parallax;
4893
4915
 
4894
4916
  // src/ts/components/pushpin.ts
4895
4917
  var _defaults16 = {
@@ -6129,60 +6151,96 @@ var _defaults21 = {
6129
6151
  var _TapTarget = class _TapTarget extends Component {
6130
6152
  constructor(el, options) {
6131
6153
  super(el, options, _TapTarget);
6132
- // Built once per instance; see the note in carousel.ts - the previous form
6133
- // returned a new throttled function on every resize and never invoked it.
6134
- this._handleThrottledResize = Utils.throttle(() => this._handleResize(), 200);
6135
- this._handleKeyboardInteraction = (e) => {
6136
- if (Utils.keys.ENTER.includes(e.key)) {
6137
- this._handleTargetToggle();
6138
- }
6139
- };
6140
- this._handleTargetToggle = () => {
6141
- if (!this.isOpen) this.open();
6142
- else this.close();
6143
- };
6144
- /*_handleOriginClick = () => {
6145
- this.close();
6146
- }*/
6147
- this._handleResize = () => {
6148
- this._calculatePositioning();
6149
- };
6150
- this._handleDocumentClick = (e) => {
6151
- const target = e.target;
6152
- if (!(target instanceof Element)) return;
6153
- if (!this.originEl.contains(target) && !target.closest(".tap-target-wrapper")) {
6154
- this.close();
6155
- }
6156
- };
6154
+ this.wrapper = null;
6155
+ this.waveEl = null;
6156
+ this.contentEl = null;
6157
+ this._createdWrapper = false;
6158
+ this._createdWave = false;
6159
+ this._usePopover = false;
6160
+ this._liftedStatic = false;
6161
+ this._opening = false;
6157
6162
  /**
6158
6163
  * Open Tap Target.
6159
6164
  */
6160
6165
  this.open = () => {
6161
- if (this.isOpen) return;
6166
+ if (this.isOpen || !this.originEl || !this.wrapper) return;
6167
+ _TapTarget._taptargets.forEach((other) => {
6168
+ if (other !== this) other.close();
6169
+ });
6170
+ this._calculatePositioning();
6162
6171
  if (typeof this.options.onOpen === "function") {
6163
6172
  this.options.onOpen.call(this, this.originEl);
6164
6173
  }
6165
6174
  this.isOpen = true;
6166
6175
  this.wrapper.classList.add("open");
6167
- this.wrapper.ariaExpanded = "true";
6168
- document.body.addEventListener("click", this._handleDocumentClick, true);
6169
- document.body.addEventListener("keypress", this._handleDocumentClick, true);
6170
- document.body.addEventListener("touchend", this._handleDocumentClick);
6176
+ this.originEl.setAttribute("aria-expanded", "true");
6177
+ if (!this._usePopover) {
6178
+ this.originEl.classList.add("tap-target-origin");
6179
+ if (getComputedStyle(this.originEl).position === "static") {
6180
+ this.originEl.style.position = "relative";
6181
+ this._liftedStatic = true;
6182
+ }
6183
+ }
6184
+ if (this._usePopover) {
6185
+ this._opening = true;
6186
+ try {
6187
+ this.wrapper.showPopover();
6188
+ } catch {
6189
+ }
6190
+ requestAnimationFrame(() => {
6191
+ this._opening = false;
6192
+ });
6193
+ }
6171
6194
  };
6172
6195
  /**
6173
6196
  * Close Tap Target.
6174
6197
  */
6175
6198
  this.close = () => {
6176
6199
  if (!this.isOpen) return;
6177
- if (typeof this.options.onClose === "function") {
6200
+ if (typeof this.options.onClose === "function" && this.originEl) {
6178
6201
  this.options.onClose.call(this, this.originEl);
6179
6202
  }
6180
6203
  this.isOpen = false;
6181
- this.wrapper.classList.remove("open");
6182
- this.wrapper.ariaExpanded = "false";
6183
- document.body.removeEventListener("click", this._handleDocumentClick, true);
6184
- document.body.removeEventListener("keypress", this._handleDocumentClick, true);
6185
- document.body.removeEventListener("touchend", this._handleDocumentClick);
6204
+ this.wrapper?.classList.remove("open");
6205
+ this.originEl?.setAttribute("aria-expanded", "false");
6206
+ this.originEl?.classList.remove("tap-target-origin");
6207
+ if (this._liftedStatic && this.originEl) {
6208
+ this.originEl.style.position = "";
6209
+ this._liftedStatic = false;
6210
+ }
6211
+ if (this._usePopover && this.wrapper) {
6212
+ try {
6213
+ this.wrapper.hidePopover();
6214
+ } catch {
6215
+ }
6216
+ }
6217
+ };
6218
+ // Built once per instance; a fresh throttle per event never fires.
6219
+ this._handleThrottledResize = Utils.throttle(() => this._handleResize(), 200);
6220
+ this._handleOriginKeydown = (e) => {
6221
+ if (Utils.keys.ENTER.includes(e.key) || e.key === " ") {
6222
+ e.preventDefault();
6223
+ this._handleTargetToggle();
6224
+ }
6225
+ };
6226
+ this._handleTargetToggle = () => {
6227
+ if (!this.isOpen) this.open();
6228
+ else this.close();
6229
+ };
6230
+ this._handleResize = () => {
6231
+ if (this.isOpen) this._calculatePositioning();
6232
+ };
6233
+ this._handlePopoverToggle = (e) => {
6234
+ const state = "newState" in e ? String(e.newState) : "";
6235
+ if (state === "open") return;
6236
+ if (this._opening) {
6237
+ try {
6238
+ this.wrapper?.showPopover();
6239
+ } catch {
6240
+ }
6241
+ return;
6242
+ }
6243
+ if (this.isOpen) this.close();
6186
6244
  };
6187
6245
  this.el["Expressive_TapTarget"] = this;
6188
6246
  this.options = {
@@ -6195,7 +6253,6 @@ var _TapTarget = class _TapTarget extends Component {
6195
6253
  console.error(`TapTarget: no element with id "${el.dataset.target ?? ""}" to attach to`);
6196
6254
  return;
6197
6255
  }
6198
- this.originEl.tabIndex = 0;
6199
6256
  this._setup();
6200
6257
  this._calculatePositioning();
6201
6258
  this._setupEventHandlers();
@@ -6204,11 +6261,6 @@ var _TapTarget = class _TapTarget extends Component {
6204
6261
  static get defaults() {
6205
6262
  return _defaults21;
6206
6263
  }
6207
- /**
6208
- * Initializes instances of TapTarget.
6209
- * @param els HTML elements.
6210
- * @param options Component options.
6211
- */
6212
6264
  static init(els, options = {}) {
6213
6265
  return super.init(els, options, _TapTarget);
6214
6266
  }
@@ -6218,116 +6270,95 @@ var _TapTarget = class _TapTarget extends Component {
6218
6270
  destroy() {
6219
6271
  this.close();
6220
6272
  this._removeEventHandlers();
6273
+ this._teardownDom();
6221
6274
  this.el["Expressive_TapTarget"] = void 0;
6222
6275
  const index = _TapTarget._taptargets.indexOf(this);
6223
- if (index >= 0) {
6224
- _TapTarget._taptargets.splice(index, 1);
6225
- }
6226
- }
6227
- _setupEventHandlers() {
6228
- if (!this.originEl) return;
6229
- this.originEl.addEventListener("click", this._handleTargetToggle);
6230
- this.originEl.addEventListener("keypress", this._handleKeyboardInteraction, true);
6231
- window.addEventListener("resize", this._handleThrottledResize);
6232
- }
6233
- _removeEventHandlers() {
6234
- if (!this.originEl) return;
6235
- this.originEl.removeEventListener("click", this._handleTargetToggle);
6236
- this.originEl.removeEventListener("keypress", this._handleKeyboardInteraction, true);
6237
- window.removeEventListener("resize", this._handleThrottledResize);
6276
+ if (index >= 0) _TapTarget._taptargets.splice(index, 1);
6238
6277
  }
6239
6278
  _setup() {
6240
- this.wrapper = this.el.parentElement;
6241
- this.waveEl = this.wrapper.querySelector(".tap-target-wave");
6242
- this.el.parentElement.ariaExpanded = "false";
6243
- this.originEl.style.zIndex = "1002";
6244
- this.contentEl = this.el.querySelector(".tap-target-content");
6245
- if (!this.wrapper.classList.contains(".tap-target-wrapper")) {
6279
+ const parent = this.el.parentElement;
6280
+ if (parent?.classList.contains("tap-target-wrapper")) {
6281
+ this.wrapper = parent;
6282
+ } else {
6246
6283
  this.wrapper = document.createElement("div");
6247
6284
  this.wrapper.classList.add("tap-target-wrapper");
6248
6285
  this.el.before(this.wrapper);
6249
6286
  this.wrapper.append(this.el);
6287
+ this._createdWrapper = true;
6250
6288
  }
6289
+ this.contentEl = this.el.querySelector(".tap-target-content");
6251
6290
  if (!this.contentEl) {
6252
6291
  this.contentEl = document.createElement("div");
6253
- this.contentEl.classList.add("tap-target-content");
6292
+ this.contentEl.className = "tap-target-content";
6293
+ while (this.el.firstChild) this.contentEl.append(this.el.firstChild);
6254
6294
  this.el.append(this.contentEl);
6255
6295
  }
6296
+ this.waveEl = this.el.querySelector(".tap-target-wave") ?? this.wrapper.querySelector(".tap-target-wave");
6256
6297
  if (!this.waveEl) {
6257
6298
  this.waveEl = document.createElement("div");
6258
- this.waveEl.classList.add("tap-target-wave");
6259
- this.wrapper.append(this.waveEl);
6299
+ this.waveEl.className = "tap-target-wave";
6300
+ this._createdWave = true;
6301
+ }
6302
+ this.el.prepend(this.waveEl);
6303
+ this.el.setAttribute("role", "dialog");
6304
+ this._labelFromHeading();
6305
+ const origin = this.originEl;
6306
+ origin.setAttribute("aria-expanded", "false");
6307
+ origin.setAttribute("aria-controls", this.el.id || this._ensureId());
6308
+ this._usePopover = typeof this.wrapper.showPopover === "function";
6309
+ if (this._usePopover) {
6310
+ this.wrapper.setAttribute("popover", "auto");
6311
+ }
6312
+ }
6313
+ _ensureId() {
6314
+ if (!this.el.id) this.el.id = `tap-target-${Utils.guid()}`;
6315
+ return this.el.id;
6316
+ }
6317
+ _labelFromHeading() {
6318
+ const heading = this.contentEl?.querySelector("h1, h2, h3, h4, h5, h6");
6319
+ if (!heading) return;
6320
+ if (!heading.id) heading.id = `tap-target-title-${Utils.guid()}`;
6321
+ this.el.setAttribute("aria-labelledby", heading.id);
6322
+ }
6323
+ _teardownDom() {
6324
+ if (this._createdWave) this.waveEl?.remove();
6325
+ if (this._createdWrapper && this.wrapper) {
6326
+ this.wrapper.before(this.el);
6327
+ this.wrapper.remove();
6328
+ }
6329
+ this.wrapper = null;
6330
+ this.waveEl = null;
6331
+ }
6332
+ _setupEventHandlers() {
6333
+ if (!this.originEl) return;
6334
+ this.originEl.addEventListener("click", this._handleTargetToggle);
6335
+ if (!this.originEl.matches('button, a, [tabindex], .btn, [role="button"]')) {
6336
+ this.originEl.tabIndex = 0;
6337
+ this.originEl.addEventListener("keydown", this._handleOriginKeydown);
6260
6338
  }
6339
+ this.wrapper?.addEventListener("toggle", this._handlePopoverToggle);
6340
+ window.addEventListener("resize", this._handleThrottledResize, { passive: true });
6261
6341
  }
6262
- _offset(el) {
6263
- const box = el.getBoundingClientRect();
6264
- const docElem = document.documentElement;
6265
- return {
6266
- top: box.top + window.pageYOffset - docElem.clientTop,
6267
- left: box.left + window.pageXOffset - docElem.clientLeft
6268
- };
6342
+ _removeEventHandlers() {
6343
+ if (!this.originEl) return;
6344
+ this.originEl.removeEventListener("click", this._handleTargetToggle);
6345
+ this.originEl.removeEventListener("keydown", this._handleOriginKeydown);
6346
+ this.wrapper?.removeEventListener("toggle", this._handlePopoverToggle);
6347
+ window.removeEventListener("resize", this._handleThrottledResize);
6269
6348
  }
6270
6349
  _calculatePositioning() {
6271
- let isFixed = getComputedStyle(this.originEl).position === "fixed";
6272
- if (!isFixed) {
6273
- let currentElem = this.originEl;
6274
- const parents = [];
6275
- while ((currentElem = currentElem.parentNode) && currentElem !== document)
6276
- parents.push(currentElem);
6277
- for (let i = 0; i < parents.length; i++) {
6278
- isFixed = getComputedStyle(parents[i]).position === "fixed";
6279
- if (isFixed) break;
6280
- }
6281
- }
6282
- const originWidth = this.originEl.offsetWidth;
6283
- const originHeight = this.originEl.offsetHeight;
6284
- const originOffset = this._offset(this.originEl);
6285
- const originTop = isFixed ? originOffset.top - Utils.getDocumentScrollTop() : originOffset.top;
6286
- const originLeft = isFixed ? originOffset.left - Utils.getDocumentScrollLeft() : originOffset.left;
6287
- const windowWidth = window.innerWidth;
6288
- const windowHeight = window.innerHeight;
6289
- const scrollBarWidth = windowWidth - document.documentElement.clientWidth;
6290
- const centerX = windowWidth / 2;
6291
- const centerY = windowHeight / 2;
6292
- const isLeft = originLeft <= centerX;
6293
- const isRight = originLeft > centerX;
6294
- const isTop = originTop <= centerY;
6295
- const isBottom = originTop > centerY;
6296
- const isCenterX = originLeft >= windowWidth * 0.25 && originLeft <= windowWidth * 0.75;
6297
- const tapTargetWidth = this.el.offsetWidth;
6298
- const tapTargetHeight = this.el.offsetHeight;
6299
- const tapTargetTop = originTop + originHeight / 2 - tapTargetHeight / 2;
6300
- const tapTargetLeft = originLeft + originWidth / 2 - tapTargetWidth / 2;
6301
- const tapTargetPosition = isFixed ? "fixed" : "absolute";
6302
- const tapTargetTextWidth = isCenterX ? tapTargetWidth : tapTargetWidth / 2 + originWidth;
6303
- const tapTargetTextHeight = tapTargetHeight / 2;
6304
- const tapTargetTextTop = isTop ? tapTargetHeight / 2 : 0;
6305
- const tapTargetTextBottom = 0;
6306
- const tapTargetTextLeft = isLeft && !isCenterX ? tapTargetWidth / 2 - originWidth : 0;
6307
- const tapTargetTextRight = 0;
6308
- const tapTargetTextPadding = originWidth;
6309
- const tapTargetTextAlign = isBottom ? "bottom" : "top";
6310
- const tapTargetWaveWidth = originWidth > originHeight ? originWidth * 2 : originWidth * 2;
6311
- const tapTargetWaveHeight = tapTargetWaveWidth;
6312
- const tapTargetWaveTop = tapTargetHeight / 2 - tapTargetWaveHeight / 2;
6313
- const tapTargetWaveLeft = tapTargetWidth / 2 - tapTargetWaveWidth / 2;
6314
- this.wrapper.style.top = isTop ? tapTargetTop + "px" : "";
6315
- this.wrapper.style.right = isRight ? windowWidth - tapTargetLeft - tapTargetWidth - scrollBarWidth + "px" : "";
6316
- this.wrapper.style.bottom = isBottom ? windowHeight - tapTargetTop - tapTargetHeight + "px" : "";
6317
- this.wrapper.style.left = isLeft ? tapTargetLeft + "px" : "";
6318
- this.wrapper.style.position = tapTargetPosition;
6319
- this.contentEl.style.width = tapTargetTextWidth + "px";
6320
- this.contentEl.style.height = tapTargetTextHeight + "px";
6321
- this.contentEl.style.top = tapTargetTextTop + "px";
6322
- this.contentEl.style.right = tapTargetTextRight + "px";
6323
- this.contentEl.style.bottom = tapTargetTextBottom + "px";
6324
- this.contentEl.style.left = tapTargetTextLeft + "px";
6325
- this.contentEl.style.padding = tapTargetTextPadding + "px";
6326
- this.contentEl.style.verticalAlign = tapTargetTextAlign;
6327
- this.waveEl.style.top = tapTargetWaveTop + "px";
6328
- this.waveEl.style.left = tapTargetWaveLeft + "px";
6329
- this.waveEl.style.width = tapTargetWaveWidth + "px";
6330
- this.waveEl.style.height = tapTargetWaveHeight + "px";
6350
+ if (!this.originEl || !this.wrapper) return;
6351
+ const rect = this.originEl.getBoundingClientRect();
6352
+ const x = rect.left + rect.width / 2;
6353
+ const y = rect.top + rect.height / 2;
6354
+ const size = Math.max(rect.width, rect.height, 1);
6355
+ this.wrapper.style.setProperty("--md-comp-tap-target-x", `${x}px`);
6356
+ this.wrapper.style.setProperty("--md-comp-tap-target-y", `${y}px`);
6357
+ this.wrapper.style.setProperty("--md-comp-tap-target-origin-size", `${size}px`);
6358
+ const vertical = y <= window.innerHeight / 2 ? "top" : "bottom";
6359
+ const onCenter = x >= window.innerWidth * 0.25 && x <= window.innerWidth * 0.75;
6360
+ const horizontal = onCenter ? "center" : x <= window.innerWidth / 2 ? "left" : "right";
6361
+ this.wrapper.dataset.edge = `${vertical}-${horizontal}`;
6331
6362
  }
6332
6363
  };
6333
6364
  _TapTarget._taptargets = [];
@@ -7071,7 +7102,7 @@ var _Toast = class _Toast {
7071
7102
  close.className = "circle";
7072
7103
  close.setAttribute("aria-label", "Dismiss");
7073
7104
  const icon = document.createElement("i");
7074
- icon.className = "material-icons";
7105
+ icon.className = "material-symbols";
7075
7106
  icon.setAttribute("aria-hidden", "true");
7076
7107
  icon.textContent = "close";
7077
7108
  close.appendChild(icon);
@@ -7436,7 +7467,7 @@ function AutoInit(context = document.body, options) {
7436
7467
  }
7437
7468
 
7438
7469
  // src/ts/index.ts
7439
- var version = "0.1.0";
7470
+ var version = "0.3.0";
7440
7471
  if (typeof document !== "undefined") {
7441
7472
  document.addEventListener("keydown", Utils.docHandleKeydown, true);
7442
7473
  document.addEventListener("keyup", Utils.docHandleKeyup, true);
@@ -7448,4 +7479,5 @@ Chips.Init();
7448
7479
  Waves.Init();
7449
7480
  Range.Init();
7450
7481
  Cards.Init();
7482
+ Dialogs.Init();
7451
7483
  //# sourceMappingURL=expressive.cjs.map