From bf15fcd608ed050016d2437132f9e7a1c1840c8c Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Fri, 22 Jan 2021 15:25:40 +0100 Subject: [PATCH] apply disabled fix when inside a disabled fieldset And if we are in a disabled fieldset, double check that we are not in the first legend. Because in that case we are visually outside of the fieldset and according to the spec those elements should **not** be considered disabled. Fixes: #194 --- .../src/components/menu/menu.tsx | 4 ++- .../src/components/switch/switch.tsx | 2 ++ packages/@headlessui-react/src/utils/bugs.ts | 30 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 packages/@headlessui-react/src/utils/bugs.ts diff --git a/packages/@headlessui-react/src/components/menu/menu.tsx b/packages/@headlessui-react/src/components/menu/menu.tsx index dd733657d7..2bea2c8568 100644 --- a/packages/@headlessui-react/src/components/menu/menu.tsx +++ b/packages/@headlessui-react/src/components/menu/menu.tsx @@ -12,6 +12,7 @@ import { useId } from '../../hooks/use-id' import { Keys } from '../keyboard' import { Focus, calculateActiveIndex } from '../../utils/calculate-active-index' import { resolvePropValue } from '../../utils/resolve-prop-value' +import { isDisabledReactIssue7711 } from '../../utils/bugs' enum MenuStates { Open, @@ -413,7 +414,8 @@ function Item( }, [bag, id]) const handleClick = React.useCallback( - (event: { preventDefault: Function }) => { + (event: React.MouseEvent) => { + if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault() if (disabled) return event.preventDefault() dispatch({ type: ActionTypes.CloseMenu }) disposables().nextFrame(() => state.buttonRef.current?.focus({ preventScroll: true })) diff --git a/packages/@headlessui-react/src/components/switch/switch.tsx b/packages/@headlessui-react/src/components/switch/switch.tsx index b389b80c7d..7fd2024b7a 100644 --- a/packages/@headlessui-react/src/components/switch/switch.tsx +++ b/packages/@headlessui-react/src/components/switch/switch.tsx @@ -5,6 +5,7 @@ import { render } from '../../utils/render' import { useId } from '../../hooks/use-id' import { Keys } from '../keyboard' import { resolvePropValue } from '../../utils/resolve-prop-value' +import { isDisabledReactIssue7711 } from '../../utils/bugs' type StateDefinition = { switch: HTMLButtonElement | null @@ -84,6 +85,7 @@ export function Switch onChange(!checked), [onChange, checked]) const handleClick = React.useCallback( (event: React.MouseEvent) => { + if (isDisabledReactIssue7711(event.currentTarget)) return event.preventDefault() event.preventDefault() toggle() }, diff --git a/packages/@headlessui-react/src/utils/bugs.ts b/packages/@headlessui-react/src/utils/bugs.ts new file mode 100644 index 0000000000..712047796e --- /dev/null +++ b/packages/@headlessui-react/src/utils/bugs.ts @@ -0,0 +1,30 @@ +// See: https://github.com/facebook/react/issues/7711 +// See: https://github.com/facebook/react/pull/20612 +// See: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-disabled (2.) +export function isDisabledReactIssue7711(element: Element): boolean { + let parent = element.parentElement + let legend = null + + while (parent && !(parent instanceof HTMLFieldSetElement)) { + if (parent instanceof HTMLLegendElement) legend = parent + parent = parent.parentElement + } + + let isParentDisabled = parent?.getAttribute('disabled') === '' ?? false + if (isParentDisabled && isFirstLegend(legend)) return false + + return isParentDisabled +} + +function isFirstLegend(element: HTMLLegendElement | null): boolean { + if (!element) return false + + let previous = element.previousElementSibling + + while (previous !== null) { + if (previous instanceof HTMLLegendElement) return false + previous = previous.previousElementSibling + } + + return true +}