@richardmcquiston01/dashboard-widgets-toolkit 0.8.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
@@ -524,6 +539,40 @@ var DEFAULT_LABELS = {
524
539
  moveLater: (title) => `Move ${title} later`,
525
540
  hiddenWidgets: "Hidden:",
526
541
  minimizedWidgets: "Minimized:",
542
+ customize: "Customize",
543
+ done: "Done",
544
+ reset: "Reset layout",
545
+ revertChanges: "Revert changes",
546
+ confirmReset: "Reset the layout to the default?",
547
+ confirmRevert: "Revert to how this looked before you started editing?",
548
+ confirmYes: "Yes",
549
+ confirmNo: "No",
550
+ editingOn: "Editing dashboard. Use the buttons on each card to reorder or hide it.",
551
+ editingOff: "Finished editing.",
552
+ dashboardControls: "Dashboard controls",
553
+ locked: (title) => `${title} is locked`,
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.",
527
576
  opensInNewTab: "(opens in a new tab)",
528
577
  view: (title) => `View ${title}`,
529
578
  close: "Close",
@@ -1394,6 +1443,7 @@ function WidgetCard({
1394
1443
  columnSpan,
1395
1444
  width,
1396
1445
  widgetKey,
1446
+ editing = false,
1397
1447
  headingLevel = 2,
1398
1448
  className,
1399
1449
  children
@@ -1438,6 +1488,7 @@ function WidgetCard({
1438
1488
  "dwt-card",
1439
1489
  `dwt-card--${status}`,
1440
1490
  minimized && "dwt-card--minimized",
1491
+ editing && "dwt-card--editing",
1441
1492
  size !== void 0 && `dwt-card--size-${size}`,
1442
1493
  fillsHeight(fill) && "dwt-card--fill-height",
1443
1494
  fillsWidth(fill) && "dwt-card--fill-width",
@@ -1610,6 +1661,9 @@ function isWidgetVisibleTo(definition, roles) {
1610
1661
  }
1611
1662
 
1612
1663
  // src/core/layout.ts
1664
+ var MAX_PAGE_TITLE_LENGTH = 30;
1665
+ var MAX_PARSED_PAGES = 50;
1666
+ var MAX_PAGE_ROWS = 20;
1613
1667
  var EMPTY_LAYOUT = Object.freeze({
1614
1668
  order: Object.freeze([]),
1615
1669
  hidden: Object.freeze([]),
@@ -1724,6 +1778,22 @@ function restoreWidget(layout, key) {
1724
1778
  function toggleMinimized(layout, key) {
1725
1779
  return isMinimized(layout, key) ? restoreWidget(layout, key) : minimizeWidget(layout, key);
1726
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
+ }
1727
1797
  function enforceLocks(definitions, layout, options = {}) {
1728
1798
  if (options.overrideLocks === true) return layout;
1729
1799
  const noMove = /* @__PURE__ */ new Set();
@@ -1735,11 +1805,337 @@ function enforceLocks(definitions, layout, options = {}) {
1735
1805
  if (lock.hide) noHide.add(definition.key);
1736
1806
  if (lock.minimize) noMinimize.add(definition.key);
1737
1807
  }
1738
- const without = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
1739
- const order = without(layout.order, noMove);
1740
- const hidden = without(layout.hidden, noHide);
1741
- const minimized = without(layout.minimized, noMinimize);
1742
- 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);
1743
2139
  }
1744
2140
  function nextSort(current, column) {
1745
2141
  if (current?.column !== column) return { column, direction: "asc" };
@@ -2020,6 +2416,25 @@ function useDetailData(definition, load, enabled, fallback) {
2020
2416
  }, [enabled, load, fallback, definition, attempt]);
2021
2417
  return { state, reload: () => setAttempt((count) => count + 1) };
2022
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
+ }
2023
2438
  var NARROW_VIEWPORT_QUERY = "(max-width: 640px)";
2024
2439
  var WIDE_GRID_PX = 900;
2025
2440
  function useFillSpans(definitions, gridRef, twelve) {
@@ -2139,6 +2554,232 @@ function DetailHost({
2139
2554
  }
2140
2555
  ) });
2141
2556
  }
2557
+ function InlineConfirm({
2558
+ message,
2559
+ onAnswer
2560
+ }) {
2561
+ const slot = useSlotClassName();
2562
+ const { labels } = useWidgetSettings();
2563
+ const noRef = react.useRef(null);
2564
+ react.useEffect(() => {
2565
+ noRef.current?.focus();
2566
+ }, []);
2567
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2568
+ "span",
2569
+ {
2570
+ role: "group",
2571
+ "aria-label": message,
2572
+ className: "dwt-confirm",
2573
+ onKeyDown: (event) => {
2574
+ if (event.key === "Escape") {
2575
+ event.stopPropagation();
2576
+ onAnswer(false);
2577
+ }
2578
+ },
2579
+ children: [
2580
+ /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", className: "dwt-confirm-message", children: message }),
2581
+ /* @__PURE__ */ jsxRuntime.jsx(
2582
+ "button",
2583
+ {
2584
+ type: "button",
2585
+ className: slot("button", "dwt-button", "dwt-icon-button"),
2586
+ "aria-label": labels.confirmYes,
2587
+ title: labels.confirmYes,
2588
+ onClick: () => onAnswer(true),
2589
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2713" })
2590
+ }
2591
+ ),
2592
+ /* @__PURE__ */ jsxRuntime.jsx(
2593
+ "button",
2594
+ {
2595
+ ref: noRef,
2596
+ type: "button",
2597
+ className: slot("button", "dwt-button", "dwt-icon-button"),
2598
+ "aria-label": labels.confirmNo,
2599
+ title: labels.confirmNo,
2600
+ onClick: () => onAnswer(false),
2601
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2715" })
2602
+ }
2603
+ )
2604
+ ]
2605
+ }
2606
+ );
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
+ }
2142
2783
  function withoutLock(definition) {
2143
2784
  if (definition.locked === void 0) return definition;
2144
2785
  const copy = { ...definition };
@@ -2150,6 +2791,17 @@ function DashboardInner({
2150
2791
  layout: savedLayout = EMPTY_LAYOUT,
2151
2792
  onLayoutChange,
2152
2793
  overrideLocks = false,
2794
+ editMode = "always",
2795
+ editing: editingProp,
2796
+ defaultEditing = false,
2797
+ onEditingChange,
2798
+ defaultLayout,
2799
+ toolbar = true,
2800
+ activePage: activePageProp,
2801
+ defaultActivePage,
2802
+ onActivePageChange,
2803
+ maxRows,
2804
+ maxPages,
2153
2805
  onRetry,
2154
2806
  loadDetail,
2155
2807
  onOpenDetail,
@@ -2165,11 +2817,47 @@ function DashboardInner({
2165
2817
  const byKey = new Map(
2166
2818
  widgets.map((w) => [w.definition.key, w])
2167
2819
  );
2168
- const visible = visibleWidgets(definitions, layout);
2169
- 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);
2170
2833
  const editable = onLayoutChange !== void 0;
2171
- const minimized = editable ? visible.filter((definition) => isMinimized(layout, definition.key)) : [];
2172
- const shown = editable ? visible.filter((definition) => !isMinimized(layout, definition.key)) : visible;
2834
+ const toggleMode = editMode === "toggle";
2835
+ const [editingState, setEditingState] = react.useState(defaultEditing);
2836
+ const editing = editingProp ?? editingState;
2837
+ const controlsShown = editable && (!toggleMode || editing);
2838
+ const [snapshot, setSnapshot] = react.useState(
2839
+ void 0
2840
+ );
2841
+ if (toggleMode && editable && editing) {
2842
+ if (snapshot === void 0) setSnapshot(savedLayout);
2843
+ } else if (snapshot !== void 0) {
2844
+ setSnapshot(void 0);
2845
+ }
2846
+ const [confirming, setConfirming] = react.useState(
2847
+ void 0
2848
+ );
2849
+ const pending = editing ? confirming : void 0;
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`;
2857
+ const toggleRef = react.useRef(null);
2858
+ const resetRef = react.useRef(null);
2859
+ const minimized = editable ? visible.filter((definition) => isMinimized(view, definition.key)) : [];
2860
+ const shown = editable ? visible.filter((definition) => !isMinimized(view, definition.key)) : visible;
2173
2861
  const [detailKey, setDetailKey] = react.useState(void 0);
2174
2862
  const gridRef = react.useRef(null);
2175
2863
  const twelve = usesWidthColumns(shown);
@@ -2181,142 +2869,507 @@ function DashboardInner({
2181
2869
  function change(next) {
2182
2870
  if (next !== layout) onLayoutChange?.(next);
2183
2871
  }
2184
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: slot("dashboard", "dwt-dashboard", className), children: [
2185
- editable && hidden.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: slot("hiddenBar", "dwt-hidden-bar"), children: [
2186
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dwt-hidden-label", children: labels.hiddenWidgets }),
2187
- hidden.map((definition) => /* @__PURE__ */ jsxRuntime.jsxs(
2188
- "button",
2189
- {
2190
- type: "button",
2191
- className: slot("button", "dwt-button", "dwt-show-button"),
2192
- "aria-label": labels.show(definition.title),
2193
- onClick: () => change(showWidget(layout, definition.key)),
2194
- children: [
2195
- /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "+ " }),
2196
- definition.title
2197
- ]
2198
- },
2199
- definition.key
2200
- ))
2201
- ] }),
2202
- minimized.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
2203
- "div",
2204
- {
2205
- className: slot("hiddenBar", "dwt-hidden-bar", "dwt-minimized-bar"),
2206
- children: [
2207
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dwt-hidden-label", children: labels.minimizedWidgets }),
2208
- minimized.map((definition) => /* @__PURE__ */ jsxRuntime.jsxs(
2209
- "button",
2210
- {
2211
- type: "button",
2212
- className: slot("button", "dwt-button", "dwt-show-button"),
2213
- "aria-label": labels.expand(definition.title),
2214
- onClick: () => change(toggleMinimized(layout, definition.key)),
2215
- children: [
2216
- /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u25B8 " }),
2217
- definition.title
2218
- ]
2219
- },
2220
- definition.key
2221
- ))
2222
- ]
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;
2223
2933
  }
2224
- ),
2225
- 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(
2226
- "div",
2227
- {
2228
- ref: gridRef,
2229
- className: slot("grid", "dwt-grid", twelve && "dwt-grid--twelve"),
2230
- children: shown.map((definition, index) => {
2231
- const widget = byKey.get(
2232
- definition.key
2233
- );
2234
- if (widget === void 0) return null;
2235
- const key = definition.key;
2236
- const title = definition.title;
2237
- const lock = resolveWidgetLock(definition);
2238
- const canMoveEarlier = !lock.move && moveWidgetBy(definitions, layout, key, -1) !== layout;
2239
- const canMoveLater = !lock.move && moveWidgetBy(definitions, layout, key, 1) !== layout;
2240
- const actions = editable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2241
- !lock.move && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
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
+ }
2976
+ function setEditing(next) {
2977
+ if (editingProp === void 0) setEditingState(next);
2978
+ onEditingChange?.(next);
2979
+ setAnnouncement(next ? labels.editingOn : labels.editingOff);
2980
+ setConfirming(void 0);
2981
+ }
2982
+ function answerConfirmation(yes) {
2983
+ const which = pending;
2984
+ setConfirming(void 0);
2985
+ if (yes && which === "reset") {
2986
+ change(enforceLocks(definitions, defaultLayout ?? EMPTY_LAYOUT));
2987
+ } else if (yes && which === "revert" && snapshot !== void 0) {
2988
+ change(snapshot);
2989
+ }
2990
+ (which === "revert" ? toggleRef : resetRef).current?.focus();
2991
+ }
2992
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2993
+ "div",
2994
+ {
2995
+ className: slot(
2996
+ "dashboard",
2997
+ "dwt-dashboard",
2998
+ toggleMode && controlsShown && "dwt-dashboard--editing",
2999
+ className
3000
+ ),
3001
+ children: [
3002
+ editable && toggleMode && toolbar && /* @__PURE__ */ jsxRuntime.jsxs(
3003
+ "div",
3004
+ {
3005
+ role: "group",
3006
+ "aria-label": labels.dashboardControls,
3007
+ className: slot("toolbar", "dwt-toolbar"),
3008
+ children: [
2242
3009
  /* @__PURE__ */ jsxRuntime.jsx(
2243
3010
  "button",
2244
3011
  {
3012
+ ref: toggleRef,
2245
3013
  type: "button",
2246
- className: slot(
2247
- "button",
2248
- "dwt-button",
2249
- "dwt-icon-button"
2250
- ),
2251
- "aria-label": labels.moveEarlier(title),
2252
- title: labels.moveEarlier(title),
2253
- disabled: index === 0 || !canMoveEarlier,
2254
- onClick: () => change(moveWidgetBy(definitions, layout, key, -1)),
2255
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2191" })
3014
+ "aria-pressed": editing,
3015
+ className: slot("button", "dwt-button", "dwt-toolbar-toggle"),
3016
+ onClick: () => setEditing(!editing),
3017
+ children: editing ? labels.done : labels.customize
2256
3018
  }
2257
3019
  ),
3020
+ editing && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3021
+ /* @__PURE__ */ jsxRuntime.jsx(
3022
+ "button",
3023
+ {
3024
+ ref: resetRef,
3025
+ type: "button",
3026
+ className: slot("button", "dwt-button"),
3027
+ onClick: () => setConfirming("reset"),
3028
+ children: labels.reset
3029
+ }
3030
+ ),
3031
+ /* @__PURE__ */ jsxRuntime.jsx(
3032
+ "button",
3033
+ {
3034
+ type: "button",
3035
+ className: slot("button", "dwt-button"),
3036
+ disabled: snapshot === void 0 || savedLayout === snapshot,
3037
+ onClick: () => setConfirming("revert"),
3038
+ children: labels.revertChanges
3039
+ }
3040
+ )
3041
+ ] }),
3042
+ pending !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
3043
+ InlineConfirm,
3044
+ {
3045
+ message: pending === "reset" ? labels.confirmReset : labels.confirmRevert,
3046
+ onAnswer: answerConfirmation
3047
+ }
3048
+ ),
3049
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dwt-visually-hidden", "aria-live": "polite", children: announcement })
3050
+ ]
3051
+ }
3052
+ ),
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(
3065
+ "div",
3066
+ {
3067
+ role: "group",
3068
+ "aria-label": labels.managePages,
3069
+ className: "dwt-page-manage",
3070
+ children: [
2258
3071
  /* @__PURE__ */ jsxRuntime.jsx(
2259
3072
  "button",
2260
3073
  {
2261
3074
  type: "button",
2262
- className: slot(
2263
- "button",
2264
- "dwt-button",
2265
- "dwt-icon-button"
2266
- ),
2267
- "aria-label": labels.moveLater(title),
2268
- title: labels.moveLater(title),
2269
- disabled: index === shown.length - 1 || !canMoveLater,
2270
- onClick: () => change(moveWidgetBy(definitions, layout, key, 1)),
2271
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2193" })
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)
3087
+ },
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 })
3146
+ ]
3147
+ }
3148
+ ),
3149
+ /* @__PURE__ */ jsxRuntime.jsxs(
3150
+ PagePanel,
3151
+ {
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",
3160
+ {
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",
3181
+ {
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)),
3186
+ children: [
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",
3226
+ {
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
+ ]
3254
+ }
3255
+ )
3256
+ }
3257
+ ),
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(
3319
+ "button",
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
+ })
2272
3356
  }
2273
3357
  )
2274
- ] }),
2275
- !lock.hide && /* @__PURE__ */ jsxRuntime.jsx(
2276
- "button",
2277
- {
2278
- type: "button",
2279
- className: slot("button", "dwt-button", "dwt-icon-button"),
2280
- "aria-label": labels.hide(title),
2281
- title: labels.hide(title),
2282
- onClick: () => change(hideWidget(layout, key)),
2283
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\xD7" })
2284
- }
2285
- )
2286
- ] }) : void 0;
2287
- const viewable = resolveDetailOptions(definition) !== void 0 && (onOpenDetail !== void 0 || loadDetail !== void 0 || widget.status === "ok" && deriveDetailData(widget.data) !== void 0);
2288
- return /* @__PURE__ */ jsxRuntime.jsx(
2289
- ResolvedWidgetCard,
2290
- {
2291
- widget,
2292
- ...viewable ? {
2293
- onView: () => onOpenDetail === void 0 ? setDetailKey(key) : onOpenDetail(key)
2294
- } : {},
2295
- ...spanProps(spans, key),
2296
- ...widthProps(twelve, definition),
2297
- minimized: isMinimized(layout, key),
2298
- ...actions === void 0 ? {} : { actions },
2299
- ...editable && !lock.minimize ? {
2300
- onToggleMinimized: () => change(toggleMinimized(layout, key))
2301
- } : {},
2302
- ...headingLevel === void 0 ? {} : { headingLevel },
2303
- ...onRetry === void 0 || widget.status !== "error" ? {} : { onRetry: () => onRetry(key) }
2304
- },
2305
- key
2306
- );
2307
- })
2308
- }
2309
- ),
2310
- detailKey !== void 0 && byKey.get(detailKey) !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
2311
- DetailHost,
2312
- {
2313
- widget: byKey.get(detailKey),
2314
- loadDetail,
2315
- onClose: () => setDetailKey(void 0)
2316
- },
2317
- detailKey
2318
- )
2319
- ] });
3358
+ ]
3359
+ }
3360
+ ),
3361
+ detailKey !== void 0 && byKey.get(detailKey) !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
3362
+ DetailHost,
3363
+ {
3364
+ widget: byKey.get(detailKey),
3365
+ loadDetail,
3366
+ onClose: () => setDetailKey(void 0)
3367
+ },
3368
+ detailKey
3369
+ )
3370
+ ]
3371
+ }
3372
+ );
2320
3373
  }
2321
3374
  function Dashboard({
2322
3375
  locale,
@@ -2961,8 +4014,21 @@ function useWidgets(definitions, providers, context, options = {}) {
2961
4014
  },
2962
4015
  { rootMargin }
2963
4016
  );
2964
- grid.querySelectorAll("[data-widget-key]").forEach((card) => observer.observe(card));
2965
- 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
+ };
2966
4032
  }, [loader, loadWhen, rootMargin, cardKeys]);
2967
4033
  react.useEffect(() => {
2968
4034
  if (loader === null || !(refreshMs > 0)) return void 0;