ShadowDOM の slot に手動でアサインできるようになった
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ slot の assign メソッドで slot の割り当てを JavaScript の処理で行える
◆ 手動割当をするには attachShadow のオプションで slotAssignment を manual に指定する必要あり
◆ assign 対象は Shadow DOM をアタッチした要素の子要素のみ
◆ assign しても slot 要素の子要素が消えないバグあり
◆ 手動割当をするには attachShadow のオプションで slotAssignment を manual に指定する必要あり
◆ assign 対象は Shadow DOM をアタッチした要素の子要素のみ
◆ assign しても slot 要素の子要素が消えないバグあり
Chrome86 で ShadowDOM の slot 機能が強化されたようです
https://github.com/WICG/webcomponents/blob/gh-pages/proposals/Imperative-Shadow-DOM-Distribution-API.md
これだと elem-1 の内側は空っぽです
text は表示されません
Shadow DOM 内に slot 要素を配置すると その子要素として Shadow DOM をアタッチした要素の子要素が表示されます
これだと foo クラスの div の内側に text が表示されます
foo の style に 「color: red」 をつけると text が赤色で表示されます
複数の slot がある場合は slot の name 属性と子要素の slot 属性を元にどの slot に表示するか決まります
これだと header の中の slot に text2 が表示されます
section の中の slot には text が表示されます
footer の slot はないので text3 は表示されません
slot や name 属性を省略した場合は空文字と一緒で 空文字どうしでマッチします
なので slot 未指定の子要素は name 未指定の slot 要素に表示されます
name 属性なしの slot はマッチするものがなかった場合に表示する場所ではないです
slot 属性は Shadow DOM をアタッチする要素の子要素である必要があります
div などでグループ化して 孫要素などになると効果がありません
こうしても text2 だけが main の slot には表示されず text2 を含む section ごと name を指定しない slot に表示されます
のように slot 要素のメソッドで どの要素を割り当てるか指定できます
複数箇所に割り当てることはできないので すでに割り当て済みの要素を別の slot に割り当てるとそっちに移動します
DOM の append メソッドみたいな感じですね
また この機能を使う場合は attachShadow メソッドのオプションが必要です
slotAssignment に manual を指定します
このモードでは自動割当は行われないので 手動で割り当てない限りは slot には何も表示されません
手動割当でも 割り当てられるのは子要素のみです
孫要素などを assign 対象に含めるとエラーになります
引数は配列など @@iterator を実装してる必要があります
append などの感覚で可変長引数で渡すとこれが出ます
配列で渡しましょう
assign に渡したものに Node 以外のものがあると出るエラーです
querySelector などで取得した要素が見つからず null になってたりすると出ます
childNodes に含まれない Node を assign に渡すと出るエラーです
孫要素や新規作成した要素を slot に割り当てることはできません
この例の DIV のように 要素の種類は教えてくれます
slot 要素自体の子要素は slot で表示するものがない場合に表示されます
こういう感じで使えます
このメッセージは 後から子要素が追加されて それが slot に表示されると表示されなくなります
しかし 手動割当を行った場合は slot に要素が割り当てられても消えずに残っていました
一旦別の slot に割り当ててから再度割り当てると今度は消えているので回避策は一応あります
プレビュー表示できます
elem-1 は基本的な slot 1 つに配置
elem-2 は name, slot 属性を使って配置
elem-3 は slotAssignment: "manual" を指定して手動配置
になってます

もうひとつ 手動割当で assign 先を選択できるものです
child ごとにチェックボックスにチェックを入れて slotA, slotB のどっちに割り当てるかを選びます
現状では バグのせいでどちらかに最初に assign したときには No Item が残ってますが 別の slot に割り当てて一旦 slot を空にするとそれ以降はちゃんと動きます
https://github.com/WICG/webcomponents/blob/gh-pages/proposals/Imperative-Shadow-DOM-Distribution-API.md
これまでの slot 機能
Shadow DOM をアタッチした要素の子要素は画面には表示されません<elem-1>
<div>text</div>
</elem-1>
<!-- shadow dom of elem-1 -->
<div>
</div>
これだと elem-1 の内側は空っぽです
text は表示されません
Shadow DOM 内に slot 要素を配置すると その子要素として Shadow DOM をアタッチした要素の子要素が表示されます
<!-- shadow dom of elem-1 -->
<div class="foo">
<slot></slot>
</div>
これだと foo クラスの div の内側に text が表示されます
foo の style に 「color: red」 をつけると text が赤色で表示されます
複数の slot がある場合は slot の name 属性と子要素の slot 属性を元にどの slot に表示するか決まります
<elem-1>
<div>text</div>
<div slot="header">text2</div>
<div slot="footer">text3</div>
</elem-1>
<!-- shadow dom of elem-1 -->
<header>
<slot name="header"></slot>
</header>
<section>
<slot></slot>
</section>
これだと header の中の slot に text2 が表示されます
section の中の slot には text が表示されます
footer の slot はないので text3 は表示されません
slot や name 属性を省略した場合は空文字と一緒で 空文字どうしでマッチします
なので slot 未指定の子要素は name 未指定の slot 要素に表示されます
name 属性なしの slot はマッチするものがなかった場合に表示する場所ではないです
slot 属性は Shadow DOM をアタッチする要素の子要素である必要があります
div などでグループ化して 孫要素などになると効果がありません
<elem-1>
<section>
<div>text</div>
<div slot="main">text2</div>
</section>
</elem-1>
こうしても text2 だけが main の slot には表示されず text2 を含む section ごと name を指定しない slot に表示されます
新機能
この slot の割り当て機能をプログラムで制御できるようになりましたslot_elem.assign([elem1, elem2])
のように slot 要素のメソッドで どの要素を割り当てるか指定できます
複数箇所に割り当てることはできないので すでに割り当て済みの要素を別の slot に割り当てるとそっちに移動します
DOM の append メソッドみたいな感じですね
また この機能を使う場合は attachShadow メソッドのオプションが必要です
elem.attachShadow({ mode: "open", slotAssignment: "manual" })
slotAssignment に manual を指定します
このモードでは自動割当は行われないので 手動で割り当てない限りは slot には何も表示されません
手動割当でも 割り当てられるのは子要素のみです
孫要素などを assign 対象に含めるとエラーになります
assign でのよくあるエラー
Uncaught TypeError: Failed to execute 'assign' on 'HTMLSlotElement': The object must have a callable @@iterator property.
引数は配列など @@iterator を実装してる必要があります
append などの感覚で可変長引数で渡すとこれが出ます
配列で渡しましょう
Uncaught TypeError: Failed to execute 'assign' on 'HTMLSlotElement': Failed to convert value to 'Node'.
assign に渡したものに Node 以外のものがあると出るエラーです
querySelector などで取得した要素が見つからず null になってたりすると出ます
Uncaught DOMException: Failed to execute 'assign' on 'HTMLSlotElement': Node: 'DIV' is invalid for manual slot assignment.
childNodes に含まれない Node を assign に渡すと出るエラーです
孫要素や新規作成した要素を slot に割り当てることはできません
この例の DIV のように 要素の種類は教えてくれます
バグ
実装されたばかりだからか slot の子要素の扱いにバグがありましたslot 要素自体の子要素は slot で表示するものがない場合に表示されます
<slot name="contents">このスロットにマッチする要素がありませんでした</slot>
こういう感じで使えます
このメッセージは 後から子要素が追加されて それが slot に表示されると表示されなくなります
しかし 手動割当を行った場合は slot に要素が割り当てられても消えずに残っていました
一旦別の slot に割り当ててから再度割り当てると今度は消えているので回避策は一応あります
コード
slot のこれまでの機能と新機能の実際に動かせるコードですプレビュー表示できます
elem-1 は基本的な slot 1 つに配置
elem-2 は name, slot 属性を使って配置
elem-3 は slotAssignment: "manual" を指定して手動配置
になってます
<!DOCTYPE html>
<script type="module">
customElements.define("elem-1", class extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: "open" }).innerHTML = `
<div style="border: 1px solid red; padding: 10px;">
<slot></slot>
</div>
`
}
})
customElements.define("elem-2", class extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: "open" }).innerHTML = `
<div style="border: 1px solid red; padding: 10px;">
<div style="border: 1px solid blue; padding: 10px;">
<slot name="x"></slot>
</div>
<slot></slot>
<div style="border: 1px solid green; padding: 10px;">
<slot name="y"></slot>
</div>
</div>
`
}
})
customElements.define("elem-3", class extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: "open", slotAssignment: "manual" }).innerHTML = `
<div style="border: 1px solid red; padding: 10px;">
<div style="border: 1px solid blue; padding: 10px;">
<slot id="a"></slot>
</div>
<div style="border: 1px solid green; padding: 10px;">
<slot id="b"></slot>
</div>
</div>
`
}
connectedCallback() {
const a = this.shadowRoot.getElementById("a")
a.assign([this.children[0]])
const b = this.shadowRoot.getElementById("b")
b.assign([this.children[2]])
}
})
</script>
<elem-1>
<div>div1</div>
<div>div2</div>
<div>div3</div>
</elem-1>
<elem-2>
<div slot="x">div1</div>
<div>div2</div>
<div slot="y">div3</div>
</elem-2>
<elem-3>
<div>div1</div>
<div>div2</div>
<div>div3</div>
</elem-3>

もうひとつ 手動割当で assign 先を選択できるものです
child ごとにチェックボックスにチェックを入れて slotA, slotB のどっちに割り当てるかを選びます
現状では バグのせいでどちらかに最初に assign したときには No Item が残ってますが 別の slot に割り当てて一旦 slot を空にするとそれ以降はちゃんと動きます
<!DOCTYPE html>
<script type="module">
customElements.define("elem-1", class extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: "open", slotAssignment: "manual" }).innerHTML = `
<style>
.block {
margin: 10px;
padding: 10px;
border: 1px solid silver;
}
h1 {
margin: 10px 0;
font-size: 1.5em;
}
label {
margin: 0 5px;
}
</style>
<div class="block">
<h1>SlotA</h1>
<slot id="a">No Item</slot>
</div>
<div class="block">
<h1>SlotB</h1>
<slot id="b">No Item</slot>
</div>
<div>
<div>
<label>child1 <input id="c1" type="checkbox"></label>
<label>child2 <input id="c2" type="checkbox"></label>
<label>child3 <input id="c3" type="checkbox"></label>
</div>
<div>
<label>slotA <input type="radio" name="slot" value="a" checked></label>
<label>slotB <input type="radio" name="slot" value="b"></label>
</div>
<button id="assign">assign</button>
</div>
`
this.shadowRoot.getElementById("assign").onclick = () => {
const children = []
if (this.shadowRoot.getElementById("c1").checked) children.push(this.children[0])
if (this.shadowRoot.getElementById("c2").checked) children.push(this.children[1])
if (this.shadowRoot.getElementById("c3").checked) children.push(this.children[2])
const slot = this.shadowRoot.querySelector("[name=slot]:checked").value
this.shadowRoot.getElementById(slot).assign(children)
}
}
})
</script>
<elem-1>
<div>div1</div>
<div>div2</div>
<div>div3</div>
</elem-1>