@canopy-iiif/app 0.10.6 → 0.10.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canopy-iiif/app",
3
- "version": "0.10.6",
3
+ "version": "0.10.7",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Mat Jordan <mat@northwestern.edu>",
@@ -612,9 +612,13 @@ import navigationHelpers2 from "@canopy-iiif/app/lib/components/navigation.js";
612
612
  // ui/src/layout/ContentNavigation.jsx
613
613
  import React11 from "react";
614
614
  var SCROLL_OFFSET_REM = 1.618;
615
+ var MAX_HEADING_DEPTH = 3;
615
616
  function depthIndex(depth) {
616
617
  return Math.max(0, Math.min(5, (depth || 1) - 1));
617
618
  }
619
+ function resolveDepth(value, fallback = 1) {
620
+ return Math.max(1, typeof value === "number" ? value : fallback);
621
+ }
618
622
  function ContentNavigation({
619
623
  items = [],
620
624
  className = "",
@@ -627,7 +631,11 @@ function ContentNavigation({
627
631
  const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
628
632
  const savedDepthsRef = React11.useRef(null);
629
633
  if ((!items || !items.length) && !headingId) return null;
630
- const combinedClassName = ["canopy-sub-navigation canopy-content-navigation", className].filter(Boolean).join(" ");
634
+ const combinedClassName = [
635
+ "canopy-sub-navigation canopy-content-navigation",
636
+ "canopy-content-navigation--collapsed",
637
+ className
638
+ ].filter(Boolean).join(" ");
631
639
  const effectiveHeading = heading || pageTitle || null;
632
640
  const navLabel = ariaLabel || (effectiveHeading ? `${effectiveHeading} navigation` : "Section navigation");
633
641
  const getSavedDepth = React11.useCallback(
@@ -656,7 +664,11 @@ function ContentNavigation({
656
664
  const id = String(node.id);
657
665
  if (seen.has(id)) return;
658
666
  seen.add(id);
659
- const depth = node.depth || node.level || getSavedDepth(id, 2);
667
+ const depth = resolveDepth(
668
+ typeof node.depth === "number" ? node.depth : node.level,
669
+ getSavedDepth(id, 2)
670
+ );
671
+ if (depth > MAX_HEADING_DEPTH) return;
660
672
  entries.push({ id, depth });
661
673
  if (node.children && node.children.length) pushNodes(node.children);
662
674
  });
@@ -772,9 +784,14 @@ function ContentNavigation({
772
784
  return nodes.map((node) => {
773
785
  if (!node) return null;
774
786
  const id = node.id ? String(node.id) : "";
775
- const depth = node.depth || node.level || getSavedDepth(id, 2);
787
+ const depth = resolveDepth(
788
+ typeof node.depth === "number" ? node.depth : node.level,
789
+ getSavedDepth(id, 2)
790
+ );
791
+ if (depth > MAX_HEADING_DEPTH) return null;
776
792
  const idx = depthIndex(depth);
777
793
  const isActive = id && activeId === id;
794
+ const childNodes = depth < MAX_HEADING_DEPTH ? renderNodes2(node.children) : null;
778
795
  return /* @__PURE__ */ React11.createElement("li", { key: id || node.title, className: "canopy-sub-navigation__item", "data-depth": idx }, /* @__PURE__ */ React11.createElement(
779
796
  "a",
780
797
  {
@@ -784,19 +801,19 @@ function ContentNavigation({
784
801
  "aria-current": isActive ? "location" : void 0
785
802
  },
786
803
  node.title
787
- ), node.children && node.children.length ? /* @__PURE__ */ React11.createElement(
804
+ ), childNodes ? /* @__PURE__ */ React11.createElement(
788
805
  "ul",
789
806
  {
790
807
  className: "canopy-sub-navigation__list canopy-sub-navigation__list--nested",
791
808
  role: "list"
792
809
  },
793
- renderNodes2(node.children)
810
+ childNodes
794
811
  ) : null);
795
812
  });
796
813
  },
797
814
  [handleAnchorClick, activeId, getSavedDepth]
798
815
  );
799
- const nestedItems = renderNodes2(items);
816
+ const nestedItems = React11.useMemo(() => renderNodes2(items), [items, renderNodes2]);
800
817
  const topLink = headingId ? /* @__PURE__ */ React11.createElement("li", { className: "canopy-sub-navigation__item", "data-depth": 0 }, /* @__PURE__ */ React11.createElement(
801
818
  "a",
802
819
  {
@@ -814,7 +831,32 @@ function ContentNavigation({
814
831
  },
815
832
  nestedItems
816
833
  ) : null) : null;
817
- return /* @__PURE__ */ React11.createElement("nav", { className: combinedClassName, style, "aria-label": navLabel }, /* @__PURE__ */ React11.createElement("ul", { className: "canopy-sub-navigation__list", role: "list" }, topLink || nestedItems));
834
+ return /* @__PURE__ */ React11.createElement(
835
+ "nav",
836
+ {
837
+ className: combinedClassName,
838
+ style,
839
+ "aria-label": navLabel,
840
+ "data-canopy-content-nav": "true"
841
+ },
842
+ /* @__PURE__ */ React11.createElement(
843
+ "button",
844
+ {
845
+ type: "button",
846
+ className: "canopy-content-navigation__toggle",
847
+ "aria-expanded": "false",
848
+ "aria-label": "Show section navigation",
849
+ title: "Show section navigation",
850
+ "data-canopy-content-nav-toggle": "true",
851
+ "data-show-label": "Show",
852
+ "data-hide-label": "Hide",
853
+ "data-show-full-label": "Show section navigation",
854
+ "data-hide-full-label": "Hide section navigation"
855
+ },
856
+ "Show"
857
+ ),
858
+ /* @__PURE__ */ React11.createElement("ul", { className: "canopy-sub-navigation__list", role: "list" }, topLink || nestedItems)
859
+ );
818
860
  }
819
861
 
820
862
  // ui/src/layout/Layout.jsx
@@ -853,6 +895,106 @@ function buildNavigationAside(sidebar, className) {
853
895
  }
854
896
  return sidebar;
855
897
  }
898
+ function ContentNavigationScript() {
899
+ const code = `
900
+ (function () {
901
+ if (typeof window === 'undefined') return;
902
+ if (window.__CANOPY_CONTENT_NAV_READY__) return;
903
+ window.__CANOPY_CONTENT_NAV_READY__ = true;
904
+ var STORAGE_KEY = 'canopy_content_nav_collapsed';
905
+ var storage = null;
906
+ try {
907
+ storage = window.localStorage;
908
+ } catch (error) {
909
+ storage = null;
910
+ }
911
+
912
+ function setStored(value) {
913
+ if (!storage) return;
914
+ try {
915
+ if (value == null) {
916
+ storage.removeItem(STORAGE_KEY);
917
+ } else {
918
+ storage.setItem(STORAGE_KEY, value);
919
+ }
920
+ } catch (error) {}
921
+ }
922
+
923
+ function getStored() {
924
+ if (!storage) return null;
925
+ try {
926
+ return storage.getItem(STORAGE_KEY);
927
+ } catch (error) {
928
+ return null;
929
+ }
930
+ }
931
+
932
+ function ready(fn) {
933
+ if (!fn) return;
934
+ if (document.readyState === 'loading') {
935
+ document.addEventListener('DOMContentLoaded', fn, { once: true });
936
+ } else {
937
+ fn();
938
+ }
939
+ }
940
+
941
+ function applyState(root, collapsed) {
942
+ if (!root) return;
943
+ var isCollapsed = !!collapsed;
944
+ root.classList.toggle('is-collapsed', isCollapsed);
945
+ var layout = root.closest('.canopy-layout');
946
+ if (layout) layout.classList.toggle('canopy-layout--content-nav-collapsed', isCollapsed);
947
+ var nav = root.querySelector('[data-canopy-content-nav]');
948
+ if (nav) nav.classList.toggle('canopy-content-navigation--collapsed', isCollapsed);
949
+ var toggle = root.querySelector('[data-canopy-content-nav-toggle]');
950
+ if (toggle) {
951
+ var showLabel = toggle.getAttribute('data-show-label') || 'Show';
952
+ var hideLabel = toggle.getAttribute('data-hide-label') || 'Hide';
953
+ var showFull = toggle.getAttribute('data-show-full-label') || 'Show section navigation';
954
+ var hideFull = toggle.getAttribute('data-hide-full-label') || 'Hide section navigation';
955
+ toggle.textContent = isCollapsed ? showLabel : hideLabel;
956
+ toggle.setAttribute('aria-expanded', isCollapsed ? 'false' : 'true');
957
+ toggle.setAttribute('aria-label', isCollapsed ? showFull : hideFull);
958
+ toggle.setAttribute('title', isCollapsed ? showFull : hideFull);
959
+ }
960
+ }
961
+
962
+ ready(function () {
963
+ var roots = Array.prototype.slice.call(
964
+ document.querySelectorAll('[data-canopy-content-nav-root]')
965
+ );
966
+ if (!roots.length) return;
967
+ var stored = getStored();
968
+ var collapsed = true;
969
+ if (stored === '0' || stored === 'false') {
970
+ collapsed = false;
971
+ } else if (stored === '1' || stored === 'true') {
972
+ collapsed = true;
973
+ }
974
+
975
+ function sync(next) {
976
+ collapsed = !!next;
977
+ roots.forEach(function (root) {
978
+ applyState(root, collapsed);
979
+ });
980
+ setStored(collapsed ? '1' : '0');
981
+ }
982
+
983
+ sync(collapsed);
984
+
985
+ roots.forEach(function (root) {
986
+ var toggle = root.querySelector('[data-canopy-content-nav-toggle]');
987
+ if (!toggle) return;
988
+ toggle.addEventListener('click', function (event) {
989
+ event.preventDefault();
990
+ sync(!collapsed);
991
+ });
992
+ });
993
+ });
994
+ })();
995
+ `;
996
+ return /* @__PURE__ */ React12.createElement("script", { dangerouslySetInnerHTML: { __html: code } });
997
+ }
856
998
  function Layout({
857
999
  children,
858
1000
  sidebar,
@@ -892,38 +1034,40 @@ function Layout({
892
1034
  const showLeftColumn = navigation !== false;
893
1035
  const hasContentNavigation = navigation !== false && contentNavigation !== false && headingTree.length > 0;
894
1036
  const containerClassName = (() => {
895
- const classes = ["getting-started-layout"];
896
- classes.push(
897
- fluid ? "getting-started-layout--fluid" : "getting-started-layout--fixed"
898
- );
899
- if (showLeftColumn) classes.push("getting-started-layout--with-sidebar");
900
- if (hasContentNavigation)
901
- classes.push("getting-started-layout--with-content-nav");
1037
+ const classes = ["canopy-layout"];
1038
+ classes.push(fluid ? "canopy-layout--fluid" : "canopy-layout--fixed");
1039
+ if (showLeftColumn) classes.push("canopy-layout--with-sidebar");
1040
+ if (hasContentNavigation) {
1041
+ classes.push("canopy-layout--with-content-nav");
1042
+ classes.push("canopy-layout--content-nav-collapsed");
1043
+ }
902
1044
  if (className) classes.push(className);
903
1045
  return classes.join(" ");
904
1046
  })();
905
- const leftAsideClassName = [
906
- "getting-started-layout__sidebar",
907
- sidebarClassName
908
- ].filter(Boolean).join(" ");
909
- const contentClassNames = [
910
- "getting-started-layout__content",
911
- contentClassName
912
- ].filter(Boolean).join(" ");
1047
+ const leftAsideClassName = ["canopy-layout__sidebar", sidebarClassName].filter(Boolean).join(" ");
1048
+ const contentClassNames = ["canopy-layout__content", contentClassName].filter(Boolean).join(" ");
913
1049
  const contentNavigationAsideClassName = [
914
- "getting-started-layout__content-nav",
1050
+ "canopy-layout__content-nav",
1051
+ "is-collapsed",
915
1052
  contentNavigationClassName
916
1053
  ].filter(Boolean).join(" ");
917
1054
  const sidebarNode = showLeftColumn ? buildNavigationAside(sidebar, sidebarClassName) : null;
918
- return /* @__PURE__ */ React12.createElement("div", { className: containerClassName, ...rest }, showLeftColumn ? /* @__PURE__ */ React12.createElement("aside", { className: leftAsideClassName }, sidebarNode) : null, /* @__PURE__ */ React12.createElement("div", { className: contentClassNames }, children), hasContentNavigation ? /* @__PURE__ */ React12.createElement("aside", { className: contentNavigationAsideClassName }, /* @__PURE__ */ React12.createElement(
919
- ContentNavigation,
1055
+ return /* @__PURE__ */ React12.createElement("div", { className: containerClassName, ...rest }, showLeftColumn ? /* @__PURE__ */ React12.createElement("aside", { className: leftAsideClassName }, sidebarNode) : null, /* @__PURE__ */ React12.createElement("div", { className: contentClassNames }, children), hasContentNavigation ? /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(
1056
+ "aside",
920
1057
  {
921
- items: headingTree,
922
- heading: contentHeading || void 0,
923
- headingId: headingAnchorId || void 0,
924
- pageTitle: context && context.page ? context.page.title : void 0
925
- }
926
- )) : null);
1058
+ className: contentNavigationAsideClassName,
1059
+ "data-canopy-content-nav-root": "true"
1060
+ },
1061
+ /* @__PURE__ */ React12.createElement(
1062
+ ContentNavigation,
1063
+ {
1064
+ items: headingTree,
1065
+ heading: contentHeading || void 0,
1066
+ headingId: headingAnchorId || void 0,
1067
+ pageTitle: context && context.page ? context.page.title : void 0
1068
+ }
1069
+ )
1070
+ ), /* @__PURE__ */ React12.createElement(ContentNavigationScript, null)) : null);
927
1071
  }
928
1072
 
929
1073
  // ui/src/layout/CanopyHeader.jsx