From b1e2c8fd5cb5dfa46bc440a12eafaf56cd844b1c Mon Sep 17 00:00:00 2001 From: Philipp Tanlak Date: Mon, 24 Nov 2025 20:54:57 +0100 Subject: Docs --- .../tailwindcss/src/util/applyImportantSelector.js | 27 ++ node_modules/tailwindcss/src/util/bigSign.js | 3 + .../tailwindcss/src/util/buildMediaQuery.js | 22 ++ node_modules/tailwindcss/src/util/cloneDeep.js | 11 + node_modules/tailwindcss/src/util/cloneNodes.js | 28 ++ node_modules/tailwindcss/src/util/color.js | 88 +++++ node_modules/tailwindcss/src/util/colorNames.js | 150 ++++++++ .../tailwindcss/src/util/configurePlugins.js | 23 ++ node_modules/tailwindcss/src/util/createPlugin.js | 27 ++ .../tailwindcss/src/util/createUtilityPlugin.js | 37 ++ node_modules/tailwindcss/src/util/dataTypes.js | 394 +++++++++++++++++++++ node_modules/tailwindcss/src/util/defaults.js | 17 + .../tailwindcss/src/util/escapeClassName.js | 8 + node_modules/tailwindcss/src/util/escapeCommas.js | 3 + .../tailwindcss/src/util/flattenColorPalette.js | 13 + .../tailwindcss/src/util/formatVariantSelector.js | 324 +++++++++++++++++ node_modules/tailwindcss/src/util/getAllConfigs.js | 38 ++ node_modules/tailwindcss/src/util/hashConfig.js | 5 + .../tailwindcss/src/util/isKeyframeRule.js | 3 + node_modules/tailwindcss/src/util/isPlainObject.js | 8 + .../src/util/isSyntacticallyValidPropertyValue.js | 61 ++++ node_modules/tailwindcss/src/util/log.js | 29 ++ node_modules/tailwindcss/src/util/nameClass.js | 30 ++ node_modules/tailwindcss/src/util/negateValue.js | 24 ++ .../tailwindcss/src/util/normalizeConfig.js | 301 ++++++++++++++++ .../tailwindcss/src/util/normalizeScreens.js | 140 ++++++++ .../tailwindcss/src/util/parseAnimationValue.js | 68 ++++ .../tailwindcss/src/util/parseBoxShadowValue.js | 72 ++++ .../tailwindcss/src/util/parseDependency.js | 44 +++ node_modules/tailwindcss/src/util/parseGlob.js | 24 ++ .../tailwindcss/src/util/parseObjectStyles.js | 19 + node_modules/tailwindcss/src/util/pluginUtils.js | 291 +++++++++++++++ .../tailwindcss/src/util/prefixSelector.js | 33 ++ .../tailwindcss/src/util/pseudoElements.js | 167 +++++++++ .../tailwindcss/src/util/removeAlphaVariables.js | 24 ++ node_modules/tailwindcss/src/util/resolveConfig.js | 277 +++++++++++++++ .../tailwindcss/src/util/resolveConfigPath.js | 66 ++++ node_modules/tailwindcss/src/util/responsive.js | 10 + .../tailwindcss/src/util/splitAtTopLevelOnly.js | 52 +++ node_modules/tailwindcss/src/util/tap.js | 4 + node_modules/tailwindcss/src/util/toColorValue.js | 3 + node_modules/tailwindcss/src/util/toPath.js | 26 ++ .../tailwindcss/src/util/transformThemeValue.js | 62 ++++ .../tailwindcss/src/util/validateConfig.js | 26 ++ .../tailwindcss/src/util/validateFormalSyntax.js | 34 ++ .../tailwindcss/src/util/withAlphaVariable.js | 49 +++ 46 files changed, 3165 insertions(+) create mode 100644 node_modules/tailwindcss/src/util/applyImportantSelector.js create mode 100644 node_modules/tailwindcss/src/util/bigSign.js create mode 100644 node_modules/tailwindcss/src/util/buildMediaQuery.js create mode 100644 node_modules/tailwindcss/src/util/cloneDeep.js create mode 100644 node_modules/tailwindcss/src/util/cloneNodes.js create mode 100644 node_modules/tailwindcss/src/util/color.js create mode 100644 node_modules/tailwindcss/src/util/colorNames.js create mode 100644 node_modules/tailwindcss/src/util/configurePlugins.js create mode 100644 node_modules/tailwindcss/src/util/createPlugin.js create mode 100644 node_modules/tailwindcss/src/util/createUtilityPlugin.js create mode 100644 node_modules/tailwindcss/src/util/dataTypes.js create mode 100644 node_modules/tailwindcss/src/util/defaults.js create mode 100644 node_modules/tailwindcss/src/util/escapeClassName.js create mode 100644 node_modules/tailwindcss/src/util/escapeCommas.js create mode 100644 node_modules/tailwindcss/src/util/flattenColorPalette.js create mode 100644 node_modules/tailwindcss/src/util/formatVariantSelector.js create mode 100644 node_modules/tailwindcss/src/util/getAllConfigs.js create mode 100644 node_modules/tailwindcss/src/util/hashConfig.js create mode 100644 node_modules/tailwindcss/src/util/isKeyframeRule.js create mode 100644 node_modules/tailwindcss/src/util/isPlainObject.js create mode 100644 node_modules/tailwindcss/src/util/isSyntacticallyValidPropertyValue.js create mode 100644 node_modules/tailwindcss/src/util/log.js create mode 100644 node_modules/tailwindcss/src/util/nameClass.js create mode 100644 node_modules/tailwindcss/src/util/negateValue.js create mode 100644 node_modules/tailwindcss/src/util/normalizeConfig.js create mode 100644 node_modules/tailwindcss/src/util/normalizeScreens.js create mode 100644 node_modules/tailwindcss/src/util/parseAnimationValue.js create mode 100644 node_modules/tailwindcss/src/util/parseBoxShadowValue.js create mode 100644 node_modules/tailwindcss/src/util/parseDependency.js create mode 100644 node_modules/tailwindcss/src/util/parseGlob.js create mode 100644 node_modules/tailwindcss/src/util/parseObjectStyles.js create mode 100644 node_modules/tailwindcss/src/util/pluginUtils.js create mode 100644 node_modules/tailwindcss/src/util/prefixSelector.js create mode 100644 node_modules/tailwindcss/src/util/pseudoElements.js create mode 100644 node_modules/tailwindcss/src/util/removeAlphaVariables.js create mode 100644 node_modules/tailwindcss/src/util/resolveConfig.js create mode 100644 node_modules/tailwindcss/src/util/resolveConfigPath.js create mode 100644 node_modules/tailwindcss/src/util/responsive.js create mode 100644 node_modules/tailwindcss/src/util/splitAtTopLevelOnly.js create mode 100644 node_modules/tailwindcss/src/util/tap.js create mode 100644 node_modules/tailwindcss/src/util/toColorValue.js create mode 100644 node_modules/tailwindcss/src/util/toPath.js create mode 100644 node_modules/tailwindcss/src/util/transformThemeValue.js create mode 100644 node_modules/tailwindcss/src/util/validateConfig.js create mode 100644 node_modules/tailwindcss/src/util/validateFormalSyntax.js create mode 100644 node_modules/tailwindcss/src/util/withAlphaVariable.js (limited to 'node_modules/tailwindcss/src/util') diff --git a/node_modules/tailwindcss/src/util/applyImportantSelector.js b/node_modules/tailwindcss/src/util/applyImportantSelector.js new file mode 100644 index 0000000..ff9ec4f --- /dev/null +++ b/node_modules/tailwindcss/src/util/applyImportantSelector.js @@ -0,0 +1,27 @@ +import parser from 'postcss-selector-parser' +import { movePseudos } from './pseudoElements' + +export function applyImportantSelector(selector, important) { + let sel = parser().astSync(selector) + + sel.each((sel) => { + // Wrap with :is if it's not already wrapped + let isWrapped = + sel.nodes[0].type === 'pseudo' && + sel.nodes[0].value === ':is' && + sel.nodes.every((node) => node.type !== 'combinator') + + if (!isWrapped) { + sel.nodes = [ + parser.pseudo({ + value: ':is', + nodes: [sel.clone()], + }), + ] + } + + movePseudos(sel) + }) + + return `${important} ${sel.toString()}` +} diff --git a/node_modules/tailwindcss/src/util/bigSign.js b/node_modules/tailwindcss/src/util/bigSign.js new file mode 100644 index 0000000..8514aef --- /dev/null +++ b/node_modules/tailwindcss/src/util/bigSign.js @@ -0,0 +1,3 @@ +export default function bigSign(bigIntValue) { + return (bigIntValue > 0n) - (bigIntValue < 0n) +} diff --git a/node_modules/tailwindcss/src/util/buildMediaQuery.js b/node_modules/tailwindcss/src/util/buildMediaQuery.js new file mode 100644 index 0000000..8489dd4 --- /dev/null +++ b/node_modules/tailwindcss/src/util/buildMediaQuery.js @@ -0,0 +1,22 @@ +export default function buildMediaQuery(screens) { + screens = Array.isArray(screens) ? screens : [screens] + + return screens + .map((screen) => { + let values = screen.values.map((screen) => { + if (screen.raw !== undefined) { + return screen.raw + } + + return [ + screen.min && `(min-width: ${screen.min})`, + screen.max && `(max-width: ${screen.max})`, + ] + .filter(Boolean) + .join(' and ') + }) + + return screen.not ? `not all and ${values}` : values + }) + .join(', ') +} diff --git a/node_modules/tailwindcss/src/util/cloneDeep.js b/node_modules/tailwindcss/src/util/cloneDeep.js new file mode 100644 index 0000000..47a1217 --- /dev/null +++ b/node_modules/tailwindcss/src/util/cloneDeep.js @@ -0,0 +1,11 @@ +export function cloneDeep(value) { + if (Array.isArray(value)) { + return value.map((child) => cloneDeep(child)) + } + + if (typeof value === 'object' && value !== null) { + return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, cloneDeep(v)])) + } + + return value +} diff --git a/node_modules/tailwindcss/src/util/cloneNodes.js b/node_modules/tailwindcss/src/util/cloneNodes.js new file mode 100644 index 0000000..299dd63 --- /dev/null +++ b/node_modules/tailwindcss/src/util/cloneNodes.js @@ -0,0 +1,28 @@ +export default function cloneNodes(nodes, source = undefined, raws = undefined) { + return nodes.map((node) => { + let cloned = node.clone() + + // We always want override the source map + // except when explicitly told not to + let shouldOverwriteSource = node.raws.tailwind?.preserveSource !== true || !cloned.source + + if (source !== undefined && shouldOverwriteSource) { + cloned.source = source + + if ('walk' in cloned) { + cloned.walk((child) => { + child.source = source + }) + } + } + + if (raws !== undefined) { + cloned.raws.tailwind = { + ...cloned.raws.tailwind, + ...raws, + } + } + + return cloned + }) +} diff --git a/node_modules/tailwindcss/src/util/color.js b/node_modules/tailwindcss/src/util/color.js new file mode 100644 index 0000000..733ca99 --- /dev/null +++ b/node_modules/tailwindcss/src/util/color.js @@ -0,0 +1,88 @@ +import namedColors from './colorNames' + +let HEX = /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i +let SHORT_HEX = /^#([a-f\d])([a-f\d])([a-f\d])([a-f\d])?$/i +let VALUE = /(?:\d+|\d*\.\d+)%?/ +let SEP = /(?:\s*,\s*|\s+)/ +let ALPHA_SEP = /\s*[,/]\s*/ +let CUSTOM_PROPERTY = /var\(--(?:[^ )]*?)(?:,(?:[^ )]*?|var\(--[^ )]*?\)))?\)/ + +let RGB = new RegExp( + `^(rgba?)\\(\\s*(${VALUE.source}|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$` +) +let HSL = new RegExp( + `^(hsla?)\\(\\s*((?:${VALUE.source})(?:deg|rad|grad|turn)?|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$` +) + +// In "loose" mode the color may contain fewer than 3 parts, as long as at least +// one of the parts is variable. +export function parseColor(value, { loose = false } = {}) { + if (typeof value !== 'string') { + return null + } + + value = value.trim() + if (value === 'transparent') { + return { mode: 'rgb', color: ['0', '0', '0'], alpha: '0' } + } + + if (value in namedColors) { + return { mode: 'rgb', color: namedColors[value].map((v) => v.toString()) } + } + + let hex = value + .replace(SHORT_HEX, (_, r, g, b, a) => ['#', r, r, g, g, b, b, a ? a + a : ''].join('')) + .match(HEX) + + if (hex !== null) { + return { + mode: 'rgb', + color: [parseInt(hex[1], 16), parseInt(hex[2], 16), parseInt(hex[3], 16)].map((v) => + v.toString() + ), + alpha: hex[4] ? (parseInt(hex[4], 16) / 255).toString() : undefined, + } + } + + let match = value.match(RGB) ?? value.match(HSL) + + if (match === null) { + return null + } + + let color = [match[2], match[3], match[4]].filter(Boolean).map((v) => v.toString()) + + // rgba(var(--my-color), 0.1) + // hsla(var(--my-color), 0.1) + if (color.length === 2 && color[0].startsWith('var(')) { + return { + mode: match[1], + color: [color[0]], + alpha: color[1], + } + } + + if (!loose && color.length !== 3) { + return null + } + + if (color.length < 3 && !color.some((part) => /^var\(.*?\)$/.test(part))) { + return null + } + + return { + mode: match[1], + color, + alpha: match[5]?.toString?.(), + } +} + +export function formatColor({ mode, color, alpha }) { + let hasAlpha = alpha !== undefined + + if (mode === 'rgba' || mode === 'hsla') { + return `${mode}(${color.join(', ')}${hasAlpha ? `, ${alpha}` : ''})` + } + + return `${mode}(${color.join(' ')}${hasAlpha ? ` / ${alpha}` : ''})` +} diff --git a/node_modules/tailwindcss/src/util/colorNames.js b/node_modules/tailwindcss/src/util/colorNames.js new file mode 100644 index 0000000..a056cce --- /dev/null +++ b/node_modules/tailwindcss/src/util/colorNames.js @@ -0,0 +1,150 @@ +export default { + aliceblue: [240, 248, 255], + antiquewhite: [250, 235, 215], + aqua: [0, 255, 255], + aquamarine: [127, 255, 212], + azure: [240, 255, 255], + beige: [245, 245, 220], + bisque: [255, 228, 196], + black: [0, 0, 0], + blanchedalmond: [255, 235, 205], + blue: [0, 0, 255], + blueviolet: [138, 43, 226], + brown: [165, 42, 42], + burlywood: [222, 184, 135], + cadetblue: [95, 158, 160], + chartreuse: [127, 255, 0], + chocolate: [210, 105, 30], + coral: [255, 127, 80], + cornflowerblue: [100, 149, 237], + cornsilk: [255, 248, 220], + crimson: [220, 20, 60], + cyan: [0, 255, 255], + darkblue: [0, 0, 139], + darkcyan: [0, 139, 139], + darkgoldenrod: [184, 134, 11], + darkgray: [169, 169, 169], + darkgreen: [0, 100, 0], + darkgrey: [169, 169, 169], + darkkhaki: [189, 183, 107], + darkmagenta: [139, 0, 139], + darkolivegreen: [85, 107, 47], + darkorange: [255, 140, 0], + darkorchid: [153, 50, 204], + darkred: [139, 0, 0], + darksalmon: [233, 150, 122], + darkseagreen: [143, 188, 143], + darkslateblue: [72, 61, 139], + darkslategray: [47, 79, 79], + darkslategrey: [47, 79, 79], + darkturquoise: [0, 206, 209], + darkviolet: [148, 0, 211], + deeppink: [255, 20, 147], + deepskyblue: [0, 191, 255], + dimgray: [105, 105, 105], + dimgrey: [105, 105, 105], + dodgerblue: [30, 144, 255], + firebrick: [178, 34, 34], + floralwhite: [255, 250, 240], + forestgreen: [34, 139, 34], + fuchsia: [255, 0, 255], + gainsboro: [220, 220, 220], + ghostwhite: [248, 248, 255], + gold: [255, 215, 0], + goldenrod: [218, 165, 32], + gray: [128, 128, 128], + green: [0, 128, 0], + greenyellow: [173, 255, 47], + grey: [128, 128, 128], + honeydew: [240, 255, 240], + hotpink: [255, 105, 180], + indianred: [205, 92, 92], + indigo: [75, 0, 130], + ivory: [255, 255, 240], + khaki: [240, 230, 140], + lavender: [230, 230, 250], + lavenderblush: [255, 240, 245], + lawngreen: [124, 252, 0], + lemonchiffon: [255, 250, 205], + lightblue: [173, 216, 230], + lightcoral: [240, 128, 128], + lightcyan: [224, 255, 255], + lightgoldenrodyellow: [250, 250, 210], + lightgray: [211, 211, 211], + lightgreen: [144, 238, 144], + lightgrey: [211, 211, 211], + lightpink: [255, 182, 193], + lightsalmon: [255, 160, 122], + lightseagreen: [32, 178, 170], + lightskyblue: [135, 206, 250], + lightslategray: [119, 136, 153], + lightslategrey: [119, 136, 153], + lightsteelblue: [176, 196, 222], + lightyellow: [255, 255, 224], + lime: [0, 255, 0], + limegreen: [50, 205, 50], + linen: [250, 240, 230], + magenta: [255, 0, 255], + maroon: [128, 0, 0], + mediumaquamarine: [102, 205, 170], + mediumblue: [0, 0, 205], + mediumorchid: [186, 85, 211], + mediumpurple: [147, 112, 219], + mediumseagreen: [60, 179, 113], + mediumslateblue: [123, 104, 238], + mediumspringgreen: [0, 250, 154], + mediumturquoise: [72, 209, 204], + mediumvioletred: [199, 21, 133], + midnightblue: [25, 25, 112], + mintcream: [245, 255, 250], + mistyrose: [255, 228, 225], + moccasin: [255, 228, 181], + navajowhite: [255, 222, 173], + navy: [0, 0, 128], + oldlace: [253, 245, 230], + olive: [128, 128, 0], + olivedrab: [107, 142, 35], + orange: [255, 165, 0], + orangered: [255, 69, 0], + orchid: [218, 112, 214], + palegoldenrod: [238, 232, 170], + palegreen: [152, 251, 152], + paleturquoise: [175, 238, 238], + palevioletred: [219, 112, 147], + papayawhip: [255, 239, 213], + peachpuff: [255, 218, 185], + peru: [205, 133, 63], + pink: [255, 192, 203], + plum: [221, 160, 221], + powderblue: [176, 224, 230], + purple: [128, 0, 128], + rebeccapurple: [102, 51, 153], + red: [255, 0, 0], + rosybrown: [188, 143, 143], + royalblue: [65, 105, 225], + saddlebrown: [139, 69, 19], + salmon: [250, 128, 114], + sandybrown: [244, 164, 96], + seagreen: [46, 139, 87], + seashell: [255, 245, 238], + sienna: [160, 82, 45], + silver: [192, 192, 192], + skyblue: [135, 206, 235], + slateblue: [106, 90, 205], + slategray: [112, 128, 144], + slategrey: [112, 128, 144], + snow: [255, 250, 250], + springgreen: [0, 255, 127], + steelblue: [70, 130, 180], + tan: [210, 180, 140], + teal: [0, 128, 128], + thistle: [216, 191, 216], + tomato: [255, 99, 71], + turquoise: [64, 224, 208], + violet: [238, 130, 238], + wheat: [245, 222, 179], + white: [255, 255, 255], + whitesmoke: [245, 245, 245], + yellow: [255, 255, 0], + yellowgreen: [154, 205, 50], +} diff --git a/node_modules/tailwindcss/src/util/configurePlugins.js b/node_modules/tailwindcss/src/util/configurePlugins.js new file mode 100644 index 0000000..4220eae --- /dev/null +++ b/node_modules/tailwindcss/src/util/configurePlugins.js @@ -0,0 +1,23 @@ +export default function (pluginConfig, plugins) { + if (pluginConfig === undefined) { + return plugins + } + + const pluginNames = Array.isArray(pluginConfig) + ? pluginConfig + : [ + ...new Set( + plugins + .filter((pluginName) => { + return pluginConfig !== false && pluginConfig[pluginName] !== false + }) + .concat( + Object.keys(pluginConfig).filter((pluginName) => { + return pluginConfig[pluginName] !== false + }) + ) + ), + ] + + return pluginNames +} diff --git a/node_modules/tailwindcss/src/util/createPlugin.js b/node_modules/tailwindcss/src/util/createPlugin.js new file mode 100644 index 0000000..c2c7f5f --- /dev/null +++ b/node_modules/tailwindcss/src/util/createPlugin.js @@ -0,0 +1,27 @@ +function createPlugin(plugin, config) { + return { + handler: plugin, + config, + } +} + +createPlugin.withOptions = function (pluginFunction, configFunction = () => ({})) { + const optionsFunction = function (options) { + return { + __options: options, + handler: pluginFunction(options), + config: configFunction(options), + } + } + + optionsFunction.__isOptionsFunction = true + + // Expose plugin dependencies so that `object-hash` returns a different + // value if anything here changes, to ensure a rebuild is triggered. + optionsFunction.__pluginFunction = pluginFunction + optionsFunction.__configFunction = configFunction + + return optionsFunction +} + +export default createPlugin diff --git a/node_modules/tailwindcss/src/util/createUtilityPlugin.js b/node_modules/tailwindcss/src/util/createUtilityPlugin.js new file mode 100644 index 0000000..32c2c8a --- /dev/null +++ b/node_modules/tailwindcss/src/util/createUtilityPlugin.js @@ -0,0 +1,37 @@ +import transformThemeValue from './transformThemeValue' + +export default function createUtilityPlugin( + themeKey, + utilityVariations = [[themeKey, [themeKey]]], + { filterDefault = false, ...options } = {} +) { + let transformValue = transformThemeValue(themeKey) + return function ({ matchUtilities, theme }) { + for (let utilityVariation of utilityVariations) { + let group = Array.isArray(utilityVariation[0]) ? utilityVariation : [utilityVariation] + + matchUtilities( + group.reduce((obj, [classPrefix, properties]) => { + return Object.assign(obj, { + [classPrefix]: (value) => { + return properties.reduce((obj, name) => { + if (Array.isArray(name)) { + return Object.assign(obj, { [name[0]]: name[1] }) + } + return Object.assign(obj, { [name]: transformValue(value) }) + }, {}) + }, + }) + }, {}), + { + ...options, + values: filterDefault + ? Object.fromEntries( + Object.entries(theme(themeKey) ?? {}).filter(([modifier]) => modifier !== 'DEFAULT') + ) + : theme(themeKey), + } + ) + } + } +} diff --git a/node_modules/tailwindcss/src/util/dataTypes.js b/node_modules/tailwindcss/src/util/dataTypes.js new file mode 100644 index 0000000..005c37a --- /dev/null +++ b/node_modules/tailwindcss/src/util/dataTypes.js @@ -0,0 +1,394 @@ +import { parseColor } from './color' +import { parseBoxShadowValue } from './parseBoxShadowValue' +import { splitAtTopLevelOnly } from './splitAtTopLevelOnly' + +let cssFunctions = ['min', 'max', 'clamp', 'calc'] + +// Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types + +function isCSSFunction(value) { + return cssFunctions.some((fn) => new RegExp(`^${fn}\\(.*\\)`).test(value)) +} + +// These properties accept a `` as one of the values. This means that you can use them +// as: `timeline-scope: --tl;` +// +// Without the `var(--tl)`, in these cases we don't want to normalize the value, and you should add +// the `var()` yourself. +// +// More info: +// - https://drafts.csswg.org/scroll-animations/#propdef-timeline-scope +// - https://developer.mozilla.org/en-US/docs/Web/CSS/timeline-scope#dashed-ident +// +const AUTO_VAR_INJECTION_EXCEPTIONS = new Set([ + // Concrete properties + 'scroll-timeline-name', + 'timeline-scope', + 'view-timeline-name', + 'font-palette', + + // Shorthand properties + 'scroll-timeline', + 'animation-timeline', + 'view-timeline', +]) + +// This is not a data type, but rather a function that can normalize the +// correct values. +export function normalize(value, context = null, isRoot = true) { + let isVarException = context && AUTO_VAR_INJECTION_EXCEPTIONS.has(context.property) + if (value.startsWith('--') && !isVarException) { + return `var(${value})` + } + + // Keep raw strings if it starts with `url(` + if (value.includes('url(')) { + return value + .split(/(url\(.*?\))/g) + .filter(Boolean) + .map((part) => { + if (/^url\(.*?\)$/.test(part)) { + return part + } + + return normalize(part, context, false) + }) + .join('') + } + + // Convert `_` to ` `, except for escaped underscores `\_` + value = value + .replace( + /([^\\])_+/g, + (fullMatch, characterBefore) => characterBefore + ' '.repeat(fullMatch.length - 1) + ) + .replace(/^_/g, ' ') + .replace(/\\_/g, '_') + + // Remove leftover whitespace + if (isRoot) { + value = value.trim() + } + + value = normalizeMathOperatorSpacing(value) + + return value +} + +/** + * Add spaces around operators inside math functions + * like calc() that do not follow an operator or '('. + * + * @param {string} value + * @returns {string} + */ +function normalizeMathOperatorSpacing(value) { + let preventFormattingInFunctions = ['theme'] + let preventFormattingKeywords = [ + 'min-content', + 'max-content', + 'fit-content', + + // Env + 'safe-area-inset-top', + 'safe-area-inset-right', + 'safe-area-inset-bottom', + 'safe-area-inset-left', + + 'titlebar-area-x', + 'titlebar-area-y', + 'titlebar-area-width', + 'titlebar-area-height', + + 'keyboard-inset-top', + 'keyboard-inset-right', + 'keyboard-inset-bottom', + 'keyboard-inset-left', + 'keyboard-inset-width', + 'keyboard-inset-height', + ] + + return value.replace(/(calc|min|max|clamp)\(.+\)/g, (match) => { + let result = '' + + function lastChar() { + let char = result.trimEnd() + return char[char.length - 1] + } + + for (let i = 0; i < match.length; i++) { + function peek(word) { + return word.split('').every((char, j) => match[i + j] === char) + } + + function consumeUntil(chars) { + let minIndex = Infinity + for (let char of chars) { + let index = match.indexOf(char, i) + if (index !== -1 && index < minIndex) { + minIndex = index + } + } + + let result = match.slice(i, minIndex) + i += result.length - 1 + return result + } + + let char = match[i] + + // Handle `var(--variable)` + if (peek('var')) { + // When we consume until `)`, then we are dealing with this scenario: + // `var(--example)` + // + // When we consume until `,`, then we are dealing with this scenario: + // `var(--example, 1rem)` + // + // In this case we do want to "format", the default value as well + result += consumeUntil([')', ',']) + } + + // Skip formatting of known keywords + else if (preventFormattingKeywords.some((keyword) => peek(keyword))) { + let keyword = preventFormattingKeywords.find((keyword) => peek(keyword)) + result += keyword + i += keyword.length - 1 + } + + // Skip formatting inside known functions + else if (preventFormattingInFunctions.some((fn) => peek(fn))) { + result += consumeUntil([')']) + } + + // Handle operators + else if ( + ['+', '-', '*', '/'].includes(char) && + !['(', '+', '-', '*', '/'].includes(lastChar()) + ) { + result += ` ${char} ` + } else { + result += char + } + } + + // Simplify multiple spaces + return result.replace(/\s+/g, ' ') + }) +} + +export function url(value) { + return value.startsWith('url(') +} + +export function number(value) { + return !isNaN(Number(value)) || isCSSFunction(value) +} + +export function percentage(value) { + return (value.endsWith('%') && number(value.slice(0, -1))) || isCSSFunction(value) +} + +// Please refer to MDN when updating this list: +// https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Values_and_units +// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries#container_query_length_units +let lengthUnits = [ + 'cm', + 'mm', + 'Q', + 'in', + 'pc', + 'pt', + 'px', + 'em', + 'ex', + 'ch', + 'rem', + 'lh', + 'rlh', + 'vw', + 'vh', + 'vmin', + 'vmax', + 'vb', + 'vi', + 'svw', + 'svh', + 'lvw', + 'lvh', + 'dvw', + 'dvh', + 'cqw', + 'cqh', + 'cqi', + 'cqb', + 'cqmin', + 'cqmax', +] +let lengthUnitsPattern = `(?:${lengthUnits.join('|')})` +export function length(value) { + return ( + value === '0' || + new RegExp(`^[+-]?[0-9]*\.?[0-9]+(?:[eE][+-]?[0-9]+)?${lengthUnitsPattern}$`).test(value) || + isCSSFunction(value) + ) +} + +let lineWidths = new Set(['thin', 'medium', 'thick']) +export function lineWidth(value) { + return lineWidths.has(value) +} + +export function shadow(value) { + let parsedShadows = parseBoxShadowValue(normalize(value)) + + for (let parsedShadow of parsedShadows) { + if (!parsedShadow.valid) { + return false + } + } + + return true +} + +export function color(value) { + let colors = 0 + + let result = splitAtTopLevelOnly(value, '_').every((part) => { + part = normalize(part) + + if (part.startsWith('var(')) return true + if (parseColor(part, { loose: true }) !== null) return colors++, true + + return false + }) + + if (!result) return false + return colors > 0 +} + +export function image(value) { + let images = 0 + let result = splitAtTopLevelOnly(value, ',').every((part) => { + part = normalize(part) + + if (part.startsWith('var(')) return true + if ( + url(part) || + gradient(part) || + ['element(', 'image(', 'cross-fade(', 'image-set('].some((fn) => part.startsWith(fn)) + ) { + images++ + return true + } + + return false + }) + + if (!result) return false + return images > 0 +} + +let gradientTypes = new Set([ + 'conic-gradient', + 'linear-gradient', + 'radial-gradient', + 'repeating-conic-gradient', + 'repeating-linear-gradient', + 'repeating-radial-gradient', +]) +export function gradient(value) { + value = normalize(value) + + for (let type of gradientTypes) { + if (value.startsWith(`${type}(`)) { + return true + } + } + return false +} + +let validPositions = new Set(['center', 'top', 'right', 'bottom', 'left']) +export function position(value) { + let positions = 0 + let result = splitAtTopLevelOnly(value, '_').every((part) => { + part = normalize(part) + + if (part.startsWith('var(')) return true + if (validPositions.has(part) || length(part) || percentage(part)) { + positions++ + return true + } + + return false + }) + + if (!result) return false + return positions > 0 +} + +export function familyName(value) { + let fonts = 0 + let result = splitAtTopLevelOnly(value, ',').every((part) => { + part = normalize(part) + + if (part.startsWith('var(')) return true + + // If it contains spaces, then it should be quoted + if (part.includes(' ')) { + if (!/(['"])([^"']+)\1/g.test(part)) { + return false + } + } + + // If it starts with a number, it's invalid + if (/^\d/g.test(part)) { + return false + } + + fonts++ + + return true + }) + + if (!result) return false + return fonts > 0 +} + +let genericNames = new Set([ + 'serif', + 'sans-serif', + 'monospace', + 'cursive', + 'fantasy', + 'system-ui', + 'ui-serif', + 'ui-sans-serif', + 'ui-monospace', + 'ui-rounded', + 'math', + 'emoji', + 'fangsong', +]) +export function genericName(value) { + return genericNames.has(value) +} + +let absoluteSizes = new Set([ + 'xx-small', + 'x-small', + 'small', + 'medium', + 'large', + 'x-large', + 'x-large', + 'xxx-large', +]) +export function absoluteSize(value) { + return absoluteSizes.has(value) +} + +let relativeSizes = new Set(['larger', 'smaller']) +export function relativeSize(value) { + return relativeSizes.has(value) +} diff --git a/node_modules/tailwindcss/src/util/defaults.js b/node_modules/tailwindcss/src/util/defaults.js new file mode 100644 index 0000000..1d4aa7b --- /dev/null +++ b/node_modules/tailwindcss/src/util/defaults.js @@ -0,0 +1,17 @@ +export function defaults(target, ...sources) { + for (let source of sources) { + for (let k in source) { + if (!target?.hasOwnProperty?.(k)) { + target[k] = source[k] + } + } + + for (let k of Object.getOwnPropertySymbols(source)) { + if (!target?.hasOwnProperty?.(k)) { + target[k] = source[k] + } + } + } + + return target +} diff --git a/node_modules/tailwindcss/src/util/escapeClassName.js b/node_modules/tailwindcss/src/util/escapeClassName.js new file mode 100644 index 0000000..cb5924a --- /dev/null +++ b/node_modules/tailwindcss/src/util/escapeClassName.js @@ -0,0 +1,8 @@ +import parser from 'postcss-selector-parser' +import escapeCommas from './escapeCommas' + +export default function escapeClassName(className) { + let node = parser.className() + node.value = className + return escapeCommas(node?.raws?.value ?? node.value) +} diff --git a/node_modules/tailwindcss/src/util/escapeCommas.js b/node_modules/tailwindcss/src/util/escapeCommas.js new file mode 100644 index 0000000..e7f1c73 --- /dev/null +++ b/node_modules/tailwindcss/src/util/escapeCommas.js @@ -0,0 +1,3 @@ +export default function escapeCommas(className) { + return className.replace(/\\,/g, '\\2c ') +} diff --git a/node_modules/tailwindcss/src/util/flattenColorPalette.js b/node_modules/tailwindcss/src/util/flattenColorPalette.js new file mode 100644 index 0000000..c1259a7 --- /dev/null +++ b/node_modules/tailwindcss/src/util/flattenColorPalette.js @@ -0,0 +1,13 @@ +const flattenColorPalette = (colors) => + Object.assign( + {}, + ...Object.entries(colors ?? {}).flatMap(([color, values]) => + typeof values == 'object' + ? Object.entries(flattenColorPalette(values)).map(([number, hex]) => ({ + [color + (number === 'DEFAULT' ? '' : `-${number}`)]: hex, + })) + : [{ [`${color}`]: values }] + ) + ) + +export default flattenColorPalette diff --git a/node_modules/tailwindcss/src/util/formatVariantSelector.js b/node_modules/tailwindcss/src/util/formatVariantSelector.js new file mode 100644 index 0000000..6ba6f2c --- /dev/null +++ b/node_modules/tailwindcss/src/util/formatVariantSelector.js @@ -0,0 +1,324 @@ +import selectorParser from 'postcss-selector-parser' +import unescape from 'postcss-selector-parser/dist/util/unesc' +import escapeClassName from '../util/escapeClassName' +import prefixSelector from '../util/prefixSelector' +import { movePseudos } from './pseudoElements' +import { splitAtTopLevelOnly } from './splitAtTopLevelOnly' + +/** @typedef {import('postcss-selector-parser').Root} Root */ +/** @typedef {import('postcss-selector-parser').Selector} Selector */ +/** @typedef {import('postcss-selector-parser').Pseudo} Pseudo */ +/** @typedef {import('postcss-selector-parser').Node} Node */ + +/** @typedef {{format: string, respectPrefix: boolean}[]} RawFormats */ +/** @typedef {import('postcss-selector-parser').Root} ParsedFormats */ +/** @typedef {RawFormats | ParsedFormats} AcceptedFormats */ + +let MERGE = ':merge' + +/** + * @param {RawFormats} formats + * @param {{context: any, candidate: string, base: string | null}} options + * @returns {ParsedFormats | null} + */ +export function formatVariantSelector(formats, { context, candidate }) { + let prefix = context?.tailwindConfig.prefix ?? '' + + // Parse the format selector into an AST + let parsedFormats = formats.map((format) => { + let ast = selectorParser().astSync(format.format) + + return { + ...format, + ast: format.respectPrefix ? prefixSelector(prefix, ast) : ast, + } + }) + + // We start with the candidate selector + let formatAst = selectorParser.root({ + nodes: [ + selectorParser.selector({ + nodes: [selectorParser.className({ value: escapeClassName(candidate) })], + }), + ], + }) + + // And iteratively merge each format selector into the candidate selector + for (let { ast } of parsedFormats) { + // 1. Handle :merge() special pseudo-class + ;[formatAst, ast] = handleMergePseudo(formatAst, ast) + + // 2. Merge the format selector into the current selector AST + ast.walkNesting((nesting) => nesting.replaceWith(...formatAst.nodes[0].nodes)) + + // 3. Keep going! + formatAst = ast + } + + return formatAst +} + +/** + * Given any node in a selector this gets the "simple" selector it's a part of + * A simple selector is just a list of nodes without any combinators + * Technically :is(), :not(), :has(), etc… can have combinators but those are nested + * inside the relevant node and won't be picked up so they're fine to ignore + * + * @param {Node} node + * @returns {Node[]} + **/ +function simpleSelectorForNode(node) { + /** @type {Node[]} */ + let nodes = [] + + // Walk backwards until we hit a combinator node (or the start) + while (node.prev() && node.prev().type !== 'combinator') { + node = node.prev() + } + + // Now record all non-combinator nodes until we hit one (or the end) + while (node && node.type !== 'combinator') { + nodes.push(node) + node = node.next() + } + + return nodes +} + +/** + * Resorts the nodes in a selector to ensure they're in the correct order + * Tags go before classes, and pseudo classes go after classes + * + * @param {Selector} sel + * @returns {Selector} + **/ +function resortSelector(sel) { + sel.sort((a, b) => { + if (a.type === 'tag' && b.type === 'class') { + return -1 + } else if (a.type === 'class' && b.type === 'tag') { + return 1 + } else if (a.type === 'class' && b.type === 'pseudo' && b.value.startsWith('::')) { + return -1 + } else if (a.type === 'pseudo' && a.value.startsWith('::') && b.type === 'class') { + return 1 + } + + return sel.index(a) - sel.index(b) + }) + + return sel +} + +/** + * Remove extraneous selectors that do not include the base class/candidate + * + * Example: + * Given the utility `.a, .b { color: red}` + * Given the candidate `sm:b` + * + * The final selector should be `.sm\:b` and not `.a, .sm\:b` + * + * @param {Selector} ast + * @param {string} base + */ +export function eliminateIrrelevantSelectors(sel, base) { + let hasClassesMatchingCandidate = false + + sel.walk((child) => { + if (child.type === 'class' && child.value === base) { + hasClassesMatchingCandidate = true + return false // Stop walking + } + }) + + if (!hasClassesMatchingCandidate) { + sel.remove() + } + + // We do NOT recursively eliminate sub selectors that don't have the base class + // as this is NOT a safe operation. For example, if we have: + // `.space-x-2 > :not([hidden]) ~ :not([hidden])` + // We cannot remove the [hidden] from the :not() because it would change the + // meaning of the selector. + + // TODO: Can we do this for :matches, :is, and :where? +} + +/** + * @param {string} current + * @param {AcceptedFormats} formats + * @param {{context: any, candidate: string, base: string | null}} options + * @returns {string} + */ +export function finalizeSelector(current, formats, { context, candidate, base }) { + let separator = context?.tailwindConfig?.separator ?? ':' + + // Split by the separator, but ignore the separator inside square brackets: + // + // E.g.: dark:lg:hover:[paint-order:markers] + // ┬ ┬ ┬ ┬ + // │ │ │ ╰── We will not split here + // ╰──┴─────┴─────────────── We will split here + // + base = base ?? splitAtTopLevelOnly(candidate, separator).pop() + + // Parse the selector into an AST + let selector = selectorParser().astSync(current) + + // Normalize escaped classes, e.g.: + // + // The idea would be to replace the escaped `base` in the selector with the + // `format`. However, in css you can escape the same selector in a few + // different ways. This would result in different strings and therefore we + // can't replace it properly. + // + // base: bg-[rgb(255,0,0)] + // base in selector: bg-\\[rgb\\(255\\,0\\,0\\)\\] + // escaped base: bg-\\[rgb\\(255\\2c 0\\2c 0\\)\\] + // + selector.walkClasses((node) => { + if (node.raws && node.value.includes(base)) { + node.raws.value = escapeClassName(unescape(node.raws.value)) + } + }) + + // Remove extraneous selectors that do not include the base candidate + selector.each((sel) => eliminateIrrelevantSelectors(sel, base)) + + // If ffter eliminating irrelevant selectors, we end up with nothing + // Then the whole "rule" this is associated with does not need to exist + // We use `null` as a marker value for that case + if (selector.length === 0) { + return null + } + + // If there are no formats that means there were no variants added to the candidate + // so we can just return the selector as-is + let formatAst = Array.isArray(formats) + ? formatVariantSelector(formats, { context, candidate }) + : formats + + if (formatAst === null) { + return selector.toString() + } + + let simpleStart = selectorParser.comment({ value: '/*__simple__*/' }) + let simpleEnd = selectorParser.comment({ value: '/*__simple__*/' }) + + // We can safely replace the escaped base now, since the `base` section is + // now in a normalized escaped value. + selector.walkClasses((node) => { + if (node.value !== base) { + return + } + + let parent = node.parent + let formatNodes = formatAst.nodes[0].nodes + + // Perf optimization: if the parent is a single class we can just replace it and be done + if (parent.nodes.length === 1) { + node.replaceWith(...formatNodes) + return + } + + let simpleSelector = simpleSelectorForNode(node) + parent.insertBefore(simpleSelector[0], simpleStart) + parent.insertAfter(simpleSelector[simpleSelector.length - 1], simpleEnd) + + for (let child of formatNodes) { + parent.insertBefore(simpleSelector[0], child.clone()) + } + + node.remove() + + // Re-sort the simple selector to ensure it's in the correct order + simpleSelector = simpleSelectorForNode(simpleStart) + let firstNode = parent.index(simpleStart) + + parent.nodes.splice( + firstNode, + simpleSelector.length, + ...resortSelector(selectorParser.selector({ nodes: simpleSelector })).nodes + ) + + simpleStart.remove() + simpleEnd.remove() + }) + + // Remove unnecessary pseudo selectors that we used as placeholders + selector.walkPseudos((p) => { + if (p.value === MERGE) { + p.replaceWith(p.nodes) + } + }) + + // Move pseudo elements to the end of the selector (if necessary) + selector.each((sel) => movePseudos(sel)) + + return selector.toString() +} + +/** + * + * @param {Selector} selector + * @param {Selector} format + */ +export function handleMergePseudo(selector, format) { + /** @type {{pseudo: Pseudo, value: string}[]} */ + let merges = [] + + // Find all :merge() pseudo-classes in `selector` + selector.walkPseudos((pseudo) => { + if (pseudo.value === MERGE) { + merges.push({ + pseudo, + value: pseudo.nodes[0].toString(), + }) + } + }) + + // Find all :merge() "attachments" in `format` and attach them to the matching selector in `selector` + format.walkPseudos((pseudo) => { + if (pseudo.value !== MERGE) { + return + } + + let value = pseudo.nodes[0].toString() + + // Does `selector` contain a :merge() pseudo-class with the same value? + let existing = merges.find((merge) => merge.value === value) + + // Nope so there's nothing to do + if (!existing) { + return + } + + // Everything after `:merge()` up to the next combinator is what is attached to the merged selector + let attachments = [] + let next = pseudo.next() + while (next && next.type !== 'combinator') { + attachments.push(next) + next = next.next() + } + + let combinator = next + + existing.pseudo.parent.insertAfter( + existing.pseudo, + selectorParser.selector({ nodes: attachments.map((node) => node.clone()) }) + ) + + pseudo.remove() + attachments.forEach((node) => node.remove()) + + // What about this case: + // :merge(.group):focus > & + // :merge(.group):hover & + if (combinator && combinator.type === 'combinator') { + combinator.remove() + } + }) + + return [selector, format] +} diff --git a/node_modules/tailwindcss/src/util/getAllConfigs.js b/node_modules/tailwindcss/src/util/getAllConfigs.js new file mode 100644 index 0000000..ce3665b --- /dev/null +++ b/node_modules/tailwindcss/src/util/getAllConfigs.js @@ -0,0 +1,38 @@ +import defaultFullConfig from '../../stubs/config.full.js' +import { flagEnabled } from '../featureFlags' + +export default function getAllConfigs(config) { + const configs = (config?.presets ?? [defaultFullConfig]) + .slice() + .reverse() + .flatMap((preset) => getAllConfigs(preset instanceof Function ? preset() : preset)) + + const features = { + // Add experimental configs here... + respectDefaultRingColorOpacity: { + theme: { + ringColor: ({ theme }) => ({ + DEFAULT: '#3b82f67f', + ...theme('colors'), + }), + }, + }, + + disableColorOpacityUtilitiesByDefault: { + corePlugins: { + backgroundOpacity: false, + borderOpacity: false, + divideOpacity: false, + placeholderOpacity: false, + ringOpacity: false, + textOpacity: false, + }, + }, + } + + const experimentals = Object.keys(features) + .filter((feature) => flagEnabled(config, feature)) + .map((feature) => features[feature]) + + return [config, ...experimentals, ...configs] +} diff --git a/node_modules/tailwindcss/src/util/hashConfig.js b/node_modules/tailwindcss/src/util/hashConfig.js new file mode 100644 index 0000000..543e020 --- /dev/null +++ b/node_modules/tailwindcss/src/util/hashConfig.js @@ -0,0 +1,5 @@ +import hash from 'object-hash' + +export default function hashConfig(config) { + return hash(config, { ignoreUnknown: true }) +} diff --git a/node_modules/tailwindcss/src/util/isKeyframeRule.js b/node_modules/tailwindcss/src/util/isKeyframeRule.js new file mode 100644 index 0000000..a745e97 --- /dev/null +++ b/node_modules/tailwindcss/src/util/isKeyframeRule.js @@ -0,0 +1,3 @@ +export default function isKeyframeRule(rule) { + return rule.parent && rule.parent.type === 'atrule' && /keyframes$/.test(rule.parent.name) +} diff --git a/node_modules/tailwindcss/src/util/isPlainObject.js b/node_modules/tailwindcss/src/util/isPlainObject.js new file mode 100644 index 0000000..6bd031a --- /dev/null +++ b/node_modules/tailwindcss/src/util/isPlainObject.js @@ -0,0 +1,8 @@ +export default function isPlainObject(value) { + if (Object.prototype.toString.call(value) !== '[object Object]') { + return false + } + + const prototype = Object.getPrototypeOf(value) + return prototype === null || Object.getPrototypeOf(prototype) === null +} diff --git a/node_modules/tailwindcss/src/util/isSyntacticallyValidPropertyValue.js b/node_modules/tailwindcss/src/util/isSyntacticallyValidPropertyValue.js new file mode 100644 index 0000000..ff00074 --- /dev/null +++ b/node_modules/tailwindcss/src/util/isSyntacticallyValidPropertyValue.js @@ -0,0 +1,61 @@ +let matchingBrackets = new Map([ + ['{', '}'], + ['[', ']'], + ['(', ')'], +]) +let inverseMatchingBrackets = new Map( + Array.from(matchingBrackets.entries()).map(([k, v]) => [v, k]) +) + +let quotes = new Set(['"', "'", '`']) + +// Arbitrary values must contain balanced brackets (), [] and {}. Escaped +// values don't count, and brackets inside quotes also don't count. +// +// E.g.: w-[this-is]w-[weird-and-invalid] +// E.g.: w-[this-is\\]w-\\[weird-but-valid] +// E.g.: content-['this-is-also-valid]-weirdly-enough'] +export default function isSyntacticallyValidPropertyValue(value) { + let stack = [] + let inQuotes = false + + for (let i = 0; i < value.length; i++) { + let char = value[i] + + if (char === ':' && !inQuotes && stack.length === 0) { + return false + } + + // Non-escaped quotes allow us to "allow" anything in between + if (quotes.has(char) && value[i - 1] !== '\\') { + inQuotes = !inQuotes + } + + if (inQuotes) continue + if (value[i - 1] === '\\') continue // Escaped + + if (matchingBrackets.has(char)) { + stack.push(char) + } else if (inverseMatchingBrackets.has(char)) { + let inverse = inverseMatchingBrackets.get(char) + + // Nothing to pop from, therefore it is unbalanced + if (stack.length <= 0) { + return false + } + + // Popped value must match the inverse value, otherwise it is unbalanced + if (stack.pop() !== inverse) { + return false + } + } + } + + // If there is still something on the stack, it is also unbalanced + if (stack.length > 0) { + return false + } + + // All good, totally balanced! + return true +} diff --git a/node_modules/tailwindcss/src/util/log.js b/node_modules/tailwindcss/src/util/log.js new file mode 100644 index 0000000..0df5c87 --- /dev/null +++ b/node_modules/tailwindcss/src/util/log.js @@ -0,0 +1,29 @@ +import colors from 'picocolors' + +let alreadyShown = new Set() + +function log(type, messages, key) { + if (typeof process !== 'undefined' && process.env.JEST_WORKER_ID) return + + if (key && alreadyShown.has(key)) return + if (key) alreadyShown.add(key) + + console.warn('') + messages.forEach((message) => console.warn(type, '-', message)) +} + +export function dim(input) { + return colors.dim(input) +} + +export default { + info(key, messages) { + log(colors.bold(colors.cyan('info')), ...(Array.isArray(key) ? [key] : [messages, key])) + }, + warn(key, messages) { + log(colors.bold(colors.yellow('warn')), ...(Array.isArray(key) ? [key] : [messages, key])) + }, + risk(key, messages) { + log(colors.bold(colors.magenta('risk')), ...(Array.isArray(key) ? [key] : [messages, key])) + }, +} diff --git a/node_modules/tailwindcss/src/util/nameClass.js b/node_modules/tailwindcss/src/util/nameClass.js new file mode 100644 index 0000000..cb37dba --- /dev/null +++ b/node_modules/tailwindcss/src/util/nameClass.js @@ -0,0 +1,30 @@ +import escapeClassName from './escapeClassName' +import escapeCommas from './escapeCommas' + +export function asClass(name) { + return escapeCommas(`.${escapeClassName(name)}`) +} + +export default function nameClass(classPrefix, key) { + return asClass(formatClass(classPrefix, key)) +} + +export function formatClass(classPrefix, key) { + if (key === 'DEFAULT') { + return classPrefix + } + + if (key === '-' || key === '-DEFAULT') { + return `-${classPrefix}` + } + + if (key.startsWith('-')) { + return `-${classPrefix}${key}` + } + + if (key.startsWith('/')) { + return `${classPrefix}${key}` + } + + return `${classPrefix}-${key}` +} diff --git a/node_modules/tailwindcss/src/util/negateValue.js b/node_modules/tailwindcss/src/util/negateValue.js new file mode 100644 index 0000000..b1cdf78 --- /dev/null +++ b/node_modules/tailwindcss/src/util/negateValue.js @@ -0,0 +1,24 @@ +export default function negateValue(value) { + value = `${value}` + + if (value === '0') { + return '0' + } + + // Flip sign of numbers + if (/^[+-]?(\d+|\d*\.\d+)(e[+-]?\d+)?(%|\w+)?$/.test(value)) { + return value.replace(/^[+-]?/, (sign) => (sign === '-' ? '' : '-')) + } + + // What functions we support negating numeric values for + // var() isn't inherently a numeric function but we support it anyway + // The trigonometric functions are omitted because you'll need to use calc(…) with them _anyway_ + // to produce generally useful results and that will be covered already + let numericFunctions = ['var', 'calc', 'min', 'max', 'clamp'] + + for (const fn of numericFunctions) { + if (value.includes(`${fn}(`)) { + return `calc(${value} * -1)` + } + } +} diff --git a/node_modules/tailwindcss/src/util/normalizeConfig.js b/node_modules/tailwindcss/src/util/normalizeConfig.js new file mode 100644 index 0000000..7e1a592 --- /dev/null +++ b/node_modules/tailwindcss/src/util/normalizeConfig.js @@ -0,0 +1,301 @@ +import { flagEnabled } from '../featureFlags' +import log, { dim } from './log' + +export function normalizeConfig(config) { + // Quick structure validation + /** + * type FilePath = string + * type RawFile = { raw: string, extension?: string } + * type ExtractorFn = (content: string) => Array + * type TransformerFn = (content: string) => string + * + * type Content = + * | Array + * | { + * files: Array, + * extract?: ExtractorFn | { [extension: string]: ExtractorFn } + * transform?: TransformerFn | { [extension: string]: TransformerFn } + * } + */ + let valid = (() => { + // `config.purge` should not exist anymore + if (config.purge) { + return false + } + + // `config.content` should exist + if (!config.content) { + return false + } + + // `config.content` should be an object or an array + if ( + !Array.isArray(config.content) && + !(typeof config.content === 'object' && config.content !== null) + ) { + return false + } + + // When `config.content` is an array, it should consist of FilePaths or RawFiles + if (Array.isArray(config.content)) { + return config.content.every((path) => { + // `path` can be a string + if (typeof path === 'string') return true + + // `path` can be an object { raw: string, extension?: string } + // `raw` must be a string + if (typeof path?.raw !== 'string') return false + + // `extension` (if provided) should also be a string + if (path?.extension && typeof path?.extension !== 'string') { + return false + } + + return true + }) + } + + // When `config.content` is an object + if (typeof config.content === 'object' && config.content !== null) { + // Only `files`, `relative`, `extract`, and `transform` can exist in `config.content` + if ( + Object.keys(config.content).some( + (key) => !['files', 'relative', 'extract', 'transform'].includes(key) + ) + ) { + return false + } + + // `config.content.files` should exist of FilePaths or RawFiles + if (Array.isArray(config.content.files)) { + if ( + !config.content.files.every((path) => { + // `path` can be a string + if (typeof path === 'string') return true + + // `path` can be an object { raw: string, extension?: string } + // `raw` must be a string + if (typeof path?.raw !== 'string') return false + + // `extension` (if provided) should also be a string + if (path?.extension && typeof path?.extension !== 'string') { + return false + } + + return true + }) + ) { + return false + } + + // `config.content.extract` is optional, and can be a Function or a Record + if (typeof config.content.extract === 'object') { + for (let value of Object.values(config.content.extract)) { + if (typeof value !== 'function') { + return false + } + } + } else if ( + !(config.content.extract === undefined || typeof config.content.extract === 'function') + ) { + return false + } + + // `config.content.transform` is optional, and can be a Function or a Record + if (typeof config.content.transform === 'object') { + for (let value of Object.values(config.content.transform)) { + if (typeof value !== 'function') { + return false + } + } + } else if ( + !( + config.content.transform === undefined || typeof config.content.transform === 'function' + ) + ) { + return false + } + + // `config.content.relative` is optional and can be a boolean + if ( + typeof config.content.relative !== 'boolean' && + typeof config.content.relative !== 'undefined' + ) { + return false + } + } + + return true + } + + return false + })() + + if (!valid) { + log.warn('purge-deprecation', [ + 'The `purge`/`content` options have changed in Tailwind CSS v3.0.', + 'Update your configuration file to eliminate this warning.', + 'https://tailwindcss.com/docs/upgrade-guide#configure-content-sources', + ]) + } + + // Normalize the `safelist` + config.safelist = (() => { + let { content, purge, safelist } = config + + if (Array.isArray(safelist)) return safelist + if (Array.isArray(content?.safelist)) return content.safelist + if (Array.isArray(purge?.safelist)) return purge.safelist + if (Array.isArray(purge?.options?.safelist)) return purge.options.safelist + + return [] + })() + + // Normalize the `blocklist` + config.blocklist = (() => { + let { blocklist } = config + + if (Array.isArray(blocklist)) { + if (blocklist.every((item) => typeof item === 'string')) { + return blocklist + } + + log.warn('blocklist-invalid', [ + 'The `blocklist` option must be an array of strings.', + 'https://tailwindcss.com/docs/content-configuration#discarding-classes', + ]) + } + + return [] + })() + + // Normalize prefix option + if (typeof config.prefix === 'function') { + log.warn('prefix-function', [ + 'As of Tailwind CSS v3.0, `prefix` cannot be a function.', + 'Update `prefix` in your configuration to be a string to eliminate this warning.', + 'https://tailwindcss.com/docs/upgrade-guide#prefix-cannot-be-a-function', + ]) + config.prefix = '' + } else { + config.prefix = config.prefix ?? '' + } + + // Normalize the `content` + config.content = { + relative: (() => { + let { content } = config + + if (content?.relative) { + return content.relative + } + + return flagEnabled(config, 'relativeContentPathsByDefault') + })(), + + files: (() => { + let { content, purge } = config + + if (Array.isArray(purge)) return purge + if (Array.isArray(purge?.content)) return purge.content + if (Array.isArray(content)) return content + if (Array.isArray(content?.content)) return content.content + if (Array.isArray(content?.files)) return content.files + + return [] + })(), + + extract: (() => { + let extract = (() => { + if (config.purge?.extract) return config.purge.extract + if (config.content?.extract) return config.content.extract + + if (config.purge?.extract?.DEFAULT) return config.purge.extract.DEFAULT + if (config.content?.extract?.DEFAULT) return config.content.extract.DEFAULT + + if (config.purge?.options?.extractors) return config.purge.options.extractors + if (config.content?.options?.extractors) return config.content.options.extractors + + return {} + })() + + let extractors = {} + + let defaultExtractor = (() => { + if (config.purge?.options?.defaultExtractor) { + return config.purge.options.defaultExtractor + } + + if (config.content?.options?.defaultExtractor) { + return config.content.options.defaultExtractor + } + + return undefined + })() + + if (defaultExtractor !== undefined) { + extractors.DEFAULT = defaultExtractor + } + + // Functions + if (typeof extract === 'function') { + extractors.DEFAULT = extract + } + + // Arrays + else if (Array.isArray(extract)) { + for (let { extensions, extractor } of extract ?? []) { + for (let extension of extensions) { + extractors[extension] = extractor + } + } + } + + // Objects + else if (typeof extract === 'object' && extract !== null) { + Object.assign(extractors, extract) + } + + return extractors + })(), + + transform: (() => { + let transform = (() => { + if (config.purge?.transform) return config.purge.transform + if (config.content?.transform) return config.content.transform + + if (config.purge?.transform?.DEFAULT) return config.purge.transform.DEFAULT + if (config.content?.transform?.DEFAULT) return config.content.transform.DEFAULT + + return {} + })() + + let transformers = {} + + if (typeof transform === 'function') { + transformers.DEFAULT = transform + } + + if (typeof transform === 'object' && transform !== null) { + Object.assign(transformers, transform) + } + + return transformers + })(), + } + + // Validate globs to prevent bogus globs. + // E.g.: `./src/*.{html}` is invalid, the `{html}` should just be `html` + for (let file of config.content.files) { + if (typeof file === 'string' && /{([^,]*?)}/g.test(file)) { + log.warn('invalid-glob-braces', [ + `The glob pattern ${dim(file)} in your Tailwind CSS configuration is invalid.`, + `Update it to ${dim(file.replace(/{([^,]*?)}/g, '$1'))} to silence this warning.`, + // TODO: Add https://tw.wtf/invalid-glob-braces + ]) + break + } + } + + return config +} diff --git a/node_modules/tailwindcss/src/util/normalizeScreens.js b/node_modules/tailwindcss/src/util/normalizeScreens.js new file mode 100644 index 0000000..559f7cc --- /dev/null +++ b/node_modules/tailwindcss/src/util/normalizeScreens.js @@ -0,0 +1,140 @@ +/** + * @typedef {object} ScreenValue + * @property {number|undefined} min + * @property {number|undefined} max + * @property {string|undefined} raw + */ + +/** + * @typedef {object} Screen + * @property {string} name + * @property {boolean} not + * @property {ScreenValue[]} values + */ + +/** + * A function that normalizes the various forms that the screens object can be + * provided in. + * + * Input(s): + * - ['100px', '200px'] // Raw strings + * - { sm: '100px', md: '200px' } // Object with string values + * - { sm: { min: '100px' }, md: { max: '100px' } } // Object with object values + * - { sm: [{ min: '100px' }, { max: '200px' }] } // Object with object array (multiple values) + * + * Output(s): + * - [{ name: 'sm', values: [{ min: '100px', max: '200px' }] }] // List of objects, that contains multiple values + * + * @returns {Screen[]} + */ +export function normalizeScreens(screens, root = true) { + if (Array.isArray(screens)) { + return screens.map((screen) => { + if (root && Array.isArray(screen)) { + throw new Error('The tuple syntax is not supported for `screens`.') + } + + if (typeof screen === 'string') { + return { name: screen.toString(), not: false, values: [{ min: screen, max: undefined }] } + } + + let [name, options] = screen + name = name.toString() + + if (typeof options === 'string') { + return { name, not: false, values: [{ min: options, max: undefined }] } + } + + if (Array.isArray(options)) { + return { name, not: false, values: options.map((option) => resolveValue(option)) } + } + + return { name, not: false, values: [resolveValue(options)] } + }) + } + + return normalizeScreens(Object.entries(screens ?? {}), false) +} + +/** + * @param {Screen} screen + * @returns {{result: false, reason: string} | {result: true, reason: null}} + */ +export function isScreenSortable(screen) { + if (screen.values.length !== 1) { + return { result: false, reason: 'multiple-values' } + } else if (screen.values[0].raw !== undefined) { + return { result: false, reason: 'raw-values' } + } else if (screen.values[0].min !== undefined && screen.values[0].max !== undefined) { + return { result: false, reason: 'min-and-max' } + } + + return { result: true, reason: null } +} + +/** + * @param {'min' | 'max'} type + * @param {Screen | 'string'} a + * @param {Screen | 'string'} z + * @returns {number} + */ +export function compareScreens(type, a, z) { + let aScreen = toScreen(a, type) + let zScreen = toScreen(z, type) + + let aSorting = isScreenSortable(aScreen) + let bSorting = isScreenSortable(zScreen) + + // These cases should never happen and indicate a bug in Tailwind CSS itself + if (aSorting.reason === 'multiple-values' || bSorting.reason === 'multiple-values') { + throw new Error( + 'Attempted to sort a screen with multiple values. This should never happen. Please open a bug report.' + ) + } else if (aSorting.reason === 'raw-values' || bSorting.reason === 'raw-values') { + throw new Error( + 'Attempted to sort a screen with raw values. This should never happen. Please open a bug report.' + ) + } else if (aSorting.reason === 'min-and-max' || bSorting.reason === 'min-and-max') { + throw new Error( + 'Attempted to sort a screen with both min and max values. This should never happen. Please open a bug report.' + ) + } + + // Let the sorting begin + let { min: aMin, max: aMax } = aScreen.values[0] + let { min: zMin, max: zMax } = zScreen.values[0] + + // Negating screens flip their behavior. Basically `not min-width` is `max-width` + if (a.not) [aMin, aMax] = [aMax, aMin] + if (z.not) [zMin, zMax] = [zMax, zMin] + + aMin = aMin === undefined ? aMin : parseFloat(aMin) + aMax = aMax === undefined ? aMax : parseFloat(aMax) + zMin = zMin === undefined ? zMin : parseFloat(zMin) + zMax = zMax === undefined ? zMax : parseFloat(zMax) + + let [aValue, zValue] = type === 'min' ? [aMin, zMin] : [zMax, aMax] + + return aValue - zValue +} + +/** + * + * @param {PartialScreen> | string} value + * @param {'min' | 'max'} type + * @returns {Screen} + */ +export function toScreen(value, type) { + if (typeof value === 'object') { + return value + } + + return { + name: 'arbitrary-screen', + values: [{ [type]: value }], + } +} + +function resolveValue({ 'min-width': _minWidth, min = _minWidth, max, raw } = {}) { + return { min, max, raw } +} diff --git a/node_modules/tailwindcss/src/util/parseAnimationValue.js b/node_modules/tailwindcss/src/util/parseAnimationValue.js new file mode 100644 index 0000000..990e7aa --- /dev/null +++ b/node_modules/tailwindcss/src/util/parseAnimationValue.js @@ -0,0 +1,68 @@ +const DIRECTIONS = new Set(['normal', 'reverse', 'alternate', 'alternate-reverse']) +const PLAY_STATES = new Set(['running', 'paused']) +const FILL_MODES = new Set(['none', 'forwards', 'backwards', 'both']) +const ITERATION_COUNTS = new Set(['infinite']) +const TIMINGS = new Set([ + 'linear', + 'ease', + 'ease-in', + 'ease-out', + 'ease-in-out', + 'step-start', + 'step-end', +]) +const TIMING_FNS = ['cubic-bezier', 'steps'] + +const COMMA = /\,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count. +const SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead. +const TIME = /^(-?[\d.]+m?s)$/ +const DIGIT = /^(\d+)$/ + +export default function parseAnimationValue(input) { + let animations = input.split(COMMA) + return animations.map((animation) => { + let value = animation.trim() + let result = { value } + let parts = value.split(SPACE) + let seen = new Set() + + for (let part of parts) { + if (!seen.has('DIRECTIONS') && DIRECTIONS.has(part)) { + result.direction = part + seen.add('DIRECTIONS') + } else if (!seen.has('PLAY_STATES') && PLAY_STATES.has(part)) { + result.playState = part + seen.add('PLAY_STATES') + } else if (!seen.has('FILL_MODES') && FILL_MODES.has(part)) { + result.fillMode = part + seen.add('FILL_MODES') + } else if ( + !seen.has('ITERATION_COUNTS') && + (ITERATION_COUNTS.has(part) || DIGIT.test(part)) + ) { + result.iterationCount = part + seen.add('ITERATION_COUNTS') + } else if (!seen.has('TIMING_FUNCTION') && TIMINGS.has(part)) { + result.timingFunction = part + seen.add('TIMING_FUNCTION') + } else if (!seen.has('TIMING_FUNCTION') && TIMING_FNS.some((f) => part.startsWith(`${f}(`))) { + result.timingFunction = part + seen.add('TIMING_FUNCTION') + } else if (!seen.has('DURATION') && TIME.test(part)) { + result.duration = part + seen.add('DURATION') + } else if (!seen.has('DELAY') && TIME.test(part)) { + result.delay = part + seen.add('DELAY') + } else if (!seen.has('NAME')) { + result.name = part + seen.add('NAME') + } else { + if (!result.unknown) result.unknown = [] + result.unknown.push(part) + } + } + + return result + }) +} diff --git a/node_modules/tailwindcss/src/util/parseBoxShadowValue.js b/node_modules/tailwindcss/src/util/parseBoxShadowValue.js new file mode 100644 index 0000000..4be3efa --- /dev/null +++ b/node_modules/tailwindcss/src/util/parseBoxShadowValue.js @@ -0,0 +1,72 @@ +import { splitAtTopLevelOnly } from './splitAtTopLevelOnly' + +let KEYWORDS = new Set(['inset', 'inherit', 'initial', 'revert', 'unset']) +let SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead. +let LENGTH = /^-?(\d+|\.\d+)(.*?)$/g + +export function parseBoxShadowValue(input) { + let shadows = splitAtTopLevelOnly(input, ',') + return shadows.map((shadow) => { + let value = shadow.trim() + let result = { raw: value } + let parts = value.split(SPACE) + let seen = new Set() + + for (let part of parts) { + // Reset index, since the regex is stateful. + LENGTH.lastIndex = 0 + + // Keyword + if (!seen.has('KEYWORD') && KEYWORDS.has(part)) { + result.keyword = part + seen.add('KEYWORD') + } + + // Length value + else if (LENGTH.test(part)) { + if (!seen.has('X')) { + result.x = part + seen.add('X') + } else if (!seen.has('Y')) { + result.y = part + seen.add('Y') + } else if (!seen.has('BLUR')) { + result.blur = part + seen.add('BLUR') + } else if (!seen.has('SPREAD')) { + result.spread = part + seen.add('SPREAD') + } + } + + // Color or unknown + else { + if (!result.color) { + result.color = part + } else { + if (!result.unknown) result.unknown = [] + result.unknown.push(part) + } + } + } + + // Check if valid + result.valid = result.x !== undefined && result.y !== undefined + + return result + }) +} + +export function formatBoxShadowValue(shadows) { + return shadows + .map((shadow) => { + if (!shadow.valid) { + return shadow.raw + } + + return [shadow.keyword, shadow.x, shadow.y, shadow.blur, shadow.spread, shadow.color] + .filter(Boolean) + .join(' ') + }) + .join(', ') +} diff --git a/node_modules/tailwindcss/src/util/parseDependency.js b/node_modules/tailwindcss/src/util/parseDependency.js new file mode 100644 index 0000000..f26eb1a --- /dev/null +++ b/node_modules/tailwindcss/src/util/parseDependency.js @@ -0,0 +1,44 @@ +// @ts-check + +/** + * @typedef {{type: 'dependency', file: string} | {type: 'dir-dependency', dir: string, glob: string}} Dependency + */ + +/** + * + * @param {import('../lib/content.js').ContentPath} contentPath + * @returns {Dependency[]} + */ +export default function parseDependency(contentPath) { + if (contentPath.ignore) { + return [] + } + + if (!contentPath.glob) { + return [ + { + type: 'dependency', + file: contentPath.base, + }, + ] + } + + if (process.env.ROLLUP_WATCH === 'true') { + // rollup-plugin-postcss does not support dir-dependency messages + // but directories can be watched in the same way as files + return [ + { + type: 'dependency', + file: contentPath.base, + }, + ] + } + + return [ + { + type: 'dir-dependency', + dir: contentPath.base, + glob: contentPath.glob, + }, + ] +} diff --git a/node_modules/tailwindcss/src/util/parseGlob.js b/node_modules/tailwindcss/src/util/parseGlob.js new file mode 100644 index 0000000..5c03f41 --- /dev/null +++ b/node_modules/tailwindcss/src/util/parseGlob.js @@ -0,0 +1,24 @@ +import globParent from 'glob-parent' + +// Based on `glob-base` +// https://github.com/micromatch/glob-base/blob/master/index.js +export function parseGlob(pattern) { + let glob = pattern + let base = globParent(pattern) + + if (base !== '.') { + glob = pattern.substr(base.length) + if (glob.charAt(0) === '/') { + glob = glob.substr(1) + } + } + + if (glob.substr(0, 2) === './') { + glob = glob.substr(2) + } + if (glob.charAt(0) === '/') { + glob = glob.substr(1) + } + + return { base, glob } +} diff --git a/node_modules/tailwindcss/src/util/parseObjectStyles.js b/node_modules/tailwindcss/src/util/parseObjectStyles.js new file mode 100644 index 0000000..cb54787 --- /dev/null +++ b/node_modules/tailwindcss/src/util/parseObjectStyles.js @@ -0,0 +1,19 @@ +import postcss from 'postcss' +import postcssNested from 'postcss-nested' +import postcssJs from 'postcss-js' + +export default function parseObjectStyles(styles) { + if (!Array.isArray(styles)) { + return parseObjectStyles([styles]) + } + + return styles.flatMap((style) => { + return postcss([ + postcssNested({ + bubble: ['screen'], + }), + ]).process(style, { + parser: postcssJs, + }).root.nodes + }) +} 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('')) { + let oldValue = value + + return ({ opacityValue = 1 }) => oldValue.replace('', 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] + } +} diff --git a/node_modules/tailwindcss/src/util/prefixSelector.js b/node_modules/tailwindcss/src/util/prefixSelector.js new file mode 100644 index 0000000..93cbeb9 --- /dev/null +++ b/node_modules/tailwindcss/src/util/prefixSelector.js @@ -0,0 +1,33 @@ +import parser from 'postcss-selector-parser' + +/** + * @template {string | import('postcss-selector-parser').Root} T + * + * Prefix all classes in the selector with the given prefix + * + * It can take either a string or a selector AST and will return the same type + * + * @param {string} prefix + * @param {T} selector + * @param {boolean} prependNegative + * @returns {T} + */ +export default function (prefix, selector, prependNegative = false) { + if (prefix === '') { + return selector + } + + /** @type {import('postcss-selector-parser').Root} */ + let ast = typeof selector === 'string' ? parser().astSync(selector) : selector + + ast.walkClasses((classSelector) => { + let baseClass = classSelector.value + let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith('-') + + classSelector.value = shouldPlaceNegativeBeforePrefix + ? `-${prefix}${baseClass.slice(1)}` + : `${prefix}${baseClass}` + }) + + return typeof selector === 'string' ? ast.toString() : ast +} diff --git a/node_modules/tailwindcss/src/util/pseudoElements.js b/node_modules/tailwindcss/src/util/pseudoElements.js new file mode 100644 index 0000000..5795cdd --- /dev/null +++ b/node_modules/tailwindcss/src/util/pseudoElements.js @@ -0,0 +1,167 @@ +/** @typedef {import('postcss-selector-parser').Root} Root */ +/** @typedef {import('postcss-selector-parser').Selector} Selector */ +/** @typedef {import('postcss-selector-parser').Pseudo} Pseudo */ +/** @typedef {import('postcss-selector-parser').Node} Node */ + +// There are some pseudo-elements that may or may not be: + +// **Actionable** +// Zero or more user-action pseudo-classes may be attached to the pseudo-element itself +// structural-pseudo-classes are NOT allowed but we don't make +// The spec is not clear on whether this is allowed or not — but in practice it is. + +// **Terminal** +// It MUST be placed at the end of a selector +// +// This is the required in the spec. However, some pseudo elements are not "terminal" because +// they represent a "boundary piercing" that is compiled out by a build step. + +// **Jumpable** +// Any terminal element may "jump" over combinators when moving to the end of the selector +// +// This is a backwards-compat quirk of pseudo element variants from earlier versions of Tailwind CSS. + +/** @typedef {'terminal' | 'actionable' | 'jumpable'} PseudoProperty */ + +/** @type {Record} */ +let elementProperties = { + // Pseudo elements from the spec + '::after': ['terminal', 'jumpable'], + '::backdrop': ['terminal', 'jumpable'], + '::before': ['terminal', 'jumpable'], + '::cue': ['terminal'], + '::cue-region': ['terminal'], + '::first-letter': ['terminal', 'jumpable'], + '::first-line': ['terminal', 'jumpable'], + '::grammar-error': ['terminal'], + '::marker': ['terminal', 'jumpable'], + '::part': ['terminal', 'actionable'], + '::placeholder': ['terminal', 'jumpable'], + '::selection': ['terminal', 'jumpable'], + '::slotted': ['terminal'], + '::spelling-error': ['terminal'], + '::target-text': ['terminal'], + + // Pseudo elements from the spec with special rules + '::file-selector-button': ['terminal', 'actionable'], + + // Library-specific pseudo elements used by component libraries + // These are Shadow DOM-like + '::deep': ['actionable'], + '::v-deep': ['actionable'], + '::ng-deep': ['actionable'], + + // Note: As a rule, double colons (::) should be used instead of a single colon + // (:). This distinguishes pseudo-classes from pseudo-elements. However, since + // this distinction was not present in older versions of the W3C spec, most + // browsers support both syntaxes for the original pseudo-elements. + ':after': ['terminal', 'jumpable'], + ':before': ['terminal', 'jumpable'], + ':first-letter': ['terminal', 'jumpable'], + ':first-line': ['terminal', 'jumpable'], + + // The default value is used when the pseudo-element is not recognized + // Because it's not recognized, we don't know if it's terminal or not + // So we assume it can be moved AND can have user-action pseudo classes attached to it + __default__: ['terminal', 'actionable'], +} + +/** + * @param {Selector} sel + * @returns {Selector} + */ +export function movePseudos(sel) { + let [pseudos] = movablePseudos(sel) + + // Remove all pseudo elements from their respective selectors + pseudos.forEach(([sel, pseudo]) => sel.removeChild(pseudo)) + + // Re-add them to the end of the selector in the correct order. + // This moves terminal pseudo elements to the end of the + // selector otherwise the selector will not be valid. + // + // Examples: + // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before` + // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before` + // + // The selector `::before:hover` does not work but we + // can make it work for you by flipping the order. + sel.nodes.push(...pseudos.map(([, pseudo]) => pseudo)) + + return sel +} + +/** @typedef {[sel: Selector, pseudo: Pseudo, attachedTo: Pseudo | null]} MovablePseudo */ +/** @typedef {[pseudos: MovablePseudo[], lastSeenElement: Pseudo | null]} MovablePseudosResult */ + +/** + * @param {Selector} sel + * @returns {MovablePseudosResult} + */ +function movablePseudos(sel) { + /** @type {MovablePseudo[]} */ + let buffer = [] + + /** @type {Pseudo | null} */ + let lastSeenElement = null + + for (let node of sel.nodes) { + if (node.type === 'combinator') { + buffer = buffer.filter(([, node]) => propertiesForPseudo(node).includes('jumpable')) + lastSeenElement = null + } else if (node.type === 'pseudo') { + if (isMovablePseudoElement(node)) { + lastSeenElement = node + buffer.push([sel, node, null]) + } else if (lastSeenElement && isAttachablePseudoClass(node, lastSeenElement)) { + buffer.push([sel, node, lastSeenElement]) + } else { + lastSeenElement = null + } + + for (let sub of node.nodes ?? []) { + let [movable, lastSeenElementInSub] = movablePseudos(sub) + lastSeenElement = lastSeenElementInSub || lastSeenElement + buffer.push(...movable) + } + } + } + + return [buffer, lastSeenElement] +} + +/** + * @param {Node} node + * @returns {boolean} + */ +function isPseudoElement(node) { + return node.value.startsWith('::') || elementProperties[node.value] !== undefined +} + +/** + * @param {Node} node + * @returns {boolean} + */ +function isMovablePseudoElement(node) { + return isPseudoElement(node) && propertiesForPseudo(node).includes('terminal') +} + +/** + * @param {Node} node + * @param {Pseudo} pseudo + * @returns {boolean} + */ +function isAttachablePseudoClass(node, pseudo) { + if (node.type !== 'pseudo') return false + if (isPseudoElement(node)) return false + + return propertiesForPseudo(pseudo).includes('actionable') +} + +/** + * @param {Pseudo} pseudo + * @returns {PseudoProperty[]} + */ +function propertiesForPseudo(pseudo) { + return elementProperties[pseudo.value] ?? elementProperties.__default__ +} diff --git a/node_modules/tailwindcss/src/util/removeAlphaVariables.js b/node_modules/tailwindcss/src/util/removeAlphaVariables.js new file mode 100644 index 0000000..76655be --- /dev/null +++ b/node_modules/tailwindcss/src/util/removeAlphaVariables.js @@ -0,0 +1,24 @@ +/** + * This function removes any uses of CSS variables used as an alpha channel + * + * This is required for selectors like `:visited` which do not allow + * changes in opacity or external control using CSS variables. + * + * @param {import('postcss').Container} container + * @param {string[]} toRemove + */ +export function removeAlphaVariables(container, toRemove) { + container.walkDecls((decl) => { + if (toRemove.includes(decl.prop)) { + decl.remove() + + return + } + + for (let varName of toRemove) { + if (decl.value.includes(`/ var(${varName})`)) { + decl.value = decl.value.replace(`/ var(${varName})`, '') + } + } + }) +} diff --git a/node_modules/tailwindcss/src/util/resolveConfig.js b/node_modules/tailwindcss/src/util/resolveConfig.js new file mode 100644 index 0000000..cfeda6e --- /dev/null +++ b/node_modules/tailwindcss/src/util/resolveConfig.js @@ -0,0 +1,277 @@ +import negateValue from './negateValue' +import corePluginList from '../corePluginList' +import configurePlugins from './configurePlugins' +import colors from '../public/colors' +import { defaults } from './defaults' +import { toPath } from './toPath' +import { normalizeConfig } from './normalizeConfig' +import isPlainObject from './isPlainObject' +import { cloneDeep } from './cloneDeep' +import { parseColorFormat } from './pluginUtils' +import { withAlphaValue } from './withAlphaVariable' +import toColorValue from './toColorValue' + +function isFunction(input) { + return typeof input === 'function' +} + +function mergeWith(target, ...sources) { + let customizer = sources.pop() + + for (let source of sources) { + for (let k in source) { + let merged = customizer(target[k], source[k]) + + if (merged === undefined) { + if (isPlainObject(target[k]) && isPlainObject(source[k])) { + target[k] = mergeWith({}, target[k], source[k], customizer) + } else { + target[k] = source[k] + } + } else { + target[k] = merged + } + } + } + + return target +} + +const configUtils = { + colors, + negative(scale) { + // TODO: Log that this function isn't really needed anymore? + return Object.keys(scale) + .filter((key) => scale[key] !== '0') + .reduce((negativeScale, key) => { + let negativeValue = negateValue(scale[key]) + + if (negativeValue !== undefined) { + negativeScale[`-${key}`] = negativeValue + } + + return negativeScale + }, {}) + }, + breakpoints(screens) { + return Object.keys(screens) + .filter((key) => typeof screens[key] === 'string') + .reduce( + (breakpoints, key) => ({ + ...breakpoints, + [`screen-${key}`]: screens[key], + }), + {} + ) + }, +} + +function value(valueToResolve, ...args) { + return isFunction(valueToResolve) ? valueToResolve(...args) : valueToResolve +} + +function collectExtends(items) { + return items.reduce((merged, { extend }) => { + return mergeWith(merged, extend, (mergedValue, extendValue) => { + if (mergedValue === undefined) { + return [extendValue] + } + + if (Array.isArray(mergedValue)) { + return [extendValue, ...mergedValue] + } + + return [extendValue, mergedValue] + }) + }, {}) +} + +function mergeThemes(themes) { + return { + ...themes.reduce((merged, theme) => defaults(merged, theme), {}), + + // In order to resolve n config objects, we combine all of their `extend` properties + // into arrays instead of objects so they aren't overridden. + extend: collectExtends(themes), + } +} + +function mergeExtensionCustomizer(merged, value) { + // When we have an array of objects, we do want to merge it + if (Array.isArray(merged) && isPlainObject(merged[0])) { + return merged.concat(value) + } + + // When the incoming value is an array, and the existing config is an object, prepend the existing object + if (Array.isArray(value) && isPlainObject(value[0]) && isPlainObject(merged)) { + return [merged, ...value] + } + + // Override arrays (for example for font-families, box-shadows, ...) + if (Array.isArray(value)) { + return value + } + + // Execute default behaviour + return undefined +} + +function mergeExtensions({ extend, ...theme }) { + return mergeWith(theme, extend, (themeValue, extensions) => { + // The `extend` property is an array, so we need to check if it contains any functions + if (!isFunction(themeValue) && !extensions.some(isFunction)) { + return mergeWith({}, themeValue, ...extensions, mergeExtensionCustomizer) + } + + return (resolveThemePath, utils) => + mergeWith( + {}, + ...[themeValue, ...extensions].map((e) => value(e, resolveThemePath, utils)), + mergeExtensionCustomizer + ) + }) +} + +/** + * + * @param {string} key + * @return {Iterable} + */ +function* toPaths(key) { + let path = toPath(key) + + if (path.length === 0) { + return + } + + yield path + + if (Array.isArray(key)) { + return + } + + let pattern = /^(.*?)\s*\/\s*([^/]+)$/ + let matches = key.match(pattern) + + if (matches !== null) { + let [, prefix, alpha] = matches + + let newPath = toPath(prefix) + newPath.alpha = alpha + + yield newPath + } +} + +function resolveFunctionKeys(object) { + // theme('colors.red.500 / 0.5') -> ['colors', 'red', '500 / 0', '5] + + const resolvePath = (key, defaultValue) => { + for (const path of toPaths(key)) { + let index = 0 + let val = object + + while (val !== undefined && val !== null && index < path.length) { + val = val[path[index++]] + + let shouldResolveAsFn = + isFunction(val) && (path.alpha === undefined || index <= path.length - 1) + + val = shouldResolveAsFn ? val(resolvePath, configUtils) : val + } + + if (val !== undefined) { + if (path.alpha !== undefined) { + let normalized = parseColorFormat(val) + + return withAlphaValue(normalized, path.alpha, toColorValue(normalized)) + } + + if (isPlainObject(val)) { + return cloneDeep(val) + } + + return val + } + } + + return defaultValue + } + + Object.assign(resolvePath, { + theme: resolvePath, + ...configUtils, + }) + + return Object.keys(object).reduce((resolved, key) => { + resolved[key] = isFunction(object[key]) ? object[key](resolvePath, configUtils) : object[key] + + return resolved + }, {}) +} + +function extractPluginConfigs(configs) { + let allConfigs = [] + + configs.forEach((config) => { + allConfigs = [...allConfigs, config] + + const plugins = config?.plugins ?? [] + + if (plugins.length === 0) { + return + } + + plugins.forEach((plugin) => { + if (plugin.__isOptionsFunction) { + plugin = plugin() + } + allConfigs = [...allConfigs, ...extractPluginConfigs([plugin?.config ?? {}])] + }) + }) + + return allConfigs +} + +function resolveCorePlugins(corePluginConfigs) { + const result = [...corePluginConfigs].reduceRight((resolved, corePluginConfig) => { + if (isFunction(corePluginConfig)) { + return corePluginConfig({ corePlugins: resolved }) + } + return configurePlugins(corePluginConfig, resolved) + }, corePluginList) + + return result +} + +function resolvePluginLists(pluginLists) { + const result = [...pluginLists].reduceRight((resolved, pluginList) => { + return [...resolved, ...pluginList] + }, []) + + return result +} + +export default function resolveConfig(configs) { + let allConfigs = [ + ...extractPluginConfigs(configs), + { + prefix: '', + important: false, + separator: ':', + }, + ] + + return normalizeConfig( + defaults( + { + theme: resolveFunctionKeys( + mergeExtensions(mergeThemes(allConfigs.map((t) => t?.theme ?? {}))) + ), + corePlugins: resolveCorePlugins(allConfigs.map((c) => c.corePlugins)), + plugins: resolvePluginLists(configs.map((c) => c?.plugins ?? [])), + }, + ...allConfigs + ) + ) +} diff --git a/node_modules/tailwindcss/src/util/resolveConfigPath.js b/node_modules/tailwindcss/src/util/resolveConfigPath.js new file mode 100644 index 0000000..2b50789 --- /dev/null +++ b/node_modules/tailwindcss/src/util/resolveConfigPath.js @@ -0,0 +1,66 @@ +import fs from 'fs' +import path from 'path' + +const defaultConfigFiles = [ + './tailwind.config.js', + './tailwind.config.cjs', + './tailwind.config.mjs', + './tailwind.config.ts', +] + +function isObject(value) { + return typeof value === 'object' && value !== null +} + +function isEmpty(obj) { + return Object.keys(obj).length === 0 +} + +function isString(value) { + return typeof value === 'string' || value instanceof String +} + +export default function resolveConfigPath(pathOrConfig) { + // require('tailwindcss')({ theme: ..., variants: ... }) + if (isObject(pathOrConfig) && pathOrConfig.config === undefined && !isEmpty(pathOrConfig)) { + return null + } + + // require('tailwindcss')({ config: 'custom-config.js' }) + if ( + isObject(pathOrConfig) && + pathOrConfig.config !== undefined && + isString(pathOrConfig.config) + ) { + return path.resolve(pathOrConfig.config) + } + + // require('tailwindcss')({ config: { theme: ..., variants: ... } }) + if ( + isObject(pathOrConfig) && + pathOrConfig.config !== undefined && + isObject(pathOrConfig.config) + ) { + return null + } + + // require('tailwindcss')('custom-config.js') + if (isString(pathOrConfig)) { + return path.resolve(pathOrConfig) + } + + // require('tailwindcss') + return resolveDefaultConfigPath() +} + +export function resolveDefaultConfigPath() { + for (const configFile of defaultConfigFiles) { + try { + const configPath = path.resolve(configFile) + fs.accessSync(configPath) + return configPath + } catch (err) {} + } + + return null +} diff --git a/node_modules/tailwindcss/src/util/responsive.js b/node_modules/tailwindcss/src/util/responsive.js new file mode 100644 index 0000000..29bf9e9 --- /dev/null +++ b/node_modules/tailwindcss/src/util/responsive.js @@ -0,0 +1,10 @@ +import postcss from 'postcss' +import cloneNodes from './cloneNodes' + +export default function responsive(rules) { + return postcss + .atRule({ + name: 'responsive', + }) + .append(cloneNodes(Array.isArray(rules) ? rules : [rules])) +} diff --git a/node_modules/tailwindcss/src/util/splitAtTopLevelOnly.js b/node_modules/tailwindcss/src/util/splitAtTopLevelOnly.js new file mode 100644 index 0000000..a749c79 --- /dev/null +++ b/node_modules/tailwindcss/src/util/splitAtTopLevelOnly.js @@ -0,0 +1,52 @@ +/** + * This splits a string on a top-level character. + * + * Regex doesn't support recursion (at least not the JS-flavored version). + * So we have to use a tiny state machine to keep track of paren placement. + * + * Expected behavior using commas: + * var(--a, 0 0 1px rgb(0, 0, 0)), 0 0 1px rgb(0, 0, 0) + * ─┬─ ┬ ┬ ┬ + * x x x ╰──────── Split because top-level + * ╰──────────────┴──┴───────────── Ignored b/c inside >= 1 levels of parens + * + * @param {string} input + * @param {string} separator + */ +export function splitAtTopLevelOnly(input, separator) { + let stack = [] + let parts = [] + let lastPos = 0 + let isEscaped = false + + for (let idx = 0; idx < input.length; idx++) { + let char = input[idx] + + if (stack.length === 0 && char === separator[0] && !isEscaped) { + if (separator.length === 1 || input.slice(idx, idx + separator.length) === separator) { + parts.push(input.slice(lastPos, idx)) + lastPos = idx + separator.length + } + } + + if (isEscaped) { + isEscaped = false + } else if (char === '\\') { + isEscaped = true + } + + if (char === '(' || char === '[' || char === '{') { + stack.push(char) + } else if ( + (char === ')' && stack[stack.length - 1] === '(') || + (char === ']' && stack[stack.length - 1] === '[') || + (char === '}' && stack[stack.length - 1] === '{') + ) { + stack.pop() + } + } + + parts.push(input.slice(lastPos)) + + return parts +} diff --git a/node_modules/tailwindcss/src/util/tap.js b/node_modules/tailwindcss/src/util/tap.js new file mode 100644 index 0000000..0590e4b --- /dev/null +++ b/node_modules/tailwindcss/src/util/tap.js @@ -0,0 +1,4 @@ +export function tap(value, mutator) { + mutator(value) + return value +} diff --git a/node_modules/tailwindcss/src/util/toColorValue.js b/node_modules/tailwindcss/src/util/toColorValue.js new file mode 100644 index 0000000..288d907 --- /dev/null +++ b/node_modules/tailwindcss/src/util/toColorValue.js @@ -0,0 +1,3 @@ +export default function toColorValue(maybeFunction) { + return typeof maybeFunction === 'function' ? maybeFunction({}) : maybeFunction +} diff --git a/node_modules/tailwindcss/src/util/toPath.js b/node_modules/tailwindcss/src/util/toPath.js new file mode 100644 index 0000000..6dce924 --- /dev/null +++ b/node_modules/tailwindcss/src/util/toPath.js @@ -0,0 +1,26 @@ +/** + * Parse a path string into an array of path segments. + * + * Square bracket notation `a[b]` may be used to "escape" dots that would otherwise be interpreted as path separators. + * + * Example: + * a -> ['a'] + * a.b.c -> ['a', 'b', 'c'] + * a[b].c -> ['a', 'b', 'c'] + * a[b.c].e.f -> ['a', 'b.c', 'e', 'f'] + * a[b][c][d] -> ['a', 'b', 'c', 'd'] + * + * @param {string|string[]} path + **/ +export function toPath(path) { + if (Array.isArray(path)) return path + + let openBrackets = path.split('[').length - 1 + let closedBrackets = path.split(']').length - 1 + + if (openBrackets !== closedBrackets) { + throw new Error(`Path is invalid. Has unbalanced brackets: ${path}`) + } + + return path.split(/\.(?![^\[]*\])|[\[\]]/g).filter(Boolean) +} diff --git a/node_modules/tailwindcss/src/util/transformThemeValue.js b/node_modules/tailwindcss/src/util/transformThemeValue.js new file mode 100644 index 0000000..2469612 --- /dev/null +++ b/node_modules/tailwindcss/src/util/transformThemeValue.js @@ -0,0 +1,62 @@ +import postcss from 'postcss' +import isPlainObject from './isPlainObject' + +export default function transformThemeValue(themeSection) { + if (['fontSize', 'outline'].includes(themeSection)) { + return (value) => { + if (typeof value === 'function') value = value({}) + if (Array.isArray(value)) value = value[0] + + return value + } + } + + if (themeSection === 'fontFamily') { + return (value) => { + if (typeof value === 'function') value = value({}) + let families = Array.isArray(value) && isPlainObject(value[1]) ? value[0] : value + return Array.isArray(families) ? families.join(', ') : families + } + } + + if ( + [ + 'boxShadow', + 'transitionProperty', + 'transitionDuration', + 'transitionDelay', + 'transitionTimingFunction', + 'backgroundImage', + 'backgroundSize', + 'backgroundColor', + 'cursor', + 'animation', + ].includes(themeSection) + ) { + return (value) => { + if (typeof value === 'function') value = value({}) + if (Array.isArray(value)) value = value.join(', ') + + return value + } + } + + // For backwards compatibility reasons, before we switched to underscores + // instead of commas for arbitrary values. + if (['gridTemplateColumns', 'gridTemplateRows', 'objectPosition'].includes(themeSection)) { + return (value) => { + if (typeof value === 'function') value = value({}) + if (typeof value === 'string') value = postcss.list.comma(value).join(' ') + + return value + } + } + + return (value, opts = {}) => { + if (typeof value === 'function') { + value = value(opts) + } + + return value + } +} diff --git a/node_modules/tailwindcss/src/util/validateConfig.js b/node_modules/tailwindcss/src/util/validateConfig.js new file mode 100644 index 0000000..8c22e44 --- /dev/null +++ b/node_modules/tailwindcss/src/util/validateConfig.js @@ -0,0 +1,26 @@ +import log from './log' + +export function validateConfig(config) { + if (config.content.files.length === 0) { + log.warn('content-problems', [ + 'The `content` option in your Tailwind CSS configuration is missing or empty.', + 'Configure your content sources or your generated CSS will be missing styles.', + 'https://tailwindcss.com/docs/content-configuration', + ]) + } + + // Warn if the line-clamp plugin is installed + try { + let plugin = require('@tailwindcss/line-clamp') + if (config.plugins.includes(plugin)) { + log.warn('line-clamp-in-core', [ + 'As of Tailwind CSS v3.3, the `@tailwindcss/line-clamp` plugin is now included by default.', + 'Remove it from the `plugins` array in your configuration to eliminate this warning.', + ]) + + config.plugins = config.plugins.filter((p) => p !== plugin) + } + } catch {} + + return config +} diff --git a/node_modules/tailwindcss/src/util/validateFormalSyntax.js b/node_modules/tailwindcss/src/util/validateFormalSyntax.js new file mode 100644 index 0000000..d3dafea --- /dev/null +++ b/node_modules/tailwindcss/src/util/validateFormalSyntax.js @@ -0,0 +1,34 @@ +import { length, percentage } from './dataTypes' +import { splitAtTopLevelOnly } from './splitAtTopLevelOnly' + +/** + * + * https://developer.mozilla.org/en-US/docs/Web/CSS/background-size#formal_syntax + * + * background-size = + * # + * + * = + * [ | auto ]{1,2} | + * cover | + * contain + * + * = + * | + * + * + * @param {string} value + */ +export function backgroundSize(value) { + let keywordValues = ['cover', 'contain'] + // the type will probably be a css function + // so we have to use `splitAtTopLevelOnly` + return splitAtTopLevelOnly(value, ',').every((part) => { + let sizes = splitAtTopLevelOnly(part, '_').filter(Boolean) + if (sizes.length === 1 && keywordValues.includes(sizes[0])) return true + + if (sizes.length !== 1 && sizes.length !== 2) return false + + return sizes.every((size) => length(size) || percentage(size) || size === 'auto') + }) +} diff --git a/node_modules/tailwindcss/src/util/withAlphaVariable.js b/node_modules/tailwindcss/src/util/withAlphaVariable.js new file mode 100644 index 0000000..15aedb7 --- /dev/null +++ b/node_modules/tailwindcss/src/util/withAlphaVariable.js @@ -0,0 +1,49 @@ +import { parseColor, formatColor } from './color' + +export function withAlphaValue(color, alphaValue, defaultValue) { + if (typeof color === 'function') { + return color({ opacityValue: alphaValue }) + } + + let parsed = parseColor(color, { loose: true }) + + if (parsed === null) { + return defaultValue + } + + return formatColor({ ...parsed, alpha: alphaValue }) +} + +export default function withAlphaVariable({ color, property, variable }) { + let properties = [].concat(property) + if (typeof color === 'function') { + return { + [variable]: '1', + ...Object.fromEntries( + properties.map((p) => { + return [p, color({ opacityVariable: variable, opacityValue: `var(${variable})` })] + }) + ), + } + } + + const parsed = parseColor(color) + + if (parsed === null) { + return Object.fromEntries(properties.map((p) => [p, color])) + } + + if (parsed.alpha !== undefined) { + // Has an alpha value, return color as-is + return Object.fromEntries(properties.map((p) => [p, color])) + } + + return { + [variable]: '1', + ...Object.fromEntries( + properties.map((p) => { + return [p, formatColor({ ...parsed, alpha: `var(${variable})` })] + }) + ), + } +} -- cgit v1.2.3