kit icon indicating copy to clipboard operation
kit copied to clipboard

Create a restore-focus option for progressively-enhanced GET forms

Open arackaf opened this issue 3 years ago • 10 comments

Describe the problem

There's an amazing, brand new feature whereby GET forms can now be progressively enhanced, which means we can use them to navigate to new search results, with less code, that will even work without JavaScript. This is a huge win. Unfortunately there's still one shortcoming: there's no way to have focus restored after the navigation.

In other words, type into the textbox, hit enter, have the new results show up, but also have focus restore into the same textbox

Describe the proposed solution

I guess there's be a data-sveltekit-restore-focus attribute?

Alternatives considered

n/a

Importance

would make my life easier - I think this feature (progressively enhanced GET forms) is severely hampered without this.

Additional Information

No response

arackaf avatar Dec 01 '22 00:12 arackaf

Use autofocus:

<form>
  <input autofocus name="q" />
  <button>click</button>
</form>

If you want the input to only focus after a submission, you can use afterNavigate:

<script>
  import { afterNavigate } from '$app/navigation';

  /** @type {HTMLInputElement} */
  let input;

  afterNavigate(({ type }) => {
    if (type === 'form') {
      input.focus();
    }
  });
</script>

<form>
  <input bind:this={input} name="q" />
  <button>click</button>
</form>

A data-sveltekit-keepfocus attribute would keep focus on the current element, which is potentially useful in some cases but doesn't work if you interact with the form via a <button>, say. The approaches above are therefore probably better, I think

Rich-Harris avatar Dec 01 '22 00:12 Rich-Harris

@Rich-Harris thanks for replying! I know you can sort of hack this with an afterNavigate event. And I know restore-focus wouldn’t restore focus to the textbox if you clicked a submit button.

But it does seem lacking that you can call goto from a keyDown handler, with restoreFocus, and keep your input focused, but if you replace that with a form, it’s no longer possible.

Also, the afterNavigate handler that checks for type === “form” would be a bit fragile. You’d only want it to restore focus if you submitted via pressing enter, but NOT if you clicked a submit button.

autofocus is a decent middleground, but that causes the input to always focus after the page loads, regardless of what was focused, or what caused the navigation.

arackaf avatar Dec 01 '22 00:12 arackaf

The purpose of the progressive enhancement is to emulate the browser native behaviour but without reloading the page. If you want to only focus the input after an enter but not a button click, you could do this:

<script>
  import { beforeNavigate, afterNavigate } from '$app/navigation';

  /** @type {HTMLElement} */
  let focused;

  beforeNavigate(({ type }) => {
    focused = document.activeElement;
  });

  afterNavigate(({ type }) => {
    if (type === 'form') {
      focused?.focus();
    }
  });
</script>

<form>
  <input name="q" />
  <button>click</button>
</form>

There's also nothing preventing you from providing a custom on:submit function, if the emulated browser native behaviour isn't what you're looking for.

Rich-Harris avatar Dec 01 '22 01:12 Rich-Harris

(To be clear: not ruling out a declarative option. Just exploring whether or not it's truly necessary, and providing a workaround until we reach a decision on that.)

Rich-Harris avatar Dec 01 '22 01:12 Rich-Harris

@Rich-Harris thanks! - yeah, the primitives are there. I'm sure some subset of the approaches above could be used to make a nice helper to streamline this.

This certainly isn't any kind of blocking need, but I am curious if you think this would be a useful addition to SvelteKit at some point. goto already has this option; shouldn't it also be available for forms? Most of the other goto / link options work with forms, so I'd be surprised if this was by design the lone exception.

arackaf avatar Dec 01 '22 01:12 arackaf

Our replies criss-crosed - you answered my point - thank you!

arackaf avatar Dec 01 '22 01:12 arackaf

Coming back to this, I've been making a lot of progressive-enhanced-form demos with <form method="GET"> and often find myself needing to replicate SK's form handling so I can use keepFocus and replaceState from goto. I wish I didn't have to do that, and would prefer a declarative solution if possible.

For example, I find myself doing this pattern for a debounced search input. Because I'm submitting the form as they're typing, I need to keep focus and want to replace state to avoid pushing a bunch of partial searches into the history.

<script lang="ts">
	import { goto } from '$app/navigation';
	let form: HTMLFormElement;

	const debouncedSubmit = debounce(() => {
		// not supported in all browsers
		if (typeof HTMLFormElement.prototype.requestSubmit == 'function') {
			form.requestSubmit();
		}
	}, 300);

	export function submitReplaceState(e: SubmitEvent) {
		e.preventDefault();
		const form = e.target as HTMLFormElement;
		const url = new URL(form.action);
		// @ts-expect-error
		const params = new URLSearchParams(new FormData(form));
		url.search = params.toString();
		goto(url, { replaceState: true, keepFocus: true, noScroll: true });
	}
</script>
<form bind:this={form} on:submit|preventDefault={submitReplaceState}>
	<label for="q">Query</label>
	<input
		id="q"
		type="text"
		name="q"
		on:input={debouncedSubmit}
	/>
</form>

I think it would be cool to be able to teach something like this without having to introduce a custom submit handler. Otherwise SvelteKit's automatic <form method="GET"> handling is a bit lacking with all the scenarios in which you need to eject.

I'm sure this has been considered, but what about introducing new data-sveltekit link attributes for both keepFocus and replaceState? They could be misused on regular ol' <a> tags, but I think it would be quite useful on forms. We already have one goto option available declaratively (noscroll), why not more?

geoffrich avatar Jan 10 '23 05:01 geoffrich

Things like this are why I voted against SvelteKit handling this as a <a> attribute and instead introducing a use:enhanceGet action, which I feel could have handled such use cases easier while better managing expectations ("ok I need to add this action and I can customize this, nice" vs "Oooh so cool I gotta do nothing to progressively enhance these ... but oh, the default behavior is useless for me")

dummdidumm avatar Jan 10 '23 08:01 dummdidumm

@dummdidumm more basically, is there some sort of structural problem why a keepFocus option can't / shouldn't be added to a form? That option already exists in the goto api, so if nothing else this seems like a question of consistent api.

arackaf avatar Jan 10 '23 13:01 arackaf

Things like this are why I voted against SvelteKit handling this as a attribute and instead introducing a use:enhanceGet action, which I feel could have handled such use cases easier while better managing expectations ("ok I need to add this action and I can customize this, nice" vs "Oooh so cool I gotta do nothing to progressively enhance these ... but oh, the default behavior is useless for me")

I don't think having a dedicated enhanceGet for this would have really changed the approach for solving this issue - we're talking about the difference between

<form data-sveltekit-replacestate data-sveltekit-keepfocus>

and

<form use:enhanceGet={{ replaceState: true, keepFocus: true }}>

either way, it's some sort of boolean flag.

And I think it makes sense to have parity between our declarative navigation API (<a> and <form> with data-sveltekit-* attributes) and our imperative navigation API (goto with options).

geoffrich avatar Jan 10 '23 15:01 geoffrich

I'd love a data-sveltekit-keepfocus option, I think. We had a path-based (ie, not querystring-based) app navigation structure, and we realised that it did not produce a good keyboard experience. As a workaround we did a:

on:click|preventDefault

and used gotoUrl (with keepfocus: true)

https://github.com/ONSdigital/dp-census-atlas/blob/3e29b03b8505a704ddcb2530d51228fd502b12e2/src/components/CategoryPage.svelte#L47

It would be nicer to be able to decorate the <a> with an attribute.

petem-methods avatar Jan 19 '23 12:01 petem-methods

This would be really nice to have, as mentioned - especially for those inline-saving fields. And I believe that passing {keepFocus: true} to the update() would be quite elegant. You obviously can go with a custom on:submit but since use:ehnace is doing 99% of the things you usually want, some configuration passed to update() wouldn't hurt, would it?

z-x avatar Feb 26 '23 14:02 z-x

@dummdidumm thank you!!!

arackaf avatar Mar 13 '23 14:03 arackaf