@fusedashlabs/widgets 0.4.0 → 0.4.2

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.
Files changed (117) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +1 -1
  3. package/dist/components/chart-renderer/element/ui9000-chart-renderer.d.ts +1 -0
  4. package/dist/components/chart-renderer/element/ui9000-chart-renderer.d.ts.map +1 -1
  5. package/dist/components/chart-renderer/element/ui9000-chart-renderer.js +18 -4
  6. package/dist/components/chart-renderer/element/ui9000-chart-renderer.js.map +1 -1
  7. package/dist/components/chart-renderer/lib/registry.d.ts.map +1 -1
  8. package/dist/components/chart-renderer/lib/registry.js +3 -1
  9. package/dist/components/chart-renderer/lib/registry.js.map +1 -1
  10. package/dist/components/kpi-widget/element/styles.d.ts.map +1 -1
  11. package/dist/components/kpi-widget/element/styles.js +268 -5
  12. package/dist/components/kpi-widget/element/styles.js.map +1 -1
  13. package/dist/components/kpi-widget/element/ui9000-kpi-widget.d.ts +10 -1
  14. package/dist/components/kpi-widget/element/ui9000-kpi-widget.d.ts.map +1 -1
  15. package/dist/components/kpi-widget/element/ui9000-kpi-widget.js +154 -27
  16. package/dist/components/kpi-widget/element/ui9000-kpi-widget.js.map +1 -1
  17. package/dist/components/kpi-widget/index.d.ts +1 -1
  18. package/dist/components/kpi-widget/index.d.ts.map +1 -1
  19. package/dist/components/kpi-widget/index.js +7 -4
  20. package/dist/components/kpi-widget/lib/extract.d.ts +5 -1
  21. package/dist/components/kpi-widget/lib/extract.d.ts.map +1 -1
  22. package/dist/components/kpi-widget/lib/extract.js +110 -34
  23. package/dist/components/kpi-widget/lib/extract.js.map +1 -1
  24. package/dist/components/kpi-widget/lib/format.d.ts +5 -0
  25. package/dist/components/kpi-widget/lib/format.d.ts.map +1 -1
  26. package/dist/components/kpi-widget/lib/format.js +10 -1
  27. package/dist/components/kpi-widget/lib/format.js.map +1 -1
  28. package/dist/components/kpi-widget/lib/grid.d.ts +5 -0
  29. package/dist/components/kpi-widget/lib/grid.d.ts.map +1 -1
  30. package/dist/components/kpi-widget/lib/grid.js +18 -0
  31. package/dist/components/kpi-widget/lib/grid.js.map +1 -1
  32. package/dist/components/kpi-widget/lib/index.d.ts +5 -5
  33. package/dist/components/kpi-widget/lib/index.d.ts.map +1 -1
  34. package/dist/components/kpi-widget/lib/normalize.d.ts +2 -0
  35. package/dist/components/kpi-widget/lib/normalize.d.ts.map +1 -1
  36. package/dist/components/kpi-widget/lib/normalize.js +73 -12
  37. package/dist/components/kpi-widget/lib/normalize.js.map +1 -1
  38. package/dist/components/kpi-widget/lib/types.d.ts +41 -0
  39. package/dist/components/kpi-widget/lib/types.d.ts.map +1 -1
  40. package/dist/components/kpi-widget/metadata.json.d.ts +1 -1
  41. package/dist/components/kpi-widget/metadata.json.js +1 -1
  42. package/dist/components/map-chart/element/ui9000-map-chart.d.ts +1 -0
  43. package/dist/components/map-chart/element/ui9000-map-chart.d.ts.map +1 -1
  44. package/dist/components/map-chart/element/ui9000-map-chart.js +12 -4
  45. package/dist/components/map-chart/element/ui9000-map-chart.js.map +1 -1
  46. package/dist/components/map-chart/lib/geojson.d.ts.map +1 -1
  47. package/dist/components/map-chart/lib/geojson.js +3 -1
  48. package/dist/components/map-chart/lib/geojson.js.map +1 -1
  49. package/dist/components/map-chart/metadata.json.d.ts +1 -1
  50. package/dist/components/map-chart/metadata.json.js +1 -1
  51. package/dist/components/map-chart/render/draw.d.ts.map +1 -1
  52. package/dist/components/map-chart/render/draw.js +2 -1
  53. package/dist/components/map-chart/render/draw.js.map +1 -1
  54. package/dist/components/partial-dependence-chart/element/styles.d.ts +2 -0
  55. package/dist/components/partial-dependence-chart/element/styles.d.ts.map +1 -0
  56. package/dist/components/partial-dependence-chart/element/styles.js +48 -0
  57. package/dist/components/partial-dependence-chart/element/styles.js.map +1 -0
  58. package/dist/components/partial-dependence-chart/element/ui9000-partial-dependence-chart.d.ts +30 -0
  59. package/dist/components/partial-dependence-chart/element/ui9000-partial-dependence-chart.d.ts.map +1 -0
  60. package/dist/components/partial-dependence-chart/element/ui9000-partial-dependence-chart.js +163 -0
  61. package/dist/components/partial-dependence-chart/element/ui9000-partial-dependence-chart.js.map +1 -0
  62. package/dist/components/partial-dependence-chart/index.d.ts +5 -0
  63. package/dist/components/partial-dependence-chart/index.d.ts.map +1 -0
  64. package/dist/components/partial-dependence-chart/index.js +18 -0
  65. package/dist/components/partial-dependence-chart/index.js.map +1 -0
  66. package/dist/components/partial-dependence-chart/lib/domain.d.ts +6 -0
  67. package/dist/components/partial-dependence-chart/lib/domain.d.ts.map +1 -0
  68. package/dist/components/partial-dependence-chart/lib/domain.js +16 -0
  69. package/dist/components/partial-dependence-chart/lib/domain.js.map +1 -0
  70. package/dist/components/partial-dependence-chart/lib/index.d.ts +5 -0
  71. package/dist/components/partial-dependence-chart/lib/index.d.ts.map +1 -0
  72. package/dist/components/partial-dependence-chart/lib/normalize.d.ts +4 -0
  73. package/dist/components/partial-dependence-chart/lib/normalize.d.ts.map +1 -0
  74. package/dist/components/partial-dependence-chart/lib/normalize.js +167 -0
  75. package/dist/components/partial-dependence-chart/lib/normalize.js.map +1 -0
  76. package/dist/components/partial-dependence-chart/lib/series.d.ts +15 -0
  77. package/dist/components/partial-dependence-chart/lib/series.d.ts.map +1 -0
  78. package/dist/components/partial-dependence-chart/lib/series.js +63 -0
  79. package/dist/components/partial-dependence-chart/lib/series.js.map +1 -0
  80. package/dist/components/partial-dependence-chart/lib/types.d.ts +50 -0
  81. package/dist/components/partial-dependence-chart/lib/types.d.ts.map +1 -0
  82. package/dist/components/partial-dependence-chart/metadata.json.d.ts +80 -0
  83. package/dist/components/partial-dependence-chart/metadata.json.js +42 -0
  84. package/dist/components/partial-dependence-chart/metadata.json.js.map +1 -0
  85. package/dist/components/partial-dependence-chart/render/draw.d.ts +34 -0
  86. package/dist/components/partial-dependence-chart/render/draw.d.ts.map +1 -0
  87. package/dist/components/partial-dependence-chart/render/draw.js +95 -0
  88. package/dist/components/partial-dependence-chart/render/draw.js.map +1 -0
  89. package/dist/components/partial-dependence-chart/render/index.d.ts +2 -0
  90. package/dist/components/partial-dependence-chart/render/index.d.ts.map +1 -0
  91. package/dist/index.d.ts +5 -3
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +25 -10
  94. package/dist/index.js.map +1 -1
  95. package/dist/lazy/index.d.ts +2 -1
  96. package/dist/lazy/index.d.ts.map +1 -1
  97. package/dist/lazy/index.js +8 -0
  98. package/dist/lazy/index.js.map +1 -1
  99. package/dist/lazy/partial-dependence-chart.d.ts +4 -0
  100. package/dist/lazy/partial-dependence-chart.d.ts.map +1 -0
  101. package/dist/lazy/partial-dependence-chart.js +5 -0
  102. package/dist/lazy/partial-dependence-chart.js.map +1 -0
  103. package/dist/utils/chart-helpers.d.ts +2 -0
  104. package/dist/utils/chart-helpers.d.ts.map +1 -1
  105. package/dist/utils/chart-helpers.js +15 -0
  106. package/dist/utils/chart-helpers.js.map +1 -1
  107. package/dist/utils/fusedash-visual.d.ts +12 -0
  108. package/dist/utils/fusedash-visual.d.ts.map +1 -1
  109. package/dist/utils/fusedash-visual.js +7 -0
  110. package/dist/utils/fusedash-visual.js.map +1 -1
  111. package/package.json +11 -2
  112. package/dist/node_modules/lit-html/directive.js +0 -28
  113. package/dist/node_modules/lit-html/directive.js.map +0 -1
  114. package/dist/node_modules/lit-html/directives/style-map.js +0 -37
  115. package/dist/node_modules/lit-html/directives/style-map.js.map +0 -1
  116. package/dist/node_modules/lit-html/lit-html.js +0 -231
  117. package/dist/node_modules/lit-html/lit-html.js.map +0 -1
@@ -1,11 +1,12 @@
1
1
  import { nothing, html } from "lit";
2
- import { styleMap as o } from "../../../node_modules/lit-html/directives/style-map.js";
2
+ import { styleMap } from "lit/directives/style-map.js";
3
3
  import { property, state, customElement } from "lit/decorators.js";
4
4
  import { Ui9000ChartElement } from "../../../element/ui9000-chart-base.js";
5
5
  import { parseJsonAttr } from "../../../utils/chart-helpers.js";
6
6
  import { kpiWidgetStyles } from "./styles.js";
7
7
  import { normalizeKpiData } from "../lib/normalize.js";
8
- import { getKpiGridColumns, MIN_KPI_CELL_WIDTH, getKpiGridScrollAxis } from "../lib/grid.js";
8
+ import { getKpiGridColumns, getAdvancedSplitLayout, MIN_KPI_CELL_WIDTH, getKpiGridScrollAxis } from "../lib/grid.js";
9
+ import { visualisationChartType } from "../lib/extract.js";
9
10
  import { chartShellStyles } from "../../../element/chart-shell-styles.js";
10
11
  var __defProp = Object.defineProperty;
11
12
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -21,75 +22,198 @@ let Ui9000KpiWidget = class extends Ui9000ChartElement {
21
22
  constructor() {
22
23
  super(...arguments);
23
24
  this.scale = "default";
24
- this._cards = [];
25
+ this._model = { layout: "empty", cards: [], supporting: [] };
25
26
  this._gridColumns = 1;
26
27
  this._scrollAxis = "vertical";
28
+ this._advancedSplit = "horizontal";
29
+ this._raf = 0;
30
+ this._rendererLoading = false;
27
31
  }
28
32
  connectedCallback() {
29
33
  super.connectedCallback();
30
- this._resizeObserver = new ResizeObserver(() => this.updateGridLayout());
34
+ this._resizeObserver = new ResizeObserver(() => this.scheduleLayout());
31
35
  this._resizeObserver.observe(this);
32
36
  }
33
37
  disconnectedCallback() {
34
38
  var _a;
35
39
  (_a = this._resizeObserver) == null ? void 0 : _a.disconnect();
40
+ cancelAnimationFrame(this._raf);
36
41
  super.disconnectedCallback();
37
42
  }
38
43
  updated(changed) {
39
44
  if (changed.has("dataJson")) {
40
- const model = normalizeKpiData(parseJsonAttr(this.dataJson, null));
41
- this._cards = model.cards;
45
+ this._model = normalizeKpiData(parseJsonAttr(this.dataJson, null));
42
46
  this.updateGridLayout();
47
+ this.ensureChartRenderer();
43
48
  }
44
49
  }
50
+ /** Nested viz uses `<ui9000-chart-renderer>` — load it only for advanced KPIs. */
51
+ ensureChartRenderer() {
52
+ if (this._rendererLoading || this._model.layout !== "advanced" || !this._model.visualisation) {
53
+ return;
54
+ }
55
+ this._rendererLoading = true;
56
+ void import("../../chart-renderer/index.js").then(() => this.requestUpdate());
57
+ }
58
+ scheduleLayout() {
59
+ cancelAnimationFrame(this._raf);
60
+ this._raf = requestAnimationFrame(() => this.updateGridLayout());
61
+ }
45
62
  updateGridLayout() {
46
- const count = this._cards.length;
47
- const columns = getKpiGridColumns(this.clientWidth, count);
63
+ const layout = this._model.layout;
64
+ const count = layout === "advanced" ? this._model.supporting.length : this._model.cards.length;
65
+ const columns = getKpiGridColumns(this.clientWidth, Math.max(count, 1));
48
66
  const scrollAxis = getKpiGridScrollAxis(columns, count);
67
+ const split = getAdvancedSplitLayout(
68
+ this.clientWidth,
69
+ this.clientHeight,
70
+ this._advancedSplit
71
+ );
49
72
  if (columns !== this._gridColumns) this._gridColumns = columns;
50
73
  if (scrollAxis !== this._scrollAxis) this._scrollAxis = scrollAxis;
74
+ if (split !== this._advancedSplit) this._advancedSplit = split;
75
+ }
76
+ renderBadge(card) {
77
+ if (!card.status) return nothing;
78
+ return html`
79
+ <span class="kpi-badge" data-variant=${card.status.variant}>${card.status.text}</span>
80
+ `;
51
81
  }
52
82
  renderValue(card) {
53
83
  const label = card.label;
54
84
  const unitLeft = (label == null ? void 0 : label.position) === "left" && label.text;
55
85
  const unitRight = (label == null ? void 0 : label.position) === "right" && label.text;
86
+ const showIndicator = Boolean(card.showPercentage && card.indicator);
56
87
  return html`
57
88
  <div class="kpi-value-row">
58
89
  ${unitLeft ? html`<span class="kpi-unit">${unitLeft}</span>` : nothing}
59
90
  <h2 class="kpi-value">${card.value}</h2>
91
+ ${card.suffix ? html`<span class="kpi-suffix">${card.suffix}</span>` : nothing}
60
92
  ${unitRight ? html`<span class="kpi-unit">${unitRight}</span>` : nothing}
61
- ${card.indicator ? html`<span class="kpi-indicator">${card.indicator}</span>` : nothing}
93
+ ${showIndicator ? html`
94
+ <span class="kpi-indicator">
95
+ <span class="kpi-indicator-arrow" aria-hidden="true">›</span>
96
+ ${card.indicator}
97
+ </span>
98
+ ` : nothing}
62
99
  </div>
63
100
  `;
64
101
  }
65
- renderCard(card) {
102
+ renderCard(card, role) {
103
+ const showName = !card.hideName;
104
+ const showHeaderBadge = Boolean(card.status) && (role === "single" || role === "main");
105
+ const showInlineBadge = Boolean(card.status) && !showHeaderBadge;
66
106
  return html`
67
- <article class="kpi-card" aria-label=${card.name}>
68
- <p class="kpi-name">${card.name}</p>
107
+ <article class="kpi-card" data-role=${role} aria-label=${card.name}>
108
+ ${showName || showHeaderBadge ? html`
109
+ <div class="kpi-header">
110
+ ${showName ? html`<p class="kpi-name">${card.name}</p>` : html`<span></span>`}
111
+ ${showHeaderBadge ? this.renderBadge(card) : nothing}
112
+ </div>
113
+ ` : nothing}
69
114
  ${this.renderValue(card)}
70
- ${card.subtitle ? html`<p class="kpi-subtitle">${card.subtitle}</p>` : nothing}
115
+ ${showInlineBadge || card.subtitle ? html`
116
+ <div class="kpi-subtitle-row">
117
+ ${showInlineBadge ? this.renderBadge(card) : nothing}
118
+ ${card.subtitle ? html`<p class="kpi-subtitle">${card.subtitle}</p>` : nothing}
119
+ </div>
120
+ ` : nothing}
71
121
  </article>
72
122
  `;
73
123
  }
74
- render() {
75
- const title = this.headerTitle();
76
- const single = this._cards.length <= 1;
77
- const gridStyle = single ? nothing : o({
124
+ renderFooter() {
125
+ const { target, showTimestamp, updatedAt } = this._model;
126
+ const hasTarget = Boolean(target && String(target).length);
127
+ const updated = showTimestamp && updatedAt ? new Date(updatedAt).toLocaleDateString("en-US") : "";
128
+ if (!hasTarget && !updated) return nothing;
129
+ return html`
130
+ <div class="kpi-footer">
131
+ ${hasTarget ? html`
132
+ <div class="kpi-footer-block">
133
+ <span class="kpi-footer-label">Target</span>
134
+ <span class="kpi-footer-value">${target}</span>
135
+ </div>
136
+ ` : html`<span></span>`}
137
+ ${updated ? html`
138
+ <div class="kpi-footer-block">
139
+ <span class="kpi-footer-label">Updated</span>
140
+ <span class="kpi-footer-value">${updated}</span>
141
+ </div>
142
+ ` : nothing}
143
+ </div>
144
+ `;
145
+ }
146
+ renderAdvanced() {
147
+ const main = this._model.main;
148
+ if (!main) return nothing;
149
+ const viz = this._model.visualisation;
150
+ const chartType = viz && typeof viz === "object" ? visualisationChartType({ visualisation: viz }) : void 0;
151
+ const supporting = this._model.supporting;
152
+ const supportingStyle = supporting.length > 0 ? styleMap({
78
153
  gridTemplateColumns: `repeat(${this._gridColumns}, minmax(${MIN_KPI_CELL_WIDTH}px, 1fr))`
79
- });
154
+ }) : nothing;
80
155
  return html`
81
- <div class="kpi-root" data-single=${single ? "true" : "false"}>
82
- ${this.renderShellHeader(title)}
83
- ${this._cards.length === 0 ? html`<div class="empty">No KPI data</div>` : html`
156
+ <div class="kpi-advanced">
157
+ <div class="kpi-advanced-main">
158
+ <article class="kpi-card" data-role="main" aria-label=${main.name}>
159
+ <div class="kpi-header">
160
+ <p class="kpi-name">${main.name}</p>
161
+ ${this.renderBadge(main)}
162
+ </div>
163
+ <div class="kpi-advanced-split" data-split=${this._advancedSplit}>
164
+ <div class="kpi-advanced-value">${this.renderValue(main)}</div>
165
+ ${viz && chartType ? html`
166
+ <div class="kpi-advanced-chart">
167
+ <ui9000-chart-renderer
168
+ data=${JSON.stringify(viz)}
169
+ chart-type=${chartType}
170
+ scale=${this.scale}
171
+ show-header="false"
172
+ show-legend="false"
173
+ show-grid
174
+ show-tooltip
175
+ ></ui9000-chart-renderer>
176
+ </div>
177
+ ` : nothing}
178
+ </div>
179
+ </article>
180
+ </div>
181
+ ${supporting.length ? html`
84
182
  <div
85
- class="kpi-grid"
86
- data-layout=${single ? "single" : "grid"}
183
+ class="kpi-supporting"
87
184
  data-scroll=${this._scrollAxis}
88
- ${gridStyle}
185
+ style=${supportingStyle}
89
186
  >
90
- ${this._cards.map((card) => this.renderCard(card))}
187
+ ${supporting.map((card) => this.renderCard(card, "supporting"))}
91
188
  </div>
92
- `}
189
+ ` : nothing}
190
+ </div>
191
+ `;
192
+ }
193
+ render() {
194
+ const model = this._model;
195
+ const layout = model.layout;
196
+ const single = layout === "single";
197
+ const title = model.title;
198
+ const gridStyle = layout === "grid" ? styleMap({
199
+ gridTemplateColumns: `repeat(${this._gridColumns}, minmax(${MIN_KPI_CELL_WIDTH}px, 1fr))`
200
+ }) : nothing;
201
+ return html`
202
+ <div class="kpi-root" data-layout=${layout} data-single=${single ? "true" : "false"}>
203
+ ${title ? html`<div class="kpi-section-title">${title}</div>` : nothing}
204
+ ${layout === "empty" ? html`<div class="empty">No KPI data</div>` : layout === "advanced" ? this.renderAdvanced() : html`
205
+ <div
206
+ class="kpi-grid"
207
+ data-layout=${single ? "single" : "grid"}
208
+ data-scroll=${this._scrollAxis}
209
+ style=${gridStyle}
210
+ >
211
+ ${model.cards.map(
212
+ (card) => this.renderCard(card, single ? "single" : "grid")
213
+ )}
214
+ </div>
215
+ `}
216
+ ${this.renderFooter()}
93
217
  ${this.renderShellLabelTooltip()}
94
218
  </div>
95
219
  `;
@@ -101,13 +225,16 @@ __decorateClass([
101
225
  ], Ui9000KpiWidget.prototype, "scale", 2);
102
226
  __decorateClass([
103
227
  state()
104
- ], Ui9000KpiWidget.prototype, "_cards", 2);
228
+ ], Ui9000KpiWidget.prototype, "_model", 2);
105
229
  __decorateClass([
106
230
  state()
107
231
  ], Ui9000KpiWidget.prototype, "_gridColumns", 2);
108
232
  __decorateClass([
109
233
  state()
110
234
  ], Ui9000KpiWidget.prototype, "_scrollAxis", 2);
235
+ __decorateClass([
236
+ state()
237
+ ], Ui9000KpiWidget.prototype, "_advancedSplit", 2);
111
238
  Ui9000KpiWidget = __decorateClass([
112
239
  customElement("ui9000-kpi-widget")
113
240
  ], Ui9000KpiWidget);
@@ -1 +1 @@
1
- {"version":3,"file":"ui9000-kpi-widget.js","sources":["../../../../src/components/kpi-widget/element/ui9000-kpi-widget.ts"],"sourcesContent":["import { html, nothing, type PropertyValues } from 'lit';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport { customElement, property, state } from 'lit/decorators.js';\n\nimport { chartShellStyles, Ui9000ChartElement } from '../../../element/ui9000-chart-base.js';\nimport type { WidgetScale } from '../../../types/index.js';\nimport { parseJsonAttr } from '../../../utils/chart-helpers.js';\nimport {\n getKpiGridColumns,\n getKpiGridScrollAxis,\n MIN_KPI_CELL_WIDTH,\n normalizeKpiData,\n type KpiCardModel,\n type KpiGridScrollAxis,\n type KpiWidgetData,\n} from '../lib/index.js';\nimport { kpiWidgetStyles } from './styles.js';\n\n@customElement('ui9000-kpi-widget')\nexport class Ui9000KpiWidget extends Ui9000ChartElement {\n static override styles = [kpiWidgetStyles, chartShellStyles];\n\n @property({ type: String, attribute: 'scale' })\n scale: WidgetScale = 'default';\n\n @state()\n private _cards: KpiCardModel[] = [];\n\n @state()\n private _gridColumns = 1;\n\n @state()\n private _scrollAxis: KpiGridScrollAxis = 'vertical';\n\n private _resizeObserver?: ResizeObserver;\n\n override connectedCallback(): void {\n super.connectedCallback();\n this._resizeObserver = new ResizeObserver(() => this.updateGridLayout());\n this._resizeObserver.observe(this);\n }\n\n override disconnectedCallback(): void {\n this._resizeObserver?.disconnect();\n super.disconnectedCallback();\n }\n\n override updated(changed: PropertyValues): void {\n if (changed.has('dataJson')) {\n const model = normalizeKpiData(parseJsonAttr<KpiWidgetData>(this.dataJson, null));\n this._cards = model.cards;\n this.updateGridLayout();\n }\n }\n\n private updateGridLayout(): void {\n const count = this._cards.length;\n const columns = getKpiGridColumns(this.clientWidth, count);\n const scrollAxis = getKpiGridScrollAxis(columns, count);\n if (columns !== this._gridColumns) this._gridColumns = columns;\n if (scrollAxis !== this._scrollAxis) this._scrollAxis = scrollAxis;\n }\n\n private renderValue(card: KpiCardModel) {\n const label = card.label;\n const unitLeft = label?.position === 'left' && label.text;\n const unitRight = label?.position === 'right' && label.text;\n\n return html`\n <div class=\"kpi-value-row\">\n ${unitLeft ? html`<span class=\"kpi-unit\">${unitLeft}</span>` : nothing}\n <h2 class=\"kpi-value\">${card.value}</h2>\n ${unitRight ? html`<span class=\"kpi-unit\">${unitRight}</span>` : nothing}\n ${card.indicator ? html`<span class=\"kpi-indicator\">${card.indicator}</span>` : nothing}\n </div>\n `;\n }\n\n private renderCard(card: KpiCardModel) {\n return html`\n <article class=\"kpi-card\" aria-label=${card.name}>\n <p class=\"kpi-name\">${card.name}</p>\n ${this.renderValue(card)}\n ${card.subtitle ? html`<p class=\"kpi-subtitle\">${card.subtitle}</p>` : nothing}\n </article>\n `;\n }\n\n override render() {\n const title = this.headerTitle();\n const single = this._cards.length <= 1;\n\n const gridStyle = single\n ? nothing\n : styleMap({\n gridTemplateColumns: `repeat(${this._gridColumns}, minmax(${MIN_KPI_CELL_WIDTH}px, 1fr))`,\n });\n\n return html`\n <div class=\"kpi-root\" data-single=${single ? 'true' : 'false'}>\n ${this.renderShellHeader(title)}\n ${this._cards.length === 0\n ? html`<div class=\"empty\">No KPI data</div>`\n : html`\n <div\n class=\"kpi-grid\"\n data-layout=${single ? 'single' : 'grid'}\n data-scroll=${this._scrollAxis}\n ${gridStyle}\n >\n ${this._cards.map((card) => this.renderCard(card))}\n </div>\n `}\n ${this.renderShellLabelTooltip()}\n </div>\n `;\n }\n}\n\nexport function registerKpiWidget(): void {\n if (!customElements.get('ui9000-kpi-widget')) {\n customElements.define('ui9000-kpi-widget', Ui9000KpiWidget);\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ui9000-kpi-widget': Ui9000KpiWidget;\n }\n}\n"],"names":["styleMap"],"mappings":"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"}
1
+ {"version":3,"file":"ui9000-kpi-widget.js","sources":["../../../../src/components/kpi-widget/element/ui9000-kpi-widget.ts"],"sourcesContent":["import { html, nothing, type PropertyValues } from 'lit';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport { customElement, property, state } from 'lit/decorators.js';\n\nimport { chartShellStyles, Ui9000ChartElement } from '../../../element/ui9000-chart-base.js';\nimport type { WidgetScale } from '../../../types/index.js';\nimport { parseJsonAttr } from '../../../utils/chart-helpers.js';\nimport {\n getAdvancedSplitLayout,\n getKpiGridColumns,\n getKpiGridScrollAxis,\n MIN_KPI_CELL_WIDTH,\n visualisationChartType,\n normalizeKpiData,\n type AdvancedSplitLayout,\n type KpiCardModel,\n type KpiCardRole,\n type KpiGridScrollAxis,\n type KpiWidgetData,\n type KpiWidgetModel,\n} from '../lib/index.js';\nimport { kpiWidgetStyles } from './styles.js';\n\n@customElement('ui9000-kpi-widget')\nexport class Ui9000KpiWidget extends Ui9000ChartElement {\n static override styles = [kpiWidgetStyles, chartShellStyles];\n\n @property({ type: String, attribute: 'scale' })\n scale: WidgetScale = 'default';\n\n @state()\n private _model: KpiWidgetModel = { layout: 'empty', cards: [], supporting: [] };\n\n @state()\n private _gridColumns = 1;\n\n @state()\n private _scrollAxis: KpiGridScrollAxis = 'vertical';\n\n @state()\n private _advancedSplit: AdvancedSplitLayout = 'horizontal';\n\n private _resizeObserver?: ResizeObserver;\n private _raf = 0;\n private _rendererLoading = false;\n\n override connectedCallback(): void {\n super.connectedCallback();\n this._resizeObserver = new ResizeObserver(() => this.scheduleLayout());\n this._resizeObserver.observe(this);\n }\n\n override disconnectedCallback(): void {\n this._resizeObserver?.disconnect();\n cancelAnimationFrame(this._raf);\n super.disconnectedCallback();\n }\n\n override updated(changed: PropertyValues): void {\n if (changed.has('dataJson')) {\n this._model = normalizeKpiData(parseJsonAttr<KpiWidgetData>(this.dataJson, null));\n this.updateGridLayout();\n this.ensureChartRenderer();\n }\n }\n\n /** Nested viz uses `<ui9000-chart-renderer>` — load it only for advanced KPIs. */\n private ensureChartRenderer(): void {\n if (\n this._rendererLoading ||\n this._model.layout !== 'advanced' ||\n !this._model.visualisation\n ) {\n return;\n }\n this._rendererLoading = true;\n void import('../../chart-renderer/index.js').then(() => this.requestUpdate());\n }\n\n private scheduleLayout(): void {\n cancelAnimationFrame(this._raf);\n this._raf = requestAnimationFrame(() => this.updateGridLayout());\n }\n\n private updateGridLayout(): void {\n const layout = this._model.layout;\n const count =\n layout === 'advanced' ? this._model.supporting.length : this._model.cards.length;\n const columns = getKpiGridColumns(this.clientWidth, Math.max(count, 1));\n const scrollAxis = getKpiGridScrollAxis(columns, count);\n const split = getAdvancedSplitLayout(\n this.clientWidth,\n this.clientHeight,\n this._advancedSplit,\n );\n if (columns !== this._gridColumns) this._gridColumns = columns;\n if (scrollAxis !== this._scrollAxis) this._scrollAxis = scrollAxis;\n if (split !== this._advancedSplit) this._advancedSplit = split;\n }\n\n private renderBadge(card: KpiCardModel) {\n if (!card.status) return nothing;\n return html`\n <span class=\"kpi-badge\" data-variant=${card.status.variant}>${card.status.text}</span>\n `;\n }\n\n private renderValue(card: KpiCardModel) {\n const label = card.label;\n const unitLeft = label?.position === 'left' && label.text;\n const unitRight = label?.position === 'right' && label.text;\n const showIndicator = Boolean(card.showPercentage && card.indicator);\n\n return html`\n <div class=\"kpi-value-row\">\n ${unitLeft ? html`<span class=\"kpi-unit\">${unitLeft}</span>` : nothing}\n <h2 class=\"kpi-value\">${card.value}</h2>\n ${card.suffix ? html`<span class=\"kpi-suffix\">${card.suffix}</span>` : nothing}\n ${unitRight ? html`<span class=\"kpi-unit\">${unitRight}</span>` : nothing}\n ${showIndicator\n ? html`\n <span class=\"kpi-indicator\">\n <span class=\"kpi-indicator-arrow\" aria-hidden=\"true\">›</span>\n ${card.indicator}\n </span>\n `\n : nothing}\n </div>\n `;\n }\n\n private renderCard(card: KpiCardModel, role: KpiCardRole) {\n const showName = !card.hideName;\n const showHeaderBadge = Boolean(card.status) && (role === 'single' || role === 'main');\n const showInlineBadge = Boolean(card.status) && !showHeaderBadge;\n\n return html`\n <article class=\"kpi-card\" data-role=${role} aria-label=${card.name}>\n ${showName || showHeaderBadge\n ? html`\n <div class=\"kpi-header\">\n ${showName ? html`<p class=\"kpi-name\">${card.name}</p>` : html`<span></span>`}\n ${showHeaderBadge ? this.renderBadge(card) : nothing}\n </div>\n `\n : nothing}\n ${this.renderValue(card)}\n ${showInlineBadge || card.subtitle\n ? html`\n <div class=\"kpi-subtitle-row\">\n ${showInlineBadge ? this.renderBadge(card) : nothing}\n ${card.subtitle ? html`<p class=\"kpi-subtitle\">${card.subtitle}</p>` : nothing}\n </div>\n `\n : nothing}\n </article>\n `;\n }\n\n private renderFooter() {\n const { target, showTimestamp, updatedAt } = this._model;\n const hasTarget = Boolean(target && String(target).length);\n const updated =\n showTimestamp && updatedAt\n ? new Date(updatedAt).toLocaleDateString('en-US')\n : '';\n if (!hasTarget && !updated) return nothing;\n\n return html`\n <div class=\"kpi-footer\">\n ${hasTarget\n ? html`\n <div class=\"kpi-footer-block\">\n <span class=\"kpi-footer-label\">Target</span>\n <span class=\"kpi-footer-value\">${target}</span>\n </div>\n `\n : html`<span></span>`}\n ${updated\n ? html`\n <div class=\"kpi-footer-block\">\n <span class=\"kpi-footer-label\">Updated</span>\n <span class=\"kpi-footer-value\">${updated}</span>\n </div>\n `\n : nothing}\n </div>\n `;\n }\n\n private renderAdvanced() {\n const main = this._model.main;\n if (!main) return nothing;\n const viz = this._model.visualisation;\n const chartType =\n viz && typeof viz === 'object'\n ? visualisationChartType({ visualisation: viz })\n : undefined;\n const supporting = this._model.supporting;\n const supportingStyle =\n supporting.length > 0\n ? styleMap({\n gridTemplateColumns: `repeat(${this._gridColumns}, minmax(${MIN_KPI_CELL_WIDTH}px, 1fr))`,\n })\n : nothing;\n\n return html`\n <div class=\"kpi-advanced\">\n <div class=\"kpi-advanced-main\">\n <article class=\"kpi-card\" data-role=\"main\" aria-label=${main.name}>\n <div class=\"kpi-header\">\n <p class=\"kpi-name\">${main.name}</p>\n ${this.renderBadge(main)}\n </div>\n <div class=\"kpi-advanced-split\" data-split=${this._advancedSplit}>\n <div class=\"kpi-advanced-value\">${this.renderValue(main)}</div>\n ${viz && chartType\n ? html`\n <div class=\"kpi-advanced-chart\">\n <ui9000-chart-renderer\n data=${JSON.stringify(viz)}\n chart-type=${chartType}\n scale=${this.scale}\n show-header=\"false\"\n show-legend=\"false\"\n show-grid\n show-tooltip\n ></ui9000-chart-renderer>\n </div>\n `\n : nothing}\n </div>\n </article>\n </div>\n ${supporting.length\n ? html`\n <div\n class=\"kpi-supporting\"\n data-scroll=${this._scrollAxis}\n style=${supportingStyle}\n >\n ${supporting.map((card) => this.renderCard(card, 'supporting'))}\n </div>\n `\n : nothing}\n </div>\n `;\n }\n\n override render() {\n const model = this._model;\n const layout = model.layout;\n const single = layout === 'single';\n const title = model.title;\n\n const gridStyle =\n layout === 'grid'\n ? styleMap({\n gridTemplateColumns: `repeat(${this._gridColumns}, minmax(${MIN_KPI_CELL_WIDTH}px, 1fr))`,\n })\n : nothing;\n\n return html`\n <div class=\"kpi-root\" data-layout=${layout} data-single=${single ? 'true' : 'false'}>\n ${title ? html`<div class=\"kpi-section-title\">${title}</div>` : nothing}\n ${layout === 'empty'\n ? html`<div class=\"empty\">No KPI data</div>`\n : layout === 'advanced'\n ? this.renderAdvanced()\n : html`\n <div\n class=\"kpi-grid\"\n data-layout=${single ? 'single' : 'grid'}\n data-scroll=${this._scrollAxis}\n style=${gridStyle}\n >\n ${model.cards.map((card) =>\n this.renderCard(card, single ? 'single' : 'grid'),\n )}\n </div>\n `}\n ${this.renderFooter()}\n ${this.renderShellLabelTooltip()}\n </div>\n `;\n }\n}\n\nexport function registerKpiWidget(): void {\n if (!customElements.get('ui9000-kpi-widget')) {\n customElements.define('ui9000-kpi-widget', Ui9000KpiWidget);\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ui9000-kpi-widget': Ui9000KpiWidget;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
1
  export { Ui9000KpiWidget, registerKpiWidget } from './element/ui9000-kpi-widget.js';
2
- export { normalizeKpiData, extractCardsFromKpi, formatKpiValue, getKpiGridColumns, getKpiGridScrollAxis, MIN_KPI_CELL_WIDTH, type KpiCardModel, type KpiGridScrollAxis, type KpiValueLabel, type KpiWidgetData, type KpiWidgetModel, type RawKpiItem, } from './lib/index.js';
2
+ export { normalizeKpiData, extractCardsFromKpi, isAdvancedKpiItem, formatKpiValue, splitFormattedKpiValue, getKpiGridColumns, getKpiGridScrollAxis, getAdvancedSplitLayout, MIN_KPI_CELL_WIDTH, type AdvancedSplitLayout, type KpiCardModel, type KpiGridScrollAxis, type KpiStatusBadge, type KpiValueLabel, type KpiWidgetData, type KpiWidgetLayout, type KpiWidgetModel, type RawKpiItem, } from './lib/index.js';
3
3
  export { default as kpiWidgetMetadata } from './metadata.json';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/kpi-widget/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACpF,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,EACd,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/kpi-widget/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACpF,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACd,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -1,19 +1,22 @@
1
1
  import { registerKpiWidget } from "./element/ui9000-kpi-widget.js";
2
2
  import { Ui9000KpiWidget } from "./element/ui9000-kpi-widget.js";
3
3
  import { default as default2 } from "./metadata.json.js";
4
- import { MIN_KPI_CELL_WIDTH, getKpiGridColumns, getKpiGridScrollAxis } from "./lib/grid.js";
5
- import { extractCardsFromKpi } from "./lib/extract.js";
6
- import { formatKpiValue } from "./lib/format.js";
4
+ import { MIN_KPI_CELL_WIDTH, getAdvancedSplitLayout, getKpiGridColumns, getKpiGridScrollAxis } from "./lib/grid.js";
5
+ import { extractCardsFromKpi, isAdvancedKpiItem } from "./lib/extract.js";
6
+ import { formatKpiValue, splitFormattedKpiValue } from "./lib/format.js";
7
7
  import { normalizeKpiData } from "./lib/normalize.js";
8
8
  export {
9
9
  MIN_KPI_CELL_WIDTH,
10
10
  Ui9000KpiWidget,
11
11
  extractCardsFromKpi,
12
12
  formatKpiValue,
13
+ getAdvancedSplitLayout,
13
14
  getKpiGridColumns,
14
15
  getKpiGridScrollAxis,
16
+ isAdvancedKpiItem,
15
17
  default2 as kpiWidgetMetadata,
16
18
  normalizeKpiData,
17
- registerKpiWidget
19
+ registerKpiWidget,
20
+ splitFormattedKpiValue
18
21
  };
19
22
  //# sourceMappingURL=index.js.map
@@ -1,4 +1,8 @@
1
- import { KpiCardModel, RawKpiItem } from './types.js';
1
+ import { KpiCardModel, RawKpiItem, RawKpiVisualisation } from './types.js';
2
+ export declare function isAdvancedKpiItem(kpi: RawKpiItem): boolean;
3
+ export declare function visualisationChartType(kpi: {
4
+ visualisation?: RawKpiVisualisation | null;
5
+ }): string | undefined;
2
6
  /** Map one FuseDash / MCP KPI item to display cards (mirrors KPIComponentWrapper). */
3
7
  export declare function extractCardsFromKpi(kpi: RawKpiItem): KpiCardModel[];
4
8
  //# sourceMappingURL=extract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../../../src/components/kpi-widget/lib/extract.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAiB,UAAU,EAAE,MAAM,YAAY,CAAC;AAsB1E,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,UAAU,GAAG,YAAY,EAAE,CA4EnE"}
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../../../src/components/kpi-widget/lib/extract.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,YAAY,EAIZ,UAAU,EACV,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAuEpB,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAM1D;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE;IAC1C,aAAa,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAC5C,GAAG,MAAM,GAAG,SAAS,CAKrB;AAED,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,UAAU,GAAG,YAAY,EAAE,CAyGnE"}
@@ -1,4 +1,5 @@
1
- import { formatKpiValue } from "./format.js";
1
+ import { formatKpiValue, splitFormattedKpiValue } from "./format.js";
2
+ const HIGH_LOW_TYPES = /* @__PURE__ */ new Set(["high/low", "high/low_overall", "high/low_trend"]);
2
3
  function valueLabel(axisInfo) {
3
4
  if (!axisInfo || typeof axisInfo !== "object") return void 0;
4
5
  const info = axisInfo;
@@ -10,6 +11,51 @@ function valueLabel(axisInfo) {
10
11
  function asRecord(value) {
11
12
  return value != null && typeof value === "object" && !Array.isArray(value) ? value : null;
12
13
  }
14
+ function formatDisplay(raw) {
15
+ const formatted = formatKpiValue(raw);
16
+ const split = splitFormattedKpiValue(formatted);
17
+ return split.suffix ? split : { value: formatted };
18
+ }
19
+ function percentLabel(raw) {
20
+ const n = Number(raw);
21
+ if (!Number.isFinite(n) || n === 0) return void 0;
22
+ return `${n.toFixed(2)}%`;
23
+ }
24
+ function statusFromKpi(kpi) {
25
+ var _a, _b;
26
+ if (kpi.kpiKind === "mission") {
27
+ return { text: "Mission", variant: "Mission" };
28
+ }
29
+ if (kpi.kpiKind === "goal") {
30
+ return kpi.targetStatus === "on_target" ? { text: "On target", variant: "OnTarget" } : { text: "Off target", variant: "OffTarget" };
31
+ }
32
+ const raw = (_a = kpi.status) == null ? void 0 : _a.trim();
33
+ if (!raw) return void 0;
34
+ const text = ((_b = raw.split("=")[0]) == null ? void 0 : _b.trim()) || raw;
35
+ const lower = raw.toLowerCase();
36
+ const variant = lower.includes("good") ? "Good" : lower.includes("low") ? "Low" : "Normal";
37
+ return { text, variant };
38
+ }
39
+ function withMeta(card, kpi) {
40
+ const scoreValue = kpi.kpiKind && typeof kpi.score === "number" ? `${kpi.score.toFixed(1)}%` : void 0;
41
+ return {
42
+ ...card,
43
+ ...scoreValue ? { value: scoreValue, suffix: void 0 } : {},
44
+ status: statusFromKpi(kpi),
45
+ showPercentage: Boolean(kpi.showPercentage)
46
+ };
47
+ }
48
+ function isAdvancedKpiItem(kpi) {
49
+ return Boolean(
50
+ kpi.supportingKpis || kpi.showVisualisation || kpi.variant === "advanced" && kpi.visualisation
51
+ );
52
+ }
53
+ function visualisationChartType(kpi) {
54
+ const viz = kpi.visualisation;
55
+ if (!viz || typeof viz !== "object") return void 0;
56
+ const type = viz.chartType ?? viz.chart_type;
57
+ return typeof type === "string" && type.trim() ? type.trim() : void 0;
58
+ }
13
59
  function extractCardsFromKpi(kpi) {
14
60
  var _a;
15
61
  const column = kpi.column ?? "value";
@@ -22,41 +68,62 @@ function extractCardsFromKpi(kpi) {
22
68
  const type = kpi.type ?? "single_value";
23
69
  const baseId = kpi.id ?? kpi.name ?? column;
24
70
  const baseName = kpi.name ?? column;
71
+ const showPct = Boolean(kpi.showPercentage);
25
72
  if (type === "trend" || type === "overall") {
26
- const pct = dataRow.percentage;
73
+ const display = formatDisplay(dataRow[fieldKey] ?? dataRow[column]);
74
+ const subtitle = typeof dataRow.subtitle === "string" ? dataRow.subtitle : void 0;
27
75
  return [
28
- {
29
- id: baseId,
30
- name: baseName,
31
- value: formatKpiValue(dataRow[fieldKey]),
32
- indicator: Number(pct) ? `${Number(pct).toFixed(2)}%` : void 0,
33
- label
34
- }
76
+ withMeta(
77
+ {
78
+ id: String(baseId),
79
+ name: baseName,
80
+ value: display.value,
81
+ suffix: display.suffix,
82
+ indicator: showPct ? percentLabel(dataRow.percentage) : void 0,
83
+ subtitle,
84
+ label
85
+ },
86
+ kpi
87
+ )
35
88
  ];
36
89
  }
37
90
  if (type === "comparison") {
38
91
  const current = asRecord(dataRow.current);
39
- const pct = dataRow.percentage;
92
+ const previous = asRecord(dataRow.previous);
93
+ const display = formatDisplay((current == null ? void 0 : current[fieldKey]) ?? (current == null ? void 0 : current[column]));
94
+ const prevRaw = (previous == null ? void 0 : previous[fieldKey]) ?? (previous == null ? void 0 : previous[column]);
95
+ const subtitle = prevRaw != null && prevRaw !== "" ? `vs ${formatKpiValue(prevRaw)}` : void 0;
40
96
  return [
41
- {
42
- id: baseId,
43
- name: baseName,
44
- value: formatKpiValue(current == null ? void 0 : current[fieldKey]),
45
- indicator: Number(pct) ? `${Number(pct).toFixed(2)}%` : void 0,
46
- label
47
- }
97
+ withMeta(
98
+ {
99
+ id: String(baseId),
100
+ name: baseName,
101
+ value: display.value,
102
+ suffix: display.suffix,
103
+ indicator: showPct ? percentLabel(dataRow.percentage) : void 0,
104
+ subtitle,
105
+ label
106
+ },
107
+ kpi
108
+ )
48
109
  ];
49
110
  }
50
111
  if (type === "single_value") {
51
112
  const valueBucket = asRecord(dataRow.value) ?? dataRow;
52
113
  const raw = valueBucket[fieldKey] ?? valueBucket[column] ?? Object.values(valueBucket)[0];
114
+ const display = formatDisplay(raw);
53
115
  return [
54
- {
55
- id: baseId,
56
- name: baseName,
57
- value: formatKpiValue(raw),
58
- label
59
- }
116
+ withMeta(
117
+ {
118
+ id: String(baseId),
119
+ name: baseName,
120
+ value: display.value,
121
+ suffix: display.suffix,
122
+ indicator: showPct ? percentLabel(dataRow.percentage) : void 0,
123
+ label
124
+ },
125
+ kpi
126
+ )
60
127
  ];
61
128
  }
62
129
  const cards = [];
@@ -64,20 +131,29 @@ function extractCardsFromKpi(kpi) {
64
131
  if (key === "percentage" || key === "value") continue;
65
132
  const local = asRecord(dataRow[key]);
66
133
  if (!local) continue;
67
- const subtitle = kpi.groupBy ? String(local[kpi.groupBy] ?? key) : key;
68
- const indicator = Number(local.percentage) ? `${Number(local.percentage).toFixed(2)}%` : void 0;
69
- cards.push({
70
- id: `${baseId}-${key}`,
71
- name: baseName,
72
- value: formatKpiValue(local[fieldKey]),
73
- subtitle,
74
- indicator,
75
- label
76
- });
134
+ const subtitle = kpi.groupBy ? String(local[kpi.groupBy] ?? key) : HIGH_LOW_TYPES.has(type) ? key : void 0;
135
+ const raw = local[fieldKey] ?? local[column];
136
+ const display = formatDisplay(raw);
137
+ cards.push(
138
+ withMeta(
139
+ {
140
+ id: `${baseId}-${key}`,
141
+ name: baseName,
142
+ value: display.value,
143
+ suffix: display.suffix,
144
+ indicator: showPct ? percentLabel(local.percentage) : void 0,
145
+ subtitle,
146
+ label
147
+ },
148
+ kpi
149
+ )
150
+ );
77
151
  }
78
152
  return cards;
79
153
  }
80
154
  export {
81
- extractCardsFromKpi
155
+ extractCardsFromKpi,
156
+ isAdvancedKpiItem,
157
+ visualisationChartType
82
158
  };
83
159
  //# sourceMappingURL=extract.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"extract.js","sources":["../../../../src/components/kpi-widget/lib/extract.ts"],"sourcesContent":["import { formatKpiValue } from './format.js';\nimport type { KpiCardModel, KpiValueLabel, RawKpiItem } from './types.js';\n\ntype AxisInfo = NonNullable<RawKpiItem['axisDetails']>[string];\n\nfunction valueLabel(axisInfo: AxisInfo | undefined): KpiValueLabel | undefined {\n if (!axisInfo || typeof axisInfo !== 'object') return undefined;\n const info = axisInfo;\n return {\n position: info.measure_unit_type === 'currency' ? 'left' : 'right',\n text:\n info.measure_unit_type === 'currency'\n ? (info.measure_unit_symbol ?? '')\n : (info.measure_unit ?? ''),\n };\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n return value != null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\n/** Map one FuseDash / MCP KPI item to display cards (mirrors KPIComponentWrapper). */\nexport function extractCardsFromKpi(kpi: RawKpiItem): KpiCardModel[] {\n const column = kpi.column ?? 'value';\n const aggregation = kpi.aggregations ?? 'sum';\n const fieldKey = `${aggregation}_${column}`;\n const dataRow = Array.isArray(kpi.data) ? asRecord(kpi.data[0]) : null;\n if (!dataRow) return [];\n\n const axisInfo = kpi.axisDetails?.[column];\n const label = valueLabel(axisInfo);\n const type = kpi.type ?? 'single_value';\n const baseId = kpi.id ?? kpi.name ?? column;\n const baseName = kpi.name ?? column;\n\n if (type === 'trend' || type === 'overall') {\n const pct = dataRow.percentage;\n return [\n {\n id: baseId,\n name: baseName,\n value: formatKpiValue(dataRow[fieldKey] as number | string),\n indicator: Number(pct) ? `${Number(pct).toFixed(2)}%` : undefined,\n label,\n },\n ];\n }\n\n if (type === 'comparison') {\n const current = asRecord(dataRow.current);\n const pct = dataRow.percentage;\n return [\n {\n id: baseId,\n name: baseName,\n value: formatKpiValue(current?.[fieldKey] as number | string),\n indicator: Number(pct) ? `${Number(pct).toFixed(2)}%` : undefined,\n label,\n },\n ];\n }\n\n if (type === 'single_value') {\n const valueBucket = asRecord(dataRow.value) ?? dataRow;\n const raw =\n valueBucket[fieldKey] ??\n valueBucket[column] ??\n Object.values(valueBucket)[0];\n return [\n {\n id: baseId,\n name: baseName,\n value: formatKpiValue(raw as number | string),\n label,\n },\n ];\n }\n\n const cards: KpiCardModel[] = [];\n for (const key of Object.keys(dataRow)) {\n if (key === 'percentage' || key === 'value') continue;\n const local = asRecord(dataRow[key]);\n if (!local) continue;\n const subtitle = kpi.groupBy ? String(local[kpi.groupBy] ?? key) : key;\n const indicator = Number(local.percentage)\n ? `${Number(local.percentage).toFixed(2)}%`\n : undefined;\n cards.push({\n id: `${baseId}-${key}`,\n name: baseName,\n value: formatKpiValue(local[fieldKey] as number | string),\n subtitle,\n indicator,\n label,\n });\n }\n\n return cards;\n}\n"],"names":[],"mappings":";AAKA,SAAS,WAAW,UAA2D;AAC7E,MAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AACtD,QAAM,OAAO;AACb,SAAO;AAAA,IACL,UAAU,KAAK,sBAAsB,aAAa,SAAS;AAAA,IAC3D,MACE,KAAK,sBAAsB,aACtB,KAAK,uBAAuB,KAC5B,KAAK,gBAAgB;AAAA,EAAA;AAEhC;AAEA,SAAS,SAAS,OAAgD;AAChE,SAAO,SAAS,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IACpE,QACD;AACN;AAGO,SAAS,oBAAoB,KAAiC;;AACnE,QAAM,SAAS,IAAI,UAAU;AAC7B,QAAM,cAAc,IAAI,gBAAgB;AACxC,QAAM,WAAW,GAAG,WAAW,IAAI,MAAM;AACzC,QAAM,UAAU,MAAM,QAAQ,IAAI,IAAI,IAAI,SAAS,IAAI,KAAK,CAAC,CAAC,IAAI;AAClE,MAAI,CAAC,QAAS,QAAO,CAAA;AAErB,QAAM,YAAW,SAAI,gBAAJ,mBAAkB;AACnC,QAAM,QAAQ,WAAW,QAAQ;AACjC,QAAM,OAAO,IAAI,QAAQ;AACzB,QAAM,SAAS,IAAI,MAAM,IAAI,QAAQ;AACrC,QAAM,WAAW,IAAI,QAAQ;AAE7B,MAAI,SAAS,WAAW,SAAS,WAAW;AAC1C,UAAM,MAAM,QAAQ;AACpB,WAAO;AAAA,MACL;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,OAAO,eAAe,QAAQ,QAAQ,CAAoB;AAAA,QAC1D,WAAW,OAAO,GAAG,IAAI,GAAG,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,MAAM;AAAA,QACxD;AAAA,MAAA;AAAA,IACF;AAAA,EAEJ;AAEA,MAAI,SAAS,cAAc;AACzB,UAAM,UAAU,SAAS,QAAQ,OAAO;AACxC,UAAM,MAAM,QAAQ;AACpB,WAAO;AAAA,MACL;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,OAAO,eAAe,mCAAU,SAA4B;AAAA,QAC5D,WAAW,OAAO,GAAG,IAAI,GAAG,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,MAAM;AAAA,QACxD;AAAA,MAAA;AAAA,IACF;AAAA,EAEJ;AAEA,MAAI,SAAS,gBAAgB;AAC3B,UAAM,cAAc,SAAS,QAAQ,KAAK,KAAK;AAC/C,UAAM,MACJ,YAAY,QAAQ,KACpB,YAAY,MAAM,KAClB,OAAO,OAAO,WAAW,EAAE,CAAC;AAC9B,WAAO;AAAA,MACL;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,OAAO,eAAe,GAAsB;AAAA,QAC5C;AAAA,MAAA;AAAA,IACF;AAAA,EAEJ;AAEA,QAAM,QAAwB,CAAA;AAC9B,aAAW,OAAO,OAAO,KAAK,OAAO,GAAG;AACtC,QAAI,QAAQ,gBAAgB,QAAQ,QAAS;AAC7C,UAAM,QAAQ,SAAS,QAAQ,GAAG,CAAC;AACnC,QAAI,CAAC,MAAO;AACZ,UAAM,WAAW,IAAI,UAAU,OAAO,MAAM,IAAI,OAAO,KAAK,GAAG,IAAI;AACnE,UAAM,YAAY,OAAO,MAAM,UAAU,IACrC,GAAG,OAAO,MAAM,UAAU,EAAE,QAAQ,CAAC,CAAC,MACtC;AACJ,UAAM,KAAK;AAAA,MACT,IAAI,GAAG,MAAM,IAAI,GAAG;AAAA,MACpB,MAAM;AAAA,MACN,OAAO,eAAe,MAAM,QAAQ,CAAoB;AAAA,MACxD;AAAA,MACA;AAAA,MACA;AAAA,IAAA,CACD;AAAA,EACH;AAEA,SAAO;AACT;"}
1
+ {"version":3,"file":"extract.js","sources":["../../../../src/components/kpi-widget/lib/extract.ts"],"sourcesContent":["import {\n formatKpiValue,\n splitFormattedKpiValue,\n} from './format.js';\nimport type {\n KpiCardModel,\n KpiStatusBadge,\n KpiStatusVariant,\n KpiValueLabel,\n RawKpiItem,\n RawKpiVisualisation,\n} from './types.js';\n\ntype AxisInfo = NonNullable<RawKpiItem['axisDetails']>[string];\n\nconst HIGH_LOW_TYPES = new Set(['high/low', 'high/low_overall', 'high/low_trend']);\n\nfunction valueLabel(axisInfo: AxisInfo | undefined): KpiValueLabel | undefined {\n if (!axisInfo || typeof axisInfo !== 'object') return undefined;\n const info = axisInfo;\n return {\n position: info.measure_unit_type === 'currency' ? 'left' : 'right',\n text:\n info.measure_unit_type === 'currency'\n ? (info.measure_unit_symbol ?? '')\n : (info.measure_unit ?? ''),\n };\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n return value != null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\nfunction formatDisplay(raw: unknown): { value: string; suffix?: string } {\n const formatted = formatKpiValue(raw as number | string);\n const split = splitFormattedKpiValue(formatted);\n return split.suffix ? split : { value: formatted };\n}\n\nfunction percentLabel(raw: unknown): string | undefined {\n const n = Number(raw);\n if (!Number.isFinite(n) || n === 0) return undefined;\n return `${n.toFixed(2)}%`;\n}\n\nfunction statusFromKpi(kpi: RawKpiItem): KpiStatusBadge | undefined {\n if (kpi.kpiKind === 'mission') {\n return { text: 'Mission', variant: 'Mission' };\n }\n if (kpi.kpiKind === 'goal') {\n return kpi.targetStatus === 'on_target'\n ? { text: 'On target', variant: 'OnTarget' }\n : { text: 'Off target', variant: 'OffTarget' };\n }\n const raw = kpi.status?.trim();\n if (!raw) return undefined;\n const text = raw.split('=')[0]?.trim() || raw;\n const lower = raw.toLowerCase();\n const variant: KpiStatusVariant = lower.includes('good')\n ? 'Good'\n : lower.includes('low')\n ? 'Low'\n : 'Normal';\n return { text, variant };\n}\n\nfunction withMeta(\n card: Omit<KpiCardModel, 'status' | 'showPercentage'>,\n kpi: RawKpiItem,\n): KpiCardModel {\n const scoreValue =\n kpi.kpiKind && typeof kpi.score === 'number' ? `${kpi.score.toFixed(1)}%` : undefined;\n return {\n ...card,\n ...(scoreValue ? { value: scoreValue, suffix: undefined } : {}),\n status: statusFromKpi(kpi),\n showPercentage: Boolean(kpi.showPercentage),\n };\n}\n\nexport function isAdvancedKpiItem(kpi: RawKpiItem): boolean {\n return Boolean(\n kpi.supportingKpis ||\n kpi.showVisualisation ||\n (kpi.variant === 'advanced' && kpi.visualisation),\n );\n}\n\nexport function visualisationChartType(kpi: {\n visualisation?: RawKpiVisualisation | null;\n}): string | undefined {\n const viz = kpi.visualisation;\n if (!viz || typeof viz !== 'object') return undefined;\n const type = viz.chartType ?? viz.chart_type;\n return typeof type === 'string' && type.trim() ? type.trim() : undefined;\n}\n\n/** Map one FuseDash / MCP KPI item to display cards (mirrors KPIComponentWrapper). */\nexport function extractCardsFromKpi(kpi: RawKpiItem): KpiCardModel[] {\n const column = kpi.column ?? 'value';\n const aggregation = kpi.aggregations ?? 'sum';\n const fieldKey = `${aggregation}_${column}`;\n const dataRow = Array.isArray(kpi.data) ? asRecord(kpi.data[0]) : null;\n if (!dataRow) return [];\n\n const axisInfo = kpi.axisDetails?.[column];\n const label = valueLabel(axisInfo);\n const type = kpi.type ?? 'single_value';\n const baseId = kpi.id ?? kpi.name ?? column;\n const baseName = kpi.name ?? column;\n const showPct = Boolean(kpi.showPercentage);\n\n if (type === 'trend' || type === 'overall') {\n const display = formatDisplay(dataRow[fieldKey] ?? dataRow[column]);\n const subtitle = typeof dataRow.subtitle === 'string' ? dataRow.subtitle : undefined;\n return [\n withMeta(\n {\n id: String(baseId),\n name: baseName,\n value: display.value,\n suffix: display.suffix,\n indicator: showPct ? percentLabel(dataRow.percentage) : undefined,\n subtitle,\n label,\n },\n kpi,\n ),\n ];\n }\n\n if (type === 'comparison') {\n const current = asRecord(dataRow.current);\n const previous = asRecord(dataRow.previous);\n const display = formatDisplay(current?.[fieldKey] ?? current?.[column]);\n const prevRaw = previous?.[fieldKey] ?? previous?.[column];\n const subtitle =\n prevRaw != null && prevRaw !== '' ? `vs ${formatKpiValue(prevRaw as number | string)}` : undefined;\n return [\n withMeta(\n {\n id: String(baseId),\n name: baseName,\n value: display.value,\n suffix: display.suffix,\n indicator: showPct ? percentLabel(dataRow.percentage) : undefined,\n subtitle,\n label,\n },\n kpi,\n ),\n ];\n }\n\n if (type === 'single_value') {\n const valueBucket = asRecord(dataRow.value) ?? dataRow;\n const raw =\n valueBucket[fieldKey] ?? valueBucket[column] ?? Object.values(valueBucket)[0];\n const display = formatDisplay(raw);\n return [\n withMeta(\n {\n id: String(baseId),\n name: baseName,\n value: display.value,\n suffix: display.suffix,\n indicator: showPct ? percentLabel(dataRow.percentage) : undefined,\n label,\n },\n kpi,\n ),\n ];\n }\n\n const cards: KpiCardModel[] = [];\n for (const key of Object.keys(dataRow)) {\n if (key === 'percentage' || key === 'value') continue;\n const local = asRecord(dataRow[key]);\n if (!local) continue;\n const subtitle = kpi.groupBy\n ? String(local[kpi.groupBy] ?? key)\n : HIGH_LOW_TYPES.has(type)\n ? key\n : undefined;\n const raw = local[fieldKey] ?? local[column];\n const display = formatDisplay(raw);\n cards.push(\n withMeta(\n {\n id: `${baseId}-${key}`,\n name: baseName,\n value: display.value,\n suffix: display.suffix,\n indicator: showPct ? percentLabel(local.percentage) : undefined,\n subtitle,\n label,\n },\n kpi,\n ),\n );\n }\n\n return cards;\n}\n"],"names":[],"mappings":"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"}
@@ -1,3 +1,8 @@
1
1
  /** Port of FuseDash `formatKpiValue` (client/libs/shared/utils/kpi.ts). */
2
2
  export declare function formatKpiValue(value: number | string, decimals?: number): string;
3
+ /** Split compact scientific notation so the exponent can render as a suffix. */
4
+ export declare function splitFormattedKpiValue(formatted: string): {
5
+ value: string;
6
+ suffix: string;
7
+ };
3
8
  //# sourceMappingURL=format.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../../src/components/kpi-widget/lib/format.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,SAAI,GAAG,MAAM,CA4B3E"}
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../../src/components/kpi-widget/lib/format.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,SAAI,GAAG,MAAM,CA4B3E;AAED,gFAAgF;AAChF,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAOA"}
@@ -18,7 +18,16 @@ function formatKpiValue(value, decimals = 2) {
18
18
  const formattedModifiedValue = parseFloat(modifiedValue.toFixed(decimals)).toString();
19
19
  return `${formattedModifiedValue}×10⁻${power}`;
20
20
  }
21
+ function splitFormattedKpiValue(formatted) {
22
+ if (!formatted.includes("×10⁻")) return { value: formatted, suffix: "" };
23
+ const [valuePart, suffixPart] = formatted.split("×10⁻");
24
+ return {
25
+ value: valuePart ?? formatted,
26
+ suffix: suffixPart != null ? `×10⁻${suffixPart}` : ""
27
+ };
28
+ }
21
29
  export {
22
- formatKpiValue
30
+ formatKpiValue,
31
+ splitFormattedKpiValue
23
32
  };
24
33
  //# sourceMappingURL=format.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"format.js","sources":["../../../../src/components/kpi-widget/lib/format.ts"],"sourcesContent":["/** Port of FuseDash `formatKpiValue` (client/libs/shared/utils/kpi.ts). */\nexport function formatKpiValue(value: number | string, decimals = 2): string {\n const numericValue = Number(value);\n if (\n Number.isNaN(numericValue) ||\n numericValue === null ||\n !Number.isFinite(numericValue)\n ) {\n return '—';\n }\n\n const absValue = Math.abs(numericValue);\n\n if (absValue === 0) {\n return '0';\n }\n\n if (absValue >= 0.1) {\n return new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: decimals,\n }).format(numericValue);\n }\n\n const power = Math.ceil(Math.abs(Math.log10(absValue) / 3)) * 3;\n const modifiedValue = numericValue * 10 ** power;\n const formattedModifiedValue = parseFloat(modifiedValue.toFixed(decimals)).toString();\n\n return `${formattedModifiedValue}×10⁻${power}`;\n}\n"],"names":[],"mappings":"AACO,SAAS,eAAe,OAAwB,WAAW,GAAW;AAC3E,QAAM,eAAe,OAAO,KAAK;AACjC,MACE,OAAO,MAAM,YAAY,KACzB,iBAAiB,QACjB,CAAC,OAAO,SAAS,YAAY,GAC7B;AACA,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,KAAK,IAAI,YAAY;AAEtC,MAAI,aAAa,GAAG;AAClB,WAAO;AAAA,EACT;AAEA,MAAI,YAAY,KAAK;AACnB,WAAO,IAAI,KAAK,aAAa,SAAS;AAAA,MACpC,UAAU;AAAA,MACV,uBAAuB;AAAA,IAAA,CACxB,EAAE,OAAO,YAAY;AAAA,EACxB;AAEA,QAAM,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,MAAM,QAAQ,IAAI,CAAC,CAAC,IAAI;AAC9D,QAAM,gBAAgB,eAAe,MAAM;AAC3C,QAAM,yBAAyB,WAAW,cAAc,QAAQ,QAAQ,CAAC,EAAE,SAAA;AAE3E,SAAO,GAAG,sBAAsB,OAAO,KAAK;AAC9C;"}
1
+ {"version":3,"file":"format.js","sources":["../../../../src/components/kpi-widget/lib/format.ts"],"sourcesContent":["/** Port of FuseDash `formatKpiValue` (client/libs/shared/utils/kpi.ts). */\nexport function formatKpiValue(value: number | string, decimals = 2): string {\n const numericValue = Number(value);\n if (\n Number.isNaN(numericValue) ||\n numericValue === null ||\n !Number.isFinite(numericValue)\n ) {\n return '—';\n }\n\n const absValue = Math.abs(numericValue);\n\n if (absValue === 0) {\n return '0';\n }\n\n if (absValue >= 0.1) {\n return new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: decimals,\n }).format(numericValue);\n }\n\n const power = Math.ceil(Math.abs(Math.log10(absValue) / 3)) * 3;\n const modifiedValue = numericValue * 10 ** power;\n const formattedModifiedValue = parseFloat(modifiedValue.toFixed(decimals)).toString();\n\n return `${formattedModifiedValue}×10⁻${power}`;\n}\n\n/** Split compact scientific notation so the exponent can render as a suffix. */\nexport function splitFormattedKpiValue(formatted: string): {\n value: string;\n suffix: string;\n} {\n if (!formatted.includes('×10⁻')) return { value: formatted, suffix: '' };\n const [valuePart, suffixPart] = formatted.split('×10⁻');\n return {\n value: valuePart ?? formatted,\n suffix: suffixPart != null ? `×10⁻${suffixPart}` : '',\n };\n}\n"],"names":[],"mappings":"AACO,SAAS,eAAe,OAAwB,WAAW,GAAW;AAC3E,QAAM,eAAe,OAAO,KAAK;AACjC,MACE,OAAO,MAAM,YAAY,KACzB,iBAAiB,QACjB,CAAC,OAAO,SAAS,YAAY,GAC7B;AACA,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,KAAK,IAAI,YAAY;AAEtC,MAAI,aAAa,GAAG;AAClB,WAAO;AAAA,EACT;AAEA,MAAI,YAAY,KAAK;AACnB,WAAO,IAAI,KAAK,aAAa,SAAS;AAAA,MACpC,UAAU;AAAA,MACV,uBAAuB;AAAA,IAAA,CACxB,EAAE,OAAO,YAAY;AAAA,EACxB;AAEA,QAAM,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,MAAM,QAAQ,IAAI,CAAC,CAAC,IAAI;AAC9D,QAAM,gBAAgB,eAAe,MAAM;AAC3C,QAAM,yBAAyB,WAAW,cAAc,QAAQ,QAAQ,CAAC,EAAE,SAAA;AAE3E,SAAO,GAAG,sBAAsB,OAAO,KAAK;AAC9C;AAGO,SAAS,uBAAuB,WAGrC;AACA,MAAI,CAAC,UAAU,SAAS,MAAM,UAAU,EAAE,OAAO,WAAW,QAAQ,GAAA;AACpE,QAAM,CAAC,WAAW,UAAU,IAAI,UAAU,MAAM,MAAM;AACtD,SAAO;AAAA,IACL,OAAO,aAAa;AAAA,IACpB,QAAQ,cAAc,OAAO,OAAO,UAAU,KAAK;AAAA,EAAA;AAEvD;"}