-
Notifications
You must be signed in to change notification settings - Fork 377
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
Form-associated custom elements: being a submit button #814
Comments
And it would throw if this's form-associated is not true? Do you have to setup your own activation behavior (by invoking |
For sure.
Good question. I had envisioned yes, but I don't have strong feelings. I can't immediately think of any good use cases that would be prevented if we automatically added such activation behavior. |
Never mind my comments above (just read the comments in issue #187 and now understand what you're mentioning here 👍). (excuse my ignorance) |
Are there any blockers to this sort of addition or places that the community could specifically support moving this forward? |
I really like the |
I think the main blocker is having someone willing to work out the details. I.e., a more concrete form of OP that can be turned into a PR against the HTML Standard. And then have someone write web-platform-tests to accompany that. |
So I've been playing with this in my project at work and this seems like it mimics what I'd like to see on this front: HTMLFormElement.prototype.requestSubmit = function() {
if (this.reportValidity() === false) {
return;
}
const submitEvent = new Event('submit', { cancelable: true });
this.dispatchEvent(submitEvent);
if (!submitEvent.defaultPrevented) {
this.submit();
}
} While this could live on class SomeCustomElement extends HTMLElement {
#internals = this.attachInternals();
/** Interesting custom element things */
connectedCallback() {
this.someTarget.addEventListener('keypress', event => {
if (this.#internals.form && event.code === 'Enter') {
this.#internals.form.requestSubmit();
}
}
}
} Since <form id="rootForm">
<x-personal-info-form></x-personal-info-form>
<x-address-form></x-address-form>
<x-ds-button-row></x-ds-button-row>
<form> Where any |
requestSubmit() already exists and is implemented: https://html.spec.whatwg.org/#dom-form-requestsubmit https://wpt.fyi/results/html/semantics/forms/the-form-element/form-requestsubmit.html?label=master&label=experimental&aligned&q=requestsubmit |
Welp, learn something new every day. Thanks for the link. |
@domenic Quick question: What if we, instead of adding a |
|
It is not automatic; indeed you need to add code such as that in the comment I was replying to: #814 (comment) |
When designating a CE as a button via internals it should behave as a native button out of the box. I think it's too easy to get wrong otherwise, especially when you consider more esoteric cases like using a Just wanted to voice a vote in favour of automatically getting that behaviour :) |
Be warned, it's not enough to just I'm currently working around that.
And
I can't use Customized HTML Elements because Very hacky solution: const duplicatedButton = this.buttonElement.cloneNode();
duplicatedButton.hidden = true;
form.append(duplicatedButton);
form.requestSubmit(duplicatedButton);
duplicatedButton.remove(); The |
Yeah creating a "proxy" button in the light dom is the approach I've been using. But it's definitely hacky. And still not perfect, since you get a seemingly incorrect element (of course it's technically correct, but for a dev using your custom button it seems wrong) for I personally leave the proxy button in the light DOM always, so that hitting enter in a text input will work. |
@clshortfuse |
@annevk Thanks. My comment before was more for posterity's sake for people searching for a workaround. I haven't fully delved what I'd like to see a proposal consist of. Right now I have my own code in my FACE /**
* @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#implicit-submission
* @param {Event} event
* @return {void}
*/
performImplicitSubmission(event) {
const form = this.form;
if (!form) return;
/** @type {HTMLInputElement} */
let defaultButton;
const submissionBlockers = new Set();
for (const element of /** @type {HTMLCollectionOf<HTMLInputElement>} */ (form.elements)) {
// Spec doesn't specify disabled, but browsers do skip them.
if (element.type === 'submit' && !element.disabled && !element.matches(':disabled')) {
defaultButton ??= element;
break;
}
if (IMPLICIT_SUBMISSION_BLOCKING_TYPES.has(element.type)) {
submissionBlockers.add(element);
}
}
if (defaultButton) {
defaultButton.click();
return;
}
if (submissionBlockers.size > 1) return;
this.form.submit();
} This is to handle if users press 'Enter' on a FACE element, like The question would really be, would a FACE listed in The other question is, why wouldn't it be enough for any FACE button that has
I'm of the opinion that
Edit: Misread the spec. "If submitter is not a submit button, then throw a TypeError." Still, would be satisfied by point 2. |
I don't understand what As for implicit submission, if the only FACE you have is a submit button, I think you'd want implicit submission from a non-FACE to do something with it. It's a good question what should happen inside a FACE with regards to implicit submission. I guess that cannot deviate from what happens today, though perhaps we should create an affordance for that as well? |
@annevk Forgive my ignorance. I see now what you mean about invoking JS to get a state/property. I was thinking about how the disabled state works. In my use case I bind The reality is
Along the same vein a
The steps for implicit submission should cover it, IMO. Once the browser finds the submit button, FACE or native, it should perform a
At first I was worried about authors who have built combo-boxes that expect Example: <x-checkbox>Vendor-1 Checkbox</x-checkbox>
<y-submit-button>Vendor-2 Submit button</y-submit-button> If FACE Side note: I did now notice that we don't have the ability to block implicit submission from FACE elements. Now that I understand
That means we also need Summed up:
|
Agreed on continuing to use the internal equivalent of I'm not convinced we need to support the case of a form without a submit button that essentially has a single control. I suspect that behavior is there largely for Implicit submission can be implemented as follows, though this is not very ergonomic: const submitButton = control.form.querySelector(":default");
if (submitButton) {
submitButton.click();
} Offering a shortcut for that might be reasonable, though it should probably go on |
Unfortunately Or, we ask the browser to perform the steps. I thought of it being on |
Thanks. I like the idea of keeping the changes to the minimum set that meets the use case, if possible. Would just |
Seems like yes according to https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#implicit-submission
const { form } = this.elementInternals;
const { defaultSubmitButton } = form;
if (defaultSubmitButton) {
if (!defaultSubmitButton.matches(':disabled')) { // edit: use :disabled instead of [disabled]
defaultSubmitButton.click();
}
return;
}
if (getSubmissionBlockers(form).size > 1) return; // extraneous
form.requestSubmit(); That's how I'd implement it, given access to |
Point taken - it’s still complicated. So we do need a way to do the submission also. Ok. I’m supportive of the concept. |
I could be wrong but since the default submit button for a form is typically the first in source order wouldn’t it make more sense to have an API on either class CustomSubmitButton extends HTMLElement {
static formAssociated = true;
#internals = this.attachInternals();
formAssociatedCallback(form) {
this.#internals.registerSubmitButtonFor(form);
}
} |
@calebdwilliams I think the difference would lie with it being implicit registration ( If it's a An example: <form id=form>
<input> <!-- Receives ENTER press -->
<button type=submit>1</button>
<x-submit>2</x-submit>
<button type=submit>3</button>
<form>
<button type=submit for=form>4</button>
<x-submit for=form>5</x-submit> Button 1 should take preference. If 1 is not available (disabled or removed) then 2. If not 2, then 3 (and so on). We don't want 4 to jump before 3, which could be the case if we bypass the DOM order steps. Also, if a DOM is removed/moved, you have to unregister as well. And you need the form with which you were registered to keep proper logic symmetry (if there's a register, you assume an unregister). If you want to keep it implicit and "auto-unregister", then the form itself has to double check that the FACE is still associated with itself, or perform the unregister steps automatically. I feel like that's extra steps for the browser implementers. (And I guess maybe even yourself as a polyfill writer 😆 .) There's also a lifecycle/state question. Is a FACE a submit button only if associated with a form? Can it just be a detached submit button? Can I register a button with a form manually? Can I use a detached form and register the submit before adding it to the DOM? Can a button be associated with one form, but the submit for another? If not, will it throw an error? But I could be wrong of course, but as a component author, I don't see a problem with scripting my FACE submit buttons to flag themselves with a boolean, and scripting my FACE input fields to call the implicit submit function. In my logic, I would set a boolean as As for part of HTMLFormElement or internal, it's a question of scope. Non-boundary crossing native elements (aka LightDOM) don't need to attach ever, AFAIK. They have the PS: Love your polyfill work and use them all the time :) |
I’m not crazy about the assumption that the entire element itself should trigger the implicit submit process by default. What if there are other, non-actionable elements in that node’s shadow root (or even a reset or back button). I’d like to amend my suggestion above to have the internals method return a function that could be called to trigger submission from the custom element though. Having a single flag and assuming that’s sufficient doesn’t always make sense in the use cases I’ve seen. |
@calebdwilliams I'm not sure what you're saying. The capabilities we're discussing would make the custom element submit button no different from |
There is also the possibility to use I don't quite understand what behavior |
It would give the elements the semantics of a lowercase "submit button". So that means:
And yeah, it should have activation behavior that matches that of other submit buttons. |
I think I'm not keen on having magical states which change activation behavior. I'd rather expose some sort of activation callback to custom elements. That is roughly how XBL did it. |
I do question if FACE with As of now, we expect authors to call <!-- had set .submitButton during construction -->
<x-button onclick="this._elementInternals.form?.requestSubmit(this)">FACE Button</x-button>
<x-input onkeydown="event.key === 'Enter' ? this._elementInternals.implicitSubmit() : null">FACE Input</x-input>
Customized built-in elements ( |
I guess that could work as well. The lines are a bit blurred here and there, also with respect to attributes that carry certain semantics. |
WCCG had their spring F2F in which this was discussed. You can read the full notes of the discussion (#978 (comment)), heading entitled "Enhancements to Form-Associated Custom Elements". |
I just want to chime in that this could be related to supporting the |
See discussion starting around #187 (comment).
If you want your FACE to be a submit button, which means:
:default
you currently cannot.
A strawperson proposal would be to add a
submitButton
boolean toElementInternals
. Then custom submit buttons would just dothis.#internals.submitButton = true
.The text was updated successfully, but these errors were encountered: