Skip to content

Commit 250c843

Browse files
authored
Add suggestions when --spacing(--value(integer, number)) is used (#17308)
This PR adds suggestions to CSS based functional utilities when the `--spacing(…)` function is used. Given this CSS: ```css @import "tailwindcss"; @theme { --spacing: 0.25rem; --spacing-custom: 123px; } @Utility with-custom-spacing-* { size: --value(--spacing); } @Utility with-integer-spacing-* { size: --spacing(--value(integer)); } @Utility with-number-spacing-* { size: --spacing(--value(number)); } ``` And this HTML: ```html <div class="with-custom-spacing-custom"></div> <div class="with-custom-spacing-0"></div> <div class="with-custom-spacing-0.5"></div> <div class="with-custom-spacing-1"></div> <div class="with-custom-spacing-1.5"></div> <div class="with-integer-spacing-custom"></div> <div class="with-integer-spacing-0"></div> <div class="with-integer-spacing-0.5"></div> <div class="with-integer-spacing-1"></div> <div class="with-integer-spacing-1.5"></div> <div class="with-number-spacing-custom"></div> <div class="with-number-spacing-0"></div> <div class="with-number-spacing-0.5"></div> <div class="with-number-spacing-1"></div> <div class="with-number-spacing-1.5"></div> ``` Play: https://play.tailwindcss.com/tYDaSNiNtS Then you will see the following suggestions: ```json [ "with-custom-spacing-custom", "with-integer-spacing-0", "with-integer-spacing-1", "with-integer-spacing-2", "with-integer-spacing-3", "with-integer-spacing-4", "with-integer-spacing-5", "with-integer-spacing-6", "with-integer-spacing-7", "with-integer-spacing-8", "with-integer-spacing-9", "with-integer-spacing-10", "with-integer-spacing-11", "with-integer-spacing-12", "with-integer-spacing-14", "with-integer-spacing-16", "with-integer-spacing-20", "with-integer-spacing-24", "with-integer-spacing-28", "with-integer-spacing-32", "with-integer-spacing-36", "with-integer-spacing-40", "with-integer-spacing-44", "with-integer-spacing-48", "with-integer-spacing-52", "with-integer-spacing-56", "with-integer-spacing-60", "with-integer-spacing-64", "with-integer-spacing-72", "with-integer-spacing-80", "with-integer-spacing-96", "with-number-spacing-0", "with-number-spacing-0.5", "with-number-spacing-1", "with-number-spacing-1.5", "with-number-spacing-2", "with-number-spacing-2.5", "with-number-spacing-3", "with-number-spacing-3.5", "with-number-spacing-4", "with-number-spacing-5", "with-number-spacing-6", "with-number-spacing-7", "with-number-spacing-8", "with-number-spacing-9", "with-number-spacing-10", "with-number-spacing-11", "with-number-spacing-12", "with-number-spacing-14", "with-number-spacing-16", "with-number-spacing-20", "with-number-spacing-24", "with-number-spacing-28", "with-number-spacing-32", "with-number-spacing-36", "with-number-spacing-40", "with-number-spacing-44", "with-number-spacing-48", "with-number-spacing-52", "with-number-spacing-56", "with-number-spacing-60", "with-number-spacing-64", "with-number-spacing-72", "with-number-spacing-80", "with-number-spacing-96" ] ``` This is because `--spacing(--value(number))` will include all default spacing scale suggestions we use. And `--pacing(--value(integer))` will include the same list but without the floating point numbers. Follow up PR for: #17304
1 parent a3316f2 commit 250c843

File tree

3 files changed

+149
-68
lines changed

3 files changed

+149
-68
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
- _Experimental_: Add `wrap-anywhere`, `wrap-break-word`, and `wrap-normal` utilities ([#12128](https://github.com/tailwindlabs/tailwindcss/pull/12128))
2121
- _Experimental_: Add `@source inline(…)` ([#17147](https://github.com/tailwindlabs/tailwindcss/pull/17147))
2222
- Add support for literal values in `--value('…')` and `--modifier('…')` ([#17304](https://github.com/tailwindlabs/tailwindcss/pull/17304))
23+
- Add suggestions when `--spacing(--value(integer, number))` is used ([#17308](https://github.com/tailwindlabs/tailwindcss/pull/17308))
2324

2425
### [4.0.15] - 2025-03-20
2526

packages/tailwindcss/src/intellisense.test.ts

+33
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,9 @@ test('Custom functional @utility', async () => {
476476
477477
--leading-foo: 1.5;
478478
--leading-bar: 2;
479+
480+
--spacing: 0.25rem;
481+
--spacing-custom: 123px;
479482
}
480483
481484
@utility tab-* {
@@ -488,6 +491,18 @@ test('Custom functional @utility', async () => {
488491
line-height: --modifier(--leading, 'normal');
489492
}
490493
494+
@utility with-custom-spacing-* {
495+
size: --value(--spacing);
496+
}
497+
498+
@utility with-integer-spacing-* {
499+
size: --spacing(--value(integer));
500+
}
501+
502+
@utility with-number-spacing-* {
503+
size: --spacing(--value(number));
504+
}
505+
491506
@utility -negative-* {
492507
margin: --value(--tab-size- *);
493508
}
@@ -515,6 +530,24 @@ test('Custom functional @utility', async () => {
515530
expect(classNames).not.toContain('-tab-4')
516531
expect(classNames).not.toContain('-tab-github')
517532

533+
expect(classNames).toContain('with-custom-spacing-custom')
534+
expect(classNames).not.toContain('with-custom-spacing-0')
535+
expect(classNames).not.toContain('with-custom-spacing-0.5')
536+
expect(classNames).not.toContain('with-custom-spacing-1')
537+
expect(classNames).not.toContain('with-custom-spacing-1.5')
538+
539+
expect(classNames).not.toContain('with-integer-spacing-custom')
540+
expect(classNames).toContain('with-integer-spacing-0')
541+
expect(classNames).not.toContain('with-integer-spacing-0.5')
542+
expect(classNames).toContain('with-integer-spacing-1')
543+
expect(classNames).not.toContain('with-integer-spacing-1.5')
544+
545+
expect(classNames).not.toContain('with-number-spacing-custom')
546+
expect(classNames).toContain('with-number-spacing-0')
547+
expect(classNames).toContain('with-number-spacing-0.5')
548+
expect(classNames).toContain('with-number-spacing-1')
549+
expect(classNames).toContain('with-number-spacing-1.5')
550+
518551
expect(classNames).toContain('-negative-1')
519552
expect(classNames).toContain('-negative-2')
520553
expect(classNames).toContain('-negative-4')

packages/tailwindcss/src/utilities.ts

+115-68
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,43 @@ import * as ValueParser from './value-parser'
2929
const IS_VALID_STATIC_UTILITY_NAME = /^-?[a-z][a-zA-Z0-9/%._-]*$/
3030
const IS_VALID_FUNCTIONAL_UTILITY_NAME = /^-?[a-z][a-zA-Z0-9/%._-]*-\*$/
3131

32+
const DEFAULT_SPACING_SUGGESTIONS = [
33+
'0',
34+
'0.5',
35+
'1',
36+
'1.5',
37+
'2',
38+
'2.5',
39+
'3',
40+
'3.5',
41+
'4',
42+
'5',
43+
'6',
44+
'7',
45+
'8',
46+
'9',
47+
'10',
48+
'11',
49+
'12',
50+
'14',
51+
'16',
52+
'20',
53+
'24',
54+
'28',
55+
'32',
56+
'36',
57+
'40',
58+
'44',
59+
'48',
60+
'52',
61+
'56',
62+
'60',
63+
'64',
64+
'72',
65+
'80',
66+
'96',
67+
]
68+
3269
type CompileFn<T extends Candidate['kind']> = (
3370
value: Extract<Candidate, { kind: T }>,
3471
) => AstNode[] | undefined | null
@@ -476,44 +513,7 @@ export function createUtilities(theme: Theme) {
476513

477514
suggest(name, () => [
478515
{
479-
values: theme.get(['--spacing'])
480-
? [
481-
'0',
482-
'0.5',
483-
'1',
484-
'1.5',
485-
'2',
486-
'2.5',
487-
'3',
488-
'3.5',
489-
'4',
490-
'5',
491-
'6',
492-
'7',
493-
'8',
494-
'9',
495-
'10',
496-
'11',
497-
'12',
498-
'14',
499-
'16',
500-
'20',
501-
'24',
502-
'28',
503-
'32',
504-
'36',
505-
'40',
506-
'44',
507-
'48',
508-
'52',
509-
'56',
510-
'60',
511-
'64',
512-
'72',
513-
'80',
514-
'96',
515-
]
516-
: [],
516+
values: theme.get(['--spacing']) ? DEFAULT_SPACING_SUGGESTIONS : [],
517517
supportsNegative,
518518
supportsFractions,
519519
valueThemeKeys: themeKeys,
@@ -4731,11 +4731,20 @@ export function createCssUtility(node: AtRule) {
47314731
// If you then use `foo-1/2`, this is invalid, because the modifier is not used.
47324732

47334733
return (designSystem: DesignSystem) => {
4734-
let valueThemeKeys = new Set<`--${string}`>()
4735-
let valueLiterals = new Set<string>()
4736-
4737-
let modifierThemeKeys = new Set<`--${string}`>()
4738-
let modifierLiterals = new Set<string>()
4734+
let storage = {
4735+
'--value': {
4736+
usedSpacingInteger: false,
4737+
usedSpacingNumber: false,
4738+
themeKeys: new Set<`--${string}`>(),
4739+
literals: new Set<string>(),
4740+
},
4741+
'--modifier': {
4742+
usedSpacingInteger: false,
4743+
usedSpacingNumber: false,
4744+
themeKeys: new Set<`--${string}`>(),
4745+
literals: new Set<string>(),
4746+
},
4747+
}
47394748

47404749
// Pre-process the AST to make it easier to work with.
47414750
//
@@ -4762,6 +4771,41 @@ export function createCssUtility(node: AtRule) {
47624771
// `\\*` or not inserting whitespace) then most of these can go away.
47634772
ValueParser.walk(declarationValueAst, (fn) => {
47644773
if (fn.kind !== 'function') return
4774+
4775+
// Track usage of `--spacing(…)`
4776+
if (
4777+
fn.value === '--spacing' &&
4778+
// Quick bail check if we already know that `--value` and `--modifier` are
4779+
// using the full `--spacing` theme scale.
4780+
!(storage['--modifier'].usedSpacingNumber && storage['--value'].usedSpacingNumber)
4781+
) {
4782+
ValueParser.walk(fn.nodes, (node) => {
4783+
if (node.kind !== 'function') return
4784+
if (node.value !== '--value' && node.value !== '--modifier') return
4785+
const key = node.value
4786+
4787+
for (let arg of node.nodes) {
4788+
if (arg.kind !== 'word') continue
4789+
4790+
if (arg.value === 'integer') {
4791+
storage[key].usedSpacingInteger ||= true
4792+
} else if (arg.value === 'number') {
4793+
storage[key].usedSpacingNumber ||= true
4794+
4795+
// Once both `--value` and `--modifier` are using the full
4796+
// `number` spacing scale, then there's no need to continue
4797+
if (
4798+
storage['--modifier'].usedSpacingNumber &&
4799+
storage['--value'].usedSpacingNumber
4800+
) {
4801+
return ValueParser.ValueWalkAction.Stop
4802+
}
4803+
}
4804+
}
4805+
})
4806+
return ValueParser.ValueWalkAction.Continue
4807+
}
4808+
47654809
if (fn.value !== '--value' && fn.value !== '--modifier') return
47664810

47674811
let args = segment(ValueParser.toCss(fn.nodes), ',')
@@ -4796,23 +4840,13 @@ export function createCssUtility(node: AtRule) {
47964840
node.value[0] === node.value[node.value.length - 1]
47974841
) {
47984842
let value = node.value.slice(1, -1)
4799-
4800-
if (fn.value === '--value') {
4801-
valueLiterals.add(value)
4802-
} else if (fn.value === '--modifier') {
4803-
modifierLiterals.add(value)
4804-
}
4843+
storage[fn.value].literals.add(value)
48054844
}
48064845

48074846
// Track theme keys
48084847
else if (node.kind === 'word' && node.value[0] === '-' && node.value[1] === '-') {
48094848
let value = node.value.replace(/-\*.*$/g, '') as `--${string}`
4810-
4811-
if (fn.value === '--value') {
4812-
valueThemeKeys.add(value)
4813-
} else if (fn.value === '--modifier') {
4814-
modifierThemeKeys.add(value)
4815-
}
4849+
storage[fn.value].themeKeys.add(value)
48164850
}
48174851
}
48184852
})
@@ -4949,20 +4983,33 @@ export function createCssUtility(node: AtRule) {
49494983
})
49504984

49514985
designSystem.utilities.suggest(name.slice(0, -2), () => {
4952-
let values = []
4953-
for (let value of valueLiterals) {
4954-
values.push(value)
4955-
}
4956-
for (let value of designSystem.theme.keysInNamespaces(valueThemeKeys)) {
4957-
values.push(value)
4958-
}
4986+
let values: string[] = []
4987+
let modifiers: string[] = []
4988+
4989+
for (let [target, { literals, usedSpacingNumber, usedSpacingInteger, themeKeys }] of [
4990+
[values, storage['--value']],
4991+
[modifiers, storage['--modifier']],
4992+
] as const) {
4993+
// Suggest literal values. E.g.: `--value('literal')`
4994+
for (let value of literals) {
4995+
target.push(value)
4996+
}
49594997

4960-
let modifiers = []
4961-
for (let modifier of modifierLiterals) {
4962-
modifiers.push(modifier)
4963-
}
4964-
for (let value of designSystem.theme.keysInNamespaces(modifierThemeKeys)) {
4965-
modifiers.push(value)
4998+
// Suggest `--spacing(…)` values. E.g.: `--spacing(--value(integer))`
4999+
if (usedSpacingNumber) {
5000+
target.push(...DEFAULT_SPACING_SUGGESTIONS)
5001+
} else if (usedSpacingInteger) {
5002+
for (let value of DEFAULT_SPACING_SUGGESTIONS) {
5003+
if (isPositiveInteger(value)) {
5004+
target.push(value)
5005+
}
5006+
}
5007+
}
5008+
5009+
// Suggest theme values. E.g.: `--value(--color-*)`
5010+
for (let value of designSystem.theme.keysInNamespaces(themeKeys)) {
5011+
target.push(value)
5012+
}
49665013
}
49675014

49685015
return [{ values, modifiers }] satisfies SuggestionGroup[]

0 commit comments

Comments
 (0)