diff --git a/change/@fluentui-react-tabster-8d4667d2-30a7-4c68-bbcc-7b6331016905.json b/change/@fluentui-react-tabster-8d4667d2-30a7-4c68-bbcc-7b6331016905.json new file mode 100644 index 00000000000000..8153dc30307629 --- /dev/null +++ b/change/@fluentui-react-tabster-8d4667d2-30a7-4c68-bbcc-7b6331016905.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "feat: use browser `:focus-visible` if supported", + "packageName": "@fluentui/react-tabster", + "email": "lingfangao@hotmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-tabster/etc/react-tabster.api.md b/packages/react-components/react-tabster/etc/react-tabster.api.md index ac4ae061269cc6..83014d0a75f8c8 100644 --- a/packages/react-components/react-tabster/etc/react-tabster.api.md +++ b/packages/react-components/react-tabster/etc/react-tabster.api.md @@ -67,10 +67,14 @@ export const useFocusFinders: () => { findPrevFocusable: (currentElement: HTMLElement, options?: Pick) => HTMLElement | null | undefined; }; -// @public (undocumented) +// Warning: (ae-internal-missing-underscore) The name "useFocusVisible" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) export function useFocusVisible(): React_2.RefObject; -// @public +// Warning: (ae-internal-missing-underscore) The name "useFocusWithin" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal export function useFocusWithin(): React_2.RefObject; // @public diff --git a/packages/react-components/react-tabster/src/focus/createCustomFocusIndicatorStyle.ts b/packages/react-components/react-tabster/src/focus/createCustomFocusIndicatorStyle.ts index 31c1e4564f2bde..86dbf3c3f9a4e3 100644 --- a/packages/react-components/react-tabster/src/focus/createCustomFocusIndicatorStyle.ts +++ b/packages/react-components/react-tabster/src/focus/createCustomFocusIndicatorStyle.ts @@ -28,6 +28,8 @@ export const createCustomFocusIndicatorStyle = ( [`:global(.fui-FluentProvider)`]: { [`& .${FOCUS_VISIBLE_CLASS}`]: style, }, + + ':focus-visible': style, }), ...(selector === 'focus-within' && { [`:global(.fui-FluentProvider)`]: { diff --git a/packages/react-components/react-tabster/src/focus/focusVisiblePolyfill.ts b/packages/react-components/react-tabster/src/focus/focusVisiblePolyfill.ts index 28a62fa8f32f22..79f81be18991ad 100644 --- a/packages/react-components/react-tabster/src/focus/focusVisiblePolyfill.ts +++ b/packages/react-components/react-tabster/src/focus/focusVisiblePolyfill.ts @@ -20,8 +20,7 @@ type HTMLElementWithFocusVisibleScope = { } & HTMLElement; export function applyFocusVisiblePolyfill(scope: HTMLElement, win: Window): () => void { - if (alreadyInScope(scope)) { - // Focus visible polyfill already applied at this scope + if (alreadyInScope(scope) || browserSupportsFocusVisible()) { return () => undefined; } @@ -103,3 +102,7 @@ function alreadyInScope(el: HTMLElement | null | undefined): boolean { return alreadyInScope(el?.parentElement); } + +function browserSupportsFocusVisible() { + return CSS.supports('selector(:focus-visible)'); +} diff --git a/packages/react-components/react-tabster/src/hooks/useFocusVisible.ts b/packages/react-components/react-tabster/src/hooks/useFocusVisible.ts index f32d148eef862d..3b62a5058775e3 100644 --- a/packages/react-components/react-tabster/src/hooks/useFocusVisible.ts +++ b/packages/react-components/react-tabster/src/hooks/useFocusVisible.ts @@ -2,6 +2,10 @@ import * as React from 'react'; import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; import { applyFocusVisiblePolyfill } from '../focus/focusVisiblePolyfill'; +/** + * @internal + * @returns ref to the container element whose children have `:focus-visible` styles + */ export function useFocusVisible() { const { targetDocument } = useFluent(); const scopeRef = React.useRef(null); diff --git a/packages/react-components/react-tabster/src/hooks/useFocusWithin.ts b/packages/react-components/react-tabster/src/hooks/useFocusWithin.ts index 8e3a20077ffebe..ab52d3435befb7 100644 --- a/packages/react-components/react-tabster/src/hooks/useFocusWithin.ts +++ b/packages/react-components/react-tabster/src/hooks/useFocusWithin.ts @@ -3,6 +3,7 @@ import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts import { applyFocusWithinPolyfill } from '../focus/focusWithinPolyfill'; /** + * @internal * A ponyfill that allows `:focus-within` to support visibility based on keyboard/mouse navigation * like `:focus-visible` https://github.com/WICG/focus-visible/issues/151 * @returns ref to the element that uses `:focus-within` styles