Skip to content

Commit dd17d14

Browse files
committed
one fix to rule them all
1 parent 6261a87 commit dd17d14

File tree

1 file changed

+42
-40
lines changed

1 file changed

+42
-40
lines changed

packages/kit/src/runtime/client/client.js

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1602,11 +1602,7 @@ async function navigate({
16021602
const scroll = popped ? popped.scroll : noscroll ? scroll_state() : null;
16031603

16041604
if (autoscroll) {
1605-
const deep_linked =
1606-
url.hash &&
1607-
document.getElementById(
1608-
decodeURIComponent(app.hash ? (url.hash.split('#')[2] ?? '') : url.hash.slice(1))
1609-
);
1605+
const deep_linked = url.hash && document.getElementById(get_id(url));
16101606
if (scroll) {
16111607
scrollTo(scroll.x, scroll.y);
16121608
} else if (deep_linked) {
@@ -1627,7 +1623,7 @@ async function navigate({
16271623
document.activeElement !== document.body;
16281624

16291625
if (!keepfocus && !changed_focus) {
1630-
reset_focus();
1626+
reset_focus(url);
16311627
}
16321628

16331629
autoscroll = true;
@@ -2194,7 +2190,7 @@ export async function applyAction(result) {
21942190
root.$set(navigation_result.props);
21952191
update(navigation_result.props.page);
21962192

2197-
void tick().then(reset_focus);
2193+
void tick().then(() => reset_focus(url));
21982194
}
21992195
} else if (result.type === 'redirect') {
22002196
await _goto(result.location, { invalidateAll: true }, 0);
@@ -2215,7 +2211,7 @@ export async function applyAction(result) {
22152211
root.$set({ form: result.data });
22162212

22172213
if (result.type === 'success') {
2218-
reset_focus();
2214+
reset_focus(page.url);
22192215
}
22202216
}
22212217
}
@@ -2793,50 +2789,48 @@ function deserialize_uses(uses) {
27932789
};
27942790
}
27952791

2796-
function reset_focus() {
2792+
/**
2793+
* @param {URL} url
2794+
*/
2795+
function reset_focus(url) {
27972796
const autofocus = document.querySelector('[autofocus]');
27982797
if (autofocus) {
27992798
// @ts-ignore
28002799
autofocus.focus();
28012800
} else {
28022801
// Reset page selection and focus
2803-
if (location.hash && document.querySelector(location.hash)) {
2804-
const { x, y } = scroll_state();
2805-
2806-
setTimeout(() => {
2807-
const history_state = history.state;
2808-
// Mimic the browsers' behaviour and set the sequential focus navigation
2809-
// starting point to the fragment identifier
2810-
location.replace(location.hash);
2811-
// but Firefox has a bug that sets the history state as null so we
2812-
// need to restore the history state
2813-
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1199924
2814-
history.replaceState(history_state, '', location.hash);
2815-
2816-
// Scroll management has already happened earlier so we need to restore
2817-
// the scroll position after setting the sequential focus navigation starting point
2818-
scrollTo(x, y);
2819-
});
2820-
} else {
2821-
// We try to mimic browsers' behaviour as closely as possible by targeting the
2822-
// first scrollable region, but unfortunately it's not a perfect match — e.g.
2823-
// shift-tabbing won't immediately cycle up from the end of the page on Chromium
2824-
// See https://html.spec.whatwg.org/multipage/interaction.html#get-the-focusable-area
2825-
const root = document.body;
2826-
const tabindex = root.getAttribute('tabindex');
2827-
2828-
root.tabIndex = -1;
2802+
const id = get_id(url);
2803+
2804+
// We try to mimic browsers' behaviour as closely as possible by targeting the
2805+
// first scrollable region, but unfortunately it's not a perfect match — e.g.
2806+
// shift-tabbing won't immediately cycle up from the end of the page on Chromium
2807+
// See https://html.spec.whatwg.org/multipage/interaction.html#get-the-focusable-area
2808+
// If there's a fragment identifier, we mimic the browsers' behaviour and set the
2809+
// sequential focus navigation starting point to it
2810+
const element = document.getElementById(id) ?? document.body;
2811+
2812+
const { x, y } = scroll_state();
2813+
2814+
// doing this in a `setTimeout` makes `.focus()` work on Firefox
2815+
setTimeout(() => {
2816+
const tabindex = element.getAttribute('tabindex');
2817+
element.tabIndex = -1;
28292818
// @ts-expect-error options.focusVisible is only supported in Firefox
28302819
// See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#browser_compatibility
2831-
root.focus({ preventScroll: true, focusVisible: false });
2820+
element.focus({ preventScroll: true, focusVisible: false });
28322821

2833-
// restore `tabindex` as to prevent `root` from stealing input from elements
28342822
if (tabindex !== null) {
2835-
root.setAttribute('tabindex', tabindex);
2823+
element.setAttribute('tabindex', tabindex);
28362824
} else {
2837-
root.removeAttribute('tabindex');
2825+
element.removeAttribute('tabindex');
28382826
}
2839-
}
2827+
2828+
if (id) {
2829+
// Scroll management has already happened earlier so we need to restore
2830+
// the scroll position after setting the sequential focus navigation starting point
2831+
scrollTo(x, y);
2832+
}
2833+
});
28402834

28412835
// capture current selection, so we can compare the state after
28422836
// snapshot restoration and afterNavigate callbacks have run
@@ -2960,6 +2954,14 @@ function decode_hash(url) {
29602954
return new_url;
29612955
}
29622956

2957+
/**
2958+
* @param {URL} url
2959+
* @returns {string}
2960+
*/
2961+
function get_id(url) {
2962+
return decodeURIComponent(app.hash ? (url.hash.split('#')[2] ?? '') : url.hash.slice(1));
2963+
}
2964+
29632965
if (DEV) {
29642966
// Nasty hack to silence harmless warnings the user can do nothing about
29652967
const console_warn = console.warn;

0 commit comments

Comments
 (0)