UNPKG

@mui/material

Version:

Material UI is an open-source React component library that implements Google's Material Design. It's comprehensive and can be used in production out of the box.

85 lines (79 loc) 4.23 kB
import deepmerge from '@mui/utils/deepmerge'; const focusVisibleOffsetVar = '--_focusVisible-offset'; const focusVisibleBehaviorVar = '--_focusVisible-behavior'; const focusVisibleShadowVar = '--_focusVisible-shadow'; const offsetValue = `var(${focusVisibleOffsetVar}, 1)`; const behaviorValue = `var(${focusVisibleBehaviorVar}, )`; // Spread on a root whose ring must stay outset: the inset vars inherit, so a clip-prone ancestor // would otherwise inset a descendant's ring too. export const outsetFocusRing = { [focusVisibleOffsetVar]: 1, [focusVisibleBehaviorVar]: 'initial' // reverts the var to guaranteed-invalid so `var(--_focusVisible-behavior, )` falls back to empty — there is no explicit `outset` keyword. }; // Used by the colored-background surfaces (AppBar, Alert, SnackbarContent) to make the focus visible appear through box-shadow export function applyChildrenFocusVisible(color) { return { [focusVisibleShadowVar]: color }; } // Clip-prone components (Tab, MenuItem, …) spread this on their root to inset the ring, so an // `overflow: hidden` ancestor cannot clip it — without the component knowing the ring width. // `offset` multiplies the ring's own outlineOffset, so 1 = mirror it inward. export function applyInsetFocusVisible(offset) { return { [focusVisibleOffsetVar]: -offset, [focusVisibleBehaviorVar]: 'inset' }; } // Merge the raw `focusVisible` input across `createTheme(options, ...args)` with the theme's own // deep-merge semantics. The single-key wrapper is load-bearing — a raw value hits deepmerge's // top-level plain-object guard (`deepmerge(true, obj)` drops the object). export function mergeFocusVisibleInput(optionsFocusVisible, args) { return args.reduce((acc, arg) => arg && 'focusVisible' in arg ? deepmerge(acc, { focusVisible: arg.focusVisible }) : acc, { focusVisible: optionsFocusVisible }).focusVisible; } /** * Whether an input is this module's own output. Only `wireFocusVisibleVars` emits the offset calc, * so it identifies a ring fed back in by `createTheme(existingTheme, …)`. */ export function isResolvedFocusVisible(input) { return input != null && typeof input === 'object' && typeof input.outlineOffset === 'string' && input.outlineOffset.includes(focusVisibleOffsetVar); } /** * Resolve the opt-in ring, wiring in the private inset vars. `outlineColor` is the caller's default * — a hex, the palette var, or a scheme's primary — overridden by a user-provided `outlineColor`. */ export function resolveFocusVisible(input, outlineColor) { return wireFocusVisibleVars({ outlineStyle: 'solid', outlineColor, outlineWidth: 2, outlineOffset: 2, // invisible shadow for parent component with solid background (AppBar, Snackbar, Alert) can control the ring color. boxShadow: `var(${focusVisibleShadowVar}, 0 0)`, ...(input === true ? null : input) }); } /** * Wire the private inset vars into a resolved `theme.focusVisible` so a custom `outlineOffset` or * `boxShadow` insets automatically on clip-prone components. Mutates and returns the object. */ export function wireFocusVisibleVars(resolved) { // Multiply the offset by the sign var so it flips to inset on clip-prone components. Skip if // already wired: re-composing a resolved theme must not wrap the calc twice and invert the sign. const offset = resolved.outlineOffset ?? 0; if (typeof offset !== 'string' || !offset.includes(focusVisibleOffsetVar)) { const offsetPx = typeof offset === 'number' ? `${offset}px` : offset; resolved.outlineOffset = `calc(${offsetValue} * ${offsetPx})`; } // Prefix a box-shadow with the behavior var so it insets there too, unless it already opts in. // Standalone keywords are complete values — prefixing would produce an invalid `inset none`. const standaloneBoxShadows = new Set(['none', 'initial', 'inherit', 'unset', 'revert', 'revert-layer']); if (typeof resolved.boxShadow === 'string' && !standaloneBoxShadows.has(resolved.boxShadow.trim().toLowerCase()) && !/\binset\b/.test(resolved.boxShadow) && !resolved.boxShadow.includes(focusVisibleBehaviorVar)) { resolved.boxShadow = `${behaviorValue} ${resolved.boxShadow}`; } return resolved; }