Closed jacobmischka closed 7 months ago
Hm. I think the thing we want to listen to here is input_element.form.addEventListener('reset', ...)
. It'd be nice to not have to generate this for every single input element, but I'm not sure when we'd be sure this would be safe to omit. There's nothing stopping anyone from somehow getting a reference to the form object and calling .reset()
on it, even if there is no button.
Isn't the event on form level instead of input level? So in theory the event listener is just one for each form.
Well yes, but you need to be able to access each child component and its reactive properties, so it's not as easy as just traversing the DOM beneath each form element, unfortunately.
The repetition here could be reduced a bit by creating a helper function that checks whether the input element in question has a .form
and calling .form.addEventListener('reset', ...)
if so, but it would still be additional code. I'm torn about whether we want to force the extra code for this fix on everyone, or add another flag to <svelte:options/>
that controls listening to reset events.
The opt in feature sounds nice. It's not always needed as most of forms out there don't have the reset button (or call to programmatically)
Has this feature been implemented already?
No
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
I don't think this should be closed, even though it's understandably not a priority.
Just got bit by this... an opt-in solution for svelte:options would be nice!
Also got bit by this. I'm having to explicitly set the bound variables to nothing to reset the form. I have a form that's conditionally shown, and I'm guessing that even though the form is reset before hiding, when it's re-shown the form values are populated from the bound variables...
FWIW Vue doesn't trigger the binding update with v-model
too (Example). I think it's fine that Svelte doesn't support this either, as it preserves the easier mental model of "on input events" only (anything external is out of reach).
I did made a REPL with a fixFormReset
action that can be used on an input
to emulate the input event on form reset. Another way is to invert this logic, to be able to use the action on a form
instead.
@bluwy Thank you a lot. You action solved my use case!
Side-note: I just wanted to leave this HTML spec that explains (proves?) that what you mentioned earlier about the form and input events is by design, emphasis mine:
4.10.22 Resetting a form
(...)
Each resettable element defines its own reset algorithm. Changes made to form controls as part of these algorithms do not count as changes caused by the user (and thus, e.g., do not cause input events to fire).
Another caveat about Svelte doing the .form.addEventListener('reset', ...)
for you is that the form can be changed arbitrarily using the input[form=#formId]
attribute, so Svelte would also have to check if the form changed.
For the .01 percent who needed to handle form resets AND dynamically changing forms here's a remix of @bluwy 's action from above:
// reset.js
/** @type {import('svelte/action').Action<HTMLInputElement>} */
export function reset(element/*, options */) {
let removeResetListener = () => {};
/**
* @param {Event} event
*/
function handleReset(event) {
// `element.value` is only updated on the next frame
requestAnimationFrame(() => {
if (event.defaultPrevented) return;
/**
* Edge case: if disabled we don't want input handlers to fire
* but we want the binding to know the value has been cleared out
* `options.ignoreDisabled = true` is probably a more intuitive
* default for binding
*/
// if (element.disabled) return;
element.dispatchEvent(new Event('input'));
});
}
/**
* Check the form on every update. The parent form can be changed using
* the `form` attribute or if form is a `<svelte:element>` parent
*/
/** @type {HTMLFormElement} */
let previousForm;
function update() {
const { form } = element;
// remove listener from previous form in case the form changed
if (!form) return removeResetListener();
if (form === previousForm) return;
// new form found, remove old listener
removeResetListener();
removeResetListener = listen(form, 'reset', handleReset);
previousForm = form;
}
update();
return {
update,
destroy() {
removeResetListener();
},
};
}
Chiming in with my use case:
I was writing a Markdown editor, very much like GitHub's comment box that I'm typing this in right now, where the user could preview their rendered Markdown. This component is very simple, with a textarea whose value
is bound to a variable that is fed into SvelteMarkdown
when the user clicks on the preview button. Of course I used <form use:enhance>
to get a progressively enhanced form that works without JS. The problem is that after I submit the form, the textarea gets cleared, but the bound value does not, meaning that the rendered Markdown is still visible in the preview tab.
I worked around this by setting a listener on the reset event for the textarea's parent form that reset the bound value like: textarea.form.onreset = () => (value = '')
. Wasn't the ugliest thing ever but...would have been nice to not have to do that.
I'm honestly not sure if this is something that's even possible to fix, but I just noticed that when using a
<button type="reset">
on a form value bindings aren't changed accordingly.REPL: https://svelte.dev/repl?version=3.1.0&gist=5d0ced9c95afbdac3491bfb69bc591aa