Dog*_*oku 3 html javascript web-component custom-element
I\xe2\x80\x99m<select>使用本机 DOM(无 Polymer)编写自定义元素。
I\xe2\x80\x99m 尝试将我的元素与<label>元素一起使用,并在单击时正确触发我的元素的单击事件<label>,即:
<label>\n My Select:\n <my-select placeholder="Please select one">...</my-select>\n</label>\nRun Code Online (Sandbox Code Playgroud)\n或者
\n<label for=\'mySelect1\'>My Select:</label>\n<my-select id=\'mySelect1\' placeholder="Please select one">...</my-select>\nRun Code Online (Sandbox Code Playgroud)\ntabindex但是,即使我添加 a使其可聚焦,这种行为似乎也无法开箱即用。
这里\xe2\x80\x99是代码的精简版本和带有一些基本调试的JSFiddle :
\nvar 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});\nRun Code Online (Sandbox Code Playgroud)\n
\n\n但标准尚未完成,也许将来您\xe2\x80\x99也能够将内置语义与自主自定义元素一起使用。
\n\xe2\x80\x94 Supersharp,2016 年 7 月 15 日 16:05
\n
我们现在生活在未来。\n标准已经发生了显着变化,我们现在拥有更强大的自定义元素。
\nformAssociated当与您的自定义元素关联的构造函数具有值为 的属性时,标签才起作用true。
当查看文档时,乍一看,您可能会得出结论,不允许将自定义元素作为<label>目标。\n文档说:
\n\n可以与元素关联的元素
\n<label>包括<button>、<input>( 除外type="hidden")<meter>、<output>、<progress>、<select>和<textarea>。
但未提及自定义元素。\n无论如何,您试试运气并尝试在HTML 验证器中输入此 HTML 片段:
\n<label>Label: <my-custom-element></my-custom-element></label>\nRun Code Online (Sandbox Code Playgroud)\n或者:
\n<label for="my-id">Label: </label><my-custom-element id="my-id"></my-custom-element>\nRun Code Online (Sandbox Code Playgroud)\n这两个都是有效的 HTML 片段!
\n然而,尝试这个 HTML 片段:
\n<label for="my-id">Label: </label><span id="my-id"></span>\nRun Code Online (Sandbox Code Playgroud)\n显示此错误消息:
\n\n\n\n
for错误:该元素的属性值label必须是非隐藏表单控件的ID。
但为什么随机<my-custom-element>被认为是\xe2\x80\x9c非隐藏表单控件\xe2\x80\x9d?\n在验证器\xe2\x80\x99s GitHub问题中,你会发现已经修复的问题#963:
\n\n\n
label for不允许\xe2\x80\x99t 允许自定义元素作为目标该规范允许与表单相关的自定义元素作为
\nfor目标
\n https://html.spec.whatwg.org/multipage/forms.html#attr-label-for目前这会触发一个错误:
\nRun Code Online (Sandbox Code Playgroud)\n<label for="mat-select-0">Select:</label>\n<mat-select role="listbox" id="mat-select-0"></mat-select>\n很容易检查元素名称是否包含破折号。\n检查自定义元素定义是否包含
\nstatic formAssociated返回的属性要困难得多true,因为这可能深埋在外部框架.js文件中:
\n https://html.spec.whatwg.org/多页/custom-elements.html#custom-elements-face-example
WHATWG 规范中确实有一个特殊的附录,它枚举了与文档相同 的 \xe2\x80\x9clabelable elements\xe2\x80\x9d 以及与表单相关的自定义元素。\n规范中有一个示例,正在研究这个问题今天在结果中产生了这篇问答文章以及博客文章,例如Arthur Evans 的\xe2\x80\x9c更强大的表单控件\xe2\x80\x9d(存档链接),其中显示了解决方案是什么。
\n<label>:formAssociated要启用该操作,所需要做的<label>就是将formAssociated具有该值的属性添加true到自定义元素的构造函数中。
然而,问题中的代码需要移植到当前的Web\xc2\xa0Components API。\n新formAssociated属性是新 ElementInternals API 的一部分(请参阅 参考资料attachInternals),它用于更丰富的表单元素控制和可访问性。
现在,自定义元素是用classes和定义的customElements.define,并使用ShadowRootsstatic formAssociated = true; 。\n然后可以将该字段添加到您认为属于 \xe2\x80\x9cform 关联的自定义元素\xe2\x80\x9d 的类中。\n有此属性使您的自定义元素在单击;click时调度事件。<label>现在我们\xe2\x80\x99 已经取得进展了!
为了将其变成click一个focus动作,在调用中我们还需要\xe2\x80\x99s一件事attachShadow:将属性delegatesFocus设置为true。
// 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\ndelegatesFocus当单击自定义元素的某些不可聚焦部分时,将聚焦影子根的第一个可聚焦子级。
如果需要更多控制,可以删除该属性,并向自定义元素实例添加事件侦听器。\n为了确保单击来自 ,<label>您可以检查事件\xe2\x80\x99s 是否target是您的自定义元素,即this,并且屏幕上 x 和 y 位置的元素是针对<label>您的自定义元素。\n幸运的是,通过将x和传递y给可以非常可靠地完成此操作document.elementFromPoint。\n然后,只需调用focus所需的元素即可工作。
ElementInternals API 通过属性提供的另一件事是针对自定义元素的标签列表labels。
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}\nRun Code Online (Sandbox Code Playgroud)\n