@richardmcquiston01/dashboard-widgets-toolkit 0.6.0 → 0.8.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
@@ -1570,6 +1570,45 @@ function ResolvedWidgetCard({
1570
1570
  );
1571
1571
  }
1572
1572
 
1573
+ // src/core/definition.ts
1574
+ var NOT_LOCKED = Object.freeze({
1575
+ move: false,
1576
+ hide: false,
1577
+ minimize: false
1578
+ });
1579
+ var FULLY_LOCKED = Object.freeze({
1580
+ move: true,
1581
+ hide: true,
1582
+ minimize: true
1583
+ });
1584
+ function resolveWidgetLock(definition) {
1585
+ const setting = definition.locked;
1586
+ if (setting === true) return FULLY_LOCKED;
1587
+ if (setting === void 0 || setting === false) return NOT_LOCKED;
1588
+ return {
1589
+ move: setting.move === true,
1590
+ hide: setting.hide === true,
1591
+ minimize: setting.minimize === true
1592
+ };
1593
+ }
1594
+ function sortDefinitions(definitions) {
1595
+ return definitions.map((definition, index) => ({ definition, index })).sort(
1596
+ (a, b) => (a.definition.sortOrder ?? 0) - (b.definition.sortOrder ?? 0) || a.index - b.index
1597
+ ).map(({ definition }) => definition);
1598
+ }
1599
+ function isWidgetVisibleTo(definition, roles) {
1600
+ if (definition.active === false) {
1601
+ return false;
1602
+ }
1603
+ if (definition.roles === void 0 || definition.roles.length === 0) {
1604
+ return true;
1605
+ }
1606
+ if (roles === void 0) {
1607
+ return false;
1608
+ }
1609
+ return definition.roles.some((role) => roles.includes(role));
1610
+ }
1611
+
1573
1612
  // src/core/layout.ts
1574
1613
  var EMPTY_LAYOUT = Object.freeze({
1575
1614
  order: Object.freeze([]),
@@ -1581,9 +1620,10 @@ function bySortOrder(items) {
1581
1620
  (a, b) => (a.item.sortOrder ?? 0) - (b.item.sortOrder ?? 0) || a.index - b.index
1582
1621
  ).map(({ item }) => item);
1583
1622
  }
1584
- function visibleWidgets(definitions, layout) {
1585
- const hidden = new Set(layout.hidden);
1586
- const visible = definitions.filter((d) => !hidden.has(d.key));
1623
+ function isPinned(item) {
1624
+ return resolveWidgetLock(item).move;
1625
+ }
1626
+ function viewerOrder(visible, layout) {
1587
1627
  const byKey = new Map(visible.map((d) => [d.key, d]));
1588
1628
  const ordered = [];
1589
1629
  for (const key of layout.order) {
@@ -1598,34 +1638,62 @@ function visibleWidgets(definitions, layout) {
1598
1638
  }
1599
1639
  return ordered;
1600
1640
  }
1641
+ function placeAroundPinned(visible, free) {
1642
+ const slots = bySortOrder(visible).map(
1643
+ (item) => isPinned(item) ? item : void 0
1644
+ );
1645
+ let next = 0;
1646
+ return slots.map((pinned) => pinned ?? free[next++]);
1647
+ }
1648
+ function visibleWidgets(definitions, layout) {
1649
+ const hidden = new Set(layout.hidden);
1650
+ const visible = definitions.filter((d) => !hidden.has(d.key));
1651
+ const ordered = viewerOrder(visible, layout);
1652
+ if (!visible.some(isPinned)) return ordered;
1653
+ return placeAroundPinned(
1654
+ visible,
1655
+ ordered.filter((item) => !isPinned(item))
1656
+ );
1657
+ }
1601
1658
  function hiddenWidgets(definitions, layout) {
1602
1659
  const hidden = new Set(layout.hidden);
1603
1660
  return bySortOrder(definitions.filter((d) => hidden.has(d.key)));
1604
1661
  }
1605
1662
  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) {
1663
+ const current = visibleWidgets(definitions, layout);
1664
+ const fromIndex = current.findIndex((d) => d.key === key);
1665
+ const mover = current[fromIndex];
1666
+ if (mover === void 0 || !Number.isInteger(toIndex) || toIndex < 0 || toIndex >= current.length || toIndex === fromIndex || isPinned(mover) || isPinned(current[toIndex])) {
1611
1667
  return layout;
1612
1668
  }
1613
- const next = [...current];
1614
- next.splice(fromIndex, 1);
1615
- next.splice(toIndex, 0, key);
1616
- return { ...layout, order: next };
1669
+ const freeSlots = freeSlotIndexes(current);
1670
+ const free = current.filter((item) => !isPinned(item));
1671
+ const fromFree = freeSlots.indexOf(fromIndex);
1672
+ const toFree = freeSlots.indexOf(toIndex);
1673
+ free.splice(fromFree, 1);
1674
+ free.splice(toFree, 0, mover);
1675
+ const next = placeAroundPinned(current, free);
1676
+ return { ...layout, order: next.map((d) => d.key) };
1677
+ }
1678
+ function freeSlotIndexes(ordered) {
1679
+ const slots = [];
1680
+ ordered.forEach((item, index) => {
1681
+ if (!isPinned(item)) slots.push(index);
1682
+ });
1683
+ return slots;
1617
1684
  }
1618
1685
  function moveWidgetBy(definitions, layout, key, offset) {
1619
- const current = visibleWidgets(definitions, layout).map(
1620
- (d) => d.key
1621
- );
1622
- const fromIndex = current.indexOf(key);
1686
+ const current = visibleWidgets(definitions, layout);
1687
+ const fromIndex = current.findIndex((d) => d.key === key);
1623
1688
  if (fromIndex === -1) return layout;
1624
- const toIndex = Math.max(
1689
+ const freeSlots = freeSlotIndexes(current);
1690
+ const fromFree = freeSlots.indexOf(fromIndex);
1691
+ if (fromFree === -1) return layout;
1692
+ const toFree = Math.max(
1625
1693
  0,
1626
- Math.min(current.length - 1, fromIndex + Math.trunc(offset))
1694
+ Math.min(freeSlots.length - 1, fromFree + Math.trunc(offset))
1627
1695
  );
1628
- return moveWidget(definitions, layout, key, toIndex);
1696
+ return moveWidget(definitions, layout, key, freeSlots[toFree]);
1629
1697
  }
1630
1698
  function isHidden(layout, key) {
1631
1699
  return layout.hidden.includes(key);
@@ -1656,6 +1724,23 @@ function restoreWidget(layout, key) {
1656
1724
  function toggleMinimized(layout, key) {
1657
1725
  return isMinimized(layout, key) ? restoreWidget(layout, key) : minimizeWidget(layout, key);
1658
1726
  }
1727
+ function enforceLocks(definitions, layout, options = {}) {
1728
+ if (options.overrideLocks === true) return layout;
1729
+ const noMove = /* @__PURE__ */ new Set();
1730
+ const noHide = /* @__PURE__ */ new Set();
1731
+ const noMinimize = /* @__PURE__ */ new Set();
1732
+ for (const definition of definitions) {
1733
+ const lock = resolveWidgetLock(definition);
1734
+ if (lock.move) noMove.add(definition.key);
1735
+ if (lock.hide) noHide.add(definition.key);
1736
+ if (lock.minimize) noMinimize.add(definition.key);
1737
+ }
1738
+ const without = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
1739
+ const order = without(layout.order, noMove);
1740
+ const hidden = without(layout.hidden, noHide);
1741
+ const minimized = without(layout.minimized, noMinimize);
1742
+ return order === layout.order && hidden === layout.hidden && minimized === layout.minimized ? layout : { ...layout, order, hidden, minimized };
1743
+ }
1659
1744
  function nextSort(current, column) {
1660
1745
  if (current?.column !== column) return { column, direction: "asc" };
1661
1746
  return current.direction === "asc" ? { column, direction: "desc" } : void 0;
@@ -2054,10 +2139,17 @@ function DetailHost({
2054
2139
  }
2055
2140
  ) });
2056
2141
  }
2142
+ function withoutLock(definition) {
2143
+ if (definition.locked === void 0) return definition;
2144
+ const copy = { ...definition };
2145
+ delete copy.locked;
2146
+ return copy;
2147
+ }
2057
2148
  function DashboardInner({
2058
2149
  widgets,
2059
- layout = EMPTY_LAYOUT,
2150
+ layout: savedLayout = EMPTY_LAYOUT,
2060
2151
  onLayoutChange,
2152
+ overrideLocks = false,
2061
2153
  onRetry,
2062
2154
  loadDetail,
2063
2155
  onOpenDetail,
@@ -2066,7 +2158,10 @@ function DashboardInner({
2066
2158
  }) {
2067
2159
  const slot = useSlotClassName();
2068
2160
  const { labels } = useWidgetSettings();
2069
- const definitions = widgets.map((w) => w.definition);
2161
+ const definitions = widgets.map(
2162
+ (w) => overrideLocks ? withoutLock(w.definition) : w.definition
2163
+ );
2164
+ const layout = enforceLocks(definitions, savedLayout);
2070
2165
  const byKey = new Map(
2071
2166
  widgets.map((w) => [w.definition.key, w])
2072
2167
  );
@@ -2139,32 +2234,45 @@ function DashboardInner({
2139
2234
  if (widget === void 0) return null;
2140
2235
  const key = definition.key;
2141
2236
  const title = definition.title;
2237
+ const lock = resolveWidgetLock(definition);
2238
+ const canMoveEarlier = !lock.move && moveWidgetBy(definitions, layout, key, -1) !== layout;
2239
+ const canMoveLater = !lock.move && moveWidgetBy(definitions, layout, key, 1) !== layout;
2142
2240
  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(
2241
+ !lock.move && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2242
+ /* @__PURE__ */ jsxRuntime.jsx(
2243
+ "button",
2244
+ {
2245
+ type: "button",
2246
+ className: slot(
2247
+ "button",
2248
+ "dwt-button",
2249
+ "dwt-icon-button"
2250
+ ),
2251
+ "aria-label": labels.moveEarlier(title),
2252
+ title: labels.moveEarlier(title),
2253
+ disabled: index === 0 || !canMoveEarlier,
2254
+ onClick: () => change(moveWidgetBy(definitions, layout, key, -1)),
2255
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2191" })
2256
+ }
2257
+ ),
2258
+ /* @__PURE__ */ jsxRuntime.jsx(
2259
+ "button",
2260
+ {
2261
+ type: "button",
2262
+ className: slot(
2263
+ "button",
2264
+ "dwt-button",
2265
+ "dwt-icon-button"
2266
+ ),
2267
+ "aria-label": labels.moveLater(title),
2268
+ title: labels.moveLater(title),
2269
+ disabled: index === shown.length - 1 || !canMoveLater,
2270
+ onClick: () => change(moveWidgetBy(definitions, layout, key, 1)),
2271
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2193" })
2272
+ }
2273
+ )
2274
+ ] }),
2275
+ !lock.hide && /* @__PURE__ */ jsxRuntime.jsx(
2168
2276
  "button",
2169
2277
  {
2170
2278
  type: "button",
@@ -2188,7 +2296,7 @@ function DashboardInner({
2188
2296
  ...widthProps(twelve, definition),
2189
2297
  minimized: isMinimized(layout, key),
2190
2298
  ...actions === void 0 ? {} : { actions },
2191
- ...editable ? {
2299
+ ...editable && !lock.minimize ? {
2192
2300
  onToggleMinimized: () => change(toggleMinimized(layout, key))
2193
2301
  } : {},
2194
2302
  ...headingLevel === void 0 ? {} : { headingLevel },
@@ -2232,25 +2340,6 @@ function Dashboard({
2232
2340
  );
2233
2341
  }
2234
2342
 
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
2343
  // src/core/payload.ts
2255
2344
  var WIDGET_KINDS = [
2256
2345
  "TEXT",
@@ -2332,6 +2421,14 @@ function optionalNumber(record, field, path, problems) {
2332
2421
  requireNumber(record, field, path, problems);
2333
2422
  }
2334
2423
  }
2424
+ function optionalSortValue(record, path, problems) {
2425
+ const value = record["value"];
2426
+ if (value !== void 0 && typeof value !== "string" && !(typeof value === "number" && Number.isFinite(value))) {
2427
+ problems.push(
2428
+ `${path}.value must be a string or a finite number, got ${describeType2(value)}.`
2429
+ );
2430
+ }
2431
+ }
2335
2432
  function optionalBoolean(record, field, path, problems) {
2336
2433
  const value = record[field];
2337
2434
  if (value !== void 0 && typeof value !== "boolean") {
@@ -2435,6 +2532,7 @@ function checkPayloadFields(kind, data, problems) {
2435
2532
  }
2436
2533
  eachRecord(row, rowPath, problems, (cell, cellPath) => {
2437
2534
  requireString(cell, "text", cellPath, problems);
2535
+ optionalSortValue(cell, cellPath, problems);
2438
2536
  optionalUrl(
2439
2537
  cell,
2440
2538
  "href",