diff --git a/components/_util/cssinjs/index.ts b/components/_util/cssinjs/index.ts index 49d0443b5..a22943787 100644 --- a/components/_util/cssinjs/index.ts +++ b/components/_util/cssinjs/index.ts @@ -3,14 +3,14 @@ import type { CSSInterpolation, CSSObject } from './hooks/useStyleRegister'; import useStyleRegister, { extractStyle } from './hooks/useStyleRegister'; import Keyframes from './Keyframes'; import type { Linter } from './linters'; -import { legacyNotSelectorLinter, logicalPropertiesLinter } from './linters'; +import { legacyNotSelectorLinter, logicalPropertiesLinter, parentSelectorLinter } from './linters'; import type { StyleContextProps, StyleProviderProps } from './StyleContext'; import { createCache, useStyleInject, useStyleProvider, StyleProvider } from './StyleContext'; import type { DerivativeFunc, TokenType } from './theme'; import { createTheme, Theme } from './theme'; import type { Transformer } from './transformers/interface'; import legacyLogicalPropertiesTransformer from './transformers/legacyLogicalProperties'; - +import px2remTransformer from './transformers/px2rem'; const cssinjs = { Theme, createTheme, @@ -24,10 +24,12 @@ const cssinjs = { // Transformer legacyLogicalPropertiesTransformer, + px2remTransformer, // Linters logicalPropertiesLinter, legacyNotSelectorLinter, + parentSelectorLinter, // cssinjs StyleProvider, @@ -45,10 +47,12 @@ export { // Transformer legacyLogicalPropertiesTransformer, + px2remTransformer, // Linters logicalPropertiesLinter, legacyNotSelectorLinter, + parentSelectorLinter, // cssinjs StyleProvider, diff --git a/components/_util/cssinjs/linters/index.ts b/components/_util/cssinjs/linters/index.ts index a7a3ee100..ae7d8cc9a 100644 --- a/components/_util/cssinjs/linters/index.ts +++ b/components/_util/cssinjs/linters/index.ts @@ -3,3 +3,4 @@ export { default as hashedAnimationLinter } from './hashedAnimationLinter'; export type { Linter } from './interface'; export { default as legacyNotSelectorLinter } from './legacyNotSelectorLinter'; export { default as logicalPropertiesLinter } from './logicalPropertiesLinter'; +export { default as parentSelectorLinter } from './parentSelectorLinter'; diff --git a/components/_util/cssinjs/linters/parentSelectorLinter.ts b/components/_util/cssinjs/linters/parentSelectorLinter.ts new file mode 100644 index 000000000..7a061ce8a --- /dev/null +++ b/components/_util/cssinjs/linters/parentSelectorLinter.ts @@ -0,0 +1,15 @@ +import type { Linter } from '..'; +import { lintWarning } from './utils'; + +const linter: Linter = (_key, _value, info) => { + if ( + info.parentSelectors.some(selector => { + const selectors = selector.split(','); + return selectors.some(item => item.split('&').length > 2); + }) + ) { + lintWarning('Should not use more than one `&` in a selector.', info); + } +}; + +export default linter; diff --git a/components/_util/cssinjs/transformers/px2rem.ts b/components/_util/cssinjs/transformers/px2rem.ts new file mode 100644 index 000000000..328cfbc42 --- /dev/null +++ b/components/_util/cssinjs/transformers/px2rem.ts @@ -0,0 +1,76 @@ +/** + * respect https://github.com/cuth/postcss-pxtorem + */ +import unitless from '@emotion/unitless'; +import type { CSSObject } from '..'; +import type { Transformer } from './interface'; + +interface Options { + /** + * The root font size. + * @default 16 + */ + rootValue?: number; + /** + * The decimal numbers to allow the REM units to grow to. + * @default 5 + */ + precision?: number; + /** + * Whether to allow px to be converted in media queries. + * @default false + */ + mediaQuery?: boolean; +} + +const pxRegex = /url\([^)]+\)|var\([^)]+\)|(\d*\.?\d+)px/g; + +function toFixed(number: number, precision: number) { + const multiplier = Math.pow(10, precision + 1), + wholeNumber = Math.floor(number * multiplier); + return (Math.round(wholeNumber / 10) * 10) / multiplier; +} + +const transform = (options: Options = {}): Transformer => { + const { rootValue = 16, precision = 5, mediaQuery = false } = options; + + const pxReplace = (m: string, $1: any) => { + if (!$1) return m; + const pixels = parseFloat($1); + // covenant: pixels <= 1, not transform to rem @zombieJ + if (pixels <= 1) return m; + const fixedVal = toFixed(pixels / rootValue, precision); + return `${fixedVal}rem`; + }; + + const visit = (cssObj: CSSObject): CSSObject => { + const clone: CSSObject = { ...cssObj }; + + Object.entries(cssObj).forEach(([key, value]) => { + if (typeof value === 'string' && value.includes('px')) { + const newValue = value.replace(pxRegex, pxReplace); + clone[key] = newValue; + } + + // no unit + if (!unitless[key] && typeof value === 'number' && value !== 0) { + clone[key] = `${value}px`.replace(pxRegex, pxReplace); + } + + // Media queries + const mergedKey = key.trim(); + if (mergedKey.startsWith('@') && mergedKey.includes('px') && mediaQuery) { + const newKey = key.replace(pxRegex, pxReplace); + + clone[newKey] = clone[key]; + delete clone[key]; + } + }); + + return clone; + }; + + return { visit }; +}; + +export default transform;