@schukai/monster 3.107.0 → 3.108.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,38 +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
- import {addErrorAttribute} from "../../dom/error.mjs";
45
-
46
- 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 };
47
47
 
48
48
  /**
49
49
  * @private
@@ -73,201 +73,200 @@ const keyEventHandler = Symbol("keyEventHandler");
73
73
  * @fires monster-changed
74
74
  */
75
75
  class TreeSelect extends Select {
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
- }
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
+ const parts = filter.split(":");
175
+ parts.shift(); // remove prefix
176
+ const fkt = parts.shift();
177
+
178
+ switch (fkt) {
179
+ case "filterValueOfAttribute":
180
+ const attribute = parts.shift();
181
+ const attrValue = self.getAttribute(attribute);
182
+
183
+ filter = (m, v, k) => {
184
+ return m?.[id] != attrValue; // no type check, no !==
185
+ };
186
+ break;
187
+
188
+ default:
189
+ addErrorAttribute(
190
+ this,
191
+ 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
+ }
253
253
  }
254
254
 
255
-
256
255
  /**
257
256
  * @private
258
257
  * @param event
259
258
  */
260
259
  function handleOptionKeyboardEvents(event) {
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
- }
260
+ switch (event?.["code"]) {
261
+ case "ArrowLeft":
262
+ closeOrOpenCurrentOption.call(this, event, "close");
263
+ event.preventDefault();
264
+ break;
265
+ case "ArrowRight":
266
+ closeOrOpenCurrentOption.call(this, event, "open");
267
+ event.preventDefault();
268
+ break;
269
+ }
271
270
  }
272
271
 
273
272
  /**
@@ -275,24 +274,24 @@ function handleOptionKeyboardEvents(event) {
275
274
  * @param {event} event
276
275
  */
277
276
  function closeOrOpenCurrentOption(event, mode) {
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
- }
277
+ validateInstance(event, Event);
278
+
279
+ if (typeof event.composedPath !== "function") {
280
+ throw new Error("unsupported event");
281
+ }
282
+
283
+ const path = event.composedPath();
284
+ const optionNode = path.shift();
285
+
286
+ const state = optionNode.getAttribute("data-monster-state");
287
+ if (state !== mode) {
288
+ const handler = optionNode.querySelector(
289
+ "[data-monster-role=folder-handler]",
290
+ );
291
+ if (handler instanceof HTMLElement) {
292
+ fireEvent(handler, "click");
293
+ }
294
+ }
296
295
  }
297
296
 
298
297
  /**
@@ -302,25 +301,25 @@ function closeOrOpenCurrentOption(event, mode) {
302
301
  * @private
303
302
  */
304
303
  function formatKeyLabel(node) {
305
- validateInstance(node, Node);
304
+ validateInstance(node, Node);
306
305
 
307
- const v = node.value;
308
- if (v === undefined) {
309
- throw new Error("the object has no value for the specified id");
310
- }
306
+ const v = node.value;
307
+ if (v === undefined) {
308
+ throw new Error("the object has no value for the specified id");
309
+ }
311
310
 
312
- const label = new Formatter(v).format(
313
- this.getOption("mapping.labelTemplate", ""),
314
- );
311
+ const label = new Formatter(v).format(
312
+ this.getOption("mapping.labelTemplate", ""),
313
+ );
315
314
 
316
- const value = new Formatter(v).format(
317
- this.getOption("mapping.valueTemplate", ""),
318
- );
315
+ const value = new Formatter(v).format(
316
+ this.getOption("mapping.valueTemplate", ""),
317
+ );
319
318
 
320
- return {
321
- value,
322
- label,
323
- };
319
+ return {
320
+ value,
321
+ label,
322
+ };
324
323
  }
325
324
 
326
325
  /**
@@ -329,27 +328,27 @@ function formatKeyLabel(node) {
329
328
  * @return {Array}
330
329
  */
331
330
  function buildTreeLabels(value) {
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;
331
+ if (!this[internalNodesSymbol]) {
332
+ return [value];
333
+ }
334
+
335
+ let node = this[internalNodesSymbol].get(value);
336
+ if (node === undefined) {
337
+ node = this[internalNodesSymbol].get(parseInt(value));
338
+ }
339
+
340
+ const parts = [];
341
+
342
+ if (node instanceof Node) {
343
+ let ptr = node;
344
+ while (ptr) {
345
+ const formattedValues = formatKeyLabel.call(this, ptr);
346
+ parts.unshift(formattedValues.label);
347
+ ptr = ptr.parent;
348
+ }
349
+ }
350
+
351
+ return parts;
353
352
  }
354
353
 
355
354
  /**
@@ -367,9 +366,9 @@ function buildTreeLabels(value) {
367
366
  * @return {string}
368
367
  */
369
368
  function formatHierarchicalSelection(value) {
370
- return buildTreeLabels
371
- .call(this, value)
372
- .join(this.getOption("formatter.separator", " / "));
369
+ return buildTreeLabels
370
+ .call(this, value)
371
+ .join(this.getOption("formatter.separator", " / "));
373
372
  }
374
373
 
375
374
  /**
@@ -383,99 +382,99 @@ const openOptionEventHandler = Symbol("openOptionEventHandler");
383
382
  * @throws {Error} no shadow-root is defined
384
383
  */
385
384
  function initEventHandler() {
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]);
385
+ if (!this.shadowRoot) {
386
+ throw new Error("no shadow-root is defined");
387
+ }
388
+
389
+ this[openOptionEventHandler] = (event) => {
390
+ const element = findTargetElementFromEvent(
391
+ event,
392
+ ATTRIBUTE_ROLE,
393
+ "folder-handler",
394
+ );
395
+ if (!(element instanceof HTMLElement)) {
396
+ return;
397
+ }
398
+
399
+ const container = findClosestByAttribute(element, ATTRIBUTE_ROLE, "option");
400
+ const index = container
401
+ .getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE)
402
+ .split("-")
403
+ .pop();
404
+
405
+ const currentState = this.getOption(`options.${index}.state`);
406
+
407
+ const newState = currentState === "close" ? "open" : "close";
408
+ this.setOption(`options.${index}.state`, newState);
409
+
410
+ const newVisibility = newState === "open" ? "visible" : "hidden";
411
+
412
+ if (container.hasAttribute(ATTRIBUTE_INTEND)) {
413
+ const intend = container.getAttribute(ATTRIBUTE_INTEND);
414
+
415
+ let ref = container.nextElementSibling;
416
+ const childIntend = parseInt(intend) + 1;
417
+
418
+ const cmp = (a, b) => {
419
+ if (newState === "open") {
420
+ return a === b;
421
+ }
422
+
423
+ return a >= b;
424
+ };
425
+
426
+ while (
427
+ ref?.hasAttribute(ATTRIBUTE_INTEND) &&
428
+ cmp(parseInt(ref.getAttribute(ATTRIBUTE_INTEND)), childIntend)
429
+ ) {
430
+ const refIndex = ref
431
+ .getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE)
432
+ .split("-")
433
+ .pop();
434
+ this.setOption(`options.${refIndex}.visibility`, newVisibility);
435
+
436
+ if (newState === "close") {
437
+ this.setOption(`options.${refIndex}.state`, "close");
438
+ }
439
+
440
+ ref = ref.nextElementSibling;
441
+ }
442
+ }
443
+ };
444
+
445
+ this[keyEventHandler] = (event) => {
446
+ const path = event.composedPath();
447
+ const element = path?.[0];
448
+
449
+ let role;
450
+
451
+ if (element instanceof HTMLElement) {
452
+ if (element.hasAttribute(ATTRIBUTE_ROLE)) {
453
+ role = element.getAttribute(ATTRIBUTE_ROLE);
454
+ } else if (element === this) {
455
+ show.call(this);
456
+ focusFilter.call(this);
457
+ } else {
458
+ const e = element.closest(`[${ATTRIBUTE_ROLE}]`);
459
+ if (e instanceof HTMLElement && e.hasAttribute()) {
460
+ role = e.getAttribute(ATTRIBUTE_ROLE);
461
+ }
462
+ }
463
+ } else {
464
+ return;
465
+ }
466
+
467
+ switch (role) {
468
+ case "option-label":
469
+ case "option-control":
470
+ case "option":
471
+ handleOptionKeyboardEvents.call(this, event);
472
+ break;
473
+ }
474
+ };
475
+
476
+ this.shadowRoot.addEventListener("keydown", this[keyEventHandler]);
477
+ this.shadowRoot.addEventListener("click", this[openOptionEventHandler]);
479
478
  }
480
479
 
481
480
  /**
@@ -489,14 +488,14 @@ function initEventHandler() {
489
488
  * @return {object}
490
489
  */
491
490
  function initOptionsFromArguments() {
492
- const options = {};
491
+ const options = {};
493
492
 
494
- const url = this.getAttribute(ATTRIBUTE_FORM_URL);
495
- if (isString(url)) {
496
- options["url"] = new URL(url).toString();
497
- }
493
+ const url = this.getAttribute(ATTRIBUTE_FORM_URL);
494
+ if (isString(url)) {
495
+ options["url"] = new URL(url).toString();
496
+ }
498
497
 
499
- return options;
498
+ return options;
500
499
  }
501
500
 
502
501
  /**
@@ -504,8 +503,8 @@ function initOptionsFromArguments() {
504
503
  * @return {string}
505
504
  */
506
505
  function getTemplate() {
507
- // language=HTML
508
- return `
506
+ // language=HTML
507
+ return `
509
508
  <template id="options">
510
509
  <div data-monster-role="option"
511
510
  tabindex="-1"