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