单击标签不聚焦自定义元素(Web 组件)

Dog*_*oku 3 html javascript web-component custom-element

I\xe2\x80\x99m<select>使用本机 DOM(无 Polymer)编写自定义元素。

\n

I\xe2\x80\x99m 尝试将我的元素与<label>元素一起使用,并在单击时正确触发我的元素的单击事件<label>,即:

\n
<label>\n  My Select:\n  <my-select placeholder="Please select one">...</my-select>\n</label>\n
Run Code Online (Sandbox Code Playgroud)\n

或者

\n
<label for=\'mySelect1\'>My Select:</label>\n<my-select id=\'mySelect1\' placeholder="Please select one">...</my-select>\n
Run Code Online (Sandbox Code Playgroud)\n

tabindex但是,即使我添加 a使其可聚焦,这种行为似乎也无法开箱即用。

\n

这里\xe2\x80\x99是代码的精简版本和带有一些基本调试的JSFiddle :

\n
var MySelectOptionProto = Object.create(HTMLElement.prototype);\ndocument.registerElement(\'my-select-option\', {\n  prototype: MySelectOptionProto\n});\nvar MySelectProto = Object.create(HTMLElement.prototype);\n\nMySelectProto.createdCallback = function() {\n  if (!this.getAttribute(\'tabindex\')) {\n    this.setAttribute(\'tabindex\', 0);\n  }\n  \n  this.placeholder = document.createElement(\'span\');\n  this.placeholder.className = \'my-select-placeholder\';\n  this.appendChild(this.placeholder);\n\n  var selected = this.querySelector(\'my-select-option[selected]\');\n  \n  this.placeholder.textContent = selected\n    ? selected.textContent\n    : (this.getAttribute(\'placeholder\') || \'\');\n};\ndocument.registerElement(\'my-select\', {\n  prototype: MySelectProto\n});\n
Run Code Online (Sandbox Code Playgroud)\n

Seb*_*mon 5

\n

但标准尚未完成,也许将来您\xe2\x80\x99也能够将内置语义与自主自定义元素一起使用。

\n

\xe2\x80\x94 Supersharp,2016 年 7 月 15 日 16:05

\n
\n

我们现在生活在未来。\n标准已经发生了显着变化,我们现在拥有更强大的自定义元素。

\n
\n

太尔;博士:

\n

formAssociated当与您的自定义元素关联的构造函数具有值为 的属性时,标签才起作用true

\n
\n

当查看文档时,乍一看,您可能会得出结论,不允许将自定义元素作为<label>目标。\n文档说:

\n
\n

可以与元素关联的元素<label>包括<button><input>( 除外type="hidden"<meter><output><progress><select><textarea>

\n
\n

但未提及自定义元素。\n无论如何,您试试运气并尝试在HTML 验证器中输入此 HTML 片段:

\n
<label>Label: <my-custom-element></my-custom-element></label>\n
Run Code Online (Sandbox Code Playgroud)\n

或者:

\n
<label for="my-id">Label: </label><my-custom-element id="my-id"></my-custom-element>\n
Run Code Online (Sandbox Code Playgroud)\n

这两个都是有效的 HTML 片段!

\n

然而,尝试这个 HTML 片段:

\n
<label for="my-id">Label: </label><span id="my-id"></span>\n
Run Code Online (Sandbox Code Playgroud)\n

显示此错误消息:

\n
\n

for错误:该元素的属性值label必须是非隐藏表单控件的ID。

\n
\n

但为什么随机<my-custom-element>被认为是\xe2\x80\x9c非隐藏表单控件\xe2\x80\x9d?\n在验证器\xe2\x80\x99s GitHub问题中,你会发现已经修复的问题#963

\n
\n

label for不允许\xe2\x80\x99t 允许自定义元素作为目标

\n

该规范允许与表单相关的自定义元素作为for目标
\n https://html.spec.whatwg.org/multipage/forms.html#attr-label-for

\n

目前这会触发一个错误:

\n
<label for="mat-select-0">Select:</label>\n<mat-select role="listbox" id="mat-select-0"></mat-select>\n
Run Code Online (Sandbox Code Playgroud)\n

很容易检查元素名称是否包含破折号。\n检查自定义元素定义是否包含static formAssociated返回的属性要困难得多true,因为这可能深埋在外部框架.js文件中:
\n https://html.spec.whatwg.org/多页/custom-elements.html#custom-elements-face-example

\n
\n

WHATWG 规范中确实有一个特殊的附录,它枚举了与文档相同 的 \xe2\x80\x9clabelable elements\xe2\x80\x9d 以及与表单相关的自定义元素。\n规范中有一个示例,正在研究这个问题今天在结果中产生了这篇问答文章以及博客文章,例如Arthur Evans 的\xe2\x80\x9c更强大的表单控件\xe2\x80\x9d存档链接),其中显示了解决方案是什么。

\n

启用点击定位自定义元素的解决方案<label>formAssociated

\n

要启用该操作,所需要做的<label>就是将formAssociated具有该值的属性添加true到自定义元素的构造函数中。

\n

然而,问题中的代码需要移植到当前的Web\xc2\xa0Components API。\n新formAssociated属性是新 ElementInternals API 的一部分(请参阅 参考资料attachInternals),它用于更丰富的表单元素控制和可访问性。

\n

现在,自定义元素是用classes和定义的customElements.define,并使用ShadowRootsstatic formAssociated = true; 。\n然后可以将该字段添加到您认为属于 \xe2\x80\x9cform 关联的自定义元素\xe2\x80\x9d 的类中。\n有此属性使您的自定义元素在单击;click时调度事件。<label>现在我们\xe2\x80\x99 已经取得进展了!

\n

为了将其变成click一个focus动作,在调用中我们还需要\xe2\x80\x99s一件事attachShadow:将属性delegatesFocus设置为true

\n

\r\n
\r\n
// Rough translation of your original code to modern code, minus the `tabIndex` experimentation.\n\nclass MySelectOptionProto extends HTMLElement{}\n\ncustomElements.define("my-select-option", MySelectOptionProto);\n\nclass MySelectProto extends HTMLElement{\n  #shadowRoot;\n  #internals = this.attachInternals();\n  static formAssociated = true;\n  constructor(){\n    super();\n    this.#shadowRoot = this.attachShadow({\n      mode: "open",\n      delegatesFocus: true\n    });\n    this.#shadowRoot.replaceChildren(Object.assign(document.createElement("span"), {\n      classList: "my-select-placeholder"\n    }));\n    this.#shadowRoot.firstElementChild.textContent = this.querySelector("my-select-option[selected]")?.textContent ?? (this.getAttribute("placeholder") || "");\n  }\n}\n\ncustomElements.define("my-select", MySelectProto);
Run Code Online (Sandbox Code Playgroud)\r\n
<label>My label:\n  <my-select placeholder="Hello">\n    <my-select-option>Goodbye</my-select-option>\n    <my-select-option>world</my-select-option>\n  </my-select>\n</label>\n\n<label for="my-id">My label:</label>\n<my-select id="my-id">\n  <my-select-option>Never gonna give you</my-select-option>\n  <my-select-option selected>up</my-select-option>\n</my-select>\n<label for="my-id">My other label</label>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

delegatesFocus当单击自定义元素的某些不可聚焦部分时,将聚焦影子根的第一个可聚焦子级。

\n

如果需要更多控制,可以删除该属性,并向自定义元素实例添加事件侦听器。\n为了确保单击来自 ,<label>您可以检查事件\xe2\x80\x99s 是否target是您的自定义元素,即this,并且屏幕上 x 和 y 位置的元素是针对<label>您的自定义元素。\n幸运的是,通过将x和传递y给可以非常可靠地完成此操作document.elementFromPoint。\n然后,只需调用focus所需的元素即可工作。

\n

ElementInternals API 通过属性提供的另一件事是针对自定义元素的标签列表labels

\n
class MySelectProto extends HTMLElement{\n  // \xe2\x80\xa6\n  constructor(){\n    // \xe2\x80\xa6\n\n    this.addEventListener("click", ({ target, x, y }) => {\n      const relatedTarget = document.elementFromPoint(x, y);\n      \n      if(target === this && new Set(this.#internals.labels).has(relatedTarget)){\n        console.log("Label", relatedTarget, "has been clicked and", this, "can now become focused.");\n        // this.focus(); // Or, more likely, some target within `this.#shadowRoot`.\n      }\n    });\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n