@canopy-iiif/app 1.11.1 → 1.11.2

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.
@@ -1,9 +1,4 @@
1
- import Swiper from 'swiper';
2
- import { Navigation, Pagination, Autoplay, EffectFade } from 'swiper/modules';
3
- import 'swiper/css';
4
- import 'swiper/css/navigation';
5
- import 'swiper/css/pagination';
6
- import 'swiper/css/effect-fade';
1
+ import EmblaCarousel from 'embla-carousel';
7
2
 
8
3
  function ready(fn) {
9
4
  if (typeof document === 'undefined') return;
@@ -16,61 +11,85 @@ function ready(fn) {
16
11
 
17
12
  function initSlider(host) {
18
13
  if (!host || host.__canopyHeroBound) return;
19
- const slider = host.querySelector('.canopy-interstitial__slider');
20
- if (!slider) return;
21
- const prev = host.querySelector('.canopy-interstitial__nav-btn--prev');
22
- const next = host.querySelector('.canopy-interstitial__nav-btn--next');
23
- const pagination = host.querySelector('.canopy-interstitial__pagination');
24
- const transitionAttr = (host.getAttribute && host.getAttribute('data-transition')) || 'fade';
25
- const transition = transitionAttr && transitionAttr.toLowerCase() === 'slide' ? 'slide' : 'fade';
14
+ const viewport = host.querySelector('.canopy-interstitial__slider');
15
+ if (!viewport) return;
26
16
 
27
- try {
28
- const baseModules = [Navigation, Pagination, Autoplay];
29
- if (transition === 'fade') baseModules.push(EffectFade);
30
- const swiperInstance = new Swiper(slider, {
31
- modules: baseModules,
32
- loop: true,
33
- slidesPerView: 1,
34
- effect: transition,
35
- fadeEffect: transition === 'fade' ? { crossFade: true } : undefined,
36
- navigation: {
37
- prevEl: prev || undefined,
38
- nextEl: next || undefined,
39
- },
40
- pagination: {
41
- el: pagination || undefined,
42
- clickable: true,
43
- },
44
- autoplay: {
45
- delay: 6000,
46
- disableOnInteraction: false,
47
- },
48
- });
17
+ const slides = Array.from(viewport.querySelectorAll('.canopy-interstitial__slide'));
18
+ if (slides.length <= 1) {
49
19
  host.__canopyHeroBound = true;
50
- host.__canopyHeroSwiper = swiperInstance;
51
- } catch (error) {
52
- try {
53
- console.warn('[canopy][hero] failed to initialise slider', error);
54
- } catch (_) {}
20
+ return;
55
21
  }
22
+
23
+ const paginationEl = host.querySelector('.canopy-interstitial__pagination');
24
+ const prevBtn = host.querySelector('.canopy-interstitial__nav-btn--prev');
25
+ const nextBtn = host.querySelector('.canopy-interstitial__nav-btn--next');
26
+
27
+ // Live region for screen-reader slide announcements
28
+ const liveEl = document.createElement('div');
29
+ liveEl.setAttribute('aria-live', 'polite');
30
+ liveEl.setAttribute('aria-atomic', 'true');
31
+ liveEl.className = 'canopy-interstitial__sr-live';
32
+ host.appendChild(liveEl);
33
+
34
+ const embla = EmblaCarousel(viewport, { loop: true, duration: 0 });
35
+
36
+ const announce = (idx) => {
37
+ liveEl.textContent = `Slide ${idx + 1} of ${slides.length}`;
38
+ };
39
+
40
+ if (paginationEl) {
41
+ slides.forEach((_, i) => {
42
+ const dot = document.createElement('button');
43
+ dot.type = 'button';
44
+ dot.className =
45
+ 'canopy-interstitial__dot' +
46
+ (i === 0 ? ' canopy-interstitial__dot--active' : '');
47
+ dot.setAttribute('aria-label', `Go to slide ${i + 1}`);
48
+ dot.setAttribute('aria-current', i === 0 ? 'true' : 'false');
49
+ dot.addEventListener('click', () => embla.scrollTo(i));
50
+ paginationEl.appendChild(dot);
51
+ });
52
+
53
+ embla.on('select', () => {
54
+ const idx = embla.selectedScrollSnap();
55
+ announce(idx);
56
+ paginationEl.querySelectorAll('.canopy-interstitial__dot').forEach((dot, i) => {
57
+ const active = i === idx;
58
+ dot.classList.toggle('canopy-interstitial__dot--active', active);
59
+ dot.setAttribute('aria-current', active ? 'true' : 'false');
60
+ });
61
+ });
62
+ }
63
+
64
+ if (prevBtn) prevBtn.addEventListener('click', () => embla.scrollPrev());
65
+ if (nextBtn) nextBtn.addEventListener('click', () => embla.scrollNext());
66
+
67
+ let timer = setInterval(() => embla.scrollNext(), 6000);
68
+ const stopAutoplay = () => { clearInterval(timer); timer = null; };
69
+ const startAutoplay = () => { if (!timer) timer = setInterval(() => embla.scrollNext(), 6000); };
70
+
71
+ embla.on('pointerDown', stopAutoplay);
72
+ embla.on('pointerUp', startAutoplay);
73
+
74
+ host.__canopyHeroBound = true;
56
75
  }
57
76
 
58
77
  function observeHosts() {
59
78
  try {
60
- const observer = new MutationObserver((mutations) => {
79
+ new MutationObserver((mutations) => {
61
80
  mutations.forEach((mutation) => {
62
81
  mutation.addedNodes &&
63
82
  mutation.addedNodes.forEach((node) => {
64
83
  if (!(node instanceof Element)) return;
65
- if (node.matches && node.matches('[data-canopy-hero-slider]')) initSlider(node);
84
+ if (node.matches && node.matches('[data-canopy-hero-slider]'))
85
+ initSlider(node);
66
86
  const inner = node.querySelectorAll
67
87
  ? node.querySelectorAll('[data-canopy-hero-slider]')
68
88
  : [];
69
89
  inner && inner.forEach && inner.forEach((el) => initSlider(el));
70
90
  });
71
91
  });
72
- });
73
- observer.observe(document.documentElement || document.body, {
92
+ }).observe(document.documentElement || document.body, {
74
93
  childList: true,
75
94
  subtree: true,
76
95
  });
@@ -79,7 +98,6 @@ function observeHosts() {
79
98
 
80
99
  ready(() => {
81
100
  if (typeof document === 'undefined') return;
82
- const hosts = document.querySelectorAll('[data-canopy-hero-slider]');
83
- hosts.forEach((host) => initSlider(host));
101
+ document.querySelectorAll('[data-canopy-hero-slider]').forEach((host) => initSlider(host));
84
102
  observeHosts();
85
103
  });
@@ -1,8 +1,5 @@
1
1
  import React from 'react';
2
2
  import { createRoot } from 'react-dom/client';
3
- import 'swiper/css';
4
- import 'swiper/css/navigation';
5
- import 'swiper/css/pagination';
6
3
  import {
7
4
  mergeSliderOptions,
8
5
  normalizeSliderOptions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canopy-iiif/app",
3
- "version": "1.11.1",
3
+ "version": "1.11.2",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Mat Jordan <mat@northwestern.edu>",
@@ -1182,7 +1182,6 @@ function Hero({
1182
1182
  item,
1183
1183
  index,
1184
1184
  random = true,
1185
- transition = "fade",
1186
1185
  headline,
1187
1186
  description,
1188
1187
  links = [],
@@ -1308,15 +1307,6 @@ function Hero({
1308
1307
  backgroundClassName,
1309
1308
  className
1310
1309
  ].filter(Boolean).join(" ");
1311
- const normalizedTransition = (() => {
1312
- try {
1313
- const raw = transition == null ? "" : String(transition);
1314
- const normalized = raw.trim().toLowerCase();
1315
- return normalized === "slide" ? "slide" : "fade";
1316
- } catch (_) {
1317
- return "fade";
1318
- }
1319
- })();
1320
1310
  const renderSlide = (slide, idx, { showVeil = true, captionVariant = "overlay" } = {}) => {
1321
1311
  const safeHref = applyBasePath(slide.href || "#");
1322
1312
  const isStaticCaption = captionVariant === "static";
@@ -1351,38 +1341,68 @@ function Hero({
1351
1341
  );
1352
1342
  };
1353
1343
  if (isStaticCaption) {
1354
- return /* @__PURE__ */ React12.createElement("div", { className: "swiper-slide", key: safeHref || idx }, wrapWithLink(
1355
- /* @__PURE__ */ React12.createElement("article", { className: paneClassName }, slide.thumbnail ? /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__media-frame" }, /* @__PURE__ */ React12.createElement(
1356
- "img",
1357
- {
1358
- ...buildImageProps(
1359
- "canopy-interstitial__media canopy-interstitial__media--static"
1360
- )
1361
- }
1362
- )) : null, slide.title ? /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__caption canopy-interstitial__caption--static" }, /* @__PURE__ */ React12.createElement("span", { className: "canopy-interstitial__caption-link" }, slide.title)) : null)
1363
- ));
1344
+ return /* @__PURE__ */ React12.createElement(
1345
+ "div",
1346
+ {
1347
+ className: "canopy-interstitial__slide",
1348
+ key: safeHref || idx,
1349
+ role: "group",
1350
+ "aria-roledescription": "slide",
1351
+ "aria-label": `${idx + 1} of ${orderedSlides.length}`
1352
+ },
1353
+ wrapWithLink(
1354
+ /* @__PURE__ */ React12.createElement("article", { className: paneClassName }, slide.thumbnail ? /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__media-frame" }, /* @__PURE__ */ React12.createElement(
1355
+ "img",
1356
+ {
1357
+ ...buildImageProps(
1358
+ "canopy-interstitial__media canopy-interstitial__media--static"
1359
+ )
1360
+ }
1361
+ )) : null, slide.title ? /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__caption canopy-interstitial__caption--static" }, /* @__PURE__ */ React12.createElement("span", { className: "canopy-interstitial__caption-link" }, slide.title)) : null)
1362
+ )
1363
+ );
1364
1364
  }
1365
- return /* @__PURE__ */ React12.createElement("div", { className: "swiper-slide", key: safeHref || idx }, wrapWithLink(
1366
- /* @__PURE__ */ React12.createElement("article", { className: paneClassName }, slide.thumbnail ? /* @__PURE__ */ React12.createElement("img", { ...buildImageProps("canopy-interstitial__media") }) : null, showVeil ? /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__veil", "aria-hidden": "true" }) : null, slide.title ? /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__caption" }, /* @__PURE__ */ React12.createElement("span", { className: "canopy-interstitial__caption-link" }, slide.title)) : null)
1367
- ));
1365
+ return /* @__PURE__ */ React12.createElement(
1366
+ "div",
1367
+ {
1368
+ className: "canopy-interstitial__slide",
1369
+ key: safeHref || idx,
1370
+ role: "group",
1371
+ "aria-roledescription": "slide",
1372
+ "aria-label": `${idx + 1} of ${orderedSlides.length}`
1373
+ },
1374
+ wrapWithLink(
1375
+ /* @__PURE__ */ React12.createElement("article", { className: paneClassName }, slide.thumbnail ? /* @__PURE__ */ React12.createElement("img", { ...buildImageProps("canopy-interstitial__media") }) : null, showVeil ? /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__veil", "aria-hidden": "true" }) : null, slide.title ? /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__caption" }, /* @__PURE__ */ React12.createElement("span", { className: "canopy-interstitial__caption-link" }, slide.title)) : null)
1376
+ )
1377
+ );
1368
1378
  };
1369
- const renderSlider = (options = {}) => /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__slider swiper" }, /* @__PURE__ */ React12.createElement("div", { className: "swiper-wrapper" }, orderedSlides.map((slide, idx) => renderSlide(slide, idx, options))), /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__nav" }, /* @__PURE__ */ React12.createElement(
1370
- "button",
1371
- {
1372
- type: "button",
1373
- "aria-label": "Previous slide",
1374
- className: "canopy-interstitial__nav-btn canopy-interstitial__nav-btn--prev swiper-button-prev"
1375
- },
1376
- /* @__PURE__ */ React12.createElement(PrevArrowIcon, null)
1377
- ), /* @__PURE__ */ React12.createElement(
1378
- "button",
1379
+ const renderSlider = (options = {}) => /* @__PURE__ */ React12.createElement(
1380
+ "div",
1379
1381
  {
1380
- type: "button",
1381
- "aria-label": "Next slide",
1382
- className: "canopy-interstitial__nav-btn canopy-interstitial__nav-btn--next swiper-button-next"
1382
+ className: "canopy-interstitial__slider",
1383
+ role: "region",
1384
+ "aria-roledescription": "carousel",
1385
+ "aria-label": overlayTitle || "Featured content"
1383
1386
  },
1384
- /* @__PURE__ */ React12.createElement(NextArrowIcon, null)
1385
- )), /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__pagination swiper-pagination" }));
1387
+ /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__slide-wrapper" }, orderedSlides.map((slide, idx) => renderSlide(slide, idx, options))),
1388
+ /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__nav" }, /* @__PURE__ */ React12.createElement(
1389
+ "button",
1390
+ {
1391
+ type: "button",
1392
+ "aria-label": "Previous slide",
1393
+ className: "canopy-interstitial__nav-btn canopy-interstitial__nav-btn--prev"
1394
+ },
1395
+ /* @__PURE__ */ React12.createElement(PrevArrowIcon, null)
1396
+ ), /* @__PURE__ */ React12.createElement(
1397
+ "button",
1398
+ {
1399
+ type: "button",
1400
+ "aria-label": "Next slide",
1401
+ className: "canopy-interstitial__nav-btn canopy-interstitial__nav-btn--next"
1402
+ },
1403
+ /* @__PURE__ */ React12.createElement(NextArrowIcon, null)
1404
+ ))
1405
+ );
1386
1406
  const overlayContent = /* @__PURE__ */ React12.createElement(React12.Fragment, null, overlayTitle ? /* @__PURE__ */ React12.createElement("h1", { className: "canopy-interstitial__headline" }, overlayTitle) : null, derivedDescription ? /* @__PURE__ */ React12.createElement("p", { className: "canopy-interstitial__description" }, derivedDescription) : null, finalOverlayLinks.length ? /* @__PURE__ */ React12.createElement(ButtonWrapper, { className: "canopy-interstitial__actions" }, finalOverlayLinks.map((link) => /* @__PURE__ */ React12.createElement(
1387
1407
  Button,
1388
1408
  {
@@ -1401,11 +1421,10 @@ function Hero({
1401
1421
  };
1402
1422
  if (!isBreadcrumbVariant) {
1403
1423
  sectionProps["data-canopy-hero-slider"] = "1";
1404
- sectionProps["data-transition"] = normalizedTransition;
1405
1424
  } else {
1406
1425
  sectionProps["data-canopy-hero-variant"] = "breadcrumb";
1407
1426
  }
1408
- return /* @__PURE__ */ React12.createElement("section", { ...sectionProps }, isBreadcrumbVariant ? /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__layout canopy-interstitial__layout--breadcrumb" }, /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__panel" }, /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__body" }, breadcrumbNode, overlayContent))) : /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__layout" }, /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__panel" }, /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__body" }, overlayContent)), /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__media-group" }, renderSlider({ showVeil: false, captionVariant: "static" }))));
1427
+ return /* @__PURE__ */ React12.createElement("section", { ...sectionProps }, isBreadcrumbVariant ? /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__layout canopy-interstitial__layout--breadcrumb" }, /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__panel" }, /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__body" }, breadcrumbNode, overlayContent))) : /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__layout" }, /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__panel" }, /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__body" }, overlayContent)), /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__media-group" }, renderSlider({ showVeil: false, captionVariant: "static" }), /* @__PURE__ */ React12.createElement("div", { className: "canopy-interstitial__pagination" }))));
1409
1428
  }
1410
1429
 
1411
1430
  // ui/src/layout/SubNavigation.jsx