@blukavantui/charts-community 0.0.0-stage → 1.0.1

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 (206) hide show
  1. package/LICENSE.txt +105 -0
  2. package/README.md +93 -2
  3. package/THIRD_PARTY_NOTICES.txt +288 -0
  4. package/dist/accessibility.d.ts +53 -0
  5. package/dist/async.d.ts +2 -0
  6. package/dist/bar.css +1 -0
  7. package/dist/bar.d.ts +5 -0
  8. package/dist/bar.js +1 -0
  9. package/dist/browser/charts-community.css +1 -0
  10. package/dist/browser/charts-community.js +52 -0
  11. package/dist/build-audit.json +164 -0
  12. package/dist/builders/bar.d.ts +6 -0
  13. package/dist/builders/doughnut.d.ts +6 -0
  14. package/dist/builders/heatmap.d.ts +16 -0
  15. package/dist/builders/line.d.ts +6 -0
  16. package/dist/builders/pie.d.ts +6 -0
  17. package/dist/builders/radar.d.ts +6 -0
  18. package/dist/builders/scatter.d.ts +6 -0
  19. package/dist/catalogue-model.d.ts +11 -0
  20. package/dist/catalogue-runtime.d.ts +11 -0
  21. package/dist/catalogue-types.d.ts +167 -0
  22. package/dist/chart-state.d.ts +18 -0
  23. package/dist/chunk-5NXLEMCV.js +3 -0
  24. package/dist/chunk-FHHQWCUO.js +1 -0
  25. package/dist/chunk-FZ7J7BEC.js +1 -0
  26. package/dist/chunk-JNSBTZUP.js +2 -0
  27. package/dist/chunk-KCW3WN5P.js +1 -0
  28. package/dist/chunk-MSZ5J5WG.js +1 -0
  29. package/dist/chunk-QBKO4AG7.js +1 -0
  30. package/dist/chunk-QETOF22L.js +1 -0
  31. package/dist/chunk-QFXXWRD5.js +1 -0
  32. package/dist/chunk-YJKEV5J7.js +2 -0
  33. package/dist/colors.d.ts +15 -0
  34. package/dist/community.d.ts +11 -0
  35. package/dist/doughnut.css +1 -0
  36. package/dist/doughnut.d.ts +5 -0
  37. package/dist/doughnut.js +1 -0
  38. package/dist/export.d.ts +5 -0
  39. package/dist/fonts/dm-sans-LICENSE.txt +93 -0
  40. package/dist/fonts/dm-sans-latin-ext-wght-italic.woff2 +0 -0
  41. package/dist/fonts/dm-sans-latin-ext-wght-normal.woff2 +0 -0
  42. package/dist/fonts/dm-sans-latin-wght-italic.woff2 +0 -0
  43. package/dist/fonts/dm-sans-latin-wght-normal.woff2 +0 -0
  44. package/dist/fonts/ibm-plex-mono-LICENSE.txt +93 -0
  45. package/dist/fonts/ibm-plex-mono-latin-100-italic.woff2 +0 -0
  46. package/dist/fonts/ibm-plex-mono-latin-100-normal.woff2 +0 -0
  47. package/dist/fonts/ibm-plex-mono-latin-200-italic.woff2 +0 -0
  48. package/dist/fonts/ibm-plex-mono-latin-200-normal.woff2 +0 -0
  49. package/dist/fonts/ibm-plex-mono-latin-300-italic.woff2 +0 -0
  50. package/dist/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
  51. package/dist/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
  52. package/dist/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  53. package/dist/fonts/ibm-plex-mono-latin-500-italic.woff2 +0 -0
  54. package/dist/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  55. package/dist/fonts/ibm-plex-mono-latin-600-italic.woff2 +0 -0
  56. package/dist/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  57. package/dist/fonts/ibm-plex-mono-latin-700-italic.woff2 +0 -0
  58. package/dist/fonts/ibm-plex-mono-latin-700-normal.woff2 +0 -0
  59. package/dist/fonts/ibm-plex-mono-latin-ext-100-italic.woff2 +0 -0
  60. package/dist/fonts/ibm-plex-mono-latin-ext-100-normal.woff2 +0 -0
  61. package/dist/fonts/ibm-plex-mono-latin-ext-200-italic.woff2 +0 -0
  62. package/dist/fonts/ibm-plex-mono-latin-ext-200-normal.woff2 +0 -0
  63. package/dist/fonts/ibm-plex-mono-latin-ext-300-italic.woff2 +0 -0
  64. package/dist/fonts/ibm-plex-mono-latin-ext-300-normal.woff2 +0 -0
  65. package/dist/fonts/ibm-plex-mono-latin-ext-400-italic.woff2 +0 -0
  66. package/dist/fonts/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  67. package/dist/fonts/ibm-plex-mono-latin-ext-500-italic.woff2 +0 -0
  68. package/dist/fonts/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  69. package/dist/fonts/ibm-plex-mono-latin-ext-600-italic.woff2 +0 -0
  70. package/dist/fonts/ibm-plex-mono-latin-ext-600-normal.woff2 +0 -0
  71. package/dist/fonts/ibm-plex-mono-latin-ext-700-italic.woff2 +0 -0
  72. package/dist/fonts/ibm-plex-mono-latin-ext-700-normal.woff2 +0 -0
  73. package/dist/fonts/inter-LICENSE.txt +93 -0
  74. package/dist/fonts/inter-latin-ext-wght-italic.woff2 +0 -0
  75. package/dist/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
  76. package/dist/fonts/inter-latin-wght-italic.woff2 +0 -0
  77. package/dist/fonts/inter-latin-wght-normal.woff2 +0 -0
  78. package/dist/fonts/jetbrains-mono-LICENSE.txt +93 -0
  79. package/dist/fonts/jetbrains-mono-latin-ext-wght-italic.woff2 +0 -0
  80. package/dist/fonts/jetbrains-mono-latin-ext-wght-normal.woff2 +0 -0
  81. package/dist/fonts/jetbrains-mono-latin-wght-italic.woff2 +0 -0
  82. package/dist/fonts/jetbrains-mono-latin-wght-normal.woff2 +0 -0
  83. package/dist/fonts/lora-LICENSE.txt +93 -0
  84. package/dist/fonts/lora-latin-ext-wght-italic.woff2 +0 -0
  85. package/dist/fonts/lora-latin-ext-wght-normal.woff2 +0 -0
  86. package/dist/fonts/lora-latin-wght-italic.woff2 +0 -0
  87. package/dist/fonts/lora-latin-wght-normal.woff2 +0 -0
  88. package/dist/fonts/manifest.json +618 -0
  89. package/dist/fonts/manrope-LICENSE.txt +93 -0
  90. package/dist/fonts/manrope-latin-ext-wght-normal.woff2 +0 -0
  91. package/dist/fonts/manrope-latin-wght-normal.woff2 +0 -0
  92. package/dist/fonts/merriweather-LICENSE.txt +93 -0
  93. package/dist/fonts/merriweather-latin-ext-wght-italic.woff2 +0 -0
  94. package/dist/fonts/merriweather-latin-ext-wght-normal.woff2 +0 -0
  95. package/dist/fonts/merriweather-latin-wght-italic.woff2 +0 -0
  96. package/dist/fonts/merriweather-latin-wght-normal.woff2 +0 -0
  97. package/dist/fonts/montserrat-LICENSE.txt +93 -0
  98. package/dist/fonts/montserrat-latin-ext-wght-italic.woff2 +0 -0
  99. package/dist/fonts/montserrat-latin-ext-wght-normal.woff2 +0 -0
  100. package/dist/fonts/montserrat-latin-wght-italic.woff2 +0 -0
  101. package/dist/fonts/montserrat-latin-wght-normal.woff2 +0 -0
  102. package/dist/fonts/playfair-display-LICENSE.txt +93 -0
  103. package/dist/fonts/playfair-display-latin-ext-wght-italic.woff2 +0 -0
  104. package/dist/fonts/playfair-display-latin-ext-wght-normal.woff2 +0 -0
  105. package/dist/fonts/playfair-display-latin-wght-italic.woff2 +0 -0
  106. package/dist/fonts/playfair-display-latin-wght-normal.woff2 +0 -0
  107. package/dist/fonts/roboto-LICENSE.txt +93 -0
  108. package/dist/fonts/roboto-latin-ext-wght-italic.woff2 +0 -0
  109. package/dist/fonts/roboto-latin-ext-wght-normal.woff2 +0 -0
  110. package/dist/fonts/roboto-latin-wght-italic.woff2 +0 -0
  111. package/dist/fonts/roboto-latin-wght-normal.woff2 +0 -0
  112. package/dist/fonts/source-sans-3-LICENSE.txt +93 -0
  113. package/dist/fonts/source-sans-3-latin-ext-wght-italic.woff2 +0 -0
  114. package/dist/fonts/source-sans-3-latin-ext-wght-normal.woff2 +0 -0
  115. package/dist/fonts/source-sans-3-latin-wght-italic.woff2 +0 -0
  116. package/dist/fonts/source-sans-3-latin-wght-normal.woff2 +0 -0
  117. package/dist/heatmap.css +1 -0
  118. package/dist/heatmap.d.ts +3 -0
  119. package/dist/heatmap.js +1 -0
  120. package/dist/index.css +1 -0
  121. package/dist/index.d.ts +11 -0
  122. package/dist/index.js +1 -0
  123. package/dist/legend-export.d.ts +9 -0
  124. package/dist/legend-model.d.ts +39 -0
  125. package/dist/legend.d.ts +19 -0
  126. package/dist/limits.d.ts +16 -0
  127. package/dist/line.css +1 -0
  128. package/dist/line.d.ts +5 -0
  129. package/dist/line.js +1 -0
  130. package/dist/loading-state.d.ts +6 -0
  131. package/dist/map-assets.d.ts +4 -0
  132. package/dist/map-resources.d.ts +24 -0
  133. package/dist/messages.d.ts +3 -0
  134. package/dist/model.d.ts +41 -0
  135. package/dist/options.d.ts +11 -0
  136. package/dist/pie.css +1 -0
  137. package/dist/pie.d.ts +5 -0
  138. package/dist/pie.js +1 -0
  139. package/dist/radar.css +1 -0
  140. package/dist/radar.d.ts +4 -0
  141. package/dist/radar.js +1 -0
  142. package/dist/runtime.d.ts +4 -0
  143. package/dist/scatter.css +1 -0
  144. package/dist/scatter.d.ts +5 -0
  145. package/dist/scatter.js +1 -0
  146. package/dist/sparkline-runtime/runtime.d.ts +10 -0
  147. package/dist/sparkline-validation.d.ts +3 -0
  148. package/dist/sparkline.css +25 -0
  149. package/dist/sparkline.d.ts +44 -0
  150. package/dist/sparkline.js +1 -0
  151. package/dist/state-announcement.d.ts +6 -0
  152. package/dist/styles.css +488 -0
  153. package/dist/theme.css +1391 -0
  154. package/dist/types.d.ts +137 -0
  155. package/dist/validation.d.ts +8 -0
  156. package/dist/web-api.json +4145 -0
  157. package/edition.json +48 -0
  158. package/package.json +96 -4
  159. package/scripts/build.mjs +31 -0
  160. package/src/accessibility.ts +81 -0
  161. package/src/assets.d.ts +1 -0
  162. package/src/async.ts +12 -0
  163. package/src/bar.ts +13 -0
  164. package/src/builders/bar.ts +39 -0
  165. package/src/builders/doughnut.ts +66 -0
  166. package/src/builders/heatmap.ts +60 -0
  167. package/src/builders/line.ts +39 -0
  168. package/src/builders/pie.ts +66 -0
  169. package/src/builders/radar.ts +65 -0
  170. package/src/builders/scatter.ts +34 -0
  171. package/src/catalogue-model.ts +235 -0
  172. package/src/catalogue-runtime.ts +200 -0
  173. package/src/catalogue-types.ts +163 -0
  174. package/src/chart-state.ts +35 -0
  175. package/src/charts.css +135 -0
  176. package/src/colors.ts +277 -0
  177. package/src/community.ts +11 -0
  178. package/src/doughnut.ts +12 -0
  179. package/src/export.ts +50 -0
  180. package/src/heatmap.ts +11 -0
  181. package/src/index.ts +11 -0
  182. package/src/legend-export.ts +36 -0
  183. package/src/legend-model.ts +76 -0
  184. package/src/legend.css +64 -0
  185. package/src/legend.ts +90 -0
  186. package/src/limits.ts +7 -0
  187. package/src/line.ts +13 -0
  188. package/src/loading-state.ts +39 -0
  189. package/src/map-assets.ts +1 -0
  190. package/src/map-resources.ts +46 -0
  191. package/src/messages.ts +5 -0
  192. package/src/model.ts +32 -0
  193. package/src/options.ts +24 -0
  194. package/src/pie.ts +12 -0
  195. package/src/radar.ts +12 -0
  196. package/src/runtime.ts +7 -0
  197. package/src/scatter.ts +13 -0
  198. package/src/sparkline-runtime/runtime.d.ts +10 -0
  199. package/src/sparkline-runtime/runtime.js +263 -0
  200. package/src/sparkline-validation.ts +19 -0
  201. package/src/sparkline.css +25 -0
  202. package/src/sparkline.ts +68 -0
  203. package/src/state-announcement.ts +17 -0
  204. package/src/types.ts +104 -0
  205. package/src/validation.ts +75 -0
  206. package/tsconfig.json +16 -0
@@ -0,0 +1,263 @@
1
+ // Lightweight SVG renderer, migrated from the Community Sparkline.
2
+ // Module-local ownership keeps Charts independent of the native UI runtime.
3
+ var shared = { instances: new Map(), serial: 0 };
4
+ var installed;
5
+ function el(tag, cls, text) {
6
+ var n = document.createElement(tag);
7
+ if (cls) n.className = cls;
8
+ if (text !== void 0) n.textContent = text;
9
+ return n;
10
+ }
11
+ function attr(n, key, value) {
12
+ n.setAttribute(key, String(value));
13
+ return n;
14
+ }
15
+ function report(s, message) {
16
+ if (!s.error) {
17
+ s.error = attr(el("p", "blu-common-error"), "role", "alert");
18
+ s.host.append(s.error);
19
+ }
20
+ s.error.textContent = message;
21
+ s.error.hidden = !message;
22
+ if (message && s.lastError !== message && s.events.Error) {
23
+ s.lastError = message;
24
+ try {
25
+ s.events.Error(message);
26
+ } catch (_) {
27
+ }
28
+ }
29
+ if (!message) s.lastError = "";
30
+ }
31
+ function integer(value, fallback, min, max) {
32
+ value = value === void 0 ? fallback : Number(value);
33
+ if (!Number.isInteger(value) || value < min || value > max) throw Error("Expected an integer between " + min + " and " + max + ".");
34
+ return value;
35
+ }
36
+ function destroy(key) {
37
+ var s = shared.instances.get(key);
38
+ if (!s) return;
39
+ s.dead = true;
40
+ s.cleanup.forEach(function(f) {
41
+ f();
42
+ });
43
+ s.owned.forEach(function(n) {
44
+ n.remove();
45
+ });
46
+ if (s.host._bluChartsSparklineOwner === key) {
47
+ delete s.host._bluChartsSparklineOwner;
48
+ s.host.removeAttribute("data-blu-ready");
49
+ }
50
+ shared.instances.delete(key);
51
+ }
52
+ function own(s, node) {
53
+ s.owned.push(node);
54
+ return node;
55
+ }
56
+ function geometry(raw, height, width = 300, lineStyle = 'Smooth') {
57
+ var rows = typeof raw === "string" ? JSON.parse(raw || "[]") : raw || [];
58
+ if (!Array.isArray(rows) || rows.length > 1e4) throw Error("Points must be a list with at most 10,000 items.");
59
+ var values = rows.map(function(p) {
60
+ if (!p || typeof p.Value !== "number" || !Number.isFinite(p.Value)) throw Error("Every Sparkline point needs a finite numeric Value.");
61
+ return p.Value;
62
+ });
63
+ if (!values.length) return { rows, coordinates: [], line: "", area: "" };
64
+ var min = Math.min.apply(null, values), max = Math.max.apply(null, values), range = max - min;
65
+ var scale = Math.max(Math.abs(min), Math.abs(max), 1), low = min / scale, spread = max / scale - low;
66
+ var coordinates = values.map(function(value, index) {
67
+ return [values.length === 1 ? width / 2 : 6 + index * (width - 12) / (values.length - 1), spread ? height - 6 - (value / scale - low) / spread * (height - 12) : height / 2];
68
+ });
69
+ var line = coordinates.map(function(p, i) {
70
+ return (i ? "L" : "M") + p[0].toFixed(3) + " " + p[1].toFixed(3);
71
+ }).join(" ");
72
+ if (lineStyle === 'Smooth' && coordinates.length > 2) {
73
+ // Monotone cubic interpolation keeps each segment within its observed values.
74
+ var step = (width - 12) / (coordinates.length - 1);
75
+ var slopes = coordinates.slice(1).map(function(p, i) { return (p[1] - coordinates[i][1]) / step; });
76
+ var tangents = coordinates.map(function(_, i) {
77
+ if (i === 0) return slopes[0];
78
+ if (i === coordinates.length - 1) return slopes[i - 1];
79
+ var before = slopes[i - 1], after = slopes[i];
80
+ return before * after > 0 ? 2 * before * after / (before + after) : 0;
81
+ });
82
+ line = 'M' + coordinates[0][0].toFixed(3) + ' ' + coordinates[0][1].toFixed(3);
83
+ for (var i = 1; i < coordinates.length; i++) {
84
+ var start = coordinates[i - 1], end = coordinates[i];
85
+ line += ' C' + (start[0] + step / 3).toFixed(3) + ' ' + (start[1] + tangents[i - 1] * step / 3).toFixed(3)
86
+ + ' ' + (end[0] - step / 3).toFixed(3) + ' ' + (end[1] - tangents[i] * step / 3).toFixed(3)
87
+ + ' ' + end[0].toFixed(3) + ' ' + end[1].toFixed(3);
88
+ }
89
+ }
90
+ return { rows, coordinates, line, area: line + " L" + coordinates[coordinates.length - 1][0] + " " + (height - 4) + " L" + coordinates[0][0] + " " + (height - 4) + " Z", min, max, first: values[0], last: values[values.length - 1] };
91
+ }
92
+ function sparkline(s) {
93
+ var o = s.options, width = Math.max(24, Math.round(s.host.clientWidth || 300)), signature = JSON.stringify([o, width, s.reducedMotion.matches]);
94
+ const msg=(key,fallback,values={})=>(o.Messages?.[key]??fallback).replace(/\{(\w+)\}/g,(token,name)=>values[name]??token);
95
+ if (s.renderSignature === signature) return;
96
+ var lineStyle = o.LineStyle || 'Smooth';
97
+ if (!['Straight', 'Smooth'].includes(lineStyle)) throw Error('LineStyle must be Straight or Smooth.');
98
+ var height = integer(o.Height, 64, 24, 240), geometryKey = JSON.stringify([o.PointsJson, height, width, lineStyle]);
99
+ var g = s.geometryKey === geometryKey ? s.geometry : geometry(o.PointsJson, height, width, lineStyle);
100
+ var animation = o.Animation || (o.AnimateGlow === false ? 'Off' : 'Once');
101
+ if (!['Off', 'Once', 'Continuous'].includes(animation)) throw Error('Animation must be Off, Once or Continuous.');
102
+ var duration = o.AnimationDuration === undefined ? 5 : Number(o.AnimationDuration);
103
+ if (!Number.isFinite(duration) || duration < 1 || duration > 60) throw Error('AnimationDuration must be between 1 and 60 seconds.');
104
+ var opacity = o.AreaOpacity === undefined ? 18 : Number(o.AreaOpacity);
105
+ if (!Number.isFinite(opacity) || opacity < 0 || opacity > 100) throw Error('AreaOpacity must be between 0 and 100 percent.');
106
+ if (s.animation !== animation) s.animationPaused = false;
107
+ s.animation = animation;
108
+ s.geometryKey = geometryKey;
109
+ s.geometry = g;
110
+ var color = String(o.Color || "Primary").toLowerCase();
111
+ if ("default primary secondary success processing warning error danger magenta red volcano orange gold lime green cyan blue geekblue purple".split(" ").indexOf(color) < 0) throw Error("Unknown ComponentColor.");
112
+ if (!s.body) {
113
+ s.body = own(s, el("div", "blu-sparkline"));
114
+ s.host.append(s.body);
115
+ }
116
+ var body = s.body, wasOpen = !!(body.querySelector("details") && body.querySelector("details").open), hadFocus = body.contains(document.activeElement);
117
+ while (body.firstChild) body.removeChild(body.firstChild);
118
+ body.style.setProperty("--blu-sparkline-color", "var(--bluui-color-" + color + "-text)");
119
+ body.style.setProperty("--blu-sparkline-height", height + "px");
120
+ if (!g.rows.length) {
121
+ body.append(el("p", "blu-sparkline__empty", o.EmptyText || "No trend data."));
122
+ s.renderSignature = signature;
123
+ return;
124
+ }
125
+ var ns = "http://www.w3.org/2000/svg", svg = document.createElementNS(ns, "svg");
126
+ attr(svg, "viewBox", "0 0 " + width + " " + height);
127
+ attr(svg, "preserveAspectRatio", "none");
128
+ attr(svg, "role", "img");
129
+ const direction=g.last>g.first?'Rising':g.last<g.first?'Falling':'Unchanged';
130
+ var summary = o.SummaryText || msg('TrendSummary','{count} points. {direction} from {first} to {last}. Minimum {min}, maximum {max}.',{count:g.rows.length,direction:msg(direction,direction),first:g.first,last:g.last,min:g.min,max:g.max});
131
+ attr(svg, "aria-label", (o.AccessibleLabel || "Trend") + ". " + summary);
132
+ var title = document.createElementNS(ns, "title");
133
+ title.textContent = (o.AccessibleLabel || "Trend") + ". " + summary;
134
+ svg.append(title);
135
+ if (o.ShowArea !== false && g.rows.length > 1) {
136
+ var defs = document.createElementNS(ns, 'defs'), gradient = document.createElementNS(ns, 'linearGradient');
137
+ attr(gradient, 'id', s.uid + '-area');
138
+ attr(gradient, 'x1', '0%'); attr(gradient, 'y1', '0%'); attr(gradient, 'x2', '0%'); attr(gradient, 'y2', '100%');
139
+ [opacity / 100, o.AreaFade === false ? opacity / 100 : 0].forEach(function(value, index) {
140
+ var stop = document.createElementNS(ns, 'stop');
141
+ attr(stop, 'offset', index ? '100%' : '0%');
142
+ stop.style.stopColor = 'var(--blu-sparkline-color)';
143
+ attr(stop, 'stop-opacity', value); gradient.append(stop);
144
+ });
145
+ defs.append(gradient); svg.append(defs);
146
+ var area = document.createElementNS(ns, "path");
147
+ attr(area, "d", g.area);
148
+ attr(area, "class", "blu-sparkline__area");
149
+ area.style.fill = 'url(#' + s.uid + '-area)';
150
+ svg.append(area);
151
+ }
152
+ var path = document.createElementNS(ns, "path");
153
+ attr(path, "d", g.line);
154
+ attr(path, "class", "blu-sparkline__line");
155
+ attr(path, "vector-effect", "non-scaling-stroke");
156
+ svg.append(path);
157
+ var moving = animation !== 'Off' && g.rows.length > 1 && !s.reducedMotion.matches;
158
+ if ((!moving && o.ShowEndMarker !== false) || g.rows.length === 1) {
159
+ var p = g.coordinates[g.coordinates.length - 1], circle = document.createElementNS(ns, "circle");
160
+ attr(circle, "cx", p[0]);
161
+ attr(circle, "cy", p[1]);
162
+ attr(circle, "r", 3);
163
+ attr(circle, "class", "blu-sparkline__point");
164
+ svg.append(circle);
165
+ }
166
+ if (moving) {
167
+ var traveler = document.createElementNS(ns, 'g');
168
+ attr(traveler, 'class', 'blu-sparkline__traveler'); attr(traveler, 'aria-hidden', 'true');
169
+ [['halo', 6], ['ring', 4.5], ['cursor', 3]].forEach(function(part) {
170
+ var marker = document.createElementNS(ns, 'circle');
171
+ attr(marker, 'r', part[1]);
172
+ attr(marker, 'class', 'blu-sparkline__' + part[0]); traveler.append(marker);
173
+ });
174
+ var motion = document.createElementNS(ns, 'animateMotion'), fade = document.createElementNS(ns, 'animate');
175
+ attr(motion, 'path', g.line); attr(motion, 'calcMode', 'spline');
176
+ attr(motion, 'keyPoints', '0;1'); attr(motion, 'keyTimes', '0;1');
177
+ attr(motion, 'keySplines', '0.22 0.61 0.36 1');
178
+ attr(fade, 'attributeName', 'opacity'); attr(fade, 'values', '0;1;1;0'); attr(fade, 'keyTimes', '0;0.03;0.96;1');
179
+ [motion, fade].forEach(function(effect) {
180
+ attr(effect, 'dur', duration + 's'); attr(effect, 'repeatCount', animation === 'Continuous' ? 'indefinite' : '1'); attr(effect, 'fill', 'freeze'); traveler.append(effect);
181
+ });
182
+ svg.append(traveler);
183
+ }
184
+ body.append(svg);
185
+ if (moving && (animation === 'Continuous' || duration > 5)) {
186
+ var pause = el('button', 'blu-sparkline__pause'); pause.type = 'button';
187
+ var updatePause = function() {
188
+ pause.textContent = s.animationPaused ? msg('ResumeAnimation','Resume animation') : msg('PauseAnimation','Pause animation');
189
+ if (s.animationPaused) svg.pauseAnimations(); else svg.unpauseAnimations();
190
+ };
191
+ pause.addEventListener('click', function() { s.animationPaused = !s.animationPaused; updatePause(); });
192
+ body.append(pause); updatePause();
193
+ }
194
+ if (o.ShowDataTable) {
195
+ let buildTable = function() {
196
+ if (!details.open || tableBuilt) return;
197
+ var table = el("table"), caption = el("caption", "", o.AccessibleLabel || "Trend"), thead = el("thead"), headings = el("tr");
198
+ headings.append(attr(el("th", "", msg('Point','Point')), "scope", "col"), attr(el("th", "", msg('Value','Value')), "scope", "col"));
199
+ thead.append(headings);
200
+ table.append(caption, thead);
201
+ var tbody = el("tbody");
202
+ g.rows.forEach(function(row, index) {
203
+ var tr = el("tr");
204
+ tr.append(attr(el("th", "", row.Label || String(index + 1)), "scope", "row"), el("td", "", String(row.Value)));
205
+ tbody.append(tr);
206
+ });
207
+ table.append(tbody);
208
+ details.append(table);
209
+ tableBuilt = true;
210
+ };
211
+ var details = el("details", "blu-sparkline__data");
212
+ details.open = wasOpen;
213
+ details.append(el("summary", "", msg('ViewData','View trend data')));
214
+ var tableBuilt = false;
215
+ details.addEventListener("toggle", buildTable);
216
+ body.append(details);
217
+ buildTable();
218
+ if (hadFocus) details.querySelector("summary").focus();
219
+ }
220
+ s.renderSignature = signature;
221
+ }
222
+ function api(kind, render) {
223
+ return { sync: function(id, options, events, key) {
224
+ var host = document.getElementById(id);
225
+ if (!host) return "Component host was not found.";
226
+ key = key || id;
227
+ var s = shared.instances.get(key);
228
+ if (s && s.host !== host) {
229
+ destroy(key);
230
+ s = null;
231
+ }
232
+ if (host._bluChartsSparklineOwner && host._bluChartsSparklineOwner !== key) destroy(host._bluChartsSparklineOwner);
233
+ if (!s) {
234
+ s = { host, key, uid: "blu-charts-trend-" + ++shared.serial, owned: [], cleanup: [], reducedMotion: matchMedia('(prefers-reduced-motion: reduce)') };
235
+ shared.instances.set(key, s);
236
+ var refresh = function() { if (!s.dead && s.options) { try { render(s); } catch (_) {} } };
237
+ var observer = new ResizeObserver(refresh); observer.observe(host);
238
+ s.reducedMotion.addEventListener('change', refresh);
239
+ s.cleanup.push(function() { observer.disconnect(); s.reducedMotion.removeEventListener('change', refresh); });
240
+ }
241
+ var preview = host.nextElementSibling;
242
+ if (preview && preview.classList.contains("blu-component-preview")) preview.hidden = true;
243
+ host._bluChartsSparklineOwner = key;
244
+ s.options = options || {};
245
+ s.events = events || {};
246
+ attr(host, "data-blu-ready", "true");
247
+ try {
248
+ render(s);
249
+ report(s, "");
250
+ return "";
251
+ } catch (e) {
252
+ s.renderSignature = void 0;
253
+ report(s, e.message || String(e));
254
+ return e.message || String(e);
255
+ }
256
+ }, destroy };
257
+ }
258
+ function installSparkline() {
259
+ return installed || (installed = Object.assign(api("Sparkline", sparkline), { geometry }));
260
+ }
261
+ export {
262
+ installSparkline
263
+ };
@@ -0,0 +1,19 @@
1
+ import type {SparklineOptions} from './sparkline.ts';
2
+ import {boundedList,boundedText,safeNumber} from './limits.ts';
3
+ import {isComponentColor} from './colors.ts';
4
+ /** Validate before touching the SVG or committing an update. */
5
+ export function validateSparkline(o:SparklineOptions) {
6
+ boundedList(o.Points,'Points');
7
+ if(o.Messages!==undefined&&(!o.Messages||typeof o.Messages!=='object'||Array.isArray(o.Messages)||Object.keys(o.Messages).length>100||!Object.values(o.Messages).every(boundedText)))throw new Error('Messages must contain plain text translations.');
8
+ if(o.Points.some(p=>!p||!safeNumber(p.Value)||p.Label!==undefined&&!boundedText(p.Label)))throw new Error('Points require supported finite values and plain text labels.');
9
+ for(const key of ['AnimateGlow','ShowArea','AreaFade','ShowEndMarker','ShowDataTable'] as const)
10
+ if(o[key]!==undefined&&typeof o[key]!=='boolean')throw new Error(`${key} must be Boolean.`);
11
+ for(const key of ['AccessibleLabel','SummaryText','EmptyText','ExtendedClass'] as const)
12
+ if(o[key]!==undefined&&!boundedText(o[key]))throw new Error(`${key} must be plain text within the text limit.`);
13
+ if(o.Color!==undefined&&!isComponentColor(o.Color))throw new Error('Unknown ComponentColor.');
14
+ if(o.LineStyle!==undefined&&!['Straight','Smooth'].includes(o.LineStyle))throw new Error('LineStyle must be Straight or Smooth.');
15
+ if(o.Animation!==undefined&&!['Off','Once','Continuous'].includes(o.Animation))throw new Error('Animation must be Off, Once or Continuous.');
16
+ if(o.Height!==undefined&&(!Number.isInteger(o.Height)||o.Height<24||o.Height>240))throw new Error('Height must be an integer from 24 to 240.');
17
+ if(o.AnimationDuration!==undefined&&(!safeNumber(o.AnimationDuration)||o.AnimationDuration<1||o.AnimationDuration>60))throw new Error('AnimationDuration must be between 1 and 60.');
18
+ if(o.AreaOpacity!==undefined&&(!safeNumber(o.AreaOpacity)||o.AreaOpacity<0||o.AreaOpacity>100))throw new Error('AreaOpacity must be between 0 and 100.');
19
+ }
@@ -0,0 +1,25 @@
1
+ /* BluKavantUI Charts Community / Sparkline. SVG scales with the container; colors inherit the
2
+ shared palette. Values are always exposed through an accessible summary. */
3
+ .blu-sparkline { min-width: 0; width: 100%; color: var(--bluui-text); }
4
+ .blu-sparkline svg { display: block; width: 100%; height: var(--blu-sparkline-height, 64px); overflow: visible; }
5
+ .blu-sparkline__line { fill: none; stroke: var(--blu-sparkline-color); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
6
+ .blu-sparkline__area { fill: var(--blu-sparkline-color); opacity: 1; }
7
+ .blu-sparkline__point { fill: var(--blu-sparkline-color); }
8
+ .blu-sparkline__empty { margin: 0; min-height: var(--blu-sparkline-height, 64px); display: flex; align-items: center; color: var(--bluui-text-muted); }
9
+ .blu-sparkline__data { margin-top: 8px; font-size: 13px; }
10
+ .blu-sparkline__data summary { width: fit-content; padding: 6px 0; cursor: pointer; color: var(--bluui-color-primary-text); }
11
+ .blu-sparkline__data table { border-collapse: collapse; width: 100%; margin-top: 8px; }
12
+ .blu-sparkline__data :is(th,td) { padding: 6px 12px; border-bottom: 1px solid var(--bluui-border); text-align: start; }
13
+ .blu-sparkline__data summary:focus-visible { outline: 2px solid var(--bluui-primary); outline-offset: 3px; }
14
+
15
+
16
+ /* A small point and its soft halo travel along the line, easing into the end. */
17
+ .blu-sparkline__traveler { pointer-events: none; fill: var(--blu-sparkline-color); }
18
+ .blu-sparkline__halo { opacity: .18; filter: blur(1.5px); }
19
+ .blu-sparkline__ring { opacity: .12; }
20
+ .blu-sparkline__cursor { stroke: none; }
21
+ .blu-sparkline__pause { margin-top: 8px; padding: 5px 8px; min-height: 28px; border: 1px solid var(--bluui-border); border-radius: 5px; background: var(--bluui-surface); color: var(--bluui-text-muted); font: inherit; font-size: 11px; cursor: pointer; }
22
+ .blu-sparkline__pause:focus-visible { outline: 2px solid var(--bluui-primary); outline-offset: 3px; }
23
+ @media (prefers-reduced-motion: reduce), (forced-colors: active) {
24
+ .blu-sparkline__traveler { display: none; }
25
+ }
@@ -0,0 +1,68 @@
1
+ import { installSparkline } from './sparkline-runtime/runtime.js'
2
+ import type { ComponentColor } from './types.ts'
3
+ import { validateSparkline } from './sparkline-validation.ts'
4
+
5
+ /** One ordered numeric observation. */
6
+ export interface BluSparklinePoint { Label?: string; Value: number }
7
+ export interface SparklineOptions {
8
+ Points: BluSparklinePoint[]
9
+ /** Height in pixels, from 24 to 240. Defaults to 64. */
10
+ Height?: number
11
+ Color?: ComponentColor
12
+ /** A smooth curve that stays within the data range, or straight segments. Defaults to Smooth. */
13
+ LineStyle?: 'Straight' | 'Smooth'
14
+ /** @deprecated Use Animation. False disables motion; true uses Once. */
15
+ AnimateGlow?: boolean
16
+ /** A glow and point follow the line. Defaults to Once; reduced motion disables it. */
17
+ Animation?: 'Off' | 'Once' | 'Continuous'
18
+ /** Seconds per pass, from 1 to 60. Defaults to 5. */
19
+ AnimationDuration?: number
20
+ ShowArea?: boolean
21
+ /** Area opacity from 0 to 100 percent. Defaults to 18. */
22
+ AreaOpacity?: number
23
+ /** Fade the area to transparent at the bottom. Defaults to true. */
24
+ AreaFade?: boolean
25
+ /** Final point for a static trend. Hidden during animation; a single value always remains visible. */
26
+ ShowEndMarker?: boolean
27
+ ShowDataTable?: boolean
28
+ AccessibleLabel?: string
29
+ SummaryText?: string
30
+ EmptyText?: string
31
+ ExtendedClass?: string
32
+ /** Plain-text translations for the summary, animation controls and data disclosure. */
33
+ Messages?: Record<string,string>
34
+ }
35
+ export interface SparklineEvents { Error?(message: string): unknown }
36
+ export interface SparklineInstance {
37
+ element: HTMLElement
38
+ update(options: Partial<SparklineOptions>): void
39
+ destroy(): void
40
+ }
41
+
42
+ /** Compact SVG trend. Importing this entry does not load ECharts. */
43
+ export function createSparkline(host: HTMLElement, options: SparklineOptions, events: SparklineEvents = {}): SparklineInstance {
44
+ const runtime = installSparkline()
45
+ const element = document.createElement('div')
46
+ element.id = 'blu-charts-sparkline-' + crypto.randomUUID()
47
+ host.append(element)
48
+ let current = { ...options }
49
+ let disposed = false
50
+ const update = (patch: Partial<SparklineOptions>) => {
51
+ if (disposed) throw new Error('Component is destroyed.')
52
+ const next = { ...current, ...structuredClone(patch) }
53
+ try { validateSparkline(next) } catch(error) { try { events.Error?.((error as Error).message) } catch {} throw error }
54
+ const { Points, ExtendedClass, ...rest } = next
55
+ const error = runtime.sync(element.id, { ...rest, PointsJson: JSON.stringify(Points || []) }, events, element.id)
56
+ if (error) throw new Error(error)
57
+ current = next
58
+ element.className = ExtendedClass || ''
59
+ }
60
+ const destroy = () => {
61
+ if (disposed) return
62
+ disposed = true
63
+ runtime.destroy(element.id)
64
+ element.remove()
65
+ }
66
+ try { update({}) } catch (error) { destroy(); throw error }
67
+ return { element, update, destroy }
68
+ }
@@ -0,0 +1,17 @@
1
+ /** Give a control's focus/value announcement time to settle before a chart state
2
+ * change. Keep the live region outside aria-busy so loading is not deferred. */
3
+ export function stateAnnouncement(element: HTMLElement) {
4
+ let previous = '', timer = 0;
5
+ return {
6
+ update(message: string) {
7
+ if (message === previous) return;
8
+ const initial = !previous;
9
+ previous = message;
10
+ window.clearTimeout(timer);
11
+ if (initial) { element.textContent = message; return; }
12
+ element.textContent = '';
13
+ timer = window.setTimeout(() => { if (element.isConnected) element.textContent = message; }, 150);
14
+ },
15
+ destroy() { window.clearTimeout(timer); },
16
+ };
17
+ }
package/src/types.ts ADDED
@@ -0,0 +1,104 @@
1
+ /** Shared Web chart contracts; native inputs are normalized by the private adapter. */
2
+ export type ComponentColor = 'Default' | 'Primary' | 'Secondary' | 'Success' | 'Processing' | 'Warning' | 'Error' | 'Danger' | 'Magenta' | 'Red' | 'Volcano' | 'Orange' | 'Gold' | 'Lime' | 'Green' | 'Cyan' | 'Blue' | 'GeekBlue' | 'Purple';
3
+ export type ChartColor = ComponentColor | `#${string}`;
4
+ export type ChartFormat = 'png' | 'svg';
5
+ export type ValueFormat = 'Currency' | 'Number' | 'Percent';
6
+ export type LegendStyle = 'Standard' | 'ValueList' | 'Compact' | 'SummaryCards';
7
+ export type LegendPosition = 'Top' | 'Left' | 'Right' | 'Bottom';
8
+ export interface ChartIdentity { Key: string; Label: string; Color?: ChartColor }
9
+ export interface ChartDatum extends ChartIdentity { Value: number | null }
10
+ export interface ChartCategory extends ChartIdentity {}
11
+ export interface ChartOptions {
12
+ Title?: string;
13
+ Description?: string;
14
+ /** Plot height in pixels, excluding title, toolbar and data table. */
15
+ Height?: number;
16
+ ValueFormat?: ValueFormat;
17
+ CurrencyCode?: string;
18
+ Locale?: string;
19
+ UseCustomPrecision?: boolean;
20
+ /** Display precision only; the data alternative retains the original values. */
21
+ DecimalPlaces?: number;
22
+ AccessibleLabel?: string;
23
+ ExtendedClass?: string;
24
+ /** Change to reset map/treemap view state. */
25
+ ResetKey?: string;
26
+ /** Plain-text replacements for shared controls and announcements. */
27
+ Messages?: Record<string, string>;
28
+ ShowHeader?: boolean;
29
+ ShowToolbar?: boolean;
30
+ ShowLegend?: boolean;
31
+ LegendStyle?: LegendStyle;
32
+ LegendPosition?: LegendPosition;
33
+ LegendShowValues?: boolean;
34
+ /** Percentages only appear for meaningful partitions, separately for each nested ring. */
35
+ LegendShowPercentages?: boolean;
36
+ ShowTooltip?: boolean;
37
+ ShowLabels?: boolean;
38
+ ShowDataTable?: boolean;
39
+ /** Optional fill textures. Off by default; exact-value data remains available. */
40
+ ShowPatterns?: boolean;
41
+ EnableSelection?: boolean;
42
+ EnableAnimation?: boolean;
43
+ IsLoading?: boolean;
44
+ EmptyMessage?: string;
45
+ SelectedKey?: string | null;
46
+ ExportFootnote?: string;
47
+ }
48
+ export interface MixedSeries {
49
+ Key: string;
50
+ Name: string;
51
+ Type: 'Bar' | 'Line';
52
+ /** One value per category. null means missing; zero is a real value. */
53
+ Values: (number | null)[];
54
+ /** Shared BluKavant ComponentColor or a custom hex color. */
55
+ Color?: ChartColor;
56
+ IsDashed?: boolean;
57
+ }
58
+ export interface MixedChartOptions extends ChartOptions {
59
+ Categories: ChartCategory[];
60
+ Series: MixedSeries[];
61
+ BarLayout?: 'Stacked' | 'Grouped';
62
+ SmoothLines?: boolean;
63
+ }
64
+ export interface HeatmapCell {
65
+ Key: string;
66
+ XKey: string;
67
+ YKey: string;
68
+ Value: number | null;
69
+ }
70
+ export interface HeatmapOptions extends ChartOptions {
71
+ XCategories: ChartCategory[];
72
+ YCategories: ChartCategory[];
73
+ Cells: HeatmapCell[];
74
+ ColorScale?: ComponentColor | 'Automatic' | 'Teal' | 'Diverging';
75
+ MinValue?: number;
76
+ MaxValue?: number;
77
+ }
78
+ export interface MixedPoint {
79
+ Key: string; CategoryLabel: string; SeriesKey: string; SeriesName: string; Value: number | null;
80
+ }
81
+ export interface HeatmapPoint extends HeatmapCell { XLabel: string; YLabel: string }
82
+ export interface ChartEvents<Point> {
83
+ PointClick?: (point: Point, origin?: 'Chart' | 'Table') => void;
84
+ SelectionChange?: (selection: { Key: string | null; Reason?: 'User' | 'DataChanged' }) => void;
85
+ Ready?: () => void;
86
+ StateChange?: (state: { State: 'Loading' | 'Ready' | 'Empty' | 'Error'; Revision: number }) => void;
87
+ PatternsChange?: (state: { ShowPatterns: boolean }) => void;
88
+ ExportStarted?: (result: { Format: ChartFormat; FileName: string; RequestId: string }) => void;
89
+ /** @deprecated Use ExportStarted. Completion means download initiated, not file saved. */
90
+ ExportComplete?: (result: { Format: ChartFormat; FileName: string }) => void;
91
+ Error?: (error: { Message: string; Code?: string; RequestId?: string }) => void;
92
+ }
93
+ export interface ChartInstance<Options> {
94
+ update(options: Partial<Options>): void;
95
+ resize(): void;
96
+ /** Silent application-controlled selection. Does not emit SelectionChange. */
97
+ setSelection(key: string | null): void;
98
+ /** Transient emphasis, useful for Grid row hover/click. */
99
+ highlight(key: string | null): void;
100
+ /** PNG defaults to 2× the plot resolution. Initiates a browser download. */
101
+ download(format?: ChartFormat, filename?: string, requestId?: string): Promise<void>;
102
+ /** Idempotent. Releases observers, events and the ECharts instance. */
103
+ destroy(): void;
104
+ }
@@ -0,0 +1,75 @@
1
+ import type { ChartCategory, ChartOptions, MixedChartOptions, HeatmapOptions } from './types.ts';
2
+ import { isChartColor, isComponentColor } from './colors.ts';
3
+ import { boundedList, boundedText, safeNumber, limits } from './limits.ts';
4
+
5
+ export function assert(condition: unknown, message: string): asserts condition {
6
+ if (!condition) throw new Error(message);
7
+ }
8
+ export function categories(items: ChartCategory[], name: string) {
9
+ boundedList(items, name);
10
+ const keys = new Set<string>();
11
+ for (const item of items) {
12
+ assert(item && boundedText(item.Key) && item.Key.length > 0 && boundedText(item.Label), `${name} require a non-empty Key and a Label within the text limit.`);
13
+ assert(!keys.has(item.Key), `${name} contain a duplicate key: ${item.Key}.`);
14
+ keys.add(item.Key);
15
+ }
16
+ return keys;
17
+ }
18
+ export function value(value: unknown) { return value === null || safeNumber(value); }
19
+ export function base(options: ChartOptions) {
20
+ assert(options.Height === undefined || (Number.isFinite(options.Height) && options.Height >= 160 && options.Height <= 1000), 'Height must be between 160 and 1000 pixels.');
21
+ assert(options.ValueFormat === undefined || ['Currency', 'Number', 'Percent'].includes(options.ValueFormat), 'Invalid ValueFormat.');
22
+ for (const key of ['UseCustomPrecision','ShowHeader', 'ShowToolbar', 'ShowLegend', 'LegendShowValues', 'LegendShowPercentages', 'ShowTooltip', 'ShowLabels', 'ShowDataTable', 'ShowPatterns', 'EnableSelection', 'EnableAnimation', 'IsLoading'] as const) {
23
+ assert(options[key] === undefined || typeof options[key] === 'boolean', `${key} must be a boolean.`);
24
+ }
25
+ for (const key of ['AccessibleLabel','ExtendedClass','ResetKey','Title', 'Description', 'EmptyMessage', 'ExportFootnote', 'Locale', 'CurrencyCode'] as const) {
26
+ assert(options[key] === undefined || boundedText(options[key]), `${key} must be text within the text limit.`);
27
+ }
28
+ assert(options.LegendStyle === undefined || ['Standard', 'ValueList', 'Compact', 'SummaryCards'].includes(options.LegendStyle), 'Invalid LegendStyle.');
29
+ assert(options.LegendPosition === undefined || ['Top', 'Left', 'Right', 'Bottom'].includes(options.LegendPosition), 'Invalid LegendPosition.');
30
+ assert(options.DecimalPlaces === undefined || Number.isInteger(options.DecimalPlaces) && options.DecimalPlaces >= 0 && options.DecimalPlaces <= 12, 'DecimalPlaces must be an integer from 0 to 12.');
31
+ assert(options.Messages === undefined || !!options.Messages && typeof options.Messages === 'object' && !Array.isArray(options.Messages) && Object.keys(options.Messages).length <= 100 && Object.values(options.Messages).every(boundedText), 'Messages must contain plain text translations.');
32
+ new Intl.NumberFormat(options.Locale || 'en-US', options.ValueFormat === 'Currency' ? {style:'currency',currency:options.CurrencyCode || 'USD'} : {});
33
+ }
34
+ export function validateMixed(options: MixedChartOptions) {
35
+ base(options);
36
+ const keys = categories(options.Categories, 'Categories');
37
+ assert(options.SelectedKey == null || keys.has(options.SelectedKey), 'SelectedKey must match a category.');
38
+ assert(options.BarLayout === undefined || ['Stacked', 'Grouped'].includes(options.BarLayout), 'Invalid BarLayout.');
39
+ assert(options.SmoothLines === undefined || typeof options.SmoothLines === 'boolean', 'SmoothLines must be a boolean.');
40
+ boundedList(options.Series, 'Series');
41
+ assert(options.Categories.length * options.Series.length <= limits.observations, 'Too many chart observations. Aggregate the data first.');
42
+ const seriesKeys = new Set<string>();
43
+ for (const series of options.Series) {
44
+ assert(series && boundedText(series.Key) && series.Key.length && !seriesKeys.has(series.Key), 'Each series needs a unique, non-empty Key.');
45
+ seriesKeys.add(series.Key);
46
+ assert(boundedText(series.Name) && ['Bar', 'Line'].includes(series.Type), 'Each series needs a Name and a Bar or Line Type.');
47
+ assert(Array.isArray(series.Values) && series.Values.length === options.Categories.length, `${series.Key}: Values must match the category count.`);
48
+ assert(series.Values.every(value), `${series.Key}: Values must be finite numbers or null.`);
49
+ assert(series.Color === undefined || isChartColor(series.Color), 'Series Color must be a ComponentColor or hex color.');
50
+ assert(series.IsDashed === undefined || typeof series.IsDashed === 'boolean', 'IsDashed must be a boolean.');
51
+ }
52
+ }
53
+ export function validateHeatmap(options: HeatmapOptions) {
54
+ base(options);
55
+ const x = categories(options.XCategories, 'XCategories'), y = categories(options.YCategories, 'YCategories');
56
+ boundedList(options.Cells, 'Cells');
57
+ assert(x.size * y.size <= limits.observations, 'Too many heatmap positions. Aggregate the data first.');
58
+ const keys = new Set<string>(), positions = new Set<string>();
59
+ for (const cell of options.Cells) {
60
+ assert(cell && typeof cell.Key === 'string' && cell.Key.length && !keys.has(cell.Key), 'Each cell needs a unique, non-empty Key.');
61
+ assert(x.has(cell.XKey) && y.has(cell.YKey), `${cell.Key}: XKey and YKey must match category keys.`);
62
+ const position = JSON.stringify([cell.XKey, cell.YKey]);
63
+ assert(!positions.has(position), 'Only one cell may occupy each X/Y position.');
64
+ assert(value(cell.Value), `${cell.Key}: Value must be a finite number or null.`);
65
+ positions.add(position); keys.add(cell.Key);
66
+ }
67
+ assert(options.SelectedKey == null || keys.has(options.SelectedKey), 'SelectedKey must match a cell.');
68
+ assert(options.ColorScale === undefined || (isComponentColor(options.ColorScale) || ['Automatic', 'Teal', 'Diverging'].includes(options.ColorScale)), 'Invalid ColorScale.');
69
+ assert(options.MinValue === undefined || Number.isFinite(options.MinValue), 'MinValue must be finite.');
70
+ assert(options.MaxValue === undefined || Number.isFinite(options.MaxValue), 'MaxValue must be finite.');
71
+ assert(options.MinValue === undefined || options.MaxValue === undefined || options.MinValue < options.MaxValue, 'MinValue must be less than MaxValue.');
72
+ }
73
+ export function escapeText(text: string): string {
74
+ return text.replace(/[&<>"']/g, char => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[char]!));
75
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "strict": true,
8
+ "skipLibCheck": false,
9
+ "allowImportingTsExtensions": true,
10
+ "declaration": true,
11
+ "emitDeclarationOnly": true,
12
+ "types": ["node"],
13
+ "rootDir": "src"
14
+ },
15
+ "include": ["src/**/*.ts"]
16
+ }