@orangesk/orange-design-system 3.12.0 → 4.0.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/build/components/Breadcrumbs/style.css +1 -1
- package/build/components/Breadcrumbs/style.css.map +1 -1
- package/build/components/Carousel/style.css +1 -1
- package/build/components/Carousel/style.css.map +1 -1
- package/build/components/Footer/style.css +1 -1
- package/build/components/Footer/style.css.map +1 -1
- package/build/components/Megamenu/style.css +1 -1
- package/build/components/Megamenu/style.css.map +1 -1
- package/build/components/index.js +1 -1
- package/build/components/index.js.map +1 -1
- package/build/lib/base.css +1 -1
- package/build/lib/base.css.map +1 -1
- package/build/lib/breadcrumbs.css +2 -2
- package/build/lib/breadcrumbs.css.map +1 -1
- package/build/lib/components.css +1 -1
- package/build/lib/components.css.map +1 -1
- package/build/lib/fonts.css +1 -1
- package/build/lib/fonts.css.map +1 -1
- package/build/lib/footer.css +1 -1
- package/build/lib/footer.css.map +1 -1
- package/build/lib/footer.iife.js +2 -0
- package/build/lib/footer.iife.js.map +1 -0
- package/build/lib/megamenu.css +1 -1
- package/build/lib/megamenu.css.map +1 -1
- package/build/lib/megamenu.iife.js +2 -0
- package/build/lib/megamenu.iife.js.map +1 -0
- package/build/lib/megamenu.js +1 -1
- package/build/lib/megamenu.js.map +1 -1
- package/build/lib/scripts.js +1 -1
- package/build/lib/scripts.js.map +1 -1
- package/build/lib/style.css +1 -1
- package/build/lib/style.css.map +1 -1
- package/package.json +10 -12
- package/src/components/AnchorNavigation/AnchorNavigation.static.ts +3 -4
- package/src/components/AnchorNavigation/tests/AnchorNavigation.megamenu.unit.test.ts +74 -0
- package/src/components/AnchorNavigation/tests/AnchorNavigation.unit.test.jsx +6 -0
- package/src/components/Breadcrumbs/styles/mixins.scss +6 -0
- package/src/components/Breadcrumbs/styles/rules.scss +30 -0
- package/src/components/Breadcrumbs/styles/style.scss +2 -26
- package/src/components/Carousel/Carousel.static.ts +0 -3
- package/src/components/Carousel/styles/mixins.scss +0 -4
- package/src/components/Carousel/tests/Carousel.interactions.test.ts +116 -85
- package/src/components/CarouselHero/CarouselHero.static.ts +0 -3
- package/src/components/CarouselPromotions/CarouselPromotions.static.ts +0 -3
- package/src/components/Footer/classic.ts +3 -0
- package/src/components/Footer/styles/mixins.scss +32 -1
- package/src/components/Footer/styles/style.scss +8 -5
- package/src/components/Footer/tests/Footer.styles.unit.test.jsx +7 -2
- package/src/components/Megamenu/Megamenu.static.ts +25 -6
- package/src/components/Megamenu/classic.ts +3 -0
- package/src/components/Megamenu/styles/mixins.scss +114 -52
- package/src/components/Megamenu/styles/style.scss +6 -7
- package/src/components/Megamenu/tests/Megamenu.scroll.unit.test.ts +185 -0
- package/src/styles/base/globals.scss +1 -37
- package/src/styles/base/header-height-values.scss +9 -0
- package/src/styles/base/header-height.scss +50 -0
- package/src/styles/base/styleguide.scss +0 -1
- package/build/files/tone-of-voice.pdf +0 -26572
- package/build/images/card-device-example.png +0 -0
- package/build/images/cta-device.png +0 -0
- package/build/images/cta-plans.png +0 -0
- package/build/images/cta-renewal.png +0 -0
- package/build/images/device-mobile.png +0 -0
- package/build/images/form-validation-format.png +0 -0
- package/build/images/form-validation-info-structure.png +0 -0
- package/build/images/form-validation-semantics.png +0 -0
- package/build/images/forms-labels-grouping.png +0 -0
- package/build/images/forms-labels-instruction.png +0 -0
- package/build/images/forms-labels-structure.png +0 -0
- package/build/images/hero/example-lg.jpg +0 -0
- package/build/images/hero/example-lg@2x.jpg +0 -0
- package/build/images/hero/example-xl.jpg +0 -0
- package/build/images/hero/example-xl@2x.jpg +0 -0
- package/build/images/hero/example-xxl.jpg +0 -0
- package/build/images/hero/example-xxl@2x.jpg +0 -0
- package/build/images/hero/example.jpg +0 -0
- package/build/images/hero/example@2x.jpg +0 -0
- package/build/images/hero/old-vendor-2x.jpg +0 -0
- package/build/images/hero/old-vendor-lg-2x.jpg +0 -0
- package/build/images/hero/old-vendor-lg.jpg +0 -0
- package/build/images/hero/old-vendor-xl-2x.jpg +0 -0
- package/build/images/hero/old-vendor-xl.jpg +0 -0
- package/build/images/hero/old-vendor-xxl-2x.jpg +0 -0
- package/build/images/hero/old-vendor-xxl.jpg +0 -0
- package/build/images/hero/old-vendor.jpg +0 -0
- package/build/images/hero/photo-2x.jpg +0 -0
- package/build/images/hero/photo-lg-2x.jpg +0 -0
- package/build/images/hero/photo-lg.jpg +0 -0
- package/build/images/hero/photo-xl-2x.jpg +0 -0
- package/build/images/hero/photo-xl.jpg +0 -0
- package/build/images/hero/photo-xxl-2x.jpg +0 -0
- package/build/images/hero/photo-xxl.jpg +0 -0
- package/build/images/hero/photo.jpg +0 -0
- package/build/images/hero/single.png +0 -0
- package/build/images/hero/single@2x.png +0 -0
- package/build/images/hero/vendor-lg.png +0 -0
- package/build/images/hero/vendor-lg@2x.png +0 -0
- package/build/images/hero/vendor-xl.png +0 -0
- package/build/images/hero/vendor-xl@2x.png +0 -0
- package/build/images/hero/vendor-xxl.png +0 -0
- package/build/images/hero/vendor-xxl@2x.png +0 -0
- package/build/images/hero/vendor.png +0 -0
- package/build/images/hero/vendor@2x.png +0 -0
- package/build/images/phones.png +0 -0
- package/build/images/product-1.svg +0 -10
- package/build/images/product-2.svg +0 -14
- package/build/images/product-3.svg +0 -9
- package/build/images/tone-of-voice.jpg +0 -0
- package/build/lib/base.js +0 -3
- package/build/lib/breadcrumbs.js +0 -3
- package/build/lib/components.js +0 -3
- package/build/lib/fonts.js +0 -3
- package/build/lib/reset.js +0 -3
- package/build/lib/style.js +0 -3
- package/build/lib/utilities.js +0 -3
|
@@ -21,7 +21,10 @@ describe("Footer standalone styles", () => {
|
|
|
21
21
|
afterAll(() => stylesheet.remove());
|
|
22
22
|
|
|
23
23
|
const styleFor = (selector) =>
|
|
24
|
-
rules.find(
|
|
24
|
+
rules.find(
|
|
25
|
+
(rule) =>
|
|
26
|
+
rule.selectorText.replaceAll(":is(:root, :host) ", "") === selector,
|
|
27
|
+
)?.style;
|
|
25
28
|
|
|
26
29
|
it("sizes social buttons and icons without global ODS styles", () => {
|
|
27
30
|
expect(styleFor(".footer .social-button")?.display).toBe("inline-flex");
|
|
@@ -54,7 +57,9 @@ describe("Footer standalone styles", () => {
|
|
|
54
57
|
);
|
|
55
58
|
expect(componentRules.length).toBeGreaterThan(0);
|
|
56
59
|
for (const rule of componentRules) {
|
|
57
|
-
for (const selector of rule.selectorText
|
|
60
|
+
for (const selector of rule.selectorText
|
|
61
|
+
.replaceAll(":is(:root, :host) ", "")
|
|
62
|
+
.split(",")) {
|
|
58
63
|
expect(selector.trim()).toMatch(/^\.footer /);
|
|
59
64
|
}
|
|
60
65
|
}
|
|
@@ -47,6 +47,7 @@ export default class Megamenu {
|
|
|
47
47
|
mousedownHandler!: () => void;
|
|
48
48
|
touchstartHandler!: () => void;
|
|
49
49
|
instanceName!: string;
|
|
50
|
+
private scrollFrameId: number | null = null;
|
|
50
51
|
|
|
51
52
|
private isModalOpen(): boolean {
|
|
52
53
|
return document.body.classList.contains(MODAL_OPEN_CLASS);
|
|
@@ -80,6 +81,7 @@ export default class Megamenu {
|
|
|
80
81
|
this.handleToggleClick = this.handleToggleClick.bind(this);
|
|
81
82
|
this.handleKeyPress = this.handleKeyPress.bind(this);
|
|
82
83
|
this.handleScroll = this.handleScroll.bind(this);
|
|
84
|
+
this.scheduleScrollUpdate = this.scheduleScrollUpdate.bind(this);
|
|
83
85
|
this.handleAccordionClick = this.handleAccordionClick.bind(this);
|
|
84
86
|
this.handleArrowNavigation = this.handleArrowNavigation.bind(this);
|
|
85
87
|
this.trapFocus = this.trapFocus.bind(this);
|
|
@@ -182,7 +184,9 @@ export default class Megamenu {
|
|
|
182
184
|
|
|
183
185
|
// Add scoped event listeners to this element
|
|
184
186
|
this.element.addEventListener("keydown", this.handleKeyPress);
|
|
185
|
-
window.addEventListener("scroll", this.
|
|
187
|
+
window.addEventListener("scroll", this.scheduleScrollUpdate, {
|
|
188
|
+
passive: true,
|
|
189
|
+
});
|
|
186
190
|
|
|
187
191
|
// Track keyboard usage for proper focus management
|
|
188
192
|
this.isKeyboardUser = false;
|
|
@@ -429,13 +433,21 @@ export default class Megamenu {
|
|
|
429
433
|
}
|
|
430
434
|
}
|
|
431
435
|
|
|
436
|
+
private scheduleScrollUpdate() {
|
|
437
|
+
if (this.scrollFrameId !== null) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
this.scrollFrameId = window.requestAnimationFrame(() => {
|
|
442
|
+
this.scrollFrameId = null;
|
|
443
|
+
this.handleScroll();
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
432
447
|
handleScroll() {
|
|
433
448
|
const topElement = this.element.querySelector(
|
|
434
449
|
'[data-hide-when-sticky="true"]',
|
|
435
450
|
) as HTMLElement;
|
|
436
|
-
const hasBrandMark = Boolean(
|
|
437
|
-
this.element.querySelector(BRAND_MARK_SELECTOR),
|
|
438
|
-
);
|
|
439
451
|
const effectiveScrollY = this.getEffectiveScrollY();
|
|
440
452
|
const hideThreshold = topElement?.offsetHeight || 15;
|
|
441
453
|
const shouldHideTop = effectiveScrollY > hideThreshold;
|
|
@@ -449,7 +461,10 @@ export default class Megamenu {
|
|
|
449
461
|
topElement.classList.remove("is-hidden");
|
|
450
462
|
this.element.classList.remove("top-hidden");
|
|
451
463
|
}
|
|
452
|
-
} else if (
|
|
464
|
+
} else if (
|
|
465
|
+
!this.element.classList.contains("non-sticky") &&
|
|
466
|
+
this.element.querySelector(BRAND_MARK_SELECTOR)
|
|
467
|
+
) {
|
|
453
468
|
this.element.classList.toggle("top-hidden", shouldHideTop);
|
|
454
469
|
}
|
|
455
470
|
}
|
|
@@ -775,7 +790,11 @@ export default class Megamenu {
|
|
|
775
790
|
});
|
|
776
791
|
|
|
777
792
|
this.element.removeEventListener("keydown", this.handleKeyPress);
|
|
778
|
-
window.removeEventListener("scroll", this.
|
|
793
|
+
window.removeEventListener("scroll", this.scheduleScrollUpdate);
|
|
794
|
+
if (this.scrollFrameId !== null) {
|
|
795
|
+
window.cancelAnimationFrame(this.scrollFrameId);
|
|
796
|
+
this.scrollFrameId = null;
|
|
797
|
+
}
|
|
779
798
|
|
|
780
799
|
// Remove keyboard tracking event listeners
|
|
781
800
|
this.element.removeEventListener("keydown", this.keydownHandler);
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
@use "../../Card/styles/mixins" as card;
|
|
19
19
|
@use "../../../styles/typography/mixins" as typography;
|
|
20
20
|
@use "../../../styles/tokens/overlay";
|
|
21
|
+
@use "../../../styles/base/header-height-values" as headerHeights;
|
|
21
22
|
|
|
22
23
|
@use "./config";
|
|
23
24
|
|
|
@@ -45,6 +46,34 @@
|
|
|
45
46
|
ul {
|
|
46
47
|
@include typography.list-unstyled();
|
|
47
48
|
margin-bottom: convert.to-rem(30px);
|
|
49
|
+
font-weight: 400;
|
|
50
|
+
|
|
51
|
+
// Match ODS body lists without depending on its global typography sheet.
|
|
52
|
+
@each $breakpoint, $props in map.get(typographyConfig.$body-text, "default")
|
|
53
|
+
{
|
|
54
|
+
@include breakpoint.get($breakpoint) {
|
|
55
|
+
font-size: map.get($props, font-size);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
li {
|
|
61
|
+
font-size: inherit;
|
|
62
|
+
line-height: inherit;
|
|
63
|
+
font-weight: inherit;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
:where(h3, strong, b) {
|
|
67
|
+
font-family: inherit;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
:where(strong, b) {
|
|
71
|
+
font-weight: 700;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
:where(button, input, select, textarea) {
|
|
75
|
+
font: inherit;
|
|
76
|
+
color: inherit;
|
|
48
77
|
}
|
|
49
78
|
|
|
50
79
|
h3 {
|
|
@@ -55,18 +84,24 @@
|
|
|
55
84
|
color: var(--color-text-default);
|
|
56
85
|
}
|
|
57
86
|
|
|
58
|
-
a {
|
|
87
|
+
:where(a) {
|
|
59
88
|
color: var(--color-text-default);
|
|
89
|
+
font-family: inherit;
|
|
60
90
|
font-weight: 700;
|
|
61
91
|
font-size: inherit;
|
|
62
92
|
text-decoration: underline;
|
|
63
93
|
text-underline-offset: convert.to-rem(2px);
|
|
64
94
|
text-decoration-thickness: auto;
|
|
95
|
+
background-color: transparent;
|
|
96
|
+
box-shadow: none;
|
|
65
97
|
|
|
66
98
|
&:hover,
|
|
67
99
|
&:focus,
|
|
68
100
|
&:active {
|
|
69
101
|
color: var(--color-text-accent);
|
|
102
|
+
text-decoration: underline;
|
|
103
|
+
background-color: transparent;
|
|
104
|
+
box-shadow: none;
|
|
70
105
|
}
|
|
71
106
|
|
|
72
107
|
&:focus-visible {
|
|
@@ -86,6 +121,11 @@
|
|
|
86
121
|
[tabindex]:not([tabindex="-1"]),
|
|
87
122
|
[contentEditable="true"] {
|
|
88
123
|
@include base.focusring;
|
|
124
|
+
|
|
125
|
+
&:focus-visible {
|
|
126
|
+
// Legacy ODS explicitly suppresses outlines with !important.
|
|
127
|
+
outline-width: base.$focusring-width !important;
|
|
128
|
+
}
|
|
89
129
|
}
|
|
90
130
|
|
|
91
131
|
hr {
|
|
@@ -141,13 +181,14 @@
|
|
|
141
181
|
}
|
|
142
182
|
|
|
143
183
|
@mixin base {
|
|
144
|
-
--megamenu-main-height: #{convert.to-rem(
|
|
184
|
+
--megamenu-main-height: #{convert.to-rem(headerHeights.$collapsed)};
|
|
145
185
|
|
|
146
186
|
top: 0;
|
|
147
187
|
left: 0;
|
|
148
188
|
width: 100%;
|
|
149
189
|
z-index: 11;
|
|
150
190
|
font-size: 1rem;
|
|
191
|
+
font-weight: 400;
|
|
151
192
|
color: var(--color-text-default);
|
|
152
193
|
background-color: var(--color-surface-primary);
|
|
153
194
|
box-sizing: border-box;
|
|
@@ -155,7 +196,7 @@
|
|
|
155
196
|
|
|
156
197
|
@include breakpoint.get("lg") {
|
|
157
198
|
&:has(.megamenu__nav-logo > [data-megamenu-brand-mark]):not(.top-hidden) {
|
|
158
|
-
--megamenu-main-height: #{convert.to-rem(
|
|
199
|
+
--megamenu-main-height: #{convert.to-rem(headerHeights.$brand-main)};
|
|
159
200
|
}
|
|
160
201
|
}
|
|
161
202
|
|
|
@@ -163,7 +204,7 @@
|
|
|
163
204
|
&:not(.non-sticky) {
|
|
164
205
|
position: fixed;
|
|
165
206
|
|
|
166
|
-
body.has-modal
|
|
207
|
+
@at-root body.has-modal .megamenu:not(.non-sticky) {
|
|
167
208
|
padding-right: var(--ods-modal-scrollbar-width, 0px);
|
|
168
209
|
}
|
|
169
210
|
|
|
@@ -177,9 +218,9 @@
|
|
|
177
218
|
@include breakpoint.get("lg") {
|
|
178
219
|
#{config.$class-root}__top[data-hide-when-sticky="true"] {
|
|
179
220
|
transition:
|
|
180
|
-
height
|
|
181
|
-
transform
|
|
182
|
-
opacity
|
|
221
|
+
height var(--header-transition, #{headerHeights.$transition}),
|
|
222
|
+
transform var(--header-transition, #{headerHeights.$transition}),
|
|
223
|
+
opacity var(--header-transition, #{headerHeights.$transition});
|
|
183
224
|
|
|
184
225
|
&.is-hidden {
|
|
185
226
|
height: 0;
|
|
@@ -191,7 +232,8 @@
|
|
|
191
232
|
}
|
|
192
233
|
|
|
193
234
|
#{config.$class-root}__main {
|
|
194
|
-
transition: min-height
|
|
235
|
+
transition: min-height
|
|
236
|
+
var(--header-transition, #{headerHeights.$transition});
|
|
195
237
|
}
|
|
196
238
|
}
|
|
197
239
|
}
|
|
@@ -333,34 +375,34 @@
|
|
|
333
375
|
}
|
|
334
376
|
|
|
335
377
|
@include breakpoint.get("lg") {
|
|
336
|
-
.megamenu:not(.top-hidden)
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
378
|
+
@at-root .megamenu:not(.top-hidden)
|
|
379
|
+
.megamenu__nav-logo
|
|
380
|
+
> [data-megamenu-brand-mark]
|
|
381
|
+
> [data-megamenu-logo-full] {
|
|
340
382
|
visibility: visible;
|
|
341
383
|
pointer-events: auto;
|
|
342
384
|
}
|
|
343
385
|
|
|
344
|
-
.megamenu:not(.top-hidden)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
386
|
+
@at-root .megamenu:not(.top-hidden)
|
|
387
|
+
.megamenu__nav-logo
|
|
388
|
+
> [data-megamenu-brand-mark]
|
|
389
|
+
> [data-megamenu-logo-compact] {
|
|
348
390
|
visibility: hidden;
|
|
349
391
|
pointer-events: none;
|
|
350
392
|
}
|
|
351
393
|
|
|
352
|
-
.megamenu.top-hidden
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
394
|
+
@at-root .megamenu.top-hidden
|
|
395
|
+
.megamenu__nav-logo
|
|
396
|
+
> [data-megamenu-brand-mark]
|
|
397
|
+
> [data-megamenu-logo-full] {
|
|
356
398
|
visibility: hidden;
|
|
357
399
|
pointer-events: none;
|
|
358
400
|
}
|
|
359
401
|
|
|
360
|
-
.megamenu.top-hidden
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
402
|
+
@at-root .megamenu.top-hidden
|
|
403
|
+
.megamenu__nav-logo
|
|
404
|
+
> [data-megamenu-brand-mark]
|
|
405
|
+
> [data-megamenu-logo-compact] {
|
|
364
406
|
visibility: visible;
|
|
365
407
|
pointer-events: auto;
|
|
366
408
|
}
|
|
@@ -409,6 +451,7 @@
|
|
|
409
451
|
&:active,
|
|
410
452
|
&.is-active {
|
|
411
453
|
color: var(--color-text-default) !important;
|
|
454
|
+
text-decoration: none;
|
|
412
455
|
box-shadow: config.$active-line;
|
|
413
456
|
}
|
|
414
457
|
}
|
|
@@ -456,6 +499,7 @@
|
|
|
456
499
|
&:active,
|
|
457
500
|
&.is-active {
|
|
458
501
|
color: var(--color-text-default) !important;
|
|
502
|
+
background-color: var(--color-surface-accent);
|
|
459
503
|
outline: solid 2px var(--color-border-accent);
|
|
460
504
|
outline-offset: -2px;
|
|
461
505
|
}
|
|
@@ -520,6 +564,7 @@
|
|
|
520
564
|
align-items: center;
|
|
521
565
|
color: var(--color-text-default);
|
|
522
566
|
background: none;
|
|
567
|
+
text-decoration: none;
|
|
523
568
|
border: none;
|
|
524
569
|
cursor: pointer;
|
|
525
570
|
padding: 0;
|
|
@@ -529,6 +574,7 @@
|
|
|
529
574
|
&:active,
|
|
530
575
|
&.is-active {
|
|
531
576
|
color: var(--color-text-default) !important;
|
|
577
|
+
text-decoration: none;
|
|
532
578
|
box-shadow: config.$active-line;
|
|
533
579
|
}
|
|
534
580
|
|
|
@@ -687,8 +733,10 @@
|
|
|
687
733
|
|
|
688
734
|
@mixin top {
|
|
689
735
|
display: flex;
|
|
690
|
-
border-
|
|
691
|
-
|
|
736
|
+
// Draw the divider without a border that keeps a zero-height top bar 1px tall.
|
|
737
|
+
border-bottom: 0;
|
|
738
|
+
box-shadow: inset 0 -1px 0 var(--color-border-subtle);
|
|
739
|
+
height: convert.to-rem(headerHeights.$top);
|
|
692
740
|
overflow: hidden;
|
|
693
741
|
}
|
|
694
742
|
|
|
@@ -698,7 +746,7 @@
|
|
|
698
746
|
min-height: var(--megamenu-main-height);
|
|
699
747
|
|
|
700
748
|
@include breakpoint.get("md", "down") {
|
|
701
|
-
min-height: convert.to-rem(
|
|
749
|
+
min-height: convert.to-rem(headerHeights.$mobile-spacer);
|
|
702
750
|
}
|
|
703
751
|
}
|
|
704
752
|
|
|
@@ -1085,8 +1133,11 @@
|
|
|
1085
1133
|
}
|
|
1086
1134
|
|
|
1087
1135
|
&:hover,
|
|
1088
|
-
&:focus
|
|
1136
|
+
&:focus,
|
|
1137
|
+
&:focus-visible,
|
|
1138
|
+
&:active {
|
|
1089
1139
|
text-decoration: none;
|
|
1140
|
+
background-color: var(--color-fill-primary);
|
|
1090
1141
|
}
|
|
1091
1142
|
}
|
|
1092
1143
|
|
|
@@ -1146,30 +1197,38 @@
|
|
|
1146
1197
|
|
|
1147
1198
|
@mixin spacer {
|
|
1148
1199
|
height: 0;
|
|
1149
|
-
transition: height
|
|
1200
|
+
transition: height var(--header-transition, #{headerHeights.$transition});
|
|
1150
1201
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
@include breakpoint.get("md", "down") {
|
|
1156
|
-
.megamenu:not(.non-sticky) + & {
|
|
1157
|
-
height: convert.to-rem(58px);
|
|
1202
|
+
// A spacer belongs to its adjacent menu, including inside a shadow root.
|
|
1203
|
+
@at-root :is(:root, :host) .megamenu:not(.non-sticky) {
|
|
1204
|
+
+ :is(.megamenu-spacer, .megamenu-spacer--small) {
|
|
1205
|
+
height: convert.to-rem(headerHeights.$mobile-spacer);
|
|
1158
1206
|
}
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
1207
|
|
|
1162
|
-
@
|
|
1163
|
-
|
|
1164
|
-
|
|
1208
|
+
@include breakpoint.get("lg") {
|
|
1209
|
+
+ :is(.megamenu-spacer, .megamenu-spacer--small) {
|
|
1210
|
+
height: headerHeights.$desktop;
|
|
1211
|
+
}
|
|
1165
1212
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1213
|
+
&:not(.top-hidden):has(.megamenu__nav-logo > [data-megamenu-brand-mark])
|
|
1214
|
+
+ :is(.megamenu-spacer, .megamenu-spacer--small) {
|
|
1215
|
+
height: headerHeights.$brand;
|
|
1216
|
+
}
|
|
1169
1217
|
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1218
|
+
&:not(.top-hidden):has(
|
|
1219
|
+
.megamenu__nav-logo > [data-megamenu-brand-mark] + span
|
|
1220
|
+
)
|
|
1221
|
+
+ :is(.megamenu-spacer, .megamenu-spacer--small) {
|
|
1222
|
+
height: headerHeights.$brand-with-title;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
&.top-hidden + :is(.megamenu-spacer, .megamenu-spacer--small) {
|
|
1226
|
+
height: headerHeights.$collapsed;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
+ .megamenu-spacer--small {
|
|
1230
|
+
height: headerHeights.$collapsed;
|
|
1231
|
+
}
|
|
1173
1232
|
}
|
|
1174
1233
|
}
|
|
1175
1234
|
}
|
|
@@ -1216,8 +1275,11 @@
|
|
|
1216
1275
|
@include accordion.icon;
|
|
1217
1276
|
transform: rotate(90deg);
|
|
1218
1277
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1278
|
+
@at-root :is(:root, :host)
|
|
1279
|
+
.megamenu
|
|
1280
|
+
[aria-expanded="true"]
|
|
1281
|
+
> .megamenu__accordion-icon,
|
|
1282
|
+
#{&}.is-active {
|
|
1221
1283
|
@include accordion.icon-active;
|
|
1222
1284
|
color: var(--color-text-default) !important;
|
|
1223
1285
|
transform: rotate(270deg);
|
|
@@ -1337,17 +1399,17 @@
|
|
|
1337
1399
|
}
|
|
1338
1400
|
|
|
1339
1401
|
// Interactive styles for button and anchor tags
|
|
1340
|
-
&:not(
|
|
1402
|
+
&:not(#{config.$class-root}__tag--transparent):where(button) {
|
|
1341
1403
|
@include tag.button();
|
|
1342
1404
|
z-index: 2;
|
|
1343
1405
|
}
|
|
1344
1406
|
|
|
1345
|
-
&:not(
|
|
1407
|
+
&:not(#{config.$class-root}__tag--transparent):where(a) {
|
|
1346
1408
|
@include tag.button();
|
|
1347
1409
|
z-index: 2;
|
|
1348
1410
|
}
|
|
1349
1411
|
|
|
1350
|
-
&:not(
|
|
1412
|
+
&:not(#{config.$class-root}__tag--transparent) {
|
|
1351
1413
|
@include tag.buttton-disabled();
|
|
1352
1414
|
}
|
|
1353
1415
|
}
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
@use "../../Tile/styles/mixins" as tile;
|
|
4
4
|
@use "../../../styles/typography/mixins" as typography;
|
|
5
5
|
@use "../../../styles/tools/convert";
|
|
6
|
+
@use "../../../styles/base/header-height";
|
|
6
7
|
|
|
7
8
|
@use "./mixins";
|
|
8
9
|
@use "./config";
|
|
9
10
|
|
|
10
11
|
// Modifiers global states and resets
|
|
11
|
-
|
|
12
|
+
// Keep specificity equal in document and shadow trees, above legacy link rules.
|
|
13
|
+
:is(:root, :host) #{config.$class-root} {
|
|
12
14
|
@include mixins.globals;
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -24,7 +26,7 @@
|
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// Component
|
|
27
|
-
#{config.$class-root} {
|
|
29
|
+
:is(:root, :host) #{config.$class-root} {
|
|
28
30
|
@include mixins.base;
|
|
29
31
|
|
|
30
32
|
&__nav {
|
|
@@ -292,12 +294,9 @@
|
|
|
292
294
|
@include mixins.overlay;
|
|
293
295
|
}
|
|
294
296
|
|
|
295
|
-
&-spacer
|
|
297
|
+
&-spacer,
|
|
298
|
+
&-spacer--small {
|
|
296
299
|
@include mixins.spacer;
|
|
297
|
-
|
|
298
|
-
&--small {
|
|
299
|
-
@include mixins.spacer-small;
|
|
300
|
-
}
|
|
301
300
|
}
|
|
302
301
|
|
|
303
302
|
&__accordion {
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import Megamenu from "../Megamenu.static";
|
|
4
|
+
|
|
5
|
+
describe("Megamenu scroll lifecycle", () => {
|
|
6
|
+
let frames: Map<number, FrameRequestCallback>;
|
|
7
|
+
let instances: Megamenu[];
|
|
8
|
+
|
|
9
|
+
const flushFrame = () => {
|
|
10
|
+
const callbacks = [...frames.values()];
|
|
11
|
+
frames.clear();
|
|
12
|
+
callbacks.forEach((callback) => callback(0));
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const createMenu = (root: HTMLElement | ShadowRoot = document.body) => {
|
|
16
|
+
const element = document.createElement("nav");
|
|
17
|
+
element.setAttribute("data-megamenu", "");
|
|
18
|
+
element.innerHTML = '<div data-hide-when-sticky="true"></div>';
|
|
19
|
+
root.append(element);
|
|
20
|
+
const top = element.firstElementChild as HTMLElement;
|
|
21
|
+
Object.defineProperty(top, "offsetHeight", {
|
|
22
|
+
configurable: true,
|
|
23
|
+
value: 40,
|
|
24
|
+
});
|
|
25
|
+
const instance = new Megamenu(element);
|
|
26
|
+
instances.push(instance);
|
|
27
|
+
return { element, top, instance };
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
frames = new Map();
|
|
32
|
+
instances = [];
|
|
33
|
+
let nextId = 0;
|
|
34
|
+
vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => {
|
|
35
|
+
const id = nextId++;
|
|
36
|
+
frames.set(id, callback);
|
|
37
|
+
return id;
|
|
38
|
+
});
|
|
39
|
+
vi.stubGlobal("cancelAnimationFrame", (id: number) => frames.delete(id));
|
|
40
|
+
vi.stubGlobal("scrollY", 0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
instances.forEach((instance) => instance.destroy());
|
|
45
|
+
document.body.replaceChildren();
|
|
46
|
+
document.body.classList.remove("has-modal");
|
|
47
|
+
document.body.removeAttribute("data-lock-scrolltop");
|
|
48
|
+
vi.restoreAllMocks();
|
|
49
|
+
vi.unstubAllGlobals();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("applies the initial scroll state synchronously", () => {
|
|
53
|
+
vi.stubGlobal("scrollY", 100);
|
|
54
|
+
const { element, top } = createMenu();
|
|
55
|
+
|
|
56
|
+
expect(element.classList.contains("top-hidden")).toBe(true);
|
|
57
|
+
expect(top.classList.contains("is-hidden")).toBe(true);
|
|
58
|
+
expect(frames.size).toBe(0);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("batches a burst of scroll events into one DOM update using the latest position", () => {
|
|
62
|
+
const { element, top } = createMenu();
|
|
63
|
+
const query = vi.spyOn(element, "querySelector");
|
|
64
|
+
|
|
65
|
+
vi.stubGlobal("scrollY", 10);
|
|
66
|
+
window.dispatchEvent(new Event("scroll"));
|
|
67
|
+
vi.stubGlobal("scrollY", 100);
|
|
68
|
+
window.dispatchEvent(new Event("scroll"));
|
|
69
|
+
window.dispatchEvent(new Event("scroll"));
|
|
70
|
+
|
|
71
|
+
expect(query).not.toHaveBeenCalled();
|
|
72
|
+
expect(frames.size).toBe(1);
|
|
73
|
+
flushFrame();
|
|
74
|
+
expect(element.classList.contains("top-hidden")).toBe(true);
|
|
75
|
+
expect(top.classList.contains("is-hidden")).toBe(true);
|
|
76
|
+
expect(query).toHaveBeenCalledTimes(1);
|
|
77
|
+
|
|
78
|
+
vi.stubGlobal("scrollY", 0);
|
|
79
|
+
window.dispatchEvent(new Event("scroll"));
|
|
80
|
+
flushFrame();
|
|
81
|
+
expect(element.classList.contains("top-hidden")).toBe(false);
|
|
82
|
+
expect(top.classList.contains("is-hidden")).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("cancels pending work and removes the scroll listener on destroy", () => {
|
|
86
|
+
const { element, instance } = createMenu();
|
|
87
|
+
vi.stubGlobal("scrollY", 100);
|
|
88
|
+
window.dispatchEvent(new Event("scroll"));
|
|
89
|
+
expect(frames.size).toBe(1);
|
|
90
|
+
|
|
91
|
+
instance.destroy();
|
|
92
|
+
|
|
93
|
+
expect(frames.size).toBe(0);
|
|
94
|
+
flushFrame();
|
|
95
|
+
window.dispatchEvent(new Event("scroll"));
|
|
96
|
+
expect(frames.size).toBe(0);
|
|
97
|
+
expect(element.classList.contains("top-hidden")).toBe(false);
|
|
98
|
+
|
|
99
|
+
instance.init();
|
|
100
|
+
expect(element.classList.contains("top-hidden")).toBe(true);
|
|
101
|
+
vi.stubGlobal("scrollY", 0);
|
|
102
|
+
window.dispatchEvent(new Event("scroll"));
|
|
103
|
+
flushFrame();
|
|
104
|
+
expect(element.classList.contains("top-hidden")).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("uses replacement markup and its current height without reinitialization", () => {
|
|
108
|
+
const { element, top } = createMenu();
|
|
109
|
+
vi.stubGlobal("scrollY", 100);
|
|
110
|
+
window.dispatchEvent(new Event("scroll"));
|
|
111
|
+
const replacement = top.cloneNode() as HTMLElement;
|
|
112
|
+
Object.defineProperty(replacement, "offsetHeight", {
|
|
113
|
+
configurable: true,
|
|
114
|
+
value: 120,
|
|
115
|
+
});
|
|
116
|
+
top.replaceWith(replacement);
|
|
117
|
+
flushFrame();
|
|
118
|
+
expect(element.classList.contains("top-hidden")).toBe(false);
|
|
119
|
+
|
|
120
|
+
Object.defineProperty(replacement, "offsetHeight", {
|
|
121
|
+
configurable: true,
|
|
122
|
+
value: 60,
|
|
123
|
+
});
|
|
124
|
+
window.dispatchEvent(new Event("scroll"));
|
|
125
|
+
flushFrame();
|
|
126
|
+
expect(replacement.classList.contains("is-hidden")).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("uses the locked scroll position while a modal is open", () => {
|
|
130
|
+
const { element } = createMenu();
|
|
131
|
+
document.body.classList.add("has-modal");
|
|
132
|
+
document.body.setAttribute("data-lock-scrolltop", "100");
|
|
133
|
+
vi.stubGlobal("scrollY", 0);
|
|
134
|
+
window.dispatchEvent(new Event("scroll"));
|
|
135
|
+
flushFrame();
|
|
136
|
+
expect(element.classList.contains("top-hidden")).toBe(true);
|
|
137
|
+
|
|
138
|
+
document.body.removeAttribute("data-lock-scrolltop");
|
|
139
|
+
window.dispatchEvent(new Event("scroll"));
|
|
140
|
+
flushFrame();
|
|
141
|
+
expect(element.classList.contains("top-hidden")).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("preserves the brand-mark variant and non-sticky behavior with dynamic markup", () => {
|
|
145
|
+
const { element, top } = createMenu();
|
|
146
|
+
top.remove();
|
|
147
|
+
vi.stubGlobal("scrollY", 100);
|
|
148
|
+
window.dispatchEvent(new Event("scroll"));
|
|
149
|
+
flushFrame();
|
|
150
|
+
expect(element.classList.contains("top-hidden")).toBe(false);
|
|
151
|
+
|
|
152
|
+
element.innerHTML = "<span data-megamenu-brand-mark></span>";
|
|
153
|
+
window.dispatchEvent(new Event("scroll"));
|
|
154
|
+
flushFrame();
|
|
155
|
+
expect(element.classList.contains("top-hidden")).toBe(true);
|
|
156
|
+
|
|
157
|
+
vi.stubGlobal("scrollY", 0);
|
|
158
|
+
window.dispatchEvent(new Event("scroll"));
|
|
159
|
+
flushFrame();
|
|
160
|
+
element.classList.add("non-sticky");
|
|
161
|
+
vi.stubGlobal("scrollY", 100);
|
|
162
|
+
window.dispatchEvent(new Event("scroll"));
|
|
163
|
+
flushFrame();
|
|
164
|
+
expect(element.classList.contains("top-hidden")).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it.each(["open", "closed"] as const)(
|
|
168
|
+
"supports explicit initialization in a %s shadow root",
|
|
169
|
+
(mode) => {
|
|
170
|
+
const host = document.createElement("vendor-menu");
|
|
171
|
+
document.body.append(host);
|
|
172
|
+
const { element, top, instance } = createMenu(
|
|
173
|
+
host.attachShadow({ mode }),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
expect(Megamenu.getInstance(element)).toBe(instance);
|
|
177
|
+
expect(new Megamenu(element)).toBe(instance);
|
|
178
|
+
vi.stubGlobal("scrollY", 100);
|
|
179
|
+
window.dispatchEvent(new Event("scroll"));
|
|
180
|
+
flushFrame();
|
|
181
|
+
expect(element.classList.contains("top-hidden")).toBe(true);
|
|
182
|
+
expect(top.classList.contains("is-hidden")).toBe(true);
|
|
183
|
+
},
|
|
184
|
+
);
|
|
185
|
+
});
|