selector: gate :disabled / :enabled on the HTML "disabled concept"

Per HTML "concept-fe-disabled", only listed elements (button, input,
select, textarea, optgroup, option, fieldset) participate in the
disabled concept. Anything else (e.g. <div disabled>) has no disabled
state and never matches :disabled / :enabled.

Add Element.hasDisabledConcept() and gate isDisabled() on it. Update
the :enabled selector arm so non-form-controls no longer match.
This commit is contained in:
Navid EMAD
2026-04-29 05:50:54 +02:00
parent 628c170b11
commit aca6518850
3 changed files with 33 additions and 1 deletions

View File

@@ -33,6 +33,10 @@
<input id="own_enabled" type="text">
</form>
<div id="div_plain">x</div>
<div id="div_with_disabled_attr" disabled>y</div>
<span id="span_plain">z</span>
<script id="fieldset_inheritance">
{
// Form controls inside <fieldset disabled> match :disabled.
@@ -93,6 +97,20 @@
}
</script>
<script id="concept_gate">
{
// Per HTML "concept-fe-disabled", only listed elements (button, input,
// select, textarea, optgroup, option, fieldset) participate in the
// disabled concept. Anything else never matches :disabled / :enabled,
// even with an own [disabled] attribute.
testing.expectFalse($('#div_plain').matches(':disabled'));
testing.expectFalse($('#div_plain').matches(':enabled'));
testing.expectFalse($('#span_plain').matches(':enabled'));
testing.expectFalse($('#div_with_disabled_attr').matches(':disabled'));
testing.expectFalse($('#div_with_disabled_attr').matches(':enabled'));
}
</script>
<script id="querySelectorAll">
{
// querySelectorAll(':disabled') should find every disabled element.

View File

@@ -600,7 +600,21 @@ pub fn hasAttributeSafe(self: *const Element, name: String) bool {
return attributes.hasSafe(name);
}
// Per HTML "concept-fe-disabled", only listed elements participate in the
// disabled concept. Anything else (e.g. <div disabled>) has no disabled
// state and never matches :disabled / :enabled.
pub fn hasDisabledConcept(self: *const Element) bool {
return switch (self.getTag()) {
.button, .input, .select, .textarea, .optgroup, .option, .fieldset => true,
else => false,
};
}
pub fn isDisabled(self: *Element) bool {
if (!self.hasDisabledConcept()) {
return false;
}
if (self.getAttributeSafe(comptime .wrap("disabled")) != null) {
return true;
}

View File

@@ -538,7 +538,7 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, scope: *N
return el.isDisabled();
},
.enabled => {
return !el.isDisabled();
return el.hasDisabledConcept() and !el.isDisabled();
},
.indeterminate => {
const input = el.is(Node.Element.Html.Input) orelse return false;