@canopy-iiif/app 1.5.14 → 1.5.16
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/lib/build/mdx.js +3 -0
- package/lib/common.js +34 -2
- package/lib/head.js +39 -4
- package/lib/page-context.js +1 -1
- package/package.json +1 -1
- package/ui/dist/index.mjs +28 -9
- package/ui/dist/index.mjs.map +2 -2
- package/ui/dist/server.mjs +425 -86
- package/ui/dist/server.mjs.map +3 -3
- package/ui/styles/components/_layout.scss +3 -3
- package/ui/styles/components/_nav-tree.scss +12 -5
- package/ui/styles/components/_sub-navigation.scss +69 -38
- package/ui/styles/index.css +78 -38
package/ui/dist/server.mjs
CHANGED
|
@@ -891,7 +891,7 @@ function NavigationTreeItem({ node, depth, nodeKey }) {
|
|
|
891
891
|
"data-expanded": allowToggle ? defaultExpanded ? "true" : "false" : void 0,
|
|
892
892
|
"data-default-expanded": allowToggle && defaultExpanded ? "true" : void 0
|
|
893
893
|
},
|
|
894
|
-
/* @__PURE__ */ React10.createElement("div", { className: "canopy-nav-tree__row" }, /* @__PURE__ */ React10.createElement(
|
|
894
|
+
/* @__PURE__ */ React10.createElement("div", { className: "canopy-nav-tree__row" }, /* @__PURE__ */ React10.createElement("div", { className: "canopy-nav-tree__link-wrapper" }, /* @__PURE__ */ React10.createElement(
|
|
895
895
|
Tag,
|
|
896
896
|
{
|
|
897
897
|
className: classes.join(" "),
|
|
@@ -901,7 +901,7 @@ function NavigationTreeItem({ node, depth, nodeKey }) {
|
|
|
901
901
|
},
|
|
902
902
|
node.title || node.slug,
|
|
903
903
|
isRoadmap ? /* @__PURE__ */ React10.createElement("span", { className: "canopy-nav-tree__badge" }, "Roadmap") : null
|
|
904
|
-
), allowToggle ? /* @__PURE__ */ React10.createElement(
|
|
904
|
+
)), allowToggle ? /* @__PURE__ */ React10.createElement(
|
|
905
905
|
"button",
|
|
906
906
|
{
|
|
907
907
|
type: "button",
|
|
@@ -921,7 +921,14 @@ function NavigationTreeItem({ node, depth, nodeKey }) {
|
|
|
921
921
|
strokeWidth: "1.5",
|
|
922
922
|
className: "canopy-nav-tree__toggle-icon"
|
|
923
923
|
},
|
|
924
|
-
/* @__PURE__ */ React10.createElement(
|
|
924
|
+
/* @__PURE__ */ React10.createElement(
|
|
925
|
+
"path",
|
|
926
|
+
{
|
|
927
|
+
strokeLinecap: "round",
|
|
928
|
+
strokeLinejoin: "round",
|
|
929
|
+
d: "M5 9l7 7 7-7"
|
|
930
|
+
}
|
|
931
|
+
)
|
|
925
932
|
),
|
|
926
933
|
/* @__PURE__ */ React10.createElement("span", { className: "sr-only" }, toggleLabel)
|
|
927
934
|
) : null),
|
|
@@ -966,7 +973,14 @@ function NavigationTree({
|
|
|
966
973
|
...rest
|
|
967
974
|
},
|
|
968
975
|
heading ? /* @__PURE__ */ React10.createElement("div", { className: headingClassName }, heading) : null,
|
|
969
|
-
/* @__PURE__ */ React10.createElement(
|
|
976
|
+
/* @__PURE__ */ React10.createElement(
|
|
977
|
+
NavigationTreeList,
|
|
978
|
+
{
|
|
979
|
+
nodes,
|
|
980
|
+
depth: includeRoot ? -1 : 0,
|
|
981
|
+
parentKey
|
|
982
|
+
}
|
|
983
|
+
)
|
|
970
984
|
);
|
|
971
985
|
}
|
|
972
986
|
|
|
@@ -1047,12 +1061,14 @@ import navigationHelpers3 from "@canopy-iiif/app/lib/components/navigation.js";
|
|
|
1047
1061
|
import React12 from "react";
|
|
1048
1062
|
var SCROLL_OFFSET_REM = 1.618;
|
|
1049
1063
|
var MAX_HEADING_DEPTH = 3;
|
|
1050
|
-
function depthIndex(depth) {
|
|
1051
|
-
return Math.max(0, Math.min(5, (depth || 1) - 1));
|
|
1052
|
-
}
|
|
1053
1064
|
function resolveDepth(value, fallback = 1) {
|
|
1054
1065
|
return Math.max(1, typeof value === "number" ? value : fallback);
|
|
1055
1066
|
}
|
|
1067
|
+
function buildNodeKey(id, parentKey, index) {
|
|
1068
|
+
const base = id ? String(id) : `section-${parentKey || "root"}-${index}`;
|
|
1069
|
+
const sanitized = base.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
1070
|
+
return `${parentKey || "section"}-${sanitized || index}`;
|
|
1071
|
+
}
|
|
1056
1072
|
function ContentNavigation({
|
|
1057
1073
|
items = [],
|
|
1058
1074
|
className = "",
|
|
@@ -1076,17 +1092,16 @@ function ContentNavigation({
|
|
|
1076
1092
|
].filter(Boolean).join(" ");
|
|
1077
1093
|
const effectiveHeading = heading || pageTitle || null;
|
|
1078
1094
|
const navLabel = ariaLabel || (effectiveHeading ? `${effectiveHeading} navigation` : "Section navigation");
|
|
1079
|
-
const
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
);
|
|
1095
|
+
const toggleSrLabel = isExpanded ? "Hide section navigation" : "Show section navigation";
|
|
1096
|
+
const toggleStateClass = isExpanded ? "is-expanded" : "is-collapsed";
|
|
1097
|
+
const getSavedDepth = React12.useCallback((id, fallback) => {
|
|
1098
|
+
if (!id) return fallback;
|
|
1099
|
+
if (!savedDepthsRef.current) savedDepthsRef.current = /* @__PURE__ */ new Map();
|
|
1100
|
+
const store = savedDepthsRef.current;
|
|
1101
|
+
if (store.has(id)) return store.get(id);
|
|
1102
|
+
store.set(id, fallback);
|
|
1103
|
+
return fallback;
|
|
1104
|
+
}, []);
|
|
1090
1105
|
const headingEntries = React12.useMemo(() => {
|
|
1091
1106
|
const entries = [];
|
|
1092
1107
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1118,6 +1133,9 @@ function ContentNavigation({
|
|
|
1118
1133
|
const [activeId, setActiveId] = React12.useState(fallbackId);
|
|
1119
1134
|
const activeIdRef = React12.useRef(activeId);
|
|
1120
1135
|
React12.useEffect(() => {
|
|
1136
|
+
if (process.env.NODE_ENV !== "production" && activeIdRef.current !== activeId) {
|
|
1137
|
+
console.log("[ContentNavigation] activeId changed:", activeId);
|
|
1138
|
+
}
|
|
1121
1139
|
activeIdRef.current = activeId;
|
|
1122
1140
|
}, [activeId]);
|
|
1123
1141
|
React12.useEffect(() => {
|
|
@@ -1143,16 +1161,28 @@ function ContentNavigation({
|
|
|
1143
1161
|
(elements) => {
|
|
1144
1162
|
if (!elements || !elements.length) return;
|
|
1145
1163
|
const offset = computeOffsetPx();
|
|
1146
|
-
|
|
1147
|
-
|
|
1164
|
+
const viewportLimit = typeof window !== "undefined" && window.innerHeight ? window.innerHeight * 0.5 : 0;
|
|
1165
|
+
let fallbackId2 = elements[0].id;
|
|
1166
|
+
let bestId = fallbackId2;
|
|
1167
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
1168
|
+
elements.forEach(({ id, element }) => {
|
|
1169
|
+
if (!element || !id) return;
|
|
1148
1170
|
const rect = element.getBoundingClientRect();
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
break;
|
|
1171
|
+
const relativeTop = rect.top - offset;
|
|
1172
|
+
if (viewportLimit > 0 && relativeTop < -viewportLimit) {
|
|
1173
|
+
return;
|
|
1153
1174
|
}
|
|
1154
|
-
|
|
1175
|
+
const distance = Math.abs(relativeTop);
|
|
1176
|
+
if (distance < bestDistance) {
|
|
1177
|
+
bestDistance = distance;
|
|
1178
|
+
bestId = id;
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
const nextId = bestId || fallbackId2;
|
|
1155
1182
|
if (nextId && nextId !== activeIdRef.current) {
|
|
1183
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1184
|
+
console.log("[ContentNavigation] updateActive ->", nextId);
|
|
1185
|
+
}
|
|
1156
1186
|
activeIdRef.current = nextId;
|
|
1157
1187
|
setActiveId(nextId);
|
|
1158
1188
|
}
|
|
@@ -1173,6 +1203,13 @@ function ContentNavigation({
|
|
|
1173
1203
|
if (!ticking) {
|
|
1174
1204
|
ticking = true;
|
|
1175
1205
|
window.requestAnimationFrame(() => {
|
|
1206
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1207
|
+
console.log(
|
|
1208
|
+
"[ContentNavigation] scroll event",
|
|
1209
|
+
window.scrollY,
|
|
1210
|
+
window.innerHeight
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1176
1213
|
updateActiveFromElements(elements);
|
|
1177
1214
|
ticking = false;
|
|
1178
1215
|
});
|
|
@@ -1189,7 +1226,8 @@ function ContentNavigation({
|
|
|
1189
1226
|
(event, targetId, options = {}) => {
|
|
1190
1227
|
var _a;
|
|
1191
1228
|
try {
|
|
1192
|
-
if (event && typeof event.preventDefault === "function")
|
|
1229
|
+
if (event && typeof event.preventDefault === "function")
|
|
1230
|
+
event.preventDefault();
|
|
1193
1231
|
} catch (_) {
|
|
1194
1232
|
}
|
|
1195
1233
|
if (!isBrowser) return;
|
|
@@ -1216,10 +1254,10 @@ function ContentNavigation({
|
|
|
1216
1254
|
},
|
|
1217
1255
|
[computeOffsetPx, headingEntries, headingId, isBrowser]
|
|
1218
1256
|
);
|
|
1219
|
-
const
|
|
1220
|
-
(
|
|
1221
|
-
if (!
|
|
1222
|
-
return
|
|
1257
|
+
const navTreeRoot = React12.useMemo(() => {
|
|
1258
|
+
function mapNodes(nodes2, parentKey = "section") {
|
|
1259
|
+
if (!Array.isArray(nodes2) || !nodes2.length) return [];
|
|
1260
|
+
return nodes2.map((node, index) => {
|
|
1223
1261
|
if (!node) return null;
|
|
1224
1262
|
const id = node.id ? String(node.id) : "";
|
|
1225
1263
|
const depth = resolveDepth(
|
|
@@ -1227,48 +1265,34 @@ function ContentNavigation({
|
|
|
1227
1265
|
getSavedDepth(id, 2)
|
|
1228
1266
|
);
|
|
1229
1267
|
if (depth > MAX_HEADING_DEPTH) return null;
|
|
1230
|
-
const
|
|
1268
|
+
const key = buildNodeKey(
|
|
1269
|
+
id || node.title || `section-${index}`,
|
|
1270
|
+
parentKey,
|
|
1271
|
+
index
|
|
1272
|
+
);
|
|
1273
|
+
const href = id ? `#${id}` : "#";
|
|
1274
|
+
const childNodes = mapNodes(node.children || [], key);
|
|
1231
1275
|
const isActive = id && activeId === id;
|
|
1232
|
-
const
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
[handleAnchorClick, activeId, getSavedDepth]
|
|
1253
|
-
);
|
|
1254
|
-
const nestedItems = React12.useMemo(() => renderNodes(items), [items, renderNodes]);
|
|
1255
|
-
const topLink = headingId ? /* @__PURE__ */ React12.createElement("li", { className: "canopy-sub-navigation__item", "data-depth": 0 }, /* @__PURE__ */ React12.createElement(
|
|
1256
|
-
"a",
|
|
1257
|
-
{
|
|
1258
|
-
className: `canopy-sub-navigation__link depth-0${activeId === headingId ? " is-active" : ""}`,
|
|
1259
|
-
href: `#${headingId}`,
|
|
1260
|
-
onClick: (event) => handleAnchorClick(event, headingId, { scrollToTop: true }),
|
|
1261
|
-
"aria-current": activeId === headingId ? "location" : void 0
|
|
1262
|
-
},
|
|
1263
|
-
effectiveHeading || pageTitle || headingId
|
|
1264
|
-
), nestedItems ? /* @__PURE__ */ React12.createElement(
|
|
1265
|
-
"ul",
|
|
1266
|
-
{
|
|
1267
|
-
className: "canopy-sub-navigation__list canopy-sub-navigation__list--nested",
|
|
1268
|
-
role: "list"
|
|
1269
|
-
},
|
|
1270
|
-
nestedItems
|
|
1271
|
-
) : null) : null;
|
|
1276
|
+
const hasActiveChild = childNodes.some(
|
|
1277
|
+
(child) => child && child.isActive
|
|
1278
|
+
);
|
|
1279
|
+
return {
|
|
1280
|
+
slug: key,
|
|
1281
|
+
title: node.title || node.text || id || `Section ${index + 1}`,
|
|
1282
|
+
href,
|
|
1283
|
+
children: childNodes,
|
|
1284
|
+
isActive: Boolean(isActive),
|
|
1285
|
+
isExpanded: isActive || hasActiveChild
|
|
1286
|
+
};
|
|
1287
|
+
}).filter(Boolean);
|
|
1288
|
+
}
|
|
1289
|
+
const nodes = mapNodes(items, "section");
|
|
1290
|
+
return {
|
|
1291
|
+
slug: "content-nav-root",
|
|
1292
|
+
title: effectiveHeading || pageTitle || "On this page",
|
|
1293
|
+
children: nodes
|
|
1294
|
+
};
|
|
1295
|
+
}, [items, effectiveHeading, pageTitle, activeId, getSavedDepth]);
|
|
1272
1296
|
return /* @__PURE__ */ React12.createElement(
|
|
1273
1297
|
"nav",
|
|
1274
1298
|
{
|
|
@@ -1281,20 +1305,101 @@ function ContentNavigation({
|
|
|
1281
1305
|
"button",
|
|
1282
1306
|
{
|
|
1283
1307
|
type: "button",
|
|
1284
|
-
className:
|
|
1308
|
+
className: `canopy-content-navigation__toggle ${toggleStateClass}`,
|
|
1285
1309
|
"aria-expanded": isExpanded,
|
|
1286
|
-
"aria-label":
|
|
1287
|
-
title:
|
|
1310
|
+
"aria-label": toggleSrLabel,
|
|
1311
|
+
title: toggleSrLabel,
|
|
1288
1312
|
onClick: handleToggle,
|
|
1289
1313
|
"data-canopy-content-nav-toggle": "true",
|
|
1290
1314
|
"data-show-label": "Show",
|
|
1291
1315
|
"data-hide-label": "Hide",
|
|
1292
|
-
"data-show-full-label": "Show
|
|
1293
|
-
"data-hide-full-label": "Hide
|
|
1316
|
+
"data-show-full-label": "Show content navigation",
|
|
1317
|
+
"data-hide-full-label": "Hide content navigation"
|
|
1294
1318
|
},
|
|
1295
|
-
|
|
1319
|
+
/* @__PURE__ */ React12.createElement(
|
|
1320
|
+
"span",
|
|
1321
|
+
{
|
|
1322
|
+
className: "canopy-content-navigation__toggle-icon",
|
|
1323
|
+
"aria-hidden": "true"
|
|
1324
|
+
},
|
|
1325
|
+
/* @__PURE__ */ React12.createElement(
|
|
1326
|
+
"svg",
|
|
1327
|
+
{
|
|
1328
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1329
|
+
class: "ionicon",
|
|
1330
|
+
viewBox: "0 0 512 512"
|
|
1331
|
+
},
|
|
1332
|
+
/* @__PURE__ */ React12.createElement(
|
|
1333
|
+
"path",
|
|
1334
|
+
{
|
|
1335
|
+
fill: "none",
|
|
1336
|
+
stroke: "currentColor",
|
|
1337
|
+
"stroke-linecap": "round",
|
|
1338
|
+
"stroke-linejoin": "round",
|
|
1339
|
+
"stroke-width": "50",
|
|
1340
|
+
d: "M160 144h288M160 256h288M160 368h288"
|
|
1341
|
+
}
|
|
1342
|
+
),
|
|
1343
|
+
/* @__PURE__ */ React12.createElement(
|
|
1344
|
+
"circle",
|
|
1345
|
+
{
|
|
1346
|
+
cx: "80",
|
|
1347
|
+
cy: "144",
|
|
1348
|
+
r: "16",
|
|
1349
|
+
fill: "none",
|
|
1350
|
+
stroke: "currentColor",
|
|
1351
|
+
"stroke-linecap": "round",
|
|
1352
|
+
"stroke-linejoin": "round",
|
|
1353
|
+
"stroke-width": "32"
|
|
1354
|
+
}
|
|
1355
|
+
),
|
|
1356
|
+
/* @__PURE__ */ React12.createElement(
|
|
1357
|
+
"circle",
|
|
1358
|
+
{
|
|
1359
|
+
cx: "80",
|
|
1360
|
+
cy: "256",
|
|
1361
|
+
r: "16",
|
|
1362
|
+
fill: "none",
|
|
1363
|
+
stroke: "currentColor",
|
|
1364
|
+
"stroke-linecap": "round",
|
|
1365
|
+
"stroke-linejoin": "round",
|
|
1366
|
+
"stroke-width": "32"
|
|
1367
|
+
}
|
|
1368
|
+
),
|
|
1369
|
+
/* @__PURE__ */ React12.createElement(
|
|
1370
|
+
"circle",
|
|
1371
|
+
{
|
|
1372
|
+
cx: "80",
|
|
1373
|
+
cy: "368",
|
|
1374
|
+
r: "16",
|
|
1375
|
+
fill: "none",
|
|
1376
|
+
stroke: "currentColor",
|
|
1377
|
+
"stroke-linecap": "round",
|
|
1378
|
+
"stroke-linejoin": "round",
|
|
1379
|
+
"stroke-width": "32"
|
|
1380
|
+
}
|
|
1381
|
+
)
|
|
1382
|
+
)
|
|
1383
|
+
),
|
|
1384
|
+
/* @__PURE__ */ React12.createElement(
|
|
1385
|
+
"span",
|
|
1386
|
+
{
|
|
1387
|
+
className: "canopy-content-navigation__toggle-label",
|
|
1388
|
+
"data-canopy-content-nav-toggle-label": "true"
|
|
1389
|
+
},
|
|
1390
|
+
/* @__PURE__ */ React12.createElement("span", { className: "sr-only" }, toggleSrLabel)
|
|
1391
|
+
),
|
|
1392
|
+
/* @__PURE__ */ React12.createElement("span", { className: "sr-only", "data-canopy-content-nav-toggle-sr": "true" }, toggleSrLabel)
|
|
1296
1393
|
),
|
|
1297
|
-
/* @__PURE__ */ React12.createElement(
|
|
1394
|
+
/* @__PURE__ */ React12.createElement(
|
|
1395
|
+
NavigationTree,
|
|
1396
|
+
{
|
|
1397
|
+
root: navTreeRoot,
|
|
1398
|
+
includeRoot: false,
|
|
1399
|
+
parentKey: "content-nav",
|
|
1400
|
+
className: "canopy-sub-navigation__tree canopy-content-navigation__tree"
|
|
1401
|
+
}
|
|
1402
|
+
)
|
|
1298
1403
|
);
|
|
1299
1404
|
}
|
|
1300
1405
|
|
|
@@ -1384,18 +1489,219 @@ function ContentNavigationScript() {
|
|
|
1384
1489
|
var layout = root.closest('.canopy-layout');
|
|
1385
1490
|
if (layout) layout.classList.toggle('canopy-layout--content-nav-collapsed', isCollapsed);
|
|
1386
1491
|
var nav = root.querySelector('[data-canopy-content-nav]');
|
|
1387
|
-
if (nav)
|
|
1492
|
+
if (nav) {
|
|
1493
|
+
nav.classList.toggle('canopy-content-navigation--collapsed', isCollapsed);
|
|
1494
|
+
nav.classList.toggle('canopy-content-navigation--expanded', !isCollapsed);
|
|
1495
|
+
nav.setAttribute('data-expanded', isCollapsed ? 'false' : 'true');
|
|
1496
|
+
}
|
|
1388
1497
|
var toggle = root.querySelector('[data-canopy-content-nav-toggle]');
|
|
1389
1498
|
if (toggle) {
|
|
1390
1499
|
var showLabel = toggle.getAttribute('data-show-label') || 'Show';
|
|
1391
1500
|
var hideLabel = toggle.getAttribute('data-hide-label') || 'Hide';
|
|
1392
1501
|
var showFull = toggle.getAttribute('data-show-full-label') || 'Show section navigation';
|
|
1393
1502
|
var hideFull = toggle.getAttribute('data-hide-full-label') || 'Hide section navigation';
|
|
1394
|
-
|
|
1503
|
+
var labelNode = toggle.querySelector('[data-canopy-content-nav-toggle-label]');
|
|
1504
|
+
var srNode = toggle.querySelector('[data-canopy-content-nav-toggle-sr]');
|
|
1395
1505
|
toggle.setAttribute('aria-expanded', isCollapsed ? 'false' : 'true');
|
|
1396
1506
|
toggle.setAttribute('aria-label', isCollapsed ? showFull : hideFull);
|
|
1397
1507
|
toggle.setAttribute('title', isCollapsed ? showFull : hideFull);
|
|
1508
|
+
toggle.classList.toggle('is-collapsed', isCollapsed);
|
|
1509
|
+
toggle.classList.toggle('is-expanded', !isCollapsed);
|
|
1510
|
+
if (!labelNode) {
|
|
1511
|
+
toggle.textContent = isCollapsed ? showLabel : hideLabel;
|
|
1512
|
+
}
|
|
1513
|
+
if (srNode) {
|
|
1514
|
+
srNode.textContent = isCollapsed ? showFull : hideFull;
|
|
1515
|
+
}
|
|
1398
1516
|
}
|
|
1517
|
+
if (root.__canopyContentNavSync) {
|
|
1518
|
+
try {
|
|
1519
|
+
root.__canopyContentNavSync();
|
|
1520
|
+
} catch (_) {}
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
function setupFloatingState(root) {
|
|
1525
|
+
if (!root || typeof IntersectionObserver !== 'function') return;
|
|
1526
|
+
if (root.__canopyContentNavFloating) return;
|
|
1527
|
+
var sentinel = root.querySelector('[data-canopy-content-nav-sentinel]');
|
|
1528
|
+
var placeholder = root.querySelector('[data-canopy-content-nav-placeholder]');
|
|
1529
|
+
var nav = root.querySelector('[data-canopy-content-nav]');
|
|
1530
|
+
if (!sentinel || !nav) return;
|
|
1531
|
+
root.__canopyContentNavFloating = true;
|
|
1532
|
+
|
|
1533
|
+
function syncPosition() {
|
|
1534
|
+
try {
|
|
1535
|
+
var rect = root.getBoundingClientRect();
|
|
1536
|
+
nav.style.setProperty('--canopy-content-nav-fixed-right', '1.618rem');
|
|
1537
|
+
nav.style.setProperty('--canopy-content-nav-fixed-width', rect.width + 'px');
|
|
1538
|
+
if (placeholder) placeholder.style.width = rect.width + 'px';
|
|
1539
|
+
} catch (_) {}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
var observer = new IntersectionObserver(function (entries) {
|
|
1543
|
+
entries.forEach(function (entry) {
|
|
1544
|
+
var stuck = !entry.isIntersecting && entry.boundingClientRect.top < 0;
|
|
1545
|
+
nav.classList.toggle('canopy-content-navigation--floating', stuck);
|
|
1546
|
+
nav.setAttribute('data-stuck', stuck ? 'true' : 'false');
|
|
1547
|
+
if (placeholder) {
|
|
1548
|
+
placeholder.style.height = stuck ? nav.offsetHeight + 'px' : '0px';
|
|
1549
|
+
}
|
|
1550
|
+
});
|
|
1551
|
+
});
|
|
1552
|
+
|
|
1553
|
+
observer.observe(sentinel);
|
|
1554
|
+
syncPosition();
|
|
1555
|
+
var handleResize = function () {
|
|
1556
|
+
syncPosition();
|
|
1557
|
+
};
|
|
1558
|
+
window.addEventListener('resize', handleResize);
|
|
1559
|
+
root.__canopyContentNavCleanup = function () {
|
|
1560
|
+
observer.disconnect();
|
|
1561
|
+
window.removeEventListener('resize', handleResize);
|
|
1562
|
+
};
|
|
1563
|
+
root.__canopyContentNavSync = syncPosition;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
function computeOffsetPx() {
|
|
1567
|
+
try {
|
|
1568
|
+
var root = document.documentElement;
|
|
1569
|
+
var size = root ? parseFloat(window.getComputedStyle(root).fontSize || '16') || 16 : 16;
|
|
1570
|
+
return size * 1.618;
|
|
1571
|
+
} catch (error) {
|
|
1572
|
+
return 0;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
function setupActiveHeadingWatcher(root) {
|
|
1577
|
+
if (!root || root.__canopyContentNavActive) return;
|
|
1578
|
+
var nav = root.querySelector('[data-canopy-content-nav]');
|
|
1579
|
+
if (!nav) return;
|
|
1580
|
+
var linkNodes = Array.prototype.slice.call(
|
|
1581
|
+
nav.querySelectorAll('.canopy-nav-tree__link[href^="#"]')
|
|
1582
|
+
);
|
|
1583
|
+
var entries = linkNodes
|
|
1584
|
+
.map(function (link) {
|
|
1585
|
+
if (!link || !link.getAttribute) return null;
|
|
1586
|
+
var href = link.getAttribute('href') || '';
|
|
1587
|
+
if (!href || href.charAt(0) !== '#') return null;
|
|
1588
|
+
var id = href.slice(1);
|
|
1589
|
+
if (!id) return null;
|
|
1590
|
+
var target = document.getElementById(id);
|
|
1591
|
+
if (!target) return null;
|
|
1592
|
+
return {
|
|
1593
|
+
id: id,
|
|
1594
|
+
link: link,
|
|
1595
|
+
target: target,
|
|
1596
|
+
item: link.closest('[data-canopy-nav-item]') || null,
|
|
1597
|
+
};
|
|
1598
|
+
})
|
|
1599
|
+
.filter(Boolean);
|
|
1600
|
+
if (!entries.length) return;
|
|
1601
|
+
root.__canopyContentNavActive = true;
|
|
1602
|
+
var activeId = null;
|
|
1603
|
+
|
|
1604
|
+
function expandParents(link) {
|
|
1605
|
+
var parent = link ? link.closest('[data-canopy-nav-item]') : null;
|
|
1606
|
+
while (parent) {
|
|
1607
|
+
parent.setAttribute('data-expanded', 'true');
|
|
1608
|
+
var toggle = parent.querySelector('[data-canopy-nav-item-toggle]');
|
|
1609
|
+
if (toggle) {
|
|
1610
|
+
toggle.setAttribute('aria-expanded', 'true');
|
|
1611
|
+
var targetId = toggle.getAttribute('data-canopy-nav-item-toggle');
|
|
1612
|
+
if (targetId) {
|
|
1613
|
+
var panel = document.getElementById(targetId);
|
|
1614
|
+
if (panel) {
|
|
1615
|
+
panel.hidden = false;
|
|
1616
|
+
panel.removeAttribute('hidden');
|
|
1617
|
+
panel.setAttribute('aria-hidden', 'false');
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
parent = parent.parentElement
|
|
1622
|
+
? parent.parentElement.closest('[data-canopy-nav-item]')
|
|
1623
|
+
: null;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
function applyActive(id) {
|
|
1628
|
+
if (!id || activeId === id) return;
|
|
1629
|
+
activeId = id;
|
|
1630
|
+
var activeParents = new Set();
|
|
1631
|
+
entries.forEach(function (entry) {
|
|
1632
|
+
var isActive = entry.id === id;
|
|
1633
|
+
entry.link.classList.toggle('is-active', isActive);
|
|
1634
|
+
if (entry.item) entry.item.classList.toggle('is-active', isActive);
|
|
1635
|
+
if (isActive) {
|
|
1636
|
+
expandParents(entry.link);
|
|
1637
|
+
var parent = entry.link.closest('[data-canopy-nav-item]');
|
|
1638
|
+
while (parent) {
|
|
1639
|
+
activeParents.add(parent);
|
|
1640
|
+
parent = parent.parentElement
|
|
1641
|
+
? parent.parentElement.closest('[data-canopy-nav-item]')
|
|
1642
|
+
: null;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1646
|
+
entries.forEach(function (entry) {
|
|
1647
|
+
var item = entry.item;
|
|
1648
|
+
if (!item) return;
|
|
1649
|
+
if (!activeParents.has(item) && entry.id !== id) {
|
|
1650
|
+
item.setAttribute('data-expanded', 'false');
|
|
1651
|
+
var toggle = item.querySelector('[data-canopy-nav-item-toggle]');
|
|
1652
|
+
if (toggle) {
|
|
1653
|
+
toggle.setAttribute('aria-expanded', 'false');
|
|
1654
|
+
var targetId = toggle.getAttribute('data-canopy-nav-item-toggle');
|
|
1655
|
+
if (targetId) {
|
|
1656
|
+
var panel = document.getElementById(targetId);
|
|
1657
|
+
if (panel) {
|
|
1658
|
+
panel.hidden = true;
|
|
1659
|
+
panel.setAttribute('hidden', '');
|
|
1660
|
+
panel.setAttribute('aria-hidden', 'true');
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
function updateActive() {
|
|
1669
|
+
var offset = computeOffsetPx();
|
|
1670
|
+
var baseFont = 16;
|
|
1671
|
+
try {
|
|
1672
|
+
var root = document.documentElement;
|
|
1673
|
+
baseFont = parseFloat(window.getComputedStyle(root).fontSize || '16') || 16;
|
|
1674
|
+
} catch (_) {}
|
|
1675
|
+
var proximityLimit = baseFont * 5;
|
|
1676
|
+
var fallbackId = entries[0].id;
|
|
1677
|
+
var bestId = fallbackId;
|
|
1678
|
+
var bestDistance = Number.POSITIVE_INFINITY;
|
|
1679
|
+
entries.forEach(function (entry) {
|
|
1680
|
+
if (!entry || !entry.target) return;
|
|
1681
|
+
var rect = entry.target.getBoundingClientRect();
|
|
1682
|
+
var relativeTop = rect.top - offset;
|
|
1683
|
+
if (relativeTop < -proximityLimit) return;
|
|
1684
|
+
var distance = Math.abs(relativeTop);
|
|
1685
|
+
if (distance < bestDistance) {
|
|
1686
|
+
bestDistance = distance;
|
|
1687
|
+
bestId = entry.id;
|
|
1688
|
+
}
|
|
1689
|
+
});
|
|
1690
|
+
applyActive(bestId || fallbackId);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
updateActive();
|
|
1694
|
+
var ticking = false;
|
|
1695
|
+
function handle() {
|
|
1696
|
+
if (ticking) return;
|
|
1697
|
+
ticking = true;
|
|
1698
|
+
window.requestAnimationFrame(function () {
|
|
1699
|
+
updateActive();
|
|
1700
|
+
ticking = false;
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
window.addEventListener('scroll', handle, { passive: true });
|
|
1704
|
+
window.addEventListener('resize', handle);
|
|
1399
1705
|
}
|
|
1400
1706
|
|
|
1401
1707
|
ready(function () {
|
|
@@ -1405,7 +1711,16 @@ function ContentNavigationScript() {
|
|
|
1405
1711
|
if (!roots.length) return;
|
|
1406
1712
|
var stored = getStored();
|
|
1407
1713
|
var collapsed = true;
|
|
1408
|
-
|
|
1714
|
+
var isDesktop = false;
|
|
1715
|
+
try {
|
|
1716
|
+
var bp = window.getComputedStyle(document.documentElement).getPropertyValue('--canopy-desktop-breakpoint') || '70rem';
|
|
1717
|
+
isDesktop = window.matchMedia('(min-width: ' + bp.trim() + ')').matches;
|
|
1718
|
+
} catch (_) {
|
|
1719
|
+
isDesktop = false;
|
|
1720
|
+
}
|
|
1721
|
+
if (!isDesktop) {
|
|
1722
|
+
collapsed = true;
|
|
1723
|
+
} else if (stored === '0' || stored === 'false') {
|
|
1409
1724
|
collapsed = false;
|
|
1410
1725
|
} else if (stored === '1' || stored === 'true') {
|
|
1411
1726
|
collapsed = true;
|
|
@@ -1429,6 +1744,11 @@ function ContentNavigationScript() {
|
|
|
1429
1744
|
sync(!collapsed);
|
|
1430
1745
|
});
|
|
1431
1746
|
});
|
|
1747
|
+
|
|
1748
|
+
roots.forEach(function (root) {
|
|
1749
|
+
setupFloatingState(root);
|
|
1750
|
+
setupActiveHeadingWatcher(root);
|
|
1751
|
+
});
|
|
1432
1752
|
});
|
|
1433
1753
|
})();
|
|
1434
1754
|
`;
|
|
@@ -1497,6 +1817,13 @@ function Layout({
|
|
|
1497
1817
|
className: contentNavigationAsideClassName,
|
|
1498
1818
|
"data-canopy-content-nav-root": "true"
|
|
1499
1819
|
},
|
|
1820
|
+
/* @__PURE__ */ React13.createElement(
|
|
1821
|
+
"div",
|
|
1822
|
+
{
|
|
1823
|
+
"data-canopy-content-nav-sentinel": "true",
|
|
1824
|
+
"aria-hidden": "true"
|
|
1825
|
+
}
|
|
1826
|
+
),
|
|
1500
1827
|
/* @__PURE__ */ React13.createElement(
|
|
1501
1828
|
ContentNavigation,
|
|
1502
1829
|
{
|
|
@@ -1505,6 +1832,13 @@ function Layout({
|
|
|
1505
1832
|
headingId: headingAnchorId || void 0,
|
|
1506
1833
|
pageTitle: context && context.page ? context.page.title : void 0
|
|
1507
1834
|
}
|
|
1835
|
+
),
|
|
1836
|
+
/* @__PURE__ */ React13.createElement(
|
|
1837
|
+
"div",
|
|
1838
|
+
{
|
|
1839
|
+
"data-canopy-content-nav-placeholder": "true",
|
|
1840
|
+
"aria-hidden": "true"
|
|
1841
|
+
}
|
|
1508
1842
|
)
|
|
1509
1843
|
), /* @__PURE__ */ React13.createElement(ContentNavigationScript, null)) : null);
|
|
1510
1844
|
}
|
|
@@ -2132,7 +2466,7 @@ function getSharedRoot() {
|
|
|
2132
2466
|
function getSafePageContext() {
|
|
2133
2467
|
const root = getSharedRoot();
|
|
2134
2468
|
if (root && root[CONTEXT_KEY]) return root[CONTEXT_KEY];
|
|
2135
|
-
const ctx = React20.createContext({ navigation: null, page: null });
|
|
2469
|
+
const ctx = React20.createContext({ navigation: null, page: null, site: null });
|
|
2136
2470
|
if (root) root[CONTEXT_KEY] = ctx;
|
|
2137
2471
|
return ctx;
|
|
2138
2472
|
}
|
|
@@ -2199,13 +2533,18 @@ function CanopyHeader(props = {}) {
|
|
|
2199
2533
|
searchHotkey = "mod+k",
|
|
2200
2534
|
searchPlaceholder = "Search\u2026",
|
|
2201
2535
|
brandHref = "/",
|
|
2202
|
-
title
|
|
2536
|
+
title: titleProp,
|
|
2203
2537
|
logo: SiteLogo
|
|
2204
2538
|
} = props;
|
|
2205
2539
|
const navLinks = ensureArray(navLinksProp);
|
|
2206
2540
|
const PageContext = getSafePageContext();
|
|
2207
2541
|
const context = React20.useContext(PageContext);
|
|
2208
2542
|
const contextNavigation = context && context.navigation ? context.navigation : null;
|
|
2543
|
+
const contextSite = context && context.site ? context.site : null;
|
|
2544
|
+
const contextSiteTitle = contextSite && typeof contextSite.title === "string" ? contextSite.title.trim() : "";
|
|
2545
|
+
const defaultHeaderTitle = contextSiteTitle || "Site title";
|
|
2546
|
+
const normalizedTitleProp = typeof titleProp === "string" ? titleProp.trim() : "";
|
|
2547
|
+
const resolvedTitle = normalizedTitleProp || defaultHeaderTitle;
|
|
2209
2548
|
const sectionNavigation = contextNavigation && contextNavigation.root ? contextNavigation : null;
|
|
2210
2549
|
const navigationRoots = contextNavigation && contextNavigation.allRoots ? contextNavigation.allRoots : null;
|
|
2211
2550
|
const sectionHeading = sectionNavigation && sectionNavigation.title || (sectionNavigation && sectionNavigation.root ? sectionNavigation.root.title : "");
|
|
@@ -2234,7 +2573,7 @@ function CanopyHeader(props = {}) {
|
|
|
2234
2573
|
/* @__PURE__ */ React20.createElement("div", { className: "canopy-header__brand" }, /* @__PURE__ */ React20.createElement(
|
|
2235
2574
|
CanopyBrand,
|
|
2236
2575
|
{
|
|
2237
|
-
label:
|
|
2576
|
+
label: resolvedTitle,
|
|
2238
2577
|
href: brandHref,
|
|
2239
2578
|
className: "canopy-header__brand-link",
|
|
2240
2579
|
Logo: SiteLogo
|
|
@@ -2329,7 +2668,7 @@ function CanopyHeader(props = {}) {
|
|
|
2329
2668
|
id: "canopy-modal-nav",
|
|
2330
2669
|
variant: "nav",
|
|
2331
2670
|
labelledBy: "canopy-modal-nav-label",
|
|
2332
|
-
label:
|
|
2671
|
+
label: resolvedTitle,
|
|
2333
2672
|
logo: SiteLogo,
|
|
2334
2673
|
href: brandHref,
|
|
2335
2674
|
closeLabel: "Close navigation",
|
|
@@ -2424,7 +2763,7 @@ function CanopyHeader(props = {}) {
|
|
|
2424
2763
|
id: "canopy-modal-search",
|
|
2425
2764
|
variant: "search",
|
|
2426
2765
|
labelledBy: "canopy-modal-search-label",
|
|
2427
|
-
label:
|
|
2766
|
+
label: resolvedTitle,
|
|
2428
2767
|
logo: SiteLogo,
|
|
2429
2768
|
href: brandHref,
|
|
2430
2769
|
closeLabel: "Close search",
|