twin.macro
Version:
Twin blends the magic of Tailwind with the flexibility of css-in-js
1,327 lines (1,117 loc) • 118 kB
JavaScript
var babelPluginMacros = require('babel-plugin-macros');
var path = require('path');
var fs = require('fs');
var toPath$1 = require('tailwindcss/lib/util/toPath');
var generateRules = require('tailwindcss/lib/lib/generateRules');
var setupContextUtils = require('tailwindcss/lib/lib/setupContextUtils');
require('tailwindcss/stubs/config.full');
var transformThemeValueRaw = require('tailwindcss/lib/util/transformThemeValue');
var resolveTailwindConfigRaw = require('tailwindcss/lib/util/resolveConfig');
var getAllConfigsRaw = require('tailwindcss/lib/util/getAllConfigs');
var splitAtTopLevelOnly$1 = require('tailwindcss/lib/util/splitAtTopLevelOnly');
var unescapeRaw = require('postcss-selector-parser/dist/util/unesc');
var chalk = require('chalk');
var loadConfig = require('tailwindcss/loadConfig');
var deepMerge = require('lodash.merge');
var get = require('lodash.get');
var template = require('@babel/template');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var transformThemeValueRaw__default = /*#__PURE__*/_interopDefaultLegacy(transformThemeValueRaw);
var resolveTailwindConfigRaw__default = /*#__PURE__*/_interopDefaultLegacy(resolveTailwindConfigRaw);
var getAllConfigsRaw__default = /*#__PURE__*/_interopDefaultLegacy(getAllConfigsRaw);
var unescapeRaw__default = /*#__PURE__*/_interopDefaultLegacy(unescapeRaw);
var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);
var loadConfig__default = /*#__PURE__*/_interopDefaultLegacy(loadConfig);
var deepMerge__default = /*#__PURE__*/_interopDefaultLegacy(deepMerge);
var get__default = /*#__PURE__*/_interopDefaultLegacy(get);
var template__default = /*#__PURE__*/_interopDefaultLegacy(template);
function escalade (start, callback) {
let dir = path.resolve('.', start);
let tmp, stats = fs.statSync(dir);
if (!stats.isDirectory()) {
dir = path.dirname(dir);
}
while (true) {
tmp = callback(dir, fs.readdirSync(dir));
if (tmp) return path.resolve(dir, tmp);
dir = path.dirname(tmp = dir);
if (tmp === dir) break;
}
}
const TWIN_CONFIG_DEFAULTS = {
allowStyleProp: false,
autoCssProp: false,
config: undefined,
convertHtmlElementToStyled: false,
convertStyledDotToParam: false,
convertStyledDotToFunction: false,
css: {
import: '',
from: ''
},
dataCsProp: false,
dataTwProp: false,
debug: false,
disableCsProp: true,
disableShortCss: true,
global: {
import: '',
from: ''
},
hasLogColors: true,
includeClassNames: false,
moveTwPropToStyled: false,
moveKeyframesToGlobalStyles: false,
preset: undefined,
sassyPseudo: false,
stitchesConfig: undefined,
styled: {
import: '',
from: ''
}
}; // Defaults for different css-in-js libraries
const configDefaultsStyledComponents = {
sassyPseudo: true // Sets selectors like hover to &:hover
};
const configDefaultsGoober = {
sassyPseudo: true // Sets selectors like hover to &:hover
};
const configDefaultsSolid = {
sassyPseudo: true,
moveTwPropToStyled: true,
convertHtmlElementToStyled: true,
convertStyledDotToFunction: true // Convert styled.[element] to a default syntax
};
const configDefaultsStitches = {
sassyPseudo: true,
convertStyledDotToParam: true,
moveTwPropToStyled: true,
convertHtmlElementToStyled: true,
stitchesConfig: undefined,
moveKeyframesToGlobalStyles: true // Stitches doesn't support inline @keyframes
};
function configDefaultsTwin({
isSolid,
isStyledComponents,
isGoober,
isStitches,
isDev
}) {
return { ...TWIN_CONFIG_DEFAULTS,
...(isSolid && configDefaultsSolid),
...(isStyledComponents && configDefaultsStyledComponents),
...(isGoober && configDefaultsGoober),
...(isStitches && configDefaultsStitches),
dataTwProp: isDev,
dataCsProp: isDev
};
}
function isBoolean(value) {
return typeof value === 'boolean';
}
const allowedPresets = ['styled-components', 'emotion', 'goober', 'stitches', 'solid'];
const configTwinValidators = {
preset: [value => value === undefined || typeof value === 'string' && allowedPresets.includes(value), `The config “preset” can only be:\n${allowedPresets.map(p => `'${p}'`).join(', ')}`],
allowStyleProp: [isBoolean, 'The config “allowStyleProp” can only be a boolean'],
autoCssProp: [value => !value, 'The “autoCssProp” feature has been removed from twin.macro@2.8.2+\nThis means the css prop must be added by styled-components instead.\nSetup info at https://twinredirect.page.link/auto-css-prop\n\nRemove the “autoCssProp” item from your config to avoid this message.'],
convertStyledDot: [value => !value, 'The “convertStyledDot” feature was changed to “convertStyledDotParam”.'],
disableColorVariables: [value => !value, 'The disableColorVariables feature has been removed from twin.macro@3+\n\nRemove the disableColorVariables item from your config to avoid this message.'],
sassyPseudo: [isBoolean, 'The config “sassyPseudo” can only be a boolean'],
dataTwProp: [value => isBoolean(value) || value === 'all', 'The config “dataTwProp” can only be true, false or "all"'],
dataCsProp: [value => isBoolean(value) || value === 'all', 'The config “dataCsProp” can only be true, false or "all"'],
includeClassNames: [isBoolean, 'The config “includeClassNames” can only be a boolean'],
disableCsProp: [isBoolean, 'The config “disableCsProp” can only be a boolean'],
convertStyledDotToParam: [isBoolean, 'The config “convertStyledDotToParam” can only be a boolean'],
convertStyledDotToFunction: [isBoolean, 'The config “convertStyledDotToFunction” can only be a boolean'],
moveTwPropToStyled: [isBoolean, 'The config “moveTwPropToStyled” can only be a boolean'],
convertHtmlElementToStyled: [isBoolean, 'The config “convertHtmlElementToStyled” can only be a boolean']
};
function toArray(array) {
if (Array.isArray(array)) return array;
return [array];
}
const AMPERSAND_AFTER = /&(.+)/g;
const AMPERSAND = /&/g;
function stripAmpersands(string) {
return typeof string === 'string' ? string.replace(AMPERSAND, '').trim() : string;
}
const EXTRA_VARIANTS = [['all', '& *'], ['all-child', '& > *'], ['sibling', '& ~ *'], ['hocus', ['&:hover', '&:focus']], 'link', 'read-write', ['svg', '& svg'], ['even-of-type', '&:nth-of-type(even)'], ['odd-of-type', '&:nth-of-type(odd)']];
const EXTRA_NOT_VARIANTS = [// Positional
['first', '&:first-child'], ['last', '&:last-child'], ['only', '&:only-child'], ['odd', '&:nth-child(odd)'], ['even', '&:nth-child(even)'], 'first-of-type', 'last-of-type', 'only-of-type', // State
'target', ['open', '&[open]'], // Forms
'default', 'checked', 'indeterminate', 'placeholder-shown', 'autofill', 'optional', 'required', 'valid', 'invalid', 'in-range', 'out-of-range', 'read-only', // Content
'empty', // Interactive
'focus-within', 'hover', 'focus', 'focus-visible', 'active', 'enabled', 'disabled'];
function defaultVariants({
config,
addVariant
}) {
const extraVariants = EXTRA_VARIANTS.flatMap(v => {
let [name, selector] = toArray(v);
selector = selector || `&:${String(name)}`;
const variant = [name, selector]; // Create a :not() version of the selectors above
const notVariant = [`not-${String(name)}`, toArray(selector).map(s => `&:not(${stripAmpersands(s)})`)];
return [variant, notVariant];
}); // Create :not() versions of these selectors
const notPseudoVariants = EXTRA_NOT_VARIANTS.map(v => {
const [name, selector] = toArray(v);
const notConfig = [`not-${name}`, toArray(selector || `&:${name}`).map(s => `&:not(${stripAmpersands(s)})`)];
return notConfig;
});
const variants = [...extraVariants, ...notPseudoVariants];
for (const [name, selector] of variants) {
addVariant(name, toArray(selector));
}
for (const [name, selector] of variants) {
const groupSelector = toArray(selector).map(s => s.replace(AMPERSAND_AFTER, ':merge(.group)$1 &'));
addVariant(`group-${name}`, groupSelector);
}
for (const [name, selector] of variants) {
const peerSelector = toArray(selector).map(s => s.replace(AMPERSAND_AFTER, ':merge(.peer)$1 ~ &'));
addVariant(`peer-${name}`, peerSelector);
} // https://developer.mozilla.org/en-US/docs/Web/CSS/@media/any-pointer
addVariant('any-pointer-none', '@media (any-pointer: none)');
addVariant('any-pointer-fine', '@media (any-pointer: fine)');
addVariant('any-pointer-coarse', '@media (any-pointer: coarse)'); // https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer
addVariant('pointer-none', '@media (pointer: none)');
addVariant('pointer-fine', '@media (pointer: fine)');
addVariant('pointer-coarse', '@media (pointer: coarse)'); // https://developer.mozilla.org/en-US/docs/Web/CSS/@media/any-hover
addVariant('any-hover-none', '@media (any-hover: none)');
addVariant('any-hover', '@media (any-hover: hover)'); // https://developer.mozilla.org/en-US/docs/Web/CSS/@media/hover
addVariant('can-hover', '@media (hover: hover)');
addVariant('cant-hover', '@media (hover: none)');
addVariant('screen', '@media screen'); // Light mode
// eslint-disable-next-line unicorn/prefer-spread
let [mode, className = '.light'] = [].concat(config('lightMode', 'media'));
if (mode === false) mode = 'media';
if (mode === 'class') {
addVariant('light', `${String(className)} &`);
} else if (mode === 'media') {
addVariant('light', '@media (prefers-color-scheme: light)');
} // eslint-disable-next-line unicorn/prefer-spread
[mode, className = '.light'] = [].concat(config('lightMode', 'media'));
if (mode === 'class') {
addVariant('light', `${className} &`);
} else if (mode === 'media') {
addVariant('light', '@media (prefers-color-scheme: light)');
}
}
const defaultTailwindConfig = {
presets: [{
content: [''],
theme: {
extend: {
content: {
DEFAULT: ''
},
zIndex: {
1: '1'
} // Add a handy small zIndex (`z-1` / `-z-1`)
}
},
plugins: [defaultVariants] // Add extra variants
}]
};
// @ts-expect-error Types added below
const toPath = toPath$1.toPath;
const createContext = setupContextUtils.createContext;
const resolveMatches = generateRules.resolveMatches;
const transformThemeValue = transformThemeValueRaw__default["default"];
const resolveTailwindConfig = resolveTailwindConfigRaw__default["default"];
const getAllConfigs = getAllConfigsRaw__default["default"];
const splitAtTopLevelOnly = splitAtTopLevelOnly$1.splitAtTopLevelOnly;
const unescape = unescapeRaw__default["default"];
function isObject(value) {
// eslint-disable-next-line eqeqeq, no-eq-null
return value != null && typeof value === 'object' && !Array.isArray(value);
}
const colors$1 = {
error: chalk__default["default"].hex('#ff8383'),
errorLight: chalk__default["default"].hex('#ffd3d3'),
warn: chalk__default["default"].yellowBright,
success: chalk__default["default"].greenBright,
highlight: chalk__default["default"].yellowBright,
subdued: chalk__default["default"].hex('#999')
};
function makeColor$1(hasColor) {
return (message, type = 'error') => {
if (!hasColor) return message;
return colors$1[type](message);
};
}
function spaced(string) {
return `\n\n${string}\n`;
}
function warning(string) {
return colors$1.error(`✕ ${string}`);
}
function logGeneralError(error) {
return Array.isArray(error) ? spaced(`${warning(typeof error[0] === 'function' ? error[0](colors$1) : error[0])}\n\n${error[1]}`) : spaced(warning(error));
}
function createDebug(isDev, twinConfig) {
return (reference, data, type = 'subdued') => {
if (!isDev) return;
if (!twinConfig.debug) return;
const log = `${String(colors$1[type]('-'))} ${reference} ${String(colors$1[type](JSON.stringify(data)))}`; // eslint-disable-next-line no-console
console.log(log);
};
}
function getTailwindConfig({
sourceRoot,
filename,
config,
assert
}) {
var _sourceRoot, _escalade;
sourceRoot = (_sourceRoot = sourceRoot) != null ? _sourceRoot : '.';
const baseDirectory = filename ? path.dirname(filename) : process.cwd();
const userTailwindConfig = config == null ? void 0 : config.config;
if (isObject(userTailwindConfig)) return resolveTailwindConfig([// User config
...getAllConfigs(userTailwindConfig), // Default config
...getAllConfigs(defaultTailwindConfig)]);
const configPath = userTailwindConfig ? path.resolve(sourceRoot, userTailwindConfig) : (_escalade = escalade(baseDirectory, (_, names) => {
if (names.includes('tailwind.config.js')) return 'tailwind.config.js';
if (names.includes('tailwind.config.cjs')) return 'tailwind.config.cjs';
if (names.includes('tailwind.config.ts')) return 'tailwind.config.ts';
})) != null ? _escalade : '';
const configExists = Boolean(configPath && fs.existsSync(configPath));
if (userTailwindConfig) assert(configExists, ({
color
}) => [`${String(color(`✕ The tailwind config ${color(String(userTailwindConfig), 'errorLight')} wasn’t found`))}`, `Update the \`config\` option in your twin config`].join('\n\n'));
const configs = [// User config
...(configExists ? getAllConfigs(loadConfig__default["default"](configPath)) : []), // Default config
...getAllConfigs(defaultTailwindConfig)];
const tailwindConfig = resolveTailwindConfig(configs);
return tailwindConfig;
}
function runConfigValidator([item, value]) {
const validatorConfig = configTwinValidators[item];
if (!validatorConfig) return true;
const [validator, errorMessage] = validatorConfig;
if (typeof validator !== 'function') return false;
if (!validator(value)) {
throw new Error(logGeneralError(String(errorMessage)));
}
return true;
}
function getConfigTwin(config, params) {
const output = { ...configDefaultsTwin(params),
...config
};
return output;
}
function getConfigTwinValidated(config, params) {
const twinConfig = getConfigTwin(config, params); // eslint-disable-next-line unicorn/no-array-reduce
return Object.entries(twinConfig).reduce((result, item) => {
const validatedItem = item;
return { ...result,
...(runConfigValidator(validatedItem) && {
[validatedItem[0]]: validatedItem[1]
})
};
}, {});
}
function getFirstValue(list, getValue) {
let firstValue;
const listLength = list.length - 1;
const listItem = list.find((listItem, index) => {
const isLast = index === listLength;
firstValue = getValue(listItem, {
index,
isLast
});
return Boolean(firstValue);
});
return [firstValue, listItem];
}
function checkExists(fileName, sourceRoot) {
const [, value] = getFirstValue(toArray(fileName), existingFileName => fs.existsSync(path.resolve(sourceRoot, `./${existingFileName}`)));
return value;
}
function getRelativePath(comparePath, filename) {
const pathName = path.parse(filename).dir;
return path.relative(pathName, comparePath);
}
function getStitchesPath({
sourceRoot,
filename,
config
}) {
var _sourceRoot, _config$stitchesConfi;
sourceRoot = (_sourceRoot = sourceRoot) != null ? _sourceRoot : '.';
const configPathCheck = (_config$stitchesConfi = config.stitchesConfig) != null ? _config$stitchesConfi : ['stitches.config.ts', 'stitches.config.js'];
const configPath = checkExists(configPathCheck, sourceRoot);
if (!configPath) throw new Error(logGeneralError(`Couldn’t find the Stitches config at ${config.stitchesConfig ? `“${String(config.stitchesConfig)}”` : 'the project root'}.\nUse the twin config: stitchesConfig="PATH_FROM_PROJECT_ROOT" to set the location.`));
return getRelativePath(configPath, filename);
}
/**
* Config presets
*
* To change the preset, add the following in `package.json`:
* `{ "babelMacros": { "twin": { "preset": "styled-components" } } }`
*
* Or in `babel-plugin-macros.config.js`:
* `module.exports = { twin: { preset: "styled-components" } }`
*/
const userPresets = {
'styled-components': {
styled: {
import: 'default',
from: 'styled-components'
},
css: {
import: 'css',
from: 'styled-components'
},
global: {
import: 'createGlobalStyle',
from: 'styled-components'
}
},
emotion: {
styled: {
import: 'default',
from: '@emotion/styled'
},
css: {
import: 'css',
from: '@emotion/react'
},
global: {
import: 'Global',
from: '@emotion/react'
}
},
goober: {
styled: {
import: 'styled',
from: 'goober'
},
css: {
import: 'css',
from: 'goober'
},
global: {
import: 'createGlobalStyles',
from: 'goober/global'
}
},
stitches: {
styled: {
import: 'styled',
from: 'stitches.config'
},
css: {
import: 'css',
from: 'stitches.config'
},
global: {
import: 'global',
from: 'stitches.config'
}
},
solid: {
styled: {
import: 'styled',
from: 'solid-styled-components'
},
css: {
import: 'css',
from: 'solid-styled-components'
},
global: {
import: 'createGlobalStyles',
from: 'solid-styled-components'
}
}
};
function dlv(t,e,l,n,r){for(e=e.split?e.split("."):e,n=0;n<e.length;n++)t=t?t[e[n]]:r;return t===r?l:t}
function createTheme(tailwindConfig) {
function getConfigValue(path, defaultValue) {
return dlv(tailwindConfig, path, defaultValue);
}
function resolveThemeValue(path, defaultValue, options = {}) {
let [pathRoot, ...subPaths] = toPath(path); // Retain dots in spacing values, eg: `ml-[theme(spacing.0.5)]`
if (pathRoot === 'spacing' && subPaths.length === 2 && subPaths.every(x => !Number.isNaN(Number(x)))) {
subPaths = [subPaths.join('.')];
}
const value = getConfigValue(path ? ['theme', pathRoot, ...subPaths] : ['theme'], defaultValue);
return sassifyValues(transformThemeValue(pathRoot)(value, options));
}
const out = Object.assign((path, defaultValue) => resolveThemeValue(path, defaultValue), {
withAlpha: (path, opacityValue) => resolveThemeValue(path, undefined, {
opacityValue
})
});
return out;
}
function sassifyValues(values) {
if (!isObject(values)) return values;
const transformed = Object.entries(values).map(([k, v]) => [k, isObject(v) && sassifyValues(v) || typeof v === 'number' && String(v) || v]);
return Object.fromEntries(transformed);
}
function createAssert(CustomError = Error, isSilent = false, hasLogColors = true) {
return (expression, message) => {
if (isSilent) return;
if (typeof expression === 'string') {
throw new CustomError(`\n\n${expression}\n`);
}
const messageContext = {
color: makeColor$1(hasLogColors)
};
if (typeof expression === 'function') {
throw new CustomError(`\n\n${expression(messageContext)}\n`);
}
if (expression) return;
if (typeof message === 'string') {
throw new CustomError(`\n\n${message}\n`);
}
if (typeof message === 'function') {
throw new CustomError(`\n\n${message(messageContext)}\n`);
}
};
}
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
function packageCheck(packageToCheck, params, hasNoFallback) {
if (params.config && params.config.preset === packageToCheck) return true;
if (hasNoFallback) return false;
return params.styledImport.from.includes(packageToCheck) || params.cssImport.from.includes(packageToCheck);
}
function getPackageUsed(params) {
return {
isEmotion: packageCheck('emotion', params),
isStyledComponents: packageCheck('styled-components', params, true),
isGoober: packageCheck('goober', params),
isSolid: packageCheck('solid', params),
isStitches: packageCheck('stitches', params)
};
}
function getStyledConfig({
sourceRoot,
filename,
config
}) {
const usedConfig = (config == null ? void 0 : config.styled) && config || (config == null ? void 0 : config.preset) && userPresets[config.preset] || userPresets.emotion;
if (typeof usedConfig.styled === 'string') {
return {
import: 'default',
from: usedConfig.styled
};
}
if (config && config.preset === 'stitches') {
const stitchesPath = getStitchesPath({
sourceRoot,
filename,
config
});
if (stitchesPath && usedConfig.styled) {
// Overwrite the stitches import data with the path from the current file
usedConfig.styled.from = stitchesPath;
}
}
return usedConfig.styled;
}
function getCssConfig({
sourceRoot,
filename,
config
}) {
const usedConfig = (config == null ? void 0 : config.css) && config || (config == null ? void 0 : config.preset) && userPresets[config.preset] || userPresets.emotion;
if (typeof usedConfig.css === 'string') {
return {
import: 'css',
from: usedConfig.css
};
}
if (config && config.preset === 'stitches') {
const stitchesPath = getStitchesPath({
sourceRoot,
filename,
config
});
if (stitchesPath && usedConfig.css) {
// Overwrite the stitches import data with the path from the current file
usedConfig.css.from = stitchesPath;
}
}
return usedConfig.css;
}
function getGlobalConfig(config) {
const usedConfig = config.global && config || config.preset && userPresets[config.preset] || userPresets.emotion;
return usedConfig.global;
}
function createCoreContext(params) {
var _params$tailwindConfi;
const {
sourceRoot,
filename,
config,
isDev = false,
CustomError
} = params;
const assert = createAssert(CustomError, false, config == null ? void 0 : config.hasLogColors);
const configParameters = {
sourceRoot,
assert,
filename: filename != null ? filename : '',
config
};
const styledImport = getStyledConfig(configParameters);
const cssImport = getCssConfig(configParameters);
const tailwindConfig = (_params$tailwindConfi = params.tailwindConfig) != null ? _params$tailwindConfi : getTailwindConfig(configParameters);
const packageUsed = getPackageUsed({
config,
cssImport,
styledImport
});
const twinConfig = getConfigTwinValidated(config, { ...packageUsed,
isDev
});
const importConfig = {
styled: styledImport,
css: cssImport,
global: getGlobalConfig(config != null ? config : {})
};
return {
isDev,
assert,
debug: createDebug(isDev, twinConfig),
theme: createTheme(tailwindConfig),
tailwindContext: createContext(tailwindConfig),
packageUsed,
tailwindConfig,
twinConfig,
CustomError,
importConfig
};
}
const CAMEL_FIND = /\W+(.)/g;
function camelize(string) {
return string == null ? void 0 : string.replace(CAMEL_FIND, (_, chr) => chr.toUpperCase());
}
const MATCH_THEME = /theme\((.+?)\)/;
const MATCH_QUOTES = /["'`]/g;
function replaceThemeValue(value, {
assert,
theme
}) {
const match = MATCH_THEME.exec(value);
if (!match) return value;
const themeFunction = match[0];
const themeParameters = match[1].replace(MATCH_QUOTES, '').trim();
const [main, second] = themeParameters.split(',');
let themeValue = theme(main, second);
assert(Boolean(themeValue), ({
color
}) => color(`✕ ${color(themeParameters, 'errorLight')} doesn’t match a theme value from the config`)); // Account for the 'DEFAULT' key
if (typeof themeValue === 'object' && 'DEFAULT' in themeValue) {
themeValue = themeValue.DEFAULT;
} // Escape spaces in the value - without this we get an incorrect order
// in class groups like this:
// tw`w-[calc(100%-theme('spacing.1'))] w-[calc(100%-theme('spacing[0.5]'))]`
// theme: { spacing: { 0.5: "calc(.5 * .25rem)", 1: "calc(1 * .25rem)" } }
const stringValue = String(themeValue).replace(/\./g, '\\.');
const replacedValue = value.replace(themeFunction, stringValue);
return replacedValue;
}
const SELECTOR_PARENT_CANDIDATE = /^[ #.[]/;
const SELECTOR_SPECIAL_STARTS = /^ [>@]/;
const SELECTOR_ROOT = /(^| ):root(?!\w)/g;
const UNDERSCORE_ESCAPING$1 = /\\+(_)/g;
const WRAPPED_PARENT_SELECTORS = /(\({3}&(.*?)\){3})/g;
const sassifySelectorTasks = [selector => selector.trim(), // Prefix with the parent selector when sassyPseudo is enabled,
// otherwise just replace the class with the parent selector
(selector, {
selectorMatchReg,
sassyPseudo,
original
}) => {
const out = selector.replace(selectorMatchReg, (match, __, offset) => {
if (selector === match) return '';
if (/\w/.test(selector[offset - 1]) && selector[offset + match.length] === ':') {
if (sassyPseudo && selector[offset - 1] === undefined) return '&';
return ''; // Cover [section&]:hover:block / .btn.loading&:before
}
return offset === 0 ? '' : '&';
}); // Fix certain matches not covered by the previous task, eg: `first:[section]:m-1`
// (Arbitrary variants targeting html elements)
if (original && out === selector && selector.includes(`.${original}`)) return selector.replace(`.${original}`, '');
return out;
}, // Unwrap the pre-wrapped parent selectors (pre-wrapping avoids matching issues against word characters, eg: `[§ion]:block`)
selector => selector.replace(WRAPPED_PARENT_SELECTORS, '&$2'), // Remove unneeded escaping from the selector
selector => selector.replace(UNDERSCORE_ESCAPING$1, '$1'), // Prefix classes/ids/attribute selectors with a parent selector so styles
// are applied to the current element rather than its children
selector => {
if (selector.includes('&')) return selector;
const addParentSelector = SELECTOR_PARENT_CANDIDATE.test(selector);
if (!addParentSelector) return selector; // Fix: ` > :not([hidden]) ~ :not([hidden])` / ` > *`
// Fix: `[@page]:x`
if (SELECTOR_SPECIAL_STARTS.test(selector)) return selector;
return `&${selector}`;
}, // Fix the spotty `:root` support in emotion/styled-components
selector => selector.replace(SELECTOR_ROOT, '*:root'), // Escape selectors containing forward slashes, eg: group-hover/link:bg-black
selector => selector.replace(/\//g, '\\/'), selector => selector.trim()];
function sassifySelector(selector, params) {
// Remove the selector if it only contains the parent selector
if (selector === '&') {
params.debug('selector not required', selector);
return '';
}
for (const task of sassifySelectorTasks) {
selector = task(selector, params);
}
return selector;
}
const DEFAULTS_UNIVERSAL = '*, ::before, ::after';
const EMPTY_CSS_VARIABLE_VALUE = 'var(--tw-empty,/*!*/ /*!*/)';
const PRESERVED_ATRULE_TYPES = new Set(['charset', 'counter-style', 'document', 'font-face', 'font-feature-values', 'import', 'keyframes', 'namespace']);
const LAYER_DEFAULTS = 'defaults';
const LINEFEED$1 = /\n/g;
const WORD_CHARACTER = /\w/;
const SPACE_ID$1 = '_';
const SPACES = /\s+/g;
const BRACKETED = /^\(.*?\)$/;
const BRACKETED_MAYBE_IMPORTANT = /\)!?$/;
const ESCAPE_CHARACTERS$1 = /\n|\t/g;
function spreadVariantGroups(classes, context) {
var _context$tailwindConf, _context$beforeImport, _context$afterImporta;
const pieces = [...splitAtTopLevelOnly(classes.trim(), (_context$tailwindConf = context.tailwindConfig.separator) != null ? _context$tailwindConf : ':')];
let groupedClasses = pieces.pop();
if (!groupedClasses) return []; // type guard
// Check for too many dividers used
// Added here instead of "validateClasses" as it's less error prone to check here
context.assert(!pieces.includes(''), ({
color
}) => {
var _context$tailwindConf2;
return `${color(`✕ ${String(color(classes, 'errorLight'))} has too many dividers`)}\n\nUpdate to ${String(color(`${pieces.filter(Boolean).join((_context$tailwindConf2 = context.tailwindConfig.separator) != null ? _context$tailwindConf2 : ':')}`, 'success'))}`;
});
let beforeImportant = (_context$beforeImport = context == null ? void 0 : context.beforeImportant) != null ? _context$beforeImport : '';
let afterImportant = (_context$afterImporta = context == null ? void 0 : context.afterImportant) != null ? _context$afterImporta : '';
if (!beforeImportant && groupedClasses.startsWith('!')) {
groupedClasses = groupedClasses.slice(1);
beforeImportant = '!';
}
if (!afterImportant && groupedClasses.endsWith('!')) {
groupedClasses = groupedClasses.slice(0, -1);
afterImportant = '!';
} // Remove () brackets and split
const unwrapped = BRACKETED.test(groupedClasses) ? groupedClasses.slice(1, -1) : groupedClasses;
const classList = [...splitAtTopLevelOnly(unwrapped, ' ')].filter(Boolean);
const group = classList.map(className => {
var _context$tailwindConf4;
if (BRACKETED_MAYBE_IMPORTANT.test(className) && // Avoid infinite loop due to lack of separator, eg: `[em](block)`
!className.includes('](')) {
var _context$tailwindConf3;
const ctx = { ...context,
beforeImportant,
afterImportant
};
return expandVariantGroups([...pieces, className].join((_context$tailwindConf3 = context.tailwindConfig.separator) != null ? _context$tailwindConf3 : ':'), ctx);
}
return [...pieces, [beforeImportant, className, afterImportant].join('')].filter(Boolean).join((_context$tailwindConf4 = context.tailwindConfig.separator) != null ? _context$tailwindConf4 : ':');
}).filter(Boolean);
return group;
}
function expandVariantGroups(classes, context) {
const classList = [...splitAtTopLevelOnly(classes.replace(ESCAPE_CHARACTERS$1, ' ').trim(), ' ')];
if (classList.length === 1 && ['', '()'].includes(classList[0])) return '';
const expandedClasses = classList.flatMap(item => spreadVariantGroups(item, context));
return expandedClasses.join(' ');
}
const REGEX_SPECIAL_CHARACTERS = /[$()*+./?[\\\]^{|}-]/g;
function escapeRegex(string) {
return string.replace(REGEX_SPECIAL_CHARACTERS, '\\$&');
}
function isShortCss(fullClassName, tailwindConfig) {
var _tailwindConfig$separ;
const classPieces = [...splitAtTopLevelOnly(fullClassName, (_tailwindConfig$separ = tailwindConfig.separator) != null ? _tailwindConfig$separ : ':')];
const className = classPieces.slice(-1)[0];
if (!className.includes('[')) return false; // Replace brackets before splitting on them as the split function already
// reads brackets to determine where the top level is
const splitAtArbitrary = [...splitAtTopLevelOnly(className.replace(/\[/g, '∀'), '∀')]; // Normal class
if (splitAtArbitrary[0].endsWith('-')) return false; // Important suffix
if (splitAtArbitrary[0].endsWith('!')) return false; // Arbitrary property
if (splitAtArbitrary[0] === '') return false; // Slash opacity, eg: bg-red-500/fromConfig/[.555]
if (splitAtArbitrary[0].endsWith('/')) return false;
return true;
}
// Split a string at a value and return an array of the two parts
function splitOnFirst(input, delim) {
return (([first, ...rest]) => [first, rest.join(delim)])(input.split(delim));
}
const ALL_COMMAS = /,/g;
const ALL_AMPERSANDS = /&/g;
const ENDING_AMP_THEN_WHITESPACE = /&[\s_]*$/;
const ALL_CLASS_DOTS = /(?<!\\)(\.)(?=\w)/g;
const ALL_CLASS_ATS = /(?<!\\)(@)(?=\w)(?!media)/g;
const ALL_WRAPPABLE_PARENT_SELECTORS = /&(?=([^ $)*+,.:>[_~])[\w-])/g;
const BASIC_SELECTOR_TYPES = /^#|^\\.|[^\W_]/;
function convertShortCssToArbitraryProperty(className, {
tailwindConfig,
assert,
disableShortCss,
isShortCssOnly,
origClassName
}) {
var _tailwindConfig$separ, _tailwindConfig$separ2, _tailwindConfig$separ3;
const splitArray = [...splitAtTopLevelOnly(className, (_tailwindConfig$separ = tailwindConfig.separator) != null ? _tailwindConfig$separ : ':')];
const lastValue = splitArray.slice(-1)[0];
let [property, value] = splitOnFirst(lastValue, '[');
value = value.slice(0, -1).trim();
let preSelector = '';
if (property.startsWith('!')) {
property = property.slice(1);
preSelector = '!';
}
const template = `${preSelector}[${[property, value === '' ? "''" : value].join((_tailwindConfig$separ2 = tailwindConfig.separator) != null ? _tailwindConfig$separ2 : ':')}]`;
splitArray.splice(-1, 1, template);
const arbitraryProperty = splitArray.join((_tailwindConfig$separ3 = tailwindConfig.separator) != null ? _tailwindConfig$separ3 : ':');
const isShortCssDisabled = disableShortCss && !isShortCssOnly;
assert(!isShortCssDisabled, ({
color
}) => [`${String(color(`✕ ${String(color(origClassName, 'errorLight'))} uses twin’s deprecated short-css syntax`))}`, `Update to ${String(color(arbitraryProperty, 'success'))}`, `To ignore this notice, add this to your twin config:\n{ "disableShortCss": false }`, `Read more at https://twinredirect.page.link/short-css`].join('\n\n'));
return arbitraryProperty;
}
function checkForVariantSupport({
className,
tailwindConfig,
assert
}) {
var _tailwindConfig$separ4;
const pieces = [...splitAtTopLevelOnly(className, (_tailwindConfig$separ4 = tailwindConfig.separator) != null ? _tailwindConfig$separ4 : ':')];
const hasMultipleVariants = pieces.length > 2; // One is the class name
const hasACommaInVariants = pieces.some(p => splitAtTopLevelOnly(p.slice(1, -1), ',').length > 1);
const hasIssue = hasMultipleVariants && hasACommaInVariants;
assert(!hasIssue, ({
color
}) => `${color(`✕ The variants on ${String(color(className, 'errorLight'))} are invalid tailwind and twin classes`)}\n\n${color(`To fix, either reduce all variants into a single arbitrary variant:`, 'success')}\nFrom: \`[.this, .that]:first:block\`\nTo: \`[.this:first, .that:first]:block\`\n\n${color(`Or split the class into separate classes instead of using commas:`, 'success')}\nFrom: \`[.this, .that]:first:block\`\nTo: \`[.this]:first:block [.that]:first:block\`\n\nRead more at https://twinredirect.page.link/arbitrary-variants-with-commas`);
} // Convert a twin class to a tailwindcss friendly class
function convertClassName(className, {
tailwindConfig,
theme,
isShortCssOnly,
disableShortCss,
assert,
debug
}) {
checkForVariantSupport({
className,
tailwindConfig,
assert
});
const origClassName = className; // Convert spaces to class friendly underscores
className = className.replace(SPACES, SPACE_ID$1); // Move the bang to the front of the class
if (className.endsWith('!')) {
var _tailwindConfig$separ5, _tailwindConfig$separ6;
debug('trailing bang found', className);
const splitArray = [...splitAtTopLevelOnly(className.slice(0, -1), (_tailwindConfig$separ5 = tailwindConfig.separator) != null ? _tailwindConfig$separ5 : ':')]; // Place a ! before the class
splitArray.splice(-1, 1, `!${splitArray[splitArray.length - 1]}`);
className = splitArray.join((_tailwindConfig$separ6 = tailwindConfig.separator) != null ? _tailwindConfig$separ6 : ':');
} // Convert short css to an arbitrary property, eg: `[display:block]`
// (Short css is deprecated)
if (isShortCss(className, tailwindConfig)) {
debug('short css found', className);
className = convertShortCssToArbitraryProperty(className, {
tailwindConfig,
assert,
disableShortCss,
isShortCssOnly,
origClassName
});
} // Replace theme values throughout the class
className = replaceThemeValue(className, {
assert,
theme
}); // Add missing parent selectors and collapse arbitrary variants
className = sassifyArbitraryVariants(className, {
tailwindConfig
});
debug('class after format', className);
return className;
}
function isArbitraryVariant(variant) {
return variant.startsWith('[') && variant.endsWith(']');
}
function unbracket(variant) {
return variant.slice(1, -1);
}
function sassifyArbitraryVariants(fullClassName, {
tailwindConfig
}) {
var _tailwindConfig$separ7, _tailwindConfig$separ8;
const splitArray = [...splitAtTopLevelOnly(fullClassName, (_tailwindConfig$separ7 = tailwindConfig.separator) != null ? _tailwindConfig$separ7 : ':')];
const variants = splitArray.slice(0, -1);
const className = splitArray.slice(-1)[0];
if (variants.length === 0) return fullClassName; // Collapse arbitrary variants when they don't contain `&`.
// `[> div]:[.nav]:(flex block)` -> `[> div_.nav]:flex [> div_.nav]:block`
const collapsed = [];
variants.forEach((variant, index) => {
// We can’t match the selector if there's a character right next to the parent selector (eg: `[§ion]:block`) otherwise we'd accidentally replace `.step` in classes like this:
// Bad: `.steps-primary .steps` -> `&-primary &`
// Good: `.steps-primary .steps` -> `.steps-primary &`
// So here we replace it with crazy brackets to identify and unwrap it later
if (isArbitraryVariant(variant)) variant = variant.replace(ALL_WRAPPABLE_PARENT_SELECTORS, '(((&)))');
if (index === 0 || !isArbitraryVariant(variant) || !isArbitraryVariant(variants[index - 1])) return collapsed.push(variant);
const prev = collapsed[collapsed.length - 1];
if (variant.includes('&')) {
const prevHasParent = prev.includes('&'); // Merge with current
if (prevHasParent) {
const mergedWithCurrent = variant.replace(ALL_AMPERSANDS, unbracket(prev));
const isLast = index === variants.length - 1;
collapsed[index - 1] = isLast ? mergedWithCurrent.replace(ALL_AMPERSANDS, '') : mergedWithCurrent;
return;
} // Merge with previous
if (!prevHasParent) {
const mergedWithPrev = `[${unbracket(variant).replace(ALL_AMPERSANDS, unbracket(prev))}]`;
collapsed[collapsed.length - 1] = mergedWithPrev;
return;
}
} // Parentless variants are merged into the previous arbitrary variant
const mergedWithPrev = `[${[unbracket(prev).replace(ENDING_AMP_THEN_WHITESPACE, ''), unbracket(variant)].join('_')}]`;
collapsed[collapsed.length - 1] = mergedWithPrev;
}); // The supplied class requires the reversal of it's variants as resolveMatches adds them in reverse order
const reversedVariantList = [...collapsed].slice().reverse();
const allVariants = reversedVariantList.map((v, idx) => {
if (!isArbitraryVariant(v)) return v;
const unwrappedVariant = unbracket(v) // Unescaped dots incorrectly add the prefix within arbitrary variants (only when`prefix` is set in tailwind config)
// eg: tw`[.a]:first:tw-block` -> `.tw-a &:first-child`
.replace(ALL_CLASS_DOTS, '\\.') // Unescaped ats will throw a conversion error
.replace(ALL_CLASS_ATS, '\\@');
const variantList = unwrappedVariant.startsWith('@') ? [unwrappedVariant] : // Arbitrary variants with commas are split, handled as separate selectors then joined
[...splitAtTopLevelOnly(unwrappedVariant, ',')];
const out = variantList.map(variant => {
var _collapsed;
return addParentSelector(variant, collapsed[idx - 1], (_collapsed = collapsed[idx + 1]) != null ? _collapsed : '');
}) // Tailwindcss removes everything from a comma onwards in arbitrary variants, so we need to encode to preserve them
// Underscore is needed to distance the code from another possible number
// Eg: [path[fill='rgb(51,100,51)']]:[fill:white]
.join('\\2c_').replace(ALL_COMMAS, '\\2c_');
return `[${out}]`;
});
return [...allVariants, className].join((_tailwindConfig$separ8 = tailwindConfig.separator) != null ? _tailwindConfig$separ8 : ':');
}
function addParentSelector(selector, prev, next) {
// Preserve selectors with a parent selector and media queries
if (selector.includes('&') || selector.startsWith('@')) return selector; // Arbitrary variants
// Pseudo elements get an auto parent selector prefixed
if (selector.startsWith(':')) return `&${selector}`; // Variants that start with a class/id get treated as a child
if (BASIC_SELECTOR_TYPES.test(selector) && !prev) return `& ${selector}`; // When there's more than one variant and it's at the end then prefix it
if (!next && prev) return `&${selector}`;
return `& ${selector}`;
}
const IMPORTANT_OUTSIDE_BRACKETS = /(:!|^!)(?=(?:(?:(?!\)).)*\()|[^()]*$)(?=(?:(?:(?!]).)*\[)|[^[\]]*$)/;
const COMMENTS_MULTI_LINE = /(?<!\/)\/(?!\/)\*[\S\s]*?\*\//g;
const COMMENTS_SINGLE_LINE = /(?<!:)\/\/.*/g;
const CLASS_DIVIDER_PIPE = / \| /g;
const ALL_BRACKET_SQUARE_LEFT = /\[/g;
const ALL_BRACKET_SQUARE_RIGHT = /]/g;
const ALL_BRACKET_ROUND_LEFT = /\(/g;
const ALL_BRACKET_ROUND_RIGHT = /\)/g;
const ESCAPE_CHARACTERS = /\n|\t/g;
function getStylesFromMatches(matches, params) {
if (matches.length === 0) {
params.debug('no matches supplied', {}, 'error');
return;
}
const rulesets = matches.map(([data, rule]) => extractRuleStyles([rule], { ...params,
options: data.options
})).filter(Boolean);
if (rulesets.length === 0) {
params.debug('no node rulesets found', {}, 'error');
return;
} // @ts-expect-error Avoid tuple type error
return deepMerge__default["default"](...rulesets);
} // When removing a multiline comment, determine if a space is left or not
// eg: You'd want a space left in this situation: tw`class1/* comment */class2`
function multilineReplaceWith(match, index, input) {
const charBefore = input[index - 1];
const directPrefixMatch = charBefore && WORD_CHARACTER.exec(charBefore);
const charAfter = input[Number(index) + Number(match.length)];
const directSuffixMatch = charAfter && WORD_CHARACTER.exec(charAfter);
return directPrefixMatch != null && directPrefixMatch[0] && directSuffixMatch && directSuffixMatch[0] ? ' ' : '';
}
function validateClasses(classes, {
assert,
tailwindConfig
}) {
var _classes$match, _classes$match2, _classes$match3, _classes$match4;
// TOFIX: Avoid counting brackets within arbitrary values
assert(((_classes$match = classes.match(ALL_BRACKET_SQUARE_LEFT)) != null ? _classes$match : []).length === ((_classes$match2 = classes.match(ALL_BRACKET_SQUARE_RIGHT)) != null ? _classes$match2 : []).length, ({
color
}) => `${color(`✕ Unbalanced square brackets found in classes:\n\n${color(classes, 'errorLight')}`)}`); // TOFIX: Avoid counting brackets within arbitrary values
assert(((_classes$match3 = classes.match(ALL_BRACKET_ROUND_LEFT)) != null ? _classes$match3 : []).length === ((_classes$match4 = classes.match(ALL_BRACKET_ROUND_RIGHT)) != null ? _classes$match4 : []).length, ({
color
}) => `${color(`✕ Unbalanced round brackets found in classes:\n\n${color(classes, 'errorLight')}`)}`);
for (const className of splitAtTopLevelOnly(classes, ' ')) {
var _tailwindConfig$separ;
// Check for missing class attached to a variant
const classCheck = className.replace(ESCAPE_CHARACTERS, ' ').trim();
assert(!classCheck.endsWith((_tailwindConfig$separ = tailwindConfig.separator) != null ? _tailwindConfig$separ : ':'), ({
color
}) => `${color(`✕ The variant ${String(color(classCheck, 'errorLight'))} doesn’t look right`)}\n\nUpdate to ${String(color(`${classCheck}block`, 'success'))} or ${String(color(`${classCheck}(block mt-4)`, 'success'))}`);
}
return true;
}
const tasks = [classes => classes.replace(CLASS_DIVIDER_PIPE, ' '), classes => classes.replace(COMMENTS_MULTI_LINE, multilineReplaceWith), classes => classes.replace(COMMENTS_SINGLE_LINE, ''), (classes, tailwindConfig, assert) => expandVariantGroups(classes, {
assert,
tailwindConfig
}) // Expand grouped variants to individual classes
];
function sortBigSign(bigIntValue) {
return Number(bigIntValue > 0n) - Number(bigIntValue < 0n);
}
function getOrderedClassList(tailwindContext, convertedClassList, classList, assert) {
assert(typeof (tailwindContext == null ? void 0 : tailwindContext.getClassOrder) === 'function', ({
color
}) => color('Twin requires a newer version of tailwindcss, please update')); // `getClassOrder` was added in tailwindcss@3.0.23
let orderedClassList;
try {
orderedClassList = tailwindContext.getClassOrder(convertedClassList).map(([className, order], index) => [order || 0n, className, classList[index]]).sort(([a], [z]) => sortBigSign(a - z));
} catch (error) {
assert(false, ({
color
}) => `${color(String(error).replace('with \\ may', 'with a single \\ may') // Improve error
)}\n\n${color('Found in:')} ${color(convertedClassList.join(' '), 'errorLight')}`);
}
return orderedClassList;
}
function getStyles(classes, params) {
const assert = createAssert(params.CustomError, params.isSilent, params.twinConfig.hasLogColors);
params.debug('string in', classes);
assert(![null, 'null', undefined].includes(classes), ({
color
}) => `${color(`✕ Your classes need to be complete strings for Twin to detect them correctly`)}\n\nRead more at https://twinredirect.page.link/template-literals`);
validateClasses(classes, {
tailwindConfig: params.tailwindConfig,
assert
});
for (const task of tasks) {
classes = task(classes, params.tailwindConfig, assert);
}
params.debug('classes after format', classes);
const matched = [];
const unmatched = [];
const styles = [];
const commonContext = {
assert,
theme: params.theme,
debug: params.debug
};
const convertedClassNameContext = { ...commonContext,
tailwindConfig: params.tailwindConfig,
isShortCssOnly: params.isShortCssOnly,
disableShortCss: params.twinConfig.disableShortCss
};
const classList = [...splitAtTopLevelOnly(classes, ' ')];
const convertedClassList = classList.map(c => convertClassName(c, convertedClassNameContext));
const orderedClassList = getOrderedClassList(params.tailwindContext, convertedClassList, classList, assert);
const commonMatchContext = { ...commonContext,
includeUniversalStyles: false,
coreContext: params,
twinConfig: params.twinConfig,
tailwindConfig: params.tailwindConfig,
tailwindContext: params.tailwindContext,
sassyPseudo: params.twinConfig.sassyPseudo
};
for (const [, convertedClassName, className] of orderedClassList) {
const matches = [...resolveMatches(convertedClassName, params.tailwindContext)];
const results = getStylesFromMatches(matches, { ...commonMatchContext,
hasImportant: IMPORTANT_OUTSIDE_BRACKETS.test(escapeRegex(convertedClassName)),
selectorMatchReg: new RegExp( // This regex specifies a list of characters allowed for the character
// immediately after the class ends - this avoids matching other classes
// eg: Input 'btn' will avoid matching '.btn-primary' in `.btn + .btn-primary`
`(${escapeRegex(`.${convertedClassName}`)})(?=[\\[.# >~+*:$\\)]|$)`),
original: convertedClassName
});
if (!results) {
params.debug('🔥 No matching rules found', className, 'error'); // Allow tw``/tw="" to pass through
if (className !== '') unmatched.push(className); // If non-match and is on silent mode: Continue next iteration
if (params.isSilent) continue; // If non-match: Stop iteration and return
// (This "for of" loop returns to the parent function)
return {
styles: undefined,
matched,
unmatched
};
}
matched.push(className);
params.debug('✨ ruleset out', results, 'success');
styles.push(results);
}
if (styles.length === 0) return {
styles: undefined,
matched,
unmatched
}; // @ts-expect-error Avoid tuple type error
const mergedStyles = deepMerge__default["default"](...styles);
return {
styles: mergedStyles,
matched,
unmatched
};
}
const ESC_COMMA = /\\2c/g;
const ESC_DIGIT = /\\3(\d)/g;
const UNDERSCORE_ESCAPING = /\\+(_)/g;
const SLASH_DOT_ESCAPING = /\\\./g;
const BACKSLASH_ESCAPING = /\\\\/g;
function transformImportant(value, params) {
var _params$options;
if (params.passChecks === true) return value;
if (!params.hasImportant) return value; // Avoid adding important if the rule doesn't respect it
if (params.hasImportant && ((_params$options = params.options) == null ? void 0 : _params$options.respectImportant) === false) return value;
return `${value} !important`;
}
function transformEscaping(value) {
return value.replace(UNDERSCORE_ESCAPING, '$1') // Remove slash dot encoding in values
// eg: calc(\\.5 * .25rem)
.replace(SLASH_DOT_ESCAPING, '.') // Fix the duplicate escaping babel delivers
.replace(BACKSLASH_ESCAPING, '\\');
}
const transformValueTasks = [replaceThemeValue, transformImportant, transformEscaping];
function transformDeclValue(value, params) {
const valueOriginal = value;
for (const task of transformValueTasks) {
value = task(value, params);
}
if (value !== valueOriginal) params.debug('converted theme/important', {
old: valueOriginal,
new: value
});
return value;
}
function extractFromRule(rule, params) {
const selectorForUnescape = rule.selector.replace(ESC_DIGIT, '$1'); // Remove digit escaping
const selector = unescape(selectorForUnescape).replace(LINEFEED$1, ' ');
return [selector, extractRuleStyles(rule.nodes, params)];
}
function extractSelectorFromAtRule(name, value, params) {
if (name === LAYER_DEFAULTS) {
if (params.includeUniversalStyles === false) return;
return DEFAULTS_UNIVERSAL;
}
const val = value.replace(ESC_COMMA, ','); // Handle @screen usage in plugins, eg: `@screen md`
if (name === 'screen') {
const screenConfig = get__default["default"](params, 'tailwindConfig.theme.screens');
return `@media (min-width: ${screenConfig[val]})`;
}
return `@${name} ${val}`.trim();
}
const ruleTypes = {
decl(decl, params) {
const property = decl.prop.startsWith('--') ? decl.prop : camelize(decl.prop);
const value = decl.prop.startsWith('--') && decl.value === ' ' ? EMPTY_CSS_VARIABLE_VALUE // valid empty value in js, unlike ` `
: transformDeclValue(decl.value, { ...params,
decl,
property
});
if (value === null) return; // `background-clip: text` is still in "unofficial" phase and needs a
// prefix in Firefox, Chrome and Safari.
// https://caniuse.com/background-img-opts
if (property === 'backgroundClip' && (value === 'text' || value === 'text !important')) return {
WebkitBackgroundClip: value,
[property]: value
};
return {
[property]: value