@orangesk/orange-design-system 2.0.0-beta.41 → 2.0.0-beta.42
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/AnchorNavigation/style.css +1 -1
- package/build/components/AnchorNavigation/style.css.map +1 -1
- package/build/components/Buttons/style.css +1 -1
- package/build/components/Buttons/style.css.map +1 -1
- package/build/components/Grid/style.css +1 -1
- package/build/components/Grid/style.css.map +1 -1
- package/build/components/Link/style.css +1 -1
- package/build/components/Link/style.css.map +1 -1
- package/build/components/Modal/style.css +1 -1
- package/build/components/Modal/style.css.map +1 -1
- package/build/components/index.js +1 -1
- package/build/components/index.js.map +1 -1
- package/build/components/tsconfig.tsbuildinfo +1 -1
- package/build/components/types/src/components/AnchorNavigation/AnchorNavigation.static.d.ts +13 -0
- package/build/lib/components.css +1 -1
- package/build/lib/components.css.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/build/sprite.svg +1 -1
- package/package.json +20 -20
- package/src/assets/icons/youtube.svg +3 -1
- package/src/components/AnchorNavigation/AnchorNavigation.static.ts +146 -45
- package/src/components/AnchorNavigation/styles/mixins.scss +10 -2
- package/src/components/AnchorNavigation/tests/AnchorNavigation.unit.test.jsx +194 -8
- package/src/components/Buttons/styles/mixins.scss +8 -13
- package/src/components/Grid/styles/mixins.scss +14 -6
- package/src/components/Link/styles/mixins.scss +0 -1
- package/src/components/Modal/styles/mixins.scss +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangesk/orange-design-system",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.42",
|
|
4
4
|
"private": false,
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.x"
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"@cloudfour/transition-hidden-element": "^2.0.2",
|
|
56
56
|
"@mdx-js/loader": "^3.1.1",
|
|
57
57
|
"@mdx-js/react": "^3.1.1",
|
|
58
|
-
"@next/mdx": "16.2.
|
|
58
|
+
"@next/mdx": "16.2.3",
|
|
59
59
|
"@orangesk/accessible-autocomplete": "3.2.2",
|
|
60
60
|
"@popperjs/core": "^2.11.8",
|
|
61
61
|
"@types/mdx": "^2.0.13",
|
|
@@ -63,19 +63,19 @@
|
|
|
63
63
|
"classnames": "^2.5.1",
|
|
64
64
|
"daypickr": "^0.3.4",
|
|
65
65
|
"diff2html": "^3.4.56",
|
|
66
|
-
"dompurify": "^3.
|
|
67
|
-
"html-react-parser": "
|
|
66
|
+
"dompurify": "^3.4.0",
|
|
67
|
+
"html-react-parser": "6.0.1",
|
|
68
68
|
"lorem-ipsum": "^2.0.8",
|
|
69
69
|
"minisearch": "7.2.0",
|
|
70
|
-
"next": "16.2.
|
|
70
|
+
"next": "16.2.3",
|
|
71
71
|
"normalize.css": "^8.0.1",
|
|
72
72
|
"nouislider": "^15.8.1",
|
|
73
73
|
"prism-react-renderer": "^2.4.1",
|
|
74
74
|
"query-string": "^9.3.1",
|
|
75
|
-
"react": "^19.2.
|
|
76
|
-
"react-dom": "^19.2.
|
|
75
|
+
"react": "^19.2.5",
|
|
76
|
+
"react-dom": "^19.2.5",
|
|
77
77
|
"react-element-to-jsx-string": "^17.0.1",
|
|
78
|
-
"react-is": "^19.2.
|
|
78
|
+
"react-is": "^19.2.5",
|
|
79
79
|
"rehype-autolink-headings": "^7.1.0",
|
|
80
80
|
"rehype-slug": "^6.0.0",
|
|
81
81
|
"remark-gemoji": "^8.0.0",
|
|
@@ -104,35 +104,35 @@
|
|
|
104
104
|
"@testing-library/jest-dom": "^6.9.1",
|
|
105
105
|
"@testing-library/react": "^16.3.2",
|
|
106
106
|
"@testing-library/user-event": "^14.6.1",
|
|
107
|
-
"@types/node": "25.
|
|
107
|
+
"@types/node": "25.6.0",
|
|
108
108
|
"@types/react": "19.2.14",
|
|
109
109
|
"@types/react-dom": "19.2.3",
|
|
110
110
|
"@types/wnumb": "^1.2.3",
|
|
111
111
|
"@vitejs/plugin-react": "6.0.1",
|
|
112
|
-
"@vitest/browser": "^4.1.
|
|
113
|
-
"@vitest/browser-playwright": "^4.1.
|
|
114
|
-
"@vitest/coverage-v8": "^4.1.
|
|
115
|
-
"@vitest/ui": "^4.1.
|
|
112
|
+
"@vitest/browser": "^4.1.4",
|
|
113
|
+
"@vitest/browser-playwright": "^4.1.4",
|
|
114
|
+
"@vitest/coverage-v8": "^4.1.4",
|
|
115
|
+
"@vitest/ui": "^4.1.4",
|
|
116
116
|
"canvas": "^3.2.3",
|
|
117
117
|
"fs-extra": "^11.3.4",
|
|
118
118
|
"glob": "13.0.6",
|
|
119
|
-
"html-validate": "10.
|
|
119
|
+
"html-validate": "10.12.1",
|
|
120
120
|
"husky": "^9.1.7",
|
|
121
121
|
"identity-obj-proxy": "^3.0.0",
|
|
122
|
-
"jsdom": "29.0.
|
|
122
|
+
"jsdom": "29.0.2",
|
|
123
123
|
"lint-staged": "16.4.0",
|
|
124
|
-
"playwright": "^1.59.
|
|
125
|
-
"prettier": "^3.8.
|
|
124
|
+
"playwright": "^1.59.1",
|
|
125
|
+
"prettier": "^3.8.2",
|
|
126
126
|
"rollup": "^4.60.1",
|
|
127
127
|
"rollup-plugin-copy": "^3.5.0",
|
|
128
128
|
"rollup-plugin-dts": "^6.4.1",
|
|
129
129
|
"rollup-plugin-postcss": "^4.0.2",
|
|
130
|
-
"sass": "^1.
|
|
130
|
+
"sass": "^1.99.0",
|
|
131
131
|
"svg-sprite": "^2.0.4",
|
|
132
132
|
"typescript": "6.0.2",
|
|
133
|
-
"vitest": "^4.1.
|
|
133
|
+
"vitest": "^4.1.4",
|
|
134
134
|
"vitest-axe": "^0.1.0",
|
|
135
|
-
"vitest-browser-react": "^2.
|
|
135
|
+
"vitest-browser-react": "^2.2.0"
|
|
136
136
|
},
|
|
137
137
|
"lint-staged": {
|
|
138
138
|
"*.{js,jsx,ts,tsx,json,css}": "biome check --write --no-errors-on-unmatched",
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
<svg
|
|
1
|
+
<svg viewBox="0 0 80 80" id="youtube">
|
|
2
|
+
<path xmlns="http://www.w3.org/2000/svg" d="M63.275 30.05S62.8 26.625 61.4 25.1c-1.825-1.975-3.85-2-4.75-2.1-6.7-.5-16.65-.5-16.65-.5s-10 0-16.625.5c-.925.125-2.95.125-4.75 2.1-1.425 1.5-1.9 4.95-1.9 4.95s-.475 4.025-.475 8.05v3.775c0 4.025.475 8.05.475 8.05s.475 3.425 1.875 4.95c1.825 2 4.2 1.925 5.25 2.125 3.8.375 16.15.5 16.15.5s10 0 16.625-.5c.925-.15 2.95-.15 4.75-2.125 1.425-1.5 1.9-4.95 1.9-4.95s.475-4 .475-8.05v-3.75c0-4.05-.475-8.075-.475-8.075ZM36.25 47.5v-15l12.5 7.525-12.5 7.475Z"></path>
|
|
3
|
+
</svg>
|
|
@@ -4,6 +4,8 @@ export default class AnchorNavigation {
|
|
|
4
4
|
private static readonly DRAG_START_THRESHOLD = 6;
|
|
5
5
|
private static readonly SCROLL_END_DEBOUNCE_MS = 150;
|
|
6
6
|
private static readonly SCROLL_EDGE_TOLERANCE = 1;
|
|
7
|
+
private static readonly SCROLL_ALIGNMENT_TOLERANCE = 1;
|
|
8
|
+
private static readonly MAX_SCROLL_CORRECTIONS = 2;
|
|
7
9
|
|
|
8
10
|
private element: HTMLElement;
|
|
9
11
|
private contentLeftElement: HTMLElement | null;
|
|
@@ -34,6 +36,9 @@ export default class AnchorNavigation {
|
|
|
34
36
|
private sections: HTMLElement[] = [];
|
|
35
37
|
private currentPath: string;
|
|
36
38
|
private lastActiveIndex: number = 0;
|
|
39
|
+
private autoScrollTargetId: string | null = null;
|
|
40
|
+
private autoScrollCorrectionCount: number = 0;
|
|
41
|
+
private lastStickyOffset: number = 0;
|
|
37
42
|
|
|
38
43
|
constructor(element: HTMLElement) {
|
|
39
44
|
this.element = element;
|
|
@@ -78,13 +83,35 @@ export default class AnchorNavigation {
|
|
|
78
83
|
|
|
79
84
|
private updateStickyPosition(): void {
|
|
80
85
|
if (!this.megamenuElement) return;
|
|
81
|
-
|
|
86
|
+
|
|
87
|
+
const stickyOffset = this.megamenuElement.offsetHeight;
|
|
88
|
+
const hasStickyOffsetChanged = stickyOffset !== this.lastStickyOffset;
|
|
89
|
+
|
|
90
|
+
this.lastStickyOffset = stickyOffset;
|
|
91
|
+
this.element.style.top = `${stickyOffset}px`;
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
!hasStickyOffsetChanged ||
|
|
95
|
+
!this.isAutoScrolling ||
|
|
96
|
+
!this.autoScrollTargetId ||
|
|
97
|
+
this.autoScrollCorrectionCount >= AnchorNavigation.MAX_SCROLL_CORRECTIONS
|
|
98
|
+
) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const targetElement = document.getElementById(this.autoScrollTargetId);
|
|
103
|
+
if (!targetElement) return;
|
|
104
|
+
|
|
105
|
+
// Re-target smoothly as soon as sticky offset changes to avoid a visible snap at scroll end.
|
|
106
|
+
this.autoScrollCorrectionCount += 1;
|
|
107
|
+
this.scrollToSection(targetElement, "smooth");
|
|
82
108
|
}
|
|
83
109
|
|
|
84
110
|
private setupMegamenuObserver(): void {
|
|
85
111
|
this.megamenuElement = this.findMegamenuElement();
|
|
86
112
|
|
|
87
113
|
if (!this.megamenuElement) {
|
|
114
|
+
this.lastStickyOffset = 0;
|
|
88
115
|
this.element.style.top = "0px";
|
|
89
116
|
return;
|
|
90
117
|
}
|
|
@@ -132,6 +159,7 @@ export default class AnchorNavigation {
|
|
|
132
159
|
window.addEventListener("resize", this.resizeHandler);
|
|
133
160
|
|
|
134
161
|
this.initScrollSpy();
|
|
162
|
+
this.alignInitialHashIfNeeded();
|
|
135
163
|
}
|
|
136
164
|
|
|
137
165
|
private teardownScrollSpyListeners(): void {
|
|
@@ -190,27 +218,108 @@ export default class AnchorNavigation {
|
|
|
190
218
|
const targetElement = document.getElementById(targetId);
|
|
191
219
|
if (!targetElement) return;
|
|
192
220
|
|
|
193
|
-
this.
|
|
221
|
+
this.startAutoScroll(targetId);
|
|
194
222
|
anchor.blur();
|
|
195
223
|
|
|
224
|
+
this.scrollToSection(targetElement, "smooth");
|
|
225
|
+
|
|
226
|
+
const nextUrl = `${window.location.pathname}${window.location.search}#${targetId}`;
|
|
227
|
+
window.history.pushState(null, "", nextUrl);
|
|
228
|
+
this.initScrollSpy(targetId);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private getHashSectionId(): string | null {
|
|
232
|
+
const hash = window.location.hash;
|
|
233
|
+
if (!hash || hash.length <= 1) return null;
|
|
234
|
+
|
|
235
|
+
let hashId = hash.slice(1);
|
|
236
|
+
try {
|
|
237
|
+
hashId = decodeURIComponent(hashId);
|
|
238
|
+
} catch {
|
|
239
|
+
// Keep raw hash when decoding fails.
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return hashId || null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private startAutoScroll(sectionId: string): void {
|
|
246
|
+
this.isAutoScrolling = true;
|
|
247
|
+
this.autoScrollTargetId = sectionId;
|
|
248
|
+
this.autoScrollCorrectionCount = 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private resetAutoScrollState(): void {
|
|
252
|
+
this.isAutoScrolling = false;
|
|
253
|
+
this.autoScrollTargetId = null;
|
|
254
|
+
this.autoScrollCorrectionCount = 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private tryCorrectAutoScrollAlignment(): boolean {
|
|
258
|
+
if (!this.autoScrollTargetId) return true;
|
|
259
|
+
|
|
260
|
+
const targetElement = document.getElementById(this.autoScrollTargetId);
|
|
261
|
+
if (!targetElement) return true;
|
|
262
|
+
|
|
263
|
+
const targetTop = this.getTargetTop(targetElement);
|
|
264
|
+
const distance = Math.abs(window.scrollY - targetTop);
|
|
265
|
+
if (distance <= AnchorNavigation.SCROLL_ALIGNMENT_TOLERANCE) {
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (
|
|
270
|
+
this.autoScrollCorrectionCount >= AnchorNavigation.MAX_SCROLL_CORRECTIONS
|
|
271
|
+
) {
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
this.autoScrollCorrectionCount += 1;
|
|
276
|
+
const correctionBehavior: ScrollBehavior =
|
|
277
|
+
this.autoScrollCorrectionCount === 1 ? "smooth" : "auto";
|
|
278
|
+
this.scrollToSection(targetElement, correctionBehavior);
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private getTotalStickyOffset(): number {
|
|
196
283
|
const scrollOffset = this.megamenuElement
|
|
197
284
|
? this.megamenuElement.offsetHeight
|
|
198
285
|
: 0;
|
|
199
|
-
const
|
|
286
|
+
const anchorNavOffset = this.element.offsetHeight;
|
|
287
|
+
return scrollOffset + anchorNavOffset;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private getTargetTop(targetElement: HTMLElement): number {
|
|
200
291
|
const targetTop =
|
|
201
292
|
targetElement.getBoundingClientRect().top +
|
|
202
293
|
window.scrollY -
|
|
203
|
-
|
|
204
|
-
|
|
294
|
+
this.getTotalStickyOffset();
|
|
295
|
+
return Math.max(0, targetTop);
|
|
296
|
+
}
|
|
205
297
|
|
|
298
|
+
private scrollToSection(
|
|
299
|
+
targetElement: HTMLElement,
|
|
300
|
+
behavior: ScrollBehavior,
|
|
301
|
+
): void {
|
|
206
302
|
window.scrollTo({
|
|
207
|
-
top:
|
|
208
|
-
behavior
|
|
303
|
+
top: this.getTargetTop(targetElement),
|
|
304
|
+
behavior,
|
|
209
305
|
});
|
|
306
|
+
}
|
|
210
307
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
308
|
+
private alignInitialHashIfNeeded(): void {
|
|
309
|
+
const hashId = this.getHashSectionId();
|
|
310
|
+
if (!hashId) return;
|
|
311
|
+
|
|
312
|
+
const targetElement = document.getElementById(hashId);
|
|
313
|
+
if (!targetElement) return;
|
|
314
|
+
|
|
315
|
+
this.startAutoScroll(hashId);
|
|
316
|
+
|
|
317
|
+
requestAnimationFrame(() => {
|
|
318
|
+
this.scrollToSection(targetElement, "auto");
|
|
319
|
+
requestAnimationFrame(() => {
|
|
320
|
+
this.handleScrollEnd();
|
|
321
|
+
});
|
|
322
|
+
});
|
|
214
323
|
}
|
|
215
324
|
|
|
216
325
|
private initScrollSpy(forcedSectionId: string | null = null): void {
|
|
@@ -323,37 +432,15 @@ export default class AnchorNavigation {
|
|
|
323
432
|
0,
|
|
324
433
|
contentLeft.scrollWidth - contentLeft.clientWidth,
|
|
325
434
|
);
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const isFirstItem = activeLink === firstNavLink;
|
|
336
|
-
const isLastItem = activeLink === lastNavLink;
|
|
337
|
-
|
|
338
|
-
let targetScrollLeft: number | null = null;
|
|
339
|
-
if (isFirstItem) {
|
|
340
|
-
targetScrollLeft = 0;
|
|
341
|
-
} else if (isLastItem) {
|
|
342
|
-
targetScrollLeft = maxScrollLeft;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const itemLeft = activeLink.offsetLeft;
|
|
346
|
-
const itemRight = itemLeft + activeLink.clientWidth;
|
|
347
|
-
const viewportLeft = contentLeft.scrollLeft;
|
|
348
|
-
const viewportRight = viewportLeft + contentLeft.clientWidth;
|
|
349
|
-
const isVisible = itemLeft >= viewportLeft && itemRight <= viewportRight;
|
|
350
|
-
|
|
351
|
-
if (targetScrollLeft === null && !forceCenter && isVisible) return;
|
|
352
|
-
|
|
353
|
-
if (targetScrollLeft === null) {
|
|
354
|
-
targetScrollLeft =
|
|
355
|
-
itemLeft - contentLeft.clientWidth / 2 + activeLink.clientWidth / 2;
|
|
356
|
-
}
|
|
435
|
+
const contentRect = contentLeft.getBoundingClientRect();
|
|
436
|
+
const itemRect = activeLink.getBoundingClientRect();
|
|
437
|
+
const itemCenterWithinContent =
|
|
438
|
+
itemRect.left -
|
|
439
|
+
contentRect.left +
|
|
440
|
+
contentLeft.scrollLeft +
|
|
441
|
+
itemRect.width / 2;
|
|
442
|
+
const targetScrollLeft =
|
|
443
|
+
itemCenterWithinContent - contentLeft.clientWidth / 2;
|
|
357
444
|
|
|
358
445
|
const behavior = window.innerWidth < 768 ? "auto" : "smooth";
|
|
359
446
|
const nextScrollLeft = Math.min(
|
|
@@ -361,6 +448,11 @@ export default class AnchorNavigation {
|
|
|
361
448
|
Math.max(0, targetScrollLeft),
|
|
362
449
|
);
|
|
363
450
|
|
|
451
|
+
const isAlreadyAligned =
|
|
452
|
+
Math.abs(contentLeft.scrollLeft - nextScrollLeft) <=
|
|
453
|
+
AnchorNavigation.SCROLL_ALIGNMENT_TOLERANCE;
|
|
454
|
+
if (!forceCenter && isAlreadyAligned) return;
|
|
455
|
+
|
|
364
456
|
if (typeof contentLeft.scrollTo === "function") {
|
|
365
457
|
contentLeft.scrollTo({
|
|
366
458
|
left: nextScrollLeft,
|
|
@@ -573,9 +665,7 @@ export default class AnchorNavigation {
|
|
|
573
665
|
|
|
574
666
|
// Set a timeout to detect when scrolling has ended
|
|
575
667
|
this.scrollTimeout = setTimeout(() => {
|
|
576
|
-
this.
|
|
577
|
-
this.scrollTimeout = null;
|
|
578
|
-
this.initScrollSpy();
|
|
668
|
+
this.handleScrollEnd();
|
|
579
669
|
}, AnchorNavigation.SCROLL_END_DEBOUNCE_MS);
|
|
580
670
|
} else {
|
|
581
671
|
this.initScrollSpy();
|
|
@@ -587,7 +677,12 @@ export default class AnchorNavigation {
|
|
|
587
677
|
clearTimeout(this.scrollTimeout);
|
|
588
678
|
this.scrollTimeout = null;
|
|
589
679
|
}
|
|
590
|
-
|
|
680
|
+
|
|
681
|
+
if (!this.tryCorrectAutoScrollAlignment()) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
this.resetAutoScrollState();
|
|
591
686
|
this.initScrollSpy();
|
|
592
687
|
}
|
|
593
688
|
|
|
@@ -614,9 +709,12 @@ export default class AnchorNavigation {
|
|
|
614
709
|
|
|
615
710
|
this.element.style.top = "";
|
|
616
711
|
this.megamenuElement = null;
|
|
712
|
+
this.lastStickyOffset = 0;
|
|
617
713
|
this.navLinks = null;
|
|
618
714
|
this.sections = [];
|
|
619
715
|
this.lastActiveIndex = 0;
|
|
716
|
+
this.autoScrollTargetId = null;
|
|
717
|
+
this.autoScrollCorrectionCount = 0;
|
|
620
718
|
(this.element as any).ODS_AnchorNavigation = null;
|
|
621
719
|
}
|
|
622
720
|
|
|
@@ -636,9 +734,12 @@ export default class AnchorNavigation {
|
|
|
636
734
|
}
|
|
637
735
|
|
|
638
736
|
this.megamenuElement = null;
|
|
737
|
+
this.lastStickyOffset = 0;
|
|
639
738
|
this.navLinks = null;
|
|
640
739
|
this.sections = [];
|
|
641
740
|
this.lastActiveIndex = 0;
|
|
741
|
+
this.autoScrollTargetId = null;
|
|
742
|
+
this.autoScrollCorrectionCount = 0;
|
|
642
743
|
|
|
643
744
|
this.init();
|
|
644
745
|
}
|
|
@@ -48,15 +48,23 @@
|
|
|
48
48
|
padding: convert.to-rem(15px) 0 !important;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
&:hover,
|
|
52
51
|
&:focus-visible,
|
|
53
|
-
&:active,
|
|
54
52
|
&.is-active {
|
|
55
53
|
box-shadow: megamenuConfig.$active-line;
|
|
56
54
|
color: inherit;
|
|
57
55
|
text-decoration: none !important;
|
|
58
56
|
outline: none;
|
|
59
57
|
}
|
|
58
|
+
|
|
59
|
+
@media (hover: hover) and (pointer: fine) {
|
|
60
|
+
&:hover,
|
|
61
|
+
&:active {
|
|
62
|
+
box-shadow: megamenuConfig.$active-line;
|
|
63
|
+
color: inherit;
|
|
64
|
+
text-decoration: none !important;
|
|
65
|
+
outline: none;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
@mixin anchor-navigation-content() {
|
|
@@ -362,13 +362,33 @@ describe("rendering AnchorNavigation", () => {
|
|
|
362
362
|
value: 0,
|
|
363
363
|
writable: true,
|
|
364
364
|
});
|
|
365
|
-
Object.defineProperty(
|
|
365
|
+
Object.defineProperty(contentLeft, "getBoundingClientRect", {
|
|
366
366
|
configurable: true,
|
|
367
|
-
value:
|
|
367
|
+
value: () => ({
|
|
368
|
+
top: 0,
|
|
369
|
+
bottom: 0,
|
|
370
|
+
left: 0,
|
|
371
|
+
right: 180,
|
|
372
|
+
width: 180,
|
|
373
|
+
height: 0,
|
|
374
|
+
x: 0,
|
|
375
|
+
y: 0,
|
|
376
|
+
toJSON: () => ({}),
|
|
377
|
+
}),
|
|
368
378
|
});
|
|
369
|
-
Object.defineProperty(activeLink, "
|
|
379
|
+
Object.defineProperty(activeLink, "getBoundingClientRect", {
|
|
370
380
|
configurable: true,
|
|
371
|
-
value:
|
|
381
|
+
value: () => ({
|
|
382
|
+
top: 0,
|
|
383
|
+
bottom: 0,
|
|
384
|
+
left: 220,
|
|
385
|
+
right: 300,
|
|
386
|
+
width: 80,
|
|
387
|
+
height: 0,
|
|
388
|
+
x: 220,
|
|
389
|
+
y: 0,
|
|
390
|
+
toJSON: () => ({}),
|
|
391
|
+
}),
|
|
372
392
|
});
|
|
373
393
|
contentLeft.scrollTo = scrollToSpy;
|
|
374
394
|
|
|
@@ -412,13 +432,33 @@ describe("rendering AnchorNavigation", () => {
|
|
|
412
432
|
value: 180,
|
|
413
433
|
writable: true,
|
|
414
434
|
});
|
|
415
|
-
Object.defineProperty(
|
|
435
|
+
Object.defineProperty(contentLeft, "getBoundingClientRect", {
|
|
416
436
|
configurable: true,
|
|
417
|
-
value:
|
|
437
|
+
value: () => ({
|
|
438
|
+
top: 0,
|
|
439
|
+
bottom: 0,
|
|
440
|
+
left: 0,
|
|
441
|
+
right: 200,
|
|
442
|
+
width: 200,
|
|
443
|
+
height: 0,
|
|
444
|
+
x: 0,
|
|
445
|
+
y: 0,
|
|
446
|
+
toJSON: () => ({}),
|
|
447
|
+
}),
|
|
418
448
|
});
|
|
419
|
-
Object.defineProperty(activeLink, "
|
|
449
|
+
Object.defineProperty(activeLink, "getBoundingClientRect", {
|
|
420
450
|
configurable: true,
|
|
421
|
-
value:
|
|
451
|
+
value: () => ({
|
|
452
|
+
top: 0,
|
|
453
|
+
bottom: 0,
|
|
454
|
+
left: 40,
|
|
455
|
+
right: 120,
|
|
456
|
+
width: 80,
|
|
457
|
+
height: 0,
|
|
458
|
+
x: 40,
|
|
459
|
+
y: 0,
|
|
460
|
+
toJSON: () => ({}),
|
|
461
|
+
}),
|
|
422
462
|
});
|
|
423
463
|
contentLeft.scrollTo = scrollToSpy;
|
|
424
464
|
|
|
@@ -433,6 +473,71 @@ describe("rendering AnchorNavigation", () => {
|
|
|
433
473
|
section.remove();
|
|
434
474
|
});
|
|
435
475
|
|
|
476
|
+
it("centers scroll-spy active item even when it is already visible", () => {
|
|
477
|
+
const { container } = render(<AnchorNavigation items={basicItems} />);
|
|
478
|
+
const anchorNavigationElement = initializeAnchorNavigation(container);
|
|
479
|
+
const anchorNavigation = AnchorNavigationStatic.getInstance(
|
|
480
|
+
anchorNavigationElement,
|
|
481
|
+
);
|
|
482
|
+
const contentLeft = container.querySelector(
|
|
483
|
+
".anchor-navigation__content-left",
|
|
484
|
+
);
|
|
485
|
+
const activeLink = container.querySelector('a[href="#pricing"]');
|
|
486
|
+
const scrollToSpy = vi.fn();
|
|
487
|
+
|
|
488
|
+
Object.defineProperty(contentLeft, "clientWidth", {
|
|
489
|
+
configurable: true,
|
|
490
|
+
value: 200,
|
|
491
|
+
});
|
|
492
|
+
Object.defineProperty(contentLeft, "scrollWidth", {
|
|
493
|
+
configurable: true,
|
|
494
|
+
value: 600,
|
|
495
|
+
});
|
|
496
|
+
Object.defineProperty(contentLeft, "scrollLeft", {
|
|
497
|
+
configurable: true,
|
|
498
|
+
value: 180,
|
|
499
|
+
writable: true,
|
|
500
|
+
});
|
|
501
|
+
Object.defineProperty(contentLeft, "getBoundingClientRect", {
|
|
502
|
+
configurable: true,
|
|
503
|
+
value: () => ({
|
|
504
|
+
top: 0,
|
|
505
|
+
bottom: 0,
|
|
506
|
+
left: 0,
|
|
507
|
+
right: 200,
|
|
508
|
+
width: 200,
|
|
509
|
+
height: 0,
|
|
510
|
+
x: 0,
|
|
511
|
+
y: 0,
|
|
512
|
+
toJSON: () => ({}),
|
|
513
|
+
}),
|
|
514
|
+
});
|
|
515
|
+
Object.defineProperty(activeLink, "getBoundingClientRect", {
|
|
516
|
+
configurable: true,
|
|
517
|
+
value: () => ({
|
|
518
|
+
top: 0,
|
|
519
|
+
bottom: 0,
|
|
520
|
+
left: 40,
|
|
521
|
+
right: 120,
|
|
522
|
+
width: 80,
|
|
523
|
+
height: 0,
|
|
524
|
+
x: 40,
|
|
525
|
+
y: 0,
|
|
526
|
+
toJSON: () => ({}),
|
|
527
|
+
}),
|
|
528
|
+
});
|
|
529
|
+
contentLeft.scrollTo = scrollToSpy;
|
|
530
|
+
|
|
531
|
+
anchorNavigation.scrollActiveLinkIntoView(contentLeft, activeLink, false);
|
|
532
|
+
|
|
533
|
+
expect(scrollToSpy).toHaveBeenCalledWith({
|
|
534
|
+
left: 160,
|
|
535
|
+
behavior: "smooth",
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
anchorNavigation?.destroy();
|
|
539
|
+
});
|
|
540
|
+
|
|
436
541
|
it("does not duplicate anchor click handling after update", () => {
|
|
437
542
|
const section = document.createElement("section");
|
|
438
543
|
section.id = "features";
|
|
@@ -498,6 +603,87 @@ describe("rendering AnchorNavigation", () => {
|
|
|
498
603
|
section.remove();
|
|
499
604
|
});
|
|
500
605
|
|
|
606
|
+
it("realigns anchor after sticky offset changes during smooth scroll", () => {
|
|
607
|
+
vi.useFakeTimers();
|
|
608
|
+
|
|
609
|
+
const section = document.createElement("section");
|
|
610
|
+
section.id = "pricing";
|
|
611
|
+
Object.defineProperty(section, "getBoundingClientRect", {
|
|
612
|
+
configurable: true,
|
|
613
|
+
value: () => ({
|
|
614
|
+
top: 600 - window.scrollY,
|
|
615
|
+
bottom: 700 - window.scrollY,
|
|
616
|
+
left: 0,
|
|
617
|
+
right: 0,
|
|
618
|
+
width: 0,
|
|
619
|
+
height: 100,
|
|
620
|
+
x: 0,
|
|
621
|
+
y: 600,
|
|
622
|
+
toJSON: () => ({}),
|
|
623
|
+
}),
|
|
624
|
+
});
|
|
625
|
+
document.body.appendChild(section);
|
|
626
|
+
|
|
627
|
+
const megamenu = document.createElement("div");
|
|
628
|
+
megamenu.setAttribute("data-megamenu", "");
|
|
629
|
+
let megamenuHeight = 120;
|
|
630
|
+
Object.defineProperty(megamenu, "offsetHeight", {
|
|
631
|
+
configurable: true,
|
|
632
|
+
get: () => megamenuHeight,
|
|
633
|
+
});
|
|
634
|
+
document.body.appendChild(megamenu);
|
|
635
|
+
|
|
636
|
+
Object.defineProperty(window, "scrollY", {
|
|
637
|
+
configurable: true,
|
|
638
|
+
value: 0,
|
|
639
|
+
writable: true,
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
const scrollToSpy = vi
|
|
643
|
+
.spyOn(window, "scrollTo")
|
|
644
|
+
.mockImplementation((options) => {
|
|
645
|
+
if (typeof options === "object" && typeof options.top === "number") {
|
|
646
|
+
window.scrollY = options.top;
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
const { container } = render(<AnchorNavigation items={basicItems} />);
|
|
651
|
+
const anchorNavigationElement = initializeAnchorNavigation(container);
|
|
652
|
+
const anchorNavigation = AnchorNavigationStatic.getInstance(
|
|
653
|
+
anchorNavigationElement,
|
|
654
|
+
);
|
|
655
|
+
const pricingLink = container.querySelector('a[href="#pricing"]');
|
|
656
|
+
|
|
657
|
+
Object.defineProperty(anchorNavigationElement, "offsetHeight", {
|
|
658
|
+
configurable: true,
|
|
659
|
+
value: 50,
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
fireEvent.click(pricingLink);
|
|
663
|
+
|
|
664
|
+
expect(scrollToSpy).toHaveBeenNthCalledWith(1, {
|
|
665
|
+
top: 430,
|
|
666
|
+
behavior: "smooth",
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
megamenuHeight = 80;
|
|
670
|
+
fireEvent.scroll(window);
|
|
671
|
+
vi.advanceTimersByTime(
|
|
672
|
+
AnchorNavigationStatic.SCROLL_END_DEBOUNCE_MS + 20,
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
expect(scrollToSpy).toHaveBeenNthCalledWith(2, {
|
|
676
|
+
top: 470,
|
|
677
|
+
behavior: "smooth",
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
anchorNavigation?.destroy();
|
|
681
|
+
scrollToSpy.mockRestore();
|
|
682
|
+
megamenu.remove();
|
|
683
|
+
section.remove();
|
|
684
|
+
vi.useRealTimers();
|
|
685
|
+
});
|
|
686
|
+
|
|
501
687
|
it("toggles left and right overflow classes based on scroll position", () => {
|
|
502
688
|
const { container } = render(<AnchorNavigation items={moreItems} />);
|
|
503
689
|
const anchorNavigationElement = initializeAnchorNavigation(container);
|
|
@@ -9,31 +9,26 @@
|
|
|
9
9
|
) {
|
|
10
10
|
display: flex;
|
|
11
11
|
flex-flow: row wrap;
|
|
12
|
-
|
|
13
|
-
margin:
|
|
12
|
+
gap: $spacing;
|
|
13
|
+
margin: 0 0 space.get("large") 0;
|
|
14
14
|
|
|
15
15
|
#{$button-selector},
|
|
16
|
-
#{$button-selector}:last-child,
|
|
17
16
|
#{$link-selector},
|
|
18
|
-
#{$link-selector}
|
|
19
|
-
|
|
20
|
-
button#{$link-selector}:last-child {
|
|
21
|
-
margin: $spacing $spacing 0 0;
|
|
17
|
+
button#{$link-selector} {
|
|
18
|
+
margin: 0;
|
|
22
19
|
}
|
|
23
20
|
}
|
|
24
21
|
|
|
25
22
|
@mixin stack-on-xs($spacing: config.$spacing, $button-selector: ".btn") {
|
|
26
23
|
@include breakpoint.get("xs", "down") {
|
|
27
24
|
flex-flow: column;
|
|
25
|
+
gap: $spacing;
|
|
28
26
|
max-width: 100%;
|
|
29
27
|
margin: 0 0 $spacing 0;
|
|
30
28
|
|
|
31
|
-
#{$button-selector}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
button.link:last-child {
|
|
35
|
-
margin-right: 0;
|
|
36
|
-
margin-bottom: 0;
|
|
29
|
+
#{$button-selector},
|
|
30
|
+
button.link {
|
|
31
|
+
margin: 0;
|
|
37
32
|
}
|
|
38
33
|
}
|
|
39
34
|
}
|