Show suggestions for fractions in IntelliSense (#16353)

Fixes
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1178
(partially)

An IntelliSense PR (+ Play update) is required to make this work better
with v4:
https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1182

<img width="671" alt="Screenshot 2025-02-07 at 15 19 59"
src="https://github.com/user-attachments/assets/7642b80c-f431-4a1e-964d-2a98ffe7a67a"
/>
This commit is contained in:
Jordan Pittman 2025-02-11 12:03:37 -05:00 committed by GitHub
parent f995dae5ca
commit aad440ecf6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 998 additions and 6 deletions

View File

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Export `tailwindcss/lib/util/flattenColorPalette.js` for backward compatibility ([#16411](https://github.com/tailwindlabs/tailwindcss/pull/16411))
- Fix sorting numeric utilities when they have different magnitudes ([#16414](https://github.com/tailwindlabs/tailwindcss/pull/16414))
- Show suggestions for fractions in IntelliSense ([#16353](https://github.com/tailwindlabs/tailwindcss/pull/16353))
## [4.0.6] - 2025-02-10

View File

@ -2,19 +2,34 @@ import { styleRule, walkDepth } from './ast'
import { applyVariant } from './compile'
import type { DesignSystem } from './design-system'
import { compare } from './utils/compare'
import { DefaultMap } from './utils/default-map'
interface ClassMetadata {
modifiers: string[]
}
export type ClassItem = {
name: string
utility: string
fraction: boolean
modifiers: string[]
}
export type ClassEntry = [string, ClassMetadata]
const IS_FRACTION = /^\d+\/\d+$/
export function getClassList(design: DesignSystem): ClassEntry[] {
let list: [string, ClassMetadata][] = []
let list: ClassItem[] = []
// Static utilities only work as-is
for (let utility of design.utilities.keys('static')) {
list.push([utility, { modifiers: [] }])
list.push({
name: utility,
utility,
fraction: false,
modifiers: [],
})
}
// Functional utilities have their own list of completions
@ -23,20 +38,99 @@ export function getClassList(design: DesignSystem): ClassEntry[] {
for (let group of completions) {
for (let value of group.values) {
let fraction = value !== null && IS_FRACTION.test(value)
let name = value === null ? utility : `${utility}-${value}`
list.push([name, { modifiers: group.modifiers }])
list.push({
name,
utility,
fraction,
modifiers: group.modifiers,
})
if (group.supportsNegative) {
list.push([`-${name}`, { modifiers: group.modifiers }])
list.push({
name: `-${name}`,
utility: `-${utility}`,
fraction,
modifiers: group.modifiers,
})
}
}
}
}
list.sort((a, b) => compare(a[0], b[0]))
if (list.length === 0) return []
return list
// Sort utilities by their class name
list.sort((a, b) => compare(a.name, b.name))
let entries = sortFractionsLast(list)
return entries
}
function sortFractionsLast(list: ClassItem[]) {
type Bucket = {
utility: string
items: ClassItem[]
}
// 1. Create "buckets" for each utility group
let buckets: Bucket[] = []
let current: Bucket | null = null
// 2. Determine the last bucket for each utility group
let lastUtilityBucket = new Map<string, Bucket>()
// 3. Collect all fractions in a given utility group
let fractions = new DefaultMap<string, ClassItem[]>(() => [])
for (let item of list) {
let { utility, fraction } = item
if (!current) {
current = { utility, items: [] }
lastUtilityBucket.set(utility, current)
}
if (utility !== current.utility) {
buckets.push(current)
current = { utility, items: [] }
lastUtilityBucket.set(utility, current)
}
if (fraction) {
fractions.get(utility).push(item)
} else {
current.items.push(item)
}
}
if (current && buckets[buckets.length - 1] !== current) {
buckets.push(current)
}
// 4. Add fractions to their respective last utility buckets
for (let [utility, items] of fractions) {
let bucket = lastUtilityBucket.get(utility)
if (!bucket) continue
bucket.items.push(...items)
}
// 5. Flatten the buckets into a single list
let entries: ClassEntry[] = []
for (let bucket of buckets) {
for (let entry of bucket.items) {
entries.push([entry.name, { modifiers: entry.modifiers }])
}
}
return entries
}
interface SelectorOptions {

View File

@ -42,6 +42,7 @@ type SuggestionDefinition =
| string
| {
supportsNegative?: boolean
supportsFractions?: boolean
values?: string[]
modifiers?: string[]
valueThemeKeys?: ThemeKey[]
@ -225,6 +226,35 @@ export function createUtilities(theme: Theme) {
}
}
let suggestedFractions = [
'1/2',
'1/3',
'2/3',
'1/4',
'2/4',
'3/4',
'1/5',
'2/5',
'3/5',
'4/5',
'1/6',
'2/6',
'3/6',
'4/6',
'5/6',
'1/12',
'2/12',
'3/12',
'4/12',
'5/12',
'6/12',
'7/12',
'8/12',
'9/12',
'10/12',
'11/12',
]
utilities.suggest(classRoot, () => {
let groups: SuggestionGroup[] = []
@ -238,8 +268,13 @@ export function createUtilities(theme: Theme) {
...(defn.values ?? []),
...resolve(defn.valueThemeKeys ?? []),
]
let modifiers = [...(defn.modifiers ?? []), ...resolve(defn.modifierThemeKeys ?? [])]
if (defn.supportsFractions) {
values.push(...suggestedFractions)
}
if (defn.hasDefaultValue) {
values.unshift(null)
}
@ -341,6 +376,7 @@ export function createUtilities(theme: Theme) {
supportsNegative: desc.supportsNegative,
valueThemeKeys: desc.themeKeys ?? [],
hasDefaultValue: desc.defaultValue !== undefined && desc.defaultValue !== null,
supportsFractions: desc.supportsFractions,
},
])
}
@ -467,6 +503,7 @@ export function createUtilities(theme: Theme) {
]
: [],
supportsNegative,
supportsFractions,
valueThemeKeys: themeKeys,
},
])
@ -966,6 +1003,8 @@ export function createUtilities(theme: Theme) {
}
})
suggest('flex', () => [{ supportsFractions: true }])
/**
* @css `flex-shrink`
*/