表单关联自定义元素(FACE)验证异常:无效控件无法聚焦求助
解决Form-Associated Custom Element表单验证时的「不可聚焦」报错问题
你遇到的「An invalid form control with name='foobar' is not focusable.」报错,核心原因是:浏览器在表单验证失败时,会自动尝试聚焦第一个无效控件,但Form-Associated Custom Element(FACE)默认不属于原生可聚焦元素,也没有内置聚焦逻辑,导致浏览器无法完成聚焦操作,抛出错误。
下面是具体的解决方案,结合代码示例说明:
关键修复步骤
1. 让自定义元素成为可聚焦元素
通过给自定义元素添加tabindex属性,使其具备可聚焦能力。推荐设为-1,这样元素不会被Tab键遍历到,但仍能通过代码触发聚焦。
2. 实现聚焦转发逻辑
当浏览器尝试聚焦自定义元素时,将焦点转发到内部第一个未通过验证的输入框,既满足浏览器的聚焦要求,又能让用户直观看到哪个输入框有问题。
3. 重写验证相关方法
FACE需要实现checkValidity()和reportValidity()方法,同步内部输入框的验证状态,让表单能正确识别自定义元素的有效性。
4. 同步表单值
通过attachInternals()提供的setFormValue()方法,将内部输入框的值组合后同步到表单,确保提交时能获取到自定义元素的数据。
完整修复代码示例
class DualInputField extends HTMLElement { // 声明为表单关联元素 static formAssociated = true; constructor() { super(); this.attachShadow({ mode: 'open' }); // 渲染内部两个输入框 this.shadowRoot.innerHTML = ` <div> <label>输入1(必填):<input type="text" id="input1" required></label> <label>输入2(必填):<input type="text" id="input2" required></label> </div> `; // 关联表单内部实例 this.internals = this.attachInternals(); // 设置tabindex让元素可聚焦 this.setAttribute('tabindex', '-1'); // 获取内部输入框引用 this.input1 = this.shadowRoot.getElementById('input1'); this.input2 = this.shadowRoot.getElementById('input2'); } // 重写focus方法,转发焦点到第一个无效输入框 focus() { const invalidInput = this.input1.validity.valid ? this.input2 : this.input1; invalidInput.focus(); } // 重写checkValidity,返回内部输入框的整体验证状态 checkValidity() { return this.input1.checkValidity() && this.input2.checkValidity(); } // 重写reportValidity,触发内部无效输入框的原生验证提示 reportValidity() { const isValid = this.checkValidity(); if (!isValid) { const invalidInput = this.input1.validity.valid ? this.input2 : this.input1; invalidInput.reportValidity(); } return isValid; } // 监听输入事件,同步表单值 connectedCallback() { this.input1.addEventListener('input', () => this.#syncFormValue()); this.input2.addEventListener('input', () => this.#syncFormValue()); this.#syncFormValue(); } // 私有方法:同步内部输入值到表单 #syncFormValue() { this.internals.setFormValue(`${this.input1.value}|${this.input2.value}`); } } // 注册自定义元素 customElements.define('dual-input-field', DualInputField);
表单使用示例
<form id="demoForm"> <dual-input-field name="foobar"></dual-input-field> <button type="submit">提交表单</button> </form> <script> document.getElementById('demoForm').addEventListener('submit', (e) => { e.preventDefault(); // 触发表单整体验证 if (e.target.checkValidity()) { const formData = new FormData(e.target); console.log('验证通过,表单数据:', formData.get('foobar')); } }); </script>
额外说明
- 如果需要让自定义元素可以通过Tab键访问,可将
tabindex设为0,同时记得处理blur等事件保持逻辑一致性。 - 若内部输入框有自定义验证规则,可在
checkValidity()中添加对应的校验逻辑,确保整体验证状态准确。
内容的提问来源于stack exchange,提问作者Mark Tyers
相关产品推荐
相关产品推荐

