@schukai/monster 3.58.4 → 3.59.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 +12 -0
- package/package.json +1 -1
- package/source/components/form/tabs.mjs +5 -1026
- package/source/components/host/overlay.mjs +1 -1
- package/source/components/host/viewer.mjs +2 -14
- package/source/components/layout/namespace.mjs +13 -0
- package/source/components/layout/split-screen.mjs +341 -0
- package/source/components/layout/style/split-screen.pcss +59 -0
- package/source/components/{form → layout}/style/tabs.pcss +0 -3
- package/source/components/layout/stylesheet/split-screen.mjs +27 -0
- package/source/components/layout/tabs.mjs +1075 -0
- package/source/monster.mjs +5 -1
- package/source/types/version.mjs +1 -1
- package/test/cases/components/{form → layout}/tabs.mjs +1 -1
- package/test/cases/monster.mjs +1 -1
- package/test/web/import.js +1 -1
- package/test/web/test.html +2 -2
- package/test/web/tests.js +16158 -16161
- /package/source/components/{form → layout}/stylesheet/tabs.mjs +0 -0
@@ -0,0 +1,1075 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright schukai GmbH and contributors 2023. All Rights Reserved.
|
3
|
+
* Node module: @schukai/monster
|
4
|
+
* This file is licensed under the AGPLv3 License.
|
5
|
+
* License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
|
6
|
+
*/
|
7
|
+
import { instanceSymbol } from "../../constants.mjs";
|
8
|
+
import { createPopper } from "@popperjs/core";
|
9
|
+
import { extend } from "../../data/extend.mjs";
|
10
|
+
import { Pathfinder } from "../../data/pathfinder.mjs";
|
11
|
+
import {
|
12
|
+
addAttributeToken,
|
13
|
+
addToObjectLink,
|
14
|
+
hasObjectLink,
|
15
|
+
} from "../../dom/attributes.mjs";
|
16
|
+
import {
|
17
|
+
ATTRIBUTE_ERRORMESSAGE,
|
18
|
+
ATTRIBUTE_PREFIX,
|
19
|
+
ATTRIBUTE_ROLE,
|
20
|
+
} from "../../dom/constants.mjs";
|
21
|
+
import {
|
22
|
+
assembleMethodSymbol,
|
23
|
+
CustomElement,
|
24
|
+
getSlottedElements,
|
25
|
+
registerCustomElement,
|
26
|
+
} from "../../dom/customelement.mjs";
|
27
|
+
import {
|
28
|
+
findTargetElementFromEvent,
|
29
|
+
fireCustomEvent,
|
30
|
+
} from "../../dom/events.mjs";
|
31
|
+
import { getDocument } from "../../dom/util.mjs";
|
32
|
+
import { random } from "../../math/random.mjs";
|
33
|
+
import { getGlobal } from "../../types/global.mjs";
|
34
|
+
import { ID } from "../../types/id.mjs";
|
35
|
+
import { isArray, isString } from "../../types/is.mjs";
|
36
|
+
import { TokenList } from "../../types/tokenlist.mjs";
|
37
|
+
import { clone } from "../../util/clone.mjs";
|
38
|
+
import { DeadMansSwitch } from "../../util/deadmansswitch.mjs";
|
39
|
+
import { Processing } from "../../util/processing.mjs";
|
40
|
+
import {
|
41
|
+
ATTRIBUTE_BUTTON_LABEL,
|
42
|
+
ATTRIBUTE_FORM_RELOAD,
|
43
|
+
ATTRIBUTE_FORM_URL,
|
44
|
+
STYLE_DISPLAY_MODE_BLOCK,
|
45
|
+
} from "../form/constants.mjs";
|
46
|
+
|
47
|
+
import { TabsStyleSheet } from "./stylesheet/tabs.mjs";
|
48
|
+
import { loadAndAssignContent } from "../form/util/fetch.mjs";
|
49
|
+
import { ThemeStyleSheet } from "../stylesheet/theme.mjs";
|
50
|
+
import {
|
51
|
+
popperInstanceSymbol,
|
52
|
+
setEventListenersModifiers,
|
53
|
+
} from "../form/util/popper.mjs";
|
54
|
+
|
55
|
+
export { Tabs };
|
56
|
+
|
57
|
+
/**
|
58
|
+
* @private
|
59
|
+
* @type {symbol}
|
60
|
+
*/
|
61
|
+
const popperElementSymbol = Symbol("popperElement");
|
62
|
+
|
63
|
+
/**
|
64
|
+
* @private
|
65
|
+
* @type {symbol}
|
66
|
+
*/
|
67
|
+
const popperNavElementSymbol = Symbol("popperNavElement");
|
68
|
+
|
69
|
+
/**
|
70
|
+
* @private
|
71
|
+
* @type {symbol}
|
72
|
+
*/
|
73
|
+
const controlElementSymbol = Symbol("controlElement");
|
74
|
+
|
75
|
+
/**
|
76
|
+
* @private
|
77
|
+
* @type {symbol}
|
78
|
+
*/
|
79
|
+
const navElementSymbol = Symbol("navElement");
|
80
|
+
/**
|
81
|
+
* @private
|
82
|
+
* @type {symbol}
|
83
|
+
*/
|
84
|
+
const switchElementSymbol = Symbol("switchElement");
|
85
|
+
|
86
|
+
/**
|
87
|
+
* @private
|
88
|
+
* @type {symbol}
|
89
|
+
*/
|
90
|
+
const changeTabEventHandler = Symbol("changeTabEventHandler");
|
91
|
+
/**
|
92
|
+
* @private
|
93
|
+
* @type {symbol}
|
94
|
+
*/
|
95
|
+
const removeTabEventHandler = Symbol("removeTabEventHandler");
|
96
|
+
|
97
|
+
/**
|
98
|
+
* @private
|
99
|
+
* @type {symbol}
|
100
|
+
*/
|
101
|
+
const popperSwitchEventHandler = Symbol("popperSwitchEventHandler");
|
102
|
+
|
103
|
+
/**
|
104
|
+
* local symbol
|
105
|
+
* @private
|
106
|
+
* @type {symbol}
|
107
|
+
*/
|
108
|
+
const closeEventHandler = Symbol("closeEventHandler");
|
109
|
+
|
110
|
+
/**
|
111
|
+
* @private
|
112
|
+
* @type {symbol}
|
113
|
+
*/
|
114
|
+
const mutationObserverSymbol = Symbol("mutationObserver");
|
115
|
+
|
116
|
+
/**
|
117
|
+
* @private
|
118
|
+
* @type {symbol}
|
119
|
+
*/
|
120
|
+
const dimensionsSymbol = Symbol("dimensions");
|
121
|
+
|
122
|
+
/**
|
123
|
+
* @private
|
124
|
+
* @type {symbol}
|
125
|
+
*/
|
126
|
+
const timerCallbackSymbol = Symbol("timerCallback");
|
127
|
+
|
128
|
+
/**
|
129
|
+
* local symbol
|
130
|
+
* @private
|
131
|
+
* @type {symbol}
|
132
|
+
*/
|
133
|
+
const resizeObserverSymbol = Symbol("resizeObserver");
|
134
|
+
|
135
|
+
/**
|
136
|
+
* This CustomControl creates a tab element with a variety of options.
|
137
|
+
*
|
138
|
+
* <img src="./images/tabs.png">
|
139
|
+
*
|
140
|
+
* You can create this control either by specifying the HTML tag `<monster-tabs />` directly in the HTML or using
|
141
|
+
* Javascript via the `document.createElement('monster-tabs');` method.
|
142
|
+
*
|
143
|
+
* ```html
|
144
|
+
* <monster-tabs></monster-tabs>
|
145
|
+
* ```
|
146
|
+
*
|
147
|
+
* Or you can create this CustomControl directly in Javascript:
|
148
|
+
*
|
149
|
+
* ```js
|
150
|
+
* import {Tabs} from '@schukai/monster/components/layout/tabs.mjs';
|
151
|
+
* document.createElement('monster-tabs');
|
152
|
+
* ```
|
153
|
+
*
|
154
|
+
* @example <caption>Create a simple tab control</caption>
|
155
|
+
* <monster-tabs>
|
156
|
+
* <div id="tab1">Tab 1</div>
|
157
|
+
* <div id="tab2">Tab 2</div>
|
158
|
+
* </monster-tabs>
|
159
|
+
*
|
160
|
+
* @startuml tabs.png
|
161
|
+
* skinparam monochrome true
|
162
|
+
* skinparam shadowing false
|
163
|
+
* HTMLElement <|-- CustomElement
|
164
|
+
* CustomElement <|-- Tabs
|
165
|
+
* @enduml
|
166
|
+
*
|
167
|
+
* @since 1.10.0
|
168
|
+
* @copyright schukai GmbH
|
169
|
+
* @memberOf Monster.Components.Layout
|
170
|
+
* @summary A configurable tab control
|
171
|
+
* @fires Monster.Components.Layout.event:monster-fetched
|
172
|
+
*/
|
173
|
+
class Tabs extends CustomElement {
|
174
|
+
/**
|
175
|
+
* This method is called by the `instanceof` operator.
|
176
|
+
* @returns {symbol}
|
177
|
+
* @since 2.1.0
|
178
|
+
*/
|
179
|
+
static get [instanceSymbol]() {
|
180
|
+
return Symbol.for("@schukai/monster/components/layout/tabs");
|
181
|
+
}
|
182
|
+
|
183
|
+
/**
|
184
|
+
* To set the options via the html tag the attribute `data-monster-options` must be used.
|
185
|
+
* @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
|
186
|
+
*
|
187
|
+
* The individual configuration values can be found in the table.
|
188
|
+
*
|
189
|
+
* @property {Object} templates Template definitions
|
190
|
+
* @property {string} templates.main Main template
|
191
|
+
* @property {Object} labels
|
192
|
+
* @property {string} labels.new-tab-label="New Tab"
|
193
|
+
* @property {Object} fetch Fetch [see Using Fetch mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)
|
194
|
+
* @property {String} fetch.redirect=error
|
195
|
+
* @property {String} fetch.method=GET
|
196
|
+
* @property {String} fetch.mode=same-origin
|
197
|
+
* @property {String} fetch.credentials=same-origin
|
198
|
+
* @property {Object} fetch.headers={"accept":"text/html"}}
|
199
|
+
* @property {Object} popper [PopperJS Options](https://popper.js.org/docs/v2/)
|
200
|
+
* @property {string} popper.placement=bottom PopperJS placement
|
201
|
+
* @property {Object[]} modifiers={name:offset} PopperJS placement
|
202
|
+
*/
|
203
|
+
get defaults() {
|
204
|
+
return Object.assign({}, super.defaults, {
|
205
|
+
templates: {
|
206
|
+
main: getTemplate(),
|
207
|
+
},
|
208
|
+
labels: {
|
209
|
+
"new-tab-label": "New Tab",
|
210
|
+
},
|
211
|
+
buttons: {
|
212
|
+
standard: [],
|
213
|
+
popper: [],
|
214
|
+
},
|
215
|
+
fetch: {
|
216
|
+
redirect: "error",
|
217
|
+
method: "GET",
|
218
|
+
mode: "same-origin",
|
219
|
+
credentials: "same-origin",
|
220
|
+
headers: {
|
221
|
+
accept: "text/html",
|
222
|
+
},
|
223
|
+
},
|
224
|
+
|
225
|
+
classes: {
|
226
|
+
button: "monster-theme-primary-1",
|
227
|
+
popper: "monster-theme-primary-1",
|
228
|
+
},
|
229
|
+
|
230
|
+
popper: {
|
231
|
+
placement: "bottom",
|
232
|
+
modifiers: [
|
233
|
+
{
|
234
|
+
name: "offset",
|
235
|
+
options: {
|
236
|
+
offset: [0, 2],
|
237
|
+
},
|
238
|
+
},
|
239
|
+
|
240
|
+
{
|
241
|
+
name: "eventListeners",
|
242
|
+
enabled: false,
|
243
|
+
},
|
244
|
+
],
|
245
|
+
},
|
246
|
+
});
|
247
|
+
}
|
248
|
+
|
249
|
+
/**
|
250
|
+
* This method is called internal and should not be called directly.
|
251
|
+
*/
|
252
|
+
[assembleMethodSymbol]() {
|
253
|
+
super[assembleMethodSymbol]();
|
254
|
+
|
255
|
+
initControlReferences.call(this);
|
256
|
+
|
257
|
+
this[dimensionsSymbol] = new Pathfinder({ data: {} });
|
258
|
+
|
259
|
+
initEventHandler.call(this);
|
260
|
+
|
261
|
+
// setup structure
|
262
|
+
initTabButtons.call(this).then(() => {
|
263
|
+
initPopperSwitch.call(this);
|
264
|
+
initPopper.call(this);
|
265
|
+
attachResizeObserver.call(this);
|
266
|
+
attachTabChangeObserver.call(this);
|
267
|
+
});
|
268
|
+
}
|
269
|
+
|
270
|
+
/**
|
271
|
+
* This method is called internal and should not be called directly.
|
272
|
+
*
|
273
|
+
* @return {CSSStyleSheet[]}
|
274
|
+
*/
|
275
|
+
static getCSSStyleSheet() {
|
276
|
+
return [TabsStyleSheet, ThemeStyleSheet];
|
277
|
+
}
|
278
|
+
|
279
|
+
/**
|
280
|
+
* This method is called internal and should not be called directly.
|
281
|
+
*
|
282
|
+
* @return {string}
|
283
|
+
*/
|
284
|
+
static getTag() {
|
285
|
+
return "monster-tabs";
|
286
|
+
}
|
287
|
+
|
288
|
+
/**
|
289
|
+
* This method is called by the dom and should not be called directly.
|
290
|
+
*
|
291
|
+
* @return {void}
|
292
|
+
*/
|
293
|
+
connectedCallback() {
|
294
|
+
super.connectedCallback();
|
295
|
+
|
296
|
+
const document = getDocument();
|
297
|
+
|
298
|
+
for (const [, type] of Object.entries(["click", "touch"])) {
|
299
|
+
// close on outside ui-events
|
300
|
+
document.addEventListener(type, this[closeEventHandler]);
|
301
|
+
}
|
302
|
+
}
|
303
|
+
|
304
|
+
/**
|
305
|
+
* This method is called by the dom and should not be called directly.
|
306
|
+
*
|
307
|
+
* @return {void}
|
308
|
+
*/
|
309
|
+
disconnectedCallback() {
|
310
|
+
super.disconnectedCallback();
|
311
|
+
|
312
|
+
const document = getDocument();
|
313
|
+
|
314
|
+
// close on outside ui-events
|
315
|
+
for (const [, type] of Object.entries(["click", "touch"])) {
|
316
|
+
document.removeEventListener(type, this[closeEventHandler]);
|
317
|
+
}
|
318
|
+
}
|
319
|
+
}
|
320
|
+
|
321
|
+
/**
|
322
|
+
* @private
|
323
|
+
*/
|
324
|
+
function initPopperSwitch() {
|
325
|
+
const nodes = getSlottedElements.call(this, `[${ATTRIBUTE_ROLE}="switch"]`); // null ↦ only unnamed slots
|
326
|
+
let switchButton;
|
327
|
+
if (nodes.size === 0) {
|
328
|
+
switchButton = document.createElement("button");
|
329
|
+
switchButton.setAttribute(ATTRIBUTE_ROLE, "switch");
|
330
|
+
switchButton.setAttribute("part", "switch");
|
331
|
+
switchButton.classList.add("hidden");
|
332
|
+
const classList = this.getOption("classes.button");
|
333
|
+
if (classList) {
|
334
|
+
switchButton.classList.add(classList);
|
335
|
+
}
|
336
|
+
switchButton.innerHTML =
|
337
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><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"/></svg>';
|
338
|
+
this[navElementSymbol].prepend(switchButton);
|
339
|
+
} else {
|
340
|
+
switchButton = nodes.next();
|
341
|
+
}
|
342
|
+
|
343
|
+
/**
|
344
|
+
* @param {Event} event
|
345
|
+
*/
|
346
|
+
this[popperSwitchEventHandler] = (event) => {
|
347
|
+
const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "switch");
|
348
|
+
|
349
|
+
if (element instanceof HTMLButtonElement) {
|
350
|
+
togglePopper.call(this);
|
351
|
+
}
|
352
|
+
};
|
353
|
+
|
354
|
+
for (const type of ["click", "touch"]) {
|
355
|
+
switchButton.addEventListener(type, this[popperSwitchEventHandler]);
|
356
|
+
}
|
357
|
+
|
358
|
+
this[switchElementSymbol] = switchButton;
|
359
|
+
}
|
360
|
+
|
361
|
+
/**
|
362
|
+
* @private
|
363
|
+
*/
|
364
|
+
function hidePopper() {
|
365
|
+
if (!this[popperInstanceSymbol]) {
|
366
|
+
return;
|
367
|
+
}
|
368
|
+
|
369
|
+
this[popperElementSymbol].style.display = "none";
|
370
|
+
// performance https://popper.js.org/docs/v2/tutorial/#performance
|
371
|
+
setEventListenersModifiers.call(this, false);
|
372
|
+
}
|
373
|
+
|
374
|
+
/**
|
375
|
+
* @private
|
376
|
+
*/
|
377
|
+
function showPopper() {
|
378
|
+
if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
|
379
|
+
return;
|
380
|
+
}
|
381
|
+
|
382
|
+
this[popperElementSymbol].style.visibility = "hidden";
|
383
|
+
this[popperElementSymbol].style.display = STYLE_DISPLAY_MODE_BLOCK;
|
384
|
+
// performance https://popper.js.org/docs/v2/tutorial/#performance
|
385
|
+
setEventListenersModifiers.call(this, true);
|
386
|
+
|
387
|
+
this[popperInstanceSymbol].update();
|
388
|
+
|
389
|
+
new Processing(() => {
|
390
|
+
this[popperElementSymbol].style.removeProperty("visibility");
|
391
|
+
})
|
392
|
+
.run(undefined)
|
393
|
+
.then(() => {})
|
394
|
+
.catch((e) => {
|
395
|
+
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
|
396
|
+
});
|
397
|
+
}
|
398
|
+
|
399
|
+
/**
|
400
|
+
* @private
|
401
|
+
*/
|
402
|
+
function togglePopper() {
|
403
|
+
if (this[popperElementSymbol].style.display === STYLE_DISPLAY_MODE_BLOCK) {
|
404
|
+
hidePopper.call(this);
|
405
|
+
} else {
|
406
|
+
showPopper.call(this);
|
407
|
+
}
|
408
|
+
}
|
409
|
+
|
410
|
+
/**
|
411
|
+
* @private
|
412
|
+
*/
|
413
|
+
function attachResizeObserver() {
|
414
|
+
// against flickering
|
415
|
+
this[resizeObserverSymbol] = new ResizeObserver((entries) => {
|
416
|
+
if (this[timerCallbackSymbol] instanceof DeadMansSwitch) {
|
417
|
+
try {
|
418
|
+
this[timerCallbackSymbol].touch();
|
419
|
+
return;
|
420
|
+
} catch (e) {
|
421
|
+
delete this[timerCallbackSymbol];
|
422
|
+
}
|
423
|
+
}
|
424
|
+
|
425
|
+
this[timerCallbackSymbol] = new DeadMansSwitch(200, () => {
|
426
|
+
this[dimensionsSymbol].setVia("data.calculated", false);
|
427
|
+
checkAndRearrangeButtons.call(this);
|
428
|
+
});
|
429
|
+
});
|
430
|
+
|
431
|
+
this[resizeObserverSymbol].observe(this[navElementSymbol]);
|
432
|
+
}
|
433
|
+
|
434
|
+
/**
|
435
|
+
* @private
|
436
|
+
*/
|
437
|
+
function attachTabChangeObserver() {
|
438
|
+
// against flickering
|
439
|
+
new MutationObserver((mutations) => {
|
440
|
+
let runUpdate = false;
|
441
|
+
|
442
|
+
for (const mutation of mutations) {
|
443
|
+
if (mutation.type === "childList") {
|
444
|
+
if (
|
445
|
+
mutation.addedNodes.length > 0 ||
|
446
|
+
mutation.removedNodes.length > 0
|
447
|
+
) {
|
448
|
+
runUpdate = true;
|
449
|
+
break;
|
450
|
+
}
|
451
|
+
}
|
452
|
+
}
|
453
|
+
|
454
|
+
if (runUpdate === true) {
|
455
|
+
this[dimensionsSymbol].setVia("data.calculated", false);
|
456
|
+
initTabButtons.call(this);
|
457
|
+
}
|
458
|
+
}).observe(this, {
|
459
|
+
childList: true,
|
460
|
+
});
|
461
|
+
}
|
462
|
+
|
463
|
+
/**
|
464
|
+
* @private
|
465
|
+
* @return {Select}
|
466
|
+
* @external "external:createPopper"
|
467
|
+
* @see {@link Plugins}
|
468
|
+
*/
|
469
|
+
function initPopper() {
|
470
|
+
const self = this;
|
471
|
+
|
472
|
+
const options = extend({}, self.getOption("popper"));
|
473
|
+
|
474
|
+
self[popperInstanceSymbol] = createPopper(
|
475
|
+
self[switchElementSymbol],
|
476
|
+
self[popperElementSymbol],
|
477
|
+
options,
|
478
|
+
);
|
479
|
+
|
480
|
+
const observer1 = new MutationObserver(function (mutations) {
|
481
|
+
let runUpdate = false;
|
482
|
+
|
483
|
+
for (const mutation of mutations) {
|
484
|
+
if (mutation.type === "childList") {
|
485
|
+
if (
|
486
|
+
mutation.addedNodes.length > 0 ||
|
487
|
+
mutation.removedNodes.length > 0
|
488
|
+
) {
|
489
|
+
runUpdate = true;
|
490
|
+
break;
|
491
|
+
}
|
492
|
+
}
|
493
|
+
}
|
494
|
+
|
495
|
+
if (runUpdate === true) {
|
496
|
+
self[popperInstanceSymbol].update();
|
497
|
+
}
|
498
|
+
});
|
499
|
+
|
500
|
+
observer1.observe(self[popperNavElementSymbol], {
|
501
|
+
childList: true,
|
502
|
+
subtree: true,
|
503
|
+
});
|
504
|
+
|
505
|
+
return self;
|
506
|
+
}
|
507
|
+
|
508
|
+
/**
|
509
|
+
* @private
|
510
|
+
* @param {HTMLElement} element
|
511
|
+
*/
|
512
|
+
function show(element) {
|
513
|
+
if (!this.shadowRoot) {
|
514
|
+
throw new Error("no shadow-root is defined");
|
515
|
+
}
|
516
|
+
|
517
|
+
const reference = element.getAttribute(`${ATTRIBUTE_PREFIX}tab-reference`);
|
518
|
+
|
519
|
+
const nodes = getSlottedElements.call(this);
|
520
|
+
for (const node of nodes) {
|
521
|
+
const id = node.getAttribute("id");
|
522
|
+
|
523
|
+
if (id === reference) {
|
524
|
+
node.classList.add("active");
|
525
|
+
|
526
|
+
// get all data- from button and filter out data-monster-attributes and data-monster-insert
|
527
|
+
const data = {};
|
528
|
+
const mask = [
|
529
|
+
"data-monster-attributes",
|
530
|
+
"data-monster-insert-reference",
|
531
|
+
"data-monster-state",
|
532
|
+
"data-monster-button-label",
|
533
|
+
"data-monster-objectlink",
|
534
|
+
"data-monster-role",
|
535
|
+
];
|
536
|
+
|
537
|
+
for (const [, attr] of Object.entries(node.attributes)) {
|
538
|
+
if (attr.name.startsWith("data-") && mask.indexOf(attr.name) === -1) {
|
539
|
+
data[attr.name] = attr.value;
|
540
|
+
}
|
541
|
+
}
|
542
|
+
|
543
|
+
if (node.hasAttribute(ATTRIBUTE_FORM_URL)) {
|
544
|
+
const url = node.getAttribute(ATTRIBUTE_FORM_URL);
|
545
|
+
|
546
|
+
if (
|
547
|
+
!node.hasAttribute(ATTRIBUTE_FORM_RELOAD) ||
|
548
|
+
node.getAttribute(ATTRIBUTE_FORM_RELOAD).toLowerCase() === "onshow"
|
549
|
+
) {
|
550
|
+
node.removeAttribute(ATTRIBUTE_FORM_URL);
|
551
|
+
}
|
552
|
+
|
553
|
+
const options = this.getOption("fetch", {});
|
554
|
+
const filter = undefined;
|
555
|
+
loadAndAssignContent(node, url, options, filter)
|
556
|
+
.then(() => {
|
557
|
+
fireCustomEvent(this, "monster-tab-changed", {
|
558
|
+
reference,
|
559
|
+
});
|
560
|
+
})
|
561
|
+
.catch((e) => {
|
562
|
+
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
|
563
|
+
});
|
564
|
+
} else {
|
565
|
+
fireCustomEvent(this, "monster-tab-changed", {
|
566
|
+
reference,
|
567
|
+
data,
|
568
|
+
});
|
569
|
+
}
|
570
|
+
} else {
|
571
|
+
node.classList.remove("active");
|
572
|
+
}
|
573
|
+
}
|
574
|
+
|
575
|
+
const standardButtons = this.getOption("buttons.standard");
|
576
|
+
for (const index in standardButtons) {
|
577
|
+
const button = standardButtons[index];
|
578
|
+
const state = button["reference"] === reference ? "active" : "inactive";
|
579
|
+
this.setOption(`buttons.standard.${index}.state`, state);
|
580
|
+
}
|
581
|
+
|
582
|
+
const popperButton = this.getOption("buttons.popper");
|
583
|
+
for (const index in popperButton) {
|
584
|
+
const button = popperButton[index];
|
585
|
+
const state = button["reference"] === reference ? "active" : "inactive";
|
586
|
+
this.setOption(`buttons.popper.${index}.state`, state);
|
587
|
+
}
|
588
|
+
|
589
|
+
hidePopper.call(this);
|
590
|
+
}
|
591
|
+
|
592
|
+
/**
|
593
|
+
* @private
|
594
|
+
*/
|
595
|
+
function initEventHandler() {
|
596
|
+
if (!this.shadowRoot) {
|
597
|
+
throw new Error("no shadow-root is defined");
|
598
|
+
}
|
599
|
+
|
600
|
+
/**
|
601
|
+
* @param {Event} event
|
602
|
+
*/
|
603
|
+
this[changeTabEventHandler] = (event) => {
|
604
|
+
const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "button");
|
605
|
+
|
606
|
+
if (element instanceof HTMLButtonElement && element.disabled !== true) {
|
607
|
+
show.call(this, element);
|
608
|
+
}
|
609
|
+
};
|
610
|
+
|
611
|
+
/**
|
612
|
+
* event:monster-tab-remove
|
613
|
+
* @event Monster.Components.Layout.event:monster-tab-remove
|
614
|
+
*/
|
615
|
+
|
616
|
+
/**
|
617
|
+
* @param {Event} event
|
618
|
+
* @fires Monster.Components.Layout.event:monster-tab-remove
|
619
|
+
*/
|
620
|
+
this[removeTabEventHandler] = (event) => {
|
621
|
+
const element = findTargetElementFromEvent(
|
622
|
+
event,
|
623
|
+
ATTRIBUTE_ROLE,
|
624
|
+
"remove-tab",
|
625
|
+
);
|
626
|
+
|
627
|
+
if (element instanceof HTMLElement) {
|
628
|
+
const button = findTargetElementFromEvent(
|
629
|
+
event,
|
630
|
+
ATTRIBUTE_ROLE,
|
631
|
+
"button",
|
632
|
+
);
|
633
|
+
|
634
|
+
if (button instanceof HTMLButtonElement && button.disabled !== true) {
|
635
|
+
const reference = button.getAttribute(
|
636
|
+
`${ATTRIBUTE_PREFIX}tab-reference`,
|
637
|
+
);
|
638
|
+
if (reference) {
|
639
|
+
const container = this.querySelector(`[id=${reference}]`);
|
640
|
+
if (container instanceof HTMLElement) {
|
641
|
+
container.remove();
|
642
|
+
initTabButtons.call(this);
|
643
|
+
fireCustomEvent(this, "monster-tab-remove", {
|
644
|
+
reference,
|
645
|
+
});
|
646
|
+
}
|
647
|
+
}
|
648
|
+
}
|
649
|
+
}
|
650
|
+
};
|
651
|
+
|
652
|
+
this[navElementSymbol].addEventListener("touch", this[changeTabEventHandler]);
|
653
|
+
this[navElementSymbol].addEventListener("click", this[changeTabEventHandler]);
|
654
|
+
|
655
|
+
this[navElementSymbol].addEventListener("touch", this[removeTabEventHandler]);
|
656
|
+
this[navElementSymbol].addEventListener("click", this[removeTabEventHandler]);
|
657
|
+
|
658
|
+
/**
|
659
|
+
* @param {Event} event
|
660
|
+
*/
|
661
|
+
this[closeEventHandler] = (event) => {
|
662
|
+
const path = event.composedPath();
|
663
|
+
|
664
|
+
for (const [, element] of Object.entries(path)) {
|
665
|
+
if (element === this) {
|
666
|
+
return;
|
667
|
+
}
|
668
|
+
}
|
669
|
+
|
670
|
+
hidePopper.call(this);
|
671
|
+
};
|
672
|
+
|
673
|
+
return this;
|
674
|
+
}
|
675
|
+
|
676
|
+
/**
|
677
|
+
* @private
|
678
|
+
* @param observedNode
|
679
|
+
*/
|
680
|
+
function attachTabMutationObserver(observedNode) {
|
681
|
+
const self = this;
|
682
|
+
|
683
|
+
if (hasObjectLink(observedNode, mutationObserverSymbol)) {
|
684
|
+
return;
|
685
|
+
}
|
686
|
+
|
687
|
+
/**
|
688
|
+
* this construct monitors a node whether it is disabled or modified
|
689
|
+
* @type {MutationObserver}
|
690
|
+
*/
|
691
|
+
const observer = new MutationObserver(function (mutations) {
|
692
|
+
if (isArray(mutations)) {
|
693
|
+
const mutation = mutations.pop();
|
694
|
+
if (mutation instanceof MutationRecord) {
|
695
|
+
initTabButtons.call(self);
|
696
|
+
}
|
697
|
+
}
|
698
|
+
});
|
699
|
+
|
700
|
+
observer.observe(observedNode, {
|
701
|
+
childList: false,
|
702
|
+
attributes: true,
|
703
|
+
subtree: false,
|
704
|
+
attributeFilter: [
|
705
|
+
"disabled",
|
706
|
+
ATTRIBUTE_BUTTON_LABEL,
|
707
|
+
`${ATTRIBUTE_PREFIX}button-icon`,
|
708
|
+
],
|
709
|
+
});
|
710
|
+
|
711
|
+
addToObjectLink(observedNode, mutationObserverSymbol, observer);
|
712
|
+
}
|
713
|
+
|
714
|
+
/**
|
715
|
+
* @private
|
716
|
+
* @return {Select}
|
717
|
+
* @throws {Error} no shadow-root is defined
|
718
|
+
*/
|
719
|
+
function initControlReferences() {
|
720
|
+
if (!this.shadowRoot) {
|
721
|
+
throw new Error("no shadow-root is defined");
|
722
|
+
}
|
723
|
+
|
724
|
+
this[controlElementSymbol] = this.shadowRoot.querySelector(
|
725
|
+
`[${ATTRIBUTE_ROLE}=control]`,
|
726
|
+
);
|
727
|
+
this[navElementSymbol] = this.shadowRoot.querySelector(
|
728
|
+
`nav[${ATTRIBUTE_ROLE}=nav]`,
|
729
|
+
);
|
730
|
+
this[popperElementSymbol] = this.shadowRoot.querySelector(
|
731
|
+
`[${ATTRIBUTE_ROLE}=popper]`,
|
732
|
+
);
|
733
|
+
this[popperNavElementSymbol] = this.shadowRoot.querySelector(
|
734
|
+
`[${ATTRIBUTE_ROLE}=popper-nav]`,
|
735
|
+
);
|
736
|
+
}
|
737
|
+
|
738
|
+
/**
|
739
|
+
* @private
|
740
|
+
* @return {Promise<unknown>}
|
741
|
+
* @throws {Error} no shadow-root is defined
|
742
|
+
*
|
743
|
+
*/
|
744
|
+
function initTabButtons() {
|
745
|
+
if (!this.shadowRoot) {
|
746
|
+
throw new Error("no shadow-root is defined");
|
747
|
+
}
|
748
|
+
|
749
|
+
let activeReference;
|
750
|
+
|
751
|
+
const dimensionsCalculated = this[dimensionsSymbol].getVia(
|
752
|
+
"data.calculated",
|
753
|
+
false,
|
754
|
+
);
|
755
|
+
|
756
|
+
const buttons = [];
|
757
|
+
const nodes = getSlottedElements.call(this, undefined, null); // null ↦ only unnamed slots
|
758
|
+
|
759
|
+
for (const node of nodes) {
|
760
|
+
if (!(node instanceof HTMLElement)) continue;
|
761
|
+
let label = getButtonLabel.call(this, node);
|
762
|
+
|
763
|
+
let reference;
|
764
|
+
if (node.hasAttribute("id")) {
|
765
|
+
reference = node.getAttribute("id");
|
766
|
+
}
|
767
|
+
|
768
|
+
let disabled;
|
769
|
+
if (node.hasAttribute("disabled") || node.disabled === true) {
|
770
|
+
disabled = true;
|
771
|
+
}
|
772
|
+
|
773
|
+
if (!reference) {
|
774
|
+
reference = new ID("tab").toString();
|
775
|
+
node.setAttribute("id", reference);
|
776
|
+
}
|
777
|
+
|
778
|
+
if (node.hasAttribute(`${ATTRIBUTE_PREFIX}button-icon`)) {
|
779
|
+
label = `<span part="label">${label}</span><img part="icon" src="${node.getAttribute(
|
780
|
+
`${ATTRIBUTE_PREFIX}button-icon`,
|
781
|
+
)}">`;
|
782
|
+
}
|
783
|
+
|
784
|
+
let remove = false;
|
785
|
+
if (node.hasAttribute(`${ATTRIBUTE_PREFIX}removable`)) {
|
786
|
+
remove = true;
|
787
|
+
}
|
788
|
+
|
789
|
+
if (node.matches(".active") === true && disabled !== true) {
|
790
|
+
node.classList.remove("active");
|
791
|
+
activeReference = reference;
|
792
|
+
}
|
793
|
+
|
794
|
+
const state = "";
|
795
|
+
const classes = dimensionsCalculated ? "" : "invisible";
|
796
|
+
|
797
|
+
buttons.push({
|
798
|
+
reference,
|
799
|
+
label,
|
800
|
+
state,
|
801
|
+
class: classes,
|
802
|
+
disabled,
|
803
|
+
remove,
|
804
|
+
});
|
805
|
+
|
806
|
+
attachTabMutationObserver.call(this, node);
|
807
|
+
}
|
808
|
+
|
809
|
+
this.setOption("buttons.standard", clone(buttons));
|
810
|
+
this.setOption("buttons.popper", []);
|
811
|
+
this.setOption("marker", random());
|
812
|
+
|
813
|
+
return adjustButtonVisibility.call(this).then(() => {
|
814
|
+
if (activeReference) {
|
815
|
+
return new Processing(() => {
|
816
|
+
const button = this.shadowRoot.querySelector(
|
817
|
+
`[${ATTRIBUTE_PREFIX}tab-reference="${activeReference}"]`,
|
818
|
+
);
|
819
|
+
if (button instanceof HTMLButtonElement && button.disabled !== true) {
|
820
|
+
show.call(this, button);
|
821
|
+
}
|
822
|
+
})
|
823
|
+
.run(undefined)
|
824
|
+
.then(() => {})
|
825
|
+
.catch((e) => {
|
826
|
+
addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
|
827
|
+
});
|
828
|
+
}
|
829
|
+
|
830
|
+
return Promise.resolve();
|
831
|
+
});
|
832
|
+
}
|
833
|
+
|
834
|
+
function checkAndRearrangeButtons() {
|
835
|
+
if (this[dimensionsSymbol].getVia("data.calculated", false) !== true) {
|
836
|
+
calculateNavigationButtonsDimensions.call(this);
|
837
|
+
}
|
838
|
+
|
839
|
+
rearrangeButtons.call(this);
|
840
|
+
}
|
841
|
+
|
842
|
+
/**
|
843
|
+
* @private
|
844
|
+
* @return {Promise<unknown>}
|
845
|
+
*/
|
846
|
+
function adjustButtonVisibility() {
|
847
|
+
const self = this;
|
848
|
+
|
849
|
+
return new Promise((resolve) => {
|
850
|
+
const observer = new MutationObserver(function (mutations) {
|
851
|
+
const defCount = self.getOption("buttons.standard").length;
|
852
|
+
const domCount = self[navElementSymbol].querySelectorAll(
|
853
|
+
'button[data-monster-role="button"]',
|
854
|
+
).length;
|
855
|
+
|
856
|
+
// in drawing
|
857
|
+
if (defCount !== domCount) return;
|
858
|
+
|
859
|
+
observer.disconnect();
|
860
|
+
|
861
|
+
checkAndRearrangeButtons.call(self);
|
862
|
+
|
863
|
+
resolve();
|
864
|
+
});
|
865
|
+
|
866
|
+
observer.observe(self[navElementSymbol], {
|
867
|
+
attributes: true,
|
868
|
+
});
|
869
|
+
});
|
870
|
+
}
|
871
|
+
|
872
|
+
/**
|
873
|
+
* @private
|
874
|
+
* @param {string} value
|
875
|
+
* @return {number}
|
876
|
+
*/
|
877
|
+
function getDimValue(value) {
|
878
|
+
if ([undefined, null].indexOf(value) !== -1) {
|
879
|
+
return 0;
|
880
|
+
}
|
881
|
+
|
882
|
+
const valueAsInt = parseInt(value, 10);
|
883
|
+
|
884
|
+
if (isNaN(valueAsInt)) {
|
885
|
+
return 0;
|
886
|
+
}
|
887
|
+
|
888
|
+
return valueAsInt;
|
889
|
+
}
|
890
|
+
|
891
|
+
/**
|
892
|
+
* @private
|
893
|
+
* @param {HTMLElement} node
|
894
|
+
* @return {number}
|
895
|
+
*/
|
896
|
+
function calcBoxWidth(node) {
|
897
|
+
const dim = getGlobal("window").getComputedStyle(node);
|
898
|
+
const bounding = node.getBoundingClientRect();
|
899
|
+
|
900
|
+
return (
|
901
|
+
getDimValue(dim["border-left-width"]) +
|
902
|
+
getDimValue(dim["padding-left"]) +
|
903
|
+
getDimValue(dim["margin-left"]) +
|
904
|
+
getDimValue(bounding["width"]) +
|
905
|
+
getDimValue(dim["border-right-width"]) +
|
906
|
+
getDimValue(dim["margin-right"]) +
|
907
|
+
getDimValue(dim["padding-left"])
|
908
|
+
);
|
909
|
+
}
|
910
|
+
|
911
|
+
/**
|
912
|
+
* @private
|
913
|
+
* @return {Object}
|
914
|
+
*/
|
915
|
+
function rearrangeButtons() {
|
916
|
+
const standardButtons = [];
|
917
|
+
const popperButtons = [];
|
918
|
+
|
919
|
+
let sum = 0;
|
920
|
+
const space = this[dimensionsSymbol].getVia("data.space");
|
921
|
+
|
922
|
+
const buttons = this.getOption("buttons.standard");
|
923
|
+
for (const [, button] of buttons.entries()) {
|
924
|
+
const ref = button?.reference;
|
925
|
+
|
926
|
+
sum += this[dimensionsSymbol].getVia(`data.button.${ref}`);
|
927
|
+
|
928
|
+
if (sum > space) {
|
929
|
+
popperButtons.push(clone(button));
|
930
|
+
} else {
|
931
|
+
standardButtons.push(clone(button));
|
932
|
+
}
|
933
|
+
}
|
934
|
+
|
935
|
+
this.setOption("buttons.standard", standardButtons);
|
936
|
+
this.setOption("buttons.popper", popperButtons);
|
937
|
+
|
938
|
+
if (this[switchElementSymbol]) {
|
939
|
+
if (popperButtons.length > 0) {
|
940
|
+
this[switchElementSymbol].classList.remove("hidden");
|
941
|
+
} else {
|
942
|
+
this[switchElementSymbol].classList.add("hidden");
|
943
|
+
}
|
944
|
+
}
|
945
|
+
}
|
946
|
+
|
947
|
+
/**
|
948
|
+
* @private
|
949
|
+
* @return {Object}
|
950
|
+
*/
|
951
|
+
function calculateNavigationButtonsDimensions() {
|
952
|
+
const width = this[navElementSymbol].getBoundingClientRect().width;
|
953
|
+
|
954
|
+
let startEndWidth = 0;
|
955
|
+
|
956
|
+
getSlottedElements.call(this, undefined, "start").forEach((node) => {
|
957
|
+
startEndWidth += calcBoxWidth.call(this, node);
|
958
|
+
});
|
959
|
+
|
960
|
+
getSlottedElements.call(this, undefined, "end").forEach((node) => {
|
961
|
+
startEndWidth += calcBoxWidth.call(this, node);
|
962
|
+
});
|
963
|
+
|
964
|
+
this[dimensionsSymbol].setVia("data.space", width - startEndWidth - 2);
|
965
|
+
this[dimensionsSymbol].setVia("data.visible", !(width === 0));
|
966
|
+
|
967
|
+
const buttons = this.getOption("buttons.standard").concat(
|
968
|
+
this.getOption("buttons.popper"),
|
969
|
+
);
|
970
|
+
|
971
|
+
for (const [i, button] of buttons.entries()) {
|
972
|
+
const ref = button?.reference;
|
973
|
+
const element = this[navElementSymbol].querySelector(
|
974
|
+
`:scope > [${ATTRIBUTE_PREFIX}tab-reference="${ref}"]`,
|
975
|
+
);
|
976
|
+
if (!(element instanceof HTMLButtonElement)) continue;
|
977
|
+
|
978
|
+
this[dimensionsSymbol].setVia(
|
979
|
+
`data.button.${ref}`,
|
980
|
+
calcBoxWidth.call(this, element),
|
981
|
+
);
|
982
|
+
button["class"] = new TokenList(button["class"])
|
983
|
+
.remove("invisible")
|
984
|
+
.toString();
|
985
|
+
}
|
986
|
+
|
987
|
+
const slots = this[controlElementSymbol].querySelectorAll(
|
988
|
+
`nav[${ATTRIBUTE_PREFIX}role=nav] > slot.invisible, slot[${ATTRIBUTE_PREFIX}role=slot].invisible`,
|
989
|
+
);
|
990
|
+
for (const [, slot] of slots.entries()) {
|
991
|
+
slot.classList.remove("invisible");
|
992
|
+
}
|
993
|
+
|
994
|
+
this[dimensionsSymbol].setVia("data.calculated", true);
|
995
|
+
this.setOption("buttons.standard", clone(buttons));
|
996
|
+
}
|
997
|
+
|
998
|
+
/**
|
999
|
+
* @private
|
1000
|
+
* @param {HTMLElement} node
|
1001
|
+
* @return {string}
|
1002
|
+
*/
|
1003
|
+
function getButtonLabel(node) {
|
1004
|
+
let label;
|
1005
|
+
let setLabel = false;
|
1006
|
+
if (node.hasAttribute(ATTRIBUTE_BUTTON_LABEL)) {
|
1007
|
+
label = node.getAttribute(ATTRIBUTE_BUTTON_LABEL);
|
1008
|
+
} else {
|
1009
|
+
label = node.innerText;
|
1010
|
+
setLabel = true;
|
1011
|
+
}
|
1012
|
+
|
1013
|
+
if (!isString(label)) {
|
1014
|
+
label = "";
|
1015
|
+
}
|
1016
|
+
|
1017
|
+
label = label.trim();
|
1018
|
+
|
1019
|
+
if (label === "") {
|
1020
|
+
label = this.getOption("labels.new-tab-label", "New Tab");
|
1021
|
+
}
|
1022
|
+
|
1023
|
+
if (label.length > 100) {
|
1024
|
+
label = `${label.substring(0, 99)}…`;
|
1025
|
+
}
|
1026
|
+
|
1027
|
+
if (setLabel === true) {
|
1028
|
+
node.setAttribute(ATTRIBUTE_BUTTON_LABEL, label);
|
1029
|
+
}
|
1030
|
+
|
1031
|
+
return label;
|
1032
|
+
}
|
1033
|
+
|
1034
|
+
/**
|
1035
|
+
* @private
|
1036
|
+
* @return {string}
|
1037
|
+
*/
|
1038
|
+
function getTemplate() {
|
1039
|
+
// language=HTML
|
1040
|
+
return `
|
1041
|
+
<template id="buttons">
|
1042
|
+
<button part="button"
|
1043
|
+
data-monster-role="button"
|
1044
|
+
data-monster-attributes="
|
1045
|
+
class path:classes.button,
|
1046
|
+
data-monster-state path:buttons.state,
|
1047
|
+
disabled path:buttons.disabled | if:true,
|
1048
|
+
data-monster-tab-reference path:buttons.reference"><span
|
1049
|
+
data-monster-replace="path:buttons.label"></span><span part="remove-tab"
|
1050
|
+
data-monster-attributes="class path:buttons.remove | ?:remove-tab:hidden "
|
1051
|
+
data-monster-role="remove-tab"
|
1052
|
+
tabindex="-1"></span></button>
|
1053
|
+
</template>
|
1054
|
+
<div data-monster-role="control" part="control">
|
1055
|
+
<nav data-monster-role="nav" part="nav"
|
1056
|
+
data-monster-attributes="data-monster-marker path:marker"
|
1057
|
+
data-monster-insert="buttons path:buttons.standard">
|
1058
|
+
<slot name="start" class="invisible"></slot>
|
1059
|
+
<div data-monster-role="popper" part="popper" tabindex="-1"
|
1060
|
+
data-monster-attributes="class path:classes.popper">
|
1061
|
+
<div data-popper-arrow></div>
|
1062
|
+
|
1063
|
+
|
1064
|
+
<div part="popper-nav" data-monster-role="popper-nav"
|
1065
|
+
data-monster-insert="buttons path:buttons.popper"
|
1066
|
+
tabindex="-1"></div>
|
1067
|
+
</div>
|
1068
|
+
<slot name="popper" class="invisible"></slot>
|
1069
|
+
<slot name="end" class="invisible"></slot>
|
1070
|
+
</nav>
|
1071
|
+
<slot data-monster-role="slot" class="invisible"></slot>
|
1072
|
+
</div>`;
|
1073
|
+
}
|
1074
|
+
|
1075
|
+
registerCustomElement(Tabs);
|