Skip to content

Commit 1a00fdf

Browse files
authored
fix: check for inherited :disabled (#872)
1 parent 75fdd44 commit 1a00fdf

File tree

2 files changed

+108
-3
lines changed

2 files changed

+108
-3
lines changed

src/utils/misc/isDisabled.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,37 @@
1-
// This should probably be extended with checking the element type
2-
// https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled
1+
import {isElementType} from './isElementType'
2+
3+
// This should probably just rely on the :disabled pseudo-class, but JSDOM doesn't implement it properly.
34
export function isDisabled(element: Element | null): boolean {
4-
return Boolean(element && (element as {disabled?: boolean}).disabled)
5+
for (let el = element; el; el = el.parentElement) {
6+
if (
7+
isElementType(el, [
8+
'button',
9+
'input',
10+
'select',
11+
'textarea',
12+
'optgroup',
13+
'option',
14+
])
15+
) {
16+
if (el.hasAttribute('disabled')) {
17+
return true
18+
}
19+
} else if (isElementType(el, 'fieldset')) {
20+
if (
21+
el.hasAttribute('disabled') &&
22+
!el.querySelector(':scope > legend')?.contains(element)
23+
) {
24+
return true
25+
}
26+
} else if (el.tagName.includes('-')) {
27+
if (
28+
(el.constructor as {formAssociated?: boolean}).formAssociated &&
29+
el.hasAttribute('disabled')
30+
) {
31+
return true
32+
}
33+
}
34+
}
35+
36+
return false
537
}

tests/utils/misc/isDisabled.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import cases from 'jest-in-case'
2+
import {isDisabled} from '#src/utils'
3+
import {render} from '#testHelpers'
4+
5+
customElements.define(
6+
'form-associated',
7+
class FormAssociated extends HTMLElement {
8+
static formAssociated = true
9+
get disabled() {
10+
return this.hasAttribute('disabled')
11+
}
12+
},
13+
)
14+
15+
customElements.define(
16+
'custom-el',
17+
class CustomEl extends HTMLElement {
18+
get disabled() {
19+
return this.hasAttribute('disabled')
20+
}
21+
},
22+
)
23+
24+
// https://html.spec.whatwg.org/multipage/semantics-other.html#disabled-elements
25+
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#enabling-and-disabling-form-controls:-the-disabled-attribute
26+
cases(
27+
'check if element is disabled',
28+
({html, node = '//input', expected = true}) => {
29+
const {xpathNode} = render(html)
30+
expect(isDisabled(xpathNode<Element>(node))).toBe(expected)
31+
},
32+
{
33+
control: {
34+
html: `<input/>`,
35+
expected: false,
36+
},
37+
'disabled control': {
38+
html: `<input disabled/>`,
39+
},
40+
'control in disabled fieldset': {
41+
html: `<fieldset disabled><input/></fieldset>`,
42+
},
43+
'control in first legend of disabled fieldset': {
44+
html: `<fieldset disabled><legend><input/></legend></fieldset>`,
45+
expected: false,
46+
},
47+
'control in other legend of disabled fieldset': {
48+
html: `<fieldset disabled><legend></legend><legend><input/></legend></fieldset>`,
49+
},
50+
'control in nested legend of disabled fieldset': {
51+
html: `<fieldset disabled><div>><legend><input/></legend></div></fieldset>`,
52+
},
53+
'element without support for disabled': {
54+
html: `<div disabled></div>`,
55+
node: '*',
56+
expected: false,
57+
},
58+
'form-associated disabled custom element': {
59+
html: `<form-associated disabled></form-associated>`,
60+
node: '*',
61+
},
62+
'form-associated enabled custom element': {
63+
html: `<form-associated></form-associated>`,
64+
node: '*',
65+
expected: false,
66+
},
67+
'other custom element': {
68+
html: `<custom-el disabled></custom-el>`,
69+
node: '*',
70+
expected: false,
71+
},
72+
},
73+
)

0 commit comments

Comments
 (0)