Skip to content

Commit 17f9aae

Browse files
committed
Add custom state pseudo class
This is based on the WICG draft spec here: https://wicg.github.io/custom-state-pseudo-class Here are some spec issues where this feature has been discussed: w3ctag/design-reviews#428 w3c/csswg-drafts#4805
1 parent 7d8951c commit 17f9aae

File tree

1 file changed

+159
-0
lines changed

1 file changed

+159
-0
lines changed

source

+159
Original file line numberDiff line numberDiff line change
@@ -2761,6 +2761,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
27612761
<li><dfn data-x="LegacyTreatNonObjectAsNull" data-x-href="https://webidl.spec.whatwg.org/#LegacyTreatNonObjectAsNull"><code>[LegacyTreatNonObjectAsNull]</code></dfn></li>
27622762
<li><dfn data-x="LegacyUnenumerableNamedProperties" data-x-href="https://webidl.spec.whatwg.org/#LegacyUnenumerableNamedProperties"><code>[LegacyUnenumerableNamedProperties]</code></dfn></li>
27632763
<li><dfn data-x="LegacyUnforgeable" data-x-href="https://webidl.spec.whatwg.org/#LegacyUnforgeable"><code>[LegacyUnforgeable]</code></dfn></li>
2764+
<li><dfn data-x-href="https://webidl.spec.whatwg.org/#es-add-delete">Default add operation</dfn></li>
27642765
</ul>
27652766

27662767
<p><cite>Web IDL</cite> also defines the following types that are used in Web IDL fragments in
@@ -3902,6 +3903,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
39023903
<li>The <dfn data-x-href="https://drafts.csswg.org/css-values/#pt">'pt'</dfn> unit</li>
39033904
<li>The <dfn data-x-href="https://drafts.csswg.org/css-values/#funcdef-attr">'attr()'</dfn> function</li>
39043905
<li>The <dfn data-x-href="https://drafts.csswg.org/css-values/#math-function">math functions</dfn></li>
3906+
<li>The <dfn data-x-href="https://drafts.csswg.org/css-values-4/#typedef-dashed-ident">dashed ident</dfn> identifier</li>
39053907
</ul>
39063908

39073909
<p>The term <dfn data-x="css-styling-attribute"
@@ -70681,6 +70683,8 @@ interface <dfn interface>ElementInternals</dfn> {
7068170683
boolean <span data-x="dom-ElementInternals-reportValidity">reportValidity</span>();
7068270684

7068370685
readonly attribute <span>NodeList</span> <span data-x="dom-ElementInternals-labels">labels</span>;
70686+
70687+
[SameObject] readonly attribute <span>CustomStateSet</span> <dfn data-x="dom-elementinternals-states">states</dfn>;
7068470688
};
7068570689

7068670690
// <a href="#accessibility-semantics">Accessibility semantics</a>
@@ -71047,6 +71051,153 @@ dictionary <dfn dictionary>ValidityStateFlags</dfn> {
7104771051

7104871052
</div>
7104971053

71054+
<h4>Custom state pseudo class</h4>
71055+
71056+
<!-- my own attempt at an intro/definition -->
71057+
71058+
<p>The <dfn>custom state pseudo class</dfn> allows <span data-x="custom element">custom
71059+
elements</span> to set and remove <span data-x="pseudo-class">pseudo classes</span> with custom
71060+
names starting with <span data-x="">"--"</span>.</p>
71061+
71062+
<!-- the wicg spec's intro/definition. which is better? -->
71063+
71064+
<p>The <span>custom state pseudo class</span> allows <span data-x="custom element">custom
71065+
elements</span> to inform custom element's states to the user agent, and a
71066+
<span>pseudo-class</span> to select elements with specific states. The former is the <span
71067+
data-x="dom-elementinternals-states">states</span> IDL attribute of <code>ElementInternals</code>,
71068+
and the latter is the <span>custom state pseudo class</span>.</p>
71069+
71070+
<!-- The next two paragraphs are "motivation" from the wicg spec. should they be included...? -->
71071+
71072+
<p>Built-in elements provided by user agents have certain “states” that can change over time
71073+
depending on user interaction and other factors, and are exposed to web authors through <span
71074+
data-x="pseudo-class">pseudo classes</span>. For example, some form controls have the "invalid"
71075+
state, which is exposed through the <code data-x="selector-invalid">:invalid</code>
71076+
<span>pseudo-class</span>.</p>
71077+
71078+
<p>Like built-in elements, <span data-x="custom element">custom elements</span> can have various
71079+
states to be in too, and <span>custom element</span> authors want to expose these states in a
71080+
similar fashion as the built-in elements.</p>
71081+
71082+
<div class="example">
71083+
<p>The following shows how a <span>custom state pseudo class</span> can be used to style a custom
71084+
checkbox element. Assume that <code data-x="">LabeledCheckbox</code> doesn't expose its "checked"
71085+
state via a content attribute.</p>
71086+
71087+
<pre><code class="html">&lt;script>
71088+
class LabeledCheckbox extends HTMLElement {
71089+
constructor() {
71090+
super();
71091+
this._internals = this.attachInternals();
71092+
this.addEventListener('click', this._onClick.bind(this));
71093+
71094+
const shadowRoot = this.attachShadow({mode: 'closed'});
71095+
shadowRoot.innerHTML =
71096+
&#96;&lt;style>
71097+
:host::before {
71098+
content: '[ ]';
71099+
white-space: pre;
71100+
font-family: monospace;
71101+
}
71102+
:host(:--checked)::before { content: '[x]' }
71103+
&lt;/style>
71104+
&lt;slot>Label&lt;/slot>&#96;;
71105+
}
71106+
71107+
get checked() { return this._internals.states.has('--checked'); }
71108+
71109+
set checked(flag) {
71110+
if (flag)
71111+
this._internals.states.add('--checked');
71112+
else
71113+
this._internals.states.delete('--checked');
71114+
}
71115+
71116+
_onClick(event) {
71117+
this.checked = !this.checked;
71118+
}
71119+
}
71120+
71121+
customElements.define('labeled-checkbox', LabeledCheckbox);
71122+
&lt;/script>
71123+
71124+
&lt;style>
71125+
labeled-checkbox { border: dashed red; }
71126+
labeled-checkbox:--checked { border: solid; }
71127+
&lt;/style>
71128+
71129+
&lt;labeled-checkbox>You need to check this&lt;/labeled-checkbox>
71130+
71131+
<!-- Works even on ::part()s -->
71132+
&lt;script>
71133+
class QuestionBox extends HTMLElement {
71134+
constructor() {
71135+
super();
71136+
const shadowRoot = this.attachShadow({mode: 'closed'});
71137+
shadowRoot.innerHTML =
71138+
&#96;&lt;div>&lt;slot>Question&lt;/slot>&lt;/div>
71139+
&lt;labeled-checkbox part='checkbox'>Yes&lt;/labeled-checkbox>&#96;;
71140+
}
71141+
}
71142+
customElements.define('question-box', QuestionBox);
71143+
&lt;/script>
71144+
71145+
&lt;style>
71146+
question-box::part(checkbox) { color: red; }
71147+
question-box::part(checkbox):--checked { color: green; }
71148+
&lt;/style>
71149+
71150+
&lt;question-box>Continue?&lt;/question-box></code></pre>
71151+
</div>
71152+
71153+
<!-- TODO should i keep this heading? its in the wicg draft spec -->
71154+
<h5>Exposing custom element states</h5>
71155+
71156+
<p>Each <span>autonomous custom element</span> has a <dfn>states set</dfn>, which is a
71157+
<code>CustomStateSet</code>, initially empty.</p>
71158+
71159+
<span data-x="concept-element-dom">DOM interface</span>:
71160+
<pre><code class="idl">[Exposed=Window]
71161+
interface <dfn>CustomStateSet</dfn> {
71162+
setlike&lt;DOMString>;
71163+
undefined add(DOMString value);
71164+
};</code></pre>
71165+
71166+
<!-- TODO should this go next to the IDL code of ElementInternals instead? -->
71167+
<p>The <dfn for="HTMLElement"><code data-x="dom-htmlelement-states">states</code></dfn> IDL
71168+
attribute must return the <span>states set</span>.</p>
71169+
71170+
<p>The <dfn for="CustomStateSet"><code
71171+
data-x="dom-customstateset-add">add(<var>value</var>)</code></dfn> method must run the following
71172+
steps:</p>
71173+
71174+
<ol>
71175+
<!-- TODO the draft spec doesn't say what "match" means. Should it be defined? Should I just
71176+
say "starts with" instead? -->
71177+
<li><p>If <var>value</var> does not match <span data-x="dashed ident">&lt;dashed-ident></span>,
71178+
then throw a <span>"<code>SyntaxError</code>"</span> <code>DOMException</code>.</p></li>
71179+
71180+
<li><p>Invoke the <span>default add operation</span>, which the <code
71181+
data-x="">setlike&lt;DOMString></code> would have if <code>CustomStateSet</code> interface had no
71182+
<code data-x="dom-customstateset-add">add</code> operation, given <var>value</var>.</p></li>
71183+
</ol>
71184+
71185+
<div class="example">
71186+
<p><span>States set</span> can expose boolean states represented by existence/non-existence of
71187+
string values. If an author wants to expose a state which can have three values, it can be
71188+
converted to three exclusive boolean states. For example, a state called <code
71189+
data-x="">readyState</code> with <code data-x="">"loading"</code>, <code
71190+
data-x="">"interactive"</code>, and <code data-x="">"complete"</code> values can be mapped to
71191+
three exclusive boolean states, <code data-x="">"--loading"</code>, <code
71192+
data-x="">"--interactive"</code>, and <code data-x="">"--complete"</code>.
71193+
71194+
<pre><code class="js">// Change the readyState from anything to "complete".
71195+
this._readyState = "complete";
71196+
this._internals.states.delete("--loading");
71197+
this._internals.states.delete("--interactive");
71198+
this._internals.states.add("--complete");</code></pre>
71199+
</div>
71200+
7105071201
<h3 split-filename="semantics-other" id="common-idioms">Common idioms without dedicated elements</h3>
7105171202

7105271203
<h4 id="rel-up">Breadcrumb navigation</h4>
@@ -71976,6 +72127,14 @@ Demos:
7197672127
elements whose <span data-x="the directionality">directionality</span> is '<span
7197772128
data-x="concept-rtl">rtl</span>'.</p>
7197872129
</dd>
72130+
72131+
<dt><dfn selector noexport data-x="selector-custom">Custom state pseudo class</dfn></dt>
72132+
<dd>
72133+
<p>The <span data-x="selector-custom">custom state pseudo class</span> is any selector which
72134+
begins with <span data-x="dashed ident">&lt;dashed-ident></span>. It must match any element that
72135+
is an <span>autonomous custom element</span> and whose <span>states set</span> contains a string
72136+
matching the name of the pseudo class.</p>
72137+
</dd>
7197972138
</dl>
7198072139

7198172140
<p class="note">This specification does not define when an element matches the <code undefined

0 commit comments

Comments
 (0)