@schukai/monster 4.137.9 → 4.139.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/package.json +1 -1
- package/source/components/form/button-bar.mjs +7 -1056
- package/source/components/form/control-bar.mjs +1437 -0
- package/source/components/form/style/button.pcss +10 -2
- package/source/components/form/style/control-bar.pcss +146 -0
- package/source/components/form/style/input-group.pcss +5 -0
- package/source/components/form/style/select.pcss +10 -5
- package/source/components/form/stylesheet/button.mjs +1 -1
- package/source/components/form/stylesheet/control-bar.mjs +38 -0
- package/source/components/form/stylesheet/input-group.mjs +1 -1
- package/source/components/form/stylesheet/select.mjs +1 -1
- package/source/monster.mjs +1 -0
- package/test/cases/components/form/button-bar.mjs +14 -0
- package/test/cases/components/form/button.mjs +11 -1
- package/test/cases/components/form/control-bar.mjs +410 -0
- package/test/cases/components/form/input-group.mjs +37 -0
- package/test/cases/components/form/select.mjs +12 -0
|
@@ -0,0 +1,1437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
|
|
3
|
+
* Node module: @schukai/monster
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
|
|
6
|
+
* The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
|
|
7
|
+
*
|
|
8
|
+
* For those who do not wish to adhere to the AGPLv3, a commercial license is available.
|
|
9
|
+
* Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
|
|
10
|
+
* For more information about purchasing a commercial license, please contact Volker Schukai.
|
|
11
|
+
*
|
|
12
|
+
* SPDX-License-Identifier: AGPL-3.0
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { instanceSymbol } from "../../constants.mjs";
|
|
16
|
+
import { Pathfinder } from "../../data/pathfinder.mjs";
|
|
17
|
+
import {
|
|
18
|
+
addAttributeToken,
|
|
19
|
+
removeAttributeToken,
|
|
20
|
+
} from "../../dom/attributes.mjs";
|
|
21
|
+
import {
|
|
22
|
+
ATTRIBUTE_ERRORMESSAGE,
|
|
23
|
+
ATTRIBUTE_ROLE,
|
|
24
|
+
} from "../../dom/constants.mjs";
|
|
25
|
+
import {
|
|
26
|
+
assembleMethodSymbol,
|
|
27
|
+
getSlottedElements,
|
|
28
|
+
registerCustomElement,
|
|
29
|
+
} from "../../dom/customelement.mjs";
|
|
30
|
+
import {
|
|
31
|
+
CustomElement,
|
|
32
|
+
attributeObserverSymbol,
|
|
33
|
+
} from "../../dom/customelement.mjs";
|
|
34
|
+
import { findTargetElementFromEvent } from "../../dom/events.mjs";
|
|
35
|
+
import { getDocument } from "../../dom/util.mjs";
|
|
36
|
+
import { getGlobal } from "../../types/global.mjs";
|
|
37
|
+
import { ID } from "../../types/id.mjs";
|
|
38
|
+
import { Observer } from "../../types/observer.mjs";
|
|
39
|
+
import { STYLE_DISPLAY_MODE_BLOCK } from "./constants.mjs";
|
|
40
|
+
import { ControlBarStyleSheet } from "./stylesheet/control-bar.mjs";
|
|
41
|
+
import { positionPopper } from "./util/floating-ui.mjs";
|
|
42
|
+
import { convertToPixels } from "../../dom/dimension.mjs";
|
|
43
|
+
import { addErrorAttribute } from "../../dom/error.mjs";
|
|
44
|
+
import { Processing } from "../../util/processing.mjs";
|
|
45
|
+
export { ControlBar };
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @private
|
|
49
|
+
* @type {symbol}
|
|
50
|
+
*/
|
|
51
|
+
/**
|
|
52
|
+
* local symbol
|
|
53
|
+
* @private
|
|
54
|
+
* @type {symbol}
|
|
55
|
+
*/
|
|
56
|
+
const resizeObserverSymbol = Symbol("windowResizeObserver");
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @private
|
|
60
|
+
* @type {symbol}
|
|
61
|
+
*/
|
|
62
|
+
const dimensionsSymbol = Symbol("dimensions");
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @private
|
|
66
|
+
* @type {symbol}
|
|
67
|
+
*/
|
|
68
|
+
const controlElementSymbol = Symbol("controlElement");
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @private
|
|
72
|
+
* @type {symbol}
|
|
73
|
+
*/
|
|
74
|
+
const controlBarSlotElementSymbol = Symbol("controlBarSlotElement");
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @private
|
|
78
|
+
* @type {symbol}
|
|
79
|
+
*/
|
|
80
|
+
const popperSlotElementSymbol = Symbol("popperSlotElement");
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @private
|
|
84
|
+
* @type {symbol}
|
|
85
|
+
*/
|
|
86
|
+
const controlBarElementSymbol = Symbol("controlBarElement");
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @private
|
|
90
|
+
* @type {symbol}
|
|
91
|
+
*/
|
|
92
|
+
const popperElementSymbol = Symbol("popperElement");
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* local symbol
|
|
96
|
+
* @private
|
|
97
|
+
* @type {symbol}
|
|
98
|
+
*/
|
|
99
|
+
const closeEventHandler = Symbol("closeEventHandler");
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @private
|
|
103
|
+
* @type {symbol}
|
|
104
|
+
*/
|
|
105
|
+
const popperSwitchEventHandler = Symbol("popperSwitchEventHandler");
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @private
|
|
109
|
+
* @type {symbol}
|
|
110
|
+
*/
|
|
111
|
+
const popperNavElementSymbol = Symbol("popperNavElement");
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @private
|
|
115
|
+
* @type {symbol}
|
|
116
|
+
*/
|
|
117
|
+
const mutationObserverSymbol = Symbol("mutationObserver");
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @private
|
|
121
|
+
* @type {symbol}
|
|
122
|
+
*/
|
|
123
|
+
const switchElementSymbol = Symbol("switchElement");
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @private
|
|
127
|
+
* @type {symbol}
|
|
128
|
+
*/
|
|
129
|
+
const layoutStateSymbol = Symbol("layoutState");
|
|
130
|
+
const layoutFrameSymbol = Symbol("layoutFrame");
|
|
131
|
+
const layoutTokenSymbol = Symbol("layoutToken");
|
|
132
|
+
const observedLayoutNodesSignatureSymbol = Symbol(
|
|
133
|
+
"observedLayoutNodesSignature",
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @private
|
|
138
|
+
* @type {WeakMap<HTMLElement, number>}
|
|
139
|
+
*/
|
|
140
|
+
const layoutNodeIds = new WeakMap();
|
|
141
|
+
let layoutNodeId = 0;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @private
|
|
145
|
+
* @type {string}
|
|
146
|
+
*/
|
|
147
|
+
const ATTRIBUTE_POPPER_POSITION = "data-monster-popper-position";
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @private
|
|
151
|
+
* @type {string}
|
|
152
|
+
*/
|
|
153
|
+
const ATTRIBUTE_LAYOUT_ALIGNMENT = "data-monster-layout-alignment";
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* A control bar control.
|
|
157
|
+
*
|
|
158
|
+
* @fragments /fragments/components/form/control-bar/
|
|
159
|
+
*
|
|
160
|
+
* @example /examples/components/form/control-bar-simple Control bar
|
|
161
|
+
*
|
|
162
|
+
* @copyright Volker Schukai
|
|
163
|
+
* @summary A responsive control bar that groups multiple controls and moves overflowed controls into a menu.
|
|
164
|
+
* @fires monster-fetched
|
|
165
|
+
*/
|
|
166
|
+
class ControlBar extends CustomElement {
|
|
167
|
+
/**
|
|
168
|
+
* This method is called by the `instanceof` operator.
|
|
169
|
+
* @return {symbol}
|
|
170
|
+
*/
|
|
171
|
+
static get [instanceSymbol]() {
|
|
172
|
+
return Symbol.for("@schukai/monster/components/form/control-bar@@instance");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* To set the options via the HTML tag, the attribute `data-monster-options` must be used.
|
|
177
|
+
* @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
|
|
178
|
+
*
|
|
179
|
+
* The individual configuration values can be found in the table.
|
|
180
|
+
*
|
|
181
|
+
* @property {Object} templates Template definitions
|
|
182
|
+
* @property {string} templates.main Main template
|
|
183
|
+
* @property {Object} labels
|
|
184
|
+
* @property {Object} popper FloatingUI popper configuration
|
|
185
|
+
* @property {string} popper.placement=top Placement of the popper
|
|
186
|
+
* @property {Array<string>} popper.middleware Middleware for the popper
|
|
187
|
+
*/
|
|
188
|
+
get defaults() {
|
|
189
|
+
const obj = Object.assign({}, super.defaults, {
|
|
190
|
+
templates: {
|
|
191
|
+
main: getTemplate(),
|
|
192
|
+
},
|
|
193
|
+
labels: {},
|
|
194
|
+
layout: {
|
|
195
|
+
alignment: "left",
|
|
196
|
+
},
|
|
197
|
+
popper: {
|
|
198
|
+
placement: "left",
|
|
199
|
+
middleware: ["flip", "shift", "offset:5"],
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
initDefaultsFromAttributes.call(this, obj);
|
|
204
|
+
|
|
205
|
+
return obj;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* This method is called internal and should not be called directly.
|
|
210
|
+
*/
|
|
211
|
+
[assembleMethodSymbol]() {
|
|
212
|
+
super[assembleMethodSymbol]();
|
|
213
|
+
|
|
214
|
+
this[dimensionsSymbol] = new Pathfinder({ data: {} });
|
|
215
|
+
this[layoutStateSymbol] = {
|
|
216
|
+
scheduled: false,
|
|
217
|
+
running: false,
|
|
218
|
+
needsMeasure: true,
|
|
219
|
+
needsLayout: true,
|
|
220
|
+
needsObserve: true,
|
|
221
|
+
suppressSlotChange: false,
|
|
222
|
+
suppressMutation: false,
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
initControlReferences.call(this);
|
|
226
|
+
initEventHandler.call(this);
|
|
227
|
+
|
|
228
|
+
// setup structure
|
|
229
|
+
initControlBar.call(this);
|
|
230
|
+
initPopperSwitch.call(this);
|
|
231
|
+
applyLayoutAlignment.call(this);
|
|
232
|
+
this.attachObserver(
|
|
233
|
+
new Observer(() => {
|
|
234
|
+
applyLayoutAlignment.call(this);
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* This method is called internal and should not be called directly.
|
|
241
|
+
*
|
|
242
|
+
* @return {CSSStyleSheet[]}
|
|
243
|
+
*/
|
|
244
|
+
static getCSSStyleSheet() {
|
|
245
|
+
return [ControlBarStyleSheet];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* This method is called internal and should not be called directly.
|
|
250
|
+
*
|
|
251
|
+
* @return {string}
|
|
252
|
+
*/
|
|
253
|
+
static getTag() {
|
|
254
|
+
return "monster-control-bar";
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* This method is called by the dom and should not be called directly.
|
|
259
|
+
*
|
|
260
|
+
* @return {void}
|
|
261
|
+
*/
|
|
262
|
+
connectedCallback() {
|
|
263
|
+
super.connectedCallback();
|
|
264
|
+
|
|
265
|
+
const document = getDocument();
|
|
266
|
+
|
|
267
|
+
for (const [, type] of Object.entries(["click", "touch"])) {
|
|
268
|
+
// close on outside ui-events
|
|
269
|
+
document.addEventListener(type, this[closeEventHandler]);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
scheduleLayout.call(this, { measure: true, layout: true, observe: true });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* This method determines which attributes are to be monitored by `attributeChangedCallback()`.
|
|
277
|
+
*
|
|
278
|
+
* @return {string[]}
|
|
279
|
+
*/
|
|
280
|
+
static get observedAttributes() {
|
|
281
|
+
const attributes = super.observedAttributes;
|
|
282
|
+
attributes.push(ATTRIBUTE_POPPER_POSITION);
|
|
283
|
+
return attributes;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* This method is called by the dom and should not be called directly.
|
|
288
|
+
*
|
|
289
|
+
* @return {void}
|
|
290
|
+
*/
|
|
291
|
+
disconnectedCallback() {
|
|
292
|
+
super.disconnectedCallback();
|
|
293
|
+
|
|
294
|
+
const document = getDocument();
|
|
295
|
+
|
|
296
|
+
// close on outside ui-events
|
|
297
|
+
for (const [, type] of Object.entries(["click", "touch"])) {
|
|
298
|
+
document.removeEventListener(type, this[closeEventHandler]);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (typeof this[layoutFrameSymbol] === "number") {
|
|
302
|
+
cancelAnimationFrame(this[layoutFrameSymbol]);
|
|
303
|
+
}
|
|
304
|
+
delete this[layoutFrameSymbol];
|
|
305
|
+
this[layoutTokenSymbol] = (this[layoutTokenSymbol] || 0) + 1;
|
|
306
|
+
|
|
307
|
+
disconnectResizeObserver.call(this);
|
|
308
|
+
if (this[mutationObserverSymbol]) {
|
|
309
|
+
this[mutationObserverSymbol].disconnect();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Close the slotted dialog.
|
|
315
|
+
* @return {ControlBar}
|
|
316
|
+
*/
|
|
317
|
+
hideDialog() {
|
|
318
|
+
hide.call(this);
|
|
319
|
+
return this;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Open the slotted dialog.
|
|
324
|
+
* @return {ControlBar}
|
|
325
|
+
*/
|
|
326
|
+
showDialog() {
|
|
327
|
+
show.call(this);
|
|
328
|
+
return this;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Toggle the slotted dialog.
|
|
333
|
+
* @return {ControlBar}
|
|
334
|
+
*/
|
|
335
|
+
toggleDialog() {
|
|
336
|
+
toggle.call(this);
|
|
337
|
+
return this;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* @private
|
|
343
|
+
* @param obj
|
|
344
|
+
* @return {*}
|
|
345
|
+
*/
|
|
346
|
+
function initDefaultsFromAttributes(obj) {
|
|
347
|
+
if (this.hasAttribute(ATTRIBUTE_POPPER_POSITION)) {
|
|
348
|
+
obj.popper.placement = this.getAttribute(ATTRIBUTE_POPPER_POSITION);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return obj;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* @private
|
|
356
|
+
* @param {HTMLElement} element
|
|
357
|
+
* @return {boolean}
|
|
358
|
+
*/
|
|
359
|
+
function isElementTrulyVisible(element) {
|
|
360
|
+
if (!(element instanceof HTMLElement)) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
const computedStyle = getComputedStyle(element);
|
|
364
|
+
return (
|
|
365
|
+
computedStyle.display !== "none" &&
|
|
366
|
+
computedStyle.visibility !== "hidden" &&
|
|
367
|
+
computedStyle.opacity !== "0" &&
|
|
368
|
+
element.offsetWidth > 0 &&
|
|
369
|
+
element.offsetHeight > 0
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* @private
|
|
375
|
+
* @param {HTMLElement} element
|
|
376
|
+
* @return {boolean}
|
|
377
|
+
*/
|
|
378
|
+
function isElementSelfHidden(element) {
|
|
379
|
+
if (!(element instanceof HTMLElement)) {
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const computedStyle = getComputedStyle(element);
|
|
384
|
+
return (
|
|
385
|
+
element.hidden === true ||
|
|
386
|
+
element.hasAttribute("hidden") ||
|
|
387
|
+
computedStyle.display === "none" ||
|
|
388
|
+
computedStyle.visibility === "hidden"
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* @private
|
|
394
|
+
*/
|
|
395
|
+
function initEventHandler() {
|
|
396
|
+
const self = this;
|
|
397
|
+
|
|
398
|
+
const mutationCallback = (mutationList) => {
|
|
399
|
+
if (self[layoutStateSymbol]?.suppressMutation) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
let needsRecalc = false;
|
|
404
|
+
for (const mutation of mutationList) {
|
|
405
|
+
if (mutation.type === "attributes") {
|
|
406
|
+
const target = mutation.target;
|
|
407
|
+
if (target instanceof HTMLElement) {
|
|
408
|
+
const ref = target.getAttribute("data-monster-reference");
|
|
409
|
+
if (ref) {
|
|
410
|
+
if (!isElementTrulyVisible(target)) {
|
|
411
|
+
self[dimensionsSymbol].setVia(`data.item.${ref}`, 0);
|
|
412
|
+
}
|
|
413
|
+
needsRecalc = true;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (needsRecalc) {
|
|
419
|
+
scheduleLayout.call(self, { measure: true, layout: true });
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* @param {Event} event
|
|
425
|
+
*/
|
|
426
|
+
self[closeEventHandler] = (event) => {
|
|
427
|
+
const path = event.composedPath();
|
|
428
|
+
for (const element of path) {
|
|
429
|
+
if (element === self) return;
|
|
430
|
+
}
|
|
431
|
+
hide.call(self);
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
if (self[controlBarSlotElementSymbol]) {
|
|
435
|
+
self[controlBarSlotElementSymbol].addEventListener("slotchange", () => {
|
|
436
|
+
if (self[layoutStateSymbol]?.suppressSlotChange) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
scheduleLayout.call(self, {
|
|
440
|
+
measure: true,
|
|
441
|
+
layout: true,
|
|
442
|
+
observe: true,
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (self[popperElementSymbol]) {
|
|
448
|
+
self[popperElementSymbol].addEventListener("slotchange", () => {
|
|
449
|
+
if (self[layoutStateSymbol]?.suppressSlotChange) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
scheduleLayout.call(self, {
|
|
453
|
+
measure: true,
|
|
454
|
+
layout: true,
|
|
455
|
+
observe: true,
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
self[attributeObserverSymbol][ATTRIBUTE_POPPER_POSITION] = function (value) {
|
|
461
|
+
self.setOption("popper.placement", value);
|
|
462
|
+
updatePopper.call(self);
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
self[resizeObserverSymbol] = new ResizeObserver(() => {
|
|
466
|
+
scheduleLayout.call(self, { measure: true, layout: true });
|
|
467
|
+
});
|
|
468
|
+
self[mutationObserverSymbol] = new MutationObserver(mutationCallback);
|
|
469
|
+
|
|
470
|
+
initSlotChangedHandler.call(self);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function initSlotChangedHandler() {
|
|
474
|
+
this[controlBarElementSymbol].addEventListener("slotchange", () => {
|
|
475
|
+
if (this[layoutStateSymbol]?.suppressSlotChange) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
scheduleLayout.call(this, { observe: true });
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function scheduleLayout(options = {}) {
|
|
483
|
+
if (!this[layoutStateSymbol]) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const state = this[layoutStateSymbol];
|
|
488
|
+
state.needsMeasure = state.needsMeasure || options.measure === true;
|
|
489
|
+
state.needsLayout = state.needsLayout || options.layout === true;
|
|
490
|
+
state.needsObserve = state.needsObserve || options.observe === true;
|
|
491
|
+
|
|
492
|
+
if (state.scheduled || state.running) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
scheduleLayoutFrame.call(this);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function scheduleLayoutFrame() {
|
|
500
|
+
const state = this[layoutStateSymbol];
|
|
501
|
+
if (!state || state.scheduled || state.running) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
state.scheduled = true;
|
|
506
|
+
const token = (this[layoutTokenSymbol] || 0) + 1;
|
|
507
|
+
this[layoutTokenSymbol] = token;
|
|
508
|
+
this[layoutFrameSymbol] = requestAnimationFrame(() => {
|
|
509
|
+
if (this[layoutTokenSymbol] !== token) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
delete this[layoutFrameSymbol];
|
|
513
|
+
runLayout.call(this);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function runLayout() {
|
|
518
|
+
const state = this[layoutStateSymbol];
|
|
519
|
+
if (!state) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
state.scheduled = false;
|
|
524
|
+
if (!this.isConnected) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (state.running) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const needsObserve = state.needsObserve;
|
|
533
|
+
const needsMeasure = state.needsMeasure;
|
|
534
|
+
const needsLayout = state.needsLayout;
|
|
535
|
+
|
|
536
|
+
state.needsObserve = false;
|
|
537
|
+
state.needsMeasure = false;
|
|
538
|
+
state.needsLayout = false;
|
|
539
|
+
state.running = true;
|
|
540
|
+
|
|
541
|
+
new Processing(() => {
|
|
542
|
+
if (needsObserve) {
|
|
543
|
+
updateResizeObserverObservation.call(this);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (needsMeasure) {
|
|
547
|
+
try {
|
|
548
|
+
calculateControlBarDimensions.call(this);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
addErrorAttribute(
|
|
551
|
+
this,
|
|
552
|
+
error?.message || "An error occurred while calculating dimensions",
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (needsLayout) {
|
|
558
|
+
try {
|
|
559
|
+
rearrangeItems.call(this);
|
|
560
|
+
} catch (error) {
|
|
561
|
+
addErrorAttribute(
|
|
562
|
+
this,
|
|
563
|
+
error?.message || "An error occurred while rearranging the items",
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return updatePopper.call(this);
|
|
569
|
+
})
|
|
570
|
+
.run()
|
|
571
|
+
.catch((error) => {
|
|
572
|
+
addErrorAttribute(
|
|
573
|
+
this,
|
|
574
|
+
error?.message ||
|
|
575
|
+
"An error occurred while running the control bar layout",
|
|
576
|
+
);
|
|
577
|
+
})
|
|
578
|
+
.finally(() => {
|
|
579
|
+
state.running = false;
|
|
580
|
+
if (state.needsObserve || state.needsMeasure || state.needsLayout) {
|
|
581
|
+
scheduleLayoutFrame.call(this);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* @private
|
|
588
|
+
* @return {Object}
|
|
589
|
+
*/
|
|
590
|
+
function rearrangeItems() {
|
|
591
|
+
let space = 0;
|
|
592
|
+
try {
|
|
593
|
+
space = this[dimensionsSymbol].getVia("data.space");
|
|
594
|
+
} catch {}
|
|
595
|
+
|
|
596
|
+
const itemReferences = this[dimensionsSymbol].getVia(
|
|
597
|
+
"data.itemReferences",
|
|
598
|
+
[],
|
|
599
|
+
);
|
|
600
|
+
const hasItems = itemReferences.length > 0;
|
|
601
|
+
|
|
602
|
+
const itemEntries = [];
|
|
603
|
+
|
|
604
|
+
for (const ref of itemReferences) {
|
|
605
|
+
let elements = getSlottedElements.call(
|
|
606
|
+
this,
|
|
607
|
+
'[data-monster-reference="' + ref + '"]',
|
|
608
|
+
null,
|
|
609
|
+
); // null ↦ o
|
|
610
|
+
if (elements.size === 0) {
|
|
611
|
+
elements = getSlottedElements.call(
|
|
612
|
+
this,
|
|
613
|
+
'[data-monster-reference="' + ref + '"]',
|
|
614
|
+
"popper",
|
|
615
|
+
); // null ↦ o
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const nextValue = elements.values().next();
|
|
619
|
+
if (!nextValue) {
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const element = nextValue?.value;
|
|
624
|
+
if (!(element instanceof HTMLElement)) {
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
let itemWidth = 0;
|
|
629
|
+
try {
|
|
630
|
+
itemWidth = this[dimensionsSymbol].getVia(`data.item.${ref}`);
|
|
631
|
+
} catch (e) {
|
|
632
|
+
// If the path does not exist, pathfinder throws an error.
|
|
633
|
+
// In this case, we assume the width is 0.
|
|
634
|
+
// This can happen for items that have never been visible.
|
|
635
|
+
}
|
|
636
|
+
itemEntries.push({
|
|
637
|
+
element,
|
|
638
|
+
width: itemWidth,
|
|
639
|
+
hidden: isElementSelfHidden(element),
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const switchWidth = this[dimensionsSymbol].getVia("data.switchWidth") || 2;
|
|
644
|
+
|
|
645
|
+
const layoutItems = (availableSpace) => {
|
|
646
|
+
if (availableSpace < 0) {
|
|
647
|
+
availableSpace = 0;
|
|
648
|
+
}
|
|
649
|
+
let sum = 0;
|
|
650
|
+
const visibleItemsInMainSlot = [];
|
|
651
|
+
const itemsToMoveToPopper = [];
|
|
652
|
+
|
|
653
|
+
for (const entry of itemEntries) {
|
|
654
|
+
if (entry.hidden) {
|
|
655
|
+
visibleItemsInMainSlot.push(entry.element);
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (sum + entry.width > availableSpace) {
|
|
660
|
+
itemsToMoveToPopper.push(entry.element);
|
|
661
|
+
} else {
|
|
662
|
+
sum += entry.width;
|
|
663
|
+
visibleItemsInMainSlot.push(entry.element);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
return { visibleItemsInMainSlot, itemsToMoveToPopper };
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
let layout = layoutItems(space);
|
|
671
|
+
if (layout.itemsToMoveToPopper.length > 0) {
|
|
672
|
+
layout = layoutItems(space - switchWidth);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const shouldShowSwitch =
|
|
676
|
+
layout.itemsToMoveToPopper.length > 0 && hasItems;
|
|
677
|
+
|
|
678
|
+
suppressLayoutFeedback.call(this);
|
|
679
|
+
|
|
680
|
+
for (const item of layout.itemsToMoveToPopper) {
|
|
681
|
+
if (item.getAttribute("slot") !== "popper") {
|
|
682
|
+
item.setAttribute("slot", "popper");
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
for (const item of layout.visibleItemsInMainSlot) {
|
|
687
|
+
if (item.hasAttribute("slot")) {
|
|
688
|
+
item.removeAttribute("slot");
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
setSwitchVisible.call(this, shouldShowSwitch);
|
|
693
|
+
updateControlSizing.call(this, layout, shouldShowSwitch);
|
|
694
|
+
updateJoinedBorders.call(this, layout, shouldShowSwitch);
|
|
695
|
+
if (!shouldShowSwitch) {
|
|
696
|
+
hide.call(this);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* @private
|
|
702
|
+
* @param {HTMLElement} node
|
|
703
|
+
* @return {number}
|
|
704
|
+
*/
|
|
705
|
+
function calcBoxWidth(node) {
|
|
706
|
+
const dim = getGlobal()?.getComputedStyle(node);
|
|
707
|
+
if (dim === null) {
|
|
708
|
+
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "no computed style");
|
|
709
|
+
throw new Error("no computed style");
|
|
710
|
+
}
|
|
711
|
+
const bounding = node.getBoundingClientRect();
|
|
712
|
+
|
|
713
|
+
return (
|
|
714
|
+
getComputedCssPixels(dim["border-left-width"]) +
|
|
715
|
+
getComputedCssPixels(dim["padding-left"]) +
|
|
716
|
+
getComputedCssPixels(dim["margin-left"]) +
|
|
717
|
+
bounding["width"] +
|
|
718
|
+
getComputedCssPixels(dim["border-right-width"]) +
|
|
719
|
+
getComputedCssPixels(dim["margin-right"]) +
|
|
720
|
+
getComputedCssPixels(dim["padding-right"])
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* @private
|
|
726
|
+
* @param {string} value
|
|
727
|
+
* @return {number}
|
|
728
|
+
*/
|
|
729
|
+
function getComputedCssPixels(value) {
|
|
730
|
+
if (typeof value !== "string" || value === "") {
|
|
731
|
+
return 0;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (value.endsWith("px")) {
|
|
735
|
+
return parseFloat(value) || 0;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
try {
|
|
739
|
+
return convertToPixels(value);
|
|
740
|
+
} catch {
|
|
741
|
+
return 0;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* @private
|
|
747
|
+
* @param {Object} layout
|
|
748
|
+
* @param {boolean} shouldShowSwitch
|
|
749
|
+
* @return {void}
|
|
750
|
+
*/
|
|
751
|
+
function updateControlSizing(layout, shouldShowSwitch) {
|
|
752
|
+
const mainItems = [...layout.visibleItemsInMainSlot];
|
|
753
|
+
if (
|
|
754
|
+
shouldShowSwitch &&
|
|
755
|
+
this[popperNavElementSymbol] instanceof HTMLElement
|
|
756
|
+
) {
|
|
757
|
+
mainItems.push(this[popperNavElementSymbol]);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const controlItems = [
|
|
761
|
+
...mainItems,
|
|
762
|
+
...layout.itemsToMoveToPopper,
|
|
763
|
+
];
|
|
764
|
+
for (const element of controlItems) {
|
|
765
|
+
applyControlSizing.call(this, element);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* @private
|
|
771
|
+
* @param {HTMLElement} element
|
|
772
|
+
* @return {void}
|
|
773
|
+
*/
|
|
774
|
+
function applyControlSizing(element) {
|
|
775
|
+
setStylePropertyIfChanged(element, "boxSizing", "border-box");
|
|
776
|
+
setStylePropertyIfChanged(
|
|
777
|
+
element,
|
|
778
|
+
"height",
|
|
779
|
+
"var(--monster-control-bar-height)",
|
|
780
|
+
);
|
|
781
|
+
setStylePropertyIfChanged(
|
|
782
|
+
element,
|
|
783
|
+
"minHeight",
|
|
784
|
+
"var(--monster-control-bar-height)",
|
|
785
|
+
);
|
|
786
|
+
if (isWrapperElement(element)) {
|
|
787
|
+
setStylePropertyIfChanged(element, "display", "flex");
|
|
788
|
+
setStylePropertyIfChanged(element, "alignItems", "stretch");
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
for (const control of getContainedControlElements(element)) {
|
|
792
|
+
setStylePropertyIfChanged(control, "display", "flex");
|
|
793
|
+
setStylePropertyIfChanged(control, "boxSizing", "border-box");
|
|
794
|
+
setStylePropertyIfChanged(control, "height", "100%");
|
|
795
|
+
setStylePropertyIfChanged(control, "minHeight", "0px");
|
|
796
|
+
if (control.tagName === "MONSTER-INPUT-GROUP") {
|
|
797
|
+
setStylePropertyIfChanged(control, "flex", "1 1 auto");
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* @private
|
|
804
|
+
* @param {HTMLElement} element
|
|
805
|
+
* @return {boolean}
|
|
806
|
+
*/
|
|
807
|
+
function isWrapperElement(element) {
|
|
808
|
+
return ["DIV", "FORM", "SECTION"].includes(element.tagName);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* @private
|
|
813
|
+
* @param {HTMLElement} element
|
|
814
|
+
* @return {HTMLElement[]}
|
|
815
|
+
*/
|
|
816
|
+
function getContainedControlElements(element) {
|
|
817
|
+
if (!isWrapperElement(element)) {
|
|
818
|
+
return [];
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
return Array.from(
|
|
822
|
+
element.querySelectorAll(
|
|
823
|
+
[
|
|
824
|
+
"monster-input-group",
|
|
825
|
+
"monster-select",
|
|
826
|
+
"monster-button",
|
|
827
|
+
"monster-state-button",
|
|
828
|
+
"monster-message-state-button",
|
|
829
|
+
"monster-action-button",
|
|
830
|
+
"monster-api-button",
|
|
831
|
+
"monster-confirm-button",
|
|
832
|
+
].join(","),
|
|
833
|
+
),
|
|
834
|
+
).filter((control) => control instanceof HTMLElement);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* @private
|
|
839
|
+
* @param {Object} layout
|
|
840
|
+
* @param {boolean} shouldShowSwitch
|
|
841
|
+
* @return {void}
|
|
842
|
+
*/
|
|
843
|
+
function updateJoinedBorders(layout, shouldShowSwitch) {
|
|
844
|
+
const mainItems = [...layout.visibleItemsInMainSlot];
|
|
845
|
+
if (
|
|
846
|
+
shouldShowSwitch &&
|
|
847
|
+
this[popperNavElementSymbol] instanceof HTMLElement &&
|
|
848
|
+
this[switchElementSymbol] instanceof HTMLElement
|
|
849
|
+
) {
|
|
850
|
+
mainItems.push(this[popperNavElementSymbol]);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const marginLeftByElement = new Map();
|
|
854
|
+
const marginTopByElement = new Map();
|
|
855
|
+
|
|
856
|
+
collectInlineJoinedBorders.call(this, mainItems, marginLeftByElement);
|
|
857
|
+
collectBlockJoinedBorders.call(
|
|
858
|
+
this,
|
|
859
|
+
layout.itemsToMoveToPopper,
|
|
860
|
+
marginTopByElement,
|
|
861
|
+
);
|
|
862
|
+
applyJoinedBorderOffsets.call(this, marginLeftByElement, marginTopByElement);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* @private
|
|
867
|
+
* @param {HTMLElement[]} elements
|
|
868
|
+
* @param {Map<HTMLElement, string>} marginLeftByElement
|
|
869
|
+
* @return {void}
|
|
870
|
+
*/
|
|
871
|
+
function collectInlineJoinedBorders(elements, marginLeftByElement) {
|
|
872
|
+
for (let i = 1; i < elements.length; i++) {
|
|
873
|
+
const previous = elements[i - 1];
|
|
874
|
+
const current = elements[i];
|
|
875
|
+
const overlap = getBorderOverlap(
|
|
876
|
+
getVisualBorderWidth.call(this, previous, "right"),
|
|
877
|
+
getVisualBorderWidth.call(this, current, "left"),
|
|
878
|
+
);
|
|
879
|
+
|
|
880
|
+
if (overlap > 0) {
|
|
881
|
+
marginLeftByElement.set(current, `${-1 * overlap}px`);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* @private
|
|
888
|
+
* @param {HTMLElement[]} elements
|
|
889
|
+
* @param {Map<HTMLElement, string>} marginTopByElement
|
|
890
|
+
* @return {void}
|
|
891
|
+
*/
|
|
892
|
+
function collectBlockJoinedBorders(elements, marginTopByElement) {
|
|
893
|
+
for (let i = 1; i < elements.length; i++) {
|
|
894
|
+
const previous = elements[i - 1];
|
|
895
|
+
const current = elements[i];
|
|
896
|
+
const overlap = getBorderOverlap(
|
|
897
|
+
getVisualBorderWidth.call(this, previous, "bottom"),
|
|
898
|
+
getVisualBorderWidth.call(this, current, "top"),
|
|
899
|
+
);
|
|
900
|
+
|
|
901
|
+
if (overlap > 0) {
|
|
902
|
+
marginTopByElement.set(current, `${-1 * overlap}px`);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* @private
|
|
909
|
+
* @param {Map<HTMLElement, string>} marginLeftByElement
|
|
910
|
+
* @param {Map<HTMLElement, string>} marginTopByElement
|
|
911
|
+
* @return {void}
|
|
912
|
+
*/
|
|
913
|
+
function applyJoinedBorderOffsets(marginLeftByElement, marginTopByElement) {
|
|
914
|
+
for (const element of getJoinedBorderOffsetElements.call(this)) {
|
|
915
|
+
setStylePropertyIfChanged(
|
|
916
|
+
element,
|
|
917
|
+
"marginLeft",
|
|
918
|
+
marginLeftByElement.get(element) || "",
|
|
919
|
+
);
|
|
920
|
+
setStylePropertyIfChanged(
|
|
921
|
+
element,
|
|
922
|
+
"marginTop",
|
|
923
|
+
marginTopByElement.get(element) || "",
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* @private
|
|
930
|
+
* @return {HTMLElement[]}
|
|
931
|
+
*/
|
|
932
|
+
function getJoinedBorderOffsetElements() {
|
|
933
|
+
const elements = Array.from(this.children).filter(
|
|
934
|
+
(element) => element instanceof HTMLElement,
|
|
935
|
+
);
|
|
936
|
+
if (this[popperNavElementSymbol] instanceof HTMLElement) {
|
|
937
|
+
elements.push(this[popperNavElementSymbol]);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
return elements;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* @private
|
|
945
|
+
* @param {HTMLElement} element
|
|
946
|
+
* @param {"marginLeft"|"marginTop"} property
|
|
947
|
+
* @param {string} value
|
|
948
|
+
* @return {void}
|
|
949
|
+
*/
|
|
950
|
+
function setStylePropertyIfChanged(element, property, value) {
|
|
951
|
+
if (element.style[property] !== value) {
|
|
952
|
+
element.style[property] = value;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* @private
|
|
958
|
+
* @param {number} previousBorderWidth
|
|
959
|
+
* @param {number} currentBorderWidth
|
|
960
|
+
* @return {number}
|
|
961
|
+
*/
|
|
962
|
+
function getBorderOverlap(previousBorderWidth, currentBorderWidth) {
|
|
963
|
+
return Math.min(previousBorderWidth, currentBorderWidth);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* @private
|
|
968
|
+
* @param {HTMLElement} element
|
|
969
|
+
* @param {"top"|"right"|"bottom"|"left"} side
|
|
970
|
+
* @return {number}
|
|
971
|
+
*/
|
|
972
|
+
function getVisualBorderWidth(element, side) {
|
|
973
|
+
const visualElement = getVisualBorderElement.call(this, element, side);
|
|
974
|
+
const computedStyle = getComputedStyle(visualElement);
|
|
975
|
+
const value = computedStyle.getPropertyValue(`border-${side}-width`);
|
|
976
|
+
|
|
977
|
+
return getComputedCssPixels(value);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* @private
|
|
982
|
+
* @param {HTMLElement} element
|
|
983
|
+
* @param {"top"|"right"|"bottom"|"left"} side
|
|
984
|
+
* @return {HTMLElement}
|
|
985
|
+
*/
|
|
986
|
+
function getVisualBorderElement(element, side) {
|
|
987
|
+
if (element === this[popperNavElementSymbol]) {
|
|
988
|
+
return this[switchElementSymbol] || element;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const selector = [
|
|
992
|
+
"button",
|
|
993
|
+
"input",
|
|
994
|
+
"select",
|
|
995
|
+
"textarea",
|
|
996
|
+
"monster-input-group",
|
|
997
|
+
"monster-select",
|
|
998
|
+
"[data-monster-role=control]",
|
|
999
|
+
].join(",");
|
|
1000
|
+
if (element.shadowRoot instanceof ShadowRoot) {
|
|
1001
|
+
return element.shadowRoot.querySelector(selector) || element;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const candidates = Array.from(element.querySelectorAll(selector)).filter(
|
|
1005
|
+
(candidate) => candidate instanceof HTMLElement,
|
|
1006
|
+
);
|
|
1007
|
+
if (candidates.length === 0) {
|
|
1008
|
+
return element;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
if (side === "right" || side === "bottom") {
|
|
1012
|
+
return candidates[candidates.length - 1];
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return candidates[0];
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* @private
|
|
1020
|
+
* @return {Object}
|
|
1021
|
+
*/
|
|
1022
|
+
function calculateControlBarDimensions() {
|
|
1023
|
+
if (!(this.parentElement instanceof HTMLElement)) {
|
|
1024
|
+
this[dimensionsSymbol].setVia("data.space", 0);
|
|
1025
|
+
this[dimensionsSymbol].setVia("data.visible", false);
|
|
1026
|
+
this[dimensionsSymbol].setVia("data.calculated", true);
|
|
1027
|
+
this[dimensionsSymbol].setVia("data.itemReferences", []);
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
const computedStyle = getComputedStyle(this.parentElement);
|
|
1032
|
+
|
|
1033
|
+
if (computedStyle === null) {
|
|
1034
|
+
throw new Error("no computed style");
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
let width = this.parentElement.clientWidth;
|
|
1038
|
+
if (computedStyle.getPropertyValue("box-sizing") !== "border-box") {
|
|
1039
|
+
width = computedStyle.getPropertyValue("width");
|
|
1040
|
+
|
|
1041
|
+
const pixel = getComputedCssPixels(width);
|
|
1042
|
+
|
|
1043
|
+
this[dimensionsSymbol].setVia("data.space", pixel);
|
|
1044
|
+
} else {
|
|
1045
|
+
let borderWidth = getComputedStyle(this).getPropertyValue(
|
|
1046
|
+
"--monster-border-width",
|
|
1047
|
+
);
|
|
1048
|
+
if (borderWidth === null || borderWidth === "") {
|
|
1049
|
+
borderWidth = "0px";
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const borderWidthWithoutUnit = getComputedCssPixels(borderWidth);
|
|
1053
|
+
|
|
1054
|
+
// space to be allocated
|
|
1055
|
+
this[dimensionsSymbol].setVia(
|
|
1056
|
+
"data.space",
|
|
1057
|
+
width - 2 * borderWidthWithoutUnit,
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
this[dimensionsSymbol].setVia("data.visible", !(width === 0));
|
|
1062
|
+
|
|
1063
|
+
const itemReferences = [];
|
|
1064
|
+
|
|
1065
|
+
// Get all direct controls, regardless of their current slot.
|
|
1066
|
+
const combinedItems = Array.from(this.children).filter(
|
|
1067
|
+
(item, index, self) => {
|
|
1068
|
+
if (!(item instanceof HTMLElement)) {
|
|
1069
|
+
return false;
|
|
1070
|
+
}
|
|
1071
|
+
if (item.slot && item.slot !== "popper") {
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
// Filter out duplicates based on data-monster-reference if present, or element itself
|
|
1075
|
+
return (
|
|
1076
|
+
self.findIndex((other) => {
|
|
1077
|
+
if (!(other instanceof HTMLElement)) {
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1080
|
+
const itemReference = item.dataset.monsterReference;
|
|
1081
|
+
const otherReference = other.dataset.monsterReference;
|
|
1082
|
+
if (itemReference && otherReference) {
|
|
1083
|
+
return itemReference === otherReference;
|
|
1084
|
+
}
|
|
1085
|
+
return other === item;
|
|
1086
|
+
}) === index
|
|
1087
|
+
);
|
|
1088
|
+
},
|
|
1089
|
+
);
|
|
1090
|
+
|
|
1091
|
+
for (const item of combinedItems) {
|
|
1092
|
+
if (!(item instanceof HTMLElement)) {
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (!item.hasAttribute("data-monster-reference")) {
|
|
1097
|
+
item.setAttribute("data-monster-reference", new ID("ctrl").toString());
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
const ref = item.getAttribute("data-monster-reference");
|
|
1101
|
+
if (ref === null) continue;
|
|
1102
|
+
|
|
1103
|
+
itemReferences.push(ref);
|
|
1104
|
+
|
|
1105
|
+
// Only calculate width for visible items. Assume invisible ones
|
|
1106
|
+
// (e.g. in popper) have their width calculated previously and stored.
|
|
1107
|
+
if (isElementTrulyVisible(item)) {
|
|
1108
|
+
this[dimensionsSymbol].setVia(
|
|
1109
|
+
`data.item.${ref}`,
|
|
1110
|
+
calcBoxWidth.call(this, item),
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
if (this[switchElementSymbol]) {
|
|
1116
|
+
this[dimensionsSymbol].setVia(
|
|
1117
|
+
"data.switchWidth",
|
|
1118
|
+
this[switchElementSymbol].offsetWidth,
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
this[dimensionsSymbol].setVia("data.calculated", true);
|
|
1123
|
+
this[dimensionsSymbol].setVia("data.itemReferences", itemReferences);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* @private
|
|
1128
|
+
*/
|
|
1129
|
+
function updateResizeObserverObservation() {
|
|
1130
|
+
const observedNodes = getLayoutObservedNodes.call(this);
|
|
1131
|
+
const signature = getLayoutObservedNodesSignature(observedNodes);
|
|
1132
|
+
if (this[observedLayoutNodesSignatureSymbol] === signature) {
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
this[observedLayoutNodesSignatureSymbol] = signature;
|
|
1136
|
+
|
|
1137
|
+
this[resizeObserverSymbol].disconnect();
|
|
1138
|
+
if (this[mutationObserverSymbol]) {
|
|
1139
|
+
this[mutationObserverSymbol].disconnect();
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
observedNodes.forEach((node) => {
|
|
1143
|
+
this[resizeObserverSymbol].observe(node);
|
|
1144
|
+
if (node !== this.parentElement && this[mutationObserverSymbol]) {
|
|
1145
|
+
this[mutationObserverSymbol].observe(node, {
|
|
1146
|
+
attributes: true,
|
|
1147
|
+
attributeFilter: ["style", "class", "hidden"],
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/**
|
|
1154
|
+
* @private
|
|
1155
|
+
* @return {HTMLElement[]}
|
|
1156
|
+
*/
|
|
1157
|
+
function getLayoutObservedNodes() {
|
|
1158
|
+
const observedNodes = [];
|
|
1159
|
+
Array.from(this.children).forEach((node) => {
|
|
1160
|
+
if (node instanceof HTMLElement) {
|
|
1161
|
+
observedNodes.push(node);
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
let parent = this.parentNode;
|
|
1166
|
+
while (!(parent instanceof HTMLElement) && parent !== null) {
|
|
1167
|
+
parent = parent.parentNode;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
if (parent instanceof HTMLElement) {
|
|
1171
|
+
observedNodes.push(parent);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
return observedNodes;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* @private
|
|
1179
|
+
* @param {HTMLElement[]} nodes
|
|
1180
|
+
* @return {string}
|
|
1181
|
+
*/
|
|
1182
|
+
function getLayoutObservedNodesSignature(nodes) {
|
|
1183
|
+
return nodes.map(getLayoutNodeId).join("|");
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* @private
|
|
1188
|
+
* @param {HTMLElement} node
|
|
1189
|
+
* @return {number}
|
|
1190
|
+
*/
|
|
1191
|
+
function getLayoutNodeId(node) {
|
|
1192
|
+
let id = layoutNodeIds.get(node);
|
|
1193
|
+
if (id === undefined) {
|
|
1194
|
+
id = ++layoutNodeId;
|
|
1195
|
+
layoutNodeIds.set(node, id);
|
|
1196
|
+
}
|
|
1197
|
+
return id;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* @private
|
|
1202
|
+
* @return {void}
|
|
1203
|
+
*/
|
|
1204
|
+
function suppressLayoutFeedback() {
|
|
1205
|
+
const state = this[layoutStateSymbol];
|
|
1206
|
+
if (!state) {
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
state.suppressSlotChange = true;
|
|
1211
|
+
state.suppressMutation = true;
|
|
1212
|
+
queueMicrotask(() => {
|
|
1213
|
+
state.suppressSlotChange = false;
|
|
1214
|
+
state.suppressMutation = false;
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* @private
|
|
1220
|
+
* @param {boolean} visible
|
|
1221
|
+
* @return {void}
|
|
1222
|
+
*/
|
|
1223
|
+
function setSwitchVisible(visible) {
|
|
1224
|
+
if (!(this[switchElementSymbol] instanceof HTMLElement)) {
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
if (visible) {
|
|
1229
|
+
if (this[switchElementSymbol].hasAttribute("hidden")) {
|
|
1230
|
+
this[switchElementSymbol].removeAttribute("hidden");
|
|
1231
|
+
}
|
|
1232
|
+
if (this[switchElementSymbol].classList.contains("hidden")) {
|
|
1233
|
+
this[switchElementSymbol].classList.remove("hidden");
|
|
1234
|
+
}
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
if (!this[switchElementSymbol].hasAttribute("hidden")) {
|
|
1239
|
+
this[switchElementSymbol].setAttribute("hidden", "");
|
|
1240
|
+
}
|
|
1241
|
+
if (!this[switchElementSymbol].classList.contains("hidden")) {
|
|
1242
|
+
this[switchElementSymbol].classList.add("hidden");
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
/**
|
|
1247
|
+
* @private
|
|
1248
|
+
*/
|
|
1249
|
+
function disconnectResizeObserver() {
|
|
1250
|
+
if (this[resizeObserverSymbol] instanceof ResizeObserver) {
|
|
1251
|
+
this[resizeObserverSymbol].disconnect();
|
|
1252
|
+
}
|
|
1253
|
+
this[observedLayoutNodesSignatureSymbol] = null;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
/**
|
|
1257
|
+
* @private
|
|
1258
|
+
*/
|
|
1259
|
+
function toggle() {
|
|
1260
|
+
if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
|
|
1261
|
+
hide.call(this);
|
|
1262
|
+
} else {
|
|
1263
|
+
show.call(this);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/**
|
|
1268
|
+
* @private
|
|
1269
|
+
*/
|
|
1270
|
+
function hide() {
|
|
1271
|
+
this[popperElementSymbol].style.display = "none";
|
|
1272
|
+
removeAttributeToken(this[controlElementSymbol], "class", "open");
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* @private
|
|
1277
|
+
* @this PopperButton
|
|
1278
|
+
*/
|
|
1279
|
+
function show() {
|
|
1280
|
+
if (this.getOption("disabled", false) === true) {
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
this[popperElementSymbol].style.visibility = "hidden";
|
|
1289
|
+
this[popperElementSymbol].style.display = STYLE_DISPLAY_MODE_BLOCK;
|
|
1290
|
+
|
|
1291
|
+
addAttributeToken(this[controlElementSymbol], "class", "open");
|
|
1292
|
+
|
|
1293
|
+
updatePopper.call(this);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
/**
|
|
1297
|
+
* @private
|
|
1298
|
+
*/
|
|
1299
|
+
function updatePopper() {
|
|
1300
|
+
if (this[popperElementSymbol].style.display !== STYLE_DISPLAY_MODE_BLOCK) {
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
if (this.getOption("disabled", false) === true) {
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
positionPopper.call(
|
|
1309
|
+
this,
|
|
1310
|
+
this[switchElementSymbol],
|
|
1311
|
+
this[popperElementSymbol],
|
|
1312
|
+
this.getOption("popper", {}),
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
/**
|
|
1317
|
+
* @private
|
|
1318
|
+
*/
|
|
1319
|
+
function applyLayoutAlignment() {
|
|
1320
|
+
if (!(this[controlBarElementSymbol] instanceof HTMLElement)) {
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
const alignment = this.getOption("layout.alignment", "left");
|
|
1325
|
+
|
|
1326
|
+
if (alignment === "right") {
|
|
1327
|
+
this[controlBarElementSymbol].setAttribute(
|
|
1328
|
+
ATTRIBUTE_LAYOUT_ALIGNMENT,
|
|
1329
|
+
"right",
|
|
1330
|
+
);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
this[controlBarElementSymbol].setAttribute(ATTRIBUTE_LAYOUT_ALIGNMENT, "left");
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
/**
|
|
1338
|
+
* @private
|
|
1339
|
+
* @return {Select}
|
|
1340
|
+
* @throws {Error} no shadow-root is defined
|
|
1341
|
+
*/
|
|
1342
|
+
function initControlReferences() {
|
|
1343
|
+
if (!this.shadowRoot) {
|
|
1344
|
+
throw new Error("no shadow-root is defined");
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
this[controlElementSymbol] = this.shadowRoot.querySelector(
|
|
1348
|
+
`[${ATTRIBUTE_ROLE}=control]`,
|
|
1349
|
+
);
|
|
1350
|
+
this[controlBarElementSymbol] =
|
|
1351
|
+
this.shadowRoot.querySelector(`[${ATTRIBUTE_ROLE}=control-bar]`) ||
|
|
1352
|
+
this.shadowRoot.querySelector(`[${ATTRIBUTE_ROLE}=button-bar]`);
|
|
1353
|
+
this[popperElementSymbol] = this.shadowRoot.querySelector(
|
|
1354
|
+
`[${ATTRIBUTE_ROLE}=popper]`,
|
|
1355
|
+
);
|
|
1356
|
+
this[popperNavElementSymbol] = this.shadowRoot.querySelector(
|
|
1357
|
+
`[${ATTRIBUTE_ROLE}=popper-nav]`,
|
|
1358
|
+
);
|
|
1359
|
+
this[switchElementSymbol] = this.shadowRoot.querySelector(
|
|
1360
|
+
`[${ATTRIBUTE_ROLE}=switch]`,
|
|
1361
|
+
);
|
|
1362
|
+
|
|
1363
|
+
this[controlBarSlotElementSymbol] = null;
|
|
1364
|
+
if (this[controlBarElementSymbol])
|
|
1365
|
+
this[controlBarSlotElementSymbol] =
|
|
1366
|
+
this[controlBarElementSymbol].querySelector(`slot`);
|
|
1367
|
+
this[popperSlotElementSymbol] = null;
|
|
1368
|
+
if (this[popperElementSymbol])
|
|
1369
|
+
this[popperSlotElementSymbol] =
|
|
1370
|
+
this[popperElementSymbol].querySelector(`slot`);
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
/**
|
|
1374
|
+
* @private
|
|
1375
|
+
* @return {Promise<unknown>}
|
|
1376
|
+
* @throws {Error} no shadow-root is defined
|
|
1377
|
+
*
|
|
1378
|
+
*/
|
|
1379
|
+
function initControlBar() {
|
|
1380
|
+
if (!this.shadowRoot) {
|
|
1381
|
+
throw new Error("no shadow-root is defined");
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
scheduleLayout.call(this, { measure: true, layout: true, observe: true });
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
/**
|
|
1388
|
+
* @private
|
|
1389
|
+
*/
|
|
1390
|
+
function initPopperSwitch() {
|
|
1391
|
+
/**
|
|
1392
|
+
* @param {Event} event
|
|
1393
|
+
*/
|
|
1394
|
+
this[popperSwitchEventHandler] = (event) => {
|
|
1395
|
+
const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "switch");
|
|
1396
|
+
|
|
1397
|
+
if (element instanceof HTMLButtonElement) {
|
|
1398
|
+
toggle.call(this);
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
|
|
1402
|
+
for (const type of ["click", "touch"]) {
|
|
1403
|
+
this[switchElementSymbol].addEventListener(
|
|
1404
|
+
type,
|
|
1405
|
+
this[popperSwitchEventHandler],
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
/**
|
|
1411
|
+
* @private
|
|
1412
|
+
* @return {string}
|
|
1413
|
+
*/
|
|
1414
|
+
function getTemplate() {
|
|
1415
|
+
// language=HTML
|
|
1416
|
+
return `
|
|
1417
|
+
<div data-monster-role="control" part="control">
|
|
1418
|
+
<div data-monster-role="control-bar">
|
|
1419
|
+
<slot></slot>
|
|
1420
|
+
<div part="popper-nav" data-monster-role="popper-nav"
|
|
1421
|
+
tabindex="-1">
|
|
1422
|
+
<button part="popper-switch" data-monster-role="switch"
|
|
1423
|
+
class="monster-button-outline-tertiary hidden" hidden>
|
|
1424
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
|
1425
|
+
viewBox="0 0 16 16">
|
|
1426
|
+
<path d="M9.5 13a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/>
|
|
1427
|
+
</svg>
|
|
1428
|
+
</button>
|
|
1429
|
+
</div>
|
|
1430
|
+
</div>
|
|
1431
|
+
<div data-monster-role="popper">
|
|
1432
|
+
<slot name="popper"></slot>
|
|
1433
|
+
</div>
|
|
1434
|
+
</div>`;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
registerCustomElement(ControlBar);
|