@canopy-iiif/app 0.10.30 → 0.10.32

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 {
@@ -464,26 +619,36 @@ function Hero({
464
619
  loading: idx === 0 ? "eager" : "lazy"
465
620
  };
466
621
  if (slide.srcset) props.srcSet = slide.srcset;
467
- if (slide.srcset)
468
- props.sizes = slide.sizes || HERO_DEFAULT_SIZES_ATTR;
622
+ if (slide.srcset) props.sizes = slide.sizes || HERO_DEFAULT_SIZES_ATTR;
469
623
  return props;
470
624
  };
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",
625
+ const wrapWithLink = (node) => {
626
+ if (!safeHref) return node;
627
+ return /* @__PURE__ */ React9.createElement(
628
+ "a",
474
629
  {
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));
630
+ href: safeHref,
631
+ className: "canopy-interstitial__slide-link",
632
+ "aria-label": slide.title || void 0
633
+ },
634
+ node
635
+ );
636
+ };
637
+ if (isStaticCaption) {
638
+ return /* @__PURE__ */ React9.createElement("div", { className: "swiper-slide", key: safeHref || idx }, wrapWithLink(
639
+ /* @__PURE__ */ React9.createElement("article", { className: paneClassName }, slide.thumbnail ? /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__media-frame" }, /* @__PURE__ */ React9.createElement(
640
+ "img",
641
+ {
642
+ ...buildImageProps(
643
+ "canopy-interstitial__media canopy-interstitial__media--static"
644
+ )
645
+ }
646
+ )) : 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)
647
+ ));
480
648
  }
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));
649
+ return /* @__PURE__ */ React9.createElement("div", { className: "swiper-slide", key: safeHref || idx }, wrapWithLink(
650
+ /* @__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)
651
+ ));
487
652
  };
488
653
  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
654
  "button",
@@ -491,14 +656,16 @@ function Hero({
491
656
  type: "button",
492
657
  "aria-label": "Previous slide",
493
658
  className: "canopy-interstitial__nav-btn canopy-interstitial__nav-btn--prev swiper-button-prev"
494
- }
659
+ },
660
+ /* @__PURE__ */ React9.createElement(PrevArrowIcon, null)
495
661
  ), /* @__PURE__ */ React9.createElement(
496
662
  "button",
497
663
  {
498
664
  type: "button",
499
665
  "aria-label": "Next slide",
500
666
  className: "canopy-interstitial__nav-btn canopy-interstitial__nav-btn--next swiper-button-next"
501
- }
667
+ },
668
+ /* @__PURE__ */ React9.createElement(NextArrowIcon, null)
502
669
  )), /* @__PURE__ */ React9.createElement("div", { className: "canopy-interstitial__pagination swiper-pagination" }));
503
670
  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(
504
671
  Button,