fix: patch children of focused Form-Associated Custom Elements (FACEs)#4323
Conversation
|
Quoting the README
Are you sure that this approach works with all form associated custom elements? I’m wondering if, should we merge this, we’d get bug reports from users that did rely on this behavior. |
bd30c08 to
e9e0a84
Compare
Thanks for the response and the interest on this PR @SteffenDE. Fast answer: NO 😅. That's why I've added a specific set of tests for this issue, which I should have done from the beginning:
You should have access to all artifacts in case you want to review them. Now I can say I'm more confident on the solution 😄. |
Form-Associated Custom Elements store their value in ElementInternals (via setFormValue), not in DOM attributes or children. When a FACE has focus, morphdom should still descend into its light-DOM children to patch them normally, since skipping children is only necessary for native inputs whose value lives in the DOM. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Five scenarios covering Form-Associated Custom Element morphdom behavior: basic FACE with focus, FACE with input child, slotted input with focus, delegatesFocus, and non-FACE control case. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers the pattern used by components like ui-select: shadow DOM with delegatesFocus, a focusable input inside the shadow root, and slotted light DOM children that must be patched while the host has focus. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9aead4e to
61ac933
Compare



I hope this helps someone.
Problem
When a Form-Associated Custom Element (FACE) has focus,
onBeforeElUpdatedindom_patch.tsidentifies it as a focused form input viaDOM.isEditableInput()and returnsfalse. This tells morphdom to skip the element and all its children.For native inputs (
<input>,<select>,<textarea>) this is correct, their value lives in the DOM and we don't want morphdom to overwrite what the user is typing. But FACEs are fundamentally different: they store their value inElementInternalsviasetFormValue(), not in DOM attributes or children. Their light-DOM children are structural (e.g. slotted elements, in my case is a custom select web component with slotted elements) and must be patched normally.How to reproduce
static formAssociated = truethat has light-DOM children managed by LiveView (e.g. a slotted button with aphx-clickattribute whose payload changes on each render).phx-change).Root cause
Solution
A new helper
DOM.isFormAssociatedCustomElement()detects FACEs. When a focused FACE is encountered, the patch returns the element instead offalse, which tells morphdom to update attributes and descend intomorphChildrenas usual. Native inputs retain their existing behavior.Notes
delegatesFocus: trueon the shadow root does not help —document.activeElementis always the host element regardless of this setting.isFormAssociated(),isEditableInput()), and FACEs participate correctly in form serialization viaFormData. This fix completes that support by ensuring their children are also patched.