@orangesk/orange-design-system 3.13.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.
Files changed (111) hide show
  1. package/build/components/Breadcrumbs/style.css +1 -1
  2. package/build/components/Breadcrumbs/style.css.map +1 -1
  3. package/build/components/Carousel/style.css +1 -1
  4. package/build/components/Carousel/style.css.map +1 -1
  5. package/build/components/Footer/style.css +1 -1
  6. package/build/components/Footer/style.css.map +1 -1
  7. package/build/components/Megamenu/style.css +1 -1
  8. package/build/components/Megamenu/style.css.map +1 -1
  9. package/build/components/index.js +1 -1
  10. package/build/components/index.js.map +1 -1
  11. package/build/lib/base.css +1 -1
  12. package/build/lib/base.css.map +1 -1
  13. package/build/lib/breadcrumbs.css +2 -2
  14. package/build/lib/breadcrumbs.css.map +1 -1
  15. package/build/lib/components.css +1 -1
  16. package/build/lib/components.css.map +1 -1
  17. package/build/lib/fonts.css +1 -1
  18. package/build/lib/fonts.css.map +1 -1
  19. package/build/lib/footer.css +1 -1
  20. package/build/lib/footer.css.map +1 -1
  21. package/build/lib/footer.iife.js +2 -0
  22. package/build/lib/footer.iife.js.map +1 -0
  23. package/build/lib/megamenu.css +1 -1
  24. package/build/lib/megamenu.css.map +1 -1
  25. package/build/lib/megamenu.iife.js +2 -0
  26. package/build/lib/megamenu.iife.js.map +1 -0
  27. package/build/lib/megamenu.js +1 -1
  28. package/build/lib/megamenu.js.map +1 -1
  29. package/build/lib/scripts.js +1 -1
  30. package/build/lib/scripts.js.map +1 -1
  31. package/build/lib/style.css +1 -1
  32. package/build/lib/style.css.map +1 -1
  33. package/package.json +10 -12
  34. package/src/components/AnchorNavigation/AnchorNavigation.static.ts +3 -4
  35. package/src/components/AnchorNavigation/tests/AnchorNavigation.megamenu.unit.test.ts +74 -0
  36. package/src/components/AnchorNavigation/tests/AnchorNavigation.unit.test.jsx +6 -0
  37. package/src/components/Breadcrumbs/styles/mixins.scss +6 -0
  38. package/src/components/Breadcrumbs/styles/rules.scss +30 -0
  39. package/src/components/Breadcrumbs/styles/style.scss +2 -26
  40. package/src/components/Carousel/styles/mixins.scss +0 -4
  41. package/src/components/Footer/classic.ts +3 -0
  42. package/src/components/Footer/styles/mixins.scss +32 -1
  43. package/src/components/Footer/styles/style.scss +8 -5
  44. package/src/components/Footer/tests/Footer.styles.unit.test.jsx +7 -2
  45. package/src/components/Megamenu/Megamenu.static.ts +25 -6
  46. package/src/components/Megamenu/classic.ts +3 -0
  47. package/src/components/Megamenu/styles/mixins.scss +114 -52
  48. package/src/components/Megamenu/styles/style.scss +6 -7
  49. package/src/components/Megamenu/tests/Megamenu.scroll.unit.test.ts +185 -0
  50. package/src/styles/base/globals.scss +1 -37
  51. package/src/styles/base/header-height-values.scss +9 -0
  52. package/src/styles/base/header-height.scss +50 -0
  53. package/src/styles/base/styleguide.scss +0 -1
  54. package/build/files/tone-of-voice.pdf +0 -26572
  55. package/build/images/card-device-example.png +0 -0
  56. package/build/images/cta-device.png +0 -0
  57. package/build/images/cta-plans.png +0 -0
  58. package/build/images/cta-renewal.png +0 -0
  59. package/build/images/device-mobile.png +0 -0
  60. package/build/images/form-validation-format.png +0 -0
  61. package/build/images/form-validation-info-structure.png +0 -0
  62. package/build/images/form-validation-semantics.png +0 -0
  63. package/build/images/forms-labels-grouping.png +0 -0
  64. package/build/images/forms-labels-instruction.png +0 -0
  65. package/build/images/forms-labels-structure.png +0 -0
  66. package/build/images/hero/example-lg.jpg +0 -0
  67. package/build/images/hero/example-lg@2x.jpg +0 -0
  68. package/build/images/hero/example-xl.jpg +0 -0
  69. package/build/images/hero/example-xl@2x.jpg +0 -0
  70. package/build/images/hero/example-xxl.jpg +0 -0
  71. package/build/images/hero/example-xxl@2x.jpg +0 -0
  72. package/build/images/hero/example.jpg +0 -0
  73. package/build/images/hero/example@2x.jpg +0 -0
  74. package/build/images/hero/old-vendor-2x.jpg +0 -0
  75. package/build/images/hero/old-vendor-lg-2x.jpg +0 -0
  76. package/build/images/hero/old-vendor-lg.jpg +0 -0
  77. package/build/images/hero/old-vendor-xl-2x.jpg +0 -0
  78. package/build/images/hero/old-vendor-xl.jpg +0 -0
  79. package/build/images/hero/old-vendor-xxl-2x.jpg +0 -0
  80. package/build/images/hero/old-vendor-xxl.jpg +0 -0
  81. package/build/images/hero/old-vendor.jpg +0 -0
  82. package/build/images/hero/photo-2x.jpg +0 -0
  83. package/build/images/hero/photo-lg-2x.jpg +0 -0
  84. package/build/images/hero/photo-lg.jpg +0 -0
  85. package/build/images/hero/photo-xl-2x.jpg +0 -0
  86. package/build/images/hero/photo-xl.jpg +0 -0
  87. package/build/images/hero/photo-xxl-2x.jpg +0 -0
  88. package/build/images/hero/photo-xxl.jpg +0 -0
  89. package/build/images/hero/photo.jpg +0 -0
  90. package/build/images/hero/single.png +0 -0
  91. package/build/images/hero/single@2x.png +0 -0
  92. package/build/images/hero/vendor-lg.png +0 -0
  93. package/build/images/hero/vendor-lg@2x.png +0 -0
  94. package/build/images/hero/vendor-xl.png +0 -0
  95. package/build/images/hero/vendor-xl@2x.png +0 -0
  96. package/build/images/hero/vendor-xxl.png +0 -0
  97. package/build/images/hero/vendor-xxl@2x.png +0 -0
  98. package/build/images/hero/vendor.png +0 -0
  99. package/build/images/hero/vendor@2x.png +0 -0
  100. package/build/images/phones.png +0 -0
  101. package/build/images/product-1.svg +0 -10
  102. package/build/images/product-2.svg +0 -14
  103. package/build/images/product-3.svg +0 -9
  104. package/build/images/tone-of-voice.jpg +0 -0
  105. package/build/lib/base.js +0 -3
  106. package/build/lib/breadcrumbs.js +0 -3
  107. package/build/lib/components.js +0 -3
  108. package/build/lib/fonts.js +0 -3
  109. package/build/lib/reset.js +0 -3
  110. package/build/lib/style.js +0 -3
  111. package/build/lib/utilities.js +0 -3
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@orangesk/orange-design-system",
3
- "version": "3.13.0",
3
+ "version": "4.0.0",
4
4
  "private": false,
5
5
  "engines": {
6
6
  "node": ">=20.x"
7
7
  },
8
8
  "files": [
9
9
  "build/",
10
+ "!build/images/**",
11
+ "!build/files/**",
10
12
  "public/fonts/",
11
13
  "public/footer-sprite-*.svg",
12
14
  "src/components/",
@@ -35,7 +37,6 @@
35
37
  "a11y-dialog": "^8.1.5",
36
38
  "classnames": "^2.5.1",
37
39
  "daypickr": "^0.3.4",
38
- "diff2html": "^3.4.56",
39
40
  "dompurify": "^3.4.17",
40
41
  "html-react-parser": "6.1.9",
41
42
  "lorem-ipsum": "3.0.0",
@@ -58,7 +59,7 @@
58
59
  "wnumb": "^1.2.0"
59
60
  },
60
61
  "devDependencies": {
61
- "@babel/core": "8.0.9",
62
+ "@babel/core": "8.0.10",
62
63
  "@babel/preset-env": "8.0.7",
63
64
  "@babel/preset-react": "8.0.1",
64
65
  "@babel/preset-typescript": "8.0.7",
@@ -71,32 +72,28 @@
71
72
  "@rollup/plugin-terser": "1.0.2",
72
73
  "@rollup/plugin-typescript": "^12.3.1",
73
74
  "@rollup/plugin-url": "^8.0.2",
74
- "@rollup/rollup-darwin-arm64": "^4.65.0",
75
75
  "@testing-library/dom": "^10.4.2",
76
76
  "@testing-library/jest-dom": "7.0.1",
77
77
  "@testing-library/react": "^16.3.3",
78
78
  "@testing-library/user-event": "^14.6.8",
79
- "@types/node": "26.6.5",
79
+ "@types/node": "26.6.6",
80
80
  "@types/react": "19.3.0",
81
81
  "@types/react-dom": "19.3.0",
82
82
  "@types/wnumb": "^1.2.3",
83
- "@vitejs/plugin-react": "6.1.2",
84
83
  "@vitest/coverage-v8": "5.0.5",
85
84
  "@vitest/ui": "5.0.5",
86
85
  "canvas": "^3.2.3",
87
- "fs-extra": "^11.4.1",
88
- "glob": "13.0.6",
86
+ "glob": "14.0.3",
89
87
  "html-validate": "11.17.0",
90
88
  "husky": "^9.1.7",
91
- "identity-obj-proxy": "^3.0.0",
92
89
  "jsdom": "30.1.3",
93
90
  "lint-staged": "17.6.0",
94
91
  "prettier": "^3.9.10",
95
- "rollup": "^4.65.0",
92
+ "rollup": "^4.66.0",
96
93
  "rollup-plugin-copy": "^3.5.0",
97
94
  "rollup-plugin-dts": "^6.5.1",
98
95
  "rollup-plugin-postcss": "^4.0.2",
99
- "sass": "^1.106.1",
96
+ "sass": "^1.107.0",
100
97
  "svg-sprite": "^2.0.4",
101
98
  "typescript": "6.0.3",
102
99
  "vitest": "5.0.5",
@@ -109,11 +106,12 @@
109
106
  "scripts": {
110
107
  "dev": "npm run sprite && npm run build:search-index && npm run build:preview-iframe && next dev",
111
108
  "build": "npm run sprite && npm run build:search-index && npm run build:preview-iframe && next build",
112
- "build:bundle": "pnpm run sprite && pnpm exec rollup -c && pnpm exec rollup -c rollup.megamenu.config.mjs && pnpm exec rollup -c rollup.footer.config.mjs && pnpm run verify:production-bundle",
109
+ "build:bundle": "pnpm run sprite && pnpm exec rollup -c && pnpm exec rollup -c rollup.megamenu.config.mjs && pnpm exec rollup -c rollup.footer.config.mjs && pnpm run verify:production-bundle && pnpm run verify:standalone-bundles",
113
110
  "build:preview-iframe": "node scripts/build-preview-iframe-bundle.mjs",
114
111
  "build:megamenu": "pnpm run sprite && pnpm exec rollup -c rollup.megamenu.config.mjs",
115
112
  "build:footer": "pnpm run build:scss-exports && node scripts/build-footer-sprites.mjs && pnpm exec rollup -c rollup.footer.config.mjs",
116
113
  "verify:production-bundle": "node scripts/verify-production-bundle.mjs",
114
+ "verify:standalone-bundles": "node scripts/verify-standalone-bundles.mjs",
117
115
  "dev:footer": "npm run build:scss-exports && node scripts/build-footer-sprites.mjs && pnpm exec rollup -c rollup.footer.config.mjs --watch",
118
116
  "build:scss-exports": "node scripts/build-scss-exports.js",
119
117
  "build:search-index": "node scripts/mdx-search-index.js",
@@ -99,7 +99,8 @@ export default class AnchorNavigation {
99
99
  }
100
100
 
101
101
  private getMegamenuOffsetHeight(): number {
102
- return this.megamenuElement?.offsetHeight || 0;
102
+ // offsetHeight rounds fractional CSS pixels and can leave a visible seam.
103
+ return this.megamenuElement?.getBoundingClientRect().height || 0;
103
104
  }
104
105
 
105
106
  private updateStickyPosition(): void {
@@ -304,9 +305,7 @@ export default class AnchorNavigation {
304
305
  }
305
306
 
306
307
  private getTotalStickyOffset(): number {
307
- const scrollOffset = this.megamenuElement
308
- ? this.megamenuElement.offsetHeight
309
- : 0;
308
+ const scrollOffset = this.getMegamenuOffsetHeight();
310
309
  const anchorNavOffset = this.element.offsetHeight;
311
310
  return scrollOffset + anchorNavOffset;
312
311
  }
@@ -0,0 +1,74 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ import AnchorNavigation from "../AnchorNavigation.static";
4
+
5
+ describe("AnchorNavigation fractional Megamenu height", () => {
6
+ let instance: AnchorNavigation;
7
+ let menu: HTMLElement;
8
+ let anchor: HTMLElement;
9
+ let height: number;
10
+
11
+ beforeEach(() => {
12
+ height = 80.5;
13
+ menu = document.createElement("nav");
14
+ menu.setAttribute("data-megamenu", "");
15
+ // jsdom has no layout; represent the browser's fractional and rounded APIs.
16
+ vi.spyOn(menu, "getBoundingClientRect").mockImplementation(
17
+ () => ({ height }) as DOMRect,
18
+ );
19
+ Object.defineProperty(menu, "offsetHeight", {
20
+ get: () => Math.round(height),
21
+ });
22
+ anchor = document.createElement("nav");
23
+ anchor.innerHTML = '<ul class="anchor-navigation__content-left"></ul>';
24
+ document.body.append(menu, anchor);
25
+ instance = new AnchorNavigation(anchor);
26
+ });
27
+
28
+ afterEach(() => {
29
+ instance.destroy();
30
+ menu.remove();
31
+ anchor.remove();
32
+ vi.restoreAllMocks();
33
+ });
34
+
35
+ it("places the navigation at the fractional menu edge on initialization", () => {
36
+ expect(anchor.style.top).toBe("80.5px");
37
+ });
38
+
39
+ it("follows fractional animation heights through its ResizeObserver", () => {
40
+ height = 113.625;
41
+ const observer = (instance as any).resizeObserver;
42
+ observer.callback([], observer);
43
+ expect(anchor.style.top).toBe("113.625px");
44
+ });
45
+
46
+ it("keeps the fractional menu offset when scrolling to a section", () => {
47
+ const section = document.createElement("section");
48
+ section.id = "fractional-section";
49
+ vi.spyOn(section, "getBoundingClientRect").mockReturnValue({
50
+ top: 600,
51
+ } as DOMRect);
52
+ document.body.append(section);
53
+ Object.defineProperty(anchor, "offsetHeight", { value: 50 });
54
+ const previousScrollY = window.scrollY;
55
+ Object.defineProperty(window, "scrollY", { configurable: true, value: 0 });
56
+ const scroll = vi.spyOn(window, "scrollTo").mockImplementation(() => {});
57
+ const link = document.createElement("a");
58
+ link.className = "anchor-navigation__item";
59
+ link.href = "#fractional-section";
60
+ anchor.append(link);
61
+
62
+ try {
63
+ link.click();
64
+ expect(scroll).toHaveBeenCalledWith({ top: 469.5, behavior: "smooth" });
65
+ } finally {
66
+ Object.defineProperty(window, "scrollY", {
67
+ configurable: true,
68
+ value: previousScrollY,
69
+ writable: true,
70
+ });
71
+ section.remove();
72
+ }
73
+ });
74
+ });
@@ -628,6 +628,9 @@ describe("rendering AnchorNavigation", () => {
628
628
  configurable: true,
629
629
  value: 120,
630
630
  });
631
+ vi.spyOn(megamenu, "getBoundingClientRect").mockReturnValue({
632
+ height: 120,
633
+ });
631
634
  document.body.appendChild(megamenu);
632
635
 
633
636
  Object.defineProperty(window, "scrollY", {
@@ -703,6 +706,9 @@ describe("rendering AnchorNavigation", () => {
703
706
  configurable: true,
704
707
  get: () => megamenuHeight,
705
708
  });
709
+ vi.spyOn(megamenu, "getBoundingClientRect").mockImplementation(() => ({
710
+ height: megamenuHeight,
711
+ }));
706
712
  document.body.appendChild(megamenu);
707
713
 
708
714
  Object.defineProperty(window, "scrollY", {
@@ -24,6 +24,7 @@
24
24
  }
25
25
 
26
26
  @mixin container {
27
+ @include typoMixins.list-unstyled;
27
28
  @include container.base;
28
29
 
29
30
  font-size: 0;
@@ -111,6 +112,11 @@
111
112
  text-decoration: none;
112
113
  display: inline;
113
114
 
115
+ &:focus-visible {
116
+ outline: 2px solid currentColor;
117
+ outline-offset: 2px;
118
+ }
119
+
114
120
  &:hover {
115
121
  color: var(--color-text-default);
116
122
  text-decoration: underline;
@@ -0,0 +1,30 @@
1
+ @use "./mixins";
2
+
3
+ // Shared selectors for the layered ODS and unlayered standalone outputs.
4
+ @mixin styles {
5
+ .osk-breadcrumbs {
6
+ @include mixins.base;
7
+
8
+ &__container {
9
+ @include mixins.container;
10
+ }
11
+ }
12
+
13
+ .osk-breadcrumbs__item {
14
+ @include mixins.item;
15
+ }
16
+
17
+ .background-contrast .osk-breadcrumbs__item,
18
+ .surface-contrast .osk-breadcrumbs__item,
19
+ .is-dark .osk-breadcrumbs__item {
20
+ @include mixins.item-dark;
21
+ }
22
+
23
+ .osk-breadcrumbs__link {
24
+ @include mixins.link;
25
+
26
+ &[aria-current="page"] {
27
+ @include mixins.link-current;
28
+ }
29
+ }
30
+ }
@@ -1,29 +1,5 @@
1
- @use "./mixins";
1
+ @use "./rules";
2
2
 
3
3
  @layer components {
4
- .osk-breadcrumbs {
5
- @include mixins.base;
6
-
7
- &__container {
8
- @include mixins.container;
9
- }
10
- }
11
-
12
- .osk-breadcrumbs__item {
13
- @include mixins.item;
14
-
15
- .background-contrast &,
16
- .surface-contrast &,
17
- .is-dark & {
18
- @include mixins.item-dark;
19
- }
20
- }
21
-
22
- .osk-breadcrumbs__link {
23
- @include mixins.link;
24
-
25
- &[aria-current="page"] {
26
- @include mixins.link-current;
27
- }
28
- }
4
+ @include rules.styles;
29
5
  }
@@ -106,10 +106,6 @@
106
106
  top: 50%;
107
107
  transform: translate(50%, -50%);
108
108
  }
109
-
110
- @include breakpoint.get("md", "down") {
111
- width: var(--carousel-side-arrows-width, 100%);
112
- }
113
109
  }
114
110
 
115
111
  @mixin centered-mobile-variant {
@@ -0,0 +1,3 @@
1
+ // The shared entry owns window.ODSFooter and auto-initialization.
2
+ // No exports: Rollup emits a classic script without creating another global.
3
+ import "./static";
@@ -16,10 +16,33 @@
16
16
  @include visibility.visually-hidden;
17
17
  }
18
18
 
19
- a {
19
+ :where(button, input, select, textarea) {
20
+ font: inherit;
21
+ color: inherit;
22
+ }
23
+
24
+ :where(h3, strong, b) {
25
+ font-family: inherit;
26
+ }
27
+
28
+ :where(strong, b) {
29
+ font-weight: 700;
30
+ }
31
+
32
+ li {
33
+ font-size: inherit;
34
+ line-height: inherit;
35
+ font-weight: inherit;
36
+ }
37
+
38
+ :where(a) {
20
39
  color: var(--color-text-default);
21
40
  text-decoration: none;
41
+ font-family: inherit;
42
+ font-weight: inherit;
22
43
  font-size: inherit;
44
+ background-color: transparent;
45
+ box-shadow: none;
23
46
 
24
47
  &:hover,
25
48
  &:focus,
@@ -27,6 +50,8 @@
27
50
  text-decoration: underline;
28
51
  text-underline-offset: convert.to-rem(2px);
29
52
  text-decoration-thickness: auto;
53
+ background-color: transparent;
54
+ box-shadow: none;
30
55
  }
31
56
 
32
57
  &:focus-visible {
@@ -46,6 +71,11 @@
46
71
  [tabindex]:not([tabindex="-1"]),
47
72
  [contentEditable="true"] {
48
73
  @include base.focusring;
74
+
75
+ &:focus-visible {
76
+ // Legacy ODS explicitly suppresses outlines with !important.
77
+ outline-width: base.$focusring-width !important;
78
+ }
49
79
  }
50
80
 
51
81
  *,
@@ -74,6 +104,7 @@
74
104
 
75
105
  @mixin base {
76
106
  font-size: 1rem;
107
+ font-weight: 400;
77
108
  letter-spacing: convert.to-rem(-0.1px);
78
109
  line-height: convert.to-rem(18px);
79
110
  color: var(--color-text-default);
@@ -7,7 +7,8 @@
7
7
  @use "../../SocialButton/styles/mixins" as socialButtonMixins;
8
8
  @use "../../Icon/styles/mixins" as iconMixins;
9
9
 
10
- #{config.$class-root} {
10
+ // Keep specificity equal in document and shadow trees, above legacy link rules.
11
+ :is(:root, :host) #{config.$class-root} {
11
12
  @include mixins.globals;
12
13
  }
13
14
 
@@ -22,7 +23,7 @@
22
23
  @include megamenuMixins.dark-vars;
23
24
  }
24
25
 
25
- #{config.$class-root} {
26
+ :is(:root, :host) #{config.$class-root} {
26
27
  @include mixins.base;
27
28
 
28
29
  &__container {
@@ -62,11 +63,13 @@
62
63
  }
63
64
  }
64
65
 
65
- [data-footer-toggle][aria-expanded="false"] > &__accordion-icon {
66
+ [data-footer-toggle][aria-expanded="false"]
67
+ > #{config.$class-root}__accordion-icon {
66
68
  transform: rotate(0deg);
67
69
  }
68
70
 
69
- [data-footer-toggle][aria-expanded="true"] > &__accordion-icon {
71
+ [data-footer-toggle][aria-expanded="true"]
72
+ > #{config.$class-root}__accordion-icon {
70
73
  transform: rotate(180deg);
71
74
  }
72
75
 
@@ -91,7 +94,7 @@
91
94
  }
92
95
  }
93
96
 
94
- &__menu-section > &__list {
97
+ &__menu-section > #{config.$class-root}__list {
95
98
  @include mixins.accordion-list;
96
99
  }
97
100
 
@@ -21,7 +21,10 @@ describe("Footer standalone styles", () => {
21
21
  afterAll(() => stylesheet.remove());
22
22
 
23
23
  const styleFor = (selector) =>
24
- rules.find((rule) => rule.selectorText === selector)?.style;
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.split(",")) {
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.handleScroll);
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 (!this.element.classList.contains("non-sticky") && hasBrandMark) {
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.handleScroll);
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);
@@ -0,0 +1,3 @@
1
+ // The shared entry owns window.ODSMegamenu and auto-initialization.
2
+ // No exports: Rollup emits a classic script without creating another global.
3
+ import "./static";