@schukai/monster 4.139.1 → 4.140.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/control-bar-spacer.mjs +85 -0
- package/source/components/form/control-bar.mjs +77 -0
- package/source/components/form/style/control-bar-spacer.pcss +28 -0
- package/source/components/form/style/control-bar.pcss +14 -0
- package/source/components/form/stylesheet/control-bar-spacer.mjs +38 -0
- package/source/components/form/stylesheet/control-bar.mjs +1 -1
- package/source/components/layout/tabs.mjs +384 -68
- package/source/components/layout/utils/attach-tabs-hash-sync.mjs +10 -5
- package/source/monster.mjs +2 -1
- package/test/cases/components/form/control-bar-spacer.mjs +51 -0
- package/test/cases/components/form/control-bar.mjs +181 -2
- package/test/cases/components/layout/tabs.mjs +323 -0
|
@@ -25,6 +25,10 @@ export function attachTabsHashSync(
|
|
|
25
25
|
let lastKnownActiveTabId = null;
|
|
26
26
|
let lastKnownAllTabIds = [];
|
|
27
27
|
|
|
28
|
+
function getTabKey(tab) {
|
|
29
|
+
return tab.getAttribute("data-monster-name") || tab.getAttribute("id");
|
|
30
|
+
}
|
|
31
|
+
|
|
28
32
|
/**
|
|
29
33
|
* Reads active and all tab IDs from the URL hash.
|
|
30
34
|
* @returns {{activeTabId: string|null, allTabIds: string[]}}
|
|
@@ -53,7 +57,7 @@ export function attachTabsHashSync(
|
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
// Sync all tabs (add/remove tabs based on hash)
|
|
56
|
-
const currentTabs = tabsEl.getTabs().map(
|
|
60
|
+
const currentTabs = tabsEl.getTabs().map(getTabKey);
|
|
57
61
|
|
|
58
62
|
// Add tabs that are in hash but not in DOM
|
|
59
63
|
for (const tabId of allTabIds) {
|
|
@@ -120,8 +124,9 @@ export function attachTabsHashSync(
|
|
|
120
124
|
// Listen for tab changes (active tab)
|
|
121
125
|
tabsEl.addEventListener("monster-tab-changed", (e) => {
|
|
122
126
|
if (e.target !== tabsEl) return; // Ignore bubbled events
|
|
123
|
-
const newActiveTabId =
|
|
124
|
-
|
|
127
|
+
const newActiveTabId =
|
|
128
|
+
e.detail?.tab || e.detail?.name || e.detail?.reference;
|
|
129
|
+
const currentTabs = tabsEl.getTabs().map(getTabKey);
|
|
125
130
|
writeHash(newActiveTabId, currentTabs);
|
|
126
131
|
});
|
|
127
132
|
|
|
@@ -154,7 +159,7 @@ export function attachTabsHashSync(
|
|
|
154
159
|
}
|
|
155
160
|
if (tabsChanged) {
|
|
156
161
|
const currentActiveTabId = tabsEl.getActiveTab();
|
|
157
|
-
const currentTabs = tabsEl.getTabs().map(
|
|
162
|
+
const currentTabs = tabsEl.getTabs().map(getTabKey);
|
|
158
163
|
// Only update hash if the list of tabs has actually changed
|
|
159
164
|
if (
|
|
160
165
|
currentTabs.length !== lastKnownAllTabIds.length ||
|
|
@@ -170,6 +175,6 @@ export function attachTabsHashSync(
|
|
|
170
175
|
|
|
171
176
|
// Initial write of all existing tabs to the hash
|
|
172
177
|
const initialActiveTab = tabsEl.getActiveTab();
|
|
173
|
-
const initialTabs = tabsEl.getTabs().map(
|
|
178
|
+
const initialTabs = tabsEl.getTabs().map(getTabKey);
|
|
174
179
|
writeHash(initialActiveTab, initialTabs);
|
|
175
180
|
}
|
package/source/monster.mjs
CHANGED
|
@@ -55,6 +55,7 @@ export * from "./components/form/util/floating-ui.mjs";
|
|
|
55
55
|
export * from "./components/form/context-help.mjs";
|
|
56
56
|
export * from "./components/form/api-bar.mjs";
|
|
57
57
|
export * from "./components/form/tabs.mjs";
|
|
58
|
+
export * from "./components/form/control-bar-spacer.mjs";
|
|
58
59
|
export * from "./components/form/state-button.mjs";
|
|
59
60
|
export * from "./components/form/popper.mjs";
|
|
60
61
|
export * from "./components/form/cart-control.mjs";
|
|
@@ -69,6 +70,7 @@ export * from "./components/form/context-warning.mjs";
|
|
|
69
70
|
export * from "./components/form/context-note.mjs";
|
|
70
71
|
export * from "./components/form/context-error.mjs";
|
|
71
72
|
export * from "./components/form/variant-select.mjs";
|
|
73
|
+
export * from "./components/form/choice-cards.mjs";
|
|
72
74
|
export * from "./components/form/register-wizard.mjs";
|
|
73
75
|
export * from "./components/form/action-button.mjs";
|
|
74
76
|
export * from "./components/form/form.mjs";
|
|
@@ -83,7 +85,6 @@ export * from "./components/form/credential-button.mjs";
|
|
|
83
85
|
export * from "./components/form/wizard.mjs";
|
|
84
86
|
export * from "./components/form/input-group.mjs";
|
|
85
87
|
export * from "./components/form/checklist.mjs";
|
|
86
|
-
export * from "./components/form/choice-cards.mjs";
|
|
87
88
|
export * from "./components/form/shadow-reload.mjs";
|
|
88
89
|
export * from "./components/form/button.mjs";
|
|
89
90
|
export * from "./components/form/field-set.mjs";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as chai from "chai";
|
|
2
|
+
import { chaiDom } from "../../../util/chai-dom.mjs";
|
|
3
|
+
import { initJSDOM } from "../../../util/jsdom.mjs";
|
|
4
|
+
|
|
5
|
+
let expect = chai.expect;
|
|
6
|
+
chai.use(chaiDom);
|
|
7
|
+
|
|
8
|
+
let ControlBarSpacer;
|
|
9
|
+
|
|
10
|
+
describe("ControlBarSpacer", function () {
|
|
11
|
+
before(function (done) {
|
|
12
|
+
initJSDOM()
|
|
13
|
+
.then(() =>
|
|
14
|
+
import("../../../../source/components/form/control-bar-spacer.mjs"),
|
|
15
|
+
)
|
|
16
|
+
.then((m) => {
|
|
17
|
+
ControlBarSpacer = m.ControlBarSpacer;
|
|
18
|
+
done();
|
|
19
|
+
})
|
|
20
|
+
.catch((e) => done(e));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should create a control bar spacer element", function () {
|
|
24
|
+
const spacer = document.createElement("monster-control-bar-spacer");
|
|
25
|
+
|
|
26
|
+
expect(spacer).to.be.instanceof(ControlBarSpacer);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should render a non-interactive separator line", function () {
|
|
30
|
+
const spacer = document.createElement("monster-control-bar-spacer");
|
|
31
|
+
document.getElementById("mocks").appendChild(spacer);
|
|
32
|
+
|
|
33
|
+
const separator = spacer.shadowRoot.querySelector(
|
|
34
|
+
'[data-monster-role="separator"]',
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
expect(separator).to.be.instanceof(HTMLDivElement);
|
|
38
|
+
expect(spacer.getAttribute("aria-hidden")).to.equal("true");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should define spacer dimensions through css variables", function () {
|
|
42
|
+
const cssText = ControlBarSpacer.getCSSStyleSheet()
|
|
43
|
+
.flatMap((styleSheet) => Array.from(styleSheet.cssRules))
|
|
44
|
+
.map((rule) => rule.cssText)
|
|
45
|
+
.join("\n");
|
|
46
|
+
|
|
47
|
+
expect(cssText).to.contain("--monster-control-bar-spacer-inline-size");
|
|
48
|
+
expect(cssText).to.contain("--monster-control-bar-spacer-block-size");
|
|
49
|
+
expect(cssText).to.contain("pointer-events: none");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -27,9 +27,12 @@ describe("ControlBar", function () {
|
|
|
27
27
|
this.timeout(5000);
|
|
28
28
|
initJSDOM()
|
|
29
29
|
.then(() => {
|
|
30
|
-
|
|
30
|
+
Promise.all([
|
|
31
|
+
import("../../../../source/components/form/control-bar.mjs"),
|
|
32
|
+
import("../../../../source/components/form/control-bar-spacer.mjs"),
|
|
33
|
+
])
|
|
31
34
|
.then((m) => {
|
|
32
|
-
ControlBar = m.ControlBar;
|
|
35
|
+
ControlBar = m[0].ControlBar;
|
|
33
36
|
done();
|
|
34
37
|
})
|
|
35
38
|
.catch((e) => done(e));
|
|
@@ -126,6 +129,13 @@ describe("ControlBar", function () {
|
|
|
126
129
|
expect(cssText).to.contain("appearance: none");
|
|
127
130
|
expect(cssText).to.contain("--monster-select-container-overflow: hidden");
|
|
128
131
|
expect(cssText).to.contain("::slotted(monster-input-group)");
|
|
132
|
+
expect(cssText).to.contain("::slotted(monster-control-bar-spacer)");
|
|
133
|
+
expect(cssText).to.contain(
|
|
134
|
+
"--monster-control-bar-spacer-line-block-size: 60%",
|
|
135
|
+
);
|
|
136
|
+
expect(cssText).to.contain(
|
|
137
|
+
"--monster-control-bar-spacer-line-inline-size: calc(100% - 1rem)",
|
|
138
|
+
);
|
|
129
139
|
});
|
|
130
140
|
|
|
131
141
|
it("should join adjacent borders using the smaller computed border width", async function () {
|
|
@@ -301,6 +311,175 @@ describe("ControlBar", function () {
|
|
|
301
311
|
}
|
|
302
312
|
});
|
|
303
313
|
|
|
314
|
+
it("should size inline spacer lines from adjacent border widths", async function () {
|
|
315
|
+
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
|
316
|
+
const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
|
|
317
|
+
|
|
318
|
+
const scheduledCallbacks = [];
|
|
319
|
+
const flushFrames = async () => {
|
|
320
|
+
while (scheduledCallbacks.length > 0) {
|
|
321
|
+
scheduledCallbacks.shift()();
|
|
322
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
window.requestAnimationFrame = (callback) => {
|
|
328
|
+
scheduledCallbacks.push(callback);
|
|
329
|
+
return scheduledCallbacks.length;
|
|
330
|
+
};
|
|
331
|
+
globalThis.requestAnimationFrame = window.requestAnimationFrame;
|
|
332
|
+
|
|
333
|
+
const mocks = document.getElementById("mocks");
|
|
334
|
+
mocks.innerHTML = `
|
|
335
|
+
<div id="spacer-border-bar-wrapper">
|
|
336
|
+
<monster-control-bar id="spacer-border-bar">
|
|
337
|
+
<button id="spacer-border-left">A</button>
|
|
338
|
+
<monster-control-bar-spacer id="spacer-border-spacer"></monster-control-bar-spacer>
|
|
339
|
+
<input id="spacer-border-right">
|
|
340
|
+
</monster-control-bar>
|
|
341
|
+
</div>
|
|
342
|
+
`;
|
|
343
|
+
|
|
344
|
+
const wrapper = document.getElementById("spacer-border-bar-wrapper");
|
|
345
|
+
const left = document.getElementById("spacer-border-left");
|
|
346
|
+
const spacer = document.getElementById("spacer-border-spacer");
|
|
347
|
+
const right = document.getElementById("spacer-border-right");
|
|
348
|
+
|
|
349
|
+
wrapper.style.boxSizing = "border-box";
|
|
350
|
+
wrapper.style.width = "300px";
|
|
351
|
+
Object.defineProperty(wrapper, "clientWidth", {
|
|
352
|
+
configurable: true,
|
|
353
|
+
value: 300,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
for (const control of [left, spacer, right]) {
|
|
357
|
+
Object.defineProperty(control, "offsetWidth", {
|
|
358
|
+
configurable: true,
|
|
359
|
+
value: 40,
|
|
360
|
+
});
|
|
361
|
+
Object.defineProperty(control, "offsetHeight", {
|
|
362
|
+
configurable: true,
|
|
363
|
+
value: 30,
|
|
364
|
+
});
|
|
365
|
+
control.getBoundingClientRect = () => ({
|
|
366
|
+
width: 40,
|
|
367
|
+
height: 30,
|
|
368
|
+
top: 0,
|
|
369
|
+
right: 40,
|
|
370
|
+
bottom: 30,
|
|
371
|
+
left: 0,
|
|
372
|
+
x: 0,
|
|
373
|
+
y: 0,
|
|
374
|
+
toJSON: () => {},
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
left.style.borderRightWidth = "4px";
|
|
379
|
+
right.style.borderLeftWidth = "2px";
|
|
380
|
+
|
|
381
|
+
await flushFrames();
|
|
382
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
383
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
384
|
+
|
|
385
|
+
expect(
|
|
386
|
+
spacer.style.getPropertyValue(
|
|
387
|
+
"--monster-control-bar-spacer-line-inline-size",
|
|
388
|
+
),
|
|
389
|
+
).to.equal("4px");
|
|
390
|
+
expect(spacer.style.marginLeft).to.equal("");
|
|
391
|
+
expect(right.style.marginLeft).to.equal("");
|
|
392
|
+
} finally {
|
|
393
|
+
window.requestAnimationFrame = originalRequestAnimationFrame;
|
|
394
|
+
globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("should size popper spacer lines from adjacent border widths", async function () {
|
|
399
|
+
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
|
400
|
+
const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
|
|
401
|
+
|
|
402
|
+
const scheduledCallbacks = [];
|
|
403
|
+
const flushFrames = async () => {
|
|
404
|
+
while (scheduledCallbacks.length > 0) {
|
|
405
|
+
scheduledCallbacks.shift()();
|
|
406
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
try {
|
|
411
|
+
window.requestAnimationFrame = (callback) => {
|
|
412
|
+
scheduledCallbacks.push(callback);
|
|
413
|
+
return scheduledCallbacks.length;
|
|
414
|
+
};
|
|
415
|
+
globalThis.requestAnimationFrame = window.requestAnimationFrame;
|
|
416
|
+
|
|
417
|
+
const mocks = document.getElementById("mocks");
|
|
418
|
+
mocks.innerHTML = `
|
|
419
|
+
<div id="popper-spacer-border-bar-wrapper">
|
|
420
|
+
<monster-control-bar id="popper-spacer-border-bar">
|
|
421
|
+
<button id="popper-spacer-border-top">A</button>
|
|
422
|
+
<monster-control-bar-spacer id="popper-spacer-border-spacer"></monster-control-bar-spacer>
|
|
423
|
+
<input id="popper-spacer-border-bottom">
|
|
424
|
+
</monster-control-bar>
|
|
425
|
+
</div>
|
|
426
|
+
`;
|
|
427
|
+
|
|
428
|
+
const wrapper = document.getElementById("popper-spacer-border-bar-wrapper");
|
|
429
|
+
const top = document.getElementById("popper-spacer-border-top");
|
|
430
|
+
const spacer = document.getElementById("popper-spacer-border-spacer");
|
|
431
|
+
const bottom = document.getElementById("popper-spacer-border-bottom");
|
|
432
|
+
|
|
433
|
+
wrapper.style.boxSizing = "border-box";
|
|
434
|
+
wrapper.style.width = "1px";
|
|
435
|
+
Object.defineProperty(wrapper, "clientWidth", {
|
|
436
|
+
configurable: true,
|
|
437
|
+
value: 1,
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
for (const control of [top, spacer, bottom]) {
|
|
441
|
+
Object.defineProperty(control, "offsetWidth", {
|
|
442
|
+
configurable: true,
|
|
443
|
+
value: 40,
|
|
444
|
+
});
|
|
445
|
+
Object.defineProperty(control, "offsetHeight", {
|
|
446
|
+
configurable: true,
|
|
447
|
+
value: 30,
|
|
448
|
+
});
|
|
449
|
+
control.getBoundingClientRect = () => ({
|
|
450
|
+
width: 40,
|
|
451
|
+
height: 30,
|
|
452
|
+
top: 0,
|
|
453
|
+
right: 40,
|
|
454
|
+
bottom: 30,
|
|
455
|
+
left: 0,
|
|
456
|
+
x: 0,
|
|
457
|
+
y: 0,
|
|
458
|
+
toJSON: () => {},
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
top.style.borderBottomWidth = "2px";
|
|
463
|
+
bottom.style.borderTopWidth = "5px";
|
|
464
|
+
|
|
465
|
+
await flushFrames();
|
|
466
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
467
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
468
|
+
|
|
469
|
+
expect(spacer.getAttribute("slot")).to.equal("popper");
|
|
470
|
+
expect(
|
|
471
|
+
spacer.style.getPropertyValue(
|
|
472
|
+
"--monster-control-bar-spacer-line-block-size",
|
|
473
|
+
),
|
|
474
|
+
).to.equal("5px");
|
|
475
|
+
expect(spacer.style.marginTop).to.equal("");
|
|
476
|
+
expect(bottom.style.marginTop).to.equal("");
|
|
477
|
+
} finally {
|
|
478
|
+
window.requestAnimationFrame = originalRequestAnimationFrame;
|
|
479
|
+
globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
304
483
|
it("should move overflowing mixed controls into the popper", async function () {
|
|
305
484
|
const OriginalResizeObserver = window.ResizeObserver;
|
|
306
485
|
const originalGlobalResizeObserver = globalThis.ResizeObserver;
|
|
@@ -61,6 +61,90 @@ let htmlOverflow = `
|
|
|
61
61
|
</monster-tabs>
|
|
62
62
|
`;
|
|
63
63
|
|
|
64
|
+
// language=html
|
|
65
|
+
let htmlAvailability = `
|
|
66
|
+
<monster-tabs id="availability-tabs">
|
|
67
|
+
<div id="overview-panel" data-monster-name="overview" data-monster-button-label="Overview">
|
|
68
|
+
Overview content
|
|
69
|
+
</div>
|
|
70
|
+
<div id="vacation-panel" data-monster-name="vacation" data-monster-button-label="Vacation" data-monster-tab-available="false">
|
|
71
|
+
Vacation content
|
|
72
|
+
</div>
|
|
73
|
+
<div id="details-panel" data-monster-name="details" data-monster-button-label="Details">
|
|
74
|
+
Details content
|
|
75
|
+
</div>
|
|
76
|
+
</monster-tabs>
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
// language=html
|
|
80
|
+
let htmlActiveUnavailable = `
|
|
81
|
+
<monster-tabs id="fallback-tabs">
|
|
82
|
+
<div id="first-panel" data-monster-name="first" data-monster-button-label="First">
|
|
83
|
+
First content
|
|
84
|
+
</div>
|
|
85
|
+
<div id="second-panel" data-monster-name="second" data-monster-button-label="Second" class="active">
|
|
86
|
+
Second content
|
|
87
|
+
</div>
|
|
88
|
+
<div id="third-panel" data-monster-name="third" data-monster-button-label="Third">
|
|
89
|
+
Third content
|
|
90
|
+
</div>
|
|
91
|
+
</monster-tabs>
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
// language=html
|
|
95
|
+
let htmlRemoveFallback = `
|
|
96
|
+
<monster-tabs id="remove-fallback-tabs" data-monster-options='{"features":{"openFirst":false}}'>
|
|
97
|
+
<div id="remove-first-panel" data-monster-name="first" data-monster-button-label="First">
|
|
98
|
+
First content
|
|
99
|
+
</div>
|
|
100
|
+
<div id="remove-second-panel" data-monster-name="second" data-monster-button-label="Second" class="active">
|
|
101
|
+
Second content
|
|
102
|
+
</div>
|
|
103
|
+
<div id="remove-third-panel" data-monster-name="third" data-monster-button-label="Third">
|
|
104
|
+
Third content
|
|
105
|
+
</div>
|
|
106
|
+
</monster-tabs>
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
// language=html
|
|
110
|
+
let htmlDisabled = `
|
|
111
|
+
<monster-tabs id="disabled-tabs">
|
|
112
|
+
<div id="enabled-panel" data-monster-name="enabled" data-monster-button-label="Enabled">
|
|
113
|
+
Enabled content
|
|
114
|
+
</div>
|
|
115
|
+
<div id="disabled-panel"
|
|
116
|
+
data-monster-name="disabled"
|
|
117
|
+
data-monster-button-label="Disabled"
|
|
118
|
+
data-monster-tab-disabled="true"
|
|
119
|
+
data-monster-tab-disabled-reason="Only available for employees">
|
|
120
|
+
Disabled content
|
|
121
|
+
</div>
|
|
122
|
+
</monster-tabs>
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
// language=html
|
|
126
|
+
let htmlMetadata = `
|
|
127
|
+
<monster-tabs id="metadata-tabs">
|
|
128
|
+
<div id="metadata-panel"
|
|
129
|
+
data-monster-name="metadata"
|
|
130
|
+
data-monster-button-label="Metadata"
|
|
131
|
+
data-monster-tab-kind="profile"
|
|
132
|
+
data-monster-tab-priority="10"
|
|
133
|
+
data-monster-tab-group="main">
|
|
134
|
+
Metadata content
|
|
135
|
+
</div>
|
|
136
|
+
</monster-tabs>
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
// language=html
|
|
140
|
+
let htmlNoDerivedLabel = `
|
|
141
|
+
<monster-tabs id="label-tabs" data-monster-options='{"features":{"deriveLabelFromContent":false}}'>
|
|
142
|
+
<div id="content-panel">
|
|
143
|
+
Overview about your profile and vacation configuration
|
|
144
|
+
</div>
|
|
145
|
+
</monster-tabs>
|
|
146
|
+
`;
|
|
147
|
+
|
|
64
148
|
let Tabs;
|
|
65
149
|
let restoreBoundingClientRect = null;
|
|
66
150
|
let restoreResizeObserver = null;
|
|
@@ -407,6 +491,245 @@ describe('Tabs', function () {
|
|
|
407
491
|
});
|
|
408
492
|
});
|
|
409
493
|
|
|
494
|
+
it('should ignore unavailable panels when creating tabs and buttons', function (done) {
|
|
495
|
+
let mocks = document.getElementById('mocks');
|
|
496
|
+
mocks.innerHTML = htmlAvailability;
|
|
497
|
+
|
|
498
|
+
waitForCondition(() => {
|
|
499
|
+
const tabs = document.getElementById('availability-tabs');
|
|
500
|
+
return (
|
|
501
|
+
tabs instanceof Tabs &&
|
|
502
|
+
tabs.shadowRoot.querySelectorAll('button[part=button]').length === 2 &&
|
|
503
|
+
tabs.getActiveTab() === 'overview'
|
|
504
|
+
);
|
|
505
|
+
}).then(() => {
|
|
506
|
+
try {
|
|
507
|
+
const tabs = document.getElementById('availability-tabs');
|
|
508
|
+
const vacation = document.getElementById('vacation-panel');
|
|
509
|
+
const buttons = tabs.shadowRoot.querySelectorAll('button[part=button]');
|
|
510
|
+
|
|
511
|
+
expect(tabs.getTabs().length).to.equal(2);
|
|
512
|
+
expect(tabs.getTabs().includes(vacation)).to.equal(false);
|
|
513
|
+
expect(tabs.shadowRoot.querySelector(
|
|
514
|
+
`button[part=button][data-monster-tab-reference="${vacation.getAttribute('id')}"]`,
|
|
515
|
+
)).to.equal(null);
|
|
516
|
+
expect(buttons[0].textContent.trim()).to.equal('Overview');
|
|
517
|
+
expect(buttons[1].textContent.trim()).to.equal('Details');
|
|
518
|
+
expect(tabs.getActiveTab()).to.equal('overview');
|
|
519
|
+
done();
|
|
520
|
+
} catch (e) {
|
|
521
|
+
done(e);
|
|
522
|
+
}
|
|
523
|
+
}).catch(done);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('should ignore unavailable tabs in activeTab and rebuild when they become available', function (done) {
|
|
527
|
+
let mocks = document.getElementById('mocks');
|
|
528
|
+
mocks.innerHTML = htmlAvailability;
|
|
529
|
+
|
|
530
|
+
waitForCondition(() => {
|
|
531
|
+
const tabs = document.getElementById('availability-tabs');
|
|
532
|
+
return tabs instanceof Tabs && tabs.getActiveTab() === 'overview';
|
|
533
|
+
}).then(() => {
|
|
534
|
+
const tabs = document.getElementById('availability-tabs');
|
|
535
|
+
const vacation = document.getElementById('vacation-panel');
|
|
536
|
+
let availabilityEvent = null;
|
|
537
|
+
tabs.addEventListener('monster-tab-availability-changed', (event) => {
|
|
538
|
+
availabilityEvent = event;
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
tabs.activeTab('vacation');
|
|
542
|
+
expect(tabs.getActiveTab()).to.equal('overview');
|
|
543
|
+
|
|
544
|
+
vacation.setAttribute('data-monster-tab-available', 'true');
|
|
545
|
+
|
|
546
|
+
return waitForCondition(() => {
|
|
547
|
+
return tabs.shadowRoot.querySelectorAll('button[part=button]').length === 3;
|
|
548
|
+
}).then(() => {
|
|
549
|
+
try {
|
|
550
|
+
expect(availabilityEvent).to.not.equal(null);
|
|
551
|
+
expect(availabilityEvent.detail.name).to.equal('vacation');
|
|
552
|
+
expect(availabilityEvent.detail.available).to.equal(true);
|
|
553
|
+
|
|
554
|
+
tabs.activeTab('vacation');
|
|
555
|
+
expect(tabs.getActiveTab()).to.equal('vacation');
|
|
556
|
+
done();
|
|
557
|
+
} catch (e) {
|
|
558
|
+
done(e);
|
|
559
|
+
}
|
|
560
|
+
}, 100);
|
|
561
|
+
}).catch(done);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it('should fallback when the active tab becomes unavailable', function (done) {
|
|
565
|
+
let mocks = document.getElementById('mocks');
|
|
566
|
+
mocks.innerHTML = htmlActiveUnavailable;
|
|
567
|
+
|
|
568
|
+
waitForCondition(() => {
|
|
569
|
+
const tabs = document.getElementById('fallback-tabs');
|
|
570
|
+
return tabs instanceof Tabs && tabs.getActiveTab() === 'second';
|
|
571
|
+
}).then(() => {
|
|
572
|
+
const tabs = document.getElementById('fallback-tabs');
|
|
573
|
+
const activePanel = document.getElementById('second-panel');
|
|
574
|
+
activePanel.setAttribute('data-monster-tab-available', 'false');
|
|
575
|
+
|
|
576
|
+
return waitForCondition(() => tabs.getActiveTab() === 'first').then(() => {
|
|
577
|
+
try {
|
|
578
|
+
expect(activePanel.classList.contains('active')).to.equal(false);
|
|
579
|
+
expect(tabs.shadowRoot.querySelectorAll('button[part=button]').length).to.equal(2);
|
|
580
|
+
done();
|
|
581
|
+
} catch (e) {
|
|
582
|
+
done(e);
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
}).catch(done);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it('should fallback when the active tab is removed directly', function (done) {
|
|
589
|
+
let mocks = document.getElementById('mocks');
|
|
590
|
+
mocks.innerHTML = htmlRemoveFallback;
|
|
591
|
+
|
|
592
|
+
waitForCondition(() => {
|
|
593
|
+
const tabs = document.getElementById('remove-fallback-tabs');
|
|
594
|
+
return (
|
|
595
|
+
tabs instanceof Tabs &&
|
|
596
|
+
tabs.getActiveTab() === 'second' &&
|
|
597
|
+
tabs.shadowRoot.querySelectorAll('button[part=button]').length === 3
|
|
598
|
+
);
|
|
599
|
+
}).then(() => {
|
|
600
|
+
const tabs = document.getElementById('remove-fallback-tabs');
|
|
601
|
+
setTimeout(() => {
|
|
602
|
+
document.getElementById('remove-second-panel').remove();
|
|
603
|
+
|
|
604
|
+
waitForCondition(() => tabs.getActiveTab() === 'first').then(() => {
|
|
605
|
+
try {
|
|
606
|
+
expect(tabs.shadowRoot.querySelectorAll('button[part=button]').length).to.equal(2);
|
|
607
|
+
done();
|
|
608
|
+
} catch (e) {
|
|
609
|
+
done(e);
|
|
610
|
+
}
|
|
611
|
+
}).catch(done);
|
|
612
|
+
});
|
|
613
|
+
}).catch(done);
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
it('should keep disabled tabs visible but not activatable', function (done) {
|
|
617
|
+
let mocks = document.getElementById('mocks');
|
|
618
|
+
mocks.innerHTML = htmlDisabled;
|
|
619
|
+
|
|
620
|
+
waitForCondition(() => {
|
|
621
|
+
const tabs = document.getElementById('disabled-tabs');
|
|
622
|
+
const disabledPanel = document.getElementById('disabled-panel');
|
|
623
|
+
const disabledButton = tabs?.shadowRoot?.querySelector(
|
|
624
|
+
`button[part=button][data-monster-tab-reference="${disabledPanel?.getAttribute('id')}"]`,
|
|
625
|
+
);
|
|
626
|
+
return (
|
|
627
|
+
tabs instanceof Tabs &&
|
|
628
|
+
disabledButton instanceof HTMLButtonElement &&
|
|
629
|
+
tabs.getActiveTab() === 'enabled'
|
|
630
|
+
);
|
|
631
|
+
}).then(() => {
|
|
632
|
+
try {
|
|
633
|
+
const tabs = document.getElementById('disabled-tabs');
|
|
634
|
+
const disabledPanel = document.getElementById('disabled-panel');
|
|
635
|
+
const disabledButton = tabs.shadowRoot.querySelector(
|
|
636
|
+
`button[part=button][data-monster-tab-reference="${disabledPanel.getAttribute('id')}"]`,
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
expect(disabledButton.disabled).to.equal(true);
|
|
640
|
+
expect(disabledButton.getAttribute('title')).to.equal('Only available for employees');
|
|
641
|
+
tabs.activeTab('disabled');
|
|
642
|
+
expect(tabs.getActiveTab()).to.equal('enabled');
|
|
643
|
+
done();
|
|
644
|
+
} catch (e) {
|
|
645
|
+
done(e);
|
|
646
|
+
}
|
|
647
|
+
}).catch(done);
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
it('should expose refresh, sync, hide and show APIs', function (done) {
|
|
651
|
+
let mocks = document.getElementById('mocks');
|
|
652
|
+
mocks.innerHTML = htmlAvailability;
|
|
653
|
+
|
|
654
|
+
waitForCondition(() => {
|
|
655
|
+
const tabs = document.getElementById('availability-tabs');
|
|
656
|
+
return tabs instanceof Tabs && tabs.shadowRoot.querySelectorAll('button[part=button]').length === 2;
|
|
657
|
+
}).then(() => {
|
|
658
|
+
const tabs = document.getElementById('availability-tabs');
|
|
659
|
+
|
|
660
|
+
tabs.showTab('vacation');
|
|
661
|
+
return tabs.refreshTabs().then((result) => {
|
|
662
|
+
expect(result).to.equal(tabs);
|
|
663
|
+
expect(tabs.getTabs().length).to.equal(3);
|
|
664
|
+
|
|
665
|
+
tabs.hideTab('vacation');
|
|
666
|
+
return tabs.syncTabs();
|
|
667
|
+
}).then((result) => {
|
|
668
|
+
try {
|
|
669
|
+
expect(result).to.equal(tabs);
|
|
670
|
+
expect(tabs.getTabs().length).to.equal(2);
|
|
671
|
+
expect(document.getElementById('vacation-panel').getAttribute('data-monster-tab-available')).to.equal('false');
|
|
672
|
+
done();
|
|
673
|
+
} catch (e) {
|
|
674
|
+
done(e);
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
}).catch(done);
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
it('should not derive labels from content when disabled by option', function (done) {
|
|
681
|
+
let mocks = document.getElementById('mocks');
|
|
682
|
+
mocks.innerHTML = htmlNoDerivedLabel;
|
|
683
|
+
|
|
684
|
+
waitForCondition(() => {
|
|
685
|
+
const tabs = document.getElementById('label-tabs');
|
|
686
|
+
return tabs instanceof Tabs && tabs.shadowRoot.querySelector('button[part=button]') !== null;
|
|
687
|
+
}).then(() => {
|
|
688
|
+
try {
|
|
689
|
+
const tabs = document.getElementById('label-tabs');
|
|
690
|
+
const button = tabs.shadowRoot.querySelector('button[part=button]');
|
|
691
|
+
expect(button.textContent.trim()).to.equal('New Tab');
|
|
692
|
+
expect(document.getElementById('content-panel').hasAttribute('data-monster-button-label')).to.equal(false);
|
|
693
|
+
done();
|
|
694
|
+
} catch (e) {
|
|
695
|
+
done(e);
|
|
696
|
+
}
|
|
697
|
+
}).catch(done);
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
it('should include stable names and metadata in tab events and buttons', function (done) {
|
|
701
|
+
let mocks = document.getElementById('mocks');
|
|
702
|
+
mocks.innerHTML = htmlMetadata;
|
|
703
|
+
|
|
704
|
+
waitForCondition(() => {
|
|
705
|
+
const tabs = document.getElementById('metadata-tabs');
|
|
706
|
+
return tabs instanceof Tabs && tabs.shadowRoot.querySelector('button[part=button]') !== null;
|
|
707
|
+
}).then(() => {
|
|
708
|
+
try {
|
|
709
|
+
const tabs = document.getElementById('metadata-tabs');
|
|
710
|
+
const button = tabs.shadowRoot.querySelector('button[part=button]');
|
|
711
|
+
let changedEvent = null;
|
|
712
|
+
tabs.addEventListener('monster-tab-changed', (event) => {
|
|
713
|
+
changedEvent = event;
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
expect(button.getAttribute('data-monster-tab-name')).to.equal('metadata');
|
|
717
|
+
expect(button.getAttribute('data-monster-tab-kind')).to.equal('profile');
|
|
718
|
+
expect(button.getAttribute('data-monster-tab-priority')).to.equal('10');
|
|
719
|
+
expect(button.getAttribute('data-monster-tab-group')).to.equal('main');
|
|
720
|
+
|
|
721
|
+
tabs.activeTab('metadata');
|
|
722
|
+
expect(changedEvent).to.not.equal(null);
|
|
723
|
+
expect(changedEvent.detail.name).to.equal('metadata');
|
|
724
|
+
expect(changedEvent.detail.tab).to.equal('metadata');
|
|
725
|
+
expect(changedEvent.detail.metadata.kind).to.equal('profile');
|
|
726
|
+
done();
|
|
727
|
+
} catch (e) {
|
|
728
|
+
done(e);
|
|
729
|
+
}
|
|
730
|
+
}).catch(done);
|
|
731
|
+
});
|
|
732
|
+
|
|
410
733
|
});
|
|
411
734
|
|
|
412
735
|
|