@@ -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+
29632965if ( DEV ) {
29642966 // Nasty hack to silence harmless warnings the user can do nothing about
29652967 const console_warn = console . warn ;
0 commit comments