diff options
| author | Philipp Tanlak <philipp.tanlak@gmail.com> | 2025-11-24 20:54:57 +0100 |
|---|---|---|
| committer | Philipp Tanlak <philipp.tanlak@gmail.com> | 2025-11-24 20:57:48 +0100 |
| commit | b1e2c8fd5cb5dfa46bc440a12eafaf56cd844b1c (patch) | |
| tree | 49d360fd6cbc6a2754efe93524ac47ff0fbe0f7d /node_modules/tailwindcss/src/util/pluginUtils.js | |
Docs
Diffstat (limited to 'node_modules/tailwindcss/src/util/pluginUtils.js')
| -rw-r--r-- | node_modules/tailwindcss/src/util/pluginUtils.js | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/node_modules/tailwindcss/src/util/pluginUtils.js b/node_modules/tailwindcss/src/util/pluginUtils.js new file mode 100644 index 0000000..bef9fc1 --- /dev/null +++ b/node_modules/tailwindcss/src/util/pluginUtils.js @@ -0,0 +1,291 @@ +import escapeCommas from './escapeCommas' +import { withAlphaValue } from './withAlphaVariable' +import { + normalize, + length, + number, + percentage, + url, + color as validateColor, + genericName, + familyName, + image, + absoluteSize, + relativeSize, + position, + lineWidth, + shadow, +} from './dataTypes' +import negateValue from './negateValue' +import { backgroundSize } from './validateFormalSyntax' +import { flagEnabled } from '../featureFlags.js' + +/** + * @param {import('postcss-selector-parser').Container} selectors + * @param {(className: string) => string} updateClass + * @returns {string} + */ +export function updateAllClasses(selectors, updateClass) { + selectors.walkClasses((sel) => { + sel.value = updateClass(sel.value) + + if (sel.raws && sel.raws.value) { + sel.raws.value = escapeCommas(sel.raws.value) + } + }) +} + +function resolveArbitraryValue(modifier, validate) { + if (!isArbitraryValue(modifier)) { + return undefined + } + + let value = modifier.slice(1, -1) + + if (!validate(value)) { + return undefined + } + + return normalize(value) +} + +function asNegativeValue(modifier, lookup = {}, validate) { + let positiveValue = lookup[modifier] + + if (positiveValue !== undefined) { + return negateValue(positiveValue) + } + + if (isArbitraryValue(modifier)) { + let resolved = resolveArbitraryValue(modifier, validate) + + if (resolved === undefined) { + return undefined + } + + return negateValue(resolved) + } +} + +export function asValue(modifier, options = {}, { validate = () => true } = {}) { + let value = options.values?.[modifier] + + if (value !== undefined) { + return value + } + + if (options.supportsNegativeValues && modifier.startsWith('-')) { + return asNegativeValue(modifier.slice(1), options.values, validate) + } + + return resolveArbitraryValue(modifier, validate) +} + +function isArbitraryValue(input) { + return input.startsWith('[') && input.endsWith(']') +} + +function splitUtilityModifier(modifier) { + let slashIdx = modifier.lastIndexOf('/') + + if (slashIdx === -1 || slashIdx === modifier.length - 1) { + return [modifier, undefined] + } + + let arbitrary = isArbitraryValue(modifier) + + // The modifier could be of the form `[foo]/[bar]` + // We want to handle this case properly + // without affecting `[foo/bar]` + if (arbitrary && !modifier.includes(']/[')) { + return [modifier, undefined] + } + + return [modifier.slice(0, slashIdx), modifier.slice(slashIdx + 1)] +} + +export function parseColorFormat(value) { + if (typeof value === 'string' && value.includes('<alpha-value>')) { + let oldValue = value + + return ({ opacityValue = 1 }) => oldValue.replace('<alpha-value>', opacityValue) + } + + return value +} + +function unwrapArbitraryModifier(modifier) { + return normalize(modifier.slice(1, -1)) +} + +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(modifier) + + if (alpha !== undefined) { + let normalizedColor = + options.values?.[color] ?? (isArbitraryValue(color) ? color.slice(1, -1) : undefined) + + if (normalizedColor === undefined) { + return undefined + } + + normalizedColor = parseColorFormat(normalizedColor) + + if (isArbitraryValue(alpha)) { + return withAlphaValue(normalizedColor, unwrapArbitraryModifier(alpha)) + } + + if (tailwindConfig.theme?.opacity?.[alpha] === undefined) { + return undefined + } + + return withAlphaValue(normalizedColor, tailwindConfig.theme.opacity[alpha]) + } + + return asValue(modifier, options, { validate: validateColor }) +} + +export function asLookupValue(modifier, options = {}) { + return options.values?.[modifier] +} + +function guess(validate) { + return (modifier, options) => { + return asValue(modifier, options, { validate }) + } +} + +export let typeMap = { + any: asValue, + color: asColor, + url: guess(url), + image: guess(image), + length: guess(length), + percentage: guess(percentage), + position: guess(position), + lookup: asLookupValue, + 'generic-name': guess(genericName), + 'family-name': guess(familyName), + number: guess(number), + 'line-width': guess(lineWidth), + 'absolute-size': guess(absoluteSize), + 'relative-size': guess(relativeSize), + shadow: guess(shadow), + size: guess(backgroundSize), +} + +let supportedTypes = Object.keys(typeMap) + +function splitAtFirst(input, delim) { + let idx = input.indexOf(delim) + if (idx === -1) return [undefined, input] + return [input.slice(0, idx), input.slice(idx + 1)] +} + +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] + } + } + + if (isArbitraryValue(modifier)) { + let arbitraryValue = modifier.slice(1, -1) + let [explicitType, value] = splitAtFirst(arbitraryValue, ':') + + // It could be that this resolves to `url(https` which is not a valid + // identifier. We currently only support "simple" words with dashes or + // underscores. E.g.: family-name + if (!/^[\w-_]+$/g.test(explicitType)) { + value = arbitraryValue + } + + // + else if (explicitType !== undefined && !supportedTypes.includes(explicitType)) { + return [] + } + + if (value.length > 0 && supportedTypes.includes(explicitType)) { + return [asValue(`[${value}]`, options), explicitType, null] + } + } + + let matches = getMatchingTypes(types, modifier, options, tailwindConfig) + + // Find first matching type + for (let match of matches) { + return match + } + + return [] +} + +/** + * + * @param {{type: string}[]} types + * @param {string} rawModifier + * @param {any} options + * @param {any} tailwindConfig + * @returns {Iterator<[value: string, type: string, modifier: string | null]>} + */ +export function* getMatchingTypes(types, rawModifier, options, tailwindConfig) { + let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers') + + let [modifier, utilityModifier] = splitUtilityModifier(rawModifier) + + let canUseUtilityModifier = + modifiersEnabled && + options.modifiers != null && + (options.modifiers === 'any' || + (typeof options.modifiers === 'object' && + ((utilityModifier && isArbitraryValue(utilityModifier)) || + utilityModifier in options.modifiers))) + + if (!canUseUtilityModifier) { + modifier = rawModifier + utilityModifier = undefined + } + + if (utilityModifier !== undefined && modifier === '') { + modifier = 'DEFAULT' + } + + // Check the full value first + // TODO: Move to asValue… somehow + if (utilityModifier !== undefined) { + if (typeof options.modifiers === 'object') { + let configValue = options.modifiers?.[utilityModifier] ?? null + if (configValue !== null) { + utilityModifier = configValue + } else if (isArbitraryValue(utilityModifier)) { + utilityModifier = unwrapArbitraryModifier(utilityModifier) + } + } + } + + for (let { type } of types ?? []) { + let result = typeMap[type](modifier, options, { + tailwindConfig, + }) + + if (result === undefined) { + continue + } + + yield [result, type, utilityModifier ?? null] + } +} |