@richardmcquiston01/dashboard-widgets-toolkit 0.7.0 → 0.9.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
@@ -524,6 +524,19 @@ var DEFAULT_LABELS = {
524
524
  moveLater: (title) => `Move ${title} later`,
525
525
  hiddenWidgets: "Hidden:",
526
526
  minimizedWidgets: "Minimized:",
527
+ customize: "Customize",
528
+ done: "Done",
529
+ reset: "Reset layout",
530
+ revertChanges: "Revert changes",
531
+ confirmReset: "Reset the layout to the default?",
532
+ confirmRevert: "Revert to how this looked before you started editing?",
533
+ confirmYes: "Yes",
534
+ confirmNo: "No",
535
+ editingOn: "Editing dashboard. Use the buttons on each card to reorder or hide it.",
536
+ editingOff: "Finished editing.",
537
+ dashboardControls: "Dashboard controls",
538
+ locked: (title) => `${title} is locked`,
539
+ lockedForViewers: (title) => `${title} is locked for viewers`,
527
540
  opensInNewTab: "(opens in a new tab)",
528
541
  view: (title) => `View ${title}`,
529
542
  close: "Close",
@@ -1394,6 +1407,7 @@ function WidgetCard({
1394
1407
  columnSpan,
1395
1408
  width,
1396
1409
  widgetKey,
1410
+ editing = false,
1397
1411
  headingLevel = 2,
1398
1412
  className,
1399
1413
  children
@@ -1438,6 +1452,7 @@ function WidgetCard({
1438
1452
  "dwt-card",
1439
1453
  `dwt-card--${status}`,
1440
1454
  minimized && "dwt-card--minimized",
1455
+ editing && "dwt-card--editing",
1441
1456
  size !== void 0 && `dwt-card--size-${size}`,
1442
1457
  fillsHeight(fill) && "dwt-card--fill-height",
1443
1458
  fillsWidth(fill) && "dwt-card--fill-width",
@@ -1570,6 +1585,45 @@ function ResolvedWidgetCard({
1570
1585
  );
1571
1586
  }
1572
1587
 
1588
+ // src/core/definition.ts
1589
+ var NOT_LOCKED = Object.freeze({
1590
+ move: false,
1591
+ hide: false,
1592
+ minimize: false
1593
+ });
1594
+ var FULLY_LOCKED = Object.freeze({
1595
+ move: true,
1596
+ hide: true,
1597
+ minimize: true
1598
+ });
1599
+ function resolveWidgetLock(definition) {
1600
+ const setting = definition.locked;
1601
+ if (setting === true) return FULLY_LOCKED;
1602
+ if (setting === void 0 || setting === false) return NOT_LOCKED;
1603
+ return {
1604
+ move: setting.move === true,
1605
+ hide: setting.hide === true,
1606
+ minimize: setting.minimize === true
1607
+ };
1608
+ }
1609
+ function sortDefinitions(definitions) {
1610
+ return definitions.map((definition, index) => ({ definition, index })).sort(
1611
+ (a, b) => (a.definition.sortOrder ?? 0) - (b.definition.sortOrder ?? 0) || a.index - b.index
1612
+ ).map(({ definition }) => definition);
1613
+ }
1614
+ function isWidgetVisibleTo(definition, roles) {
1615
+ if (definition.active === false) {
1616
+ return false;
1617
+ }
1618
+ if (definition.roles === void 0 || definition.roles.length === 0) {
1619
+ return true;
1620
+ }
1621
+ if (roles === void 0) {
1622
+ return false;
1623
+ }
1624
+ return definition.roles.some((role) => roles.includes(role));
1625
+ }
1626
+
1573
1627
  // src/core/layout.ts
1574
1628
  var EMPTY_LAYOUT = Object.freeze({
1575
1629
  order: Object.freeze([]),
@@ -1581,9 +1635,10 @@ function bySortOrder(items) {
1581
1635
  (a, b) => (a.item.sortOrder ?? 0) - (b.item.sortOrder ?? 0) || a.index - b.index
1582
1636
  ).map(({ item }) => item);
1583
1637
  }
1584
- function visibleWidgets(definitions, layout) {
1585
- const hidden = new Set(layout.hidden);
1586
- const visible = definitions.filter((d) => !hidden.has(d.key));
1638
+ function isPinned(item) {
1639
+ return resolveWidgetLock(item).move;
1640
+ }
1641
+ function viewerOrder(visible, layout) {
1587
1642
  const byKey = new Map(visible.map((d) => [d.key, d]));
1588
1643
  const ordered = [];
1589
1644
  for (const key of layout.order) {
@@ -1598,34 +1653,62 @@ function visibleWidgets(definitions, layout) {
1598
1653
  }
1599
1654
  return ordered;
1600
1655
  }
1656
+ function placeAroundPinned(visible, free) {
1657
+ const slots = bySortOrder(visible).map(
1658
+ (item) => isPinned(item) ? item : void 0
1659
+ );
1660
+ let next = 0;
1661
+ return slots.map((pinned) => pinned ?? free[next++]);
1662
+ }
1663
+ function visibleWidgets(definitions, layout) {
1664
+ const hidden = new Set(layout.hidden);
1665
+ const visible = definitions.filter((d) => !hidden.has(d.key));
1666
+ const ordered = viewerOrder(visible, layout);
1667
+ if (!visible.some(isPinned)) return ordered;
1668
+ return placeAroundPinned(
1669
+ visible,
1670
+ ordered.filter((item) => !isPinned(item))
1671
+ );
1672
+ }
1601
1673
  function hiddenWidgets(definitions, layout) {
1602
1674
  const hidden = new Set(layout.hidden);
1603
1675
  return bySortOrder(definitions.filter((d) => hidden.has(d.key)));
1604
1676
  }
1605
1677
  function moveWidget(definitions, layout, key, toIndex) {
1606
- const current = visibleWidgets(definitions, layout).map(
1607
- (d) => d.key
1608
- );
1609
- const fromIndex = current.indexOf(key);
1610
- if (fromIndex === -1 || !Number.isInteger(toIndex) || toIndex < 0 || toIndex >= current.length || toIndex === fromIndex) {
1678
+ const current = visibleWidgets(definitions, layout);
1679
+ const fromIndex = current.findIndex((d) => d.key === key);
1680
+ const mover = current[fromIndex];
1681
+ if (mover === void 0 || !Number.isInteger(toIndex) || toIndex < 0 || toIndex >= current.length || toIndex === fromIndex || isPinned(mover) || isPinned(current[toIndex])) {
1611
1682
  return layout;
1612
1683
  }
1613
- const next = [...current];
1614
- next.splice(fromIndex, 1);
1615
- next.splice(toIndex, 0, key);
1616
- return { ...layout, order: next };
1684
+ const freeSlots = freeSlotIndexes(current);
1685
+ const free = current.filter((item) => !isPinned(item));
1686
+ const fromFree = freeSlots.indexOf(fromIndex);
1687
+ const toFree = freeSlots.indexOf(toIndex);
1688
+ free.splice(fromFree, 1);
1689
+ free.splice(toFree, 0, mover);
1690
+ const next = placeAroundPinned(current, free);
1691
+ return { ...layout, order: next.map((d) => d.key) };
1692
+ }
1693
+ function freeSlotIndexes(ordered) {
1694
+ const slots = [];
1695
+ ordered.forEach((item, index) => {
1696
+ if (!isPinned(item)) slots.push(index);
1697
+ });
1698
+ return slots;
1617
1699
  }
1618
1700
  function moveWidgetBy(definitions, layout, key, offset) {
1619
- const current = visibleWidgets(definitions, layout).map(
1620
- (d) => d.key
1621
- );
1622
- const fromIndex = current.indexOf(key);
1701
+ const current = visibleWidgets(definitions, layout);
1702
+ const fromIndex = current.findIndex((d) => d.key === key);
1623
1703
  if (fromIndex === -1) return layout;
1624
- const toIndex = Math.max(
1704
+ const freeSlots = freeSlotIndexes(current);
1705
+ const fromFree = freeSlots.indexOf(fromIndex);
1706
+ if (fromFree === -1) return layout;
1707
+ const toFree = Math.max(
1625
1708
  0,
1626
- Math.min(current.length - 1, fromIndex + Math.trunc(offset))
1709
+ Math.min(freeSlots.length - 1, fromFree + Math.trunc(offset))
1627
1710
  );
1628
- return moveWidget(definitions, layout, key, toIndex);
1711
+ return moveWidget(definitions, layout, key, freeSlots[toFree]);
1629
1712
  }
1630
1713
  function isHidden(layout, key) {
1631
1714
  return layout.hidden.includes(key);
@@ -1656,6 +1739,23 @@ function restoreWidget(layout, key) {
1656
1739
  function toggleMinimized(layout, key) {
1657
1740
  return isMinimized(layout, key) ? restoreWidget(layout, key) : minimizeWidget(layout, key);
1658
1741
  }
1742
+ function enforceLocks(definitions, layout, options = {}) {
1743
+ if (options.overrideLocks === true) return layout;
1744
+ const noMove = /* @__PURE__ */ new Set();
1745
+ const noHide = /* @__PURE__ */ new Set();
1746
+ const noMinimize = /* @__PURE__ */ new Set();
1747
+ for (const definition of definitions) {
1748
+ const lock = resolveWidgetLock(definition);
1749
+ if (lock.move) noMove.add(definition.key);
1750
+ if (lock.hide) noHide.add(definition.key);
1751
+ if (lock.minimize) noMinimize.add(definition.key);
1752
+ }
1753
+ const without = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
1754
+ const order = without(layout.order, noMove);
1755
+ const hidden = without(layout.hidden, noHide);
1756
+ const minimized = without(layout.minimized, noMinimize);
1757
+ return order === layout.order && hidden === layout.hidden && minimized === layout.minimized ? layout : { ...layout, order, hidden, minimized };
1758
+ }
1659
1759
  function nextSort(current, column) {
1660
1760
  if (current?.column !== column) return { column, direction: "asc" };
1661
1761
  return current.direction === "asc" ? { column, direction: "desc" } : void 0;
@@ -2054,10 +2154,74 @@ function DetailHost({
2054
2154
  }
2055
2155
  ) });
2056
2156
  }
2157
+ function InlineConfirm({
2158
+ message,
2159
+ onAnswer
2160
+ }) {
2161
+ const slot = useSlotClassName();
2162
+ const { labels } = useWidgetSettings();
2163
+ const noRef = react.useRef(null);
2164
+ react.useEffect(() => {
2165
+ noRef.current?.focus();
2166
+ }, []);
2167
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2168
+ "span",
2169
+ {
2170
+ role: "group",
2171
+ "aria-label": message,
2172
+ className: "dwt-confirm",
2173
+ onKeyDown: (event) => {
2174
+ if (event.key === "Escape") {
2175
+ event.stopPropagation();
2176
+ onAnswer(false);
2177
+ }
2178
+ },
2179
+ children: [
2180
+ /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", className: "dwt-confirm-message", children: message }),
2181
+ /* @__PURE__ */ jsxRuntime.jsx(
2182
+ "button",
2183
+ {
2184
+ type: "button",
2185
+ className: slot("button", "dwt-button", "dwt-icon-button"),
2186
+ "aria-label": labels.confirmYes,
2187
+ title: labels.confirmYes,
2188
+ onClick: () => onAnswer(true),
2189
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2713" })
2190
+ }
2191
+ ),
2192
+ /* @__PURE__ */ jsxRuntime.jsx(
2193
+ "button",
2194
+ {
2195
+ ref: noRef,
2196
+ type: "button",
2197
+ className: slot("button", "dwt-button", "dwt-icon-button"),
2198
+ "aria-label": labels.confirmNo,
2199
+ title: labels.confirmNo,
2200
+ onClick: () => onAnswer(false),
2201
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2715" })
2202
+ }
2203
+ )
2204
+ ]
2205
+ }
2206
+ );
2207
+ }
2208
+ function withoutLock(definition) {
2209
+ if (definition.locked === void 0) return definition;
2210
+ const copy = { ...definition };
2211
+ delete copy.locked;
2212
+ return copy;
2213
+ }
2057
2214
  function DashboardInner({
2058
2215
  widgets,
2059
- layout = EMPTY_LAYOUT,
2216
+ layout: savedLayout = EMPTY_LAYOUT,
2060
2217
  onLayoutChange,
2218
+ overrideLocks = false,
2219
+ editMode = "always",
2220
+ editing: editingProp,
2221
+ defaultEditing = false,
2222
+ onEditingChange,
2223
+ defaultLayout,
2224
+ toolbar = true,
2061
2225
  onRetry,
2062
2226
  loadDetail,
2063
2227
  onOpenDetail,
@@ -2066,13 +2230,35 @@ function DashboardInner({
2066
2230
  }) {
2067
2231
  const slot = useSlotClassName();
2068
2232
  const { labels } = useWidgetSettings();
2069
- const definitions = widgets.map((w) => w.definition);
2233
+ const definitions = widgets.map(
2234
+ (w) => overrideLocks ? withoutLock(w.definition) : w.definition
2235
+ );
2236
+ const layout = enforceLocks(definitions, savedLayout);
2070
2237
  const byKey = new Map(
2071
2238
  widgets.map((w) => [w.definition.key, w])
2072
2239
  );
2073
2240
  const visible = visibleWidgets(definitions, layout);
2074
2241
  const hidden = hiddenWidgets(definitions, layout);
2075
2242
  const editable = onLayoutChange !== void 0;
2243
+ const toggleMode = editMode === "toggle";
2244
+ const [editingState, setEditingState] = react.useState(defaultEditing);
2245
+ const editing = editingProp ?? editingState;
2246
+ const controlsShown = editable && (!toggleMode || editing);
2247
+ const [snapshot, setSnapshot] = react.useState(
2248
+ void 0
2249
+ );
2250
+ if (toggleMode && editable && editing) {
2251
+ if (snapshot === void 0) setSnapshot(savedLayout);
2252
+ } else if (snapshot !== void 0) {
2253
+ setSnapshot(void 0);
2254
+ }
2255
+ const [confirming, setConfirming] = react.useState(
2256
+ void 0
2257
+ );
2258
+ const pending = editing ? confirming : void 0;
2259
+ const [announcement, setAnnouncement] = react.useState("");
2260
+ const toggleRef = react.useRef(null);
2261
+ const resetRef = react.useRef(null);
2076
2262
  const minimized = editable ? visible.filter((definition) => isMinimized(layout, definition.key)) : [];
2077
2263
  const shown = editable ? visible.filter((definition) => !isMinimized(layout, definition.key)) : visible;
2078
2264
  const [detailKey, setDetailKey] = react.useState(void 0);
@@ -2086,129 +2272,267 @@ function DashboardInner({
2086
2272
  function change(next) {
2087
2273
  if (next !== layout) onLayoutChange?.(next);
2088
2274
  }
2089
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: slot("dashboard", "dwt-dashboard", className), children: [
2090
- editable && hidden.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: slot("hiddenBar", "dwt-hidden-bar"), children: [
2091
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dwt-hidden-label", children: labels.hiddenWidgets }),
2092
- hidden.map((definition) => /* @__PURE__ */ jsxRuntime.jsxs(
2093
- "button",
2094
- {
2095
- type: "button",
2096
- className: slot("button", "dwt-button", "dwt-show-button"),
2097
- "aria-label": labels.show(definition.title),
2098
- onClick: () => change(showWidget(layout, definition.key)),
2099
- children: [
2100
- /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "+ " }),
2101
- definition.title
2102
- ]
2103
- },
2104
- definition.key
2105
- ))
2106
- ] }),
2107
- minimized.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
2108
- "div",
2109
- {
2110
- className: slot("hiddenBar", "dwt-hidden-bar", "dwt-minimized-bar"),
2111
- children: [
2112
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dwt-hidden-label", children: labels.minimizedWidgets }),
2113
- minimized.map((definition) => /* @__PURE__ */ jsxRuntime.jsxs(
2275
+ function setEditing(next) {
2276
+ if (editingProp === void 0) setEditingState(next);
2277
+ onEditingChange?.(next);
2278
+ setAnnouncement(next ? labels.editingOn : labels.editingOff);
2279
+ setConfirming(void 0);
2280
+ }
2281
+ function answerConfirmation(yes) {
2282
+ const which = pending;
2283
+ setConfirming(void 0);
2284
+ if (yes && which === "reset") {
2285
+ change(enforceLocks(definitions, defaultLayout ?? EMPTY_LAYOUT));
2286
+ } else if (yes && which === "revert" && snapshot !== void 0) {
2287
+ change(snapshot);
2288
+ }
2289
+ (which === "revert" ? toggleRef : resetRef).current?.focus();
2290
+ }
2291
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2292
+ "div",
2293
+ {
2294
+ className: slot(
2295
+ "dashboard",
2296
+ "dwt-dashboard",
2297
+ toggleMode && controlsShown && "dwt-dashboard--editing",
2298
+ className
2299
+ ),
2300
+ children: [
2301
+ editable && toggleMode && toolbar && /* @__PURE__ */ jsxRuntime.jsxs(
2302
+ "div",
2303
+ {
2304
+ role: "group",
2305
+ "aria-label": labels.dashboardControls,
2306
+ className: slot("toolbar", "dwt-toolbar"),
2307
+ children: [
2308
+ /* @__PURE__ */ jsxRuntime.jsx(
2309
+ "button",
2310
+ {
2311
+ ref: toggleRef,
2312
+ type: "button",
2313
+ "aria-pressed": editing,
2314
+ className: slot("button", "dwt-button", "dwt-toolbar-toggle"),
2315
+ onClick: () => setEditing(!editing),
2316
+ children: editing ? labels.done : labels.customize
2317
+ }
2318
+ ),
2319
+ editing && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2320
+ /* @__PURE__ */ jsxRuntime.jsx(
2321
+ "button",
2322
+ {
2323
+ ref: resetRef,
2324
+ type: "button",
2325
+ className: slot("button", "dwt-button"),
2326
+ onClick: () => setConfirming("reset"),
2327
+ children: labels.reset
2328
+ }
2329
+ ),
2330
+ /* @__PURE__ */ jsxRuntime.jsx(
2331
+ "button",
2332
+ {
2333
+ type: "button",
2334
+ className: slot("button", "dwt-button"),
2335
+ disabled: snapshot === void 0 || savedLayout === snapshot,
2336
+ onClick: () => setConfirming("revert"),
2337
+ children: labels.revertChanges
2338
+ }
2339
+ )
2340
+ ] }),
2341
+ pending !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
2342
+ InlineConfirm,
2343
+ {
2344
+ message: pending === "reset" ? labels.confirmReset : labels.confirmRevert,
2345
+ onAnswer: answerConfirmation
2346
+ }
2347
+ ),
2348
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dwt-visually-hidden", "aria-live": "polite", children: announcement })
2349
+ ]
2350
+ }
2351
+ ),
2352
+ controlsShown && hidden.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: slot("hiddenBar", "dwt-hidden-bar"), children: [
2353
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dwt-hidden-label", children: labels.hiddenWidgets }),
2354
+ hidden.map((definition) => /* @__PURE__ */ jsxRuntime.jsxs(
2114
2355
  "button",
2115
2356
  {
2116
2357
  type: "button",
2117
2358
  className: slot("button", "dwt-button", "dwt-show-button"),
2118
- "aria-label": labels.expand(definition.title),
2119
- onClick: () => change(toggleMinimized(layout, definition.key)),
2359
+ "aria-label": labels.show(definition.title),
2360
+ onClick: () => change(showWidget(layout, definition.key)),
2120
2361
  children: [
2121
- /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u25B8 " }),
2362
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "+ " }),
2122
2363
  definition.title
2123
2364
  ]
2124
2365
  },
2125
2366
  definition.key
2126
2367
  ))
2127
- ]
2128
- }
2129
- ),
2130
- 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(
2131
- "div",
2132
- {
2133
- ref: gridRef,
2134
- className: slot("grid", "dwt-grid", twelve && "dwt-grid--twelve"),
2135
- children: shown.map((definition, index) => {
2136
- const widget = byKey.get(
2137
- definition.key
2138
- );
2139
- if (widget === void 0) return null;
2140
- const key = definition.key;
2141
- const title = definition.title;
2142
- const actions = editable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2143
- /* @__PURE__ */ jsxRuntime.jsx(
2144
- "button",
2145
- {
2146
- type: "button",
2147
- className: slot("button", "dwt-button", "dwt-icon-button"),
2148
- "aria-label": labels.moveEarlier(title),
2149
- title: labels.moveEarlier(title),
2150
- disabled: index === 0,
2151
- onClick: () => change(moveWidgetBy(definitions, layout, key, -1)),
2152
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2191" })
2153
- }
2154
- ),
2155
- /* @__PURE__ */ jsxRuntime.jsx(
2156
- "button",
2157
- {
2158
- type: "button",
2159
- className: slot("button", "dwt-button", "dwt-icon-button"),
2160
- "aria-label": labels.moveLater(title),
2161
- title: labels.moveLater(title),
2162
- disabled: index === shown.length - 1,
2163
- onClick: () => change(moveWidgetBy(definitions, layout, key, 1)),
2164
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2193" })
2165
- }
2166
- ),
2167
- /* @__PURE__ */ jsxRuntime.jsx(
2168
- "button",
2169
- {
2170
- type: "button",
2171
- className: slot("button", "dwt-button", "dwt-icon-button"),
2172
- "aria-label": labels.hide(title),
2173
- title: labels.hide(title),
2174
- onClick: () => change(hideWidget(layout, key)),
2175
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\xD7" })
2176
- }
2177
- )
2178
- ] }) : void 0;
2179
- const viewable = resolveDetailOptions(definition) !== void 0 && (onOpenDetail !== void 0 || loadDetail !== void 0 || widget.status === "ok" && deriveDetailData(widget.data) !== void 0);
2180
- return /* @__PURE__ */ jsxRuntime.jsx(
2181
- ResolvedWidgetCard,
2182
- {
2183
- widget,
2184
- ...viewable ? {
2185
- onView: () => onOpenDetail === void 0 ? setDetailKey(key) : onOpenDetail(key)
2186
- } : {},
2187
- ...spanProps(spans, key),
2188
- ...widthProps(twelve, definition),
2189
- minimized: isMinimized(layout, key),
2190
- ...actions === void 0 ? {} : { actions },
2191
- ...editable ? {
2192
- onToggleMinimized: () => change(toggleMinimized(layout, key))
2193
- } : {},
2194
- ...headingLevel === void 0 ? {} : { headingLevel },
2195
- ...onRetry === void 0 || widget.status !== "error" ? {} : { onRetry: () => onRetry(key) }
2196
- },
2197
- key
2198
- );
2199
- })
2200
- }
2201
- ),
2202
- detailKey !== void 0 && byKey.get(detailKey) !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
2203
- DetailHost,
2204
- {
2205
- widget: byKey.get(detailKey),
2206
- loadDetail,
2207
- onClose: () => setDetailKey(void 0)
2208
- },
2209
- detailKey
2210
- )
2211
- ] });
2368
+ ] }),
2369
+ minimized.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
2370
+ "div",
2371
+ {
2372
+ className: slot("hiddenBar", "dwt-hidden-bar", "dwt-minimized-bar"),
2373
+ children: [
2374
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dwt-hidden-label", children: labels.minimizedWidgets }),
2375
+ minimized.map((definition) => /* @__PURE__ */ jsxRuntime.jsxs(
2376
+ "button",
2377
+ {
2378
+ type: "button",
2379
+ className: slot("button", "dwt-button", "dwt-show-button"),
2380
+ "aria-label": labels.expand(definition.title),
2381
+ onClick: () => change(toggleMinimized(layout, definition.key)),
2382
+ children: [
2383
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u25B8 " }),
2384
+ definition.title
2385
+ ]
2386
+ },
2387
+ definition.key
2388
+ ))
2389
+ ]
2390
+ }
2391
+ ),
2392
+ visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: slot("empty", "dwt-empty", "dwt-dashboard-empty"), children: definitions.length === 0 ? labels.noWidgets : labels.allHidden }) : shown.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(
2393
+ "div",
2394
+ {
2395
+ ref: gridRef,
2396
+ className: slot("grid", "dwt-grid", twelve && "dwt-grid--twelve"),
2397
+ children: shown.map((definition, index) => {
2398
+ const widget = byKey.get(
2399
+ definition.key
2400
+ );
2401
+ if (widget === void 0) return null;
2402
+ const key = definition.key;
2403
+ const title = definition.title;
2404
+ const lock = resolveWidgetLock(definition);
2405
+ const ownLock = resolveWidgetLock(
2406
+ widget.definition
2407
+ );
2408
+ const isLocked = ownLock.move || ownLock.hide || ownLock.minimize;
2409
+ const lockLabel = overrideLocks ? labels.lockedForViewers(title) : labels.locked(title);
2410
+ const canMoveEarlier = !lock.move && moveWidgetBy(definitions, layout, key, -1) !== layout;
2411
+ const canMoveLater = !lock.move && moveWidgetBy(definitions, layout, key, 1) !== layout;
2412
+ const actions = controlsShown ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2413
+ isLocked && /* @__PURE__ */ jsxRuntime.jsx(
2414
+ "span",
2415
+ {
2416
+ role: "img",
2417
+ className: "dwt-lock",
2418
+ "aria-label": lockLabel,
2419
+ title: lockLabel,
2420
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
2421
+ "svg",
2422
+ {
2423
+ viewBox: "0 0 16 16",
2424
+ width: "14",
2425
+ height: "14",
2426
+ "aria-hidden": "true",
2427
+ focusable: "false",
2428
+ children: [
2429
+ /* @__PURE__ */ jsxRuntime.jsx(
2430
+ "rect",
2431
+ {
2432
+ x: "3",
2433
+ y: "7",
2434
+ width: "10",
2435
+ height: "7",
2436
+ rx: "1.5",
2437
+ fill: "currentColor"
2438
+ }
2439
+ ),
2440
+ /* @__PURE__ */ jsxRuntime.jsx(
2441
+ "path",
2442
+ {
2443
+ d: "M5.5 7V5a2.5 2.5 0 0 1 5 0v2",
2444
+ fill: "none",
2445
+ stroke: "currentColor",
2446
+ strokeWidth: "1.4"
2447
+ }
2448
+ )
2449
+ ]
2450
+ }
2451
+ )
2452
+ }
2453
+ ),
2454
+ !lock.move && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2455
+ /* @__PURE__ */ jsxRuntime.jsx(
2456
+ "button",
2457
+ {
2458
+ type: "button",
2459
+ className: slot(
2460
+ "button",
2461
+ "dwt-button",
2462
+ "dwt-icon-button"
2463
+ ),
2464
+ "aria-label": labels.moveEarlier(title),
2465
+ title: labels.moveEarlier(title),
2466
+ disabled: index === 0 || !canMoveEarlier,
2467
+ onClick: () => change(moveWidgetBy(definitions, layout, key, -1)),
2468
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2191" })
2469
+ }
2470
+ ),
2471
+ /* @__PURE__ */ jsxRuntime.jsx(
2472
+ "button",
2473
+ {
2474
+ type: "button",
2475
+ className: slot(
2476
+ "button",
2477
+ "dwt-button",
2478
+ "dwt-icon-button"
2479
+ ),
2480
+ "aria-label": labels.moveLater(title),
2481
+ title: labels.moveLater(title),
2482
+ disabled: index === shown.length - 1 || !canMoveLater,
2483
+ onClick: () => change(moveWidgetBy(definitions, layout, key, 1)),
2484
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2193" })
2485
+ }
2486
+ )
2487
+ ] }),
2488
+ !lock.hide && /* @__PURE__ */ jsxRuntime.jsx(
2489
+ "button",
2490
+ {
2491
+ type: "button",
2492
+ className: slot("button", "dwt-button", "dwt-icon-button"),
2493
+ "aria-label": labels.hide(title),
2494
+ title: labels.hide(title),
2495
+ onClick: () => change(hideWidget(layout, key)),
2496
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\xD7" })
2497
+ }
2498
+ )
2499
+ ] }) : void 0;
2500
+ const viewable = resolveDetailOptions(definition) !== void 0 && (onOpenDetail !== void 0 || loadDetail !== void 0 || widget.status === "ok" && deriveDetailData(widget.data) !== void 0);
2501
+ return /* @__PURE__ */ jsxRuntime.jsx(
2502
+ ResolvedWidgetCard,
2503
+ {
2504
+ widget,
2505
+ ...viewable ? {
2506
+ onView: () => onOpenDetail === void 0 ? setDetailKey(key) : onOpenDetail(key)
2507
+ } : {},
2508
+ ...spanProps(spans, key),
2509
+ ...widthProps(twelve, definition),
2510
+ minimized: isMinimized(layout, key),
2511
+ editing: toggleMode && controlsShown,
2512
+ ...actions === void 0 ? {} : { actions },
2513
+ ...editable && !lock.minimize ? {
2514
+ onToggleMinimized: () => change(toggleMinimized(layout, key))
2515
+ } : {},
2516
+ ...headingLevel === void 0 ? {} : { headingLevel },
2517
+ ...onRetry === void 0 || widget.status !== "error" ? {} : { onRetry: () => onRetry(key) }
2518
+ },
2519
+ key
2520
+ );
2521
+ })
2522
+ }
2523
+ ),
2524
+ detailKey !== void 0 && byKey.get(detailKey) !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
2525
+ DetailHost,
2526
+ {
2527
+ widget: byKey.get(detailKey),
2528
+ loadDetail,
2529
+ onClose: () => setDetailKey(void 0)
2530
+ },
2531
+ detailKey
2532
+ )
2533
+ ]
2534
+ }
2535
+ );
2212
2536
  }
2213
2537
  function Dashboard({
2214
2538
  locale,
@@ -2232,25 +2556,6 @@ function Dashboard({
2232
2556
  );
2233
2557
  }
2234
2558
 
2235
- // src/core/definition.ts
2236
- function sortDefinitions(definitions) {
2237
- return definitions.map((definition, index) => ({ definition, index })).sort(
2238
- (a, b) => (a.definition.sortOrder ?? 0) - (b.definition.sortOrder ?? 0) || a.index - b.index
2239
- ).map(({ definition }) => definition);
2240
- }
2241
- function isWidgetVisibleTo(definition, roles) {
2242
- if (definition.active === false) {
2243
- return false;
2244
- }
2245
- if (definition.roles === void 0 || definition.roles.length === 0) {
2246
- return true;
2247
- }
2248
- if (roles === void 0) {
2249
- return false;
2250
- }
2251
- return definition.roles.some((role) => roles.includes(role));
2252
- }
2253
-
2254
2559
  // src/core/payload.ts
2255
2560
  var WIDGET_KINDS = [
2256
2561
  "TEXT",