UNPKG

@mui/styles

Version:

MUI Styles - The legacy JSS-based styling solution of Material UI.

232 lines (229 loc) 7.6 kB
import * as React from 'react'; import { getDynamicStyles } from 'jss'; import mergeClasses from "../mergeClasses/index.js"; import multiKeyStore from "./multiKeyStore.js"; import useTheme from "../useTheme/index.js"; import { StylesContext } from "../StylesProvider/index.js"; import { increment } from "./indexCounter.js"; import getStylesCreator from "../getStylesCreator/index.js"; import noopTheme from "../getStylesCreator/noopTheme.js"; function getClasses({ state, stylesOptions }, classes, Component) { if (stylesOptions.disableGeneration) { return classes || {}; } if (!state.cacheClasses) { state.cacheClasses = { // Cache for the finalized classes value. value: null, // Cache for the last used classes prop pointer. lastProp: null, // Cache for the last used rendered classes pointer. lastJSS: {} }; } // Tracks if either the rendered classes or classes prop has changed, // requiring the generation of a new finalized classes object. let generate = false; if (state.classes !== state.cacheClasses.lastJSS) { state.cacheClasses.lastJSS = state.classes; generate = true; } if (classes !== state.cacheClasses.lastProp) { state.cacheClasses.lastProp = classes; generate = true; } if (generate) { state.cacheClasses.value = mergeClasses({ baseClasses: state.cacheClasses.lastJSS, newClasses: classes, Component }); } return state.cacheClasses.value; } function attach({ state, theme, stylesOptions, stylesCreator, name }, props) { if (stylesOptions.disableGeneration) { return; } let sheetManager = multiKeyStore.get(stylesOptions.sheetsManager, stylesCreator, theme); if (!sheetManager) { sheetManager = { refs: 0, staticSheet: null, dynamicStyles: null }; multiKeyStore.set(stylesOptions.sheetsManager, stylesCreator, theme, sheetManager); } const options = { ...stylesCreator.options, ...stylesOptions, theme, flip: typeof stylesOptions.flip === 'boolean' ? stylesOptions.flip : theme.direction === 'rtl' }; options.generateId = options.serverGenerateClassName || options.generateClassName; const sheetsRegistry = stylesOptions.sheetsRegistry; if (sheetManager.refs === 0) { let staticSheet; if (stylesOptions.sheetsCache) { staticSheet = multiKeyStore.get(stylesOptions.sheetsCache, stylesCreator, theme); } const styles = stylesCreator.create(theme, name); if (!staticSheet) { staticSheet = stylesOptions.jss.createStyleSheet(styles, { link: false, ...options }); staticSheet.attach(); if (stylesOptions.sheetsCache) { multiKeyStore.set(stylesOptions.sheetsCache, stylesCreator, theme, staticSheet); } } if (sheetsRegistry) { sheetsRegistry.add(staticSheet); } sheetManager.staticSheet = staticSheet; sheetManager.dynamicStyles = getDynamicStyles(styles); } if (sheetManager.dynamicStyles) { const dynamicSheet = stylesOptions.jss.createStyleSheet(sheetManager.dynamicStyles, { link: true, ...options }); dynamicSheet.update(props); dynamicSheet.attach(); state.dynamicSheet = dynamicSheet; state.classes = mergeClasses({ baseClasses: sheetManager.staticSheet.classes, newClasses: dynamicSheet.classes }); if (sheetsRegistry) { sheetsRegistry.add(dynamicSheet); } } else { state.classes = sheetManager.staticSheet.classes; } sheetManager.refs += 1; } function update({ state }, props) { if (state.dynamicSheet) { state.dynamicSheet.update(props); } } function detach({ state, theme, stylesOptions, stylesCreator }) { if (stylesOptions.disableGeneration) { return; } const sheetManager = multiKeyStore.get(stylesOptions.sheetsManager, stylesCreator, theme); sheetManager.refs -= 1; const sheetsRegistry = stylesOptions.sheetsRegistry; if (sheetManager.refs === 0) { multiKeyStore.delete(stylesOptions.sheetsManager, stylesCreator, theme); stylesOptions.jss.removeStyleSheet(sheetManager.staticSheet); if (sheetsRegistry) { sheetsRegistry.remove(sheetManager.staticSheet); } } if (state.dynamicSheet) { stylesOptions.jss.removeStyleSheet(state.dynamicSheet); if (sheetsRegistry) { sheetsRegistry.remove(state.dynamicSheet); } } } function useSynchronousEffect(func, values) { const key = React.useRef([]); let output; // Store "generation" key. Just returns a new object every time // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler const currentKey = React.useMemo(() => ({}), values); // eslint-disable-line react-hooks/exhaustive-deps // "the first render", or "memo dropped the value" if (key.current !== currentKey) { key.current = currentKey; output = func(); } React.useEffect(() => () => { if (output) { output(); } }, [currentKey] // eslint-disable-line react-hooks/exhaustive-deps ); } export default function makeStyles(stylesOrCreator, options = {}) { const { // alias for classNamePrefix, if provided will listen to theme (required for theme.components[name].styleOverrides) name, // Help with debuggability. classNamePrefix: classNamePrefixOption, Component, defaultTheme = noopTheme, ...stylesOptions2 } = options; const stylesCreator = getStylesCreator(stylesOrCreator); const classNamePrefix = name || classNamePrefixOption || 'makeStyles'; stylesCreator.options = { index: increment(), name, meta: classNamePrefix, classNamePrefix }; const useStyles = (props = {}) => { const theme = useTheme() || defaultTheme; const stylesOptions = { ...React.useContext(StylesContext), ...stylesOptions2 }; const instance = React.useRef(); const shouldUpdate = React.useRef(); useSynchronousEffect(() => { const current = { name, state: {}, stylesCreator, stylesOptions, theme }; attach(current, props); shouldUpdate.current = false; instance.current = current; return () => { detach(current); }; }, [theme, stylesCreator]); React.useEffect(() => { if (shouldUpdate.current) { update(instance.current, props); } shouldUpdate.current = true; }); const classes = getClasses(instance.current, props.classes, Component); if (process.env.NODE_ENV !== 'production') { // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/rules-of-hooks -- process.env never changes React.useDebugValue(classes); } if (process.env.NODE_ENV !== 'production') { const supportedComponents = ['MuiAvatar', 'MuiBadge', 'MuiButton', 'MuiButtonGroup', 'MuiChip', 'MuiDivider', 'MuiFab', 'MuiPaper', 'MuiToolbar', 'MuiTypography', 'MuiAlert', 'MuiPagination', 'MuiPaginationItem', 'MuiSkeleton', 'MuiTimelineDot']; if (name && supportedComponents.indexOf(name) >= 0 && props.variant && !classes[props.variant]) { console.error([`MUI: You are using a variant value \`${props.variant}\` for which you didn't define styles.`, `Please create a new variant matcher in your theme for this variant. To learn more about matchers visit https://mui.com/r/custom-component-variants.`].join('\n')); } } return classes; }; return useStyles; }