feat: [#1419] ElementInternals and form-associated custom elements - #2371
Open
marciomazza wants to merge 4 commits into
Open
feat: [#1419] ElementInternals and form-associated custom elements#2371marciomazza wants to merge 4 commits into
ElementInternals and form-associated custom elements#2371marciomazza wants to merge 4 commits into
Conversation
7 tasks
Removes the repeated inline [formAssociated] check and double cast from FormData and HTMLFormElement.
…alue() Matches the spec signature setFormValue(value, state). happy-dom does not persist/restore state, so it is accepted and ignored.
Add HTMLFormControlElementUtility.getFormOwner() (identical to the copy in fix/form-attribute-precedence) so the "form" attribute wins over the ancestor <form>, and route ElementInternals.form and HTMLFormElement.getFormControlItems through it. getFormControlItems now filters by form owner and scans the whole root for form="id" controls, keeping form-associated custom element support on top.
marciomazza
force-pushed
the
feat/element-internals
branch
from
September 8, 2026 14:03
2950695 to
177ee03
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves #1419
Refs #957
Refs #2392
happy-dom has no
ElementInternalssupport: a form-associated custom element can't callattachInternals(), and even with a hand-rolled shim it never shows up inform.elementsornew FormData(form).Root cause:
HTMLElement.attachInternalsandElementInternalsdon't exist,CustomElementRegistry.define()never reads the class'sstatic formAssociated, andboth
HTMLFormElement[getFormControlItems]()(backingform.elements,form.reset(),checkValidity(), submit) andFormData's form constructor key off a fixed tag-namelist — so a custom element is invisible to all of them.
The fix:
define()recordsformAssociatedon the prototype; a newElementInternalsclass (modeled on
ValidityState) withsetFormValue,form,labels,willValidate,validity,validationMessage,setValidity/checkValidity/reportValidity;HTMLElement.prototype.attachInternals(); andgetFormControlItems()/FormDatanowalso collect elements flagged
formAssociated, deduped and honoringform="",disabled, andsetFormValue(null).Deliberately narrow for a first PR:
attachInternals()throws on a non-form-associatedcustom element — real browsers allow it, but the members that motivate that
(
shadowRoot, ARIA) are out of scope here (Refs #957). Also out:states/CustomStateSet(:state()), theformAssociatedCallback/formDisabledCallback/formResetCallback/formStateRestoreCallbacklifecycle hooks,<fieldset>-drivendisabling, and full constraint-validation semantics (
setValidity()doesn't require amessage or validate the
anchorargument).form.elementsmembership resolves ownershipthrough a shared
HTMLFormControlElementUtility.getFormOwner()(a presentformattribute beats an ancestor
<form>, per the WHATWG form-owner algorithm); the built-inget form()copies keep the old ancestor-first order and are fixed in #2392, which alsoextends
getFormOwner()to radio scoping andValidityState. This PR and #2392 add theidentical
HTMLFormControlElementUtilityfile and both touchgetFormControlItems—whichever merges first, the other rebases to drop the duplicate.
Tests
Added 20 tests:
ElementInternals(form/labels/willValidate/validity+setValidity/checkValidityflag round-trips),attachInternals()(throws when notform-associated, throws on second call, returns an instance, exposed as a window global),
FormData(custom element value collected / omitted whennull/ omitted whendisabled),
HTMLFormElement(custom element listed once among descendants, and viaform="id"). Fullnpm testpasses (7739 tests). Repro also cross-checked in Chrome.Before submitting the PR, please make sure you do the following:
Tests
form.elements(MY-CONTROL,INPUT) and innew FormData(form)(foo=hello);setFormValue(null)drops the entry. Confirmed browsers also allowattachInternals()on a non-form-associated element (this PR throws there on purpose — see description).npm testlocally to make sure that all tests pass before submitting the PR.Title
feat,fix,choreorBREAKING CHANGE. The issue is optional and can be omitted if the pull request does not relate to an issue.AI
I used Claude Code to write this, over multiple iterations. I guided and reviewed
it myself at every step.