我正在编写一个名为
fancy-input
的自定义输入元素,它可以通过 FormInternals
API 参与表单。特别是, ElementInternals.labels
属性为我提供了通过其 label
属性关联到 for
实例的 fancy-input
元素列表。
但是现在假设我还想编写一个名为 fancy-label
的自定义
label元素。它在其影子 DOM 中以奇特的样式、格式等渲染原生标签元素。如何让它知道
fancy-label
的实例是与 fancy-input
实例关联的“标签”。特别是,我希望我的 fancy-label
节点出现在通过在关联的 NodeList
元素上调用 ElementInternals.labels
返回的 fancy-input
中。
一种可能的方法是让
fancy-label
将原生 label
渲染到插槽中,将其保留在 light DOM 中,因此对表单和 fancy-input
元素可见。但是有没有办法在 label
的影子 DOM 中使用原生 fancy-label
呢?
当今与表单关联元素 (FACE) 相关的 API 不允许您创建像本机
label
一样功能的自定义标签。这个话题已经去年讨论过,可以通过委托标签或跨 DOM 引用来解决。但是,您今天仍然可以相当轻松地实现可用的自定义标签。可行性将取决于您的目标。
例如,代替这个要求:
“我希望我的
节点出现在通过在关联的 fancy-input 元素上调用fancy-label
返回的NodeList
中。”ElementInternals.labels
这么说吧:
“我希望我的
节点能够像本机fancy-label
一样使用鼠标、键盘和屏幕阅读器 (SR) 等辅助技术。”label
此要求可以通过
aria-labelledby
属性来实现。您可以让字段指向其标签,而不是让标签指向其字段。您需要为标签而不是字段分配唯一的 ID:
<fancy-label id="text-label">
Disabled pc-checkbox:
<input name="text" type="text" aria-labelledby="text-label">
</fancy-label>
<fancy-label id="text-label">Disabled pc-checkbox:</fancy-label>
<input name="text" type="text" aria-labelledby="text-label">
您可以通过
aria-labelledby
引用任何有内容的元素。 SR 将像读取本机内容一样读取其内容 label
。剩下的就是处理自定义标签上的点击,以便他们在字段上执行点击。你需要一点 JavaScript 来完成它:
class FancyLabel extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
this.shadowRoot.appendChild(document.createElement('slot'))
this.addEventListener('click', event => this.#handleClick(event))
}
#handleClick(event) {
const field = document.querySelector(`[aria-labelledby="${this.id}"]`)
// ignore the click if the field does not exist, or if it is disabled,
// or if the field is inside the label and this event bubbled from it
if (!field || field.disabled || event.target === field) return
field.focus()
field.click()
}
}
customElements.define('fancy-label', FancyLabel)