@richardmcquiston01/dashboard-widgets-toolkit 0.13.0 → 0.15.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 CHANGED
@@ -89,12 +89,14 @@ var WIDGET_FILLS = ["height", "width", "both"];
89
89
  var NOT_LOCKED = Object.freeze({
90
90
  move: false,
91
91
  hide: false,
92
- minimize: false
92
+ minimize: false,
93
+ options: false
93
94
  });
94
95
  var FULLY_LOCKED = Object.freeze({
95
96
  move: true,
96
97
  hide: true,
97
- minimize: true
98
+ minimize: true,
99
+ options: true
98
100
  });
99
101
  function resolveWidgetLock(definition) {
100
102
  const setting = definition.locked;
@@ -103,7 +105,8 @@ function resolveWidgetLock(definition) {
103
105
  return {
104
106
  move: setting.move === true,
105
107
  hide: setting.hide === true,
106
- minimize: setting.minimize === true
108
+ minimize: setting.minimize === true,
109
+ options: setting.options === true
107
110
  };
108
111
  }
109
112
  function defineWidget(definition) {
@@ -685,261 +688,261 @@ function placeAndFillRows(items, columns, spanOf) {
685
688
  return { spans, rows };
686
689
  }
687
690
 
688
- // src/core/layout.ts
689
- var MAX_PAGE_TITLE_LENGTH = 30;
690
- var MAX_PARSED_PAGES = 50;
691
- var MAX_PAGE_ROWS = 20;
692
- var EMPTY_LAYOUT = Object.freeze({
693
- order: Object.freeze([]),
694
- hidden: Object.freeze([]),
695
- minimized: Object.freeze([])
696
- });
697
- function uniqueStrings(value) {
698
- if (!Array.isArray(value)) return [];
699
- const seen = /* @__PURE__ */ new Set();
700
- for (const entry of value) {
701
- if (typeof entry === "string" && entry !== "") seen.add(entry);
702
- }
703
- return [...seen];
704
- }
705
- function parseLayout(raw) {
706
- let value = raw;
707
- if (typeof raw === "string") {
708
- try {
709
- value = JSON.parse(raw);
710
- } catch {
711
- return EMPTY_LAYOUT;
712
- }
713
- }
714
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
715
- return EMPTY_LAYOUT;
716
- }
717
- const record = value;
718
- const order = uniqueStrings(record["order"]);
719
- const hidden = uniqueStrings(record["hidden"]);
720
- const minimized = uniqueStrings(record["minimized"]);
721
- const pages = parsePages(record["pages"]);
722
- if (pages.length > 0) {
723
- return {
724
- order: EMPTY_LAYOUT.order,
725
- hidden: EMPTY_LAYOUT.hidden,
726
- minimized: EMPTY_LAYOUT.minimized,
727
- pages
728
- };
729
- }
730
- if (order.length === 0 && hidden.length === 0 && minimized.length === 0) {
731
- return EMPTY_LAYOUT;
732
- }
733
- return { order, hidden, minimized };
691
+ // src/core/views.ts
692
+ var BASE_VIEW_KEY = "base";
693
+ var MAX_WIDGET_VIEWS = 8;
694
+ var VIEW_CONVERSIONS = {
695
+ tableToBarList: { from: "TABLE", to: "BAR_LIST" },
696
+ tableToGraph: { from: "TABLE", to: "GRAPH" },
697
+ barListToTable: { from: "BAR_LIST", to: "TABLE" },
698
+ graphToTable: { from: "GRAPH", to: "TABLE" },
699
+ barListToGraph: { from: "BAR_LIST", to: "GRAPH" }
700
+ };
701
+ var KIND_LABELS = {
702
+ TEXT: "Text",
703
+ KPI: "Number",
704
+ GAUGE: "Gauge",
705
+ TABLE: "Table",
706
+ BAR_LIST: "Bars",
707
+ ALERT_LIST: "List",
708
+ GRAPH: "Chart"
709
+ };
710
+ function kindLabel(kind) {
711
+ return KIND_LABELS[kind];
734
712
  }
735
- function parsePages(value) {
736
- if (!Array.isArray(value)) return [];
737
- const pages = [];
738
- const keys = /* @__PURE__ */ new Set();
739
- for (const entry of value.slice(0, MAX_PARSED_PAGES)) {
740
- if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
741
- continue;
742
- }
743
- const record = entry;
744
- const key = record["key"];
745
- if (typeof key !== "string" || key === "" || keys.has(key)) continue;
746
- keys.add(key);
747
- const rawTitle = record["title"];
748
- const title = typeof rawTitle === "string" && rawTitle.trim() !== "" ? rawTitle.trim().slice(0, MAX_PAGE_TITLE_LENGTH) : `Page ${pages.length + 1}`;
749
- const maxRows = record["maxRows"];
750
- const held = /* @__PURE__ */ new Set();
751
- for (const earlier of pages) {
752
- for (const k of [...earlier.order, ...earlier.hidden]) held.add(k);
753
- }
754
- const keep = (list) => uniqueStrings(list).filter((k) => !held.has(k));
755
- pages.push({
756
- key,
757
- title,
758
- order: keep(record["order"]),
759
- hidden: keep(record["hidden"]),
760
- minimized: keep(record["minimized"]),
761
- ...typeof maxRows === "number" && Number.isInteger(maxRows) && maxRows >= 1 && maxRows <= MAX_PAGE_ROWS ? { maxRows } : {}
762
- });
763
- }
764
- return pages;
713
+ function hasViews(item) {
714
+ return item.views !== void 0 && item.views.length > 0;
765
715
  }
766
- function serializeLayout(layout) {
767
- return JSON.stringify({
768
- order: [...layout.order],
769
- hidden: [...layout.hidden],
770
- minimized: [...layout.minimized],
771
- ...layout.pages === void 0 || layout.pages.length === 0 ? {} : {
772
- pages: layout.pages.map((page) => ({
773
- key: page.key,
774
- title: page.title,
775
- order: [...page.order],
776
- hidden: [...page.hidden],
777
- minimized: [...page.minimized],
778
- ...page.maxRows === void 0 ? {} : { maxRows: page.maxRows }
779
- }))
780
- }
781
- });
716
+ function findView(item, key) {
717
+ return item.views?.find((view) => view.key === key);
782
718
  }
783
- function bySortOrder(items) {
784
- return items.map((item, index) => ({ item, index })).sort(
785
- (a, b) => (a.item.sortOrder ?? 0) - (b.item.sortOrder ?? 0) || a.index - b.index
786
- ).map(({ item }) => item);
719
+ function viewChoices(item) {
720
+ if (!hasViews(item)) return [];
721
+ return [
722
+ {
723
+ key: BASE_VIEW_KEY,
724
+ label: item.baseViewLabel ?? kindLabel(item.kind),
725
+ kind: item.kind
726
+ },
727
+ ...(item.views ?? []).map(({ key, label, kind }) => ({
728
+ key,
729
+ label,
730
+ kind
731
+ }))
732
+ ];
787
733
  }
788
- function isPinned(item) {
789
- return resolveWidgetLock(item).move;
734
+ function isKnownView(item, key) {
735
+ return key !== void 0 && (key === BASE_VIEW_KEY || findView(item, key) !== void 0);
790
736
  }
791
- function viewerOrder(visible, layout) {
792
- const byKey = new Map(visible.map((d) => [d.key, d]));
793
- const ordered = [];
794
- for (const key of layout.order) {
795
- const definition = byKey.get(key);
796
- if (definition !== void 0) {
797
- ordered.push(definition);
798
- byKey.delete(key);
799
- }
800
- }
801
- for (const definition of bySortOrder(visible)) {
802
- if (byKey.has(definition.key)) ordered.push(definition);
803
- }
804
- return ordered;
737
+ function effectiveViewKey(item, chosen) {
738
+ if (!hasViews(item)) return BASE_VIEW_KEY;
739
+ if (isKnownView(item, chosen)) return chosen;
740
+ if (isKnownView(item, item.defaultView)) return item.defaultView;
741
+ return BASE_VIEW_KEY;
805
742
  }
806
- function placeAroundPinned(visible, free) {
807
- const slots = bySortOrder(visible).map(
808
- (item) => isPinned(item) ? item : void 0
809
- );
810
- let next = 0;
811
- return slots.map((pinned) => pinned ?? free[next++]);
743
+ function viewKind(item, chosen) {
744
+ return findView(item, effectiveViewKey(item, chosen))?.kind ?? item.kind;
812
745
  }
813
- function visibleWidgets(definitions, layout) {
814
- const hidden = new Set(layout.hidden);
815
- const visible = definitions.filter((d) => !hidden.has(d.key));
816
- const ordered = viewerOrder(visible, layout);
817
- if (!visible.some(isPinned)) return ordered;
818
- return placeAroundPinned(
819
- visible,
820
- ordered.filter((item) => !isPinned(item))
821
- );
746
+ function cellValue2(cell) {
747
+ const value = cell?.value;
748
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
822
749
  }
823
- function hiddenWidgets(definitions, layout) {
824
- const hidden = new Set(layout.hidden);
825
- return bySortOrder(definitions.filter((d) => hidden.has(d.key)));
750
+ function columnName(table, index) {
751
+ return table.columns[index]?.label ?? `#${index + 1}`;
826
752
  }
827
- function moveWidget(definitions, layout, key, toIndex) {
828
- const current = visibleWidgets(definitions, layout);
829
- const fromIndex = current.findIndex((d) => d.key === key);
830
- const mover = current[fromIndex];
831
- if (mover === void 0 || !Number.isInteger(toIndex) || toIndex < 0 || toIndex >= current.length || toIndex === fromIndex || isPinned(mover) || isPinned(current[toIndex])) {
832
- return layout;
753
+ function checkColumn(table, index, field, context) {
754
+ if (!Number.isInteger(index) || index < 0 || index >= table.columns.length) {
755
+ return `${context.where}: ${field} column ${index} does not exist (the table has ${table.columns.length} columns, numbered from 0).`;
833
756
  }
834
- const freeSlots = freeSlotIndexes(current);
835
- const free = current.filter((item) => !isPinned(item));
836
- const fromFree = freeSlots.indexOf(fromIndex);
837
- const toFree = freeSlots.indexOf(toIndex);
838
- free.splice(fromFree, 1);
839
- free.splice(toFree, 0, mover);
840
- const next = placeAroundPinned(current, free);
841
- return { ...layout, order: next.map((d) => d.key) };
757
+ return void 0;
842
758
  }
843
- function freeSlotIndexes(ordered) {
844
- const slots = [];
845
- ordered.forEach((item, index) => {
846
- if (!isPinned(item)) slots.push(index);
847
- });
848
- return slots;
759
+ function numericColumn(table, index, context) {
760
+ const numbers = [];
761
+ for (const [rowIndex, row] of table.rows.entries()) {
762
+ const value = cellValue2(row[index]);
763
+ if (value === void 0) {
764
+ return err(
765
+ `${context.where}: row ${rowIndex + 1}, column ${index + 1} ("${columnName(table, index)}") has no numeric value. Add value to the cells.`
766
+ );
767
+ }
768
+ numbers.push(value);
769
+ }
770
+ return ok(numbers);
771
+ }
772
+ function tableToBarList(table, conversion, context) {
773
+ const bad = checkColumn(table, conversion.label, "label", context) ?? checkColumn(table, conversion.value, "value", context);
774
+ if (bad !== void 0) return err(bad);
775
+ const numbers = numericColumn(table, conversion.value, context);
776
+ if (!numbers.ok) return numbers;
777
+ const items = table.rows.map((row, index) => ({
778
+ label: row[conversion.label]?.text ?? "",
779
+ value: numbers.value[index],
780
+ display: row[conversion.value]?.text ?? String(numbers.value[index])
781
+ }));
782
+ return ok({ kind: "BAR_LIST", items });
849
783
  }
850
- function moveWidgetBy(definitions, layout, key, offset) {
851
- const current = visibleWidgets(definitions, layout);
852
- const fromIndex = current.findIndex((d) => d.key === key);
853
- if (fromIndex === -1) return layout;
854
- const freeSlots = freeSlotIndexes(current);
855
- const fromFree = freeSlots.indexOf(fromIndex);
856
- if (fromFree === -1) return layout;
857
- const toFree = Math.max(
858
- 0,
859
- Math.min(freeSlots.length - 1, fromFree + Math.trunc(offset))
784
+ function tableToGraph(table, conversion, context) {
785
+ if (conversion.values.length === 0 || conversion.values.length > MAX_GRAPH_SERIES) {
786
+ return err(
787
+ `${context.where}: a chart needs 1 to ${MAX_GRAPH_SERIES} series columns, got ${conversion.values.length}.`
788
+ );
789
+ }
790
+ let bad = checkColumn(
791
+ table,
792
+ conversion.label,
793
+ "label",
794
+ context
860
795
  );
861
- return moveWidget(definitions, layout, key, freeSlots[toFree]);
862
- }
863
- function moveWidgetUp(definitions, layout, key) {
864
- return moveWidgetBy(definitions, layout, key, -1);
865
- }
866
- function moveWidgetDown(definitions, layout, key) {
867
- return moveWidgetBy(definitions, layout, key, 1);
868
- }
869
- function isHidden(layout, key) {
870
- return layout.hidden.includes(key);
871
- }
872
- function isMinimized(layout, key) {
873
- return layout.minimized.includes(key);
796
+ for (const index of conversion.values) {
797
+ bad ??= checkColumn(table, index, "series", context);
798
+ }
799
+ if (bad !== void 0) return err(bad);
800
+ const series = [];
801
+ for (const index of conversion.values) {
802
+ const numbers = numericColumn(table, index, context);
803
+ if (!numbers.ok) return numbers;
804
+ series.push({
805
+ name: columnName(table, index),
806
+ points: table.rows.map((row, rowIndex) => ({
807
+ label: row[conversion.label]?.text ?? "",
808
+ value: numbers.value[rowIndex]
809
+ }))
810
+ });
811
+ }
812
+ return ok({
813
+ kind: "GRAPH",
814
+ series,
815
+ chartType: conversion.chartType,
816
+ valueFormat: conversion.valueFormat ?? "number",
817
+ ...conversion.currency === void 0 ? {} : { currency: conversion.currency },
818
+ xLabel: columnName(table, conversion.label)
819
+ });
874
820
  }
875
- function hideWidget(layout, key) {
876
- if (isHidden(layout, key)) return layout;
877
- return { ...layout, hidden: [...layout.hidden, key] };
821
+ function barListToTable(list, conversion) {
822
+ return ok({
823
+ kind: "TABLE",
824
+ columns: [
825
+ { label: conversion.labelHeading ?? "Name" },
826
+ { label: conversion.valueHeading ?? "Value", numeric: true }
827
+ ],
828
+ rows: list.items.map((item) => [
829
+ { text: item.label },
830
+ { text: item.display ?? String(item.value), value: item.value }
831
+ ])
832
+ });
878
833
  }
879
- function showWidget(layout, key) {
880
- if (!isHidden(layout, key)) return layout;
881
- return {
882
- ...layout,
883
- hidden: layout.hidden.filter((k) => k !== key),
884
- minimized: layout.minimized.filter((k) => k !== key)
834
+ function graphToTable(graph, context) {
835
+ const first = graph.series[0];
836
+ if (first === void 0)
837
+ return err(`${context.where}: the chart has no series.`);
838
+ const format = {
839
+ ...graph.currency === void 0 ? {} : { currency: graph.currency },
840
+ ...context.locale === void 0 ? {} : { locale: context.locale }
885
841
  };
842
+ return ok({
843
+ kind: "TABLE",
844
+ columns: [
845
+ { label: graph.xLabel ?? "Label" },
846
+ ...graph.series.map((series) => ({ label: series.name, numeric: true }))
847
+ ],
848
+ rows: first.points.map((point, index) => [
849
+ { text: point.label },
850
+ ...graph.series.map((series) => {
851
+ const value = series.points[index]?.value;
852
+ return value === void 0 ? { text: "\u2014" } : { text: formatValue(value, graph.valueFormat, format), value };
853
+ })
854
+ ])
855
+ });
886
856
  }
887
- function toggleHidden(layout, key) {
888
- return isHidden(layout, key) ? showWidget(layout, key) : hideWidget(layout, key);
889
- }
890
- function minimizeWidget(layout, key) {
891
- if (isMinimized(layout, key)) return layout;
892
- return { ...layout, minimized: [...layout.minimized, key] };
893
- }
894
- function restoreWidget(layout, key) {
895
- if (!isMinimized(layout, key)) return layout;
896
- return { ...layout, minimized: layout.minimized.filter((k) => k !== key) };
897
- }
898
- function toggleMinimized(layout, key) {
899
- return isMinimized(layout, key) ? restoreWidget(layout, key) : minimizeWidget(layout, key);
900
- }
901
- function pruneLayout(definitions, layout) {
902
- const known = new Set(definitions.map((d) => d.key));
903
- const keep = (keys) => keys.every((k) => known.has(k)) ? keys : keys.filter((k) => known.has(k));
904
- return mapLayoutLists(layout, (lists) => ({
905
- order: keep(lists.order),
906
- hidden: keep(lists.hidden),
907
- minimized: keep(lists.minimized)
908
- }));
909
- }
910
- function mapLayoutLists(layout, change) {
911
- const top = change(layout);
912
- const pages = layout.pages?.map((page) => {
913
- const next = change(page);
914
- return next.order === page.order && next.hidden === page.hidden && next.minimized === page.minimized ? page : { ...page, ...next };
857
+ function barListToGraph(list, conversion) {
858
+ return ok({
859
+ kind: "GRAPH",
860
+ series: [
861
+ {
862
+ name: conversion.seriesName ?? "Value",
863
+ points: list.items.map((item) => ({
864
+ label: item.label,
865
+ value: item.value
866
+ }))
867
+ }
868
+ ],
869
+ chartType: conversion.chartType,
870
+ valueFormat: conversion.valueFormat ?? "number",
871
+ ...conversion.currency === void 0 ? {} : { currency: conversion.currency }
915
872
  });
916
- const pagesChanged = pages !== void 0 && pages.some((page, i) => page !== layout.pages?.[i]);
917
- if (top.order === layout.order && top.hidden === layout.hidden && top.minimized === layout.minimized && !pagesChanged) {
918
- return layout;
873
+ }
874
+ function convertView(widgetKey, view, data, options = {}) {
875
+ const conversion = view.convert;
876
+ const context = {
877
+ where: `Widget ${JSON.stringify(widgetKey)} view ${JSON.stringify(view.key)}`,
878
+ ...options.locale === void 0 ? {} : { locale: options.locale }
879
+ };
880
+ const expected = VIEW_CONVERSIONS[conversion.type]?.from;
881
+ if (expected === void 0) {
882
+ return err(
883
+ `${context.where}: unknown conversion type ${JSON.stringify(conversion.type)}.`
884
+ );
919
885
  }
920
- return {
921
- ...layout,
922
- ...top,
923
- ...pages === void 0 ? {} : { pages }
886
+ if (data.kind !== expected) {
887
+ return err(
888
+ `${context.where}: ${conversion.type} reads a ${expected} payload, but the widget returned ${data.kind}.`
889
+ );
890
+ }
891
+ switch (conversion.type) {
892
+ case "tableToBarList":
893
+ return tableToBarList(data, conversion, context);
894
+ case "tableToGraph":
895
+ return tableToGraph(data, conversion, context);
896
+ case "barListToTable":
897
+ return barListToTable(data, conversion);
898
+ case "graphToTable":
899
+ return graphToTable(data, context);
900
+ case "barListToGraph":
901
+ return barListToGraph(data, conversion);
902
+ }
903
+ }
904
+ function viewNote(view, data) {
905
+ if (data?.kind !== "TABLE") return "";
906
+ const conversion = view.convert;
907
+ let indexes;
908
+ if (conversion.type === "tableToBarList") {
909
+ indexes = [conversion.label, conversion.value];
910
+ } else if (conversion.type === "tableToGraph") {
911
+ indexes = [conversion.label, ...conversion.values];
912
+ } else {
913
+ return "";
914
+ }
915
+ const names = indexes.map(
916
+ (index) => data.columns[index]?.label ?? `#${index + 1}`
917
+ );
918
+ return `Shows ${names.join(", ")} only.`;
919
+ }
920
+ function applyView(widget, chosen, options = {}) {
921
+ const key = effectiveViewKey(widget.definition, chosen);
922
+ const view = findView(widget.definition, key);
923
+ if (view === void 0) return widget;
924
+ const definition = {
925
+ ...widget.definition,
926
+ kind: view.kind
924
927
  };
925
- }
926
- function enforceLocks(definitions, layout, options = {}) {
927
- if (options.overrideLocks === true) return layout;
928
- const noMove = /* @__PURE__ */ new Set();
929
- const noHide = /* @__PURE__ */ new Set();
930
- const noMinimize = /* @__PURE__ */ new Set();
931
- for (const definition of definitions) {
932
- const lock = resolveWidgetLock(definition);
933
- if (lock.move) noMove.add(definition.key);
934
- if (lock.hide) noHide.add(definition.key);
935
- if (lock.minimize) noMinimize.add(definition.key);
928
+ if (widget.status === "loading") return { ...widget, definition };
929
+ if (widget.status === "empty") return { ...widget, definition };
930
+ if (widget.status === "error") return { ...widget, definition };
931
+ const converted = convertView(
932
+ widget.definition.key,
933
+ view,
934
+ widget.data,
935
+ options
936
+ );
937
+ if (!converted.ok) {
938
+ return {
939
+ definition,
940
+ status: "error",
941
+ error: converted.error,
942
+ ...widget.options === void 0 ? {} : { options: widget.options }
943
+ };
936
944
  }
937
- const without2 = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
938
- return mapLayoutLists(layout, (lists) => ({
939
- order: without2(lists.order, noMove),
940
- hidden: without2(lists.hidden, noHide),
941
- minimized: without2(lists.minimized, noMinimize)
942
- }));
945
+ return { ...widget, definition, data: converted.value };
943
946
  }
944
947
 
945
948
  // src/core/options.ts
@@ -1116,107 +1119,644 @@ function coerceOptionValue(option, value, context = {}) {
1116
1119
  }
1117
1120
  }
1118
1121
  }
1119
- function defaultOptionValue(option, context = {}) {
1120
- switch (option.type) {
1121
- case "dateRange":
1122
- return resolveDateRange(option.default, context);
1123
- case "sort":
1124
- return option.default;
1125
- case "columns":
1126
- return [...option.default];
1127
- default:
1128
- return option.default;
1129
- }
1122
+ function defaultOptionValue(option, context = {}) {
1123
+ switch (option.type) {
1124
+ case "dateRange":
1125
+ return resolveDateRange(option.default, context);
1126
+ case "sort":
1127
+ return option.default;
1128
+ case "columns":
1129
+ return [...option.default];
1130
+ default:
1131
+ return option.default;
1132
+ }
1133
+ }
1134
+ var NO_VALUES = Object.freeze({});
1135
+ function resolveOptionValues(definition, chosen, context = {}) {
1136
+ const options = definition.options;
1137
+ if (options === void 0 || options.length === 0) return NO_VALUES;
1138
+ const values = {};
1139
+ for (const option of options) {
1140
+ const picked = chosen?.[option.key];
1141
+ const value = (picked === void 0 ? void 0 : coerceOptionValue(option, picked, context)) ?? defaultOptionValue(option, context);
1142
+ if (value !== void 0) values[option.key] = value;
1143
+ }
1144
+ return values;
1145
+ }
1146
+ function sameValue(a, b) {
1147
+ if (Array.isArray(a) && Array.isArray(b)) {
1148
+ return a.length === b.length && a.every((item, i) => item === b[i]);
1149
+ }
1150
+ return a === b;
1151
+ }
1152
+ function sameOptionValues(a, b) {
1153
+ const keys = Object.keys({ ...a, ...b });
1154
+ return keys.every((key) => sameValue(a[key], b[key]));
1155
+ }
1156
+ function optionsCacheSuffix(definition, values, context = {}) {
1157
+ const parts = [];
1158
+ for (const option of definition.options ?? []) {
1159
+ if (isClientApplied(option)) continue;
1160
+ const value = values[option.key];
1161
+ if (sameValue(value, defaultOptionValue(option, context))) continue;
1162
+ parts.push(
1163
+ `${option.key}=${encodeURIComponent(JSON.stringify(value ?? null))}`
1164
+ );
1165
+ }
1166
+ parts.sort();
1167
+ return parts.length === 0 ? "" : `?${parts.join("&")}`;
1168
+ }
1169
+ function tableColumnIndex(key) {
1170
+ const match = /^c(\d+)$/.exec(key);
1171
+ return match === null ? void 0 : Number(match[1]);
1172
+ }
1173
+ function sortBarItems(items, sort) {
1174
+ const sign = sort.direction === "desc" ? -1 : 1;
1175
+ return items.map((item, index) => ({ item, index })).sort((a, b) => {
1176
+ const order = sort.column === "value" ? a.item.value - b.item.value : a.item.label.localeCompare(b.item.label);
1177
+ return order * sign || a.index - b.index;
1178
+ }).map(({ item }) => item);
1179
+ }
1180
+ function applyClientOptions(definition, data, values) {
1181
+ const options = definition.options ?? [];
1182
+ const sortOption = options.find(
1183
+ (o) => o.type === "sort" && o.apply === "client"
1184
+ );
1185
+ const columnsOption = options.find(
1186
+ (o) => o.type === "columns" && o.apply === "client"
1187
+ );
1188
+ const sortValue = sortOption === void 0 ? void 0 : values[sortOption.key];
1189
+ const columnsValue = columnsOption === void 0 ? void 0 : values[columnsOption.key];
1190
+ let next = data;
1191
+ if (typeof sortValue === "string") {
1192
+ const sort = parseSortValue(sortValue);
1193
+ if (sort !== void 0 && next.kind === "BAR_LIST") {
1194
+ if (sort.column === "label" || sort.column === "value") {
1195
+ next = { ...next, items: sortBarItems(next.items, sort) };
1196
+ }
1197
+ } else if (sort !== void 0 && next.kind === "TABLE") {
1198
+ const index = tableColumnIndex(sort.column);
1199
+ if (index !== void 0 && index < next.columns.length) {
1200
+ const sorted = queryRows(tableDetailData(next), {
1201
+ page: 1,
1202
+ pageSize: Math.max(1, next.rows.length),
1203
+ sort
1204
+ });
1205
+ if (sorted.ok) next = { ...next, rows: sorted.value.rows };
1206
+ }
1207
+ }
1208
+ }
1209
+ if (Array.isArray(columnsValue) && next.kind === "TABLE") {
1210
+ const table = next;
1211
+ const indexes = columnsValue.map(tableColumnIndex).filter(
1212
+ (index) => index !== void 0 && index < table.columns.length
1213
+ );
1214
+ if (indexes.length > 0) {
1215
+ next = {
1216
+ ...table,
1217
+ columns: indexes.map((i) => table.columns[i]),
1218
+ rows: table.rows.map((row) => indexes.map((i) => row[i]))
1219
+ };
1220
+ }
1221
+ }
1222
+ return next;
1223
+ }
1224
+
1225
+ // src/core/settings.ts
1226
+ var MAX_WIDGET_TITLE_LENGTH = 80;
1227
+ var MAX_PARSED_SETTINGS = 500;
1228
+ var MAX_COLUMN_LIST = 100;
1229
+ var MAX_VALUE_LENGTH = 500;
1230
+ function isRecord(value) {
1231
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1232
+ }
1233
+ function parseOptionValue(value) {
1234
+ if (typeof value === "string") {
1235
+ return value.length <= MAX_VALUE_LENGTH ? value : void 0;
1236
+ }
1237
+ if (typeof value === "number")
1238
+ return Number.isFinite(value) ? value : void 0;
1239
+ if (typeof value === "boolean") return value;
1240
+ if (Array.isArray(value) && value.length <= MAX_COLUMN_LIST) {
1241
+ return value.every(
1242
+ (entry) => typeof entry === "string" && entry.length <= MAX_VALUE_LENGTH
1243
+ ) ? value : void 0;
1244
+ }
1245
+ return void 0;
1246
+ }
1247
+ function parseOne(raw) {
1248
+ if (!isRecord(raw)) return void 0;
1249
+ const title = raw["title"];
1250
+ const width = raw["width"];
1251
+ const flexible = raw["flexible"];
1252
+ const options = raw["options"];
1253
+ const view = raw["view"];
1254
+ const cleanTitle = typeof title === "string" ? title.trim().slice(0, MAX_WIDGET_TITLE_LENGTH) : "";
1255
+ const chosen = {};
1256
+ if (isRecord(options)) {
1257
+ for (const [key, value] of Object.entries(options)) {
1258
+ const parsed = parseOptionValue(value);
1259
+ if (parsed !== void 0) chosen[key] = parsed;
1260
+ }
1261
+ }
1262
+ const settings = {
1263
+ ...cleanTitle === "" ? {} : { title: cleanTitle },
1264
+ ...isWidgetWidth(width) ? { width } : {},
1265
+ ...typeof flexible === "boolean" ? { flexible } : {},
1266
+ ...Object.keys(chosen).length === 0 ? {} : { options: chosen },
1267
+ ...typeof view === "string" && view !== "" && view.length <= 80 ? { view } : {}
1268
+ };
1269
+ return Object.keys(settings).length === 0 ? void 0 : settings;
1270
+ }
1271
+ function parseSettings(raw) {
1272
+ if (!isRecord(raw)) return void 0;
1273
+ const parsed = {};
1274
+ for (const [key, value] of Object.entries(raw).slice(
1275
+ 0,
1276
+ MAX_PARSED_SETTINGS
1277
+ )) {
1278
+ if (key === "") continue;
1279
+ const one = parseOne(value);
1280
+ if (one !== void 0) parsed[key] = one;
1281
+ }
1282
+ return Object.keys(parsed).length === 0 ? void 0 : parsed;
1283
+ }
1284
+ function serializeSettings(settings) {
1285
+ if (settings === void 0) return void 0;
1286
+ const out = {};
1287
+ for (const key of Object.keys(settings)) {
1288
+ const one = settings[key];
1289
+ if (one === void 0) continue;
1290
+ const piece = {
1291
+ ...one.title === void 0 ? {} : { title: one.title },
1292
+ ...one.width === void 0 ? {} : { width: one.width },
1293
+ ...one.flexible === void 0 ? {} : { flexible: one.flexible },
1294
+ ...one.options === void 0 || Object.keys(one.options).length === 0 ? {} : { options: { ...one.options } },
1295
+ ...one.view === void 0 ? {} : { view: one.view }
1296
+ };
1297
+ if (Object.keys(piece).length > 0) out[key] = piece;
1298
+ }
1299
+ return Object.keys(out).length === 0 ? void 0 : out;
1300
+ }
1301
+ function widthRange(item) {
1302
+ const min = isWidgetWidth(item.minWidth) ? item.minWidth : MIN_WIDGET_WIDTH;
1303
+ const max = isWidgetWidth(item.maxWidth) ? item.maxWidth : MAX_WIDGET_WIDTH;
1304
+ return { min, max: Math.max(min, max) };
1305
+ }
1306
+ var WIDTH_SHARES = {
1307
+ 2: "1/6",
1308
+ 3: "1/4",
1309
+ 4: "1/3",
1310
+ 5: "5/12",
1311
+ 6: "1/2",
1312
+ 7: "7/12",
1313
+ 8: "2/3",
1314
+ 9: "3/4",
1315
+ 10: "5/6",
1316
+ 11: "11/12",
1317
+ 12: "Full"
1318
+ };
1319
+ function widthShareLabel(width) {
1320
+ return WIDTH_SHARES[width] ?? `${width}/12`;
1321
+ }
1322
+ function isOptionsLocked(item) {
1323
+ return resolveWidgetLock(item).options;
1324
+ }
1325
+ function fillWith(fill, flexible) {
1326
+ const heightToo = fill === "height" || fill === "both";
1327
+ if (flexible) return heightToo ? "both" : "width";
1328
+ return heightToo ? "height" : void 0;
1329
+ }
1330
+ function isFlexible(item, settings) {
1331
+ if (settings?.flexible !== void 0) return settings.flexible;
1332
+ return item.fill === "width" || item.fill === "both";
1333
+ }
1334
+ function applySettings(definition, settings) {
1335
+ if (settings === void 0) return definition;
1336
+ const range = widthRange(definition);
1337
+ const width = settings.width === void 0 ? void 0 : Math.min(range.max, Math.max(range.min, settings.width));
1338
+ const title = settings.title === void 0 || settings.title.trim() === "" ? void 0 : settings.title;
1339
+ const flexibleChanged = settings.flexible !== void 0 && settings.flexible !== isFlexible(definition, void 0);
1340
+ if (title === void 0 && width === void 0 && !flexibleChanged) {
1341
+ return definition;
1342
+ }
1343
+ const next = { ...definition };
1344
+ if (title !== void 0) next.title = title;
1345
+ if (width !== void 0) next.width = width;
1346
+ if (flexibleChanged) {
1347
+ const fill = fillWith(
1348
+ definition.fill,
1349
+ settings.flexible === true
1350
+ );
1351
+ if (fill === void 0) {
1352
+ delete next.fill;
1353
+ } else {
1354
+ next.fill = fill;
1355
+ }
1356
+ }
1357
+ return next;
1358
+ }
1359
+ function effectiveDefinitions(definitions, layout, options = {}) {
1360
+ return definitions.map(
1361
+ (definition) => options.overrideLocks !== true && isOptionsLocked(definition) ? definition : applySettings(definition, layout.settings?.[definition.key])
1362
+ );
1363
+ }
1364
+ function widgetOptionValues(definition, settings) {
1365
+ const declared = definition.options ?? [];
1366
+ const values = {};
1367
+ for (const option of declared) {
1368
+ const picked = settings?.options?.[option.key];
1369
+ const value = (picked === void 0 ? void 0 : coerceOptionValue(option, picked)) ?? defaultOptionValue(option);
1370
+ if (value !== void 0) values[option.key] = value;
1371
+ }
1372
+ return values;
1373
+ }
1374
+ function optionValuesFromLayout(definitions, layout, options = {}) {
1375
+ const out = {};
1376
+ for (const definition of definitions) {
1377
+ if (definition.options === void 0 || definition.options.length === 0) {
1378
+ continue;
1379
+ }
1380
+ const locked = options.overrideLocks !== true && isOptionsLocked(definition);
1381
+ out[definition.key] = locked ? {} : layout.settings?.[definition.key]?.options ?? {};
1382
+ }
1383
+ return out;
1384
+ }
1385
+ function sameSettings(a, b) {
1386
+ if (a === b) return true;
1387
+ return a?.title === b?.title && a?.width === b?.width && a?.flexible === b?.flexible && a?.view === b?.view && sameOptionValues(a?.options ?? {}, b?.options ?? {});
1388
+ }
1389
+ function setWidgetSettings(layout, key, patch) {
1390
+ const before = layout.settings?.[key];
1391
+ const draft = { ...before };
1392
+ for (const field of [
1393
+ "title",
1394
+ "width",
1395
+ "flexible",
1396
+ "options",
1397
+ "view"
1398
+ ]) {
1399
+ if (!(field in patch)) continue;
1400
+ const value = patch[field];
1401
+ if (value === void 0) {
1402
+ delete draft[field];
1403
+ } else {
1404
+ draft[field] = value;
1405
+ }
1406
+ }
1407
+ const merged = parseOne(draft);
1408
+ if (sameSettings(before, merged)) return layout;
1409
+ const rest = { ...layout.settings };
1410
+ if (merged === void 0) {
1411
+ delete rest[key];
1412
+ } else {
1413
+ rest[key] = merged;
1414
+ }
1415
+ const { settings: _previous, ...others } = layout;
1416
+ return Object.keys(rest).length === 0 ? others : { ...others, settings: rest };
1417
+ }
1418
+ function clearWidgetSettings(layout, key) {
1419
+ if (layout.settings?.[key] === void 0) return layout;
1420
+ return setWidgetSettings(layout, key, {
1421
+ title: void 0,
1422
+ width: void 0,
1423
+ flexible: void 0,
1424
+ options: void 0,
1425
+ view: void 0
1426
+ });
1427
+ }
1428
+ function normalizeTitle(title) {
1429
+ return title.normalize("NFKC").trim().toLowerCase();
1430
+ }
1431
+ function isTitleInUse(definitions, layout, key, title) {
1432
+ const wanted = normalizeTitle(title);
1433
+ if (wanted === "") return false;
1434
+ return effectiveDefinitions(definitions, layout, {
1435
+ overrideLocks: true
1436
+ }).some(
1437
+ (other) => other.key !== key && normalizeTitle(other.title ?? other.key) === wanted
1438
+ );
1439
+ }
1440
+ function uniqueTitle(taken, title) {
1441
+ if (!taken.has(normalizeTitle(title))) return title;
1442
+ for (let n = 2; ; n += 1) {
1443
+ const suffix = ` (#${n})`;
1444
+ const candidate = `${title.slice(0, MAX_WIDGET_TITLE_LENGTH - suffix.length)}${suffix}`;
1445
+ if (!taken.has(normalizeTitle(candidate))) return candidate;
1446
+ }
1447
+ }
1448
+ function normalizeSettings(definitions, layout, options = {}) {
1449
+ const settings = layout.settings;
1450
+ if (settings === void 0) return layout;
1451
+ const cleaned = {};
1452
+ const taken = /* @__PURE__ */ new Set();
1453
+ for (const definition of definitions) {
1454
+ const renamed = settings[definition.key]?.title !== void 0 && (options.overrideLocks === true || !isOptionsLocked(definition));
1455
+ if (!renamed) taken.add(normalizeTitle(definition.title ?? definition.key));
1456
+ }
1457
+ for (const definition of definitions) {
1458
+ const one = settings[definition.key];
1459
+ if (one === void 0) continue;
1460
+ if (options.overrideLocks !== true && isOptionsLocked(definition)) continue;
1461
+ const range = widthRange(definition);
1462
+ const chosen = {};
1463
+ for (const option of definition.options ?? []) {
1464
+ const picked = one.options?.[option.key];
1465
+ if (picked === void 0) continue;
1466
+ const value = coerceOptionValue(option, picked);
1467
+ if (value !== void 0) {
1468
+ chosen[option.key] = option.type === "dateRange" ? picked : value;
1469
+ }
1470
+ }
1471
+ let title = one.title;
1472
+ if (title !== void 0) {
1473
+ title = title.trim().slice(0, MAX_WIDGET_TITLE_LENGTH);
1474
+ if (title === "") {
1475
+ title = void 0;
1476
+ } else {
1477
+ title = uniqueTitle(taken, title);
1478
+ taken.add(normalizeTitle(title));
1479
+ }
1480
+ }
1481
+ const next = {
1482
+ ...title === void 0 ? {} : { title },
1483
+ ...one.width === void 0 ? {} : { width: Math.min(range.max, Math.max(range.min, one.width)) },
1484
+ ...one.flexible === void 0 ? {} : { flexible: one.flexible },
1485
+ ...Object.keys(chosen).length === 0 ? {} : { options: chosen },
1486
+ ...one.view !== void 0 && (one.view === BASE_VIEW_KEY || findView(definition, one.view) !== void 0) ? { view: one.view } : {}
1487
+ };
1488
+ if (Object.keys(next).length > 0) {
1489
+ cleaned[definition.key] = sameSettings(one, next) ? one : next;
1490
+ }
1491
+ }
1492
+ const keys = Object.keys(cleaned);
1493
+ const unchanged = keys.length === Object.keys(settings).length && keys.every((key) => cleaned[key] === settings[key]);
1494
+ if (unchanged) return layout;
1495
+ const { settings: _previous, ...others } = layout;
1496
+ return keys.length === 0 ? others : { ...others, settings: cleaned };
1497
+ }
1498
+
1499
+ // src/core/layout.ts
1500
+ var MAX_PAGE_TITLE_LENGTH = 30;
1501
+ var MAX_PARSED_PAGES = 50;
1502
+ var MAX_PAGE_ROWS = 20;
1503
+ var EMPTY_LAYOUT = Object.freeze({
1504
+ order: Object.freeze([]),
1505
+ hidden: Object.freeze([]),
1506
+ minimized: Object.freeze([])
1507
+ });
1508
+ function uniqueStrings(value) {
1509
+ if (!Array.isArray(value)) return [];
1510
+ const seen = /* @__PURE__ */ new Set();
1511
+ for (const entry of value) {
1512
+ if (typeof entry === "string" && entry !== "") seen.add(entry);
1513
+ }
1514
+ return [...seen];
1515
+ }
1516
+ function parseLayout(raw) {
1517
+ let value = raw;
1518
+ if (typeof raw === "string") {
1519
+ try {
1520
+ value = JSON.parse(raw);
1521
+ } catch {
1522
+ return EMPTY_LAYOUT;
1523
+ }
1524
+ }
1525
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1526
+ return EMPTY_LAYOUT;
1527
+ }
1528
+ const record = value;
1529
+ const order = uniqueStrings(record["order"]);
1530
+ const hidden = uniqueStrings(record["hidden"]);
1531
+ const minimized = uniqueStrings(record["minimized"]);
1532
+ const pages = parsePages(record["pages"]);
1533
+ const settings = parseSettings(
1534
+ record["settings"]
1535
+ );
1536
+ const withSettings = settings === void 0 ? {} : { settings };
1537
+ if (pages.length > 0) {
1538
+ return {
1539
+ order: EMPTY_LAYOUT.order,
1540
+ hidden: EMPTY_LAYOUT.hidden,
1541
+ minimized: EMPTY_LAYOUT.minimized,
1542
+ pages,
1543
+ ...withSettings
1544
+ };
1545
+ }
1546
+ if (order.length === 0 && hidden.length === 0 && minimized.length === 0) {
1547
+ return settings === void 0 ? EMPTY_LAYOUT : { ...EMPTY_LAYOUT, settings };
1548
+ }
1549
+ return { order, hidden, minimized, ...withSettings };
1550
+ }
1551
+ function parsePages(value) {
1552
+ if (!Array.isArray(value)) return [];
1553
+ const pages = [];
1554
+ const keys = /* @__PURE__ */ new Set();
1555
+ for (const entry of value.slice(0, MAX_PARSED_PAGES)) {
1556
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
1557
+ continue;
1558
+ }
1559
+ const record = entry;
1560
+ const key = record["key"];
1561
+ if (typeof key !== "string" || key === "" || keys.has(key)) continue;
1562
+ keys.add(key);
1563
+ const rawTitle = record["title"];
1564
+ const title = typeof rawTitle === "string" && rawTitle.trim() !== "" ? rawTitle.trim().slice(0, MAX_PAGE_TITLE_LENGTH) : `Page ${pages.length + 1}`;
1565
+ const maxRows = record["maxRows"];
1566
+ const held = /* @__PURE__ */ new Set();
1567
+ for (const earlier of pages) {
1568
+ for (const k of [...earlier.order, ...earlier.hidden]) held.add(k);
1569
+ }
1570
+ const keep = (list) => uniqueStrings(list).filter((k) => !held.has(k));
1571
+ pages.push({
1572
+ key,
1573
+ title,
1574
+ order: keep(record["order"]),
1575
+ hidden: keep(record["hidden"]),
1576
+ minimized: keep(record["minimized"]),
1577
+ ...typeof maxRows === "number" && Number.isInteger(maxRows) && maxRows >= 1 && maxRows <= MAX_PAGE_ROWS ? { maxRows } : {}
1578
+ });
1579
+ }
1580
+ return pages;
1581
+ }
1582
+ function serializeLayout(layout) {
1583
+ return JSON.stringify({
1584
+ order: [...layout.order],
1585
+ hidden: [...layout.hidden],
1586
+ minimized: [...layout.minimized],
1587
+ ...layout.pages === void 0 || layout.pages.length === 0 ? {} : {
1588
+ pages: layout.pages.map((page) => ({
1589
+ key: page.key,
1590
+ title: page.title,
1591
+ order: [...page.order],
1592
+ hidden: [...page.hidden],
1593
+ minimized: [...page.minimized],
1594
+ ...page.maxRows === void 0 ? {} : { maxRows: page.maxRows }
1595
+ }))
1596
+ },
1597
+ ...serializeSettings(layout.settings) === void 0 ? {} : { settings: serializeSettings(layout.settings) }
1598
+ });
1599
+ }
1600
+ function bySortOrder(items) {
1601
+ return items.map((item, index) => ({ item, index })).sort(
1602
+ (a, b) => (a.item.sortOrder ?? 0) - (b.item.sortOrder ?? 0) || a.index - b.index
1603
+ ).map(({ item }) => item);
1604
+ }
1605
+ function isPinned(item) {
1606
+ return resolveWidgetLock(item).move;
1607
+ }
1608
+ function viewerOrder(visible, layout) {
1609
+ const byKey = new Map(visible.map((d) => [d.key, d]));
1610
+ const ordered = [];
1611
+ for (const key of layout.order) {
1612
+ const definition = byKey.get(key);
1613
+ if (definition !== void 0) {
1614
+ ordered.push(definition);
1615
+ byKey.delete(key);
1616
+ }
1617
+ }
1618
+ for (const definition of bySortOrder(visible)) {
1619
+ if (byKey.has(definition.key)) ordered.push(definition);
1620
+ }
1621
+ return ordered;
1622
+ }
1623
+ function placeAroundPinned(visible, free) {
1624
+ const slots = bySortOrder(visible).map(
1625
+ (item) => isPinned(item) ? item : void 0
1626
+ );
1627
+ let next = 0;
1628
+ return slots.map((pinned) => pinned ?? free[next++]);
1629
+ }
1630
+ function visibleWidgets(definitions, layout) {
1631
+ const hidden = new Set(layout.hidden);
1632
+ const visible = definitions.filter((d) => !hidden.has(d.key));
1633
+ const ordered = viewerOrder(visible, layout);
1634
+ if (!visible.some(isPinned)) return ordered;
1635
+ return placeAroundPinned(
1636
+ visible,
1637
+ ordered.filter((item) => !isPinned(item))
1638
+ );
1639
+ }
1640
+ function hiddenWidgets(definitions, layout) {
1641
+ const hidden = new Set(layout.hidden);
1642
+ return bySortOrder(definitions.filter((d) => hidden.has(d.key)));
1643
+ }
1644
+ function moveWidget(definitions, layout, key, toIndex) {
1645
+ const current = visibleWidgets(definitions, layout);
1646
+ const fromIndex = current.findIndex((d) => d.key === key);
1647
+ const mover = current[fromIndex];
1648
+ if (mover === void 0 || !Number.isInteger(toIndex) || toIndex < 0 || toIndex >= current.length || toIndex === fromIndex || isPinned(mover) || isPinned(current[toIndex])) {
1649
+ return layout;
1650
+ }
1651
+ const freeSlots = freeSlotIndexes(current);
1652
+ const free = current.filter((item) => !isPinned(item));
1653
+ const fromFree = freeSlots.indexOf(fromIndex);
1654
+ const toFree = freeSlots.indexOf(toIndex);
1655
+ free.splice(fromFree, 1);
1656
+ free.splice(toFree, 0, mover);
1657
+ const next = placeAroundPinned(current, free);
1658
+ return { ...layout, order: next.map((d) => d.key) };
1659
+ }
1660
+ function freeSlotIndexes(ordered) {
1661
+ const slots = [];
1662
+ ordered.forEach((item, index) => {
1663
+ if (!isPinned(item)) slots.push(index);
1664
+ });
1665
+ return slots;
1666
+ }
1667
+ function moveWidgetBy(definitions, layout, key, offset) {
1668
+ const current = visibleWidgets(definitions, layout);
1669
+ const fromIndex = current.findIndex((d) => d.key === key);
1670
+ if (fromIndex === -1) return layout;
1671
+ const freeSlots = freeSlotIndexes(current);
1672
+ const fromFree = freeSlots.indexOf(fromIndex);
1673
+ if (fromFree === -1) return layout;
1674
+ const toFree = Math.max(
1675
+ 0,
1676
+ Math.min(freeSlots.length - 1, fromFree + Math.trunc(offset))
1677
+ );
1678
+ return moveWidget(definitions, layout, key, freeSlots[toFree]);
1679
+ }
1680
+ function moveWidgetUp(definitions, layout, key) {
1681
+ return moveWidgetBy(definitions, layout, key, -1);
1130
1682
  }
1131
- var NO_VALUES = Object.freeze({});
1132
- function resolveOptionValues(definition, chosen, context = {}) {
1133
- const options = definition.options;
1134
- if (options === void 0 || options.length === 0) return NO_VALUES;
1135
- const values = {};
1136
- for (const option of options) {
1137
- const picked = chosen?.[option.key];
1138
- const value = (picked === void 0 ? void 0 : coerceOptionValue(option, picked, context)) ?? defaultOptionValue(option, context);
1139
- if (value !== void 0) values[option.key] = value;
1140
- }
1141
- return values;
1683
+ function moveWidgetDown(definitions, layout, key) {
1684
+ return moveWidgetBy(definitions, layout, key, 1);
1142
1685
  }
1143
- function sameValue(a, b) {
1144
- if (Array.isArray(a) && Array.isArray(b)) {
1145
- return a.length === b.length && a.every((item, i) => item === b[i]);
1146
- }
1147
- return a === b;
1686
+ function isHidden(layout, key) {
1687
+ return layout.hidden.includes(key);
1148
1688
  }
1149
- function sameOptionValues(a, b) {
1150
- const keys = Object.keys({ ...a, ...b });
1151
- return keys.every((key) => sameValue(a[key], b[key]));
1689
+ function isMinimized(layout, key) {
1690
+ return layout.minimized.includes(key);
1152
1691
  }
1153
- function optionsCacheSuffix(definition, values, context = {}) {
1154
- const parts = [];
1155
- for (const option of definition.options ?? []) {
1156
- if (isClientApplied(option)) continue;
1157
- const value = values[option.key];
1158
- if (sameValue(value, defaultOptionValue(option, context))) continue;
1159
- parts.push(
1160
- `${option.key}=${encodeURIComponent(JSON.stringify(value ?? null))}`
1161
- );
1162
- }
1163
- parts.sort();
1164
- return parts.length === 0 ? "" : `?${parts.join("&")}`;
1692
+ function hideWidget(layout, key) {
1693
+ if (isHidden(layout, key)) return layout;
1694
+ return { ...layout, hidden: [...layout.hidden, key] };
1165
1695
  }
1166
- function tableColumnIndex(key) {
1167
- const match = /^c(\d+)$/.exec(key);
1168
- return match === null ? void 0 : Number(match[1]);
1696
+ function showWidget(layout, key) {
1697
+ if (!isHidden(layout, key)) return layout;
1698
+ return {
1699
+ ...layout,
1700
+ hidden: layout.hidden.filter((k) => k !== key),
1701
+ minimized: layout.minimized.filter((k) => k !== key)
1702
+ };
1169
1703
  }
1170
- function sortBarItems(items, sort) {
1171
- const sign = sort.direction === "desc" ? -1 : 1;
1172
- return items.map((item, index) => ({ item, index })).sort((a, b) => {
1173
- const order = sort.column === "value" ? a.item.value - b.item.value : a.item.label.localeCompare(b.item.label);
1174
- return order * sign || a.index - b.index;
1175
- }).map(({ item }) => item);
1704
+ function toggleHidden(layout, key) {
1705
+ return isHidden(layout, key) ? showWidget(layout, key) : hideWidget(layout, key);
1176
1706
  }
1177
- function applyClientOptions(definition, data, values) {
1178
- const options = definition.options ?? [];
1179
- const sortOption = options.find(
1180
- (o) => o.type === "sort" && o.apply === "client"
1181
- );
1182
- const columnsOption = options.find(
1183
- (o) => o.type === "columns" && o.apply === "client"
1184
- );
1185
- const sortValue = sortOption === void 0 ? void 0 : values[sortOption.key];
1186
- const columnsValue = columnsOption === void 0 ? void 0 : values[columnsOption.key];
1187
- let next = data;
1188
- if (typeof sortValue === "string") {
1189
- const sort = parseSortValue(sortValue);
1190
- if (sort !== void 0 && next.kind === "BAR_LIST") {
1191
- if (sort.column === "label" || sort.column === "value") {
1192
- next = { ...next, items: sortBarItems(next.items, sort) };
1193
- }
1194
- } else if (sort !== void 0 && next.kind === "TABLE") {
1195
- const index = tableColumnIndex(sort.column);
1196
- if (index !== void 0 && index < next.columns.length) {
1197
- const sorted = queryRows(tableDetailData(next), {
1198
- page: 1,
1199
- pageSize: Math.max(1, next.rows.length),
1200
- sort
1201
- });
1202
- if (sorted.ok) next = { ...next, rows: sorted.value.rows };
1203
- }
1204
- }
1707
+ function minimizeWidget(layout, key) {
1708
+ if (isMinimized(layout, key)) return layout;
1709
+ return { ...layout, minimized: [...layout.minimized, key] };
1710
+ }
1711
+ function restoreWidget(layout, key) {
1712
+ if (!isMinimized(layout, key)) return layout;
1713
+ return { ...layout, minimized: layout.minimized.filter((k) => k !== key) };
1714
+ }
1715
+ function toggleMinimized(layout, key) {
1716
+ return isMinimized(layout, key) ? restoreWidget(layout, key) : minimizeWidget(layout, key);
1717
+ }
1718
+ function pruneLayout(definitions, layout) {
1719
+ const known = new Set(definitions.map((d) => d.key));
1720
+ const keep = (keys) => keys.every((k) => known.has(k)) ? keys : keys.filter((k) => known.has(k));
1721
+ return mapLayoutLists(layout, (lists) => ({
1722
+ order: keep(lists.order),
1723
+ hidden: keep(lists.hidden),
1724
+ minimized: keep(lists.minimized)
1725
+ }));
1726
+ }
1727
+ function mapLayoutLists(layout, change) {
1728
+ const top = change(layout);
1729
+ const pages = layout.pages?.map((page) => {
1730
+ const next = change(page);
1731
+ return next.order === page.order && next.hidden === page.hidden && next.minimized === page.minimized ? page : { ...page, ...next };
1732
+ });
1733
+ const pagesChanged = pages !== void 0 && pages.some((page, i) => page !== layout.pages?.[i]);
1734
+ if (top.order === layout.order && top.hidden === layout.hidden && top.minimized === layout.minimized && !pagesChanged) {
1735
+ return layout;
1205
1736
  }
1206
- if (Array.isArray(columnsValue) && next.kind === "TABLE") {
1207
- const table = next;
1208
- const indexes = columnsValue.map(tableColumnIndex).filter(
1209
- (index) => index !== void 0 && index < table.columns.length
1210
- );
1211
- if (indexes.length > 0) {
1212
- next = {
1213
- ...table,
1214
- columns: indexes.map((i) => table.columns[i]),
1215
- rows: table.rows.map((row) => indexes.map((i) => row[i]))
1216
- };
1217
- }
1737
+ return {
1738
+ ...layout,
1739
+ ...top,
1740
+ ...pages === void 0 ? {} : { pages }
1741
+ };
1742
+ }
1743
+ function enforceLocks(definitions, layout, options = {}) {
1744
+ if (options.overrideLocks === true) return layout;
1745
+ const noMove = /* @__PURE__ */ new Set();
1746
+ const noHide = /* @__PURE__ */ new Set();
1747
+ const noMinimize = /* @__PURE__ */ new Set();
1748
+ for (const definition of definitions) {
1749
+ const lock = resolveWidgetLock(definition);
1750
+ if (lock.move) noMove.add(definition.key);
1751
+ if (lock.hide) noHide.add(definition.key);
1752
+ if (lock.minimize) noMinimize.add(definition.key);
1218
1753
  }
1219
- return next;
1754
+ const without2 = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
1755
+ return mapLayoutLists(layout, (lists) => ({
1756
+ order: without2(lists.order, noMove),
1757
+ hidden: without2(lists.hidden, noHide),
1758
+ minimized: without2(lists.minimized, noMinimize)
1759
+ }));
1220
1760
  }
1221
1761
 
1222
1762
  // src/core/validate.ts
@@ -1229,7 +1769,7 @@ function describeType2(value) {
1229
1769
  }
1230
1770
  return typeof value;
1231
1771
  }
1232
- function isRecord(value) {
1772
+ function isRecord2(value) {
1233
1773
  return typeof value === "object" && value !== null && !Array.isArray(value);
1234
1774
  }
1235
1775
  function join(path, field) {
@@ -1311,7 +1851,7 @@ function requireOneOf(record, field, options, path, problems) {
1311
1851
  function eachRecord(items, path, problems, check) {
1312
1852
  items.forEach((item, index) => {
1313
1853
  const itemPath = `${path}[${index}]`;
1314
- if (isRecord(item)) {
1854
+ if (isRecord2(item)) {
1315
1855
  check(item, itemPath);
1316
1856
  } else {
1317
1857
  problems.push(
@@ -1460,7 +2000,7 @@ function checkPayloadFields(kind, data, problems) {
1460
2000
  }
1461
2001
  function checkDetailOption(detail, problems) {
1462
2002
  if (detail === void 0 || typeof detail === "boolean") return;
1463
- if (!isRecord(detail)) {
2003
+ if (!isRecord2(detail)) {
1464
2004
  problems.push(
1465
2005
  `detail must be true, false or an options object, got ${describeType2(detail)}.`
1466
2006
  );
@@ -1479,13 +2019,13 @@ function checkDetailOption(detail, problems) {
1479
2019
  }
1480
2020
  function checkTableControlsOption(controls, kind, problems) {
1481
2021
  if (controls === void 0 || controls === false) return;
1482
- if (typeof controls !== "boolean" && !isRecord(controls)) {
2022
+ if (typeof controls !== "boolean" && !isRecord2(controls)) {
1483
2023
  problems.push(
1484
2024
  `tableControls must be true, false or an options object, got ${describeType2(controls)}.`
1485
2025
  );
1486
2026
  return;
1487
2027
  }
1488
- if (isRecord(controls)) {
2028
+ if (isRecord2(controls)) {
1489
2029
  optionalBoolean(controls, "search", "tableControls", problems);
1490
2030
  optionalBoolean(controls, "sort", "tableControls", problems);
1491
2031
  }
@@ -1495,9 +2035,39 @@ function checkTableControlsOption(controls, kind, problems) {
1495
2035
  );
1496
2036
  }
1497
2037
  }
2038
+ function checkWidthLimits(value, problems) {
2039
+ for (const field of ["minWidth", "maxWidth"]) {
2040
+ const limit = value[field];
2041
+ if (limit !== void 0 && !isWidgetWidth(limit)) {
2042
+ problems.push(
2043
+ `${field} must be an integer from ${MIN_WIDGET_WIDTH} to ${MAX_WIDGET_WIDTH} (twelfths of the row), got ${typeof limit === "number" ? String(limit) : describeType2(limit)}.`
2044
+ );
2045
+ return;
2046
+ }
2047
+ }
2048
+ const { minWidth, maxWidth, width } = value;
2049
+ if (isWidgetWidth(minWidth) && isWidgetWidth(maxWidth) && minWidth > maxWidth) {
2050
+ problems.push(
2051
+ `minWidth (${minWidth}) must not be more than maxWidth (${maxWidth}).`
2052
+ );
2053
+ return;
2054
+ }
2055
+ if (isWidgetWidth(width)) {
2056
+ if (isWidgetWidth(minWidth) && width < minWidth) {
2057
+ problems.push(
2058
+ `width (${width}) must not be less than minWidth (${minWidth}).`
2059
+ );
2060
+ }
2061
+ if (isWidgetWidth(maxWidth) && width > maxWidth) {
2062
+ problems.push(
2063
+ `width (${width}) must not be more than maxWidth (${maxWidth}).`
2064
+ );
2065
+ }
2066
+ }
2067
+ }
1498
2068
  function checkLockedOption(locked, problems) {
1499
2069
  if (locked === void 0 || typeof locked === "boolean") return;
1500
- if (!isRecord(locked)) {
2070
+ if (!isRecord2(locked)) {
1501
2071
  problems.push(
1502
2072
  `locked must be true, false or an object, got ${describeType2(locked)}.`
1503
2073
  );
@@ -1506,6 +2076,7 @@ function checkLockedOption(locked, problems) {
1506
2076
  optionalBoolean(locked, "move", "locked", problems);
1507
2077
  optionalBoolean(locked, "hide", "locked", problems);
1508
2078
  optionalBoolean(locked, "minimize", "locked", problems);
2079
+ optionalBoolean(locked, "options", "locked", problems);
1509
2080
  }
1510
2081
  function checkOptionColumns(option, path, kind, problems) {
1511
2082
  const columns = option["columns"];
@@ -1518,7 +2089,7 @@ function checkOptionColumns(option, path, kind, problems) {
1518
2089
  const keys = [];
1519
2090
  columns.forEach((entry, index) => {
1520
2091
  const entryPath = `${path}.columns[${index}]`;
1521
- if (!isRecord(entry)) {
2092
+ if (!isRecord2(entry)) {
1522
2093
  problems.push(
1523
2094
  `${entryPath} must be an object, got ${describeType2(entry)}.`
1524
2095
  );
@@ -1543,7 +2114,7 @@ function checkOptionColumns(option, path, kind, problems) {
1543
2114
  return keys;
1544
2115
  }
1545
2116
  function checkOption(option, path, kind, seenKeys, seenTypes, problems) {
1546
- if (!isRecord(option)) {
2117
+ if (!isRecord2(option)) {
1547
2118
  problems.push(`${path} must be an object, got ${describeType2(option)}.`);
1548
2119
  return;
1549
2120
  }
@@ -1575,7 +2146,7 @@ function checkOption(option, path, kind, seenKeys, seenTypes, problems) {
1575
2146
  } else {
1576
2147
  choices.forEach((choice, index) => {
1577
2148
  const choicePath = `${path}.choices[${index}]`;
1578
- if (!isRecord(choice)) {
2149
+ if (!isRecord2(choice)) {
1579
2150
  problems.push(
1580
2151
  `${choicePath} must be an object, got ${describeType2(choice)}.`
1581
2152
  );
@@ -1665,7 +2236,7 @@ function checkOption(option, path, kind, seenKeys, seenTypes, problems) {
1665
2236
  } else {
1666
2237
  presets.forEach((preset, index) => {
1667
2238
  const presetPath = `${path}.presets[${index}]`;
1668
- if (!isRecord(preset)) {
2239
+ if (!isRecord2(preset)) {
1669
2240
  problems.push(
1670
2241
  `${presetPath} must be an object, got ${describeType2(preset)}.`
1671
2242
  );
@@ -1785,6 +2356,137 @@ function checkOptions(options, kind, problems) {
1785
2356
  );
1786
2357
  });
1787
2358
  }
2359
+ var MAX_VIEW_COLUMN = 1e3;
2360
+ function checkColumnIndex(value, path, problems) {
2361
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > MAX_VIEW_COLUMN) {
2362
+ problems.push(
2363
+ `${path} must be a column index (a whole number from 0), got ${typeof value === "number" ? String(value) : describeType2(value)}.`
2364
+ );
2365
+ }
2366
+ }
2367
+ function checkConversion(conversion, path, baseKind, viewKind2, problems) {
2368
+ if (!isRecord2(conversion)) {
2369
+ problems.push(
2370
+ `${path} must be an object with a type, got ${describeType2(conversion)}.`
2371
+ );
2372
+ return;
2373
+ }
2374
+ const type = conversion["type"];
2375
+ const known = typeof type === "string" && Object.hasOwn(VIEW_CONVERSIONS, type) ? VIEW_CONVERSIONS[type] : void 0;
2376
+ if (known === void 0) {
2377
+ problems.push(
2378
+ `${path}.type must be one of ${Object.keys(VIEW_CONVERSIONS).map((name) => `"${name}"`).join(", ")}, got ${describeType2(type)}.`
2379
+ );
2380
+ return;
2381
+ }
2382
+ if (baseKind !== known.from) {
2383
+ problems.push(
2384
+ `${path}.type "${String(type)}" reads a ${known.from} widget, but this one is ${String(baseKind)}.`
2385
+ );
2386
+ }
2387
+ if (viewKind2 !== known.to) {
2388
+ problems.push(
2389
+ `${path}.type "${String(type)}" produces ${known.to}, but the view's kind is ${String(viewKind2)}.`
2390
+ );
2391
+ }
2392
+ switch (type) {
2393
+ case "tableToBarList":
2394
+ checkColumnIndex(conversion["label"], `${path}.label`, problems);
2395
+ checkColumnIndex(conversion["value"], `${path}.value`, problems);
2396
+ break;
2397
+ case "tableToGraph": {
2398
+ checkColumnIndex(conversion["label"], `${path}.label`, problems);
2399
+ const values = conversion["values"];
2400
+ if (!Array.isArray(values) || values.length === 0) {
2401
+ problems.push(`${path}.values must list 1 or more column indexes.`);
2402
+ } else if (values.length > MAX_GRAPH_SERIES) {
2403
+ problems.push(
2404
+ `${path}.values lists ${values.length} series; a chart carries at most ${MAX_GRAPH_SERIES}.`
2405
+ );
2406
+ } else {
2407
+ values.forEach(
2408
+ (index, at) => checkColumnIndex(index, `${path}.values[${at}]`, problems)
2409
+ );
2410
+ }
2411
+ if (conversion["chartType"] !== "bar" && conversion["chartType"] !== "line") {
2412
+ problems.push(`${path}.chartType must be "bar" or "line".`);
2413
+ }
2414
+ break;
2415
+ }
2416
+ case "barListToGraph":
2417
+ if (conversion["chartType"] !== "bar" && conversion["chartType"] !== "line") {
2418
+ problems.push(`${path}.chartType must be "bar" or "line".`);
2419
+ }
2420
+ break;
2421
+ }
2422
+ if (conversion["valueFormat"] !== void 0 && !VALUE_FORMATS.includes(conversion["valueFormat"])) {
2423
+ problems.push(
2424
+ `${path}.valueFormat must be one of ${VALUE_FORMATS.map((f) => `"${f}"`).join(", ")}.`
2425
+ );
2426
+ }
2427
+ }
2428
+ function checkViews(value, problems) {
2429
+ const views = value["views"];
2430
+ const defaultView = value["defaultView"];
2431
+ optionalString(value, "baseViewLabel", "", problems);
2432
+ if (value["baseViewLabel"] === "") {
2433
+ problems.push("baseViewLabel must not be empty.");
2434
+ }
2435
+ const declared = /* @__PURE__ */ new Set([BASE_VIEW_KEY]);
2436
+ if (views !== void 0) {
2437
+ if (!Array.isArray(views)) {
2438
+ problems.push(`views must be an array, got ${describeType2(views)}.`);
2439
+ } else if (views.length > MAX_WIDGET_VIEWS) {
2440
+ problems.push(
2441
+ `views lists ${views.length} views; the most a widget may declare is ${MAX_WIDGET_VIEWS}.`
2442
+ );
2443
+ } else {
2444
+ views.forEach((view, index) => {
2445
+ const path = `views[${index}]`;
2446
+ if (!isRecord2(view)) {
2447
+ problems.push(
2448
+ `${path} must be an object, got ${describeType2(view)}.`
2449
+ );
2450
+ return;
2451
+ }
2452
+ const key = view["key"];
2453
+ if (typeof key !== "string" || key.trim() === "") {
2454
+ problems.push(`${path}.key must be a non-empty string.`);
2455
+ } else if (declared.has(key)) {
2456
+ problems.push(
2457
+ key === BASE_VIEW_KEY ? `${path}.key "${BASE_VIEW_KEY}" is reserved for the widget's own kind.` : `${path}.key "${key}" is used twice.`
2458
+ );
2459
+ } else {
2460
+ declared.add(key);
2461
+ }
2462
+ if (typeof view["label"] !== "string" || view["label"].trim() === "") {
2463
+ problems.push(`${path}.label must be a non-empty string.`);
2464
+ }
2465
+ requireOneOf(view, "kind", WIDGET_KINDS, path, problems);
2466
+ if (view["convert"] === void 0) {
2467
+ problems.push(
2468
+ `${path}.convert is required: views supplied by a provider are not supported yet.`
2469
+ );
2470
+ } else {
2471
+ checkConversion(
2472
+ view["convert"],
2473
+ `${path}.convert`,
2474
+ value["kind"],
2475
+ view["kind"],
2476
+ problems
2477
+ );
2478
+ }
2479
+ });
2480
+ }
2481
+ }
2482
+ if (defaultView !== void 0) {
2483
+ if (typeof defaultView !== "string" || !declared.has(defaultView)) {
2484
+ problems.push(
2485
+ `defaultView must be "${BASE_VIEW_KEY}" or the key of a declared view, got ${typeof defaultView === "string" ? JSON.stringify(defaultView) : describeType2(defaultView)}.`
2486
+ );
2487
+ }
2488
+ }
2489
+ }
1788
2490
  function formatProblems(prefix, problems) {
1789
2491
  const shown = problems.slice(0, MAX_REPORTED_PROBLEMS);
1790
2492
  const more = problems.length - shown.length;
@@ -1792,7 +2494,7 @@ function formatProblems(prefix, problems) {
1792
2494
  }
1793
2495
  function validateWidgetData(value, options = {}) {
1794
2496
  const subject = options.widgetKey === void 0 ? "Widget data" : `Widget ${JSON.stringify(options.widgetKey)}`;
1795
- if (!isRecord(value)) {
2497
+ if (!isRecord2(value)) {
1796
2498
  return err(`${subject}: must be an object, got ${describeType2(value)}.`);
1797
2499
  }
1798
2500
  if (value["empty"] === true) {
@@ -1822,7 +2524,7 @@ function validateWidgetData(value, options = {}) {
1822
2524
  return ok(value);
1823
2525
  }
1824
2526
  function validateWidgetDefinition(value) {
1825
- if (!isRecord(value)) {
2527
+ if (!isRecord2(value)) {
1826
2528
  return err(
1827
2529
  `Widget definition: must be an object, got ${describeType2(value)}.`
1828
2530
  );
@@ -1856,10 +2558,12 @@ function validateWidgetDefinition(value) {
1856
2558
  `width must be an integer from ${MIN_WIDGET_WIDTH} to ${MAX_WIDGET_WIDTH} (twelfths of the row), got ${typeof value["width"] === "number" ? String(value["width"]) : describeType2(value["width"])}.`
1857
2559
  );
1858
2560
  }
2561
+ checkWidthLimits(value, problems);
1859
2562
  checkDetailOption(value["detail"], problems);
1860
2563
  checkTableControlsOption(value["tableControls"], value["kind"], problems);
1861
2564
  checkLockedOption(value["locked"], problems);
1862
2565
  checkOptions(value["options"], value["kind"], problems);
2566
+ checkViews(value, problems);
1863
2567
  if (value["page"] !== void 0) {
1864
2568
  if (typeof value["page"] !== "string" || value["page"].trim() === "") {
1865
2569
  problems.push(
@@ -2517,7 +3221,7 @@ function withoutLockField(definition) {
2517
3221
  delete copy.locked;
2518
3222
  return copy;
2519
3223
  }
2520
- function uniqueTitle(taken, title, index) {
3224
+ function uniqueTitle2(taken, title, index) {
2521
3225
  let base = title.trim().slice(0, MAX_PAGE_TITLE_LENGTH);
2522
3226
  if (base === "") base = `Page ${index + 1}`;
2523
3227
  let candidate = base;
@@ -2538,9 +3242,18 @@ function samePages(a, b) {
2538
3242
  }
2539
3243
  function normalizeLayout(definitions, layout, options = {}) {
2540
3244
  const known = pruneLayout(definitions, layout);
2541
- const locked = enforceLocks(definitions, known, options);
3245
+ const enforced = enforceLocks(definitions, known, options);
3246
+ const locked = normalizeSettings(
3247
+ definitions,
3248
+ enforced,
3249
+ options
3250
+ );
2542
3251
  if (!hasPages(locked)) return locked === layout ? layout : locked;
2543
- const defs = options.overrideLocks === true ? definitions.map(withoutLockField) : definitions;
3252
+ const defs = effectiveDefinitions(
3253
+ options.overrideLocks === true ? definitions.map(withoutLockField) : definitions,
3254
+ locked,
3255
+ options
3256
+ );
2544
3257
  const taken = /* @__PURE__ */ new Set();
2545
3258
  const seenKeys = /* @__PURE__ */ new Set();
2546
3259
  const held = /* @__PURE__ */ new Set();
@@ -2553,7 +3266,7 @@ function normalizeLayout(definitions, layout, options = {}) {
2553
3266
  ];
2554
3267
  const next = {
2555
3268
  ...page,
2556
- title: uniqueTitle(taken, page.title, index),
3269
+ title: uniqueTitle2(taken, page.title, index),
2557
3270
  order: keep(page.order),
2558
3271
  hidden: keep(page.hidden),
2559
3272
  minimized: keep(page.minimized)
@@ -2641,7 +3354,7 @@ function normalizeLayout(definitions, layout, options = {}) {
2641
3354
  }
2642
3355
  if (!placed) {
2643
3356
  const key = nextPageKey(work());
2644
- const title = uniqueTitle(
3357
+ const title = uniqueTitle2(
2645
3358
  new Set(pages.map((p) => normalizedTitle(p.title))),
2646
3359
  `Page ${pages.length + 1}`,
2647
3360
  pages.length
@@ -2653,7 +3366,7 @@ function normalizeLayout(definitions, layout, options = {}) {
2653
3366
  }
2654
3367
  }
2655
3368
  const result = work();
2656
- return samePages(layout.pages ?? [], pages) && sameKeys(layout.order, result.order) ? layout : result;
3369
+ return samePages(layout.pages ?? [], pages) && sameKeys(layout.order, result.order) && locked.settings === layout.settings ? layout : result;
2657
3370
  }
2658
3371
 
2659
3372
  // src/core/scale.ts
@@ -3485,6 +4198,7 @@ function indent(block) {
3485
4198
  return block.split("\n").map((line) => ` ${line}`).join("\n");
3486
4199
  }
3487
4200
 
4201
+ exports.BASE_VIEW_KEY = BASE_VIEW_KEY;
3488
4202
  exports.DATE_RANGE_PRESETS = DATE_RANGE_PRESETS;
3489
4203
  exports.DEFAULT_COMPARISON_LABEL = DEFAULT_COMPARISON_LABEL;
3490
4204
  exports.DEFAULT_CURRENCY = DEFAULT_CURRENCY;
@@ -3501,14 +4215,18 @@ exports.MAX_GRAPH_SERIES = MAX_GRAPH_SERIES;
3501
4215
  exports.MAX_PAGE_ROWS = MAX_PAGE_ROWS;
3502
4216
  exports.MAX_PAGE_TITLE_LENGTH = MAX_PAGE_TITLE_LENGTH;
3503
4217
  exports.MAX_PARSED_PAGES = MAX_PARSED_PAGES;
4218
+ exports.MAX_PARSED_SETTINGS = MAX_PARSED_SETTINGS;
3504
4219
  exports.MAX_REPORTED_PROBLEMS = MAX_REPORTED_PROBLEMS;
3505
4220
  exports.MAX_TEXT_OPTION_LENGTH = MAX_TEXT_OPTION_LENGTH;
3506
4221
  exports.MAX_WIDGET_OPTIONS = MAX_WIDGET_OPTIONS;
4222
+ exports.MAX_WIDGET_TITLE_LENGTH = MAX_WIDGET_TITLE_LENGTH;
4223
+ exports.MAX_WIDGET_VIEWS = MAX_WIDGET_VIEWS;
3507
4224
  exports.MAX_WIDGET_WIDTH = MAX_WIDGET_WIDTH;
3508
4225
  exports.MIN_WIDGET_WIDTH = MIN_WIDGET_WIDTH;
3509
4226
  exports.OPTION_KEY_PATTERN = OPTION_KEY_PATTERN;
3510
4227
  exports.THEME_TOKEN_VARIABLES = THEME_TOKEN_VARIABLES;
3511
4228
  exports.VALUE_FORMATS = VALUE_FORMATS;
4229
+ exports.VIEW_CONVERSIONS = VIEW_CONVERSIONS;
3512
4230
  exports.WIDGET_FILLS = WIDGET_FILLS;
3513
4231
  exports.WIDGET_KINDS = WIDGET_KINDS;
3514
4232
  exports.WIDGET_OPTION_TYPES = WIDGET_OPTION_TYPES;
@@ -3516,10 +4234,14 @@ exports.WIDGET_SIZES = WIDGET_SIZES;
3516
4234
  exports.WIDTH_COLUMNS = WIDTH_COLUMNS;
3517
4235
  exports.addPage = addPage;
3518
4236
  exports.applyClientOptions = applyClientOptions;
4237
+ exports.applySettings = applySettings;
4238
+ exports.applyView = applyView;
3519
4239
  exports.assignPages = assignPages;
3520
4240
  exports.baseColumnSpan = baseColumnSpan;
3521
4241
  exports.cacheKeyFor = cacheKeyFor;
4242
+ exports.clearWidgetSettings = clearWidgetSettings;
3522
4243
  exports.coerceOptionValue = coerceOptionValue;
4244
+ exports.convertView = convertView;
3523
4245
  exports.createLayoutPersistence = createLayoutPersistence;
3524
4246
  exports.createTheme = createTheme;
3525
4247
  exports.createWidgetLoader = createWidgetLoader;
@@ -3528,6 +4250,8 @@ exports.defaultOptionValue = defaultOptionValue;
3528
4250
  exports.defineWidget = defineWidget;
3529
4251
  exports.deriveDetailData = deriveDetailData;
3530
4252
  exports.describeUnknownError = describeUnknownError;
4253
+ exports.effectiveDefinitions = effectiveDefinitions;
4254
+ exports.effectiveViewKey = effectiveViewKey;
3531
4255
  exports.emptyTextFor = emptyTextFor;
3532
4256
  exports.emptyWidget = emptyWidget;
3533
4257
  exports.enforceLocks = enforceLocks;
@@ -3538,26 +4262,32 @@ exports.fillWidthSpans = fillWidthSpans;
3538
4262
  exports.fillsHeight = fillsHeight;
3539
4263
  exports.fillsWidth = fillsWidth;
3540
4264
  exports.finalizeWidget = finalizeWidget;
4265
+ exports.findView = findView;
3541
4266
  exports.fitCount = fitCount;
3542
4267
  exports.formatCount = formatCount;
3543
4268
  exports.formatPercentChange = formatPercentChange;
3544
4269
  exports.formatValue = formatValue;
3545
4270
  exports.fromLegacyDefinition = fromLegacyDefinition;
3546
4271
  exports.hasPages = hasPages;
4272
+ exports.hasViews = hasViews;
3547
4273
  exports.hiddenWidgets = hiddenWidgets;
3548
4274
  exports.hideWidget = hideWidget;
3549
4275
  exports.isClientApplied = isClientApplied;
3550
4276
  exports.isColumnKeyFor = isColumnKeyFor;
4277
+ exports.isFlexible = isFlexible;
3551
4278
  exports.isHidden = isHidden;
3552
4279
  exports.isIsoInterval = isIsoInterval;
3553
4280
  exports.isMinimized = isMinimized;
4281
+ exports.isOptionsLocked = isOptionsLocked;
3554
4282
  exports.isSafeHref = isSafeHref;
3555
4283
  exports.isSafeImageUrl = isSafeImageUrl;
4284
+ exports.isTitleInUse = isTitleInUse;
3556
4285
  exports.isWidgetEmptyState = isWidgetEmptyState;
3557
4286
  exports.isWidgetKind = isWidgetKind;
3558
4287
  exports.isWidgetVisibleTo = isWidgetVisibleTo;
3559
4288
  exports.isWidgetWidth = isWidgetWidth;
3560
4289
  exports.itemWidth = itemWidth;
4290
+ exports.kindLabel = kindLabel;
3561
4291
  exports.kpiDelta = kpiDelta;
3562
4292
  exports.labelStride = labelStride;
3563
4293
  exports.loadingWidgets = loadingWidgets;
@@ -3573,8 +4303,11 @@ exports.nextPageKey = nextPageKey;
3573
4303
  exports.niceDomain = niceDomain;
3574
4304
  exports.niceStep = niceStep;
3575
4305
  exports.normalizeLayout = normalizeLayout;
4306
+ exports.normalizeSettings = normalizeSettings;
4307
+ exports.normalizeTitle = normalizeTitle;
3576
4308
  exports.ok = ok;
3577
4309
  exports.optionValuesFor = optionValuesFor;
4310
+ exports.optionValuesFromLayout = optionValuesFromLayout;
3578
4311
  exports.optionsCacheSuffix = optionsCacheSuffix;
3579
4312
  exports.pageLayout = pageLayout;
3580
4313
  exports.pageList = pageList;
@@ -3583,6 +4316,7 @@ exports.pageRoom = pageRoom;
3583
4316
  exports.pageWidgets = pageWidgets;
3584
4317
  exports.parseDetailQuery = parseDetailQuery;
3585
4318
  exports.parseLayout = parseLayout;
4319
+ exports.parseSettings = parseSettings;
3586
4320
  exports.parseSortValue = parseSortValue;
3587
4321
  exports.percentChange = percentChange;
3588
4322
  exports.placeRows = placeRows;
@@ -3605,6 +4339,8 @@ exports.restoreWidget = restoreWidget;
3605
4339
  exports.sameOptionValues = sameOptionValues;
3606
4340
  exports.serializeDetailQuery = serializeDetailQuery;
3607
4341
  exports.serializeLayout = serializeLayout;
4342
+ exports.serializeSettings = serializeSettings;
4343
+ exports.setWidgetSettings = setWidgetSettings;
3608
4344
  exports.shareOf = shareOf;
3609
4345
  exports.showWidget = showWidget;
3610
4346
  exports.sortDefinitions = sortDefinitions;
@@ -3618,9 +4354,15 @@ exports.utf8Length = utf8Length;
3618
4354
  exports.validateDetailData = validateDetailData;
3619
4355
  exports.validateWidgetData = validateWidgetData;
3620
4356
  exports.validateWidgetDefinition = validateWidgetDefinition;
4357
+ exports.viewChoices = viewChoices;
4358
+ exports.viewKind = viewKind;
4359
+ exports.viewNote = viewNote;
3621
4360
  exports.visibleWidgets = visibleWidgets;
4361
+ exports.widgetOptionValues = widgetOptionValues;
3622
4362
  exports.widgetsFor = widgetsFor;
3623
4363
  exports.widthForSize = widthForSize;
4364
+ exports.widthRange = widthRange;
4365
+ exports.widthShareLabel = widthShareLabel;
3624
4366
  exports.withEncoding = withEncoding;
3625
4367
  exports.withFallback = withFallback;
3626
4368
  exports.withLogging = withLogging;