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