@schukai/monster 4.154.3 → 4.154.5

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,22 @@
2
2
 
3
3
 
4
4
 
5
+ ## [4.154.5] - 2026-10-04
6
+
7
+ ### Bug Fixes
8
+
9
+ - **image-editor:** load raw SVG markup ([#589](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/589))
10
+
11
+
12
+
13
+ ## [4.154.4] - 2026-10-04
14
+
15
+ ### Bug Fixes
16
+
17
+ - **data:** render and document metric values ([#587](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/587), [#588](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/588))
18
+
19
+
20
+
5
21
  ## [4.154.3] - 2026-10-02
6
22
 
7
23
  ### Bug Fixes
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","exports":{".":"./source/monster.mjs","./CHANGELOG.md":"./CHANGELOG.md","./LICENSE":"./LICENSE","./README.md":"./README.md","./package.json":"./package.json","./source/*":"./source/*","./test/*":"./test/*"},"files":["source","test/cases","test/util","CHANGELOG.md"],"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"},"sideEffects":["./source/monster.mjs","./source/components/**","./source/i18n/**","./test/**"],"type":"module","version":"4.154.3"}
1
+ {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","exports":{".":"./source/monster.mjs","./CHANGELOG.md":"./CHANGELOG.md","./LICENSE":"./LICENSE","./README.md":"./README.md","./package.json":"./package.json","./source/*":"./source/*","./test/*":"./test/*"},"files":["source","test/cases","test/util","CHANGELOG.md"],"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"},"sideEffects":["./source/monster.mjs","./source/components/**","./source/i18n/**","./test/**"],"type":"module","version":"4.154.5"}
@@ -1181,6 +1181,25 @@ function setImageFromData(data, options) {
1181
1181
 
1182
1182
  if (isString(data)) {
1183
1183
  const trimmed = data.trim();
1184
+ const normalizedContentType = contentType
1185
+ .split(";", 1)[0]
1186
+ .trim()
1187
+ .toLowerCase();
1188
+ const svgMarkup = trimmed.replace(/^<\?xml[\s\S]*?\?>\s*/iu, "");
1189
+ if (
1190
+ normalizedContentType === "image/svg+xml" &&
1191
+ /^<svg(?:\s|>)/iu.test(svgMarkup)
1192
+ ) {
1193
+ return loadImageFromSource.call(
1194
+ this,
1195
+ `data:image/svg+xml;charset=utf-8,${encodeURIComponent(trimmed)}`,
1196
+ {
1197
+ storeOriginal,
1198
+ resetAspect: storeOriginal,
1199
+ resetView: storeOriginal,
1200
+ },
1201
+ );
1202
+ }
1184
1203
  if (trimmed.startsWith("data:") || trimmed.startsWith("blob:")) {
1185
1204
  return loadImageFromSource.call(this, trimmed, {
1186
1205
  storeOriginal,
@@ -77,17 +77,20 @@ class MetricGraph extends CustomElement {
77
77
  *
78
78
  * @property {Object} templates Template definitions
79
79
  * @property {string} templates.main Main template
80
- * @property {string} curve The curve of the graph (step, smooth, bubble, bar, area, dot, lollipop, line)
80
+ * @property {string} graphType The graph renderer (step, smooth, bubble, bar, area, dot, lollipop, line)
81
81
  * @property {Object} values Value definitions
82
- * @property {number} values.value The value of the metric
82
+ * @property {string|number|null} values.main The primary value of the metric
83
83
  * @property {number} values.change The change of the metric
84
- * @property {number} values.secondary The secondary value of the metric
85
- * @property {Array} values.points The points of the metric
84
+ * @property {string|number|null} values.secondary The secondary value of the metric
85
+ * @property {Array<number>|string} values.points The points of the metric
86
86
  * @property {Object} labels Label definitions
87
- * @property {string} labels.title Title of the metric
88
- * @property {string} labels.subtext Subtext of the metric
87
+ * @property {string|null} labels.title Title of the metric
88
+ * @property {string|null} labels.subtext Subtext of the metric
89
89
  * @property {Object} classes CSS classes
90
90
  * @property {string} classes.dot CSS class for the dot
91
+ * @property {Object} aria Accessible descriptions
92
+ * @property {string|null} aria.description Description of the complete metric card
93
+ * @property {string|null} aria.graph Text alternative for the compact graph
91
94
  */
92
95
  get defaults() {
93
96
  return Object.assign({}, super.defaults, {
@@ -113,6 +116,7 @@ class MetricGraph extends CustomElement {
113
116
 
114
117
  aria: {
115
118
  description: null,
119
+ graph: null,
116
120
  },
117
121
  });
118
122
  }
@@ -74,16 +74,18 @@ class Metric extends CustomElement {
74
74
  * @property {Object} templates Template definitions
75
75
  * @property {string} templates.main Main template
76
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.direction The direction of the metric
80
- * @property {number} values.secondary The secondary value of the metric
77
+ * @property {string|number|null} values.main The primary value of the metric
78
+ * @property {string|number|null} values.change The displayed change of the metric
79
+ * @property {number} values.direction Rotation of the direction indicator in degrees
80
+ * @property {string|number|null} values.secondary The secondary value of the metric
81
81
  * @property {Object} labels Label definitions
82
- * @property {string} labels.title Title of the metric
83
- * @property {string} labels.subtext Subtext of the metric
82
+ * @property {string|null} labels.title Title of the metric
83
+ * @property {string|null} labels.subtext Subtext of the metric
84
84
  * @property {Object} classes CSS classes
85
85
  * @property {string} classes.dot CSS class for the dot
86
- * @property {string} classes.metricChange CSS class for the metric change (positive/negative)
86
+ * @property {string} classes.change CSS class for the metric change (positive/negative)
87
+ * @property {Object} aria Accessible descriptions
88
+ * @property {string|null} aria.description Description of the complete metric card
87
89
  */
88
90
  get defaults() {
89
91
  return Object.assign({}, super.defaults, {
@@ -1372,11 +1372,11 @@ function runUpdateContent(
1372
1372
  }
1373
1373
 
1374
1374
  function applyReplacementValue(element, value) {
1375
- if (!(element instanceof HTMLElement)) {
1375
+ if (!(element instanceof Element)) {
1376
1376
  return false;
1377
1377
  }
1378
1378
 
1379
- if (value instanceof HTMLElement) {
1379
+ if (value instanceof Element) {
1380
1380
  if (element.childNodes.length === 1 && element.firstChild === value) {
1381
1381
  return false;
1382
1382
  }
@@ -109,4 +109,31 @@ describe('ImageEditor', function () {
109
109
  '<monster-button-bar class="grid-span-full monster-margin-top-2">',
110
110
  );
111
111
  });
112
+
113
+ it('loads raw SVG markup with its documented content type issue #589', async function () {
114
+ const OriginalImage = globalThis.Image;
115
+ const sources = [];
116
+
117
+ class ImageMock {
118
+ set src(value) {
119
+ sources.push(value);
120
+ queueMicrotask(() => this.onerror?.(new Error('test stop')));
121
+ }
122
+ }
123
+
124
+ globalThis.Image = ImageMock;
125
+
126
+ try {
127
+ const element = document.createElement('monster-image-editor');
128
+ const markup = '<svg xmlns="http://www.w3.org/2000/svg"><rect width="4" height="3"/></svg>';
129
+
130
+ await element.setImage(markup, {contentType: 'image/svg+xml'}).catch(() => {});
131
+
132
+ expect(sources).to.have.length(1);
133
+ expect(sources[0]).to.match(/^data:image\/svg\+xml;charset=utf-8,/);
134
+ expect(decodeURIComponent(sources[0].split(',', 2)[1])).to.equal(markup);
135
+ } finally {
136
+ globalThis.Image = OriginalImage;
137
+ }
138
+ });
112
139
  });
@@ -0,0 +1,40 @@
1
+ import {expect} from "chai";
2
+ import {initJSDOM} from "../../../util/jsdom.mjs";
3
+
4
+ describe("MetricGraph", () => {
5
+ let MetricGraph;
6
+
7
+ before(async () => {
8
+ await initJSDOM();
9
+ ({MetricGraph} = await import(
10
+ "../../../../source/components/data/metric-graph.mjs"
11
+ ));
12
+ });
13
+
14
+ afterEach(() => document.getElementById("mocks").replaceChildren());
15
+
16
+ it("initializes its documented graph options from attributes", async () => {
17
+ const mocks = document.getElementById("mocks");
18
+ mocks.innerHTML = `
19
+ <monster-metric-graph
20
+ data-monster-option-labels-title="Weekly requests"
21
+ data-monster-option-values-main="840 requests"
22
+ data-monster-option-values-points="62::75::68"
23
+ data-monster-option-graph-type="bar"
24
+ data-monster-option-aria-graph="Three daily request totals">
25
+ </monster-metric-graph>
26
+ `;
27
+ const graph = mocks.firstElementChild;
28
+
29
+ await new Promise((resolve) => setTimeout(resolve, 50));
30
+
31
+ expect(graph).to.be.instanceOf(MetricGraph);
32
+ expect(graph.getOption("graphType")).to.equal("bar");
33
+ expect(graph.getOption("values.main")).to.equal("840 requests");
34
+ expect(graph.getOption("values.points")).to.deep.equal(["62", "75", "68"]);
35
+ expect(graph.getOption("aria.graph")).to.equal("Three daily request totals");
36
+ expect(graph.shadowRoot.querySelectorAll("svg rect")).to.have.length(3);
37
+ expect(graph.shadowRoot.getElementById("metric-graph-desc").textContent.trim())
38
+ .to.equal("Three daily request totals");
39
+ });
40
+ });
@@ -0,0 +1,41 @@
1
+ import {expect} from "chai";
2
+ import {initJSDOM} from "../../../util/jsdom.mjs";
3
+
4
+ describe("Metric", () => {
5
+ let Metric;
6
+
7
+ before(async () => {
8
+ await initJSDOM();
9
+ ({Metric} = await import("../../../../source/components/data/metric.mjs"));
10
+ });
11
+
12
+ afterEach(() => document.getElementById("mocks").replaceChildren());
13
+
14
+ it("initializes its documented metric options from attributes", async () => {
15
+ const mocks = document.getElementById("mocks");
16
+ mocks.innerHTML = `
17
+ <monster-metric
18
+ data-monster-option-labels-title="Active subscriptions"
19
+ data-monster-option-labels-subtext="Previous hour"
20
+ data-monster-option-values-main="1,248"
21
+ data-monster-option-values-secondary="1,204"
22
+ data-monster-option-values-change="+3.7%"
23
+ data-monster-option-values-direction="225"
24
+ data-monster-option-classes-change="positive"
25
+ data-monster-option-aria-description="Subscriptions increased">
26
+ </monster-metric>
27
+ `;
28
+ const metric = mocks.firstElementChild;
29
+
30
+ await new Promise((resolve) => setTimeout(resolve, 50));
31
+
32
+ expect(metric).to.be.instanceOf(Metric);
33
+ expect(metric.getOption("values.main")).to.equal("1,248");
34
+ expect(metric.getOption("values.change")).to.equal("+3.7%");
35
+ expect(metric.getOption("classes.change")).to.equal("positive");
36
+ expect(metric.shadowRoot.getElementById("metric-value").textContent).to.equal("1,248");
37
+ const change = metric.shadowRoot.getElementById("metric-change-text");
38
+ expect(change.classList.contains("positive")).to.equal(true);
39
+ expect(change.style.getPropertyValue("--arrow-direction")).to.equal("225deg");
40
+ });
41
+ });
@@ -1388,6 +1388,32 @@ describe("DOM", function () {
1388
1388
  });
1389
1389
  });
1390
1390
 
1391
+ it("should replace SVG child markup issue #587", async function () {
1392
+ const mocks = document.getElementById("mocks");
1393
+ mocks.innerHTML = `
1394
+ <div id="svg-replace-root">
1395
+ <svg viewBox="0 0 100 30">
1396
+ <g id="svg-replace-target" data-monster-replace="path:markup"></g>
1397
+ </svg>
1398
+ </div>
1399
+ `;
1400
+ const updater = new Updater(
1401
+ document.getElementById("svg-replace-root"),
1402
+ { markup: '<rect x="1" y="2" width="3" height="4"></rect>' },
1403
+ );
1404
+
1405
+ await updater.run();
1406
+
1407
+ const target = document.getElementById("svg-replace-target");
1408
+ expect(target.querySelector("rect")?.getAttribute("width")).to.equal("3");
1409
+
1410
+ updater.getSubject().markup = '<circle cx="5" cy="6" r="2"></circle>';
1411
+ await new Promise((resolve) => setTimeout(resolve, 0));
1412
+
1413
+ expect(target.querySelector("rect")).to.equal(null);
1414
+ expect(target.querySelector("circle")?.getAttribute("r")).to.equal("2");
1415
+ });
1416
+
1391
1417
  it("should dispose linked updaters when replacing a mounted stateful subtree", function (done) {
1392
1418
  let mocks = document.getElementById("mocks");
1393
1419
  mocks.innerHTML = htmlStatefulReplace;