CustomStateSet
Baseline 2024>
Neu verfügbar
Seit Mai 2024 funktioniert diese Funktion auf aktuellen Geräten und in aktuellen Browserversionen. Auf älteren Geräten oder in älteren Browsern funktioniert sie möglicherweise nicht.
Die CustomStateSet-Schnittstelle des Document Object Model speichert eine Liste von Zuständen für ein autonomes Custom Element. Zustände können der Menge hinzugefügt und aus ihr entfernt werden.
Mit der Schnittstelle lassen sich die internen Zustände eines Custom Elements zugänglich machen, sodass Code, der das Element verwendet, sie in CSS-Selektoren nutzen kann.
Instanzeigenschaften
CustomStateSet.size-
Gibt die Anzahl der Werte im
CustomStateSetzurück.
Instanzmethoden
CustomStateSet.add()-
Fügt der Menge einen Wert hinzu.
CustomStateSet.clear()-
Entfernt alle Elemente aus dem
CustomStateSet-Objekt. CustomStateSet.delete()-
Entfernt einen Wert aus dem
CustomStateSet-Objekt. CustomStateSet.entries()-
Gibt einen neuen Iterator mit den Werten der Elemente im
CustomStateSetin der Reihenfolge ihres Einfügens zurück. CustomStateSet.forEach()-
Führt eine übergebene Funktion für jeden Wert im
CustomStateSet-Objekt aus. CustomStateSet.has()-
Gibt einen
Boolean-Wert zurück, der angibt, ob ein Element mit dem angegebenen Wert vorhanden ist. CustomStateSet.keys()-
Ein Alias für
CustomStateSet.values(). CustomStateSet.values()-
Gibt ein neues Iterator-Objekt zurück, das die Werte der Elemente im
CustomStateSet-Objekt in der Reihenfolge ihres Einfügens liefert.
Beschreibung
Integrierte HTML-Elemente können unterschiedliche Zustände haben, etwa „enabled“ und „disabled“, „checked“ und „unchecked“ oder „initial“, „loading“ und „ready“. Einige dieser Zustände sind öffentlich und können über Eigenschaften oder Attribute gesetzt oder abgefragt werden. Andere sind dagegen praktisch intern und können nicht direkt gesetzt werden. Unabhängig davon, ob sie öffentlich oder intern sind, können Elementzustände im Allgemeinen mithilfe von CSS-Pseudoklassen ausgewählt und gestaltet werden.
Mit CustomStateSet können Entwickler Zustände für autonome Custom Elements hinzufügen und entfernen, nicht jedoch für Elemente, die von integrierten Elementen abgeleitet sind.
Diese Zustände lassen sich anschließend mit benutzerdefinierten Zustands-Pseudoklassen auswählen, ähnlich wie bei den Pseudoklassen für integrierte Elemente.
Zustände von Custom Elements festlegen
Damit CustomStateSet verfügbar ist, muss ein Custom Element zunächst HTMLElement.attachInternals() aufrufen, um ein ElementInternals-Objekt anzubinden.
CustomStateSet wird dann von ElementInternals.states zurückgegeben.
Beachten Sie, dass ElementInternals nicht an ein Custom Element angebunden werden kann, das auf einem integrierten Element basiert. Diese Funktion steht daher nur für autonome Custom Elements zur Verfügung (siehe github.com/whatwg/html/issues/5166).
Die CustomStateSet-Instanz ist ein Set-ähnliches Objekt, das eine geordnete Menge von Zustandswerten enthalten kann.
Jeder Wert ist ein benutzerdefinierter Bezeichner.
Bezeichner können der Menge hinzugefügt oder aus ihr entfernt werden.
Ist ein Bezeichner in der Menge enthalten, hat der entsprechende Zustand den Wert true; wird er entfernt, hat der Zustand den Wert false.
Custom Elements mit Zuständen, die mehr als zwei Werte annehmen können, können diese durch mehrere boolesche Zustände darstellen, von denen jeweils nur einer true ist, also im CustomStateSet enthalten ist.
Die Zustände können innerhalb des Custom Elements verwendet werden, sind aber außerhalb der benutzerdefinierten Komponente nicht direkt zugänglich.
Zusammenspiel mit CSS
Mit der benutzerdefinierten Zustands-Pseudoklasse :state() können Sie ein Custom Element auswählen, das sich in einem bestimmten Zustand befindet.
Die Pseudoklasse hat das Format :state(my-state-name), wobei my-state-name der im Element definierte Zustand ist.
Die benutzerdefinierte Zustands-Pseudoklasse stimmt nur dann mit dem Custom Element überein, wenn der Zustand true ist, also wenn my-state-name im CustomStateSet enthalten ist.
Das folgende CSS wählt beispielsweise ein labeled-checkbox-Custom-Element aus, wenn dessen CustomStateSet den Zustand checked enthält, und weist der Checkbox einen solid-Rahmen zu:
labeled-checkbox:state(checked) {
border: solid;
}
Mit CSS lässt sich auch ein benutzerdefinierter Zustand innerhalb des Shadow DOM eines Custom Elements auswählen, indem :state() innerhalb der Pseudoklassenfunktion :host() angegeben wird.
Außerdem kann die Pseudoklasse :state() nach dem Pseudoelement ::part() verwendet werden, um Shadow Parts eines Custom Elements auszuwählen, die sich in einem bestimmten Zustand befinden.
Warnung:
Browser, die :state() noch nicht unterstützen, verwenden zum Auswählen benutzerdefinierter Zustände einen CSS-<dashed-ident>. Diese Syntax ist inzwischen veraltet.
Wie Sie beide Ansätze unterstützen können, erfahren Sie unten im Abschnitt Kompatibilität mit der <dashed-ident>-Syntax.
Beispiele
>Benutzerdefinierten Zustand eines Custom-Checkbox-Elements auswählen
Dieses an die Spezifikation angelehnte Beispiel zeigt ein benutzerdefiniertes Checkbox-Element mit einem internen Zustand „checked“.
Dieser wird dem benutzerdefinierten Zustand checked zugeordnet, sodass die Gestaltung über die benutzerdefinierte Zustands-Pseudoklasse :state(checked) erfolgen kann.
JavaScript
Zunächst definieren wir die Klasse LabeledCheckbox, die HTMLElement erweitert.
Im Konstruktor rufen wir die Methode super() auf, fügen einen Listener für das Klickereignis hinzu und rufen this.attachInternals() auf, um ein ElementInternals-Objekt anzubinden.
Der Großteil der übrigen Arbeit erfolgt in connectedCallback(), das aufgerufen wird, wenn ein Custom Element zur Seite hinzugefügt wird.
Der Inhalt des Elements wird mithilfe eines <style>-Elements als Text [] oder [x], gefolgt von einer Beschriftung, definiert.
Bemerkenswert ist hier, dass die benutzerdefinierte Zustands-Pseudoklasse den anzuzeigenden Text auswählt: :host(:state(checked)).
Nach dem folgenden Beispiel erläutern wir genauer, was im Codeausschnitt geschieht.
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);
}
}
In der Klasse LabeledCheckbox:
- In
get checked()undset checked()verwenden wirElementInternals.states, um dasCustomStateSetabzurufen. - Die Methode
set checked(flag)fügt den Bezeichner"checked"zumCustomStateSethinzu, wenn das Flag gesetzt ist, und entfernt ihn, wenn das Flagfalseist. - Die Methode
get checked()prüft lediglich, ob die Eigenschaftcheckedin der Menge definiert ist. - Beim Klicken auf das Element wird der Eigenschaftswert umgeschaltet.
Anschließend rufen wir die Methode define() für das von Window.customElements zurückgegebene Objekt auf, um das Custom Element zu registrieren:
customElements.define("labeled-checkbox", LabeledCheckbox);
HTML
Nach der Registrierung können wir das Custom Element wie folgt in HTML verwenden:
<labeled-checkbox>You need to check this</labeled-checkbox>
CSS
Schließlich wählen wir mit der benutzerdefinierten Zustands-Pseudoklasse :state(checked) die CSS-Regeln aus, die gelten sollen, wenn die Checkbox aktiviert ist.
labeled-checkbox {
border: dashed red;
}
labeled-checkbox:state(checked) {
border: solid;
}
Ergebnis
Klicken Sie auf das Element, um zu sehen, wie beim Umschalten des Zustands checked ein anderer Rahmen angewendet wird.
Benutzerdefinierten Zustand in einem Shadow Part eines Custom Elements auswählen
Dieses an die Spezifikation angelehnte Beispiel zeigt, dass benutzerdefinierte Zustände verwendet werden können, um Shadow Parts eines Custom Elements gezielt zu gestalten. Shadow Parts sind Bereiche des Shadow Trees, die für Seiten, die das Custom Element verwenden, bewusst zugänglich gemacht werden.
Das Beispiel erstellt ein <question-box>-Custom-Element, das eine Frage zusammen mit einer Checkbox mit der Beschriftung „Yes“ anzeigt.
Für die Checkbox verwendet das Element das im vorherigen Beispiel definierte <labeled-checkbox>.
JavaScript
Zunächst definieren wir die Custom-Element-Klasse QuestionBox, die HTMLElement erweitert.
Wie üblich ruft der Konstruktor zuerst die Methode super() auf.
Danach binden wir durch Aufrufen von attachShadow() einen Shadow-DOM-Baum an das Custom Element an.
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>
`;
}
}
Der Inhalt der Shadow Root wird über innerHTML festgelegt.
Damit wird ein <slot>-Element definiert, das den Standardtext „Question“ für das Element enthält.
Anschließend definieren wir ein <labeled-checkbox>-Custom-Element mit dem Standardtext "Yes".
Diese Checkbox wird mithilfe des Attributs part unter dem Namen checkbox als Shadow Part der Fragebox zugänglich gemacht.
Beachten Sie, dass Code und Gestaltung des <labeled-checkbox>-Elements genau dem vorherigen Beispiel entsprechen und daher hier nicht wiederholt werden.
Danach rufen wir die Methode define() für das von Window.customElements zurückgegebene Objekt auf, um das Custom Element unter dem Namen question-box zu registrieren:
customElements.define("question-box", QuestionBox);
HTML
Nach der Registrierung können wir das Custom Element wie folgt in HTML verwenden.
<!-- Question box with default prompt "Question" -->
<question-box></question-box>
<!-- Question box with custom prompt "Continue?" -->
<question-box>Continue?</question-box>
CSS
Der erste CSS-Block wählt mit dem Selektor ::part() den zugänglich gemachten Shadow Part namens checkbox aus und gestaltet ihn standardmäßig red.
question-box::part(checkbox) {
color: red;
}
Im zweiten Block folgt auf ::part() die Pseudoklasse :state(), um checkbox-Parts im Zustand checked auszuwählen:
question-box::part(checkbox):state(checked) {
color: green;
outline: dashed 1px green;
}
Ergebnis
Klicken Sie auf eine der Checkboxes, um zu sehen, wie ihre Farbe beim Umschalten des Zustands checked von red zu green wechselt und eine Umrandung erscheint.
Nicht-boolesche interne Zustände
Dieses Beispiel zeigt, wie Sie vorgehen, wenn ein Custom Element eine interne Eigenschaft mit mehreren möglichen Werten hat.
Das Custom Element hat hier eine Eigenschaft state mit den zulässigen Werten „loading“, „interactive“ und „complete“.
Dazu ordnen wir jedem Wert einen benutzerdefinierten Zustand zu und stellen im Code sicher, dass nur der Bezeichner gesetzt ist, der dem internen Zustand entspricht.
Dies ist in der Implementierung der Methode set state() zu sehen: Wir setzen den internen Zustand, fügen den Bezeichner für den entsprechenden benutzerdefinierten Zustand zum CustomStateSet hinzu und entfernen die Bezeichner für alle anderen Werte.
Der übrige Code ähnelt größtenteils dem Beispiel mit einem einzelnen booleschen Zustand. Für jeden Zustand zeigen wir einen anderen Text an, während der Benutzer zwischen den Zuständen wechselt.
JavaScript
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
Nach der Registrierung fügen wir das neue Element zum HTML hinzu.
Dies ähnelt dem Beispiel mit einem einzelnen booleschen Zustand. Allerdings geben wir keinen Wert an, sondern verwenden den Standardwert aus dem Slot (<slot>Click me</slot>).
<many-state-element></many-state-element>
CSS
Im CSS verwenden wir die drei benutzerdefinierten Zustands-Pseudoklassen, um CSS für die jeweiligen internen Zustandswerte auszuwählen: :state(loading), :state(interactive) und :state(complete).
Beachten Sie, dass der Code des Custom Elements sicherstellt, dass jeweils nur einer dieser benutzerdefinierten Zustände gesetzt sein kann.
many-state-element:state(loading) {
border: dotted grey;
}
many-state-element:state(interactive) {
border: dashed blue;
}
many-state-element:state(complete) {
border: solid green;
}
Ergebnis
Klicken Sie auf das Element, um zu sehen, wie beim Wechsel des Zustands ein anderer Rahmen angewendet wird.
Kompatibilität mit der <dashed-ident>-Syntax
Früher wurden Custom Elements mit benutzerdefinierten Zuständen mithilfe eines <dashed-ident> statt der Funktion :state() ausgewählt.
Browserversionen, die :state() nicht unterstützen, lösen einen Fehler aus, wenn ihnen ein Bezeichner ohne Präfix aus zwei Bindestrichen übergeben wird.
Wenn diese Browser unterstützt werden müssen, verwenden Sie entweder einen try...catch-Block für beide Syntaxvarianten oder verwenden Sie einen <dashed-ident> als Zustandswert und wählen Sie ihn mit den CSS-Selektoren :--my-state und :state(--my-state) aus.
Einen try...catch-Block verwenden
Dieser Code zeigt, wie Sie mit try...catch versuchen können, einen Zustandsbezeichner hinzuzufügen, der kein <dashed-ident> verwendet, und bei einem Fehler auf <dashed-ident> zurückgreifen.
JavaScript
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
compatible-state-element:is(:--loaded, :state(loaded)) {
border: solid green;
}
Bezeichner mit zwei vorangestellten Bindestrichen verwenden
Eine alternative Lösung besteht darin, <dashed-ident> in JavaScript zu verwenden.
Der Nachteil dieses Ansatzes ist, dass die Bindestriche auch bei Verwendung der CSS-Syntax :state() angegeben werden müssen.
JavaScript
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
compatible-state-element:is(:--loaded, :state(--loaded)) {
border: solid green;
}
Spezifikationen
| Spezifikation |
|---|
| HTML> # customstateset> |