CustomStateSet

Baseline 2024
新推出

自 2024 年 5 月以來,此功能已在最新裝置和瀏覽器版本中可用。此功能可能不適用於較舊的裝置或瀏覽器。

CustomStateSet 介面是 文件物件模型 (DOM) 的一部分,用於儲存 自主自定義元素 的狀態列表,並允許在集合中新增和移除狀態。

該介面可用於暴露自定義元素的內部狀態,使其能夠被使用該元素的程式碼在 CSS 選擇器中使用。

例項屬性

CustomStateSet.size

返回 CustomStateSet 中值的數量。

例項方法

CustomStateSet.add()

將一個值新增到集合中。

CustomStateSet.clear()

CustomStateSet 物件中移除所有元素。

CustomStateSet.delete()

CustomStateSet 物件中移除一個值。

CustomStateSet.entries()

返回一個新的迭代器,其中包含 CustomStateSet 中每個元素按插入順序排列的值。

CustomStateSet.forEach()

CustomStateSet 物件中的每個值執行提供的函式。

CustomStateSet.has()

返回一個 Boolean 值,指示是否存在具有給定值的元素。

CustomStateSet.keys()

CustomStateSet.values() 的別名。

CustomStateSet.values()

返回一個新的迭代器物件,該物件按插入順序產生 CustomStateSet 物件中每個元素的值。

描述

內建 HTML 元素可以有不同的狀態,例如“啟用”和“停用”、“選中”和“未選中”、“初始”、“載入中”和“就緒”。其中一些狀態是公開的,可以透過屬性/特性進行設定或查詢,而另一些狀態本質上是內部的,無法直接設定。無論外部還是內部,元素的狀態通常都可以使用 CSS 偽類 作為選擇器進行選擇和樣式設定。

CustomStateSet 允許開發者為自主自定義元素(但不能為派生自內建元素的元素)新增和刪除狀態。然後,這些狀態可以以類似方式用作自定義狀態偽類選擇器,就像內建元素的偽類一樣。

設定自定義元素狀態

要使 CustomStateSet 可用,自定義元素必須首先呼叫 HTMLElement.attachInternals() 來附加一個 ElementInternals 物件。然後,CustomStateSetElementInternals.states 返回。請注意,ElementInternals 不能附加到基於內建元素的自定義元素上,因此此功能僅適用於自主自定義元素(請參閱 github.com/whatwg/html/issues/5166)。

CustomStateSet 例項是一個 Set 型別物件,可以容納一個有序的狀態值集合。每個值都是一個自定義識別符號。可以向集合新增或刪除識別符號。如果識別符號存在於集合中,則該特定狀態為 true;如果被移除,則狀態為 false

具有多於兩個值的狀態的自定義元素可以透過多個布林狀態來表示它們,其中一次只有一個狀態為 true(存在於 CustomStateSet 中)。

狀態可以在自定義元素內部使用,但不能在自定義元件外部直接訪問。

與 CSS 的互動

您可以使用 :state()自定義狀態偽類來選擇處於特定狀態的自定義元素。此偽類的格式為 :state(my-state-name),其中 my-state-name 是在元素中定義的狀態。僅當狀態為 true(即,如果 my-state-name 存在於 CustomStateSet 中)時,自定義狀態偽類才會匹配自定義元素。

例如,以下 CSS 在元素的 CustomStateSet 包含 checked 狀態時,會匹配一個 labeled-checkbox 自定義元素,併為該複選框應用 solid 邊框。

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

CSS 也可以透過在 :host() 偽類函式內指定 :state() 來匹配自定義元素影子 DOM 中的自定義狀態

此外,:state() 偽類可以在 ::part() 偽元素之後使用,以匹配處於特定狀態的自定義元素的影子部分

警告: 尚未支援 :state() 的瀏覽器將使用 CSS <dashed-ident> 來選擇自定義狀態,該語法現在已棄用。有關如何同時支援這兩種方法的資訊,請參閱下方的 <dashed-ident> 語法的相容性 部分。

示例

匹配自定義複選框元素的自定義狀態

此示例改編自規範,演示了一個具有內部“checked”狀態的自定義複選框元素。這被對映到 checked 自定義狀態,允許使用 :state(checked) 自定義狀態偽類來應用樣式。

JavaScript

首先,我們定義擴充套件自 HTMLElement 的類 LabeledCheckbox。在建構函式中,我們呼叫 super() 方法,新增一個點選事件監聽器,並呼叫 this.attachInternals() 來附加一個 ElementInternals 物件。

其餘大部分“工作”留給 connectedCallback(),當自定義元素新增到頁面時會呼叫它。元素的內容使用 <style> 元素定義,顯示為文字 [][x],後跟一個標籤。值得注意的是,自定義狀態偽類用於選擇要顯示的文字::host(:state(checked))。在下面的示例之後,我們將更詳細地介紹程式碼片段中的內容。

js
class LabeledCheckbox extends HTMLElement {
  constructor() {
    super();
    this._boundOnClick = this._onClick.bind(this);
    this.addEventListener("click", this._boundOnClick);

    // Attach an ElementInternals to get states property
    this._internals = this.attachInternals();
  }

  connectedCallback() {
    const shadowRoot = this.attachShadow({ mode: "open" });
    shadowRoot.innerHTML = `<style>
  :host {
    display: block;
  }
  :host::before {
    content: "[ ]";
    white-space: pre;
    font-family: monospace;
  }
  :host(:state(checked))::before {
    content: "[x]";
  }
</style>
<slot>Label</slot>
`;
  }

  get checked() {
    return this._internals.states.has("checked");
  }

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

  _onClick(event) {
    // Toggle the 'checked' property when the element is clicked
    this.checked = !this.checked;
  }

  static isStateSyntaxSupported() {
    return CSS.supports("selector(:state(checked))");
  }
}

customElements.define("labeled-checkbox", LabeledCheckbox);

// Display a warning to unsupported browsers
if (!LabeledCheckbox.isStateSyntaxSupported()) {
  if (!document.getElementById("state-warning")) {
    const warning = document.createElement("div");
    warning.id = "state-warning";
    warning.style.color = "red";
    warning.textContent = "This feature is not supported by your browser.";
    document.body.insertBefore(warning, document.body.firstChild);
  }
}

LabeledCheckbox 類中

  • get checked()set checked() 中,我們使用 ElementInternals.states 來獲取 CustomStateSet
  • set checked(flag) 方法會在 flag 設定為 true 時將 "checked" 識別符號新增到 CustomStateSet,並在 flag 為 false 時刪除該識別符號。
  • get checked() 方法只是檢查 checked 屬性是否在集合中定義。
  • 當元素被點選時,屬性值會被切換。

然後,我們在 Window.customElements 返回的物件上呼叫 define() 方法,以註冊自定義元素。

js
customElements.define("labeled-checkbox", LabeledCheckbox);

HTML

在註冊自定義元素後,我們可以在 HTML 中使用該元素,如下所示。

html
<labeled-checkbox>You need to check this</labeled-checkbox>

CSS

最後,我們使用 :state(checked) 自定義狀態偽類來選擇複選框被選中時的 CSS。

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

結果

點選元素以檢視當複選框 checked 狀態切換時應用的邊框變化。

在自定義元素的影子部分匹配自定義狀態

此示例改編自規範,演示了自定義狀態可用於定位自定義元素的影子部分進行樣式設定。影子部分是影子樹中故意暴露給使用自定義元素的頁面的部分。

該示例建立了一個 <question-box> 自定義元素,該元素顯示一個問題提示以及一個標有“Yes”的複選框。該元素使用上一個示例中定義的 <labeled-checkbox> 作為複選框。

JavaScript

首先,我們定義了擴充套件自 HTMLElement 的自定義元素類 QuestionBox。一如既往,建構函式首先呼叫 super() 方法。接下來,我們透過呼叫 attachShadow() 為自定義元素附加一個影子 DOM 樹。

js
class QuestionBox extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: "open" });
    shadowRoot.innerHTML = `<div><slot>Question</slot></div>
<labeled-checkbox part="checkbox">Yes</labeled-checkbox>
`;
  }
}

影子根的內容使用 innerHTML 設定。這定義了一個 <slot> 元素,其中包含元素的預設提示文字“Question”。然後,我們定義一個 <labeled-checkbox> 自定義元素,其預設文字為 "Yes"。該複選框使用 part 屬性作為問題框的影子部分暴露,名稱為 checkbox

請注意,<labeled-checkbox> 元素的程式碼和樣式與上一個示例中的完全相同,因此在此不再重複。

接下來,我們在 Window.customElements 返回的物件上呼叫 define() 方法,以名稱 question-box 註冊自定義元素。

js
customElements.define("question-box", QuestionBox);

HTML

註冊自定義元素後,我們可以在 HTML 中使用該元素,如下所示。

html
<!-- Question box with default prompt "Question" -->
<question-box></question-box>

<!-- Question box with custom prompt "Continue?" -->
<question-box>Continue?</question-box>

CSS

第一塊 CSS 使用 ::part() 選擇器匹配名為 checkbox 的暴露的影子部分,預設將其樣式設定為 red

css
question-box::part(checkbox) {
  color: red;
}

第二塊在 ::part() 之後跟隨 :state(),以匹配處於 checked 狀態的 checkbox 部分。

css
question-box::part(checkbox):state(checked) {
  color: green;
  outline: dashed 1px green;
}

結果

點選其中一個複選框,當 checked 狀態切換時,顏色會從 red 變為 green 並帶有輪廓。

非布林內部狀態

此示例展示瞭如何處理自定義元素具有多個可能值的內部屬性的情況。

在這種情況下,自定義元素有一個 state 屬性,其允許值為:“loading”、“interactive”和“complete”。為了使其正常工作,我們將每個值對映到其自定義狀態,並建立程式碼以確保只設置對應於內部狀態的識別符號。您可以在 set state() 方法的實現中看到這一點:我們設定內部狀態,將匹配自定義狀態的識別符號新增到 CustomStateSet,並移除與所有其他值關聯的識別符號。

其餘大部分程式碼與演示單個布林狀態的示例相似(我們顯示不同的文字以反映使用者在它們之間切換時的狀態)。

JavaScript

js
class ManyStateElement extends HTMLElement {
  constructor() {
    super();
    this._boundOnClick = this._onClick.bind(this);
    this.addEventListener("click", this._boundOnClick);
    // Attach an ElementInternals to get states property
    this._internals = this.attachInternals();
  }

  connectedCallback() {
    this.state = "loading";

    const shadowRoot = this.attachShadow({ mode: "open" });
    shadowRoot.innerHTML = `<style>
  :host {
    display: block;
    font-family: monospace;
  }
  :host::before {
    content: "[ unknown ]";
    white-space: pre;
  }
  :host(:state(loading))::before {
    content: "[ loading ]";
  }
  :host(:state(interactive))::before {
    content: "[ interactive ]";
  }
  :host(:state(complete))::before {
    content: "[ complete ]";
  }
</style>
<slot>Click me</slot>
`;
  }

  get state() {
    return this._state;
  }

  set state(stateName) {
    // Set internal state to passed value
    // Add identifier matching state and delete others
    if (stateName === "loading") {
      this._state = "loading";
      this._internals.states.add("loading");
      this._internals.states.delete("interactive");
      this._internals.states.delete("complete");
    } else if (stateName === "interactive") {
      this._state = "interactive";
      this._internals.states.delete("loading");
      this._internals.states.add("interactive");
      this._internals.states.delete("complete");
    } else if (stateName === "complete") {
      this._state = "complete";
      this._internals.states.delete("loading");
      this._internals.states.delete("interactive");
      this._internals.states.add("complete");
    }
  }

  _onClick(event) {
    // Cycle the state when element clicked
    if (this.state === "loading") {
      this.state = "interactive";
    } else if (this.state === "interactive") {
      this.state = "complete";
    } else if (this.state === "complete") {
      this.state = "loading";
    }
  }

  static isStateSyntaxSupported() {
    return CSS.supports("selector(:state(loading))");
  }
}

customElements.define("many-state-element", ManyStateElement);

if (!LabeledCheckbox.isStateSyntaxSupported()) {
  if (!document.getElementById("state-warning")) {
    const warning = document.createElement("div");
    warning.id = "state-warning";
    warning.style.color = "red";
    warning.textContent = "This feature is not supported by your browser.";
    document.body.insertBefore(warning, document.body.firstChild);
  }
}

HTML

註冊新元素後,將其新增到 HTML 中。這與演示單個布林狀態的示例類似,除了我們不指定值,而是使用插槽的預設值(<slot>Click me</slot>)。

html
<many-state-element></many-state-element>

CSS

在 CSS 中,我們使用三個自定義狀態偽類來選擇每個內部狀態值的 CSS::state(loading):state(interactive):state(complete)。請注意,自定義元素程式碼確保一次只能定義其中一個自定義狀態。

css
many-state-element:state(loading) {
  border: dotted grey;
}
many-state-element:state(interactive) {
  border: dashed blue;
}
many-state-element:state(complete) {
  border: solid green;
}

結果

點選元素以檢視當狀態改變時應用的邊框變化。

<dashed-ident> 語法的相容性

以前,具有自定義狀態的自定義元素使用 <dashed-ident> 而不是 :state() 函式進行選擇。不支援 :state() 的瀏覽器版本在提供未以雙連字元為字首的識別符號時會丟擲錯誤。如果需要支援這些瀏覽器,可以使用 try...catch 塊來同時支援這兩種語法,或者使用 <dashed-ident> 作為狀態的值,並同時使用 :--my-state:state(--my-state) CSS 選擇器進行選擇。

使用 try...catch 塊

此程式碼顯示瞭如何使用 try...catch 來嘗試新增不使用 <dashed-ident> 的狀態識別符號,並在丟擲錯誤時回退到 <dashed-ident>

JavaScript

js
class CompatibleStateElement extends HTMLElement {
  constructor() {
    super();
    this._internals = this.attachInternals();
  }

  connectedCallback() {
    // The double dash is required in browsers with the
    // legacy syntax, not supplying it will throw
    try {
      this._internals.states.add("loaded");
    } catch {
      this._internals.states.add("--loaded");
    }
  }
}

CSS

css
compatible-state-element:is(:--loaded, :state(loaded)) {
  border: solid green;
}

使用雙連字元字首識別符號

另一種解決方案是在 JavaScript 中使用 <dashed-ident>。這種方法的缺點是,在使用 CSS :state() 語法時必須包含連字元。

JavaScript

js
class CompatibleStateElement extends HTMLElement {
  constructor() {
    super();
    this._internals = this.attachInternals();
  }
  connectedCallback() {
    // The double dash is required in browsers with the
    // legacy syntax, but works with the modern syntax
    this._internals.states.add("--loaded");
  }
}

CSS

css
compatible-state-element:is(:--loaded, :state(--loaded)) {
  border: solid green;
}

規範

規範
HTML
# customstateset

瀏覽器相容性

另見

使用自定義元素