@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
@@ -1476,7 +1476,7 @@ var Expressive = (() => {
1476
1476
  data: [],
1477
1477
  placeholder: "",
1478
1478
  secondaryPlaceholder: "",
1479
- closeIconClass: "material-icons",
1479
+ closeIconClass: "material-symbols",
1480
1480
  autocompleteOptions: {},
1481
1481
  autocompleteOnly: false,
1482
1482
  limit: Infinity,
@@ -2115,83 +2115,76 @@ var Expressive = (() => {
2115
2115
  var FloatingActionButton = class _FloatingActionButton extends Component {
2116
2116
  constructor(el, options) {
2117
2117
  super(el, options, _FloatingActionButton);
2118
- /** Toolbar-mode backdrop, kept so closing can take it back out. */
2119
- this._backdrop = null;
2120
- this._handleFABClick = () => {
2121
- this._handleFABToggle();
2122
- };
2123
- this._handleFABKeyPress = (e) => {
2124
- if (Utils.keys.ENTER.includes(e.key)) {
2125
- this._handleFABToggle();
2126
- }
2127
- };
2128
- this._handleFABToggle = () => {
2129
- if (this.isOpen) {
2130
- this.close();
2131
- } else {
2132
- this.open();
2133
- }
2134
- };
2135
- this._handleDocumentClick = (e) => {
2136
- const elem = e.target;
2137
- if (elem !== this._menu) this.close();
2138
- };
2139
2118
  /**
2140
2119
  * Open FAB.
2141
2120
  */
2142
2121
  this.open = () => {
2143
2122
  if (this.isOpen) return;
2144
- if (this.options.toolbarEnabled) this._animateInToolbar();
2145
- else this._animateInFAB();
2146
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);
2147
2128
  };
2148
2129
  /**
2149
2130
  * Close FAB.
2150
2131
  */
2151
2132
  this.close = () => {
2152
2133
  if (!this.isOpen) return;
2153
- if (this.options.toolbarEnabled) {
2154
- window.removeEventListener("scroll", this.close, true);
2155
- document.body.removeEventListener("click", this._handleDocumentClick, true);
2156
- this._animateOutToolbar();
2157
- } else {
2158
- this._animateOutFAB();
2159
- }
2160
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();
2161
2157
  };
2162
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;
2163
2162
  this.options = {
2164
2163
  ..._FloatingActionButton.defaults,
2165
- ...options
2164
+ ...options,
2165
+ direction: options.direction ?? fromMarkup ?? _FloatingActionButton.defaults.direction,
2166
+ hoverEnabled: !clickToToggle,
2167
+ toolbarEnabled: toolbar
2166
2168
  };
2167
2169
  this.isOpen = false;
2168
- this._anchor = this.el.querySelector("a, button");
2169
- this._menu = this.el.querySelector("ul");
2170
- this._floatingBtns = Array.from(this.el.querySelectorAll("ul .btn-floating"));
2171
- this._floatingBtnsReverse = this._floatingBtns.reverse();
2172
- this.offsetY = 0;
2173
- this.offsetX = 0;
2170
+ this._anchor = this.el.querySelector(":scope > a, :scope > button");
2171
+ this._menu = this.el.querySelector("ul, menu");
2174
2172
  this.el.classList.add(`direction-${this.options.direction}`);
2175
- if (this._anchor) this._anchor.tabIndex = 0;
2176
- if (this._menu) this._menu.ariaExpanded = "false";
2177
- if (this.options.direction === "top")
2178
- this.offsetY = 40;
2179
- else if (this.options.direction === "right")
2180
- this.offsetX = -40;
2181
- else if (this.options.direction === "bottom")
2182
- this.offsetY = -40;
2183
- else
2184
- 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");
2185
2183
  this._setupEventHandlers();
2186
2184
  }
2187
2185
  static get defaults() {
2188
2186
  return _defaults6;
2189
2187
  }
2190
- /**
2191
- * Initializes instances of FloatingActionButton.
2192
- * @param els HTML elements.
2193
- * @param options Component options.
2194
- */
2195
2188
  static init(els, options = {}) {
2196
2189
  return super.init(els, options, _FloatingActionButton);
2197
2190
  }
@@ -2199,229 +2192,109 @@ var Expressive = (() => {
2199
2192
  return el["Expressive_FloatingActionButton"];
2200
2193
  }
2201
2194
  destroy() {
2202
- this._removeEventHandlers();
2203
2195
  this.close();
2196
+ this._removeEventHandlers();
2204
2197
  this.el["Expressive_FloatingActionButton"] = void 0;
2205
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
+ }
2206
2207
  _setupEventHandlers() {
2207
- if (this.options.hoverEnabled && !this.options.toolbarEnabled) {
2208
- this.el.addEventListener("mouseenter", this.open);
2209
- this.el.addEventListener("mouseleave", this.close);
2210
- } else {
2211
- 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);
2212
2211
  }
2213
- this.el.addEventListener("keypress", this._handleFABKeyPress);
2214
2212
  }
2215
2213
  _removeEventHandlers() {
2216
- if (this.options.hoverEnabled && !this.options.toolbarEnabled) {
2217
- this.el.removeEventListener("mouseenter", this.open);
2218
- this.el.removeEventListener("mouseleave", this.close);
2219
- } else {
2220
- this.el.removeEventListener("click", this._handleFABClick);
2221
- }
2222
- this.el.removeEventListener("keypress", this._handleFABKeyPress);
2223
- }
2224
- _animateInFAB() {
2225
- this.el.classList.add("active");
2226
- if (this._menu) this._menu.ariaExpanded = "true";
2227
- const delayIncrement = 40;
2228
- const duration = 275;
2229
- this._floatingBtnsReverse.forEach((el, index) => {
2230
- const delay = delayIncrement * index;
2231
- el.style.transition = "none";
2232
- el.style.opacity = "0";
2233
- el.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px) scale(0.4)`;
2234
- setTimeout(() => {
2235
- el.style.opacity = "0.4";
2236
- setTimeout(() => {
2237
- el.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`;
2238
- el.style.opacity = "1";
2239
- el.style.transform = "translate(0, 0) scale(1)";
2240
- el.tabIndex = 0;
2241
- }, 1);
2242
- }, delay);
2243
- });
2244
- }
2245
- _animateOutFAB() {
2246
- const duration = 175;
2247
- setTimeout(() => {
2248
- this.el.classList.remove("active");
2249
- if (this._menu) this._menu.ariaExpanded = "false";
2250
- }, duration);
2251
- this._floatingBtnsReverse.forEach((el) => {
2252
- el.style.transition = `opacity ${duration}ms ease, transform ${duration}ms ease`;
2253
- el.style.opacity = "0";
2254
- el.style.transform = `translate(${this.offsetX}px, ${this.offsetY}px) scale(0.4)`;
2255
- el.tabIndex = -1;
2256
- });
2257
- }
2258
- _animateInToolbar() {
2259
- const windowWidth = window.innerWidth;
2260
- const windowHeight = window.innerHeight;
2261
- const btnRect = this.el.getBoundingClientRect();
2262
- const fabColor = getComputedStyle(this._anchor).backgroundColor;
2263
- const backdrop = document.createElement("div");
2264
- backdrop.classList.add("fab-backdrop");
2265
- backdrop.style.backgroundColor = fabColor;
2266
- this._anchor.append(backdrop);
2267
- this._backdrop = backdrop;
2268
- const backdropWidth = backdrop.clientWidth;
2269
- const scaleFactor = backdropWidth > 0 ? windowWidth / backdropWidth : 1;
2270
- this.offsetX = btnRect.left - windowWidth / 2 + btnRect.width / 2;
2271
- this.offsetY = windowHeight - btnRect.bottom;
2272
- this.btnBottom = btnRect.bottom;
2273
- this.btnLeft = btnRect.left;
2274
- this.btnWidth = btnRect.width;
2275
- this.el.classList.add("active");
2276
- this.el.style.textAlign = "center";
2277
- this.el.style.width = "100%";
2278
- this.el.style.bottom = "0";
2279
- this.el.style.left = "0";
2280
- this.el.style.transform = "translateX(" + this.offsetX + "px)";
2281
- this.el.style.transition = "none";
2282
- if (this._menu) this._menu.ariaExpanded = "true";
2283
- this._anchor.style.transform = `translateY(${this.offsetY}px)`;
2284
- this._anchor.style.transition = "none";
2285
- setTimeout(() => {
2286
- this.el.style.transform = "";
2287
- this.el.style.transition = "transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s";
2288
- this._anchor.style.overflow = "visible";
2289
- this._anchor.style.transform = "";
2290
- this._anchor.style.transition = "transform .2s";
2291
- setTimeout(() => {
2292
- this.el.style.overflow = "hidden";
2293
- this.el.style.backgroundColor = fabColor;
2294
- backdrop.style.transform = "scale(" + scaleFactor + ")";
2295
- backdrop.style.transition = "transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)";
2296
- this._menu.querySelectorAll("li > a").forEach((a) => {
2297
- a.style.opacity = "1";
2298
- a.tabIndex = 0;
2299
- });
2300
- window.addEventListener("scroll", this.close, true);
2301
- document.body.addEventListener("click", this._handleDocumentClick, true);
2302
- }, 100);
2303
- }, 0);
2214
+ this.el.removeEventListener("click", this._handleFABClick);
2215
+ this._anchor?.removeEventListener("keydown", this._handleAnchorKeydown);
2304
2216
  }
2305
- /**
2306
- * Reverse {@link _animateInToolbar}.
2307
- *
2308
- * Closing used to only unbind the listeners, leaving the FAB stretched
2309
- * across the viewport with its backdrop still in the DOM - and a fresh
2310
- * backdrop appended on every subsequent open.
2311
- */
2312
- _animateOutToolbar() {
2313
- const backdrop = this._backdrop;
2314
- this._backdrop = null;
2315
- this._menu?.querySelectorAll("li > a").forEach((a) => {
2316
- a.style.opacity = "";
2317
- a.tabIndex = -1;
2318
- });
2319
- if (backdrop) {
2320
- backdrop.style.transition = "transform .2s";
2321
- backdrop.style.transform = "scale(0)";
2322
- }
2323
- this.el.style.overflow = "";
2324
- this.el.style.backgroundColor = "";
2325
- this.el.style.transition = "transform .2s";
2326
- this.el.style.transform = `translateX(${this.offsetX}px)`;
2327
- this._anchor.style.transition = "transform .2s";
2328
- this._anchor.style.transform = `translateY(${this.offsetY}px)`;
2329
- setTimeout(() => {
2330
- backdrop?.remove();
2331
- this.el.classList.remove("active");
2332
- if (this._menu) this._menu.ariaExpanded = "false";
2333
- this.el.style.textAlign = "";
2334
- this.el.style.width = "";
2335
- this.el.style.bottom = "";
2336
- this.el.style.left = "";
2337
- this.el.style.transform = "";
2338
- this.el.style.transition = "";
2339
- this._anchor.style.overflow = "";
2340
- this._anchor.style.transform = "";
2341
- this._anchor.style.transition = "";
2342
- }, 200);
2217
+ _toggle() {
2218
+ if (this.isOpen) this.close();
2219
+ else this.open();
2343
2220
  }
2344
2221
  };
2345
2222
 
2346
2223
  // src/ts/components/carousel.ts
2347
2224
  var _defaults7 = {
2348
2225
  duration: 200,
2349
- // ms
2350
2226
  dist: -100,
2351
- // zoom scale TODO: make this more intuitive as an option
2352
2227
  shift: 0,
2353
- // spacing for center image
2354
2228
  padding: 0,
2355
- // Padding between non center items
2356
2229
  numVisible: 5,
2357
- // Number of visible items in carousel
2358
2230
  fullWidth: false,
2359
- // Change to full width styles
2360
2231
  indicators: false,
2361
- // Toggle indicators
2362
2232
  noWrap: false,
2363
- // Don't wrap around and cycle through items.
2364
2233
  onCycleTo: null
2365
- // Callback for when a new slide is cycled to.
2366
2234
  };
2367
- var Carousel = class _Carousel extends Component {
2235
+ var _Carousel = class _Carousel extends Component {
2368
2236
  constructor(el, options) {
2369
2237
  super(el, options, _Carousel);
2370
- /** The index of the center carousel item. */
2371
2238
  this.center = 0;
2372
- // Built once per instance. Creating the throttled function inside the
2373
- // handler - as this used to - produced a fresh closure per resize event and
2374
- // 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;
2375
2244
  this._handleThrottledResize = Utils.throttle(() => this._handleResize(), 200);
2376
2245
  this._handleCarouselTap = (e) => {
2377
- 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") {
2378
2248
  e.preventDefault();
2379
2249
  }
2380
2250
  this.pressed = true;
2381
2251
  this.dragged = false;
2382
2252
  this.verticalDragged = false;
2383
- this.reference = this._xpos(e);
2384
- this.referenceY = this._ypos(e);
2253
+ this.reference = e.clientX;
2254
+ this.referenceY = e.clientY;
2385
2255
  this.velocity = this.amplitude = 0;
2386
2256
  this.frame = this.offset;
2387
2257
  this.timestamp = Date.now();
2388
- clearInterval(this.ticker);
2389
- 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);
2390
2269
  };
2391
2270
  this._handleCarouselDrag = (e) => {
2392
- let x, y, delta, deltaY;
2393
- if (this.pressed) {
2394
- x = this._xpos(e);
2395
- y = this._ypos(e);
2396
- delta = this.reference - x;
2397
- deltaY = Math.abs(this.referenceY - y);
2398
- if (deltaY < 30 && !this.verticalDragged) {
2399
- if (delta > 2 || delta < -2) {
2400
- this.dragged = true;
2401
- this.reference = x;
2402
- this._scroll(this.offset + delta);
2403
- }
2404
- } else if (this.dragged) {
2405
- e.preventDefault();
2406
- e.stopPropagation();
2407
- return false;
2408
- } else {
2409
- 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);
2410
2279
  }
2411
- }
2412
- if (this.dragged) {
2280
+ } else if (this.dragged) {
2413
2281
  e.preventDefault();
2414
- e.stopPropagation();
2415
- return false;
2282
+ } else {
2283
+ this.verticalDragged = true;
2416
2284
  }
2285
+ if (this.dragged) e.preventDefault();
2417
2286
  };
2418
2287
  this._handleCarouselRelease = (e) => {
2419
- if (this.pressed) {
2420
- this.pressed = false;
2421
- } else {
2422
- 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 {
2423
2297
  }
2424
- clearInterval(this.ticker);
2425
2298
  this.target = this.offset;
2426
2299
  if (this.velocity > 10 || this.velocity < -10) {
2427
2300
  this.amplitude = 0.9 * this.velocity;
@@ -2438,86 +2311,91 @@ var Expressive = (() => {
2438
2311
  this.amplitude = this.target - this.offset;
2439
2312
  this.timestamp = Date.now();
2440
2313
  requestAnimationFrame(this._autoScroll);
2314
+ if (this.dragged) e.preventDefault();
2315
+ };
2316
+ this._handleCarouselClick = (e) => {
2441
2317
  if (this.dragged) {
2442
2318
  e.preventDefault();
2443
2319
  e.stopPropagation();
2320
+ return;
2444
2321
  }
2445
- return false;
2446
- };
2447
- this._handleCarouselClick = (e) => {
2448
- 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) {
2449
2327
  e.preventDefault();
2450
2328
  e.stopPropagation();
2451
- return false;
2452
- } else if (!this.options.fullWidth) {
2453
- const clickedElem = e.target.closest(".carousel-item");
2454
- if (!clickedElem) return;
2455
- const clickedIndex = [...clickedElem.parentNode.children].indexOf(clickedElem);
2456
- const diff = this._wrap(this.center) - clickedIndex;
2457
- if (diff !== 0) {
2458
- e.preventDefault();
2459
- e.stopPropagation();
2460
- }
2461
- if (clickedIndex < 0) {
2462
- if (e.clientX - e.target.getBoundingClientRect().left > this.el.clientWidth / 2) {
2463
- this.next();
2464
- } else {
2465
- this.prev();
2466
- }
2329
+ }
2330
+ if (clickedIndex < 0) {
2331
+ if (e.clientX - e.target.getBoundingClientRect().left > this.el.clientWidth / 2) {
2332
+ this.next();
2467
2333
  } else {
2468
- this._cycleTo(clickedIndex);
2334
+ this.prev();
2469
2335
  }
2336
+ } else {
2337
+ this._cycleTo(clickedIndex);
2470
2338
  }
2471
2339
  };
2472
2340
  this._handleIndicatorClick = (e) => {
2473
2341
  e.stopPropagation();
2474
- 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);
2475
2347
  };
2476
- this._handleIndicatorKeyPress = (e) => {
2477
- e.stopPropagation();
2478
- if (Utils.keys.ENTER.includes(e.key)) {
2479
- 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();
2480
2357
  }
2481
2358
  };
2482
- this._handleIndicatorInteraction = (e) => {
2483
- const indicator = e.target.closest(".indicator-item");
2484
- if (indicator) {
2485
- const index = [...indicator.parentNode.children].indexOf(indicator);
2486
- this._cycleTo(index);
2487
- }
2359
+ this._handleFlatScroll = () => {
2360
+ if (this._ignoreScroll) return;
2361
+ const index = this._nearestIndex();
2362
+ if (index !== this.center) this._syncActive(index, this.dragged);
2488
2363
  };
2489
2364
  this._handleResize = () => {
2365
+ if (!this.images.length) return;
2366
+ if (this._flat) {
2367
+ this._scrollToIndex(this.center, false);
2368
+ return;
2369
+ }
2490
2370
  if (this.options.fullWidth) {
2491
- const firstItem = this.el.querySelector(".carousel-item");
2492
- const activeItem = this.el.querySelector(".carousel-item.active");
2493
- if (!firstItem || !activeItem) return;
2371
+ const firstItem = this.images[0];
2372
+ if (!firstItem) return;
2494
2373
  this.itemWidth = firstItem.clientWidth;
2495
- this.imageHeight = activeItem.clientHeight;
2496
2374
  this.dim = this.itemWidth * 2 + this.options.padding;
2497
2375
  this.offset = this.center * 2 * this.itemWidth;
2498
2376
  this.target = this.offset;
2499
- this._setCarouselHeight(true);
2500
2377
  } else {
2501
2378
  this._scroll();
2502
2379
  }
2503
2380
  };
2504
2381
  this._track = () => {
2505
- 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);
2506
2386
  this.timestamp = now;
2507
2387
  this.frame = this.offset;
2508
2388
  this.velocity = 0.8 * v + 0.2 * this.velocity;
2509
2389
  };
2510
2390
  this._autoScroll = () => {
2511
- let elapsed, delta;
2512
- if (this.amplitude) {
2513
- elapsed = Date.now() - this.timestamp;
2514
- delta = this.amplitude * Math.exp(-elapsed / this.options.duration);
2515
- if (delta > 2 || delta < -2) {
2516
- this._scroll(this.target - delta);
2517
- requestAnimationFrame(this._autoScroll);
2518
- } else {
2519
- this._scroll(this.target);
2520
- }
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);
2521
2399
  }
2522
2400
  };
2523
2401
  this.el["Expressive_Carousel"] = this;
@@ -2525,56 +2403,58 @@ var Expressive = (() => {
2525
2403
  ..._Carousel.defaults,
2526
2404
  ...options
2527
2405
  };
2528
- this.hasMultipleSlides = this.el.querySelectorAll(".carousel-item").length > 1;
2529
- this.showIndicators = this.options.indicators && this.hasMultipleSlides;
2530
- 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
+ }
2531
2416
  this.pressed = false;
2532
2417
  this.dragged = false;
2533
2418
  this.offset = this.target = 0;
2534
- this.images = [];
2535
- const firstItem = this.el.querySelector(".carousel-item");
2536
- if (!firstItem) {
2419
+ this.images = this._collectItems();
2420
+ if (!this.images.length) {
2537
2421
  console.error("Carousel: no .carousel-item elements to show");
2538
2422
  return;
2539
2423
  }
2540
- this.itemWidth = firstItem.clientWidth;
2541
- this.itemHeight = firstItem.clientHeight;
2542
- this.dim = this.itemWidth * 2 + this.options.padding || 1;
2543
- if (this.options.fullWidth) {
2544
- this.options.dist = 0;
2545
- this._setCarouselHeight();
2546
- if (this.showIndicators) {
2547
- this.el.querySelector(".carousel-fixed-item")?.classList.add("with-indicators");
2548
- }
2549
- }
2550
- this._indicators = document.createElement("ul");
2551
- this._indicators.classList.add("indicators");
2552
- this.el.querySelectorAll(".carousel-item").forEach((item, i) => {
2553
- this.images.push(item);
2554
- if (this.showIndicators) {
2555
- const indicator = document.createElement("li");
2556
- indicator.classList.add("indicator-item");
2557
- indicator.tabIndex = 0;
2558
- if (i === 0) {
2559
- indicator.classList.add("active");
2560
- }
2561
- this._indicators.appendChild(indicator);
2562
- }
2563
- });
2564
- 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;
2565
2427
  this.count = this.images.length;
2566
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");
2567
2438
  this._setupEventHandlers();
2568
- 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;
2569
2454
  }
2570
2455
  static get defaults() {
2571
2456
  return _defaults7;
2572
2457
  }
2573
- /**
2574
- * Initializes instances of Carousel.
2575
- * @param els HTML elements.
2576
- * @param options Component options.
2577
- */
2578
2458
  static init(els, options = {}) {
2579
2459
  return super.init(els, options, _Carousel);
2580
2460
  }
@@ -2582,81 +2462,104 @@ var Expressive = (() => {
2582
2462
  return el["Expressive_Carousel"];
2583
2463
  }
2584
2464
  destroy() {
2585
- this._removeEventHandlers();
2465
+ if (this._started) this._teardown();
2586
2466
  this.el["Expressive_Carousel"] = void 0;
2587
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
+ }
2588
2506
  _setupEventHandlers() {
2589
- if (typeof window.ontouchstart !== "undefined") {
2590
- this.el.addEventListener("touchstart", this._handleCarouselTap);
2591
- this.el.addEventListener("touchmove", this._handleCarouselDrag);
2592
- this.el.addEventListener("touchend", this._handleCarouselRelease);
2593
- }
2594
- this.el.addEventListener("mousedown", this._handleCarouselTap);
2595
- this.el.addEventListener("mousemove", this._handleCarouselDrag);
2596
- this.el.addEventListener("mouseup", this._handleCarouselRelease);
2597
- this.el.addEventListener("mouseleave", this._handleCarouselRelease);
2598
- this.el.addEventListener("click", this._handleCarouselClick);
2507
+ this.el.addEventListener("keydown", this._handleKeydown);
2599
2508
  if (this.showIndicators && this._indicators) {
2600
- this._indicators.querySelectorAll(".indicator-item").forEach((el) => {
2601
- el.addEventListener("click", this._handleIndicatorClick);
2602
- el.addEventListener("keypress", this._handleIndicatorKeyPress);
2603
- });
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;
2515
+ }
2516
+ if (this._flat) {
2517
+ this._scroller.addEventListener("scroll", this._handleFlatScroll, { passive: true });
2518
+ return;
2604
2519
  }
2605
- window.addEventListener("resize", this._handleThrottledResize);
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);
2606
2525
  }
2607
2526
  _removeEventHandlers() {
2608
- if (typeof window.ontouchstart !== "undefined") {
2609
- this.el.removeEventListener("touchstart", this._handleCarouselTap);
2610
- this.el.removeEventListener("touchmove", this._handleCarouselDrag);
2611
- this.el.removeEventListener("touchend", this._handleCarouselRelease);
2612
- }
2613
- this.el.removeEventListener("mousedown", this._handleCarouselTap);
2614
- this.el.removeEventListener("mousemove", this._handleCarouselDrag);
2615
- this.el.removeEventListener("mouseup", this._handleCarouselRelease);
2616
- 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);
2617
2536
  this.el.removeEventListener("click", this._handleCarouselClick);
2618
- if (this.showIndicators && this._indicators) {
2619
- this._indicators.querySelectorAll(".indicator-item").forEach((el) => {
2620
- el.removeEventListener("click", this._handleIndicatorClick);
2621
- });
2537
+ _Carousel._live.delete(this);
2538
+ if (_Carousel._live.size === 0 && _Carousel._resizeListening) {
2539
+ window.removeEventListener("resize", _Carousel._onResize);
2540
+ _Carousel._resizeListening = false;
2622
2541
  }
2623
- window.removeEventListener("resize", this._handleThrottledResize);
2624
2542
  }
2625
- _setCarouselHeight(imageOnly = false) {
2626
- const firstSlide = this.el.querySelector(".carousel-item.active") ? this.el.querySelector(".carousel-item.active") : this.el.querySelector(".carousel-item");
2627
- const firstImage = firstSlide.querySelector("img");
2628
- if (firstImage) {
2629
- if (firstImage.complete) {
2630
- const imageHeight = firstImage.clientHeight;
2631
- if (imageHeight > 0) {
2632
- this.el.style.height = imageHeight + "px";
2633
- } else {
2634
- const naturalWidth = firstImage.naturalWidth;
2635
- const naturalHeight = firstImage.naturalHeight;
2636
- const adjustedHeight = this.el.clientWidth / naturalWidth * naturalHeight;
2637
- this.el.style.height = adjustedHeight + "px";
2638
- }
2639
- } else {
2640
- firstImage.addEventListener("load", () => {
2641
- this.el.style.height = firstImage.offsetHeight + "px";
2642
- });
2643
- }
2644
- } else if (!imageOnly) {
2645
- const slideHeight = firstSlide.clientHeight;
2646
- this.el.style.height = slideHeight + "px";
2647
- }
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
+ });
2648
2555
  }
2649
2556
  _xpos(e) {
2650
- if (e.type.startsWith("touch") && e.targetTouches.length >= 1) {
2651
- return e.targetTouches[0].clientX;
2652
- }
2653
- return e.clientX;
2557
+ if ("clientX" in e) return e.clientX;
2558
+ return 0;
2654
2559
  }
2655
2560
  _ypos(e) {
2656
- if (e.type.startsWith("touch") && e.targetTouches.length >= 1) {
2657
- return e.targetTouches[0].clientY;
2658
- }
2659
- return e.clientY;
2561
+ if ("clientY" in e) return e.clientY;
2562
+ return 0;
2660
2563
  }
2661
2564
  _wrap(x) {
2662
2565
  return x >= this.count ? x % this.count : x < 0 ? this._wrap(this.count + x % this.count) : x;
@@ -2665,18 +2568,20 @@ var Expressive = (() => {
2665
2568
  if (!this.el.classList.contains("scrolling")) {
2666
2569
  this.el.classList.add("scrolling");
2667
2570
  }
2668
- if (this.scrollingTimeout != null) {
2669
- clearTimeout(this.scrollingTimeout);
2670
- }
2571
+ window.clearTimeout(this.scrollingTimeout);
2671
2572
  this.scrollingTimeout = setTimeout(() => {
2672
2573
  this.el.classList.remove("scrolling");
2673
2574
  }, this.options.duration);
2674
2575
  this.offset = typeof x === "number" ? x : this.offset;
2675
2576
  this.center = Math.floor((this.offset + this.dim / 2) / this.dim);
2676
- const half = this.count >> 1, delta = this.offset - this.center * this.dim, dir = delta < 0 ? 1 : -1, tween = -dir * delta * 2 / this.dim;
2677
- 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;
2678
2581
  const lastCenter = this.center;
2679
2582
  const numVisibleOffset = 1 / this.options.numVisible;
2583
+ let alignment;
2584
+ let centerTweenedOpacity;
2680
2585
  if (this.options.fullWidth) {
2681
2586
  alignment = "translateX(0)";
2682
2587
  centerTweenedOpacity = 1;
@@ -2685,26 +2590,17 @@ var Expressive = (() => {
2685
2590
  alignment += "translateY(" + (this.el.clientHeight - this.itemHeight) / 2 + "px)";
2686
2591
  centerTweenedOpacity = 1 - numVisibleOffset * tween;
2687
2592
  }
2688
- if (this.showIndicators) {
2689
- const diff = this.center % this.count;
2690
- const activeIndicator = this._indicators.querySelector(".indicator-item.active");
2691
- const activeIndicatorIndex = [...activeIndicator.parentNode.children].indexOf(
2692
- activeIndicator
2693
- );
2694
- if (activeIndicatorIndex !== diff) {
2695
- activeIndicator.classList.remove("active");
2696
- const pos = diff < 0 ? this.count + diff : diff;
2697
- this._indicators.querySelectorAll(".indicator-item")[pos].classList.add("active");
2698
- }
2699
- }
2593
+ this._paintIndicators(this.center);
2700
2594
  if (!this.noWrap || this.center >= 0 && this.center < this.count) {
2701
- el = this.images[this._wrap(this.center)];
2702
- if (!el.classList.contains("active")) {
2595
+ const el = this.images[this._wrap(this.center)];
2596
+ if (el && !el.classList.contains("active")) {
2703
2597
  this.el.querySelector(".carousel-item.active")?.classList.remove("active");
2704
2598
  el.classList.add("active");
2705
2599
  }
2706
2600
  }
2707
- for (i = 1; i <= half; ++i) {
2601
+ for (let i = 1; i <= half; ++i) {
2602
+ let zTranslation;
2603
+ let tweenedOpacity;
2708
2604
  if (this.options.fullWidth) {
2709
2605
  zTranslation = this.options.dist;
2710
2606
  tweenedOpacity = i === half && delta < 0 ? 1 - tween : 1;
@@ -2713,7 +2609,7 @@ var Expressive = (() => {
2713
2609
  tweenedOpacity = 1 - numVisibleOffset * (i * 2 + tween * dir);
2714
2610
  }
2715
2611
  if (!this.noWrap || this.center + i < this.count) {
2716
- el = this.images[this._wrap(this.center + i)];
2612
+ const el = this.images[this._wrap(this.center + i)];
2717
2613
  const transformString = `${alignment} translateX(${this.options.shift + (this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`;
2718
2614
  this._updateItemStyle(el, tweenedOpacity, -i, transformString);
2719
2615
  }
@@ -2725,32 +2621,113 @@ var Expressive = (() => {
2725
2621
  tweenedOpacity = 1 - numVisibleOffset * (i * 2 - tween * dir);
2726
2622
  }
2727
2623
  if (!this.noWrap || this.center - i >= 0) {
2728
- el = this.images[this._wrap(this.center - i)];
2624
+ const el = this.images[this._wrap(this.center - i)];
2729
2625
  const transformString = `${alignment} translateX(${-this.options.shift + (-this.dim * i - delta) / 2}px) translateZ(${zTranslation}px)`;
2730
2626
  this._updateItemStyle(el, tweenedOpacity, -i, transformString);
2731
2627
  }
2732
2628
  }
2733
2629
  if (!this.noWrap || this.center >= 0 && this.center < this.count) {
2734
- el = this.images[this._wrap(this.center)];
2630
+ const el = this.images[this._wrap(this.center)];
2735
2631
  const transformString = `${alignment} translateX(${-delta / 2}px) translateX(${dir * this.options.shift * tween}px) translateZ(${this.options.dist * tween}px)`;
2736
2632
  this._updateItemStyle(el, centerTweenedOpacity, 0, transformString);
2737
2633
  }
2738
- 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));
2739
2636
  if (lastCenter !== this.center && typeof this.options.onCycleTo === "function") {
2740
- this.options.onCycleTo.call(this, _currItem, this.dragged);
2637
+ this.options.onCycleTo.call(this, currItem, this.dragged);
2741
2638
  }
2742
2639
  if (typeof this.oneTimeCallback === "function") {
2743
- this.oneTimeCallback.call(this, _currItem, this.dragged);
2640
+ this.oneTimeCallback.call(this, currItem, this.dragged);
2744
2641
  this.oneTimeCallback = null;
2745
2642
  }
2746
2643
  }
2747
2644
  _updateItemStyle(el, opacity, zIndex, transform) {
2645
+ if (!el) return;
2748
2646
  el.style.transform = transform;
2749
2647
  el.style.zIndex = zIndex.toString();
2750
2648
  el.style.opacity = opacity.toString();
2751
2649
  el.style.visibility = "visible";
2752
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
+ }
2753
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
+ }
2754
2731
  let diff = this.center % this.count - n;
2755
2732
  if (!this.noWrap) {
2756
2733
  if (diff < 0) {
@@ -2778,14 +2755,8 @@ var Expressive = (() => {
2778
2755
  requestAnimationFrame(this._autoScroll);
2779
2756
  }
2780
2757
  }
2781
- /**
2782
- * Move carousel to next slide or go forward a given amount of slides.
2783
- * @param n How many times the carousel slides.
2784
- */
2785
2758
  next(n = 1) {
2786
- if (n === void 0 || isNaN(n)) {
2787
- n = 1;
2788
- }
2759
+ if (n === void 0 || isNaN(n)) n = 1;
2789
2760
  let index = this.center + n;
2790
2761
  if (index >= this.count || index < 0) {
2791
2762
  if (this.noWrap) return;
@@ -2793,14 +2764,8 @@ var Expressive = (() => {
2793
2764
  }
2794
2765
  this._cycleTo(index);
2795
2766
  }
2796
- /**
2797
- * Move carousel to previous slide or go back a given amount of slides.
2798
- * @param n How many times the carousel slides.
2799
- */
2800
2767
  prev(n = 1) {
2801
- if (n === void 0 || isNaN(n)) {
2802
- n = 1;
2803
- }
2768
+ if (n === void 0 || isNaN(n)) n = 1;
2804
2769
  let index = this.center - n;
2805
2770
  if (index >= this.count || index < 0) {
2806
2771
  if (this.noWrap) return;
@@ -2808,15 +2773,8 @@ var Expressive = (() => {
2808
2773
  }
2809
2774
  this._cycleTo(index);
2810
2775
  }
2811
- /**
2812
- * Move carousel to nth slide.
2813
- * @param n Index of slide.
2814
- * @param callback "onCycleTo" optional callback.
2815
- */
2816
2776
  set(n, callback) {
2817
- if (n === void 0 || isNaN(n)) {
2818
- n = 0;
2819
- }
2777
+ if (n === void 0 || isNaN(n)) n = 0;
2820
2778
  if (n > this.count || n < 0) {
2821
2779
  if (this.noWrap) return;
2822
2780
  n = this._wrap(n);
@@ -2824,6 +2782,12 @@ var Expressive = (() => {
2824
2782
  this._cycleTo(n, callback);
2825
2783
  }
2826
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;
2827
2791
 
2828
2792
  // src/ts/components/characterCounter.ts
2829
2793
  var _defaults8 = Object.freeze({});
@@ -2913,77 +2877,75 @@ var Expressive = (() => {
2913
2877
  var Collapsible = class _Collapsible extends Component {
2914
2878
  constructor(el, options) {
2915
2879
  super(el, options, _Collapsible);
2916
- this._handleCollapsibleClick = (e) => {
2917
- const header = e.target.closest(".collapsible-header");
2918
- if (e.target && header) {
2919
- const collapsible = header.closest(".collapsible");
2920
- if (collapsible !== this.el) return;
2921
- const li = header.closest("li");
2922
- const isActive = li.classList.contains("active");
2923
- const index = [...li.parentNode.children].indexOf(li);
2924
- if (isActive) this.close(index);
2925
- else this.open(index);
2926
- }
2927
- };
2928
- this._handleCollapsibleKeydown = (e) => {
2929
- if (Utils.keys.ENTER.includes(e.key)) {
2930
- this._handleCollapsibleClick(e);
2931
- }
2932
- };
2880
+ this._items = [];
2881
+ this._endTimers = [];
2882
+ this._wrappedBodies = [];
2933
2883
  /**
2934
2884
  * Open collapsible section.
2935
- * @param n Nth section to open.
2885
+ * @param index 0-based index of the section to open.
2936
2886
  */
2937
2887
  this.open = (index) => {
2938
- const listItems = Array.from(this.el.children).filter((c) => c.tagName === "LI");
2939
- const li = listItems[index];
2940
- if (li && !li.classList.contains("active")) {
2941
- if (typeof this.options.onOpenStart === "function") {
2942
- this.options.onOpenStart.call(this, li);
2943
- }
2944
- if (this.options.accordion) {
2945
- const activeLis = listItems.filter((li2) => li2.classList.contains("active"));
2946
- activeLis.forEach((activeLi) => {
2947
- const index2 = listItems.indexOf(activeLi);
2948
- this.close(index2);
2949
- });
2950
- }
2951
- li.classList.add("active");
2952
- 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
+ });
2953
2894
  }
2895
+ this._setOpen(item, true);
2954
2896
  };
2955
2897
  /**
2956
2898
  * Close collapsible section.
2957
- * @param n Nth section to close.
2899
+ * @param index 0-based index of the section to close.
2958
2900
  */
2959
2901
  this.close = (index) => {
2960
- const li = Array.from(this.el.children).filter((c) => c.tagName === "LI")[index];
2961
- if (li && li.classList.contains("active")) {
2962
- if (typeof this.options.onCloseStart === "function") {
2963
- this.options.onCloseStart.call(this, li);
2964
- }
2965
- li.classList.remove("active");
2966
- this._animateOut(index);
2967
- }
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);
2968
2936
  };
2969
2937
  this.el["Expressive_Collapsible"] = this;
2938
+ const accordion = options.accordion !== void 0 ? options.accordion : !el.classList.contains("expandable");
2970
2939
  this.options = {
2971
2940
  ..._Collapsible.defaults,
2972
- ...options
2941
+ ...options,
2942
+ accordion
2973
2943
  };
2974
- this._headers = Array.from(this.el.querySelectorAll("li > .collapsible-header"));
2975
- 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();
2976
2948
  this._setupEventHandlers();
2977
- const activeBodies = Array.from(
2978
- this.el.querySelectorAll("li.active > .collapsible-body")
2979
- );
2980
- if (this.options.accordion) {
2981
- if (activeBodies.length > 0) {
2982
- this._setExpanded(activeBodies[0]);
2983
- }
2984
- } else {
2985
- activeBodies.forEach((el2) => this._setExpanded(el2));
2986
- }
2987
2949
  }
2988
2950
  static get defaults() {
2989
2951
  return _defaults9;
@@ -3001,48 +2963,130 @@ var Expressive = (() => {
3001
2963
  }
3002
2964
  destroy() {
3003
2965
  this._removeEventHandlers();
2966
+ this._endTimers.forEach((id) => clearTimeout(id));
2967
+ this._endTimers = [];
2968
+ this._unwrapDetailsBodies();
3004
2969
  this.el["Expressive_Collapsible"] = void 0;
3005
2970
  }
3006
- _setupEventHandlers() {
3007
- this.el.addEventListener("click", this._handleCollapsibleClick);
3008
- this._headers.forEach(
3009
- (header) => header.addEventListener("keydown", this._handleCollapsibleKeydown)
3010
- );
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");
3011
2975
  }
3012
- _removeEventHandlers() {
3013
- this.el.removeEventListener("click", this._handleCollapsibleClick);
3014
- this._headers.forEach(
3015
- (header) => header.removeEventListener("keydown", this._handleCollapsibleKeydown)
3016
- );
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));
3017
2992
  }
3018
- _setExpanded(li) {
3019
- li.style.maxHeight = li.scrollHeight + "px";
2993
+ _isOpen(item) {
2994
+ if (item instanceof HTMLDetailsElement) return item.open;
2995
+ return item.classList.contains("active");
3020
2996
  }
3021
- _animateIn(index) {
3022
- const li = this.el.children[index];
3023
- if (!li) return;
3024
- const body = li.querySelector(".collapsible-body");
3025
- const duration = this.options.inDuration;
3026
- body.style.transition = `max-height ${duration}ms ease-out`;
3027
- this._setExpanded(body);
3028
- setTimeout(() => {
3029
- if (typeof this.options.onOpenEnd === "function") {
3030
- this.options.onOpenEnd.call(this, li);
3031
- }
3032
- }, 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
+ });
3033
3025
  }
3034
- _animateOut(index) {
3035
- const li = this.el.children[index];
3036
- if (!li) return;
3037
- const body = li.querySelector(".collapsible-body");
3038
- const duration = this.options.outDuration;
3039
- body.style.transition = `max-height ${duration}ms ease-out`;
3040
- body.style.maxHeight = "0";
3041
- setTimeout(() => {
3042
- if (typeof this.options.onCloseEnd === "function") {
3043
- this.options.onCloseEnd.call(this, li);
3044
- }
3045
- }, 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;
3046
3090
  }
3047
3091
  };
3048
3092
 
@@ -4842,38 +4886,22 @@ var Expressive = (() => {
4842
4886
  // src/ts/components/parallax.ts
4843
4887
  var _defaults15 = {
4844
4888
  responsiveThreshold: 0
4845
- // breakpoint for swipeable
4846
4889
  };
4847
- var _Parallax = class _Parallax extends Component {
4890
+ var Parallax = class _Parallax extends Component {
4848
4891
  constructor(el, options) {
4849
4892
  super(el, options, _Parallax);
4850
- this._handleImageLoad = () => {
4851
- this._updateParallax();
4852
- };
4853
4893
  this.el["Expressive_Parallax"] = this;
4854
4894
  this.options = {
4855
4895
  ..._Parallax.defaults,
4856
4896
  ...options
4857
4897
  };
4858
- this._enabled = window.innerWidth > this.options.responsiveThreshold;
4859
- this._img = this.el.querySelector("img");
4860
- if (!this._img) {
4861
- console.error("Parallax: no <img> to move inside the .parallax element");
4862
- return;
4898
+ if (!this.el.querySelector("img, video")) {
4899
+ console.error("Parallax: no <img> or <video> to move inside the .parallax element");
4863
4900
  }
4864
- this._updateParallax();
4865
- this._setupEventHandlers();
4866
- this._setupStyles();
4867
- _Parallax._parallaxes.push(this);
4868
4901
  }
4869
4902
  static get defaults() {
4870
4903
  return _defaults15;
4871
4904
  }
4872
- /**
4873
- * Initializes instances of Parallax.
4874
- * @param els HTML elements.
4875
- * @param options Component options.
4876
- */
4877
4905
  static init(els, options = {}) {
4878
4906
  return super.init(els, options, _Parallax);
4879
4907
  }
@@ -4881,84 +4909,9 @@ var Expressive = (() => {
4881
4909
  return el["Expressive_Parallax"];
4882
4910
  }
4883
4911
  destroy() {
4884
- const index = _Parallax._parallaxes.indexOf(this);
4885
- if (index >= 0) _Parallax._parallaxes.splice(index, 1);
4886
- if (this._img) this._img.style.transform = "";
4887
- this._removeEventHandlers();
4888
4912
  this.el["Expressive_Parallax"] = void 0;
4889
4913
  }
4890
- static _handleScroll() {
4891
- for (let i = 0; i < _Parallax._parallaxes.length; i++) {
4892
- const parallaxInstance = _Parallax._parallaxes[i];
4893
- parallaxInstance._updateParallax.call(parallaxInstance);
4894
- }
4895
- }
4896
- static _handleWindowResize() {
4897
- for (let i = 0; i < _Parallax._parallaxes.length; i++) {
4898
- const parallaxInstance = _Parallax._parallaxes[i];
4899
- parallaxInstance._enabled = window.innerWidth > parallaxInstance.options.responsiveThreshold;
4900
- }
4901
- }
4902
- _setupEventHandlers() {
4903
- this._img?.addEventListener("load", this._handleImageLoad);
4904
- if (_Parallax._parallaxes.length === 0) {
4905
- if (!_Parallax._handleWindowResizeThrottled) {
4906
- _Parallax._handleWindowResizeThrottled = Utils.throttle(_Parallax._handleWindowResize, 100);
4907
- }
4908
- window.addEventListener("scroll", _Parallax._requestScrollUpdate, { passive: true });
4909
- window.addEventListener("resize", _Parallax._handleWindowResizeThrottled, { passive: true });
4910
- }
4911
- }
4912
- _removeEventHandlers() {
4913
- this._img?.removeEventListener("load", this._handleImageLoad);
4914
- if (_Parallax._parallaxes.length === 0) {
4915
- window.removeEventListener("scroll", _Parallax._requestScrollUpdate);
4916
- window.removeEventListener("resize", _Parallax._handleWindowResizeThrottled);
4917
- if (_Parallax._scrollFrame !== null) {
4918
- cancelAnimationFrame(_Parallax._scrollFrame);
4919
- _Parallax._scrollFrame = null;
4920
- }
4921
- }
4922
- }
4923
- _setupStyles() {
4924
- if (this._img) this._img.style.opacity = "1";
4925
- }
4926
- _updateParallax() {
4927
- const rect = this.el.getBoundingClientRect();
4928
- const containerHeight = rect.height > 0 ? this.el.parentElement.offsetHeight : 500;
4929
- const imgHeight = this._img.offsetHeight;
4930
- const parallaxDist = imgHeight - containerHeight;
4931
- const top = rect.top + window.scrollY - document.documentElement.clientTop;
4932
- const bottom = top + containerHeight;
4933
- const scrollTop = Utils.getDocumentScrollTop();
4934
- const windowHeight = window.innerHeight;
4935
- const windowBottom = scrollTop + windowHeight;
4936
- const percentScrolled = (windowBottom - top) / (containerHeight + windowHeight);
4937
- const parallax = parallaxDist * percentScrolled;
4938
- if (!this._enabled) {
4939
- this._img.style.transform = "";
4940
- } else if (bottom > scrollTop && top < scrollTop + windowHeight) {
4941
- this._img.style.transform = `translate3D(-50%, ${parallax}px, 0)`;
4942
- }
4943
- }
4944
- };
4945
- _Parallax._parallaxes = [];
4946
- _Parallax._scrollFrame = null;
4947
- /**
4948
- * Coalesce scroll events onto animation frames.
4949
- *
4950
- * The handler reads layout and then writes a transform, so running it more
4951
- * than once per frame is wasted work - and the 5ms throttle it replaces was
4952
- * short enough to let several run per frame anyway.
4953
- */
4954
- _Parallax._requestScrollUpdate = () => {
4955
- if (_Parallax._scrollFrame !== null) return;
4956
- _Parallax._scrollFrame = requestAnimationFrame(() => {
4957
- _Parallax._scrollFrame = null;
4958
- _Parallax._handleScroll();
4959
- });
4960
4914
  };
4961
- var Parallax = _Parallax;
4962
4915
 
4963
4916
  // src/ts/components/pushpin.ts
4964
4917
  var _defaults16 = {
@@ -6198,60 +6151,96 @@ var Expressive = (() => {
6198
6151
  var _TapTarget = class _TapTarget extends Component {
6199
6152
  constructor(el, options) {
6200
6153
  super(el, options, _TapTarget);
6201
- // Built once per instance; see the note in carousel.ts - the previous form
6202
- // returned a new throttled function on every resize and never invoked it.
6203
- this._handleThrottledResize = Utils.throttle(() => this._handleResize(), 200);
6204
- this._handleKeyboardInteraction = (e) => {
6205
- if (Utils.keys.ENTER.includes(e.key)) {
6206
- this._handleTargetToggle();
6207
- }
6208
- };
6209
- this._handleTargetToggle = () => {
6210
- if (!this.isOpen) this.open();
6211
- else this.close();
6212
- };
6213
- /*_handleOriginClick = () => {
6214
- this.close();
6215
- }*/
6216
- this._handleResize = () => {
6217
- this._calculatePositioning();
6218
- };
6219
- this._handleDocumentClick = (e) => {
6220
- const target = e.target;
6221
- if (!(target instanceof Element)) return;
6222
- if (!this.originEl.contains(target) && !target.closest(".tap-target-wrapper")) {
6223
- this.close();
6224
- }
6225
- };
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;
6226
6162
  /**
6227
6163
  * Open Tap Target.
6228
6164
  */
6229
6165
  this.open = () => {
6230
- 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();
6231
6171
  if (typeof this.options.onOpen === "function") {
6232
6172
  this.options.onOpen.call(this, this.originEl);
6233
6173
  }
6234
6174
  this.isOpen = true;
6235
6175
  this.wrapper.classList.add("open");
6236
- this.wrapper.ariaExpanded = "true";
6237
- document.body.addEventListener("click", this._handleDocumentClick, true);
6238
- document.body.addEventListener("keypress", this._handleDocumentClick, true);
6239
- 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
+ }
6240
6194
  };
6241
6195
  /**
6242
6196
  * Close Tap Target.
6243
6197
  */
6244
6198
  this.close = () => {
6245
6199
  if (!this.isOpen) return;
6246
- if (typeof this.options.onClose === "function") {
6200
+ if (typeof this.options.onClose === "function" && this.originEl) {
6247
6201
  this.options.onClose.call(this, this.originEl);
6248
6202
  }
6249
6203
  this.isOpen = false;
6250
- this.wrapper.classList.remove("open");
6251
- this.wrapper.ariaExpanded = "false";
6252
- document.body.removeEventListener("click", this._handleDocumentClick, true);
6253
- document.body.removeEventListener("keypress", this._handleDocumentClick, true);
6254
- 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();
6255
6244
  };
6256
6245
  this.el["Expressive_TapTarget"] = this;
6257
6246
  this.options = {
@@ -6264,7 +6253,6 @@ var Expressive = (() => {
6264
6253
  console.error(`TapTarget: no element with id "${el.dataset.target ?? ""}" to attach to`);
6265
6254
  return;
6266
6255
  }
6267
- this.originEl.tabIndex = 0;
6268
6256
  this._setup();
6269
6257
  this._calculatePositioning();
6270
6258
  this._setupEventHandlers();
@@ -6273,11 +6261,6 @@ var Expressive = (() => {
6273
6261
  static get defaults() {
6274
6262
  return _defaults21;
6275
6263
  }
6276
- /**
6277
- * Initializes instances of TapTarget.
6278
- * @param els HTML elements.
6279
- * @param options Component options.
6280
- */
6281
6264
  static init(els, options = {}) {
6282
6265
  return super.init(els, options, _TapTarget);
6283
6266
  }
@@ -6287,116 +6270,95 @@ var Expressive = (() => {
6287
6270
  destroy() {
6288
6271
  this.close();
6289
6272
  this._removeEventHandlers();
6273
+ this._teardownDom();
6290
6274
  this.el["Expressive_TapTarget"] = void 0;
6291
6275
  const index = _TapTarget._taptargets.indexOf(this);
6292
- if (index >= 0) {
6293
- _TapTarget._taptargets.splice(index, 1);
6294
- }
6295
- }
6296
- _setupEventHandlers() {
6297
- if (!this.originEl) return;
6298
- this.originEl.addEventListener("click", this._handleTargetToggle);
6299
- this.originEl.addEventListener("keypress", this._handleKeyboardInteraction, true);
6300
- window.addEventListener("resize", this._handleThrottledResize);
6301
- }
6302
- _removeEventHandlers() {
6303
- if (!this.originEl) return;
6304
- this.originEl.removeEventListener("click", this._handleTargetToggle);
6305
- this.originEl.removeEventListener("keypress", this._handleKeyboardInteraction, true);
6306
- window.removeEventListener("resize", this._handleThrottledResize);
6276
+ if (index >= 0) _TapTarget._taptargets.splice(index, 1);
6307
6277
  }
6308
6278
  _setup() {
6309
- this.wrapper = this.el.parentElement;
6310
- this.waveEl = this.wrapper.querySelector(".tap-target-wave");
6311
- this.el.parentElement.ariaExpanded = "false";
6312
- this.originEl.style.zIndex = "1002";
6313
- this.contentEl = this.el.querySelector(".tap-target-content");
6314
- 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 {
6315
6283
  this.wrapper = document.createElement("div");
6316
6284
  this.wrapper.classList.add("tap-target-wrapper");
6317
6285
  this.el.before(this.wrapper);
6318
6286
  this.wrapper.append(this.el);
6287
+ this._createdWrapper = true;
6319
6288
  }
6289
+ this.contentEl = this.el.querySelector(".tap-target-content");
6320
6290
  if (!this.contentEl) {
6321
6291
  this.contentEl = document.createElement("div");
6322
- 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);
6323
6294
  this.el.append(this.contentEl);
6324
6295
  }
6296
+ this.waveEl = this.el.querySelector(".tap-target-wave") ?? this.wrapper.querySelector(".tap-target-wave");
6325
6297
  if (!this.waveEl) {
6326
6298
  this.waveEl = document.createElement("div");
6327
- this.waveEl.classList.add("tap-target-wave");
6328
- 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);
6329
6338
  }
6339
+ this.wrapper?.addEventListener("toggle", this._handlePopoverToggle);
6340
+ window.addEventListener("resize", this._handleThrottledResize, { passive: true });
6330
6341
  }
6331
- _offset(el) {
6332
- const box = el.getBoundingClientRect();
6333
- const docElem = document.documentElement;
6334
- return {
6335
- top: box.top + window.pageYOffset - docElem.clientTop,
6336
- left: box.left + window.pageXOffset - docElem.clientLeft
6337
- };
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);
6338
6348
  }
6339
6349
  _calculatePositioning() {
6340
- let isFixed = getComputedStyle(this.originEl).position === "fixed";
6341
- if (!isFixed) {
6342
- let currentElem = this.originEl;
6343
- const parents = [];
6344
- while ((currentElem = currentElem.parentNode) && currentElem !== document)
6345
- parents.push(currentElem);
6346
- for (let i = 0; i < parents.length; i++) {
6347
- isFixed = getComputedStyle(parents[i]).position === "fixed";
6348
- if (isFixed) break;
6349
- }
6350
- }
6351
- const originWidth = this.originEl.offsetWidth;
6352
- const originHeight = this.originEl.offsetHeight;
6353
- const originOffset = this._offset(this.originEl);
6354
- const originTop = isFixed ? originOffset.top - Utils.getDocumentScrollTop() : originOffset.top;
6355
- const originLeft = isFixed ? originOffset.left - Utils.getDocumentScrollLeft() : originOffset.left;
6356
- const windowWidth = window.innerWidth;
6357
- const windowHeight = window.innerHeight;
6358
- const scrollBarWidth = windowWidth - document.documentElement.clientWidth;
6359
- const centerX = windowWidth / 2;
6360
- const centerY = windowHeight / 2;
6361
- const isLeft = originLeft <= centerX;
6362
- const isRight = originLeft > centerX;
6363
- const isTop = originTop <= centerY;
6364
- const isBottom = originTop > centerY;
6365
- const isCenterX = originLeft >= windowWidth * 0.25 && originLeft <= windowWidth * 0.75;
6366
- const tapTargetWidth = this.el.offsetWidth;
6367
- const tapTargetHeight = this.el.offsetHeight;
6368
- const tapTargetTop = originTop + originHeight / 2 - tapTargetHeight / 2;
6369
- const tapTargetLeft = originLeft + originWidth / 2 - tapTargetWidth / 2;
6370
- const tapTargetPosition = isFixed ? "fixed" : "absolute";
6371
- const tapTargetTextWidth = isCenterX ? tapTargetWidth : tapTargetWidth / 2 + originWidth;
6372
- const tapTargetTextHeight = tapTargetHeight / 2;
6373
- const tapTargetTextTop = isTop ? tapTargetHeight / 2 : 0;
6374
- const tapTargetTextBottom = 0;
6375
- const tapTargetTextLeft = isLeft && !isCenterX ? tapTargetWidth / 2 - originWidth : 0;
6376
- const tapTargetTextRight = 0;
6377
- const tapTargetTextPadding = originWidth;
6378
- const tapTargetTextAlign = isBottom ? "bottom" : "top";
6379
- const tapTargetWaveWidth = originWidth > originHeight ? originWidth * 2 : originWidth * 2;
6380
- const tapTargetWaveHeight = tapTargetWaveWidth;
6381
- const tapTargetWaveTop = tapTargetHeight / 2 - tapTargetWaveHeight / 2;
6382
- const tapTargetWaveLeft = tapTargetWidth / 2 - tapTargetWaveWidth / 2;
6383
- this.wrapper.style.top = isTop ? tapTargetTop + "px" : "";
6384
- this.wrapper.style.right = isRight ? windowWidth - tapTargetLeft - tapTargetWidth - scrollBarWidth + "px" : "";
6385
- this.wrapper.style.bottom = isBottom ? windowHeight - tapTargetTop - tapTargetHeight + "px" : "";
6386
- this.wrapper.style.left = isLeft ? tapTargetLeft + "px" : "";
6387
- this.wrapper.style.position = tapTargetPosition;
6388
- this.contentEl.style.width = tapTargetTextWidth + "px";
6389
- this.contentEl.style.height = tapTargetTextHeight + "px";
6390
- this.contentEl.style.top = tapTargetTextTop + "px";
6391
- this.contentEl.style.right = tapTargetTextRight + "px";
6392
- this.contentEl.style.bottom = tapTargetTextBottom + "px";
6393
- this.contentEl.style.left = tapTargetTextLeft + "px";
6394
- this.contentEl.style.padding = tapTargetTextPadding + "px";
6395
- this.contentEl.style.verticalAlign = tapTargetTextAlign;
6396
- this.waveEl.style.top = tapTargetWaveTop + "px";
6397
- this.waveEl.style.left = tapTargetWaveLeft + "px";
6398
- this.waveEl.style.width = tapTargetWaveWidth + "px";
6399
- 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}`;
6400
6362
  }
6401
6363
  };
6402
6364
  _TapTarget._taptargets = [];
@@ -7140,7 +7102,7 @@ var Expressive = (() => {
7140
7102
  close.className = "circle";
7141
7103
  close.setAttribute("aria-label", "Dismiss");
7142
7104
  const icon = document.createElement("i");
7143
- icon.className = "material-icons";
7105
+ icon.className = "material-symbols";
7144
7106
  icon.setAttribute("aria-hidden", "true");
7145
7107
  icon.textContent = "close";
7146
7108
  close.appendChild(icon);
@@ -7505,7 +7467,7 @@ var Expressive = (() => {
7505
7467
  }
7506
7468
 
7507
7469
  // src/ts/index.ts
7508
- var version = "0.2.0";
7470
+ var version = "0.3.0";
7509
7471
  if (typeof document !== "undefined") {
7510
7472
  document.addEventListener("keydown", Utils.docHandleKeydown, true);
7511
7473
  document.addEventListener("keyup", Utils.docHandleKeyup, true);