Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
arackaf opened this issue Dec 1, 2022 · 13 comments · Fixed by #9019
Closed

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

arackaf opened this issue Dec 1, 2022 · 13 comments · Fixed by #9019
Labels
forms Stuff relating to forms and form actions
Milestone

Comments

@arackaf
Copy link

arackaf commented Dec 1, 2022

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

@Rich-Harris
Copy link
Member

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

@arackaf
Copy link
Author

arackaf commented Dec 1, 2022

@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.

@Rich-Harris
Copy link
Member

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
Copy link
Member

(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.)

@arackaf
Copy link
Author

arackaf commented Dec 1, 2022

@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
Copy link
Author

arackaf commented Dec 1, 2022

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

@dummdidumm dummdidumm added this to the post-1.0 milestone Dec 12, 2022
@geoffrich
Copy link
Member

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?

@dummdidumm
Copy link
Member

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")

@arackaf
Copy link
Author

arackaf commented Jan 10, 2023

@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.

@geoffrich
Copy link
Member

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).

@dummdidumm dummdidumm added the forms Stuff relating to forms and form actions label Jan 13, 2023
@petem-methods
Copy link

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.

@z-x
Copy link

z-x commented Feb 26, 2023

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?

dummdidumm added a commit that referenced this issue Mar 13, 2023
…#9019)

Closes #9014
Closes #7895

---------

Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
@arackaf
Copy link
Author

arackaf commented Mar 13, 2023

@dummdidumm thank you!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
forms Stuff relating to forms and form actions
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants