@canopy-iiif/app 0.10.31 → 0.11.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.
@@ -80,7 +80,120 @@ var Viewer = (props) => {
80
80
 
81
81
  // ui/src/iiif/Slider.jsx
82
82
  import React3, { useEffect as useEffect2, useState as useState2 } from "react";
83
- var Slider = (props) => {
83
+
84
+ // ui/src/iiif/sliderOptions.js
85
+ var UNIT_TOKEN = "__canopySliderUnit";
86
+ var UNIT_REM = "rem";
87
+ var DEFAULT_FONT_SIZE = 16;
88
+ var cachedRootFontSize = null;
89
+ var sliderOptions = {
90
+ breakpoints: {
91
+ 400: {
92
+ slidesPerView: 2,
93
+ spaceBetween: rem(1)
94
+ },
95
+ 640: {
96
+ slidesPerView: 3,
97
+ spaceBetween: rem(1.618)
98
+ },
99
+ 1024: {
100
+ slidesPerView: 4,
101
+ spaceBetween: rem(1.618)
102
+ }
103
+ }
104
+ };
105
+ function rem(value) {
106
+ const numeric = typeof value === "number" ? value : parseFloat(value);
107
+ return {
108
+ [UNIT_TOKEN]: UNIT_REM,
109
+ value: Number.isFinite(numeric) ? numeric : 0
110
+ };
111
+ }
112
+ function cloneBreakpoints(source) {
113
+ if (!source || typeof source !== "object") return void 0;
114
+ const clone = {};
115
+ Object.entries(source).forEach(([key, entry]) => {
116
+ clone[key] = entry && typeof entry === "object" ? { ...entry } : {};
117
+ });
118
+ return clone;
119
+ }
120
+ function cloneOptions(options = {}) {
121
+ const clone = { ...options };
122
+ if (options.breakpoints && typeof options.breakpoints === "object") {
123
+ clone.breakpoints = cloneBreakpoints(options.breakpoints);
124
+ }
125
+ return clone;
126
+ }
127
+ function mergeSliderOptions(overrides) {
128
+ const base = cloneOptions(sliderOptions);
129
+ const incoming = cloneOptions(overrides || {});
130
+ const merged = {
131
+ ...base,
132
+ ...incoming
133
+ };
134
+ if (base.breakpoints || incoming.breakpoints) {
135
+ merged.breakpoints = {
136
+ ...base.breakpoints || {},
137
+ ...incoming.breakpoints || {}
138
+ };
139
+ }
140
+ return merged;
141
+ }
142
+ function hasBrowserEnv() {
143
+ return typeof window !== "undefined" && typeof document !== "undefined";
144
+ }
145
+ function measureRootFontSize() {
146
+ if (!hasBrowserEnv()) return DEFAULT_FONT_SIZE;
147
+ if (cachedRootFontSize !== null) return cachedRootFontSize;
148
+ let size = DEFAULT_FONT_SIZE;
149
+ try {
150
+ const root = window.document && window.document.documentElement;
151
+ if (root && window.getComputedStyle) {
152
+ const computed = window.getComputedStyle(root).fontSize;
153
+ const parsed = parseFloat(computed);
154
+ if (Number.isFinite(parsed)) size = parsed;
155
+ }
156
+ } catch (_) {
157
+ size = DEFAULT_FONT_SIZE;
158
+ }
159
+ cachedRootFontSize = size;
160
+ return size;
161
+ }
162
+ function convertSpacing(value) {
163
+ if (!hasBrowserEnv()) return value;
164
+ if (value && typeof value === "object" && value[UNIT_TOKEN] === UNIT_REM) {
165
+ const remValue = typeof value.value === "number" ? value.value : parseFloat(value.value);
166
+ if (!Number.isFinite(remValue)) return value;
167
+ return remValue * measureRootFontSize();
168
+ }
169
+ return value;
170
+ }
171
+ function normalizeBreakpoints(breakpoints) {
172
+ if (!breakpoints || typeof breakpoints !== "object") return breakpoints;
173
+ const normalized = {};
174
+ Object.entries(breakpoints).forEach(([key, entry]) => {
175
+ const clone = entry && typeof entry === "object" ? { ...entry } : {};
176
+ if (Object.prototype.hasOwnProperty.call(clone, "spaceBetween")) {
177
+ clone.spaceBetween = convertSpacing(clone.spaceBetween);
178
+ }
179
+ normalized[key] = clone;
180
+ });
181
+ return normalized;
182
+ }
183
+ function normalizeSliderOptions(options) {
184
+ const clone = cloneOptions(options || {});
185
+ if (!hasBrowserEnv()) return clone;
186
+ if (Object.prototype.hasOwnProperty.call(clone, "spaceBetween")) {
187
+ clone.spaceBetween = convertSpacing(clone.spaceBetween);
188
+ }
189
+ if (clone.breakpoints) {
190
+ clone.breakpoints = normalizeBreakpoints(clone.breakpoints);
191
+ }
192
+ return clone;
193
+ }
194
+
195
+ // ui/src/iiif/Slider.jsx
196
+ var Slider = (props = {}) => {
84
197
  const [CloverSlider, setCloverSlider] = useState2(null);
85
198
  useEffect2(() => {
86
199
  let mounted = true;
@@ -88,7 +201,6 @@ var Slider = (props) => {
88
201
  if (canUseDom) {
89
202
  import("@samvera/clover-iiif/slider").then((mod) => {
90
203
  if (!mounted) return;
91
- console.log(mod);
92
204
  const Comp = mod && (mod.default || mod.Slider || mod);
93
205
  setCloverSlider(() => Comp);
94
206
  }).catch(() => {
@@ -98,14 +210,23 @@ var Slider = (props) => {
98
210
  mounted = false;
99
211
  };
100
212
  }, []);
213
+ const { className, ...rest } = props || {};
214
+ const sliderClassName = ["canopy-slider", className].filter(Boolean).join(" ");
215
+ const mergedOptions = mergeSliderOptions(rest.options);
216
+ const normalizedOptions = normalizeSliderOptions(mergedOptions);
217
+ const resolvedProps = {
218
+ ...rest,
219
+ className: sliderClassName,
220
+ options: normalizedOptions
221
+ };
101
222
  if (!CloverSlider) {
102
223
  let json = "{}";
103
224
  try {
104
- json = JSON.stringify(props || {});
225
+ json = JSON.stringify(resolvedProps || {});
105
226
  } catch (_) {
106
227
  json = "{}";
107
228
  }
108
- return /* @__PURE__ */ React3.createElement("div", { className: "canopy-slider", "data-canopy-slider": "1" }, /* @__PURE__ */ React3.createElement(
229
+ return /* @__PURE__ */ React3.createElement("div", { className: sliderClassName, "data-canopy-slider": "1" }, /* @__PURE__ */ React3.createElement(
109
230
  "script",
110
231
  {
111
232
  type: "application/json",
@@ -113,7 +234,7 @@ var Slider = (props) => {
113
234
  }
114
235
  ));
115
236
  }
116
- return /* @__PURE__ */ React3.createElement(CloverSlider, { ...props, className: "canopy-slider" });
237
+ return /* @__PURE__ */ React3.createElement(CloverSlider, { ...resolvedProps });
117
238
  };
118
239
 
119
240
  // ui/src/iiif/Scroll.jsx
@@ -295,6 +416,40 @@ function ButtonWrapper({
295
416
  }
296
417
 
297
418
  // ui/src/interstitials/Hero.jsx
419
+ var NavIconBase = ({ children, ...rest }) => /* @__PURE__ */ React9.createElement(
420
+ "svg",
421
+ {
422
+ width: "16",
423
+ height: "16",
424
+ viewBox: "0 0 16 16",
425
+ fill: "none",
426
+ xmlns: "http://www.w3.org/2000/svg",
427
+ "aria-hidden": "true",
428
+ focusable: "false",
429
+ ...rest
430
+ },
431
+ children
432
+ );
433
+ var PrevArrowIcon = (props) => /* @__PURE__ */ React9.createElement(NavIconBase, { ...props }, /* @__PURE__ */ React9.createElement(
434
+ "path",
435
+ {
436
+ d: "M10.5 3L5.5 8L10.5 13",
437
+ stroke: "currentColor",
438
+ strokeWidth: "1.618",
439
+ strokeLinecap: "round",
440
+ strokeLinejoin: "round"
441
+ }
442
+ ));
443
+ var NextArrowIcon = (props) => /* @__PURE__ */ React9.createElement(NavIconBase, { ...props }, /* @__PURE__ */ React9.createElement(
444
+ "path",
445
+ {
446
+ d: "M5.5 3L10.5 8L5.5 13",
447
+ stroke: "currentColor",
448
+ strokeWidth: "1.618",
449
+ strokeLinecap: "round",
450
+ strokeLinejoin: "round"
451
+ }
452
+ ));
298
453
  var HERO_DEFAULT_SIZES_ATTR = "(min-width: 1024px) 1280px, 100vw";
299
454
  var basePath = (() => {
300
455
  try {
@@ -365,6 +520,15 @@ function normalizeBackground(value) {
365
520
  return "theme";
366
521
  }
367
522
  }
523
+ function normalizeVariant(value) {
524
+ try {
525
+ const raw = value == null ? "" : String(value);
526
+ const normalized = raw.trim().toLowerCase();
527
+ return normalized === "text" ? "text" : "featured";
528
+ } catch (_) {
529
+ return "featured";
530
+ }
531
+ }
368
532
  function Hero({
369
533
  height = 520,
370
534
  item,
@@ -377,47 +541,56 @@ function Hero({
377
541
  className = "",
378
542
  style = {},
379
543
  background = "theme",
544
+ variant = "featured",
380
545
  ...rest
381
546
  }) {
382
- const resolved = resolveFeaturedItem({ item, index, random });
383
- const helpersList = helpers && helpers.readFeaturedFromCacheSync ? helpers.readFeaturedFromCacheSync() : [];
384
- const slides = [];
385
- const pushUnique = (entry) => {
386
- if (!entry) return;
387
- const key = String(entry.href || entry.id || entry.title || "");
388
- const hasKey = slides.some(
389
- (item2) => String(item2 && (item2.href || item2.id || item2.title || "")) === key
390
- );
391
- if (!hasKey) {
392
- slides.push(entry);
393
- }
394
- };
395
- if (resolved) pushUnique(resolved);
396
- helpersList.forEach(pushUnique);
397
- if (!slides.length) return null;
398
- let orderedSlides = slides.slice();
399
- if (typeof index === "number" && orderedSlides.length > 1) {
400
- const clamp = Math.max(
401
- 0,
402
- Math.min(orderedSlides.length - 1, Math.floor(index))
403
- );
404
- if (clamp > 0) {
405
- orderedSlides = orderedSlides.slice(clamp).concat(orderedSlides.slice(0, clamp));
406
- }
407
- } else if (random === true || random === "true") {
408
- const rand = Math.floor(Math.random() * orderedSlides.length);
409
- if (rand > 0) {
410
- orderedSlides = orderedSlides.slice(rand).concat(orderedSlides.slice(0, rand));
547
+ const normalizedVariant = normalizeVariant(variant);
548
+ const isTextVariant = normalizedVariant === "text";
549
+ let orderedSlides = [];
550
+ if (!isTextVariant) {
551
+ const resolved = resolveFeaturedItem({ item, index, random });
552
+ const helpersList = helpers && helpers.readFeaturedFromCacheSync ? helpers.readFeaturedFromCacheSync() : [];
553
+ const slides = [];
554
+ const pushUnique = (entry) => {
555
+ if (!entry) return;
556
+ const key = String(entry.href || entry.id || entry.title || "");
557
+ const hasKey = slides.some(
558
+ (item2) => String(item2 && (item2.href || item2.id || item2.title || "")) === key
559
+ );
560
+ if (!hasKey) {
561
+ slides.push(entry);
562
+ }
563
+ };
564
+ if (resolved) pushUnique(resolved);
565
+ helpersList.forEach(pushUnique);
566
+ if (!slides.length) return null;
567
+ orderedSlides = slides.slice();
568
+ if (typeof index === "number" && orderedSlides.length > 1) {
569
+ const clamp = Math.max(
570
+ 0,
571
+ Math.min(orderedSlides.length - 1, Math.floor(index))
572
+ );
573
+ if (clamp > 0) {
574
+ orderedSlides = orderedSlides.slice(clamp).concat(orderedSlides.slice(0, clamp));
575
+ }
576
+ } else if (random === true || random === "true") {
577
+ const rand = Math.floor(Math.random() * orderedSlides.length);
578
+ if (rand > 0) {
579
+ orderedSlides = orderedSlides.slice(rand).concat(orderedSlides.slice(0, rand));
580
+ }
411
581
  }
412
582
  }
413
583
  const heroHeight = computeHeroHeightStyle(height);
414
584
  const heroStyles = { ...style || {} };
415
- if (heroHeight && heroHeight.height) {
585
+ if (heroHeight && heroHeight.height && !isTextVariant) {
416
586
  heroStyles["--hero-height"] = heroHeight.height;
417
587
  }
588
+ if (isTextVariant) {
589
+ heroStyles["--hero-height"] = "auto";
590
+ }
418
591
  const derivedDescription = description ? String(description) : "";
419
592
  const normalizedLinks = normalizeLinks(links);
420
- const primarySlide = orderedSlides[0] || null;
593
+ const primarySlide = !isTextVariant ? orderedSlides[0] || null : null;
421
594
  const overlayTitle = headline || primarySlide && primarySlide.title || "";
422
595
  const defaultLinkHref = applyBasePath(
423
596
  primarySlide && primarySlide.href ? primarySlide.href : "#"
@@ -428,12 +601,15 @@ function Hero({
428
601
  title: "View work",
429
602
  type: "primary"
430
603
  }
431
- ];
604
+ ].filter(Boolean);
605
+ const finalOverlayLinks = isTextVariant ? normalizedLinks : overlayLinks;
432
606
  const normalizedBackground = normalizeBackground(background);
433
607
  const backgroundClassName = normalizedBackground === "transparent" ? "canopy-interstitial--bg-transparent" : "";
608
+ const variantClassName = isTextVariant ? "canopy-interstitial--hero-text" : "canopy-interstitial--hero-featured";
434
609
  const containerClassName = [
435
610
  "canopy-interstitial",
436
611
  "canopy-interstitial--hero",
612
+ variantClassName,
437
613
  backgroundClassName,
438
614
  className
439
615
  ].filter(Boolean).join(" ");
@@ -464,26 +640,36 @@ function Hero({
464
640
  loading: idx === 0 ? "eager" : "lazy"
465
641
  };
466
642
  if (slide.srcset) props.srcSet = slide.srcset;
467
- if (slide.srcset)
468
- props.sizes = slide.sizes || HERO_DEFAULT_SIZES_ATTR;
643
+ if (slide.srcset) props.sizes = slide.sizes || HERO_DEFAULT_SIZES_ATTR;
469
644
  return props;
470
645
  };
471
- if (isStaticCaption) {
472
- return /* @__PURE__ */ React9.createElement("div", { className: "swiper-slide", key: safeHref || idx }, /* @__PURE__ */ React9.createElement("article", { className: paneClassName }, slide.thumbnail ? /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__media-frame" }, /* @__PURE__ */ React9.createElement(
473
- "img",
646
+ const wrapWithLink = (node) => {
647
+ if (!safeHref) return node;
648
+ return /* @__PURE__ */ React9.createElement(
649
+ "a",
474
650
  {
475
- ...buildImageProps(
476
- "canopy-interstitial__media canopy-interstitial__media--static"
477
- )
478
- }
479
- )) : null, slide.title ? /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__caption canopy-interstitial__caption--static" }, /* @__PURE__ */ React9.createElement("a", { href: safeHref, className: "canopy-interstitial__caption-link" }, slide.title)) : null));
651
+ href: safeHref,
652
+ className: "canopy-interstitial__slide-link",
653
+ "aria-label": slide.title || void 0
654
+ },
655
+ node
656
+ );
657
+ };
658
+ if (isStaticCaption) {
659
+ return /* @__PURE__ */ React9.createElement("div", { className: "swiper-slide", key: safeHref || idx }, wrapWithLink(
660
+ /* @__PURE__ */ React9.createElement("article", { className: paneClassName }, slide.thumbnail ? /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__media-frame" }, /* @__PURE__ */ React9.createElement(
661
+ "img",
662
+ {
663
+ ...buildImageProps(
664
+ "canopy-interstitial__media canopy-interstitial__media--static"
665
+ )
666
+ }
667
+ )) : null, slide.title ? /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__caption canopy-interstitial__caption--static" }, /* @__PURE__ */ React9.createElement("span", { className: "canopy-interstitial__caption-link" }, slide.title)) : null)
668
+ ));
480
669
  }
481
- return /* @__PURE__ */ React9.createElement("div", { className: "swiper-slide", key: safeHref || idx }, /* @__PURE__ */ React9.createElement("article", { className: paneClassName }, slide.thumbnail ? /* @__PURE__ */ React9.createElement(
482
- "img",
483
- {
484
- ...buildImageProps("canopy-interstitial__media")
485
- }
486
- ) : null, showVeil ? /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__veil", "aria-hidden": "true" }) : null, slide.title ? /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__caption" }, /* @__PURE__ */ React9.createElement("a", { href: safeHref, className: "canopy-interstitial__caption-link" }, slide.title)) : null));
670
+ return /* @__PURE__ */ React9.createElement("div", { className: "swiper-slide", key: safeHref || idx }, wrapWithLink(
671
+ /* @__PURE__ */ React9.createElement("article", { className: paneClassName }, slide.thumbnail ? /* @__PURE__ */ React9.createElement("img", { ...buildImageProps("canopy-interstitial__media") }) : null, showVeil ? /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__veil", "aria-hidden": "true" }) : null, slide.title ? /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__caption" }, /* @__PURE__ */ React9.createElement("span", { className: "canopy-interstitial__caption-link" }, slide.title)) : null)
672
+ ));
487
673
  };
488
674
  const renderSlider = (options = {}) => /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__slider swiper" }, /* @__PURE__ */ React9.createElement("div", { className: "swiper-wrapper" }, orderedSlides.map((slide, idx) => renderSlide(slide, idx, options))), /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__nav" }, /* @__PURE__ */ React9.createElement(
489
675
  "button",
@@ -491,16 +677,18 @@ function Hero({
491
677
  type: "button",
492
678
  "aria-label": "Previous slide",
493
679
  className: "canopy-interstitial__nav-btn canopy-interstitial__nav-btn--prev swiper-button-prev"
494
- }
680
+ },
681
+ /* @__PURE__ */ React9.createElement(PrevArrowIcon, null)
495
682
  ), /* @__PURE__ */ React9.createElement(
496
683
  "button",
497
684
  {
498
685
  type: "button",
499
686
  "aria-label": "Next slide",
500
687
  className: "canopy-interstitial__nav-btn canopy-interstitial__nav-btn--next swiper-button-next"
501
- }
688
+ },
689
+ /* @__PURE__ */ React9.createElement(NextArrowIcon, null)
502
690
  )), /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__pagination swiper-pagination" }));
503
- const overlayContent = /* @__PURE__ */ React9.createElement(React9.Fragment, null, overlayTitle ? /* @__PURE__ */ React9.createElement("h1", { className: "canopy-interstitial__headline" }, overlayTitle) : null, derivedDescription ? /* @__PURE__ */ React9.createElement("p", { className: "canopy-interstitial__description" }, derivedDescription) : null, overlayLinks.length ? /* @__PURE__ */ React9.createElement(ButtonWrapper, { className: "canopy-interstitial__actions" }, overlayLinks.map((link) => /* @__PURE__ */ React9.createElement(
691
+ const overlayContent = /* @__PURE__ */ React9.createElement(React9.Fragment, null, overlayTitle ? /* @__PURE__ */ React9.createElement("h1", { className: "canopy-interstitial__headline" }, overlayTitle) : null, derivedDescription ? /* @__PURE__ */ React9.createElement("p", { className: "canopy-interstitial__description" }, derivedDescription) : null, finalOverlayLinks.length ? /* @__PURE__ */ React9.createElement(ButtonWrapper, { className: "canopy-interstitial__actions" }, finalOverlayLinks.map((link) => /* @__PURE__ */ React9.createElement(
504
692
  Button,
505
693
  {
506
694
  key: `${link.href}-${link.title}`,
@@ -511,17 +699,18 @@ function Hero({
511
699
  }
512
700
  ))) : null);
513
701
  const cleanedProps = sanitizeRest(rest);
514
- return /* @__PURE__ */ React9.createElement(
515
- "section",
516
- {
517
- className: containerClassName,
518
- "data-canopy-hero-slider": "1",
519
- "data-transition": normalizedTransition,
520
- style: heroStyles,
521
- ...cleanedProps
522
- },
523
- /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__layout" }, /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__panel" }, /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__body" }, overlayContent)), /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__media-group" }, renderSlider({ showVeil: false, captionVariant: "static" })))
524
- );
702
+ const sectionProps = {
703
+ className: containerClassName,
704
+ style: heroStyles,
705
+ ...cleanedProps
706
+ };
707
+ if (!isTextVariant) {
708
+ sectionProps["data-canopy-hero-slider"] = "1";
709
+ sectionProps["data-transition"] = normalizedTransition;
710
+ } else {
711
+ sectionProps["data-canopy-hero-variant"] = "text";
712
+ }
713
+ return /* @__PURE__ */ React9.createElement("section", { ...sectionProps }, isTextVariant ? /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__layout canopy-interstitial__layout--text" }, /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__panel" }, /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__body" }, overlayContent))) : /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__layout" }, /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__panel" }, /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__body" }, overlayContent)), /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__media-group" }, renderSlider({ showVeil: false, captionVariant: "static" }))));
525
714
  }
526
715
 
527
716
  // ui/src/layout/SubNavigation.jsx