@canopy-iiif/app 1.10.3 → 1.10.5

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": "1.10.3",
3
+ "version": "1.10.5",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Mat Jordan <mat@northwestern.edu>",
package/ui/dist/index.mjs CHANGED
@@ -4172,7 +4172,7 @@ var init_rgb_hex = __esm({
4172
4172
  });
4173
4173
 
4174
4174
  // ../../node_modules/@allmaps/stdlib/dist/color.js
4175
- function hexToRgb(hex) {
4175
+ function hexToRgb2(hex) {
4176
4176
  return hexRgb(hex, { format: "array" }).slice(0, 3);
4177
4177
  }
4178
4178
  function hexToOpaqueRgba(hex) {
@@ -4181,7 +4181,7 @@ function hexToOpaqueRgba(hex) {
4181
4181
  return color;
4182
4182
  }
4183
4183
  function hexToFractionalRgb(hex) {
4184
- return hexToRgb(hex).map((c) => c / 255);
4184
+ return hexToRgb2(hex).map((c) => c / 255);
4185
4185
  }
4186
4186
  function hexToFractionalOpaqueRgba(hex) {
4187
4187
  return hexToOpaqueRgba(hex).map((c) => c / 255);
@@ -46543,6 +46543,214 @@ function ReferencedManifestCard({
46543
46543
  );
46544
46544
  }
46545
46545
 
46546
+ // ui/src/utils/keyLegend.js
46547
+ var DEFAULT_ACCENT_HEX = "#2563eb";
46548
+ function normalizeHex(value) {
46549
+ if (!value) return "";
46550
+ let input = String(value).trim();
46551
+ if (!input) return "";
46552
+ if (input.startsWith("var(")) return input;
46553
+ if (/^#[0-9a-f]{3}$/i.test(input)) {
46554
+ return "#" + input.replace(/^#/, "").split("").map((ch) => ch + ch).join("").toLowerCase();
46555
+ }
46556
+ if (/^#[0-9a-f]{6}$/i.test(input)) return input.toLowerCase();
46557
+ return "";
46558
+ }
46559
+ function hexToRgb(hex) {
46560
+ if (!hex) return null;
46561
+ const normalized = normalizeHex(hex);
46562
+ if (!normalized || normalized.startsWith("var(")) return null;
46563
+ const int = parseInt(normalized.slice(1), 16);
46564
+ if (Number.isNaN(int)) return null;
46565
+ return {
46566
+ r: int >> 16 & 255,
46567
+ g: int >> 8 & 255,
46568
+ b: int & 255
46569
+ };
46570
+ }
46571
+ function rgbToHsl({ r, g, b }) {
46572
+ const rn = r / 255;
46573
+ const gn = g / 255;
46574
+ const bn = b / 255;
46575
+ const max2 = Math.max(rn, gn, bn);
46576
+ const min2 = Math.min(rn, gn, bn);
46577
+ let h = 0;
46578
+ let s = 0;
46579
+ const l = (max2 + min2) / 2;
46580
+ const delta = max2 - min2;
46581
+ if (delta !== 0) {
46582
+ s = l > 0.5 ? delta / (2 - max2 - min2) : delta / (max2 + min2);
46583
+ switch (max2) {
46584
+ case rn:
46585
+ h = (gn - bn) / delta + (gn < bn ? 6 : 0);
46586
+ break;
46587
+ case gn:
46588
+ h = (bn - rn) / delta + 2;
46589
+ break;
46590
+ case bn:
46591
+ h = (rn - gn) / delta + 4;
46592
+ break;
46593
+ default:
46594
+ break;
46595
+ }
46596
+ h /= 6;
46597
+ }
46598
+ return { h: h * 360, s: s * 100, l: l * 100 };
46599
+ }
46600
+ function hslToHex(h, s, l) {
46601
+ const sat = s / 100;
46602
+ const light = l / 100;
46603
+ const c = (1 - Math.abs(2 * light - 1)) * sat;
46604
+ const hh = h / 60;
46605
+ const x = c * (1 - Math.abs(hh % 2 - 1));
46606
+ let r = 0;
46607
+ let g = 0;
46608
+ let b = 0;
46609
+ if (hh >= 0 && hh < 1) {
46610
+ r = c;
46611
+ g = x;
46612
+ } else if (hh >= 1 && hh < 2) {
46613
+ r = x;
46614
+ g = c;
46615
+ } else if (hh >= 2 && hh < 3) {
46616
+ g = c;
46617
+ b = x;
46618
+ } else if (hh >= 3 && hh < 4) {
46619
+ g = x;
46620
+ b = c;
46621
+ } else if (hh >= 4 && hh < 5) {
46622
+ r = x;
46623
+ b = c;
46624
+ } else {
46625
+ r = c;
46626
+ b = x;
46627
+ }
46628
+ const m = light - c / 2;
46629
+ const rn = Math.round((r + m) * 255);
46630
+ const gn = Math.round((g + m) * 255);
46631
+ const bn = Math.round((b + m) * 255);
46632
+ const toHex = (value) => value.toString(16).padStart(2, "0");
46633
+ return `#${toHex(rn)}${toHex(gn)}${toHex(bn)}`;
46634
+ }
46635
+ function rotateHue(baseHue, degrees) {
46636
+ return (baseHue + degrees + 360) % 360;
46637
+ }
46638
+ function resolveAccentHex() {
46639
+ let value = "";
46640
+ try {
46641
+ if (typeof window !== "undefined") {
46642
+ const styles = window.getComputedStyle(document.documentElement);
46643
+ value = styles.getPropertyValue("--color-accent-default");
46644
+ }
46645
+ } catch (_) {
46646
+ }
46647
+ const normalized = normalizeHex(value);
46648
+ return normalized && !normalized.startsWith("var(") ? normalized : DEFAULT_ACCENT_HEX;
46649
+ }
46650
+ function generateLegendColors(count) {
46651
+ if (!count || count <= 0) return [];
46652
+ const colors = [];
46653
+ const baseHex = resolveAccentHex();
46654
+ const accentVar = `var(--color-accent-default, ${baseHex})`;
46655
+ colors.push(accentVar);
46656
+ if (count === 1) return colors;
46657
+ const rgb = hexToRgb(baseHex);
46658
+ const baseHsl = rgb ? rgbToHsl(rgb) : { h: 220, s: 85, l: 56 };
46659
+ const rotations = [180, 120, -120, 60, -60, 90, -90, 30, -30];
46660
+ const needed = count - 1;
46661
+ for (let i = 0; i < needed; i += 1) {
46662
+ const angle2 = rotations[i] != null ? rotations[i] : 360 / (needed + 1) * (i + 1);
46663
+ const rotatedHue = rotateHue(baseHsl.h, angle2);
46664
+ const hex = hslToHex(rotatedHue, baseHsl.s, baseHsl.l);
46665
+ colors.push(hex);
46666
+ }
46667
+ return colors;
46668
+ }
46669
+ function createLookupStore() {
46670
+ try {
46671
+ if (typeof globalThis !== "undefined" && typeof globalThis.Map === "function") {
46672
+ return new globalThis.Map();
46673
+ }
46674
+ } catch (_) {
46675
+ }
46676
+ try {
46677
+ if (typeof window !== "undefined" && typeof window.Map === "function") {
46678
+ return new window.Map();
46679
+ }
46680
+ } catch (_) {
46681
+ }
46682
+ const store = /* @__PURE__ */ Object.create(null);
46683
+ return {
46684
+ has(key) {
46685
+ return Object.prototype.hasOwnProperty.call(store, key);
46686
+ },
46687
+ get(key) {
46688
+ return store[key];
46689
+ },
46690
+ set(key, value) {
46691
+ store[key] = value;
46692
+ return this;
46693
+ }
46694
+ };
46695
+ }
46696
+ function normalizeText(value) {
46697
+ if (value == null) return "";
46698
+ try {
46699
+ return String(value).trim();
46700
+ } catch (_) {
46701
+ return "";
46702
+ }
46703
+ }
46704
+ function normalizeKeyValue(entry) {
46705
+ var _a2, _b, _c;
46706
+ if (!entry) return "";
46707
+ const value = (_c = (_b = (_a2 = entry.id) != null ? _a2 : entry.value) != null ? _b : entry.key) != null ? _c : entry.slug;
46708
+ return normalizeText(value);
46709
+ }
46710
+ function normalizeKeyLabel(entry) {
46711
+ var _a2, _b, _c;
46712
+ if (!entry) return "";
46713
+ const label = (_c = (_b = (_a2 = entry.label) != null ? _a2 : entry.name) != null ? _b : entry.title) != null ? _c : entry.text;
46714
+ return normalizeText(label);
46715
+ }
46716
+ function normalizeKeyLegendEntries(entries, options = {}) {
46717
+ if (!Array.isArray(entries)) return [];
46718
+ const resolveVariant = typeof options.resolveVariant === "function" ? options.resolveVariant : null;
46719
+ const variantProp = options.variantProp || "variant";
46720
+ return entries.map((entry) => {
46721
+ if (!entry) return null;
46722
+ const keyValue = normalizeKeyValue(entry);
46723
+ const label = normalizeKeyLabel(entry);
46724
+ if (!keyValue || !label) return null;
46725
+ const normalized = {
46726
+ keyValue,
46727
+ label
46728
+ };
46729
+ if (resolveVariant) {
46730
+ const variant = resolveVariant(entry);
46731
+ if (variant) normalized[variantProp] = variant;
46732
+ }
46733
+ return normalized;
46734
+ }).filter(Boolean);
46735
+ }
46736
+ function buildKeyLegend(entries) {
46737
+ if (!Array.isArray(entries) || !entries.length) {
46738
+ return { groups: [], lookup: null };
46739
+ }
46740
+ const lookup = createLookupStore();
46741
+ const palette = generateLegendColors(entries.length);
46742
+ const groups = entries.map((entry, index) => {
46743
+ const color = palette[index] || palette[0] || DEFAULT_ACCENT_HEX;
46744
+ const group = {
46745
+ ...entry,
46746
+ color
46747
+ };
46748
+ lookup.set(entry.keyValue, group);
46749
+ return group;
46750
+ });
46751
+ return { groups, lookup };
46752
+ }
46753
+
46546
46754
  // ui/src/content/timeline/Timeline.jsx
46547
46755
  var DAY_MS = 24 * 60 * 60 * 1e3;
46548
46756
  var DEFAULT_TRACK_HEIGHT = 640;
@@ -46680,6 +46888,7 @@ function sanitizePoints(points) {
46680
46888
  label: meta.label || "",
46681
46889
  timestamp: Number.isFinite(timestamp) ? timestamp : null
46682
46890
  },
46891
+ keyValue: point2.keyValue ? String(point2.keyValue).trim() : "",
46683
46892
  manifests,
46684
46893
  resources
46685
46894
  };
@@ -46710,7 +46919,7 @@ function resolveTrackHeight(height, pointCount) {
46710
46919
  }
46711
46920
  return fallback;
46712
46921
  }
46713
- function TimelineConnector({ side, isActive, highlight }) {
46922
+ function TimelineConnector({ side, isActive, highlight, color }) {
46714
46923
  const connectorClasses = [
46715
46924
  "canopy-timeline__connector",
46716
46925
  side === "left" ? "canopy-timeline__connector--left" : "canopy-timeline__connector--right"
@@ -46719,7 +46928,8 @@ function TimelineConnector({ side, isActive, highlight }) {
46719
46928
  "canopy-timeline__connector-dot",
46720
46929
  highlight || isActive ? "is-active" : ""
46721
46930
  ].filter(Boolean).join(" ");
46722
- return /* @__PURE__ */ React41.createElement("span", { className: connectorClasses, "aria-hidden": "true" }, side === "left" ? /* @__PURE__ */ React41.createElement(React41.Fragment, null, /* @__PURE__ */ React41.createElement("span", { className: "canopy-timeline__connector-line" }), /* @__PURE__ */ React41.createElement("span", { className: dotClasses })) : /* @__PURE__ */ React41.createElement(React41.Fragment, null, /* @__PURE__ */ React41.createElement("span", { className: dotClasses }), /* @__PURE__ */ React41.createElement("span", { className: "canopy-timeline__connector-line" })));
46931
+ const connectorStyle = color ? { "--canopy-timeline-point-color": color } : void 0;
46932
+ return /* @__PURE__ */ React41.createElement("span", { className: connectorClasses, "aria-hidden": "true", style: connectorStyle }, side === "left" ? /* @__PURE__ */ React41.createElement(React41.Fragment, null, /* @__PURE__ */ React41.createElement("span", { className: "canopy-timeline__connector-line" }), /* @__PURE__ */ React41.createElement("span", { className: dotClasses })) : /* @__PURE__ */ React41.createElement(React41.Fragment, null, /* @__PURE__ */ React41.createElement("span", { className: dotClasses }), /* @__PURE__ */ React41.createElement("span", { className: "canopy-timeline__connector-line" })));
46723
46933
  }
46724
46934
  function renderResourceSection(point2) {
46725
46935
  if (!point2) return null;
@@ -46749,6 +46959,9 @@ function Timeline({
46749
46959
  align = ALIGN_OPTIONS.CENTER,
46750
46960
  steps = null,
46751
46961
  points: pointsProp,
46962
+ keyConfig = [],
46963
+ timelineKey = [],
46964
+ legend = [],
46752
46965
  __canopyTimeline: payload = null,
46753
46966
  ...rest
46754
46967
  }) {
@@ -46762,6 +46975,35 @@ function Timeline({
46762
46975
  () => sanitizePoints(rawPoints),
46763
46976
  [rawPoints]
46764
46977
  );
46978
+ const resolvedKeyInput = React41.useMemo(() => {
46979
+ if (Array.isArray(keyConfig) && keyConfig.length) return keyConfig;
46980
+ if (Array.isArray(timelineKey) && timelineKey.length) return timelineKey;
46981
+ if (Array.isArray(legend) && legend.length) return legend;
46982
+ return [];
46983
+ }, [keyConfig, timelineKey, legend]);
46984
+ const normalizedLegendConfig = React41.useMemo(
46985
+ () => normalizeKeyLegendEntries(resolvedKeyInput),
46986
+ [resolvedKeyInput]
46987
+ );
46988
+ const timelineKeyData = React41.useMemo(
46989
+ () => buildKeyLegend(normalizedLegendConfig),
46990
+ [normalizedLegendConfig]
46991
+ );
46992
+ const timelineKeyGroups = timelineKeyData.groups;
46993
+ const timelineKeyLookup = timelineKeyData.lookup;
46994
+ const getLegendMeta = React41.useCallback(
46995
+ (value) => {
46996
+ if (!value || !timelineKeyLookup || typeof timelineKeyLookup.get !== "function") {
46997
+ return null;
46998
+ }
46999
+ try {
47000
+ return timelineKeyLookup.get(value) || null;
47001
+ } catch (_) {
47002
+ return null;
47003
+ }
47004
+ },
47005
+ [timelineKeyLookup]
47006
+ );
46765
47007
  const localeValue = payload && payload.locale ? payload.locale : localeProp;
46766
47008
  const baseLocale = React41.useMemo(
46767
47009
  () => createLocale(localeValue),
@@ -46792,13 +47034,22 @@ function Timeline({
46792
47034
  const fallbackProgress = sanitizedPoints.length > 1 ? index / (sanitizedPoints.length - 1) : 0;
46793
47035
  const progress = useUniformSpacing ? fallbackProgress : Number.isFinite(timestamp) ? clampProgress((timestamp - spanStart) / span) : fallbackProgress;
46794
47036
  const side = enforcedSide || point2.side || (index % 2 === 0 ? "left" : "right");
47037
+ const keyMeta = point2.keyValue ? getLegendMeta(point2.keyValue) : null;
46795
47038
  return {
46796
47039
  ...point2,
46797
47040
  progress,
46798
- side
47041
+ side,
47042
+ keyMeta
46799
47043
  };
46800
47044
  });
46801
- }, [sanitizedPoints, spanStart, span, useUniformSpacing, enforcedSide]);
47045
+ }, [
47046
+ sanitizedPoints,
47047
+ spanStart,
47048
+ span,
47049
+ useUniformSpacing,
47050
+ enforcedSide,
47051
+ getLegendMeta
47052
+ ]);
46802
47053
  const [activeId, setActiveId] = React41.useState(
46803
47054
  () => getActivePointId(pointsWithPosition)
46804
47055
  );
@@ -46851,6 +47102,7 @@ function Timeline({
46851
47102
  className
46852
47103
  ].filter(Boolean).join(" ");
46853
47104
  const rangeLabel = formatRangeLabel(effectiveRange);
47105
+ const hasKeyLegend = timelineKeyGroups.length > 0;
46854
47106
  function renderPointEntry(point2) {
46855
47107
  if (!point2) return null;
46856
47108
  const wrapperClasses = [
@@ -46863,12 +47115,14 @@ function Timeline({
46863
47115
  point2.id === activeId ? "is-active" : "",
46864
47116
  point2.highlight ? "is-highlighted" : ""
46865
47117
  ].filter(Boolean).join(" ");
47118
+ const pointColor = point2.keyMeta && point2.keyMeta.color ? point2.keyMeta.color : null;
46866
47119
  const connector = /* @__PURE__ */ React41.createElement(
46867
47120
  TimelineConnector,
46868
47121
  {
46869
47122
  side: point2.side,
46870
47123
  isActive: point2.id === activeId,
46871
- highlight: point2.highlight
47124
+ highlight: point2.highlight,
47125
+ color: pointColor
46872
47126
  }
46873
47127
  );
46874
47128
  const body = /* @__PURE__ */ React41.createElement("div", { className: "canopy-timeline__point-body" }, /* @__PURE__ */ React41.createElement("span", { className: "canopy-timeline__point-date" }, point2.meta.label), /* @__PURE__ */ React41.createElement("span", { className: "canopy-timeline__point-title" }, point2.title), point2.summary ? /* @__PURE__ */ React41.createElement("span", { className: "canopy-timeline__point-summary" }, point2.summary) : null);
@@ -46892,12 +47146,24 @@ function Timeline({
46892
47146
  const wrapperStyle = { top: `${entry.progress * 100}%` };
46893
47147
  const isExpanded = expandedGroupIds.has(entry.id);
46894
47148
  const hasActivePoint = entry.points.some((point2) => point2.id === activeId);
47149
+ const groupKeyMeta = (() => {
47150
+ if (!entry.points || !entry.points.length) return null;
47151
+ const firstPoint = entry.points[0];
47152
+ if (!firstPoint || !firstPoint.keyValue) return null;
47153
+ const sameKey = entry.points.every(
47154
+ (point2) => point2 && point2.keyValue === firstPoint.keyValue
47155
+ );
47156
+ if (!sameKey) return null;
47157
+ return firstPoint.keyMeta || getLegendMeta(firstPoint.keyValue);
47158
+ })();
47159
+ const groupColor = groupKeyMeta && groupKeyMeta.color ? groupKeyMeta.color : null;
46895
47160
  const connector = /* @__PURE__ */ React41.createElement(
46896
47161
  TimelineConnector,
46897
47162
  {
46898
47163
  side: entry.side,
46899
47164
  isActive: hasActivePoint,
46900
- highlight: hasActivePoint
47165
+ highlight: hasActivePoint,
47166
+ color: groupColor
46901
47167
  }
46902
47168
  );
46903
47169
  const groupClasses = [
@@ -46955,7 +47221,22 @@ function Timeline({
46955
47221
  if (entry.type === "group") return renderGroupEntry(entry);
46956
47222
  return renderPointEntry(entry.point);
46957
47223
  })
46958
- )));
47224
+ )), hasKeyLegend ? /* @__PURE__ */ React41.createElement("div", { className: "canopy-timeline__key", "aria-label": "Timeline key" }, /* @__PURE__ */ React41.createElement("ul", { className: "canopy-timeline__key-list" }, timelineKeyGroups.map((group) => /* @__PURE__ */ React41.createElement(
47225
+ "li",
47226
+ {
47227
+ key: group.keyValue || group.label,
47228
+ className: "canopy-timeline__key-item"
47229
+ },
47230
+ /* @__PURE__ */ React41.createElement(
47231
+ "span",
47232
+ {
47233
+ className: "canopy-timeline__key-dot",
47234
+ "aria-hidden": "true",
47235
+ style: { backgroundColor: group.color || void 0 }
47236
+ }
47237
+ ),
47238
+ /* @__PURE__ */ React41.createElement("span", { className: "canopy-timeline__key-label" }, group.label)
47239
+ )))) : null);
46959
47240
  }
46960
47241
  function renderSteps(stepSize, range) {
46961
47242
  if (!Number.isFinite(stepSize) || stepSize <= 0 || !range) return null;
@@ -47034,7 +47315,6 @@ var TRANSPARENT_TILE_URL = "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAA
47034
47315
  var CUSTOM_MARKER_SIZE = 40;
47035
47316
  var CUSTOM_MARKER_RADIUS = CUSTOM_MARKER_SIZE / 2;
47036
47317
  var CUSTOM_MARKER_POPUP_OFFSET = -CUSTOM_MARKER_RADIUS + 6;
47037
- var DEFAULT_ACCENT_HEX = "#2563eb";
47038
47318
  function resolveGlobalLeaflet() {
47039
47319
  try {
47040
47320
  if (typeof globalThis !== "undefined" && globalThis.L) return globalThis.L;
@@ -47167,126 +47447,6 @@ function createMarkerMap() {
47167
47447
  }
47168
47448
  };
47169
47449
  }
47170
- function normalizeHex(value) {
47171
- if (!value) return "";
47172
- let input = String(value).trim();
47173
- if (!input) return "";
47174
- if (input.startsWith("var(")) return input;
47175
- if (/^#[0-9a-f]{3}$/i.test(input)) {
47176
- return input.replace(/^#/, "").split("").map((ch) => ch + ch).join("").replace(/^/, "#");
47177
- }
47178
- if (/^#[0-9a-f]{6}$/i.test(input)) return input;
47179
- return "";
47180
- }
47181
- function hexToRgb2(hex) {
47182
- if (!hex) return null;
47183
- const normalized = normalizeHex(hex);
47184
- if (!normalized) return null;
47185
- const int = parseInt(normalized.slice(1), 16);
47186
- return {
47187
- r: int >> 16 & 255,
47188
- g: int >> 8 & 255,
47189
- b: int & 255
47190
- };
47191
- }
47192
- function rgbToHsl({ r, g, b }) {
47193
- const rn = r / 255;
47194
- const gn = g / 255;
47195
- const bn = b / 255;
47196
- const max2 = Math.max(rn, gn, bn);
47197
- const min2 = Math.min(rn, gn, bn);
47198
- let h = 0;
47199
- let s = 0;
47200
- const l = (max2 + min2) / 2;
47201
- const delta = max2 - min2;
47202
- if (delta !== 0) {
47203
- s = l > 0.5 ? delta / (2 - max2 - min2) : delta / (max2 + min2);
47204
- switch (max2) {
47205
- case rn:
47206
- h = (gn - bn) / delta + (gn < bn ? 6 : 0);
47207
- break;
47208
- case gn:
47209
- h = (bn - rn) / delta + 2;
47210
- break;
47211
- case bn:
47212
- h = (rn - gn) / delta + 4;
47213
- break;
47214
- default:
47215
- break;
47216
- }
47217
- h /= 6;
47218
- }
47219
- return { h: h * 360, s: s * 100, l: l * 100 };
47220
- }
47221
- function hslToHex(h, s, l) {
47222
- const sat = s / 100;
47223
- const light = l / 100;
47224
- const c = (1 - Math.abs(2 * light - 1)) * sat;
47225
- const hh = h / 60;
47226
- const x = c * (1 - Math.abs(hh % 2 - 1));
47227
- let r = 0;
47228
- let g = 0;
47229
- let b = 0;
47230
- if (hh >= 0 && hh < 1) {
47231
- r = c;
47232
- g = x;
47233
- } else if (hh >= 1 && hh < 2) {
47234
- r = x;
47235
- g = c;
47236
- } else if (hh >= 2 && hh < 3) {
47237
- g = c;
47238
- b = x;
47239
- } else if (hh >= 3 && hh < 4) {
47240
- g = x;
47241
- b = c;
47242
- } else if (hh >= 4 && hh < 5) {
47243
- r = x;
47244
- b = c;
47245
- } else {
47246
- r = c;
47247
- b = x;
47248
- }
47249
- const m = light - c / 2;
47250
- const rn = Math.round((r + m) * 255);
47251
- const gn = Math.round((g + m) * 255);
47252
- const bn = Math.round((b + m) * 255);
47253
- const toHex = (value) => value.toString(16).padStart(2, "0");
47254
- return `#${toHex(rn)}${toHex(gn)}${toHex(bn)}`;
47255
- }
47256
- function rotateHue(baseHue, degrees) {
47257
- return (baseHue + degrees + 360) % 360;
47258
- }
47259
- function resolveAccentHex() {
47260
- let value = "";
47261
- try {
47262
- if (typeof window !== "undefined") {
47263
- const styles = window.getComputedStyle(document.documentElement);
47264
- value = styles.getPropertyValue("--color-accent-default");
47265
- }
47266
- } catch (_) {
47267
- }
47268
- const normalized = normalizeHex(value);
47269
- return normalized || DEFAULT_ACCENT_HEX;
47270
- }
47271
- function generateLegendColors(count) {
47272
- if (!count || count <= 0) return [];
47273
- const colors = [];
47274
- const baseHex = resolveAccentHex();
47275
- const accentVar = `var(--color-accent-default, ${baseHex})`;
47276
- colors.push(accentVar);
47277
- if (count === 1) return colors;
47278
- const rgb = hexToRgb2(baseHex);
47279
- const baseHsl = rgb ? rgbToHsl(rgb) : { h: 220, s: 85, l: 56 };
47280
- const rotations = [180, 120, -120, 60, -60, 90, -90, 30, -30];
47281
- const needed = count - 1;
47282
- for (let i = 0; i < needed; i += 1) {
47283
- const angle2 = rotations[i] != null ? rotations[i] : 360 / (needed + 1) * (i + 1);
47284
- const rotatedHue = rotateHue(baseHsl.h, angle2);
47285
- const hex = hslToHex(rotatedHue, baseHsl.s, baseHsl.l);
47286
- colors.push(hex);
47287
- }
47288
- return colors;
47289
- }
47290
47450
  function readIiifType(resource) {
47291
47451
  if (!resource) return "";
47292
47452
  const raw = resource.type || resource["@type"];
@@ -47816,43 +47976,19 @@ function Map3({
47816
47976
  if (Array.isArray(legend) && legend.length) return legend;
47817
47977
  return [];
47818
47978
  }, [keyConfig, mapKey, legend]);
47819
- const normalizedLegendConfig = React42.useMemo(() => {
47820
- if (!Array.isArray(resolvedKeyInput) || !resolvedKeyInput.length) return [];
47821
- return resolvedKeyInput.map((entry) => {
47822
- if (!entry) return null;
47823
- const value = entry.id || entry.value || entry.key;
47824
- const label = entry.label || entry.name || entry.title;
47825
- if (!value || !label) return null;
47826
- return {
47827
- keyValue: String(value).trim(),
47828
- label: String(label).trim(),
47829
- markerType: normalizeMarkerVariant(
47830
- entry.markerType || entry.type || entry.variant
47831
- )
47832
- };
47833
- }).filter(Boolean);
47834
- }, [resolvedKeyInput]);
47835
- const markerKeyData = React42.useMemo(() => {
47836
- if (!normalizedLegendConfig.length) return { groups: [], metaMap: null };
47837
- const metaMap = createMarkerMap();
47838
- const palette = generateLegendColors(normalizedLegendConfig.length);
47839
- const groups = normalizedLegendConfig.map((entry, index) => {
47840
- const color = palette[index] || palette[0] || DEFAULT_ACCENT_HEX;
47841
- metaMap.set(entry.keyValue, {
47842
- color,
47843
- markerType: entry.markerType || null
47844
- });
47845
- return {
47846
- keyValue: entry.keyValue,
47847
- label: entry.label,
47848
- color,
47849
- markerType: entry.markerType || null
47850
- };
47851
- });
47852
- return { groups, metaMap };
47853
- }, [normalizedLegendConfig]);
47979
+ const normalizedLegendConfig = React42.useMemo(
47980
+ () => normalizeKeyLegendEntries(resolvedKeyInput, {
47981
+ resolveVariant: (entry) => normalizeMarkerVariant(entry.markerType || entry.type || entry.variant),
47982
+ variantProp: "markerType"
47983
+ }),
47984
+ [resolvedKeyInput]
47985
+ );
47986
+ const markerKeyData = React42.useMemo(
47987
+ () => buildKeyLegend(normalizedLegendConfig),
47988
+ [normalizedLegendConfig]
47989
+ );
47854
47990
  const markerKeyGroups = markerKeyData.groups;
47855
- const markerKeyMetaMap = markerKeyData.metaMap;
47991
+ const markerKeyMetaMap = markerKeyData.lookup;
47856
47992
  const clusterOptions = React42.useMemo(
47857
47993
  () => buildClusterOptions(leafletLib, typeof maxClusterRadius === "number" ? maxClusterRadius : null),
47858
47994
  [leafletLib, maxClusterRadius]
@@ -48472,6 +48608,55 @@ var INLINE_SCRIPT = `(() => {
48472
48608
  emitModalState(previous, 'close');
48473
48609
  }
48474
48610
 
48611
+ function closeModalFromBackground(modal) {
48612
+ if (!modal) return;
48613
+ const closeId = modal.getAttribute('data-canopy-gallery-close');
48614
+ const targetHash = closeId ? '#' + closeId : '';
48615
+ if (targetHash) {
48616
+ if (window.location.hash === targetHash) {
48617
+ try {
48618
+ window.location.hash = targetHash;
48619
+ } catch (_) {}
48620
+ } else {
48621
+ window.location.hash = targetHash;
48622
+ }
48623
+ } else {
48624
+ window.location.hash = '';
48625
+ }
48626
+ }
48627
+
48628
+ function shouldCloseFromBackground(event, modal) {
48629
+ if (!event || !modal) return false;
48630
+ const target = event.target;
48631
+ if (!target) return false;
48632
+ const panel = modal.querySelector('.canopy-gallery__modal-panel');
48633
+ if (panel && panel.contains(target)) return false;
48634
+ const actions = modal.querySelector('.canopy-gallery__modal-actions');
48635
+ if (actions && actions.contains(target)) return false;
48636
+ if (target === modal) return true;
48637
+ const scrim = modal.querySelector('.canopy-gallery__modal-scrim');
48638
+ if (scrim && scrim.contains(target)) return true;
48639
+ return false;
48640
+ }
48641
+
48642
+ function bindModalDismissal(modal) {
48643
+ if (!modal || modal.getAttribute('data-canopy-gallery-dismiss-bound') === '1') {
48644
+ return;
48645
+ }
48646
+ modal.setAttribute('data-canopy-gallery-dismiss-bound', '1');
48647
+ modal.addEventListener('click', function (event) {
48648
+ if (!shouldCloseFromBackground(event, modal)) return;
48649
+ event.preventDefault();
48650
+ closeModalFromBackground(modal);
48651
+ });
48652
+ }
48653
+
48654
+ function bindModalDismissals() {
48655
+ const modals = document.querySelectorAll('[data-canopy-gallery-modal]');
48656
+ if (!modals || !modals.length) return;
48657
+ Array.prototype.forEach.call(modals, bindModalDismissal);
48658
+ }
48659
+
48475
48660
  function modalFromHash() {
48476
48661
  const id = window.location.hash.replace(/^#/, '');
48477
48662
  if (!id) return null;
@@ -48755,10 +48940,12 @@ var INLINE_SCRIPT = `(() => {
48755
48940
  window.addEventListener('pageshow', function () {
48756
48941
  syncFromHash();
48757
48942
  bindGalleryNavs();
48943
+ bindModalDismissals();
48758
48944
  refreshGalleryNavs({reveal: true});
48759
48945
  });
48760
48946
  syncFromHash();
48761
48947
  bindGalleryNavs();
48948
+ bindModalDismissals();
48762
48949
  refreshGalleryNavs({reveal: true});
48763
48950
  })()`;
48764
48951
  var galleryInstanceCounter = 0;
@@ -49126,7 +49313,7 @@ function GalleryItem() {
49126
49313
  GalleryItem.displayName = "GalleryItem";
49127
49314
  function normalizePopupSize(value) {
49128
49315
  const normalized = String(value || "full").toLowerCase();
49129
- return normalized === "medium" ? "medium" : "full";
49316
+ return normalized === "window" ? "window" : "full";
49130
49317
  }
49131
49318
  function Gallery({
49132
49319
  children,
@@ -49150,7 +49337,7 @@ function Gallery({
49150
49337
  const orderedItems = orderMode === "random" ? shuffleItems(items) : items;
49151
49338
  const rootClassName = [
49152
49339
  "canopy-gallery",
49153
- popupMode === "medium" ? "canopy-gallery--popup-medium" : "canopy-gallery--popup-full",
49340
+ popupMode === "window" ? "canopy-gallery--popup-window" : "canopy-gallery--popup-full",
49154
49341
  className
49155
49342
  ].filter(Boolean).join(" ");
49156
49343
  const navGroupName = `${galleryId}-nav`;