@canopy-iiif/app 0.12.4 → 0.12.6

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
@@ -1171,8 +1171,51 @@ function CanopyFooter({ className = "", children }) {
1171
1171
  return /* @__PURE__ */ React15.createElement("footer", { className: footerClassName }, /* @__PURE__ */ React15.createElement("div", { className: "canopy-footer__inner" }, children));
1172
1172
  }
1173
1173
 
1174
+ // ui/src/layout/TeaserCard.jsx
1175
+ import React16 from "react";
1176
+ function TeaserCard({
1177
+ href = "",
1178
+ title = "",
1179
+ metadata = [],
1180
+ summary = "",
1181
+ thumbnail = null,
1182
+ type = "work",
1183
+ className = "",
1184
+ ...rest
1185
+ }) {
1186
+ const Tag = href ? "a" : "div";
1187
+ const classes = [
1188
+ "canopy-card",
1189
+ "canopy-card--teaser",
1190
+ "canopy-search-teaser__item",
1191
+ "canopy-teaser-card",
1192
+ className
1193
+ ].filter(Boolean).join(" ");
1194
+ const showThumb = type === "work" && thumbnail;
1195
+ const metaLine = (Array.isArray(metadata) && metadata.length ? metadata.filter(Boolean) : summary ? [summary] : []).filter(Boolean).slice(0, 2).join(" \u2022 ");
1196
+ return /* @__PURE__ */ React16.createElement(
1197
+ Tag,
1198
+ {
1199
+ className: classes,
1200
+ href: href || void 0,
1201
+ "data-canopy-item": href ? "" : void 0,
1202
+ ...rest
1203
+ },
1204
+ showThumb ? /* @__PURE__ */ React16.createElement("div", { className: "canopy-search-teaser__thumb" }, /* @__PURE__ */ React16.createElement(
1205
+ "img",
1206
+ {
1207
+ src: thumbnail,
1208
+ alt: "",
1209
+ loading: "lazy",
1210
+ className: "canopy-search-teaser__thumb-img"
1211
+ }
1212
+ )) : null,
1213
+ /* @__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)
1214
+ );
1215
+ }
1216
+
1174
1217
  // ui/src/iiif/Viewer.jsx
1175
- import React16, { useEffect as useEffect2, useState as useState2 } from "react";
1218
+ import React17, { useEffect as useEffect2, useState as useState2 } from "react";
1176
1219
  var DEFAULT_VIEWER_OPTIONS = {
1177
1220
  showDownload: false,
1178
1221
  showIIIFBadge: false,
@@ -1228,7 +1271,7 @@ var Viewer = (props) => {
1228
1271
  } catch (_) {
1229
1272
  json = "{}";
1230
1273
  }
1231
- return /* @__PURE__ */ React16.createElement("div", { "data-canopy-viewer": "1", className: "not-prose" }, /* @__PURE__ */ React16.createElement(
1274
+ return /* @__PURE__ */ React17.createElement("div", { "data-canopy-viewer": "1", className: "not-prose" }, /* @__PURE__ */ React17.createElement(
1232
1275
  "script",
1233
1276
  {
1234
1277
  type: "application/json",
@@ -1236,11 +1279,11 @@ var Viewer = (props) => {
1236
1279
  }
1237
1280
  ));
1238
1281
  }
1239
- return /* @__PURE__ */ React16.createElement(CloverViewer, { ...props, options: mergedOptions });
1282
+ return /* @__PURE__ */ React17.createElement(CloverViewer, { ...props, options: mergedOptions });
1240
1283
  };
1241
1284
 
1242
1285
  // ui/src/iiif/Slider.jsx
1243
- import React17, { useEffect as useEffect3, useState as useState3 } from "react";
1286
+ import React18, { useEffect as useEffect3, useState as useState3 } from "react";
1244
1287
 
1245
1288
  // ui/src/iiif/sliderOptions.js
1246
1289
  var UNIT_TOKEN = "__canopySliderUnit";
@@ -1387,7 +1430,7 @@ var Slider = (props = {}) => {
1387
1430
  } catch (_) {
1388
1431
  json = "{}";
1389
1432
  }
1390
- return /* @__PURE__ */ React17.createElement("div", { className: sliderClassName, "data-canopy-slider": "1" }, /* @__PURE__ */ React17.createElement(
1433
+ return /* @__PURE__ */ React18.createElement("div", { className: sliderClassName, "data-canopy-slider": "1" }, /* @__PURE__ */ React18.createElement(
1391
1434
  "script",
1392
1435
  {
1393
1436
  type: "application/json",
@@ -1395,11 +1438,11 @@ var Slider = (props = {}) => {
1395
1438
  }
1396
1439
  ));
1397
1440
  }
1398
- return /* @__PURE__ */ React17.createElement(CloverSlider, { ...resolvedProps });
1441
+ return /* @__PURE__ */ React18.createElement(CloverSlider, { ...resolvedProps });
1399
1442
  };
1400
1443
 
1401
1444
  // ui/src/iiif/Scroll.jsx
1402
- import React18, { useEffect as useEffect4, useState as useState4 } from "react";
1445
+ import React19, { useEffect as useEffect4, useState as useState4 } from "react";
1403
1446
  var Scroll = (props) => {
1404
1447
  const [CloverScroll, setCloverScroll] = useState4(null);
1405
1448
  useEffect4(() => {
@@ -1424,7 +1467,7 @@ var Scroll = (props) => {
1424
1467
  } catch (_) {
1425
1468
  json = "{}";
1426
1469
  }
1427
- return /* @__PURE__ */ React18.createElement("div", { "data-canopy-scroll": "1", className: "not-prose" }, /* @__PURE__ */ React18.createElement(
1470
+ return /* @__PURE__ */ React19.createElement("div", { "data-canopy-scroll": "1", className: "not-prose" }, /* @__PURE__ */ React19.createElement(
1428
1471
  "script",
1429
1472
  {
1430
1473
  type: "application/json",
@@ -1432,11 +1475,11 @@ var Scroll = (props) => {
1432
1475
  }
1433
1476
  ));
1434
1477
  }
1435
- return /* @__PURE__ */ React18.createElement(CloverScroll, { ...props });
1478
+ return /* @__PURE__ */ React19.createElement(CloverScroll, { ...props });
1436
1479
  };
1437
1480
 
1438
1481
  // ui/src/iiif/Image.jsx
1439
- import React19, { useEffect as useEffect5, useState as useState5 } from "react";
1482
+ import React20, { useEffect as useEffect5, useState as useState5 } from "react";
1440
1483
  var Image = (props = {}) => {
1441
1484
  const [CloverImage, setCloverImage] = useState5(null);
1442
1485
  const baseClass = "canopy-iiif-image";
@@ -1469,7 +1512,7 @@ var Image = (props = {}) => {
1469
1512
  } catch (_) {
1470
1513
  json = "{}";
1471
1514
  }
1472
- return /* @__PURE__ */ React19.createElement("figure", { className: rootClassName }, /* @__PURE__ */ React19.createElement(
1515
+ return /* @__PURE__ */ React20.createElement("figure", { className: rootClassName }, /* @__PURE__ */ React20.createElement(
1473
1516
  "div",
1474
1517
  {
1475
1518
  className: `${baseClass}__placeholder`,
@@ -1479,20 +1522,20 @@ var Image = (props = {}) => {
1479
1522
  "--canopy-iiif-image-bg": backgroundColor
1480
1523
  }
1481
1524
  },
1482
- /* @__PURE__ */ React19.createElement(
1525
+ /* @__PURE__ */ React20.createElement(
1483
1526
  "script",
1484
1527
  {
1485
1528
  type: "application/json",
1486
1529
  dangerouslySetInnerHTML: { __html: json }
1487
1530
  }
1488
1531
  )
1489
- ), caption && /* @__PURE__ */ React19.createElement("figcaption", { className: `${baseClass}__caption` }, caption));
1532
+ ), caption && /* @__PURE__ */ React20.createElement("figcaption", { className: `${baseClass}__caption` }, caption));
1490
1533
  }
1491
- return /* @__PURE__ */ React19.createElement(CloverImage, { ...props, className: rootClassName });
1534
+ return /* @__PURE__ */ React20.createElement(CloverImage, { ...props, className: rootClassName });
1492
1535
  };
1493
1536
 
1494
1537
  // ui/src/iiif/MdxRelatedItems.jsx
1495
- import React20 from "react";
1538
+ import React21 from "react";
1496
1539
  function MdxRelatedItems(props) {
1497
1540
  let json = "{}";
1498
1541
  try {
@@ -1500,7 +1543,7 @@ function MdxRelatedItems(props) {
1500
1543
  } catch (_) {
1501
1544
  json = "{}";
1502
1545
  }
1503
- return /* @__PURE__ */ React20.createElement("div", { "data-canopy-related-items": "1" }, /* @__PURE__ */ React20.createElement(
1546
+ return /* @__PURE__ */ React21.createElement("div", { "data-canopy-related-items": "1" }, /* @__PURE__ */ React21.createElement(
1504
1547
  "script",
1505
1548
  {
1506
1549
  type: "application/json",
@@ -1510,7 +1553,7 @@ function MdxRelatedItems(props) {
1510
1553
  }
1511
1554
 
1512
1555
  // ui/src/search/MdxSearchResults.jsx
1513
- import React21 from "react";
1556
+ import React22 from "react";
1514
1557
  function MdxSearchResults(props) {
1515
1558
  let json = "{}";
1516
1559
  try {
@@ -1518,11 +1561,11 @@ function MdxSearchResults(props) {
1518
1561
  } catch (_) {
1519
1562
  json = "{}";
1520
1563
  }
1521
- return /* @__PURE__ */ React21.createElement("div", { "data-canopy-search-results": "1" }, /* @__PURE__ */ React21.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1564
+ return /* @__PURE__ */ React22.createElement("div", { "data-canopy-search-results": "1" }, /* @__PURE__ */ React22.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1522
1565
  }
1523
1566
 
1524
1567
  // ui/src/search/SearchSummary.jsx
1525
- import React22 from "react";
1568
+ import React23 from "react";
1526
1569
  function SearchSummary(props) {
1527
1570
  let json = "{}";
1528
1571
  try {
@@ -1530,11 +1573,11 @@ function SearchSummary(props) {
1530
1573
  } catch (_) {
1531
1574
  json = "{}";
1532
1575
  }
1533
- return /* @__PURE__ */ React22.createElement("div", { "data-canopy-search-summary": "1" }, /* @__PURE__ */ React22.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1576
+ return /* @__PURE__ */ React23.createElement("div", { "data-canopy-search-summary": "1" }, /* @__PURE__ */ React23.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1534
1577
  }
1535
1578
 
1536
1579
  // ui/src/search/MdxSearchTabs.jsx
1537
- import React23 from "react";
1580
+ import React24 from "react";
1538
1581
  function MdxSearchTabs(props) {
1539
1582
  let json = "{}";
1540
1583
  try {
@@ -1542,15 +1585,15 @@ function MdxSearchTabs(props) {
1542
1585
  } catch (_) {
1543
1586
  json = "{}";
1544
1587
  }
1545
- return /* @__PURE__ */ React23.createElement("div", { "data-canopy-search-tabs": "1" }, /* @__PURE__ */ React23.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1588
+ return /* @__PURE__ */ React24.createElement("div", { "data-canopy-search-tabs": "1" }, /* @__PURE__ */ React24.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1546
1589
  }
1547
1590
 
1548
1591
  // ui/src/search/SearchResults.jsx
1549
- import React24 from "react";
1592
+ import React25 from "react";
1550
1593
  function DefaultArticleTemplate({ record, query }) {
1551
1594
  if (!record) return null;
1552
1595
  const metadata = Array.isArray(record.metadata) ? record.metadata : [];
1553
- return /* @__PURE__ */ React24.createElement(
1596
+ return /* @__PURE__ */ React25.createElement(
1554
1597
  ArticleCard,
1555
1598
  {
1556
1599
  href: record.href,
@@ -1568,7 +1611,7 @@ function DefaultFigureTemplate({ record, thumbnailAspectRatio }) {
1568
1611
  if (!record) return null;
1569
1612
  const hasDims = Number.isFinite(Number(record.thumbnailWidth)) && Number(record.thumbnailWidth) > 0 && Number.isFinite(Number(record.thumbnailHeight)) && Number(record.thumbnailHeight) > 0;
1570
1613
  const aspect = Number.isFinite(Number(thumbnailAspectRatio)) && Number(thumbnailAspectRatio) > 0 ? Number(thumbnailAspectRatio) : hasDims ? Number(record.thumbnailWidth) / Number(record.thumbnailHeight) : void 0;
1571
- return /* @__PURE__ */ React24.createElement(
1614
+ return /* @__PURE__ */ React25.createElement(
1572
1615
  Card,
1573
1616
  {
1574
1617
  href: record.href,
@@ -1589,7 +1632,7 @@ function SearchResults({
1589
1632
  variant = "auto"
1590
1633
  }) {
1591
1634
  if (!results.length) {
1592
- return /* @__PURE__ */ React24.createElement("div", { className: "text-slate-600" }, /* @__PURE__ */ React24.createElement("em", null, "No results"));
1635
+ return /* @__PURE__ */ React25.createElement("div", { className: "text-slate-600" }, /* @__PURE__ */ React25.createElement("em", null, "No results"));
1593
1636
  }
1594
1637
  const normalizedType = String(type || "all").toLowerCase();
1595
1638
  const normalizedVariant = variant === "figure" || variant === "article" ? variant : "auto";
@@ -1597,9 +1640,9 @@ function SearchResults({
1597
1640
  const FigureTemplate = templates && templates.figure ? templates.figure : DefaultFigureTemplate;
1598
1641
  const ArticleTemplate = templates && templates.article ? templates.article : DefaultArticleTemplate;
1599
1642
  if (isAnnotationView) {
1600
- return /* @__PURE__ */ React24.createElement("div", { id: "search-results", className: "space-y-4" }, results.map((r, i) => {
1643
+ return /* @__PURE__ */ React25.createElement("div", { id: "search-results", className: "space-y-4" }, results.map((r, i) => {
1601
1644
  if (!r) return null;
1602
- return /* @__PURE__ */ React24.createElement(
1645
+ return /* @__PURE__ */ React25.createElement(
1603
1646
  ArticleTemplate,
1604
1647
  {
1605
1648
  key: r.id || i,
@@ -1617,20 +1660,20 @@ function SearchResults({
1617
1660
  return !isWorkRecord(record) || normalizedType !== "work";
1618
1661
  };
1619
1662
  if (layout === "list") {
1620
- return /* @__PURE__ */ React24.createElement("div", { id: "search-results", className: "space-y-6" }, results.map((r, i) => {
1663
+ return /* @__PURE__ */ React25.createElement("div", { id: "search-results", className: "space-y-6" }, results.map((r, i) => {
1621
1664
  if (shouldRenderAsArticle(r)) {
1622
- return /* @__PURE__ */ React24.createElement("div", { key: i, className: `search-result ${r && r.type}` }, /* @__PURE__ */ React24.createElement(ArticleTemplate, { record: r, query, layout }));
1665
+ return /* @__PURE__ */ React25.createElement("div", { key: i, className: `search-result ${r && r.type}` }, /* @__PURE__ */ React25.createElement(ArticleTemplate, { record: r, query, layout }));
1623
1666
  }
1624
1667
  const hasDims = Number.isFinite(Number(r.thumbnailWidth)) && Number(r.thumbnailWidth) > 0 && Number.isFinite(Number(r.thumbnailHeight)) && Number(r.thumbnailHeight) > 0;
1625
1668
  const aspect = hasDims ? Number(r.thumbnailWidth) / Number(r.thumbnailHeight) : void 0;
1626
- return /* @__PURE__ */ React24.createElement(
1669
+ return /* @__PURE__ */ React25.createElement(
1627
1670
  "div",
1628
1671
  {
1629
1672
  key: i,
1630
1673
  className: `search-result ${r.type}`,
1631
1674
  "data-thumbnail-aspect-ratio": aspect
1632
1675
  },
1633
- /* @__PURE__ */ React24.createElement(
1676
+ /* @__PURE__ */ React25.createElement(
1634
1677
  FigureTemplate,
1635
1678
  {
1636
1679
  record: r,
@@ -1642,20 +1685,20 @@ function SearchResults({
1642
1685
  );
1643
1686
  }));
1644
1687
  }
1645
- return /* @__PURE__ */ React24.createElement("div", { id: "search-results" }, /* @__PURE__ */ React24.createElement(Grid, null, results.map((r, i) => {
1688
+ return /* @__PURE__ */ React25.createElement("div", { id: "search-results" }, /* @__PURE__ */ React25.createElement(Grid, null, results.map((r, i) => {
1646
1689
  if (shouldRenderAsArticle(r)) {
1647
- return /* @__PURE__ */ React24.createElement(GridItem, { key: i, className: `search-result ${r && r.type}` }, /* @__PURE__ */ React24.createElement(ArticleTemplate, { record: r, query, layout }));
1690
+ return /* @__PURE__ */ React25.createElement(GridItem, { key: i, className: `search-result ${r && r.type}` }, /* @__PURE__ */ React25.createElement(ArticleTemplate, { record: r, query, layout }));
1648
1691
  }
1649
1692
  const hasDims = Number.isFinite(Number(r.thumbnailWidth)) && Number(r.thumbnailWidth) > 0 && Number.isFinite(Number(r.thumbnailHeight)) && Number(r.thumbnailHeight) > 0;
1650
1693
  const aspect = hasDims ? Number(r.thumbnailWidth) / Number(r.thumbnailHeight) : void 0;
1651
- return /* @__PURE__ */ React24.createElement(
1694
+ return /* @__PURE__ */ React25.createElement(
1652
1695
  GridItem,
1653
1696
  {
1654
1697
  key: i,
1655
1698
  className: `search-result ${r.type}`,
1656
1699
  "data-thumbnail-aspect-ratio": aspect
1657
1700
  },
1658
- /* @__PURE__ */ React24.createElement(
1701
+ /* @__PURE__ */ React25.createElement(
1659
1702
  FigureTemplate,
1660
1703
  {
1661
1704
  record: r,
@@ -1669,7 +1712,7 @@ function SearchResults({
1669
1712
  }
1670
1713
 
1671
1714
  // ui/src/search/SearchTabs.jsx
1672
- import React25, { useRef as useRef2, useState as useState6, useEffect as useEffect6 } from "react";
1715
+ import React26, { useRef as useRef2, useState as useState6, useEffect as useEffect6 } from "react";
1673
1716
  function SearchTabs({
1674
1717
  type = "all",
1675
1718
  onTypeChange,
@@ -1723,7 +1766,7 @@ function SearchTabs({
1723
1766
  width: `${itemBoundingBox.width}px`
1724
1767
  };
1725
1768
  }
1726
- return /* @__PURE__ */ React25.createElement("div", { className: "canopy-search-tabs-wrapper" }, /* @__PURE__ */ React25.createElement(
1769
+ return /* @__PURE__ */ React26.createElement("div", { className: "canopy-search-tabs-wrapper" }, /* @__PURE__ */ React26.createElement(
1727
1770
  "div",
1728
1771
  {
1729
1772
  role: "tablist",
@@ -1732,7 +1775,7 @@ function SearchTabs({
1732
1775
  ref: wrapperRef,
1733
1776
  onMouseLeave: resetHighlight
1734
1777
  },
1735
- /* @__PURE__ */ React25.createElement(
1778
+ /* @__PURE__ */ React26.createElement(
1736
1779
  "div",
1737
1780
  {
1738
1781
  ref: highlightRef,
@@ -1744,7 +1787,7 @@ function SearchTabs({
1744
1787
  const active = String(type).toLowerCase() === String(t).toLowerCase();
1745
1788
  const cRaw = counts && Object.prototype.hasOwnProperty.call(counts, t) ? counts[t] : void 0;
1746
1789
  const c = Number.isFinite(Number(cRaw)) ? Number(cRaw) : 0;
1747
- return /* @__PURE__ */ React25.createElement(
1790
+ return /* @__PURE__ */ React26.createElement(
1748
1791
  "button",
1749
1792
  {
1750
1793
  key: t,
@@ -1762,7 +1805,7 @@ function SearchTabs({
1762
1805
  ")"
1763
1806
  );
1764
1807
  })
1765
- ), hasFilters ? /* @__PURE__ */ React25.createElement(
1808
+ ), hasFilters ? /* @__PURE__ */ React26.createElement(
1766
1809
  "button",
1767
1810
  {
1768
1811
  type: "button",
@@ -1770,12 +1813,12 @@ function SearchTabs({
1770
1813
  "aria-expanded": filtersOpen ? "true" : "false",
1771
1814
  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"
1772
1815
  },
1773
- /* @__PURE__ */ React25.createElement("span", null, filtersLabel, filterBadge)
1816
+ /* @__PURE__ */ React26.createElement("span", null, filtersLabel, filterBadge)
1774
1817
  ) : null);
1775
1818
  }
1776
1819
 
1777
1820
  // ui/src/search/SearchFiltersDialog.jsx
1778
- import React26 from "react";
1821
+ import React27 from "react";
1779
1822
  function toArray(input) {
1780
1823
  if (!input) return [];
1781
1824
  if (Array.isArray(input)) return input;
@@ -1814,20 +1857,20 @@ function FacetSection({ facet, selected, onToggle }) {
1814
1857
  const selectedValues = selected.get(String(slug)) || /* @__PURE__ */ new Set();
1815
1858
  const checkboxId = (valueSlug) => `filter-${slug}-${valueSlug}`;
1816
1859
  const hasSelection = selectedValues.size > 0;
1817
- const [quickQuery, setQuickQuery] = React26.useState("");
1860
+ const [quickQuery, setQuickQuery] = React27.useState("");
1818
1861
  const hasQuery = quickQuery.trim().length > 0;
1819
- const filteredValues = React26.useMemo(
1862
+ const filteredValues = React27.useMemo(
1820
1863
  () => facetMatches(values, quickQuery),
1821
1864
  [values, quickQuery]
1822
1865
  );
1823
- return /* @__PURE__ */ React26.createElement(
1866
+ return /* @__PURE__ */ React27.createElement(
1824
1867
  "details",
1825
1868
  {
1826
1869
  className: "canopy-search-filters__facet",
1827
1870
  open: hasSelection
1828
1871
  },
1829
- /* @__PURE__ */ React26.createElement("summary", { className: "canopy-search-filters__facet-summary" }, /* @__PURE__ */ React26.createElement("span", null, label), /* @__PURE__ */ React26.createElement("span", { className: "canopy-search-filters__facet-count" }, values.length)),
1830
- /* @__PURE__ */ React26.createElement("div", { className: "canopy-search-filters__facet-content" }, /* @__PURE__ */ React26.createElement("div", { className: "canopy-search-filters__quick" }, /* @__PURE__ */ React26.createElement(
1872
+ /* @__PURE__ */ React27.createElement("summary", { className: "canopy-search-filters__facet-summary" }, /* @__PURE__ */ React27.createElement("span", null, label), /* @__PURE__ */ React27.createElement("span", { className: "canopy-search-filters__facet-count" }, values.length)),
1873
+ /* @__PURE__ */ React27.createElement("div", { className: "canopy-search-filters__facet-content" }, /* @__PURE__ */ React27.createElement("div", { className: "canopy-search-filters__quick" }, /* @__PURE__ */ React27.createElement(
1831
1874
  "input",
1832
1875
  {
1833
1876
  type: "search",
@@ -1837,7 +1880,7 @@ function FacetSection({ facet, selected, onToggle }) {
1837
1880
  className: "canopy-search-filters__quick-input",
1838
1881
  "aria-label": `Filter ${label} values`
1839
1882
  }
1840
- ), quickQuery ? /* @__PURE__ */ React26.createElement(
1883
+ ), quickQuery ? /* @__PURE__ */ React27.createElement(
1841
1884
  "button",
1842
1885
  {
1843
1886
  type: "button",
@@ -1845,11 +1888,11 @@ function FacetSection({ facet, selected, onToggle }) {
1845
1888
  className: "canopy-search-filters__quick-clear"
1846
1889
  },
1847
1890
  "Clear"
1848
- ) : null), hasQuery && !filteredValues.length ? /* @__PURE__ */ React26.createElement("p", { className: "canopy-search-filters__facet-notice" }, "No matches found.") : null, /* @__PURE__ */ React26.createElement("ul", { className: "canopy-search-filters__facet-list" }, filteredValues.map((entry) => {
1891
+ ) : null), hasQuery && !filteredValues.length ? /* @__PURE__ */ React27.createElement("p", { className: "canopy-search-filters__facet-notice" }, "No matches found.") : null, /* @__PURE__ */ React27.createElement("ul", { className: "canopy-search-filters__facet-list" }, filteredValues.map((entry) => {
1849
1892
  const valueSlug = String(entry.slug || entry.value || "");
1850
1893
  const isChecked = selectedValues.has(valueSlug);
1851
1894
  const inputId = checkboxId(valueSlug);
1852
- return /* @__PURE__ */ React26.createElement("li", { key: valueSlug, className: "canopy-search-filters__facet-item" }, /* @__PURE__ */ React26.createElement(
1895
+ return /* @__PURE__ */ React27.createElement("li", { key: valueSlug, className: "canopy-search-filters__facet-item" }, /* @__PURE__ */ React27.createElement(
1853
1896
  "input",
1854
1897
  {
1855
1898
  id: inputId,
@@ -1861,15 +1904,15 @@ function FacetSection({ facet, selected, onToggle }) {
1861
1904
  if (onToggle) onToggle(slug, valueSlug, nextChecked);
1862
1905
  }
1863
1906
  }
1864
- ), /* @__PURE__ */ React26.createElement(
1907
+ ), /* @__PURE__ */ React27.createElement(
1865
1908
  "label",
1866
1909
  {
1867
1910
  htmlFor: inputId,
1868
1911
  className: "canopy-search-filters__facet-label"
1869
1912
  },
1870
- /* @__PURE__ */ React26.createElement("span", null, entry.value, " ", Number.isFinite(entry.doc_count) ? /* @__PURE__ */ React26.createElement("span", { className: "canopy-search-filters__facet-count" }, "(", entry.doc_count, ")") : null)
1913
+ /* @__PURE__ */ React27.createElement("span", null, entry.value, " ", Number.isFinite(entry.doc_count) ? /* @__PURE__ */ React27.createElement("span", { className: "canopy-search-filters__facet-count" }, "(", entry.doc_count, ")") : null)
1871
1914
  ));
1872
- }), !filteredValues.length && !hasQuery ? /* @__PURE__ */ React26.createElement("li", { className: "canopy-search-filters__facet-empty" }, "No values available.") : null))
1915
+ }), !filteredValues.length && !hasQuery ? /* @__PURE__ */ React27.createElement("li", { className: "canopy-search-filters__facet-empty" }, "No values available.") : null))
1873
1916
  );
1874
1917
  }
1875
1918
  function SearchFiltersDialog(props = {}) {
@@ -1891,7 +1934,7 @@ function SearchFiltersDialog(props = {}) {
1891
1934
  (total, set) => total + set.size,
1892
1935
  0
1893
1936
  );
1894
- React26.useEffect(() => {
1937
+ React27.useEffect(() => {
1895
1938
  if (!open) return void 0;
1896
1939
  if (typeof document === "undefined") return void 0;
1897
1940
  const body = document.body;
@@ -1908,7 +1951,7 @@ function SearchFiltersDialog(props = {}) {
1908
1951
  if (!open) return null;
1909
1952
  const brandId = "canopy-modal-filters-label";
1910
1953
  const subtitleText = subtitle != null ? subtitle : title;
1911
- return /* @__PURE__ */ React26.createElement(
1954
+ return /* @__PURE__ */ React27.createElement(
1912
1955
  CanopyModal,
1913
1956
  {
1914
1957
  id: "canopy-modal-filters",
@@ -1923,8 +1966,8 @@ function SearchFiltersDialog(props = {}) {
1923
1966
  onBackgroundClick: () => onOpenChange && onOpenChange(false),
1924
1967
  bodyClassName: "canopy-modal__body--filters"
1925
1968
  },
1926
- subtitleText ? /* @__PURE__ */ React26.createElement("p", { className: "canopy-search-filters__subtitle" }, subtitleText) : null,
1927
- /* @__PURE__ */ React26.createElement("div", { className: "canopy-search-filters__body" }, Array.isArray(facets) && facets.length ? /* @__PURE__ */ React26.createElement("div", { className: "canopy-search-filters__facets" }, facets.map((facet) => /* @__PURE__ */ React26.createElement(
1969
+ subtitleText ? /* @__PURE__ */ React27.createElement("p", { className: "canopy-search-filters__subtitle" }, subtitleText) : null,
1970
+ /* @__PURE__ */ React27.createElement("div", { className: "canopy-search-filters__body" }, Array.isArray(facets) && facets.length ? /* @__PURE__ */ React27.createElement("div", { className: "canopy-search-filters__facets" }, facets.map((facet) => /* @__PURE__ */ React27.createElement(
1928
1971
  FacetSection,
1929
1972
  {
1930
1973
  key: facet.slug || facet.label,
@@ -1932,8 +1975,8 @@ function SearchFiltersDialog(props = {}) {
1932
1975
  selected: selectedMap,
1933
1976
  onToggle
1934
1977
  }
1935
- ))) : /* @__PURE__ */ React26.createElement("p", { className: "canopy-search-filters__empty" }, "No filters are available for this collection.")),
1936
- /* @__PURE__ */ React26.createElement("footer", { className: "canopy-search-filters__footer" }, /* @__PURE__ */ React26.createElement("div", null, activeCount ? `${activeCount} filter${activeCount === 1 ? "" : "s"} applied` : "No filters applied"), /* @__PURE__ */ React26.createElement("div", { className: "canopy-search-filters__footer-actions" }, /* @__PURE__ */ React26.createElement(
1978
+ ))) : /* @__PURE__ */ React27.createElement("p", { className: "canopy-search-filters__empty" }, "No filters are available for this collection.")),
1979
+ /* @__PURE__ */ React27.createElement("footer", { className: "canopy-search-filters__footer" }, /* @__PURE__ */ React27.createElement("div", null, activeCount ? `${activeCount} filter${activeCount === 1 ? "" : "s"} applied` : "No filters applied"), /* @__PURE__ */ React27.createElement("div", { className: "canopy-search-filters__footer-actions" }, /* @__PURE__ */ React27.createElement(
1937
1980
  "button",
1938
1981
  {
1939
1982
  type: "button",
@@ -1944,7 +1987,7 @@ function SearchFiltersDialog(props = {}) {
1944
1987
  className: "canopy-search-filters__button canopy-search-filters__button--secondary"
1945
1988
  },
1946
1989
  "Clear all"
1947
- ), /* @__PURE__ */ React26.createElement(
1990
+ ), /* @__PURE__ */ React27.createElement(
1948
1991
  "button",
1949
1992
  {
1950
1993
  type: "button",
@@ -1957,7 +2000,7 @@ function SearchFiltersDialog(props = {}) {
1957
2000
  }
1958
2001
 
1959
2002
  // ui/src/search-form/MdxSearchFormModal.jsx
1960
- import React27 from "react";
2003
+ import React28 from "react";
1961
2004
  function MdxSearchFormModal(props = {}) {
1962
2005
  const {
1963
2006
  placeholder = "Search\u2026",
@@ -1973,21 +2016,640 @@ function MdxSearchFormModal(props = {}) {
1973
2016
  const text = typeof label === "string" && label.trim() ? label.trim() : buttonLabel;
1974
2017
  const resolvedSearchPath = resolveSearchPath(searchPath);
1975
2018
  const data = { placeholder, hotkey, maxResults, groupOrder, label: text, searchPath: resolvedSearchPath };
1976
- return /* @__PURE__ */ React27.createElement("div", { "data-canopy-search-form": true, className: "flex-1 min-w-0" }, /* @__PURE__ */ React27.createElement("div", { className: "relative w-full" }, /* @__PURE__ */ React27.createElement(SearchPanelForm, { placeholder, buttonLabel, label, searchPath: resolvedSearchPath }), /* @__PURE__ */ React27.createElement(SearchPanelTeaserResults, null)), /* @__PURE__ */ React27.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: JSON.stringify(data) } }));
2019
+ return /* @__PURE__ */ React28.createElement("div", { "data-canopy-search-form": true, className: "flex-1 min-w-0" }, /* @__PURE__ */ React28.createElement("div", { className: "relative w-full" }, /* @__PURE__ */ React28.createElement(SearchPanelForm, { placeholder, buttonLabel, label, searchPath: resolvedSearchPath }), /* @__PURE__ */ React28.createElement(SearchPanelTeaserResults, null)), /* @__PURE__ */ React28.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: JSON.stringify(data) } }));
1977
2020
  }
1978
2021
 
1979
2022
  // ui/src/docs/MarkdownTable.jsx
1980
- import React28 from "react";
2023
+ import React29 from "react";
1981
2024
  function MarkdownTable({ className = "", ...rest }) {
1982
2025
  const merged = ["markdown-table", className].filter(Boolean).join(" ");
1983
- return /* @__PURE__ */ React28.createElement("div", { className: "markdown-table__frame" }, /* @__PURE__ */ React28.createElement("table", { className: merged, ...rest }));
2026
+ return /* @__PURE__ */ React29.createElement("div", { className: "markdown-table__frame" }, /* @__PURE__ */ React29.createElement("table", { className: merged, ...rest }));
1984
2027
  }
1985
2028
 
1986
2029
  // ui/src/docs/Diagram.jsx
1987
- import React29 from "react";
2030
+ import React30 from "react";
1988
2031
  function CanopyDiagram() {
1989
- return /* @__PURE__ */ React29.createElement("div", { className: "canopy-diagram" }, /* @__PURE__ */ React29.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--collections" }, /* @__PURE__ */ React29.createElement("h3", null, "IIIF Collection(s)"), /* @__PURE__ */ React29.createElement("span", { className: "canopy-diagram__section-summary" }, "Source collections contribute 105 total manifests that Canopy retrieves as-is via IIIF endpoints."), /* @__PURE__ */ React29.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Collection A"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "70 Manifests"), /* @__PURE__ */ React29.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React29.createElement("li", null, "Textual Annotations"))), /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Collection B"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "35 Manifests"), /* @__PURE__ */ React29.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React29.createElement("li", null, "Textual Annotations"))))), /* @__PURE__ */ React29.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React29.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React29.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React29.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--build" }, /* @__PURE__ */ React29.createElement("h3", null, "Canopy Build Process"), /* @__PURE__ */ React29.createElement("span", { className: "canopy-diagram__section-summary" }, "Canopy syncs manifests, page content, and annotations before bundling the site."), /* @__PURE__ */ React29.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Automated content"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "105 manifests \u2192 105 work pages"), /* @__PURE__ */ React29.createElement("li", null, "One page per manifest"), /* @__PURE__ */ React29.createElement("li", null, "Customize page layout"))), /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Contextual content"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "Markdown & MDX pages"), /* @__PURE__ */ React29.createElement("li", null, "Author narratives & tours"), /* @__PURE__ */ React29.createElement("li", null, "Reference manifests inline"))), /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Search index"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "Combines works + pages"), /* @__PURE__ */ React29.createElement("li", null, "Customize result layout"), /* @__PURE__ */ React29.createElement("li", null, "Optional annotations"))))), /* @__PURE__ */ React29.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React29.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React29.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React29.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--output" }, /* @__PURE__ */ React29.createElement("h3", null, "Static Digital Project"), /* @__PURE__ */ React29.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__ */ React29.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Work pages"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "105 generated HTML pages"), /* @__PURE__ */ React29.createElement("li", null, "Each links back to source manifests"), /* @__PURE__ */ React29.createElement("li", null, "Styled with Canopy components"))), /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Custom pages"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "Markdown & MDX-authored content"), /* @__PURE__ */ React29.createElement("li", null, "Reusable layouts for narratives"), /* @__PURE__ */ React29.createElement("li", null, "Embed IIIF media & interstitials"))), /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Search bundle"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "Static FlexSearch index"), /* @__PURE__ */ React29.createElement("li", null, "Works + pages share records"), /* @__PURE__ */ React29.createElement("li", null, "Optional annotation dataset"))))));
2032
+ return /* @__PURE__ */ React30.createElement("div", { className: "canopy-diagram" }, /* @__PURE__ */ React30.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--collections" }, /* @__PURE__ */ React30.createElement("h3", null, "IIIF Collection(s)"), /* @__PURE__ */ React30.createElement("span", { className: "canopy-diagram__section-summary" }, "Source collections contribute 105 total manifests that Canopy retrieves as-is via IIIF endpoints."), /* @__PURE__ */ React30.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React30.createElement("article", null, /* @__PURE__ */ React30.createElement("h4", null, "Collection A"), /* @__PURE__ */ React30.createElement("ul", null, /* @__PURE__ */ React30.createElement("li", null, "70 Manifests"), /* @__PURE__ */ React30.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React30.createElement("li", null, "Textual Annotations"))), /* @__PURE__ */ React30.createElement("article", null, /* @__PURE__ */ React30.createElement("h4", null, "Collection B"), /* @__PURE__ */ React30.createElement("ul", null, /* @__PURE__ */ React30.createElement("li", null, "35 Manifests"), /* @__PURE__ */ React30.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React30.createElement("li", null, "Textual Annotations"))))), /* @__PURE__ */ React30.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React30.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React30.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React30.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--build" }, /* @__PURE__ */ React30.createElement("h3", null, "Canopy Build Process"), /* @__PURE__ */ React30.createElement("span", { className: "canopy-diagram__section-summary" }, "Canopy syncs manifests, page content, and annotations before bundling the site."), /* @__PURE__ */ React30.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React30.createElement("article", null, /* @__PURE__ */ React30.createElement("h4", null, "Automated content"), /* @__PURE__ */ React30.createElement("ul", null, /* @__PURE__ */ React30.createElement("li", null, "105 manifests \u2192 105 work pages"), /* @__PURE__ */ React30.createElement("li", null, "One page per manifest"), /* @__PURE__ */ React30.createElement("li", null, "Customize page layout"))), /* @__PURE__ */ React30.createElement("article", null, /* @__PURE__ */ React30.createElement("h4", null, "Contextual content"), /* @__PURE__ */ React30.createElement("ul", null, /* @__PURE__ */ React30.createElement("li", null, "Markdown & MDX pages"), /* @__PURE__ */ React30.createElement("li", null, "Author narratives & tours"), /* @__PURE__ */ React30.createElement("li", null, "Reference manifests inline"))), /* @__PURE__ */ React30.createElement("article", null, /* @__PURE__ */ React30.createElement("h4", null, "Search index"), /* @__PURE__ */ React30.createElement("ul", null, /* @__PURE__ */ React30.createElement("li", null, "Combines works + pages"), /* @__PURE__ */ React30.createElement("li", null, "Customize result layout"), /* @__PURE__ */ React30.createElement("li", null, "Optional annotations"))))), /* @__PURE__ */ React30.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React30.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React30.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React30.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--output" }, /* @__PURE__ */ React30.createElement("h3", null, "Static Digital Project"), /* @__PURE__ */ React30.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__ */ React30.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React30.createElement("article", null, /* @__PURE__ */ React30.createElement("h4", null, "Work pages"), /* @__PURE__ */ React30.createElement("ul", null, /* @__PURE__ */ React30.createElement("li", null, "105 generated HTML pages"), /* @__PURE__ */ React30.createElement("li", null, "Each links back to source manifests"), /* @__PURE__ */ React30.createElement("li", null, "Styled with Canopy components"))), /* @__PURE__ */ React30.createElement("article", null, /* @__PURE__ */ React30.createElement("h4", null, "Custom pages"), /* @__PURE__ */ React30.createElement("ul", null, /* @__PURE__ */ React30.createElement("li", null, "Markdown & MDX-authored content"), /* @__PURE__ */ React30.createElement("li", null, "Reusable layouts for narratives"), /* @__PURE__ */ React30.createElement("li", null, "Embed IIIF media & interstitials"))), /* @__PURE__ */ React30.createElement("article", null, /* @__PURE__ */ React30.createElement("h4", null, "Search bundle"), /* @__PURE__ */ React30.createElement("ul", null, /* @__PURE__ */ React30.createElement("li", null, "Static FlexSearch index"), /* @__PURE__ */ React30.createElement("li", null, "Works + pages share records"), /* @__PURE__ */ React30.createElement("li", null, "Optional annotation dataset"))))));
2033
+ }
2034
+
2035
+ // ui/src/content/timeline/Timeline.jsx
2036
+ import React31 from "react";
2037
+
2038
+ // ui/src/content/timeline/date-utils.js
2039
+ var FALLBACK_LOCALE = (() => {
2040
+ try {
2041
+ return new Intl.Locale("en-US");
2042
+ } catch (_) {
2043
+ return "en-US";
2044
+ }
2045
+ })();
2046
+ function createLocale(localeValue) {
2047
+ if (!localeValue) return FALLBACK_LOCALE;
2048
+ if (typeof Intl !== "undefined" && localeValue instanceof Intl.Locale)
2049
+ return localeValue;
2050
+ try {
2051
+ return new Intl.Locale(localeValue);
2052
+ } catch (_) {
2053
+ return FALLBACK_LOCALE;
2054
+ }
2055
+ }
2056
+ function parseMonthIndex(name = "", locale = FALLBACK_LOCALE) {
2057
+ const localeId = typeof locale === "string" ? locale : locale && locale.baseName ? locale.baseName : "en-US";
2058
+ const normalized = String(name || "").trim().toLocaleLowerCase(localeId);
2059
+ if (!normalized) return 0;
2060
+ const months = [
2061
+ "january",
2062
+ "february",
2063
+ "march",
2064
+ "april",
2065
+ "may",
2066
+ "june",
2067
+ "july",
2068
+ "august",
2069
+ "september",
2070
+ "october",
2071
+ "november",
2072
+ "december"
2073
+ ];
2074
+ const idx = months.indexOf(normalized);
2075
+ return idx === -1 ? 0 : idx;
2076
+ }
2077
+ function parseStructuredInput(value) {
2078
+ if (!value || typeof value !== "object") return null;
2079
+ const { year, month, day } = value;
2080
+ if (!Number.isFinite(Number(year))) return null;
2081
+ const y = Number(year);
2082
+ const m = Number.isFinite(Number(month)) ? Number(month) - 1 : 0;
2083
+ const d = Number.isFinite(Number(day)) ? Number(day) : 1;
2084
+ return new Date(Date.UTC(y, Math.max(0, Math.min(11, m)), Math.max(1, Math.min(31, d))));
2085
+ }
2086
+ function createDateFromInput(value, { locale } = {}) {
2087
+ const loc = createLocale(locale);
2088
+ if (value instanceof Date) return new Date(value.getTime());
2089
+ if (Number.isFinite(Number(value))) {
2090
+ const year = Number(value);
2091
+ return new Date(Date.UTC(year, 0, 1));
2092
+ }
2093
+ if (value && typeof value === "object") {
2094
+ const structured = parseStructuredInput(value);
2095
+ if (structured) return structured;
2096
+ }
2097
+ const text = typeof value === "string" ? value.trim() : "";
2098
+ if (!text) return null;
2099
+ const isoYear = text.match(/^(\d{4})$/);
2100
+ if (isoYear) {
2101
+ return new Date(Date.UTC(Number(isoYear[1]), 0, 1));
2102
+ }
2103
+ const isoYearMonth = text.match(/^(\d{4})-(\d{2})$/);
2104
+ if (isoYearMonth) {
2105
+ return new Date(
2106
+ Date.UTC(Number(isoYearMonth[1]), Number(isoYearMonth[2]) - 1, 1)
2107
+ );
2108
+ }
2109
+ const isoDate = text.match(/^(\d{4})-(\d{2})-(\d{2})$/);
2110
+ if (isoDate) {
2111
+ return new Date(
2112
+ Date.UTC(
2113
+ Number(isoDate[1]),
2114
+ Number(isoDate[2]) - 1,
2115
+ Number(isoDate[3])
2116
+ )
2117
+ );
2118
+ }
2119
+ const monthDayYear = text.match(/^([A-Za-z]+)\s+(\d{1,2})(?:,)?\s*(\d{4})$/);
2120
+ if (monthDayYear) {
2121
+ const month = parseMonthIndex(monthDayYear[1], loc);
2122
+ const day = Number(monthDayYear[2]);
2123
+ const year = Number(monthDayYear[3]);
2124
+ return new Date(Date.UTC(year, month, day));
2125
+ }
2126
+ const monthYear = text.match(/^([A-Za-z]+)\s+(\d{4})$/);
2127
+ if (monthYear) {
2128
+ const month = parseMonthIndex(monthYear[1], loc);
2129
+ const year = Number(monthYear[2]);
2130
+ return new Date(Date.UTC(year, month, 1));
2131
+ }
2132
+ const fallback = new Date(text);
2133
+ if (!Number.isNaN(fallback.getTime())) return fallback;
2134
+ const alt = new Date(Date.parse(text));
2135
+ if (!Number.isNaN(alt.getTime())) return alt;
2136
+ return null;
2137
+ }
2138
+ function formatDateLabel(date, { granularity = "day", locale } = {}) {
2139
+ if (!(date instanceof Date) || Number.isNaN(date.getTime())) return "";
2140
+ const loc = createLocale(locale);
2141
+ const baseName = typeof loc === "string" ? loc : loc.baseName || "en-US";
2142
+ const options = { year: "numeric" };
2143
+ if (granularity === "month" || granularity === "day") {
2144
+ options.month = "long";
2145
+ }
2146
+ if (granularity === "day") {
2147
+ options.day = "numeric";
2148
+ }
2149
+ options.timeZone = "UTC";
2150
+ try {
2151
+ const formatter = new Intl.DateTimeFormat(baseName, options);
2152
+ return formatter.format(date);
2153
+ } catch (_) {
2154
+ return date.toISOString().slice(0, 10);
2155
+ }
2156
+ }
2157
+ function normalizeRange(range = {}) {
2158
+ const { start, end, granularity = "year", locale } = range || {};
2159
+ const loc = createLocale(locale);
2160
+ const startDate = createDateFromInput(start || /* @__PURE__ */ new Date(), { locale: loc }) || new Date(Date.UTC(0, 0, 1));
2161
+ let endDate = createDateFromInput(end, { locale: loc });
2162
+ if (!endDate) {
2163
+ const copy = new Date(startDate.getTime());
2164
+ copy.setUTCFullYear(copy.getUTCFullYear() + 1);
2165
+ endDate = copy;
2166
+ }
2167
+ if (endDate <= startDate) {
2168
+ const nextDay = new Date(startDate.getTime());
2169
+ nextDay.setUTCDate(nextDay.getUTCDate() + 1);
2170
+ endDate = nextDay;
2171
+ }
2172
+ const span = Math.max(1, endDate - startDate);
2173
+ return {
2174
+ startDate,
2175
+ endDate,
2176
+ span,
2177
+ granularity: granularity === "month" || granularity === "day" ? granularity : "year",
2178
+ locale: loc
2179
+ };
2180
+ }
2181
+ function clampProgress(value) {
2182
+ if (!Number.isFinite(value)) return 0;
2183
+ if (value < 0) return 0;
2184
+ if (value > 1) return 1;
2185
+ return value;
2186
+ }
2187
+
2188
+ // ui/src/content/timeline/Timeline.jsx
2189
+ var DAY_MS = 24 * 60 * 60 * 1e3;
2190
+ var DEFAULT_TRACK_HEIGHT = 640;
2191
+ var MIN_HEIGHT_PER_POINT = 220;
2192
+ function getThresholdMs(threshold, granularity) {
2193
+ const value = Number(threshold);
2194
+ if (!Number.isFinite(value) || value <= 0) return 0;
2195
+ if (granularity === "day") return value * DAY_MS;
2196
+ if (granularity === "month") return value * 30 * DAY_MS;
2197
+ return value * 365 * DAY_MS;
2198
+ }
2199
+ function buildGroupedEntries(points, thresholdMs, options) {
2200
+ if (!Array.isArray(points) || !points.length) return [];
2201
+ if (!thresholdMs) return points.map((point) => ({ type: "point", point }));
2202
+ const entries = [];
2203
+ let currentGroup = null;
2204
+ let groupCounter = 0;
2205
+ function flush() {
2206
+ if (!currentGroup) return;
2207
+ if (currentGroup.points.length > 1) {
2208
+ const firstPoint = currentGroup.points[0];
2209
+ entries.push({
2210
+ type: "group",
2211
+ id: `canopy-timeline-group-${groupCounter}-${currentGroup.start}`,
2212
+ points: currentGroup.points,
2213
+ progress: firstPoint.progress,
2214
+ side: firstPoint.side,
2215
+ label: formatGroupLabel(currentGroup.start, currentGroup.end, options),
2216
+ count: currentGroup.points.length
2217
+ });
2218
+ groupCounter += 1;
2219
+ } else {
2220
+ entries.push({ type: "point", point: currentGroup.points[0] });
2221
+ }
2222
+ currentGroup = null;
2223
+ }
2224
+ points.forEach((point) => {
2225
+ const timestamp = point && point.meta ? point.meta.timestamp : null;
2226
+ if (!Number.isFinite(timestamp)) {
2227
+ flush();
2228
+ if (point) entries.push({ type: "point", point });
2229
+ return;
2230
+ }
2231
+ if (!currentGroup) {
2232
+ currentGroup = {
2233
+ points: [point],
2234
+ start: timestamp,
2235
+ end: timestamp,
2236
+ last: timestamp
2237
+ };
2238
+ return;
2239
+ }
2240
+ const diff = Math.abs(timestamp - currentGroup.last);
2241
+ if (diff <= thresholdMs) {
2242
+ currentGroup.points.push(point);
2243
+ currentGroup.last = timestamp;
2244
+ if (timestamp < currentGroup.start) currentGroup.start = timestamp;
2245
+ if (timestamp > currentGroup.end) currentGroup.end = timestamp;
2246
+ } else {
2247
+ flush();
2248
+ currentGroup = {
2249
+ points: [point],
2250
+ start: timestamp,
2251
+ end: timestamp,
2252
+ last: timestamp
2253
+ };
2254
+ }
2255
+ });
2256
+ flush();
2257
+ return entries;
2258
+ }
2259
+ function formatGroupLabel(startTs, endTs, options) {
2260
+ if (!Number.isFinite(startTs) || !Number.isFinite(endTs)) return "";
2261
+ const startLabel = formatDateLabel(new Date(startTs), options);
2262
+ const endLabel = formatDateLabel(new Date(endTs), options);
2263
+ if (!startLabel || !endLabel) return "";
2264
+ return startLabel === endLabel ? startLabel : `${startLabel} \u2013 ${endLabel}`;
2265
+ }
2266
+ function deriveRangeOverrides(points, range) {
2267
+ const timestamps = points.map((point) => point && point.meta ? point.meta.timestamp : null).filter((timestamp) => Number.isFinite(timestamp));
2268
+ if (!timestamps.length) return range || {};
2269
+ const earliest = Math.min(...timestamps);
2270
+ const latest = Math.max(...timestamps);
2271
+ return {
2272
+ ...range,
2273
+ start: range && range.start ? range.start : new Date(earliest),
2274
+ end: range && range.end ? range.end : new Date(latest)
2275
+ };
2276
+ }
2277
+ function getActivePointId(points) {
2278
+ const highlighted = points.find((point) => point && point.highlight);
2279
+ if (highlighted) return highlighted.id;
2280
+ return points.length ? points[0].id : null;
2281
+ }
2282
+ function formatRangeLabel(rangeInfo) {
2283
+ if (!rangeInfo) return "";
2284
+ const startLabel = formatDateLabel(rangeInfo.startDate, {
2285
+ granularity: rangeInfo.granularity,
2286
+ locale: rangeInfo.locale
2287
+ });
2288
+ const endLabel = formatDateLabel(rangeInfo.endDate, {
2289
+ granularity: rangeInfo.granularity,
2290
+ locale: rangeInfo.locale
2291
+ });
2292
+ if (!startLabel || !endLabel) return "";
2293
+ if (startLabel === endLabel) return startLabel;
2294
+ return `${startLabel} \u2013 ${endLabel}`;
2295
+ }
2296
+ function sanitizePoints(points) {
2297
+ if (!Array.isArray(points)) return [];
2298
+ return points.map((point, index) => {
2299
+ if (!point) return null;
2300
+ const meta = point.meta || {};
2301
+ const timestamp = Number(meta.timestamp);
2302
+ const manifests = Array.isArray(point.manifests) ? point.manifests.map((manifest) => manifest ? { ...manifest } : null).filter(Boolean) : [];
2303
+ const resources = Array.isArray(point.resources) ? point.resources.filter(Boolean) : [];
2304
+ return {
2305
+ ...point,
2306
+ id: point.id || `timeline-point-${index}`,
2307
+ title: point.title || point.label || `Point ${index + 1}`,
2308
+ summary: point.summary || point.description || "",
2309
+ detailsHtml: point.detailsHtml || "",
2310
+ highlight: !!point.highlight,
2311
+ side: point.side === "left" || point.side === "right" ? point.side : null,
2312
+ meta: {
2313
+ label: meta.label || "",
2314
+ timestamp: Number.isFinite(timestamp) ? timestamp : null
2315
+ },
2316
+ manifests,
2317
+ resources
2318
+ };
2319
+ }).filter(Boolean);
2320
+ }
2321
+ function resolveTrackHeight(height, pointCount) {
2322
+ const minimumPx = Math.max(
2323
+ DEFAULT_TRACK_HEIGHT,
2324
+ pointCount * MIN_HEIGHT_PER_POINT
2325
+ );
2326
+ const fallback = `${minimumPx}px`;
2327
+ if (height == null) return fallback;
2328
+ if (typeof height === "number") {
2329
+ const numeric = Number(height);
2330
+ if (Number.isFinite(numeric)) {
2331
+ return `${Math.max(numeric, pointCount * MIN_HEIGHT_PER_POINT)}px`;
2332
+ }
2333
+ return fallback;
2334
+ }
2335
+ if (typeof height === "string") {
2336
+ const trimmed = height.trim();
2337
+ if (!trimmed) return fallback;
2338
+ const numeric = Number(trimmed);
2339
+ if (Number.isFinite(numeric)) {
2340
+ return `${Math.max(numeric, pointCount * MIN_HEIGHT_PER_POINT)}px`;
2341
+ }
2342
+ return trimmed;
2343
+ }
2344
+ return fallback;
2345
+ }
2346
+ function TimelineConnector({ side, isActive, highlight }) {
2347
+ const connectorClasses = [
2348
+ "canopy-timeline__connector",
2349
+ side === "left" ? "canopy-timeline__connector--left" : "canopy-timeline__connector--right"
2350
+ ].filter(Boolean).join(" ");
2351
+ const dotClasses = [
2352
+ "canopy-timeline__connector-dot",
2353
+ highlight || isActive ? "is-active" : ""
2354
+ ].filter(Boolean).join(" ");
2355
+ return /* @__PURE__ */ React31.createElement("span", { className: connectorClasses, "aria-hidden": "true" }, side === "left" ? /* @__PURE__ */ React31.createElement(React31.Fragment, null, /* @__PURE__ */ React31.createElement("span", { className: "canopy-timeline__connector-line" }), /* @__PURE__ */ React31.createElement("span", { className: dotClasses })) : /* @__PURE__ */ React31.createElement(React31.Fragment, null, /* @__PURE__ */ React31.createElement("span", { className: dotClasses }), /* @__PURE__ */ React31.createElement("span", { className: "canopy-timeline__connector-line" })));
2356
+ }
2357
+ function renderResourceSection(point) {
2358
+ if (!point) return null;
2359
+ const manifestCards = Array.isArray(point.manifests) ? point.manifests.filter(Boolean) : [];
2360
+ const legacyResources = Array.isArray(point.resources) ? point.resources.filter(Boolean) : [];
2361
+ if (!manifestCards.length && !legacyResources.length) return null;
2362
+ return /* @__PURE__ */ React31.createElement("div", { className: "canopy-timeline__resources" }, /* @__PURE__ */ React31.createElement("div", { className: "canopy-timeline__resources-list" }, manifestCards.map((manifest) => /* @__PURE__ */ React31.createElement("div", { key: manifest.id || manifest.href }, /* @__PURE__ */ React31.createElement(
2363
+ TeaserCard,
2364
+ {
2365
+ href: manifest.href,
2366
+ title: manifest.title || manifest.href,
2367
+ summary: manifest.summary,
2368
+ metadata: Array.isArray(manifest.metadata) && manifest.metadata.length ? manifest.metadata : manifest.summary ? [manifest.summary] : [],
2369
+ thumbnail: manifest.thumbnail,
2370
+ type: manifest.type || "work"
2371
+ }
2372
+ ))), legacyResources.map((resource, idx) => /* @__PURE__ */ React31.createElement("div", { key: resource.id || resource.href || `legacy-${idx}` }, /* @__PURE__ */ React31.createElement(
2373
+ TeaserCard,
2374
+ {
2375
+ href: resource.href,
2376
+ title: resource.label || resource.title || resource.href,
2377
+ summary: resource.summary,
2378
+ thumbnail: resource.thumbnail,
2379
+ type: resource.type || "resource"
2380
+ }
2381
+ )))));
2382
+ }
2383
+ function Timeline({
2384
+ className = "",
2385
+ title,
2386
+ description,
2387
+ range: rangeProp,
2388
+ locale: localeProp = "en-US",
2389
+ height = DEFAULT_TRACK_HEIGHT,
2390
+ threshold: thresholdProp = null,
2391
+ steps = null,
2392
+ points: pointsProp,
2393
+ __canopyTimeline: payload = null,
2394
+ ...rest
2395
+ }) {
2396
+ const payloadPoints = payload && Array.isArray(payload.points) ? payload.points : null;
2397
+ const rawPoints = React31.useMemo(() => {
2398
+ if (Array.isArray(pointsProp) && pointsProp.length) return pointsProp;
2399
+ if (payloadPoints && payloadPoints.length) return payloadPoints;
2400
+ return [];
2401
+ }, [pointsProp, payloadPoints]);
2402
+ const sanitizedPoints = React31.useMemo(
2403
+ () => sanitizePoints(rawPoints),
2404
+ [rawPoints]
2405
+ );
2406
+ const localeValue = payload && payload.locale ? payload.locale : localeProp;
2407
+ const baseLocale = React31.useMemo(
2408
+ () => createLocale(localeValue),
2409
+ [localeValue]
2410
+ );
2411
+ const rangeInput = payload && payload.range ? payload.range : rangeProp || {};
2412
+ const rangeOverrides = React31.useMemo(
2413
+ () => deriveRangeOverrides(sanitizedPoints, rangeInput),
2414
+ [sanitizedPoints, rangeInput]
2415
+ );
2416
+ const effectiveRange = React31.useMemo(
2417
+ () => normalizeRange({
2418
+ ...rangeOverrides,
2419
+ locale: baseLocale
2420
+ }),
2421
+ [rangeOverrides, baseLocale]
2422
+ );
2423
+ const spanStart = effectiveRange.startDate.getTime();
2424
+ const span = effectiveRange.span;
2425
+ const pointsWithPosition = React31.useMemo(() => {
2426
+ if (!sanitizedPoints.length) return [];
2427
+ return sanitizedPoints.map((point, index) => {
2428
+ const timestamp = point.meta.timestamp;
2429
+ const fallbackProgress = sanitizedPoints.length > 1 ? index / (sanitizedPoints.length - 1) : 0;
2430
+ const progress = Number.isFinite(timestamp) ? clampProgress((timestamp - spanStart) / span) : fallbackProgress;
2431
+ const side = point.side || (index % 2 === 0 ? "left" : "right");
2432
+ return {
2433
+ ...point,
2434
+ progress,
2435
+ side
2436
+ };
2437
+ });
2438
+ }, [sanitizedPoints, spanStart, span]);
2439
+ const [activeId, setActiveId] = React31.useState(
2440
+ () => getActivePointId(pointsWithPosition)
2441
+ );
2442
+ React31.useEffect(() => {
2443
+ setActiveId(getActivePointId(pointsWithPosition));
2444
+ }, [pointsWithPosition]);
2445
+ const thresholdValue = typeof thresholdProp === "number" ? thresholdProp : payload && payload.threshold != null ? payload.threshold : null;
2446
+ const stepsValue = typeof steps === "number" ? Number(steps) : payload && typeof payload.steps === "number" ? Number(payload.steps) : null;
2447
+ const thresholdMs = React31.useMemo(
2448
+ () => getThresholdMs(thresholdValue, effectiveRange.granularity),
2449
+ [thresholdValue, effectiveRange.granularity]
2450
+ );
2451
+ const groupedEntries = React31.useMemo(
2452
+ () => buildGroupedEntries(pointsWithPosition, thresholdMs, {
2453
+ granularity: effectiveRange.granularity,
2454
+ locale: baseLocale
2455
+ }),
2456
+ [pointsWithPosition, thresholdMs, effectiveRange.granularity, baseLocale]
2457
+ );
2458
+ const [expandedGroupIds, setExpandedGroupIds] = React31.useState(
2459
+ () => /* @__PURE__ */ new Set()
2460
+ );
2461
+ React31.useEffect(() => {
2462
+ setExpandedGroupIds((prev) => {
2463
+ if (!prev || prev.size === 0) return prev;
2464
+ const validIds = new Set(
2465
+ groupedEntries.filter((entry) => entry.type === "group").map((entry) => entry.id)
2466
+ );
2467
+ const next = /* @__PURE__ */ new Set();
2468
+ let changed = false;
2469
+ prev.forEach((id) => {
2470
+ if (validIds.has(id)) next.add(id);
2471
+ else changed = true;
2472
+ });
2473
+ return changed ? next : prev;
2474
+ });
2475
+ }, [groupedEntries]);
2476
+ const toggleGroup = React31.useCallback((groupId) => {
2477
+ setExpandedGroupIds((prev) => {
2478
+ const next = new Set(prev || []);
2479
+ if (next.has(groupId)) next.delete(groupId);
2480
+ else next.add(groupId);
2481
+ return next;
2482
+ });
2483
+ }, []);
2484
+ const trackHeight = resolveTrackHeight(height, pointsWithPosition.length);
2485
+ const containerClasses = ["canopy-timeline", className].filter(Boolean).join(" ");
2486
+ const rangeLabel = formatRangeLabel(effectiveRange);
2487
+ function renderPointEntry(point) {
2488
+ if (!point) return null;
2489
+ const wrapperClasses = [
2490
+ "canopy-timeline__point-wrapper",
2491
+ point.side === "left" ? "canopy-timeline__point-wrapper--left" : "canopy-timeline__point-wrapper--right"
2492
+ ].filter(Boolean).join(" ");
2493
+ const wrapperStyle = { top: `${point.progress * 100}%` };
2494
+ const cardClasses = [
2495
+ "canopy-timeline__point",
2496
+ point.id === activeId ? "is-active" : "",
2497
+ point.highlight ? "is-highlighted" : ""
2498
+ ].filter(Boolean).join(" ");
2499
+ const connector = /* @__PURE__ */ React31.createElement(
2500
+ TimelineConnector,
2501
+ {
2502
+ side: point.side,
2503
+ isActive: point.id === activeId,
2504
+ highlight: point.highlight
2505
+ }
2506
+ );
2507
+ const body = /* @__PURE__ */ React31.createElement("div", { className: "canopy-timeline__point-body" }, /* @__PURE__ */ React31.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label), /* @__PURE__ */ React31.createElement("span", { className: "canopy-timeline__point-title" }, point.title), point.summary ? /* @__PURE__ */ React31.createElement("span", { className: "canopy-timeline__point-summary" }, point.summary) : null);
2508
+ const resourceSection = renderResourceSection(point);
2509
+ return /* @__PURE__ */ React31.createElement(
2510
+ "div",
2511
+ {
2512
+ key: point.id,
2513
+ className: wrapperClasses,
2514
+ style: wrapperStyle,
2515
+ role: "listitem"
2516
+ },
2517
+ point.side === "left" ? /* @__PURE__ */ React31.createElement(React31.Fragment, null, /* @__PURE__ */ React31.createElement("div", { className: cardClasses }, body, resourceSection), connector) : /* @__PURE__ */ React31.createElement(React31.Fragment, null, connector, /* @__PURE__ */ React31.createElement("div", { className: cardClasses }, body, resourceSection))
2518
+ );
2519
+ }
2520
+ function renderGroupEntry(entry) {
2521
+ const wrapperClasses = [
2522
+ "canopy-timeline__point-wrapper",
2523
+ entry.side === "left" ? "canopy-timeline__point-wrapper--left" : "canopy-timeline__point-wrapper--right"
2524
+ ].filter(Boolean).join(" ");
2525
+ const wrapperStyle = { top: `${entry.progress * 100}%` };
2526
+ const isExpanded = expandedGroupIds.has(entry.id);
2527
+ const hasActivePoint = entry.points.some((point) => point.id === activeId);
2528
+ const connector = /* @__PURE__ */ React31.createElement(
2529
+ TimelineConnector,
2530
+ {
2531
+ side: entry.side,
2532
+ isActive: hasActivePoint,
2533
+ highlight: hasActivePoint
2534
+ }
2535
+ );
2536
+ const groupClasses = [
2537
+ "canopy-timeline__group",
2538
+ isExpanded ? "is-expanded" : "",
2539
+ hasActivePoint ? "is-active" : ""
2540
+ ].filter(Boolean).join(" ");
2541
+ const countLabel = `${entry.count} event${entry.count > 1 ? "s" : ""}`;
2542
+ const header = /* @__PURE__ */ React31.createElement("div", { className: "canopy-timeline__group-header" }, /* @__PURE__ */ React31.createElement("div", { className: "canopy-timeline__group-summary" }, /* @__PURE__ */ React31.createElement("span", { className: "canopy-timeline__point-date" }, entry.label), /* @__PURE__ */ React31.createElement("span", { className: "canopy-timeline__group-count" }, countLabel)), /* @__PURE__ */ React31.createElement(
2543
+ "button",
2544
+ {
2545
+ type: "button",
2546
+ className: "canopy-timeline__group-toggle",
2547
+ "aria-expanded": isExpanded ? "true" : "false",
2548
+ onClick: () => toggleGroup(entry.id)
2549
+ },
2550
+ isExpanded ? "Hide details" : "Show details"
2551
+ ));
2552
+ const groupPoints = isExpanded ? /* @__PURE__ */ React31.createElement("div", { className: "canopy-timeline__group-points" }, entry.points.map((point) => /* @__PURE__ */ React31.createElement(
2553
+ "button",
2554
+ {
2555
+ key: point.id,
2556
+ type: "button",
2557
+ className: [
2558
+ "canopy-timeline__group-point",
2559
+ point.id === activeId ? "is-active" : ""
2560
+ ].filter(Boolean).join(" "),
2561
+ onClick: () => setActiveId(point.id)
2562
+ },
2563
+ /* @__PURE__ */ React31.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label),
2564
+ /* @__PURE__ */ React31.createElement("span", { className: "canopy-timeline__group-point-title" }, point.title)
2565
+ ))) : null;
2566
+ const groupCard = /* @__PURE__ */ React31.createElement("div", { className: groupClasses }, header, groupPoints);
2567
+ return /* @__PURE__ */ React31.createElement(
2568
+ "div",
2569
+ {
2570
+ key: entry.id,
2571
+ className: wrapperClasses,
2572
+ style: wrapperStyle,
2573
+ role: "listitem"
2574
+ },
2575
+ entry.side === "left" ? /* @__PURE__ */ React31.createElement(React31.Fragment, null, groupCard, connector) : /* @__PURE__ */ React31.createElement(React31.Fragment, null, connector, groupCard)
2576
+ );
2577
+ }
2578
+ return /* @__PURE__ */ React31.createElement("section", { className: containerClasses, ...rest }, title ? /* @__PURE__ */ React31.createElement("h2", { className: "canopy-timeline__title" }, title) : null, description ? /* @__PURE__ */ React31.createElement("p", { className: "canopy-timeline__description" }, description) : null, rangeLabel ? /* @__PURE__ */ React31.createElement("p", { className: "canopy-timeline__range", "aria-live": "polite" }, rangeLabel) : null, /* @__PURE__ */ React31.createElement("div", { className: "canopy-timeline__body" }, /* @__PURE__ */ React31.createElement(
2579
+ "div",
2580
+ {
2581
+ className: "canopy-timeline__list",
2582
+ role: "list",
2583
+ style: { minHeight: trackHeight }
2584
+ },
2585
+ /* @__PURE__ */ React31.createElement("div", { className: "canopy-timeline__spine", "aria-hidden": "true" }),
2586
+ renderSteps(stepsValue, effectiveRange),
2587
+ groupedEntries.map((entry) => {
2588
+ if (entry.type === "group") return renderGroupEntry(entry);
2589
+ return renderPointEntry(entry.point);
2590
+ })
2591
+ )));
2592
+ }
2593
+ function renderSteps(stepSize, range) {
2594
+ if (!Number.isFinite(stepSize) || stepSize <= 0 || !range) return null;
2595
+ const startYear = range.startDate.getUTCFullYear();
2596
+ const endYear = range.endDate.getUTCFullYear();
2597
+ const markers = [];
2598
+ if (startYear < endYear) {
2599
+ markers.push(
2600
+ /* @__PURE__ */ React31.createElement(
2601
+ "span",
2602
+ {
2603
+ key: "timeline-step-start",
2604
+ className: "canopy-timeline__step canopy-timeline__step--start",
2605
+ style: { top: "0%" },
2606
+ "aria-hidden": "true"
2607
+ },
2608
+ /* @__PURE__ */ React31.createElement("span", { className: "canopy-timeline__step-line" }),
2609
+ /* @__PURE__ */ React31.createElement("span", { className: "canopy-timeline__step-label" }, startYear)
2610
+ )
2611
+ );
2612
+ markers.push(
2613
+ /* @__PURE__ */ React31.createElement(
2614
+ "span",
2615
+ {
2616
+ key: "timeline-step-end",
2617
+ className: "canopy-timeline__step canopy-timeline__step--end",
2618
+ style: { top: "100%" },
2619
+ "aria-hidden": "true"
2620
+ },
2621
+ /* @__PURE__ */ React31.createElement("span", { className: "canopy-timeline__step-line" }),
2622
+ /* @__PURE__ */ React31.createElement("span", { className: "canopy-timeline__step-label" }, endYear)
2623
+ )
2624
+ );
2625
+ }
2626
+ const baseYear = Math.ceil(startYear / stepSize) * stepSize;
2627
+ for (let year = baseYear; year <= endYear; year += stepSize) {
2628
+ const timestamp = Date.UTC(year, 0, 1);
2629
+ const progress = (timestamp - range.startDate.getTime()) / range.span;
2630
+ if (progress <= 0 || progress >= 1) continue;
2631
+ markers.push(
2632
+ /* @__PURE__ */ React31.createElement(
2633
+ "span",
2634
+ {
2635
+ key: `timeline-step-${year}`,
2636
+ className: "canopy-timeline__step",
2637
+ style: { top: `calc(${progress * 100}% - 0.5px)` },
2638
+ "aria-hidden": "true"
2639
+ },
2640
+ /* @__PURE__ */ React31.createElement("span", { className: "canopy-timeline__step-line" }),
2641
+ /* @__PURE__ */ React31.createElement("span", { className: "canopy-timeline__step-label" }, year)
2642
+ )
2643
+ );
2644
+ }
2645
+ return markers.length ? markers : null;
2646
+ }
2647
+
2648
+ // ui/src/content/timeline/TimelinePoint.jsx
2649
+ function TimelinePoint() {
2650
+ return null;
1990
2651
  }
2652
+ TimelinePoint.displayName = "TimelinePoint";
1991
2653
  export {
1992
2654
  ArticleCard,
1993
2655
  Button,
@@ -2017,6 +2679,9 @@ export {
2017
2679
  MdxSearchTabs as SearchTabs,
2018
2680
  SearchTabs as SearchTabsUI,
2019
2681
  Slider,
2682
+ TeaserCard,
2683
+ Timeline,
2684
+ TimelinePoint,
2020
2685
  Viewer
2021
2686
  };
2022
2687
  //# sourceMappingURL=index.mjs.map