diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 445fceb23a57..9a140096e556 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -962,23 +962,35 @@ function registerPlugins(plugins, context) { // Generate a list of strings for autocompletion purposes, e.g. // ['uppercase', 'lowercase', ...] - context.getClassList = function getClassList() { + context.getClassList = function getClassList(options = {}) { let output = [] for (let util of classList) { if (Array.isArray(util)) { - let [utilName, options] = util + let [utilName, utilOptions] = util let negativeClasses = [] - for (let [key, value] of Object.entries(options?.values ?? {})) { + let modifiers = Object.keys(utilOptions?.modifiers ?? {}) + + if (utilOptions?.types?.some(({ type }) => type === 'color')) { + modifiers.push(...Object.keys(context.tailwindConfig.theme.opacity ?? {})) + } + + let metadata = { modifiers } + let includeMetadata = options.includeMetadata && modifiers.length > 0 + + for (let [key, value] of Object.entries(utilOptions?.values ?? {})) { // Ignore undefined and null values if (value == null) { continue } - output.push(formatClass(utilName, key)) - if (options?.supportsNegativeValues && negateValue(value)) { - negativeClasses.push(formatClass(utilName, `-${key}`)) + let cls = formatClass(utilName, key) + output.push(includeMetadata ? [cls, metadata] : cls) + + if (utilOptions?.supportsNegativeValues && negateValue(value)) { + let cls = formatClass(utilName, `-${key}`) + negativeClasses.push(includeMetadata ? [cls, metadata] : cls) } } diff --git a/tests/getClassList.test.js b/tests/getClassList.test.js index adeb4f659855..671a767ecfe5 100644 --- a/tests/getClassList.test.js +++ b/tests/getClassList.test.js @@ -1,4 +1,5 @@ import resolveConfig from '../src/public/resolve-config' +import plugin from '../src/public/create-plugin' import { createContext } from '../src/lib/setupContextUtils' import { crosscheck } from './util/run' @@ -70,6 +71,111 @@ crosscheck(() => { expect(classes).not.toContain('bg-red-500/50') }) + it('should not include metadata by default', () => { + let config = {} + let context = createContext(resolveConfig(config)) + let classes = context.getClassList() + + expect(classes.every((cls) => typeof cls === 'string')).toEqual(true) + + expect(classes).toContain('bg-red-500') + expect(classes).toContain('text-2xl') + }) + + it('should generate utilities with modifier data when requested', () => { + let config = {} + let context = createContext(resolveConfig(config)) + let classes = context.getClassList({ includeMetadata: true }) + + expect(classes).not.toContain('bg-red-500') + expect(classes).not.toContain('text-2xl') + + expect(classes).toContainEqual([ + 'bg-red-500', + { + modifiers: [ + '0', + '5', + '10', + '20', + '25', + '30', + '40', + '50', + '60', + '70', + '75', + '80', + '90', + '95', + '100', + ], + }, + ]) + expect(classes).toContainEqual([ + 'text-2xl', + { + modifiers: [ + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '10', + 'none', + 'tight', + 'snug', + 'normal', + 'relaxed', + 'loose', + ], + }, + ]) + }) + + it('should generate plugin-defined utilities with modifier data when requested', () => { + let config = { + plugins: [ + plugin(function ({ matchUtilities }) { + matchUtilities( + { + foo: (value) => { + return { margin: value } + }, + }, + { + values: { xl: '32px' }, + modifiers: { bar: 'something' }, + } + ) + matchUtilities( + { + 'foo-negative': (value) => { + return { margin: value } + }, + }, + { + values: { xl: '32px' }, + modifiers: { bar: 'something' }, + supportsNegativeValues: true, + } + ) + }), + ], + } + let context = createContext(resolveConfig(config)) + let classes = context.getClassList({ includeMetadata: true }) + + expect(classes).toContainEqual(['foo-xl', { modifiers: ['bar'] }]) + expect(classes).toContainEqual(['foo-negative-xl', { modifiers: ['bar'] }]) + expect(classes).toContainEqual(['-foo-negative-xl', { modifiers: ['bar'] }]) + expect(classes).not.toContain('foo-xl') + expect(classes).not.toContain('-foo-xl') + expect(classes).not.toContainEqual(['-foo-xl', { modifiers: ['bar'] }]) + }) + it('should not generate utilities with opacity even if safe-listed', () => { let config = { safelist: [