@richardmcquiston01/dashboard-widgets-toolkit 0.9.0 → 0.10.0

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/dist/react.js CHANGED
@@ -427,21 +427,36 @@ function fillColumnSpans(items, columns, largeSpan = 2) {
427
427
  function fillWidthSpans(items) {
428
428
  return placeAndFill(items, WIDTH_COLUMNS, itemWidth);
429
429
  }
430
+ function placeRows(items) {
431
+ return placeAndFillRows(items, WIDTH_COLUMNS, itemWidth).rows;
432
+ }
430
433
  function placeAndFill(items, columns, spanOf) {
434
+ return placeAndFillRows(items, columns, spanOf).spans;
435
+ }
436
+ function placeAndFillRows(items, columns, spanOf) {
431
437
  const trackCount = Math.max(1, Math.floor(columns));
432
438
  const spans = /* @__PURE__ */ new Map();
439
+ const rows = [];
433
440
  let row = [];
434
441
  let used = 0;
435
442
  function closeRow() {
443
+ if (row.length === 0) return;
436
444
  const fillers = row.filter(({ item }) => fillsWidth(item.fill));
437
445
  const leftover = trackCount - used;
446
+ const finalSpans = /* @__PURE__ */ new Map();
438
447
  if (fillers.length > 0) {
439
448
  const share = Math.floor(leftover / fillers.length);
440
449
  const remainder = leftover % fillers.length;
441
450
  fillers.forEach(({ item, span }, index) => {
442
- spans.set(item.key, span + share + (index < remainder ? 1 : 0));
451
+ const grown = span + share + (index < remainder ? 1 : 0);
452
+ spans.set(item.key, grown);
453
+ finalSpans.set(item.key, grown);
443
454
  });
444
455
  }
456
+ rows.push({
457
+ keys: row.map(({ item }) => item.key),
458
+ spans: row.map(({ item, span }) => finalSpans.get(item.key) ?? span)
459
+ });
445
460
  row = [];
446
461
  used = 0;
447
462
  }
@@ -452,7 +467,7 @@ function placeAndFill(items, columns, spanOf) {
452
467
  used += span;
453
468
  }
454
469
  closeRow();
455
- return spans;
470
+ return { spans, rows };
456
471
  }
457
472
 
458
473
  // src/core/scale.ts
@@ -535,6 +550,27 @@ var DEFAULT_LABELS = {
535
550
  dashboardControls: "Dashboard controls",
536
551
  locked: (title) => `${title} is locked`,
537
552
  lockedForViewers: (title) => `${title} is locked for viewers`,
553
+ pageBar: "Pages",
554
+ pageTabName: (index, count, title) => `Page ${index} of ${count}: ${title}`,
555
+ previousPageButton: "Previous page",
556
+ nextPageButton: "Next page",
557
+ managePages: "Manage pages",
558
+ addPage: "Add page",
559
+ renamePage: (title) => `Rename ${title}`,
560
+ pageTitle: "Page title",
561
+ savePageTitle: "Save page title",
562
+ cancelPageTitle: "Cancel renaming",
563
+ deletePage: (title) => `Delete ${title}`,
564
+ confirmDeletePage: (title) => `Delete the page "${title}" and move its widgets to another page?`,
565
+ movePageLeft: (title) => `Move ${title} page left`,
566
+ movePageRight: (title) => `Move ${title} page right`,
567
+ moveToPage: (title) => `Move ${title} to another page`,
568
+ moveToPagePlaceholder: "Move to page\u2026",
569
+ moveToPageOption: (title, rowsFree) => `${title} (${rowsFree} ${rowsFree === 1 ? "row" : "rows"} free)`,
570
+ newPageOption: "New page\u2026",
571
+ movedToPage: (title, pageTitle) => `${title} moved to ${pageTitle}`,
572
+ pageFull: (pageTitle, maxRows) => `Page "${pageTitle}" is full (${maxRows} rows). Move or hide a widget first.`,
573
+ emptyPage: "This page has no widgets. Move one here from another page.",
538
574
  opensInNewTab: "(opens in a new tab)",
539
575
  view: (title) => `View ${title}`,
540
576
  close: "Close",
@@ -1623,6 +1659,9 @@ function isWidgetVisibleTo(definition, roles) {
1623
1659
  }
1624
1660
 
1625
1661
  // src/core/layout.ts
1662
+ var MAX_PAGE_TITLE_LENGTH = 30;
1663
+ var MAX_PARSED_PAGES = 50;
1664
+ var MAX_PAGE_ROWS = 20;
1626
1665
  var EMPTY_LAYOUT = Object.freeze({
1627
1666
  order: Object.freeze([]),
1628
1667
  hidden: Object.freeze([]),
@@ -1737,6 +1776,22 @@ function restoreWidget(layout, key) {
1737
1776
  function toggleMinimized(layout, key) {
1738
1777
  return isMinimized(layout, key) ? restoreWidget(layout, key) : minimizeWidget(layout, key);
1739
1778
  }
1779
+ function mapLayoutLists(layout, change) {
1780
+ const top = change(layout);
1781
+ const pages = layout.pages?.map((page) => {
1782
+ const next = change(page);
1783
+ return next.order === page.order && next.hidden === page.hidden && next.minimized === page.minimized ? page : { ...page, ...next };
1784
+ });
1785
+ const pagesChanged = pages !== void 0 && pages.some((page, i) => page !== layout.pages?.[i]);
1786
+ if (top.order === layout.order && top.hidden === layout.hidden && top.minimized === layout.minimized && !pagesChanged) {
1787
+ return layout;
1788
+ }
1789
+ return {
1790
+ ...layout,
1791
+ ...top,
1792
+ ...pages === void 0 ? {} : { pages }
1793
+ };
1794
+ }
1740
1795
  function enforceLocks(definitions, layout, options = {}) {
1741
1796
  if (options.overrideLocks === true) return layout;
1742
1797
  const noMove = /* @__PURE__ */ new Set();
@@ -1748,11 +1803,337 @@ function enforceLocks(definitions, layout, options = {}) {
1748
1803
  if (lock.hide) noHide.add(definition.key);
1749
1804
  if (lock.minimize) noMinimize.add(definition.key);
1750
1805
  }
1751
- const without = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
1752
- const order = without(layout.order, noMove);
1753
- const hidden = without(layout.hidden, noHide);
1754
- const minimized = without(layout.minimized, noMinimize);
1755
- return order === layout.order && hidden === layout.hidden && minimized === layout.minimized ? layout : { ...layout, order, hidden, minimized };
1806
+ const without2 = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
1807
+ return mapLayoutLists(layout, (lists) => ({
1808
+ order: without2(lists.order, noMove),
1809
+ hidden: without2(lists.hidden, noHide),
1810
+ minimized: without2(lists.minimized, noMinimize)
1811
+ }));
1812
+ }
1813
+
1814
+ // src/core/pages.ts
1815
+ var DEFAULT_PAGE_KEY = "default";
1816
+ var DEFAULT_PAGE_ROWS = 4;
1817
+ function rowsAllowed(page, options) {
1818
+ const rows = page.maxRows ?? options.maxRows ?? DEFAULT_PAGE_ROWS;
1819
+ return Math.max(1, Math.min(MAX_PAGE_ROWS, Math.floor(rows)));
1820
+ }
1821
+ function hasPages(layout) {
1822
+ return layout.pages !== void 0 && layout.pages.length > 0;
1823
+ }
1824
+ function pageList(layout) {
1825
+ if (layout.pages !== void 0 && layout.pages.length > 0) {
1826
+ return layout.pages;
1827
+ }
1828
+ return [
1829
+ {
1830
+ key: DEFAULT_PAGE_KEY,
1831
+ title: "Page 1",
1832
+ order: layout.order,
1833
+ hidden: layout.hidden,
1834
+ minimized: layout.minimized
1835
+ }
1836
+ ];
1837
+ }
1838
+ function withExplicitPages(layout) {
1839
+ if (hasPages(layout)) return layout;
1840
+ return {
1841
+ ...layout,
1842
+ order: [],
1843
+ hidden: [],
1844
+ minimized: [],
1845
+ pages: pageList(layout)
1846
+ };
1847
+ }
1848
+ function sameKeys(a, b) {
1849
+ return a === b || a.length === b.length && a.every((k, i) => k === b[i]);
1850
+ }
1851
+ function pageLayout(layout, pageKey) {
1852
+ if (!hasPages(layout))
1853
+ return pageKey === DEFAULT_PAGE_KEY ? layout : void 0;
1854
+ const page = layout.pages?.find(
1855
+ (p) => p.key === pageKey
1856
+ );
1857
+ if (page === void 0) return void 0;
1858
+ const { pages, ...rest } = layout;
1859
+ return {
1860
+ ...rest,
1861
+ order: page.order,
1862
+ hidden: page.hidden,
1863
+ minimized: page.minimized
1864
+ };
1865
+ }
1866
+ function withPageLayout(layout, pageKey, next) {
1867
+ const current = pageLayout(layout, pageKey);
1868
+ if (current === void 0 || sameKeys(current.order, next.order) && sameKeys(current.hidden, next.hidden) && sameKeys(current.minimized, next.minimized)) {
1869
+ return layout;
1870
+ }
1871
+ if (!hasPages(layout)) {
1872
+ return {
1873
+ ...layout,
1874
+ order: next.order,
1875
+ hidden: next.hidden,
1876
+ minimized: next.minimized
1877
+ };
1878
+ }
1879
+ return {
1880
+ ...layout,
1881
+ pages: (layout.pages ?? []).map(
1882
+ (page) => page.key === pageKey ? {
1883
+ ...page,
1884
+ order: next.order,
1885
+ hidden: next.hidden,
1886
+ minimized: next.minimized
1887
+ } : page
1888
+ )
1889
+ };
1890
+ }
1891
+ function normalizedTitle(title) {
1892
+ return title.trim().normalize("NFKC").toLowerCase();
1893
+ }
1894
+ function resolvePageKey(layout, reference) {
1895
+ if (reference === void 0 || reference === "") return void 0;
1896
+ const pages = pageList(layout);
1897
+ const byKey = pages.find((p) => p.key === reference);
1898
+ if (byKey !== void 0) return byKey.key;
1899
+ const wanted = normalizedTitle(reference);
1900
+ return pages.find((p) => normalizedTitle(p.title) === wanted)?.key;
1901
+ }
1902
+ function homePageKey(layout, item) {
1903
+ return resolvePageKey(layout, item.page) ?? pageList(layout)[0].key;
1904
+ }
1905
+ function assignPages(definitions, layout) {
1906
+ const pages = pageList(layout);
1907
+ const assigned = new Map(pages.map((p) => [p.key, []]));
1908
+ for (const definition of definitions) {
1909
+ let pageKey;
1910
+ if (!resolveWidgetLock(definition).move) {
1911
+ pageKey = pages.find(
1912
+ (p) => p.order.includes(definition.key) || p.hidden.includes(definition.key)
1913
+ )?.key;
1914
+ }
1915
+ assigned.get(pageKey ?? homePageKey(layout, definition))?.push(definition);
1916
+ }
1917
+ return assigned;
1918
+ }
1919
+ function pageOf(definitions, layout, key) {
1920
+ for (const [pageKey, widgets] of assignPages(definitions, layout)) {
1921
+ if (widgets.some((w) => w.key === key)) return pageKey;
1922
+ }
1923
+ return void 0;
1924
+ }
1925
+ function pageWidgets(definitions, layout, pageKey) {
1926
+ const onPage = assignPages(definitions, layout).get(pageKey) ?? [];
1927
+ const view = pageLayout(layout, pageKey);
1928
+ return view === void 0 ? [] : visibleWidgets(onPage, view);
1929
+ }
1930
+ function pageRoom(definitions, layout, pageKey, options = {}) {
1931
+ const page = pageList(layout).find(
1932
+ (p) => p.key === pageKey
1933
+ );
1934
+ if (page === void 0) return void 0;
1935
+ const maxRows = rowsAllowed(page, options);
1936
+ const rowsUsed = placeRows(
1937
+ pageWidgets(definitions, layout, pageKey)
1938
+ ).length;
1939
+ return { maxRows, rowsUsed, rowsFree: Math.max(0, maxRows - rowsUsed) };
1940
+ }
1941
+ function describePage(page) {
1942
+ return `Page "${page.title}"`;
1943
+ }
1944
+ function titleOf(item) {
1945
+ return item.title ?? item.key;
1946
+ }
1947
+ function checkTitle(layout, title, exceptKey) {
1948
+ const trimmed = title.trim();
1949
+ if (trimmed === "") return err("A page title must not be empty.");
1950
+ if (trimmed.length > MAX_PAGE_TITLE_LENGTH) {
1951
+ return err(
1952
+ `Page title "${trimmed}" is ${trimmed.length} characters; the most is ${MAX_PAGE_TITLE_LENGTH}.`
1953
+ );
1954
+ }
1955
+ const wanted = normalizedTitle(trimmed);
1956
+ const clash = pageList(layout).find(
1957
+ (p) => p.key !== exceptKey && normalizedTitle(p.title) === wanted
1958
+ );
1959
+ if (clash !== void 0) {
1960
+ return err(`Another page is already called "${clash.title}".`);
1961
+ }
1962
+ return ok(trimmed);
1963
+ }
1964
+ function nextPageKey(layout) {
1965
+ const keys = new Set(pageList(layout).map((p) => p.key));
1966
+ let n = 1;
1967
+ while (keys.has(`page-${n}`)) n += 1;
1968
+ return `page-${n}`;
1969
+ }
1970
+ function nextPageTitle(layout) {
1971
+ const used = new Set(
1972
+ pageList(layout).map((p) => normalizedTitle(p.title))
1973
+ );
1974
+ let n = pageList(layout).length + 1;
1975
+ while (used.has(normalizedTitle(`Page ${n}`))) n += 1;
1976
+ return `Page ${n}`;
1977
+ }
1978
+ function addPage(layout, options = {}) {
1979
+ if (pageList(layout).length >= MAX_PARSED_PAGES) {
1980
+ return err(
1981
+ `The layout already has ${MAX_PARSED_PAGES} pages, the most it can keep.`
1982
+ );
1983
+ }
1984
+ const key = options.key ?? nextPageKey(layout);
1985
+ if (pageList(layout).some((p) => p.key === key)) {
1986
+ return err(`A page with the key "${key}" already exists.`);
1987
+ }
1988
+ const title = checkTitle(
1989
+ layout,
1990
+ options.title ?? nextPageTitle(layout)
1991
+ );
1992
+ if (!title.ok) return title;
1993
+ const base = withExplicitPages(layout);
1994
+ const page = {
1995
+ key,
1996
+ title: title.value,
1997
+ order: [],
1998
+ hidden: [],
1999
+ minimized: []
2000
+ };
2001
+ return ok({ ...base, pages: [...base.pages ?? [], page] });
2002
+ }
2003
+ function renamePage(layout, pageKey, title) {
2004
+ const page = pageList(layout).find(
2005
+ (p) => p.key === pageKey
2006
+ );
2007
+ if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
2008
+ const checked = checkTitle(layout, title, pageKey);
2009
+ if (!checked.ok) return checked;
2010
+ if (checked.value === page.title) return ok(layout);
2011
+ const base = withExplicitPages(layout);
2012
+ return ok({
2013
+ ...base,
2014
+ pages: (base.pages ?? []).map(
2015
+ (p) => p.key === pageKey ? { ...p, title: checked.value } : p
2016
+ )
2017
+ });
2018
+ }
2019
+ function movePage(layout, pageKey, offset) {
2020
+ const pages = layout.pages ?? [];
2021
+ const from = pages.findIndex((p) => p.key === pageKey);
2022
+ if (from === -1) return layout;
2023
+ const to = Math.max(
2024
+ 0,
2025
+ Math.min(pages.length - 1, from + Math.trunc(offset))
2026
+ );
2027
+ if (to === from) return layout;
2028
+ const next = [...pages];
2029
+ const [moved] = next.splice(from, 1);
2030
+ next.splice(to, 0, moved);
2031
+ return { ...layout, pages: next };
2032
+ }
2033
+ function overflowing(definitions, layout, options) {
2034
+ return pageList(layout).find(
2035
+ (page) => placeRows(pageWidgets(definitions, layout, page.key)).length > rowsAllowed(page, options)
2036
+ );
2037
+ }
2038
+ function removePage(definitions, layout, pageKey, options = {}) {
2039
+ const pages = pageList(layout);
2040
+ const index = pages.findIndex((p) => p.key === pageKey);
2041
+ const page = pages[index];
2042
+ if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
2043
+ if (pages.length === 1) {
2044
+ return err(
2045
+ `${describePage(page)} is the only page, so it can't be removed.`
2046
+ );
2047
+ }
2048
+ const target = pages[index > 0 ? index - 1 : 1];
2049
+ const merged = {
2050
+ ...layout,
2051
+ pages: pages.filter((p) => p.key !== pageKey).map(
2052
+ (p) => p.key === target.key ? {
2053
+ ...p,
2054
+ order: index > 0 ? [...p.order, ...page.order] : [...page.order, ...p.order],
2055
+ hidden: [...p.hidden, ...page.hidden],
2056
+ minimized: [...p.minimized, ...page.minimized]
2057
+ } : p
2058
+ )
2059
+ };
2060
+ const full = overflowing(
2061
+ definitions,
2062
+ merged,
2063
+ options
2064
+ );
2065
+ if (full !== void 0) {
2066
+ return err(
2067
+ `${describePage(page)} has widgets that don't fit on the other pages (${describePage(full)} would need more than ${rowsAllowed(full, options)} rows). Move or hide some first.`
2068
+ );
2069
+ }
2070
+ return ok(merged);
2071
+ }
2072
+ function without(keys, key) {
2073
+ return keys.includes(key) ? keys.filter((k) => k !== key) : keys;
2074
+ }
2075
+ function moveWidgetToPage(definitions, layout, widgetKey, targetKey, options = {}) {
2076
+ const widget = definitions.find((d) => d.key === widgetKey);
2077
+ if (widget === void 0) {
2078
+ return err(
2079
+ `Widget "${widgetKey}" isn't one of the widgets, so it can't be moved to a page.`
2080
+ );
2081
+ }
2082
+ const pages = pageList(layout);
2083
+ const target = pages.find((p) => p.key === targetKey);
2084
+ if (target === void 0) return err(`Page "${targetKey}" doesn't exist.`);
2085
+ const sourceKey = pageOf(definitions, layout, widgetKey);
2086
+ if (sourceKey === targetKey) return ok(layout);
2087
+ if (resolveWidgetLock(widget).move) {
2088
+ return err(
2089
+ `Widget "${titleOf(widget)}" is locked against moving, so it can't be moved to ${describePage(target)}.`
2090
+ );
2091
+ }
2092
+ const base = withExplicitPages(layout);
2093
+ const source = (base.pages ?? []).find(
2094
+ (p) => p.key === sourceKey
2095
+ );
2096
+ const hidden = source.hidden.includes(widgetKey);
2097
+ const minimized = source.minimized.includes(widgetKey);
2098
+ const targetOrder = pageWidgets(
2099
+ definitions,
2100
+ base,
2101
+ targetKey
2102
+ ).map((d) => d.key);
2103
+ const moved = {
2104
+ ...base,
2105
+ pages: (base.pages ?? []).map((p) => {
2106
+ if (p.key === sourceKey) {
2107
+ return {
2108
+ ...p,
2109
+ order: without(p.order, widgetKey),
2110
+ hidden: without(p.hidden, widgetKey),
2111
+ minimized: without(p.minimized, widgetKey)
2112
+ };
2113
+ }
2114
+ if (p.key === targetKey) {
2115
+ return {
2116
+ ...p,
2117
+ order: hidden ? p.order : [...targetOrder, widgetKey],
2118
+ hidden: hidden ? [...p.hidden, widgetKey] : p.hidden,
2119
+ minimized: minimized ? [...p.minimized, widgetKey] : p.minimized
2120
+ };
2121
+ }
2122
+ return p;
2123
+ })
2124
+ };
2125
+ if (!hidden) {
2126
+ const allowed = rowsAllowed(target, options);
2127
+ const needed = placeRows(
2128
+ pageWidgets(definitions, moved, targetKey)
2129
+ ).length;
2130
+ if (needed > allowed) {
2131
+ return err(
2132
+ `${describePage(target)} has no room for "${titleOf(widget)}": it would need row ${needed}, and the page allows ${allowed}.`
2133
+ );
2134
+ }
2135
+ }
2136
+ return ok(moved);
1756
2137
  }
1757
2138
  function nextSort(current, column) {
1758
2139
  if (current?.column !== column) return { column, direction: "asc" };
@@ -2033,6 +2414,25 @@ function useDetailData(definition, load, enabled, fallback) {
2033
2414
  }, [enabled, load, fallback, definition, attempt]);
2034
2415
  return { state, reload: () => setAttempt((count) => count + 1) };
2035
2416
  }
2417
+ var NEW_PAGE_VALUE = "__new-page__";
2418
+ function PagePanel({
2419
+ paged,
2420
+ id,
2421
+ labelledBy,
2422
+ children
2423
+ }) {
2424
+ if (!paged) return /* @__PURE__ */ jsx(Fragment, { children });
2425
+ return /* @__PURE__ */ jsx(
2426
+ "div",
2427
+ {
2428
+ role: "tabpanel",
2429
+ id,
2430
+ "aria-labelledby": labelledBy,
2431
+ className: "dwt-page-panel",
2432
+ children
2433
+ }
2434
+ );
2435
+ }
2036
2436
  var NARROW_VIEWPORT_QUERY = "(max-width: 640px)";
2037
2437
  var WIDE_GRID_PX = 900;
2038
2438
  function useFillSpans(definitions, gridRef, twelve) {
@@ -2203,6 +2603,181 @@ function InlineConfirm({
2203
2603
  }
2204
2604
  );
2205
2605
  }
2606
+ function PageTabs({
2607
+ pages,
2608
+ activeKey,
2609
+ idPrefix,
2610
+ panelId,
2611
+ announcement,
2612
+ onSelect
2613
+ }) {
2614
+ const slot = useSlotClassName();
2615
+ const { labels } = useWidgetSettings();
2616
+ const tabs = useRef(/* @__PURE__ */ new Map());
2617
+ const activeIndex = pages.findIndex((p) => p.key === activeKey);
2618
+ function onKeyDown(event, index) {
2619
+ const last = pages.length - 1;
2620
+ let next;
2621
+ switch (event.key) {
2622
+ case "ArrowRight":
2623
+ case "ArrowDown":
2624
+ next = index === last ? 0 : index + 1;
2625
+ break;
2626
+ case "ArrowLeft":
2627
+ case "ArrowUp":
2628
+ next = index === 0 ? last : index - 1;
2629
+ break;
2630
+ case "Home":
2631
+ next = 0;
2632
+ break;
2633
+ case "End":
2634
+ next = last;
2635
+ break;
2636
+ default:
2637
+ return;
2638
+ }
2639
+ event.preventDefault();
2640
+ const target = pages[next];
2641
+ if (target === void 0) return;
2642
+ onSelect(target.key);
2643
+ tabs.current.get(target.key)?.focus();
2644
+ }
2645
+ return /* @__PURE__ */ jsxs("div", { className: slot("pageBar", "dwt-pages"), children: [
2646
+ /* @__PURE__ */ jsx(
2647
+ "button",
2648
+ {
2649
+ type: "button",
2650
+ className: slot(
2651
+ "button",
2652
+ "dwt-button",
2653
+ "dwt-icon-button",
2654
+ "dwt-page-nav"
2655
+ ),
2656
+ "aria-label": labels.previousPageButton,
2657
+ disabled: activeIndex <= 0,
2658
+ onClick: () => onSelect(pages[activeIndex - 1].key),
2659
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2039" })
2660
+ }
2661
+ ),
2662
+ /* @__PURE__ */ jsx("div", { role: "tablist", "aria-label": labels.pageBar, className: "dwt-page-tabs", children: pages.map((page, index) => {
2663
+ const selected = page.key === activeKey;
2664
+ return /* @__PURE__ */ jsxs(
2665
+ "button",
2666
+ {
2667
+ ref: (element) => {
2668
+ if (element === null) tabs.current.delete(page.key);
2669
+ else tabs.current.set(page.key, element);
2670
+ },
2671
+ type: "button",
2672
+ role: "tab",
2673
+ id: `${idPrefix}-tab-${index}`,
2674
+ "aria-selected": selected,
2675
+ "aria-controls": panelId,
2676
+ "aria-label": labels.pageTabName(
2677
+ index + 1,
2678
+ pages.length,
2679
+ page.title
2680
+ ),
2681
+ title: page.title,
2682
+ tabIndex: selected ? 0 : -1,
2683
+ className: slot("button", "dwt-page-tab"),
2684
+ onClick: () => onSelect(page.key),
2685
+ onKeyDown: (event) => onKeyDown(event, index),
2686
+ children: [
2687
+ /* @__PURE__ */ jsx("span", { className: "dwt-page-tab-title", children: page.title }),
2688
+ /* @__PURE__ */ jsx("span", { className: "dwt-page-dot", "aria-hidden": "true" })
2689
+ ]
2690
+ },
2691
+ page.key
2692
+ );
2693
+ }) }),
2694
+ /* @__PURE__ */ jsx(
2695
+ "button",
2696
+ {
2697
+ type: "button",
2698
+ className: slot(
2699
+ "button",
2700
+ "dwt-button",
2701
+ "dwt-icon-button",
2702
+ "dwt-page-nav"
2703
+ ),
2704
+ "aria-label": labels.nextPageButton,
2705
+ disabled: activeIndex === pages.length - 1,
2706
+ onClick: () => onSelect(pages[activeIndex + 1].key),
2707
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u203A" })
2708
+ }
2709
+ ),
2710
+ /* @__PURE__ */ jsx("span", { className: "dwt-visually-hidden", "aria-live": "polite", children: announcement })
2711
+ ] });
2712
+ }
2713
+ function PageRenameForm({
2714
+ initial,
2715
+ onSubmit,
2716
+ onCancel
2717
+ }) {
2718
+ const slot = useSlotClassName();
2719
+ const { labels } = useWidgetSettings();
2720
+ const [title, setTitle] = useState(initial);
2721
+ const [error, setError] = useState(void 0);
2722
+ const inputRef = useRef(null);
2723
+ useEffect(() => {
2724
+ inputRef.current?.focus();
2725
+ inputRef.current?.select();
2726
+ }, []);
2727
+ return /* @__PURE__ */ jsxs(
2728
+ "form",
2729
+ {
2730
+ className: "dwt-page-rename",
2731
+ onSubmit: (event) => {
2732
+ event.preventDefault();
2733
+ setError(onSubmit(title));
2734
+ },
2735
+ onKeyDown: (event) => {
2736
+ if (event.key === "Escape") {
2737
+ event.stopPropagation();
2738
+ onCancel();
2739
+ }
2740
+ },
2741
+ children: [
2742
+ /* @__PURE__ */ jsx(
2743
+ "input",
2744
+ {
2745
+ ref: inputRef,
2746
+ type: "text",
2747
+ className: "dwt-page-title-input",
2748
+ "aria-label": labels.pageTitle,
2749
+ "aria-invalid": error !== void 0,
2750
+ maxLength: MAX_PAGE_TITLE_LENGTH,
2751
+ value: title,
2752
+ onChange: (event) => setTitle(event.target.value)
2753
+ }
2754
+ ),
2755
+ /* @__PURE__ */ jsx(
2756
+ "button",
2757
+ {
2758
+ type: "submit",
2759
+ className: slot("button", "dwt-button", "dwt-icon-button"),
2760
+ "aria-label": labels.savePageTitle,
2761
+ title: labels.savePageTitle,
2762
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2713" })
2763
+ }
2764
+ ),
2765
+ /* @__PURE__ */ jsx(
2766
+ "button",
2767
+ {
2768
+ type: "button",
2769
+ className: slot("button", "dwt-button", "dwt-icon-button"),
2770
+ "aria-label": labels.cancelPageTitle,
2771
+ title: labels.cancelPageTitle,
2772
+ onClick: onCancel,
2773
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2715" })
2774
+ }
2775
+ ),
2776
+ error !== void 0 && /* @__PURE__ */ jsx("span", { role: "alert", className: "dwt-page-error", children: error })
2777
+ ]
2778
+ }
2779
+ );
2780
+ }
2206
2781
  function withoutLock(definition) {
2207
2782
  if (definition.locked === void 0) return definition;
2208
2783
  const copy = { ...definition };
@@ -2220,6 +2795,11 @@ function DashboardInner({
2220
2795
  onEditingChange,
2221
2796
  defaultLayout,
2222
2797
  toolbar = true,
2798
+ activePage: activePageProp,
2799
+ defaultActivePage,
2800
+ onActivePageChange,
2801
+ maxRows,
2802
+ maxPages,
2223
2803
  onRetry,
2224
2804
  loadDetail,
2225
2805
  onOpenDetail,
@@ -2235,8 +2815,19 @@ function DashboardInner({
2235
2815
  const byKey = new Map(
2236
2816
  widgets.map((w) => [w.definition.key, w])
2237
2817
  );
2238
- const visible = visibleWidgets(definitions, layout);
2239
- const hidden = hiddenWidgets(definitions, layout);
2818
+ const pages = pageList(layout);
2819
+ const paged = pages.length >= 2;
2820
+ const [activeState, setActiveState] = useState(
2821
+ defaultActivePage
2822
+ );
2823
+ const requested = activePageProp ?? activeState;
2824
+ const activePage = pages.find((page) => page.key === requested) ?? pages[0];
2825
+ const activeKey = activePage.key;
2826
+ const pageDefs = assignPages(definitions, layout).get(activeKey) ?? [];
2827
+ const view = pageLayout(layout, activeKey) ?? layout;
2828
+ const roomOptions = maxRows === void 0 ? {} : { maxRows };
2829
+ const visible = visibleWidgets(pageDefs, view);
2830
+ const hidden = hiddenWidgets(pageDefs, view);
2240
2831
  const editable = onLayoutChange !== void 0;
2241
2832
  const toggleMode = editMode === "toggle";
2242
2833
  const [editingState, setEditingState] = useState(defaultEditing);
@@ -2255,10 +2846,16 @@ function DashboardInner({
2255
2846
  );
2256
2847
  const pending = editing ? confirming : void 0;
2257
2848
  const [announcement, setAnnouncement] = useState("");
2849
+ const [pageAnnouncement, setPageAnnouncement] = useState("");
2850
+ const [pageError, setPageError] = useState(void 0);
2851
+ const [pageEditor, setPageEditor] = useState(void 0);
2852
+ const editorMode = pageEditor?.key === activeKey ? pageEditor.mode : void 0;
2853
+ const instanceId = useId();
2854
+ const panelId = `${instanceId}-panel`;
2258
2855
  const toggleRef = useRef(null);
2259
2856
  const resetRef = useRef(null);
2260
- const minimized = editable ? visible.filter((definition) => isMinimized(layout, definition.key)) : [];
2261
- const shown = editable ? visible.filter((definition) => !isMinimized(layout, definition.key)) : visible;
2857
+ const minimized = editable ? visible.filter((definition) => isMinimized(view, definition.key)) : [];
2858
+ const shown = editable ? visible.filter((definition) => !isMinimized(view, definition.key)) : visible;
2262
2859
  const [detailKey, setDetailKey] = useState(void 0);
2263
2860
  const gridRef = useRef(null);
2264
2861
  const twelve = usesWidthColumns(shown);
@@ -2270,6 +2867,110 @@ function DashboardInner({
2270
2867
  function change(next) {
2271
2868
  if (next !== layout) onLayoutChange?.(next);
2272
2869
  }
2870
+ function changePage(next) {
2871
+ setPageError(void 0);
2872
+ change(withPageLayout(layout, activeKey, next));
2873
+ }
2874
+ function selectPage(pageKey) {
2875
+ const index = pages.findIndex((page) => page.key === pageKey);
2876
+ if (index === -1) return;
2877
+ if (activePageProp === void 0) setActiveState(pageKey);
2878
+ onActivePageChange?.(pageKey);
2879
+ setPageError(void 0);
2880
+ setPageEditor(void 0);
2881
+ setPageAnnouncement(
2882
+ labels.pageTabName(
2883
+ index + 1,
2884
+ pages.length,
2885
+ pages[index].title
2886
+ )
2887
+ );
2888
+ }
2889
+ function addPageAction() {
2890
+ const added = addPage(layout);
2891
+ if (!added.ok) {
2892
+ setPageError(added.error);
2893
+ return;
2894
+ }
2895
+ change(added.value);
2896
+ const created = added.value.pages?.at(-1);
2897
+ if (created !== void 0) {
2898
+ if (activePageProp === void 0) setActiveState(created.key);
2899
+ onActivePageChange?.(created.key);
2900
+ setPageError(void 0);
2901
+ setPageEditor({ mode: "rename", key: created.key });
2902
+ }
2903
+ }
2904
+ function renamePageAction(title) {
2905
+ const renamed = renamePage(layout, activeKey, title);
2906
+ if (!renamed.ok) return renamed.error;
2907
+ change(renamed.value);
2908
+ setPageEditor(void 0);
2909
+ return void 0;
2910
+ }
2911
+ function deletePageAction() {
2912
+ const index = pages.findIndex((page) => page.key === activeKey);
2913
+ const removed = removePage(definitions, layout, activeKey, roomOptions);
2914
+ setPageEditor(void 0);
2915
+ if (!removed.ok) {
2916
+ setPageError(removed.error);
2917
+ return;
2918
+ }
2919
+ const neighbor = pages[index > 0 ? index - 1 : 1];
2920
+ change(removed.value);
2921
+ if (neighbor !== void 0) selectPage(neighbor.key);
2922
+ }
2923
+ function moveWidgetAction(widgetKey, targetKey) {
2924
+ let target = targetKey;
2925
+ let base = layout;
2926
+ if (targetKey === NEW_PAGE_VALUE) {
2927
+ const added = addPage(layout);
2928
+ if (!added.ok) {
2929
+ setPageError(added.error);
2930
+ return;
2931
+ }
2932
+ base = added.value;
2933
+ target = (added.value.pages?.at(-1)).key;
2934
+ }
2935
+ const moved = moveWidgetToPage(
2936
+ definitions,
2937
+ base,
2938
+ widgetKey,
2939
+ target,
2940
+ roomOptions
2941
+ );
2942
+ if (!moved.ok) {
2943
+ setPageError(moved.error);
2944
+ return;
2945
+ }
2946
+ setPageError(void 0);
2947
+ change(moved.value);
2948
+ const destination = moved.value.pages?.find(
2949
+ (page) => page.key === target
2950
+ );
2951
+ setPageAnnouncement(
2952
+ labels.movedToPage(
2953
+ byKey.get(widgetKey)?.definition.title ?? widgetKey,
2954
+ destination?.title ?? target
2955
+ )
2956
+ );
2957
+ }
2958
+ function showAction(widgetKey) {
2959
+ const next = withPageLayout(
2960
+ layout,
2961
+ activeKey,
2962
+ showWidget(view, widgetKey)
2963
+ );
2964
+ if (hasPages(next)) {
2965
+ const room = pageRoom(definitions, next, activeKey, roomOptions);
2966
+ if (room !== void 0 && room.rowsUsed > room.maxRows) {
2967
+ setPageError(labels.pageFull(activePage.title, room.maxRows));
2968
+ return;
2969
+ }
2970
+ }
2971
+ setPageError(void 0);
2972
+ change(next);
2973
+ }
2273
2974
  function setEditing(next) {
2274
2975
  if (editingProp === void 0) setEditingState(next);
2275
2976
  onEditingChange?.(next);
@@ -2347,176 +3048,312 @@ function DashboardInner({
2347
3048
  ]
2348
3049
  }
2349
3050
  ),
2350
- controlsShown && hidden.length > 0 && /* @__PURE__ */ jsxs("div", { className: slot("hiddenBar", "dwt-hidden-bar"), children: [
2351
- /* @__PURE__ */ jsx("span", { className: "dwt-hidden-label", children: labels.hiddenWidgets }),
2352
- hidden.map((definition) => /* @__PURE__ */ jsxs(
2353
- "button",
2354
- {
2355
- type: "button",
2356
- className: slot("button", "dwt-button", "dwt-show-button"),
2357
- "aria-label": labels.show(definition.title),
2358
- onClick: () => change(showWidget(layout, definition.key)),
2359
- children: [
2360
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "+ " }),
2361
- definition.title
2362
- ]
2363
- },
2364
- definition.key
2365
- ))
2366
- ] }),
2367
- minimized.length > 0 && /* @__PURE__ */ jsxs(
3051
+ paged && /* @__PURE__ */ jsx(
3052
+ PageTabs,
3053
+ {
3054
+ pages,
3055
+ activeKey,
3056
+ idPrefix: instanceId,
3057
+ panelId,
3058
+ announcement: pageAnnouncement,
3059
+ onSelect: selectPage
3060
+ }
3061
+ ),
3062
+ controlsShown && /* @__PURE__ */ jsxs(
2368
3063
  "div",
2369
3064
  {
2370
- className: slot("hiddenBar", "dwt-hidden-bar", "dwt-minimized-bar"),
3065
+ role: "group",
3066
+ "aria-label": labels.managePages,
3067
+ className: "dwt-page-manage",
2371
3068
  children: [
2372
- /* @__PURE__ */ jsx("span", { className: "dwt-hidden-label", children: labels.minimizedWidgets }),
2373
- minimized.map((definition) => /* @__PURE__ */ jsxs(
3069
+ /* @__PURE__ */ jsx(
2374
3070
  "button",
2375
3071
  {
2376
3072
  type: "button",
2377
- className: slot("button", "dwt-button", "dwt-show-button"),
2378
- "aria-label": labels.expand(definition.title),
2379
- onClick: () => change(toggleMinimized(layout, definition.key)),
2380
- children: [
2381
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u25B8 " }),
2382
- definition.title
2383
- ]
3073
+ className: slot("button", "dwt-button"),
3074
+ disabled: maxPages !== void 0 && pages.length >= maxPages,
3075
+ onClick: addPageAction,
3076
+ children: labels.addPage
3077
+ }
3078
+ ),
3079
+ paged && editorMode === "rename" && /* @__PURE__ */ jsx(
3080
+ PageRenameForm,
3081
+ {
3082
+ initial: activePage.title,
3083
+ onSubmit: renamePageAction,
3084
+ onCancel: () => setPageEditor(void 0)
2384
3085
  },
2385
- definition.key
2386
- ))
3086
+ activeKey
3087
+ ),
3088
+ paged && editorMode !== "rename" && /* @__PURE__ */ jsxs(Fragment, { children: [
3089
+ /* @__PURE__ */ jsx(
3090
+ "button",
3091
+ {
3092
+ type: "button",
3093
+ className: slot("button", "dwt-button", "dwt-icon-button"),
3094
+ "aria-label": labels.renamePage(activePage.title),
3095
+ title: labels.renamePage(activePage.title),
3096
+ onClick: () => setPageEditor({ mode: "rename", key: activeKey }),
3097
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u270E" })
3098
+ }
3099
+ ),
3100
+ /* @__PURE__ */ jsx(
3101
+ "button",
3102
+ {
3103
+ type: "button",
3104
+ className: slot("button", "dwt-button", "dwt-icon-button"),
3105
+ "aria-label": labels.movePageLeft(activePage.title),
3106
+ title: labels.movePageLeft(activePage.title),
3107
+ disabled: pages[0]?.key === activeKey,
3108
+ onClick: () => change(movePage(layout, activeKey, -1)),
3109
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2190" })
3110
+ }
3111
+ ),
3112
+ /* @__PURE__ */ jsx(
3113
+ "button",
3114
+ {
3115
+ type: "button",
3116
+ className: slot("button", "dwt-button", "dwt-icon-button"),
3117
+ "aria-label": labels.movePageRight(activePage.title),
3118
+ title: labels.movePageRight(activePage.title),
3119
+ disabled: pages.at(-1)?.key === activeKey,
3120
+ onClick: () => change(movePage(layout, activeKey, 1)),
3121
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2192" })
3122
+ }
3123
+ ),
3124
+ /* @__PURE__ */ jsx(
3125
+ "button",
3126
+ {
3127
+ type: "button",
3128
+ className: slot("button", "dwt-button", "dwt-icon-button"),
3129
+ "aria-label": labels.deletePage(activePage.title),
3130
+ title: labels.deletePage(activePage.title),
3131
+ onClick: () => setPageEditor({ mode: "delete", key: activeKey }),
3132
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xD7" })
3133
+ }
3134
+ )
3135
+ ] }),
3136
+ paged && editorMode === "delete" && /* @__PURE__ */ jsx(
3137
+ InlineConfirm,
3138
+ {
3139
+ message: labels.confirmDeletePage(activePage.title),
3140
+ onAnswer: (yes) => yes ? deletePageAction() : setPageEditor(void 0)
3141
+ }
3142
+ ),
3143
+ pageError !== void 0 && /* @__PURE__ */ jsx("span", { role: "alert", className: "dwt-page-error", children: pageError })
2387
3144
  ]
2388
3145
  }
2389
3146
  ),
2390
- visible.length === 0 ? /* @__PURE__ */ jsx("p", { className: slot("empty", "dwt-empty", "dwt-dashboard-empty"), children: definitions.length === 0 ? labels.noWidgets : labels.allHidden }) : shown.length === 0 ? null : /* @__PURE__ */ jsx(
2391
- "div",
3147
+ /* @__PURE__ */ jsxs(
3148
+ PagePanel,
2392
3149
  {
2393
- ref: gridRef,
2394
- className: slot("grid", "dwt-grid", twelve && "dwt-grid--twelve"),
2395
- children: shown.map((definition, index) => {
2396
- const widget = byKey.get(
2397
- definition.key
2398
- );
2399
- if (widget === void 0) return null;
2400
- const key = definition.key;
2401
- const title = definition.title;
2402
- const lock = resolveWidgetLock(definition);
2403
- const ownLock = resolveWidgetLock(
2404
- widget.definition
2405
- );
2406
- const isLocked = ownLock.move || ownLock.hide || ownLock.minimize;
2407
- const lockLabel = overrideLocks ? labels.lockedForViewers(title) : labels.locked(title);
2408
- const canMoveEarlier = !lock.move && moveWidgetBy(definitions, layout, key, -1) !== layout;
2409
- const canMoveLater = !lock.move && moveWidgetBy(definitions, layout, key, 1) !== layout;
2410
- const actions = controlsShown ? /* @__PURE__ */ jsxs(Fragment, { children: [
2411
- isLocked && /* @__PURE__ */ jsx(
2412
- "span",
3150
+ paged,
3151
+ id: panelId,
3152
+ labelledBy: `${instanceId}-tab-${pages.findIndex((p) => p.key === activeKey)}`,
3153
+ children: [
3154
+ controlsShown && hidden.length > 0 && /* @__PURE__ */ jsxs("div", { className: slot("hiddenBar", "dwt-hidden-bar"), children: [
3155
+ /* @__PURE__ */ jsx("span", { className: "dwt-hidden-label", children: labels.hiddenWidgets }),
3156
+ hidden.map((definition) => /* @__PURE__ */ jsxs(
3157
+ "button",
2413
3158
  {
2414
- role: "img",
2415
- className: "dwt-lock",
2416
- "aria-label": lockLabel,
2417
- title: lockLabel,
2418
- children: /* @__PURE__ */ jsxs(
2419
- "svg",
3159
+ type: "button",
3160
+ className: slot("button", "dwt-button", "dwt-show-button"),
3161
+ "aria-label": labels.show(definition.title),
3162
+ onClick: () => showAction(definition.key),
3163
+ children: [
3164
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "+ " }),
3165
+ definition.title
3166
+ ]
3167
+ },
3168
+ definition.key
3169
+ ))
3170
+ ] }),
3171
+ minimized.length > 0 && /* @__PURE__ */ jsxs(
3172
+ "div",
3173
+ {
3174
+ className: slot("hiddenBar", "dwt-hidden-bar", "dwt-minimized-bar"),
3175
+ children: [
3176
+ /* @__PURE__ */ jsx("span", { className: "dwt-hidden-label", children: labels.minimizedWidgets }),
3177
+ minimized.map((definition) => /* @__PURE__ */ jsxs(
3178
+ "button",
2420
3179
  {
2421
- viewBox: "0 0 16 16",
2422
- width: "14",
2423
- height: "14",
2424
- "aria-hidden": "true",
2425
- focusable: "false",
3180
+ type: "button",
3181
+ className: slot("button", "dwt-button", "dwt-show-button"),
3182
+ "aria-label": labels.expand(definition.title),
3183
+ onClick: () => changePage(toggleMinimized(view, definition.key)),
2426
3184
  children: [
2427
- /* @__PURE__ */ jsx(
2428
- "rect",
2429
- {
2430
- x: "3",
2431
- y: "7",
2432
- width: "10",
2433
- height: "7",
2434
- rx: "1.5",
2435
- fill: "currentColor"
2436
- }
2437
- ),
2438
- /* @__PURE__ */ jsx(
2439
- "path",
3185
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u25B8 " }),
3186
+ definition.title
3187
+ ]
3188
+ },
3189
+ definition.key
3190
+ ))
3191
+ ]
3192
+ }
3193
+ ),
3194
+ visible.length === 0 ? /* @__PURE__ */ jsx("p", { className: slot("empty", "dwt-empty", "dwt-dashboard-empty"), children: paged && pageDefs.length === 0 ? labels.emptyPage : definitions.length === 0 ? labels.noWidgets : labels.allHidden }) : shown.length === 0 ? null : /* @__PURE__ */ jsx(
3195
+ "div",
3196
+ {
3197
+ ref: gridRef,
3198
+ className: slot("grid", "dwt-grid", twelve && "dwt-grid--twelve"),
3199
+ children: shown.map((definition, index) => {
3200
+ const widget = byKey.get(
3201
+ definition.key
3202
+ );
3203
+ if (widget === void 0) return null;
3204
+ const key = definition.key;
3205
+ const title = definition.title;
3206
+ const lock = resolveWidgetLock(definition);
3207
+ const ownLock = resolveWidgetLock(
3208
+ widget.definition
3209
+ );
3210
+ const isLocked = ownLock.move || ownLock.hide || ownLock.minimize;
3211
+ const lockLabel = overrideLocks ? labels.lockedForViewers(title) : labels.locked(title);
3212
+ const canMoveEarlier = !lock.move && moveWidgetBy(pageDefs, view, key, -1) !== view;
3213
+ const canMoveLater = !lock.move && moveWidgetBy(pageDefs, view, key, 1) !== view;
3214
+ const actions = controlsShown ? /* @__PURE__ */ jsxs(Fragment, { children: [
3215
+ isLocked && /* @__PURE__ */ jsx(
3216
+ "span",
3217
+ {
3218
+ role: "img",
3219
+ className: "dwt-lock",
3220
+ "aria-label": lockLabel,
3221
+ title: lockLabel,
3222
+ children: /* @__PURE__ */ jsxs(
3223
+ "svg",
2440
3224
  {
2441
- d: "M5.5 7V5a2.5 2.5 0 0 1 5 0v2",
2442
- fill: "none",
2443
- stroke: "currentColor",
2444
- strokeWidth: "1.4"
3225
+ viewBox: "0 0 16 16",
3226
+ width: "14",
3227
+ height: "14",
3228
+ "aria-hidden": "true",
3229
+ focusable: "false",
3230
+ children: [
3231
+ /* @__PURE__ */ jsx(
3232
+ "rect",
3233
+ {
3234
+ x: "3",
3235
+ y: "7",
3236
+ width: "10",
3237
+ height: "7",
3238
+ rx: "1.5",
3239
+ fill: "currentColor"
3240
+ }
3241
+ ),
3242
+ /* @__PURE__ */ jsx(
3243
+ "path",
3244
+ {
3245
+ d: "M5.5 7V5a2.5 2.5 0 0 1 5 0v2",
3246
+ fill: "none",
3247
+ stroke: "currentColor",
3248
+ strokeWidth: "1.4"
3249
+ }
3250
+ )
3251
+ ]
2445
3252
  }
2446
3253
  )
2447
- ]
2448
- }
2449
- )
2450
- }
2451
- ),
2452
- !lock.move && /* @__PURE__ */ jsxs(Fragment, { children: [
2453
- /* @__PURE__ */ jsx(
2454
- "button",
2455
- {
2456
- type: "button",
2457
- className: slot(
2458
- "button",
2459
- "dwt-button",
2460
- "dwt-icon-button"
3254
+ }
2461
3255
  ),
2462
- "aria-label": labels.moveEarlier(title),
2463
- title: labels.moveEarlier(title),
2464
- disabled: index === 0 || !canMoveEarlier,
2465
- onClick: () => change(moveWidgetBy(definitions, layout, key, -1)),
2466
- children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2191" })
2467
- }
2468
- ),
2469
- /* @__PURE__ */ jsx(
2470
- "button",
2471
- {
2472
- type: "button",
2473
- className: slot(
3256
+ !lock.move && /* @__PURE__ */ jsxs(Fragment, { children: [
3257
+ /* @__PURE__ */ jsx(
3258
+ "button",
3259
+ {
3260
+ type: "button",
3261
+ className: slot(
3262
+ "button",
3263
+ "dwt-button",
3264
+ "dwt-icon-button"
3265
+ ),
3266
+ "aria-label": labels.moveEarlier(title),
3267
+ title: labels.moveEarlier(title),
3268
+ disabled: index === 0 || !canMoveEarlier,
3269
+ onClick: () => changePage(moveWidgetBy(pageDefs, view, key, -1)),
3270
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2191" })
3271
+ }
3272
+ ),
3273
+ /* @__PURE__ */ jsx(
3274
+ "button",
3275
+ {
3276
+ type: "button",
3277
+ className: slot(
3278
+ "button",
3279
+ "dwt-button",
3280
+ "dwt-icon-button"
3281
+ ),
3282
+ "aria-label": labels.moveLater(title),
3283
+ title: labels.moveLater(title),
3284
+ disabled: index === shown.length - 1 || !canMoveLater,
3285
+ onClick: () => changePage(moveWidgetBy(pageDefs, view, key, 1)),
3286
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2193" })
3287
+ }
3288
+ ),
3289
+ paged && /* @__PURE__ */ jsxs(
3290
+ "select",
3291
+ {
3292
+ className: "dwt-move-select",
3293
+ "aria-label": labels.moveToPage(title),
3294
+ value: "",
3295
+ onChange: (event) => {
3296
+ if (event.target.value !== "") {
3297
+ moveWidgetAction(key, event.target.value);
3298
+ }
3299
+ },
3300
+ children: [
3301
+ /* @__PURE__ */ jsx("option", { value: "", children: labels.moveToPagePlaceholder }),
3302
+ pages.filter((page) => page.key !== activeKey).map((page) => /* @__PURE__ */ jsx("option", { value: page.key, children: labels.moveToPageOption(
3303
+ page.title,
3304
+ pageRoom(
3305
+ definitions,
3306
+ layout,
3307
+ page.key,
3308
+ roomOptions
3309
+ )?.rowsFree ?? 0
3310
+ ) }, page.key)),
3311
+ (maxPages === void 0 || pages.length < maxPages) && /* @__PURE__ */ jsx("option", { value: NEW_PAGE_VALUE, children: labels.newPageOption })
3312
+ ]
3313
+ }
3314
+ )
3315
+ ] }),
3316
+ !lock.hide && /* @__PURE__ */ jsx(
2474
3317
  "button",
2475
- "dwt-button",
2476
- "dwt-icon-button"
2477
- ),
2478
- "aria-label": labels.moveLater(title),
2479
- title: labels.moveLater(title),
2480
- disabled: index === shown.length - 1 || !canMoveLater,
2481
- onClick: () => change(moveWidgetBy(definitions, layout, key, 1)),
2482
- children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2193" })
2483
- }
2484
- )
2485
- ] }),
2486
- !lock.hide && /* @__PURE__ */ jsx(
2487
- "button",
2488
- {
2489
- type: "button",
2490
- className: slot("button", "dwt-button", "dwt-icon-button"),
2491
- "aria-label": labels.hide(title),
2492
- title: labels.hide(title),
2493
- onClick: () => change(hideWidget(layout, key)),
2494
- children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xD7" })
2495
- }
2496
- )
2497
- ] }) : void 0;
2498
- const viewable = resolveDetailOptions(definition) !== void 0 && (onOpenDetail !== void 0 || loadDetail !== void 0 || widget.status === "ok" && deriveDetailData(widget.data) !== void 0);
2499
- return /* @__PURE__ */ jsx(
2500
- ResolvedWidgetCard,
2501
- {
2502
- widget,
2503
- ...viewable ? {
2504
- onView: () => onOpenDetail === void 0 ? setDetailKey(key) : onOpenDetail(key)
2505
- } : {},
2506
- ...spanProps(spans, key),
2507
- ...widthProps(twelve, definition),
2508
- minimized: isMinimized(layout, key),
2509
- editing: toggleMode && controlsShown,
2510
- ...actions === void 0 ? {} : { actions },
2511
- ...editable && !lock.minimize ? {
2512
- onToggleMinimized: () => change(toggleMinimized(layout, key))
2513
- } : {},
2514
- ...headingLevel === void 0 ? {} : { headingLevel },
2515
- ...onRetry === void 0 || widget.status !== "error" ? {} : { onRetry: () => onRetry(key) }
2516
- },
2517
- key
2518
- );
2519
- })
3318
+ {
3319
+ type: "button",
3320
+ className: slot(
3321
+ "button",
3322
+ "dwt-button",
3323
+ "dwt-icon-button"
3324
+ ),
3325
+ "aria-label": labels.hide(title),
3326
+ title: labels.hide(title),
3327
+ onClick: () => changePage(hideWidget(view, key)),
3328
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xD7" })
3329
+ }
3330
+ )
3331
+ ] }) : void 0;
3332
+ const viewable = resolveDetailOptions(definition) !== void 0 && (onOpenDetail !== void 0 || loadDetail !== void 0 || widget.status === "ok" && deriveDetailData(widget.data) !== void 0);
3333
+ return /* @__PURE__ */ jsx(
3334
+ ResolvedWidgetCard,
3335
+ {
3336
+ widget,
3337
+ ...viewable ? {
3338
+ onView: () => onOpenDetail === void 0 ? setDetailKey(key) : onOpenDetail(key)
3339
+ } : {},
3340
+ ...spanProps(spans, key),
3341
+ ...widthProps(twelve, definition),
3342
+ minimized: isMinimized(view, key),
3343
+ editing: toggleMode && controlsShown,
3344
+ ...actions === void 0 ? {} : { actions },
3345
+ ...editable && !lock.minimize ? {
3346
+ onToggleMinimized: () => changePage(toggleMinimized(view, key))
3347
+ } : {},
3348
+ ...headingLevel === void 0 ? {} : { headingLevel },
3349
+ ...onRetry === void 0 || widget.status !== "error" ? {} : { onRetry: () => onRetry(key) }
3350
+ },
3351
+ key
3352
+ );
3353
+ })
3354
+ }
3355
+ )
3356
+ ]
2520
3357
  }
2521
3358
  ),
2522
3359
  detailKey !== void 0 && byKey.get(detailKey) !== void 0 && /* @__PURE__ */ jsx(
@@ -3175,8 +4012,21 @@ function useWidgets(definitions, providers, context, options = {}) {
3175
4012
  },
3176
4013
  { rootMargin }
3177
4014
  );
3178
- grid.querySelectorAll("[data-widget-key]").forEach((card) => observer.observe(card));
3179
- return () => observer.disconnect();
4015
+ const observed = /* @__PURE__ */ new WeakSet();
4016
+ const observeNewCards = () => {
4017
+ grid.querySelectorAll("[data-widget-key]").forEach((card) => {
4018
+ if (observed.has(card)) return;
4019
+ observed.add(card);
4020
+ observer.observe(card);
4021
+ });
4022
+ };
4023
+ observeNewCards();
4024
+ const mutations = typeof MutationObserver === "undefined" ? void 0 : new MutationObserver(observeNewCards);
4025
+ mutations?.observe(grid, { childList: true, subtree: true });
4026
+ return () => {
4027
+ mutations?.disconnect();
4028
+ observer.disconnect();
4029
+ };
3180
4030
  }, [loader, loadWhen, rootMargin, cardKeys]);
3181
4031
  useEffect(() => {
3182
4032
  if (loader === null || !(refreshMs > 0)) return void 0;