diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b1981c3223c..ab1b307cac32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Cleanup unused `variantOrder` ([#9829](https://github.com/tailwindlabs/tailwindcss/pull/9829)) +- Fix `foo-[abc]/[def]` not being handled correctl ([#9866](https://github.com/tailwindlabs/tailwindcss/pull/9866)) ### Added diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js index 5049b088e38e..7f8c4ab74f7b 100644 --- a/src/util/pluginUtils.js +++ b/src/util/pluginUtils.js @@ -133,18 +133,14 @@ export function parseColorFormat(value) { return value } -export function asColor( - _, - options = {}, - { tailwindConfig = {}, utilityModifier, rawModifier } = {} -) { - if (options.values?.[rawModifier] !== undefined) { - return parseColorFormat(options.values?.[rawModifier]) +export function asColor(modifier, options = {}, { tailwindConfig = {} } = {}) { + if (options.values?.[modifier] !== undefined) { + return parseColorFormat(options.values?.[modifier]) } // TODO: Hoist this up to getMatchingTypes or something // We do this here because we need the alpha value (if any) - let [color, alpha] = splitUtilityModifier(rawModifier) + let [color, alpha] = splitUtilityModifier(modifier) if (alpha !== undefined) { let normalizedColor = @@ -167,7 +163,7 @@ export function asColor( return withAlphaValue(normalizedColor, tailwindConfig.theme.opacity[alpha]) } - return asValue(rawModifier, options, { rawModifier, utilityModifier, validate: validateColor }) + return asValue(modifier, options, { validate: validateColor }) } export function asLookupValue(modifier, options = {}) { @@ -175,8 +171,8 @@ export function asLookupValue(modifier, options = {}) { } function guess(validate) { - return (modifier, options, extras) => { - return asValue(modifier, options, { ...extras, validate }) + return (modifier, options) => { + return asValue(modifier, options, { validate }) } } @@ -208,6 +204,22 @@ function splitAtFirst(input, delim) { } export function coerceValue(types, modifier, options, tailwindConfig) { + if (options.values && modifier in options.values) { + for (let { type } of types ?? []) { + let result = typeMap[type](modifier, options, { + tailwindConfig, + }) + + if (result === undefined) { + continue + } + + return [result, type, null] + } + + return [options.values[modifier], 'lookup', null] + } + if (isArbitraryValue(modifier)) { let arbitraryValue = modifier.slice(1, -1) let [explicitType, value] = splitAtFirst(arbitraryValue, ':') @@ -280,17 +292,10 @@ export function* getMatchingTypes(types, rawModifier, options, tailwindConfig) { utilityModifier = utilityModifier.slice(1, -1) } } - - let result = asValue(rawModifier, options, { rawModifier, utilityModifier, tailwindConfig }) - if (result !== undefined) { - yield [result, 'any', null] - } } - for (const { type } of types ?? []) { + for (let { type } of types ?? []) { let result = typeMap[type](modifier, options, { - rawModifier, - utilityModifier, tailwindConfig, }) diff --git a/tests/match-utilities.test.js b/tests/match-utilities.test.js index 587a46866858..0e76373ef8eb 100644 --- a/tests/match-utilities.test.js +++ b/tests/match-utilities.test.js @@ -165,3 +165,342 @@ test('match utilities can omit utilities by returning null', async () => { } `) }) + +test('matching utilities with an arbitrary value and configured modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + modifiers: { + bar: 'configured_bar', + }, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/bar { + value: foo; + modifier: configured_bar; + } + `) + }) +}) + +test('matching utilities with an configured value and an arbitrary modifier (raw)', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + foo: 'configured_foo', + }, + modifiers: 'any', // Raw `[value]` + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-foo\/\[bar\] { + value: configured_foo; + modifier: [bar]; + } + `) + }) +}) + +test('matching utilities with an configured value and an arbitrary modifier (non-raw)', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + foo: 'configured_foo', + }, + modifiers: {}, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-foo\/\[bar\] { + value: configured_foo; + modifier: bar; + } + `) + }) +}) + +test('matching utilities with an configured value and a configured modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + foo: 'configured_foo', + }, + modifiers: { + bar: 'configured_bar', + }, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-foo\/bar { + value: configured_foo; + modifier: configured_bar; + } + `) + }) +}) + +test('matching utilities with an arbitrary value and an arbitrary modifier (raw)', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + modifiers: 'any', + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/\[bar\] { + value: foo; + modifier: [bar]; + } + `) + }) +}) + +test('matching utilities with an arbitrary value and an arbitrary modifier (non-raw)', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + modifiers: {}, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/\[bar\] { + value: foo; + modifier: bar; + } + `) + }) +}) + +test('matching utilities with a lookup value that looks like an arbitrary value and modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + '[foo]/[bar]': 'hello', + }, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/\[bar\] { + value: hello; + } + `) + }) +}) + +test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = any)', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + '[foo]/[bar]': 'hello', + }, + modifiers: 'any', + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/\[bar\] { + value: hello; + } + `) + }) +}) + +test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = {})', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + '[foo]/[bar]': 'hello', + }, + modifiers: {}, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/\[bar\] { + value: hello; + } + `) + }) +}) + +test('matching utilities with a lookup value that looks like an arbitrary value and a configured modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + '[foo]/bar': 'hello', + }, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/bar { + value: hello; + } + `) + }) +}) + +test('matching utilities with a lookup value that looks like a configured value and an arbitrary modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + 'foo/[bar]': 'hello', + }, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-foo\/\[bar\] { + value: hello; + } + `) + }) +})