UNPKG

@sveltejs/kit

Version:

SvelteKit is the fastest way to build Svelte apps

92 lines (78 loc) • 2.98 kB
import { hash_routing } from '$app/paths/internal/client'; import { get_hash_element } from './utils.js'; /** * Blurs the active element before the DOM update when a navigation resets focus, so that * blur/focusout handlers run while the outgoing component's data is still valid (#14575) * @param {boolean} reset */ export function blur_active_element(reset) { const element = document.activeElement; if ( reset && (element instanceof HTMLElement || element instanceof SVGElement) && element !== document.body ) { element.blur(); } } /** * Sets the sequential focus navigation starting point to `element` without leaving it focused * @param {HTMLElement} element */ function focus_element(element) { const tabindex = element.getAttribute('tabindex'); element.setAttribute('tabindex', '-1'); element.focus({ preventScroll: true, focusVisible: false }); // removing `tabindex` blurs it again, synchronously in Chromium and a frame later elsewhere if (tabindex !== null) { element.setAttribute('tabindex', tabindex); } else { element.removeAttribute('tabindex'); } } /** @param {URL} url */ export function reset_focus(url) { const autofocus = document.querySelector('[autofocus]'); if (autofocus) { // @ts-ignore autofocus.focus(); } else { // set the sequential focus navigation starting point to the fragment identifier, or to // the first scrollable region when there is none. Not a perfect match for browsers: // shift-tabbing won't immediately cycle up from the end of the page on Chromium // See https://html.spec.whatwg.org/multipage/interaction.html#get-the-focusable-area focus_element(get_hash_element(url, hash_routing) ?? document.body); // capture current selection, so we can compare the state after // snapshot restoration and afterNavigate callbacks have run const selection = getSelection(); if (selection && selection.type !== 'None') { /** @type {Range[]} */ const ranges = []; for (let i = 0; i < selection.rangeCount; i += 1) { ranges.push(selection.getRangeAt(i)); } setTimeout(() => { if (selection.rangeCount !== ranges.length) return; for (let i = 0; i < selection.rangeCount; i += 1) { const a = ranges[i]; const b = selection.getRangeAt(i); // compare field by field: a range modified in place keeps its identity, // and Safari before 17 returned a new Range object on every getRangeAt() if ( a.commonAncestorContainer !== b.commonAncestorContainer || a.startContainer !== b.startContainer || a.endContainer !== b.endContainer || a.startOffset !== b.startOffset || a.endOffset !== b.endOffset ) { return; } } // if the selection hasn't changed (as a result of an element being (auto)focused, // or a programmatic selection, we reset everything as part of the navigation) // fixes https://github.com/sveltejs/kit/issues/8439 selection.removeAllRanges(); }); } } }