@schukai/monster 4.10.4 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
 
4
4
 
5
+ ## [4.11.0] - 2025-05-20
6
+
7
+ ### Add Features
8
+
9
+ - Add new metric control components and associated styling
10
+
11
+
12
+
5
13
  ## [4.10.4] - 2025-05-20
6
14
 
7
15
  ### Bug Fixes
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.7.0","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.10.4"}
1
+ {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.7.0","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.11.0"}
@@ -0,0 +1,475 @@
1
+ /**
2
+ * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
3
+ * Node module: @schukai/monster
4
+ *
5
+ * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
6
+ * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
7
+ *
8
+ * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
9
+ * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
10
+ * For more information about purchasing a commercial license, please contact schukai GmbH.
11
+ */
12
+
13
+ import {instanceSymbol} from "../../constants.mjs";
14
+ import {addAttributeToken} from "../../dom/attributes.mjs";
15
+ import {
16
+ ATTRIBUTE_ERRORMESSAGE,
17
+ ATTRIBUTE_ROLE,
18
+ } from "../../dom/constants.mjs";
19
+ import {CustomElement, updaterTransformerMethodsSymbol} from "../../dom/customelement.mjs";
20
+ import {
21
+ assembleMethodSymbol,
22
+ registerCustomElement,
23
+ } from "../../dom/customelement.mjs";
24
+ import {findTargetElementFromEvent} from "../../dom/events.mjs";
25
+ import {isFunction, isString} from "../../types/is.mjs";
26
+ import {MetricGraphStyleSheet} from "./stylesheet/metric-graph.mjs";
27
+ import {fireCustomEvent} from "../../dom/events.mjs";
28
+
29
+
30
+ export {MetricGraph};
31
+
32
+ /**
33
+ * @private
34
+ * @type {symbol}
35
+ */
36
+ export const metricGraphControlElementSymbol = Symbol("metricGraphControlElement");
37
+
38
+ /**
39
+ * A MetricGraph
40
+ *
41
+ * @fragments /fragments/data/metric-graph/
42
+ *
43
+ * @example /examples/data/metric-graph-simple
44
+ *
45
+ * @since 4.11.0
46
+ * @copyright schukai GmbH
47
+ * @summary A beautiful MetricGraph that can make your life easier and also looks good.
48
+ */
49
+ class MetricGraph extends CustomElement {
50
+ /**
51
+ * This method is called by the `instanceof` operator.
52
+ * @returns {symbol}
53
+ */
54
+ static get [instanceSymbol]() {
55
+ return Symbol.for("@schukai/monster/data/metric-graph@@instance");
56
+ }
57
+
58
+ /**
59
+ * @return {Components.Data.Metric
60
+ */
61
+ [assembleMethodSymbol]() {
62
+ super[assembleMethodSymbol]();
63
+ initControlReferences.call(this);
64
+ return this;
65
+ }
66
+
67
+ /**
68
+ * To set the options via the HTML Tag, the attribute `data-monster-options` must be used.
69
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
70
+ *
71
+ * The individual configuration values can be found in the table.
72
+ *
73
+ * @property {Object} templates Template definitions
74
+ * @property {string} templates.main Main template
75
+ * @property {string} curve The curve of the graph (step, smooth, bubble, bar, area, dot, lollipop, line)
76
+ * @property {Object} values Value definitions
77
+ * @property {number} values.value The value of the metric
78
+ * @property {number} values.change The change of the metric
79
+ * @property {number} values.secondary The secondary value of the metric
80
+ * @property {Array} values.points The points of the metric
81
+ * @property {Object} labels Label definitions
82
+ * @property {string} labels.title Title of the metric
83
+ * @property {string} labels.subtext Subtext of the metric
84
+ * @property {Object} classes CSS classes
85
+ * @property {string} classes.dot CSS class for the dot
86
+ */
87
+ get defaults() {
88
+ return Object.assign({}, super.defaults, {
89
+ templates: {
90
+ main: getTemplate(),
91
+ },
92
+
93
+ graphType: "linear",
94
+
95
+ values: {
96
+ main: null,
97
+ change: null,
98
+ secondary: null,
99
+ points: [2, 2, 2, -30, 30, 5, 4,4, 3, 3, 2,2, 1, 1, 1, 1],
100
+ },
101
+ labels: {
102
+ title: null,
103
+ subtext: null,
104
+
105
+ },
106
+ classes: {
107
+ dot: "monster-theme-primary-1"
108
+ },
109
+
110
+ aria : {
111
+ description: null,
112
+ }
113
+
114
+ });
115
+ }
116
+
117
+
118
+ /**
119
+ *
120
+ * @returns {{tosparkline: ((function(*): (string|string))|*)}}
121
+ */
122
+ [updaterTransformerMethodsSymbol]() {
123
+
124
+ return {
125
+ "toGraph": (value) => {
126
+ if (isString(value)) {
127
+ value = value.split(",").map((v) => {
128
+ return parseFloat(v);
129
+ });
130
+ }
131
+
132
+ const graphType = this.getOption("graphType");
133
+
134
+ if (!Array.isArray(value) || value.length === 0) return "";
135
+ switch (graphType.toLowerCase()) {
136
+ case "step":
137
+ return renderStepGraph.call(this, value);
138
+ case "smooth":
139
+ return renderSmoothGraph.call(this, value);
140
+ case "bubble":
141
+ return renderBubbleGraph.call(this, value);
142
+ case "bar":
143
+ return renderBarGraph.call(this, value);
144
+ case "area":
145
+ return renderAreaGraph.call(this, value);
146
+ case "dot":
147
+ return renderDotGraph.call(this, value);
148
+ case "lollipop":
149
+ return renderLollipopGraph.call(this, value);
150
+ case "line":
151
+ default:
152
+ return renderLineGraph.call(this, value);
153
+ }
154
+
155
+ }
156
+ };
157
+ }
158
+
159
+ /**
160
+ * @return {string}
161
+ */
162
+ static getTag() {
163
+ return "monster-metric-graph";
164
+ }
165
+
166
+ /**
167
+ * @return {CSSStyleSheet[]}
168
+ */
169
+ static getCSSStyleSheet() {
170
+ return [MetricGraphStyleSheet];
171
+ }
172
+
173
+
174
+ }
175
+
176
+ function renderAreaGraph(values, options = {}) {
177
+ const {
178
+ width = 100,
179
+ height = 30,
180
+ stroke = "currentColor",
181
+ strokeWidth = 2,
182
+ fill = "rgba(0, 0, 0, 0.1)",
183
+ } = options;
184
+
185
+ if (!Array.isArray(values) || values.length === 0) return "";
186
+
187
+ const min = Math.min(...values);
188
+ const max = Math.max(...values);
189
+ const range = max - min || 1;
190
+
191
+ const step = width / (values.length - 1);
192
+ const points = values.map((v, i) => {
193
+ const x = i * step;
194
+ const y = height - ((v - min) / range) * height;
195
+ return { x, y };
196
+ });
197
+
198
+ let d = `M ${points[0].x},${height} L ${points[0].x},${points[0].y}`;
199
+ for (let i = 1; i < points.length; i++) {
200
+ d += ` L ${points[i].x},${points[i].y}`;
201
+ }
202
+ d += ` L ${points[points.length - 1].x},${height} Z`;
203
+
204
+ return `<path d="${d}" stroke="${stroke}" stroke-width="${strokeWidth}" fill="${fill}" stroke-linejoin="round" />`;
205
+ }
206
+
207
+ function renderBubbleGraph(values, options = {}) {
208
+ const {
209
+ width = 100,
210
+ height = 30,
211
+ minRadius = 2,
212
+ maxRadius = 8,
213
+ lightRange = [0.3, 1.0], // fill-opacity von 0.3 bis 1.0
214
+ align = "middle",
215
+ } = options;
216
+
217
+ if (!Array.isArray(values) || values.length === 0) return "";
218
+
219
+ const min = Math.min(...values);
220
+ const max = Math.max(...values);
221
+ const range = max - min || 1;
222
+
223
+ const stepX = width / values.length;
224
+
225
+ let centerY;
226
+ switch (align) {
227
+ case "top": centerY = maxRadius; break;
228
+ case "bottom": centerY = height - maxRadius; break;
229
+ case "middle":
230
+ default: centerY = height / 2;
231
+ }
232
+
233
+ return values.map((v, i) => {
234
+ const x = i * stepX + stepX / 2;
235
+ const norm = (v - min) / range;
236
+ const r = minRadius + norm * (maxRadius - minRadius);
237
+ const opacity = lightRange[0] + norm * (lightRange[1] - lightRange[0]);
238
+
239
+ return `<circle cx="${x.toFixed(2)}" cy="${centerY.toFixed(2)}" r="${r.toFixed(2)}" fill="currentColor" fill-opacity="${opacity.toFixed(2)}" />`;
240
+ }).join("");
241
+ }
242
+
243
+
244
+
245
+
246
+ function renderLollipopGraph(values, options = {}) {
247
+ const {
248
+ width = 100,
249
+ height = 30,
250
+ color = "currentColor",
251
+ radius = 2,
252
+ strokeWidth = 1,
253
+ } = options;
254
+
255
+ if (!Array.isArray(values) || values.length === 0) return "";
256
+
257
+ const min = Math.min(...values);
258
+ const max = Math.max(...values);
259
+ const range = max - min || 1;
260
+
261
+ const step = width / (values.length - 1);
262
+
263
+ return values.map((v, i) => {
264
+ const x = i * step;
265
+ const y = height - ((v - min) / range) * height;
266
+ const line = `<line x1="${x}" y1="${height}" x2="${x}" y2="${y}" stroke="${color}" stroke-width="${strokeWidth}" />`;
267
+ const circle = `<circle cx="${x}" cy="${y}" r="${radius}" fill="${color}" />`;
268
+ return line + circle;
269
+ }).join("");
270
+ }
271
+
272
+
273
+ function renderDotGraph(values, options = {}) {
274
+ const {
275
+ width = 100,
276
+ height = 30,
277
+ radius = 2,
278
+ color = "currentColor",
279
+ } = options;
280
+
281
+ if (!Array.isArray(values) || values.length === 0) return "";
282
+
283
+ const min = Math.min(...values);
284
+ const max = Math.max(...values);
285
+ const range = max - min || 1;
286
+
287
+ const step = width / (values.length - 1);
288
+
289
+ return values.map((v, i) => {
290
+ const x = i * step;
291
+ const y = height - ((v - min) / range) * height;
292
+ return `<circle cx="${x.toFixed(2)}" cy="${y.toFixed(2)}" r="${radius}" fill="${color}" />`;
293
+ }).join("");
294
+ }
295
+
296
+
297
+
298
+ function renderBarGraph(values, options = {}) {
299
+ const {
300
+ width = 100,
301
+ height = 30,
302
+ barColor = "currentColor",
303
+ barSpacing = 1,
304
+ } = options;
305
+
306
+ if (!Array.isArray(values) || values.length === 0) return "";
307
+
308
+ const min = Math.min(...values);
309
+ const max = Math.max(...values);
310
+ const range = max - min || 1;
311
+
312
+ const barWidth = width / values.length - barSpacing;
313
+
314
+ return values
315
+ .map((v, i) => {
316
+ const x = i * (barWidth + barSpacing);
317
+ const barHeight = ((v - min) / range) * height;
318
+ const y = height - barHeight;
319
+ return `<rect x="${x.toFixed(2)}" y="${y.toFixed(2)}" width="${barWidth.toFixed(2)}" height="${barHeight.toFixed(2)}" fill="${barColor}" />`;
320
+ })
321
+ .join("");
322
+ }
323
+
324
+ function renderLineGraph(values) {
325
+ if (!Array.isArray(values) || values.length === 0) return "";
326
+ const min = Math.min(...values);
327
+ const max = Math.max(...values);
328
+ const range = max - min || 1;
329
+
330
+ const step = 100 / (values.length - 1);
331
+ const points = values.map((v, i) => {
332
+ const x = i * step;
333
+ const y = 30 - ((v - min) / range) * 30;
334
+ return `${x},${y}`;
335
+ });
336
+
337
+ return `<polyline points="${points.join(" ")}" stroke="currentColor" stroke-width="2" fill="none" />`;
338
+ }
339
+
340
+ function renderSmoothGraph(values, options = {}) {
341
+ const {
342
+ width = 100,
343
+ height = 30,
344
+ stroke = "currentColor",
345
+ strokeWidth = 2,
346
+ fill = "none",
347
+ } = options;
348
+
349
+ if (!Array.isArray(values) || values.length === 0) return "";
350
+
351
+ const min = Math.min(...values);
352
+ const max = Math.max(...values);
353
+ const range = max - min || 1;
354
+
355
+ const stepX = width / (values.length - 1);
356
+
357
+ const points = values.map((v, i) => {
358
+ const x = i * stepX;
359
+ const y = height - ((v - min) / range) * height;
360
+ return { x, y };
361
+ });
362
+
363
+ // Bézier-Path erzeugen
364
+ let d = `M ${points[0].x},${points[0].y}`;
365
+ for (let i = 1; i < points.length; i++) {
366
+ const prev = points[i - 1];
367
+ const curr = points[i];
368
+ const cx = (prev.x + curr.x) / 2;
369
+ d += ` Q ${prev.x},${prev.y} ${cx},${(prev.y + curr.y) / 2}`;
370
+ }
371
+ d += ` T ${points[points.length - 1].x},${points[points.length - 1].y}`;
372
+
373
+ return `<path d="${d}" stroke="${stroke}" stroke-width="${strokeWidth}" fill="${fill}" stroke-linecap="round" stroke-linejoin="round" />`;
374
+ }
375
+
376
+ function renderStepGraph(values, options = {}) {
377
+ const {
378
+ width = 100,
379
+ height = 30,
380
+ stroke = "currentColor",
381
+ strokeWidth = 2,
382
+ fill = "none",
383
+ } = options;
384
+
385
+ if (!Array.isArray(values) || values.length === 0) return "";
386
+
387
+ const min = Math.min(...values);
388
+ const max = Math.max(...values);
389
+ const range = max - min || 1;
390
+
391
+ const stepX = width / (values.length - 1);
392
+
393
+ const points = values.map((v, i) => {
394
+ const x = i * stepX;
395
+ const y = height - ((v - min) / range) * height;
396
+ return { x, y };
397
+ });
398
+
399
+ let d = `M ${points[0].x},${points[0].y}`;
400
+ for (let i = 1; i < points.length; i++) {
401
+ const prev = points[i - 1];
402
+ const curr = points[i];
403
+ d += ` H ${curr.x} V ${curr.y}`;
404
+ }
405
+
406
+ return `<path d="${d}" stroke="${stroke}" stroke-width="${strokeWidth}" fill="${fill}" stroke-linecap="round" stroke-linejoin="round" />`;
407
+ }
408
+
409
+
410
+ /**
411
+ * @private
412
+ * @return {void}
413
+ */
414
+ function initControlReferences() {
415
+ this[metricGraphControlElementSymbol] = this.shadowRoot.querySelector(
416
+ `[${ATTRIBUTE_ROLE}="control"]`,
417
+ );
418
+ }
419
+
420
+
421
+ /**
422
+ * @private
423
+ * @return {string}
424
+ */
425
+ function getTemplate() {
426
+ // language=HTML
427
+ return `
428
+ <div data-monster-role="control"
429
+ part="control"
430
+ role="group"
431
+ aria-labelledby="metric-title"
432
+ aria-describedby="metric-value metric-subtext metric-graph-desc">
433
+
434
+ <div class="metric-card" part="card">
435
+ <div class="metric-header" part="header">
436
+ <span data-monster-attributes="class path:classes.dot | prefix:metric-icon\\ :"></span>
437
+ <span id="metric-title" class="metric-title"
438
+ data-monster-replace="path:labels.title | ??:—"></span>
439
+ </div>
440
+
441
+ <div id="metric-value" class="metric-value"
442
+ data-monster-replace="path:values.main"
443
+ part="metric-value"
444
+ aria-live="polite">—</div>
445
+
446
+ <div id="metric-subtext" class="metric-subtext" part="metric-subtext">
447
+ <span data-monster-replace="path:labels.subtext | ??:— ">—</span><br>
448
+ <span class="metric-subtext-value">
449
+ <strong data-monster-replace="path:values.secondary | ??:—">—</strong>
450
+ </span>
451
+ </div>
452
+
453
+ <div part="metric-graph" class="metric-graph">
454
+ <svg viewBox="0 0 100 30"
455
+ preserveAspectRatio="none"
456
+ role="img"
457
+ aria-labelledby="metric-graph-desc"
458
+ focusable="false"
459
+ xmlns="http://www.w3.org/2000/svg"
460
+ data-monster-replace="path:values.points | call:toGraph">
461
+ </svg>
462
+ <span id="metric-graph-desc" class="visually-hidden"
463
+ data-monster-replace="path:aria.graph | ??:Graphische Darstellung der Kennzahl">
464
+ Graphische Darstellung
465
+ </span>
466
+ </div>
467
+ </div>
468
+
469
+ <span class="visually-hidden" data-monster-replace="path:aria.description"></span>
470
+ </div>`;
471
+ }
472
+
473
+
474
+
475
+ registerCustomElement(MetricGraph);
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
3
+ * Node module: @schukai/monster
4
+ *
5
+ * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
6
+ * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
7
+ *
8
+ * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
9
+ * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
10
+ * For more information about purchasing a commercial license, please contact schukai GmbH.
11
+ */
12
+
13
+ import {instanceSymbol} from "../../constants.mjs";
14
+ import {addAttributeToken} from "../../dom/attributes.mjs";
15
+ import {
16
+ ATTRIBUTE_ERRORMESSAGE,
17
+ ATTRIBUTE_ROLE,
18
+ } from "../../dom/constants.mjs";
19
+ import {CustomControl} from "../../dom/customcontrol.mjs";
20
+ import {CustomElement} from "../../dom/customelement.mjs";
21
+ import {
22
+ assembleMethodSymbol,
23
+ registerCustomElement,
24
+ } from "../../dom/customelement.mjs";
25
+ import {findTargetElementFromEvent} from "../../dom/events.mjs";
26
+ import {isFunction} from "../../types/is.mjs";
27
+ import {MetricStyleSheet} from "./stylesheet/metric.mjs";
28
+ import {fireCustomEvent} from "../../dom/events.mjs";
29
+
30
+ export {Metric};
31
+
32
+ /**
33
+ * @private
34
+ * @type {symbol}
35
+ */
36
+ export const metricControlElementSymbol = Symbol("metricControlElement");
37
+
38
+ /**
39
+ * A Metric is a simple component that can be used to display a value.
40
+ *
41
+ * @fragments /fragments/components/data/metric/
42
+ *
43
+ * @example /examples/components/data/metric-simple
44
+ *
45
+ * @since 4.11.0
46
+ * @copyright schukai GmbH
47
+ * @summary A beautiful Metric that can make your life easier and also looks good.
48
+ */
49
+ class Metric extends CustomElement {
50
+ /**
51
+ * This method is called by the `instanceof` operator.
52
+ * @returns {symbol}
53
+ */
54
+ static get [instanceSymbol]() {
55
+ return Symbol.for("@schukai/monster/components/data/metric@@instance");
56
+ }
57
+
58
+ /**
59
+ * @return {Components.Data.Metric
60
+ */
61
+ [assembleMethodSymbol]() {
62
+ super[assembleMethodSymbol]();
63
+ initControlReferences.call(this);
64
+ return this;
65
+ }
66
+
67
+ /**
68
+ * To set the options via the HTML Tag, the attribute `data-monster-options` must be used.
69
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
70
+ *
71
+ * The individual configuration values can be found in the table.
72
+ *
73
+ * @property {Object} templates Template definitions
74
+ * @property {string} templates.main Main template
75
+ * @property {Object} values Value definitions
76
+ * @property {number} values.value The value of the metric
77
+ * @property {number} values.change The change of the metric
78
+ * @property {number} values.direction The direction of the metric
79
+ * @property {number} values.secondary The secondary value of the metric
80
+ * @property {Object} labels Label definitions
81
+ * @property {string} labels.title Title of the metric
82
+ * @property {string} labels.subtext Subtext of the metric
83
+ * @property {Object} classes CSS classes
84
+ * @property {string} classes.dot CSS class for the dot
85
+ * @property {string} classes.metricChange CSS class for the metric change (positive/negative)
86
+ */
87
+ get defaults() {
88
+ return Object.assign({}, super.defaults, {
89
+ templates: {
90
+ main: getTemplate(),
91
+ },
92
+ values: {
93
+ main: null,
94
+ change: null,
95
+ direction: 270,
96
+ secondary: null,
97
+ },
98
+ labels: {
99
+ title: null,
100
+ subtext: null,
101
+ },
102
+ classes: {
103
+ dot: "monster-theme-primary-1",
104
+ change: "positive",
105
+ },
106
+
107
+ aria: {
108
+ description: null,
109
+ }
110
+ });
111
+ }
112
+
113
+ /**
114
+ * @return {string}
115
+ */
116
+ static getTag() {
117
+ return "monster-metric";
118
+ }
119
+
120
+ /**
121
+ * @return {CSSStyleSheet[]}
122
+ */
123
+ static getCSSStyleSheet() {
124
+ return [MetricStyleSheet];
125
+ }
126
+
127
+
128
+ }
129
+
130
+ /**
131
+ * @private
132
+ * @return {void}
133
+ */
134
+ function initControlReferences() {
135
+ this[metricControlElementSymbol] = this.shadowRoot.querySelector(
136
+ `[${ATTRIBUTE_ROLE}="control"]`,
137
+ );
138
+ }
139
+
140
+ /**
141
+ * @private
142
+ * @return {string}
143
+ */
144
+ function getTemplate() {
145
+ // language=HTML
146
+ return `
147
+ <div data-monster-role="control" part="control"
148
+ role="group"
149
+ aria-labelledby="metric-title"
150
+ aria-describedby="metric-subtext metric-value metric-change-text">
151
+
152
+ <div class="metric-card" part="card">
153
+ <div class="metric-header" part="header">
154
+ <span data-monster-attributes="class path:classes.dot | prefix:metric-icon\\ :"></span>
155
+ <span id="metric-title" class="metric-title"
156
+ data-monster-replace="path:labels.title | ??:—"></span>
157
+ </div>
158
+
159
+ <div id="metric-value" class="metric-value"
160
+ data-monster-replace="path:values.main" part="metric-value">—</div>
161
+
162
+ <div id="metric-subtext" class="metric-subtext" part="metric-subtext">
163
+ <span data-monster-replace="path:labels.subtext | ??:— ">—</span><br>
164
+ <span class="metric-subtext-value">
165
+ <strong data-monster-replace="path:values.secondary | ??:—">—</strong>
166
+ </span>
167
+ </div>
168
+
169
+ <div id="metric-change-text" part="metric-change"
170
+ data-monster-attributes="style path:values.direction | tostring | prefix:--arrow-direction\\:\\ : | suffix:deg,
171
+ class path:classes.change | prefix:metric-change\\ :">
172
+ <span class="arrow">
173
+ <svg viewBox="0 0 24 24" width="16" height="16" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
174
+ <path d="M12 4v16m0 0l-6-6m6 6l6-6"
175
+ stroke="currentColor" stroke-width="2"
176
+ fill="none" stroke-linecap="round" stroke-linejoin="round"/>
177
+ </svg>
178
+ </span>
179
+ <span data-monster-replace="path:values.change | ??:—"></span>
180
+ </div>
181
+ </div>
182
+
183
+ <span class="visually-hidden" data-monster-replace="path:aria.description"></span>
184
+ </div>`;
185
+ }
186
+
187
+ registerCustomElement(Metric);