@schukai/monster 3.117.3 → 3.118.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
 
4
4
 
5
+ ## [3.118.1] - 2025-05-02
6
+
7
+ ### Bug Fixes
8
+
9
+ - remove strange chars from toggle
10
+ ### Changes
11
+
12
+ - update import
13
+
14
+
15
+
16
+ ## [3.118.0] - 2025-05-01
17
+
18
+ ### Add Features
19
+
20
+ - new funtion accessibleColor(color); [#308](https://gitlab.schukai.com/oss/libraries/javascript/monster/issues/308)
21
+
22
+
23
+
5
24
  ## [3.117.3] - 2025-04-30
6
25
 
7
26
  ### Bug Fixes
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.6.13","@popperjs/core":"^2.11.8","buffer":"^6.0.3"},"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":"3.117.3"}
1
+ {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.6.13","@popperjs/core":"^2.11.8","buffer":"^6.0.3"},"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":"3.118.1"}
@@ -16,7 +16,7 @@ import { instanceSymbol } from "../../constants.mjs";
16
16
  import { registerCustomElement } from "../../dom/customelement.mjs";
17
17
  import { CustomControl } from "../../dom/customcontrol.mjs";
18
18
  import { ContextHelpStyleSheet } from "./stylesheet/context-help.mjs";
19
- import { Popper } from "./popper.mjs";
19
+ import { Popper } from "../layout/popper.mjs";
20
20
 
21
21
  export { ContextHelp };
22
22
 
@@ -104,7 +104,7 @@ class ToggleSwitch extends CustomControl {
104
104
  off: "off",
105
105
  },
106
106
  labels: {
107
- toggleSwitchOn: "✔��",
107
+ toggleSwitchOn: "",
108
108
  toggleSwitchOff: "×",
109
109
  },
110
110
  templates: {
@@ -237,7 +237,7 @@ function disconnectResizeObserver() {
237
237
 
238
238
  /**
239
239
  * @private
240
- * @return {Select}
240
+ * @return {Panel}
241
241
  * @throws {Error} no shadow-root is defined
242
242
  */
243
243
  function initControlReferences() {
@@ -625,9 +625,12 @@ function show(element) {
625
625
  if (id === reference) {
626
626
  node.classList.add("active");
627
627
 
628
- const openDelay = parseInt(this.getOption("features.openDelay"), 10);
628
+ const openDelay = Number.parseInt(
629
+ this.getOption("features.openDelay"),
630
+ 10,
631
+ );
629
632
 
630
- if (!isNaN(openDelay) && openDelay > 0) {
633
+ if (!Number.isNaN(openDelay) && openDelay > 0) {
631
634
  node.style.visibility = "hidden";
632
635
 
633
636
  setTimeout(() => {
@@ -156,7 +156,7 @@ function getMonsterVersion() {
156
156
  }
157
157
 
158
158
  /** don't touch, replaced by make with package.json version */
159
- monsterVersion = new Version("3.117.2");
159
+ monsterVersion = new Version("3.117.3");
160
160
 
161
161
  return monsterVersion;
162
162
  }
@@ -0,0 +1,304 @@
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
+ * SPDX-License-Identifier: AGPL-3.0
13
+ */
14
+
15
+ /**
16
+ * accessibleColor
17
+ *
18
+ * `accessibleColor(baseColor [, minContrast])` returns a second
19
+ * color that meets the given WCAG contrast-ratio against
20
+ * `baseColor`.
21
+ *
22
+ * • `baseColor` String – 3- or 6-digit HEX with or without “#”.
23
+ * • `minContrast` Number – target ratio (default **4.5** for WCAG-AA).
24
+ * • Return value String – always a 6-digit HEX “#rrggbb”.
25
+ *
26
+ * HOW IT WORKS
27
+ * ------------
28
+ * 1. Fast path: tries pure **white** and **black**.
29
+ * 2. If both fail, keeps the original hue/saturation and shifts
30
+ * lightness in 5 % steps until the ratio is reached.
31
+ * 3. Last resort: picks the better of white / black if neither
32
+ * reaches the exact ratio after 100 %.
33
+ *
34
+ * TYPICAL USE-CASES
35
+ * -----------------
36
+ * • **Dynamic theming**: users pick a brand color → generate safe
37
+ * text/background automatically.
38
+ * • **Badges / chips**: text on colored pills that must stay legible.
39
+ * • **Charts**: ensure labels on bars/slices have enough contrast.
40
+ * • **Map markers**: pick a halo color that keeps icons readable.
41
+ * • **Data tables**: alternating row colors that adapt to theme.
42
+ *
43
+ * BASIC EXAMPLES
44
+ * --------------
45
+ * // Default AA (4.5 : 1)
46
+ * const badgeFg = '#007bff'; // brand blue
47
+ * const badgeBg = accessibleColor(badgeFg); // → '#ffffff'
48
+ *
49
+ * // Pick a background for an arbitrary text color
50
+ * const text = '#e52929';
51
+ * const bg = accessibleColor(text); // may return '#ffffff'
52
+ *
53
+ * // Request AAA (7 : 1) for small text
54
+ * const hiVis = accessibleColor('#888888', 7); // → '#000000'
55
+ *
56
+ * EXTENDED EXAMPLES
57
+ * -----------------
58
+ * // 1) Generate theme object
59
+ * const base = '#1abc9c';
60
+ * const theme = {
61
+ * primary: base,
62
+ * onPrimary: accessibleColor(base), // safe text color
63
+ * border: accessibleColor(base, 3), // borders need only 3 : 1
64
+ * };
65
+ *
66
+ * // 2) Build badge component
67
+ * function Badge({ color, children }) {
68
+ * const background = color;
69
+ * const foreground = accessibleColor(color); // text or icon
70
+ * return `<span style="
71
+ * background:${background};
72
+ * color:${foreground};
73
+ * padding:0.25em 0.5em;
74
+ * border-radius:0.25rem;">${children}</span>`;
75
+ * }
76
+ *
77
+ * // 3) Ensure chart label contrast
78
+ * const segmentColor = '#ffcc00';
79
+ * const labelColor = accessibleColor(segmentColor, 3); // 3 : 1 OK for large labels
80
+ *
81
+ * NOTES & TIPS
82
+ * ------------
83
+ * • `minContrast = 3` is allowed for:
84
+ * – Large / bold text (≥ 24 px normal or ≥ 19 px bold).
85
+ * – Non-text UI parts (icons, borders).
86
+ * • The algorithm preserves hue & saturation whenever possible,
87
+ * so resulting colors match the original palette.
88
+ * • Performance: pure JS, no DOM required – safe in workers.
89
+ * • Edge-case: middle-gray “#777777” – function will return black
90
+ * because white misses 4.5 : 1 by a hair (~4.3 : 1).
91
+ *
92
+ * @param color
93
+ * @param minContrast
94
+ * @returns {string|string}
95
+ * @summary This function generates a color that meets the WCAG contrast ratio requirements against a given base color.
96
+ */
97
+ export function accessibleColor(color, minContrast = 4.5) {
98
+ const baseColor = toHex(color);
99
+ const baseHex = normalizeHex(baseColor);
100
+
101
+ const white = "#ffffff";
102
+ const black = "#000000";
103
+
104
+ const baseLum = luminance(baseHex);
105
+
106
+ // 1) Quick check: try pure white or pure black first
107
+ if (contrastRatio(baseLum, luminance(white)) >= minContrast) return white;
108
+ if (contrastRatio(baseLum, luminance(black)) >= minContrast) return black;
109
+
110
+ // 2) Fallback: keep hue/saturation but shift lightness until contrast is OK
111
+ let { h, s, l } = hexToHsl(baseHex);
112
+ const direction = l < 0.5 ? +1 : -1; // brighten or darken?
113
+
114
+ for (let i = 1; i <= 20; i++) {
115
+ // max 20 × 5 % = 100 %
116
+ l = clamp(l + direction * 0.05, 0, 1);
117
+ const candidate = hslToHex({ h, s, l });
118
+ if (contrastRatio(luminance(candidate), baseLum) >= minContrast) {
119
+ return candidate;
120
+ }
121
+ }
122
+
123
+ // 3) Last resort: pick the better of (almost-good) white/black
124
+ return contrastRatio(baseLum, luminance(white)) >
125
+ contrastRatio(baseLum, luminance(black))
126
+ ? white
127
+ : black;
128
+ }
129
+
130
+ /**
131
+ * Normalizes a given hexadecimal color code to ensure it is in the standard 6-character and lowercase format, prefixed with a '#'.
132
+ *
133
+ * @param {string} hex - The hexadecimal color code, which can be in 3-character or 6-character format, with or without the leading '#'.
134
+ * @return {string} - The normalized 6-character hexadecimal color code in lowercase, prefixed with a '#'.
135
+ */
136
+ function normalizeHex(hex) {
137
+ hex = hex.toString().replace(/^#/, "");
138
+ if (hex.length === 3)
139
+ hex = hex
140
+ .split("")
141
+ .map((c) => c + c)
142
+ .join("");
143
+ return "#" + hex.toLowerCase();
144
+ }
145
+
146
+ /**
147
+ * Converts a hexadecimal color code to an RGB object.
148
+ *
149
+ * @param {string} hex - A hexadecimal color string (e.g., "#ff0000" or "ff0000").
150
+ * @return {Object} An object representing the RGB color with properties:
151
+ * - r: The red component (0-255).
152
+ * - g: The green component (0-255).
153
+ * - b: The blue component (0-255).
154
+ */
155
+ function hexToRgb(hex) {
156
+ const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
157
+ return {
158
+ r: parseInt(m[1], 16),
159
+ g: parseInt(m[2], 16),
160
+ b: parseInt(m[3], 16),
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Calculates the relative luminance of a color based on its hexadecimal representation.
166
+ * The luminance is calculated using the standard formula for relative luminance
167
+ * in the sRGB color space.
168
+ *
169
+ * @param {string} hex - The hexadecimal color code (e.g., "#FFFFFF" or "#000000").
170
+ * @return {number} The relative luminance value, ranging from 0 (darkest) to 1 (lightest).
171
+ */
172
+ function luminance(hex) {
173
+ const { r, g, b } = hexToRgb(hex);
174
+ const srgb = [r, g, b]
175
+ .map((v) => v / 255)
176
+ .map((v) =>
177
+ v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4),
178
+ );
179
+ return 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2];
180
+ }
181
+
182
+ /**
183
+ * Calculates the contrast ratio between two luminance values.
184
+ *
185
+ * The contrast ratio is determined using a formula defined by the Web Content Accessibility Guidelines (WCAG).
186
+ * This ratio helps evaluate the readability of text or visual elements against a background.
187
+ *
188
+ * @param {number} l1 - The relative luminance of the lighter element.
189
+ * @param {number} l2 - The relative luminance of the darker element.
190
+ * @returns {number} The contrast ratio, a value ranging from 1 to 21.
191
+ */
192
+ const contrastRatio = (l1, l2) =>
193
+ (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
194
+
195
+ /**
196
+ * Converts RGB color values to HSL color space.
197
+ *
198
+ * @param {Object} rgb - The RGB color object.
199
+ * @param {number} rgb.r - The red component (0-255).
200
+ * @param {number} rgb.g - The green component (0-255).
201
+ * @param {number} rgb.b - The blue component (0-255).
202
+ * @return {Object} An object with the HSL values.
203
+ * @return {number} return.h - The hue component (0-1).
204
+ * @return {number} return.s - The saturation component (0-1).
205
+ * @return {number} return.l - The lightness component (0-1).
206
+ */
207
+ function rgbToHsl({ r, g, b }) {
208
+ r /= 255;
209
+ g /= 255;
210
+ b /= 255;
211
+ const max = Math.max(r, g, b),
212
+ min = Math.min(r, g, b);
213
+ let h,
214
+ s,
215
+ l = (max + min) / 2;
216
+
217
+ if (max === min) {
218
+ h = s = 0;
219
+ } else {
220
+ const d = max - min;
221
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
222
+ switch (max) {
223
+ case r:
224
+ h = (g - b) / d + (g < b ? 6 : 0);
225
+ break;
226
+ case g:
227
+ h = (b - r) / d + 2;
228
+ break;
229
+ case b:
230
+ h = (r - g) / d + 4;
231
+ break;
232
+ }
233
+ h /= 6;
234
+ }
235
+ return { h, s, l };
236
+ }
237
+
238
+ /**
239
+ * Converts an HSL color value to its hexadecimal representation.
240
+ *
241
+ * @param {Object} hsl - An object representing the HSL color.
242
+ * @param {number} hsl.h - Hue, a number between 0 and 1.
243
+ * @param {number} hsl.s - Saturation, a number between 0 and 1.
244
+ * @param {number} hsl.l - Lightness, a number between 0 and 1.
245
+ * @return {string} The hexadecimal color string in the format "#RRGGBB".
246
+ */
247
+ function hslToHex({ h, s, l }) {
248
+ const hue2rgb = (p, q, t) => {
249
+ if (t < 0) t += 1;
250
+ if (t > 1) t -= 1;
251
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
252
+ if (t < 1 / 2) return q;
253
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
254
+ return p;
255
+ };
256
+ let r, g, b;
257
+ if (s === 0) {
258
+ r = g = b = l;
259
+ } // achromatic
260
+ else {
261
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
262
+ const p = 2 * l - q;
263
+ r = hue2rgb(p, q, h + 1 / 3);
264
+ g = hue2rgb(p, q, h);
265
+ b = hue2rgb(p, q, h - 1 / 3);
266
+ }
267
+ const toHex = (x) => ("0" + Math.round(x * 255).toString(16)).slice(-2);
268
+ return "#" + toHex(r) + toHex(g) + toHex(b);
269
+ }
270
+
271
+ // Keep number between 0 and 1
272
+ const clamp = (val, min, max) => Math.min(max, Math.max(min, val));
273
+
274
+ /**
275
+ * Converts any CSS color to full 6-digit HEX "#rrggbb".
276
+ * Falls back to throwing an Error if the input is not a valid color.
277
+ *
278
+ * Works in all browsers (DOM required). For Node.js use a library like
279
+ * `color` or `tinycolor2`, or adapt the parser accordingly.
280
+ */
281
+ function toHex(color) {
282
+ // Use the browser to parse the color for us
283
+ const temp = new Option().style; // <option> exists in all browsers
284
+ temp.color = color;
285
+
286
+ // Invalid → empty string
287
+ if (!temp.color) {
288
+ throw new Error(`"${color}" is not a valid CSS color`);
289
+ }
290
+
291
+ // temp.color is now normalized, e.g. "rgb(255, 0, 0)" or "rgba(0, 0, 0, 0.5)"
292
+ const m = temp.color.match(/\d+/g).map(Number); // [r, g, b, (a)]
293
+ const [r, g, b] = m;
294
+
295
+ // If alpha channel < 1 the color is translucent; composite onto white
296
+ const a = m[3] !== undefined ? m[3] / 255 || 0 : 1;
297
+ const toWhite = (c) => Math.round(a * c + (1 - a) * 255);
298
+
299
+ const rr = (a < 1 ? toWhite(r) : r).toString(16).padStart(2, "0");
300
+ const gg = (a < 1 ? toWhite(g) : g).toString(16).padStart(2, "0");
301
+ const bb = (a < 1 ? toWhite(b) : b).toString(16).padStart(2, "0");
302
+
303
+ return `#${rr}${gg}${bb}`;
304
+ }
@@ -7,7 +7,7 @@ describe('Monster', function () {
7
7
  let monsterVersion
8
8
 
9
9
  /** don´t touch, replaced by make with package.json version */
10
- monsterVersion = new Version("3.117.2")
10
+ monsterVersion = new Version("3.117.3")
11
11
 
12
12
  let m = getMonsterVersion();
13
13
 
@@ -9,8 +9,8 @@
9
9
  </head>
10
10
  <body>
11
11
  <div id="headline" style="display: flex;align-items: center;justify-content: center;flex-direction: column;">
12
- <h1 style='margin-bottom: 0.1em;'>Monster 3.117.2</h1>
13
- <div id="lastupdate" style='font-size:0.7em'>last update Mi 30. Apr 18:21:58 CEST 2025</div>
12
+ <h1 style='margin-bottom: 0.1em;'>Monster 3.117.3</h1>
13
+ <div id="lastupdate" style='font-size:0.7em'>last update Do 1. Mai 12:16:41 CEST 2025</div>
14
14
  </div>
15
15
  <div id="mocha-errors"
16
16
  style="color: red;font-weight: bold;display: flex;align-items: center;justify-content: center;flex-direction: column;margin:20px;"></div>