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