Skip to content

Commit

Permalink
feat: add data-sveltekit-replacestate and -keepfocus options to links (
Browse files Browse the repository at this point in the history
…#9019)

Closes #9014
Closes #7895

---------

Co-authored-by: Simon H <[email protected]>
Co-authored-by: Simon Holthausen <[email protected]>
  • Loading branch information
3 people authored Mar 13, 2023
1 parent 29ffc78 commit 75e2c6f
Show file tree
Hide file tree
Showing 38 changed files with 207 additions and 137 deletions.
5 changes: 5 additions & 0 deletions .changeset/odd-suits-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': minor
---

feat: add data-sveltekit-keepfocus and data-sveltekit-replacestate options to links (requires Svelte version 3.56 for type-checking with `svelte-check`)
4 changes: 2 additions & 2 deletions documentation/docs/20-core-concepts/30-form-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -496,8 +496,8 @@ Some forms don't need to `POST` data to the server — search inputs, for exampl
</form>
```

Submitting this form will navigate to `/search?q=...` and invoke your load function but will not invoke an action. As with `<a>` elements, you can set the [`data-sveltekit-reload`](link-options#data-sveltekit-reload) and [`data-sveltekit-noscroll`](link-options#data-sveltekit-noscroll) attributes on the `<form>` to control the router's behaviour.
Submitting this form will navigate to `/search?q=...` and invoke your load function but will not invoke an action. As with `<a>` elements, you can set the [`data-sveltekit-reload`](link-options#data-sveltekit-reload), [`data-sveltekit-replacestate`](link-options#data-sveltekit-replacestate), [`data-sveltekit-keepfocus`](link-options#data-sveltekit-keepfocus) and [`data-sveltekit-noscroll`](link-options#data-sveltekit-noscroll) attributes on the `<form>` to control the router's behaviour.

## Further reading

- [Tutorial: Forms](https://learn.svelte.dev/tutorial/the-form-element)
- [Tutorial: Forms](https://learn.svelte.dev/tutorial/the-form-element)
24 changes: 24 additions & 0 deletions documentation/docs/30-advanced/30-link-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,30 @@ Occasionally, we need to tell SvelteKit not to handle a link, but allow the brow

Links with a `rel="external"` attribute will receive the same treatment. In addition, they will be ignored during [prerendering](page-options#prerender).

## data-sveltekit-replacestate

Sometimes you don't want navigation to create a new entry in the browser's session history. Adding a `data-sveltekit-replacestate` attribute to a link...

```html
<a data-sveltekit-replacestate href="/path">Path</a>
```

...will replace the current `history` entry rather than creating a new one with `pushState` when the link is clicked.

## data-sveltekit-keepfocus

When creating a search input using a `<form>` which reflects its input value in the URL, you might not want it to lose focus after navigation. Adding a `data-sveltekit-keepfocus` attribute to it...

```html
<form data-sveltekit-keepfocus>
<input type="text" name="query">
</form>
```

...will cause the currently focused element to retain focus after navigation. Note that this only really makes sense for `<form>` elements. If you would add this to a link, the focused element would be the `<a>` tag, which is probably not what you want. You should also only use this on elements that still exist after navigation.

By default, focus will be reset to the body.

## data-sveltekit-noscroll

When navigating to internal links, SvelteKit mirrors the browser's default navigation behaviour: it will change the scroll position to 0,0 so that the user is at the very top left of the page (unless the link includes a `#hash`, in which case it will scroll to the element with a matching ID).
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"playwright": "^1.29.2",
"prettier": "^2.8.0",
"rollup": "^3.7.0",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"tiny-glob": "^0.2.9",
"typescript": "^4.9.4"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-static/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@sveltejs/kit": "workspace:^",
"@types/node": "^16.18.6",
"sirv": "^2.0.2",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"typescript": "^4.9.4",
"uvu": "^0.5.6",
"vite": "^4.1.1"
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-static/test/apps/prerendered/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
"devDependencies": {
"@sveltejs/kit": "workspace:^",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"vite": "^4.1.1"
},
"type": "module"
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-static/test/apps/spa/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@sveltejs/adapter-node": "workspace:^",
"@sveltejs/kit": "workspace:^",
"sirv-cli": "^2.0.2",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"vite": "^4.1.1"
},
"type": "module"
Expand Down
2 changes: 1 addition & 1 deletion packages/create-svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.8.1",
"sucrase": "^3.29.0",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"tiny-glob": "^0.2.9",
"uvu": "^0.5.6"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/create-svelte/templates/default/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@neoconfetti/svelte": "^1.0.0",
"@sveltejs/adapter-auto": "workspace:*",
"@sveltejs/kit": "workspace:*",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"typescript": "^4.9.4",
"vite": "^4.1.1"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@types/set-cookie-parser": "^2.4.2",
"marked": "^4.2.3",
"rollup": "^3.7.0",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-preprocess": "^5.0.0",
"typescript": "^4.9.4",
"uvu": "^0.5.6",
Expand Down
10 changes: 5 additions & 5 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -1569,11 +1569,11 @@ export function create_client(app, target) {
navigate({
url,
scroll: options.noscroll ? scroll_state() : null,
keepfocus: false,
keepfocus: options.keep_focus ?? false,
redirect_chain: [],
details: {
state: {},
replaceState: url.href === location.href
replaceState: options.replace_state ?? url.href === location.href
},
accepted: () => event.preventDefault(),
blocked: () => event.preventDefault(),
Expand Down Expand Up @@ -1604,7 +1604,7 @@ export function create_client(app, target) {

const event_form = /** @type {HTMLFormElement} */ (event.target);

const { noscroll, reload } = get_router_options(event_form);
const { keep_focus, noscroll, reload, replace_state } = get_router_options(event_form);
if (reload) return;

event.preventDefault();
Expand All @@ -1623,11 +1623,11 @@ export function create_client(app, target) {
navigate({
url,
scroll: noscroll ? scroll_state() : null,
keepfocus: false,
keepfocus: keep_focus ?? false,
redirect_chain: [],
details: {
state: {},
replaceState: false
replaceState: replace_state ?? url.href === location.href
},
nav_token: {},
accepted: () => {},
Expand Down
16 changes: 14 additions & 2 deletions packages/kit/src/runtime/client/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ const warned = new WeakSet();
const valid_link_options = /** @type {const} */ ({
'preload-code': ['', 'off', 'tap', 'hover', 'viewport', 'eager'],
'preload-data': ['', 'off', 'tap', 'hover'],
keepfocus: ['', 'off'],
noscroll: ['', 'off'],
reload: ['', 'off']
reload: ['', 'off'],
replacestate: ['', 'off']
});

/**
Expand Down Expand Up @@ -141,6 +143,9 @@ export function get_link_info(a, base) {
* @param {HTMLFormElement | HTMLAnchorElement | SVGAElement} element
*/
export function get_router_options(element) {
/** @type {ValidLinkOptions<'keepfocus'> | null} */
let keep_focus = null;

/** @type {ValidLinkOptions<'noscroll'> | null} */
let noscroll = null;

Expand All @@ -153,23 +158,30 @@ export function get_router_options(element) {
/** @type {ValidLinkOptions<'reload'> | null} */
let reload = null;

/** @type {ValidLinkOptions<'replacestate'> | null} */
let replace_state = null;

/** @type {Element} */
let el = element;

while (el && el !== document.documentElement) {
if (preload_code === null) preload_code = link_option(el, 'preload-code');
if (preload_data === null) preload_data = link_option(el, 'preload-data');
if (keep_focus === null) keep_focus = link_option(el, 'keepfocus');
if (noscroll === null) noscroll = link_option(el, 'noscroll');
if (reload === null) reload = link_option(el, 'reload');
if (replace_state === null) replace_state = link_option(el, 'replacestate');

el = /** @type {Element} */ (parent_element(el));
}

return {
preload_code: levels[preload_code ?? 'off'],
preload_data: levels[preload_data ?? 'off'],
keep_focus: keep_focus === 'off' ? false : keep_focus === '' ? true : null,
noscroll: noscroll === 'off' ? false : noscroll === '' ? true : null,
reload: reload === 'off' ? false : reload === '' ? true : null
reload: reload === 'off' ? false : reload === '' ? true : null,
replace_state: replace_state === 'off' ? false : replace_state === '' ? true : null
};
}

Expand Down
2 changes: 1 addition & 1 deletion packages/kit/test/apps/amp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"@sveltejs/kit": "workspace:^",
"cross-env": "^7.0.3",
"purify-css": "^1.2.5",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/test/apps/basics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@sveltejs/kit": "workspace:^",
"cross-env": "^7.0.3",
"rimraf": "^4.0.0",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<a id="one" href="/data-sveltekit/replacestate/target" data-sveltekit-replacestate>one</a>

<div data-sveltekit-replacestate>
<a id="two" href="/data-sveltekit/replacestate/target">two</a>
<a id="three" href="/data-sveltekit/replacestate/target" data-sveltekit-replacestate="off">
three
</a>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>target</h1>
20 changes: 20 additions & 0 deletions packages/kit/test/apps/basics/test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,26 @@ test.describe('data-sveltekit attributes', () => {
await clicknav('#three');
expect(await page.evaluate(() => window.scrollY)).toBe(0);
});

test('data-sveltekit-replacestate', async ({ page, clicknav }) => {
await page.goto('/');
await page.goto('/data-sveltekit/replacestate');
await clicknav('#one');
await page.goBack();
await expect(page).not.toHaveURL(/replacestate/);

await page.goto('/');
await page.goto('/data-sveltekit/replacestate');
await clicknav('#two');
await page.goBack();
await expect(page).not.toHaveURL(/replacestate/);

await page.goto('/');
await page.goto('/data-sveltekit/replacestate');
await clicknav('#three');
await page.goBack();
await expect(page).toHaveURL(/replacestate$/);
});
});

test.describe('Content negotiation', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/test/apps/dev-only/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"devDependencies": {
"@sveltejs/kit": "workspace:^",
"cross-env": "^7.0.3",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/test/apps/options-2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@sveltejs/adapter-node": "workspace:^",
"@sveltejs/kit": "workspace:^",
"cross-env": "^7.0.3",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/test/apps/options/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"devDependencies": {
"@sveltejs/kit": "workspace:^",
"cross-env": "^7.0.3",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/test/apps/writes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@sveltejs/kit": "workspace:^",
"cross-env": "^7.0.3",
"rimraf": "^4.0.0",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"devDependencies": {
"@sveltejs/adapter-auto": "workspace:^",
"@sveltejs/kit": "workspace:^",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"devDependencies": {
"@sveltejs/adapter-auto": "workspace:^",
"@sveltejs/kit": "workspace:^",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"devDependencies": {
"@sveltejs/kit": "workspace:^",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"devDependencies": {
"@sveltejs/kit": "workspace:^",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"devDependencies": {
"@sveltejs/kit": "workspace:^",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"devDependencies": {
"@sveltejs/kit": "workspace:^",
"cross-env": "^7.0.3",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"devDependencies": {
"@sveltejs/kit": "workspace:^",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"devDependencies": {
"@sveltejs/kit": "workspace:^",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"devDependencies": {
"@sveltejs/kit": "workspace:^",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"devDependencies": {
"@sveltejs/kit": "workspace:^",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
"devDependencies": {
"@sveltejs/kit": "workspace:^",
"svelte": "^3.55.1",
"svelte": "^3.56.0",
"svelte-check": "^3.0.2",
"typescript": "^4.9.4",
"vite": "^4.1.1"
Expand Down
Loading

0 comments on commit 75e2c6f

Please sign in to comment.