@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.js CHANGED
@@ -427,21 +427,36 @@ function fillColumnSpans(items, columns, largeSpan = 2) {
427
427
  function fillWidthSpans(items) {
428
428
  return placeAndFill(items, WIDTH_COLUMNS, itemWidth);
429
429
  }
430
+ function placeRows(items) {
431
+ return placeAndFillRows(items, WIDTH_COLUMNS, itemWidth).rows;
432
+ }
430
433
  function placeAndFill(items, columns, spanOf) {
434
+ return placeAndFillRows(items, columns, spanOf).spans;
435
+ }
436
+ function placeAndFillRows(items, columns, spanOf) {
431
437
  const trackCount = Math.max(1, Math.floor(columns));
432
438
  const spans = /* @__PURE__ */ new Map();
439
+ const rows = [];
433
440
  let row = [];
434
441
  let used = 0;
435
442
  function closeRow() {
443
+ if (row.length === 0) return;
436
444
  const fillers = row.filter(({ item }) => fillsWidth(item.fill));
437
445
  const leftover = trackCount - used;
446
+ const finalSpans = /* @__PURE__ */ new Map();
438
447
  if (fillers.length > 0) {
439
448
  const share = Math.floor(leftover / fillers.length);
440
449
  const remainder = leftover % fillers.length;
441
450
  fillers.forEach(({ item, span }, index) => {
442
- spans.set(item.key, span + share + (index < remainder ? 1 : 0));
451
+ const grown = span + share + (index < remainder ? 1 : 0);
452
+ spans.set(item.key, grown);
453
+ finalSpans.set(item.key, grown);
443
454
  });
444
455
  }
456
+ rows.push({
457
+ keys: row.map(({ item }) => item.key),
458
+ spans: row.map(({ item, span }) => finalSpans.get(item.key) ?? span)
459
+ });
445
460
  row = [];
446
461
  used = 0;
447
462
  }
@@ -452,7 +467,7 @@ function placeAndFill(items, columns, spanOf) {
452
467
  used += span;
453
468
  }
454
469
  closeRow();
455
- return spans;
470
+ return { spans, rows };
456
471
  }
457
472
 
458
473
  // src/core/scale.ts
@@ -522,6 +537,40 @@ var DEFAULT_LABELS = {
522
537
  moveLater: (title) => `Move ${title} later`,
523
538
  hiddenWidgets: "Hidden:",
524
539
  minimizedWidgets: "Minimized:",
540
+ customize: "Customize",
541
+ done: "Done",
542
+ reset: "Reset layout",
543
+ revertChanges: "Revert changes",
544
+ confirmReset: "Reset the layout to the default?",
545
+ confirmRevert: "Revert to how this looked before you started editing?",
546
+ confirmYes: "Yes",
547
+ confirmNo: "No",
548
+ editingOn: "Editing dashboard. Use the buttons on each card to reorder or hide it.",
549
+ editingOff: "Finished editing.",
550
+ dashboardControls: "Dashboard controls",
551
+ locked: (title) => `${title} is locked`,
552
+ lockedForViewers: (title) => `${title} is locked for viewers`,
553
+ pageBar: "Pages",
554
+ pageTabName: (index, count, title) => `Page ${index} of ${count}: ${title}`,
555
+ previousPageButton: "Previous page",
556
+ nextPageButton: "Next page",
557
+ managePages: "Manage pages",
558
+ addPage: "Add page",
559
+ renamePage: (title) => `Rename ${title}`,
560
+ pageTitle: "Page title",
561
+ savePageTitle: "Save page title",
562
+ cancelPageTitle: "Cancel renaming",
563
+ deletePage: (title) => `Delete ${title}`,
564
+ confirmDeletePage: (title) => `Delete the page "${title}" and move its widgets to another page?`,
565
+ movePageLeft: (title) => `Move ${title} page left`,
566
+ movePageRight: (title) => `Move ${title} page right`,
567
+ moveToPage: (title) => `Move ${title} to another page`,
568
+ moveToPagePlaceholder: "Move to page\u2026",
569
+ moveToPageOption: (title, rowsFree) => `${title} (${rowsFree} ${rowsFree === 1 ? "row" : "rows"} free)`,
570
+ newPageOption: "New page\u2026",
571
+ movedToPage: (title, pageTitle) => `${title} moved to ${pageTitle}`,
572
+ pageFull: (pageTitle, maxRows) => `Page "${pageTitle}" is full (${maxRows} rows). Move or hide a widget first.`,
573
+ emptyPage: "This page has no widgets. Move one here from another page.",
525
574
  opensInNewTab: "(opens in a new tab)",
526
575
  view: (title) => `View ${title}`,
527
576
  close: "Close",
@@ -1392,6 +1441,7 @@ function WidgetCard({
1392
1441
  columnSpan,
1393
1442
  width,
1394
1443
  widgetKey,
1444
+ editing = false,
1395
1445
  headingLevel = 2,
1396
1446
  className,
1397
1447
  children
@@ -1436,6 +1486,7 @@ function WidgetCard({
1436
1486
  "dwt-card",
1437
1487
  `dwt-card--${status}`,
1438
1488
  minimized && "dwt-card--minimized",
1489
+ editing && "dwt-card--editing",
1439
1490
  size !== void 0 && `dwt-card--size-${size}`,
1440
1491
  fillsHeight(fill) && "dwt-card--fill-height",
1441
1492
  fillsWidth(fill) && "dwt-card--fill-width",
@@ -1608,6 +1659,9 @@ function isWidgetVisibleTo(definition, roles) {
1608
1659
  }
1609
1660
 
1610
1661
  // src/core/layout.ts
1662
+ var MAX_PAGE_TITLE_LENGTH = 30;
1663
+ var MAX_PARSED_PAGES = 50;
1664
+ var MAX_PAGE_ROWS = 20;
1611
1665
  var EMPTY_LAYOUT = Object.freeze({
1612
1666
  order: Object.freeze([]),
1613
1667
  hidden: Object.freeze([]),
@@ -1722,6 +1776,22 @@ function restoreWidget(layout, key) {
1722
1776
  function toggleMinimized(layout, key) {
1723
1777
  return isMinimized(layout, key) ? restoreWidget(layout, key) : minimizeWidget(layout, key);
1724
1778
  }
1779
+ function mapLayoutLists(layout, change) {
1780
+ const top = change(layout);
1781
+ const pages = layout.pages?.map((page) => {
1782
+ const next = change(page);
1783
+ return next.order === page.order && next.hidden === page.hidden && next.minimized === page.minimized ? page : { ...page, ...next };
1784
+ });
1785
+ const pagesChanged = pages !== void 0 && pages.some((page, i) => page !== layout.pages?.[i]);
1786
+ if (top.order === layout.order && top.hidden === layout.hidden && top.minimized === layout.minimized && !pagesChanged) {
1787
+ return layout;
1788
+ }
1789
+ return {
1790
+ ...layout,
1791
+ ...top,
1792
+ ...pages === void 0 ? {} : { pages }
1793
+ };
1794
+ }
1725
1795
  function enforceLocks(definitions, layout, options = {}) {
1726
1796
  if (options.overrideLocks === true) return layout;
1727
1797
  const noMove = /* @__PURE__ */ new Set();
@@ -1733,11 +1803,337 @@ function enforceLocks(definitions, layout, options = {}) {
1733
1803
  if (lock.hide) noHide.add(definition.key);
1734
1804
  if (lock.minimize) noMinimize.add(definition.key);
1735
1805
  }
1736
- const without = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
1737
- const order = without(layout.order, noMove);
1738
- const hidden = without(layout.hidden, noHide);
1739
- const minimized = without(layout.minimized, noMinimize);
1740
- return order === layout.order && hidden === layout.hidden && minimized === layout.minimized ? layout : { ...layout, order, hidden, minimized };
1806
+ const without2 = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
1807
+ return mapLayoutLists(layout, (lists) => ({
1808
+ order: without2(lists.order, noMove),
1809
+ hidden: without2(lists.hidden, noHide),
1810
+ minimized: without2(lists.minimized, noMinimize)
1811
+ }));
1812
+ }
1813
+
1814
+ // src/core/pages.ts
1815
+ var DEFAULT_PAGE_KEY = "default";
1816
+ var DEFAULT_PAGE_ROWS = 4;
1817
+ function rowsAllowed(page, options) {
1818
+ const rows = page.maxRows ?? options.maxRows ?? DEFAULT_PAGE_ROWS;
1819
+ return Math.max(1, Math.min(MAX_PAGE_ROWS, Math.floor(rows)));
1820
+ }
1821
+ function hasPages(layout) {
1822
+ return layout.pages !== void 0 && layout.pages.length > 0;
1823
+ }
1824
+ function pageList(layout) {
1825
+ if (layout.pages !== void 0 && layout.pages.length > 0) {
1826
+ return layout.pages;
1827
+ }
1828
+ return [
1829
+ {
1830
+ key: DEFAULT_PAGE_KEY,
1831
+ title: "Page 1",
1832
+ order: layout.order,
1833
+ hidden: layout.hidden,
1834
+ minimized: layout.minimized
1835
+ }
1836
+ ];
1837
+ }
1838
+ function withExplicitPages(layout) {
1839
+ if (hasPages(layout)) return layout;
1840
+ return {
1841
+ ...layout,
1842
+ order: [],
1843
+ hidden: [],
1844
+ minimized: [],
1845
+ pages: pageList(layout)
1846
+ };
1847
+ }
1848
+ function sameKeys(a, b) {
1849
+ return a === b || a.length === b.length && a.every((k, i) => k === b[i]);
1850
+ }
1851
+ function pageLayout(layout, pageKey) {
1852
+ if (!hasPages(layout))
1853
+ return pageKey === DEFAULT_PAGE_KEY ? layout : void 0;
1854
+ const page = layout.pages?.find(
1855
+ (p) => p.key === pageKey
1856
+ );
1857
+ if (page === void 0) return void 0;
1858
+ const { pages, ...rest } = layout;
1859
+ return {
1860
+ ...rest,
1861
+ order: page.order,
1862
+ hidden: page.hidden,
1863
+ minimized: page.minimized
1864
+ };
1865
+ }
1866
+ function withPageLayout(layout, pageKey, next) {
1867
+ const current = pageLayout(layout, pageKey);
1868
+ if (current === void 0 || sameKeys(current.order, next.order) && sameKeys(current.hidden, next.hidden) && sameKeys(current.minimized, next.minimized)) {
1869
+ return layout;
1870
+ }
1871
+ if (!hasPages(layout)) {
1872
+ return {
1873
+ ...layout,
1874
+ order: next.order,
1875
+ hidden: next.hidden,
1876
+ minimized: next.minimized
1877
+ };
1878
+ }
1879
+ return {
1880
+ ...layout,
1881
+ pages: (layout.pages ?? []).map(
1882
+ (page) => page.key === pageKey ? {
1883
+ ...page,
1884
+ order: next.order,
1885
+ hidden: next.hidden,
1886
+ minimized: next.minimized
1887
+ } : page
1888
+ )
1889
+ };
1890
+ }
1891
+ function normalizedTitle(title) {
1892
+ return title.trim().normalize("NFKC").toLowerCase();
1893
+ }
1894
+ function resolvePageKey(layout, reference) {
1895
+ if (reference === void 0 || reference === "") return void 0;
1896
+ const pages = pageList(layout);
1897
+ const byKey = pages.find((p) => p.key === reference);
1898
+ if (byKey !== void 0) return byKey.key;
1899
+ const wanted = normalizedTitle(reference);
1900
+ return pages.find((p) => normalizedTitle(p.title) === wanted)?.key;
1901
+ }
1902
+ function homePageKey(layout, item) {
1903
+ return resolvePageKey(layout, item.page) ?? pageList(layout)[0].key;
1904
+ }
1905
+ function assignPages(definitions, layout) {
1906
+ const pages = pageList(layout);
1907
+ const assigned = new Map(pages.map((p) => [p.key, []]));
1908
+ for (const definition of definitions) {
1909
+ let pageKey;
1910
+ if (!resolveWidgetLock(definition).move) {
1911
+ pageKey = pages.find(
1912
+ (p) => p.order.includes(definition.key) || p.hidden.includes(definition.key)
1913
+ )?.key;
1914
+ }
1915
+ assigned.get(pageKey ?? homePageKey(layout, definition))?.push(definition);
1916
+ }
1917
+ return assigned;
1918
+ }
1919
+ function pageOf(definitions, layout, key) {
1920
+ for (const [pageKey, widgets] of assignPages(definitions, layout)) {
1921
+ if (widgets.some((w) => w.key === key)) return pageKey;
1922
+ }
1923
+ return void 0;
1924
+ }
1925
+ function pageWidgets(definitions, layout, pageKey) {
1926
+ const onPage = assignPages(definitions, layout).get(pageKey) ?? [];
1927
+ const view = pageLayout(layout, pageKey);
1928
+ return view === void 0 ? [] : visibleWidgets(onPage, view);
1929
+ }
1930
+ function pageRoom(definitions, layout, pageKey, options = {}) {
1931
+ const page = pageList(layout).find(
1932
+ (p) => p.key === pageKey
1933
+ );
1934
+ if (page === void 0) return void 0;
1935
+ const maxRows = rowsAllowed(page, options);
1936
+ const rowsUsed = placeRows(
1937
+ pageWidgets(definitions, layout, pageKey)
1938
+ ).length;
1939
+ return { maxRows, rowsUsed, rowsFree: Math.max(0, maxRows - rowsUsed) };
1940
+ }
1941
+ function describePage(page) {
1942
+ return `Page "${page.title}"`;
1943
+ }
1944
+ function titleOf(item) {
1945
+ return item.title ?? item.key;
1946
+ }
1947
+ function checkTitle(layout, title, exceptKey) {
1948
+ const trimmed = title.trim();
1949
+ if (trimmed === "") return err("A page title must not be empty.");
1950
+ if (trimmed.length > MAX_PAGE_TITLE_LENGTH) {
1951
+ return err(
1952
+ `Page title "${trimmed}" is ${trimmed.length} characters; the most is ${MAX_PAGE_TITLE_LENGTH}.`
1953
+ );
1954
+ }
1955
+ const wanted = normalizedTitle(trimmed);
1956
+ const clash = pageList(layout).find(
1957
+ (p) => p.key !== exceptKey && normalizedTitle(p.title) === wanted
1958
+ );
1959
+ if (clash !== void 0) {
1960
+ return err(`Another page is already called "${clash.title}".`);
1961
+ }
1962
+ return ok(trimmed);
1963
+ }
1964
+ function nextPageKey(layout) {
1965
+ const keys = new Set(pageList(layout).map((p) => p.key));
1966
+ let n = 1;
1967
+ while (keys.has(`page-${n}`)) n += 1;
1968
+ return `page-${n}`;
1969
+ }
1970
+ function nextPageTitle(layout) {
1971
+ const used = new Set(
1972
+ pageList(layout).map((p) => normalizedTitle(p.title))
1973
+ );
1974
+ let n = pageList(layout).length + 1;
1975
+ while (used.has(normalizedTitle(`Page ${n}`))) n += 1;
1976
+ return `Page ${n}`;
1977
+ }
1978
+ function addPage(layout, options = {}) {
1979
+ if (pageList(layout).length >= MAX_PARSED_PAGES) {
1980
+ return err(
1981
+ `The layout already has ${MAX_PARSED_PAGES} pages, the most it can keep.`
1982
+ );
1983
+ }
1984
+ const key = options.key ?? nextPageKey(layout);
1985
+ if (pageList(layout).some((p) => p.key === key)) {
1986
+ return err(`A page with the key "${key}" already exists.`);
1987
+ }
1988
+ const title = checkTitle(
1989
+ layout,
1990
+ options.title ?? nextPageTitle(layout)
1991
+ );
1992
+ if (!title.ok) return title;
1993
+ const base = withExplicitPages(layout);
1994
+ const page = {
1995
+ key,
1996
+ title: title.value,
1997
+ order: [],
1998
+ hidden: [],
1999
+ minimized: []
2000
+ };
2001
+ return ok({ ...base, pages: [...base.pages ?? [], page] });
2002
+ }
2003
+ function renamePage(layout, pageKey, title) {
2004
+ const page = pageList(layout).find(
2005
+ (p) => p.key === pageKey
2006
+ );
2007
+ if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
2008
+ const checked = checkTitle(layout, title, pageKey);
2009
+ if (!checked.ok) return checked;
2010
+ if (checked.value === page.title) return ok(layout);
2011
+ const base = withExplicitPages(layout);
2012
+ return ok({
2013
+ ...base,
2014
+ pages: (base.pages ?? []).map(
2015
+ (p) => p.key === pageKey ? { ...p, title: checked.value } : p
2016
+ )
2017
+ });
2018
+ }
2019
+ function movePage(layout, pageKey, offset) {
2020
+ const pages = layout.pages ?? [];
2021
+ const from = pages.findIndex((p) => p.key === pageKey);
2022
+ if (from === -1) return layout;
2023
+ const to = Math.max(
2024
+ 0,
2025
+ Math.min(pages.length - 1, from + Math.trunc(offset))
2026
+ );
2027
+ if (to === from) return layout;
2028
+ const next = [...pages];
2029
+ const [moved] = next.splice(from, 1);
2030
+ next.splice(to, 0, moved);
2031
+ return { ...layout, pages: next };
2032
+ }
2033
+ function overflowing(definitions, layout, options) {
2034
+ return pageList(layout).find(
2035
+ (page) => placeRows(pageWidgets(definitions, layout, page.key)).length > rowsAllowed(page, options)
2036
+ );
2037
+ }
2038
+ function removePage(definitions, layout, pageKey, options = {}) {
2039
+ const pages = pageList(layout);
2040
+ const index = pages.findIndex((p) => p.key === pageKey);
2041
+ const page = pages[index];
2042
+ if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
2043
+ if (pages.length === 1) {
2044
+ return err(
2045
+ `${describePage(page)} is the only page, so it can't be removed.`
2046
+ );
2047
+ }
2048
+ const target = pages[index > 0 ? index - 1 : 1];
2049
+ const merged = {
2050
+ ...layout,
2051
+ pages: pages.filter((p) => p.key !== pageKey).map(
2052
+ (p) => p.key === target.key ? {
2053
+ ...p,
2054
+ order: index > 0 ? [...p.order, ...page.order] : [...page.order, ...p.order],
2055
+ hidden: [...p.hidden, ...page.hidden],
2056
+ minimized: [...p.minimized, ...page.minimized]
2057
+ } : p
2058
+ )
2059
+ };
2060
+ const full = overflowing(
2061
+ definitions,
2062
+ merged,
2063
+ options
2064
+ );
2065
+ if (full !== void 0) {
2066
+ return err(
2067
+ `${describePage(page)} has widgets that don't fit on the other pages (${describePage(full)} would need more than ${rowsAllowed(full, options)} rows). Move or hide some first.`
2068
+ );
2069
+ }
2070
+ return ok(merged);
2071
+ }
2072
+ function without(keys, key) {
2073
+ return keys.includes(key) ? keys.filter((k) => k !== key) : keys;
2074
+ }
2075
+ function moveWidgetToPage(definitions, layout, widgetKey, targetKey, options = {}) {
2076
+ const widget = definitions.find((d) => d.key === widgetKey);
2077
+ if (widget === void 0) {
2078
+ return err(
2079
+ `Widget "${widgetKey}" isn't one of the widgets, so it can't be moved to a page.`
2080
+ );
2081
+ }
2082
+ const pages = pageList(layout);
2083
+ const target = pages.find((p) => p.key === targetKey);
2084
+ if (target === void 0) return err(`Page "${targetKey}" doesn't exist.`);
2085
+ const sourceKey = pageOf(definitions, layout, widgetKey);
2086
+ if (sourceKey === targetKey) return ok(layout);
2087
+ if (resolveWidgetLock(widget).move) {
2088
+ return err(
2089
+ `Widget "${titleOf(widget)}" is locked against moving, so it can't be moved to ${describePage(target)}.`
2090
+ );
2091
+ }
2092
+ const base = withExplicitPages(layout);
2093
+ const source = (base.pages ?? []).find(
2094
+ (p) => p.key === sourceKey
2095
+ );
2096
+ const hidden = source.hidden.includes(widgetKey);
2097
+ const minimized = source.minimized.includes(widgetKey);
2098
+ const targetOrder = pageWidgets(
2099
+ definitions,
2100
+ base,
2101
+ targetKey
2102
+ ).map((d) => d.key);
2103
+ const moved = {
2104
+ ...base,
2105
+ pages: (base.pages ?? []).map((p) => {
2106
+ if (p.key === sourceKey) {
2107
+ return {
2108
+ ...p,
2109
+ order: without(p.order, widgetKey),
2110
+ hidden: without(p.hidden, widgetKey),
2111
+ minimized: without(p.minimized, widgetKey)
2112
+ };
2113
+ }
2114
+ if (p.key === targetKey) {
2115
+ return {
2116
+ ...p,
2117
+ order: hidden ? p.order : [...targetOrder, widgetKey],
2118
+ hidden: hidden ? [...p.hidden, widgetKey] : p.hidden,
2119
+ minimized: minimized ? [...p.minimized, widgetKey] : p.minimized
2120
+ };
2121
+ }
2122
+ return p;
2123
+ })
2124
+ };
2125
+ if (!hidden) {
2126
+ const allowed = rowsAllowed(target, options);
2127
+ const needed = placeRows(
2128
+ pageWidgets(definitions, moved, targetKey)
2129
+ ).length;
2130
+ if (needed > allowed) {
2131
+ return err(
2132
+ `${describePage(target)} has no room for "${titleOf(widget)}": it would need row ${needed}, and the page allows ${allowed}.`
2133
+ );
2134
+ }
2135
+ }
2136
+ return ok(moved);
1741
2137
  }
1742
2138
  function nextSort(current, column) {
1743
2139
  if (current?.column !== column) return { column, direction: "asc" };
@@ -2018,6 +2414,25 @@ function useDetailData(definition, load, enabled, fallback) {
2018
2414
  }, [enabled, load, fallback, definition, attempt]);
2019
2415
  return { state, reload: () => setAttempt((count) => count + 1) };
2020
2416
  }
2417
+ var NEW_PAGE_VALUE = "__new-page__";
2418
+ function PagePanel({
2419
+ paged,
2420
+ id,
2421
+ labelledBy,
2422
+ children
2423
+ }) {
2424
+ if (!paged) return /* @__PURE__ */ jsx(Fragment, { children });
2425
+ return /* @__PURE__ */ jsx(
2426
+ "div",
2427
+ {
2428
+ role: "tabpanel",
2429
+ id,
2430
+ "aria-labelledby": labelledBy,
2431
+ className: "dwt-page-panel",
2432
+ children
2433
+ }
2434
+ );
2435
+ }
2021
2436
  var NARROW_VIEWPORT_QUERY = "(max-width: 640px)";
2022
2437
  var WIDE_GRID_PX = 900;
2023
2438
  function useFillSpans(definitions, gridRef, twelve) {
@@ -2137,6 +2552,232 @@ function DetailHost({
2137
2552
  }
2138
2553
  ) });
2139
2554
  }
2555
+ function InlineConfirm({
2556
+ message,
2557
+ onAnswer
2558
+ }) {
2559
+ const slot = useSlotClassName();
2560
+ const { labels } = useWidgetSettings();
2561
+ const noRef = useRef(null);
2562
+ useEffect(() => {
2563
+ noRef.current?.focus();
2564
+ }, []);
2565
+ return /* @__PURE__ */ jsxs(
2566
+ "span",
2567
+ {
2568
+ role: "group",
2569
+ "aria-label": message,
2570
+ className: "dwt-confirm",
2571
+ onKeyDown: (event) => {
2572
+ if (event.key === "Escape") {
2573
+ event.stopPropagation();
2574
+ onAnswer(false);
2575
+ }
2576
+ },
2577
+ children: [
2578
+ /* @__PURE__ */ jsx("span", { role: "alert", className: "dwt-confirm-message", children: message }),
2579
+ /* @__PURE__ */ jsx(
2580
+ "button",
2581
+ {
2582
+ type: "button",
2583
+ className: slot("button", "dwt-button", "dwt-icon-button"),
2584
+ "aria-label": labels.confirmYes,
2585
+ title: labels.confirmYes,
2586
+ onClick: () => onAnswer(true),
2587
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2713" })
2588
+ }
2589
+ ),
2590
+ /* @__PURE__ */ jsx(
2591
+ "button",
2592
+ {
2593
+ ref: noRef,
2594
+ type: "button",
2595
+ className: slot("button", "dwt-button", "dwt-icon-button"),
2596
+ "aria-label": labels.confirmNo,
2597
+ title: labels.confirmNo,
2598
+ onClick: () => onAnswer(false),
2599
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2715" })
2600
+ }
2601
+ )
2602
+ ]
2603
+ }
2604
+ );
2605
+ }
2606
+ function PageTabs({
2607
+ pages,
2608
+ activeKey,
2609
+ idPrefix,
2610
+ panelId,
2611
+ announcement,
2612
+ onSelect
2613
+ }) {
2614
+ const slot = useSlotClassName();
2615
+ const { labels } = useWidgetSettings();
2616
+ const tabs = useRef(/* @__PURE__ */ new Map());
2617
+ const activeIndex = pages.findIndex((p) => p.key === activeKey);
2618
+ function onKeyDown(event, index) {
2619
+ const last = pages.length - 1;
2620
+ let next;
2621
+ switch (event.key) {
2622
+ case "ArrowRight":
2623
+ case "ArrowDown":
2624
+ next = index === last ? 0 : index + 1;
2625
+ break;
2626
+ case "ArrowLeft":
2627
+ case "ArrowUp":
2628
+ next = index === 0 ? last : index - 1;
2629
+ break;
2630
+ case "Home":
2631
+ next = 0;
2632
+ break;
2633
+ case "End":
2634
+ next = last;
2635
+ break;
2636
+ default:
2637
+ return;
2638
+ }
2639
+ event.preventDefault();
2640
+ const target = pages[next];
2641
+ if (target === void 0) return;
2642
+ onSelect(target.key);
2643
+ tabs.current.get(target.key)?.focus();
2644
+ }
2645
+ return /* @__PURE__ */ jsxs("div", { className: slot("pageBar", "dwt-pages"), children: [
2646
+ /* @__PURE__ */ jsx(
2647
+ "button",
2648
+ {
2649
+ type: "button",
2650
+ className: slot(
2651
+ "button",
2652
+ "dwt-button",
2653
+ "dwt-icon-button",
2654
+ "dwt-page-nav"
2655
+ ),
2656
+ "aria-label": labels.previousPageButton,
2657
+ disabled: activeIndex <= 0,
2658
+ onClick: () => onSelect(pages[activeIndex - 1].key),
2659
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2039" })
2660
+ }
2661
+ ),
2662
+ /* @__PURE__ */ jsx("div", { role: "tablist", "aria-label": labels.pageBar, className: "dwt-page-tabs", children: pages.map((page, index) => {
2663
+ const selected = page.key === activeKey;
2664
+ return /* @__PURE__ */ jsxs(
2665
+ "button",
2666
+ {
2667
+ ref: (element) => {
2668
+ if (element === null) tabs.current.delete(page.key);
2669
+ else tabs.current.set(page.key, element);
2670
+ },
2671
+ type: "button",
2672
+ role: "tab",
2673
+ id: `${idPrefix}-tab-${index}`,
2674
+ "aria-selected": selected,
2675
+ "aria-controls": panelId,
2676
+ "aria-label": labels.pageTabName(
2677
+ index + 1,
2678
+ pages.length,
2679
+ page.title
2680
+ ),
2681
+ title: page.title,
2682
+ tabIndex: selected ? 0 : -1,
2683
+ className: slot("button", "dwt-page-tab"),
2684
+ onClick: () => onSelect(page.key),
2685
+ onKeyDown: (event) => onKeyDown(event, index),
2686
+ children: [
2687
+ /* @__PURE__ */ jsx("span", { className: "dwt-page-tab-title", children: page.title }),
2688
+ /* @__PURE__ */ jsx("span", { className: "dwt-page-dot", "aria-hidden": "true" })
2689
+ ]
2690
+ },
2691
+ page.key
2692
+ );
2693
+ }) }),
2694
+ /* @__PURE__ */ jsx(
2695
+ "button",
2696
+ {
2697
+ type: "button",
2698
+ className: slot(
2699
+ "button",
2700
+ "dwt-button",
2701
+ "dwt-icon-button",
2702
+ "dwt-page-nav"
2703
+ ),
2704
+ "aria-label": labels.nextPageButton,
2705
+ disabled: activeIndex === pages.length - 1,
2706
+ onClick: () => onSelect(pages[activeIndex + 1].key),
2707
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u203A" })
2708
+ }
2709
+ ),
2710
+ /* @__PURE__ */ jsx("span", { className: "dwt-visually-hidden", "aria-live": "polite", children: announcement })
2711
+ ] });
2712
+ }
2713
+ function PageRenameForm({
2714
+ initial,
2715
+ onSubmit,
2716
+ onCancel
2717
+ }) {
2718
+ const slot = useSlotClassName();
2719
+ const { labels } = useWidgetSettings();
2720
+ const [title, setTitle] = useState(initial);
2721
+ const [error, setError] = useState(void 0);
2722
+ const inputRef = useRef(null);
2723
+ useEffect(() => {
2724
+ inputRef.current?.focus();
2725
+ inputRef.current?.select();
2726
+ }, []);
2727
+ return /* @__PURE__ */ jsxs(
2728
+ "form",
2729
+ {
2730
+ className: "dwt-page-rename",
2731
+ onSubmit: (event) => {
2732
+ event.preventDefault();
2733
+ setError(onSubmit(title));
2734
+ },
2735
+ onKeyDown: (event) => {
2736
+ if (event.key === "Escape") {
2737
+ event.stopPropagation();
2738
+ onCancel();
2739
+ }
2740
+ },
2741
+ children: [
2742
+ /* @__PURE__ */ jsx(
2743
+ "input",
2744
+ {
2745
+ ref: inputRef,
2746
+ type: "text",
2747
+ className: "dwt-page-title-input",
2748
+ "aria-label": labels.pageTitle,
2749
+ "aria-invalid": error !== void 0,
2750
+ maxLength: MAX_PAGE_TITLE_LENGTH,
2751
+ value: title,
2752
+ onChange: (event) => setTitle(event.target.value)
2753
+ }
2754
+ ),
2755
+ /* @__PURE__ */ jsx(
2756
+ "button",
2757
+ {
2758
+ type: "submit",
2759
+ className: slot("button", "dwt-button", "dwt-icon-button"),
2760
+ "aria-label": labels.savePageTitle,
2761
+ title: labels.savePageTitle,
2762
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2713" })
2763
+ }
2764
+ ),
2765
+ /* @__PURE__ */ jsx(
2766
+ "button",
2767
+ {
2768
+ type: "button",
2769
+ className: slot("button", "dwt-button", "dwt-icon-button"),
2770
+ "aria-label": labels.cancelPageTitle,
2771
+ title: labels.cancelPageTitle,
2772
+ onClick: onCancel,
2773
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2715" })
2774
+ }
2775
+ ),
2776
+ error !== void 0 && /* @__PURE__ */ jsx("span", { role: "alert", className: "dwt-page-error", children: error })
2777
+ ]
2778
+ }
2779
+ );
2780
+ }
2140
2781
  function withoutLock(definition) {
2141
2782
  if (definition.locked === void 0) return definition;
2142
2783
  const copy = { ...definition };
@@ -2148,6 +2789,17 @@ function DashboardInner({
2148
2789
  layout: savedLayout = EMPTY_LAYOUT,
2149
2790
  onLayoutChange,
2150
2791
  overrideLocks = false,
2792
+ editMode = "always",
2793
+ editing: editingProp,
2794
+ defaultEditing = false,
2795
+ onEditingChange,
2796
+ defaultLayout,
2797
+ toolbar = true,
2798
+ activePage: activePageProp,
2799
+ defaultActivePage,
2800
+ onActivePageChange,
2801
+ maxRows,
2802
+ maxPages,
2151
2803
  onRetry,
2152
2804
  loadDetail,
2153
2805
  onOpenDetail,
@@ -2163,11 +2815,47 @@ function DashboardInner({
2163
2815
  const byKey = new Map(
2164
2816
  widgets.map((w) => [w.definition.key, w])
2165
2817
  );
2166
- const visible = visibleWidgets(definitions, layout);
2167
- const hidden = hiddenWidgets(definitions, layout);
2818
+ const pages = pageList(layout);
2819
+ const paged = pages.length >= 2;
2820
+ const [activeState, setActiveState] = useState(
2821
+ defaultActivePage
2822
+ );
2823
+ const requested = activePageProp ?? activeState;
2824
+ const activePage = pages.find((page) => page.key === requested) ?? pages[0];
2825
+ const activeKey = activePage.key;
2826
+ const pageDefs = assignPages(definitions, layout).get(activeKey) ?? [];
2827
+ const view = pageLayout(layout, activeKey) ?? layout;
2828
+ const roomOptions = maxRows === void 0 ? {} : { maxRows };
2829
+ const visible = visibleWidgets(pageDefs, view);
2830
+ const hidden = hiddenWidgets(pageDefs, view);
2168
2831
  const editable = onLayoutChange !== void 0;
2169
- const minimized = editable ? visible.filter((definition) => isMinimized(layout, definition.key)) : [];
2170
- const shown = editable ? visible.filter((definition) => !isMinimized(layout, definition.key)) : visible;
2832
+ const toggleMode = editMode === "toggle";
2833
+ const [editingState, setEditingState] = useState(defaultEditing);
2834
+ const editing = editingProp ?? editingState;
2835
+ const controlsShown = editable && (!toggleMode || editing);
2836
+ const [snapshot, setSnapshot] = useState(
2837
+ void 0
2838
+ );
2839
+ if (toggleMode && editable && editing) {
2840
+ if (snapshot === void 0) setSnapshot(savedLayout);
2841
+ } else if (snapshot !== void 0) {
2842
+ setSnapshot(void 0);
2843
+ }
2844
+ const [confirming, setConfirming] = useState(
2845
+ void 0
2846
+ );
2847
+ const pending = editing ? confirming : void 0;
2848
+ const [announcement, setAnnouncement] = useState("");
2849
+ const [pageAnnouncement, setPageAnnouncement] = useState("");
2850
+ const [pageError, setPageError] = useState(void 0);
2851
+ const [pageEditor, setPageEditor] = useState(void 0);
2852
+ const editorMode = pageEditor?.key === activeKey ? pageEditor.mode : void 0;
2853
+ const instanceId = useId();
2854
+ const panelId = `${instanceId}-panel`;
2855
+ const toggleRef = useRef(null);
2856
+ const resetRef = useRef(null);
2857
+ const minimized = editable ? visible.filter((definition) => isMinimized(view, definition.key)) : [];
2858
+ const shown = editable ? visible.filter((definition) => !isMinimized(view, definition.key)) : visible;
2171
2859
  const [detailKey, setDetailKey] = useState(void 0);
2172
2860
  const gridRef = useRef(null);
2173
2861
  const twelve = usesWidthColumns(shown);
@@ -2179,142 +2867,507 @@ function DashboardInner({
2179
2867
  function change(next) {
2180
2868
  if (next !== layout) onLayoutChange?.(next);
2181
2869
  }
2182
- return /* @__PURE__ */ jsxs("div", { className: slot("dashboard", "dwt-dashboard", className), children: [
2183
- editable && hidden.length > 0 && /* @__PURE__ */ jsxs("div", { className: slot("hiddenBar", "dwt-hidden-bar"), children: [
2184
- /* @__PURE__ */ jsx("span", { className: "dwt-hidden-label", children: labels.hiddenWidgets }),
2185
- hidden.map((definition) => /* @__PURE__ */ jsxs(
2186
- "button",
2187
- {
2188
- type: "button",
2189
- className: slot("button", "dwt-button", "dwt-show-button"),
2190
- "aria-label": labels.show(definition.title),
2191
- onClick: () => change(showWidget(layout, definition.key)),
2192
- children: [
2193
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "+ " }),
2194
- definition.title
2195
- ]
2196
- },
2197
- definition.key
2198
- ))
2199
- ] }),
2200
- minimized.length > 0 && /* @__PURE__ */ jsxs(
2201
- "div",
2202
- {
2203
- className: slot("hiddenBar", "dwt-hidden-bar", "dwt-minimized-bar"),
2204
- children: [
2205
- /* @__PURE__ */ jsx("span", { className: "dwt-hidden-label", children: labels.minimizedWidgets }),
2206
- minimized.map((definition) => /* @__PURE__ */ jsxs(
2207
- "button",
2208
- {
2209
- type: "button",
2210
- className: slot("button", "dwt-button", "dwt-show-button"),
2211
- "aria-label": labels.expand(definition.title),
2212
- onClick: () => change(toggleMinimized(layout, definition.key)),
2213
- children: [
2214
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u25B8 " }),
2215
- definition.title
2216
- ]
2217
- },
2218
- definition.key
2219
- ))
2220
- ]
2870
+ function changePage(next) {
2871
+ setPageError(void 0);
2872
+ change(withPageLayout(layout, activeKey, next));
2873
+ }
2874
+ function selectPage(pageKey) {
2875
+ const index = pages.findIndex((page) => page.key === pageKey);
2876
+ if (index === -1) return;
2877
+ if (activePageProp === void 0) setActiveState(pageKey);
2878
+ onActivePageChange?.(pageKey);
2879
+ setPageError(void 0);
2880
+ setPageEditor(void 0);
2881
+ setPageAnnouncement(
2882
+ labels.pageTabName(
2883
+ index + 1,
2884
+ pages.length,
2885
+ pages[index].title
2886
+ )
2887
+ );
2888
+ }
2889
+ function addPageAction() {
2890
+ const added = addPage(layout);
2891
+ if (!added.ok) {
2892
+ setPageError(added.error);
2893
+ return;
2894
+ }
2895
+ change(added.value);
2896
+ const created = added.value.pages?.at(-1);
2897
+ if (created !== void 0) {
2898
+ if (activePageProp === void 0) setActiveState(created.key);
2899
+ onActivePageChange?.(created.key);
2900
+ setPageError(void 0);
2901
+ setPageEditor({ mode: "rename", key: created.key });
2902
+ }
2903
+ }
2904
+ function renamePageAction(title) {
2905
+ const renamed = renamePage(layout, activeKey, title);
2906
+ if (!renamed.ok) return renamed.error;
2907
+ change(renamed.value);
2908
+ setPageEditor(void 0);
2909
+ return void 0;
2910
+ }
2911
+ function deletePageAction() {
2912
+ const index = pages.findIndex((page) => page.key === activeKey);
2913
+ const removed = removePage(definitions, layout, activeKey, roomOptions);
2914
+ setPageEditor(void 0);
2915
+ if (!removed.ok) {
2916
+ setPageError(removed.error);
2917
+ return;
2918
+ }
2919
+ const neighbor = pages[index > 0 ? index - 1 : 1];
2920
+ change(removed.value);
2921
+ if (neighbor !== void 0) selectPage(neighbor.key);
2922
+ }
2923
+ function moveWidgetAction(widgetKey, targetKey) {
2924
+ let target = targetKey;
2925
+ let base = layout;
2926
+ if (targetKey === NEW_PAGE_VALUE) {
2927
+ const added = addPage(layout);
2928
+ if (!added.ok) {
2929
+ setPageError(added.error);
2930
+ return;
2221
2931
  }
2222
- ),
2223
- visible.length === 0 ? /* @__PURE__ */ jsx("p", { className: slot("empty", "dwt-empty", "dwt-dashboard-empty"), children: definitions.length === 0 ? labels.noWidgets : labels.allHidden }) : shown.length === 0 ? null : /* @__PURE__ */ jsx(
2224
- "div",
2225
- {
2226
- ref: gridRef,
2227
- className: slot("grid", "dwt-grid", twelve && "dwt-grid--twelve"),
2228
- children: shown.map((definition, index) => {
2229
- const widget = byKey.get(
2230
- definition.key
2231
- );
2232
- if (widget === void 0) return null;
2233
- const key = definition.key;
2234
- const title = definition.title;
2235
- const lock = resolveWidgetLock(definition);
2236
- const canMoveEarlier = !lock.move && moveWidgetBy(definitions, layout, key, -1) !== layout;
2237
- const canMoveLater = !lock.move && moveWidgetBy(definitions, layout, key, 1) !== layout;
2238
- const actions = editable ? /* @__PURE__ */ jsxs(Fragment, { children: [
2239
- !lock.move && /* @__PURE__ */ jsxs(Fragment, { children: [
2932
+ base = added.value;
2933
+ target = (added.value.pages?.at(-1)).key;
2934
+ }
2935
+ const moved = moveWidgetToPage(
2936
+ definitions,
2937
+ base,
2938
+ widgetKey,
2939
+ target,
2940
+ roomOptions
2941
+ );
2942
+ if (!moved.ok) {
2943
+ setPageError(moved.error);
2944
+ return;
2945
+ }
2946
+ setPageError(void 0);
2947
+ change(moved.value);
2948
+ const destination = moved.value.pages?.find(
2949
+ (page) => page.key === target
2950
+ );
2951
+ setPageAnnouncement(
2952
+ labels.movedToPage(
2953
+ byKey.get(widgetKey)?.definition.title ?? widgetKey,
2954
+ destination?.title ?? target
2955
+ )
2956
+ );
2957
+ }
2958
+ function showAction(widgetKey) {
2959
+ const next = withPageLayout(
2960
+ layout,
2961
+ activeKey,
2962
+ showWidget(view, widgetKey)
2963
+ );
2964
+ if (hasPages(next)) {
2965
+ const room = pageRoom(definitions, next, activeKey, roomOptions);
2966
+ if (room !== void 0 && room.rowsUsed > room.maxRows) {
2967
+ setPageError(labels.pageFull(activePage.title, room.maxRows));
2968
+ return;
2969
+ }
2970
+ }
2971
+ setPageError(void 0);
2972
+ change(next);
2973
+ }
2974
+ function setEditing(next) {
2975
+ if (editingProp === void 0) setEditingState(next);
2976
+ onEditingChange?.(next);
2977
+ setAnnouncement(next ? labels.editingOn : labels.editingOff);
2978
+ setConfirming(void 0);
2979
+ }
2980
+ function answerConfirmation(yes) {
2981
+ const which = pending;
2982
+ setConfirming(void 0);
2983
+ if (yes && which === "reset") {
2984
+ change(enforceLocks(definitions, defaultLayout ?? EMPTY_LAYOUT));
2985
+ } else if (yes && which === "revert" && snapshot !== void 0) {
2986
+ change(snapshot);
2987
+ }
2988
+ (which === "revert" ? toggleRef : resetRef).current?.focus();
2989
+ }
2990
+ return /* @__PURE__ */ jsxs(
2991
+ "div",
2992
+ {
2993
+ className: slot(
2994
+ "dashboard",
2995
+ "dwt-dashboard",
2996
+ toggleMode && controlsShown && "dwt-dashboard--editing",
2997
+ className
2998
+ ),
2999
+ children: [
3000
+ editable && toggleMode && toolbar && /* @__PURE__ */ jsxs(
3001
+ "div",
3002
+ {
3003
+ role: "group",
3004
+ "aria-label": labels.dashboardControls,
3005
+ className: slot("toolbar", "dwt-toolbar"),
3006
+ children: [
2240
3007
  /* @__PURE__ */ jsx(
2241
3008
  "button",
2242
3009
  {
3010
+ ref: toggleRef,
2243
3011
  type: "button",
2244
- className: slot(
2245
- "button",
2246
- "dwt-button",
2247
- "dwt-icon-button"
2248
- ),
2249
- "aria-label": labels.moveEarlier(title),
2250
- title: labels.moveEarlier(title),
2251
- disabled: index === 0 || !canMoveEarlier,
2252
- onClick: () => change(moveWidgetBy(definitions, layout, key, -1)),
2253
- children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2191" })
3012
+ "aria-pressed": editing,
3013
+ className: slot("button", "dwt-button", "dwt-toolbar-toggle"),
3014
+ onClick: () => setEditing(!editing),
3015
+ children: editing ? labels.done : labels.customize
2254
3016
  }
2255
3017
  ),
3018
+ editing && /* @__PURE__ */ jsxs(Fragment, { children: [
3019
+ /* @__PURE__ */ jsx(
3020
+ "button",
3021
+ {
3022
+ ref: resetRef,
3023
+ type: "button",
3024
+ className: slot("button", "dwt-button"),
3025
+ onClick: () => setConfirming("reset"),
3026
+ children: labels.reset
3027
+ }
3028
+ ),
3029
+ /* @__PURE__ */ jsx(
3030
+ "button",
3031
+ {
3032
+ type: "button",
3033
+ className: slot("button", "dwt-button"),
3034
+ disabled: snapshot === void 0 || savedLayout === snapshot,
3035
+ onClick: () => setConfirming("revert"),
3036
+ children: labels.revertChanges
3037
+ }
3038
+ )
3039
+ ] }),
3040
+ pending !== void 0 && /* @__PURE__ */ jsx(
3041
+ InlineConfirm,
3042
+ {
3043
+ message: pending === "reset" ? labels.confirmReset : labels.confirmRevert,
3044
+ onAnswer: answerConfirmation
3045
+ }
3046
+ ),
3047
+ /* @__PURE__ */ jsx("span", { className: "dwt-visually-hidden", "aria-live": "polite", children: announcement })
3048
+ ]
3049
+ }
3050
+ ),
3051
+ paged && /* @__PURE__ */ jsx(
3052
+ PageTabs,
3053
+ {
3054
+ pages,
3055
+ activeKey,
3056
+ idPrefix: instanceId,
3057
+ panelId,
3058
+ announcement: pageAnnouncement,
3059
+ onSelect: selectPage
3060
+ }
3061
+ ),
3062
+ controlsShown && /* @__PURE__ */ jsxs(
3063
+ "div",
3064
+ {
3065
+ role: "group",
3066
+ "aria-label": labels.managePages,
3067
+ className: "dwt-page-manage",
3068
+ children: [
2256
3069
  /* @__PURE__ */ jsx(
2257
3070
  "button",
2258
3071
  {
2259
3072
  type: "button",
2260
- className: slot(
2261
- "button",
2262
- "dwt-button",
2263
- "dwt-icon-button"
2264
- ),
2265
- "aria-label": labels.moveLater(title),
2266
- title: labels.moveLater(title),
2267
- disabled: index === shown.length - 1 || !canMoveLater,
2268
- onClick: () => change(moveWidgetBy(definitions, layout, key, 1)),
2269
- children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2193" })
3073
+ className: slot("button", "dwt-button"),
3074
+ disabled: maxPages !== void 0 && pages.length >= maxPages,
3075
+ onClick: addPageAction,
3076
+ children: labels.addPage
3077
+ }
3078
+ ),
3079
+ paged && editorMode === "rename" && /* @__PURE__ */ jsx(
3080
+ PageRenameForm,
3081
+ {
3082
+ initial: activePage.title,
3083
+ onSubmit: renamePageAction,
3084
+ onCancel: () => setPageEditor(void 0)
3085
+ },
3086
+ activeKey
3087
+ ),
3088
+ paged && editorMode !== "rename" && /* @__PURE__ */ jsxs(Fragment, { children: [
3089
+ /* @__PURE__ */ jsx(
3090
+ "button",
3091
+ {
3092
+ type: "button",
3093
+ className: slot("button", "dwt-button", "dwt-icon-button"),
3094
+ "aria-label": labels.renamePage(activePage.title),
3095
+ title: labels.renamePage(activePage.title),
3096
+ onClick: () => setPageEditor({ mode: "rename", key: activeKey }),
3097
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u270E" })
3098
+ }
3099
+ ),
3100
+ /* @__PURE__ */ jsx(
3101
+ "button",
3102
+ {
3103
+ type: "button",
3104
+ className: slot("button", "dwt-button", "dwt-icon-button"),
3105
+ "aria-label": labels.movePageLeft(activePage.title),
3106
+ title: labels.movePageLeft(activePage.title),
3107
+ disabled: pages[0]?.key === activeKey,
3108
+ onClick: () => change(movePage(layout, activeKey, -1)),
3109
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2190" })
3110
+ }
3111
+ ),
3112
+ /* @__PURE__ */ jsx(
3113
+ "button",
3114
+ {
3115
+ type: "button",
3116
+ className: slot("button", "dwt-button", "dwt-icon-button"),
3117
+ "aria-label": labels.movePageRight(activePage.title),
3118
+ title: labels.movePageRight(activePage.title),
3119
+ disabled: pages.at(-1)?.key === activeKey,
3120
+ onClick: () => change(movePage(layout, activeKey, 1)),
3121
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2192" })
3122
+ }
3123
+ ),
3124
+ /* @__PURE__ */ jsx(
3125
+ "button",
3126
+ {
3127
+ type: "button",
3128
+ className: slot("button", "dwt-button", "dwt-icon-button"),
3129
+ "aria-label": labels.deletePage(activePage.title),
3130
+ title: labels.deletePage(activePage.title),
3131
+ onClick: () => setPageEditor({ mode: "delete", key: activeKey }),
3132
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xD7" })
3133
+ }
3134
+ )
3135
+ ] }),
3136
+ paged && editorMode === "delete" && /* @__PURE__ */ jsx(
3137
+ InlineConfirm,
3138
+ {
3139
+ message: labels.confirmDeletePage(activePage.title),
3140
+ onAnswer: (yes) => yes ? deletePageAction() : setPageEditor(void 0)
3141
+ }
3142
+ ),
3143
+ pageError !== void 0 && /* @__PURE__ */ jsx("span", { role: "alert", className: "dwt-page-error", children: pageError })
3144
+ ]
3145
+ }
3146
+ ),
3147
+ /* @__PURE__ */ jsxs(
3148
+ PagePanel,
3149
+ {
3150
+ paged,
3151
+ id: panelId,
3152
+ labelledBy: `${instanceId}-tab-${pages.findIndex((p) => p.key === activeKey)}`,
3153
+ children: [
3154
+ controlsShown && hidden.length > 0 && /* @__PURE__ */ jsxs("div", { className: slot("hiddenBar", "dwt-hidden-bar"), children: [
3155
+ /* @__PURE__ */ jsx("span", { className: "dwt-hidden-label", children: labels.hiddenWidgets }),
3156
+ hidden.map((definition) => /* @__PURE__ */ jsxs(
3157
+ "button",
3158
+ {
3159
+ type: "button",
3160
+ className: slot("button", "dwt-button", "dwt-show-button"),
3161
+ "aria-label": labels.show(definition.title),
3162
+ onClick: () => showAction(definition.key),
3163
+ children: [
3164
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "+ " }),
3165
+ definition.title
3166
+ ]
3167
+ },
3168
+ definition.key
3169
+ ))
3170
+ ] }),
3171
+ minimized.length > 0 && /* @__PURE__ */ jsxs(
3172
+ "div",
3173
+ {
3174
+ className: slot("hiddenBar", "dwt-hidden-bar", "dwt-minimized-bar"),
3175
+ children: [
3176
+ /* @__PURE__ */ jsx("span", { className: "dwt-hidden-label", children: labels.minimizedWidgets }),
3177
+ minimized.map((definition) => /* @__PURE__ */ jsxs(
3178
+ "button",
3179
+ {
3180
+ type: "button",
3181
+ className: slot("button", "dwt-button", "dwt-show-button"),
3182
+ "aria-label": labels.expand(definition.title),
3183
+ onClick: () => changePage(toggleMinimized(view, definition.key)),
3184
+ children: [
3185
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u25B8 " }),
3186
+ definition.title
3187
+ ]
3188
+ },
3189
+ definition.key
3190
+ ))
3191
+ ]
3192
+ }
3193
+ ),
3194
+ visible.length === 0 ? /* @__PURE__ */ jsx("p", { className: slot("empty", "dwt-empty", "dwt-dashboard-empty"), children: paged && pageDefs.length === 0 ? labels.emptyPage : definitions.length === 0 ? labels.noWidgets : labels.allHidden }) : shown.length === 0 ? null : /* @__PURE__ */ jsx(
3195
+ "div",
3196
+ {
3197
+ ref: gridRef,
3198
+ className: slot("grid", "dwt-grid", twelve && "dwt-grid--twelve"),
3199
+ children: shown.map((definition, index) => {
3200
+ const widget = byKey.get(
3201
+ definition.key
3202
+ );
3203
+ if (widget === void 0) return null;
3204
+ const key = definition.key;
3205
+ const title = definition.title;
3206
+ const lock = resolveWidgetLock(definition);
3207
+ const ownLock = resolveWidgetLock(
3208
+ widget.definition
3209
+ );
3210
+ const isLocked = ownLock.move || ownLock.hide || ownLock.minimize;
3211
+ const lockLabel = overrideLocks ? labels.lockedForViewers(title) : labels.locked(title);
3212
+ const canMoveEarlier = !lock.move && moveWidgetBy(pageDefs, view, key, -1) !== view;
3213
+ const canMoveLater = !lock.move && moveWidgetBy(pageDefs, view, key, 1) !== view;
3214
+ const actions = controlsShown ? /* @__PURE__ */ jsxs(Fragment, { children: [
3215
+ isLocked && /* @__PURE__ */ jsx(
3216
+ "span",
3217
+ {
3218
+ role: "img",
3219
+ className: "dwt-lock",
3220
+ "aria-label": lockLabel,
3221
+ title: lockLabel,
3222
+ children: /* @__PURE__ */ jsxs(
3223
+ "svg",
3224
+ {
3225
+ viewBox: "0 0 16 16",
3226
+ width: "14",
3227
+ height: "14",
3228
+ "aria-hidden": "true",
3229
+ focusable: "false",
3230
+ children: [
3231
+ /* @__PURE__ */ jsx(
3232
+ "rect",
3233
+ {
3234
+ x: "3",
3235
+ y: "7",
3236
+ width: "10",
3237
+ height: "7",
3238
+ rx: "1.5",
3239
+ fill: "currentColor"
3240
+ }
3241
+ ),
3242
+ /* @__PURE__ */ jsx(
3243
+ "path",
3244
+ {
3245
+ d: "M5.5 7V5a2.5 2.5 0 0 1 5 0v2",
3246
+ fill: "none",
3247
+ stroke: "currentColor",
3248
+ strokeWidth: "1.4"
3249
+ }
3250
+ )
3251
+ ]
3252
+ }
3253
+ )
3254
+ }
3255
+ ),
3256
+ !lock.move && /* @__PURE__ */ jsxs(Fragment, { children: [
3257
+ /* @__PURE__ */ jsx(
3258
+ "button",
3259
+ {
3260
+ type: "button",
3261
+ className: slot(
3262
+ "button",
3263
+ "dwt-button",
3264
+ "dwt-icon-button"
3265
+ ),
3266
+ "aria-label": labels.moveEarlier(title),
3267
+ title: labels.moveEarlier(title),
3268
+ disabled: index === 0 || !canMoveEarlier,
3269
+ onClick: () => changePage(moveWidgetBy(pageDefs, view, key, -1)),
3270
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2191" })
3271
+ }
3272
+ ),
3273
+ /* @__PURE__ */ jsx(
3274
+ "button",
3275
+ {
3276
+ type: "button",
3277
+ className: slot(
3278
+ "button",
3279
+ "dwt-button",
3280
+ "dwt-icon-button"
3281
+ ),
3282
+ "aria-label": labels.moveLater(title),
3283
+ title: labels.moveLater(title),
3284
+ disabled: index === shown.length - 1 || !canMoveLater,
3285
+ onClick: () => changePage(moveWidgetBy(pageDefs, view, key, 1)),
3286
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2193" })
3287
+ }
3288
+ ),
3289
+ paged && /* @__PURE__ */ jsxs(
3290
+ "select",
3291
+ {
3292
+ className: "dwt-move-select",
3293
+ "aria-label": labels.moveToPage(title),
3294
+ value: "",
3295
+ onChange: (event) => {
3296
+ if (event.target.value !== "") {
3297
+ moveWidgetAction(key, event.target.value);
3298
+ }
3299
+ },
3300
+ children: [
3301
+ /* @__PURE__ */ jsx("option", { value: "", children: labels.moveToPagePlaceholder }),
3302
+ pages.filter((page) => page.key !== activeKey).map((page) => /* @__PURE__ */ jsx("option", { value: page.key, children: labels.moveToPageOption(
3303
+ page.title,
3304
+ pageRoom(
3305
+ definitions,
3306
+ layout,
3307
+ page.key,
3308
+ roomOptions
3309
+ )?.rowsFree ?? 0
3310
+ ) }, page.key)),
3311
+ (maxPages === void 0 || pages.length < maxPages) && /* @__PURE__ */ jsx("option", { value: NEW_PAGE_VALUE, children: labels.newPageOption })
3312
+ ]
3313
+ }
3314
+ )
3315
+ ] }),
3316
+ !lock.hide && /* @__PURE__ */ jsx(
3317
+ "button",
3318
+ {
3319
+ type: "button",
3320
+ className: slot(
3321
+ "button",
3322
+ "dwt-button",
3323
+ "dwt-icon-button"
3324
+ ),
3325
+ "aria-label": labels.hide(title),
3326
+ title: labels.hide(title),
3327
+ onClick: () => changePage(hideWidget(view, key)),
3328
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xD7" })
3329
+ }
3330
+ )
3331
+ ] }) : void 0;
3332
+ const viewable = resolveDetailOptions(definition) !== void 0 && (onOpenDetail !== void 0 || loadDetail !== void 0 || widget.status === "ok" && deriveDetailData(widget.data) !== void 0);
3333
+ return /* @__PURE__ */ jsx(
3334
+ ResolvedWidgetCard,
3335
+ {
3336
+ widget,
3337
+ ...viewable ? {
3338
+ onView: () => onOpenDetail === void 0 ? setDetailKey(key) : onOpenDetail(key)
3339
+ } : {},
3340
+ ...spanProps(spans, key),
3341
+ ...widthProps(twelve, definition),
3342
+ minimized: isMinimized(view, key),
3343
+ editing: toggleMode && controlsShown,
3344
+ ...actions === void 0 ? {} : { actions },
3345
+ ...editable && !lock.minimize ? {
3346
+ onToggleMinimized: () => changePage(toggleMinimized(view, key))
3347
+ } : {},
3348
+ ...headingLevel === void 0 ? {} : { headingLevel },
3349
+ ...onRetry === void 0 || widget.status !== "error" ? {} : { onRetry: () => onRetry(key) }
3350
+ },
3351
+ key
3352
+ );
3353
+ })
2270
3354
  }
2271
3355
  )
2272
- ] }),
2273
- !lock.hide && /* @__PURE__ */ jsx(
2274
- "button",
2275
- {
2276
- type: "button",
2277
- className: slot("button", "dwt-button", "dwt-icon-button"),
2278
- "aria-label": labels.hide(title),
2279
- title: labels.hide(title),
2280
- onClick: () => change(hideWidget(layout, key)),
2281
- children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xD7" })
2282
- }
2283
- )
2284
- ] }) : void 0;
2285
- const viewable = resolveDetailOptions(definition) !== void 0 && (onOpenDetail !== void 0 || loadDetail !== void 0 || widget.status === "ok" && deriveDetailData(widget.data) !== void 0);
2286
- return /* @__PURE__ */ jsx(
2287
- ResolvedWidgetCard,
2288
- {
2289
- widget,
2290
- ...viewable ? {
2291
- onView: () => onOpenDetail === void 0 ? setDetailKey(key) : onOpenDetail(key)
2292
- } : {},
2293
- ...spanProps(spans, key),
2294
- ...widthProps(twelve, definition),
2295
- minimized: isMinimized(layout, key),
2296
- ...actions === void 0 ? {} : { actions },
2297
- ...editable && !lock.minimize ? {
2298
- onToggleMinimized: () => change(toggleMinimized(layout, key))
2299
- } : {},
2300
- ...headingLevel === void 0 ? {} : { headingLevel },
2301
- ...onRetry === void 0 || widget.status !== "error" ? {} : { onRetry: () => onRetry(key) }
2302
- },
2303
- key
2304
- );
2305
- })
2306
- }
2307
- ),
2308
- detailKey !== void 0 && byKey.get(detailKey) !== void 0 && /* @__PURE__ */ jsx(
2309
- DetailHost,
2310
- {
2311
- widget: byKey.get(detailKey),
2312
- loadDetail,
2313
- onClose: () => setDetailKey(void 0)
2314
- },
2315
- detailKey
2316
- )
2317
- ] });
3356
+ ]
3357
+ }
3358
+ ),
3359
+ detailKey !== void 0 && byKey.get(detailKey) !== void 0 && /* @__PURE__ */ jsx(
3360
+ DetailHost,
3361
+ {
3362
+ widget: byKey.get(detailKey),
3363
+ loadDetail,
3364
+ onClose: () => setDetailKey(void 0)
3365
+ },
3366
+ detailKey
3367
+ )
3368
+ ]
3369
+ }
3370
+ );
2318
3371
  }
2319
3372
  function Dashboard({
2320
3373
  locale,
@@ -2959,8 +4012,21 @@ function useWidgets(definitions, providers, context, options = {}) {
2959
4012
  },
2960
4013
  { rootMargin }
2961
4014
  );
2962
- grid.querySelectorAll("[data-widget-key]").forEach((card) => observer.observe(card));
2963
- return () => observer.disconnect();
4015
+ const observed = /* @__PURE__ */ new WeakSet();
4016
+ const observeNewCards = () => {
4017
+ grid.querySelectorAll("[data-widget-key]").forEach((card) => {
4018
+ if (observed.has(card)) return;
4019
+ observed.add(card);
4020
+ observer.observe(card);
4021
+ });
4022
+ };
4023
+ observeNewCards();
4024
+ const mutations = typeof MutationObserver === "undefined" ? void 0 : new MutationObserver(observeNewCards);
4025
+ mutations?.observe(grid, { childList: true, subtree: true });
4026
+ return () => {
4027
+ mutations?.disconnect();
4028
+ observer.disconnect();
4029
+ };
2964
4030
  }, [loader, loadWhen, rootMargin, cardKeys]);
2965
4031
  useEffect(() => {
2966
4032
  if (loader === null || !(refreshMs > 0)) return void 0;