Skip to content

Commit c8f36a4

Browse files
authored
Fix safari layout shift bug (playfulprogramming#1076) (playfulprogramming#1082)
Fixes issue playfulprogramming#1076. Instead of using `target.scrollIntoView()`, calculates the offset before running `changeTabs()` and apply that offset to the window vertical scroll position using `window.scroll()`. This ensures the position of the code tabs remain the same before/after changing tabs.
2 parents b8e9fe8 + 0b4ace4 commit c8f36a4

File tree

2 files changed

+31
-24
lines changed

2 files changed

+31
-24
lines changed

__mocks__/setup.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,28 @@ window.history.back = () => {
2222
};
2323

2424
// https://github.com/jsdom/jsdom/issues/3294
25-
// eslint-disable-next-line no-undef
25+
2626
HTMLDialogElement.prototype.show = vi.fn(function mock(
2727
this: HTMLDialogElement,
2828
) {
2929
this.open = true;
3030
});
3131

32-
// eslint-disable-next-line no-undef
32+
3333
HTMLDialogElement.prototype.showModal = vi.fn(function mock(
3434
this: HTMLDialogElement,
3535
) {
3636
this.open = true;
3737
});
3838

39-
// eslint-disable-next-line no-undef
39+
4040
HTMLDialogElement.prototype.close = vi.fn(function mock(
4141
this: HTMLDialogElement,
4242
) {
4343
this.open = false;
4444
});
4545

46-
// eslint-disable-next-line no-undef
46+
4747
Object.defineProperties(globalThis, {
4848
// For the location mock package
4949
jest: { value: vi },

src/utils/markdown/components/tabs/tabs-script.ts

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ export const enableTabs = () => {
2020

2121
// Handle arrow navigation between tabs in the tab list
2222
function handleKeydown(this: HTMLElement, e: KeyboardEvent) {
23-
if (e.keyCode === 39 || e.keyCode === 37) {
23+
if (e.code === "ArrowRight" || e.code === "ArrowLeft") {
2424
const tabs = this.children;
2525
let tabfocus = Number(this.dataset.tabfocus || 0);
2626
tabs[tabfocus].setAttribute("tabindex", "-1");
27-
if (e.keyCode === 39) {
27+
if (e.code === "ArrowRight") {
2828
// Move right
2929
// Increase tab index, wrap by # of tabs
3030
tabfocus = (tabfocus + 1) % tabs.length;
31-
} else if (e.keyCode === 37) {
31+
} else if (e.code === "ArrowLeft") {
3232
// Move left
3333
tabfocus--;
3434
// If we're at the start, move to the end
@@ -39,21 +39,32 @@ export const enableTabs = () => {
3939

4040
// Update tabfocus values
4141
this.dataset.tabfocus = tabfocus + "";
42-
// Focus + click selected tab
42+
// Focus selected tab
4343
const tab = tabs[tabfocus] as HTMLElement;
4444
tab.setAttribute("tabindex", "0");
4545
tab.focus();
46-
tab.click();
4746

48-
// Scroll onto screen in order to avoid jumping page locations
47+
// Check if tab list is within viewport
48+
const header = document.querySelector("#header-bar");
49+
const headerOffset = (header?.getBoundingClientRect().height ?? 0) + 20;
50+
const tabYPosition = tab.getBoundingClientRect().y;
51+
const tabHeight = tab.getBoundingClientRect().height;
52+
const isWithinViewport =
53+
tabYPosition > headerOffset &&
54+
tabYPosition < window.innerHeight - tabHeight;
55+
56+
// Preserve scroll position if within viewport
57+
// Else scroll to 20px below header
58+
const offsetBeforeChangeTabs = tab.offsetTop - window.scrollY;
59+
const offset = isWithinViewport ? offsetBeforeChangeTabs : headerOffset;
60+
61+
const tabName = tab.dataset.tabname;
62+
if (!tabName) return;
63+
changeTabs(tabName);
64+
65+
// Ensure tab list is within viewport after switching tabs
4966
setTimeout(() => {
50-
if (tab.scrollIntoView) {
51-
tab.scrollIntoView({
52-
behavior: "auto",
53-
block: "center",
54-
inline: "center",
55-
});
56-
}
67+
window.scroll(0, tab.offsetTop - offset);
5768
}, 0);
5869
}
5970
}
@@ -63,18 +74,14 @@ export const enableTabs = () => {
6374
const tabName = target.dataset.tabname;
6475
if (!tabName) return;
6576

77+
const offsetBeforeChangeTabs = target.offsetTop - window.scrollY;
78+
6679
changeTabs(tabName);
6780

6881
if (shouldScrollToTab) {
6982
// Scroll onto screen in order to avoid jumping page locations
7083
setTimeout(() => {
71-
if (target.scrollIntoView) {
72-
target.scrollIntoView({
73-
behavior: "auto",
74-
block: "center",
75-
inline: "center",
76-
});
77-
}
84+
window.scroll(0, target.offsetTop - offsetBeforeChangeTabs);
7885
}, 0);
7986
}
8087
}

0 commit comments

Comments
 (0)