mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Upgrade: Migrate spacing scale (#14905)
This PR adds migrations for the recent changes to the `--spacing` scale done in #12263. There are a few steps that we do to ensure we have the best upgrade experience: - If you are overwriting the `spacing` theme with custom values, we now check if the new values are multiplies of the default spacing scale. When they are, we can safely remove the overwrite. - If you are extending the `spacing` theme, we will unset the default `--spacing` scale and only use the values you provided. - Any `theme()` function calls are replaced with `calc(var(--spacing) * multiplier)` unless the values are extending the default scale. One caveat here is for `theme()` key which can not be replaced with `var()` (e.g. in `@media` attribute positions). These will not be able to be replaced with `calc()` either so the following needs to stay unmigrated: ```css @media (max-width: theme(spacing.96)) { .foo { color: red; } } ``` ## Test plan We are mainly testing two scenarios: The JS config _extends_ the `spacing` namespace and the JS config _overwrites_ the `spacing` namespace. For both cases we have added an integration test each to ensure this works as expected. The test contains a mixture of keys (some of it matching the default multiples, some don't, some have different scales, and some use non-numeric identifiers). In addition to asserting on the created CSS `@theme`, we also ensure that `theme()` calls are properly replaced. --------- Co-authored-by: Adam Wathan <adam.wathan@gmail.com> Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
This commit is contained in:
parent
28e46badf7
commit
95c4877200
@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- _Upgrade (experimental)_: Rename `drop-shadow` to `drop-shadow-sm` and `drop-shadow-sm` to `drop-shadow-xs` ([#14875](https://github.com/tailwindlabs/tailwindcss/pull/14875))
|
||||
- _Upgrade (experimental)_: Rename `rounded` to `rounded-sm` and `rounded-sm` to `rounded-xs` ([#14875](https://github.com/tailwindlabs/tailwindcss/pull/14875))
|
||||
- _Upgrade (experimental)_: Rename `blur` to `blur-sm` and `blur-sm` to `blur-xs` ([#14875](https://github.com/tailwindlabs/tailwindcss/pull/14875))
|
||||
- _Upgrade (experimental)_: Migrate `theme()` usage and JS config files to use the new `--spacing` multiplier where possible ([#14905](https://github.com/tailwindlabs/tailwindcss/pull/14905))
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@ -1415,4 +1415,260 @@ describe('border compatibility', () => {
|
||||
`)
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'migrates extended spacing keys',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
}
|
||||
}
|
||||
`,
|
||||
'tailwind.config.ts': ts`
|
||||
import { type Config } from 'tailwindcss'
|
||||
|
||||
export default {
|
||||
content: ['./src/**/*.html'],
|
||||
theme: {
|
||||
extend: {
|
||||
spacing: {
|
||||
2: '0.5rem',
|
||||
4.5: '1.125rem',
|
||||
5.5: '1.375em', // Units are different from --spacing scale
|
||||
13: '3.25rem',
|
||||
100: '100px',
|
||||
miami: '1337px',
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Config
|
||||
`,
|
||||
'src/input.css': css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.container {
|
||||
width: theme(spacing.2);
|
||||
width: theme(spacing[4.5]);
|
||||
width: theme(spacing[5.5]);
|
||||
width: theme(spacing[13]);
|
||||
width: theme(spacing[100]);
|
||||
width: theme(spacing.miami);
|
||||
}
|
||||
`,
|
||||
'src/index.html': html`
|
||||
<div
|
||||
class="[width:theme(spacing.2)]
|
||||
[width:theme(spacing[4.5])]
|
||||
[width:theme(spacing[5.5])]
|
||||
[width:theme(spacing[13])]
|
||||
[width:theme(spacing[100])]
|
||||
[width:theme(spacing.miami)]"
|
||||
></div>
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ exec, fs }) => {
|
||||
await exec('npx @tailwindcss/upgrade')
|
||||
|
||||
expect(await fs.dumpFiles('src/**/*.{css,html}')).toMatchInlineSnapshot(`
|
||||
"
|
||||
--- src/index.html ---
|
||||
<div
|
||||
class="[width:calc(var(--spacing)*2)]
|
||||
[width:calc(var(--spacing)*4.5)]
|
||||
[width:var(--spacing-5_5)]
|
||||
[width:calc(var(--spacing)*13)]
|
||||
[width:var(--spacing-100)]
|
||||
[width:var(--spacing-miami)]"
|
||||
></div>
|
||||
|
||||
--- src/input.css ---
|
||||
@import 'tailwindcss';
|
||||
|
||||
@theme {
|
||||
--spacing-100: 100px;
|
||||
--spacing-5_5: 1.375em;
|
||||
--spacing-miami: 1337px;
|
||||
}
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
width: calc(var(--spacing) * 2);
|
||||
width: calc(var(--spacing) * 4.5);
|
||||
width: var(--spacing-5_5);
|
||||
width: calc(var(--spacing) * 13);
|
||||
width: var(--spacing-100);
|
||||
width: var(--spacing-miami);
|
||||
}
|
||||
"
|
||||
`)
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'retains overwriting spacing scale',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
}
|
||||
}
|
||||
`,
|
||||
'tailwind.config.ts': ts`
|
||||
import { type Config } from 'tailwindcss'
|
||||
|
||||
export default {
|
||||
content: ['./src/**/*.html'],
|
||||
theme: {
|
||||
spacing: {
|
||||
2: '0.5rem',
|
||||
4.5: '1.125rem',
|
||||
5.5: '1.375em',
|
||||
13: '3.25rem',
|
||||
100: '100px',
|
||||
miami: '1337px',
|
||||
},
|
||||
},
|
||||
} satisfies Config
|
||||
`,
|
||||
'src/input.css': css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.container {
|
||||
width: theme(spacing.2);
|
||||
width: theme(spacing[4.5]);
|
||||
width: theme(spacing[5.5]);
|
||||
width: theme(spacing[13]);
|
||||
width: theme(spacing[100]);
|
||||
width: theme(spacing.miami);
|
||||
}
|
||||
`,
|
||||
'src/index.html': html`
|
||||
<div
|
||||
class="[width:theme(spacing.2)]
|
||||
[width:theme(spacing[4.5])]
|
||||
[width:theme(spacing[5.5])]
|
||||
[width:theme(spacing[13])]
|
||||
[width:theme(spacing[100])]
|
||||
[width:theme(spacing.miami)]"
|
||||
></div>
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ exec, fs }) => {
|
||||
await exec('npx @tailwindcss/upgrade')
|
||||
|
||||
expect(await fs.dumpFiles('src/**/*.{css,html}')).toMatchInlineSnapshot(`
|
||||
"
|
||||
--- src/index.html ---
|
||||
<div
|
||||
class="[width:var(--spacing-2)]
|
||||
[width:var(--spacing-4_5)]
|
||||
[width:var(--spacing-5_5)]
|
||||
[width:var(--spacing-13)]
|
||||
[width:var(--spacing-100)]
|
||||
[width:var(--spacing-miami)]"
|
||||
></div>
|
||||
|
||||
--- src/input.css ---
|
||||
@import 'tailwindcss';
|
||||
|
||||
@theme {
|
||||
--spacing-*: initial;
|
||||
--spacing-2: 0.5rem;
|
||||
--spacing-13: 3.25rem;
|
||||
--spacing-100: 100px;
|
||||
--spacing-4_5: 1.125rem;
|
||||
--spacing-5_5: 1.375em;
|
||||
--spacing-miami: 1337px;
|
||||
}
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
width: var(--spacing-2);
|
||||
width: var(--spacing-4_5);
|
||||
width: var(--spacing-5_5);
|
||||
width: var(--spacing-13);
|
||||
width: var(--spacing-100);
|
||||
width: var(--spacing-miami);
|
||||
}
|
||||
"
|
||||
`)
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
@ -12,10 +12,11 @@ import {
|
||||
} from '../../tailwindcss/src/compat/apply-config-to-theme'
|
||||
import { keyframesToRules } from '../../tailwindcss/src/compat/apply-keyframes-to-theme'
|
||||
import { resolveConfig, type ConfigFile } from '../../tailwindcss/src/compat/config/resolve-config'
|
||||
import type { ThemeConfig } from '../../tailwindcss/src/compat/config/types'
|
||||
import type { ResolvedConfig, ThemeConfig } from '../../tailwindcss/src/compat/config/types'
|
||||
import { darkModePlugin } from '../../tailwindcss/src/compat/dark-mode'
|
||||
import type { DesignSystem } from '../../tailwindcss/src/design-system'
|
||||
import { escape } from '../../tailwindcss/src/utils/escape'
|
||||
import { isValidSpacingMultiplier } from '../../tailwindcss/src/utils/infer-data-type'
|
||||
import { findStaticPlugins, type StaticPluginOptions } from './utils/extract-static-plugins'
|
||||
import { info } from './utils/renderer'
|
||||
|
||||
@ -101,6 +102,8 @@ async function migrateTheme(
|
||||
Array.from(replacedThemeKeys.entries()).map(([key]) => [key, false]),
|
||||
)
|
||||
|
||||
removeUnnecessarySpacingKeys(designSystem, resolvedConfig, replacedThemeKeys)
|
||||
|
||||
let prevSectionKey = ''
|
||||
let css = '\n@tw-bucket theme {\n'
|
||||
css += `\n@theme {\n`
|
||||
@ -317,3 +320,42 @@ function patternSourceFiles(source: { base: string; pattern: string }): string[]
|
||||
scanner.scan()
|
||||
return scanner.files
|
||||
}
|
||||
|
||||
function removeUnnecessarySpacingKeys(
|
||||
designSystem: DesignSystem,
|
||||
resolvedConfig: ResolvedConfig,
|
||||
replacedThemeKeys: Set<string>,
|
||||
) {
|
||||
// We want to keep the spacing scale as-is if the user is overwriting
|
||||
if (replacedThemeKeys.has('spacing')) return
|
||||
|
||||
// Ensure we have a spacing multiplier
|
||||
let spacingScale = designSystem.theme.get(['--spacing'])
|
||||
if (!spacingScale) return
|
||||
|
||||
let [spacingMultiplier, spacingUnit] = splitNumberAndUnit(spacingScale)
|
||||
if (!spacingMultiplier || !spacingUnit) return
|
||||
|
||||
if (spacingScale && !replacedThemeKeys.has('spacing')) {
|
||||
for (let [key, value] of Object.entries(resolvedConfig.theme.spacing ?? {})) {
|
||||
let [multiplier, unit] = splitNumberAndUnit(value as string)
|
||||
if (multiplier === null) continue
|
||||
|
||||
if (!isValidSpacingMultiplier(key)) continue
|
||||
if (unit !== spacingUnit) continue
|
||||
|
||||
if (parseFloat(multiplier) === Number(key) * parseFloat(spacingMultiplier)) {
|
||||
delete resolvedConfig.theme.spacing[key]
|
||||
designSystem.theme.clearNamespace(escape(`--spacing-${key.replaceAll('.', '_')}`), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function splitNumberAndUnit(value: string): [string, string] | [null, null] {
|
||||
let match = value.match(/^([0-9.]+)(.*)$/)
|
||||
if (!match) {
|
||||
return [null, null]
|
||||
}
|
||||
return [match[1], match[2]]
|
||||
}
|
||||
|
||||
@ -9,14 +9,18 @@ test.each([
|
||||
['[color:red]', '[color:red]'],
|
||||
|
||||
// Handle special cases around `.1` in the `theme(…)`
|
||||
['[--value:theme(spacing.1)]', '[--value:var(--spacing-1)]'],
|
||||
['[--value:theme(spacing.1)]', '[--value:calc(var(--spacing)*1)]'],
|
||||
['[--value:theme(fontSize.xs.1.lineHeight)]', '[--value:var(--text-xs--line-height)]'],
|
||||
['[--value:theme(spacing[1.25])]', '[--value:calc(var(--spacing)*1.25)]'],
|
||||
|
||||
// Should not convert invalid spacing values to calc
|
||||
['[--value:theme(spacing[1.1])]', '[--value:theme(spacing[1.1])]'],
|
||||
|
||||
// Convert to `var(…)` if we can resolve the path
|
||||
['[color:theme(colors.red.500)]', '[color:var(--color-red-500)]'], // Arbitrary property
|
||||
['[color:theme(colors.red.500)]/50', '[color:var(--color-red-500)]/50'], // Arbitrary property + modifier
|
||||
['bg-[theme(colors.red.500)]', 'bg-[var(--color-red-500)]'], // Arbitrary value
|
||||
['bg-[size:theme(spacing.4)]', 'bg-[size:var(--spacing-4)]'], // Arbitrary value + data type hint
|
||||
['bg-[size:theme(spacing.4)]', 'bg-[size:calc(var(--spacing)*4)]'], // Arbitrary value + data type hint
|
||||
|
||||
// Convert to `var(…)` if we can resolve the path, but keep fallback values
|
||||
['bg-[theme(colors.red.500,red)]', 'bg-[var(--color-red-500,red)]'],
|
||||
@ -79,15 +83,20 @@ test.each([
|
||||
|
||||
// Variants, we can't use `var(…)` especially inside of `@media(…)`. We can
|
||||
// still upgrade the `theme(…)` to the modern syntax.
|
||||
['max-[theme(spacing.4)]:flex', 'max-[theme(--spacing-4)]:flex'],
|
||||
['max-[theme(screens.lg)]:flex', 'max-[theme(--breakpoint-lg)]:flex'],
|
||||
// There are no variables for `--spacing` multiples, so we can't convert this
|
||||
['max-[theme(spacing.4)]:flex', 'max-[theme(spacing.4)]:flex'],
|
||||
|
||||
// This test in itself doesn't make much sense. But we need to make sure
|
||||
// that this doesn't end up as the modifier in the candidate itself.
|
||||
['max-[theme(spacing.4/50)]:flex', 'max-[theme(--spacing-4/50)]:flex'],
|
||||
['max-[theme(spacing.4/50)]:flex', 'max-[theme(spacing.4/50)]:flex'],
|
||||
|
||||
// `theme(…)` calls in another CSS function is replaced correctly.
|
||||
// Additionally we remove unnecessary whitespace.
|
||||
['grid-cols-[min(50%_,_theme(spacing.80))_auto]', 'grid-cols-[min(50%,var(--spacing-80))_auto]'],
|
||||
[
|
||||
'grid-cols-[min(50%_,_theme(spacing.80))_auto]',
|
||||
'grid-cols-[min(50%,calc(var(--spacing)*80))_auto]',
|
||||
],
|
||||
|
||||
// `theme(…)` calls valid in v3, but not in v4 should still be converted.
|
||||
['[--foo:theme(transitionDuration.500)]', '[--foo:theme(transitionDuration.500)]'],
|
||||
@ -113,43 +122,6 @@ test.each([
|
||||
let designSystem = await __unstable__loadDesignSystem(
|
||||
css`
|
||||
@import 'tailwindcss';
|
||||
@theme {
|
||||
--spacing-px: 1px;
|
||||
--spacing-0: 0px;
|
||||
--spacing-0_5: 0.125rem;
|
||||
--spacing-1: 0.25rem;
|
||||
--spacing-1_5: 0.375rem;
|
||||
--spacing-2: 0.5rem;
|
||||
--spacing-2_5: 0.625rem;
|
||||
--spacing-3: 0.75rem;
|
||||
--spacing-3_5: 0.875rem;
|
||||
--spacing-4: 1rem;
|
||||
--spacing-5: 1.25rem;
|
||||
--spacing-6: 1.5rem;
|
||||
--spacing-7: 1.75rem;
|
||||
--spacing-8: 2rem;
|
||||
--spacing-9: 2.25rem;
|
||||
--spacing-10: 2.5rem;
|
||||
--spacing-11: 2.75rem;
|
||||
--spacing-12: 3rem;
|
||||
--spacing-14: 3.5rem;
|
||||
--spacing-16: 4rem;
|
||||
--spacing-20: 5rem;
|
||||
--spacing-24: 6rem;
|
||||
--spacing-28: 7rem;
|
||||
--spacing-32: 8rem;
|
||||
--spacing-36: 9rem;
|
||||
--spacing-40: 10rem;
|
||||
--spacing-44: 11rem;
|
||||
--spacing-48: 12rem;
|
||||
--spacing-52: 13rem;
|
||||
--spacing-56: 14rem;
|
||||
--spacing-60: 15rem;
|
||||
--spacing-64: 16rem;
|
||||
--spacing-72: 18rem;
|
||||
--spacing-80: 20rem;
|
||||
--spacing-96: 24rem;
|
||||
}
|
||||
`,
|
||||
{
|
||||
base: __dirname,
|
||||
@ -158,3 +130,55 @@ test.each([
|
||||
|
||||
expect(themeToVar(designSystem, {}, candidate)).toEqual(result)
|
||||
})
|
||||
|
||||
test('extended space scale converts to var or calc', async () => {
|
||||
let designSystem = await __unstable__loadDesignSystem(
|
||||
css`
|
||||
@import 'tailwindcss';
|
||||
@theme {
|
||||
--spacing-2: 2px;
|
||||
--spacing-miami: 0.875rem;
|
||||
}
|
||||
`,
|
||||
{
|
||||
base: __dirname,
|
||||
},
|
||||
)
|
||||
expect(themeToVar(designSystem, {}, '[--value:theme(spacing.1)]')).toEqual(
|
||||
'[--value:calc(var(--spacing)*1)]',
|
||||
)
|
||||
expect(themeToVar(designSystem, {}, '[--value:theme(spacing.2)]')).toEqual(
|
||||
'[--value:var(--spacing-2)]',
|
||||
)
|
||||
expect(themeToVar(designSystem, {}, '[--value:theme(spacing.miami)]')).toEqual(
|
||||
'[--value:var(--spacing-miami)]',
|
||||
)
|
||||
expect(themeToVar(designSystem, {}, '[--value:theme(spacing.nyc)]')).toEqual(
|
||||
'[--value:theme(spacing.nyc)]',
|
||||
)
|
||||
})
|
||||
|
||||
test('custom space scale converts to var', async () => {
|
||||
let designSystem = await __unstable__loadDesignSystem(
|
||||
css`
|
||||
@import 'tailwindcss';
|
||||
@theme {
|
||||
--spacing-*: initial;
|
||||
--spacing-1: 0.25rem;
|
||||
--spacing-2: 0.5rem;
|
||||
}
|
||||
`,
|
||||
{
|
||||
base: __dirname,
|
||||
},
|
||||
)
|
||||
expect(themeToVar(designSystem, {}, '[--value:theme(spacing.1)]')).toEqual(
|
||||
'[--value:var(--spacing-1)]',
|
||||
)
|
||||
expect(themeToVar(designSystem, {}, '[--value:theme(spacing.2)]')).toEqual(
|
||||
'[--value:var(--spacing-2)]',
|
||||
)
|
||||
expect(themeToVar(designSystem, {}, '[--value:theme(spacing.3)]')).toEqual(
|
||||
'[--value:theme(spacing.3)]',
|
||||
)
|
||||
})
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
} from '../../../../tailwindcss/src/candidate'
|
||||
import { keyPathToCssProperty } from '../../../../tailwindcss/src/compat/apply-config-to-theme'
|
||||
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
|
||||
import { isValidSpacingMultiplier } from '../../../../tailwindcss/src/utils/infer-data-type'
|
||||
import { segment } from '../../../../tailwindcss/src/utils/segment'
|
||||
import { toKeyPath } from '../../../../tailwindcss/src/utils/to-key-path'
|
||||
import * as ValueParser from '../../../../tailwindcss/src/value-parser'
|
||||
@ -199,9 +200,17 @@ export function createConverter(designSystem: DesignSystem, { prettyPrint = fals
|
||||
|
||||
function toVar(path: string, fallback?: string) {
|
||||
let variable = pathToVariableName(path)
|
||||
if (!variable) return null
|
||||
if (variable) return fallback ? `var(${variable}, ${fallback})` : `var(${variable})`
|
||||
|
||||
return fallback ? `var(${variable}, ${fallback})` : `var(${variable})`
|
||||
let keyPath = toKeyPath(path)
|
||||
if (keyPath[0] === 'spacing' && designSystem.theme.get(['--spacing'])) {
|
||||
let multiplier = keyPath[1]
|
||||
if (!isValidSpacingMultiplier(multiplier)) return null
|
||||
|
||||
return 'calc(var(--spacing) * ' + multiplier + ')'
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function toTheme(path: string, fallback?: string) {
|
||||
|
||||
@ -2,7 +2,7 @@ import { atRoot, atRule, decl, styleRule, type AstNode } from './ast'
|
||||
import type { Candidate, CandidateModifier, NamedUtilityValue } from './candidate'
|
||||
import type { Theme, ThemeKey } from './theme'
|
||||
import { DefaultMap } from './utils/default-map'
|
||||
import { inferDataType, isPositiveInteger } from './utils/infer-data-type'
|
||||
import { inferDataType, isPositiveInteger, isValidSpacingMultiplier } from './utils/infer-data-type'
|
||||
import { replaceShadowColors } from './utils/replace-shadow-colors'
|
||||
import { segment } from './utils/segment'
|
||||
|
||||
@ -397,9 +397,7 @@ export function createUtilities(theme: Theme) {
|
||||
handleBareValue: ({ value }) => {
|
||||
let multiplier = theme.resolve(null, ['--spacing'])
|
||||
if (!multiplier) return null
|
||||
|
||||
let num = Number(value)
|
||||
if (num < 0 || num % 0.25 !== 0 || String(num) !== value) return null
|
||||
if (!isValidSpacingMultiplier(value)) return null
|
||||
|
||||
return `calc(${multiplier} * ${value})`
|
||||
},
|
||||
|
||||
@ -328,3 +328,11 @@ export function isPositiveInteger(value: any) {
|
||||
let num = Number(value)
|
||||
return Number.isInteger(num) && num >= 0 && String(num) === String(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the value is either a positive whole number or a multiple of 0.25.
|
||||
*/
|
||||
export function isValidSpacingMultiplier(value: any) {
|
||||
let num = Number(value)
|
||||
return num >= 0 && num % 0.25 === 0 && String(num) === String(value)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user