Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom state pseudo class #8467

Merged
merged 13 commits into from
Dec 24, 2023
159 changes: 159 additions & 0 deletions source
Original file line number Diff line number Diff line change
Expand Up @@ -2761,6 +2761,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
<li><dfn data-x="LegacyTreatNonObjectAsNull" data-x-href="https://webidl.spec.whatwg.org/#LegacyTreatNonObjectAsNull"><code>[LegacyTreatNonObjectAsNull]</code></dfn></li>
<li><dfn data-x="LegacyUnenumerableNamedProperties" data-x-href="https://webidl.spec.whatwg.org/#LegacyUnenumerableNamedProperties"><code>[LegacyUnenumerableNamedProperties]</code></dfn></li>
<li><dfn data-x="LegacyUnforgeable" data-x-href="https://webidl.spec.whatwg.org/#LegacyUnforgeable"><code>[LegacyUnforgeable]</code></dfn></li>
<li><dfn data-x-href="https://webidl.spec.whatwg.org/#es-add-delete">Default add operation</dfn></li>
</ul>
josepharhar marked this conversation as resolved.
Show resolved Hide resolved

<p><cite>Web IDL</cite> also defines the following types that are used in Web IDL fragments in
Expand Down Expand Up @@ -3902,6 +3903,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
<li>The <dfn data-x-href="https://drafts.csswg.org/css-values/#pt">'pt'</dfn> unit</li>
<li>The <dfn data-x-href="https://drafts.csswg.org/css-values/#funcdef-attr">'attr()'</dfn> function</li>
<li>The <dfn data-x-href="https://drafts.csswg.org/css-values/#math-function">math functions</dfn></li>
<li>The <dfn data-x-href="https://drafts.csswg.org/css-values-4/#typedef-dashed-ident">dashed ident</dfn> identifier</li>
josepharhar marked this conversation as resolved.
Show resolved Hide resolved
</ul>

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

readonly attribute <span>NodeList</span> <span data-x="dom-ElementInternals-labels">labels</span>;

[SameObject] readonly attribute <span>CustomStateSet</span> <dfn data-x="dom-elementinternals-states">states</dfn>;
josepharhar marked this conversation as resolved.
Show resolved Hide resolved
};

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

</div>

<h4>Custom state pseudo class</h4>
josepharhar marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To follow existing patterns in the custom elements section, I think we should split this up.

The introduction should go as a new section under https://whatpr.org/html/8467/custom-elements.html#custom-elements-intro . (Maybe "Exposing a custom element's states") And the normative stuff should go as a new section under https://whatpr.org/html/8467/custom-elements.html#element-internals ("Custom pseudo-classes").

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I tried moving it around. Did i get it right?


<!-- my own attempt at an intro/definition -->

<p>The <dfn>custom state pseudo class</dfn> allows <span data-x="custom element">custom
elements</span> to set and remove <span data-x="pseudo-class">pseudo classes</span> with custom
names starting with <span data-x="">"--"</span>.</p>

<!-- the wicg spec's intro/definition. which is better? -->

<p>The <span>custom state pseudo class</span> allows <span data-x="custom element">custom
elements</span> to inform custom element's states to the user agent, and a
<span>pseudo-class</span> to select elements with specific states. The former is the <span
data-x="dom-elementinternals-states">states</span> IDL attribute of <code>ElementInternals</code>,
and the latter is the <span>custom state pseudo class</span>.</p>

<!-- The next two paragraphs are "motivation" from the wicg spec. should they be included...? -->

<p>Built-in elements provided by user agents have certain “states” that can change over time
depending on user interaction and other factors, and are exposed to web authors through <span
data-x="pseudo-class">pseudo classes</span>. For example, some form controls have the "invalid"
state, which is exposed through the <code data-x="selector-invalid">:invalid</code>
<span>pseudo-class</span>.</p>

<p>Like built-in elements, <span data-x="custom element">custom elements</span> can have various
states to be in too, and <span>custom element</span> authors want to expose these states in a
similar fashion as the built-in elements.</p>
josepharhar marked this conversation as resolved.
Show resolved Hide resolved

<div class="example">
<p>The following shows how a <span>custom state pseudo class</span> can be used to style a custom
checkbox element. Assume that <code data-x="">LabeledCheckbox</code> doesn't expose its "checked"
state via a content attribute.</p>

<pre><code class="html">&lt;script>
class LabeledCheckbox extends HTMLElement {
constructor() {
super();
this._internals = this.attachInternals();
this.addEventListener('click', this._onClick.bind(this));

const shadowRoot = this.attachShadow({mode: 'closed'});
shadowRoot.innerHTML =
&#96;&lt;style>
:host::before {
content: '[ ]';
white-space: pre;
font-family: monospace;
}
:host(:--checked)::before { content: '[x]' }
&lt;/style>
&lt;slot>Label&lt;/slot>&#96;;
}

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) {
this.checked = !this.checked;
}
}

customElements.define('labeled-checkbox', LabeledCheckbox);
&lt;/script>

&lt;style>
labeled-checkbox { border: dashed red; }
labeled-checkbox:--checked { border: solid; }
&lt;/style>

&lt;labeled-checkbox>You need to check this&lt;/labeled-checkbox>

<!-- Works even on ::part()s -->
josepharhar marked this conversation as resolved.
Show resolved Hide resolved
&lt;script>
class QuestionBox extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'closed'});
shadowRoot.innerHTML =
&#96;&lt;div>&lt;slot>Question&lt;/slot>&lt;/div>
&lt;labeled-checkbox part='checkbox'>Yes&lt;/labeled-checkbox>&#96;;
}
}
customElements.define('question-box', QuestionBox);
&lt;/script>

&lt;style>
question-box::part(checkbox) { color: red; }
question-box::part(checkbox):--checked { color: green; }
&lt;/style>

&lt;question-box>Continue?&lt;/question-box></code></pre>
</div>

<!-- TODO should i keep this heading? its in the wicg draft spec -->
<h5>Exposing custom element states</h5>
josepharhar marked this conversation as resolved.
Show resolved Hide resolved

<p>Each <span>autonomous custom element</span> has a <dfn>states set</dfn>, which is a
<code>CustomStateSet</code>, initially empty.</p>

<span data-x="concept-element-dom">DOM interface</span>:
josepharhar marked this conversation as resolved.
Show resolved Hide resolved
<pre><code class="idl">[Exposed=Window]
interface <dfn>CustomStateSet</dfn> {
setlike&lt;DOMString>;
undefined add(DOMString value);
josepharhar marked this conversation as resolved.
Show resolved Hide resolved
};</code></pre>

<!-- TODO should this go next to the IDL code of ElementInternals instead? -->
josepharhar marked this conversation as resolved.
Show resolved Hide resolved
<p>The <dfn for="HTMLElement"><code data-x="dom-htmlelement-states">states</code></dfn> IDL
attribute must return the <span>states set</span>.</p>
josepharhar marked this conversation as resolved.
Show resolved Hide resolved

<p>The <dfn for="CustomStateSet"><code
data-x="dom-customstateset-add">add(<var>value</var>)</code></dfn> method must run the following
steps:</p>
josepharhar marked this conversation as resolved.
Show resolved Hide resolved

<ol>
josepharhar marked this conversation as resolved.
Show resolved Hide resolved
josepharhar marked this conversation as resolved.
Show resolved Hide resolved
<!-- TODO the draft spec doesn't say what "match" means. Should it be defined? Should I just
say "starts with" instead? -->
domenic marked this conversation as resolved.
Show resolved Hide resolved
<li><p>If <var>value</var> does not match <span data-x="dashed ident">&lt;dashed-ident></span>,
then throw a <span>"<code>SyntaxError</code>"</span> <code>DOMException</code>.</p></li>

<li><p>Invoke the <span>default add operation</span>, which the <code
data-x="">setlike&lt;DOMString></code> would have if <code>CustomStateSet</code> interface had no
<code data-x="dom-customstateset-add">add</code> operation, given <var>value</var>.</p></li>
josepharhar marked this conversation as resolved.
Show resolved Hide resolved
</ol>

<div class="example">
<p><span>States set</span> can expose boolean states represented by existence/non-existence of
josepharhar marked this conversation as resolved.
Show resolved Hide resolved
string values. If an author wants to expose a state which can have three values, it can be
converted to three exclusive boolean states. For example, a state called <code
data-x="">readyState</code> with <code data-x="">"loading"</code>, <code
data-x="">"interactive"</code>, and <code data-x="">"complete"</code> values can be mapped to
three exclusive boolean states, <code data-x="">"--loading"</code>, <code
data-x="">"--interactive"</code>, and <code data-x="">"--complete"</code>.
josepharhar marked this conversation as resolved.
Show resolved Hide resolved

<pre><code class="js">// Change the readyState from anything to "complete".
this._readyState = "complete";
this._internals.states.delete("--loading");
this._internals.states.delete("--interactive");
this._internals.states.add("--complete");</code></pre>
</div>

<h3 split-filename="semantics-other" id="common-idioms">Common idioms without dedicated elements</h3>

<h4 id="rel-up">Breadcrumb navigation</h4>
Expand Down Expand Up @@ -71976,6 +72127,14 @@ Demos:
elements whose <span data-x="the directionality">directionality</span> is '<span
data-x="concept-rtl">rtl</span>'.</p>
</dd>

<dt><dfn selector noexport data-x="selector-custom">Custom state pseudo class</dfn></dt>
josepharhar marked this conversation as resolved.
Show resolved Hide resolved
<dd>
<p>The <span data-x="selector-custom">custom state pseudo class</span> is any selector which
begins with <span data-x="dashed ident">&lt;dashed-ident></span>. It must match any element that
is an <span>autonomous custom element</span> and whose <span>states set</span> contains a string
matching the name of the pseudo class.</p>
</dd>
</dl>

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