ElementtInternals

Web Components


目次
  1. ElementInternals とは
  2. できること
    1. バリデーション
    2. state
    3. ARIA
  3. 宣言的・シリアル化可能な方法

ElementInternals とは #

ElementInternalsとは、カスタム要素が標準のHTML要素と同じようにフォームを構成する要素として機能できるようにするためのものです。

formAssociatedというstaticプロパティをtrueで設定することでそれを囲う<form>と関連づけることができ、フォーム送信時にフォーム検証ができるようになります。他にもARIA属性をつけられるようになったり、カスタムstateを設定してstateに応じたスタイリングができるようになったり、後日紹介するElementInternals.typeが検討されていたりします。

https://webkit.org/blog/13711/elementinternals-and-form-associated-custom-elements/ https://web.dev/articles/more-capable-form-controls?hl=ja#defining_a_form-associated_custom_element

これらの機能をもう少し詳しく説明していきます。

できること #

バリデーション #

ElementInternalsに生えているメソッドであるsetFormValue()で値を設定したり、setValidity()checkValidity()及びreportValidity()でバリデーションを行うことができます。

onUpdateValue() {
  if (!this.matches(':disabled') && this.hasAttribute('required') &&
      this.value_ < 0) {
    this.internals_.setValidity({customError: true}, 'Value cannot be negative.');
  } else {
    this.internals_.setValidity({});
  }
  this.internals.setFormValue(this.value_);
}

state #

カスタム要素単位で、stateをKVストアの形で持つことができるCustomStateSetを扱うことができます。

set checked(flag) {
  if (flag) {
    this._internals.states.add('--checked');
  } else {
    this._internals.states.delete('--checked');
  }
}

これをCSSの:state(<identifier>)のidentifierとして用いることで、カスタム要素を状態に応じてスタイリングすることができます。

labeled-checkbox {
  border: dashed red;
}
labeled-checkbox:state(--checked) {
  border: solid;
}

ARIA #

今までの問題点:

  • setAttributeはアップグレードされたときに実行されるので、カスタム要素にユーザーがHTMLで指定した属性を上書きしてしまう
  • setAttributeした属性が後から上書きされてさらに消されたとき、デフォルト値が消えてしまう

https://wicg.github.io/aom/explainer.html#use-case-1-setting-non-reflected-default-accessibility-properties-for-web-components

子要素がどうとかではなくて、カスタム要素自体を1つのHTML要素として扱うときの話 →カスタム組み込み要素も同じ感じのイメージかも

<script>
  class CustomTabList extends HTMLElement {
    constructor() {
      super();
      this._internals = this.attachInternals();
      this._internals.role = "tablist";
      this._internals.ariaOrientation = "horizontal";
    }
  }

  customElements.define("custom-tablist", CustomTabList);

  class CustomTab extends HTMLElement {
    constructor() {
      super();
      this._internals = this.attachInternals();
      this._internals.role = "tab";
      this._internals.ariaSelected = false;
    }
  }

  customElements.define("custom-tab", CustomTab);
</script>

<custom-tablist aria-orientation="vertical" class="vertical-tablist">
  <custom-tab aria-selected="true">Tab 1</custom-tab>
  <custom-tab>Tab 2</custom-tab>
  <custom-tab>Tab 3</custom-tab>
</custom-tablist>

宣言的・シリアル化可能な方法 #

今見てきた方法は全てJSを用いた命令的にElementInternalsを設定する方法でした。 しかし、JSが読み込まれていないタイミング・環境でも利用可能にしたかったり、SSRやSEOの文脈でもメリットがあることから、宣言的・シリアル化可能な方法が求められます。 これはShadow DOMの宣言でも同じことが求められてDeclarative Shadow DOMが誕生しました。カスタム要素の定義でも同様で、現在DeclarativeなCustom Elementsの定義方法が検討されています。