CustomStateSet
Baseline
2024
Newly available
Since May 2024, this feature works across the latest devices and browser versions. This feature might not work in older devices or browsers.
Das CustomStateSet-Interface des Document Object Model speichert eine Liste von Zuständen für ein autonomes benutzerdefiniertes Element und ermöglicht das Hinzufügen und Entfernen von Zuständen aus dem Set.
Das Interface kann verwendet werden, um die internen Zustände eines benutzerdefinierten Elements offenzulegen, sodass sie in CSS-Selektoren von Code verwendet werden können, der das Element benutzt.
Instanzeigenschaften
CustomStateSet.size-
Gibt die Anzahl der Werte im
CustomStateSetzurück.
Instanzmethoden
CustomStateSet.add()-
Fügt einen Wert zum Set 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 für jedes Element im
CustomStateSetin Einfügereihenfolge zurück. CustomStateSet.forEach()-
Führt eine bereitgestellte Funktion für jeden Wert im
CustomStateSet-Objekt aus. CustomStateSet.has()-
Gibt ein
Booleanzurück, das angibt, ob ein Element mit dem gegebenen Wert vorhanden ist. CustomStateSet.keys()-
Ein Alias für
CustomStateSet.values(). CustomStateSet.values()-
Gibt ein neues Iterator-Objekt zurück, das die Werte für jedes Element im
CustomStateSet-Objekt in Einfügereihenfolge liefert.
Beschreibung
Eingebaute HTML-Elemente können unterschiedliche Zustände haben, wie "enabled" und "disabled", "checked" und "unchecked", "initial", "loading" und "ready". Einige dieser Zustände sind öffentlich und können über Eigenschaften/Attribute festgelegt oder abgefragt werden, während andere im Wesentlichen intern sind und nicht direkt festgelegt werden können. Ob extern oder intern, Elementzustände können im Allgemeinen mit CSS-Pseudoklassen als Selektoren ausgewählt und gestylt werden.
Das CustomStateSet ermöglicht es Entwicklern, Zustände für autonome benutzerdefinierte Elemente hinzuzufügen und zu löschen (aber nicht für Elemente, die von eingebauten Elementen abgeleitet sind).
Diese Zustände können dann als benutzerdefinierte Zustands-Pseudoklassen-Selektoren ähnlich wie die Pseudoklassen für eingebaute Elemente verwendet werden.
Festlegung benutzerdefinierter Elementzustände
Um das CustomStateSet verfügbar zu machen, muss ein benutzerdefiniertes Element zuerst HTMLElement.attachInternals() aufrufen, um ein ElementInternals-Objekt anzuhängen.
CustomStateSet wird dann von ElementInternals.states zurückgegeben.
Beachten Sie, dass ElementInternals nicht an ein benutzerdefiniertes Element angehängt werden kann, das auf einem eingebauten Element basiert, sodass dieses Feature nur für autonome benutzerdefinierte Elemente funktioniert (siehe github.com/whatwg/html/issues/5166).
Die CustomStateSet-Instanz ist ein Set-ähnliches Objekt, das eine geordnete Menge von Zustandswerten halten kann.
Jeder Wert ist ein benutzerdefinierter Bezeichner.
Bezeichner können zum Set hinzugefügt oder daraus gelöscht werden.
Wenn ein Bezeichner im Set vorhanden ist, ist der entsprechende Zustand true, während der Zustand false ist, wenn er entfernt wird.
Benutzerdefinierte Elemente, die Zustände mit mehr als zwei Werten haben, können sie mit mehreren booleschen Zuständen darstellen, von denen jeweils nur einer (true) zu einem Zeitpunkt im CustomStateSet vorhanden ist.
Die Zustände können innerhalb des benutzerdefinierten Elements verwendet werden, sind aber von außerhalb der benutzerdefinierten Komponente nicht direkt zugänglich.
Interaktion mit CSS
Sie können ein benutzerdefiniertes Element auswählen, das sich in einem bestimmten Zustand befindet, indem Sie die :state() benutzerdefinierte Zustands-Pseudoklasse verwenden.
Das Format dieser Pseudoklasse ist :state(mein-zustandsname), wobei mein-zustandsname der im Element definierte Zustand ist.
Die benutzerdefinierte Zustands-Pseudoklasse passt nur, wenn der Zustand true ist (d.h. wenn mein-zustandsname im CustomStateSet vorhanden ist).
Zum Beispiel passt der folgende CSS-Code zu einem labeled-checkbox-benutzerdefinierten Element, wenn das checked-Element im CustomStateSet enthalten ist, und wendet einen solid-Rand auf das Kontrollkästchen an:
labeled-checkbox:state(checked) {
border: solid;
}
CSS kann auch verwendet werden, um einen benutzerdefinierten Zustand innerhalb des Shadow DOM eines benutzerdefinierten Elements abzugleichen, indem :state() innerhalb der :host()-Pseudoklassen-Funktion angegeben wird.
Zusätzlich kann die :state() Pseudoklasse nach dem ::part()-Pseudoelement verwendet werden, um Shadow-Parts eines benutzerdefinierten Elements abzugleichen, die sich in einem bestimmten Zustand befinden.
Warnung:
Browser, die noch nicht :state() unterstützen, verwenden ein CSS <dashed-ident>, um benutzerdefinierte Zustände auszuwählen, was jetzt veraltet ist.
Informationen dazu, wie beide Ansätze unterstützt werden können, finden Sie im Abschnitt Kompatibilität mit <dashed-ident>-Syntax weiter unten.
Beispiele
>Abgleich des benutzerdefinierten Zustands eines benutzerdefinierten Kontrollkästchenelements
Dieses Beispiel, das aus der Spezifikation adaptiert wurde, demonstriert ein benutzerdefiniertes Kontrollkästchenelement, das einen internen "checked"-Zustand hat.
Dieser wird dem benutzerdefinierten checked-Zustand zugeordnet, wodurch Stile mit der benutzerdefinierten :state(checked) Zustands-Pseudoklasse angewendet werden können.
JavaScript
Zuerst definieren wir unsere Klasse LabeledCheckbox, die von HTMLElement erbt.
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 anzuhängen.
Der Großteil der Arbeit wird dann connectedCallback() überlassen, das aufgerufen wird, wenn ein benutzerdefiniertes Element zur Seite hinzugefügt wird.
Der Inhalt des Elements wird unter Verwendung eines <style>-Elements als der Text [] oder [x] gefolgt von einem Label definiert.
Besonders hervorzuheben ist, dass die benutzerdefinierte Zustands-Pseudoklasse verwendet wird, um den anzuzeigenden Text auszuwählen: :host(:state(checked)).
Nach dem Beispiel unten werden wir genauer erläutern, was im Snippet passiert.
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 LabeledCheckbox-Klasse:
- In
get checked()undset checked()verwenden wirElementInternals.states, um dasCustomStateSetzu erhalten. - Die Methode
set checked(flag)fügt den"checked"-Bezeichner demCustomStateSethinzu, wenn das Flag gesetzt ist, und löscht den Bezeichner, wenn das Flagfalseist. - Die Methode
get checked()prüft lediglich, ob diechecked-Eigenschaft im Set definiert ist. - Der Eigenschaftswert wird umgeschaltet, wenn auf das Element geklickt wird.
Dann rufen wir die define()-Methode auf dem Objekt auf, das von Window.customElements zurückgegeben wird, um das benutzerdefinierte Element zu registrieren:
customElements.define("labeled-checkbox", LabeledCheckbox);
HTML
Nachdem wir das benutzerdefinierte Element registriert haben, können wir das Element im HTML verwenden wie gezeigt:
<labeled-checkbox>You need to check this</labeled-checkbox>
CSS
Schließlich verwenden wir die benutzerdefinierte Zustands-Pseudoklasse :state(checked), um CSS auszuwählen, wenn das Kästchen markiert ist.
labeled-checkbox {
border: dashed red;
}
labeled-checkbox:state(checked) {
border: solid;
}
Ergebnis
Klicken Sie auf das Element, um einen anderen Rand zu sehen, der angewendet wird, wenn der checked-Zustand des Kontrollkästchens umgeschaltet wird.
Abgleich eines benutzerdefinierten Zustands in einem Shadow-Part eines benutzerdefinierten Elements
Dieses Beispiel, das aus der Spezifikation adaptiert wurde, zeigt, dass benutzerdefinierte Zustände verwendet werden können, um die Shadow-Parts eines benutzerdefinierten Elements für das Styling anzusprechen. Shadow-Parts sind Abschnitte des Shadow-Baums, die absichtlich für Seiten offengelegt werden, die das benutzerdefinierte Element verwenden.
Das Beispiel erstellt ein <question-box>-benutzerdefiniertes Element, das eine Frageaufforderung zusammen mit einem Kontrollkästchen mit der Beschriftung "Yes" anzeigt.
Das Element verwendet das <labeled-checkbox> aus dem vorherigen Beispiel für das Kontrollkästchen.
JavaScript
Zuerst definieren wir die benutzerdefinierte Elementklasse QuestionBox, die HTMLElement erweitert.
Wie immer ruft der Konstruktor zuerst die super()-Methode auf.
Als nächstes hängen wir einen Shadow-DOM-Baum an das benutzerdefinierte Element, indem wir attachShadow() aufrufen.
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 mit innerHTML gesetzt.
Dies definiert ein <slot>-Element, das den Standard-Aufforderungstext "Question" für das Element enthält.
Dann definieren wir ein benutzerdefiniertes <labeled-checkbox>-Element mit dem Standardtext "Yes".
Dieses Kontrollkästchen wird als Shadow-Part der Fragebox mit dem Namen checkbox mittels des part-Attributs offengelegt.
Beachten Sie, dass der Code und das Styling für das <labeled-checkbox>-Element genau dieselben sind wie im vorherigen Beispiel und deshalb hier nicht wiederholt werden.
Als nächstes rufen wir die define()-Methode auf dem Objekt auf, das von Window.customElements zurückgegeben wird, um das benutzerdefinierte Element mit dem Namen question-box zu registrieren:
customElements.define("question-box", QuestionBox);
HTML
Nach der Registrierung des benutzerdefinierten Elements können wir das Element im HTML wie unten gezeigt 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 passt den offengelegten Shadow-Part mit dem Namen checkbox mit dem ::part()-Selektor an und stylt es standardmäßig rot.
question-box::part(checkbox) {
color: red;
}
Der zweite Block folgt ::part() mit :state(), um checkbox-Parts zu matchen, die sich im checked-Zustand befinden:
question-box::part(checkbox):state(checked) {
color: green;
outline: dashed 1px green;
}
Ergebnis
Klicken Sie auf eines der Kontrollkästchen, um die Farbänderung von rot zu grün mit einer Umrandung zu sehen, wenn der checked-Zustand umgeschaltet wird.
Nicht-boolesche interne Zustände
Dieses Beispiel zeigt, wie man den Fall behandelt, dass das benutzerdefinierte Element eine interne Eigenschaft mit mehreren möglichen Werten hat.
Das benutzerdefinierte Element in diesem Fall hat eine state-Eigenschaft mit den zulässigen Werten: "loading", "interactive" und "complete".
Um dies zum Laufen zu bringen, ordnen wir jeden Wert seinem benutzerdefinierten Zustand zu und erstellen Code, um sicherzustellen, dass nur der Bezeichner, der dem internen Zustand entspricht, gesetzt ist.
Sie können dies in der Implementierung der set state()-Methode sehen: Wir setzen den internen Zustand, fügen den Bezeichner für den passenden benutzerdefinierten Zustand dem CustomStateSet hinzu und entfernen die Bezeichner, die mit allen anderen Werten verbunden sind.
Der Großteil des restlichen Codes ähnelt dem Beispiel, das einen einzigen booleschen Zustand demonstriert (wir zeigen unterschiedlichen Text für jeden Zustand, während der Benutzer durch sie schaltet).
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 des neuen Elements fügen wir es dem HTML hinzu.
Dies ähnelt dem Beispiel, das einen einzigen booleschen Zustand demonstriert, außer dass wir keinen Wert angeben und den Standardwert vom Slot verwenden (<slot>Click me</slot>).
<many-state-element></many-state-element>
CSS
Im CSS verwenden wir die drei benutzerdefinierten Zustands-Pseudoklassen, um CSS für jeden der internen Zustandswerte auszuwählen: :state(loading), :state(interactive), :state(complete).
Beachten Sie, dass der Code des benutzerdefinierten Elements sicherstellt, dass jeweils nur einer dieser benutzerdefinierten Zustände definiert 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;
}
Ergebnisse
Klicken Sie auf das Element, um einen anderen Rand zu sehen, der angewendet wird, wenn sich der Zustand ändert.
Kompatibilität mit <dashed-ident>-Syntax
Früher wurden benutzerdefinierte Elemente mit benutzerdefinierten Zuständen mit einem <dashed-ident> anstelle der :state()-Funktion ausgewählt.
Browserversionen, die :state() nicht unterstützen, werfen einen Fehler, wenn ein Bezeichner ohne den Doppelstrich-Präfix angegeben wird.
Wenn die Unterstützung für diese Browser erforderlich ist, verwenden Sie entweder einen try...catch-Block, um beide Syntaxen zu unterstützen, oder verwenden Sie ein <dashed-ident> als Zustandswert und wählen Sie es sowohl mit dem :--my-state als auch dem :state(--my-state)-CSS-Selektor aus.
Verwendung eines try...catch-Blocks
Dieser Code zeigt, wie Sie try...catch verwenden können, um zu versuchen, einen Zustandsbezeichner hinzuzufügen, der kein <dashed-ident> verwendet, und zu <dashed-ident> zurückzukehren, wenn ein Fehler geworfen wird.
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;
}
Verwendung von mit Doppelstrich präfixierten Bezeichnern
Eine alternative Lösung besteht darin, das <dashed-ident> innerhalb von JavaScript zu verwenden.
Der Nachteil dieses Ansatzes ist, dass die Striche auch bei der Verwendung der CSS- :state()-Syntax enthalten sein 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
| Specification |
|---|
| HTML> # customstateset> |