@schukai/monster 3.106.0 → 3.107.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
 
4
4
 
5
+ ## [3.107.0] - 2025-02-09
6
+
7
+ ### Add Features
8
+
9
+ - **select:** standard value filter function
10
+
11
+
12
+
13
+ ## [3.106.1] - 2025-02-09
14
+
15
+ ### Bug Fixes
16
+
17
+ - **select:** update empty value handling
18
+ ### Changes
19
+
20
+ - update nix
21
+
22
+
23
+
5
24
  ## [3.106.0] - 2025-02-07
6
25
 
7
26
  ### Add Features
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.6.13","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"3.106.0"}
1
+ {"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.6.13","@popperjs/core":"^2.11.8"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"3.107.0"}
@@ -14,7 +14,7 @@
14
14
 
15
15
  import {instanceSymbol} from "../../constants.mjs";
16
16
  import {internalSymbol} from "../../constants.mjs";
17
- import {buildMap} from "../../data/buildmap.mjs";
17
+ import {buildMap,build as buildValue} from "../../data/buildmap.mjs";
18
18
  import {DeadMansSwitch} from "../../util/deadmansswitch.mjs";
19
19
  import {positionPopper} from "./util/floating-ui.mjs";
20
20
  import {
@@ -697,11 +697,12 @@ class Select extends CustomControl {
697
697
  * @fires monster-options-set this event is fired when the options are set
698
698
  */
699
699
  importOptions(data) {
700
+ const self = this;
700
701
  const mappingOptions = this.getOption("mapping", {});
701
702
  const selector = mappingOptions?.["selector"];
702
703
  const labelTemplate = mappingOptions?.["labelTemplate"];
703
704
  const valueTemplate = mappingOptions?.["valueTemplate"];
704
- const filter = mappingOptions?.["filter"];
705
+ let filter = mappingOptions?.["filter"];
705
706
 
706
707
  let flag = false;
707
708
  if (labelTemplate === "") {
@@ -717,6 +718,36 @@ class Select extends CustomControl {
717
718
  if (flag === true) {
718
719
  throw new Error("missing label configuration");
719
720
  }
721
+ if (isString(filter) ) {
722
+ if (0 === filter.indexOf('run:')) {
723
+ const code = filter.replace('run:', '');
724
+ filter = (m, v, k) => {
725
+ const fkt = new Function('m', 'v', 'k', "control", code);
726
+ return fkt(m, v, k, self);
727
+ }
728
+ } else if (0 === filter.indexOf('call:')) {
729
+
730
+ const parts = filter.split(':');
731
+ parts.shift(); // remove prefix
732
+ const fkt = parts.shift();
733
+
734
+ switch (fkt) {
735
+ case "filterValueOfAttribute":
736
+
737
+ const attribute = parts.shift();
738
+ const attrValue = self.getAttribute(attribute);
739
+
740
+ filter = (m, v, k) => {
741
+ const mm = buildValue(m, valueTemplate);
742
+ return mm != attrValue; // no type check, no !==
743
+ }
744
+ break;
745
+
746
+ default:
747
+ addErrorAttribute(this, new Error(`Unknown filter function ${fkt}`));
748
+ }
749
+ }
750
+ }
720
751
 
721
752
  const map = buildMap(data, selector, labelTemplate, valueTemplate, filter);
722
753
 
@@ -2290,12 +2321,13 @@ function isValueIsEmptyThenGetNormalize(value) {
2290
2321
  function setSelection(selection) {
2291
2322
  const self = this;
2292
2323
 
2324
+ selection = isValueIsEmptyThenGetNormalize.call(this, selection);
2325
+
2293
2326
  if (isString(selection) || isInteger(selection)) {
2294
2327
  const result = convertValueToSelection.call(this, selection);
2295
2328
  selection = result?.selection;
2296
2329
  }
2297
2330
 
2298
- selection = isValueIsEmptyThenGetNormalize.call(this, selection);
2299
2331
  validateArray(selection);
2300
2332
 
2301
2333
  let resultSelection = [];
@@ -12,37 +12,38 @@
12
12
  * SPDX-License-Identifier: AGPL-3.0
13
13
  */
14
14
 
15
- import { buildTree } from "../../data/buildtree.mjs";
15
+ import {buildTree} from "../../data/buildtree.mjs";
16
16
  import {
17
- addAttributeToken,
18
- findClosestByAttribute,
17
+ addAttributeToken,
18
+ findClosestByAttribute,
19
19
  } from "../../dom/attributes.mjs";
20
20
  import {
21
- ATTRIBUTE_ERRORMESSAGE,
22
- ATTRIBUTE_ROLE,
23
- ATTRIBUTE_UPDATER_INSERT_REFERENCE,
21
+ ATTRIBUTE_ERRORMESSAGE,
22
+ ATTRIBUTE_ROLE,
23
+ ATTRIBUTE_UPDATER_INSERT_REFERENCE,
24
24
  } from "../../dom/constants.mjs";
25
- import { instanceSymbol } from "../../constants.mjs";
25
+ import {instanceSymbol} from "../../constants.mjs";
26
26
  import {
27
- assembleMethodSymbol,
28
- registerCustomElement,
27
+ assembleMethodSymbol,
28
+ registerCustomElement,
29
29
  } from "../../dom/customelement.mjs";
30
30
  import {
31
- findTargetElementFromEvent,
32
- fireCustomEvent,
33
- fireEvent,
31
+ findTargetElementFromEvent,
32
+ fireCustomEvent,
33
+ fireEvent,
34
34
  } from "../../dom/events.mjs";
35
- import { Formatter } from "../../text/formatter.mjs";
36
- import { isString } from "../../types/is.mjs";
37
- import { Node } from "../../types/node.mjs";
38
- import { NodeRecursiveIterator } from "../../types/noderecursiveiterator.mjs";
39
- import { validateInstance } from "../../types/validate.mjs";
40
- import { ATTRIBUTE_FORM_URL, ATTRIBUTE_INTEND } from "./constants.mjs";
41
- import { Select } from "./select.mjs";
42
- import { SelectStyleSheet } from "./stylesheet/select.mjs";
43
- import { TreeSelectStyleSheet } from "./stylesheet/tree-select.mjs";
44
-
45
- export { TreeSelect, formatHierarchicalSelection };
35
+ import {Formatter} from "../../text/formatter.mjs";
36
+ import {isString} from "../../types/is.mjs";
37
+ import {Node} from "../../types/node.mjs";
38
+ import {NodeRecursiveIterator} from "../../types/noderecursiveiterator.mjs";
39
+ import {validateInstance} from "../../types/validate.mjs";
40
+ import {ATTRIBUTE_FORM_URL, ATTRIBUTE_INTEND} from "./constants.mjs";
41
+ import {Select} from "./select.mjs";
42
+ import {SelectStyleSheet} from "./stylesheet/select.mjs";
43
+ import {TreeSelectStyleSheet} from "./stylesheet/tree-select.mjs";
44
+ import {addErrorAttribute} from "../../dom/error.mjs";
45
+
46
+ export {TreeSelect, formatHierarchicalSelection};
46
47
 
47
48
  /**
48
49
  * @private
@@ -72,161 +73,201 @@ const keyEventHandler = Symbol("keyEventHandler");
72
73
  * @fires monster-changed
73
74
  */
74
75
  class TreeSelect extends Select {
75
- /**
76
- * This method is called by the `instanceof` operator.
77
- * @return {symbol}
78
- * @since 2.1.0
79
- */
80
- static get [instanceSymbol]() {
81
- return Symbol.for("@schukai/monster/components/form/tree-select@@instance");
82
- }
83
-
84
- /**
85
- * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
86
- * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
87
- *
88
- * The individual configuration values can be found in the table.
89
- *
90
- * @extends Select
91
- * @property {Array} mapping.rootReferences=['0', undefined, null]
92
- * @property {String} mapping.idTemplate=id
93
- * @property {String} mapping.parentTemplate=parent
94
- * @property {String} mapping.selection
95
- * @property {Object} formatter
96
- * @property {String} formatter.separator=" / "
97
- */
98
- get defaults() {
99
- return Object.assign(
100
- {},
101
- super.defaults,
102
- {
103
- mapping: {
104
- rootReferences: ["0", undefined, null],
105
- id: "id",
106
- parent: "parent",
107
-
108
- selector: "*",
109
- labelTemplate: "",
110
- valueTemplate: "",
111
- },
112
- formatter: {
113
- selection: formatHierarchicalSelection,
114
- separator: " / ",
115
- },
116
- templates: {
117
- main: getTemplate(),
118
- },
119
- },
120
- initOptionsFromArguments.call(this),
121
- );
122
- }
123
-
124
- /**
125
- *
126
- * @return {string}
127
- */
128
- static getTag() {
129
- return "monster-tree-select";
130
- }
131
-
132
- /**
133
- *
134
- * @return {CSSStyleSheet[]}
135
- */
136
- static getCSSStyleSheet() {
137
- return [SelectStyleSheet, TreeSelectStyleSheet];
138
- }
139
-
140
- /**
141
- * Import Select Options from dataset
142
- * Not to be confused with the control defaults/options
143
- *
144
- * @param {array|object|Map|Set} data
145
- * @return {Select}
146
- * @throws {Error} map is not iterable
147
- */
148
- importOptions(data) {
149
- this[internalNodesSymbol] = new Map();
150
-
151
- const mappingOptions = this.getOption("mapping", {});
152
-
153
- const filter = mappingOptions?.["filter"];
154
- const rootReferences = mappingOptions?.["rootReferences"];
155
-
156
- const id = this.getOption("mapping.id", "id");
157
- const parentID = this.getOption("mapping.parent", "parent");
158
-
159
- const selector = mappingOptions?.["selector"];
160
- const options = [];
161
-
162
- try {
163
- const nodes = buildTree(data, selector, id, parentID, {
164
- filter,
165
- rootReferences,
166
- });
167
-
168
- for (const node of nodes) {
169
- const iterator = new NodeRecursiveIterator(node);
170
- for (const n of iterator) {
171
- const formattedValues = formatKeyLabel.call(this, n);
172
-
173
- const label = formattedValues.label;
174
- const value = formattedValues.value;
175
- const intend = n.level;
176
-
177
- const visibility = intend > 0 ? "hidden" : "visible";
178
- const state = "close";
179
-
180
- this[internalNodesSymbol].set(value, n);
181
-
182
- options.push({
183
- value,
184
- label,
185
- intend,
186
- state,
187
- visibility,
188
- ["has-children"]: n.hasChildNodes(),
189
- });
190
- }
191
- }
192
-
193
- this.setOption("options", options);
194
-
195
- fireCustomEvent(this, "monster-options-set", {
196
- options,
197
- });
198
- } catch (e) {
199
- addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e?.message || e);
200
- }
201
-
202
- return this;
203
- }
204
-
205
- /**
206
- *
207
- * @return {TreeSelect}
208
- */
209
- [assembleMethodSymbol]() {
210
- super[assembleMethodSymbol]();
211
- initEventHandler.call(this);
212
- }
76
+ /**
77
+ * This method is called by the `instanceof` operator.
78
+ * @return {symbol}
79
+ * @since 2.1.0
80
+ */
81
+ static get [instanceSymbol]() {
82
+ return Symbol.for("@schukai/monster/components/form/tree-select@@instance");
83
+ }
84
+
85
+ /**
86
+ * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
87
+ * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
88
+ *
89
+ * The individual configuration values can be found in the table.
90
+ *
91
+ * @extends Select
92
+ * @property {Array} mapping.rootReferences=['0', undefined, null]
93
+ * @property {String} mapping.idTemplate=id
94
+ * @property {String} mapping.parentTemplate=parent
95
+ * @property {String} mapping.selection
96
+ * @property {String} mapping.labelTemplate
97
+ * @property {String} mapping.valueTemplate
98
+ * @property {String} mapping.filter The filter function to apply to each node, you can use run: syntax to execute a function, or use call:filterValueOfAttribute:data-my-attribute.
99
+ * @property {Object} formatter
100
+ * @property {String} formatter.separator=" / "
101
+ */
102
+ get defaults() {
103
+ return Object.assign(
104
+ {},
105
+ super.defaults,
106
+ {
107
+ mapping: {
108
+ rootReferences: ["0", undefined, null],
109
+ id: "id",
110
+ parent: "parent",
111
+
112
+ selector: "*",
113
+ labelTemplate: "",
114
+ valueTemplate: "",
115
+
116
+ filter: null,
117
+ },
118
+ formatter: {
119
+ selection: formatHierarchicalSelection,
120
+ separator: " / ",
121
+ },
122
+ templates: {
123
+ main: getTemplate(),
124
+ },
125
+ },
126
+ initOptionsFromArguments.call(this),
127
+ );
128
+ }
129
+
130
+ /**
131
+ *
132
+ * @return {string}
133
+ */
134
+ static getTag() {
135
+ return "monster-tree-select";
136
+ }
137
+
138
+ /**
139
+ *
140
+ * @return {CSSStyleSheet[]}
141
+ */
142
+ static getCSSStyleSheet() {
143
+ return [SelectStyleSheet, TreeSelectStyleSheet];
144
+ }
145
+
146
+ /**
147
+ * Import Select Options from dataset
148
+ * Not to be confused with the control defaults/options
149
+ *
150
+ * @param {array|object|Map|Set} data
151
+ * @return {Select}
152
+ * @throws {Error} map is not iterable
153
+ */
154
+ importOptions(data) {
155
+ const self = this;
156
+
157
+ this[internalNodesSymbol] = new Map();
158
+
159
+ const id = this.getOption("mapping.id", "id");
160
+ const parentID = this.getOption("mapping.parent", "parent");
161
+
162
+ const mappingOptions = this.getOption("mapping", {});
163
+
164
+ let filter = mappingOptions?.["filter"];
165
+
166
+ if (isString(filter) ) {
167
+ if (0 === filter.indexOf('run:')) {
168
+ const code = filter.replace('run:', '');
169
+ filter = (m, v, k) => {
170
+ const fkt = new Function('m', 'v', 'k', "control", code);
171
+ return fkt(m, v, k, self);
172
+ }
173
+ } else if (0 === filter.indexOf('call:')) {
174
+
175
+ const parts = filter.split(':');
176
+ parts.shift(); // remove prefix
177
+ const fkt = parts.shift();
178
+
179
+ switch (fkt) {
180
+ case "filterValueOfAttribute":
181
+
182
+ const attribute = parts.shift();
183
+ const attrValue = self.getAttribute(attribute);
184
+
185
+ filter = (m, v, k) => {
186
+ return m?.[id] != attrValue; // no type check, no !==
187
+ }
188
+ break;
189
+
190
+ default:
191
+ addErrorAttribute(this, new Error(`Unknown filter function ${fkt}`));
192
+ }
193
+ }
194
+
195
+ }
196
+
197
+ const rootReferences = mappingOptions?.["rootReferences"];
198
+
199
+ const selector = mappingOptions?.["selector"];
200
+ const options = [];
201
+
202
+ try {
203
+ const nodes = buildTree(data, selector, id, parentID, {
204
+ filter,
205
+ rootReferences,
206
+ });
207
+
208
+ for (const node of nodes) {
209
+ const iterator = new NodeRecursiveIterator(node);
210
+ for (const n of iterator) {
211
+ const formattedValues = formatKeyLabel.call(this, n);
212
+
213
+ const label = formattedValues.label;
214
+ const value = formattedValues.value;
215
+ const intend = n.level;
216
+
217
+ const visibility = intend > 0 ? "hidden" : "visible";
218
+ const state = "close";
219
+
220
+ this[internalNodesSymbol].set(value, n);
221
+
222
+ options.push({
223
+ value,
224
+ label,
225
+ intend,
226
+ state,
227
+ visibility,
228
+ ["has-children"]: n.hasChildNodes(),
229
+ });
230
+ }
231
+ }
232
+
233
+ this.setOption("options", options);
234
+
235
+ fireCustomEvent(this, "monster-options-set", {
236
+ options,
237
+ });
238
+ } catch (e) {
239
+ addErrorAttribute(this, e);
240
+ }
241
+
242
+ return this;
243
+ }
244
+
245
+ /**
246
+ *
247
+ * @return {TreeSelect}
248
+ */
249
+ [assembleMethodSymbol]() {
250
+ super[assembleMethodSymbol]();
251
+ initEventHandler.call(this);
252
+ }
213
253
  }
214
254
 
255
+
215
256
  /**
216
257
  * @private
217
258
  * @param event
218
259
  */
219
260
  function handleOptionKeyboardEvents(event) {
220
- switch (event?.["code"]) {
221
- case "ArrowLeft":
222
- closeOrOpenCurrentOption.call(this, event, "close");
223
- event.preventDefault();
224
- break;
225
- case "ArrowRight":
226
- closeOrOpenCurrentOption.call(this, event, "open");
227
- event.preventDefault();
228
- break;
229
- }
261
+ switch (event?.["code"]) {
262
+ case "ArrowLeft":
263
+ closeOrOpenCurrentOption.call(this, event, "close");
264
+ event.preventDefault();
265
+ break;
266
+ case "ArrowRight":
267
+ closeOrOpenCurrentOption.call(this, event, "open");
268
+ event.preventDefault();
269
+ break;
270
+ }
230
271
  }
231
272
 
232
273
  /**
@@ -234,24 +275,24 @@ function handleOptionKeyboardEvents(event) {
234
275
  * @param {event} event
235
276
  */
236
277
  function closeOrOpenCurrentOption(event, mode) {
237
- validateInstance(event, Event);
238
-
239
- if (typeof event.composedPath !== "function") {
240
- throw new Error("unsupported event");
241
- }
242
-
243
- const path = event.composedPath();
244
- const optionNode = path.shift();
245
-
246
- const state = optionNode.getAttribute("data-monster-state");
247
- if (state !== mode) {
248
- const handler = optionNode.querySelector(
249
- "[data-monster-role=folder-handler]",
250
- );
251
- if (handler instanceof HTMLElement) {
252
- fireEvent(handler, "click");
253
- }
254
- }
278
+ validateInstance(event, Event);
279
+
280
+ if (typeof event.composedPath !== "function") {
281
+ throw new Error("unsupported event");
282
+ }
283
+
284
+ const path = event.composedPath();
285
+ const optionNode = path.shift();
286
+
287
+ const state = optionNode.getAttribute("data-monster-state");
288
+ if (state !== mode) {
289
+ const handler = optionNode.querySelector(
290
+ "[data-monster-role=folder-handler]",
291
+ );
292
+ if (handler instanceof HTMLElement) {
293
+ fireEvent(handler, "click");
294
+ }
295
+ }
255
296
  }
256
297
 
257
298
  /**
@@ -261,25 +302,25 @@ function closeOrOpenCurrentOption(event, mode) {
261
302
  * @private
262
303
  */
263
304
  function formatKeyLabel(node) {
264
- validateInstance(node, Node);
305
+ validateInstance(node, Node);
265
306
 
266
- const v = node.value;
267
- if (v === undefined) {
268
- throw new Error("the object has no value for the specified id");
269
- }
307
+ const v = node.value;
308
+ if (v === undefined) {
309
+ throw new Error("the object has no value for the specified id");
310
+ }
270
311
 
271
- const label = new Formatter(v).format(
272
- this.getOption("mapping.labelTemplate", ""),
273
- );
312
+ const label = new Formatter(v).format(
313
+ this.getOption("mapping.labelTemplate", ""),
314
+ );
274
315
 
275
- const value = new Formatter(v).format(
276
- this.getOption("mapping.valueTemplate", ""),
277
- );
316
+ const value = new Formatter(v).format(
317
+ this.getOption("mapping.valueTemplate", ""),
318
+ );
278
319
 
279
- return {
280
- value,
281
- label,
282
- };
320
+ return {
321
+ value,
322
+ label,
323
+ };
283
324
  }
284
325
 
285
326
  /**
@@ -288,27 +329,27 @@ function formatKeyLabel(node) {
288
329
  * @return {Array}
289
330
  */
290
331
  function buildTreeLabels(value) {
291
- if (!this[internalNodesSymbol]) {
292
- return [value];
293
- }
294
-
295
- let node = this[internalNodesSymbol].get(value);
296
- if (node === undefined) {
297
- node = this[internalNodesSymbol].get(parseInt(value));
298
- }
299
-
300
- const parts = [];
301
-
302
- if (node instanceof Node) {
303
- let ptr = node;
304
- while (ptr) {
305
- const formattedValues = formatKeyLabel.call(this, ptr);
306
- parts.unshift(formattedValues.label);
307
- ptr = ptr.parent;
308
- }
309
- }
310
-
311
- return parts;
332
+ if (!this[internalNodesSymbol]) {
333
+ return [value];
334
+ }
335
+
336
+ let node = this[internalNodesSymbol].get(value);
337
+ if (node === undefined) {
338
+ node = this[internalNodesSymbol].get(parseInt(value));
339
+ }
340
+
341
+ const parts = [];
342
+
343
+ if (node instanceof Node) {
344
+ let ptr = node;
345
+ while (ptr) {
346
+ const formattedValues = formatKeyLabel.call(this, ptr);
347
+ parts.unshift(formattedValues.label);
348
+ ptr = ptr.parent;
349
+ }
350
+ }
351
+
352
+ return parts;
312
353
  }
313
354
 
314
355
  /**
@@ -326,9 +367,9 @@ function buildTreeLabels(value) {
326
367
  * @return {string}
327
368
  */
328
369
  function formatHierarchicalSelection(value) {
329
- return buildTreeLabels
330
- .call(this, value)
331
- .join(this.getOption("formatter.separator", " / "));
370
+ return buildTreeLabels
371
+ .call(this, value)
372
+ .join(this.getOption("formatter.separator", " / "));
332
373
  }
333
374
 
334
375
  /**
@@ -342,99 +383,99 @@ const openOptionEventHandler = Symbol("openOptionEventHandler");
342
383
  * @throws {Error} no shadow-root is defined
343
384
  */
344
385
  function initEventHandler() {
345
- if (!this.shadowRoot) {
346
- throw new Error("no shadow-root is defined");
347
- }
348
-
349
- this[openOptionEventHandler] = (event) => {
350
- const element = findTargetElementFromEvent(
351
- event,
352
- ATTRIBUTE_ROLE,
353
- "folder-handler",
354
- );
355
- if (!(element instanceof HTMLElement)) {
356
- return;
357
- }
358
-
359
- const container = findClosestByAttribute(element, ATTRIBUTE_ROLE, "option");
360
- const index = container
361
- .getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE)
362
- .split("-")
363
- .pop();
364
-
365
- const currentState = this.getOption(`options.${index}.state`);
366
-
367
- const newState = currentState === "close" ? "open" : "close";
368
- this.setOption(`options.${index}.state`, newState);
369
-
370
- const newVisibility = newState === "open" ? "visible" : "hidden";
371
-
372
- if (container.hasAttribute(ATTRIBUTE_INTEND)) {
373
- const intend = container.getAttribute(ATTRIBUTE_INTEND);
374
-
375
- let ref = container.nextElementSibling;
376
- const childIntend = parseInt(intend) + 1;
377
-
378
- const cmp = (a, b) => {
379
- if (newState === "open") {
380
- return a === b;
381
- }
382
-
383
- return a >= b;
384
- };
385
-
386
- while (
387
- ref?.hasAttribute(ATTRIBUTE_INTEND) &&
388
- cmp(parseInt(ref.getAttribute(ATTRIBUTE_INTEND)), childIntend)
389
- ) {
390
- const refIndex = ref
391
- .getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE)
392
- .split("-")
393
- .pop();
394
- this.setOption(`options.${refIndex}.visibility`, newVisibility);
395
-
396
- if (newState === "close") {
397
- this.setOption(`options.${refIndex}.state`, "close");
398
- }
399
-
400
- ref = ref.nextElementSibling;
401
- }
402
- }
403
- };
404
-
405
- this[keyEventHandler] = (event) => {
406
- const path = event.composedPath();
407
- const element = path?.[0];
408
-
409
- let role;
410
-
411
- if (element instanceof HTMLElement) {
412
- if (element.hasAttribute(ATTRIBUTE_ROLE)) {
413
- role = element.getAttribute(ATTRIBUTE_ROLE);
414
- } else if (element === this) {
415
- show.call(this);
416
- focusFilter.call(this);
417
- } else {
418
- const e = element.closest(`[${ATTRIBUTE_ROLE}]`);
419
- if (e instanceof HTMLElement && e.hasAttribute()) {
420
- role = e.getAttribute(ATTRIBUTE_ROLE);
421
- }
422
- }
423
- } else {
424
- return;
425
- }
426
-
427
- switch (role) {
428
- case "option-label":
429
- case "option-control":
430
- case "option":
431
- handleOptionKeyboardEvents.call(this, event);
432
- break;
433
- }
434
- };
435
-
436
- this.shadowRoot.addEventListener("keydown", this[keyEventHandler]);
437
- this.shadowRoot.addEventListener("click", this[openOptionEventHandler]);
386
+ if (!this.shadowRoot) {
387
+ throw new Error("no shadow-root is defined");
388
+ }
389
+
390
+ this[openOptionEventHandler] = (event) => {
391
+ const element = findTargetElementFromEvent(
392
+ event,
393
+ ATTRIBUTE_ROLE,
394
+ "folder-handler",
395
+ );
396
+ if (!(element instanceof HTMLElement)) {
397
+ return;
398
+ }
399
+
400
+ const container = findClosestByAttribute(element, ATTRIBUTE_ROLE, "option");
401
+ const index = container
402
+ .getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE)
403
+ .split("-")
404
+ .pop();
405
+
406
+ const currentState = this.getOption(`options.${index}.state`);
407
+
408
+ const newState = currentState === "close" ? "open" : "close";
409
+ this.setOption(`options.${index}.state`, newState);
410
+
411
+ const newVisibility = newState === "open" ? "visible" : "hidden";
412
+
413
+ if (container.hasAttribute(ATTRIBUTE_INTEND)) {
414
+ const intend = container.getAttribute(ATTRIBUTE_INTEND);
415
+
416
+ let ref = container.nextElementSibling;
417
+ const childIntend = parseInt(intend) + 1;
418
+
419
+ const cmp = (a, b) => {
420
+ if (newState === "open") {
421
+ return a === b;
422
+ }
423
+
424
+ return a >= b;
425
+ };
426
+
427
+ while (
428
+ ref?.hasAttribute(ATTRIBUTE_INTEND) &&
429
+ cmp(parseInt(ref.getAttribute(ATTRIBUTE_INTEND)), childIntend)
430
+ ) {
431
+ const refIndex = ref
432
+ .getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE)
433
+ .split("-")
434
+ .pop();
435
+ this.setOption(`options.${refIndex}.visibility`, newVisibility);
436
+
437
+ if (newState === "close") {
438
+ this.setOption(`options.${refIndex}.state`, "close");
439
+ }
440
+
441
+ ref = ref.nextElementSibling;
442
+ }
443
+ }
444
+ };
445
+
446
+ this[keyEventHandler] = (event) => {
447
+ const path = event.composedPath();
448
+ const element = path?.[0];
449
+
450
+ let role;
451
+
452
+ if (element instanceof HTMLElement) {
453
+ if (element.hasAttribute(ATTRIBUTE_ROLE)) {
454
+ role = element.getAttribute(ATTRIBUTE_ROLE);
455
+ } else if (element === this) {
456
+ show.call(this);
457
+ focusFilter.call(this);
458
+ } else {
459
+ const e = element.closest(`[${ATTRIBUTE_ROLE}]`);
460
+ if (e instanceof HTMLElement && e.hasAttribute()) {
461
+ role = e.getAttribute(ATTRIBUTE_ROLE);
462
+ }
463
+ }
464
+ } else {
465
+ return;
466
+ }
467
+
468
+ switch (role) {
469
+ case "option-label":
470
+ case "option-control":
471
+ case "option":
472
+ handleOptionKeyboardEvents.call(this, event);
473
+ break;
474
+ }
475
+ };
476
+
477
+ this.shadowRoot.addEventListener("keydown", this[keyEventHandler]);
478
+ this.shadowRoot.addEventListener("click", this[openOptionEventHandler]);
438
479
  }
439
480
 
440
481
  /**
@@ -448,14 +489,14 @@ function initEventHandler() {
448
489
  * @return {object}
449
490
  */
450
491
  function initOptionsFromArguments() {
451
- const options = {};
492
+ const options = {};
452
493
 
453
- const url = this.getAttribute(ATTRIBUTE_FORM_URL);
454
- if (isString(url)) {
455
- options["url"] = new URL(url).toString();
456
- }
494
+ const url = this.getAttribute(ATTRIBUTE_FORM_URL);
495
+ if (isString(url)) {
496
+ options["url"] = new URL(url).toString();
497
+ }
457
498
 
458
- return options;
499
+ return options;
459
500
  }
460
501
 
461
502
  /**
@@ -463,8 +504,8 @@ function initOptionsFromArguments() {
463
504
  * @return {string}
464
505
  */
465
506
  function getTemplate() {
466
- // language=HTML
467
- return `
507
+ // language=HTML
508
+ return `
468
509
  <template id="options">
469
510
  <div data-monster-role="option"
470
511
  tabindex="-1"
@@ -17,7 +17,7 @@ import { validateString } from "../types/validate.mjs";
17
17
  import { clone } from "../util/clone.mjs";
18
18
  import { DELIMITER, Pathfinder, WILDCARD } from "./pathfinder.mjs";
19
19
 
20
- export { buildMap, PARENT, assembleParts };
20
+ export { buildMap, PARENT, assembleParts};
21
21
 
22
22
  /**
23
23
  * @type {string}
@@ -361,7 +361,7 @@ function buildFlatMap(subject, selector, key, parentMap) {
361
361
  * @param {*} defaultValue
362
362
  * @return {*}
363
363
  */
364
- function build(subject, definition, defaultValue) {
364
+ export function build(subject, definition, defaultValue) {
365
365
  if (definition === undefined) return defaultValue ? defaultValue : subject;
366
366
  validateString(definition);
367
367
 
@@ -395,7 +395,7 @@ function retrieveAndSetValue(element) {
395
395
  element.constructor.prototype,
396
396
  "value",
397
397
  )?.["get"]) ||
398
- element.hasOwnProperty("value")
398
+ "value" in element
399
399
  ) {
400
400
  value = element?.["value"];
401
401
  } else {