@canopy-iiif/app 1.9.21 → 1.10.1

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,5 +1,9 @@
1
1
  import { mountImageStory } from '../../ui/dist/index.mjs';
2
2
 
3
+ const IMAGE_STORY_SELECTOR = '[data-canopy-image-story]';
4
+ const nodeStates = new WeakMap();
5
+ const SIZE_EPSILON = 1;
6
+
3
7
  function ready(fn) {
4
8
  if (typeof document === 'undefined') return;
5
9
  if (document.readyState === 'loading') {
@@ -20,19 +24,161 @@ function parseProps(node) {
20
24
  }
21
25
  }
22
26
 
27
+ function getNodeState(node) {
28
+ if (!node) return null;
29
+ let state = nodeStates.get(node);
30
+ if (!state) {
31
+ state = {
32
+ mounted: false,
33
+ cleanup: null,
34
+ resizeObserver: null,
35
+ pollId: null,
36
+ watching: false,
37
+ props: null,
38
+ lastSize: null,
39
+ };
40
+ nodeStates.set(node, state);
41
+ }
42
+ return state;
43
+ }
44
+
45
+ function disconnectWatchers(state) {
46
+ if (!state) return;
47
+ if (state.resizeObserver) {
48
+ try {
49
+ state.resizeObserver.disconnect();
50
+ } catch (_) {}
51
+ state.resizeObserver = null;
52
+ }
53
+ if (state.pollId && typeof window !== 'undefined') {
54
+ window.clearTimeout(state.pollId);
55
+ state.pollId = null;
56
+ }
57
+ state.watching = false;
58
+ }
59
+
60
+ function destroyNode(node, state) {
61
+ const currentState = state || getNodeState(node);
62
+ if (!currentState) return;
63
+ disconnectWatchers(currentState);
64
+ if (currentState.cleanup) {
65
+ try {
66
+ currentState.cleanup();
67
+ } catch (_) {}
68
+ currentState.cleanup = null;
69
+ }
70
+ currentState.mounted = false;
71
+ }
72
+
73
+ function measureSize(node) {
74
+ if (!node) return null;
75
+ const rect = node.getBoundingClientRect();
76
+ const width = rect?.width || node.offsetWidth || node.clientWidth || 0;
77
+ const height = rect?.height || node.offsetHeight || node.clientHeight || 0;
78
+ return { width, height };
79
+ }
80
+
81
+ function hasUsableSize(node, state) {
82
+ const size = measureSize(node);
83
+ if (!size) return false;
84
+ const usable = size.width > 2 && size.height > 2;
85
+ if (usable && state) {
86
+ state.lastSize = size;
87
+ }
88
+ return usable;
89
+ }
90
+
91
+ function needsSizeRefresh(node, state) {
92
+ if (!node || !state) return false;
93
+ const size = measureSize(node);
94
+ if (!size) return false;
95
+ if (size.width <= 2 || size.height <= 2) {
96
+ return true;
97
+ }
98
+ if (!state.lastSize) {
99
+ state.lastSize = size;
100
+ return true;
101
+ }
102
+ const widthDelta = Math.abs(size.width - state.lastSize.width);
103
+ const heightDelta = Math.abs(size.height - state.lastSize.height);
104
+ if (widthDelta > SIZE_EPSILON || heightDelta > SIZE_EPSILON) {
105
+ state.lastSize = size;
106
+ return true;
107
+ }
108
+ return false;
109
+ }
110
+
111
+ function attemptMount(node, state) {
112
+ if (!node || !state || state.mounted) return false;
113
+ if (!hasUsableSize(node, state)) return false;
114
+ state.mounted = true;
115
+ disconnectWatchers(state);
116
+ const props = state.props || parseProps(node);
117
+ Promise.resolve(mountImageStory(node, props)).then((destroy) => {
118
+ if (typeof destroy === 'function') {
119
+ state.cleanup = destroy;
120
+ } else {
121
+ state.cleanup = null;
122
+ }
123
+ });
124
+ return true;
125
+ }
126
+
127
+ function scheduleWatchers(node, state, tryMount) {
128
+ if (!node || !state || state.watching) return;
129
+ if (typeof window === 'undefined') return;
130
+ state.watching = true;
131
+ if (typeof window !== 'undefined' && typeof window.ResizeObserver === 'function') {
132
+ state.resizeObserver = new window.ResizeObserver(() => {
133
+ if (state.mounted) return;
134
+ tryMount();
135
+ });
136
+ try {
137
+ state.resizeObserver.observe(node);
138
+ } catch (_) {}
139
+ }
140
+ const schedulePoll = () => {
141
+ if (state.mounted) return;
142
+ state.pollId = window.setTimeout(() => {
143
+ state.pollId = null;
144
+ if (!tryMount()) {
145
+ schedulePoll();
146
+ }
147
+ }, 200);
148
+ };
149
+ schedulePoll();
150
+ }
151
+
152
+ function startMountProcess(node) {
153
+ const state = getNodeState(node);
154
+ if (!state) return;
155
+ state.props = parseProps(node);
156
+ const tryMount = () => attemptMount(node, state);
157
+ if (!tryMount()) {
158
+ scheduleWatchers(node, state, tryMount);
159
+ }
160
+ }
161
+
23
162
  function mount(node) {
24
- if (!node || node.__canopyImageStoryMounted) return;
25
- try {
26
- const props = parseProps(node);
27
- mountImageStory(node, props);
28
- node.__canopyImageStoryMounted = true;
29
- } catch (_) {}
163
+ if (!node) return;
164
+ const state = getNodeState(node);
165
+ if (!state || state.bound) return;
166
+ state.bound = true;
167
+ startMountProcess(node);
168
+ }
169
+
170
+ function remount(node) {
171
+ const state = getNodeState(node);
172
+ if (!state) return;
173
+ state.props = parseProps(node);
174
+ destroyNode(node, state);
175
+ startMountProcess(node);
30
176
  }
31
177
 
32
178
  function scan() {
33
179
  try {
34
180
  document
35
- .querySelectorAll('[data-canopy-image-story]')
181
+ .querySelectorAll(IMAGE_STORY_SELECTOR)
36
182
  .forEach((node) => mount(node));
37
183
  } catch (_) {}
38
184
  }
@@ -45,11 +191,11 @@ function observe() {
45
191
  mutation.addedNodes &&
46
192
  mutation.addedNodes.forEach((node) => {
47
193
  if (!(node instanceof Element)) return;
48
- if (node.matches && node.matches('[data-canopy-image-story]')) {
194
+ if (node.matches && node.matches(IMAGE_STORY_SELECTOR)) {
49
195
  toMount.push(node);
50
196
  }
51
197
  const inner = node.querySelectorAll
52
- ? node.querySelectorAll('[data-canopy-image-story]')
198
+ ? node.querySelectorAll(IMAGE_STORY_SELECTOR)
53
199
  : [];
54
200
  inner && inner.forEach && inner.forEach((el) => toMount.push(el));
55
201
  });
@@ -63,7 +209,41 @@ function observe() {
63
209
  } catch (_) {}
64
210
  }
65
211
 
212
+ function refreshModal(modal) {
213
+ if (!modal || typeof modal.querySelectorAll !== 'function') return;
214
+ const nodes = modal.querySelectorAll(IMAGE_STORY_SELECTOR);
215
+ if (!nodes || !nodes.length) return;
216
+ Array.prototype.forEach.call(nodes, (node) => {
217
+ const state = getNodeState(node);
218
+ if (!state || !state.mounted) return;
219
+ if (needsSizeRefresh(node, state)) {
220
+ remount(node);
221
+ }
222
+ });
223
+ }
224
+
225
+ function handleGalleryModalChange(event) {
226
+ if (!event || typeof document === 'undefined') return;
227
+ const detail = event.detail || {};
228
+ if (detail.state !== 'open') return;
229
+ let modal = detail.modal;
230
+ if (!modal && detail.modalId) {
231
+ modal = document.getElementById(detail.modalId);
232
+ }
233
+ if (modal) {
234
+ refreshModal(modal);
235
+ }
236
+ }
237
+
238
+ function bindGalleryListener() {
239
+ if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') {
240
+ return;
241
+ }
242
+ window.addEventListener('canopy:gallery:modal-change', handleGalleryModalChange);
243
+ }
244
+
66
245
  ready(function onReady() {
67
246
  scan();
68
247
  observe();
248
+ bindGalleryListener();
69
249
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canopy-iiif/app",
3
- "version": "1.9.21",
3
+ "version": "1.10.1",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Mat Jordan <mat@northwestern.edu>",
package/ui/dist/index.mjs CHANGED
@@ -45483,6 +45483,7 @@ async function mountImageStory(element, props = {}) {
45483
45483
  // ui/src/iiif/ImageStory.jsx
45484
45484
  var DEFAULT_IMAGE_STORY_HEIGHT = 600;
45485
45485
  var NUMERIC_HEIGHT_PATTERN = /^[+-]?(?:\d+|\d*\.\d+)$/;
45486
+ var SIZE_EPSILON = 1;
45486
45487
  function resolveContainerHeight(value) {
45487
45488
  if (typeof value === "number" && Number.isFinite(value)) {
45488
45489
  return `${value}px`;
@@ -45533,6 +45534,7 @@ var ImageStory = (props = {}) => {
45533
45534
  let mounted = false;
45534
45535
  let resizeObserver = null;
45535
45536
  let pollId = null;
45537
+ let lastKnownSize = null;
45536
45538
  const payload = sanitizeImageStoryProps({
45537
45539
  iiifContent,
45538
45540
  disablePanAndZoom,
@@ -45561,12 +45563,39 @@ var ImageStory = (props = {}) => {
45561
45563
  pollId = null;
45562
45564
  }
45563
45565
  };
45564
- const hasUsableSize = () => {
45565
- if (!node) return false;
45566
+ const measureSize = () => {
45567
+ if (!node) return null;
45566
45568
  const rect = node.getBoundingClientRect();
45567
- const width = (rect == null ? void 0 : rect.width) || node.offsetWidth || node.clientWidth;
45568
- const height2 = (rect == null ? void 0 : rect.height) || node.offsetHeight || node.clientHeight;
45569
- return width > 2 && height2 > 2;
45569
+ const width = (rect == null ? void 0 : rect.width) || node.offsetWidth || node.clientWidth || 0;
45570
+ const height2 = (rect == null ? void 0 : rect.height) || node.offsetHeight || node.clientHeight || 0;
45571
+ return { width, height: height2 };
45572
+ };
45573
+ const hasUsableSize = () => {
45574
+ const size = measureSize();
45575
+ if (!size) return false;
45576
+ const usable = size.width > 2 && size.height > 2;
45577
+ if (usable) {
45578
+ lastKnownSize = size;
45579
+ }
45580
+ return usable;
45581
+ };
45582
+ const hasMeaningfulSizeChange = () => {
45583
+ const size = measureSize();
45584
+ if (!size) return false;
45585
+ if (size.width <= 2 || size.height <= 2) {
45586
+ return true;
45587
+ }
45588
+ if (!lastKnownSize) {
45589
+ lastKnownSize = size;
45590
+ return true;
45591
+ }
45592
+ const widthDelta = Math.abs(size.width - lastKnownSize.width);
45593
+ const heightDelta = Math.abs(size.height - lastKnownSize.height);
45594
+ if (widthDelta > SIZE_EPSILON || heightDelta > SIZE_EPSILON) {
45595
+ lastKnownSize = size;
45596
+ return true;
45597
+ }
45598
+ return false;
45570
45599
  };
45571
45600
  const mountViewer = () => {
45572
45601
  if (!node || mounted || cancelled) return false;
@@ -45582,8 +45611,9 @@ var ImageStory = (props = {}) => {
45582
45611
  });
45583
45612
  return true;
45584
45613
  };
45585
- if (!mountViewer()) {
45586
- if (typeof window !== "undefined" && typeof window.ResizeObserver === "function") {
45614
+ const scheduleWatchers = () => {
45615
+ if (mounted || cancelled) return;
45616
+ if (!resizeObserver && typeof window !== "undefined" && typeof window.ResizeObserver === "function") {
45587
45617
  resizeObserver = new window.ResizeObserver(() => {
45588
45618
  if (mounted || cancelled) return;
45589
45619
  mountViewer();
@@ -45602,12 +45632,48 @@ var ImageStory = (props = {}) => {
45602
45632
  }
45603
45633
  }, 200);
45604
45634
  };
45605
- schedulePoll();
45635
+ if (!pollId) {
45636
+ schedulePoll();
45637
+ }
45638
+ };
45639
+ const beginMounting = () => {
45640
+ if (!mountViewer()) {
45641
+ scheduleWatchers();
45642
+ }
45643
+ };
45644
+ const remountViewer = () => {
45645
+ if (cancelled) return;
45646
+ if (mounted) {
45647
+ mounted = false;
45648
+ destroyCleanup();
45649
+ }
45650
+ beginMounting();
45651
+ };
45652
+ beginMounting();
45653
+ const handleGalleryModalChange = (event) => {
45654
+ if (!node || !event || typeof document === "undefined") return;
45655
+ const detail = event.detail || {};
45656
+ if (detail.state !== "open") return;
45657
+ const modal = detail.modal || (detail.modalId ? document.getElementById(detail.modalId) : null);
45658
+ if (!modal || !modal.contains(node)) return;
45659
+ if (!mounted) return;
45660
+ if (hasMeaningfulSizeChange()) {
45661
+ remountViewer();
45662
+ }
45663
+ };
45664
+ if (typeof window !== "undefined" && window.addEventListener) {
45665
+ window.addEventListener("canopy:gallery:modal-change", handleGalleryModalChange);
45606
45666
  }
45607
45667
  return () => {
45608
45668
  cancelled = true;
45609
45669
  disconnectWatchers();
45610
45670
  destroyCleanup();
45671
+ if (typeof window !== "undefined" && window.removeEventListener) {
45672
+ window.removeEventListener(
45673
+ "canopy:gallery:modal-change",
45674
+ handleGalleryModalChange
45675
+ );
45676
+ }
45611
45677
  };
45612
45678
  }, [iiifContent, disablePanAndZoom, pointOfInterestSvgUrl, viewerOptions]);
45613
45679
  return /* @__PURE__ */ React27.createElement(
@@ -48194,6 +48260,23 @@ var INLINE_SCRIPT = `(() => {
48194
48260
  const NAV_OPTION_SELECTOR = '[data-canopy-gallery-nav-option]';
48195
48261
  const NAV_ITEM_SELECTOR = '[data-canopy-gallery-nav-item]';
48196
48262
 
48263
+ function emitModalState(modal, state) {
48264
+ if (!modal || typeof window === 'undefined') return;
48265
+ const detail = { modalId: modal.id || '', modal, state };
48266
+ try {
48267
+ const EventCtor = window.CustomEvent || CustomEvent;
48268
+ if (typeof EventCtor === 'function') {
48269
+ window.dispatchEvent(new EventCtor('canopy:gallery:modal-change', { detail }));
48270
+ return;
48271
+ }
48272
+ } catch (_) {}
48273
+ try {
48274
+ const fallback = document.createEvent('CustomEvent');
48275
+ fallback.initCustomEvent('canopy:gallery:modal-change', true, true, detail);
48276
+ window.dispatchEvent(fallback);
48277
+ } catch (_) {}
48278
+ }
48279
+
48197
48280
  function isVisible(node) {
48198
48281
  return !!(node && (node.offsetWidth || node.offsetHeight || node.getClientRects().length));
48199
48282
  }
@@ -48324,6 +48407,7 @@ var INLINE_SCRIPT = `(() => {
48324
48407
  lockScroll();
48325
48408
  document.addEventListener('keydown', handleKeydown, true);
48326
48409
  } else if (activeModal !== modal) {
48410
+ emitModalState(activeModal, 'close');
48327
48411
  activeModal.removeAttribute('data-canopy-gallery-active');
48328
48412
  }
48329
48413
  activeModal = modal;
@@ -48332,6 +48416,7 @@ var INLINE_SCRIPT = `(() => {
48332
48416
  if (!focusActiveNav(modal)) {
48333
48417
  focusInitial(modal);
48334
48418
  }
48419
+ emitModalState(modal, 'open');
48335
48420
  return;
48336
48421
  }
48337
48422
  if (!activeModal) return;
@@ -48365,6 +48450,7 @@ var INLINE_SCRIPT = `(() => {
48365
48450
  }
48366
48451
  });
48367
48452
  }
48453
+ emitModalState(previous, 'close');
48368
48454
  }
48369
48455
 
48370
48456
  function modalFromHash() {
@@ -48823,6 +48909,7 @@ function buildCaptionContent(itemProps) {
48823
48909
  ));
48824
48910
  }
48825
48911
  function GalleryModal({ item, closeTargetId, navItems, navGroupName }) {
48912
+ const { getString, formatString } = useLocale();
48826
48913
  const {
48827
48914
  props,
48828
48915
  modalId,
@@ -48835,6 +48922,12 @@ function GalleryModal({ item, closeTargetId, navItems, navGroupName }) {
48835
48922
  const kicker = props.kicker || props.label || props.eyebrow;
48836
48923
  const summary = props.popupDescription || props.modalDescription || props.description || props.summary || null;
48837
48924
  const modalTitle = props.popupTitle || props.modalTitle || props.title || `Item ${index + 1}`;
48925
+ const closeButtonText = getString("common.actions.close", "Close");
48926
+ const closeButtonLabel = formatString(
48927
+ "common.phrases.close_content",
48928
+ "Close {content}",
48929
+ { content: modalTitle }
48930
+ );
48838
48931
  return /* @__PURE__ */ React44.createElement(
48839
48932
  "div",
48840
48933
  {
@@ -48856,13 +48949,14 @@ function GalleryModal({ item, closeTargetId, navItems, navGroupName }) {
48856
48949
  groupName: `${navGroupName || "canopy-gallery"}-${modalId}`
48857
48950
  }
48858
48951
  ), /* @__PURE__ */ React44.createElement(
48859
- "a",
48952
+ Button,
48860
48953
  {
48861
48954
  className: "canopy-gallery__modal-close",
48862
48955
  href: `#${closeTargetId}`,
48863
- "aria-label": `Close popup for ${modalTitle}`
48864
- },
48865
- "X"
48956
+ label: closeButtonText,
48957
+ "aria-label": closeButtonLabel,
48958
+ variant: "secondary"
48959
+ }
48866
48960
  )), /* @__PURE__ */ React44.createElement("div", { className: "canopy-gallery__modal-panel" }, /* @__PURE__ */ React44.createElement(
48867
48961
  "button",
48868
48962
  {