@schukai/monster 3.53.0 → 3.55.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +1 -1
  3. package/source/components/datatable/datasource/rest.mjs +358 -309
  4. package/source/components/datatable/datatable/header.mjs +8 -0
  5. package/source/components/datatable/datatable.mjs +606 -557
  6. package/source/components/datatable/embedded-pagination.mjs +50 -62
  7. package/source/components/datatable/filter/util.mjs +122 -0
  8. package/source/components/datatable/filter.mjs +893 -708
  9. package/source/components/datatable/pagination.mjs +335 -310
  10. package/source/components/datatable/status.mjs +248 -0
  11. package/source/components/datatable/style/datatable.pcss +1 -0
  12. package/source/components/datatable/style/embedded-pagination.pcss +59 -2
  13. package/source/components/datatable/style/filter.pcss +4 -0
  14. package/source/components/datatable/style/pagination.pcss +28 -4
  15. package/source/components/datatable/style/status.pcss +42 -0
  16. package/source/components/datatable/stylesheet/column-bar.mjs +1 -1
  17. package/source/components/datatable/stylesheet/datatable.mjs +1 -1
  18. package/source/components/datatable/stylesheet/filter-button.mjs +1 -1
  19. package/source/components/datatable/stylesheet/filter.mjs +1 -1
  20. package/source/components/datatable/stylesheet/pagination.mjs +1 -1
  21. package/source/components/datatable/stylesheet/status.mjs +27 -0
  22. package/source/components/form/action-button.mjs +1 -1
  23. package/source/components/form/api-button.mjs +1 -1
  24. package/source/components/form/button-bar.mjs +1 -1
  25. package/source/components/form/button.mjs +1 -1
  26. package/source/components/form/confirm-button.mjs +1 -1
  27. package/source/components/form/context-error.mjs +275 -0
  28. package/source/components/form/context-help.mjs +5 -5
  29. package/source/components/form/form.mjs +2 -2
  30. package/source/components/form/message-state-button.mjs +2 -2
  31. package/source/components/form/popper-button.mjs +7 -4
  32. package/source/components/form/popper.mjs +317 -309
  33. package/source/components/form/reload.mjs +1 -1
  34. package/source/components/form/select.mjs +9 -3
  35. package/source/components/form/shadow-reload.mjs +1 -1
  36. package/source/components/form/state-button.mjs +2 -1
  37. package/source/components/form/style/context-error.pcss +32 -0
  38. package/source/components/form/style/context-help.pcss +22 -5
  39. package/source/components/form/stylesheet/context-error.mjs +27 -0
  40. package/source/components/form/stylesheet/context-help.mjs +1 -1
  41. package/source/components/form/stylesheet/select.mjs +1 -1
  42. package/source/components/form/stylesheet/tabs.mjs +1 -1
  43. package/source/components/form/tabs.mjs +757 -707
  44. package/source/components/form/template.mjs +1 -1
  45. package/source/components/form/tree-select.mjs +1 -1
  46. package/source/components/host/collapse.mjs +22 -5
  47. package/source/components/host/config-manager.mjs +39 -2
  48. package/source/components/host/host.mjs +14 -0
  49. package/source/components/host/stylesheet/call-button.mjs +1 -1
  50. package/source/components/host/stylesheet/overlay.mjs +1 -1
  51. package/source/components/host/stylesheet/toggle-button.mjs +1 -1
  52. package/source/components/host/util.mjs +6 -1
  53. package/source/components/notify/stylesheet/message.mjs +1 -1
  54. package/source/components/stylesheet/icons.mjs +1 -1
  55. package/source/data/transformer.mjs +39 -42
  56. package/source/dom/customelement.mjs +1 -1
  57. package/source/dom/updater.mjs +700 -688
  58. package/source/dom/util.mjs +42 -0
  59. package/source/i18n/providers/embed.mjs +3 -3
  60. package/source/monster.mjs +6 -0
  61. package/source/text/formatter.mjs +2 -2
  62. package/source/types/observer.mjs +1 -1
  63. package/source/types/version.mjs +1 -1
  64. package/source/util/sleep.mjs +18 -0
  65. package/test/cases/components/form/button.mjs +2 -1
  66. package/test/cases/components/form/select.mjs +1 -1
  67. package/test/cases/components/form/tree-select.mjs +1 -1
  68. package/test/cases/data/transformer.mjs +2 -2
  69. package/test/cases/dom/updater.mjs +67 -46
  70. package/test/cases/monster.mjs +1 -1
  71. package/test/web/test.html +2 -2
  72. package/test/web/tests.js +18 -13
@@ -3,35 +3,41 @@
3
3
  * SPDX-License-Identifier: AGPL-3.0
4
4
  */
5
5
 
6
- import { instanceSymbol } from "../../constants.mjs";
7
- import { findElementWithIdUpwards } from "../../dom/util.mjs";
6
+ import {instanceSymbol} from "../../constants.mjs";
7
+ import {findTargetElementFromEvent} from "../../dom/events.mjs";
8
+ import {findElementWithIdUpwards, findElementWithSelectorUpwards} from "../../dom/util.mjs";
8
9
  import {
9
- assembleMethodSymbol,
10
- CustomElement,
11
- getSlottedElements,
12
- registerCustomElement,
10
+ assembleMethodSymbol,
11
+ CustomElement,
12
+ getSlottedElements,
13
+ registerCustomElement,
13
14
  } from "../../dom/customelement.mjs";
14
- import { ID } from "../../types/id.mjs";
15
- import { Settings } from "./filter/settings.mjs";
16
- import { FilterStyleSheet } from "./stylesheet/filter.mjs";
17
- import { getDocument, getWindow } from "../../dom/util.mjs";
18
- import { getGlobal } from "../../types/global.mjs";
19
- import { isInstance, isFunction, isObject, isArray } from "../../types/is.mjs";
20
- import { Host } from "../host/host.mjs";
21
- import { addAttributeToken } from "../../dom/attributes.mjs";
22
- import { ATTRIBUTE_ERRORMESSAGE } from "../../dom/constants.mjs";
15
+ import {ID} from "../../types/id.mjs";
16
+ import {Settings} from "./filter/settings.mjs";
17
+ import {FilterStyleSheet} from "./stylesheet/filter.mjs";
18
+ import {getDocument, getWindow} from "../../dom/util.mjs";
19
+ import {getGlobal} from "../../types/global.mjs";
20
+ import {isInstance, isFunction, isObject, isArray} from "../../types/is.mjs";
21
+ import {Host} from "../host/host.mjs";
22
+ import {addAttributeToken} from "../../dom/attributes.mjs";
23
+ import {ATTRIBUTE_ERRORMESSAGE} from "../../dom/constants.mjs";
23
24
  import "../form/message-state-button.mjs";
24
- import { Formatter } from "../../text/formatter.mjs";
25
- import { generateRangeComparisonExpression } from "../../text/util.mjs";
26
- import { generateUniqueConfigKey } from "../host/util.mjs";
25
+ import {Formatter} from "../../text/formatter.mjs";
26
+ import {generateRangeComparisonExpression} from "../../text/util.mjs";
27
+
27
28
  import {
28
- parseBracketedKeyValueHash,
29
- createBracketedKeyValueHash,
29
+ parseBracketedKeyValueHash,
30
+ createBracketedKeyValueHash,
30
31
  } from "../../text/bracketed-key-value-hash.mjs";
32
+ import {ThemeStyleSheet} from "../stylesheet/theme.mjs";
33
+ import {SpaceStyleSheet} from "../stylesheet/space.mjs";
34
+ import {FormStyleSheet} from "../stylesheet/form.mjs";
35
+
36
+ import {getStoredFilterConfigKey, getFilterConfigKey, parseDateInput} from "./filter/util.mjs";
31
37
 
32
38
  import "./filter/select.mjs";
33
39
 
34
- export { Filter };
40
+ export {Filter};
35
41
 
36
42
  /**
37
43
  * @private
@@ -51,12 +57,30 @@ const searchButtonElementSymbol = Symbol("searchButtonElement");
51
57
  */
52
58
  const resetButtonElementSymbol = Symbol("resetButtonElement");
53
59
 
60
+ /**
61
+ * @private
62
+ * @type {symbol}
63
+ */
64
+ const saveButtonElementSymbol = Symbol("saveButtonElement");
65
+
54
66
  /**
55
67
  * @private
56
68
  * @type {symbol}
57
69
  */
58
70
  const filterControlElementSymbol = Symbol("filterControlElement");
59
71
 
72
+ /**
73
+ * @private
74
+ * @type {symbol}
75
+ */
76
+ const filterSaveActionButtonElementSymbol = Symbol("filterSaveActionButtonElement");
77
+
78
+ /**
79
+ * @private
80
+ * @type {symbol}
81
+ */
82
+ const filterTabElementSymbol = Symbol("filterTabElement");
83
+
60
84
  /**
61
85
  * @private
62
86
  * @type {symbol}
@@ -110,158 +134,180 @@ const settingsSymbol = Symbol("settings");
110
134
  * @summary A data set
111
135
  */
112
136
  class Filter extends CustomElement {
113
- /**
114
- *
115
- */
116
- constructor() {
117
- super();
118
- this[settingsSymbol] = new Settings();
119
- }
120
-
121
- /**
122
- * This method is called by the `instanceof` operator.
123
- * @returns {symbol}
124
- */
125
- static get [instanceSymbol]() {
126
- return Symbol.for("@schukai/monster/components/filter@@instance");
127
- }
128
-
129
- /**
130
- *
131
- * @param {string} message
132
- * @returns {Monster.Components.Datatable.Filter}
133
- */
134
- showFailureMessage(message) {
135
- this[searchButtonElementSymbol].setState("failed", 10000);
136
- this[searchButtonElementSymbol]
137
- .setMessage(String(message))
138
- .showMessage(10000);
139
- return this;
140
- }
141
-
142
- /**
143
- *
144
- * @returns {Monster.Components.Datatable.Filter}
145
- */
146
- resetFailureMessage() {
147
- this[searchButtonElementSymbol].hideMessage();
148
- this[searchButtonElementSymbol].removeState();
149
- return this;
150
- }
151
-
152
- /**
153
- *
154
- * @returns {Monster.Components.Datatable.Filter}
155
- */
156
- showSuccess() {
157
- this[searchButtonElementSymbol].setState("successful", 5000);
158
- return this;
159
- }
160
-
161
- /**
162
- * To set the options via the html tag the attribute `data-monster-options` must be used.
163
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
164
- *
165
- * The individual configuration values can be found in the table.
166
- *
167
- * @property {Object} templates Template definitions
168
- * @property {string} templates.main Main template
169
- * @property {Object} labels Label definitions
170
- * @property {string} labels.search Search button label
171
- * @property {string} labels.reset Reset button label
172
- * @property {Object} queries Query definitions
173
- * @property {function} queries.wrap Wrap query
174
- * @property {function} queries.join Join queries
175
- * @property {string} defaultQuery Default query
176
- */
177
- get defaults() {
178
- const obj = Object.assign({}, super.defaults, {
179
- templates: {
180
- main: getTemplate(),
181
- },
182
- labels: {
183
- search: "Search",
184
- reset: "Reset",
185
- "empty-query-and-no-default": "Please select a filter",
186
- "query-not-changed": "The query has not changed",
187
- },
188
-
189
- queries: {
190
- wrap: (value, definition) => {
191
- return value;
192
- },
193
- join: (queries) => {
194
- if (queries.length === 0) {
195
- return "";
196
- }
197
- return queries.join(" AND ");
198
- },
199
- },
200
-
201
- query: "",
202
- defaultQuery: "",
203
- });
204
-
205
- return obj;
206
- }
207
-
208
- /**
209
- *
210
- * @return {string}
211
- */
212
- static getTag() {
213
- return "monster-datatable-filter";
214
- }
215
-
216
- /**
217
- * @return {FilterButton}
218
- */
219
- [assembleMethodSymbol]() {
220
- super[assembleMethodSymbol]();
221
-
222
- initControlReferences.call(this);
223
- initEventHandler.call(this);
224
-
225
- initFromConfig
226
- .call(this)
227
- .then(() => {
228
- initFilter.call(this);
229
- })
230
- .catch((error) => {
231
- console.error(error);
232
- });
233
- }
234
-
235
- /**
236
- *
237
- */
238
- connectedCallback() {
239
- super.connectedCallback();
240
-
241
- getWindow().addEventListener(
242
- "hashchange",
243
- this[locationChangeHandlerSymbol],
244
- );
245
- }
246
-
247
- /**
248
- *
249
- */
250
- disconnectedCallback() {
251
- super.disconnectedCallback();
252
-
253
- getWindow().removeEventListener(
254
- "hashchange",
255
- this[locationChangeHandlerSymbol],
256
- );
257
- }
258
-
259
- /**
260
- * @return {Array<CSSStyleSheet>}
261
- */
262
- static getCSSStyleSheet() {
263
- return [FilterStyleSheet];
264
- }
137
+ /**
138
+ *
139
+ */
140
+ constructor() {
141
+ super();
142
+ this[settingsSymbol] = new Settings();
143
+ }
144
+
145
+ /**
146
+ * This method is called by the `instanceof` operator.
147
+ * @returns {symbol}
148
+ */
149
+ static get [instanceSymbol]() {
150
+ return Symbol.for("@schukai/monster/components/filter@@instance");
151
+ }
152
+
153
+ /**
154
+ *
155
+ * @param {string} message
156
+ * @returns {Monster.Components.Datatable.Filter}
157
+ */
158
+ showFailureMessage(message) {
159
+ this[searchButtonElementSymbol].setState("failed", this.getOption("timeouts.message", 4000));
160
+ this[searchButtonElementSymbol]
161
+ .setMessage(message.toString())
162
+ .showMessage(this.getOption("timeouts.message", 4000));
163
+ return this;
164
+ }
165
+
166
+ /**
167
+ *
168
+ * @returns {Monster.Components.Datatable.Filter}
169
+ */
170
+ resetFailureMessage() {
171
+ this[searchButtonElementSymbol].hideMessage();
172
+ this[searchButtonElementSymbol].removeState();
173
+ return this;
174
+ }
175
+
176
+ /**
177
+ *
178
+ * @returns {Monster.Components.Datatable.Filter}
179
+ */
180
+ showSuccess() {
181
+ this[searchButtonElementSymbol].setState("successful", this.getOption("timeouts.message", 4000));
182
+ return this;
183
+ }
184
+
185
+ /**
186
+ * To set the options via the html tag the attribute `data-monster-options` must be used.
187
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
188
+ *
189
+ * The individual configuration values can be found in the table.
190
+ *
191
+ * @property {Object} templates Template definitions
192
+ * @property {string} templates.main Main template
193
+ * @property {Object} labels Label definitions
194
+ * @property {string} labels.search Search button label
195
+ * @property {string} labels.reset Reset button label
196
+ * @property {Object} queries Query definitions
197
+ * @property {function} queries.wrap Wrap query
198
+ * @property {function} queries.join Join queries
199
+ * @property {string} defaultQuery Default query
200
+ */
201
+ get defaults() {
202
+ return Object.assign({}, super.defaults, {
203
+ templates: {
204
+ main: getTemplate(),
205
+ },
206
+
207
+ labels: {
208
+ search: "Search",
209
+ reset: "Reset",
210
+ save: "Save",
211
+ "filter-name": "Filter name",
212
+ "empty-query-and-no-default": "Please select a filter",
213
+ "query-not-changed": "The query has not changed",
214
+ },
215
+
216
+ templateMapping: {
217
+ "filter-save-label": null,
218
+ "filter-name-label": name,
219
+ },
220
+
221
+ storedConfig: {
222
+ enabled: true,
223
+ selector: ""
224
+ },
225
+
226
+ timeouts: {
227
+ message: 4000,
228
+ },
229
+
230
+ queries: {
231
+ wrap: (value, definition) => {
232
+ return value;
233
+ },
234
+ join: (queries) => {
235
+ if (queries.length === 0) {
236
+ return "";
237
+ }
238
+ return queries.join(" AND ");
239
+ },
240
+ },
241
+
242
+ query: "",
243
+ defaultQuery: "",
244
+ });
245
+
246
+ }
247
+
248
+ /**
249
+ *
250
+ * @return {string}
251
+ */
252
+ static getTag() {
253
+ return "monster-datatable-filter";
254
+ }
255
+
256
+ /**
257
+ * @return {FilterButton}
258
+ */
259
+ [assembleMethodSymbol]() {
260
+
261
+ this.setOption("templateMapping.filter-save-label", this.getOption("labels.save"));
262
+ this.setOption("templateMapping.filter-name-label", this.getOption("labels.filter-name"));
263
+
264
+ super[assembleMethodSymbol]();
265
+
266
+ initControlReferences.call(this);
267
+ initEventHandler.call(this);
268
+
269
+ initFromConfig.call(this)
270
+ .then(() => {
271
+ initFilter.call(this);
272
+ updateFilterTabs.call(this);
273
+
274
+ })
275
+ .catch((error) => {
276
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error?.message);
277
+ });
278
+
279
+ }
280
+
281
+ /**
282
+ *
283
+ */
284
+ connectedCallback() {
285
+ super.connectedCallback();
286
+
287
+ getWindow().addEventListener(
288
+ "hashchange",
289
+ this[locationChangeHandlerSymbol],
290
+ );
291
+ }
292
+
293
+ /**
294
+ *
295
+ */
296
+ disconnectedCallback() {
297
+ super.disconnectedCallback();
298
+
299
+ getWindow().removeEventListener(
300
+ "hashchange",
301
+ this[locationChangeHandlerSymbol],
302
+ );
303
+ }
304
+
305
+ /**
306
+ * @return {Array<CSSStyleSheet>}
307
+ */
308
+ static getCSSStyleSheet() {
309
+ return [FilterStyleSheet, FormStyleSheet, ThemeStyleSheet, SpaceStyleSheet];
310
+ }
265
311
  }
266
312
 
267
313
  /**
@@ -269,23 +315,35 @@ class Filter extends CustomElement {
269
315
  * @return {FilterButton}
270
316
  */
271
317
  function initControlReferences() {
272
- if (!this.shadowRoot) {
273
- throw new Error("no shadow-root is defined");
274
- }
275
-
276
- this[filterControlElementSymbol] = this.shadowRoot.querySelector(
277
- "[data-monster-role=control]",
278
- );
279
- this[filterSelectElementSymbol] = this.shadowRoot.querySelector(
280
- "[data-monster-role=filter-select]",
281
- );
282
- this[searchButtonElementSymbol] = this.shadowRoot.querySelector(
283
- "[data-monster-role=search-button]",
284
- );
285
- this[resetButtonElementSymbol] = this.shadowRoot.querySelector(
286
- "[data-monster-role=reset-button]",
287
- );
288
- return this;
318
+ if (!this.shadowRoot) {
319
+ throw new Error("no shadow-root is defined");
320
+ }
321
+
322
+ this[filterControlElementSymbol] = this.shadowRoot.querySelector(
323
+ "[data-monster-role=control]"
324
+ );
325
+ this[filterSelectElementSymbol] = this.shadowRoot.querySelector(
326
+ "[data-monster-role=filter-select]"
327
+ );
328
+ this[searchButtonElementSymbol] = this.shadowRoot.querySelector(
329
+ "[data-monster-role=search-button]"
330
+ );
331
+ this[resetButtonElementSymbol] = this.shadowRoot.querySelector(
332
+ "[data-monster-role=reset-button]"
333
+ );
334
+
335
+ this[saveButtonElementSymbol] = this.shadowRoot.querySelector(
336
+ "[data-monster-role=save-button]"
337
+ );
338
+
339
+ this[filterSaveActionButtonElementSymbol] = this.shadowRoot.querySelector(
340
+ "[data-monster-role=save-action-button]"
341
+ );
342
+
343
+ this[filterTabElementSymbol] = findElementWithSelectorUpwards(this, this.getOption("storedConfig.selector", ""))
344
+
345
+
346
+ return this;
289
347
  }
290
348
 
291
349
  /**
@@ -293,65 +351,69 @@ function initControlReferences() {
293
351
  * @throws {Error} no filter label is defined
294
352
  */
295
353
  function initFilter() {
296
- const storedConfig = this[settingsSymbol];
297
- this[settingsSymbol] = new Settings();
298
-
299
- const result = parseBracketedKeyValueHash(getGlobal().location.hash);
300
- let valuesFromHash = {};
301
- if (isObject(result) && result?.[this.id]) {
302
- valuesFromHash = result[this.id];
303
- }
304
-
305
- getSlottedElements
306
- .call(this, "label[data-monster-label]")
307
- .forEach((element) => {
308
- const label = element.getAttribute("data-monster-label");
309
- if (!label) {
310
- throw new Error("no filter label is defined");
311
- }
312
-
313
- let value = element.id;
314
- if (!value) {
315
- const prefix = label.replace(/\W/g, "-");
316
- prefix.charAt(0).match(/[\d_]/g)?.length ? `f${prefix}` : prefix;
317
-
318
- value = new ID(prefix + "-").toString();
319
- element.id = value;
320
- }
321
-
322
- let setting = storedConfig.get(value);
323
-
324
- if (setting) {
325
- this[settingsSymbol].set(setting);
326
- }
327
-
328
- if (valuesFromHash?.[element.id]) {
329
- const v = escapeAttributeValue(valuesFromHash[element.id]);
330
- const searchInput = element.firstElementChild;
331
- try {
332
- searchInput.value = valuesFromHash[element.id];
333
- } catch (error) {}
334
- }
335
-
336
- setting = this[settingsSymbol].get(value);
337
- if (setting) {
338
- setSlotAttribute(element, setting.visible);
339
- //style.display = setting.visible ? "block" : "none";
340
- }
341
-
342
- //const visible = window.getComputedStyle(element).display !== "none";
343
- const visible = getVisibilityFromSlotAttribute(element);
344
-
345
- this[settingsSymbol].set({ value, label, visible });
346
- });
347
-
348
- this[filterSelectElementSymbol].setOption(
349
- "options",
350
- this[settingsSymbol].getOptions(),
351
- );
352
- setTimeout(() => {
353
- this[filterSelectElementSymbol].value = this[settingsSymbol].getSelected();
354
- }, 10);
354
+
355
+ const storedConfig = this[settingsSymbol];
356
+ this[settingsSymbol] = new Settings();
357
+
358
+ const result = parseBracketedKeyValueHash(getGlobal().location.hash);
359
+ let valuesFromHash = {};
360
+ if (isObject(result) && result?.[this.id]) {
361
+ valuesFromHash = result[this.id];
362
+ }
363
+
364
+ getSlottedElements
365
+ .call(this, "label[data-monster-label]")
366
+ .forEach((element) => {
367
+ const label = element.getAttribute("data-monster-label");
368
+ if (!label) {
369
+ throw new Error("no filter label is defined");
370
+ }
371
+
372
+ let value = element.id;
373
+ if (!value) {
374
+ const prefix = label.replace(/\W/g, "-");
375
+ prefix.charAt(0).match(/[\d_]/g)?.length ? `f${prefix}` : prefix;
376
+
377
+ value = new ID(prefix + "-").toString();
378
+ element.id = value;
379
+ }
380
+
381
+ let setting = storedConfig.get(value);
382
+
383
+ if (setting) {
384
+ this[settingsSymbol].set(setting);
385
+ }
386
+
387
+ if (valuesFromHash?.[element.id]) {
388
+ const v = escapeAttributeValue(valuesFromHash[element.id]);
389
+ const searchInput = element.firstElementChild;
390
+ try {
391
+ // searchInput.value = valuesFromHash[element.id];
392
+ searchInput.value = v;//valuesFromHash[element.id];
393
+ } catch (error) {
394
+ }
395
+ }
396
+
397
+ setting = this[settingsSymbol].get(value);
398
+ if (setting) {
399
+ setSlotAttribute(element, setting.visible);
400
+ //style.display = setting.visible ? "block" : "none";
401
+ }
402
+
403
+ //const visible = window.getComputedStyle(element).display !== "none";
404
+ const visible = getVisibilityFromSlotAttribute(element);
405
+
406
+ this[settingsSymbol].set({value, label, visible});
407
+ });
408
+
409
+ this[filterSelectElementSymbol].setOption(
410
+ "options",
411
+ this[settingsSymbol].getOptions(),
412
+ );
413
+
414
+ setTimeout(() => {
415
+ this[filterSelectElementSymbol].value = this[settingsSymbol].getSelected();
416
+ }, 10);
355
417
  }
356
418
 
357
419
  /**
@@ -360,12 +422,18 @@ function initFilter() {
360
422
  * @returns {*}
361
423
  */
362
424
  function escapeAttributeValue(input) {
363
- return input
364
- .replace(/&/g, "&amp;")
365
- .replace(/"/g, "&quot;")
366
- .replace(/'/g, "&#x27;")
367
- .replace(/</g, "&lt;")
368
- .replace(/>/g, "&gt;");
425
+
426
+ if (input === undefined || input === null) {
427
+ debugger
428
+ return input;
429
+ }
430
+
431
+ return input
432
+ .replace(/&/g, "&amp;")
433
+ .replace(/"/g, "&quot;")
434
+ .replace(/'/g, "&#x27;")
435
+ .replace(/</g, "&lt;")
436
+ .replace(/>/g, "&gt;");
369
437
  }
370
438
 
371
439
  /**
@@ -374,10 +442,7 @@ function escapeAttributeValue(input) {
374
442
  * @returns {boolean}
375
443
  */
376
444
  function getVisibilityFromSlotAttribute(element) {
377
- return element.hasAttribute("slot") &&
378
- element.getAttribute("slot") === "hidden"
379
- ? false
380
- : true;
445
+ return !(element.hasAttribute("slot") && element.getAttribute("slot") === "hidden");
381
446
  }
382
447
 
383
448
  /**
@@ -386,188 +451,404 @@ function getVisibilityFromSlotAttribute(element) {
386
451
  * @param {boolean} visible
387
452
  */
388
453
  function setSlotAttribute(element, visible) {
389
- if (visible) {
390
- element.removeAttribute("slot");
391
- return;
392
- }
454
+ if (visible) {
455
+ element.removeAttribute("slot");
456
+ return;
457
+ }
393
458
 
394
- element.setAttribute("slot", "hidden");
459
+ element.setAttribute("slot", "hidden");
395
460
  }
396
461
 
397
462
  /**
398
463
  * @private
399
464
  */
400
465
  function initEventHandler() {
401
- const self = this;
402
- /**
403
- * Monster.Components.Form.event:monster-selection-cleared
404
- */
405
- if (self[filterSelectElementSymbol]) {
406
- self[filterSelectElementSymbol].addEventListener(
407
- "monster-selection-cleared",
408
- function (event) {
409
- const settings = self[settingsSymbol].getOptions();
410
-
411
- for (const setting of settings) {
412
- const filterElement = findElementWithIdUpwards(self, setting.value);
413
- if (filterElement) {
414
- setSlotAttribute(filterElement, false);
415
-
416
- self[settingsSymbol].set({ value: setting.value, visible: false });
417
- }
418
- }
419
-
420
- updateConfig.call(self);
421
- },
422
- );
423
-
424
- self[filterSelectElementSymbol].addEventListener(
425
- "monster-changed",
426
- function (event) {
427
- const filterElement = findElementWithIdUpwards(
428
- self,
429
- event.detail.value,
430
- );
431
- if (filterElement) {
432
- //filterElement.style.display = event.detail.checked ? "block" : "none";
433
- setSlotAttribute(filterElement, event.detail.checked);
434
- }
435
-
436
- self[settingsSymbol].set({
437
- value: event.detail.value,
438
- visible: event.detail.checked,
439
- });
440
-
441
- updateConfig.call(self);
442
- },
443
- );
444
- }
445
-
446
- self[searchButtonElementSymbol].setOption("actions.click", () => {
447
- doSearch.call(self);
448
- });
449
-
450
- // the reset button should reset the filter and the search query
451
- // all input elements should be reset to their default values
452
- // which is the empty string. we search for all input elements
453
- // in the filter and reset them to their default value
454
- self[resetButtonElementSymbol].setOption("actions.click", () => {
455
- getSlottedElements
456
- .call(self, "label[data-monster-label]")
457
- .forEach((element) => {
458
- const label = element.getAttribute("data-monster-label");
459
- if (!label) {
460
- return;
461
- }
462
-
463
- const input = element.firstElementChild;
464
-
465
- if (input) {
466
- input.value = "";
467
- }
468
- });
469
-
470
- doSearch.call(self, { showEffect: false });
471
- });
472
-
473
- self[locationChangeHandlerSymbol] = (event) => {
474
- if (event instanceof HashChangeEvent) {
475
- if (event.oldURL === event.newURL) {
476
- return;
477
- }
478
- }
479
- };
480
-
481
- self.addEventListener("keyup", (event) => {
482
- const path = event.composedPath();
483
- if (path.length === 0) {
484
- return;
485
- }
486
-
487
- if (!(path[0] instanceof HTMLInputElement)) {
488
- return;
489
- }
490
-
491
- if (event.keyCode === 13) {
492
- doSearch.call(self, { showEffect: false });
493
- }
494
- });
466
+ const self = this;
467
+ /**
468
+ * Monster.Components.Form.event:monster-selection-cleared
469
+ */
470
+ if (self[filterSelectElementSymbol]) {
471
+ self[filterSelectElementSymbol].addEventListener(
472
+ "monster-selection-cleared",
473
+ function () {
474
+ const settings = self[settingsSymbol].getOptions();
475
+
476
+ for (const setting of settings) {
477
+ const filterElement = findElementWithIdUpwards(self, setting.value);
478
+ if (filterElement) {
479
+ setSlotAttribute(filterElement, false);
480
+
481
+ self[settingsSymbol].set({value: setting.value, visible: false});
482
+ }
483
+ }
484
+
485
+ updateConfig.call(self);
486
+ },
487
+ );
488
+
489
+ self[filterSelectElementSymbol].addEventListener(
490
+ "monster-changed",
491
+ function (event) {
492
+ const filterElement = findElementWithIdUpwards(
493
+ self,
494
+ event.detail.value,
495
+ );
496
+ if (filterElement) {
497
+ //filterElement.style.display = event.detail.checked ? "block" : "none";
498
+ setSlotAttribute(filterElement, event.detail.checked);
499
+ }
500
+
501
+ self[settingsSymbol].set({
502
+ value: event.detail.value,
503
+ visible: event.detail.checked,
504
+ });
505
+
506
+ updateConfig.call(self);
507
+ },
508
+ );
509
+ }
510
+
511
+ if (self[filterSaveActionButtonElementSymbol]) {
512
+ self[filterSaveActionButtonElementSymbol].setOption("actions.click", function (event) {
513
+
514
+ const button = findTargetElementFromEvent(event, "data-monster-role", "save-action-button");
515
+ const form = button.closest("[data-monster-role=form]");
516
+
517
+ if (!form) {
518
+ button.setState("failed", self.getOption("timeouts.message", 4000))
519
+ return;
520
+ }
521
+
522
+ const input = form.querySelector("input[name=filter-name]");
523
+ if (!input) {
524
+ button.setState("failed", self.getOption("timeouts.message", 4000))
525
+ return;
526
+ }
527
+
528
+ const name = input.value;
529
+ if (!name) {
530
+ button.setState("failed", self.getOption("timeouts.message", 4000))
531
+ button.setMessage("Please enter a name").showMessage();
532
+ return;
533
+ }
534
+
535
+ doSearch.call(self, {showEffect: false}).then(() => {
536
+
537
+ const configKey = getStoredFilterConfigKey.call(self);
538
+ const host = getDocument().querySelector("monster-host");
539
+ if (!(host)) {
540
+ return;
541
+ }
542
+
543
+ const query = self.getOption("query");
544
+ if (!query) {
545
+ button.setState("failed", self.getOption("timeouts.message", self.getOption("timeouts.message", 4000)))
546
+ button.setMessage("No query found").showMessage(self.getOption("timeouts.message", 4000));
547
+ return;
548
+ }
549
+
550
+ host.hasConfig(configKey).then((hasConfig) => {
551
+
552
+ return new Promise((resolve, reject) => {
553
+ if (hasConfig) {
554
+ host.getConfig(configKey).then(resolve).catch(reject);
555
+ return;
556
+ }
557
+ return resolve({});
558
+ })
559
+
560
+ }).then((config) => {
561
+
562
+ config[name] = query;
563
+ return host.setConfig(configKey, {
564
+ ...config,
565
+ })
566
+
567
+
568
+ }).then(() => {
569
+ button.setState("successful", self.getOption("timeouts.message", 4000))
570
+ updateFilterTabs.call(self);
571
+
572
+ }).catch((error) => {
573
+ button.setState("failed", self.getOption("timeouts.message", 4000))
574
+ button.setMessage(error.message).showMessage(self.getOption("timeouts.message", 4000));
575
+ })
576
+
577
+ }).catch((error) => {
578
+ button.setState("failed", self.getOption("timeouts.message", 4000))
579
+ const msg = error.message || error;
580
+ button.setMessage(msg).showMessage(self.getOption("timeouts.message", 4000));
581
+ })
582
+
583
+ });
584
+ }
585
+
586
+
587
+ self[searchButtonElementSymbol].setOption("actions.click", () => {
588
+ doSearch.call(self).then(() => {
589
+
590
+ }).catch((error) => {
591
+
592
+ })
593
+ });
594
+
595
+ // the reset button should reset the filter and the search query
596
+ // all input elements should be reset to their default values
597
+ // which is the empty string. we search for all input elements
598
+ // in the filter and reset them to their default value
599
+ self[resetButtonElementSymbol].setOption("actions.click", () => {
600
+ getSlottedElements
601
+ .call(self, "label[data-monster-label]")
602
+ .forEach((element) => {
603
+ const label = element.getAttribute("data-monster-label");
604
+ if (!label) {
605
+ return;
606
+ }
607
+
608
+ const input = element.firstElementChild;
609
+
610
+ if (input) {
611
+ input.value = "";
612
+ }
613
+ });
614
+
615
+ doSearch.call(self, {showEffect: false});
616
+ });
617
+
618
+ self[locationChangeHandlerSymbol] = (event) => {
619
+ if (event instanceof HashChangeEvent) {
620
+ if (event.oldURL === event.newURL) {
621
+ return;
622
+ }
623
+ }
624
+ };
625
+
626
+ self.addEventListener("keyup", (event) => {
627
+ const path = event.composedPath();
628
+ if (path.length === 0) {
629
+ return;
630
+ }
631
+
632
+ if (!(path[0] instanceof HTMLInputElement)) {
633
+ return;
634
+ }
635
+
636
+ if (event.keyCode === 13) {
637
+ doSearch.call(self, {showEffect: false});
638
+ }
639
+ });
640
+
641
+ // tabs
642
+ const element = this[filterTabElementSymbol];
643
+ if (element) {
644
+ initTabEvents.call(this)
645
+ }
646
+
647
+ }
648
+
649
+ function initTabEvents() {
650
+
651
+ this[filterTabElementSymbol].addEventListener("monster-tab-changed", (event) => {
652
+
653
+ const query = event?.detail?.data?.['data-monster-query'];
654
+ this.setOption("query", query);
655
+
656
+
657
+ })
658
+
659
+ this[filterTabElementSymbol].addEventListener("monster-tab-remove", (event) => {
660
+
661
+ const labels = []
662
+ const buttons = this[filterTabElementSymbol].getOption("buttons")
663
+
664
+ const keys=["popper", "standard"]
665
+ for(let i=0; i<keys.length; i++) {
666
+ const key = keys[i]
667
+
668
+ for (const button of buttons[key]) {
669
+ if (button.label !== event.detail.label) {
670
+ labels.push(button.label)
671
+ }
672
+ }
673
+ }
674
+
675
+ const document = getDocument();
676
+ const host = document.querySelector("monster-host");
677
+ if (!(host && this.id)) {
678
+ return;
679
+ }
680
+
681
+ const configKey = getStoredFilterConfigKey.call(this);
682
+ host.hasConfig(configKey).then((hasConfig) => {
683
+ if (!hasConfig) {
684
+ return;
685
+ }
686
+
687
+ return host.getConfig(configKey);
688
+ }).then((config) => {
689
+
690
+ for (const [name, query] of Object.entries(config)) {
691
+ if (labels.includes(name)) {
692
+ continue;
693
+ }
694
+
695
+ delete config[name]
696
+ }
697
+
698
+ return host.setConfig(configKey, {
699
+ ...config
700
+ })
701
+
702
+ })
703
+
704
+ });
705
+
706
+
707
+ }
708
+
709
+ /**
710
+ * @private
711
+ */
712
+ function updateFilterTabs() {
713
+
714
+ const element = this[filterTabElementSymbol];
715
+ if (!element) {
716
+ return;
717
+ }
718
+
719
+ const document = getDocument();
720
+ const host = document.querySelector("monster-host");
721
+ if (!(host && this.id)) {
722
+ return;
723
+ }
724
+
725
+ const configKey = getStoredFilterConfigKey.call(this);
726
+ host.hasConfig(configKey).then((hasConfig) => {
727
+ if (!hasConfig) {
728
+ return;
729
+ }
730
+
731
+ return host.getConfig(configKey);
732
+ }).then((config) => {
733
+
734
+ for (const [name, query] of Object.entries(config)) {
735
+
736
+ const found = element.querySelector(`[data-monster-button-label="${name}"]`);
737
+ if (found) {
738
+ continue;
739
+ }
740
+
741
+ if (query===undefined || query===null) {
742
+ continue;
743
+ }
744
+
745
+ const escapedQuery = escapeAttributeValue(query);
746
+
747
+ element.insertAdjacentHTML("beforeend", `
748
+ <div data-monster-button-label="${name}"
749
+ data-monster-removable="true"
750
+ data-monster-query="${escapedQuery}" data-monster-role="filter-tab" >
751
+ </div>`);
752
+ }
753
+ })
495
754
  }
496
755
 
497
756
  /**
498
757
  * @private
758
+ * @param showEffect
759
+ * @returns {Promise<*>}
499
760
  */
500
- function doSearch({ showEffect } = { showEffect: true }) {
501
- this.resetFailureMessage();
502
-
503
- if (showEffect) {
504
- this[searchButtonElementSymbol].setState("activity", 10000);
505
- }
506
-
507
- collectSearchQueries
508
- .call(this)
509
- .then((query) => {
510
- const buildQuery = buildSearchQuery.call(this, query);
511
- if (buildQuery === "" && !this.getOption("defaultQuery")) {
512
- this[searchButtonElementSymbol].removeState();
513
- this[searchButtonElementSymbol]
514
- .setMessage(this.getOption("labels.empty-query-and-no-default"))
515
- .showMessage(5000);
516
- return;
517
- }
518
-
519
- if (buildQuery === this.getOption("query")) {
520
- this[searchButtonElementSymbol].removeState();
521
- this[searchButtonElementSymbol]
522
- .setMessage(this.getOption("labels.query-not-changed"))
523
- .showMessage(5000);
524
- return;
525
- }
526
-
527
- if (showEffect) {
528
- this[searchButtonElementSymbol].setState("activity", 2000);
529
- }
530
-
531
- this.setOption("query", buildSearchQuery.call(this, query));
532
- })
533
- .catch((error) => {
534
- console.error(error);
535
- if (error instanceof Error) {
536
- addAttributeToken(
537
- this,
538
- ATTRIBUTE_ERRORMESSAGE,
539
- error.message + " " + error.stack,
540
- );
541
- } else {
542
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, String(error));
543
- }
544
-
545
- this[searchButtonElementSymbol].setState("failed", 10000);
546
- this[searchButtonElementSymbol].setMessage(String(error)).showMessage();
547
- });
761
+ function doSearch({showEffect} = {showEffect: true}) {
762
+ const self = this;
763
+
764
+ this.resetFailureMessage();
765
+
766
+ if (showEffect) {
767
+ this[searchButtonElementSymbol].setState("activity", self.getOption("timeouts.message", 4000));
768
+ }
769
+
770
+ return collectSearchQueries
771
+ .call(this)
772
+ .then((query) => {
773
+
774
+ const buildQuery = buildSearchQuery.call(this, query);
775
+ if (buildQuery === "" && !this.getOption("defaultQuery")) {
776
+
777
+ const msg = this.getOption("labels.empty-query-and-no-default");
778
+
779
+ if (showEffect) {
780
+ this[searchButtonElementSymbol].removeState();
781
+ this[searchButtonElementSymbol]
782
+ .setMessage(msg)
783
+ .showMessage(self.getOption("timeouts.message", 4000));
784
+ }
785
+
786
+ throw new Error(msg);
787
+
788
+ }
789
+
790
+ if (buildQuery === this.getOption("query")) {
791
+
792
+ const msg = this.getOption("labels.query-not-changed");
793
+
794
+ if (showEffect) {
795
+ this[searchButtonElementSymbol].removeState();
796
+ this[searchButtonElementSymbol]
797
+ .setMessage(msg)
798
+ .showMessage(self.getOption("timeouts.message", 4000));
799
+ }
800
+
801
+ throw new Error(msg);
802
+ }
803
+
804
+ if (showEffect) {
805
+ this[searchButtonElementSymbol].setState("activity", self.getOption("timeouts.message", 4000));
806
+ }
807
+
808
+ this.setOption("query", buildSearchQuery.call(this, query));
809
+ })
810
+ .catch((error) => {
811
+
812
+ if (error instanceof Error) {
813
+ addAttributeToken(
814
+ this,
815
+ ATTRIBUTE_ERRORMESSAGE,
816
+ error.message + " " + error.stack,
817
+ );
818
+ } else {
819
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, String(error));
820
+ }
821
+
822
+ if (showEffect) {
823
+ this[searchButtonElementSymbol].setState("failed", self.getOption("timeouts.message", 4000));
824
+ this[searchButtonElementSymbol].setMessage(error.message).showMessage();
825
+ }
826
+
827
+ return Promise.reject(error);
828
+ });
548
829
  }
549
830
 
550
831
  /**
551
832
  * @private
552
833
  * @param queries
553
- * @returns {string|undefind}
834
+ * @returns {*|string}
554
835
  */
555
836
  function buildSearchQuery(queries) {
556
- if (!isArray(queries) || queries.length === 0) {
557
- return this.getOption("defaultQuery");
558
- }
837
+ if (!isArray(queries) || queries.length === 0) {
838
+ return this.getOption("defaultQuery");
839
+ }
559
840
 
560
- const joinCallback = this.getOption("queries.join");
561
- if (isFunction(joinCallback)) {
562
- return joinCallback(queries);
563
- }
841
+ const joinCallback = this.getOption("queries.join");
842
+ if (isFunction(joinCallback)) {
843
+ return joinCallback(queries);
844
+ }
564
845
 
565
- const q = queries.join(" ").trim();
566
- if (q.length === 0) {
567
- return this.getOption("defaultQuery");
568
- }
846
+ const q = queries.join(" ").trim();
847
+ if (q.length === 0) {
848
+ return this.getOption("defaultQuery");
849
+ }
569
850
 
570
- return q;
851
+ return q;
571
852
  }
572
853
 
573
854
  /**
@@ -575,215 +856,103 @@ function buildSearchQuery(queries) {
575
856
  * @returns {Promise<unknown>}
576
857
  */
577
858
  function collectSearchQueries() {
578
- const currentHash = parseBracketedKeyValueHash(getGlobal().location.hash);
579
-
580
- return new Promise((resolve, reject) => {
581
- const query = [];
582
- const wrapCallback = this.getOption("queries.wrap");
583
-
584
- getSlottedElements
585
- .call(this, "label[data-monster-label]")
586
- .forEach((element) => {
587
- const label = element.getAttribute("data-monster-label");
588
- if (!label) {
589
- throw new Error("no filter label is defined");
590
- }
591
-
592
- const id = element.id;
593
- if (!id) {
594
- reject(new Error("no filter id is defined"));
595
- return;
596
- }
597
-
598
- //const visible = window.getComputedStyle(element).display !== "none";
599
- const visible = getVisibilityFromSlotAttribute(element);
600
- if (!visible) {
601
- return;
602
- }
603
-
604
- let template = element.getAttribute("data-monster-template");
605
- if (!template) {
606
- template = "${id}=${value}";
607
- }
608
-
609
- const controlValue = getControlValuesFromLabel(element);
610
- if (!controlValue) {
611
- if (controlValue === "" && currentHash?.[this.id]?.[id]) {
612
- delete currentHash[this.id][id];
613
- }
614
-
615
- return;
616
- }
617
-
618
- if (!isObject(currentHash[this.id])) {
619
- currentHash[this.id] = {};
620
- }
621
- currentHash[this.id][id] = controlValue;
622
-
623
- const mapping = {
624
- id,
625
- value: controlValue,
626
- label,
627
- };
628
-
629
- const formatter = new Formatter(mapping, {
630
- callbacks: {
631
- range: (value, key) => {
632
- return generateRangeComparisonExpression(value, key, {
633
- urlEncode: true,
634
- andOp: "AND",
635
- orOp: "OR",
636
- eqOp: "=",
637
- gtOp: ">",
638
- ltOp: "<",
639
- });
640
- },
641
- "date-range": (value, key) => {
642
- const query = parseDateInput(value, key);
643
- if (!query || query === "false") {
644
- return "";
645
- }
646
-
647
- // return query as url encoded
648
- return encodeURIComponent(query);
649
- },
650
- },
651
- });
652
-
653
- let queryPart = formatter.format(template);
654
- if (queryPart) {
655
- if (isFunction(wrapCallback)) {
656
- queryPart = wrapCallback(queryPart, mapping);
657
- }
658
- query.push(queryPart);
659
- }
660
- });
661
-
662
- getGlobal().location.hash = createBracketedKeyValueHash(currentHash);
663
- resolve(query);
664
- });
859
+ const currentHash = parseBracketedKeyValueHash(getGlobal().location.hash);
860
+
861
+ return new Promise((resolve, reject) => {
862
+ const query = [];
863
+ const wrapCallback = this.getOption("queries.wrap");
864
+
865
+ let hasNoIdError = false;
866
+
867
+ getSlottedElements
868
+ .call(this, "label[data-monster-label]")
869
+ .forEach((element) => {
870
+ const label = element.getAttribute("data-monster-label");
871
+ if (!label) {
872
+ throw new Error("no filter label is defined");
873
+ }
874
+
875
+ const id = element.id;
876
+ if (!id) {
877
+ hasNoIdError = true;
878
+ return;
879
+ }
880
+
881
+ //const visible = window.getComputedStyle(element).display !== "none";
882
+ const visible = getVisibilityFromSlotAttribute(element);
883
+ if (!visible) {
884
+ return;
885
+ }
886
+
887
+ let template = element.getAttribute("data-monster-template");
888
+ if (!template) {
889
+ template = "${id}=${value}";
890
+ }
891
+
892
+ const controlValue = getControlValuesFromLabel(element);
893
+ if (!controlValue) {
894
+ if (controlValue === "" && currentHash?.[this.id]?.[id]) {
895
+ delete currentHash[this.id][id];
896
+ }
897
+
898
+ return;
899
+ }
900
+
901
+ if (!isObject(currentHash[this.id])) {
902
+ currentHash[this.id] = {};
903
+ }
904
+ currentHash[this.id][id] = controlValue;
905
+
906
+ const mapping = {
907
+ id,
908
+ value: controlValue,
909
+ label,
910
+ };
911
+
912
+ const formatter = new Formatter(mapping, {
913
+ callbacks: {
914
+ range: (value, key) => {
915
+ return generateRangeComparisonExpression(value, key, {
916
+ urlEncode: true,
917
+ andOp: "AND",
918
+ orOp: "OR",
919
+ eqOp: "=",
920
+ gtOp: ">",
921
+ ltOp: "<",
922
+ });
923
+ },
924
+ "date-range": (value, key) => {
925
+ const query = parseDateInput(value, key);
926
+ if (!query || query === "false") {
927
+ return "";
928
+ }
929
+
930
+ // return query as url encoded
931
+ return encodeURIComponent(query);
932
+ },
933
+ },
934
+ });
935
+
936
+ let queryPart = formatter.format(template);
937
+ if (queryPart) {
938
+ if (isFunction(wrapCallback)) {
939
+ queryPart = wrapCallback(queryPart, mapping);
940
+ }
941
+ query.push(queryPart);
942
+ }
943
+ });
944
+
945
+ if (hasNoIdError) {
946
+ reject(new Error("some or all filter elements have no id"));
947
+ return;
948
+ }
949
+
950
+
951
+ getGlobal().location.hash = createBracketedKeyValueHash(currentHash);
952
+ resolve(query);
953
+ });
665
954
  }
666
955
 
667
- /**
668
- * @private
669
- * @param {String} str
670
- * @param {String} field
671
- * @returns {String}
672
- * @throws {Error} if no field is defined
673
- */
674
- function parseDateInput(str, field) {
675
- if (!str) {
676
- return "";
677
- }
678
-
679
- if (!field) {
680
- throw new Error("no field is defined");
681
- }
682
-
683
- // Define the supported formats
684
- //let formats = ['DD-MM-YYYY', 'MM-DD-YYYY', 'YYYY-MM-DD', 'YYYY/MM/DD', 'DD.MM.YYYY'];
685
- const formats = ["YYYY-MM-DD"];
686
- // Determine the current date format of the localization
687
- const currentDateFormat = new Intl.DateTimeFormat()
688
- .format(new Date())
689
- .replace(/\d/g, "D");
690
- // formats.push(currentDateFormat);
691
-
692
- // Run through the supported formats and try to parse the date
693
- for (let i = 0; i < formats.length; i++) {
694
- const format = formats[i];
695
- // Replace the corresponding placeholders in the format string with regular expressions
696
-
697
- try {
698
- const pattern = format
699
- .replace("DD", "\\d{2}")
700
- .replace("MM", "\\d{2}")
701
- .replace("YYYY", "\\d{4}");
702
- const rangePattern =
703
- "(?<from>" + pattern + ")\\s*-\\s*(?<to>" + pattern + ")";
704
-
705
- const rangeRegex = new RegExp("^" + rangePattern + "$", "g");
706
-
707
- if (rangeRegex.test(str)) {
708
- rangeRegex.lastIndex = 0;
709
-
710
- const rangeResult = rangeRegex.exec(str);
711
-
712
- if (!rangeResult) {
713
- continue;
714
- }
715
-
716
- const from = rangeResult?.groups?.from;
717
- const to = rangeResult?.groups?.to;
718
-
719
- if (from && to) {
720
- return (
721
- "(" +
722
- field +
723
- ">='" +
724
- from +
725
- " 00:00:00' AND " +
726
- field +
727
- "<='" +
728
- to +
729
- " 23:59:59')"
730
- );
731
- }
732
-
733
- if (from) {
734
- return "(" + field + ">='" + from + " 00:00:00')";
735
- } else if (to) {
736
- return "(" + field + "<='" + to + "' 23:59:59')";
737
- }
738
-
739
- return "false";
740
- }
741
-
742
- const prefix = str.substring(0, 1) === "-";
743
- const suffix = str.substring(str.length - 1, str.length) === "-";
744
-
745
- if (prefix) {
746
- str = str.substring(1, str.length);
747
- } else if (suffix) {
748
- str = str.substring(0, str.length - 1);
749
- }
750
-
751
- const regex = new RegExp("^(?<date>" + pattern + ")$", "g");
752
- if (regex.test(str)) {
753
- regex.lastIndex = 0;
754
- const result = regex.exec(str);
755
-
756
- if (!result) {
757
- continue;
758
- }
759
-
760
- const date = result?.groups?.date;
761
- if (date) {
762
- if (prefix) {
763
- return "(" + field + "<='" + date + " 23:59:59')";
764
- } else if (suffix) {
765
- return "(" + field + ">='" + date + "' 00:00:00')";
766
- }
767
- return (
768
- "(" +
769
- field +
770
- ">='" +
771
- date +
772
- " 00:00:00' AND " +
773
- field +
774
- "<='" +
775
- date +
776
- " 23:59:59')"
777
- );
778
- } else {
779
- return "false";
780
- }
781
- }
782
- } catch (e) {}
783
- }
784
-
785
- return "false";
786
- }
787
956
 
788
957
  /**
789
958
  * @private
@@ -791,97 +960,96 @@ function parseDateInput(str, field) {
791
960
  * @returns {null|Array|undefined|string}
792
961
  */
793
962
  function getControlValuesFromLabel(label) {
794
- const foundControl = label.firstElementChild;
795
-
796
- if (foundControl) {
797
- if (foundControl.tagName === "INPUT") {
798
- if (foundControl.type === "checkbox") {
799
- const checkedControls = label.querySelectorAll(`${control}:checked`);
800
- const values = [];
801
-
802
- checkedControls.forEach((checkedControl) => {
803
- values.push(checkedControl.value);
804
- });
805
-
806
- return values;
807
- } else if (foundControl.type === "radio") {
808
- const checkedControl = label.querySelector(`${control}:checked`);
809
-
810
- if (checkedControl) {
811
- return checkedControl.value;
812
- } else {
813
- return null;
814
- }
815
- } else {
816
- return foundControl.value;
817
- }
818
- } else {
819
- return foundControl.value;
820
- }
821
- }
822
-
823
- return null;
963
+ const foundControl = label.firstElementChild;
964
+
965
+ if (foundControl) {
966
+ if (foundControl.tagName === "INPUT") {
967
+ if (foundControl.type === "checkbox") {
968
+ const checkedControls = label.querySelectorAll(`${foundControl}:checked`);
969
+ const values = [];
970
+
971
+ checkedControls.forEach((checkedControl) => {
972
+ values.push(checkedControl.value);
973
+ });
974
+
975
+ return values;
976
+ } else if (foundControl.type === "radio") {
977
+ const checkedControl = label.querySelector(`${foundControl}:checked`);
978
+
979
+ if (checkedControl) {
980
+ return checkedControl.value;
981
+ } else {
982
+ return null;
983
+ }
984
+ } else {
985
+ return foundControl.value;
986
+ }
987
+ } else {
988
+ return foundControl.value;
989
+ }
990
+ }
991
+
992
+ return null;
824
993
  }
825
994
 
826
- /**
827
- * @private
828
- * @returns {string}
829
- */
830
- function getFilterConfigKey() {
831
- return generateUniqueConfigKey("datatable", this?.id, "filter");
832
- }
833
995
 
834
996
  /**
835
997
  * @private
836
998
  * @returns {Promise<unknown>}
837
999
  */
838
1000
  function initFromConfig() {
839
- const document = getDocument();
840
- const host = document.querySelector("monster-host");
841
-
842
- if (!(isInstance(host, Host) && this.id)) {
843
- return Promise.resolve();
844
- }
845
-
846
- const configKey = getFilterConfigKey.call(this);
847
-
848
- return new Promise((resolve, reject) => {
849
- host
850
- .getConfig(configKey)
851
- .then((config) => {
852
- if (config) {
853
- this[settingsSymbol].setOptions(config);
854
- }
855
- resolve();
856
- })
857
- .catch((error) => {
858
- if (error === undefined) {
859
- resolve();
860
- return;
861
- }
862
-
863
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, String(error));
864
- reject(error);
865
- });
866
- });
1001
+ const document = getDocument();
1002
+ const host = document.querySelector("monster-host");
1003
+
1004
+ if (!(isInstance(host, Host) && this.id)) {
1005
+ return Promise.resolve();
1006
+ }
1007
+
1008
+ const configKey = getFilterConfigKey.call(this);
1009
+
1010
+ return new Promise((resolve, reject) => {
1011
+ host
1012
+ .getConfig(configKey)
1013
+ .then((config) => {
1014
+ if (config && isObject(config)) {
1015
+ this[settingsSymbol].setOptions(config);
1016
+ }
1017
+ resolve();
1018
+ })
1019
+ .catch((error) => {
1020
+ if (error === undefined) {
1021
+ resolve();
1022
+ return;
1023
+ }
1024
+
1025
+ // config not written
1026
+ if (error?.message?.match(/is not defined/)) {
1027
+ resolve();
1028
+ return;
1029
+ }
1030
+
1031
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error?.message || error);
1032
+ reject(error);
1033
+ });
1034
+ });
867
1035
  }
868
1036
 
869
1037
  /**
870
1038
  * @private
871
1039
  */
872
1040
  function updateConfig() {
873
- const document = getDocument();
874
- const host = document.querySelector("monster-host");
875
- if (!(host && this.id)) {
876
- return;
877
- }
878
- const configKey = getFilterConfigKey.call(this);
879
-
880
- try {
881
- host.setConfig(configKey, this[settingsSymbol].getOptions());
882
- } catch (error) {
883
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, String(error));
884
- }
1041
+ const document = getDocument();
1042
+ const host = document.querySelector("monster-host");
1043
+ if (!(host && this.id)) {
1044
+ return;
1045
+ }
1046
+ const configKey = getFilterConfigKey.call(this);
1047
+
1048
+ try {
1049
+ host.setConfig(configKey, this[settingsSymbol].getOptions());
1050
+ } catch (error) {
1051
+ addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, error?.message || error);
1052
+ }
885
1053
  }
886
1054
 
887
1055
  /**
@@ -889,8 +1057,9 @@ function updateConfig() {
889
1057
  * @return {string}
890
1058
  */
891
1059
  function getTemplate() {
892
- // language=HTML
893
- return `
1060
+
1061
+ // language=HTML
1062
+ return `
894
1063
  <div data-monster-role="control" part="control">
895
1064
  <div data-monster-role="layout">
896
1065
  <div data-monster-role="filter">
@@ -898,15 +1067,31 @@ function getTemplate() {
898
1067
  <slot name="hidden"></slot>
899
1068
  </div>
900
1069
  <div data-monster-role="select-and-search">
1070
+ <monster-message-state-button data-monster-role="search-button" class="stretched-control"
1071
+ data-monster-replace="path:labels.search">
1072
+ </monster-message-state-button>
901
1073
  <monster-select class="stretched-control"
902
1074
  data-monster-selected-template="summary"
903
1075
  data-monster-option-type="checkbox"
904
1076
  data-monster-option-filter-mode="options"
905
1077
  data-monster-option-filter-position="popper"
906
1078
  data-monster-role="filter-select"></monster-select>
907
- <monster-message-state-button data-monster-role="search-button" class="stretched-control"
908
- data-monster-replace="path:labels.search">
909
- </monster-message-state-button>
1079
+ <monster-popper-button data-monster-role="save-button" class="stretched-control"
1080
+ data-monster-attributes="data-monster-visible path:storedConfig.enabled">
1081
+ <div slot="button">\${filter-save-label}</div>
1082
+ <div class="monster-form" data-monster-role="form">
1083
+
1084
+ <label for="filter-name">\${filter-name-label}
1085
+ <input name="filter-name"
1086
+ type="search"
1087
+ class="monster-margin-bottom-5"></label>
1088
+ <monster-message-state-button
1089
+ data-monster-role="save-action-button"
1090
+ data-monster-option-labels-button="\${filter-save-label}">
1091
+ </monster-message-state-button>
1092
+
1093
+ </div>
1094
+ </monster-popper-button>
910
1095
  <monster-button data-monster-role="reset-button" class="stretched-control"
911
1096
  data-monster-replace="path:labels.reset">
912
1097
  </monster-button>