@richardmcquiston01/dashboard-widgets-toolkit 0.14.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/react.cjs CHANGED
@@ -837,6 +837,8 @@ var DEFAULT_LABELS = {
837
837
  widthDefault: "Default",
838
838
  widthOption: (width, share) => `${width} of 12 (${share})`,
839
839
  optionsFlexible: "Grow to fill empty space in the row",
840
+ optionsViewField: "View",
841
+ viewDefault: (viewLabel) => `${viewLabel} (default)`,
840
842
  optionsDeclared: "Data",
841
843
  apply: "Apply",
842
844
  cancel: "Cancel",
@@ -1974,6 +1976,282 @@ function isWidgetVisibleTo(definition, roles) {
1974
1976
  return definition.roles.some((role) => roles.includes(role));
1975
1977
  }
1976
1978
 
1979
+ // src/core/payload.ts
1980
+ var WIDGET_KINDS = [
1981
+ "TEXT",
1982
+ "KPI",
1983
+ "GAUGE",
1984
+ "TABLE",
1985
+ "BAR_LIST",
1986
+ "ALERT_LIST",
1987
+ "GRAPH"
1988
+ ];
1989
+ function isWidgetKind(value) {
1990
+ return typeof value === "string" && WIDGET_KINDS.includes(value);
1991
+ }
1992
+ var VALUE_FORMATS = ["number", "currency", "percent"];
1993
+ var MAX_GRAPH_SERIES = 8;
1994
+ var DEFAULT_EMPTY_TEXT = "Nothing to show yet.";
1995
+ function isWidgetEmptyState(payload) {
1996
+ return "empty" in payload && payload.empty === true;
1997
+ }
1998
+ function emptyTextFor(payload) {
1999
+ if (isWidgetEmptyState(payload)) {
2000
+ return payload.text;
2001
+ }
2002
+ switch (payload.kind) {
2003
+ case "TABLE":
2004
+ return payload.rows.length === 0 ? DEFAULT_EMPTY_TEXT : null;
2005
+ case "BAR_LIST":
2006
+ return payload.items.length === 0 ? DEFAULT_EMPTY_TEXT : null;
2007
+ case "ALERT_LIST":
2008
+ return payload.items.length === 0 ? payload.emptyText : null;
2009
+ case "GRAPH":
2010
+ return payload.series.every((series) => series.points.length === 0) ? DEFAULT_EMPTY_TEXT : null;
2011
+ case "TEXT":
2012
+ case "KPI":
2013
+ case "GAUGE":
2014
+ return null;
2015
+ }
2016
+ }
2017
+
2018
+ // src/core/views.ts
2019
+ var BASE_VIEW_KEY = "base";
2020
+ var VIEW_CONVERSIONS = {
2021
+ tableToBarList: { from: "TABLE", to: "BAR_LIST" },
2022
+ tableToGraph: { from: "TABLE", to: "GRAPH" },
2023
+ barListToTable: { from: "BAR_LIST", to: "TABLE" },
2024
+ graphToTable: { from: "GRAPH", to: "TABLE" },
2025
+ barListToGraph: { from: "BAR_LIST", to: "GRAPH" }
2026
+ };
2027
+ var KIND_LABELS = {
2028
+ TEXT: "Text",
2029
+ KPI: "Number",
2030
+ GAUGE: "Gauge",
2031
+ TABLE: "Table",
2032
+ BAR_LIST: "Bars",
2033
+ ALERT_LIST: "List",
2034
+ GRAPH: "Chart"
2035
+ };
2036
+ function kindLabel(kind) {
2037
+ return KIND_LABELS[kind];
2038
+ }
2039
+ function hasViews(item) {
2040
+ return item.views !== void 0 && item.views.length > 0;
2041
+ }
2042
+ function findView(item, key) {
2043
+ return item.views?.find((view) => view.key === key);
2044
+ }
2045
+ function viewChoices(item) {
2046
+ if (!hasViews(item)) return [];
2047
+ return [
2048
+ {
2049
+ key: BASE_VIEW_KEY,
2050
+ label: item.baseViewLabel ?? kindLabel(item.kind),
2051
+ kind: item.kind
2052
+ },
2053
+ ...(item.views ?? []).map(({ key, label, kind }) => ({
2054
+ key,
2055
+ label,
2056
+ kind
2057
+ }))
2058
+ ];
2059
+ }
2060
+ function isKnownView(item, key) {
2061
+ return key !== void 0 && (key === BASE_VIEW_KEY || findView(item, key) !== void 0);
2062
+ }
2063
+ function effectiveViewKey(item, chosen) {
2064
+ if (!hasViews(item)) return BASE_VIEW_KEY;
2065
+ if (isKnownView(item, chosen)) return chosen;
2066
+ if (isKnownView(item, item.defaultView)) return item.defaultView;
2067
+ return BASE_VIEW_KEY;
2068
+ }
2069
+ function cellValue2(cell) {
2070
+ const value = cell?.value;
2071
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2072
+ }
2073
+ function columnName(table, index) {
2074
+ return table.columns[index]?.label ?? `#${index + 1}`;
2075
+ }
2076
+ function checkColumn(table, index, field, context) {
2077
+ if (!Number.isInteger(index) || index < 0 || index >= table.columns.length) {
2078
+ return `${context.where}: ${field} column ${index} does not exist (the table has ${table.columns.length} columns, numbered from 0).`;
2079
+ }
2080
+ return void 0;
2081
+ }
2082
+ function numericColumn(table, index, context) {
2083
+ const numbers = [];
2084
+ for (const [rowIndex, row] of table.rows.entries()) {
2085
+ const value = cellValue2(row[index]);
2086
+ if (value === void 0) {
2087
+ return err(
2088
+ `${context.where}: row ${rowIndex + 1}, column ${index + 1} ("${columnName(table, index)}") has no numeric value. Add value to the cells.`
2089
+ );
2090
+ }
2091
+ numbers.push(value);
2092
+ }
2093
+ return ok(numbers);
2094
+ }
2095
+ function tableToBarList(table, conversion, context) {
2096
+ const bad = checkColumn(table, conversion.label, "label", context) ?? checkColumn(table, conversion.value, "value", context);
2097
+ if (bad !== void 0) return err(bad);
2098
+ const numbers = numericColumn(table, conversion.value, context);
2099
+ if (!numbers.ok) return numbers;
2100
+ const items = table.rows.map((row, index) => ({
2101
+ label: row[conversion.label]?.text ?? "",
2102
+ value: numbers.value[index],
2103
+ display: row[conversion.value]?.text ?? String(numbers.value[index])
2104
+ }));
2105
+ return ok({ kind: "BAR_LIST", items });
2106
+ }
2107
+ function tableToGraph(table, conversion, context) {
2108
+ if (conversion.values.length === 0 || conversion.values.length > MAX_GRAPH_SERIES) {
2109
+ return err(
2110
+ `${context.where}: a chart needs 1 to ${MAX_GRAPH_SERIES} series columns, got ${conversion.values.length}.`
2111
+ );
2112
+ }
2113
+ let bad = checkColumn(
2114
+ table,
2115
+ conversion.label,
2116
+ "label",
2117
+ context
2118
+ );
2119
+ for (const index of conversion.values) {
2120
+ bad ??= checkColumn(table, index, "series", context);
2121
+ }
2122
+ if (bad !== void 0) return err(bad);
2123
+ const series = [];
2124
+ for (const index of conversion.values) {
2125
+ const numbers = numericColumn(table, index, context);
2126
+ if (!numbers.ok) return numbers;
2127
+ series.push({
2128
+ name: columnName(table, index),
2129
+ points: table.rows.map((row, rowIndex) => ({
2130
+ label: row[conversion.label]?.text ?? "",
2131
+ value: numbers.value[rowIndex]
2132
+ }))
2133
+ });
2134
+ }
2135
+ return ok({
2136
+ kind: "GRAPH",
2137
+ series,
2138
+ chartType: conversion.chartType,
2139
+ valueFormat: conversion.valueFormat ?? "number",
2140
+ ...conversion.currency === void 0 ? {} : { currency: conversion.currency },
2141
+ xLabel: columnName(table, conversion.label)
2142
+ });
2143
+ }
2144
+ function barListToTable(list, conversion) {
2145
+ return ok({
2146
+ kind: "TABLE",
2147
+ columns: [
2148
+ { label: conversion.labelHeading ?? "Name" },
2149
+ { label: conversion.valueHeading ?? "Value", numeric: true }
2150
+ ],
2151
+ rows: list.items.map((item) => [
2152
+ { text: item.label },
2153
+ { text: item.display ?? String(item.value), value: item.value }
2154
+ ])
2155
+ });
2156
+ }
2157
+ function graphToTable(graph, context) {
2158
+ const first = graph.series[0];
2159
+ if (first === void 0)
2160
+ return err(`${context.where}: the chart has no series.`);
2161
+ const format = {
2162
+ ...graph.currency === void 0 ? {} : { currency: graph.currency },
2163
+ ...context.locale === void 0 ? {} : { locale: context.locale }
2164
+ };
2165
+ return ok({
2166
+ kind: "TABLE",
2167
+ columns: [
2168
+ { label: graph.xLabel ?? "Label" },
2169
+ ...graph.series.map((series) => ({ label: series.name, numeric: true }))
2170
+ ],
2171
+ rows: first.points.map((point, index) => [
2172
+ { text: point.label },
2173
+ ...graph.series.map((series) => {
2174
+ const value = series.points[index]?.value;
2175
+ return value === void 0 ? { text: "\u2014" } : { text: formatValue(value, graph.valueFormat, format), value };
2176
+ })
2177
+ ])
2178
+ });
2179
+ }
2180
+ function barListToGraph(list, conversion) {
2181
+ return ok({
2182
+ kind: "GRAPH",
2183
+ series: [
2184
+ {
2185
+ name: conversion.seriesName ?? "Value",
2186
+ points: list.items.map((item) => ({
2187
+ label: item.label,
2188
+ value: item.value
2189
+ }))
2190
+ }
2191
+ ],
2192
+ chartType: conversion.chartType,
2193
+ valueFormat: conversion.valueFormat ?? "number",
2194
+ ...conversion.currency === void 0 ? {} : { currency: conversion.currency }
2195
+ });
2196
+ }
2197
+ function convertView(widgetKey, view, data, options = {}) {
2198
+ const conversion = view.convert;
2199
+ const context = {
2200
+ where: `Widget ${JSON.stringify(widgetKey)} view ${JSON.stringify(view.key)}`,
2201
+ ...options.locale === void 0 ? {} : { locale: options.locale }
2202
+ };
2203
+ const expected = VIEW_CONVERSIONS[conversion.type]?.from;
2204
+ if (expected === void 0) {
2205
+ return err(
2206
+ `${context.where}: unknown conversion type ${JSON.stringify(conversion.type)}.`
2207
+ );
2208
+ }
2209
+ if (data.kind !== expected) {
2210
+ return err(
2211
+ `${context.where}: ${conversion.type} reads a ${expected} payload, but the widget returned ${data.kind}.`
2212
+ );
2213
+ }
2214
+ switch (conversion.type) {
2215
+ case "tableToBarList":
2216
+ return tableToBarList(data, conversion, context);
2217
+ case "tableToGraph":
2218
+ return tableToGraph(data, conversion, context);
2219
+ case "barListToTable":
2220
+ return barListToTable(data, conversion);
2221
+ case "graphToTable":
2222
+ return graphToTable(data, context);
2223
+ case "barListToGraph":
2224
+ return barListToGraph(data, conversion);
2225
+ }
2226
+ }
2227
+ function applyView(widget, chosen, options = {}) {
2228
+ const key = effectiveViewKey(widget.definition, chosen);
2229
+ const view = findView(widget.definition, key);
2230
+ if (view === void 0) return widget;
2231
+ const definition = {
2232
+ ...widget.definition,
2233
+ kind: view.kind
2234
+ };
2235
+ if (widget.status === "loading") return { ...widget, definition };
2236
+ if (widget.status === "empty") return { ...widget, definition };
2237
+ if (widget.status === "error") return { ...widget, definition };
2238
+ const converted = convertView(
2239
+ widget.definition.key,
2240
+ view,
2241
+ widget.data,
2242
+ options
2243
+ );
2244
+ if (!converted.ok) {
2245
+ return {
2246
+ definition,
2247
+ status: "error",
2248
+ error: converted.error,
2249
+ ...widget.options === void 0 ? {} : { options: widget.options }
2250
+ };
2251
+ }
2252
+ return { ...widget, definition, data: converted.value };
2253
+ }
2254
+
1977
2255
  // src/core/settings.ts
1978
2256
  var MAX_WIDGET_TITLE_LENGTH = 80;
1979
2257
  var MAX_COLUMN_LIST = 100;
@@ -2001,6 +2279,7 @@ function parseOne(raw) {
2001
2279
  const width = raw["width"];
2002
2280
  const flexible = raw["flexible"];
2003
2281
  const options = raw["options"];
2282
+ const view = raw["view"];
2004
2283
  const cleanTitle = typeof title === "string" ? title.trim().slice(0, MAX_WIDGET_TITLE_LENGTH) : "";
2005
2284
  const chosen = {};
2006
2285
  if (isRecord(options)) {
@@ -2013,7 +2292,8 @@ function parseOne(raw) {
2013
2292
  ...cleanTitle === "" ? {} : { title: cleanTitle },
2014
2293
  ...isWidgetWidth(width) ? { width } : {},
2015
2294
  ...typeof flexible === "boolean" ? { flexible } : {},
2016
- ...Object.keys(chosen).length === 0 ? {} : { options: chosen }
2295
+ ...Object.keys(chosen).length === 0 ? {} : { options: chosen },
2296
+ ...typeof view === "string" && view !== "" && view.length <= 80 ? { view } : {}
2017
2297
  };
2018
2298
  return Object.keys(settings).length === 0 ? void 0 : settings;
2019
2299
  }
@@ -2081,12 +2361,18 @@ function effectiveDefinitions(definitions, layout, options = {}) {
2081
2361
  }
2082
2362
  function sameSettings(a, b) {
2083
2363
  if (a === b) return true;
2084
- return a?.title === b?.title && a?.width === b?.width && a?.flexible === b?.flexible && sameOptionValues(a?.options ?? {}, b?.options ?? {});
2364
+ return a?.title === b?.title && a?.width === b?.width && a?.flexible === b?.flexible && a?.view === b?.view && sameOptionValues(a?.options ?? {}, b?.options ?? {});
2085
2365
  }
2086
2366
  function setWidgetSettings(layout, key, patch) {
2087
2367
  const before = layout.settings?.[key];
2088
2368
  const draft = { ...before };
2089
- for (const field of ["title", "width", "flexible", "options"]) {
2369
+ for (const field of [
2370
+ "title",
2371
+ "width",
2372
+ "flexible",
2373
+ "options",
2374
+ "view"
2375
+ ]) {
2090
2376
  if (!(field in patch)) continue;
2091
2377
  const value = patch[field];
2092
2378
  if (value === void 0) {
@@ -2790,7 +3076,8 @@ function draftFrom(settings) {
2790
3076
  title: settings?.title ?? "",
2791
3077
  width: settings?.width,
2792
3078
  flexible: settings?.flexible,
2793
- options: settings?.options ?? {}
3079
+ options: settings?.options ?? {},
3080
+ view: settings?.view
2794
3081
  };
2795
3082
  }
2796
3083
  function storedDefault(option) {
@@ -2815,7 +3102,9 @@ function patchFrom(definition, draft) {
2815
3102
  title: title === "" ? void 0 : title,
2816
3103
  width: draft.width,
2817
3104
  flexible: draft.flexible === void 0 || draft.flexible === isFlexible(definition) ? void 0 : draft.flexible,
2818
- options: Object.keys(options).length === 0 ? void 0 : options
3105
+ options: Object.keys(options).length === 0 ? void 0 : options,
3106
+ // Choosing the author's own default view again clears the setting.
3107
+ view: draft.view === void 0 || draft.view === effectiveViewKey(definition, void 0) ? void 0 : draft.view
2819
3108
  };
2820
3109
  }
2821
3110
  function patchKey(patch) {
@@ -2823,13 +3112,20 @@ function patchKey(patch) {
2823
3112
  for (const key of Object.keys(patch.options ?? {}).sort()) {
2824
3113
  options[key] = patch.options[key];
2825
3114
  }
2826
- return JSON.stringify([patch.title, patch.width, patch.flexible, options]);
3115
+ return JSON.stringify([
3116
+ patch.title,
3117
+ patch.width,
3118
+ patch.flexible,
3119
+ options,
3120
+ patch.view
3121
+ ]);
2827
3122
  }
2828
3123
  var EMPTY_PATCH = {
2829
3124
  title: void 0,
2830
3125
  width: void 0,
2831
3126
  flexible: void 0,
2832
- options: void 0
3127
+ options: void 0,
3128
+ view: void 0
2833
3129
  };
2834
3130
  function WidgetOptionsDialog({
2835
3131
  definition,
@@ -2867,6 +3163,7 @@ function WidgetOptionsDialog({
2867
3163
  const canRevert = baselineKey !== void 0 && baselineKey !== savedKey;
2868
3164
  const canReset = savedKey !== patchKey(EMPTY_PATCH);
2869
3165
  const range = widthRange(definition);
3166
+ const views = viewChoices(definition);
2870
3167
  const effectiveFlexible = draft.flexible ?? isFlexible(definition);
2871
3168
  function requestClose() {
2872
3169
  if (confirming !== void 0) {
@@ -2992,6 +3289,21 @@ function WidgetOptionsDialog({
2992
3289
  ),
2993
3290
  labels.optionsFlexible
2994
3291
  ] }),
3292
+ views.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dwt-options-field", children: [
3293
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: `${baseId}-view`, children: labels.optionsViewField }),
3294
+ /* @__PURE__ */ jsxRuntime.jsx(
3295
+ "select",
3296
+ {
3297
+ id: `${baseId}-view`,
3298
+ value: effectiveViewKey(definition, draft.view),
3299
+ onChange: (event) => {
3300
+ const value = event.target.value;
3301
+ setDraft((current) => ({ ...current, view: value }));
3302
+ },
3303
+ children: views.map((view) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: view.key, children: view.key === effectiveViewKey(definition, void 0) ? labels.viewDefault(view.label) : view.label }, view.key))
3304
+ }
3305
+ )
3306
+ ] }),
2995
3307
  (definition.options?.length ?? 0) > 0 && /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { className: "dwt-options-group", children: [
2996
3308
  /* @__PURE__ */ jsxRuntime.jsx("legend", { children: labels.optionsDeclared }),
2997
3309
  (definition.options ?? []).map((option) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -3971,10 +4283,13 @@ function DashboardInner({
3971
4283
  className
3972
4284
  }) {
3973
4285
  const slot = useSlotClassName();
3974
- const { labels } = useWidgetSettings();
4286
+ const { labels, locale } = useWidgetSettings();
3975
4287
  const effectiveWidgets = widgets.map((w) => {
3976
- const settings = savedLayout.settings?.[w.definition.key];
3977
- return settings === void 0 || !overrideLocks && isOptionsLocked(w.definition) ? w : { ...w, definition: applySettings(w.definition, settings) };
4288
+ const settings = !overrideLocks && isOptionsLocked(w.definition) ? void 0 : savedLayout.settings?.[w.definition.key];
4289
+ const styled = settings === void 0 ? w : { ...w, definition: applySettings(w.definition, settings) };
4290
+ return applyView(styled, settings?.view, {
4291
+ ...locale === void 0 ? {} : { locale }
4292
+ });
3978
4293
  });
3979
4294
  const definitions = effectiveWidgets.map(
3980
4295
  (w) => overrideLocks ? withoutLock(w.definition) : w.definition
@@ -4819,45 +5134,6 @@ function useStoredLayout(options) {
4819
5134
  };
4820
5135
  }
4821
5136
 
4822
- // src/core/payload.ts
4823
- var WIDGET_KINDS = [
4824
- "TEXT",
4825
- "KPI",
4826
- "GAUGE",
4827
- "TABLE",
4828
- "BAR_LIST",
4829
- "ALERT_LIST",
4830
- "GRAPH"
4831
- ];
4832
- function isWidgetKind(value) {
4833
- return typeof value === "string" && WIDGET_KINDS.includes(value);
4834
- }
4835
- var VALUE_FORMATS = ["number", "currency", "percent"];
4836
- var MAX_GRAPH_SERIES = 8;
4837
- var DEFAULT_EMPTY_TEXT = "Nothing to show yet.";
4838
- function isWidgetEmptyState(payload) {
4839
- return "empty" in payload && payload.empty === true;
4840
- }
4841
- function emptyTextFor(payload) {
4842
- if (isWidgetEmptyState(payload)) {
4843
- return payload.text;
4844
- }
4845
- switch (payload.kind) {
4846
- case "TABLE":
4847
- return payload.rows.length === 0 ? DEFAULT_EMPTY_TEXT : null;
4848
- case "BAR_LIST":
4849
- return payload.items.length === 0 ? DEFAULT_EMPTY_TEXT : null;
4850
- case "ALERT_LIST":
4851
- return payload.items.length === 0 ? payload.emptyText : null;
4852
- case "GRAPH":
4853
- return payload.series.every((series) => series.points.length === 0) ? DEFAULT_EMPTY_TEXT : null;
4854
- case "TEXT":
4855
- case "KPI":
4856
- case "GAUGE":
4857
- return null;
4858
- }
4859
- }
4860
-
4861
5137
  // src/core/validate.ts
4862
5138
  var MAX_REPORTED_PROBLEMS = 10;
4863
5139
  function describeType2(value) {