👁️

Web Component内でのfocusイベントの扱い

2021/08/21に公開

bubblingしないはずのfocusイベントの話。

こういうHTMLの構造を持ったWeb Componentがあるとします。

<x-input>
  #shadow-root
  <input>Button</input>
</x-input>

Shadow DOMの要素をfocusした時のactiveElement

Shadow DOM内のinputbuttonでもselectでも良いですが、ともかくこれはインタラクティブな要素なため、focusすることが可能です。

この時document.activeElementは何を指すかと言うと、x-inputになります。

<x-input>にtabindexは付いていないので、それ自体がインタラクティブではないはずですが、LightDOMから見たらアクティブなのはコンポーネントそのものっぽい。

誰が反応しているのか

面白いので他の方法でも試してみましょう。Custom ElementとShadow DOM内のボタン、両方にfocusイベントの時に反応するリスナーを仕込んでみます。

<x-input onfocus="console.log('x!');">
  #shadow-root
  <input onfocus="console.log('b!');" />
</x-input>

Light DOMの上ではfocusイベントはbubblingしません。よって、もしこれがLight DOM上での親子関係だった場合、通常はボタンにfocusした場合でも親要素は反応しません。

Web Componentでの結果は

x!
b!

両方反応しました。

次に、focusした時のターゲットはどの要素ということになっているのでしょうか。

<x-input onfocus="(e) => console.log(e.target);">
  #shadow-root
  <input onfocus="(e) => console.log(e.target);" />
</x-input>

結果は

Focus Event {...}
  target: x-input

Focus Event {...}
  target: x-input

targetまで一緒やん。

ちなみにcssだとどうでしょうか。
:focusに反応するcssを付与し、<x-input>の中のinput要素にフォーカスを合わせてみます。

:host(:focus) {
  color: #fff;
}

input:focus {
  background: #f00;
}

内部のinputのみ反応しています。不思議ですね。

挙動まとめ

  • Shadow DOM内のの要素をfocusした時、挙動としては要素とそれを提供するCustom Element両方が反応している
  • focusイベントのevent.targetはShadow DOM側でもCustom Element側でもCustom Elementを指す
  • CSSは内部のみ反応する

複数あるfocusable elementを外部から特定できるのか

上記のことを踏まえると、同じShadow DOM内に複数のfocusable elementがあったとしても、document.activeElementは常にCustom Elementを指します。これではどの要素にfocusが当たったか調べたい時に見分けが付きません。そんな需要ある?でも想定してみます。

import lit from 'lit';

...
  onFocus(e) {
    this.dispatchEvent(nwe CustomEvent('focus-shadow', { data: this.shadowRoot?.activeElement }));
  }
  
  render() {
    return html`
      <div>
        <input id="one" @focus=${this.onFocus} />
	<input id="two" @focus=${this.onFocus} />
	<input id="three" @focus=${this.onFocus} />
      </div>
    `
  }
...

こうすることで、外部にイベントが渡った時にevent.dataは直接Shadow DOM内のfocusされた要素を指しています。ちなみにfocusイベントをそのままdispatchするとx-inputから2回focusが発火することになるため、混線します。

Web Componentのfocusの扱いについては以上になります。実際のところ他のイベントでも似たような挙動ではあるのですが、focusは本来bubblingしないために混乱することがあるかもしれません。ご参考まで。

Discussion