diff --git a/packages/mui-styled-engine/package.json b/packages/mui-styled-engine/package.json index c86f66b67f0f95..2cdea79bcec3ad 100644 --- a/packages/mui-styled-engine/package.json +++ b/packages/mui-styled-engine/package.json @@ -24,7 +24,7 @@ "url": "https://opencollective.com/mui-org" }, "scripts": { - "build": "code-infra build --flat --skipTsc", + "build": "code-infra build --flat", "release": "pnpm build && pnpm publish", "test": "pnpm --workspace-root test:unit --project \"*:@mui/styled-engine\"", "typescript": "tsc -p tsconfig.json", @@ -43,6 +43,7 @@ "@emotion/styled": "11.14.1", "@mui/styled-engine": "workspace:*", "@types/chai": "5.2.3", + "@types/prop-types": "15.7.15", "@types/react": "19.2.14", "chai": "6.2.2", "react": "19.2.6" @@ -69,7 +70,7 @@ "node": ">=14.0.0" }, "exports": { - ".": "./src/index.js", - "./*": "./src/*/index.js" + ".": "./src/index.ts", + "./*": "./src/*/index.ts" } } diff --git a/packages/mui-styled-engine/src/GlobalStyles/GlobalStyles.d.ts b/packages/mui-styled-engine/src/GlobalStyles/GlobalStyles.d.ts deleted file mode 100644 index 0d6af5c9d1d5f6..00000000000000 --- a/packages/mui-styled-engine/src/GlobalStyles/GlobalStyles.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as React from 'react'; -import { Interpolation } from '@emotion/react'; - -export interface GlobalStylesProps { - defaultTheme?: object | undefined; - styles: Interpolation; -} - -export default function GlobalStyles( - props: GlobalStylesProps, -): React.JSX.Element; diff --git a/packages/mui-styled-engine/src/GlobalStyles/GlobalStyles.js b/packages/mui-styled-engine/src/GlobalStyles/GlobalStyles.js deleted file mode 100644 index daf5648781e79e..00000000000000 --- a/packages/mui-styled-engine/src/GlobalStyles/GlobalStyles.js +++ /dev/null @@ -1,28 +0,0 @@ -'use client'; -import PropTypes from 'prop-types'; -import { Global } from '@emotion/react'; - -function isEmpty(obj) { - return obj === undefined || obj === null || Object.keys(obj).length === 0; -} - -export default function GlobalStyles(props) { - const { styles, defaultTheme = {} } = props; - - const globalStyles = - typeof styles === 'function' - ? (themeInput) => styles(isEmpty(themeInput) ? defaultTheme : themeInput) - : styles; - - return ; -} - -GlobalStyles.propTypes = { - defaultTheme: PropTypes.object, - styles: PropTypes.oneOfType([ - PropTypes.array, - PropTypes.string, - PropTypes.object, - PropTypes.func, - ]), -}; diff --git a/packages/mui-styled-engine/src/GlobalStyles/GlobalStyles.test.js b/packages/mui-styled-engine/src/GlobalStyles/GlobalStyles.test.tsx similarity index 93% rename from packages/mui-styled-engine/src/GlobalStyles/GlobalStyles.test.js rename to packages/mui-styled-engine/src/GlobalStyles/GlobalStyles.test.tsx index e84d022ec94d76..b56a5a2ddc51b8 100644 --- a/packages/mui-styled-engine/src/GlobalStyles/GlobalStyles.test.js +++ b/packages/mui-styled-engine/src/GlobalStyles/GlobalStyles.test.tsx @@ -36,7 +36,7 @@ describe('GlobalStyles', () => { it.skipIf(isJsdom())('should add global styles using function', function test() { render( - ({ span: { color: theme.color } })} /> + ({ span: { color: theme.color } })} /> Blue text , ); @@ -51,7 +51,7 @@ describe('GlobalStyles', () => { render( ({ span: { color: theme.color } })} + styles={(theme: any) => ({ span: { color: theme.color } })} />, ), ).not.to.throw(); diff --git a/packages/mui-styled-engine/src/GlobalStyles/GlobalStyles.tsx b/packages/mui-styled-engine/src/GlobalStyles/GlobalStyles.tsx new file mode 100644 index 00000000000000..5df1fb8879e130 --- /dev/null +++ b/packages/mui-styled-engine/src/GlobalStyles/GlobalStyles.tsx @@ -0,0 +1,39 @@ +'use client'; +import type * as React from 'react'; +import PropTypes from 'prop-types'; +import { Global, type Interpolation } from '@emotion/react'; + +export interface GlobalStylesProps { + defaultTheme?: object | undefined; + styles: Interpolation; +} + +function isEmpty(obj: object | null | undefined) { + return obj === undefined || obj === null || Object.keys(obj).length === 0; +} + +export default function GlobalStyles( + props: GlobalStylesProps, +): React.JSX.Element { + const { styles, defaultTheme = {} } = props; + + const globalStyles = + typeof styles === 'function' + ? (themeInput: Theme) => + (styles as (theme: Theme) => Interpolation)( + isEmpty(themeInput as object) ? (defaultTheme as Theme) : themeInput, + ) + : styles; + + return ; +} + +(GlobalStyles as any).propTypes /* remove-proptypes */ = { + defaultTheme: PropTypes.object, + styles: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.string, + PropTypes.object, + PropTypes.func, + ]), +}; diff --git a/packages/mui-styled-engine/src/GlobalStyles/index.d.ts b/packages/mui-styled-engine/src/GlobalStyles/index.d.ts deleted file mode 100644 index a815f5c4a572b2..00000000000000 --- a/packages/mui-styled-engine/src/GlobalStyles/index.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './GlobalStyles'; -export * from './GlobalStyles'; diff --git a/packages/mui-styled-engine/src/GlobalStyles/index.js b/packages/mui-styled-engine/src/GlobalStyles/index.ts similarity index 53% rename from packages/mui-styled-engine/src/GlobalStyles/index.js rename to packages/mui-styled-engine/src/GlobalStyles/index.ts index 48e20189092434..ce5e5f82dff052 100644 --- a/packages/mui-styled-engine/src/GlobalStyles/index.js +++ b/packages/mui-styled-engine/src/GlobalStyles/index.ts @@ -1 +1,2 @@ export { default } from './GlobalStyles'; +export type * from './GlobalStyles'; diff --git a/packages/mui-styled-engine/src/StyledEngineProvider/StyledEngineProvider.d.ts b/packages/mui-styled-engine/src/StyledEngineProvider/StyledEngineProvider.d.ts deleted file mode 100644 index ed7af7d5adefcd..00000000000000 --- a/packages/mui-styled-engine/src/StyledEngineProvider/StyledEngineProvider.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as React from 'react'; - -export interface StyledEngineProviderProps { - children?: React.ReactNode; - enableCssLayer?: boolean | undefined; - injectFirst?: boolean | undefined; -} - -export default function StyledEngineProvider(props: StyledEngineProviderProps): React.JSX.Element; diff --git a/packages/mui-styled-engine/src/StyledEngineProvider/StyledEngineProvider.test.js b/packages/mui-styled-engine/src/StyledEngineProvider/StyledEngineProvider.test.tsx similarity index 96% rename from packages/mui-styled-engine/src/StyledEngineProvider/StyledEngineProvider.test.js rename to packages/mui-styled-engine/src/StyledEngineProvider/StyledEngineProvider.test.tsx index b691c42cda2369..f1dd817c327efa 100644 --- a/packages/mui-styled-engine/src/StyledEngineProvider/StyledEngineProvider.test.js +++ b/packages/mui-styled-engine/src/StyledEngineProvider/StyledEngineProvider.test.tsx @@ -7,10 +7,10 @@ import { TEST_INTERNALS_DO_NOT_USE } from './StyledEngineProvider'; describe('[Emotion] StyledEngineProvider', () => { const { render } = createRenderer(); - let rule; + let rule: string | undefined; beforeAll(() => { - TEST_INTERNALS_DO_NOT_USE.insert = (...args) => { + TEST_INTERNALS_DO_NOT_USE.insert = (...args: any[]) => { rule = args[0]; }; }); diff --git a/packages/mui-styled-engine/src/StyledEngineProvider/StyledEngineProvider.js b/packages/mui-styled-engine/src/StyledEngineProvider/StyledEngineProvider.tsx similarity index 80% rename from packages/mui-styled-engine/src/StyledEngineProvider/StyledEngineProvider.js rename to packages/mui-styled-engine/src/StyledEngineProvider/StyledEngineProvider.tsx index 4a2af3ef79a953..d760e297473208 100644 --- a/packages/mui-styled-engine/src/StyledEngineProvider/StyledEngineProvider.js +++ b/packages/mui-styled-engine/src/StyledEngineProvider/StyledEngineProvider.tsx @@ -5,13 +5,24 @@ import { CacheProvider } from '@emotion/react'; import createCache from '@emotion/cache'; import { StyleSheet } from '@emotion/sheet'; +export interface StyledEngineProviderProps { + children?: React.ReactNode; + enableCssLayer?: boolean | undefined; + injectFirst?: boolean | undefined; +} + // To fix [Jest performance](https://github.com/mui/material-ui/issues/45638). const cacheMap = new Map(); // Need to add a private variable to test the generated CSS from Emotion, this is the simplest way to do it. // We can't test the CSS from `style` tag easily because the `speedy: true` (produce empty text content) is enabled by Emotion. // Even if we disable it, JSDOM needs extra configuration to be able to parse `@layer` CSS. -export const TEST_INTERNALS_DO_NOT_USE = { +/** + * @internal + */ +export const TEST_INTERNALS_DO_NOT_USE: { + insert?: ((rule: string, options?: unknown) => unknown) | undefined; +} = { /** * to intercept the generated CSS before inserting to the style tag, so that we can check the generated CSS. * @@ -27,7 +38,7 @@ export const TEST_INTERNALS_DO_NOT_USE = { // We might be able to remove this when this issue is fixed: // https://github.com/emotion-js/emotion/issues/2790 -const createEmotionCache = (options, CustomSheet) => { +const createEmotionCache = (options: any, CustomSheet: any) => { const cache = createCache(options); // Do the same as https://github.com/emotion-js/emotion/blob/main/packages/cache/src/index.js#L238-L245 @@ -43,7 +54,7 @@ const createEmotionCache = (options, CustomSheet) => { return cache; }; -let insertionPoint; +let insertionPoint: any; if (typeof document === 'object') { // Use `insertionPoint` over `prepend`(deprecated) because it can be controlled for GlobalStyles injection order // For more information, see https://github.com/mui/material-ui/issues/44597 @@ -59,7 +70,7 @@ if (typeof document === 'object') { } } -function getCache(injectFirst, enableCssLayer) { +function getCache(injectFirst?: boolean, enableCssLayer?: boolean) { if (injectFirst || enableCssLayer) { /** * This is for client-side apps only. @@ -67,14 +78,14 @@ function getCache(injectFirst, enableCssLayer) { * This is because the [sheet](https://github.com/emotion-js/emotion/blob/main/packages/react/src/global.js#L94-L99) does not consume the options. */ class MyStyleSheet extends StyleSheet { - insert(rule, options) { + insert(rule: string, options?: unknown): unknown { if (TEST_INTERNALS_DO_NOT_USE.insert) { return TEST_INTERNALS_DO_NOT_USE.insert(rule, options); } if (this.key && this.key.endsWith('global')) { this.before = insertionPoint; } - return super.insert(rule, options); + return (super.insert as (rule: string, options?: unknown) => unknown)(rule, options); } } const emotionCache = createEmotionCache( @@ -86,12 +97,12 @@ function getCache(injectFirst, enableCssLayer) { ); if (enableCssLayer) { const prevInsert = emotionCache.insert; - emotionCache.insert = (...args) => { + (emotionCache as any).insert = (...args: any[]) => { if (!args[1].styles.match(/^@layer\s+[^{]*$/)) { // avoid nested @layer args[1].styles = `@layer mui {${args[1].styles}}`; } - return prevInsert(...args); + return (prevInsert as (...insertArgs: any[]) => unknown)(...args); }; } return emotionCache; @@ -99,7 +110,7 @@ function getCache(injectFirst, enableCssLayer) { return undefined; } -export default function StyledEngineProvider(props) { +export default function StyledEngineProvider(props: StyledEngineProviderProps): React.JSX.Element { const { injectFirst, enableCssLayer, children } = props; const cache = React.useMemo(() => { const cacheKey = `${injectFirst}-${enableCssLayer}`; @@ -110,10 +121,12 @@ export default function StyledEngineProvider(props) { cacheMap.set(cacheKey, fresh); return fresh; }, [injectFirst, enableCssLayer]); - return cache ? {children} : children; + return ( + cache ? {children} : children + ) as React.JSX.Element; } -StyledEngineProvider.propTypes = { +(StyledEngineProvider as any).propTypes /* remove-proptypes */ = { /** * Your component tree. */ diff --git a/packages/mui-styled-engine/src/StyledEngineProvider/index.js b/packages/mui-styled-engine/src/StyledEngineProvider/index.js deleted file mode 100644 index 30cd6986a545fa..00000000000000 --- a/packages/mui-styled-engine/src/StyledEngineProvider/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './StyledEngineProvider'; diff --git a/packages/mui-styled-engine/src/StyledEngineProvider/index.d.ts b/packages/mui-styled-engine/src/StyledEngineProvider/index.ts similarity index 52% rename from packages/mui-styled-engine/src/StyledEngineProvider/index.d.ts rename to packages/mui-styled-engine/src/StyledEngineProvider/index.ts index 7fdd272b28e6ca..99ecbcdd5854ac 100644 --- a/packages/mui-styled-engine/src/StyledEngineProvider/index.d.ts +++ b/packages/mui-styled-engine/src/StyledEngineProvider/index.ts @@ -1,2 +1,2 @@ export { default } from './StyledEngineProvider'; -export * from './StyledEngineProvider'; +export type * from './StyledEngineProvider'; diff --git a/packages/mui-styled-engine/src/index.js b/packages/mui-styled-engine/src/index.js deleted file mode 100644 index 60431f3c814636..00000000000000 --- a/packages/mui-styled-engine/src/index.js +++ /dev/null @@ -1,50 +0,0 @@ -'use client'; -/* eslint-disable no-underscore-dangle */ -import emStyled from '@emotion/styled'; -import { serializeStyles as emSerializeStyles } from '@emotion/serialize'; - -export default function styled(tag, options) { - const stylesFactory = emStyled(tag, options); - - if (process.env.NODE_ENV !== 'production') { - return (...styles) => { - const component = typeof tag === 'string' ? `"${tag}"` : 'component'; - if (styles.length === 0) { - console.error( - [ - `MUI: Seems like you called \`styled(${component})()\` without a \`style\` argument.`, - 'You must provide a `styles` argument: `styled("div")(styleYouForgotToPass)`.', - ].join('\n'), - ); - } else if (styles.some((style) => style === undefined)) { - console.error( - `MUI: the styled(${component})(...args) API requires all its args to be defined.`, - ); - } - return stylesFactory(...styles); - }; - } - - return stylesFactory; -} - -// eslint-disable-next-line @typescript-eslint/naming-convention -export function internal_mutateStyles(tag, processor) { - // Emotion attaches all the styles as `__emotion_styles`. - // Ref: https://github.com/emotion-js/emotion/blob/16d971d0da229596d6bcc39d282ba9753c9ee7cf/packages/styled/src/base.js#L186 - if (Array.isArray(tag.__emotion_styles)) { - tag.__emotion_styles = processor(tag.__emotion_styles); - } -} - -// Emotion only accepts an array, but we want to avoid allocations -const wrapper = []; -// eslint-disable-next-line @typescript-eslint/naming-convention -export function internal_serializeStyles(styles) { - wrapper[0] = styles; - return emSerializeStyles(wrapper); -} - -export { ThemeContext, keyframes, css } from '@emotion/react'; -export { default as StyledEngineProvider } from './StyledEngineProvider'; -export { default as GlobalStyles } from './GlobalStyles'; diff --git a/packages/mui-styled-engine/src/index.d.ts b/packages/mui-styled-engine/src/index.ts similarity index 75% rename from packages/mui-styled-engine/src/index.d.ts rename to packages/mui-styled-engine/src/index.ts index 93aa5c0a244aa6..d76a00ab8d2f6f 100644 --- a/packages/mui-styled-engine/src/index.d.ts +++ b/packages/mui-styled-engine/src/index.ts @@ -1,21 +1,48 @@ -import * as CSS from 'csstype'; -import { StyledComponent, StyledOptions } from '@emotion/styled'; -import { PropsOf } from '@emotion/react'; - -export * from '@emotion/styled'; -export { default } from '@emotion/styled'; -export { ThemeContext, keyframes, css } from '@emotion/react'; - -export { default as StyledEngineProvider } from './StyledEngineProvider'; - -export { default as GlobalStyles } from './GlobalStyles'; -export * from './GlobalStyles'; +'use client'; +/* eslint-disable no-underscore-dangle */ +import emStyled, { + type CreateStyled, + type StyledComponent, + type StyledOptions, +} from '@emotion/styled'; +import { serializeStyles as emSerializeStyles } from '@emotion/serialize'; +import type * as CSS from 'csstype'; +import { type PropsOf } from '@emotion/react'; + +// Re-export the public type surface of `@emotion/styled`. Explicit names rather +// than `export type *`: an `export *` (even type-only) is disallowed by the +// `'use client'` Next.js lint rule. Names locally redeclared below intentionally +// shadow Emotion's and are not re-exported here. +export type { CreateStyled, StyledComponent, StyledOptions, StyledTags } from '@emotion/styled'; + +function styled(tag: React.ElementType, options?: StyledOptions) { + // Emotion's `styled` overloads each expect a specific tag/component shape; + // none accept the broadened `React.ElementType` union, so cast the passthrough. + const stylesFactory = emStyled(tag as any, options); + + if (process.env.NODE_ENV !== 'production') { + return (...styles: any[]) => { + const component = typeof tag === 'string' ? `"${tag}"` : 'component'; + if (styles.length === 0) { + console.error( + [ + `MUI: Seems like you called \`styled(${component})()\` without a \`style\` argument.`, + 'You must provide a `styles` argument: `styled("div")(styleYouForgotToPass)`.', + ].join('\n'), + ); + } else if (styles.some((style) => style === undefined)) { + console.error( + `MUI: the styled(${component})(...args) API requires all its args to be defined.`, + ); + } + return stylesFactory(...styles); + }; + } + + return stylesFactory; +} -export type MUIStyledComponent< - ComponentProps extends {}, - SpecificComponentProps extends {} = {}, - JSXProps extends {} = {}, -> = StyledComponent; +export default styled as unknown as CreateStyled; /** * For internal usage in `@mui/system` package @@ -24,10 +51,32 @@ export type MUIStyledComponent< export function internal_mutateStyles( tag: React.ElementType, processor: (styles: any) => any, -): void; +): void { + // Emotion attaches all the styles as `__emotion_styles`. + // Ref: https://github.com/emotion-js/emotion/blob/16d971d0da229596d6bcc39d282ba9753c9ee7cf/packages/styled/src/base.js#L186 + if (Array.isArray((tag as any).__emotion_styles)) { + (tag as any).__emotion_styles = processor((tag as any).__emotion_styles); + } +} +// Emotion only accepts an array, but we want to avoid allocations +const wrapper: any[] = []; // eslint-disable-next-line @typescript-eslint/naming-convention -export function internal_serializeStyles

(styles: Interpolation

): object; +export function internal_serializeStyles

(styles: Interpolation

): object { + wrapper[0] = styles; + return emSerializeStyles(wrapper); +} + +export { ThemeContext, keyframes, css } from '@emotion/react'; +export { default as StyledEngineProvider } from './StyledEngineProvider'; +export { default as GlobalStyles } from './GlobalStyles'; +export type { GlobalStylesProps } from './GlobalStyles'; + +export type MUIStyledComponent< + ComponentProps extends {}, + SpecificComponentProps extends {} = {}, + JSXProps extends {} = {}, +> = StyledComponent; export interface SerializedStyles { name: string; @@ -126,7 +175,7 @@ export type Interpolation = | ArrayInterpolation | FunctionInterpolation; -export function shouldForwardProp(propName: PropertyKey): boolean; +export declare function shouldForwardProp(propName: PropertyKey): boolean; /** Same as StyledOptions but shouldForwardProp must be a type guard */ export interface FilteringStyledOptions { diff --git a/packages/mui-styled-engine/src/styled.test.js b/packages/mui-styled-engine/src/styled.test.ts similarity index 100% rename from packages/mui-styled-engine/src/styled.test.js rename to packages/mui-styled-engine/src/styled.test.ts diff --git a/packages/mui-styled-engine/tsconfig.build.json b/packages/mui-styled-engine/tsconfig.build.json new file mode 100644 index 00000000000000..fb7bbbba3f40ca --- /dev/null +++ b/packages/mui-styled-engine/tsconfig.build.json @@ -0,0 +1,17 @@ +// This config is for emitting declarations (.d.ts) only +// Actual .ts source files are transpiled via babel +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "composite": true, + "declaration": true, + "noEmit": false, + "emitDeclarationOnly": true, + "outDir": "build", + "rootDir": "./src", + "stripInternal": true, + "types": ["@mui/internal-code-infra/build-env", "react"] + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["src/**/*.test.ts*", "src/**/*.spec.ts*"] +} diff --git a/packages/mui-styled-engine/tsconfig.json b/packages/mui-styled-engine/tsconfig.json index cbfffafe5ca1e7..fc9e91137dd7f5 100644 --- a/packages/mui-styled-engine/tsconfig.json +++ b/packages/mui-styled-engine/tsconfig.json @@ -1,5 +1,9 @@ { "extends": "../../tsconfig.json", - + "compilerOptions": { + "allowJs": false, + "skipLibCheck": true, + "types": ["react", "vitest/globals", "@mui/internal-code-infra/build-env"] + }, "include": ["src/**/*"] } diff --git a/packages/mui-system/tsconfig.build.json b/packages/mui-system/tsconfig.build.json index 7696cbeba02fc7..b143a0736570c0 100644 --- a/packages/mui-system/tsconfig.build.json +++ b/packages/mui-system/tsconfig.build.json @@ -12,5 +12,8 @@ }, "include": ["src/**/*.ts*"], "exclude": ["src/**/*.spec.ts*", "src/**/*.test.ts*"], - "references": [{ "path": "../mui-utils/tsconfig.build.json" }] + "references": [ + { "path": "../mui-styled-engine/tsconfig.build.json" }, + { "path": "../mui-utils/tsconfig.build.json" } + ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c47f093ca5994..9639a43d79a965 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1317,6 +1317,9 @@ importers: '@types/chai': specifier: 5.2.3 version: 5.2.3 + '@types/prop-types': + specifier: 15.7.15 + version: 15.7.15 '@types/react': specifier: 19.2.14 version: 19.2.14