@expressivecss/expressive 0.2.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 (37) hide show
  1. package/README.md +8 -8
  2. package/dist/css/expressive.css +1038 -361
  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 +708 -746
  7. package/dist/js/expressive.cjs.map +3 -3
  8. package/dist/js/expressive.js +708 -746
  9. package/dist/js/expressive.js.map +3 -3
  10. package/dist/js/expressive.min.js +10 -10
  11. package/dist/js/expressive.min.js.map +3 -3
  12. package/dist/js/expressive.mjs +708 -746
  13. package/dist/js/expressive.mjs.map +3 -3
  14. package/dist/types/components/buttons.d.ts +17 -41
  15. package/dist/types/components/carousel.d.ts +40 -46
  16. package/dist/types/components/chips.d.ts +2 -2
  17. package/dist/types/components/collapsible.d.ts +35 -13
  18. package/dist/types/components/parallax.d.ts +7 -31
  19. package/dist/types/components/tapTarget.d.ts +23 -24
  20. package/dist/types/index.d.ts +1 -1
  21. package/package.json +1 -1
  22. package/src/sass/README.md +4 -3
  23. package/src/sass/components/_badges.scss +5 -4
  24. package/src/sass/components/_breadcrumb.scss +93 -16
  25. package/src/sass/components/_buttons.scss +195 -96
  26. package/src/sass/components/_carousel.scss +130 -63
  27. package/src/sass/components/_chips.scss +1 -1
  28. package/src/sass/components/_collapsible.scss +160 -60
  29. package/src/sass/components/_collection.scss +161 -77
  30. package/src/sass/components/_icons-material-design.scss +77 -4
  31. package/src/sass/components/_lightbox.scss +4 -0
  32. package/src/sass/components/_navbar.scss +15 -0
  33. package/src/sass/components/_pagination.scss +131 -25
  34. package/src/sass/components/_parallax.scss +116 -20
  35. package/src/sass/components/_sidenav.scss +10 -5
  36. package/src/sass/components/_slider.scss +8 -0
  37. package/src/sass/components/_tapTarget.scss +134 -47
@@ -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,
@@ -2064,83 +2064,76 @@ var _defaults6 = {
2064
2064
  var FloatingActionButton = class _FloatingActionButton extends Component {
2065
2065
  constructor(el, options) {
2066
2066
  super(el, options, _FloatingActionButton);
2067
- /** Toolbar-mode backdrop, kept so closing can take it back out. */
2068
- this._backdrop = null;
2069
- this._handleFABClick = () => {
2070
- this._handleFABToggle();
2071
- };
2072
- this._handleFABKeyPress = (e) => {
2073
- if (Utils.keys.ENTER.includes(e.key)) {
2074
- this._handleFABToggle();
2075
- }
2076
- };
2077
- this._handleFABToggle = () => {
2078
- if (this.isOpen) {
2079
- this.close();
2080
- } else {
2081
- this.open();
2082
- }
2083
- };
2084
- this._handleDocumentClick = (e) => {
2085
- const elem = e.target;
2086
- if (elem !== this._menu) this.close();
2087
- };
2088
2067
  /**
2089
2068
  * Open FAB.
2090
2069
  */
2091
2070
  this.open = () => {
2092
2071
  if (this.isOpen) return;
2093
- if (this.options.toolbarEnabled) this._animateInToolbar();
2094
- else this._animateInFAB();
2095
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);
2096
2077
  };
2097
2078
  /**
2098
2079
  * Close FAB.
2099
2080
  */
2100
2081
  this.close = () => {
2101
2082
  if (!this.isOpen) return;
2102
- if (this.options.toolbarEnabled) {
2103
- window.removeEventListener("scroll", this.close, true);
2104
- document.body.removeEventListener("click", this._handleDocumentClick, true);
2105
- this._animateOutToolbar();
2106
- } else {
2107
- this._animateOutFAB();
2108
- }
2109
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();
2110
2106
  };
2111
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;
2112
2111
  this.options = {
2113
2112
  ..._FloatingActionButton.defaults,
2114
- ...options
2113
+ ...options,
2114
+ direction: options.direction ?? fromMarkup ?? _FloatingActionButton.defaults.direction,
2115
+ hoverEnabled: !clickToToggle,
2116
+ toolbarEnabled: toolbar
2115
2117
  };
2116
2118
  this.isOpen = false;
2117
- this._anchor = this.el.querySelector("a, button");
2118
- this._menu = this.el.querySelector("ul");
2119
- this._floatingBtns = Array.from(this.el.querySelectorAll("ul .btn-floating"));
2120
- this._floatingBtnsReverse = this._floatingBtns.reverse();
2121
- this.offsetY = 0;
2122
- this.offsetX = 0;
2119
+ this._anchor = this.el.querySelector(":scope > a, :scope > button");
2120
+ this._menu = this.el.querySelector("ul, menu");
2123
2121
  this.el.classList.add(`direction-${this.options.direction}`);
2124
- if (this._anchor) this._anchor.tabIndex = 0;
2125
- if (this._menu) this._menu.ariaExpanded = "false";
2126
- if (this.options.direction === "top")
2127
- this.offsetY = 40;
2128
- else if (this.options.direction === "right")
2129
- this.offsetX = -40;
2130
- else if (this.options.direction === "bottom")
2131
- this.offsetY = -40;
2132
- else
2133
- 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");
2134
2132
  this._setupEventHandlers();
2135
2133
  }
2136
2134
  static get defaults() {
2137
2135
  return _defaults6;
2138
2136
  }
2139
- /**
2140
- * Initializes instances of FloatingActionButton.
2141
- * @param els HTML elements.
2142
- * @param options Component options.
2143
- */
2144
2137
  static init(els, options = {}) {
2145
2138
  return super.init(els, options, _FloatingActionButton);
2146
2139
  }
@@ -2148,229 +2141,109 @@ var FloatingActionButton = class _FloatingActionButton extends Component {
2148
2141
  return el["Expressive_FloatingActionButton"];
2149
2142
  }
2150
2143
  destroy() {
2151
- this._removeEventHandlers();
2152
2144
  this.close();
2145
+ this._removeEventHandlers();
2153
2146
  this.el["Expressive_FloatingActionButton"] = void 0;
2154
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
+ }
2155
2156
  _setupEventHandlers() {
2156
- if (this.options.hoverEnabled && !this.options.toolbarEnabled) {
2157
- this.el.addEventListener("mouseenter", this.open);
2158
- this.el.addEventListener("mouseleave", this.close);
2159
- } else {
2160
- 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);
2161
2160
  }
2162
- this.el.addEventListener("keypress", this._handleFABKeyPress);
2163
2161
  }
2164
2162
  _removeEventHandlers() {
2165
- if (this.options.hoverEnabled && !this.options.toolbarEnabled) {
2166
- this.el.removeEventListener("mouseenter", this.open);
2167
- this.el.removeEventListener("mouseleave", this.close);
2168
- } else {
2169
- this.el.removeEventListener("click", this._handleFABClick);
2170
- }
2171
- this.el.removeEventListener("keypress", this._handleFABKeyPress);
2172
- }
2173
- _animateInFAB() {
2174
- this.el.classList.add("active");
2175
- if (this._menu) this._menu.ariaExpanded = "true";
2176
- const delayIncrement = 40;
2177
- const duration = 275;
2178
- this._floatingBtnsReverse.forEach((el, index) => {
2179
- const delay = delayIncrement * index;
2180
- el.style.transition = "none";
2181
- el.style.opacity = "0";
2182
- el.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px) scale(0.4)`;
2183
- setTimeout(() => {
2184
- el.style.opacity = "0.4";
2185
- setTimeout(() => {
2186
- el.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`;
2187
- el.style.opacity = "1";
2188
- el.style.transform = "translate(0, 0) scale(1)";
2189
- el.tabIndex = 0;
2190
- }, 1);
2191
- }, delay);
2192
- });
2193
- }
2194
- _animateOutFAB() {
2195
- const duration = 175;
2196
- setTimeout(() => {
2197
- this.el.classList.remove("active");
2198
- if (this._menu) this._menu.ariaExpanded = "false";
2199
- }, duration);
2200
- this._floatingBtnsReverse.forEach((el) => {
2201
- el.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`;
2202
- el.style.opacity = "0";
2203
- el.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px) scale(0.4)`;
2204
- el.tabIndex = -1;
2205
- });
2206
- }
2207
- _animateInToolbar() {
2208
- const windowWidth = window.innerWidth;
2209
- const windowHeight = window.innerHeight;
2210
- const btnRect = this.el.getBoundingClientRect();
2211
- const fabColor = getComputedStyle(this._anchor).backgroundColor;
2212
- const backdrop = document.createElement("div");
2213
- backdrop.classList.add("fab-backdrop");
2214
- backdrop.style.backgroundColor = fabColor;
2215
- this._anchor.append(backdrop);
2216
- this._backdrop = backdrop;
2217
- const backdropWidth = backdrop.clientWidth;
2218
- const scaleFactor = backdropWidth > 0 ? windowWidth / backdropWidth : 1;
2219
- this.offsetX = btnRect.left - windowWidth / 2 + btnRect.width / 2;
2220
- this.offsetY = windowHeight - btnRect.bottom;
2221
- this.btnBottom = btnRect.bottom;
2222
- this.btnLeft = btnRect.left;
2223
- this.btnWidth = btnRect.width;
2224
- this.el.classList.add("active");
2225
- this.el.style.textAlign = "center";
2226
- this.el.style.width = "100%";
2227
- this.el.style.bottom = "0";
2228
- this.el.style.left = "0";
2229
- this.el.style.transform = "translateX(" + this.offsetX + "px)";
2230
- this.el.style.transition = "none";
2231
- if (this._menu) this._menu.ariaExpanded = "true";
2232
- this._anchor.style.transform = `translateY(${this.offsetY}px)`;
2233
- this._anchor.style.transition = "none";
2234
- setTimeout(() => {
2235
- this.el.style.transform = "";
2236
- this.el.style.transition = "transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s";
2237
- this._anchor.style.overflow = "visible";
2238
- this._anchor.style.transform = "";
2239
- this._anchor.style.transition = "transform .2s";
2240
- setTimeout(() => {
2241
- this.el.style.overflow = "hidden";
2242
- this.el.style.backgroundColor = fabColor;
2243
- backdrop.style.transform = "scale(" + scaleFactor + ")";
2244
- backdrop.style.transition = "transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)";
2245
- this._menu.querySelectorAll("li > a").forEach((a) => {
2246
- a.style.opacity = "1";
2247
- a.tabIndex = 0;
2248
- });
2249
- window.addEventListener("scroll", this.close, true);
2250
- document.body.addEventListener("click", this._handleDocumentClick, true);
2251
- }, 100);
2252
- }, 0);
2163
+ this.el.removeEventListener("click", this._handleFABClick);
2164
+ this._anchor?.removeEventListener("keydown", this._handleAnchorKeydown);
2253
2165
  }
2254
- /**
2255
- * Reverse {@link _animateInToolbar}.
2256
- *
2257
- * Closing used to only unbind the listeners, leaving the FAB stretched
2258
- * across the viewport with its backdrop still in the DOM - and a fresh
2259
- * backdrop appended on every subsequent open.
2260
- */
2261
- _animateOutToolbar() {
2262
- const backdrop = this._backdrop;
2263
- this._backdrop = null;
2264
- this._menu?.querySelectorAll("li > a").forEach((a) => {
2265
- a.style.opacity = "";
2266
- a.tabIndex = -1;
2267
- });
2268
- if (backdrop) {
2269
- backdrop.style.transition = "transform .2s";
2270
- backdrop.style.transform = "scale(0)";
2271
- }
2272
- this.el.style.overflow = "";
2273
- this.el.style.backgroundColor = "";
2274
- this.el.style.transition = "transform .2s";
2275
- this.el.style.transform = `translateX(${this.offsetX}px)`;
2276
- this._anchor.style.transition = "transform .2s";
2277
- this._anchor.style.transform = `translateY(${this.offsetY}px)`;
2278
- setTimeout(() => {
2279
- backdrop?.remove();
2280
- this.el.classList.remove("active");
2281
- if (this._menu) this._menu.ariaExpanded = "false";
2282
- this.el.style.textAlign = "";
2283
- this.el.style.width = "";
2284
- this.el.style.bottom = "";
2285
- this.el.style.left = "";
2286
- this.el.style.transform = "";
2287
- this.el.style.transition = "";
2288
- this._anchor.style.overflow = "";
2289
- this._anchor.style.transform = "";
2290
- this._anchor.style.transition = "";
2291
- }, 200);
2166
+ _toggle() {
2167
+ if (this.isOpen) this.close();
2168
+ else this.open();
2292
2169
  }
2293
2170
  };
2294
2171
 
2295
2172
  // src/ts/components/carousel.ts
2296
2173
  var _defaults7 = {
2297
2174
  duration: 200,
2298
- // ms
2299
2175
  dist: -100,
2300
- // zoom scale TODO: make this more intuitive as an option
2301
2176
  shift: 0,
2302
- // spacing for center image
2303
2177
  padding: 0,
2304
- // Padding between non center items
2305
2178
  numVisible: 5,
2306
- // Number of visible items in carousel
2307
2179
  fullWidth: false,
2308
- // Change to full width styles
2309
2180
  indicators: false,
2310
- // Toggle indicators
2311
2181
  noWrap: false,
2312
- // Don't wrap around and cycle through items.
2313
2182
  onCycleTo: null
2314
- // Callback for when a new slide is cycled to.
2315
2183
  };
2316
- var Carousel = class _Carousel extends Component {
2184
+ var _Carousel = class _Carousel extends Component {
2317
2185
  constructor(el, options) {
2318
2186
  super(el, options, _Carousel);
2319
- /** The index of the center carousel item. */
2320
2187
  this.center = 0;
2321
- // Built once per instance. Creating the throttled function inside the
2322
- // handler - as this used to - produced a fresh closure per resize event and
2323
- // 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;
2324
2193
  this._handleThrottledResize = Utils.throttle(() => this._handleResize(), 200);
2325
2194
  this._handleCarouselTap = (e) => {
2326
- 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") {
2327
2197
  e.preventDefault();
2328
2198
  }
2329
2199
  this.pressed = true;
2330
2200
  this.dragged = false;
2331
2201
  this.verticalDragged = false;
2332
- this.reference = this._xpos(e);
2333
- this.referenceY = this._ypos(e);
2202
+ this.reference = e.clientX;
2203
+ this.referenceY = e.clientY;
2334
2204
  this.velocity = this.amplitude = 0;
2335
2205
  this.frame = this.offset;
2336
2206
  this.timestamp = Date.now();
2337
- clearInterval(this.ticker);
2338
- 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);
2339
2218
  };
2340
2219
  this._handleCarouselDrag = (e) => {
2341
- let x, y, delta, deltaY;
2342
- if (this.pressed) {
2343
- x = this._xpos(e);
2344
- y = this._ypos(e);
2345
- delta = this.reference - x;
2346
- deltaY = Math.abs(this.referenceY - y);
2347
- if (deltaY < 30 && !this.verticalDragged) {
2348
- if (delta > 2 || delta < -2) {
2349
- this.dragged = true;
2350
- this.reference = x;
2351
- this._scroll(this.offset + delta);
2352
- }
2353
- } else if (this.dragged) {
2354
- e.preventDefault();
2355
- e.stopPropagation();
2356
- return false;
2357
- } else {
2358
- 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);
2359
2228
  }
2360
- }
2361
- if (this.dragged) {
2229
+ } else if (this.dragged) {
2362
2230
  e.preventDefault();
2363
- e.stopPropagation();
2364
- return false;
2231
+ } else {
2232
+ this.verticalDragged = true;
2365
2233
  }
2234
+ if (this.dragged) e.preventDefault();
2366
2235
  };
2367
2236
  this._handleCarouselRelease = (e) => {
2368
- if (this.pressed) {
2369
- this.pressed = false;
2370
- } else {
2371
- 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 {
2372
2246
  }
2373
- clearInterval(this.ticker);
2374
2247
  this.target = this.offset;
2375
2248
  if (this.velocity > 10 || this.velocity < -10) {
2376
2249
  this.amplitude = 0.9 * this.velocity;
@@ -2387,86 +2260,91 @@ var Carousel = class _Carousel extends Component {
2387
2260
  this.amplitude = this.target - this.offset;
2388
2261
  this.timestamp = Date.now();
2389
2262
  requestAnimationFrame(this._autoScroll);
2263
+ if (this.dragged) e.preventDefault();
2264
+ };
2265
+ this._handleCarouselClick = (e) => {
2390
2266
  if (this.dragged) {
2391
2267
  e.preventDefault();
2392
2268
  e.stopPropagation();
2269
+ return;
2393
2270
  }
2394
- return false;
2395
- };
2396
- this._handleCarouselClick = (e) => {
2397
- 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) {
2398
2276
  e.preventDefault();
2399
2277
  e.stopPropagation();
2400
- return false;
2401
- } else if (!this.options.fullWidth) {
2402
- const clickedElem = e.target.closest(".carousel-item");
2403
- if (!clickedElem) return;
2404
- const clickedIndex = [...clickedElem.parentNode.children].indexOf(clickedElem);
2405
- const diff = this._wrap(this.center) - clickedIndex;
2406
- if (diff !== 0) {
2407
- e.preventDefault();
2408
- e.stopPropagation();
2409
- }
2410
- if (clickedIndex < 0) {
2411
- if (e.clientX - e.target.getBoundingClientRect().left > this.el.clientWidth / 2) {
2412
- this.next();
2413
- } else {
2414
- this.prev();
2415
- }
2278
+ }
2279
+ if (clickedIndex < 0) {
2280
+ if (e.clientX - e.target.getBoundingClientRect().left > this.el.clientWidth / 2) {
2281
+ this.next();
2416
2282
  } else {
2417
- this._cycleTo(clickedIndex);
2283
+ this.prev();
2418
2284
  }
2285
+ } else {
2286
+ this._cycleTo(clickedIndex);
2419
2287
  }
2420
2288
  };
2421
2289
  this._handleIndicatorClick = (e) => {
2422
2290
  e.stopPropagation();
2423
- 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);
2424
2296
  };
2425
- this._handleIndicatorKeyPress = (e) => {
2426
- e.stopPropagation();
2427
- if (Utils.keys.ENTER.includes(e.key)) {
2428
- 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();
2429
2306
  }
2430
2307
  };
2431
- this._handleIndicatorInteraction = (e) => {
2432
- const indicator = e.target.closest(".indicator-item");
2433
- if (indicator) {
2434
- const index = [...indicator.parentNode.children].indexOf(indicator);
2435
- this._cycleTo(index);
2436
- }
2308
+ this._handleFlatScroll = () => {
2309
+ if (this._ignoreScroll) return;
2310
+ const index = this._nearestIndex();
2311
+ if (index !== this.center) this._syncActive(index, this.dragged);
2437
2312
  };
2438
2313
  this._handleResize = () => {
2314
+ if (!this.images.length) return;
2315
+ if (this._flat) {
2316
+ this._scrollToIndex(this.center, false);
2317
+ return;
2318
+ }
2439
2319
  if (this.options.fullWidth) {
2440
- const firstItem = this.el.querySelector(".carousel-item");
2441
- const activeItem = this.el.querySelector(".carousel-item.active");
2442
- if (!firstItem || !activeItem) return;
2320
+ const firstItem = this.images[0];
2321
+ if (!firstItem) return;
2443
2322
  this.itemWidth = firstItem.clientWidth;
2444
- this.imageHeight = activeItem.clientHeight;
2445
2323
  this.dim = this.itemWidth * 2 + this.options.padding;
2446
2324
  this.offset = this.center * 2 * this.itemWidth;
2447
2325
  this.target = this.offset;
2448
- this._setCarouselHeight(true);
2449
2326
  } else {
2450
2327
  this._scroll();
2451
2328
  }
2452
2329
  };
2453
2330
  this._track = () => {
2454
- 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);
2455
2335
  this.timestamp = now;
2456
2336
  this.frame = this.offset;
2457
2337
  this.velocity = 0.8 * v + 0.2 * this.velocity;
2458
2338
  };
2459
2339
  this._autoScroll = () => {
2460
- let elapsed, delta;
2461
- if (this.amplitude) {
2462
- elapsed = Date.now() - this.timestamp;
2463
- delta = this.amplitude * Math.exp(-elapsed / this.options.duration);
2464
- if (delta > 2 || delta < -2) {
2465
- this._scroll(this.target - delta);
2466
- requestAnimationFrame(this._autoScroll);
2467
- } else {
2468
- this._scroll(this.target);
2469
- }
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);
2470
2348
  }
2471
2349
  };
2472
2350
  this.el["Expressive_Carousel"] = this;
@@ -2474,56 +2352,58 @@ var Carousel = class _Carousel extends Component {
2474
2352
  ..._Carousel.defaults,
2475
2353
  ...options
2476
2354
  };
2477
- this.hasMultipleSlides = this.el.querySelectorAll(".carousel-item").length > 1;
2478
- this.showIndicators = this.options.indicators && this.hasMultipleSlides;
2479
- 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
+ }
2480
2365
  this.pressed = false;
2481
2366
  this.dragged = false;
2482
2367
  this.offset = this.target = 0;
2483
- this.images = [];
2484
- const firstItem = this.el.querySelector(".carousel-item");
2485
- if (!firstItem) {
2368
+ this.images = this._collectItems();
2369
+ if (!this.images.length) {
2486
2370
  console.error("Carousel: no .carousel-item elements to show");
2487
2371
  return;
2488
2372
  }
2489
- this.itemWidth = firstItem.clientWidth;
2490
- this.itemHeight = firstItem.clientHeight;
2491
- this.dim = this.itemWidth * 2 + this.options.padding || 1;
2492
- if (this.options.fullWidth) {
2493
- this.options.dist = 0;
2494
- this._setCarouselHeight();
2495
- if (this.showIndicators) {
2496
- this.el.querySelector(".carousel-fixed-item")?.classList.add("with-indicators");
2497
- }
2498
- }
2499
- this._indicators = document.createElement("ul");
2500
- this._indicators.classList.add("indicators");
2501
- this.el.querySelectorAll(".carousel-item").forEach((item, i) => {
2502
- this.images.push(item);
2503
- if (this.showIndicators) {
2504
- const indicator = document.createElement("li");
2505
- indicator.classList.add("indicator-item");
2506
- indicator.tabIndex = 0;
2507
- if (i === 0) {
2508
- indicator.classList.add("active");
2509
- }
2510
- this._indicators.appendChild(indicator);
2511
- }
2512
- });
2513
- 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;
2514
2376
  this.count = this.images.length;
2515
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");
2516
2387
  this._setupEventHandlers();
2517
- 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;
2518
2403
  }
2519
2404
  static get defaults() {
2520
2405
  return _defaults7;
2521
2406
  }
2522
- /**
2523
- * Initializes instances of Carousel.
2524
- * @param els HTML elements.
2525
- * @param options Component options.
2526
- */
2527
2407
  static init(els, options = {}) {
2528
2408
  return super.init(els, options, _Carousel);
2529
2409
  }
@@ -2531,81 +2411,104 @@ var Carousel = class _Carousel extends Component {
2531
2411
  return el["Expressive_Carousel"];
2532
2412
  }
2533
2413
  destroy() {
2534
- this._removeEventHandlers();
2414
+ if (this._started) this._teardown();
2535
2415
  this.el["Expressive_Carousel"] = void 0;
2536
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
+ }
2537
2455
  _setupEventHandlers() {
2538
- if (typeof window.ontouchstart !== "undefined") {
2539
- this.el.addEventListener("touchstart", this._handleCarouselTap);
2540
- this.el.addEventListener("touchmove", this._handleCarouselDrag);
2541
- this.el.addEventListener("touchend", this._handleCarouselRelease);
2542
- }
2543
- this.el.addEventListener("mousedown", this._handleCarouselTap);
2544
- this.el.addEventListener("mousemove", this._handleCarouselDrag);
2545
- this.el.addEventListener("mouseup", this._handleCarouselRelease);
2546
- this.el.addEventListener("mouseleave", this._handleCarouselRelease);
2547
- this.el.addEventListener("click", this._handleCarouselClick);
2456
+ this.el.addEventListener("keydown", this._handleKeydown);
2548
2457
  if (this.showIndicators && this._indicators) {
2549
- this._indicators.querySelectorAll(".indicator-item").forEach((el) => {
2550
- el.addEventListener("click", this._handleIndicatorClick);
2551
- el.addEventListener("keypress", this._handleIndicatorKeyPress);
2552
- });
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;
2464
+ }
2465
+ if (this._flat) {
2466
+ this._scroller.addEventListener("scroll", this._handleFlatScroll, { passive: true });
2467
+ return;
2553
2468
  }
2554
- window.addEventListener("resize", this._handleThrottledResize);
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);
2555
2474
  }
2556
2475
  _removeEventHandlers() {
2557
- if (typeof window.ontouchstart !== "undefined") {
2558
- this.el.removeEventListener("touchstart", this._handleCarouselTap);
2559
- this.el.removeEventListener("touchmove", this._handleCarouselDrag);
2560
- this.el.removeEventListener("touchend", this._handleCarouselRelease);
2561
- }
2562
- this.el.removeEventListener("mousedown", this._handleCarouselTap);
2563
- this.el.removeEventListener("mousemove", this._handleCarouselDrag);
2564
- this.el.removeEventListener("mouseup", this._handleCarouselRelease);
2565
- 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);
2566
2485
  this.el.removeEventListener("click", this._handleCarouselClick);
2567
- if (this.showIndicators && this._indicators) {
2568
- this._indicators.querySelectorAll(".indicator-item").forEach((el) => {
2569
- el.removeEventListener("click", this._handleIndicatorClick);
2570
- });
2486
+ _Carousel._live.delete(this);
2487
+ if (_Carousel._live.size === 0 && _Carousel._resizeListening) {
2488
+ window.removeEventListener("resize", _Carousel._onResize);
2489
+ _Carousel._resizeListening = false;
2571
2490
  }
2572
- window.removeEventListener("resize", this._handleThrottledResize);
2573
2491
  }
2574
- _setCarouselHeight(imageOnly = false) {
2575
- const firstSlide = this.el.querySelector(".carousel-item.active") ? this.el.querySelector(".carousel-item.active") : this.el.querySelector(".carousel-item");
2576
- const firstImage = firstSlide.querySelector("img");
2577
- if (firstImage) {
2578
- if (firstImage.complete) {
2579
- const imageHeight = firstImage.clientHeight;
2580
- if (imageHeight > 0) {
2581
- this.el.style.height = imageHeight + "px";
2582
- } else {
2583
- const naturalWidth = firstImage.naturalWidth;
2584
- const naturalHeight = firstImage.naturalHeight;
2585
- const adjustedHeight = this.el.clientWidth / naturalWidth * naturalHeight;
2586
- this.el.style.height = adjustedHeight + "px";
2587
- }
2588
- } else {
2589
- firstImage.addEventListener("load", () => {
2590
- this.el.style.height = firstImage.offsetHeight + "px";
2591
- });
2592
- }
2593
- } else if (!imageOnly) {
2594
- const slideHeight = firstSlide.clientHeight;
2595
- this.el.style.height = slideHeight + "px";
2596
- }
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
+ });
2597
2504
  }
2598
2505
  _xpos(e) {
2599
- if (e.type.startsWith("touch") && e.targetTouches.length >= 1) {
2600
- return e.targetTouches[0].clientX;
2601
- }
2602
- return e.clientX;
2506
+ if ("clientX" in e) return e.clientX;
2507
+ return 0;
2603
2508
  }
2604
2509
  _ypos(e) {
2605
- if (e.type.startsWith("touch") && e.targetTouches.length >= 1) {
2606
- return e.targetTouches[0].clientY;
2607
- }
2608
- return e.clientY;
2510
+ if ("clientY" in e) return e.clientY;
2511
+ return 0;
2609
2512
  }
2610
2513
  _wrap(x) {
2611
2514
  return x >= this.count ? x % this.count : x < 0 ? this._wrap(this.count + x % this.count) : x;
@@ -2614,18 +2517,20 @@ var Carousel = class _Carousel extends Component {
2614
2517
  if (!this.el.classList.contains("scrolling")) {
2615
2518
  this.el.classList.add("scrolling");
2616
2519
  }
2617
- if (this.scrollingTimeout != null) {
2618
- clearTimeout(this.scrollingTimeout);
2619
- }
2520
+ window.clearTimeout(this.scrollingTimeout);
2620
2521
  this.scrollingTimeout = setTimeout(() => {
2621
2522
  this.el.classList.remove("scrolling");
2622
2523
  }, this.options.duration);
2623
2524
  this.offset = typeof x === "number" ? x : this.offset;
2624
2525
  this.center = Math.floor((this.offset + this.dim / 2) / this.dim);
2625
- const half = this.count >> 1, delta = this.offset - this.center * this.dim, dir = delta < 0 ? 1 : -1, tween = -dir * delta * 2 / this.dim;
2626
- 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;
2627
2530
  const lastCenter = this.center;
2628
2531
  const numVisibleOffset = 1 / this.options.numVisible;
2532
+ let alignment;
2533
+ let centerTweenedOpacity;
2629
2534
  if (this.options.fullWidth) {
2630
2535
  alignment = "translateX(0)";
2631
2536
  centerTweenedOpacity = 1;
@@ -2634,26 +2539,17 @@ var Carousel = class _Carousel extends Component {
2634
2539
  alignment += "translateY(" + (this.el.clientHeight - this.itemHeight) / 2 + "px)";
2635
2540
  centerTweenedOpacity = 1 - numVisibleOffset * tween;
2636
2541
  }
2637
- if (this.showIndicators) {
2638
- const diff = this.center % this.count;
2639
- const activeIndicator = this._indicators.querySelector(".indicator-item.active");
2640
- const activeIndicatorIndex = [...activeIndicator.parentNode.children].indexOf(
2641
- activeIndicator
2642
- );
2643
- if (activeIndicatorIndex !== diff) {
2644
- activeIndicator.classList.remove("active");
2645
- const pos = diff < 0 ? this.count + diff : diff;
2646
- this._indicators.querySelectorAll(".indicator-item")[pos].classList.add("active");
2647
- }
2648
- }
2542
+ this._paintIndicators(this.center);
2649
2543
  if (!this.noWrap || this.center >= 0 && this.center < this.count) {
2650
- el = this.images[this._wrap(this.center)];
2651
- if (!el.classList.contains("active")) {
2544
+ const el = this.images[this._wrap(this.center)];
2545
+ if (el && !el.classList.contains("active")) {
2652
2546
  this.el.querySelector(".carousel-item.active")?.classList.remove("active");
2653
2547
  el.classList.add("active");
2654
2548
  }
2655
2549
  }
2656
- for (i = 1; i <= half; ++i) {
2550
+ for (let i = 1; i <= half; ++i) {
2551
+ let zTranslation;
2552
+ let tweenedOpacity;
2657
2553
  if (this.options.fullWidth) {
2658
2554
  zTranslation = this.options.dist;
2659
2555
  tweenedOpacity = i === half && delta < 0 ? 1 - tween : 1;
@@ -2662,7 +2558,7 @@ var Carousel = class _Carousel extends Component {
2662
2558
  tweenedOpacity = 1 - numVisibleOffset * (i * 2 + tween * dir);
2663
2559
  }
2664
2560
  if (!this.noWrap || this.center + i < this.count) {
2665
- el = this.images[this._wrap(this.center + i)];
2561
+ const el = this.images[this._wrap(this.center + i)];
2666
2562
  const transformString = `${alignment} translateX(${this.options.shift + (this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`;
2667
2563
  this._updateItemStyle(el, tweenedOpacity, -i, transformString);
2668
2564
  }
@@ -2674,32 +2570,113 @@ var Carousel = class _Carousel extends Component {
2674
2570
  tweenedOpacity = 1 - numVisibleOffset * (i * 2 - tween * dir);
2675
2571
  }
2676
2572
  if (!this.noWrap || this.center - i >= 0) {
2677
- el = this.images[this._wrap(this.center - i)];
2573
+ const el = this.images[this._wrap(this.center - i)];
2678
2574
  const transformString = `${alignment} translateX(${-this.options.shift + (-this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`;
2679
2575
  this._updateItemStyle(el, tweenedOpacity, -i, transformString);
2680
2576
  }
2681
2577
  }
2682
2578
  if (!this.noWrap || this.center >= 0 && this.center < this.count) {
2683
- el = this.images[this._wrap(this.center)];
2579
+ const el = this.images[this._wrap(this.center)];
2684
2580
  const transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir * this.options.shift * tween}px) translateZ(${this.options.dist * tween}px)`;
2685
2581
  this._updateItemStyle(el, centerTweenedOpacity, 0, transformString);
2686
2582
  }
2687
- 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));
2688
2585
  if (lastCenter !== this.center && typeof this.options.onCycleTo === "function") {
2689
- this.options.onCycleTo.call(this, _currItem, this.dragged);
2586
+ this.options.onCycleTo.call(this, currItem, this.dragged);
2690
2587
  }
2691
2588
  if (typeof this.oneTimeCallback === "function") {
2692
- this.oneTimeCallback.call(this, _currItem, this.dragged);
2589
+ this.oneTimeCallback.call(this, currItem, this.dragged);
2693
2590
  this.oneTimeCallback = null;
2694
2591
  }
2695
2592
  }
2696
2593
  _updateItemStyle(el, opacity, zIndex, transform) {
2594
+ if (!el) return;
2697
2595
  el.style.transform = transform;
2698
2596
  el.style.zIndex = zIndex.toString();
2699
2597
  el.style.opacity = opacity.toString();
2700
2598
  el.style.visibility = "visible";
2701
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
+ }
2702
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
+ }
2703
2680
  let diff = this.center % this.count - n;
2704
2681
  if (!this.noWrap) {
2705
2682
  if (diff < 0) {
@@ -2727,14 +2704,8 @@ var Carousel = class _Carousel extends Component {
2727
2704
  requestAnimationFrame(this._autoScroll);
2728
2705
  }
2729
2706
  }
2730
- /**
2731
- * Move carousel to next slide or go forward a given amount of slides.
2732
- * @param n How many times the carousel slides.
2733
- */
2734
2707
  next(n = 1) {
2735
- if (n === void 0 || isNaN(n)) {
2736
- n = 1;
2737
- }
2708
+ if (n === void 0 || isNaN(n)) n = 1;
2738
2709
  let index = this.center + n;
2739
2710
  if (index >= this.count || index < 0) {
2740
2711
  if (this.noWrap) return;
@@ -2742,14 +2713,8 @@ var Carousel = class _Carousel extends Component {
2742
2713
  }
2743
2714
  this._cycleTo(index);
2744
2715
  }
2745
- /**
2746
- * Move carousel to previous slide or go back a given amount of slides.
2747
- * @param n How many times the carousel slides.
2748
- */
2749
2716
  prev(n = 1) {
2750
- if (n === void 0 || isNaN(n)) {
2751
- n = 1;
2752
- }
2717
+ if (n === void 0 || isNaN(n)) n = 1;
2753
2718
  let index = this.center - n;
2754
2719
  if (index >= this.count || index < 0) {
2755
2720
  if (this.noWrap) return;
@@ -2757,15 +2722,8 @@ var Carousel = class _Carousel extends Component {
2757
2722
  }
2758
2723
  this._cycleTo(index);
2759
2724
  }
2760
- /**
2761
- * Move carousel to nth slide.
2762
- * @param n Index of slide.
2763
- * @param callback "onCycleTo" optional callback.
2764
- */
2765
2725
  set(n, callback) {
2766
- if (n === void 0 || isNaN(n)) {
2767
- n = 0;
2768
- }
2726
+ if (n === void 0 || isNaN(n)) n = 0;
2769
2727
  if (n > this.count || n < 0) {
2770
2728
  if (this.noWrap) return;
2771
2729
  n = this._wrap(n);
@@ -2773,6 +2731,12 @@ var Carousel = class _Carousel extends Component {
2773
2731
  this._cycleTo(n, callback);
2774
2732
  }
2775
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;
2776
2740
 
2777
2741
  // src/ts/components/characterCounter.ts
2778
2742
  var _defaults8 = Object.freeze({});
@@ -2862,77 +2826,75 @@ var _defaults9 = {
2862
2826
  var Collapsible = class _Collapsible extends Component {
2863
2827
  constructor(el, options) {
2864
2828
  super(el, options, _Collapsible);
2865
- this._handleCollapsibleClick = (e) => {
2866
- const header = e.target.closest(".collapsible-header");
2867
- if (e.target && header) {
2868
- const collapsible = header.closest(".collapsible");
2869
- if (collapsible !== this.el) return;
2870
- const li = header.closest("li");
2871
- const isActive = li.classList.contains("active");
2872
- const index = [...li.parentNode.children].indexOf(li);
2873
- if (isActive) this.close(index);
2874
- else this.open(index);
2875
- }
2876
- };
2877
- this._handleCollapsibleKeydown = (e) => {
2878
- if (Utils.keys.ENTER.includes(e.key)) {
2879
- this._handleCollapsibleClick(e);
2880
- }
2881
- };
2829
+ this._items = [];
2830
+ this._endTimers = [];
2831
+ this._wrappedBodies = [];
2882
2832
  /**
2883
2833
  * Open collapsible section.
2884
- * @param n Nth section to open.
2834
+ * @param index 0-based index of the section to open.
2885
2835
  */
2886
2836
  this.open = (index) => {
2887
- const listItems = Array.from(this.el.children).filter((c) => c.tagName === "LI");
2888
- const li = listItems[index];
2889
- if (li && !li.classList.contains("active")) {
2890
- if (typeof this.options.onOpenStart === "function") {
2891
- this.options.onOpenStart.call(this, li);
2892
- }
2893
- if (this.options.accordion) {
2894
- const activeLis = listItems.filter((li2) => li2.classList.contains("active"));
2895
- activeLis.forEach((activeLi) => {
2896
- const index2 = listItems.indexOf(activeLi);
2897
- this.close(index2);
2898
- });
2899
- }
2900
- li.classList.add("active");
2901
- 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
+ });
2902
2843
  }
2844
+ this._setOpen(item, true);
2903
2845
  };
2904
2846
  /**
2905
2847
  * Close collapsible section.
2906
- * @param n Nth section to close.
2848
+ * @param index 0-based index of the section to close.
2907
2849
  */
2908
2850
  this.close = (index) => {
2909
- const li = Array.from(this.el.children).filter((c) => c.tagName === "LI")[index];
2910
- if (li && li.classList.contains("active")) {
2911
- if (typeof this.options.onCloseStart === "function") {
2912
- this.options.onCloseStart.call(this, li);
2913
- }
2914
- li.classList.remove("active");
2915
- this._animateOut(index);
2916
- }
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);
2917
2885
  };
2918
2886
  this.el["Expressive_Collapsible"] = this;
2887
+ const accordion = options.accordion !== void 0 ? options.accordion : !el.classList.contains("expandable");
2919
2888
  this.options = {
2920
2889
  ..._Collapsible.defaults,
2921
- ...options
2890
+ ...options,
2891
+ accordion
2922
2892
  };
2923
- this._headers = Array.from(this.el.querySelectorAll("li > .collapsible-header"));
2924
- 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();
2925
2897
  this._setupEventHandlers();
2926
- const activeBodies = Array.from(
2927
- this.el.querySelectorAll("li.active > .collapsible-body")
2928
- );
2929
- if (this.options.accordion) {
2930
- if (activeBodies.length > 0) {
2931
- this._setExpanded(activeBodies[0]);
2932
- }
2933
- } else {
2934
- activeBodies.forEach((el2) => this._setExpanded(el2));
2935
- }
2936
2898
  }
2937
2899
  static get defaults() {
2938
2900
  return _defaults9;
@@ -2950,48 +2912,130 @@ var Collapsible = class _Collapsible extends Component {
2950
2912
  }
2951
2913
  destroy() {
2952
2914
  this._removeEventHandlers();
2915
+ this._endTimers.forEach((id) => clearTimeout(id));
2916
+ this._endTimers = [];
2917
+ this._unwrapDetailsBodies();
2953
2918
  this.el["Expressive_Collapsible"] = void 0;
2954
2919
  }
2955
- _setupEventHandlers() {
2956
- this.el.addEventListener("click", this._handleCollapsibleClick);
2957
- this._headers.forEach(
2958
- (header) => header.addEventListener("keydown", this._handleCollapsibleKeydown)
2959
- );
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");
2960
2924
  }
2961
- _removeEventHandlers() {
2962
- this.el.removeEventListener("click", this._handleCollapsibleClick);
2963
- this._headers.forEach(
2964
- (header) => header.removeEventListener("keydown", this._handleCollapsibleKeydown)
2965
- );
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));
2966
2941
  }
2967
- _setExpanded(li) {
2968
- li.style.maxHeight = li.scrollHeight + "px";
2942
+ _isOpen(item) {
2943
+ if (item instanceof HTMLDetailsElement) return item.open;
2944
+ return item.classList.contains("active");
2969
2945
  }
2970
- _animateIn(index) {
2971
- const li = this.el.children[index];
2972
- if (!li) return;
2973
- const body = li.querySelector(".collapsible-body");
2974
- const duration = this.options.inDuration;
2975
- body.style.transition = `max-height ${duration}ms ease-out`;
2976
- this._setExpanded(body);
2977
- setTimeout(() => {
2978
- if (typeof this.options.onOpenEnd === "function") {
2979
- this.options.onOpenEnd.call(this, li);
2980
- }
2981
- }, 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
+ });
2982
2974
  }
2983
- _animateOut(index) {
2984
- const li = this.el.children[index];
2985
- if (!li) return;
2986
- const body = li.querySelector(".collapsible-body");
2987
- const duration = this.options.outDuration;
2988
- body.style.transition = `max-height ${duration}ms ease-out`;
2989
- body.style.maxHeight = "0";
2990
- setTimeout(() => {
2991
- if (typeof this.options.onCloseEnd === "function") {
2992
- this.options.onCloseEnd.call(this, li);
2993
- }
2994
- }, 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;
2995
3039
  }
2996
3040
  };
2997
3041
 
@@ -4791,38 +4835,22 @@ var Modal = _Modal;
4791
4835
  // src/ts/components/parallax.ts
4792
4836
  var _defaults15 = {
4793
4837
  responsiveThreshold: 0
4794
- // breakpoint for swipeable
4795
4838
  };
4796
- var _Parallax = class _Parallax extends Component {
4839
+ var Parallax = class _Parallax extends Component {
4797
4840
  constructor(el, options) {
4798
4841
  super(el, options, _Parallax);
4799
- this._handleImageLoad = () => {
4800
- this._updateParallax();
4801
- };
4802
4842
  this.el["Expressive_Parallax"] = this;
4803
4843
  this.options = {
4804
4844
  ..._Parallax.defaults,
4805
4845
  ...options
4806
4846
  };
4807
- this._enabled = window.innerWidth > this.options.responsiveThreshold;
4808
- this._img = this.el.querySelector("img");
4809
- if (!this._img) {
4810
- console.error("Parallax: no <img> to move inside the .parallax element");
4811
- return;
4847
+ if (!this.el.querySelector("img, video")) {
4848
+ console.error("Parallax: no <img> or <video> to move inside the .parallax element");
4812
4849
  }
4813
- this._updateParallax();
4814
- this._setupEventHandlers();
4815
- this._setupStyles();
4816
- _Parallax._parallaxes.push(this);
4817
4850
  }
4818
4851
  static get defaults() {
4819
4852
  return _defaults15;
4820
4853
  }
4821
- /**
4822
- * Initializes instances of Parallax.
4823
- * @param els HTML elements.
4824
- * @param options Component options.
4825
- */
4826
4854
  static init(els, options = {}) {
4827
4855
  return super.init(els, options, _Parallax);
4828
4856
  }
@@ -4830,84 +4858,9 @@ var _Parallax = class _Parallax extends Component {
4830
4858
  return el["Expressive_Parallax"];
4831
4859
  }
4832
4860
  destroy() {
4833
- const index = _Parallax._parallaxes.indexOf(this);
4834
- if (index >= 0) _Parallax._parallaxes.splice(index, 1);
4835
- if (this._img) this._img.style.transform = "";
4836
- this._removeEventHandlers();
4837
4861
  this.el["Expressive_Parallax"] = void 0;
4838
4862
  }
4839
- static _handleScroll() {
4840
- for (let i = 0; i < _Parallax._parallaxes.length; i++) {
4841
- const parallaxInstance = _Parallax._parallaxes[i];
4842
- parallaxInstance._updateParallax.call(parallaxInstance);
4843
- }
4844
- }
4845
- static _handleWindowResize() {
4846
- for (let i = 0; i < _Parallax._parallaxes.length; i++) {
4847
- const parallaxInstance = _Parallax._parallaxes[i];
4848
- parallaxInstance._enabled = window.innerWidth > parallaxInstance.options.responsiveThreshold;
4849
- }
4850
- }
4851
- _setupEventHandlers() {
4852
- this._img?.addEventListener("load", this._handleImageLoad);
4853
- if (_Parallax._parallaxes.length === 0) {
4854
- if (!_Parallax._handleWindowResizeThrottled) {
4855
- _Parallax._handleWindowResizeThrottled = Utils.throttle(_Parallax._handleWindowResize, 100);
4856
- }
4857
- window.addEventListener("scroll", _Parallax._requestScrollUpdate, { passive: true });
4858
- window.addEventListener("resize", _Parallax._handleWindowResizeThrottled, { passive: true });
4859
- }
4860
- }
4861
- _removeEventHandlers() {
4862
- this._img?.removeEventListener("load", this._handleImageLoad);
4863
- if (_Parallax._parallaxes.length === 0) {
4864
- window.removeEventListener("scroll", _Parallax._requestScrollUpdate);
4865
- window.removeEventListener("resize", _Parallax._handleWindowResizeThrottled);
4866
- if (_Parallax._scrollFrame !== null) {
4867
- cancelAnimationFrame(_Parallax._scrollFrame);
4868
- _Parallax._scrollFrame = null;
4869
- }
4870
- }
4871
- }
4872
- _setupStyles() {
4873
- if (this._img) this._img.style.opacity = "1";
4874
- }
4875
- _updateParallax() {
4876
- const rect = this.el.getBoundingClientRect();
4877
- const containerHeight = rect.height > 0 ? this.el.parentElement.offsetHeight : 500;
4878
- const imgHeight = this._img.offsetHeight;
4879
- const parallaxDist = imgHeight - containerHeight;
4880
- const top = rect.top + window.scrollY - document.documentElement.clientTop;
4881
- const bottom = top + containerHeight;
4882
- const scrollTop = Utils.getDocumentScrollTop();
4883
- const windowHeight = window.innerHeight;
4884
- const windowBottom = scrollTop + windowHeight;
4885
- const percentScrolled = (windowBottom - top) / (containerHeight + windowHeight);
4886
- const parallax = parallaxDist * percentScrolled;
4887
- if (!this._enabled) {
4888
- this._img.style.transform = "";
4889
- } else if (bottom > scrollTop && top < scrollTop + windowHeight) {
4890
- this._img.style.transform = `translate3D(-50%, ${parallax}px, 0)`;
4891
- }
4892
- }
4893
- };
4894
- _Parallax._parallaxes = [];
4895
- _Parallax._scrollFrame = null;
4896
- /**
4897
- * Coalesce scroll events onto animation frames.
4898
- *
4899
- * The handler reads layout and then writes a transform, so running it more
4900
- * than once per frame is wasted work - and the 5ms throttle it replaces was
4901
- * short enough to let several run per frame anyway.
4902
- */
4903
- _Parallax._requestScrollUpdate = () => {
4904
- if (_Parallax._scrollFrame !== null) return;
4905
- _Parallax._scrollFrame = requestAnimationFrame(() => {
4906
- _Parallax._scrollFrame = null;
4907
- _Parallax._handleScroll();
4908
- });
4909
4863
  };
4910
- var Parallax = _Parallax;
4911
4864
 
4912
4865
  // src/ts/components/pushpin.ts
4913
4866
  var _defaults16 = {
@@ -6147,60 +6100,96 @@ var _defaults21 = {
6147
6100
  var _TapTarget = class _TapTarget extends Component {
6148
6101
  constructor(el, options) {
6149
6102
  super(el, options, _TapTarget);
6150
- // Built once per instance; see the note in carousel.ts - the previous form
6151
- // returned a new throttled function on every resize and never invoked it.
6152
- this._handleThrottledResize = Utils.throttle(() => this._handleResize(), 200);
6153
- this._handleKeyboardInteraction = (e) => {
6154
- if (Utils.keys.ENTER.includes(e.key)) {
6155
- this._handleTargetToggle();
6156
- }
6157
- };
6158
- this._handleTargetToggle = () => {
6159
- if (!this.isOpen) this.open();
6160
- else this.close();
6161
- };
6162
- /*_handleOriginClick = () => {
6163
- this.close();
6164
- }*/
6165
- this._handleResize = () => {
6166
- this._calculatePositioning();
6167
- };
6168
- this._handleDocumentClick = (e) => {
6169
- const target = e.target;
6170
- if (!(target instanceof Element)) return;
6171
- if (!this.originEl.contains(target) && !target.closest(".tap-target-wrapper")) {
6172
- this.close();
6173
- }
6174
- };
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;
6175
6111
  /**
6176
6112
  * Open Tap Target.
6177
6113
  */
6178
6114
  this.open = () => {
6179
- 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();
6180
6120
  if (typeof this.options.onOpen === "function") {
6181
6121
  this.options.onOpen.call(this, this.originEl);
6182
6122
  }
6183
6123
  this.isOpen = true;
6184
6124
  this.wrapper.classList.add("open");
6185
- this.wrapper.ariaExpanded = "true";
6186
- document.body.addEventListener("click", this._handleDocumentClick, true);
6187
- document.body.addEventListener("keypress", this._handleDocumentClick, true);
6188
- 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
+ }
6189
6143
  };
6190
6144
  /**
6191
6145
  * Close Tap Target.
6192
6146
  */
6193
6147
  this.close = () => {
6194
6148
  if (!this.isOpen) return;
6195
- if (typeof this.options.onClose === "function") {
6149
+ if (typeof this.options.onClose === "function" && this.originEl) {
6196
6150
  this.options.onClose.call(this, this.originEl);
6197
6151
  }
6198
6152
  this.isOpen = false;
6199
- this.wrapper.classList.remove("open");
6200
- this.wrapper.ariaExpanded = "false";
6201
- document.body.removeEventListener("click", this._handleDocumentClick, true);
6202
- document.body.removeEventListener("keypress", this._handleDocumentClick, true);
6203
- 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();
6204
6193
  };
6205
6194
  this.el["Expressive_TapTarget"] = this;
6206
6195
  this.options = {
@@ -6213,7 +6202,6 @@ var _TapTarget = class _TapTarget extends Component {
6213
6202
  console.error(`TapTarget: no element with id "${el.dataset.target ?? ""}" to attach to`);
6214
6203
  return;
6215
6204
  }
6216
- this.originEl.tabIndex = 0;
6217
6205
  this._setup();
6218
6206
  this._calculatePositioning();
6219
6207
  this._setupEventHandlers();
@@ -6222,11 +6210,6 @@ var _TapTarget = class _TapTarget extends Component {
6222
6210
  static get defaults() {
6223
6211
  return _defaults21;
6224
6212
  }
6225
- /**
6226
- * Initializes instances of TapTarget.
6227
- * @param els HTML elements.
6228
- * @param options Component options.
6229
- */
6230
6213
  static init(els, options = {}) {
6231
6214
  return super.init(els, options, _TapTarget);
6232
6215
  }
@@ -6236,116 +6219,95 @@ var _TapTarget = class _TapTarget extends Component {
6236
6219
  destroy() {
6237
6220
  this.close();
6238
6221
  this._removeEventHandlers();
6222
+ this._teardownDom();
6239
6223
  this.el["Expressive_TapTarget"] = void 0;
6240
6224
  const index = _TapTarget._taptargets.indexOf(this);
6241
- if (index >= 0) {
6242
- _TapTarget._taptargets.splice(index, 1);
6243
- }
6244
- }
6245
- _setupEventHandlers() {
6246
- if (!this.originEl) return;
6247
- this.originEl.addEventListener("click", this._handleTargetToggle);
6248
- this.originEl.addEventListener("keypress", this._handleKeyboardInteraction, true);
6249
- window.addEventListener("resize", this._handleThrottledResize);
6250
- }
6251
- _removeEventHandlers() {
6252
- if (!this.originEl) return;
6253
- this.originEl.removeEventListener("click", this._handleTargetToggle);
6254
- this.originEl.removeEventListener("keypress", this._handleKeyboardInteraction, true);
6255
- window.removeEventListener("resize", this._handleThrottledResize);
6225
+ if (index >= 0) _TapTarget._taptargets.splice(index, 1);
6256
6226
  }
6257
6227
  _setup() {
6258
- this.wrapper = this.el.parentElement;
6259
- this.waveEl = this.wrapper.querySelector(".tap-target-wave");
6260
- this.el.parentElement.ariaExpanded = "false";
6261
- this.originEl.style.zIndex = "1002";
6262
- this.contentEl = this.el.querySelector(".tap-target-content");
6263
- 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 {
6264
6232
  this.wrapper = document.createElement("div");
6265
6233
  this.wrapper.classList.add("tap-target-wrapper");
6266
6234
  this.el.before(this.wrapper);
6267
6235
  this.wrapper.append(this.el);
6236
+ this._createdWrapper = true;
6268
6237
  }
6238
+ this.contentEl = this.el.querySelector(".tap-target-content");
6269
6239
  if (!this.contentEl) {
6270
6240
  this.contentEl = document.createElement("div");
6271
- 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);
6272
6243
  this.el.append(this.contentEl);
6273
6244
  }
6245
+ this.waveEl = this.el.querySelector(".tap-target-wave") ?? this.wrapper.querySelector(".tap-target-wave");
6274
6246
  if (!this.waveEl) {
6275
6247
  this.waveEl = document.createElement("div");
6276
- this.waveEl.classList.add("tap-target-wave");
6277
- 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);
6278
6287
  }
6288
+ this.wrapper?.addEventListener("toggle", this._handlePopoverToggle);
6289
+ window.addEventListener("resize", this._handleThrottledResize, { passive: true });
6279
6290
  }
6280
- _offset(el) {
6281
- const box = el.getBoundingClientRect();
6282
- const docElem = document.documentElement;
6283
- return {
6284
- top: box.top + window.pageYOffset - docElem.clientTop,
6285
- left: box.left + window.pageXOffset - docElem.clientLeft
6286
- };
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);
6287
6297
  }
6288
6298
  _calculatePositioning() {
6289
- let isFixed = getComputedStyle(this.originEl).position === "fixed";
6290
- if (!isFixed) {
6291
- let currentElem = this.originEl;
6292
- const parents = [];
6293
- while ((currentElem = currentElem.parentNode) && currentElem !== document)
6294
- parents.push(currentElem);
6295
- for (let i = 0; i < parents.length; i++) {
6296
- isFixed = getComputedStyle(parents[i]).position === "fixed";
6297
- if (isFixed) break;
6298
- }
6299
- }
6300
- const originWidth = this.originEl.offsetWidth;
6301
- const originHeight = this.originEl.offsetHeight;
6302
- const originOffset = this._offset(this.originEl);
6303
- const originTop = isFixed ? originOffset.top - Utils.getDocumentScrollTop() : originOffset.top;
6304
- const originLeft = isFixed ? originOffset.left - Utils.getDocumentScrollLeft() : originOffset.left;
6305
- const windowWidth = window.innerWidth;
6306
- const windowHeight = window.innerHeight;
6307
- const scrollBarWidth = windowWidth - document.documentElement.clientWidth;
6308
- const centerX = windowWidth / 2;
6309
- const centerY = windowHeight / 2;
6310
- const isLeft = originLeft <= centerX;
6311
- const isRight = originLeft > centerX;
6312
- const isTop = originTop <= centerY;
6313
- const isBottom = originTop > centerY;
6314
- const isCenterX = originLeft >= windowWidth * 0.25 && originLeft <= windowWidth * 0.75;
6315
- const tapTargetWidth = this.el.offsetWidth;
6316
- const tapTargetHeight = this.el.offsetHeight;
6317
- const tapTargetTop = originTop + originHeight / 2 - tapTargetHeight / 2;
6318
- const tapTargetLeft = originLeft + originWidth / 2 - tapTargetWidth / 2;
6319
- const tapTargetPosition = isFixed ? "fixed" : "absolute";
6320
- const tapTargetTextWidth = isCenterX ? tapTargetWidth : tapTargetWidth / 2 + originWidth;
6321
- const tapTargetTextHeight = tapTargetHeight / 2;
6322
- const tapTargetTextTop = isTop ? tapTargetHeight / 2 : 0;
6323
- const tapTargetTextBottom = 0;
6324
- const tapTargetTextLeft = isLeft && !isCenterX ? tapTargetWidth / 2 - originWidth : 0;
6325
- const tapTargetTextRight = 0;
6326
- const tapTargetTextPadding = originWidth;
6327
- const tapTargetTextAlign = isBottom ? "bottom" : "top";
6328
- const tapTargetWaveWidth = originWidth > originHeight ? originWidth * 2 : originWidth * 2;
6329
- const tapTargetWaveHeight = tapTargetWaveWidth;
6330
- const tapTargetWaveTop = tapTargetHeight / 2 - tapTargetWaveHeight / 2;
6331
- const tapTargetWaveLeft = tapTargetWidth / 2 - tapTargetWaveWidth / 2;
6332
- this.wrapper.style.top = isTop ? tapTargetTop + "px" : "";
6333
- this.wrapper.style.right = isRight ? windowWidth - tapTargetLeft - tapTargetWidth - scrollBarWidth + "px" : "";
6334
- this.wrapper.style.bottom = isBottom ? windowHeight - tapTargetTop - tapTargetHeight + "px" : "";
6335
- this.wrapper.style.left = isLeft ? tapTargetLeft + "px" : "";
6336
- this.wrapper.style.position = tapTargetPosition;
6337
- this.contentEl.style.width = tapTargetTextWidth + "px";
6338
- this.contentEl.style.height = tapTargetTextHeight + "px";
6339
- this.contentEl.style.top = tapTargetTextTop + "px";
6340
- this.contentEl.style.right = tapTargetTextRight + "px";
6341
- this.contentEl.style.bottom = tapTargetTextBottom + "px";
6342
- this.contentEl.style.left = tapTargetTextLeft + "px";
6343
- this.contentEl.style.padding = tapTargetTextPadding + "px";
6344
- this.contentEl.style.verticalAlign = tapTargetTextAlign;
6345
- this.waveEl.style.top = tapTargetWaveTop + "px";
6346
- this.waveEl.style.left = tapTargetWaveLeft + "px";
6347
- this.waveEl.style.width = tapTargetWaveWidth + "px";
6348
- 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}`;
6349
6311
  }
6350
6312
  };
6351
6313
  _TapTarget._taptargets = [];
@@ -7089,7 +7051,7 @@ var _Toast = class _Toast {
7089
7051
  close.className = "circle";
7090
7052
  close.setAttribute("aria-label", "Dismiss");
7091
7053
  const icon = document.createElement("i");
7092
- icon.className = "material-icons";
7054
+ icon.className = "material-symbols";
7093
7055
  icon.setAttribute("aria-hidden", "true");
7094
7056
  icon.textContent = "close";
7095
7057
  close.appendChild(icon);
@@ -7454,7 +7416,7 @@ function AutoInit(context = document.body, options) {
7454
7416
  }
7455
7417
 
7456
7418
  // src/ts/index.ts
7457
- var version = "0.2.0";
7419
+ var version = "0.3.0";
7458
7420
  if (typeof document !== "undefined") {
7459
7421
  document.addEventListener("keydown", Utils.docHandleKeydown, true);
7460
7422
  document.addEventListener("keyup", Utils.docHandleKeyup, true);