@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/core.cjs +605 -14
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +134 -3
- package/dist/core.d.ts +134 -3
- package/dist/core.js +583 -15
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +605 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +583 -15
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1202 -136
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +80 -3
- package/dist/react.d.ts +80 -3
- package/dist/react.js +1202 -136
- package/dist/react.js.map +1 -1
- package/dist/{scale-CrXxawQn.d.cts → scale-N69bo7wo.d.cts} +36 -1
- package/dist/{scale-CrXxawQn.d.ts → scale-N69bo7wo.d.ts} +36 -1
- package/dist/styles.css +173 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -632,21 +632,44 @@ function fillColumnSpans(items, columns, largeSpan = 2) {
|
|
|
632
632
|
function fillWidthSpans(items) {
|
|
633
633
|
return placeAndFill(items, WIDTH_COLUMNS, itemWidth);
|
|
634
634
|
}
|
|
635
|
+
function placeRows(items) {
|
|
636
|
+
return placeAndFillRows(items, WIDTH_COLUMNS, itemWidth).rows;
|
|
637
|
+
}
|
|
638
|
+
function fitCount(items, maxRows) {
|
|
639
|
+
const rows = placeRows(items);
|
|
640
|
+
let count = 0;
|
|
641
|
+
for (const row of rows.slice(0, Math.max(0, Math.floor(maxRows)))) {
|
|
642
|
+
count += row.keys.length;
|
|
643
|
+
}
|
|
644
|
+
return count;
|
|
645
|
+
}
|
|
635
646
|
function placeAndFill(items, columns, spanOf) {
|
|
647
|
+
return placeAndFillRows(items, columns, spanOf).spans;
|
|
648
|
+
}
|
|
649
|
+
function placeAndFillRows(items, columns, spanOf) {
|
|
636
650
|
const trackCount = Math.max(1, Math.floor(columns));
|
|
637
651
|
const spans = /* @__PURE__ */ new Map();
|
|
652
|
+
const rows = [];
|
|
638
653
|
let row = [];
|
|
639
654
|
let used = 0;
|
|
640
655
|
function closeRow() {
|
|
656
|
+
if (row.length === 0) return;
|
|
641
657
|
const fillers = row.filter(({ item }) => fillsWidth(item.fill));
|
|
642
658
|
const leftover = trackCount - used;
|
|
659
|
+
const finalSpans = /* @__PURE__ */ new Map();
|
|
643
660
|
if (fillers.length > 0) {
|
|
644
661
|
const share = Math.floor(leftover / fillers.length);
|
|
645
662
|
const remainder = leftover % fillers.length;
|
|
646
663
|
fillers.forEach(({ item, span }, index) => {
|
|
647
|
-
|
|
664
|
+
const grown = span + share + (index < remainder ? 1 : 0);
|
|
665
|
+
spans.set(item.key, grown);
|
|
666
|
+
finalSpans.set(item.key, grown);
|
|
648
667
|
});
|
|
649
668
|
}
|
|
669
|
+
rows.push({
|
|
670
|
+
keys: row.map(({ item }) => item.key),
|
|
671
|
+
spans: row.map(({ item, span }) => finalSpans.get(item.key) ?? span)
|
|
672
|
+
});
|
|
650
673
|
row = [];
|
|
651
674
|
used = 0;
|
|
652
675
|
}
|
|
@@ -657,10 +680,13 @@ function placeAndFill(items, columns, spanOf) {
|
|
|
657
680
|
used += span;
|
|
658
681
|
}
|
|
659
682
|
closeRow();
|
|
660
|
-
return spans;
|
|
683
|
+
return { spans, rows };
|
|
661
684
|
}
|
|
662
685
|
|
|
663
686
|
// src/core/layout.ts
|
|
687
|
+
var MAX_PAGE_TITLE_LENGTH = 30;
|
|
688
|
+
var MAX_PARSED_PAGES = 50;
|
|
689
|
+
var MAX_PAGE_ROWS = 20;
|
|
664
690
|
var EMPTY_LAYOUT = Object.freeze({
|
|
665
691
|
order: Object.freeze([]),
|
|
666
692
|
hidden: Object.freeze([]),
|
|
@@ -690,16 +716,66 @@ function parseLayout(raw) {
|
|
|
690
716
|
const order = uniqueStrings(record["order"]);
|
|
691
717
|
const hidden = uniqueStrings(record["hidden"]);
|
|
692
718
|
const minimized = uniqueStrings(record["minimized"]);
|
|
719
|
+
const pages = parsePages(record["pages"]);
|
|
720
|
+
if (pages.length > 0) {
|
|
721
|
+
return {
|
|
722
|
+
order: EMPTY_LAYOUT.order,
|
|
723
|
+
hidden: EMPTY_LAYOUT.hidden,
|
|
724
|
+
minimized: EMPTY_LAYOUT.minimized,
|
|
725
|
+
pages
|
|
726
|
+
};
|
|
727
|
+
}
|
|
693
728
|
if (order.length === 0 && hidden.length === 0 && minimized.length === 0) {
|
|
694
729
|
return EMPTY_LAYOUT;
|
|
695
730
|
}
|
|
696
731
|
return { order, hidden, minimized };
|
|
697
732
|
}
|
|
733
|
+
function parsePages(value) {
|
|
734
|
+
if (!Array.isArray(value)) return [];
|
|
735
|
+
const pages = [];
|
|
736
|
+
const keys = /* @__PURE__ */ new Set();
|
|
737
|
+
for (const entry of value.slice(0, MAX_PARSED_PAGES)) {
|
|
738
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
const record = entry;
|
|
742
|
+
const key = record["key"];
|
|
743
|
+
if (typeof key !== "string" || key === "" || keys.has(key)) continue;
|
|
744
|
+
keys.add(key);
|
|
745
|
+
const rawTitle = record["title"];
|
|
746
|
+
const title = typeof rawTitle === "string" && rawTitle.trim() !== "" ? rawTitle.trim().slice(0, MAX_PAGE_TITLE_LENGTH) : `Page ${pages.length + 1}`;
|
|
747
|
+
const maxRows = record["maxRows"];
|
|
748
|
+
const held = /* @__PURE__ */ new Set();
|
|
749
|
+
for (const earlier of pages) {
|
|
750
|
+
for (const k of [...earlier.order, ...earlier.hidden]) held.add(k);
|
|
751
|
+
}
|
|
752
|
+
const keep = (list) => uniqueStrings(list).filter((k) => !held.has(k));
|
|
753
|
+
pages.push({
|
|
754
|
+
key,
|
|
755
|
+
title,
|
|
756
|
+
order: keep(record["order"]),
|
|
757
|
+
hidden: keep(record["hidden"]),
|
|
758
|
+
minimized: keep(record["minimized"]),
|
|
759
|
+
...typeof maxRows === "number" && Number.isInteger(maxRows) && maxRows >= 1 && maxRows <= MAX_PAGE_ROWS ? { maxRows } : {}
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
return pages;
|
|
763
|
+
}
|
|
698
764
|
function serializeLayout(layout) {
|
|
699
765
|
return JSON.stringify({
|
|
700
766
|
order: [...layout.order],
|
|
701
767
|
hidden: [...layout.hidden],
|
|
702
|
-
minimized: [...layout.minimized]
|
|
768
|
+
minimized: [...layout.minimized],
|
|
769
|
+
...layout.pages === void 0 || layout.pages.length === 0 ? {} : {
|
|
770
|
+
pages: layout.pages.map((page) => ({
|
|
771
|
+
key: page.key,
|
|
772
|
+
title: page.title,
|
|
773
|
+
order: [...page.order],
|
|
774
|
+
hidden: [...page.hidden],
|
|
775
|
+
minimized: [...page.minimized],
|
|
776
|
+
...page.maxRows === void 0 ? {} : { maxRows: page.maxRows }
|
|
777
|
+
}))
|
|
778
|
+
}
|
|
703
779
|
});
|
|
704
780
|
}
|
|
705
781
|
function bySortOrder(items) {
|
|
@@ -822,13 +898,28 @@ function toggleMinimized(layout, key) {
|
|
|
822
898
|
}
|
|
823
899
|
function pruneLayout(definitions, layout) {
|
|
824
900
|
const known = new Set(definitions.map((d) => d.key));
|
|
825
|
-
const keep = (keys) => keys.filter((k) => known.has(k));
|
|
826
|
-
|
|
827
|
-
order: keep(
|
|
828
|
-
hidden: keep(
|
|
829
|
-
minimized: keep(
|
|
901
|
+
const keep = (keys) => keys.every((k) => known.has(k)) ? keys : keys.filter((k) => known.has(k));
|
|
902
|
+
return mapLayoutLists(layout, (lists) => ({
|
|
903
|
+
order: keep(lists.order),
|
|
904
|
+
hidden: keep(lists.hidden),
|
|
905
|
+
minimized: keep(lists.minimized)
|
|
906
|
+
}));
|
|
907
|
+
}
|
|
908
|
+
function mapLayoutLists(layout, change) {
|
|
909
|
+
const top = change(layout);
|
|
910
|
+
const pages = layout.pages?.map((page) => {
|
|
911
|
+
const next = change(page);
|
|
912
|
+
return next.order === page.order && next.hidden === page.hidden && next.minimized === page.minimized ? page : { ...page, ...next };
|
|
913
|
+
});
|
|
914
|
+
const pagesChanged = pages !== void 0 && pages.some((page, i) => page !== layout.pages?.[i]);
|
|
915
|
+
if (top.order === layout.order && top.hidden === layout.hidden && top.minimized === layout.minimized && !pagesChanged) {
|
|
916
|
+
return layout;
|
|
917
|
+
}
|
|
918
|
+
return {
|
|
919
|
+
...layout,
|
|
920
|
+
...top,
|
|
921
|
+
...pages === void 0 ? {} : { pages }
|
|
830
922
|
};
|
|
831
|
-
return next.order.length === layout.order.length && next.hidden.length === layout.hidden.length && next.minimized.length === layout.minimized.length ? layout : next;
|
|
832
923
|
}
|
|
833
924
|
function enforceLocks(definitions, layout, options = {}) {
|
|
834
925
|
if (options.overrideLocks === true) return layout;
|
|
@@ -841,11 +932,12 @@ function enforceLocks(definitions, layout, options = {}) {
|
|
|
841
932
|
if (lock.hide) noHide.add(definition.key);
|
|
842
933
|
if (lock.minimize) noMinimize.add(definition.key);
|
|
843
934
|
}
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
935
|
+
const without2 = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
|
|
936
|
+
return mapLayoutLists(layout, (lists) => ({
|
|
937
|
+
order: without2(lists.order, noMove),
|
|
938
|
+
hidden: without2(lists.hidden, noHide),
|
|
939
|
+
minimized: without2(lists.minimized, noMinimize)
|
|
940
|
+
}));
|
|
849
941
|
}
|
|
850
942
|
|
|
851
943
|
// src/core/validate.ts
|
|
@@ -1210,6 +1302,13 @@ function validateWidgetDefinition(value) {
|
|
|
1210
1302
|
checkDetailOption(value["detail"], problems);
|
|
1211
1303
|
checkTableControlsOption(value["tableControls"], value["kind"], problems);
|
|
1212
1304
|
checkLockedOption(value["locked"], problems);
|
|
1305
|
+
if (value["page"] !== void 0) {
|
|
1306
|
+
if (typeof value["page"] !== "string" || value["page"].trim() === "") {
|
|
1307
|
+
problems.push(
|
|
1308
|
+
`page must be a non-empty page key or title, got ${describeType2(value["page"])}.`
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1213
1312
|
if (value["fill"] !== void 0) {
|
|
1214
1313
|
requireOneOf(value, "fill", WIDGET_FILLS, "", problems);
|
|
1215
1314
|
}
|
|
@@ -1463,6 +1562,475 @@ function createWidgetLoader(options) {
|
|
|
1463
1562
|
};
|
|
1464
1563
|
}
|
|
1465
1564
|
|
|
1565
|
+
// src/core/pages.ts
|
|
1566
|
+
var DEFAULT_PAGE_KEY = "default";
|
|
1567
|
+
var DEFAULT_PAGE_ROWS = 4;
|
|
1568
|
+
function rowsAllowed(page, options) {
|
|
1569
|
+
const rows = page.maxRows ?? options.maxRows ?? DEFAULT_PAGE_ROWS;
|
|
1570
|
+
return Math.max(1, Math.min(MAX_PAGE_ROWS, Math.floor(rows)));
|
|
1571
|
+
}
|
|
1572
|
+
function hasPages(layout) {
|
|
1573
|
+
return layout.pages !== void 0 && layout.pages.length > 0;
|
|
1574
|
+
}
|
|
1575
|
+
function pageList(layout) {
|
|
1576
|
+
if (layout.pages !== void 0 && layout.pages.length > 0) {
|
|
1577
|
+
return layout.pages;
|
|
1578
|
+
}
|
|
1579
|
+
return [
|
|
1580
|
+
{
|
|
1581
|
+
key: DEFAULT_PAGE_KEY,
|
|
1582
|
+
title: "Page 1",
|
|
1583
|
+
order: layout.order,
|
|
1584
|
+
hidden: layout.hidden,
|
|
1585
|
+
minimized: layout.minimized
|
|
1586
|
+
}
|
|
1587
|
+
];
|
|
1588
|
+
}
|
|
1589
|
+
function withExplicitPages(layout) {
|
|
1590
|
+
if (hasPages(layout)) return layout;
|
|
1591
|
+
return {
|
|
1592
|
+
...layout,
|
|
1593
|
+
order: [],
|
|
1594
|
+
hidden: [],
|
|
1595
|
+
minimized: [],
|
|
1596
|
+
pages: pageList(layout)
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
function sameKeys(a, b) {
|
|
1600
|
+
return a === b || a.length === b.length && a.every((k, i) => k === b[i]);
|
|
1601
|
+
}
|
|
1602
|
+
function pageLayout(layout, pageKey) {
|
|
1603
|
+
if (!hasPages(layout))
|
|
1604
|
+
return pageKey === DEFAULT_PAGE_KEY ? layout : void 0;
|
|
1605
|
+
const page = layout.pages?.find(
|
|
1606
|
+
(p) => p.key === pageKey
|
|
1607
|
+
);
|
|
1608
|
+
if (page === void 0) return void 0;
|
|
1609
|
+
const { pages, ...rest } = layout;
|
|
1610
|
+
return {
|
|
1611
|
+
...rest,
|
|
1612
|
+
order: page.order,
|
|
1613
|
+
hidden: page.hidden,
|
|
1614
|
+
minimized: page.minimized
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
function withPageLayout(layout, pageKey, next) {
|
|
1618
|
+
const current = pageLayout(layout, pageKey);
|
|
1619
|
+
if (current === void 0 || sameKeys(current.order, next.order) && sameKeys(current.hidden, next.hidden) && sameKeys(current.minimized, next.minimized)) {
|
|
1620
|
+
return layout;
|
|
1621
|
+
}
|
|
1622
|
+
if (!hasPages(layout)) {
|
|
1623
|
+
return {
|
|
1624
|
+
...layout,
|
|
1625
|
+
order: next.order,
|
|
1626
|
+
hidden: next.hidden,
|
|
1627
|
+
minimized: next.minimized
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
return {
|
|
1631
|
+
...layout,
|
|
1632
|
+
pages: (layout.pages ?? []).map(
|
|
1633
|
+
(page) => page.key === pageKey ? {
|
|
1634
|
+
...page,
|
|
1635
|
+
order: next.order,
|
|
1636
|
+
hidden: next.hidden,
|
|
1637
|
+
minimized: next.minimized
|
|
1638
|
+
} : page
|
|
1639
|
+
)
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
function normalizedTitle(title) {
|
|
1643
|
+
return title.trim().normalize("NFKC").toLowerCase();
|
|
1644
|
+
}
|
|
1645
|
+
function resolvePageKey(layout, reference) {
|
|
1646
|
+
if (reference === void 0 || reference === "") return void 0;
|
|
1647
|
+
const pages = pageList(layout);
|
|
1648
|
+
const byKey = pages.find((p) => p.key === reference);
|
|
1649
|
+
if (byKey !== void 0) return byKey.key;
|
|
1650
|
+
const wanted = normalizedTitle(reference);
|
|
1651
|
+
return pages.find((p) => normalizedTitle(p.title) === wanted)?.key;
|
|
1652
|
+
}
|
|
1653
|
+
function homePageKey(layout, item) {
|
|
1654
|
+
return resolvePageKey(layout, item.page) ?? pageList(layout)[0].key;
|
|
1655
|
+
}
|
|
1656
|
+
function assignPages(definitions, layout) {
|
|
1657
|
+
const pages = pageList(layout);
|
|
1658
|
+
const assigned = new Map(pages.map((p) => [p.key, []]));
|
|
1659
|
+
for (const definition of definitions) {
|
|
1660
|
+
let pageKey;
|
|
1661
|
+
if (!resolveWidgetLock(definition).move) {
|
|
1662
|
+
pageKey = pages.find(
|
|
1663
|
+
(p) => p.order.includes(definition.key) || p.hidden.includes(definition.key)
|
|
1664
|
+
)?.key;
|
|
1665
|
+
}
|
|
1666
|
+
assigned.get(pageKey ?? homePageKey(layout, definition))?.push(definition);
|
|
1667
|
+
}
|
|
1668
|
+
return assigned;
|
|
1669
|
+
}
|
|
1670
|
+
function pageOf(definitions, layout, key) {
|
|
1671
|
+
for (const [pageKey, widgets] of assignPages(definitions, layout)) {
|
|
1672
|
+
if (widgets.some((w) => w.key === key)) return pageKey;
|
|
1673
|
+
}
|
|
1674
|
+
return void 0;
|
|
1675
|
+
}
|
|
1676
|
+
function pageWidgets(definitions, layout, pageKey) {
|
|
1677
|
+
const onPage = assignPages(definitions, layout).get(pageKey) ?? [];
|
|
1678
|
+
const view = pageLayout(layout, pageKey);
|
|
1679
|
+
return view === void 0 ? [] : visibleWidgets(onPage, view);
|
|
1680
|
+
}
|
|
1681
|
+
function pageRoom(definitions, layout, pageKey, options = {}) {
|
|
1682
|
+
const page = pageList(layout).find(
|
|
1683
|
+
(p) => p.key === pageKey
|
|
1684
|
+
);
|
|
1685
|
+
if (page === void 0) return void 0;
|
|
1686
|
+
const maxRows = rowsAllowed(page, options);
|
|
1687
|
+
const rowsUsed = placeRows(
|
|
1688
|
+
pageWidgets(definitions, layout, pageKey)
|
|
1689
|
+
).length;
|
|
1690
|
+
return { maxRows, rowsUsed, rowsFree: Math.max(0, maxRows - rowsUsed) };
|
|
1691
|
+
}
|
|
1692
|
+
function describePage(page) {
|
|
1693
|
+
return `Page "${page.title}"`;
|
|
1694
|
+
}
|
|
1695
|
+
function titleOf(item) {
|
|
1696
|
+
return item.title ?? item.key;
|
|
1697
|
+
}
|
|
1698
|
+
function checkTitle(layout, title, exceptKey) {
|
|
1699
|
+
const trimmed = title.trim();
|
|
1700
|
+
if (trimmed === "") return err("A page title must not be empty.");
|
|
1701
|
+
if (trimmed.length > MAX_PAGE_TITLE_LENGTH) {
|
|
1702
|
+
return err(
|
|
1703
|
+
`Page title "${trimmed}" is ${trimmed.length} characters; the most is ${MAX_PAGE_TITLE_LENGTH}.`
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
const wanted = normalizedTitle(trimmed);
|
|
1707
|
+
const clash = pageList(layout).find(
|
|
1708
|
+
(p) => p.key !== exceptKey && normalizedTitle(p.title) === wanted
|
|
1709
|
+
);
|
|
1710
|
+
if (clash !== void 0) {
|
|
1711
|
+
return err(`Another page is already called "${clash.title}".`);
|
|
1712
|
+
}
|
|
1713
|
+
return ok(trimmed);
|
|
1714
|
+
}
|
|
1715
|
+
function nextPageKey(layout) {
|
|
1716
|
+
const keys = new Set(pageList(layout).map((p) => p.key));
|
|
1717
|
+
let n = 1;
|
|
1718
|
+
while (keys.has(`page-${n}`)) n += 1;
|
|
1719
|
+
return `page-${n}`;
|
|
1720
|
+
}
|
|
1721
|
+
function nextPageTitle(layout) {
|
|
1722
|
+
const used = new Set(
|
|
1723
|
+
pageList(layout).map((p) => normalizedTitle(p.title))
|
|
1724
|
+
);
|
|
1725
|
+
let n = pageList(layout).length + 1;
|
|
1726
|
+
while (used.has(normalizedTitle(`Page ${n}`))) n += 1;
|
|
1727
|
+
return `Page ${n}`;
|
|
1728
|
+
}
|
|
1729
|
+
function addPage(layout, options = {}) {
|
|
1730
|
+
if (pageList(layout).length >= MAX_PARSED_PAGES) {
|
|
1731
|
+
return err(
|
|
1732
|
+
`The layout already has ${MAX_PARSED_PAGES} pages, the most it can keep.`
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
const key = options.key ?? nextPageKey(layout);
|
|
1736
|
+
if (pageList(layout).some((p) => p.key === key)) {
|
|
1737
|
+
return err(`A page with the key "${key}" already exists.`);
|
|
1738
|
+
}
|
|
1739
|
+
const title = checkTitle(
|
|
1740
|
+
layout,
|
|
1741
|
+
options.title ?? nextPageTitle(layout)
|
|
1742
|
+
);
|
|
1743
|
+
if (!title.ok) return title;
|
|
1744
|
+
const base = withExplicitPages(layout);
|
|
1745
|
+
const page = {
|
|
1746
|
+
key,
|
|
1747
|
+
title: title.value,
|
|
1748
|
+
order: [],
|
|
1749
|
+
hidden: [],
|
|
1750
|
+
minimized: []
|
|
1751
|
+
};
|
|
1752
|
+
return ok({ ...base, pages: [...base.pages ?? [], page] });
|
|
1753
|
+
}
|
|
1754
|
+
function renamePage(layout, pageKey, title) {
|
|
1755
|
+
const page = pageList(layout).find(
|
|
1756
|
+
(p) => p.key === pageKey
|
|
1757
|
+
);
|
|
1758
|
+
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
1759
|
+
const checked = checkTitle(layout, title, pageKey);
|
|
1760
|
+
if (!checked.ok) return checked;
|
|
1761
|
+
if (checked.value === page.title) return ok(layout);
|
|
1762
|
+
const base = withExplicitPages(layout);
|
|
1763
|
+
return ok({
|
|
1764
|
+
...base,
|
|
1765
|
+
pages: (base.pages ?? []).map(
|
|
1766
|
+
(p) => p.key === pageKey ? { ...p, title: checked.value } : p
|
|
1767
|
+
)
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
function movePage(layout, pageKey, offset) {
|
|
1771
|
+
const pages = layout.pages ?? [];
|
|
1772
|
+
const from = pages.findIndex((p) => p.key === pageKey);
|
|
1773
|
+
if (from === -1) return layout;
|
|
1774
|
+
const to = Math.max(
|
|
1775
|
+
0,
|
|
1776
|
+
Math.min(pages.length - 1, from + Math.trunc(offset))
|
|
1777
|
+
);
|
|
1778
|
+
if (to === from) return layout;
|
|
1779
|
+
const next = [...pages];
|
|
1780
|
+
const [moved] = next.splice(from, 1);
|
|
1781
|
+
next.splice(to, 0, moved);
|
|
1782
|
+
return { ...layout, pages: next };
|
|
1783
|
+
}
|
|
1784
|
+
function overflowing(definitions, layout, options) {
|
|
1785
|
+
return pageList(layout).find(
|
|
1786
|
+
(page) => placeRows(pageWidgets(definitions, layout, page.key)).length > rowsAllowed(page, options)
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
function removePage(definitions, layout, pageKey, options = {}) {
|
|
1790
|
+
const pages = pageList(layout);
|
|
1791
|
+
const index = pages.findIndex((p) => p.key === pageKey);
|
|
1792
|
+
const page = pages[index];
|
|
1793
|
+
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
1794
|
+
if (pages.length === 1) {
|
|
1795
|
+
return err(
|
|
1796
|
+
`${describePage(page)} is the only page, so it can't be removed.`
|
|
1797
|
+
);
|
|
1798
|
+
}
|
|
1799
|
+
const target = pages[index > 0 ? index - 1 : 1];
|
|
1800
|
+
const merged = {
|
|
1801
|
+
...layout,
|
|
1802
|
+
pages: pages.filter((p) => p.key !== pageKey).map(
|
|
1803
|
+
(p) => p.key === target.key ? {
|
|
1804
|
+
...p,
|
|
1805
|
+
order: index > 0 ? [...p.order, ...page.order] : [...page.order, ...p.order],
|
|
1806
|
+
hidden: [...p.hidden, ...page.hidden],
|
|
1807
|
+
minimized: [...p.minimized, ...page.minimized]
|
|
1808
|
+
} : p
|
|
1809
|
+
)
|
|
1810
|
+
};
|
|
1811
|
+
const full = overflowing(
|
|
1812
|
+
definitions,
|
|
1813
|
+
merged,
|
|
1814
|
+
options
|
|
1815
|
+
);
|
|
1816
|
+
if (full !== void 0) {
|
|
1817
|
+
return err(
|
|
1818
|
+
`${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.`
|
|
1819
|
+
);
|
|
1820
|
+
}
|
|
1821
|
+
return ok(merged);
|
|
1822
|
+
}
|
|
1823
|
+
function without(keys, key) {
|
|
1824
|
+
return keys.includes(key) ? keys.filter((k) => k !== key) : keys;
|
|
1825
|
+
}
|
|
1826
|
+
function moveWidgetToPage(definitions, layout, widgetKey, targetKey, options = {}) {
|
|
1827
|
+
const widget = definitions.find((d) => d.key === widgetKey);
|
|
1828
|
+
if (widget === void 0) {
|
|
1829
|
+
return err(
|
|
1830
|
+
`Widget "${widgetKey}" isn't one of the widgets, so it can't be moved to a page.`
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
const pages = pageList(layout);
|
|
1834
|
+
const target = pages.find((p) => p.key === targetKey);
|
|
1835
|
+
if (target === void 0) return err(`Page "${targetKey}" doesn't exist.`);
|
|
1836
|
+
const sourceKey = pageOf(definitions, layout, widgetKey);
|
|
1837
|
+
if (sourceKey === targetKey) return ok(layout);
|
|
1838
|
+
if (resolveWidgetLock(widget).move) {
|
|
1839
|
+
return err(
|
|
1840
|
+
`Widget "${titleOf(widget)}" is locked against moving, so it can't be moved to ${describePage(target)}.`
|
|
1841
|
+
);
|
|
1842
|
+
}
|
|
1843
|
+
const base = withExplicitPages(layout);
|
|
1844
|
+
const source = (base.pages ?? []).find(
|
|
1845
|
+
(p) => p.key === sourceKey
|
|
1846
|
+
);
|
|
1847
|
+
const hidden = source.hidden.includes(widgetKey);
|
|
1848
|
+
const minimized = source.minimized.includes(widgetKey);
|
|
1849
|
+
const targetOrder = pageWidgets(
|
|
1850
|
+
definitions,
|
|
1851
|
+
base,
|
|
1852
|
+
targetKey
|
|
1853
|
+
).map((d) => d.key);
|
|
1854
|
+
const moved = {
|
|
1855
|
+
...base,
|
|
1856
|
+
pages: (base.pages ?? []).map((p) => {
|
|
1857
|
+
if (p.key === sourceKey) {
|
|
1858
|
+
return {
|
|
1859
|
+
...p,
|
|
1860
|
+
order: without(p.order, widgetKey),
|
|
1861
|
+
hidden: without(p.hidden, widgetKey),
|
|
1862
|
+
minimized: without(p.minimized, widgetKey)
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
if (p.key === targetKey) {
|
|
1866
|
+
return {
|
|
1867
|
+
...p,
|
|
1868
|
+
order: hidden ? p.order : [...targetOrder, widgetKey],
|
|
1869
|
+
hidden: hidden ? [...p.hidden, widgetKey] : p.hidden,
|
|
1870
|
+
minimized: minimized ? [...p.minimized, widgetKey] : p.minimized
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
return p;
|
|
1874
|
+
})
|
|
1875
|
+
};
|
|
1876
|
+
if (!hidden) {
|
|
1877
|
+
const allowed = rowsAllowed(target, options);
|
|
1878
|
+
const needed = placeRows(
|
|
1879
|
+
pageWidgets(definitions, moved, targetKey)
|
|
1880
|
+
).length;
|
|
1881
|
+
if (needed > allowed) {
|
|
1882
|
+
return err(
|
|
1883
|
+
`${describePage(target)} has no room for "${titleOf(widget)}": it would need row ${needed}, and the page allows ${allowed}.`
|
|
1884
|
+
);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
return ok(moved);
|
|
1888
|
+
}
|
|
1889
|
+
function withoutLockField(definition) {
|
|
1890
|
+
if (definition.locked === void 0) return definition;
|
|
1891
|
+
const copy = { ...definition };
|
|
1892
|
+
delete copy.locked;
|
|
1893
|
+
return copy;
|
|
1894
|
+
}
|
|
1895
|
+
function uniqueTitle(taken, title, index) {
|
|
1896
|
+
let base = title.trim().slice(0, MAX_PAGE_TITLE_LENGTH);
|
|
1897
|
+
if (base === "") base = `Page ${index + 1}`;
|
|
1898
|
+
let candidate = base;
|
|
1899
|
+
let n = 1;
|
|
1900
|
+
while (taken.has(normalizedTitle(candidate))) {
|
|
1901
|
+
const suffix = ` (#${n})`;
|
|
1902
|
+
candidate = `${base.slice(0, MAX_PAGE_TITLE_LENGTH - suffix.length)}${suffix}`;
|
|
1903
|
+
n += 1;
|
|
1904
|
+
}
|
|
1905
|
+
taken.add(normalizedTitle(candidate));
|
|
1906
|
+
return candidate;
|
|
1907
|
+
}
|
|
1908
|
+
function samePages(a, b) {
|
|
1909
|
+
return a.length === b.length && a.every((p, i) => {
|
|
1910
|
+
const q = b[i];
|
|
1911
|
+
return p.key === q.key && p.title === q.title && p.maxRows === q.maxRows && sameKeys(p.order, q.order) && sameKeys(p.hidden, q.hidden) && sameKeys(p.minimized, q.minimized);
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
function normalizeLayout(definitions, layout, options = {}) {
|
|
1915
|
+
const known = pruneLayout(definitions, layout);
|
|
1916
|
+
const locked = enforceLocks(definitions, known, options);
|
|
1917
|
+
if (!hasPages(locked)) return locked === layout ? layout : locked;
|
|
1918
|
+
const defs = options.overrideLocks === true ? definitions.map(withoutLockField) : definitions;
|
|
1919
|
+
const taken = /* @__PURE__ */ new Set();
|
|
1920
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
1921
|
+
const held = /* @__PURE__ */ new Set();
|
|
1922
|
+
let pages = [];
|
|
1923
|
+
(locked.pages ?? []).forEach((page, index) => {
|
|
1924
|
+
if (seenKeys.has(page.key)) return;
|
|
1925
|
+
seenKeys.add(page.key);
|
|
1926
|
+
const keep = (list) => [
|
|
1927
|
+
...new Set(list.filter((k) => !held.has(k)))
|
|
1928
|
+
];
|
|
1929
|
+
const next = {
|
|
1930
|
+
...page,
|
|
1931
|
+
title: uniqueTitle(taken, page.title, index),
|
|
1932
|
+
order: keep(page.order),
|
|
1933
|
+
hidden: keep(page.hidden),
|
|
1934
|
+
minimized: keep(page.minimized)
|
|
1935
|
+
};
|
|
1936
|
+
for (const k of [...next.order, ...next.hidden]) held.add(k);
|
|
1937
|
+
pages.push(next);
|
|
1938
|
+
});
|
|
1939
|
+
const work = () => ({ ...locked, pages });
|
|
1940
|
+
const pinned = defs.filter((d) => resolveWidgetLock(d).move);
|
|
1941
|
+
for (const widget of pinned) {
|
|
1942
|
+
const home = homePageKey(work(), widget);
|
|
1943
|
+
const keysHere = (p, list) => p.key !== home && p[list].includes(widget.key);
|
|
1944
|
+
const wasHidden = pages.some((p) => keysHere(p, "hidden"));
|
|
1945
|
+
const wasMinimized = pages.some((p) => keysHere(p, "minimized"));
|
|
1946
|
+
if (!wasHidden && !wasMinimized) continue;
|
|
1947
|
+
pages = pages.map((p) => {
|
|
1948
|
+
let next = p;
|
|
1949
|
+
if (keysHere(p, "hidden")) {
|
|
1950
|
+
next = { ...next, hidden: without(next.hidden, widget.key) };
|
|
1951
|
+
}
|
|
1952
|
+
if (keysHere(p, "minimized")) {
|
|
1953
|
+
next = { ...next, minimized: without(next.minimized, widget.key) };
|
|
1954
|
+
}
|
|
1955
|
+
if (p.key === home) {
|
|
1956
|
+
next = {
|
|
1957
|
+
...next,
|
|
1958
|
+
hidden: wasHidden && !next.hidden.includes(widget.key) ? [...next.hidden, widget.key] : next.hidden,
|
|
1959
|
+
minimized: wasMinimized && !next.minimized.includes(widget.key) ? [...next.minimized, widget.key] : next.minimized
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
return next;
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
const placedKeys = /* @__PURE__ */ new Set();
|
|
1966
|
+
for (const p of pages) {
|
|
1967
|
+
for (const k of [...p.order, ...p.hidden]) placedKeys.add(k);
|
|
1968
|
+
}
|
|
1969
|
+
const pinnedKeys = new Set(pinned.map((d) => d.key));
|
|
1970
|
+
const unplaced = defs.filter((d) => !placedKeys.has(d.key) && !pinnedKeys.has(d.key)).map((definition, index) => ({ definition, index })).sort(
|
|
1971
|
+
(a, b) => (a.definition.sortOrder ?? 0) - (b.definition.sortOrder ?? 0) || a.index - b.index
|
|
1972
|
+
).map(({ definition }) => definition);
|
|
1973
|
+
for (const widget of unplaced) {
|
|
1974
|
+
const home = homePageKey(work(), widget);
|
|
1975
|
+
pages = pages.map(
|
|
1976
|
+
(p) => p.key === home ? { ...p, order: [...p.order, widget.key] } : p
|
|
1977
|
+
);
|
|
1978
|
+
}
|
|
1979
|
+
for (let i = 0; i < pages.length; i += 1) {
|
|
1980
|
+
const page = pages[i];
|
|
1981
|
+
const shown = pageWidgets(defs, work(), page.key);
|
|
1982
|
+
const fits = fitCount(shown, rowsAllowed(page, options));
|
|
1983
|
+
if (fits >= shown.length) continue;
|
|
1984
|
+
const moving = shown.slice(fits).filter((d) => !pinnedKeys.has(d.key)).map((d) => d.key);
|
|
1985
|
+
if (moving.length === 0) continue;
|
|
1986
|
+
const moveSet = new Set(moving);
|
|
1987
|
+
const movedMinimized = page.minimized.filter(
|
|
1988
|
+
(k) => moveSet.has(k)
|
|
1989
|
+
);
|
|
1990
|
+
pages = pages.map(
|
|
1991
|
+
(p) => p.key === page.key ? {
|
|
1992
|
+
...p,
|
|
1993
|
+
order: p.order.filter((k) => !moveSet.has(k)),
|
|
1994
|
+
minimized: p.minimized.filter((k) => !moveSet.has(k))
|
|
1995
|
+
} : p
|
|
1996
|
+
);
|
|
1997
|
+
let placed = false;
|
|
1998
|
+
for (let j = i + 1; j < pages.length && !placed; j += 1) {
|
|
1999
|
+
const candidate = pages[j];
|
|
2000
|
+
const trial = pages.map(
|
|
2001
|
+
(p) => p.key === candidate.key ? {
|
|
2002
|
+
...p,
|
|
2003
|
+
order: [...moving, ...p.order],
|
|
2004
|
+
minimized: [...p.minimized, ...movedMinimized]
|
|
2005
|
+
} : p
|
|
2006
|
+
);
|
|
2007
|
+
const items = pageWidgets(
|
|
2008
|
+
defs,
|
|
2009
|
+
{ ...locked, pages: trial },
|
|
2010
|
+
candidate.key
|
|
2011
|
+
);
|
|
2012
|
+
if (placeRows(items).length <= rowsAllowed(candidate, options)) {
|
|
2013
|
+
pages = trial;
|
|
2014
|
+
placed = true;
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
if (!placed) {
|
|
2018
|
+
const key = nextPageKey(work());
|
|
2019
|
+
const title = uniqueTitle(
|
|
2020
|
+
new Set(pages.map((p) => normalizedTitle(p.title))),
|
|
2021
|
+
`Page ${pages.length + 1}`,
|
|
2022
|
+
pages.length
|
|
2023
|
+
);
|
|
2024
|
+
pages = [
|
|
2025
|
+
...pages,
|
|
2026
|
+
{ key, title, order: moving, hidden: [], minimized: movedMinimized }
|
|
2027
|
+
];
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
const result = work();
|
|
2031
|
+
return samePages(layout.pages ?? [], pages) && sameKeys(layout.order, result.order) ? layout : result;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
1466
2034
|
// src/core/scale.ts
|
|
1467
2035
|
function niceStep(value) {
|
|
1468
2036
|
if (!(value > 0) || !Number.isFinite(value)) return 1;
|
|
@@ -1493,6 +2061,6 @@ function labelStride(count, maxLabels = 6) {
|
|
|
1493
2061
|
return Math.max(1, Math.ceil(count / Math.max(1, maxLabels)));
|
|
1494
2062
|
}
|
|
1495
2063
|
|
|
1496
|
-
export { DEFAULT_COMPARISON_LABEL, DEFAULT_CURRENCY, DEFAULT_DETAIL_PAGE_SIZE, DEFAULT_EMPTY_TEXT, DETAIL_MODES, EMPTY_LAYOUT, MAX_DETAIL_PAGE_SIZE, MAX_GRAPH_SERIES, MAX_REPORTED_PROBLEMS, MAX_WIDGET_WIDTH, MIN_WIDGET_WIDTH, VALUE_FORMATS, WIDGET_FILLS, WIDGET_KINDS, WIDGET_SIZES, WIDTH_COLUMNS, baseColumnSpan, cacheKeyFor, createWidgetLoader, defaultDetailQuery, defineWidget, deriveDetailData, describeUnknownError, emptyTextFor, emptyWidget, enforceLocks, err, failedWidget, fillColumnSpans, fillWidthSpans, fillsHeight, fillsWidth, formatCount, formatPercentChange, formatValue, fromLegacyDefinition, hiddenWidgets, hideWidget, isHidden, isMinimized, isSafeHref, isSafeImageUrl, isWidgetEmptyState, isWidgetKind, isWidgetVisibleTo, isWidgetWidth, itemWidth, kpiDelta, labelStride, loadingWidgets, minimizeWidget, moveWidget, moveWidgetBy, moveWidgetDown, moveWidgetUp, niceDomain, niceStep, ok, parseDetailQuery, parseLayout, percentChange, pruneLayout, queryRows, resolveDetailOptions, resolvePayload, resolveTableControls, resolveWidget, resolveWidgetLock, resolveWidgets, restoreWidget, serializeDetailQuery, serializeLayout, shareOf, showWidget, sortDefinitions, tableDetailData, toggleHidden, toggleMinimized, upgradeLegacyWidgetData, usesWidthColumns, validateDetailData, validateWidgetData, validateWidgetDefinition, visibleWidgets, widgetsFor, widthForSize };
|
|
2064
|
+
export { DEFAULT_COMPARISON_LABEL, DEFAULT_CURRENCY, DEFAULT_DETAIL_PAGE_SIZE, DEFAULT_EMPTY_TEXT, DEFAULT_PAGE_KEY, DEFAULT_PAGE_ROWS, DETAIL_MODES, EMPTY_LAYOUT, MAX_DETAIL_PAGE_SIZE, MAX_GRAPH_SERIES, MAX_PAGE_ROWS, MAX_PAGE_TITLE_LENGTH, MAX_PARSED_PAGES, MAX_REPORTED_PROBLEMS, MAX_WIDGET_WIDTH, MIN_WIDGET_WIDTH, VALUE_FORMATS, WIDGET_FILLS, WIDGET_KINDS, WIDGET_SIZES, WIDTH_COLUMNS, addPage, assignPages, baseColumnSpan, cacheKeyFor, createWidgetLoader, defaultDetailQuery, defineWidget, deriveDetailData, describeUnknownError, emptyTextFor, emptyWidget, enforceLocks, err, failedWidget, fillColumnSpans, fillWidthSpans, fillsHeight, fillsWidth, fitCount, formatCount, formatPercentChange, formatValue, fromLegacyDefinition, hasPages, hiddenWidgets, hideWidget, isHidden, isMinimized, isSafeHref, isSafeImageUrl, isWidgetEmptyState, isWidgetKind, isWidgetVisibleTo, isWidgetWidth, itemWidth, kpiDelta, labelStride, loadingWidgets, minimizeWidget, movePage, moveWidget, moveWidgetBy, moveWidgetDown, moveWidgetToPage, moveWidgetUp, nextPageKey, niceDomain, niceStep, normalizeLayout, ok, pageLayout, pageList, pageOf, pageRoom, pageWidgets, parseDetailQuery, parseLayout, percentChange, placeRows, pruneLayout, queryRows, removePage, renamePage, resolveDetailOptions, resolvePageKey, resolvePayload, resolveTableControls, resolveWidget, resolveWidgetLock, resolveWidgets, restoreWidget, serializeDetailQuery, serializeLayout, shareOf, showWidget, sortDefinitions, tableDetailData, toggleHidden, toggleMinimized, upgradeLegacyWidgetData, usesWidthColumns, validateDetailData, validateWidgetData, validateWidgetDefinition, visibleWidgets, widgetsFor, widthForSize, withPageLayout };
|
|
1497
2065
|
//# sourceMappingURL=index.js.map
|
|
1498
2066
|
//# sourceMappingURL=index.js.map
|