From 1a00fdfc3085addc4792897aecda202e07c3f5d2 Mon Sep 17 00:00:00 2001 From: Philipp Fritsche Date: Fri, 4 Mar 2022 11:58:26 +0100 Subject: [PATCH] fix: check for inherited `:disabled` (#872) --- src/utils/misc/isDisabled.ts | 38 ++++++++++++++++-- tests/utils/misc/isDisabled.ts | 73 ++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 tests/utils/misc/isDisabled.ts diff --git a/src/utils/misc/isDisabled.ts b/src/utils/misc/isDisabled.ts index 466db942..d2d590e1 100644 --- a/src/utils/misc/isDisabled.ts +++ b/src/utils/misc/isDisabled.ts @@ -1,5 +1,37 @@ -// This should probably be extended with checking the element type -// https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled +import {isElementType} from './isElementType' + +// This should probably just rely on the :disabled pseudo-class, but JSDOM doesn't implement it properly. export function isDisabled(element: Element | null): boolean { - return Boolean(element && (element as {disabled?: boolean}).disabled) + for (let el = element; el; el = el.parentElement) { + if ( + isElementType(el, [ + 'button', + 'input', + 'select', + 'textarea', + 'optgroup', + 'option', + ]) + ) { + if (el.hasAttribute('disabled')) { + return true + } + } else if (isElementType(el, 'fieldset')) { + if ( + el.hasAttribute('disabled') && + !el.querySelector(':scope > legend')?.contains(element) + ) { + return true + } + } else if (el.tagName.includes('-')) { + if ( + (el.constructor as {formAssociated?: boolean}).formAssociated && + el.hasAttribute('disabled') + ) { + return true + } + } + } + + return false } diff --git a/tests/utils/misc/isDisabled.ts b/tests/utils/misc/isDisabled.ts new file mode 100644 index 00000000..c88512fa --- /dev/null +++ b/tests/utils/misc/isDisabled.ts @@ -0,0 +1,73 @@ +import cases from 'jest-in-case' +import {isDisabled} from '#src/utils' +import {render} from '#testHelpers' + +customElements.define( + 'form-associated', + class FormAssociated extends HTMLElement { + static formAssociated = true + get disabled() { + return this.hasAttribute('disabled') + } + }, +) + +customElements.define( + 'custom-el', + class CustomEl extends HTMLElement { + get disabled() { + return this.hasAttribute('disabled') + } + }, +) + +// https://html.spec.whatwg.org/multipage/semantics-other.html#disabled-elements +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#enabling-and-disabling-form-controls:-the-disabled-attribute +cases( + 'check if element is disabled', + ({html, node = '//input', expected = true}) => { + const {xpathNode} = render(html) + expect(isDisabled(xpathNode(node))).toBe(expected) + }, + { + control: { + html: ``, + expected: false, + }, + 'disabled control': { + html: ``, + }, + 'control in disabled fieldset': { + html: `
`, + }, + 'control in first legend of disabled fieldset': { + html: `
`, + expected: false, + }, + 'control in other legend of disabled fieldset': { + html: `
`, + }, + 'control in nested legend of disabled fieldset': { + html: `
>
`, + }, + 'element without support for disabled': { + html: `
`, + node: '*', + expected: false, + }, + 'form-associated disabled custom element': { + html: ``, + node: '*', + }, + 'form-associated enabled custom element': { + html: ``, + node: '*', + expected: false, + }, + 'other custom element': { + html: ``, + node: '*', + expected: false, + }, + }, +)