@canopy-iiif/app 1.5.8 → 1.5.10

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.
package/ui/dist/index.mjs CHANGED
@@ -93,6 +93,7 @@ function Card({
93
93
  {
94
94
  src,
95
95
  alt: alt || title || "",
96
+ "aria-hidden": !alt && !title ? "true" : void 0,
96
97
  loading: "lazy",
97
98
  onLoad: () => setImageLoaded(true),
98
99
  onError: () => setImageLoaded(true)
@@ -103,6 +104,7 @@ function Card({
103
104
  {
104
105
  src,
105
106
  alt: alt || title || "",
107
+ "aria-hidden": !alt && !title ? "true" : void 0,
106
108
  loading: "lazy",
107
109
  onLoad: () => setImageLoaded(true),
108
110
  onError: () => setImageLoaded(true),
@@ -269,7 +271,7 @@ function renderMarkdownTokens(tokens, query, keyPrefix = "token") {
269
271
  case "code":
270
272
  return /* @__PURE__ */ React3.createElement("code", { key }, token.value);
271
273
  case "link":
272
- return /* @__PURE__ */ React3.createElement("a", { key, href: token.href, target: "_blank", rel: "noreferrer" }, renderMarkdownTokens(token.children || [], query, key));
274
+ return /* @__PURE__ */ React3.createElement("a", { key, href: token.href, target: "_blank", rel: "noreferrer" }, renderMarkdownTokens(token.children || [], query, key), /* @__PURE__ */ React3.createElement("span", { className: "sr-only" }, " (opens in new tab)"));
273
275
  case "break":
274
276
  return /* @__PURE__ */ React3.createElement("br", { key });
275
277
  case "text":
@@ -449,14 +451,14 @@ function ButtonWrapper({
449
451
  }
450
452
 
451
453
  // ui/src/layout/CanopyHeader.jsx
452
- import React14 from "react";
454
+ import React15 from "react";
453
455
 
454
456
  // ui/src/search/SearchPanel.jsx
455
457
  import React11 from "react";
456
458
 
457
459
  // ui/src/Icons.jsx
458
460
  import React8 from "react";
459
- var MagnifyingGlassIcon = (props) => /* @__PURE__ */ React8.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 512 512", ...props }, /* @__PURE__ */ React8.createElement("path", { d: "M456.69 421.39L362.6 327.3a173.81 173.81 0 0034.84-104.58C397.44 126.38 319.06 48 222.72 48S48 126.38 48 222.72s78.38 174.72 174.72 174.72A173.81 173.81 0 00327.3 362.6l94.09 94.09a25 25 0 0035.3-35.3zM97.92 222.72a124.8 124.8 0 11124.8 124.8 124.95 124.95 0 01-124.8-124.8z" }));
461
+ var MagnifyingGlassIcon = (props) => /* @__PURE__ */ React8.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 512 512", "aria-hidden": "true", focusable: "false", ...props }, /* @__PURE__ */ React8.createElement("path", { d: "M456.69 421.39L362.6 327.3a173.81 173.81 0 0034.84-104.58C397.44 126.38 319.06 48 222.72 48S48 126.38 48 222.72s78.38 174.72 174.72 174.72A173.81 173.81 0 00327.3 362.6l94.09 94.09a25 25 0 0035.3-35.3zM97.92 222.72a124.8 124.8 0 11124.8 124.8 124.95 124.95 0 01-124.8-124.8z" }));
460
462
 
461
463
  // ui/src/search/SearchPanelForm.jsx
462
464
  import React9 from "react";
@@ -595,7 +597,7 @@ function SearchPanelForm(props = {}) {
595
597
  onPointerDown: handlePointerDown,
596
598
  "data-has-value": hasValue ? "1" : "0"
597
599
  },
598
- /* @__PURE__ */ React9.createElement("label", { htmlFor: inputId, className: "canopy-search-form__label" }, /* @__PURE__ */ React9.createElement(MagnifyingGlassIcon, { className: "canopy-search-form__icon" }), /* @__PURE__ */ React9.createElement(
600
+ /* @__PURE__ */ React9.createElement("label", { htmlFor: inputId, className: "canopy-search-form__label" }, /* @__PURE__ */ React9.createElement(MagnifyingGlassIcon, { className: "canopy-search-form__icon" }), /* @__PURE__ */ React9.createElement("span", { className: "sr-only" }, "Search"), /* @__PURE__ */ React9.createElement(
599
601
  "input",
600
602
  {
601
603
  id: inputId,
@@ -605,7 +607,6 @@ function SearchPanelForm(props = {}) {
605
607
  "data-canopy-search-form-input": true,
606
608
  placeholder,
607
609
  className: "canopy-search-form__input",
608
- "aria-label": "Search",
609
610
  ref: inputRef,
610
611
  onChange: handleInputChange,
611
612
  onInput: handleInputChange
@@ -763,6 +764,130 @@ function CanopyModal(props = {}) {
763
764
  )) : null, children)));
764
765
  }
765
766
 
767
+ // ui/src/layout/NavigationTree.jsx
768
+ import React14 from "react";
769
+ function normalizeDepth(depth) {
770
+ if (typeof depth !== "number") return 0;
771
+ return Math.max(0, Math.min(5, depth));
772
+ }
773
+ function NavigationTreeList({ nodes, depth, parentKey }) {
774
+ if (!Array.isArray(nodes) || !nodes.length) return null;
775
+ const listClasses = ["canopy-nav-tree__list"];
776
+ if (depth > 0) listClasses.push("canopy-nav-tree__list--nested");
777
+ return /* @__PURE__ */ React14.createElement("ul", { className: listClasses.join(" "), role: "list" }, nodes.map((node, index) => /* @__PURE__ */ React14.createElement(
778
+ NavigationTreeItem,
779
+ {
780
+ key: node.slug || node.href || node.title || `${parentKey}-${index}`,
781
+ node,
782
+ depth,
783
+ nodeKey: `${parentKey}-${index}`
784
+ }
785
+ )));
786
+ }
787
+ function NavigationTreeItem({ node, depth, nodeKey }) {
788
+ if (!node) return null;
789
+ const hasChildren = Array.isArray(node.children) && node.children.length > 0;
790
+ const isRoadmap = !!node.isRoadmap;
791
+ const isInteractive = !!(node.href && !isRoadmap);
792
+ const Tag = isInteractive ? "a" : "span";
793
+ const depthClass = `depth-${normalizeDepth(depth + 1)}`;
794
+ const classes = ["canopy-nav-tree__link", depthClass];
795
+ if (!isInteractive && !isRoadmap) classes.push("is-label");
796
+ if (isRoadmap) classes.push("is-disabled");
797
+ if (node.isActive) classes.push("is-active");
798
+ const isRootLevel = depth < 0;
799
+ const panelId = hasChildren ? `canopy-section-${nodeKey}` : null;
800
+ const allowToggle = hasChildren && !isRootLevel;
801
+ const defaultExpanded = allowToggle ? !!node.isExpanded : true;
802
+ const toggleLabel = node.title ? `Toggle ${node.title} menu` : "Toggle section menu";
803
+ return /* @__PURE__ */ React14.createElement(
804
+ "li",
805
+ {
806
+ className: "canopy-nav-tree__item",
807
+ "data-depth": depth,
808
+ "data-canopy-nav-item": allowToggle ? "true" : void 0,
809
+ "data-expanded": allowToggle ? defaultExpanded ? "true" : "false" : void 0,
810
+ "data-default-expanded": allowToggle && defaultExpanded ? "true" : void 0
811
+ },
812
+ /* @__PURE__ */ React14.createElement("div", { className: "canopy-nav-tree__row" }, /* @__PURE__ */ React14.createElement(
813
+ Tag,
814
+ {
815
+ className: classes.join(" "),
816
+ href: isInteractive ? node.href : void 0,
817
+ "aria-current": node.isActive ? "page" : void 0,
818
+ tabIndex: isInteractive ? void 0 : -1
819
+ },
820
+ node.title || node.slug,
821
+ isRoadmap ? /* @__PURE__ */ React14.createElement("span", { className: "canopy-nav-tree__badge" }, "Roadmap") : null
822
+ ), allowToggle ? /* @__PURE__ */ React14.createElement(
823
+ "button",
824
+ {
825
+ type: "button",
826
+ className: "canopy-nav-tree__toggle",
827
+ "aria-expanded": defaultExpanded ? "true" : "false",
828
+ "aria-controls": panelId || void 0,
829
+ "aria-label": toggleLabel,
830
+ "data-canopy-nav-item-toggle": panelId || void 0
831
+ },
832
+ /* @__PURE__ */ React14.createElement(
833
+ "svg",
834
+ {
835
+ xmlns: "http://www.w3.org/2000/svg",
836
+ viewBox: "0 0 24 24",
837
+ fill: "none",
838
+ stroke: "currentColor",
839
+ strokeWidth: "1.5",
840
+ className: "canopy-nav-tree__toggle-icon"
841
+ },
842
+ /* @__PURE__ */ React14.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 9l7 7 7-7" })
843
+ ),
844
+ /* @__PURE__ */ React14.createElement("span", { className: "sr-only" }, toggleLabel)
845
+ ) : null),
846
+ hasChildren ? /* @__PURE__ */ React14.createElement(
847
+ "div",
848
+ {
849
+ id: panelId || void 0,
850
+ className: "canopy-nav-tree__children",
851
+ "aria-hidden": allowToggle ? defaultExpanded ? "false" : "true" : "false",
852
+ hidden: allowToggle ? !defaultExpanded : void 0
853
+ },
854
+ /* @__PURE__ */ React14.createElement(
855
+ NavigationTreeList,
856
+ {
857
+ nodes: node.children,
858
+ depth: depth + 1,
859
+ parentKey: nodeKey
860
+ }
861
+ )
862
+ ) : null
863
+ );
864
+ }
865
+ function NavigationTree({
866
+ root,
867
+ className = "",
868
+ parentKey = "nav",
869
+ includeRoot = false,
870
+ heading,
871
+ headingClassName = "canopy-nav-tree__heading",
872
+ component: Component = "div",
873
+ ...rest
874
+ }) {
875
+ if (!root) return null;
876
+ const nodes = includeRoot ? [root] : root.children;
877
+ if (!Array.isArray(nodes) || !nodes.length) return null;
878
+ const combinedClassName = ["canopy-nav-tree", className].filter(Boolean).join(" ");
879
+ return /* @__PURE__ */ React14.createElement(
880
+ Component,
881
+ {
882
+ className: combinedClassName,
883
+ "data-canopy-nav-tree": "true",
884
+ ...rest
885
+ },
886
+ heading ? /* @__PURE__ */ React14.createElement("div", { className: headingClassName }, heading) : null,
887
+ /* @__PURE__ */ React14.createElement(NavigationTreeList, { nodes, depth: includeRoot ? -1 : 0, parentKey })
888
+ );
889
+ }
890
+
766
891
  // ui/src/layout/CanopyHeader.jsx
767
892
  function HeaderScript() {
768
893
  const code = `
@@ -773,11 +898,24 @@ function HeaderScript() {
773
898
  var body = doc.body;
774
899
  var root = doc.documentElement;
775
900
 
901
+ function desktopBreakpointQuery() {
902
+ if (typeof window === 'undefined') return '(min-width: 70rem)';
903
+ try {
904
+ var styles = window.getComputedStyle ? window.getComputedStyle(root) : null;
905
+ var value = styles ? styles.getPropertyValue('--canopy-desktop-breakpoint') : '';
906
+ if (typeof value === 'string') value = value.trim();
907
+ if (!value) value = '70rem';
908
+ return '(min-width: ' + value + ')';
909
+ } catch (error) {
910
+ return '(min-width: 70rem)';
911
+ }
912
+ }
913
+
776
914
  function ready(fn) {
777
915
  if (doc.readyState === 'loading') {
778
916
  doc.addEventListener('DOMContentLoaded', fn, { once: true });
779
917
  } else {
780
- fn();
918
+ fn();
781
919
  }
782
920
  }
783
921
 
@@ -787,6 +925,8 @@ function HeaderScript() {
787
925
 
788
926
  var NAV_ATTR = 'data-mobile-nav';
789
927
  var SEARCH_ATTR = 'data-mobile-search';
928
+ var NAV_ITEM_ATTR = 'data-canopy-nav-item';
929
+ var NAV_ITEM_TOGGLE_ATTR = 'data-canopy-nav-item-toggle';
790
930
 
791
931
  function modalFor(type) {
792
932
  return doc.querySelector('[data-canopy-modal="' + type + '"]');
@@ -868,6 +1008,51 @@ function HeaderScript() {
868
1008
  });
869
1009
  }
870
1010
 
1011
+ function forEachNavTree(scope, fn) {
1012
+ if (typeof fn !== 'function') return;
1013
+ var rootNode = scope || doc;
1014
+ var trees = rootNode.querySelectorAll('[data-canopy-nav-tree]');
1015
+ each(trees, function (tree) {
1016
+ fn(tree);
1017
+ });
1018
+ }
1019
+
1020
+ function resetNavItemToggles(scope) {
1021
+ forEachNavTree(scope, function (tree) {
1022
+ var toggles = tree.querySelectorAll('[' + NAV_ITEM_TOGGLE_ATTR + ']');
1023
+ each(toggles, function (btn) {
1024
+ btn.setAttribute('aria-expanded', 'false');
1025
+ var targetId = btn.getAttribute(NAV_ITEM_TOGGLE_ATTR);
1026
+ var panel = targetId ? doc.getElementById(targetId) : null;
1027
+ var parent = btn.closest('[' + NAV_ITEM_ATTR + ']');
1028
+ if (panel) {
1029
+ panel.hidden = true;
1030
+ panel.setAttribute('aria-hidden', 'true');
1031
+ panel.setAttribute('hidden', '');
1032
+ }
1033
+ if (parent) parent.setAttribute('data-expanded', 'false');
1034
+ });
1035
+ });
1036
+ }
1037
+
1038
+ function applyDefaultNavItemState(scope) {
1039
+ forEachNavTree(scope, function (tree) {
1040
+ var defaults = tree.querySelectorAll('[data-default-expanded="true"]');
1041
+ each(defaults, function (item) {
1042
+ var toggle = item.querySelector('[' + NAV_ITEM_TOGGLE_ATTR + ']');
1043
+ var targetId = toggle ? toggle.getAttribute(NAV_ITEM_TOGGLE_ATTR) : null;
1044
+ var panel = targetId ? doc.getElementById(targetId) : null;
1045
+ item.setAttribute('data-expanded', 'true');
1046
+ if (toggle) toggle.setAttribute('aria-expanded', 'true');
1047
+ if (panel) {
1048
+ panel.hidden = false;
1049
+ panel.removeAttribute('hidden');
1050
+ panel.setAttribute('aria-hidden', 'false');
1051
+ }
1052
+ });
1053
+ });
1054
+ }
1055
+
871
1056
  function setState(type, next) {
872
1057
  if (type === 'nav') header.setAttribute(NAV_ATTR, next);
873
1058
  if (type === 'search') header.setAttribute(SEARCH_ATTR, next);
@@ -875,6 +1060,13 @@ function HeaderScript() {
875
1060
  var navOpen = header.getAttribute(NAV_ATTR) === 'open';
876
1061
  var searchOpen = header.getAttribute(SEARCH_ATTR) === 'open';
877
1062
  lockScroll(navOpen || searchOpen);
1063
+ if (type === 'nav') {
1064
+ if (next !== 'open') {
1065
+ resetNavItemToggles(modalFor('nav'));
1066
+ } else {
1067
+ applyDefaultNavItemState(modalFor('nav'));
1068
+ }
1069
+ }
878
1070
  }
879
1071
 
880
1072
  function toggle(type, force) {
@@ -887,6 +1079,35 @@ function HeaderScript() {
887
1079
  if (type === 'nav' && shouldOpen) focusNavMenu();
888
1080
  }
889
1081
 
1082
+ function setupNavItemToggles() {
1083
+ var toggles = doc.querySelectorAll('[' + NAV_ITEM_TOGGLE_ATTR + ']');
1084
+ each(toggles, function (btn) {
1085
+ if (btn.__canopyNavReady) return;
1086
+ btn.__canopyNavReady = true;
1087
+ btn.addEventListener('click', function (event) {
1088
+ event.preventDefault();
1089
+ event.stopPropagation();
1090
+ var targetId = btn.getAttribute(NAV_ITEM_TOGGLE_ATTR);
1091
+ if (!targetId) return;
1092
+ var panel = doc.getElementById(targetId);
1093
+ var parent = btn.closest('[' + NAV_ITEM_ATTR + ']');
1094
+ var expanded = btn.getAttribute('aria-expanded') === 'true';
1095
+ var next = !expanded;
1096
+ btn.setAttribute('aria-expanded', next ? 'true' : 'false');
1097
+ if (panel) {
1098
+ panel.hidden = !next;
1099
+ panel.setAttribute('aria-hidden', next ? 'false' : 'true');
1100
+ if (next) {
1101
+ panel.removeAttribute('hidden');
1102
+ } else {
1103
+ panel.setAttribute('hidden', '');
1104
+ }
1105
+ }
1106
+ if (parent) parent.setAttribute('data-expanded', next ? 'true' : 'false');
1107
+ });
1108
+ });
1109
+ }
1110
+
890
1111
  each(header.querySelectorAll('[data-canopy-header-toggle]'), function (btn) {
891
1112
  btn.addEventListener('click', function (event) {
892
1113
  event.preventDefault();
@@ -934,7 +1155,7 @@ function HeaderScript() {
934
1155
  toggle('search', false);
935
1156
  });
936
1157
 
937
- var mq = window.matchMedia('(min-width: 48rem)');
1158
+ var mq = window.matchMedia(desktopBreakpointQuery());
938
1159
  function syncDesktopState() {
939
1160
  if (mq.matches) {
940
1161
  setState('nav', 'closed');
@@ -951,11 +1172,13 @@ function HeaderScript() {
951
1172
  mq.addListener(syncDesktopState);
952
1173
  }
953
1174
 
1175
+ setupNavItemToggles();
1176
+ applyDefaultNavItemState(null);
954
1177
  syncDesktopState();
955
1178
  });
956
1179
  })();
957
1180
  `;
958
- return /* @__PURE__ */ React14.createElement(
1181
+ return /* @__PURE__ */ React15.createElement(
959
1182
  "script",
960
1183
  {
961
1184
  dangerouslySetInnerHTML: {
@@ -974,7 +1197,7 @@ function getSharedRoot() {
974
1197
  function getSafePageContext() {
975
1198
  const root = getSharedRoot();
976
1199
  if (root && root[CONTEXT_KEY]) return root[CONTEXT_KEY];
977
- const ctx = React14.createContext({ navigation: null, page: null });
1200
+ const ctx = React15.createContext({ navigation: null, page: null });
978
1201
  if (root) root[CONTEXT_KEY] = ctx;
979
1202
  return ctx;
980
1203
  }
@@ -984,29 +1207,55 @@ function ensureArray(navLinks) {
984
1207
  (link) => link && typeof link === "object" && typeof link.href === "string"
985
1208
  );
986
1209
  }
987
- function SectionNavList({ root }) {
988
- if (!root || !Array.isArray(root.children) || !root.children.length) return null;
989
- return /* @__PURE__ */ React14.createElement("ul", { className: "canopy-modal__section-list", role: "list" }, root.children.map((node) => /* @__PURE__ */ React14.createElement(SectionNavItem, { key: node.slug || node.href || node.title, node, depth: 0 })));
1210
+ function normalizeHref(href) {
1211
+ if (typeof href !== "string") return "";
1212
+ let next = href.trim();
1213
+ if (!next) return "";
1214
+ try {
1215
+ const parsed = new URL(next, "https://canopy.local");
1216
+ next = parsed.pathname || "/";
1217
+ } catch (_) {
1218
+ next = next.replace(/[?#].*$/, "");
1219
+ }
1220
+ next = next.replace(/[?#].*$/, "");
1221
+ if (next.length > 1) {
1222
+ next = next.replace(/\/+$/, "");
1223
+ }
1224
+ if (!next) return "/";
1225
+ return next;
990
1226
  }
991
- function SectionNavItem({ node, depth }) {
992
- if (!node) return null;
993
- const hasChildren = Array.isArray(node.children) && node.children.length > 0;
994
- const Tag = node.href ? "a" : "span";
995
- const classes = [
996
- "canopy-modal__section-link",
997
- `depth-${Math.min(5, Math.max(0, depth + 1))}`
998
- ];
999
- if (!node.href) classes.push("is-label");
1000
- if (node.isActive) classes.push("is-active");
1001
- return /* @__PURE__ */ React14.createElement("li", { className: "canopy-modal__section-item", "data-depth": depth }, /* @__PURE__ */ React14.createElement(
1002
- Tag,
1003
- {
1004
- className: classes.join(" "),
1005
- href: node.href || void 0,
1006
- "aria-current": node.isActive ? "page" : void 0
1007
- },
1008
- node.title || node.slug
1009
- ), hasChildren ? /* @__PURE__ */ React14.createElement("ul", { className: "canopy-modal__section-list canopy-modal__section-list--nested", role: "list" }, node.children.map((child) => /* @__PURE__ */ React14.createElement(SectionNavItem, { key: child.slug || child.href || child.title, node: child, depth: depth + 1 }))) : null);
1227
+ function doesLinkMatchSection(linkHref, sectionNavigation) {
1228
+ if (!sectionNavigation || !sectionNavigation.root || !linkHref) return false;
1229
+ const normalizedLink = normalizeHref(linkHref);
1230
+ if (!normalizedLink) return false;
1231
+ const root = sectionNavigation.root;
1232
+ if (typeof root.href === "string" && normalizeHref(root.href) === normalizedLink) {
1233
+ return true;
1234
+ }
1235
+ if (root.slug) {
1236
+ const slugPath = normalizeHref(`/${root.slug}`);
1237
+ if (slugPath && normalizedLink === slugPath) {
1238
+ return true;
1239
+ }
1240
+ }
1241
+ return false;
1242
+ }
1243
+ function rootSegmentFromHref(href) {
1244
+ const normalized = normalizeHref(href);
1245
+ if (!normalized || normalized === "/") return "";
1246
+ const trimmed = normalized.replace(/^\/+/, "");
1247
+ return trimmed.split("/")[0] || "";
1248
+ }
1249
+ function getLinkNavigationData(link, navigationRoots, sectionNavigation) {
1250
+ if (!link || typeof link.href !== "string") return null;
1251
+ const segment = rootSegmentFromHref(link.href);
1252
+ if (navigationRoots && segment && navigationRoots[segment]) {
1253
+ return navigationRoots[segment];
1254
+ }
1255
+ if (sectionNavigation && doesLinkMatchSection(link.href, sectionNavigation)) {
1256
+ return sectionNavigation;
1257
+ }
1258
+ return null;
1010
1259
  }
1011
1260
  function CanopyHeader(props = {}) {
1012
1261
  const {
@@ -1020,20 +1269,34 @@ function CanopyHeader(props = {}) {
1020
1269
  } = props;
1021
1270
  const navLinks = ensureArray(navLinksProp);
1022
1271
  const PageContext = getSafePageContext();
1023
- const context = React14.useContext(PageContext);
1024
- const sectionNavigation = context && context.navigation && context.navigation.root ? context.navigation : null;
1272
+ const context = React15.useContext(PageContext);
1273
+ const contextNavigation = context && context.navigation ? context.navigation : null;
1274
+ const sectionNavigation = contextNavigation && contextNavigation.root ? contextNavigation : null;
1275
+ const navigationRoots = contextNavigation && contextNavigation.allRoots ? contextNavigation.allRoots : null;
1025
1276
  const sectionHeading = sectionNavigation && sectionNavigation.title || (sectionNavigation && sectionNavigation.root ? sectionNavigation.root.title : "");
1026
1277
  const hasSectionNav = !!(sectionNavigation && sectionNavigation.root && Array.isArray(sectionNavigation.root.children) && sectionNavigation.root.children.length);
1027
1278
  const sectionLabel = sectionHeading ? `More in ${sectionHeading}` : "More in this section";
1028
1279
  const sectionAriaLabel = sectionHeading ? `${sectionHeading} section navigation` : "Section navigation";
1029
- return /* @__PURE__ */ React14.createElement(React14.Fragment, null, /* @__PURE__ */ React14.createElement(
1280
+ const defaultSectionLabel = sectionLabel;
1281
+ const defaultSectionAriaLabel = sectionAriaLabel;
1282
+ const shouldAttachSectionNav = (link) => {
1283
+ const navData = getLinkNavigationData(
1284
+ link,
1285
+ navigationRoots,
1286
+ sectionNavigation
1287
+ );
1288
+ const rootNode = navData && navData.root;
1289
+ return !!(rootNode && Array.isArray(rootNode.children) && rootNode.children.length);
1290
+ };
1291
+ const hasIntegratedSectionNav = navLinks.some(shouldAttachSectionNav);
1292
+ return /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(
1030
1293
  "header",
1031
1294
  {
1032
1295
  className: "canopy-header",
1033
1296
  "data-mobile-nav": "closed",
1034
1297
  "data-mobile-search": "closed"
1035
1298
  },
1036
- /* @__PURE__ */ React14.createElement("div", { className: "canopy-header__brand" }, /* @__PURE__ */ React14.createElement(
1299
+ /* @__PURE__ */ React15.createElement("div", { className: "canopy-header__brand" }, /* @__PURE__ */ React15.createElement(
1037
1300
  CanopyBrand,
1038
1301
  {
1039
1302
  label: title,
@@ -1042,7 +1305,7 @@ function CanopyHeader(props = {}) {
1042
1305
  Logo: SiteLogo
1043
1306
  }
1044
1307
  )),
1045
- /* @__PURE__ */ React14.createElement("div", { className: "canopy-header__desktop-search" }, /* @__PURE__ */ React14.createElement(
1308
+ /* @__PURE__ */ React15.createElement("div", { className: "canopy-header__desktop-search" }, /* @__PURE__ */ React15.createElement(
1046
1309
  SearchPanel,
1047
1310
  {
1048
1311
  label: searchLabel,
@@ -1050,15 +1313,23 @@ function CanopyHeader(props = {}) {
1050
1313
  placeholder: searchPlaceholder
1051
1314
  }
1052
1315
  )),
1053
- /* @__PURE__ */ React14.createElement(
1316
+ /* @__PURE__ */ React15.createElement(
1054
1317
  "nav",
1055
1318
  {
1056
1319
  className: "canopy-nav-links canopy-header__desktop-nav",
1057
1320
  "aria-label": "Primary navigation"
1058
1321
  },
1059
- navLinks.map((link) => /* @__PURE__ */ React14.createElement("a", { key: link.href, href: link.href }, link.label || link.href))
1322
+ navLinks.map((link) => /* @__PURE__ */ React15.createElement(
1323
+ "a",
1324
+ {
1325
+ key: link.href,
1326
+ href: link.href,
1327
+ "aria-current": link.isActive ? "page" : void 0
1328
+ },
1329
+ link.label || link.href
1330
+ ))
1060
1331
  ),
1061
- /* @__PURE__ */ React14.createElement("div", { className: "canopy-header__actions" }, /* @__PURE__ */ React14.createElement(
1332
+ /* @__PURE__ */ React15.createElement("div", { className: "canopy-header__actions" }, /* @__PURE__ */ React15.createElement(
1062
1333
  "button",
1063
1334
  {
1064
1335
  type: "button",
@@ -1068,7 +1339,7 @@ function CanopyHeader(props = {}) {
1068
1339
  "aria-expanded": "false",
1069
1340
  "data-canopy-header-toggle": "search"
1070
1341
  },
1071
- /* @__PURE__ */ React14.createElement(
1342
+ /* @__PURE__ */ React15.createElement(
1072
1343
  "svg",
1073
1344
  {
1074
1345
  xmlns: "http://www.w3.org/2000/svg",
@@ -1078,7 +1349,7 @@ function CanopyHeader(props = {}) {
1078
1349
  strokeWidth: "1.5",
1079
1350
  className: "canopy-header__search-icon"
1080
1351
  },
1081
- /* @__PURE__ */ React14.createElement(
1352
+ /* @__PURE__ */ React15.createElement(
1082
1353
  "path",
1083
1354
  {
1084
1355
  strokeLinecap: "round",
@@ -1087,7 +1358,7 @@ function CanopyHeader(props = {}) {
1087
1358
  }
1088
1359
  )
1089
1360
  )
1090
- ), /* @__PURE__ */ React14.createElement(
1361
+ ), /* @__PURE__ */ React15.createElement(
1091
1362
  "button",
1092
1363
  {
1093
1364
  type: "button",
@@ -1097,7 +1368,7 @@ function CanopyHeader(props = {}) {
1097
1368
  "aria-expanded": "false",
1098
1369
  "data-canopy-header-toggle": "nav"
1099
1370
  },
1100
- /* @__PURE__ */ React14.createElement(
1371
+ /* @__PURE__ */ React15.createElement(
1101
1372
  "svg",
1102
1373
  {
1103
1374
  xmlns: "http://www.w3.org/2000/svg",
@@ -1107,7 +1378,7 @@ function CanopyHeader(props = {}) {
1107
1378
  stroke: "currentColor",
1108
1379
  className: "canopy-header__menu-icon"
1109
1380
  },
1110
- /* @__PURE__ */ React14.createElement(
1381
+ /* @__PURE__ */ React15.createElement(
1111
1382
  "path",
1112
1383
  {
1113
1384
  strokeLinecap: "round",
@@ -1117,7 +1388,7 @@ function CanopyHeader(props = {}) {
1117
1388
  )
1118
1389
  )
1119
1390
  ))
1120
- ), /* @__PURE__ */ React14.createElement(
1391
+ ), /* @__PURE__ */ React15.createElement(
1121
1392
  CanopyModal,
1122
1393
  {
1123
1394
  id: "canopy-modal-nav",
@@ -1129,24 +1400,90 @@ function CanopyHeader(props = {}) {
1129
1400
  closeLabel: "Close navigation",
1130
1401
  closeDataAttr: "nav"
1131
1402
  },
1132
- /* @__PURE__ */ React14.createElement(
1403
+ /* @__PURE__ */ React15.createElement(
1133
1404
  "nav",
1134
1405
  {
1135
1406
  className: "canopy-nav-links canopy-modal__nav",
1136
1407
  "aria-label": "Primary navigation"
1137
1408
  },
1138
- navLinks.map((link) => /* @__PURE__ */ React14.createElement("a", { key: link.href, href: link.href }, link.label || link.href))
1409
+ /* @__PURE__ */ React15.createElement("ul", { className: "canopy-modal__nav-list", role: "list" }, navLinks.map((link, index) => {
1410
+ const navData = getLinkNavigationData(
1411
+ link,
1412
+ navigationRoots,
1413
+ sectionNavigation
1414
+ );
1415
+ const navRoot = navData && navData.root ? navData.root : null;
1416
+ const hasChildren = !!(navRoot && Array.isArray(navRoot.children) && navRoot.children.length);
1417
+ const nestedId = hasChildren ? `canopy-modal-section-${index}` : null;
1418
+ const toggleLabel = link.label ? `Toggle ${link.label} menu` : "Toggle section menu";
1419
+ const defaultExpanded = hasChildren && !!navRoot.isExpanded;
1420
+ return /* @__PURE__ */ React15.createElement(
1421
+ "li",
1422
+ {
1423
+ className: "canopy-modal__nav-item",
1424
+ key: link.href,
1425
+ "data-canopy-nav-item": hasChildren ? "true" : void 0,
1426
+ "data-expanded": defaultExpanded ? "true" : "false",
1427
+ "data-default-expanded": defaultExpanded ? "true" : void 0
1428
+ },
1429
+ /* @__PURE__ */ React15.createElement("div", { className: "canopy-modal__nav-row" }, /* @__PURE__ */ React15.createElement("a", { href: link.href }, link.label || link.href), hasChildren ? /* @__PURE__ */ React15.createElement(
1430
+ "button",
1431
+ {
1432
+ type: "button",
1433
+ className: "canopy-modal__nav-toggle",
1434
+ "aria-expanded": defaultExpanded ? "true" : "false",
1435
+ "aria-controls": nestedId || void 0,
1436
+ "aria-label": toggleLabel,
1437
+ "data-canopy-nav-item-toggle": nestedId || void 0
1438
+ },
1439
+ /* @__PURE__ */ React15.createElement(
1440
+ "svg",
1441
+ {
1442
+ xmlns: "http://www.w3.org/2000/svg",
1443
+ viewBox: "0 0 24 24",
1444
+ fill: "none",
1445
+ stroke: "currentColor",
1446
+ strokeWidth: "1.5",
1447
+ className: "canopy-modal__nav-toggle-icon"
1448
+ },
1449
+ /* @__PURE__ */ React15.createElement(
1450
+ "path",
1451
+ {
1452
+ strokeLinecap: "round",
1453
+ strokeLinejoin: "round",
1454
+ d: "M5 9l7 7 7-7"
1455
+ }
1456
+ )
1457
+ ),
1458
+ /* @__PURE__ */ React15.createElement("span", { className: "sr-only" }, toggleLabel)
1459
+ ) : null),
1460
+ hasChildren ? /* @__PURE__ */ React15.createElement(
1461
+ NavigationTree,
1462
+ {
1463
+ root: navRoot,
1464
+ parentKey: navData && navData.rootSegment ? navData.rootSegment : `root-${index}`,
1465
+ component: "div",
1466
+ className: "canopy-modal__section-nav canopy-modal__section-nav--nested",
1467
+ "aria-label": navData && navData.title ? `${navData.title} section navigation` : defaultSectionAriaLabel,
1468
+ "aria-hidden": defaultExpanded ? "false" : "true",
1469
+ hidden: !defaultExpanded,
1470
+ id: nestedId || void 0
1471
+ }
1472
+ ) : null
1473
+ );
1474
+ }))
1139
1475
  ),
1140
- hasSectionNav ? /* @__PURE__ */ React14.createElement(
1141
- "nav",
1476
+ hasSectionNav && !hasIntegratedSectionNav ? /* @__PURE__ */ React15.createElement(
1477
+ NavigationTree,
1142
1478
  {
1479
+ root: sectionNavigation.root,
1480
+ component: "nav",
1143
1481
  className: "canopy-modal__section-nav",
1144
- "aria-label": sectionAriaLabel
1145
- },
1146
- /* @__PURE__ */ React14.createElement("div", { className: "canopy-modal__section-label" }, sectionLabel),
1147
- /* @__PURE__ */ React14.createElement(SectionNavList, { root: sectionNavigation.root })
1482
+ "aria-label": defaultSectionAriaLabel,
1483
+ parentKey: "fallback-nav"
1484
+ }
1148
1485
  ) : null
1149
- ), /* @__PURE__ */ React14.createElement(
1486
+ ), /* @__PURE__ */ React15.createElement(
1150
1487
  CanopyModal,
1151
1488
  {
1152
1489
  id: "canopy-modal-search",
@@ -1159,7 +1496,7 @@ function CanopyHeader(props = {}) {
1159
1496
  closeDataAttr: "search",
1160
1497
  bodyClassName: "canopy-modal__body--search"
1161
1498
  },
1162
- /* @__PURE__ */ React14.createElement(
1499
+ /* @__PURE__ */ React15.createElement(
1163
1500
  SearchPanel,
1164
1501
  {
1165
1502
  label: searchLabel,
@@ -1167,18 +1504,18 @@ function CanopyHeader(props = {}) {
1167
1504
  placeholder: searchPlaceholder
1168
1505
  }
1169
1506
  )
1170
- ), /* @__PURE__ */ React14.createElement(HeaderScript, null));
1507
+ ), /* @__PURE__ */ React15.createElement(HeaderScript, null));
1171
1508
  }
1172
1509
 
1173
1510
  // ui/src/layout/CanopyFooter.jsx
1174
- import React15 from "react";
1511
+ import React16 from "react";
1175
1512
  function CanopyFooter({ className = "", children }) {
1176
1513
  const footerClassName = ["canopy-footer", className].filter(Boolean).join(" ");
1177
- return /* @__PURE__ */ React15.createElement("footer", { className: footerClassName }, /* @__PURE__ */ React15.createElement("div", { className: "canopy-footer__inner" }, children));
1514
+ return /* @__PURE__ */ React16.createElement("footer", { className: footerClassName }, /* @__PURE__ */ React16.createElement("div", { className: "canopy-footer__inner" }, children));
1178
1515
  }
1179
1516
 
1180
1517
  // ui/src/layout/TeaserCard.jsx
1181
- import React16 from "react";
1518
+ import React17 from "react";
1182
1519
  function TeaserCard({
1183
1520
  href = "",
1184
1521
  title = "",
@@ -1199,7 +1536,7 @@ function TeaserCard({
1199
1536
  ].filter(Boolean).join(" ");
1200
1537
  const showThumb = type === "work" && thumbnail;
1201
1538
  const metaLine = (Array.isArray(metadata) && metadata.length ? metadata.filter(Boolean) : summary ? [summary] : []).filter(Boolean).slice(0, 2).join(" \u2022 ");
1202
- return /* @__PURE__ */ React16.createElement(
1539
+ return /* @__PURE__ */ React17.createElement(
1203
1540
  Tag,
1204
1541
  {
1205
1542
  className: classes,
@@ -1207,21 +1544,22 @@ function TeaserCard({
1207
1544
  "data-canopy-item": href ? "" : void 0,
1208
1545
  ...rest
1209
1546
  },
1210
- showThumb ? /* @__PURE__ */ React16.createElement("div", { className: "canopy-search-teaser__thumb" }, /* @__PURE__ */ React16.createElement(
1547
+ showThumb ? /* @__PURE__ */ React17.createElement("div", { className: "canopy-search-teaser__thumb" }, /* @__PURE__ */ React17.createElement(
1211
1548
  "img",
1212
1549
  {
1213
1550
  src: thumbnail,
1214
1551
  alt: "",
1552
+ "aria-hidden": "true",
1215
1553
  loading: "lazy",
1216
1554
  className: "canopy-search-teaser__thumb-img"
1217
1555
  }
1218
1556
  )) : null,
1219
- /* @__PURE__ */ React16.createElement("div", { className: "canopy-search-teaser__text" }, /* @__PURE__ */ React16.createElement("span", { className: "canopy-search-teaser__title" }, title || href || "Untitled"), metaLine ? /* @__PURE__ */ React16.createElement("span", { className: "canopy-search-teaser__meta" }, metaLine) : null)
1557
+ /* @__PURE__ */ React17.createElement("div", { className: "canopy-search-teaser__text" }, /* @__PURE__ */ React17.createElement("span", { className: "canopy-search-teaser__title" }, title || href || "Untitled"), metaLine ? /* @__PURE__ */ React17.createElement("span", { className: "canopy-search-teaser__meta" }, metaLine) : null)
1220
1558
  );
1221
1559
  }
1222
1560
 
1223
1561
  // ui/src/layout/GoogleAnalytics.jsx
1224
- import React17 from "react";
1562
+ import React18 from "react";
1225
1563
  var GA_HOST = "https://www.googletagmanager.com/gtag/js";
1226
1564
  function GoogleAnalytics({ id }) {
1227
1565
  if (!id) return null;
@@ -1231,11 +1569,11 @@ function GoogleAnalytics({ id }) {
1231
1569
  gtag('js', new Date());
1232
1570
  gtag('config', '${id}');
1233
1571
  `;
1234
- return /* @__PURE__ */ React17.createElement(React17.Fragment, null, /* @__PURE__ */ React17.createElement("script", { async: true, src: `${GA_HOST}?id=${encodeURIComponent(id)}` }), /* @__PURE__ */ React17.createElement("script", { dangerouslySetInnerHTML: { __html: inlineConfig } }));
1572
+ return /* @__PURE__ */ React18.createElement(React18.Fragment, null, /* @__PURE__ */ React18.createElement("script", { async: true, src: `${GA_HOST}?id=${encodeURIComponent(id)}` }), /* @__PURE__ */ React18.createElement("script", { dangerouslySetInnerHTML: { __html: inlineConfig } }));
1235
1573
  }
1236
1574
 
1237
1575
  // ui/src/iiif/Viewer.jsx
1238
- import React18, { useEffect as useEffect2, useState as useState2 } from "react";
1576
+ import React19, { useEffect as useEffect2, useState as useState2 } from "react";
1239
1577
  var DEFAULT_VIEWER_OPTIONS = {
1240
1578
  showDownload: false,
1241
1579
  showIIIFBadge: false,
@@ -1291,7 +1629,7 @@ var Viewer = (props) => {
1291
1629
  } catch (_) {
1292
1630
  json = "{}";
1293
1631
  }
1294
- return /* @__PURE__ */ React18.createElement("div", { "data-canopy-viewer": "1", className: "not-prose" }, /* @__PURE__ */ React18.createElement(
1632
+ return /* @__PURE__ */ React19.createElement("div", { "data-canopy-viewer": "1", className: "not-prose" }, /* @__PURE__ */ React19.createElement(
1295
1633
  "script",
1296
1634
  {
1297
1635
  type: "application/json",
@@ -1299,11 +1637,11 @@ var Viewer = (props) => {
1299
1637
  }
1300
1638
  ));
1301
1639
  }
1302
- return /* @__PURE__ */ React18.createElement(CloverViewer, { ...props, options: mergedOptions });
1640
+ return /* @__PURE__ */ React19.createElement(CloverViewer, { ...props, options: mergedOptions });
1303
1641
  };
1304
1642
 
1305
1643
  // ui/src/iiif/Slider.jsx
1306
- import React19, { useEffect as useEffect3, useState as useState3 } from "react";
1644
+ import React20, { useEffect as useEffect3, useState as useState3 } from "react";
1307
1645
 
1308
1646
  // ui/src/iiif/sliderOptions.js
1309
1647
  var UNIT_TOKEN = "__canopySliderUnit";
@@ -1450,7 +1788,7 @@ var Slider = (props = {}) => {
1450
1788
  } catch (_) {
1451
1789
  json = "{}";
1452
1790
  }
1453
- return /* @__PURE__ */ React19.createElement("div", { className: sliderClassName, "data-canopy-slider": "1" }, /* @__PURE__ */ React19.createElement(
1791
+ return /* @__PURE__ */ React20.createElement("div", { className: sliderClassName, "data-canopy-slider": "1" }, /* @__PURE__ */ React20.createElement(
1454
1792
  "script",
1455
1793
  {
1456
1794
  type: "application/json",
@@ -1458,11 +1796,11 @@ var Slider = (props = {}) => {
1458
1796
  }
1459
1797
  ));
1460
1798
  }
1461
- return /* @__PURE__ */ React19.createElement(CloverSlider, { ...resolvedProps });
1799
+ return /* @__PURE__ */ React20.createElement(CloverSlider, { ...resolvedProps });
1462
1800
  };
1463
1801
 
1464
1802
  // ui/src/iiif/Scroll.jsx
1465
- import React20, { useEffect as useEffect4, useState as useState4 } from "react";
1803
+ import React21, { useEffect as useEffect4, useState as useState4 } from "react";
1466
1804
  var Scroll = (props) => {
1467
1805
  const [CloverScroll, setCloverScroll] = useState4(null);
1468
1806
  useEffect4(() => {
@@ -1487,7 +1825,7 @@ var Scroll = (props) => {
1487
1825
  } catch (_) {
1488
1826
  json = "{}";
1489
1827
  }
1490
- return /* @__PURE__ */ React20.createElement("div", { "data-canopy-scroll": "1", className: "not-prose" }, /* @__PURE__ */ React20.createElement(
1828
+ return /* @__PURE__ */ React21.createElement("div", { "data-canopy-scroll": "1", className: "not-prose" }, /* @__PURE__ */ React21.createElement(
1491
1829
  "script",
1492
1830
  {
1493
1831
  type: "application/json",
@@ -1495,11 +1833,11 @@ var Scroll = (props) => {
1495
1833
  }
1496
1834
  ));
1497
1835
  }
1498
- return /* @__PURE__ */ React20.createElement(CloverScroll, { ...props });
1836
+ return /* @__PURE__ */ React21.createElement(CloverScroll, { ...props });
1499
1837
  };
1500
1838
 
1501
1839
  // ui/src/iiif/Image.jsx
1502
- import React21, { useEffect as useEffect5, useState as useState5 } from "react";
1840
+ import React22, { useEffect as useEffect5, useState as useState5 } from "react";
1503
1841
  var Image = (props = {}) => {
1504
1842
  const [CloverImage, setCloverImage] = useState5(null);
1505
1843
  const baseClass = "canopy-iiif-image";
@@ -1532,7 +1870,7 @@ var Image = (props = {}) => {
1532
1870
  } catch (_) {
1533
1871
  json = "{}";
1534
1872
  }
1535
- return /* @__PURE__ */ React21.createElement("figure", { className: rootClassName }, /* @__PURE__ */ React21.createElement(
1873
+ return /* @__PURE__ */ React22.createElement("figure", { className: rootClassName }, /* @__PURE__ */ React22.createElement(
1536
1874
  "div",
1537
1875
  {
1538
1876
  className: `${baseClass}__placeholder`,
@@ -1542,26 +1880,26 @@ var Image = (props = {}) => {
1542
1880
  "--canopy-iiif-image-bg": backgroundColor
1543
1881
  }
1544
1882
  },
1545
- /* @__PURE__ */ React21.createElement(
1883
+ /* @__PURE__ */ React22.createElement(
1546
1884
  "script",
1547
1885
  {
1548
1886
  type: "application/json",
1549
1887
  dangerouslySetInnerHTML: { __html: json }
1550
1888
  }
1551
1889
  )
1552
- ), caption && /* @__PURE__ */ React21.createElement("figcaption", { className: `${baseClass}__caption` }, caption));
1890
+ ), caption && /* @__PURE__ */ React22.createElement("figcaption", { className: `${baseClass}__caption` }, caption));
1553
1891
  }
1554
- return /* @__PURE__ */ React21.createElement(CloverImage, { ...props, className: rootClassName });
1892
+ return /* @__PURE__ */ React22.createElement(CloverImage, { ...props, className: rootClassName });
1555
1893
  };
1556
1894
 
1557
1895
  // ui/src/iiif/Properties/Id.jsx
1558
- import React22 from "react";
1896
+ import React23 from "react";
1559
1897
  function Id({ title = "IIIF Manifest", id, ...props }) {
1560
- return /* @__PURE__ */ React22.createElement("dl", null, /* @__PURE__ */ React22.createElement("dt", null, title), /* @__PURE__ */ React22.createElement("dd", null, /* @__PURE__ */ React22.createElement("a", { href: id }, id)));
1898
+ return /* @__PURE__ */ React23.createElement("dl", null, /* @__PURE__ */ React23.createElement("dt", null, title), /* @__PURE__ */ React23.createElement("dd", null, /* @__PURE__ */ React23.createElement("a", { href: id }, id)));
1561
1899
  }
1562
1900
 
1563
1901
  // ui/src/iiif/MdxRelatedItems.jsx
1564
- import React23 from "react";
1902
+ import React24 from "react";
1565
1903
  function MdxRelatedItems(props) {
1566
1904
  let json = "{}";
1567
1905
  try {
@@ -1569,7 +1907,7 @@ function MdxRelatedItems(props) {
1569
1907
  } catch (_) {
1570
1908
  json = "{}";
1571
1909
  }
1572
- return /* @__PURE__ */ React23.createElement("div", { "data-canopy-related-items": "1" }, /* @__PURE__ */ React23.createElement(
1910
+ return /* @__PURE__ */ React24.createElement("div", { "data-canopy-related-items": "1" }, /* @__PURE__ */ React24.createElement(
1573
1911
  "script",
1574
1912
  {
1575
1913
  type: "application/json",
@@ -1579,7 +1917,7 @@ function MdxRelatedItems(props) {
1579
1917
  }
1580
1918
 
1581
1919
  // ui/src/search/MdxSearchResults.jsx
1582
- import React24 from "react";
1920
+ import React25 from "react";
1583
1921
  function MdxSearchResults(props) {
1584
1922
  let json = "{}";
1585
1923
  try {
@@ -1587,11 +1925,11 @@ function MdxSearchResults(props) {
1587
1925
  } catch (_) {
1588
1926
  json = "{}";
1589
1927
  }
1590
- return /* @__PURE__ */ React24.createElement("div", { "data-canopy-search-results": "1" }, /* @__PURE__ */ React24.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1928
+ return /* @__PURE__ */ React25.createElement("div", { "data-canopy-search-results": "1" }, /* @__PURE__ */ React25.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1591
1929
  }
1592
1930
 
1593
1931
  // ui/src/search/SearchSummary.jsx
1594
- import React25 from "react";
1932
+ import React26 from "react";
1595
1933
  function SearchSummary(props) {
1596
1934
  let json = "{}";
1597
1935
  try {
@@ -1599,11 +1937,11 @@ function SearchSummary(props) {
1599
1937
  } catch (_) {
1600
1938
  json = "{}";
1601
1939
  }
1602
- return /* @__PURE__ */ React25.createElement("div", { "data-canopy-search-summary": "1" }, /* @__PURE__ */ React25.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1940
+ return /* @__PURE__ */ React26.createElement("div", { "data-canopy-search-summary": "1" }, /* @__PURE__ */ React26.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1603
1941
  }
1604
1942
 
1605
1943
  // ui/src/search/MdxSearchTabs.jsx
1606
- import React26 from "react";
1944
+ import React27 from "react";
1607
1945
  function MdxSearchTabs(props) {
1608
1946
  let json = "{}";
1609
1947
  try {
@@ -1611,11 +1949,11 @@ function MdxSearchTabs(props) {
1611
1949
  } catch (_) {
1612
1950
  json = "{}";
1613
1951
  }
1614
- return /* @__PURE__ */ React26.createElement("div", { "data-canopy-search-tabs": "1" }, /* @__PURE__ */ React26.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1952
+ return /* @__PURE__ */ React27.createElement("div", { "data-canopy-search-tabs": "1" }, /* @__PURE__ */ React27.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1615
1953
  }
1616
1954
 
1617
1955
  // ui/src/search/MdxSearch.jsx
1618
- import React27 from "react";
1956
+ import React28 from "react";
1619
1957
  function MdxSearch(props = {}) {
1620
1958
  const {
1621
1959
  layout,
@@ -1633,15 +1971,15 @@ function MdxSearch(props = {}) {
1633
1971
  resultsPayload.layout = layout;
1634
1972
  }
1635
1973
  const classes = ["canopy-search", className].filter(Boolean).join(" ");
1636
- return /* @__PURE__ */ React27.createElement("section", { className: classes, "data-canopy-search": "1" }, showTabs ? /* @__PURE__ */ React27.createElement(MdxSearchTabs, { ...tabsProps }) : null, showSummary ? /* @__PURE__ */ React27.createElement(SearchSummary, { ...summaryProps }) : null, showResults ? /* @__PURE__ */ React27.createElement(MdxSearchResults, { ...resultsPayload }) : null, children || null);
1974
+ return /* @__PURE__ */ React28.createElement("section", { className: classes, "data-canopy-search": "1" }, showTabs ? /* @__PURE__ */ React28.createElement(MdxSearchTabs, { ...tabsProps }) : null, showSummary ? /* @__PURE__ */ React28.createElement(SearchSummary, { ...summaryProps }) : null, showResults ? /* @__PURE__ */ React28.createElement(MdxSearchResults, { ...resultsPayload }) : null, children || null);
1637
1975
  }
1638
1976
 
1639
1977
  // ui/src/search/SearchResults.jsx
1640
- import React28 from "react";
1978
+ import React29 from "react";
1641
1979
  function DefaultArticleTemplate({ record, query }) {
1642
1980
  if (!record) return null;
1643
1981
  const metadata = Array.isArray(record.metadata) ? record.metadata : [];
1644
- return /* @__PURE__ */ React28.createElement(
1982
+ return /* @__PURE__ */ React29.createElement(
1645
1983
  ArticleCard,
1646
1984
  {
1647
1985
  href: record.href,
@@ -1659,7 +1997,7 @@ function DefaultFigureTemplate({ record, thumbnailAspectRatio }) {
1659
1997
  if (!record) return null;
1660
1998
  const hasDims = Number.isFinite(Number(record.thumbnailWidth)) && Number(record.thumbnailWidth) > 0 && Number.isFinite(Number(record.thumbnailHeight)) && Number(record.thumbnailHeight) > 0;
1661
1999
  const aspect = Number.isFinite(Number(thumbnailAspectRatio)) && Number(thumbnailAspectRatio) > 0 ? Number(thumbnailAspectRatio) : hasDims ? Number(record.thumbnailWidth) / Number(record.thumbnailHeight) : void 0;
1662
- return /* @__PURE__ */ React28.createElement(
2000
+ return /* @__PURE__ */ React29.createElement(
1663
2001
  Card,
1664
2002
  {
1665
2003
  href: record.href,
@@ -1680,7 +2018,7 @@ function SearchResults({
1680
2018
  variant = "auto"
1681
2019
  }) {
1682
2020
  if (!results.length) {
1683
- return /* @__PURE__ */ React28.createElement("div", { className: "text-slate-600" }, /* @__PURE__ */ React28.createElement("em", null, "No results"));
2021
+ return /* @__PURE__ */ React29.createElement("div", { className: "text-slate-600" }, /* @__PURE__ */ React29.createElement("em", null, "No results"));
1684
2022
  }
1685
2023
  const normalizedType = String(type || "all").toLowerCase();
1686
2024
  const normalizedVariant = variant === "figure" || variant === "article" ? variant : "auto";
@@ -1688,9 +2026,9 @@ function SearchResults({
1688
2026
  const FigureTemplate = templates && templates.figure ? templates.figure : DefaultFigureTemplate;
1689
2027
  const ArticleTemplate = templates && templates.article ? templates.article : DefaultArticleTemplate;
1690
2028
  if (isAnnotationView) {
1691
- return /* @__PURE__ */ React28.createElement("div", { id: "search-results", className: "space-y-4" }, results.map((r, i) => {
2029
+ return /* @__PURE__ */ React29.createElement("div", { id: "search-results", className: "space-y-4", role: "region", "aria-label": "Search results" }, results.map((r, i) => {
1692
2030
  if (!r) return null;
1693
- return /* @__PURE__ */ React28.createElement(
2031
+ return /* @__PURE__ */ React29.createElement(
1694
2032
  ArticleTemplate,
1695
2033
  {
1696
2034
  key: r.id || i,
@@ -1708,20 +2046,20 @@ function SearchResults({
1708
2046
  return !isWorkRecord(record) || normalizedType !== "work";
1709
2047
  };
1710
2048
  if (layout === "list") {
1711
- return /* @__PURE__ */ React28.createElement("div", { id: "search-results", className: "space-y-6" }, results.map((r, i) => {
2049
+ return /* @__PURE__ */ React29.createElement("div", { id: "search-results", className: "space-y-6", role: "region", "aria-label": "Search results" }, results.map((r, i) => {
1712
2050
  if (shouldRenderAsArticle(r)) {
1713
- return /* @__PURE__ */ React28.createElement("div", { key: i, className: `search-result ${r && r.type}` }, /* @__PURE__ */ React28.createElement(ArticleTemplate, { record: r, query, layout }));
2051
+ return /* @__PURE__ */ React29.createElement("div", { key: i, className: `search-result ${r && r.type}` }, /* @__PURE__ */ React29.createElement(ArticleTemplate, { record: r, query, layout }));
1714
2052
  }
1715
2053
  const hasDims = Number.isFinite(Number(r.thumbnailWidth)) && Number(r.thumbnailWidth) > 0 && Number.isFinite(Number(r.thumbnailHeight)) && Number(r.thumbnailHeight) > 0;
1716
2054
  const aspect = hasDims ? Number(r.thumbnailWidth) / Number(r.thumbnailHeight) : void 0;
1717
- return /* @__PURE__ */ React28.createElement(
2055
+ return /* @__PURE__ */ React29.createElement(
1718
2056
  "div",
1719
2057
  {
1720
2058
  key: i,
1721
2059
  className: `search-result ${r.type}`,
1722
2060
  "data-thumbnail-aspect-ratio": aspect
1723
2061
  },
1724
- /* @__PURE__ */ React28.createElement(
2062
+ /* @__PURE__ */ React29.createElement(
1725
2063
  FigureTemplate,
1726
2064
  {
1727
2065
  record: r,
@@ -1733,20 +2071,20 @@ function SearchResults({
1733
2071
  );
1734
2072
  }));
1735
2073
  }
1736
- return /* @__PURE__ */ React28.createElement("div", { id: "search-results" }, /* @__PURE__ */ React28.createElement(Grid, null, results.map((r, i) => {
2074
+ return /* @__PURE__ */ React29.createElement("div", { id: "search-results", role: "region", "aria-label": "Search results" }, /* @__PURE__ */ React29.createElement(Grid, null, results.map((r, i) => {
1737
2075
  if (shouldRenderAsArticle(r)) {
1738
- return /* @__PURE__ */ React28.createElement(GridItem, { key: i, className: `search-result ${r && r.type}` }, /* @__PURE__ */ React28.createElement(ArticleTemplate, { record: r, query, layout }));
2076
+ return /* @__PURE__ */ React29.createElement(GridItem, { key: i, className: `search-result ${r && r.type}` }, /* @__PURE__ */ React29.createElement(ArticleTemplate, { record: r, query, layout }));
1739
2077
  }
1740
2078
  const hasDims = Number.isFinite(Number(r.thumbnailWidth)) && Number(r.thumbnailWidth) > 0 && Number.isFinite(Number(r.thumbnailHeight)) && Number(r.thumbnailHeight) > 0;
1741
2079
  const aspect = hasDims ? Number(r.thumbnailWidth) / Number(r.thumbnailHeight) : void 0;
1742
- return /* @__PURE__ */ React28.createElement(
2080
+ return /* @__PURE__ */ React29.createElement(
1743
2081
  GridItem,
1744
2082
  {
1745
2083
  key: i,
1746
2084
  className: `search-result ${r.type}`,
1747
2085
  "data-thumbnail-aspect-ratio": aspect
1748
2086
  },
1749
- /* @__PURE__ */ React28.createElement(
2087
+ /* @__PURE__ */ React29.createElement(
1750
2088
  FigureTemplate,
1751
2089
  {
1752
2090
  record: r,
@@ -1760,7 +2098,7 @@ function SearchResults({
1760
2098
  }
1761
2099
 
1762
2100
  // ui/src/search/SearchTabs.jsx
1763
- import React29, { useRef as useRef2, useState as useState6, useEffect as useEffect6 } from "react";
2101
+ import React30, { useRef as useRef2, useState as useState6, useEffect as useEffect6 } from "react";
1764
2102
  function SearchTabs({
1765
2103
  type = "all",
1766
2104
  onTypeChange,
@@ -1814,7 +2152,7 @@ function SearchTabs({
1814
2152
  width: `${itemBoundingBox.width}px`
1815
2153
  };
1816
2154
  }
1817
- return /* @__PURE__ */ React29.createElement("div", { className: "canopy-search-tabs-wrapper" }, /* @__PURE__ */ React29.createElement(
2155
+ return /* @__PURE__ */ React30.createElement("div", { className: "canopy-search-tabs-wrapper" }, /* @__PURE__ */ React30.createElement(
1818
2156
  "div",
1819
2157
  {
1820
2158
  role: "tablist",
@@ -1823,7 +2161,7 @@ function SearchTabs({
1823
2161
  ref: wrapperRef,
1824
2162
  onMouseLeave: resetHighlight
1825
2163
  },
1826
- /* @__PURE__ */ React29.createElement(
2164
+ /* @__PURE__ */ React30.createElement(
1827
2165
  "div",
1828
2166
  {
1829
2167
  ref: highlightRef,
@@ -1835,7 +2173,7 @@ function SearchTabs({
1835
2173
  const active = String(type).toLowerCase() === String(t).toLowerCase();
1836
2174
  const cRaw = counts && Object.prototype.hasOwnProperty.call(counts, t) ? counts[t] : void 0;
1837
2175
  const c = Number.isFinite(Number(cRaw)) ? Number(cRaw) : 0;
1838
- return /* @__PURE__ */ React29.createElement(
2176
+ return /* @__PURE__ */ React30.createElement(
1839
2177
  "button",
1840
2178
  {
1841
2179
  key: t,
@@ -1853,7 +2191,7 @@ function SearchTabs({
1853
2191
  ")"
1854
2192
  );
1855
2193
  })
1856
- ), hasFilters ? /* @__PURE__ */ React29.createElement(
2194
+ ), hasFilters ? /* @__PURE__ */ React30.createElement(
1857
2195
  "button",
1858
2196
  {
1859
2197
  type: "button",
@@ -1861,12 +2199,12 @@ function SearchTabs({
1861
2199
  "aria-expanded": filtersOpen ? "true" : "false",
1862
2200
  className: "inline-flex items-center gap-2 rounded-md border border-slate-200 bg-white px-3 py-1.5 text-sm font-medium text-slate-700 shadow-sm transition hover:border-brand-200 hover:bg-brand-50 hover:text-brand-700"
1863
2201
  },
1864
- /* @__PURE__ */ React29.createElement("span", null, filtersLabel, filterBadge)
2202
+ /* @__PURE__ */ React30.createElement("span", null, filtersLabel, filterBadge)
1865
2203
  ) : null);
1866
2204
  }
1867
2205
 
1868
2206
  // ui/src/search/SearchFiltersDialog.jsx
1869
- import React30 from "react";
2207
+ import React31 from "react";
1870
2208
  function toArray(input) {
1871
2209
  if (!input) return [];
1872
2210
  if (Array.isArray(input)) return input;
@@ -1905,20 +2243,20 @@ function FacetSection({ facet, selected, onToggle }) {
1905
2243
  const selectedValues = selected.get(String(slug)) || /* @__PURE__ */ new Set();
1906
2244
  const checkboxId = (valueSlug) => `filter-${slug}-${valueSlug}`;
1907
2245
  const hasSelection = selectedValues.size > 0;
1908
- const [quickQuery, setQuickQuery] = React30.useState("");
2246
+ const [quickQuery, setQuickQuery] = React31.useState("");
1909
2247
  const hasQuery = quickQuery.trim().length > 0;
1910
- const filteredValues = React30.useMemo(
2248
+ const filteredValues = React31.useMemo(
1911
2249
  () => facetMatches(values, quickQuery),
1912
2250
  [values, quickQuery]
1913
2251
  );
1914
- return /* @__PURE__ */ React30.createElement(
2252
+ return /* @__PURE__ */ React31.createElement(
1915
2253
  "details",
1916
2254
  {
1917
2255
  className: "canopy-search-filters__facet",
1918
2256
  open: hasSelection
1919
2257
  },
1920
- /* @__PURE__ */ React30.createElement("summary", { className: "canopy-search-filters__facet-summary" }, /* @__PURE__ */ React30.createElement("span", null, label), /* @__PURE__ */ React30.createElement("span", { className: "canopy-search-filters__facet-count" }, values.length)),
1921
- /* @__PURE__ */ React30.createElement("div", { className: "canopy-search-filters__facet-content" }, /* @__PURE__ */ React30.createElement("div", { className: "canopy-search-filters__quick" }, /* @__PURE__ */ React30.createElement(
2258
+ /* @__PURE__ */ React31.createElement("summary", { className: "canopy-search-filters__facet-summary" }, /* @__PURE__ */ React31.createElement("span", null, label), /* @__PURE__ */ React31.createElement("span", { className: "canopy-search-filters__facet-count" }, values.length)),
2259
+ /* @__PURE__ */ React31.createElement("div", { className: "canopy-search-filters__facet-content" }, /* @__PURE__ */ React31.createElement("div", { className: "canopy-search-filters__quick" }, /* @__PURE__ */ React31.createElement(
1922
2260
  "input",
1923
2261
  {
1924
2262
  type: "search",
@@ -1928,19 +2266,20 @@ function FacetSection({ facet, selected, onToggle }) {
1928
2266
  className: "canopy-search-filters__quick-input",
1929
2267
  "aria-label": `Filter ${label} values`
1930
2268
  }
1931
- ), quickQuery ? /* @__PURE__ */ React30.createElement(
2269
+ ), quickQuery ? /* @__PURE__ */ React31.createElement(
1932
2270
  "button",
1933
2271
  {
1934
2272
  type: "button",
1935
2273
  onClick: () => setQuickQuery(""),
1936
- className: "canopy-search-filters__quick-clear"
2274
+ className: "canopy-search-filters__quick-clear",
2275
+ "aria-label": `Clear ${label} filter search`
1937
2276
  },
1938
2277
  "Clear"
1939
- ) : null), hasQuery && !filteredValues.length ? /* @__PURE__ */ React30.createElement("p", { className: "canopy-search-filters__facet-notice" }, "No matches found.") : null, /* @__PURE__ */ React30.createElement("ul", { className: "canopy-search-filters__facet-list" }, filteredValues.map((entry) => {
2278
+ ) : null), hasQuery && !filteredValues.length ? /* @__PURE__ */ React31.createElement("p", { className: "canopy-search-filters__facet-notice" }, "No matches found.") : null, /* @__PURE__ */ React31.createElement("ul", { className: "canopy-search-filters__facet-list" }, filteredValues.map((entry) => {
1940
2279
  const valueSlug = String(entry.slug || entry.value || "");
1941
2280
  const isChecked = selectedValues.has(valueSlug);
1942
2281
  const inputId = checkboxId(valueSlug);
1943
- return /* @__PURE__ */ React30.createElement("li", { key: valueSlug, className: "canopy-search-filters__facet-item" }, /* @__PURE__ */ React30.createElement(
2282
+ return /* @__PURE__ */ React31.createElement("li", { key: valueSlug, className: "canopy-search-filters__facet-item" }, /* @__PURE__ */ React31.createElement(
1944
2283
  "input",
1945
2284
  {
1946
2285
  id: inputId,
@@ -1952,15 +2291,15 @@ function FacetSection({ facet, selected, onToggle }) {
1952
2291
  if (onToggle) onToggle(slug, valueSlug, nextChecked);
1953
2292
  }
1954
2293
  }
1955
- ), /* @__PURE__ */ React30.createElement(
2294
+ ), /* @__PURE__ */ React31.createElement(
1956
2295
  "label",
1957
2296
  {
1958
2297
  htmlFor: inputId,
1959
2298
  className: "canopy-search-filters__facet-label"
1960
2299
  },
1961
- /* @__PURE__ */ React30.createElement("span", null, entry.value, " ", Number.isFinite(entry.doc_count) ? /* @__PURE__ */ React30.createElement("span", { className: "canopy-search-filters__facet-count" }, "(", entry.doc_count, ")") : null)
2300
+ /* @__PURE__ */ React31.createElement("span", null, entry.value, " ", Number.isFinite(entry.doc_count) ? /* @__PURE__ */ React31.createElement("span", { className: "canopy-search-filters__facet-count" }, "(", entry.doc_count, ")") : null)
1962
2301
  ));
1963
- }), !filteredValues.length && !hasQuery ? /* @__PURE__ */ React30.createElement("li", { className: "canopy-search-filters__facet-empty" }, "No values available.") : null))
2302
+ }), !filteredValues.length && !hasQuery ? /* @__PURE__ */ React31.createElement("li", { className: "canopy-search-filters__facet-empty" }, "No values available.") : null))
1964
2303
  );
1965
2304
  }
1966
2305
  function SearchFiltersDialog(props = {}) {
@@ -1982,7 +2321,7 @@ function SearchFiltersDialog(props = {}) {
1982
2321
  (total, set) => total + set.size,
1983
2322
  0
1984
2323
  );
1985
- React30.useEffect(() => {
2324
+ React31.useEffect(() => {
1986
2325
  if (!open) return void 0;
1987
2326
  if (typeof document === "undefined") return void 0;
1988
2327
  const body = document.body;
@@ -1999,7 +2338,7 @@ function SearchFiltersDialog(props = {}) {
1999
2338
  if (!open) return null;
2000
2339
  const brandId = "canopy-modal-filters-label";
2001
2340
  const subtitleText = subtitle != null ? subtitle : title;
2002
- return /* @__PURE__ */ React30.createElement(
2341
+ return /* @__PURE__ */ React31.createElement(
2003
2342
  CanopyModal,
2004
2343
  {
2005
2344
  id: "canopy-modal-filters",
@@ -2014,8 +2353,8 @@ function SearchFiltersDialog(props = {}) {
2014
2353
  onBackgroundClick: () => onOpenChange && onOpenChange(false),
2015
2354
  bodyClassName: "canopy-modal__body--filters"
2016
2355
  },
2017
- subtitleText ? /* @__PURE__ */ React30.createElement("p", { className: "canopy-search-filters__subtitle" }, subtitleText) : null,
2018
- /* @__PURE__ */ React30.createElement("div", { className: "canopy-search-filters__body" }, Array.isArray(facets) && facets.length ? /* @__PURE__ */ React30.createElement("div", { className: "canopy-search-filters__facets" }, facets.map((facet) => /* @__PURE__ */ React30.createElement(
2356
+ subtitleText ? /* @__PURE__ */ React31.createElement("p", { className: "canopy-search-filters__subtitle" }, subtitleText) : null,
2357
+ /* @__PURE__ */ React31.createElement("div", { className: "canopy-search-filters__body" }, Array.isArray(facets) && facets.length ? /* @__PURE__ */ React31.createElement("div", { className: "canopy-search-filters__facets" }, facets.map((facet) => /* @__PURE__ */ React31.createElement(
2019
2358
  FacetSection,
2020
2359
  {
2021
2360
  key: facet.slug || facet.label,
@@ -2023,8 +2362,8 @@ function SearchFiltersDialog(props = {}) {
2023
2362
  selected: selectedMap,
2024
2363
  onToggle
2025
2364
  }
2026
- ))) : /* @__PURE__ */ React30.createElement("p", { className: "canopy-search-filters__empty" }, "No filters are available for this collection.")),
2027
- /* @__PURE__ */ React30.createElement("footer", { className: "canopy-search-filters__footer" }, /* @__PURE__ */ React30.createElement("div", null, activeCount ? `${activeCount} filter${activeCount === 1 ? "" : "s"} applied` : "No filters applied"), /* @__PURE__ */ React30.createElement("div", { className: "canopy-search-filters__footer-actions" }, /* @__PURE__ */ React30.createElement(
2365
+ ))) : /* @__PURE__ */ React31.createElement("p", { className: "canopy-search-filters__empty" }, "No filters are available for this collection.")),
2366
+ /* @__PURE__ */ React31.createElement("footer", { className: "canopy-search-filters__footer" }, /* @__PURE__ */ React31.createElement("div", null, activeCount ? `${activeCount} filter${activeCount === 1 ? "" : "s"} applied` : "No filters applied"), /* @__PURE__ */ React31.createElement("div", { className: "canopy-search-filters__footer-actions" }, /* @__PURE__ */ React31.createElement(
2028
2367
  "button",
2029
2368
  {
2030
2369
  type: "button",
@@ -2035,7 +2374,7 @@ function SearchFiltersDialog(props = {}) {
2035
2374
  className: "canopy-search-filters__button canopy-search-filters__button--secondary"
2036
2375
  },
2037
2376
  "Clear all"
2038
- ), /* @__PURE__ */ React30.createElement(
2377
+ ), /* @__PURE__ */ React31.createElement(
2039
2378
  "button",
2040
2379
  {
2041
2380
  type: "button",
@@ -2048,7 +2387,7 @@ function SearchFiltersDialog(props = {}) {
2048
2387
  }
2049
2388
 
2050
2389
  // ui/src/search-form/MdxSearchFormModal.jsx
2051
- import React31 from "react";
2390
+ import React32 from "react";
2052
2391
  function MdxSearchFormModal(props = {}) {
2053
2392
  const {
2054
2393
  placeholder = "Search\u2026",
@@ -2064,24 +2403,24 @@ function MdxSearchFormModal(props = {}) {
2064
2403
  const text = typeof label === "string" && label.trim() ? label.trim() : buttonLabel;
2065
2404
  const resolvedSearchPath = resolveSearchPath(searchPath);
2066
2405
  const data = { placeholder, hotkey, maxResults, groupOrder, label: text, searchPath: resolvedSearchPath };
2067
- return /* @__PURE__ */ React31.createElement("div", { "data-canopy-search-form": true, className: "flex-1 min-w-0" }, /* @__PURE__ */ React31.createElement("div", { className: "relative w-full" }, /* @__PURE__ */ React31.createElement(SearchPanelForm, { placeholder, buttonLabel, label, searchPath: resolvedSearchPath }), /* @__PURE__ */ React31.createElement(SearchPanelTeaserResults, null)), /* @__PURE__ */ React31.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: JSON.stringify(data) } }));
2406
+ return /* @__PURE__ */ React32.createElement("div", { "data-canopy-search-form": true, className: "flex-1 min-w-0" }, /* @__PURE__ */ React32.createElement("div", { className: "relative w-full" }, /* @__PURE__ */ React32.createElement(SearchPanelForm, { placeholder, buttonLabel, label, searchPath: resolvedSearchPath }), /* @__PURE__ */ React32.createElement(SearchPanelTeaserResults, null)), /* @__PURE__ */ React32.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: JSON.stringify(data) } }));
2068
2407
  }
2069
2408
 
2070
2409
  // ui/src/docs/MarkdownTable.jsx
2071
- import React32 from "react";
2410
+ import React33 from "react";
2072
2411
  function MarkdownTable({ className = "", ...rest }) {
2073
2412
  const merged = ["markdown-table", className].filter(Boolean).join(" ");
2074
- return /* @__PURE__ */ React32.createElement("div", { className: "markdown-table__frame" }, /* @__PURE__ */ React32.createElement("table", { className: merged, ...rest }));
2413
+ return /* @__PURE__ */ React33.createElement("div", { className: "markdown-table__frame" }, /* @__PURE__ */ React33.createElement("table", { className: merged, ...rest }));
2075
2414
  }
2076
2415
 
2077
2416
  // ui/src/docs/Diagram.jsx
2078
- import React33 from "react";
2417
+ import React34 from "react";
2079
2418
  function CanopyDiagram() {
2080
- return /* @__PURE__ */ React33.createElement("div", { className: "canopy-diagram" }, /* @__PURE__ */ React33.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--collections" }, /* @__PURE__ */ React33.createElement("h3", null, "IIIF Collection(s)"), /* @__PURE__ */ React33.createElement("span", { className: "canopy-diagram__section-summary" }, "Source collections contribute 105 total manifests that Canopy retrieves as-is via IIIF endpoints."), /* @__PURE__ */ React33.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Collection A"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "70 Manifests"), /* @__PURE__ */ React33.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React33.createElement("li", null, "Textual Annotations"))), /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Collection B"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "35 Manifests"), /* @__PURE__ */ React33.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React33.createElement("li", null, "Textual Annotations"))))), /* @__PURE__ */ React33.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React33.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React33.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React33.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--build" }, /* @__PURE__ */ React33.createElement("h3", null, "Canopy Build Process"), /* @__PURE__ */ React33.createElement("span", { className: "canopy-diagram__section-summary" }, "Canopy syncs manifests, page content, and annotations before bundling the site."), /* @__PURE__ */ React33.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Automated content"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "105 manifests \u2192 105 work pages"), /* @__PURE__ */ React33.createElement("li", null, "One page per manifest"), /* @__PURE__ */ React33.createElement("li", null, "Customize page layout"))), /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Contextual content"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "Markdown & MDX pages"), /* @__PURE__ */ React33.createElement("li", null, "Author narratives"), /* @__PURE__ */ React33.createElement("li", null, "Reference manifests inline"))), /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Search index"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "Combines works + pages"), /* @__PURE__ */ React33.createElement("li", null, "Customize result layout"), /* @__PURE__ */ React33.createElement("li", null, "Optional annotations"))))), /* @__PURE__ */ React33.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React33.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React33.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React33.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--output" }, /* @__PURE__ */ React33.createElement("h3", null, "Static Digital Project"), /* @__PURE__ */ React33.createElement("span", { className: "canopy-diagram__section-summary" }, "The output is a lightweight bundle of HTML, CSS, JS, and JSON assets that can deploy anywhere."), /* @__PURE__ */ React33.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Work pages"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "105 generated HTML pages"), /* @__PURE__ */ React33.createElement("li", null, "Each links back to source manifests"), /* @__PURE__ */ React33.createElement("li", null, "Styled with Canopy components"))), /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Custom pages"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "Markdown & MDX-authored content"), /* @__PURE__ */ React33.createElement("li", null, "Reusable layouts for narratives"), /* @__PURE__ */ React33.createElement("li", null, "Embed IIIF media & interstitials"))), /* @__PURE__ */ React33.createElement("article", null, /* @__PURE__ */ React33.createElement("h4", null, "Search bundle"), /* @__PURE__ */ React33.createElement("ul", null, /* @__PURE__ */ React33.createElement("li", null, "Static FlexSearch index"), /* @__PURE__ */ React33.createElement("li", null, "Works + pages share records"), /* @__PURE__ */ React33.createElement("li", null, "Optional annotation dataset"))))));
2419
+ return /* @__PURE__ */ React34.createElement("div", { className: "canopy-diagram" }, /* @__PURE__ */ React34.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--collections" }, /* @__PURE__ */ React34.createElement("h3", null, "IIIF Collection(s)"), /* @__PURE__ */ React34.createElement("span", { className: "canopy-diagram__section-summary" }, "Source collections contribute 105 total manifests that Canopy retrieves as-is via IIIF endpoints."), /* @__PURE__ */ React34.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Collection A"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "70 Manifests"), /* @__PURE__ */ React34.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React34.createElement("li", null, "Textual Annotations"))), /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Collection B"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "35 Manifests"), /* @__PURE__ */ React34.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React34.createElement("li", null, "Textual Annotations"))))), /* @__PURE__ */ React34.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React34.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React34.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React34.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--build" }, /* @__PURE__ */ React34.createElement("h3", null, "Canopy Build Process"), /* @__PURE__ */ React34.createElement("span", { className: "canopy-diagram__section-summary" }, "Canopy syncs manifests, page content, and annotations before bundling the site."), /* @__PURE__ */ React34.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Automated content"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "105 manifests \u2192 105 work pages"), /* @__PURE__ */ React34.createElement("li", null, "One page per manifest"), /* @__PURE__ */ React34.createElement("li", null, "Customize page layout"))), /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Contextual content"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "Markdown & MDX pages"), /* @__PURE__ */ React34.createElement("li", null, "Author narratives"), /* @__PURE__ */ React34.createElement("li", null, "Reference manifests inline"))), /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Search index"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "Combines works + pages"), /* @__PURE__ */ React34.createElement("li", null, "Customize result layout"), /* @__PURE__ */ React34.createElement("li", null, "Optional annotations"))))), /* @__PURE__ */ React34.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React34.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React34.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React34.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--output" }, /* @__PURE__ */ React34.createElement("h3", null, "Static Digital Project"), /* @__PURE__ */ React34.createElement("span", { className: "canopy-diagram__section-summary" }, "The output is a lightweight bundle of HTML, CSS, JS, and JSON assets that can deploy anywhere."), /* @__PURE__ */ React34.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Work pages"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "105 generated HTML pages"), /* @__PURE__ */ React34.createElement("li", null, "Each links back to source manifests"), /* @__PURE__ */ React34.createElement("li", null, "Styled with Canopy components"))), /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Custom pages"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "Markdown & MDX-authored content"), /* @__PURE__ */ React34.createElement("li", null, "Reusable layouts for narratives"), /* @__PURE__ */ React34.createElement("li", null, "Embed IIIF media & interstitials"))), /* @__PURE__ */ React34.createElement("article", null, /* @__PURE__ */ React34.createElement("h4", null, "Search bundle"), /* @__PURE__ */ React34.createElement("ul", null, /* @__PURE__ */ React34.createElement("li", null, "Static FlexSearch index"), /* @__PURE__ */ React34.createElement("li", null, "Works + pages share records"), /* @__PURE__ */ React34.createElement("li", null, "Optional annotation dataset"))))));
2081
2420
  }
2082
2421
 
2083
2422
  // ui/src/content/timeline/Timeline.jsx
2084
- import React35 from "react";
2423
+ import React36 from "react";
2085
2424
 
2086
2425
  // ui/src/content/timeline/date-utils.js
2087
2426
  var FALLBACK_LOCALE = (() => {
@@ -2234,7 +2573,7 @@ function clampProgress(value) {
2234
2573
  }
2235
2574
 
2236
2575
  // ui/src/layout/ReferencedManifestCard.jsx
2237
- import React34 from "react";
2576
+ import React35 from "react";
2238
2577
  function normalizeMetadata(metadata, summary) {
2239
2578
  if (Array.isArray(metadata) && metadata.length) {
2240
2579
  return metadata.filter(Boolean);
@@ -2268,7 +2607,7 @@ function ReferencedManifestCard({
2268
2607
  "canopy-referenced-manifest-card",
2269
2608
  className
2270
2609
  ].filter(Boolean).join(" ");
2271
- return /* @__PURE__ */ React34.createElement(
2610
+ return /* @__PURE__ */ React35.createElement(
2272
2611
  TeaserCard,
2273
2612
  {
2274
2613
  href: resolvedHref || void 0,
@@ -2450,14 +2789,14 @@ function TimelineConnector({ side, isActive, highlight }) {
2450
2789
  "canopy-timeline__connector-dot",
2451
2790
  highlight || isActive ? "is-active" : ""
2452
2791
  ].filter(Boolean).join(" ");
2453
- return /* @__PURE__ */ React35.createElement("span", { className: connectorClasses, "aria-hidden": "true" }, side === "left" ? /* @__PURE__ */ React35.createElement(React35.Fragment, null, /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__connector-line" }), /* @__PURE__ */ React35.createElement("span", { className: dotClasses })) : /* @__PURE__ */ React35.createElement(React35.Fragment, null, /* @__PURE__ */ React35.createElement("span", { className: dotClasses }), /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__connector-line" })));
2792
+ return /* @__PURE__ */ React36.createElement("span", { className: connectorClasses, "aria-hidden": "true" }, side === "left" ? /* @__PURE__ */ React36.createElement(React36.Fragment, null, /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__connector-line" }), /* @__PURE__ */ React36.createElement("span", { className: dotClasses })) : /* @__PURE__ */ React36.createElement(React36.Fragment, null, /* @__PURE__ */ React36.createElement("span", { className: dotClasses }), /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__connector-line" })));
2454
2793
  }
2455
2794
  function renderResourceSection(point) {
2456
2795
  if (!point) return null;
2457
2796
  const manifestCards = Array.isArray(point.manifests) ? point.manifests.filter(Boolean) : [];
2458
2797
  const legacyResources = Array.isArray(point.resources) ? point.resources.filter(Boolean) : [];
2459
2798
  if (!manifestCards.length && !legacyResources.length) return null;
2460
- return /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__resources" }, /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__resources-list" }, manifestCards.map((manifest) => /* @__PURE__ */ React35.createElement("div", { key: manifest.id || manifest.href }, /* @__PURE__ */ React35.createElement(ReferencedManifestCard, { manifest }))), legacyResources.map((resource, idx) => /* @__PURE__ */ React35.createElement("div", { key: resource.id || resource.href || `legacy-${idx}` }, /* @__PURE__ */ React35.createElement(
2799
+ return /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__resources" }, /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__resources-list" }, manifestCards.map((manifest) => /* @__PURE__ */ React36.createElement("div", { key: manifest.id || manifest.href }, /* @__PURE__ */ React36.createElement(ReferencedManifestCard, { manifest }))), legacyResources.map((resource, idx) => /* @__PURE__ */ React36.createElement("div", { key: resource.id || resource.href || `legacy-${idx}` }, /* @__PURE__ */ React36.createElement(
2461
2800
  TeaserCard,
2462
2801
  {
2463
2802
  href: resource.href,
@@ -2482,26 +2821,26 @@ function Timeline({
2482
2821
  ...rest
2483
2822
  }) {
2484
2823
  const payloadPoints = payload && Array.isArray(payload.points) ? payload.points : null;
2485
- const rawPoints = React35.useMemo(() => {
2824
+ const rawPoints = React36.useMemo(() => {
2486
2825
  if (Array.isArray(pointsProp) && pointsProp.length) return pointsProp;
2487
2826
  if (payloadPoints && payloadPoints.length) return payloadPoints;
2488
2827
  return [];
2489
2828
  }, [pointsProp, payloadPoints]);
2490
- const sanitizedPoints = React35.useMemo(
2829
+ const sanitizedPoints = React36.useMemo(
2491
2830
  () => sanitizePoints(rawPoints),
2492
2831
  [rawPoints]
2493
2832
  );
2494
2833
  const localeValue = payload && payload.locale ? payload.locale : localeProp;
2495
- const baseLocale = React35.useMemo(
2834
+ const baseLocale = React36.useMemo(
2496
2835
  () => createLocale(localeValue),
2497
2836
  [localeValue]
2498
2837
  );
2499
2838
  const rangeInput = payload && payload.range ? payload.range : rangeProp || {};
2500
- const rangeOverrides = React35.useMemo(
2839
+ const rangeOverrides = React36.useMemo(
2501
2840
  () => deriveRangeOverrides(sanitizedPoints, rangeInput),
2502
2841
  [sanitizedPoints, rangeInput]
2503
2842
  );
2504
- const effectiveRange = React35.useMemo(
2843
+ const effectiveRange = React36.useMemo(
2505
2844
  () => normalizeRange({
2506
2845
  ...rangeOverrides,
2507
2846
  locale: baseLocale
@@ -2510,7 +2849,7 @@ function Timeline({
2510
2849
  );
2511
2850
  const spanStart = effectiveRange.startDate.getTime();
2512
2851
  const span = effectiveRange.span;
2513
- const pointsWithPosition = React35.useMemo(() => {
2852
+ const pointsWithPosition = React36.useMemo(() => {
2514
2853
  if (!sanitizedPoints.length) return [];
2515
2854
  return sanitizedPoints.map((point, index) => {
2516
2855
  const timestamp = point.meta.timestamp;
@@ -2524,29 +2863,29 @@ function Timeline({
2524
2863
  };
2525
2864
  });
2526
2865
  }, [sanitizedPoints, spanStart, span]);
2527
- const [activeId, setActiveId] = React35.useState(
2866
+ const [activeId, setActiveId] = React36.useState(
2528
2867
  () => getActivePointId(pointsWithPosition)
2529
2868
  );
2530
- React35.useEffect(() => {
2869
+ React36.useEffect(() => {
2531
2870
  setActiveId(getActivePointId(pointsWithPosition));
2532
2871
  }, [pointsWithPosition]);
2533
2872
  const thresholdValue = typeof thresholdProp === "number" ? thresholdProp : payload && payload.threshold != null ? payload.threshold : null;
2534
2873
  const stepsValue = typeof steps === "number" ? Number(steps) : payload && typeof payload.steps === "number" ? Number(payload.steps) : null;
2535
- const thresholdMs = React35.useMemo(
2874
+ const thresholdMs = React36.useMemo(
2536
2875
  () => getThresholdMs(thresholdValue, effectiveRange.granularity),
2537
2876
  [thresholdValue, effectiveRange.granularity]
2538
2877
  );
2539
- const groupedEntries = React35.useMemo(
2878
+ const groupedEntries = React36.useMemo(
2540
2879
  () => buildGroupedEntries(pointsWithPosition, thresholdMs, {
2541
2880
  granularity: effectiveRange.granularity,
2542
2881
  locale: baseLocale
2543
2882
  }),
2544
2883
  [pointsWithPosition, thresholdMs, effectiveRange.granularity, baseLocale]
2545
2884
  );
2546
- const [expandedGroupIds, setExpandedGroupIds] = React35.useState(
2885
+ const [expandedGroupIds, setExpandedGroupIds] = React36.useState(
2547
2886
  () => /* @__PURE__ */ new Set()
2548
2887
  );
2549
- React35.useEffect(() => {
2888
+ React36.useEffect(() => {
2550
2889
  setExpandedGroupIds((prev) => {
2551
2890
  if (!prev || prev.size === 0) return prev;
2552
2891
  const validIds = new Set(
@@ -2561,7 +2900,7 @@ function Timeline({
2561
2900
  return changed ? next : prev;
2562
2901
  });
2563
2902
  }, [groupedEntries]);
2564
- const toggleGroup = React35.useCallback((groupId) => {
2903
+ const toggleGroup = React36.useCallback((groupId) => {
2565
2904
  setExpandedGroupIds((prev) => {
2566
2905
  const next = new Set(prev || []);
2567
2906
  if (next.has(groupId)) next.delete(groupId);
@@ -2584,7 +2923,7 @@ function Timeline({
2584
2923
  point.id === activeId ? "is-active" : "",
2585
2924
  point.highlight ? "is-highlighted" : ""
2586
2925
  ].filter(Boolean).join(" ");
2587
- const connector = /* @__PURE__ */ React35.createElement(
2926
+ const connector = /* @__PURE__ */ React36.createElement(
2588
2927
  TimelineConnector,
2589
2928
  {
2590
2929
  side: point.side,
@@ -2592,9 +2931,9 @@ function Timeline({
2592
2931
  highlight: point.highlight
2593
2932
  }
2594
2933
  );
2595
- const body = /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__point-body" }, /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label), /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__point-title" }, point.title), point.summary ? /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__point-summary" }, point.summary) : null);
2934
+ const body = /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__point-body" }, /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label), /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__point-title" }, point.title), point.summary ? /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__point-summary" }, point.summary) : null);
2596
2935
  const resourceSection = renderResourceSection(point);
2597
- return /* @__PURE__ */ React35.createElement(
2936
+ return /* @__PURE__ */ React36.createElement(
2598
2937
  "div",
2599
2938
  {
2600
2939
  key: point.id,
@@ -2602,7 +2941,7 @@ function Timeline({
2602
2941
  style: wrapperStyle,
2603
2942
  role: "listitem"
2604
2943
  },
2605
- point.side === "left" ? /* @__PURE__ */ React35.createElement(React35.Fragment, null, /* @__PURE__ */ React35.createElement("div", { className: cardClasses }, body, resourceSection), connector) : /* @__PURE__ */ React35.createElement(React35.Fragment, null, connector, /* @__PURE__ */ React35.createElement("div", { className: cardClasses }, body, resourceSection))
2944
+ point.side === "left" ? /* @__PURE__ */ React36.createElement(React36.Fragment, null, /* @__PURE__ */ React36.createElement("div", { className: cardClasses }, body, resourceSection), connector) : /* @__PURE__ */ React36.createElement(React36.Fragment, null, connector, /* @__PURE__ */ React36.createElement("div", { className: cardClasses }, body, resourceSection))
2606
2945
  );
2607
2946
  }
2608
2947
  function renderGroupEntry(entry) {
@@ -2613,7 +2952,7 @@ function Timeline({
2613
2952
  const wrapperStyle = { top: `${entry.progress * 100}%` };
2614
2953
  const isExpanded = expandedGroupIds.has(entry.id);
2615
2954
  const hasActivePoint = entry.points.some((point) => point.id === activeId);
2616
- const connector = /* @__PURE__ */ React35.createElement(
2955
+ const connector = /* @__PURE__ */ React36.createElement(
2617
2956
  TimelineConnector,
2618
2957
  {
2619
2958
  side: entry.side,
@@ -2627,7 +2966,7 @@ function Timeline({
2627
2966
  hasActivePoint ? "is-active" : ""
2628
2967
  ].filter(Boolean).join(" ");
2629
2968
  const countLabel = `${entry.count} event${entry.count > 1 ? "s" : ""}`;
2630
- const header = /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__group-header" }, /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__group-summary" }, /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__point-date" }, entry.label), /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__group-count" }, countLabel)), /* @__PURE__ */ React35.createElement(
2969
+ const header = /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__group-header" }, /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__group-summary" }, /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__point-date" }, entry.label), /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__group-count" }, countLabel)), /* @__PURE__ */ React36.createElement(
2631
2970
  "button",
2632
2971
  {
2633
2972
  type: "button",
@@ -2637,7 +2976,7 @@ function Timeline({
2637
2976
  },
2638
2977
  isExpanded ? "Hide details" : "Show details"
2639
2978
  ));
2640
- const groupPoints = isExpanded ? /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__group-points" }, entry.points.map((point) => /* @__PURE__ */ React35.createElement(
2979
+ const groupPoints = isExpanded ? /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__group-points" }, entry.points.map((point) => /* @__PURE__ */ React36.createElement(
2641
2980
  "button",
2642
2981
  {
2643
2982
  key: point.id,
@@ -2648,11 +2987,11 @@ function Timeline({
2648
2987
  ].filter(Boolean).join(" "),
2649
2988
  onClick: () => setActiveId(point.id)
2650
2989
  },
2651
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label),
2652
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__group-point-title" }, point.title)
2990
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label),
2991
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__group-point-title" }, point.title)
2653
2992
  ))) : null;
2654
- const groupCard = /* @__PURE__ */ React35.createElement("div", { className: groupClasses }, header, groupPoints);
2655
- return /* @__PURE__ */ React35.createElement(
2993
+ const groupCard = /* @__PURE__ */ React36.createElement("div", { className: groupClasses }, header, groupPoints);
2994
+ return /* @__PURE__ */ React36.createElement(
2656
2995
  "div",
2657
2996
  {
2658
2997
  key: entry.id,
@@ -2660,17 +2999,17 @@ function Timeline({
2660
2999
  style: wrapperStyle,
2661
3000
  role: "listitem"
2662
3001
  },
2663
- entry.side === "left" ? /* @__PURE__ */ React35.createElement(React35.Fragment, null, groupCard, connector) : /* @__PURE__ */ React35.createElement(React35.Fragment, null, connector, groupCard)
3002
+ entry.side === "left" ? /* @__PURE__ */ React36.createElement(React36.Fragment, null, groupCard, connector) : /* @__PURE__ */ React36.createElement(React36.Fragment, null, connector, groupCard)
2664
3003
  );
2665
3004
  }
2666
- return /* @__PURE__ */ React35.createElement("section", { className: containerClasses, ...rest }, title ? /* @__PURE__ */ React35.createElement("h2", { className: "canopy-timeline__title" }, title) : null, description ? /* @__PURE__ */ React35.createElement("p", { className: "canopy-timeline__description" }, description) : null, rangeLabel ? /* @__PURE__ */ React35.createElement("p", { className: "canopy-timeline__range", "aria-live": "polite" }, rangeLabel) : null, /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__body" }, /* @__PURE__ */ React35.createElement(
3005
+ return /* @__PURE__ */ React36.createElement("section", { className: containerClasses, ...rest }, title ? /* @__PURE__ */ React36.createElement("h2", { className: "canopy-timeline__title" }, title) : null, description ? /* @__PURE__ */ React36.createElement("p", { className: "canopy-timeline__description" }, description) : null, rangeLabel ? /* @__PURE__ */ React36.createElement("p", { className: "canopy-timeline__range", "aria-live": "polite" }, rangeLabel) : null, /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__body" }, /* @__PURE__ */ React36.createElement(
2667
3006
  "div",
2668
3007
  {
2669
3008
  className: "canopy-timeline__list",
2670
3009
  role: "list",
2671
3010
  style: { minHeight: trackHeight }
2672
3011
  },
2673
- /* @__PURE__ */ React35.createElement("div", { className: "canopy-timeline__spine", "aria-hidden": "true" }),
3012
+ /* @__PURE__ */ React36.createElement("div", { className: "canopy-timeline__spine", "aria-hidden": "true" }),
2674
3013
  renderSteps(stepsValue, effectiveRange),
2675
3014
  groupedEntries.map((entry) => {
2676
3015
  if (entry.type === "group") return renderGroupEntry(entry);
@@ -2685,7 +3024,7 @@ function renderSteps(stepSize, range) {
2685
3024
  const markers = [];
2686
3025
  if (startYear < endYear) {
2687
3026
  markers.push(
2688
- /* @__PURE__ */ React35.createElement(
3027
+ /* @__PURE__ */ React36.createElement(
2689
3028
  "span",
2690
3029
  {
2691
3030
  key: "timeline-step-start",
@@ -2693,12 +3032,12 @@ function renderSteps(stepSize, range) {
2693
3032
  style: { top: "0%" },
2694
3033
  "aria-hidden": "true"
2695
3034
  },
2696
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__step-line" }),
2697
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__step-label" }, startYear)
3035
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__step-line" }),
3036
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__step-label" }, startYear)
2698
3037
  )
2699
3038
  );
2700
3039
  markers.push(
2701
- /* @__PURE__ */ React35.createElement(
3040
+ /* @__PURE__ */ React36.createElement(
2702
3041
  "span",
2703
3042
  {
2704
3043
  key: "timeline-step-end",
@@ -2706,8 +3045,8 @@ function renderSteps(stepSize, range) {
2706
3045
  style: { top: "100%" },
2707
3046
  "aria-hidden": "true"
2708
3047
  },
2709
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__step-line" }),
2710
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__step-label" }, endYear)
3048
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__step-line" }),
3049
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__step-label" }, endYear)
2711
3050
  )
2712
3051
  );
2713
3052
  }
@@ -2717,7 +3056,7 @@ function renderSteps(stepSize, range) {
2717
3056
  const progress = (timestamp - range.startDate.getTime()) / range.span;
2718
3057
  if (progress <= 0 || progress >= 1) continue;
2719
3058
  markers.push(
2720
- /* @__PURE__ */ React35.createElement(
3059
+ /* @__PURE__ */ React36.createElement(
2721
3060
  "span",
2722
3061
  {
2723
3062
  key: `timeline-step-${year}`,
@@ -2725,8 +3064,8 @@ function renderSteps(stepSize, range) {
2725
3064
  style: { top: `calc(${progress * 100}% - 0.5px)` },
2726
3065
  "aria-hidden": "true"
2727
3066
  },
2728
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__step-line" }),
2729
- /* @__PURE__ */ React35.createElement("span", { className: "canopy-timeline__step-label" }, year)
3067
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__step-line" }),
3068
+ /* @__PURE__ */ React36.createElement("span", { className: "canopy-timeline__step-label" }, year)
2730
3069
  )
2731
3070
  );
2732
3071
  }
@@ -2740,7 +3079,7 @@ function TimelinePoint() {
2740
3079
  TimelinePoint.displayName = "TimelinePoint";
2741
3080
 
2742
3081
  // ui/src/content/map/Map.jsx
2743
- import React36 from "react";
3082
+ import React37 from "react";
2744
3083
  import { createRoot } from "react-dom/client";
2745
3084
  var DEFAULT_TILE_LAYERS = [
2746
3085
  {
@@ -3007,7 +3346,7 @@ function MapPopupContent({ marker }) {
3007
3346
  ...manifest,
3008
3347
  href: manifest.href ? withBasePath(manifest.href) : manifest.href || ""
3009
3348
  }));
3010
- return /* @__PURE__ */ React36.createElement("div", { className: "canopy-map__popup" }, thumbnail ? /* @__PURE__ */ React36.createElement("div", { className: "canopy-map__popup-media" }, /* @__PURE__ */ React36.createElement(
3349
+ return /* @__PURE__ */ React37.createElement("div", { className: "canopy-map__popup" }, thumbnail ? /* @__PURE__ */ React37.createElement("div", { className: "canopy-map__popup-media" }, /* @__PURE__ */ React37.createElement(
3011
3350
  "img",
3012
3351
  {
3013
3352
  src: thumbnail,
@@ -3016,19 +3355,19 @@ function MapPopupContent({ marker }) {
3016
3355
  width: typeof thumbWidth === "number" && thumbWidth > 0 ? thumbWidth : void 0,
3017
3356
  height: typeof thumbHeight === "number" && thumbHeight > 0 ? thumbHeight : void 0
3018
3357
  }
3019
- )) : null, /* @__PURE__ */ React36.createElement("div", { className: "canopy-map__popup-body" }, title ? href ? /* @__PURE__ */ React36.createElement("a", { href, className: "canopy-map__popup-title" }, title) : /* @__PURE__ */ React36.createElement("span", { className: "canopy-map__popup-title" }, title) : null, summary ? /* @__PURE__ */ React36.createElement("p", { className: "canopy-map__popup-summary" }, summary) : null, marker.detailsHtml ? /* @__PURE__ */ React36.createElement(
3358
+ )) : null, /* @__PURE__ */ React37.createElement("div", { className: "canopy-map__popup-body" }, title ? href ? /* @__PURE__ */ React37.createElement("a", { href, className: "canopy-map__popup-title" }, title) : /* @__PURE__ */ React37.createElement("span", { className: "canopy-map__popup-title" }, title) : null, summary ? /* @__PURE__ */ React37.createElement("p", { className: "canopy-map__popup-summary" }, summary) : null, marker.detailsHtml ? /* @__PURE__ */ React37.createElement(
3020
3359
  "div",
3021
3360
  {
3022
3361
  className: "canopy-map__popup-details",
3023
3362
  dangerouslySetInnerHTML: { __html: marker.detailsHtml }
3024
3363
  }
3025
- ) : null, !summary && !marker.detailsHtml && href && !title ? /* @__PURE__ */ React36.createElement("a", { href, className: "canopy-map__popup-link" }, "View item") : null, normalizedManifests.length ? /* @__PURE__ */ React36.createElement("div", { className: "canopy-map__popup-manifests" }, /* @__PURE__ */ React36.createElement("div", { className: "canopy-map__popup-manifests-list" }, normalizedManifests.map((manifest, index) => /* @__PURE__ */ React36.createElement(
3364
+ ) : null, !summary && !marker.detailsHtml && href && !title ? /* @__PURE__ */ React37.createElement("a", { href, className: "canopy-map__popup-link" }, "View item") : null, normalizedManifests.length ? /* @__PURE__ */ React37.createElement("div", { className: "canopy-map__popup-manifests" }, /* @__PURE__ */ React37.createElement("div", { className: "canopy-map__popup-manifests-list" }, normalizedManifests.map((manifest, index) => /* @__PURE__ */ React37.createElement(
3026
3365
  "div",
3027
3366
  {
3028
3367
  key: manifest.id || manifest.href || `manifest-${index}`,
3029
3368
  className: "canopy-map__popup-manifests-item"
3030
3369
  },
3031
- /* @__PURE__ */ React36.createElement(ReferencedManifestCard, { manifest })
3370
+ /* @__PURE__ */ React37.createElement(ReferencedManifestCard, { manifest })
3032
3371
  )))) : null));
3033
3372
  }
3034
3373
  function renderPopup(marker) {
@@ -3040,7 +3379,7 @@ function renderPopup(marker) {
3040
3379
  if (hadError) return;
3041
3380
  try {
3042
3381
  if (!root) root = createRoot(container);
3043
- root.render(/* @__PURE__ */ React36.createElement(MapPopupContent, { marker }));
3382
+ root.render(/* @__PURE__ */ React37.createElement(MapPopupContent, { marker }));
3044
3383
  } catch (error) {
3045
3384
  hadError = true;
3046
3385
  if (root) {
@@ -3165,26 +3504,26 @@ function Map2({
3165
3504
  defaultCenter = null,
3166
3505
  defaultZoom = null
3167
3506
  } = {}) {
3168
- const containerRef = React36.useRef(null);
3169
- const mapRef = React36.useRef(null);
3170
- const layerRef = React36.useRef(null);
3171
- const [leafletLib, setLeafletLib] = React36.useState(() => resolveGlobalLeaflet());
3172
- const [leafletError, setLeafletError] = React36.useState(null);
3507
+ const containerRef = React37.useRef(null);
3508
+ const mapRef = React37.useRef(null);
3509
+ const layerRef = React37.useRef(null);
3510
+ const [leafletLib, setLeafletLib] = React37.useState(() => resolveGlobalLeaflet());
3511
+ const [leafletError, setLeafletError] = React37.useState(null);
3173
3512
  const datasetInfo = navDataset && typeof navDataset === "object" ? navDataset : null;
3174
3513
  const datasetHref = datasetInfo && datasetInfo.href || "/api/navplace.json";
3175
3514
  const datasetVersion = datasetInfo && datasetInfo.version;
3176
3515
  const datasetHasFeatures = !!(datasetInfo && datasetInfo.hasFeatures);
3177
- const [navState, setNavState] = React36.useState(() => ({
3516
+ const [navState, setNavState] = React37.useState(() => ({
3178
3517
  loading: false,
3179
3518
  error: null,
3180
3519
  markers: []
3181
3520
  }));
3182
- const [iiifTargets, setIiifTargets] = React36.useState(() => ({
3521
+ const [iiifTargets, setIiifTargets] = React37.useState(() => ({
3183
3522
  loading: false,
3184
3523
  error: null,
3185
3524
  keys: []
3186
3525
  }));
3187
- React36.useEffect(() => {
3526
+ React37.useEffect(() => {
3188
3527
  if (!iiifContent) {
3189
3528
  setIiifTargets({ loading: false, error: null, keys: [] });
3190
3529
  return;
@@ -3232,7 +3571,7 @@ function Map2({
3232
3571
  const navTargets = iiifTargets.keys || [];
3233
3572
  const navTargetsKey = navTargets.join("|");
3234
3573
  const shouldFetchNav = datasetHasFeatures && navTargets.length > 0;
3235
- React36.useEffect(() => {
3574
+ React37.useEffect(() => {
3236
3575
  if (!shouldFetchNav) {
3237
3576
  setNavState({ loading: false, error: null, markers: [] });
3238
3577
  return void 0;
@@ -3264,7 +3603,7 @@ function Map2({
3264
3603
  cancelled = true;
3265
3604
  };
3266
3605
  }, [datasetHref, datasetVersion, navTargetsKey, shouldFetchNav]);
3267
- React36.useEffect(() => {
3606
+ React37.useEffect(() => {
3268
3607
  if (leafletLib) return;
3269
3608
  let cancelled = false;
3270
3609
  waitForLeaflet().then((lib) => {
@@ -3276,7 +3615,7 @@ function Map2({
3276
3615
  cancelled = true;
3277
3616
  };
3278
3617
  }, [leafletLib]);
3279
- const navMatchMap = React36.useMemo(() => {
3618
+ const navMatchMap = React37.useMemo(() => {
3280
3619
  const matchMap = createMarkerMap();
3281
3620
  (navState.markers || []).forEach((marker) => {
3282
3621
  if (!marker || !Array.isArray(marker.matchKeys)) return;
@@ -3287,7 +3626,7 @@ function Map2({
3287
3626
  });
3288
3627
  return matchMap;
3289
3628
  }, [navState.markers]);
3290
- const normalizedCustom = React36.useMemo(() => {
3629
+ const normalizedCustom = React37.useMemo(() => {
3291
3630
  return normalizeCustomMarkers(customPoints).map((point) => {
3292
3631
  if (!point || point.thumbnail || !point.href) return point;
3293
3632
  const match = navMatchMap.get(normalizeKey(point.href));
@@ -3302,11 +3641,11 @@ function Map2({
3302
3641
  };
3303
3642
  });
3304
3643
  }, [customPoints, navMatchMap]);
3305
- const allMarkers = React36.useMemo(() => {
3644
+ const allMarkers = React37.useMemo(() => {
3306
3645
  return [...navState.markers || [], ...normalizedCustom];
3307
3646
  }, [navState.markers, normalizedCustom]);
3308
- const clusterOptions = React36.useMemo(() => buildClusterOptions(leafletLib), [leafletLib]);
3309
- React36.useEffect(() => {
3647
+ const clusterOptions = React37.useMemo(() => buildClusterOptions(leafletLib), [leafletLib]);
3648
+ React37.useEffect(() => {
3310
3649
  if (!containerRef.current || mapRef.current || !leafletLib) return void 0;
3311
3650
  const map = leafletLib.map(containerRef.current, {
3312
3651
  zoomControl: true,
@@ -3344,7 +3683,7 @@ function Map2({
3344
3683
  layerRef.current = null;
3345
3684
  };
3346
3685
  }, [tileLayers, scrollWheelZoom, cluster, clusterOptions, leafletLib]);
3347
- React36.useEffect(() => {
3686
+ React37.useEffect(() => {
3348
3687
  const map = mapRef.current;
3349
3688
  const layer = layerRef.current;
3350
3689
  if (!map || !layer || !leafletLib) return;
@@ -3448,14 +3787,14 @@ function Map2({
3448
3787
  ].filter(Boolean).join(" ");
3449
3788
  const statusLabel = leafletError ? leafletError.message || "Failed to load map library" : !leafletLib ? "Loading map\u2026" : iiifTargets.error ? iiifTargets.error : datasetUnavailable ? "Map data is unavailable for this site." : navState.error ? navState.error : isLoadingMarkers ? "Loading map data\u2026" : !iiifContent && !hasCustomPoints ? "Add iiifContent or MapPoint markers to populate this map." : !hasMarkers ? "No map locations available." : "";
3450
3789
  const showStatus = Boolean(statusLabel);
3451
- return /* @__PURE__ */ React36.createElement("div", { className: rootClass, id: id || void 0, style: style || void 0 }, /* @__PURE__ */ React36.createElement(
3790
+ return /* @__PURE__ */ React37.createElement("div", { className: rootClass, id: id || void 0, style: style || void 0 }, /* @__PURE__ */ React37.createElement(
3452
3791
  "div",
3453
3792
  {
3454
3793
  ref: containerRef,
3455
3794
  className: "canopy-map__canvas",
3456
3795
  style: { height: height || "600px" }
3457
3796
  }
3458
- ), showStatus ? /* @__PURE__ */ React36.createElement("div", { className: "canopy-map__status", "aria-live": "polite" }, statusLabel) : null);
3797
+ ), showStatus ? /* @__PURE__ */ React37.createElement("div", { className: "canopy-map__status", "aria-live": "polite" }, statusLabel) : null);
3459
3798
  }
3460
3799
 
3461
3800
  // ui/src/content/map/MapPoint.jsx