@schukai/monster 4.11.1 → 4.13.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.
@@ -42,9 +42,9 @@ export const metricGraphControlElementSymbol = Symbol(
42
42
  /**
43
43
  * A MetricGraph
44
44
  *
45
- * @fragments /fragments/data/metric-graph/
45
+ * @fragments /fragments/components/data/metric-graph/
46
46
  *
47
- * @example /examples/data/metric-graph-simple
47
+ * @example /examples/components/data/metric-graph-simple
48
48
  *
49
49
  * @since 4.11.0
50
50
  * @copyright schukai GmbH
@@ -60,7 +60,7 @@ class MetricGraph extends CustomElement {
60
60
  }
61
61
 
62
62
  /**
63
- * @return {Components.Data.Metric
63
+ * @return {MetricGraph}
64
64
  */
65
65
  [assembleMethodSymbol]() {
66
66
  super[assembleMethodSymbol]();
@@ -56,7 +56,7 @@ class Metric extends CustomElement {
56
56
  }
57
57
 
58
58
  /**
59
- * @return {Components.Data.Metric
59
+ * @return {Metric}
60
60
  */
61
61
  [assembleMethodSymbol]() {
62
62
  super[assembleMethodSymbol]();
@@ -71,6 +71,7 @@ import "../form/context-error.mjs";
71
71
  import "../form/message-state-button.mjs";
72
72
 
73
73
  import { getLocaleOfDocument } from "../../dom/locale.mjs";
74
+ import { normalizeNumber } from "../../i18n/util.mjs";
74
75
 
75
76
  export { Filter };
76
77
 
@@ -1259,6 +1260,27 @@ function collectSearchQueries() {
1259
1260
  // return query as url encoded
1260
1261
  return encodeURIComponent(query);
1261
1262
  },
1263
+ "to-int-2": (value, key) => {
1264
+ const query = normalizeNumber(value);
1265
+ if (Number.isNaN(query)) {
1266
+ return key + " IS NULL";
1267
+ }
1268
+ return key + "=" + encodeURIComponent(Math.round(query * 100));
1269
+ },
1270
+ "to-int-3": (value, key) => {
1271
+ const query = normalizeNumber(value);
1272
+ if (Number.isNaN(query)) {
1273
+ return "";
1274
+ }
1275
+ return key + "=" + encodeURIComponent(Math.round(query * 1000));
1276
+ },
1277
+ "to-int-4": (value, key) => {
1278
+ const query = normalizeNumber(value);
1279
+ if (Number.isNaN(query)) {
1280
+ return "";
1281
+ }
1282
+ return key + "=" + encodeURIComponent(Math.round(query * 10000));
1283
+ },
1262
1284
  },
1263
1285
  });
1264
1286
 
@@ -35,19 +35,20 @@ export { MessageStateButton };
35
35
  const buttonElementSymbol = Symbol("buttonElement");
36
36
 
37
37
  /**
38
- * A select control that can be used to select one or more options from a list.
38
+ * A specialized button component that combines state management with message display capabilities.
39
+ * It extends the Popper component to show messages in a popup overlay and can be used for form submissions
40
+ * or manual actions.
39
41
  *
40
42
  * @fragments /fragments/components/form/message-state-button/
41
- *
42
43
  * @example /examples/components/form/message-state-button-simple
43
44
  *
44
45
  * @since 2.11.0
45
46
  * @copyright schukai GmbH
46
- * @summary A beautiful select control that can make your life easier and also looks good.
47
- * @fires monster-options-set
48
- * @fires monster-selected
49
- * @fires monster-change
50
- * @fires monster-changed
47
+ * @summary Button component with integrated message display and state management
48
+ * @fires monster-state-changed - Fired when button state changes
49
+ * @fires monster-message-shown - Fired when message is displayed
50
+ * @fires monster-message-hidden - Fired when message is hidden
51
+ * @fires monster-click - Fired when button is clicked
51
52
  */
52
53
  class MessageStateButton extends Popper {
53
54
  /**
@@ -61,12 +62,12 @@ class MessageStateButton extends Popper {
61
62
  }
62
63
 
63
64
  /**
65
+ * Sets the state of the button which affects its visual appearance
64
66
  *
65
- * @param {string} state
66
- * @param {number} timeout
67
- * @return {MessageStateButton}
68
- * @throws {TypeError} value is not a string
69
- * @throws {TypeError} value is not an instance
67
+ * @param {string} state - The state to set (e.g. 'success', 'error', 'loading')
68
+ * @param {number} timeout - Optional timeout in milliseconds after which state is removed
69
+ * @return {MessageStateButton} Returns the button instance for chaining
70
+ * @throws {TypeError} When state is not a string or timeout is not a number
70
71
  */
71
72
  setState(state, timeout) {
72
73
  return this[buttonElementSymbol].setState(state, timeout);
@@ -164,12 +165,13 @@ class MessageStateButton extends Popper {
164
165
  }
165
166
 
166
167
  /**
167
- * Sets the message
168
+ * Sets the message content to be displayed in the popup overlay
168
169
  *
169
- * @param {string|HTMLElement}message
170
- * @param {string} title
171
- * @param {string} icon
172
- * @return {MessageStateButton}
170
+ * @param {string|HTMLElement} message - The message content as string or HTML element
171
+ * @param {string} title - Optional title to show above the message
172
+ * @param {string} icon - Optional icon HTML to display next to the title
173
+ * @return {MessageStateButton} Returns the button instance for chaining
174
+ * @throws {TypeError} When message is empty or invalid type
173
175
  */
174
176
  setMessage(message, title, icon) {
175
177
  if (isString(message)) {
@@ -230,10 +232,10 @@ class MessageStateButton extends Popper {
230
232
  }
231
233
 
232
234
  /**
233
- * With this method you can show the popper with timeout feature.
235
+ * Shows the message popup overlay with optional auto-hide timeout
234
236
  *
235
- * @param {number} timeout
236
- * @return {MessageStateButton}
237
+ * @param {number} timeout - Optional time in milliseconds after which the message will auto-hide
238
+ * @return {MessageStateButton} Returns the button instance for chaining
237
239
  */
238
240
  showMessage(timeout) {
239
241
  this.showDialog.call(this);
@@ -248,7 +250,7 @@ class MessageStateButton extends Popper {
248
250
  }
249
251
 
250
252
  /**
251
- * With this method you can show the popper.
253
+ * With this method, you can show the popper.
252
254
  *
253
255
  * @return {MessageStateButton}
254
256
  */
@@ -306,10 +308,12 @@ class MessageStateButton extends Popper {
306
308
  }
307
309
 
308
310
  /**
309
- * The Button.click() method simulates a click on the internal button element.
311
+ * Programmatically triggers a click event on the button
312
+ * Will not trigger if button is disabled
310
313
  *
311
314
  * @since 3.27.0
312
315
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click}
316
+ * @fires monster-click
313
317
  */
314
318
  click() {
315
319
  if (this.getOption("disabled") === true) {
@@ -34,53 +34,60 @@ import { isArray } from "../../types/is.mjs";
34
34
  export { Popper };
35
35
 
36
36
  /**
37
+ * Symbol for timer callback reference
37
38
  * @private
38
39
  * @type {symbol}
39
40
  */
40
41
  const timerCallbackSymbol = Symbol("timerCallback");
41
42
 
42
43
  /**
43
- * local symbol
44
+ * Symbol for resize observer reference
44
45
  * @private
45
46
  * @type {symbol}
46
47
  */
47
48
  const resizeObserverSymbol = Symbol("resizeObserver");
48
49
 
49
50
  /**
50
- * local symbol
51
+ * Symbol for close event handler reference
51
52
  * @private
52
53
  * @type {symbol}
53
54
  */
54
55
  const closeEventHandler = Symbol("closeEventHandler");
55
56
 
56
57
  /**
58
+ * Symbol for control element reference
57
59
  * @private
58
60
  * @type {symbol}
59
61
  */
60
62
  const controlElementSymbol = Symbol("controlElement");
61
63
 
62
64
  /**
65
+ * Symbol for button element reference
63
66
  * @private
64
67
  * @type {symbol}
65
68
  */
66
69
  const buttonElementSymbol = Symbol("buttonElement");
67
70
 
68
71
  /**
69
- * local symbol
72
+ * Symbol for popper element reference
70
73
  * @private
71
74
  * @type {symbol}
72
75
  */
73
76
  const popperElementSymbol = Symbol("popperElement");
74
77
 
75
78
  /**
76
- * local symbol
79
+ * Symbol for arrow element reference
77
80
  * @private
78
81
  * @type {symbol}
79
82
  */
80
83
  const arrowElementSymbol = Symbol("arrowElement");
81
84
 
82
85
  /**
83
- * A Popper is a floating UI element that can be shown or hidden.
86
+ * Popper component for displaying floating UI elements
87
+ *
88
+ * The Popper class creates a floating overlay element that can be shown/hidden
89
+ * and positioned relative to a trigger element. It supports different interaction
90
+ * modes like click, hover, focus etc.
84
91
  *
85
92
  * @fragments /fragments/components/layout/popper/
86
93
  *
@@ -89,36 +96,34 @@ const arrowElementSymbol = Symbol("arrowElement");
89
96
  *
90
97
  * @since 1.65.0
91
98
  * @copyright schukai GmbH
92
- * @summary A beautiful popper that can make your life easier and also looks good.
93
- * @fires monster-popper-hide fired when the popper is hide.
94
- * @fires monster-popper-hidden fired when the popper is hidden.
95
- * @fires monster-popper-open fired when the popper is open.
96
- * @fires monster-popper-opened fired when the popper is opened.
99
+ * @summary Floating overlay component with flexible positioning and interaction modes
100
+ * @fires monster-popper-hide - Fired when popper starts hiding
101
+ * @fires monster-popper-hidden - Fired when popper is fully hidden
102
+ * @fires monster-popper-open - Fired when popper starts opening
103
+ * @fires monster-popper-opened - Fired when popper is fully opened
97
104
  */
98
105
  class Popper extends CustomElement {
99
106
  /**
100
- * This method is called by the `instanceof` operator.
101
- * @return {symbol}
107
+ * Gets the instance symbol for type checking
108
+ * @return {symbol} The instance type symbol
102
109
  */
103
110
  static get [instanceSymbol]() {
104
111
  return Symbol.for("@schukai/monster/components/layout/popper@@instance");
105
112
  }
106
113
 
107
114
  /**
108
- * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
109
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
110
- *
111
- * The individual configuration values can be found in the table.
115
+ * Default configuration options for the popper
112
116
  *
113
- * @property {Object} templates The templates for the control.
114
- * @property {string} templates.main The main template.
115
- * @property {string} mode The mode of the popper. Possible values are `click`, `enter`, `manual`, `focus`, "auto" or a combination of them.
116
- * @property {string} content The content of the popper.
117
- * @property {object} popper The popper options.
118
- * @property {string} popper.placement The placement of the popper. Possible values are `top`, `bottom`, `left` and `right`.
119
- * @property {function[]} popper.middleware The middleware functions of the popper.
120
- * @property {Object} features The features of the popper.
121
- * @property {boolean} features.preventOpenEventSent Prevents the open event from being sent.
117
+ * @property {Object} templates - Template configuration
118
+ * @property {string} templates.main - Main template HTML
119
+ * @property {string} mode - Interaction mode(s): click|enter|manual|focus|auto
120
+ * @property {string} content - Content template
121
+ * @property {Object} popper - Positioning options
122
+ * @property {string} popper.placement - Placement: top|bottom|left|right
123
+ * @property {Array} popper.middleware - Positioning middleware functions
124
+ * @property {Object} features - Feature flags
125
+ * @property {boolean} features.preventOpenEventSent - Prevent open event
126
+ * @returns {Object} Default options merged with parent defaults
122
127
  */
123
128
  get defaults() {
124
129
  return Object.assign({}, super.defaults, {
@@ -138,9 +143,9 @@ class Popper extends CustomElement {
138
143
  }
139
144
 
140
145
  /**
141
- * This method is called by the `connectedCallback` method on the first call.
142
- *
143
- * @return {void}
146
+ * Initialize the component
147
+ * Called on first connection to DOM
148
+ * @private
144
149
  */
145
150
  [assembleMethodSymbol]() {
146
151
  super[assembleMethodSymbol]();
@@ -149,27 +154,24 @@ class Popper extends CustomElement {
149
154
  }
150
155
 
151
156
  /**
152
- * This method returns the tag name of the element.
153
- *
154
- * @return {string}
157
+ * Gets the custom element tag name
158
+ * @return {string} The tag name
155
159
  */
156
160
  static getTag() {
157
161
  return "monster-popper";
158
162
  }
159
163
 
160
164
  /**
161
- * This method returns the css styles of the element.
162
- *
163
- * @return {CSSStyleSheet[]}
165
+ * Gets component stylesheets
166
+ * @return {CSSStyleSheet[]} Array of stylesheets
164
167
  */
165
168
  static getCSSStyleSheet() {
166
169
  return [PopperStyleSheet];
167
170
  }
168
171
 
169
172
  /**
170
- * This method is called when the element is connected to the dom.
171
- *
172
- * @return {void}
173
+ * Lifecycle callback when element connects to DOM
174
+ * Sets up event listeners and initializes popper
173
175
  */
174
176
  connectedCallback() {
175
177
  super.connectedCallback();
@@ -177,7 +179,6 @@ class Popper extends CustomElement {
177
179
  const document = getDocument();
178
180
 
179
181
  for (const [, type] of Object.entries(["click", "touch"])) {
180
- // close on outside ui-events
181
182
  document.addEventListener(type, this[closeEventHandler]);
182
183
  }
183
184
 
@@ -186,14 +187,12 @@ class Popper extends CustomElement {
186
187
  }
187
188
 
188
189
  /**
189
- * This method is called when the element is disconnected from the dom.
190
- *
191
- * @return {void}
190
+ * Lifecycle callback when element disconnects from DOM
191
+ * Cleans up event listeners and observers
192
192
  */
193
193
  disconnectedCallback() {
194
194
  super.disconnectedCallback();
195
195
 
196
- // close on outside ui-events
197
196
  for (const [, type] of Object.entries(["click", "touch"])) {
198
197
  document.removeEventListener(type, this[closeEventHandler]);
199
198
  }
@@ -202,9 +201,8 @@ class Popper extends CustomElement {
202
201
  }
203
202
 
204
203
  /**
205
- * With this method, you can show the popper.
206
- *
207
- * @return {Popper}
204
+ * Shows the popper element
205
+ * @return {Popper} The popper instance
208
206
  */
209
207
  showDialog() {
210
208
  show.call(this);
@@ -212,9 +210,8 @@ class Popper extends CustomElement {
212
210
  }
213
211
 
214
212
  /**
215
- * With this method you can hide the popper.
216
- *
217
- * @return {Popper}
213
+ * Hides the popper element
214
+ * @return {Popper} The popper instance
218
215
  */
219
216
  hideDialog() {
220
217
  hide.call(this);
@@ -222,9 +219,8 @@ class Popper extends CustomElement {
222
219
  }
223
220
 
224
221
  /**
225
- * With this method you can toggle the popper.
226
- *
227
- * @return {Popper}
222
+ * Toggles popper visibility
223
+ * @return {Popper} The popper instance
228
224
  */
229
225
  toggleDialog() {
230
226
  if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
@@ -237,8 +233,9 @@ class Popper extends CustomElement {
237
233
  }
238
234
 
239
235
  /**
236
+ * Initializes event handlers for popper interactivity
240
237
  * @private
241
- * @return {Popper}
238
+ * @return {Popper} The popper instance
242
239
  */
243
240
  function initEventHandler() {
244
241
  this[closeEventHandler] = (event) => {
@@ -276,10 +273,11 @@ function initEventHandler() {
276
273
  }
277
274
 
278
275
  /**
276
+ * Sets up event handlers for specific interaction mode
279
277
  * @private
280
- * @param mode
281
- * @return {Popper}
282
- * @throws Error
278
+ * @param {string} mode - Interaction mode to initialize
279
+ * @return {Popper} The popper instance
280
+ * @throws {Error} For unknown modes
283
281
  */
284
282
  function initEventHandlerByMode(mode) {
285
283
  switch (mode) {
@@ -338,10 +336,10 @@ function initEventHandlerByMode(mode) {
338
336
  }
339
337
 
340
338
  /**
339
+ * Sets up resize observer for popper repositioning
341
340
  * @private
342
341
  */
343
342
  function attachResizeObserver() {
344
- // against flickering
345
343
  this[resizeObserverSymbol] = new ResizeObserver((entries) => {
346
344
  if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
347
345
  try {
@@ -369,6 +367,10 @@ function attachResizeObserver() {
369
367
  });
370
368
  }
371
369
 
370
+ /**
371
+ * Disconnects resize observer
372
+ * @private
373
+ */
372
374
  function disconnectResizeObserver() {
373
375
  if (this[resizeObserverSymbol] instanceof ResizeObserver) {
374
376
  this[resizeObserverSymbol].disconnect();
@@ -376,6 +378,7 @@ function disconnectResizeObserver() {
376
378
  }
377
379
 
378
380
  /**
381
+ * Hides the popper element
379
382
  * @private
380
383
  */
381
384
  function hide() {
@@ -396,6 +399,7 @@ function hide() {
396
399
  }
397
400
 
398
401
  /**
402
+ * Shows the popper element
399
403
  * @private
400
404
  */
401
405
  function show() {
@@ -427,6 +431,7 @@ function show() {
427
431
  }
428
432
 
429
433
  /**
434
+ * Updates popper positioning
430
435
  * @private
431
436
  */
432
437
  function updatePopper() {
@@ -447,8 +452,9 @@ function updatePopper() {
447
452
  }
448
453
 
449
454
  /**
455
+ * Initializes references to DOM elements
450
456
  * @private
451
- * @return {Popper}
457
+ * @return {Popper} The popper instance
452
458
  */
453
459
  function initControlReferences() {
454
460
  this[controlElementSymbol] = this.shadowRoot.querySelector(
@@ -467,22 +473,23 @@ function initControlReferences() {
467
473
  }
468
474
 
469
475
  /**
476
+ * Gets the main template HTML
470
477
  * @private
471
- * @return {string}
478
+ * @return {string} Template HTML
472
479
  */
473
480
  function getTemplate() {
474
481
  // language=HTML
475
482
  return `
476
- <div data-monster-role="control" part="control">
477
- <slot name="button" data-monster-role="button"></slot>
478
-
479
- <div data-monster-role="popper" part="popper" tabindex="-1" class="monster-color-primary-1">
480
- <div data-monster-role="arrow"></div>
481
- <div part="content" class="flex" data-monster-replace="path:content">
482
- </div>
483
- </div>
484
- </div>
485
- `;
483
+ <div data-monster-role="control" part="control">
484
+ <slot name="button" data-monster-role="button"></slot>
485
+
486
+ <div data-monster-role="popper" part="popper" tabindex="-1" class="monster-color-primary-1">
487
+ <div data-monster-role="arrow"></div>
488
+ <div part="content" class="flex" data-monster-replace="path:content">
489
+ </div>
490
+ </div>
491
+ </div>
492
+ `;
486
493
  }
487
494
 
488
495
  registerCustomElement(Popper);
@@ -197,9 +197,22 @@ class CustomElement extends HTMLElement {
197
197
  super();
198
198
 
199
199
  this[attributeObserverSymbol] = {};
200
- this[internalSymbol] = new ProxyObserver({
201
- options: initOptionsFromAttributes(this, extend({}, this.defaults)),
202
- });
200
+
201
+ const options = initOptionsFromAttributes(this, extend({}, this.defaults));
202
+ if (!isObject(options)) {
203
+ throw new Error(
204
+ `The options are not defined correctly in the ${this.getTag()} element.`,
205
+ );
206
+ }
207
+
208
+ if (this.customization instanceof Map) {
209
+ const pathfinder = new Pathfinder(options);
210
+ this.customization.forEach((value, key) => {
211
+ pathfinder.setVia(key, value);
212
+ });
213
+ }
214
+
215
+ this[internalSymbol] = new ProxyObserver({ options });
203
216
  this[initMethodSymbol]();
204
217
  initOptionObserver.call(this);
205
218
  this[scriptHostElementSymbol] = [];
@@ -249,6 +262,17 @@ class CustomElement extends HTMLElement {
249
262
  return this;
250
263
  }
251
264
 
265
+ /**
266
+ * The `customization` property allows overwriting the defaults.
267
+ * Unlike the defaults that expect an object, the customization is a Map.
268
+ * This also allows overwriting individual values in a deeper structure
269
+ * without having to redefine the entire structure and thus changing the defaults.
270
+ * @returns {Map}
271
+ */
272
+ get customization() {
273
+ return new Map();
274
+ }
275
+
252
276
  /**
253
277
  * The `defaults` property defines the default values for a control. If you want to override these,
254
278
  * you can use various methods, which are described in the documentation available at
@@ -14,6 +14,47 @@
14
14
 
15
15
  import { languages } from "./map/languages.mjs";
16
16
 
17
+ /**
18
+ * Normalizes a number represented as a string by converting it into a valid floating-point number format,
19
+ * based on the provided or detected locale. It accounts for different decimal and a thousand separator conventions.
20
+ *
21
+ * @param {string} input - The string representation of the number to normalize. May include locale-specific formatting.
22
+ * @param {string} [locale=navigator.language] - The locale used to determine the decimal separator convention. Defaults to the user's browser locale.
23
+ * @return {number} The normalized number as a floating-point value. Returns NaN if the input is not a parsable number.
24
+ */
25
+ export function normalizeNumber(input, locale = navigator.language) {
26
+ if (input === null || input === undefined) return NaN;
27
+ if (typeof input === "number") return input; // If input is already a number, return it directly
28
+ if (typeof input !== "string") return NaN;
29
+
30
+ const cleaned = input.trim().replace(/\s/g, "");
31
+ const decimalSeparator = getDecimalSeparator(locale);
32
+ let normalized = cleaned;
33
+
34
+ if (decimalSeparator === "," && cleaned.includes(".")) {
35
+ normalized = cleaned.replace(/\./g, "").replace(",", ".");
36
+ } else if (decimalSeparator === "." && cleaned.includes(",")) {
37
+ normalized = cleaned.replace(/,/g, "").replace(".", ".");
38
+ } else if (decimalSeparator === "," && cleaned.includes(",")) {
39
+ normalized = cleaned.replace(",", ".");
40
+ }
41
+
42
+ const result = parseFloat(normalized);
43
+ return Number.isNaN(result) ? NaN : result;
44
+ }
45
+
46
+ /**
47
+ * Retrieves the decimal separator for a given locale.
48
+ *
49
+ * @param {string} [locale=navigator.language] - The locale identifier to determine the decimal separator. Defaults to the user's current locale if not provided.
50
+ * @return {string} The decimal separator used in the specified locale.
51
+ */
52
+ function getDecimalSeparator(locale = navigator.language) {
53
+ const numberWithDecimal = 1.1;
54
+ const formatted = new Intl.NumberFormat(locale).format(numberWithDecimal);
55
+ return formatted.replace(/\d/g, "")[0]; // z.B. "," oder "."
56
+ }
57
+
17
58
  /**
18
59
  * Determines the user's preferred language based on browser settings and available language options.
19
60
  *