summaryrefslogtreecommitdiff
path: root/node_modules/tailwindcss/src/lib
diff options
context:
space:
mode:
authorPhilipp Tanlak <philipp.tanlak@gmail.com>2025-11-24 20:54:57 +0100
committerPhilipp Tanlak <philipp.tanlak@gmail.com>2025-11-24 20:57:48 +0100
commitb1e2c8fd5cb5dfa46bc440a12eafaf56cd844b1c (patch)
tree49d360fd6cbc6a2754efe93524ac47ff0fbe0f7d /node_modules/tailwindcss/src/lib
Docs
Diffstat (limited to 'node_modules/tailwindcss/src/lib')
-rw-r--r--node_modules/tailwindcss/src/lib/cacheInvalidation.js52
-rw-r--r--node_modules/tailwindcss/src/lib/collapseAdjacentRules.js58
-rw-r--r--node_modules/tailwindcss/src/lib/collapseDuplicateDeclarations.js93
-rw-r--r--node_modules/tailwindcss/src/lib/content.js208
-rw-r--r--node_modules/tailwindcss/src/lib/defaultExtractor.js216
-rw-r--r--node_modules/tailwindcss/src/lib/detectNesting.js47
-rw-r--r--node_modules/tailwindcss/src/lib/evaluateTailwindFunctions.js272
-rw-r--r--node_modules/tailwindcss/src/lib/expandApplyAtRules.js620
-rw-r--r--node_modules/tailwindcss/src/lib/expandTailwindAtRules.js297
-rw-r--r--node_modules/tailwindcss/src/lib/findAtConfigPath.js48
-rw-r--r--node_modules/tailwindcss/src/lib/generateRules.js936
-rw-r--r--node_modules/tailwindcss/src/lib/getModuleDependencies.js79
-rw-r--r--node_modules/tailwindcss/src/lib/load-config.ts31
-rw-r--r--node_modules/tailwindcss/src/lib/normalizeTailwindDirectives.js84
-rw-r--r--node_modules/tailwindcss/src/lib/offsets.js373
-rw-r--r--node_modules/tailwindcss/src/lib/partitionApplyAtRules.js52
-rw-r--r--node_modules/tailwindcss/src/lib/regex.js74
-rw-r--r--node_modules/tailwindcss/src/lib/remap-bitfield.js82
-rw-r--r--node_modules/tailwindcss/src/lib/resolveDefaultsAtRules.js163
-rw-r--r--node_modules/tailwindcss/src/lib/setupContextUtils.js1342
-rw-r--r--node_modules/tailwindcss/src/lib/setupTrackingContext.js169
-rw-r--r--node_modules/tailwindcss/src/lib/sharedState.js61
-rw-r--r--node_modules/tailwindcss/src/lib/substituteScreenAtRules.js19
23 files changed, 5376 insertions, 0 deletions
diff --git a/node_modules/tailwindcss/src/lib/cacheInvalidation.js b/node_modules/tailwindcss/src/lib/cacheInvalidation.js
new file mode 100644
index 0000000..fa13702
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/cacheInvalidation.js
@@ -0,0 +1,52 @@
+import crypto from 'crypto'
+import * as sharedState from './sharedState'
+
+/**
+ * Calculate the hash of a string.
+ *
+ * This doesn't need to be cryptographically secure or
+ * anything like that since it's used only to detect
+ * when the CSS changes to invalidate the context.
+ *
+ * This is wrapped in a try/catch because it's really dependent
+ * on how Node itself is build and the environment and OpenSSL
+ * version / build that is installed on the user's machine.
+ *
+ * Based on the environment this can just outright fail.
+ *
+ * See https://github.com/nodejs/node/issues/40455
+ *
+ * @param {string} str
+ */
+function getHash(str) {
+ try {
+ return crypto.createHash('md5').update(str, 'utf-8').digest('binary')
+ } catch (err) {
+ return ''
+ }
+}
+
+/**
+ * Determine if the CSS tree is different from the
+ * previous version for the given `sourcePath`.
+ *
+ * @param {string} sourcePath
+ * @param {import('postcss').Node} root
+ */
+export function hasContentChanged(sourcePath, root) {
+ let css = root.toString()
+
+ // We only care about files with @tailwind directives
+ // Other files use an existing context
+ if (!css.includes('@tailwind')) {
+ return false
+ }
+
+ let existingHash = sharedState.sourceHashMap.get(sourcePath)
+ let rootHash = getHash(css)
+ let didChange = existingHash !== rootHash
+
+ sharedState.sourceHashMap.set(sourcePath, rootHash)
+
+ return didChange
+}
diff --git a/node_modules/tailwindcss/src/lib/collapseAdjacentRules.js b/node_modules/tailwindcss/src/lib/collapseAdjacentRules.js
new file mode 100644
index 0000000..119f592
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/collapseAdjacentRules.js
@@ -0,0 +1,58 @@
+let comparisonMap = {
+ atrule: ['name', 'params'],
+ rule: ['selector'],
+}
+let types = new Set(Object.keys(comparisonMap))
+
+export default function collapseAdjacentRules() {
+ function collapseRulesIn(root) {
+ let currentRule = null
+ root.each((node) => {
+ if (!types.has(node.type)) {
+ currentRule = null
+ return
+ }
+
+ if (currentRule === null) {
+ currentRule = node
+ return
+ }
+
+ let properties = comparisonMap[node.type]
+
+ if (node.type === 'atrule' && node.name === 'font-face') {
+ currentRule = node
+ } else if (
+ properties.every(
+ (property) =>
+ (node[property] ?? '').replace(/\s+/g, ' ') ===
+ (currentRule[property] ?? '').replace(/\s+/g, ' ')
+ )
+ ) {
+ // An AtRule may not have children (for example if we encounter duplicate @import url(…) rules)
+ if (node.nodes) {
+ currentRule.append(node.nodes)
+ }
+
+ node.remove()
+ } else {
+ currentRule = node
+ }
+ })
+
+ // After we've collapsed adjacent rules & at-rules, we need to collapse
+ // adjacent rules & at-rules that are children of at-rules.
+ // We do not care about nesting rules because Tailwind CSS
+ // explicitly does not handle rule nesting on its own as
+ // the user is expected to use a nesting plugin
+ root.each((node) => {
+ if (node.type === 'atrule') {
+ collapseRulesIn(node)
+ }
+ })
+ }
+
+ return (root) => {
+ collapseRulesIn(root)
+ }
+}
diff --git a/node_modules/tailwindcss/src/lib/collapseDuplicateDeclarations.js b/node_modules/tailwindcss/src/lib/collapseDuplicateDeclarations.js
new file mode 100644
index 0000000..d310d58
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/collapseDuplicateDeclarations.js
@@ -0,0 +1,93 @@
+export default function collapseDuplicateDeclarations() {
+ return (root) => {
+ root.walkRules((node) => {
+ let seen = new Map()
+ let droppable = new Set([])
+ let byProperty = new Map()
+
+ node.walkDecls((decl) => {
+ // This could happen if we have nested selectors. In that case the
+ // parent will loop over all its declarations but also the declarations
+ // of nested rules. With this we ensure that we are shallowly checking
+ // declarations.
+ if (decl.parent !== node) {
+ return
+ }
+
+ if (seen.has(decl.prop)) {
+ // Exact same value as what we have seen so far
+ if (seen.get(decl.prop).value === decl.value) {
+ // Keep the last one, drop the one we've seen so far
+ droppable.add(seen.get(decl.prop))
+ // Override the existing one with the new value. This is necessary
+ // so that if we happen to have more than one declaration with the
+ // same value, that we keep removing the previous one. Otherwise we
+ // will only remove the *first* one.
+ seen.set(decl.prop, decl)
+ return
+ }
+
+ // Not the same value, so we need to check if we can merge it so
+ // let's collect it first.
+ if (!byProperty.has(decl.prop)) {
+ byProperty.set(decl.prop, new Set())
+ }
+
+ byProperty.get(decl.prop).add(seen.get(decl.prop))
+ byProperty.get(decl.prop).add(decl)
+ }
+
+ seen.set(decl.prop, decl)
+ })
+
+ // Drop all the duplicate declarations with the exact same value we've
+ // already seen so far.
+ for (let decl of droppable) {
+ decl.remove()
+ }
+
+ // Analyze the declarations based on its unit, drop all the declarations
+ // with the same unit but the last one in the list.
+ for (let declarations of byProperty.values()) {
+ let byUnit = new Map()
+
+ for (let decl of declarations) {
+ let unit = resolveUnit(decl.value)
+ if (unit === null) {
+ // We don't have a unit, so should never try and collapse this
+ // value. This is because we can't know how to do it in a correct
+ // way (e.g.: overrides for older browsers)
+ continue
+ }
+
+ if (!byUnit.has(unit)) {
+ byUnit.set(unit, new Set())
+ }
+
+ byUnit.get(unit).add(decl)
+ }
+
+ for (let declarations of byUnit.values()) {
+ // Get all but the last one
+ let removableDeclarations = Array.from(declarations).slice(0, -1)
+
+ for (let decl of removableDeclarations) {
+ decl.remove()
+ }
+ }
+ }
+ })
+ }
+}
+
+let UNITLESS_NUMBER = Symbol('unitless-number')
+
+function resolveUnit(input) {
+ let result = /^-?\d*.?\d+([\w%]+)?$/g.exec(input)
+
+ if (result) {
+ return result[1] ?? UNITLESS_NUMBER
+ }
+
+ return null
+}
diff --git a/node_modules/tailwindcss/src/lib/content.js b/node_modules/tailwindcss/src/lib/content.js
new file mode 100644
index 0000000..e814efe
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/content.js
@@ -0,0 +1,208 @@
+// @ts-check
+
+import fs from 'fs'
+import path from 'path'
+import isGlob from 'is-glob'
+import fastGlob from 'fast-glob'
+import normalizePath from 'normalize-path'
+import { parseGlob } from '../util/parseGlob'
+import { env } from './sharedState'
+
+/** @typedef {import('../../types/config.js').RawFile} RawFile */
+/** @typedef {import('../../types/config.js').FilePath} FilePath */
+
+/**
+ * @typedef {object} ContentPath
+ * @property {string} original
+ * @property {string} base
+ * @property {string | null} glob
+ * @property {boolean} ignore
+ * @property {string} pattern
+ */
+
+/**
+ * Turn a list of content paths (absolute or not; glob or not) into a list of
+ * absolute file paths that exist on the filesystem
+ *
+ * If there are symlinks in the path then multiple paths will be returned
+ * one for the symlink and one for the actual file
+ *
+ * @param {*} context
+ * @param {import('tailwindcss').Config} tailwindConfig
+ * @returns {ContentPath[]}
+ */
+export function parseCandidateFiles(context, tailwindConfig) {
+ let files = tailwindConfig.content.files
+
+ // Normalize the file globs
+ files = files.filter((filePath) => typeof filePath === 'string')
+ files = files.map(normalizePath)
+
+ // Split into included and excluded globs
+ let tasks = fastGlob.generateTasks(files)
+
+ /** @type {ContentPath[]} */
+ let included = []
+
+ /** @type {ContentPath[]} */
+ let excluded = []
+
+ for (const task of tasks) {
+ included.push(...task.positive.map((filePath) => parseFilePath(filePath, false)))
+ excluded.push(...task.negative.map((filePath) => parseFilePath(filePath, true)))
+ }
+
+ let paths = [...included, ...excluded]
+
+ // Resolve paths relative to the config file or cwd
+ paths = resolveRelativePaths(context, paths)
+
+ // Resolve symlinks if possible
+ paths = paths.flatMap(resolvePathSymlinks)
+
+ // Update cached patterns
+ paths = paths.map(resolveGlobPattern)
+
+ return paths
+}
+
+/**
+ *
+ * @param {string} filePath
+ * @param {boolean} ignore
+ * @returns {ContentPath}
+ */
+function parseFilePath(filePath, ignore) {
+ let contentPath = {
+ original: filePath,
+ base: filePath,
+ ignore,
+ pattern: filePath,
+ glob: null,
+ }
+
+ if (isGlob(filePath)) {
+ Object.assign(contentPath, parseGlob(filePath))
+ }
+
+ return contentPath
+}
+
+/**
+ *
+ * @param {ContentPath} contentPath
+ * @returns {ContentPath}
+ */
+function resolveGlobPattern(contentPath) {
+ // This is required for Windows support to properly pick up Glob paths.
+ // Afaik, this technically shouldn't be needed but there's probably
+ // some internal, direct path matching with a normalized path in
+ // a package which can't handle mixed directory separators
+ let base = normalizePath(contentPath.base)
+
+ // If the user's file path contains any special characters (like parens) for instance fast-glob
+ // is like "OOOH SHINY" and treats them as such. So we have to escape the base path to fix this
+ base = fastGlob.escapePath(base)
+
+ contentPath.pattern = contentPath.glob ? `${base}/${contentPath.glob}` : base
+ contentPath.pattern = contentPath.ignore ? `!${contentPath.pattern}` : contentPath.pattern
+
+ return contentPath
+}
+
+/**
+ * Resolve each path relative to the config file (when possible) if the experimental flag is enabled
+ * Otherwise, resolve relative to the current working directory
+ *
+ * @param {any} context
+ * @param {ContentPath[]} contentPaths
+ * @returns {ContentPath[]}
+ */
+function resolveRelativePaths(context, contentPaths) {
+ let resolveFrom = []
+
+ // Resolve base paths relative to the config file (when possible) if the experimental flag is enabled
+ if (context.userConfigPath && context.tailwindConfig.content.relative) {
+ resolveFrom = [path.dirname(context.userConfigPath)]
+ }
+
+ return contentPaths.map((contentPath) => {
+ contentPath.base = path.resolve(...resolveFrom, contentPath.base)
+
+ return contentPath
+ })
+}
+
+/**
+ * Resolve the symlink for the base directory / file in each path
+ * These are added as additional dependencies to watch for changes because
+ * some tools (like webpack) will only watch the actual file or directory
+ * but not the symlink itself even in projects that use monorepos.
+ *
+ * @param {ContentPath} contentPath
+ * @returns {ContentPath[]}
+ */
+function resolvePathSymlinks(contentPath) {
+ let paths = [contentPath]
+
+ try {
+ let resolvedPath = fs.realpathSync(contentPath.base)
+ if (resolvedPath !== contentPath.base) {
+ paths.push({
+ ...contentPath,
+ base: resolvedPath,
+ })
+ }
+ } catch {
+ // TODO: log this?
+ }
+
+ return paths
+}
+
+/**
+ * @param {any} context
+ * @param {ContentPath[]} candidateFiles
+ * @param {Map<string, number>} fileModifiedMap
+ * @returns {[{ content: string, extension: string }[], Map<string, number>]}
+ */
+export function resolvedChangedContent(context, candidateFiles, fileModifiedMap) {
+ let changedContent = context.tailwindConfig.content.files
+ .filter((item) => typeof item.raw === 'string')
+ .map(({ raw, extension = 'html' }) => ({ content: raw, extension }))
+
+ let [changedFiles, mTimesToCommit] = resolveChangedFiles(candidateFiles, fileModifiedMap)
+
+ for (let changedFile of changedFiles) {
+ let extension = path.extname(changedFile).slice(1)
+ changedContent.push({ file: changedFile, extension })
+ }
+
+ return [changedContent, mTimesToCommit]
+}
+
+/**
+ *
+ * @param {ContentPath[]} candidateFiles
+ * @param {Map<string, number>} fileModifiedMap
+ * @returns {[Set<string>, Map<string, number>]}
+ */
+function resolveChangedFiles(candidateFiles, fileModifiedMap) {
+ let paths = candidateFiles.map((contentPath) => contentPath.pattern)
+ let mTimesToCommit = new Map()
+
+ let changedFiles = new Set()
+ env.DEBUG && console.time('Finding changed files')
+ let files = fastGlob.sync(paths, { absolute: true })
+ for (let file of files) {
+ let prevModified = fileModifiedMap.get(file) || -Infinity
+ let modified = fs.statSync(file).mtimeMs
+
+ if (modified > prevModified) {
+ changedFiles.add(file)
+ mTimesToCommit.set(file, modified)
+ }
+ }
+ env.DEBUG && console.timeEnd('Finding changed files')
+ return [changedFiles, mTimesToCommit]
+}
diff --git a/node_modules/tailwindcss/src/lib/defaultExtractor.js b/node_modules/tailwindcss/src/lib/defaultExtractor.js
new file mode 100644
index 0000000..ae546e9
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/defaultExtractor.js
@@ -0,0 +1,216 @@
+import { flagEnabled } from '../featureFlags'
+import * as regex from './regex'
+
+export function defaultExtractor(context) {
+ let patterns = Array.from(buildRegExps(context))
+
+ /**
+ * @param {string} content
+ */
+ return (content) => {
+ /** @type {(string|string)[]} */
+ let results = []
+
+ for (let pattern of patterns) {
+ for (let result of content.match(pattern) ?? []) {
+ results.push(clipAtBalancedParens(result))
+ }
+ }
+
+ return results
+ }
+}
+
+function* buildRegExps(context) {
+ let separator = context.tailwindConfig.separator
+ let prefix =
+ context.tailwindConfig.prefix !== ''
+ ? regex.optional(regex.pattern([/-?/, regex.escape(context.tailwindConfig.prefix)]))
+ : ''
+
+ let utility = regex.any([
+ // Arbitrary properties (without square brackets)
+ /\[[^\s:'"`]+:[^\s\[\]]+\]/,
+
+ // Arbitrary properties with balanced square brackets
+ // This is a targeted fix to continue to allow theme()
+ // with square brackets to work in arbitrary properties
+ // while fixing a problem with the regex matching too much
+ /\[[^\s:'"`\]]+:[^\s]+?\[[^\s]+\][^\s]+?\]/,
+
+ // Utilities
+ regex.pattern([
+ // Utility Name / Group Name
+ /-?(?:\w+)/,
+
+ // Normal/Arbitrary values
+ regex.optional(
+ regex.any([
+ regex.pattern([
+ // Arbitrary values
+ /-(?:\w+-)*\[[^\s:]+\]/,
+
+ // Not immediately followed by an `{[(`
+ /(?![{([]])/,
+
+ // optionally followed by an opacity modifier
+ /(?:\/[^\s'"`\\><$]*)?/,
+ ]),
+
+ regex.pattern([
+ // Arbitrary values
+ /-(?:\w+-)*\[[^\s]+\]/,
+
+ // Not immediately followed by an `{[(`
+ /(?![{([]])/,
+
+ // optionally followed by an opacity modifier
+ /(?:\/[^\s'"`\\$]*)?/,
+ ]),
+
+ // Normal values w/o quotes — may include an opacity modifier
+ /[-\/][^\s'"`\\$={><]*/,
+ ])
+ ),
+ ]),
+ ])
+
+ let variantPatterns = [
+ // Without quotes
+ regex.any([
+ // This is here to provide special support for the `@` variant
+ regex.pattern([/@\[[^\s"'`]+\](\/[^\s"'`]+)?/, separator]),
+
+ // With variant modifier (e.g.: group-[..]/modifier)
+ regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]\/\w+/, separator]),
+
+ regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/, separator]),
+ regex.pattern([/[^\s"'`\[\\]+/, separator]),
+ ]),
+
+ // With quotes allowed
+ regex.any([
+ // With variant modifier (e.g.: group-[..]/modifier)
+ regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]\/\w+/, separator]),
+
+ regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]/, separator]),
+ regex.pattern([/[^\s`\[\\]+/, separator]),
+ ]),
+ ]
+
+ for (const variantPattern of variantPatterns) {
+ yield regex.pattern([
+ // Variants
+ '((?=((',
+ variantPattern,
+ ')+))\\2)?',
+
+ // Important (optional)
+ /!?/,
+
+ prefix,
+
+ utility,
+ ])
+ }
+
+ // 5. Inner matches
+ yield /[^<>"'`\s.(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g
+}
+
+// We want to capture any "special" characters
+// AND the characters immediately following them (if there is one)
+let SPECIALS = /([\[\]'"`])([^\[\]'"`])?/g
+let ALLOWED_CLASS_CHARACTERS = /[^"'`\s<>\]]+/
+
+/**
+ * Clips a string ensuring that parentheses, quotes, etc… are balanced
+ * Used for arbitrary values only
+ *
+ * We will go past the end of the balanced parens until we find a non-class character
+ *
+ * Depth matching behavior:
+ * w-[calc(100%-theme('spacing[some_key][1.5]'))]']
+ * ┬ ┬ ┬┬ ┬ ┬┬ ┬┬┬┬┬┬┬
+ * 1 2 3 4 34 3 210 END
+ * ╰────┴──────────┴────────┴────────┴┴───┴─┴┴┴
+ *
+ * @param {string} input
+ */
+function clipAtBalancedParens(input) {
+ // We are care about this for arbitrary values
+ if (!input.includes('-[')) {
+ return input
+ }
+
+ let depth = 0
+ let openStringTypes = []
+
+ // Find all parens, brackets, quotes, etc
+ // Stop when we end at a balanced pair
+ // This is naive and will treat mismatched parens as balanced
+ // This shouldn't be a problem in practice though
+ let matches = input.matchAll(SPECIALS)
+
+ // We can't use lookbehind assertions because we have to support Safari
+ // So, instead, we've emulated it using capture groups and we'll re-work the matches to accommodate
+ matches = Array.from(matches).flatMap((match) => {
+ const [, ...groups] = match
+
+ return groups.map((group, idx) =>
+ Object.assign([], match, {
+ index: match.index + idx,
+ 0: group,
+ })
+ )
+ })
+
+ for (let match of matches) {
+ let char = match[0]
+ let inStringType = openStringTypes[openStringTypes.length - 1]
+
+ if (char === inStringType) {
+ openStringTypes.pop()
+ } else if (char === "'" || char === '"' || char === '`') {
+ openStringTypes.push(char)
+ }
+
+ if (inStringType) {
+ continue
+ } else if (char === '[') {
+ depth++
+ continue
+ } else if (char === ']') {
+ depth--
+ continue
+ }
+
+ // We've gone one character past the point where we should stop
+ // This means that there was an extra closing `]`
+ // We'll clip to just before it
+ if (depth < 0) {
+ return input.substring(0, match.index - 1)
+ }
+
+ // We've finished balancing the brackets but there still may be characters that can be included
+ // For example in the class `text-[#336699]/[.35]`
+ // The depth goes to `0` at the closing `]` but goes up again at the `[`
+
+ // If we're at zero and encounter a non-class character then we clip the class there
+ if (depth === 0 && !ALLOWED_CLASS_CHARACTERS.test(char)) {
+ return input.substring(0, match.index)
+ }
+ }
+
+ return input
+}
+
+// Regular utilities
+// {{modifier}:}*{namespace}{-{suffix}}*{/{opacityModifier}}?
+
+// Arbitrary values
+// {{modifier}:}*{namespace}-[{arbitraryValue}]{/{opacityModifier}}?
+// arbitraryValue: no whitespace, balanced quotes unless within quotes, balanced brackets unless within quotes
+
+// Arbitrary properties
+// {{modifier}:}*[{validCssPropertyName}:{arbitraryValue}]
diff --git a/node_modules/tailwindcss/src/lib/detectNesting.js b/node_modules/tailwindcss/src/lib/detectNesting.js
new file mode 100644
index 0000000..03252e2
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/detectNesting.js
@@ -0,0 +1,47 @@
+function isRoot(node) {
+ return node.type === 'root'
+}
+
+function isAtLayer(node) {
+ return node.type === 'atrule' && node.name === 'layer'
+}
+
+export default function (_context) {
+ return (root, result) => {
+ let found = false
+
+ root.walkAtRules('tailwind', (node) => {
+ if (found) return false
+
+ if (node.parent && !(isRoot(node.parent) || isAtLayer(node.parent))) {
+ found = true
+ node.warn(
+ result,
+ [
+ 'Nested @tailwind rules were detected, but are not supported.',
+ "Consider using a prefix to scope Tailwind's classes: https://tailwindcss.com/docs/configuration#prefix",
+ 'Alternatively, use the important selector strategy: https://tailwindcss.com/docs/configuration#selector-strategy',
+ ].join('\n')
+ )
+ return false
+ }
+ })
+
+ root.walkRules((rule) => {
+ if (found) return false
+
+ rule.walkRules((nestedRule) => {
+ found = true
+ nestedRule.warn(
+ result,
+ [
+ 'Nested CSS was detected, but CSS nesting has not been configured correctly.',
+ 'Please enable a CSS nesting plugin *before* Tailwind in your configuration.',
+ 'See how here: https://tailwindcss.com/docs/using-with-preprocessors#nesting',
+ ].join('\n')
+ )
+ return false
+ })
+ })
+ }
+}
diff --git a/node_modules/tailwindcss/src/lib/evaluateTailwindFunctions.js b/node_modules/tailwindcss/src/lib/evaluateTailwindFunctions.js
new file mode 100644
index 0000000..ff73f46
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/evaluateTailwindFunctions.js
@@ -0,0 +1,272 @@
+import dlv from 'dlv'
+import didYouMean from 'didyoumean'
+import transformThemeValue from '../util/transformThemeValue'
+import parseValue from '../value-parser/index'
+import { normalizeScreens } from '../util/normalizeScreens'
+import buildMediaQuery from '../util/buildMediaQuery'
+import { toPath } from '../util/toPath'
+import { withAlphaValue } from '../util/withAlphaVariable'
+import { parseColorFormat } from '../util/pluginUtils'
+import log from '../util/log'
+
+function isObject(input) {
+ return typeof input === 'object' && input !== null
+}
+
+function findClosestExistingPath(theme, path) {
+ let parts = toPath(path)
+ do {
+ parts.pop()
+
+ if (dlv(theme, parts) !== undefined) break
+ } while (parts.length)
+
+ return parts.length ? parts : undefined
+}
+
+function pathToString(path) {
+ if (typeof path === 'string') return path
+ return path.reduce((acc, cur, i) => {
+ if (cur.includes('.')) return `${acc}[${cur}]`
+ return i === 0 ? cur : `${acc}.${cur}`
+ }, '')
+}
+
+function list(items) {
+ return items.map((key) => `'${key}'`).join(', ')
+}
+
+function listKeys(obj) {
+ return list(Object.keys(obj))
+}
+
+function validatePath(config, path, defaultValue, themeOpts = {}) {
+ const pathString = Array.isArray(path) ? pathToString(path) : path.replace(/^['"]+|['"]+$/g, '')
+ const pathSegments = Array.isArray(path) ? path : toPath(pathString)
+ const value = dlv(config.theme, pathSegments, defaultValue)
+
+ if (value === undefined) {
+ let error = `'${pathString}' does not exist in your theme config.`
+ const parentSegments = pathSegments.slice(0, -1)
+ const parentValue = dlv(config.theme, parentSegments)
+
+ if (isObject(parentValue)) {
+ const validKeys = Object.keys(parentValue).filter(
+ (key) => validatePath(config, [...parentSegments, key]).isValid
+ )
+ const suggestion = didYouMean(pathSegments[pathSegments.length - 1], validKeys)
+ if (suggestion) {
+ error += ` Did you mean '${pathToString([...parentSegments, suggestion])}'?`
+ } else if (validKeys.length > 0) {
+ error += ` '${pathToString(parentSegments)}' has the following valid keys: ${list(
+ validKeys
+ )}`
+ }
+ } else {
+ const closestPath = findClosestExistingPath(config.theme, pathString)
+ if (closestPath) {
+ const closestValue = dlv(config.theme, closestPath)
+ if (isObject(closestValue)) {
+ error += ` '${pathToString(closestPath)}' has the following keys: ${listKeys(
+ closestValue
+ )}`
+ } else {
+ error += ` '${pathToString(closestPath)}' is not an object.`
+ }
+ } else {
+ error += ` Your theme has the following top-level keys: ${listKeys(config.theme)}`
+ }
+ }
+
+ return {
+ isValid: false,
+ error,
+ }
+ }
+
+ if (
+ !(
+ typeof value === 'string' ||
+ typeof value === 'number' ||
+ typeof value === 'function' ||
+ value instanceof String ||
+ value instanceof Number ||
+ Array.isArray(value)
+ )
+ ) {
+ let error = `'${pathString}' was found but does not resolve to a string.`
+
+ if (isObject(value)) {
+ let validKeys = Object.keys(value).filter(
+ (key) => validatePath(config, [...pathSegments, key]).isValid
+ )
+ if (validKeys.length) {
+ error += ` Did you mean something like '${pathToString([...pathSegments, validKeys[0]])}'?`
+ }
+ }
+
+ return {
+ isValid: false,
+ error,
+ }
+ }
+
+ const [themeSection] = pathSegments
+
+ return {
+ isValid: true,
+ value: transformThemeValue(themeSection)(value, themeOpts),
+ }
+}
+
+function extractArgs(node, vNodes, functions) {
+ vNodes = vNodes.map((vNode) => resolveVNode(node, vNode, functions))
+
+ let args = ['']
+
+ for (let vNode of vNodes) {
+ if (vNode.type === 'div' && vNode.value === ',') {
+ args.push('')
+ } else {
+ args[args.length - 1] += parseValue.stringify(vNode)
+ }
+ }
+
+ return args
+}
+
+function resolveVNode(node, vNode, functions) {
+ if (vNode.type === 'function' && functions[vNode.value] !== undefined) {
+ let args = extractArgs(node, vNode.nodes, functions)
+ vNode.type = 'word'
+ vNode.value = functions[vNode.value](node, ...args)
+ }
+
+ return vNode
+}
+
+function resolveFunctions(node, input, functions) {
+ let hasAnyFn = Object.keys(functions).some((fn) => input.includes(`${fn}(`))
+ if (!hasAnyFn) return input
+
+ return parseValue(input)
+ .walk((vNode) => {
+ resolveVNode(node, vNode, functions)
+ })
+ .toString()
+}
+
+let nodeTypePropertyMap = {
+ atrule: 'params',
+ decl: 'value',
+}
+
+/**
+ * @param {string} path
+ * @returns {Iterable<[path: string, alpha: string|undefined]>}
+ */
+function* toPaths(path) {
+ // Strip quotes from beginning and end of string
+ // This allows the alpha value to be present inside of quotes
+ path = path.replace(/^['"]+|['"]+$/g, '')
+
+ let matches = path.match(/^([^\s]+)(?![^\[]*\])(?:\s*\/\s*([^\/\s]+))$/)
+ let alpha = undefined
+
+ yield [path, undefined]
+
+ if (matches) {
+ path = matches[1]
+ alpha = matches[2]
+
+ yield [path, alpha]
+ }
+}
+
+/**
+ *
+ * @param {any} config
+ * @param {string} path
+ * @param {any} defaultValue
+ */
+function resolvePath(config, path, defaultValue) {
+ const results = Array.from(toPaths(path)).map(([path, alpha]) => {
+ return Object.assign(validatePath(config, path, defaultValue, { opacityValue: alpha }), {
+ resolvedPath: path,
+ alpha,
+ })
+ })
+
+ return results.find((result) => result.isValid) ?? results[0]
+}
+
+export default function (context) {
+ let config = context.tailwindConfig
+
+ let functions = {
+ theme: (node, path, ...defaultValue) => {
+ let { isValid, value, error, alpha } = resolvePath(
+ config,
+ path,
+ defaultValue.length ? defaultValue : undefined
+ )
+
+ if (!isValid) {
+ let parentNode = node.parent
+ let candidate = parentNode?.raws.tailwind?.candidate
+
+ if (parentNode && candidate !== undefined) {
+ // Remove this utility from any caches
+ context.markInvalidUtilityNode(parentNode)
+
+ // Remove the CSS node from the markup
+ parentNode.remove()
+
+ // Show a warning
+ log.warn('invalid-theme-key-in-class', [
+ `The utility \`${candidate}\` contains an invalid theme value and was not generated.`,
+ ])
+
+ return
+ }
+
+ throw node.error(error)
+ }
+
+ let maybeColor = parseColorFormat(value)
+ let isColorFunction = maybeColor !== undefined && typeof maybeColor === 'function'
+
+ if (alpha !== undefined || isColorFunction) {
+ if (alpha === undefined) {
+ alpha = 1.0
+ }
+
+ value = withAlphaValue(maybeColor, alpha, maybeColor)
+ }
+
+ return value
+ },
+ screen: (node, screen) => {
+ screen = screen.replace(/^['"]+/g, '').replace(/['"]+$/g, '')
+ let screens = normalizeScreens(config.theme.screens)
+ let screenDefinition = screens.find(({ name }) => name === screen)
+
+ if (!screenDefinition) {
+ throw node.error(`The '${screen}' screen does not exist in your theme.`)
+ }
+
+ return buildMediaQuery(screenDefinition)
+ },
+ }
+ return (root) => {
+ root.walk((node) => {
+ let property = nodeTypePropertyMap[node.type]
+
+ if (property === undefined) {
+ return
+ }
+
+ node[property] = resolveFunctions(node, node[property], functions)
+ })
+ }
+}
diff --git a/node_modules/tailwindcss/src/lib/expandApplyAtRules.js b/node_modules/tailwindcss/src/lib/expandApplyAtRules.js
new file mode 100644
index 0000000..ed48dbc
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/expandApplyAtRules.js
@@ -0,0 +1,620 @@
+import postcss from 'postcss'
+import parser from 'postcss-selector-parser'
+
+import { resolveMatches } from './generateRules'
+import escapeClassName from '../util/escapeClassName'
+import { applyImportantSelector } from '../util/applyImportantSelector'
+import { movePseudos } from '../util/pseudoElements'
+
+/** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */
+
+function extractClasses(node) {
+ /** @type {Map<string, Set<string>>} */
+ let groups = new Map()
+
+ let container = postcss.root({ nodes: [node.clone()] })
+
+ container.walkRules((rule) => {
+ parser((selectors) => {
+ selectors.walkClasses((classSelector) => {
+ let parentSelector = classSelector.parent.toString()
+
+ let classes = groups.get(parentSelector)
+ if (!classes) {
+ groups.set(parentSelector, (classes = new Set()))
+ }
+
+ classes.add(classSelector.value)
+ })
+ }).processSync(rule.selector)
+ })
+
+ let normalizedGroups = Array.from(groups.values(), (classes) => Array.from(classes))
+ let classes = normalizedGroups.flat()
+
+ return Object.assign(classes, { groups: normalizedGroups })
+}
+
+let selectorExtractor = parser()
+
+/**
+ * @param {string} ruleSelectors
+ */
+function extractSelectors(ruleSelectors) {
+ return selectorExtractor.astSync(ruleSelectors)
+}
+
+function extractBaseCandidates(candidates, separator) {
+ let baseClasses = new Set()
+
+ for (let candidate of candidates) {
+ baseClasses.add(candidate.split(separator).pop())
+ }
+
+ return Array.from(baseClasses)
+}
+
+function prefix(context, selector) {
+ let prefix = context.tailwindConfig.prefix
+ return typeof prefix === 'function' ? prefix(selector) : prefix + selector
+}
+
+function* pathToRoot(node) {
+ yield node
+ while (node.parent) {
+ yield node.parent
+ node = node.parent
+ }
+}
+
+/**
+ * Only clone the node itself and not its children
+ *
+ * @param {*} node
+ * @param {*} overrides
+ * @returns
+ */
+function shallowClone(node, overrides = {}) {
+ let children = node.nodes
+ node.nodes = []
+
+ let tmp = node.clone(overrides)
+
+ node.nodes = children
+
+ return tmp
+}
+
+/**
+ * Clone just the nodes all the way to the top that are required to represent
+ * this singular rule in the tree.
+ *
+ * For example, if we have CSS like this:
+ * ```css
+ * @media (min-width: 768px) {
+ * @supports (display: grid) {
+ * .foo {
+ * display: grid;
+ * grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ * }
+ * }
+ *
+ * @supports (backdrop-filter: blur(1px)) {
+ * .bar {
+ * backdrop-filter: blur(1px);
+ * }
+ * }
+ *
+ * .baz {
+ * color: orange;
+ * }
+ * }
+ * ```
+ *
+ * And we're cloning `.bar` it'll return a cloned version of what's required for just that single node:
+ *
+ * ```css
+ * @media (min-width: 768px) {
+ * @supports (backdrop-filter: blur(1px)) {
+ * .bar {
+ * backdrop-filter: blur(1px);
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param {import('postcss').Node} node
+ */
+function nestedClone(node) {
+ for (let parent of pathToRoot(node)) {
+ if (node === parent) {
+ continue
+ }
+
+ if (parent.type === 'root') {
+ break
+ }
+
+ node = shallowClone(parent, {
+ nodes: [node],
+ })
+ }
+
+ return node
+}
+
+/**
+ * @param {import('postcss').Root} root
+ */
+function buildLocalApplyCache(root, context) {
+ /** @type {ApplyCache} */
+ let cache = new Map()
+
+ root.walkRules((rule) => {
+ // Ignore rules generated by Tailwind
+ for (let node of pathToRoot(rule)) {
+ if (node.raws.tailwind?.layer !== undefined) {
+ return
+ }
+ }
+
+ // Clone what's required to represent this singular rule in the tree
+ let container = nestedClone(rule)
+ let sort = context.offsets.create('user')
+
+ for (let className of extractClasses(rule)) {
+ let list = cache.get(className) || []
+ cache.set(className, list)
+
+ list.push([
+ {
+ layer: 'user',
+ sort,
+ important: false,
+ },
+ container,
+ ])
+ }
+ })
+
+ return cache
+}
+
+/**
+ * @returns {ApplyCache}
+ */
+function buildApplyCache(applyCandidates, context) {
+ for (let candidate of applyCandidates) {
+ if (context.notClassCache.has(candidate) || context.applyClassCache.has(candidate)) {
+ continue
+ }
+
+ if (context.classCache.has(candidate)) {
+ context.applyClassCache.set(
+ candidate,
+ context.classCache.get(candidate).map(([meta, rule]) => [meta, rule.clone()])
+ )
+ continue
+ }
+
+ let matches = Array.from(resolveMatches(candidate, context))
+
+ if (matches.length === 0) {
+ context.notClassCache.add(candidate)
+ continue
+ }
+
+ context.applyClassCache.set(candidate, matches)
+ }
+
+ return context.applyClassCache
+}
+
+/**
+ * Build a cache only when it's first used
+ *
+ * @param {() => ApplyCache} buildCacheFn
+ * @returns {ApplyCache}
+ */
+function lazyCache(buildCacheFn) {
+ let cache = null
+
+ return {
+ get: (name) => {
+ cache = cache || buildCacheFn()
+
+ return cache.get(name)
+ },
+ has: (name) => {
+ cache = cache || buildCacheFn()
+
+ return cache.has(name)
+ },
+ }
+}
+
+/**
+ * Take a series of multiple caches and merge
+ * them so they act like one large cache
+ *
+ * @param {ApplyCache[]} caches
+ * @returns {ApplyCache}
+ */
+function combineCaches(caches) {
+ return {
+ get: (name) => caches.flatMap((cache) => cache.get(name) || []),
+ has: (name) => caches.some((cache) => cache.has(name)),
+ }
+}
+
+function extractApplyCandidates(params) {
+ let candidates = params.split(/[\s\t\n]+/g)
+
+ if (candidates[candidates.length - 1] === '!important') {
+ return [candidates.slice(0, -1), true]
+ }
+
+ return [candidates, false]
+}
+
+function processApply(root, context, localCache) {
+ let applyCandidates = new Set()
+
+ // Collect all @apply rules and candidates
+ let applies = []
+ root.walkAtRules('apply', (rule) => {
+ let [candidates] = extractApplyCandidates(rule.params)
+
+ for (let util of candidates) {
+ applyCandidates.add(util)
+ }
+
+ applies.push(rule)
+ })
+
+ // Start the @apply process if we have rules with @apply in them
+ if (applies.length === 0) {
+ return
+ }
+
+ // Fill up some caches!
+ let applyClassCache = combineCaches([localCache, buildApplyCache(applyCandidates, context)])
+
+ /**
+ * When we have an apply like this:
+ *
+ * .abc {
+ * @apply hover:font-bold;
+ * }
+ *
+ * What we essentially will do is resolve to this:
+ *
+ * .abc {
+ * @apply .hover\:font-bold:hover {
+ * font-weight: 500;
+ * }
+ * }
+ *
+ * Notice that the to-be-applied class is `.hover\:font-bold:hover` and that the utility candidate was `hover:font-bold`.
+ * What happens in this function is that we prepend a `.` and escape the candidate.
+ * This will result in `.hover\:font-bold`
+ * Which means that we can replace `.hover\:font-bold` with `.abc` in `.hover\:font-bold:hover` resulting in `.abc:hover`
+ *
+ * @param {string} selector
+ * @param {string} utilitySelectors
+ * @param {string} candidate
+ */
+ function replaceSelector(selector, utilitySelectors, candidate) {
+ let selectorList = extractSelectors(selector)
+ let utilitySelectorsList = extractSelectors(utilitySelectors)
+ let candidateList = extractSelectors(`.${escapeClassName(candidate)}`)
+ let candidateClass = candidateList.nodes[0].nodes[0]
+
+ selectorList.each((sel) => {
+ /** @type {Set<import('postcss-selector-parser').Selector>} */
+ let replaced = new Set()
+
+ utilitySelectorsList.each((utilitySelector) => {
+ let hasReplaced = false
+ utilitySelector = utilitySelector.clone()
+
+ utilitySelector.walkClasses((node) => {
+ if (node.value !== candidateClass.value) {
+ return
+ }
+
+ // Don't replace multiple instances of the same class
+ // This is theoretically correct but only partially
+ // We'd need to generate every possible permutation of the replacement
+ // For example with `.foo + .foo { … }` and `section { @apply foo; }`
+ // We'd need to generate all of these:
+ // - `.foo + .foo`
+ // - `.foo + section`
+ // - `section + .foo`
+ // - `section + section`
+ if (hasReplaced) {
+ return
+ }
+
+ // Since you can only `@apply` class names this is sufficient
+ // We want to replace the matched class name with the selector the user is using
+ // Ex: Replace `.text-blue-500` with `.foo.bar:is(.something-cool)`
+ node.replaceWith(...sel.nodes.map((node) => node.clone()))
+
+ // Record that we did something and we want to use this new selector
+ replaced.add(utilitySelector)
+
+ hasReplaced = true
+ })
+ })
+
+ // Sort tag names before class names (but only sort each group (separated by a combinator)
+ // separately and not in total)
+ // This happens when replacing `.bar` in `.foo.bar` with a tag like `section`
+ for (let sel of replaced) {
+ let groups = [[]]
+ for (let node of sel.nodes) {
+ if (node.type === 'combinator') {
+ groups.push(node)
+ groups.push([])
+ } else {
+ let last = groups[groups.length - 1]
+ last.push(node)
+ }
+ }
+
+ sel.nodes = []
+
+ for (let group of groups) {
+ if (Array.isArray(group)) {
+ group.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 0
+ })
+ }
+
+ sel.nodes = sel.nodes.concat(group)
+ }
+ }
+
+ sel.replaceWith(...replaced)
+ })
+
+ return selectorList.toString()
+ }
+
+ let perParentApplies = new Map()
+
+ // Collect all apply candidates and their rules
+ for (let apply of applies) {
+ let [candidates] = perParentApplies.get(apply.parent) || [[], apply.source]
+
+ perParentApplies.set(apply.parent, [candidates, apply.source])
+
+ let [applyCandidates, important] = extractApplyCandidates(apply.params)
+
+ if (apply.parent.type === 'atrule') {
+ if (apply.parent.name === 'screen') {
+ let screenType = apply.parent.params
+
+ throw apply.error(
+ `@apply is not supported within nested at-rules like @screen. We suggest you write this as @apply ${applyCandidates
+ .map((c) => `${screenType}:${c}`)
+ .join(' ')} instead.`
+ )
+ }
+
+ throw apply.error(
+ `@apply is not supported within nested at-rules like @${apply.parent.name}. You can fix this by un-nesting @${apply.parent.name}.`
+ )
+ }
+
+ for (let applyCandidate of applyCandidates) {
+ if ([prefix(context, 'group'), prefix(context, 'peer')].includes(applyCandidate)) {
+ // TODO: Link to specific documentation page with error code.
+ throw apply.error(`@apply should not be used with the '${applyCandidate}' utility`)
+ }
+
+ if (!applyClassCache.has(applyCandidate)) {
+ throw apply.error(
+ `The \`${applyCandidate}\` class does not exist. If \`${applyCandidate}\` is a custom class, make sure it is defined within a \`@layer\` directive.`
+ )
+ }
+
+ let rules = applyClassCache.get(applyCandidate)
+
+ candidates.push([applyCandidate, important, rules])
+ }
+ }
+
+ for (let [parent, [candidates, atApplySource]] of perParentApplies) {
+ let siblings = []
+
+ for (let [applyCandidate, important, rules] of candidates) {
+ let potentialApplyCandidates = [
+ applyCandidate,
+ ...extractBaseCandidates([applyCandidate], context.tailwindConfig.separator),
+ ]
+
+ for (let [meta, node] of rules) {
+ let parentClasses = extractClasses(parent)
+ let nodeClasses = extractClasses(node)
+
+ // When we encounter a rule like `.dark .a, .b { … }` we only want to be left with `[.dark, .a]` if the base applyCandidate is `.a` or with `[.b]` if the base applyCandidate is `.b`
+ // So we've split them into groups
+ nodeClasses = nodeClasses.groups
+ .filter((classList) =>
+ classList.some((className) => potentialApplyCandidates.includes(className))
+ )
+ .flat()
+
+ // Add base utility classes from the @apply node to the list of
+ // classes to check whether it intersects and therefore results in a
+ // circular dependency or not.
+ //
+ // E.g.:
+ // .foo {
+ // @apply hover:a; // This applies "a" but with a modifier
+ // }
+ //
+ // We only have to do that with base classes of the `node`, not of the `parent`
+ // E.g.:
+ // .hover\:foo {
+ // @apply bar;
+ // }
+ // .bar {
+ // @apply foo;
+ // }
+ //
+ // This should not result in a circular dependency because we are
+ // just applying `.foo` and the rule above is `.hover\:foo` which is
+ // unrelated. However, if we were to apply `hover:foo` then we _did_
+ // have to include this one.
+ nodeClasses = nodeClasses.concat(
+ extractBaseCandidates(nodeClasses, context.tailwindConfig.separator)
+ )
+
+ let intersects = parentClasses.some((selector) => nodeClasses.includes(selector))
+ if (intersects) {
+ throw node.error(
+ `You cannot \`@apply\` the \`${applyCandidate}\` utility here because it creates a circular dependency.`
+ )
+ }
+
+ let root = postcss.root({ nodes: [node.clone()] })
+
+ // Make sure every node in the entire tree points back at the @apply rule that generated it
+ root.walk((node) => {
+ node.source = atApplySource
+ })
+
+ let canRewriteSelector =
+ node.type !== 'atrule' || (node.type === 'atrule' && node.name !== 'keyframes')
+
+ if (canRewriteSelector) {
+ root.walkRules((rule) => {
+ // Let's imagine you have the following structure:
+ //
+ // .foo {
+ // @apply bar;
+ // }
+ //
+ // @supports (a: b) {
+ // .bar {
+ // color: blue
+ // }
+ //
+ // .something-unrelated {}
+ // }
+ //
+ // In this case we want to apply `.bar` but it happens to be in
+ // an atrule node. We clone that node instead of the nested one
+ // because we still want that @supports rule to be there once we
+ // applied everything.
+ //
+ // However it happens to be that the `.something-unrelated` is
+ // also in that same shared @supports atrule. This is not good,
+ // and this should not be there. The good part is that this is
+ // a clone already and it can be safely removed. The question is
+ // how do we know we can remove it. Basically what we can do is
+ // match it against the applyCandidate that you want to apply. If
+ // it doesn't match the we can safely delete it.
+ //
+ // If we didn't do this, then the `replaceSelector` function
+ // would have replaced this with something that didn't exist and
+ // therefore it removed the selector altogether. In this specific
+ // case it would result in `{}` instead of `.something-unrelated {}`
+ if (!extractClasses(rule).some((candidate) => candidate === applyCandidate)) {
+ rule.remove()
+ return
+ }
+
+ // Strip the important selector from the parent selector if at the beginning
+ let importantSelector =
+ typeof context.tailwindConfig.important === 'string'
+ ? context.tailwindConfig.important
+ : null
+
+ // We only want to move the "important" selector if this is a Tailwind-generated utility
+ // We do *not* want to do this for user CSS that happens to be structured the same
+ let isGenerated = parent.raws.tailwind !== undefined
+
+ let parentSelector =
+ isGenerated && importantSelector && parent.selector.indexOf(importantSelector) === 0
+ ? parent.selector.slice(importantSelector.length)
+ : parent.selector
+
+ // If the selector becomes empty after replacing the important selector
+ // This means that it's the same as the parent selector and we don't want to replace it
+ // Otherwise we'll crash
+ if (parentSelector === '') {
+ parentSelector = parent.selector
+ }
+
+ rule.selector = replaceSelector(parentSelector, rule.selector, applyCandidate)
+
+ // And then re-add it if it was removed
+ if (importantSelector && parentSelector !== parent.selector) {
+ rule.selector = applyImportantSelector(rule.selector, importantSelector)
+ }
+
+ rule.walkDecls((d) => {
+ d.important = meta.important || important
+ })
+
+ // Move pseudo elements to the end of the selector (if necessary)
+ let selector = parser().astSync(rule.selector)
+ selector.each((sel) => movePseudos(sel))
+ rule.selector = selector.toString()
+ })
+ }
+
+ // It could be that the node we were inserted was removed because the class didn't match
+ // If that was the *only* rule in the parent, then we have nothing add so we skip it
+ if (!root.nodes[0]) {
+ continue
+ }
+
+ // Insert it
+ siblings.push([meta.sort, root.nodes[0]])
+ }
+ }
+
+ // Inject the rules, sorted, correctly
+ let nodes = context.offsets.sort(siblings).map((s) => s[1])
+
+ // `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
+ parent.after(nodes)
+ }
+
+ for (let apply of applies) {
+ // If there are left-over declarations, just remove the @apply
+ if (apply.parent.nodes.length > 1) {
+ apply.remove()
+ } else {
+ // The node is empty, drop the full node
+ apply.parent.remove()
+ }
+ }
+
+ // Do it again, in case we have other `@apply` rules
+ processApply(root, context, localCache)
+}
+
+export default function expandApplyAtRules(context) {
+ return (root) => {
+ // Build a cache of the user's CSS so we can use it to resolve classes used by @apply
+ let localCache = lazyCache(() => buildLocalApplyCache(root, context))
+
+ processApply(root, context, localCache)
+ }
+}
diff --git a/node_modules/tailwindcss/src/lib/expandTailwindAtRules.js b/node_modules/tailwindcss/src/lib/expandTailwindAtRules.js
new file mode 100644
index 0000000..2933d6f
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/expandTailwindAtRules.js
@@ -0,0 +1,297 @@
+import fs from 'fs'
+import LRU from '@alloc/quick-lru'
+import * as sharedState from './sharedState'
+import { generateRules } from './generateRules'
+import log from '../util/log'
+import cloneNodes from '../util/cloneNodes'
+import { defaultExtractor } from './defaultExtractor'
+
+let env = sharedState.env
+
+const builtInExtractors = {
+ DEFAULT: defaultExtractor,
+}
+
+const builtInTransformers = {
+ DEFAULT: (content) => content,
+ svelte: (content) => content.replace(/(?:^|\s)class:/g, ' '),
+}
+
+function getExtractor(context, fileExtension) {
+ let extractors = context.tailwindConfig.content.extract
+
+ return (
+ extractors[fileExtension] ||
+ extractors.DEFAULT ||
+ builtInExtractors[fileExtension] ||
+ builtInExtractors.DEFAULT(context)
+ )
+}
+
+function getTransformer(tailwindConfig, fileExtension) {
+ let transformers = tailwindConfig.content.transform
+
+ return (
+ transformers[fileExtension] ||
+ transformers.DEFAULT ||
+ builtInTransformers[fileExtension] ||
+ builtInTransformers.DEFAULT
+ )
+}
+
+let extractorCache = new WeakMap()
+
+// Scans template contents for possible classes. This is a hot path on initial build but
+// not too important for subsequent builds. The faster the better though — if we can speed
+// up these regexes by 50% that could cut initial build time by like 20%.
+function getClassCandidates(content, extractor, candidates, seen) {
+ if (!extractorCache.has(extractor)) {
+ extractorCache.set(extractor, new LRU({ maxSize: 25000 }))
+ }
+
+ for (let line of content.split('\n')) {
+ line = line.trim()
+
+ if (seen.has(line)) {
+ continue
+ }
+ seen.add(line)
+
+ if (extractorCache.get(extractor).has(line)) {
+ for (let match of extractorCache.get(extractor).get(line)) {
+ candidates.add(match)
+ }
+ } else {
+ let extractorMatches = extractor(line).filter((s) => s !== '!*')
+ let lineMatchesSet = new Set(extractorMatches)
+
+ for (let match of lineMatchesSet) {
+ candidates.add(match)
+ }
+
+ extractorCache.get(extractor).set(line, lineMatchesSet)
+ }
+ }
+}
+
+/**
+ *
+ * @param {[import('./offsets.js').RuleOffset, import('postcss').Node][]} rules
+ * @param {*} context
+ */
+function buildStylesheet(rules, context) {
+ let sortedRules = context.offsets.sort(rules)
+
+ let returnValue = {
+ base: new Set(),
+ defaults: new Set(),
+ components: new Set(),
+ utilities: new Set(),
+ variants: new Set(),
+ }
+
+ for (let [sort, rule] of sortedRules) {
+ returnValue[sort.layer].add(rule)
+ }
+
+ return returnValue
+}
+
+export default function expandTailwindAtRules(context) {
+ return async (root) => {
+ let layerNodes = {
+ base: null,
+ components: null,
+ utilities: null,
+ variants: null,
+ }
+
+ root.walkAtRules((rule) => {
+ // Make sure this file contains Tailwind directives. If not, we can save
+ // a lot of work and bail early. Also we don't have to register our touch
+ // file as a dependency since the output of this CSS does not depend on
+ // the source of any templates. Think Vue <style> blocks for example.
+ if (rule.name === 'tailwind') {
+ if (Object.keys(layerNodes).includes(rule.params)) {
+ layerNodes[rule.params] = rule
+ }
+ }
+ })
+
+ if (Object.values(layerNodes).every((n) => n === null)) {
+ return root
+ }
+
+ // ---
+
+ // Find potential rules in changed files
+ let candidates = new Set([...(context.candidates ?? []), sharedState.NOT_ON_DEMAND])
+ let seen = new Set()
+
+ env.DEBUG && console.time('Reading changed files')
+
+ if (__OXIDE__) {
+ // TODO: Pass through or implement `extractor`
+ for (let candidate of require('@tailwindcss/oxide').parseCandidateStringsFromFiles(
+ context.changedContent
+ // Object.assign({}, builtInTransformers, context.tailwindConfig.content.transform)
+ )) {
+ candidates.add(candidate)
+ }
+
+ // for (let { file, content, extension } of context.changedContent) {
+ // let transformer = getTransformer(context.tailwindConfig, extension)
+ // let extractor = getExtractor(context, extension)
+ // getClassCandidatesOxide(file, transformer(content), extractor, candidates, seen)
+ // }
+ } else {
+ /** @type {[item: {file?: string, content?: string}, meta: {transformer: any, extractor: any}][]} */
+ let regexParserContent = []
+
+ for (let item of context.changedContent) {
+ let transformer = getTransformer(context.tailwindConfig, item.extension)
+ let extractor = getExtractor(context, item.extension)
+ regexParserContent.push([item, { transformer, extractor }])
+ }
+
+ const BATCH_SIZE = 500
+
+ for (let i = 0; i < regexParserContent.length; i += BATCH_SIZE) {
+ let batch = regexParserContent.slice(i, i + BATCH_SIZE)
+ await Promise.all(
+ batch.map(async ([{ file, content }, { transformer, extractor }]) => {
+ content = file ? await fs.promises.readFile(file, 'utf8') : content
+ getClassCandidates(transformer(content), extractor, candidates, seen)
+ })
+ )
+ }
+ }
+
+ env.DEBUG && console.timeEnd('Reading changed files')
+
+ // ---
+
+ // Generate the actual CSS
+ let classCacheCount = context.classCache.size
+
+ env.DEBUG && console.time('Generate rules')
+ env.DEBUG && console.time('Sorting candidates')
+ let sortedCandidates = __OXIDE__
+ ? candidates
+ : new Set(
+ [...candidates].sort((a, z) => {
+ if (a === z) return 0
+ if (a < z) return -1
+ return 1
+ })
+ )
+ env.DEBUG && console.timeEnd('Sorting candidates')
+ generateRules(sortedCandidates, context)
+ env.DEBUG && console.timeEnd('Generate rules')
+
+ // We only ever add to the classCache, so if it didn't grow, there is nothing new.
+ env.DEBUG && console.time('Build stylesheet')
+ if (context.stylesheetCache === null || context.classCache.size !== classCacheCount) {
+ context.stylesheetCache = buildStylesheet([...context.ruleCache], context)
+ }
+ env.DEBUG && console.timeEnd('Build stylesheet')
+
+ let {
+ defaults: defaultNodes,
+ base: baseNodes,
+ components: componentNodes,
+ utilities: utilityNodes,
+ variants: screenNodes,
+ } = context.stylesheetCache
+
+ // ---
+
+ // Replace any Tailwind directives with generated CSS
+
+ if (layerNodes.base) {
+ layerNodes.base.before(
+ cloneNodes([...baseNodes, ...defaultNodes], layerNodes.base.source, {
+ layer: 'base',
+ })
+ )
+ layerNodes.base.remove()
+ }
+
+ if (layerNodes.components) {
+ layerNodes.components.before(
+ cloneNodes([...componentNodes], layerNodes.components.source, {
+ layer: 'components',
+ })
+ )
+ layerNodes.components.remove()
+ }
+
+ if (layerNodes.utilities) {
+ layerNodes.utilities.before(
+ cloneNodes([...utilityNodes], layerNodes.utilities.source, {
+ layer: 'utilities',
+ })
+ )
+ layerNodes.utilities.remove()
+ }
+
+ // We do post-filtering to not alter the emitted order of the variants
+ const variantNodes = Array.from(screenNodes).filter((node) => {
+ const parentLayer = node.raws.tailwind?.parentLayer
+
+ if (parentLayer === 'components') {
+ return layerNodes.components !== null
+ }
+
+ if (parentLayer === 'utilities') {
+ return layerNodes.utilities !== null
+ }
+
+ return true
+ })
+
+ if (layerNodes.variants) {
+ layerNodes.variants.before(
+ cloneNodes(variantNodes, layerNodes.variants.source, {
+ layer: 'variants',
+ })
+ )
+ layerNodes.variants.remove()
+ } else if (variantNodes.length > 0) {
+ root.append(
+ cloneNodes(variantNodes, root.source, {
+ layer: 'variants',
+ })
+ )
+ }
+
+ // If we've got a utility layer and no utilities are generated there's likely something wrong
+ const hasUtilityVariants = variantNodes.some(
+ (node) => node.raws.tailwind?.parentLayer === 'utilities'
+ )
+
+ if (layerNodes.utilities && utilityNodes.size === 0 && !hasUtilityVariants) {
+ log.warn('content-problems', [
+ 'No utility classes were detected in your source files. If this is unexpected, double-check the `content` option in your Tailwind CSS configuration.',
+ 'https://tailwindcss.com/docs/content-configuration',
+ ])
+ }
+
+ // ---
+
+ if (env.DEBUG) {
+ console.log('Potential classes: ', candidates.size)
+ console.log('Active contexts: ', sharedState.contextSourcesMap.size)
+ }
+
+ // Clear the cache for the changed files
+ context.changedContent = []
+
+ // Cleanup any leftover @layer atrules
+ root.walkAtRules('layer', (rule) => {
+ if (Object.keys(layerNodes).includes(rule.params)) {
+ rule.remove()
+ }
+ })
+ }
+}
diff --git a/node_modules/tailwindcss/src/lib/findAtConfigPath.js b/node_modules/tailwindcss/src/lib/findAtConfigPath.js
new file mode 100644
index 0000000..ac0adab
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/findAtConfigPath.js
@@ -0,0 +1,48 @@
+import fs from 'fs'
+import path from 'path'
+
+/**
+ * Find the @config at-rule in the given CSS AST and return the relative path to the config file
+ *
+ * @param {import('postcss').Root} root
+ * @param {import('postcss').Result} result
+ */
+export function findAtConfigPath(root, result) {
+ let configPath = null
+ let relativeTo = null
+
+ root.walkAtRules('config', (rule) => {
+ relativeTo = rule.source?.input.file ?? result.opts.from ?? null
+
+ if (relativeTo === null) {
+ throw rule.error(
+ 'The `@config` directive cannot be used without setting `from` in your PostCSS config.'
+ )
+ }
+
+ if (configPath) {
+ throw rule.error('Only one `@config` directive is allowed per file.')
+ }
+
+ let matches = rule.params.match(/(['"])(.*?)\1/)
+ if (!matches) {
+ throw rule.error('A path is required when using the `@config` directive.')
+ }
+
+ let inputPath = matches[2]
+ if (path.isAbsolute(inputPath)) {
+ throw rule.error('The `@config` directive cannot be used with an absolute path.')
+ }
+
+ configPath = path.resolve(path.dirname(relativeTo), inputPath)
+ if (!fs.existsSync(configPath)) {
+ throw rule.error(
+ `The config file at "${inputPath}" does not exist. Make sure the path is correct and the file exists.`
+ )
+ }
+
+ rule.remove()
+ })
+
+ return configPath ? configPath : null
+}
diff --git a/node_modules/tailwindcss/src/lib/generateRules.js b/node_modules/tailwindcss/src/lib/generateRules.js
new file mode 100644
index 0000000..69b6827
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/generateRules.js
@@ -0,0 +1,936 @@
+import postcss from 'postcss'
+import selectorParser from 'postcss-selector-parser'
+import parseObjectStyles from '../util/parseObjectStyles'
+import isPlainObject from '../util/isPlainObject'
+import prefixSelector from '../util/prefixSelector'
+import { updateAllClasses, getMatchingTypes } from '../util/pluginUtils'
+import log from '../util/log'
+import * as sharedState from './sharedState'
+import {
+ formatVariantSelector,
+ finalizeSelector,
+ eliminateIrrelevantSelectors,
+} from '../util/formatVariantSelector'
+import { asClass } from '../util/nameClass'
+import { normalize } from '../util/dataTypes'
+import { isValidVariantFormatString, parseVariant, INTERNAL_FEATURES } from './setupContextUtils'
+import isValidArbitraryValue from '../util/isSyntacticallyValidPropertyValue'
+import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js'
+import { flagEnabled } from '../featureFlags'
+import { applyImportantSelector } from '../util/applyImportantSelector'
+
+let classNameParser = selectorParser((selectors) => {
+ return selectors.first.filter(({ type }) => type === 'class').pop().value
+})
+
+export function getClassNameFromSelector(selector) {
+ return classNameParser.transformSync(selector)
+}
+
+// Generate match permutations for a class candidate, like:
+// ['ring-offset-blue', '100']
+// ['ring-offset', 'blue-100']
+// ['ring', 'offset-blue-100']
+// Example with dynamic classes:
+// ['grid-cols', '[[linename],1fr,auto]']
+// ['grid', 'cols-[[linename],1fr,auto]']
+function* candidatePermutations(candidate) {
+ let lastIndex = Infinity
+
+ while (lastIndex >= 0) {
+ let dashIdx
+ let wasSlash = false
+
+ if (lastIndex === Infinity && candidate.endsWith(']')) {
+ let bracketIdx = candidate.indexOf('[')
+
+ // If character before `[` isn't a dash or a slash, this isn't a dynamic class
+ // eg. string[]
+ if (candidate[bracketIdx - 1] === '-') {
+ dashIdx = bracketIdx - 1
+ } else if (candidate[bracketIdx - 1] === '/') {
+ dashIdx = bracketIdx - 1
+ wasSlash = true
+ } else {
+ dashIdx = -1
+ }
+ } else if (lastIndex === Infinity && candidate.includes('/')) {
+ dashIdx = candidate.lastIndexOf('/')
+ wasSlash = true
+ } else {
+ dashIdx = candidate.lastIndexOf('-', lastIndex)
+ }
+
+ if (dashIdx < 0) {
+ break
+ }
+
+ let prefix = candidate.slice(0, dashIdx)
+ let modifier = candidate.slice(wasSlash ? dashIdx : dashIdx + 1)
+
+ lastIndex = dashIdx - 1
+
+ // TODO: This feels a bit hacky
+ if (prefix === '' || modifier === '/') {
+ continue
+ }
+
+ yield [prefix, modifier]
+ }
+}
+
+function applyPrefix(matches, context) {
+ if (matches.length === 0 || context.tailwindConfig.prefix === '') {
+ return matches
+ }
+
+ for (let match of matches) {
+ let [meta] = match
+ if (meta.options.respectPrefix) {
+ let container = postcss.root({ nodes: [match[1].clone()] })
+ let classCandidate = match[1].raws.tailwind.classCandidate
+
+ container.walkRules((r) => {
+ // If this is a negative utility with a dash *before* the prefix we
+ // have to ensure that the generated selector matches the candidate
+
+ // Not doing this will cause `-tw-top-1` to generate the class `.tw--top-1`
+ // The disconnect between candidate <-> class can cause @apply to hard crash.
+ let shouldPrependNegative = classCandidate.startsWith('-')
+
+ r.selector = prefixSelector(
+ context.tailwindConfig.prefix,
+ r.selector,
+ shouldPrependNegative
+ )
+ })
+
+ match[1] = container.nodes[0]
+ }
+ }
+
+ return matches
+}
+
+function applyImportant(matches, classCandidate) {
+ if (matches.length === 0) {
+ return matches
+ }
+
+ let result = []
+
+ for (let [meta, rule] of matches) {
+ let container = postcss.root({ nodes: [rule.clone()] })
+
+ container.walkRules((r) => {
+ let ast = selectorParser().astSync(r.selector)
+
+ // Remove extraneous selectors that do not include the base candidate
+ ast.each((sel) => eliminateIrrelevantSelectors(sel, classCandidate))
+
+ // Update all instances of the base candidate to include the important marker
+ updateAllClasses(ast, (className) =>
+ className === classCandidate ? `!${className}` : className
+ )
+
+ r.selector = ast.toString()
+
+ r.walkDecls((d) => (d.important = true))
+ })
+
+ result.push([{ ...meta, important: true }, container.nodes[0]])
+ }
+
+ return result
+}
+
+// Takes a list of rule tuples and applies a variant like `hover`, sm`,
+// whatever to it. We used to do some extra caching here to avoid generating
+// a variant of the same rule more than once, but this was never hit because
+// we cache at the entire selector level further up the tree.
+//
+// Technically you can get a cache hit if you have `hover:focus:text-center`
+// and `focus:hover:text-center` in the same project, but it doesn't feel
+// worth the complexity for that case.
+
+function applyVariant(variant, matches, context) {
+ if (matches.length === 0) {
+ return matches
+ }
+
+ /** @type {{modifier: string | null, value: string | null}} */
+ let args = { modifier: null, value: sharedState.NONE }
+
+ // Retrieve "modifier"
+ {
+ let [baseVariant, ...modifiers] = splitAtTopLevelOnly(variant, '/')
+
+ // This is a hack to support variants with `/` in them, like `ar-1/10/20:text-red-500`
+ // In this case 1/10 is a value but /20 is a modifier
+ if (modifiers.length > 1) {
+ baseVariant = baseVariant + '/' + modifiers.slice(0, -1).join('/')
+ modifiers = modifiers.slice(-1)
+ }
+
+ if (modifiers.length && !context.variantMap.has(variant)) {
+ variant = baseVariant
+ args.modifier = modifiers[0]
+
+ if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) {
+ return []
+ }
+ }
+ }
+
+ // Retrieve "arbitrary value"
+ if (variant.endsWith(']') && !variant.startsWith('[')) {
+ // We either have:
+ // @[200px]
+ // group-[:hover]
+ //
+ // But we don't want:
+ // @-[200px] (`-` is incorrect)
+ // group[:hover] (`-` is missing)
+ let match = /(.)(-?)\[(.*)\]/g.exec(variant)
+ if (match) {
+ let [, char, separator, value] = match
+ // @-[200px] case
+ if (char === '@' && separator === '-') return []
+ // group[:hover] case
+ if (char !== '@' && separator === '') return []
+
+ variant = variant.replace(`${separator}[${value}]`, '')
+ args.value = value
+ }
+ }
+
+ // Register arbitrary variants
+ if (isArbitraryValue(variant) && !context.variantMap.has(variant)) {
+ let sort = context.offsets.recordVariant(variant)
+
+ let selector = normalize(variant.slice(1, -1))
+ let selectors = splitAtTopLevelOnly(selector, ',')
+
+ // We do not support multiple selectors for arbitrary variants
+ if (selectors.length > 1) {
+ return []
+ }
+
+ if (!selectors.every(isValidVariantFormatString)) {
+ return []
+ }
+
+ let records = selectors.map((sel, idx) => [
+ context.offsets.applyParallelOffset(sort, idx),
+ parseVariant(sel.trim()),
+ ])
+
+ context.variantMap.set(variant, records)
+ }
+
+ if (context.variantMap.has(variant)) {
+ let isArbitraryVariant = isArbitraryValue(variant)
+ let internalFeatures = context.variantOptions.get(variant)?.[INTERNAL_FEATURES] ?? {}
+ let variantFunctionTuples = context.variantMap.get(variant).slice()
+ let result = []
+
+ let respectPrefix = (() => {
+ if (isArbitraryVariant) return false
+ if (internalFeatures.respectPrefix === false) return false
+ return true
+ })()
+
+ for (let [meta, rule] of matches) {
+ // Don't generate variants for user css
+ if (meta.layer === 'user') {
+ continue
+ }
+
+ let container = postcss.root({ nodes: [rule.clone()] })
+
+ for (let [variantSort, variantFunction, containerFromArray] of variantFunctionTuples) {
+ let clone = (containerFromArray ?? container).clone()
+ let collectedFormats = []
+
+ function prepareBackup() {
+ // Already prepared, chicken out
+ if (clone.raws.neededBackup) {
+ return
+ }
+ clone.raws.neededBackup = true
+ clone.walkRules((rule) => (rule.raws.originalSelector = rule.selector))
+ }
+
+ function modifySelectors(modifierFunction) {
+ prepareBackup()
+ clone.each((rule) => {
+ if (rule.type !== 'rule') {
+ return
+ }
+
+ rule.selectors = rule.selectors.map((selector) => {
+ return modifierFunction({
+ get className() {
+ return getClassNameFromSelector(selector)
+ },
+ selector,
+ })
+ })
+ })
+
+ return clone
+ }
+
+ let ruleWithVariant = variantFunction({
+ // Public API
+ get container() {
+ prepareBackup()
+ return clone
+ },
+ separator: context.tailwindConfig.separator,
+ modifySelectors,
+
+ // Private API for now
+ wrap(wrapper) {
+ let nodes = clone.nodes
+ clone.removeAll()
+ wrapper.append(nodes)
+ clone.append(wrapper)
+ },
+ format(selectorFormat) {
+ collectedFormats.push({
+ format: selectorFormat,
+ respectPrefix,
+ })
+ },
+ args,
+ })
+
+ // It can happen that a list of format strings is returned from within the function. In that
+ // case, we have to process them as well. We can use the existing `variantSort`.
+ if (Array.isArray(ruleWithVariant)) {
+ for (let [idx, variantFunction] of ruleWithVariant.entries()) {
+ // This is a little bit scary since we are pushing to an array of items that we are
+ // currently looping over. However, you can also think of it like a processing queue
+ // where you keep handling jobs until everything is done and each job can queue more
+ // jobs if needed.
+ variantFunctionTuples.push([
+ context.offsets.applyParallelOffset(variantSort, idx),
+ variantFunction,
+
+ // If the clone has been modified we have to pass that back
+ // though so each rule can use the modified container
+ clone.clone(),
+ ])
+ }
+ continue
+ }
+
+ if (typeof ruleWithVariant === 'string') {
+ collectedFormats.push({
+ format: ruleWithVariant,
+ respectPrefix,
+ })
+ }
+
+ if (ruleWithVariant === null) {
+ continue
+ }
+
+ // We had to backup selectors, therefore we assume that somebody touched
+ // `container` or `modifySelectors`. Let's see if they did, so that we
+ // can restore the selectors, and collect the format strings.
+ if (clone.raws.neededBackup) {
+ delete clone.raws.neededBackup
+ clone.walkRules((rule) => {
+ let before = rule.raws.originalSelector
+ if (!before) return
+ delete rule.raws.originalSelector
+ if (before === rule.selector) return // No mutation happened
+
+ let modified = rule.selector
+
+ // Rebuild the base selector, this is what plugin authors would do
+ // as well. E.g.: `${variant}${separator}${className}`.
+ // However, plugin authors probably also prepend or append certain
+ // classes, pseudos, ids, ...
+ let rebuiltBase = selectorParser((selectors) => {
+ selectors.walkClasses((classNode) => {
+ classNode.value = `${variant}${context.tailwindConfig.separator}${classNode.value}`
+ })
+ }).processSync(before)
+
+ // Now that we know the original selector, the new selector, and
+ // the rebuild part in between, we can replace the part that plugin
+ // authors need to rebuild with `&`, and eventually store it in the
+ // collectedFormats. Similar to what `format('...')` would do.
+ //
+ // E.g.:
+ // variant: foo
+ // selector: .markdown > p
+ // modified (by plugin): .foo .foo\\:markdown > p
+ // rebuiltBase (internal): .foo\\:markdown > p
+ // format: .foo &
+ collectedFormats.push({
+ format: modified.replace(rebuiltBase, '&'),
+ respectPrefix,
+ })
+ rule.selector = before
+ })
+ }
+
+ // This tracks the originating layer for the variant
+ // For example:
+ // .sm:underline {} is a variant of something in the utilities layer
+ // .sm:container {} is a variant of the container component
+ clone.nodes[0].raws.tailwind = { ...clone.nodes[0].raws.tailwind, parentLayer: meta.layer }
+
+ let withOffset = [
+ {
+ ...meta,
+ sort: context.offsets.applyVariantOffset(
+ meta.sort,
+ variantSort,
+ Object.assign(args, context.variantOptions.get(variant))
+ ),
+ collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats),
+ },
+ clone.nodes[0],
+ ]
+ result.push(withOffset)
+ }
+ }
+
+ return result
+ }
+
+ return []
+}
+
+function parseRules(rule, cache, options = {}) {
+ // PostCSS node
+ if (!isPlainObject(rule) && !Array.isArray(rule)) {
+ return [[rule], options]
+ }
+
+ // Tuple
+ if (Array.isArray(rule)) {
+ return parseRules(rule[0], cache, rule[1])
+ }
+
+ // Simple object
+ if (!cache.has(rule)) {
+ cache.set(rule, parseObjectStyles(rule))
+ }
+
+ return [cache.get(rule), options]
+}
+
+const IS_VALID_PROPERTY_NAME = /^[a-z_-]/
+
+function isValidPropName(name) {
+ return IS_VALID_PROPERTY_NAME.test(name)
+}
+
+/**
+ * @param {string} declaration
+ * @returns {boolean}
+ */
+function looksLikeUri(declaration) {
+ // Quick bailout for obvious non-urls
+ // This doesn't support schemes that don't use a leading // but that's unlikely to be a problem
+ if (!declaration.includes('://')) {
+ return false
+ }
+
+ try {
+ const url = new URL(declaration)
+ return url.scheme !== '' && url.host !== ''
+ } catch (err) {
+ // Definitely not a valid url
+ return false
+ }
+}
+
+function isParsableNode(node) {
+ let isParsable = true
+
+ node.walkDecls((decl) => {
+ if (!isParsableCssValue(decl.prop, decl.value)) {
+ isParsable = false
+ return false
+ }
+ })
+
+ return isParsable
+}
+
+function isParsableCssValue(property, value) {
+ // We don't want to to treat [https://example.com] as a custom property
+ // Even though, according to the CSS grammar, it's a totally valid CSS declaration
+ // So we short-circuit here by checking if the custom property looks like a url
+ if (looksLikeUri(`${property}:${value}`)) {
+ return false
+ }
+
+ try {
+ postcss.parse(`a{${property}:${value}}`).toResult()
+ return true
+ } catch (err) {
+ return false
+ }
+}
+
+function extractArbitraryProperty(classCandidate, context) {
+ let [, property, value] = classCandidate.match(/^\[([a-zA-Z0-9-_]+):(\S+)\]$/) ?? []
+
+ if (value === undefined) {
+ return null
+ }
+
+ if (!isValidPropName(property)) {
+ return null
+ }
+
+ if (!isValidArbitraryValue(value)) {
+ return null
+ }
+
+ let normalized = normalize(value, { property })
+
+ if (!isParsableCssValue(property, normalized)) {
+ return null
+ }
+
+ let sort = context.offsets.arbitraryProperty()
+
+ return [
+ [
+ { sort, layer: 'utilities' },
+ () => ({
+ [asClass(classCandidate)]: {
+ [property]: normalized,
+ },
+ }),
+ ],
+ ]
+}
+
+function* resolveMatchedPlugins(classCandidate, context) {
+ if (context.candidateRuleMap.has(classCandidate)) {
+ yield [context.candidateRuleMap.get(classCandidate), 'DEFAULT']
+ }
+
+ yield* (function* (arbitraryPropertyRule) {
+ if (arbitraryPropertyRule !== null) {
+ yield [arbitraryPropertyRule, 'DEFAULT']
+ }
+ })(extractArbitraryProperty(classCandidate, context))
+
+ let candidatePrefix = classCandidate
+ let negative = false
+
+ const twConfigPrefix = context.tailwindConfig.prefix
+
+ const twConfigPrefixLen = twConfigPrefix.length
+
+ const hasMatchingPrefix =
+ candidatePrefix.startsWith(twConfigPrefix) || candidatePrefix.startsWith(`-${twConfigPrefix}`)
+
+ if (candidatePrefix[twConfigPrefixLen] === '-' && hasMatchingPrefix) {
+ negative = true
+ candidatePrefix = twConfigPrefix + candidatePrefix.slice(twConfigPrefixLen + 1)
+ }
+
+ if (negative && context.candidateRuleMap.has(candidatePrefix)) {
+ yield [context.candidateRuleMap.get(candidatePrefix), '-DEFAULT']
+ }
+
+ for (let [prefix, modifier] of candidatePermutations(candidatePrefix)) {
+ if (context.candidateRuleMap.has(prefix)) {
+ yield [context.candidateRuleMap.get(prefix), negative ? `-${modifier}` : modifier]
+ }
+ }
+}
+
+function splitWithSeparator(input, separator) {
+ if (input === sharedState.NOT_ON_DEMAND) {
+ return [sharedState.NOT_ON_DEMAND]
+ }
+
+ return splitAtTopLevelOnly(input, separator)
+}
+
+function* recordCandidates(matches, classCandidate) {
+ for (const match of matches) {
+ match[1].raws.tailwind = {
+ ...match[1].raws.tailwind,
+ classCandidate,
+ preserveSource: match[0].options?.preserveSource ?? false,
+ }
+
+ yield match
+ }
+}
+
+function* resolveMatches(candidate, context) {
+ let separator = context.tailwindConfig.separator
+ let [classCandidate, ...variants] = splitWithSeparator(candidate, separator).reverse()
+ let important = false
+
+ if (classCandidate.startsWith('!')) {
+ important = true
+ classCandidate = classCandidate.slice(1)
+ }
+
+ // TODO: Reintroduce this in ways that doesn't break on false positives
+ // function sortAgainst(toSort, against) {
+ // return toSort.slice().sort((a, z) => {
+ // return bigSign(against.get(a)[0] - against.get(z)[0])
+ // })
+ // }
+ // let sorted = sortAgainst(variants, context.variantMap)
+ // if (sorted.toString() !== variants.toString()) {
+ // let corrected = sorted.reverse().concat(classCandidate).join(':')
+ // throw new Error(`Class ${candidate} should be written as ${corrected}`)
+ // }
+
+ for (let matchedPlugins of resolveMatchedPlugins(classCandidate, context)) {
+ let matches = []
+ let typesByMatches = new Map()
+
+ let [plugins, modifier] = matchedPlugins
+ let isOnlyPlugin = plugins.length === 1
+
+ for (let [sort, plugin] of plugins) {
+ let matchesPerPlugin = []
+
+ if (typeof plugin === 'function') {
+ for (let ruleSet of [].concat(plugin(modifier, { isOnlyPlugin }))) {
+ let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
+ for (let rule of rules) {
+ matchesPerPlugin.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
+ }
+ }
+ }
+ // Only process static plugins on exact matches
+ else if (modifier === 'DEFAULT' || modifier === '-DEFAULT') {
+ let ruleSet = plugin
+ let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
+ for (let rule of rules) {
+ matchesPerPlugin.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
+ }
+ }
+
+ if (matchesPerPlugin.length > 0) {
+ let matchingTypes = Array.from(
+ getMatchingTypes(
+ sort.options?.types ?? [],
+ modifier,
+ sort.options ?? {},
+ context.tailwindConfig
+ )
+ ).map(([_, type]) => type)
+
+ if (matchingTypes.length > 0) {
+ typesByMatches.set(matchesPerPlugin, matchingTypes)
+ }
+
+ matches.push(matchesPerPlugin)
+ }
+ }
+
+ if (isArbitraryValue(modifier)) {
+ if (matches.length > 1) {
+ // Partition plugins in 2 categories so that we can start searching in the plugins that
+ // don't have `any` as a type first.
+ let [withAny, withoutAny] = matches.reduce(
+ (group, plugin) => {
+ let hasAnyType = plugin.some(([{ options }]) =>
+ options.types.some(({ type }) => type === 'any')
+ )
+
+ if (hasAnyType) {
+ group[0].push(plugin)
+ } else {
+ group[1].push(plugin)
+ }
+ return group
+ },
+ [[], []]
+ )
+
+ function findFallback(matches) {
+ // If only a single plugin matches, let's take that one
+ if (matches.length === 1) {
+ return matches[0]
+ }
+
+ // Otherwise, find the plugin that creates a valid rule given the arbitrary value, and
+ // also has the correct type which preferOnConflicts the plugin in case of clashes.
+ return matches.find((rules) => {
+ let matchingTypes = typesByMatches.get(rules)
+ return rules.some(([{ options }, rule]) => {
+ if (!isParsableNode(rule)) {
+ return false
+ }
+
+ return options.types.some(
+ ({ type, preferOnConflict }) => matchingTypes.includes(type) && preferOnConflict
+ )
+ })
+ })
+ }
+
+ // Try to find a fallback plugin, because we already know that multiple plugins matched for
+ // the given arbitrary value.
+ let fallback = findFallback(withoutAny) ?? findFallback(withAny)
+ if (fallback) {
+ matches = [fallback]
+ }
+
+ // We couldn't find a fallback plugin which means that there are now multiple plugins that
+ // generated css for the current candidate. This means that the result is ambiguous and this
+ // should not happen. We won't generate anything right now, so let's report this to the user
+ // by logging some options about what they can do.
+ else {
+ let typesPerPlugin = matches.map(
+ (match) => new Set([...(typesByMatches.get(match) ?? [])])
+ )
+
+ // Remove duplicates, so that we can detect proper unique types for each plugin.
+ for (let pluginTypes of typesPerPlugin) {
+ for (let type of pluginTypes) {
+ let removeFromOwnGroup = false
+
+ for (let otherGroup of typesPerPlugin) {
+ if (pluginTypes === otherGroup) continue
+
+ if (otherGroup.has(type)) {
+ otherGroup.delete(type)
+ removeFromOwnGroup = true
+ }
+ }
+
+ if (removeFromOwnGroup) pluginTypes.delete(type)
+ }
+ }
+
+ let messages = []
+
+ for (let [idx, group] of typesPerPlugin.entries()) {
+ for (let type of group) {
+ let rules = matches[idx]
+ .map(([, rule]) => rule)
+ .flat()
+ .map((rule) =>
+ rule
+ .toString()
+ .split('\n')
+ .slice(1, -1) // Remove selector and closing '}'
+ .map((line) => line.trim())
+ .map((x) => ` ${x}`) // Re-indent
+ .join('\n')
+ )
+ .join('\n\n')
+
+ messages.push(
+ ` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``
+ )
+ break
+ }
+ }
+
+ log.warn([
+ `The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
+ ...messages,
+ `If this is content and not a class, replace it with \`${candidate
+ .replace('[', '&lsqb;')
+ .replace(']', '&rsqb;')}\` to silence this warning.`,
+ ])
+ continue
+ }
+ }
+
+ matches = matches.map((list) => list.filter((match) => isParsableNode(match[1])))
+ }
+
+ matches = matches.flat()
+ matches = Array.from(recordCandidates(matches, classCandidate))
+ matches = applyPrefix(matches, context)
+
+ if (important) {
+ matches = applyImportant(matches, classCandidate)
+ }
+
+ for (let variant of variants) {
+ matches = applyVariant(variant, matches, context)
+ }
+
+ for (let match of matches) {
+ match[1].raws.tailwind = { ...match[1].raws.tailwind, candidate }
+
+ // Apply final format selector
+ match = applyFinalFormat(match, { context, candidate })
+
+ // Skip rules with invalid selectors
+ // This will cause the candidate to be added to the "not class"
+ // cache skipping it entirely for future builds
+ if (match === null) {
+ continue
+ }
+
+ yield match
+ }
+ }
+}
+
+function applyFinalFormat(match, { context, candidate }) {
+ if (!match[0].collectedFormats) {
+ return match
+ }
+
+ let isValid = true
+ let finalFormat
+
+ try {
+ finalFormat = formatVariantSelector(match[0].collectedFormats, {
+ context,
+ candidate,
+ })
+ } catch {
+ // The format selector we produced is invalid
+ // This could be because:
+ // - A bug exists
+ // - A plugin introduced an invalid variant selector (ex: `addVariant('foo', '&;foo')`)
+ // - The user used an invalid arbitrary variant (ex: `[&;foo]:underline`)
+ // Either way the build will fail because of this
+ // We would rather that the build pass "silently" given that this could
+ // happen because of picking up invalid things when scanning content
+ // So we'll throw out the candidate instead
+
+ return null
+ }
+
+ let container = postcss.root({ nodes: [match[1].clone()] })
+
+ container.walkRules((rule) => {
+ if (inKeyframes(rule)) {
+ return
+ }
+
+ try {
+ let selector = finalizeSelector(rule.selector, finalFormat, {
+ candidate,
+ context,
+ })
+
+ // Finalize Selector determined that this candidate is irrelevant
+ // TODO: This elimination should happen earlier so this never happens
+ if (selector === null) {
+ rule.remove()
+ return
+ }
+
+ rule.selector = selector
+ } catch {
+ // If this selector is invalid we also want to skip it
+ // But it's likely that being invalid here means there's a bug in a plugin rather than too loosely matching content
+ isValid = false
+ return false
+ }
+ })
+
+ if (!isValid) {
+ return null
+ }
+
+ match[1] = container.nodes[0]
+
+ return match
+}
+
+function inKeyframes(rule) {
+ return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
+}
+
+function getImportantStrategy(important) {
+ if (important === true) {
+ return (rule) => {
+ if (inKeyframes(rule)) {
+ return
+ }
+
+ rule.walkDecls((d) => {
+ if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
+ d.important = true
+ }
+ })
+ }
+ }
+
+ if (typeof important === 'string') {
+ return (rule) => {
+ if (inKeyframes(rule)) {
+ return
+ }
+
+ rule.selectors = rule.selectors.map((selector) => {
+ return applyImportantSelector(selector, important)
+ })
+ }
+ }
+}
+
+function generateRules(candidates, context, isSorting = false) {
+ let allRules = []
+ let strategy = getImportantStrategy(context.tailwindConfig.important)
+
+ for (let candidate of candidates) {
+ if (context.notClassCache.has(candidate)) {
+ continue
+ }
+
+ if (context.candidateRuleCache.has(candidate)) {
+ allRules = allRules.concat(Array.from(context.candidateRuleCache.get(candidate)))
+ continue
+ }
+
+ let matches = Array.from(resolveMatches(candidate, context))
+
+ if (matches.length === 0) {
+ context.notClassCache.add(candidate)
+ continue
+ }
+
+ context.classCache.set(candidate, matches)
+
+ let rules = context.candidateRuleCache.get(candidate) ?? new Set()
+ context.candidateRuleCache.set(candidate, rules)
+
+ for (const match of matches) {
+ let [{ sort, options }, rule] = match
+
+ if (options.respectImportant && strategy) {
+ let container = postcss.root({ nodes: [rule.clone()] })
+ container.walkRules(strategy)
+ rule = container.nodes[0]
+ }
+
+ // Note: We have to clone rules during sorting
+ // so we eliminate some shared mutable state
+ let newEntry = [sort, isSorting ? rule.clone() : rule]
+ rules.add(newEntry)
+ context.ruleCache.add(newEntry)
+ allRules.push(newEntry)
+ }
+ }
+
+ return allRules
+}
+
+function isArbitraryValue(input) {
+ return input.startsWith('[') && input.endsWith(']')
+}
+
+export { resolveMatches, generateRules }
diff --git a/node_modules/tailwindcss/src/lib/getModuleDependencies.js b/node_modules/tailwindcss/src/lib/getModuleDependencies.js
new file mode 100644
index 0000000..e6a38a8
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/getModuleDependencies.js
@@ -0,0 +1,79 @@
+import fs from 'fs'
+import path from 'path'
+
+let jsExtensions = ['.js', '.cjs', '.mjs']
+
+// Given the current file `a.ts`, we want to make sure that when importing `b` that we resolve
+// `b.ts` before `b.js`
+//
+// E.g.:
+//
+// a.ts
+// b // .ts
+// c // .ts
+// a.js
+// b // .js or .ts
+
+let jsResolutionOrder = ['', '.js', '.cjs', '.mjs', '.ts', '.cts', '.mts', '.jsx', '.tsx']
+let tsResolutionOrder = ['', '.ts', '.cts', '.mts', '.tsx', '.js', '.cjs', '.mjs', '.jsx']
+
+function resolveWithExtension(file, extensions) {
+ // Try to find `./a.ts`, `./a.ts`, ... from `./a`
+ for (let ext of extensions) {
+ let full = `${file}${ext}`
+ if (fs.existsSync(full) && fs.statSync(full).isFile()) {
+ return full
+ }
+ }
+
+ // Try to find `./a/index.js` from `./a`
+ for (let ext of extensions) {
+ let full = `${file}/index${ext}`
+ if (fs.existsSync(full)) {
+ return full
+ }
+ }
+
+ return null
+}
+
+function* _getModuleDependencies(filename, base, seen, ext = path.extname(filename)) {
+ // Try to find the file
+ let absoluteFile = resolveWithExtension(
+ path.resolve(base, filename),
+ jsExtensions.includes(ext) ? jsResolutionOrder : tsResolutionOrder
+ )
+ if (absoluteFile === null) return // File doesn't exist
+
+ // Prevent infinite loops when there are circular dependencies
+ if (seen.has(absoluteFile)) return // Already seen
+ seen.add(absoluteFile)
+
+ // Mark the file as a dependency
+ yield absoluteFile
+
+ // Resolve new base for new imports/requires
+ base = path.dirname(absoluteFile)
+ ext = path.extname(absoluteFile)
+
+ let contents = fs.readFileSync(absoluteFile, 'utf-8')
+
+ // Find imports/requires
+ for (let match of [
+ ...contents.matchAll(/import[\s\S]*?['"](.{3,}?)['"]/gi),
+ ...contents.matchAll(/import[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi),
+ ...contents.matchAll(/require\(['"`](.+)['"`]\)/gi),
+ ]) {
+ // Bail out if it's not a relative file
+ if (!match[1].startsWith('.')) continue
+
+ yield* _getModuleDependencies(match[1], base, seen, ext)
+ }
+}
+
+export default function getModuleDependencies(absoluteFilePath) {
+ if (absoluteFilePath === null) return new Set()
+ return new Set(
+ _getModuleDependencies(absoluteFilePath, path.dirname(absoluteFilePath), new Set())
+ )
+}
diff --git a/node_modules/tailwindcss/src/lib/load-config.ts b/node_modules/tailwindcss/src/lib/load-config.ts
new file mode 100644
index 0000000..645e8e1
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/load-config.ts
@@ -0,0 +1,31 @@
+import jitiFactory from 'jiti'
+import { transform } from 'sucrase'
+
+import { Config } from '../../types/config'
+
+let jiti: ReturnType<typeof jitiFactory> | null = null
+function lazyJiti() {
+ return (
+ jiti ??
+ (jiti = jitiFactory(__filename, {
+ interopDefault: true,
+ transform: (opts) => {
+ return transform(opts.source, {
+ transforms: ['typescript', 'imports'],
+ })
+ },
+ }))
+ )
+}
+
+export function loadConfig(path: string): Config {
+ let config = (function () {
+ try {
+ return path ? require(path) : {}
+ } catch {
+ return lazyJiti()(path)
+ }
+ })()
+
+ return config.default ?? config
+}
diff --git a/node_modules/tailwindcss/src/lib/normalizeTailwindDirectives.js b/node_modules/tailwindcss/src/lib/normalizeTailwindDirectives.js
new file mode 100644
index 0000000..3349a7e
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/normalizeTailwindDirectives.js
@@ -0,0 +1,84 @@
+import log from '../util/log'
+
+export default function normalizeTailwindDirectives(root) {
+ let tailwindDirectives = new Set()
+ let layerDirectives = new Set()
+ let applyDirectives = new Set()
+
+ root.walkAtRules((atRule) => {
+ if (atRule.name === 'apply') {
+ applyDirectives.add(atRule)
+ }
+
+ if (atRule.name === 'import') {
+ if (atRule.params === '"tailwindcss/base"' || atRule.params === "'tailwindcss/base'") {
+ atRule.name = 'tailwind'
+ atRule.params = 'base'
+ } else if (
+ atRule.params === '"tailwindcss/components"' ||
+ atRule.params === "'tailwindcss/components'"
+ ) {
+ atRule.name = 'tailwind'
+ atRule.params = 'components'
+ } else if (
+ atRule.params === '"tailwindcss/utilities"' ||
+ atRule.params === "'tailwindcss/utilities'"
+ ) {
+ atRule.name = 'tailwind'
+ atRule.params = 'utilities'
+ } else if (
+ atRule.params === '"tailwindcss/screens"' ||
+ atRule.params === "'tailwindcss/screens'" ||
+ atRule.params === '"tailwindcss/variants"' ||
+ atRule.params === "'tailwindcss/variants'"
+ ) {
+ atRule.name = 'tailwind'
+ atRule.params = 'variants'
+ }
+ }
+
+ if (atRule.name === 'tailwind') {
+ if (atRule.params === 'screens') {
+ atRule.params = 'variants'
+ }
+ tailwindDirectives.add(atRule.params)
+ }
+
+ if (['layer', 'responsive', 'variants'].includes(atRule.name)) {
+ if (['responsive', 'variants'].includes(atRule.name)) {
+ log.warn(`${atRule.name}-at-rule-deprecated`, [
+ `The \`@${atRule.name}\` directive has been deprecated in Tailwind CSS v3.0.`,
+ `Use \`@layer utilities\` or \`@layer components\` instead.`,
+ 'https://tailwindcss.com/docs/upgrade-guide#replace-variants-with-layer',
+ ])
+ }
+ layerDirectives.add(atRule)
+ }
+ })
+
+ if (
+ !tailwindDirectives.has('base') ||
+ !tailwindDirectives.has('components') ||
+ !tailwindDirectives.has('utilities')
+ ) {
+ for (let rule of layerDirectives) {
+ if (rule.name === 'layer' && ['base', 'components', 'utilities'].includes(rule.params)) {
+ if (!tailwindDirectives.has(rule.params)) {
+ throw rule.error(
+ `\`@layer ${rule.params}\` is used but no matching \`@tailwind ${rule.params}\` directive is present.`
+ )
+ }
+ } else if (rule.name === 'responsive') {
+ if (!tailwindDirectives.has('utilities')) {
+ throw rule.error('`@responsive` is used but `@tailwind utilities` is missing.')
+ }
+ } else if (rule.name === 'variants') {
+ if (!tailwindDirectives.has('utilities')) {
+ throw rule.error('`@variants` is used but `@tailwind utilities` is missing.')
+ }
+ }
+ }
+ }
+
+ return { tailwindDirectives, applyDirectives }
+}
diff --git a/node_modules/tailwindcss/src/lib/offsets.js b/node_modules/tailwindcss/src/lib/offsets.js
new file mode 100644
index 0000000..a43ebe4
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/offsets.js
@@ -0,0 +1,373 @@
+// @ts-check
+
+import bigSign from '../util/bigSign'
+import { remapBitfield } from './remap-bitfield.js'
+
+/**
+ * @typedef {'base' | 'defaults' | 'components' | 'utilities' | 'variants' | 'user'} Layer
+ */
+
+/**
+ * @typedef {object} VariantOption
+ * @property {number} id An unique identifier to identify `matchVariant`
+ * @property {function | undefined} sort The sort function
+ * @property {string|null} value The value we want to compare
+ * @property {string|null} modifier The modifier that was used (if any)
+ * @property {bigint} variant The variant bitmask
+ */
+
+/**
+ * @typedef {object} RuleOffset
+ * @property {Layer} layer The layer that this rule belongs to
+ * @property {Layer} parentLayer The layer that this rule originally belonged to. Only different from layer if this is a variant.
+ * @property {bigint} arbitrary 0n if false, 1n if true
+ * @property {bigint} variants Dynamic size. 1 bit per registered variant. 0n means no variants
+ * @property {bigint} parallelIndex Rule index for the parallel variant. 0 if not applicable.
+ * @property {bigint} index Index of the rule / utility in it's given *parent* layer. Monotonically increasing.
+ * @property {VariantOption[]} options Some information on how we can sort arbitrary variants
+ */
+
+export class Offsets {
+ constructor() {
+ /**
+ * Offsets for the next rule in a given layer
+ *
+ * @type {Record<Layer, bigint>}
+ */
+ this.offsets = {
+ defaults: 0n,
+ base: 0n,
+ components: 0n,
+ utilities: 0n,
+ variants: 0n,
+ user: 0n,
+ }
+
+ /**
+ * Positions for a given layer
+ *
+ * @type {Record<Layer, bigint>}
+ */
+ this.layerPositions = {
+ defaults: 0n,
+ base: 1n,
+ components: 2n,
+ utilities: 3n,
+
+ // There isn't technically a "user" layer, but we need to give it a position
+ // Because it's used for ordering user-css from @apply
+ user: 4n,
+
+ variants: 5n,
+ }
+
+ /**
+ * The total number of functions currently registered across all variants (including arbitrary variants)
+ *
+ * @type {bigint}
+ */
+ this.reservedVariantBits = 0n
+
+ /**
+ * Positions for a given variant
+ *
+ * @type {Map<string, bigint>}
+ */
+ this.variantOffsets = new Map()
+ }
+
+ /**
+ * @param {Layer} layer
+ * @returns {RuleOffset}
+ */
+ create(layer) {
+ return {
+ layer,
+ parentLayer: layer,
+ arbitrary: 0n,
+ variants: 0n,
+ parallelIndex: 0n,
+ index: this.offsets[layer]++,
+ options: [],
+ }
+ }
+
+ /**
+ * @returns {RuleOffset}
+ */
+ arbitraryProperty() {
+ return {
+ ...this.create('utilities'),
+ arbitrary: 1n,
+ }
+ }
+
+ /**
+ * Get the offset for a variant
+ *
+ * @param {string} variant
+ * @param {number} index
+ * @returns {RuleOffset}
+ */
+ forVariant(variant, index = 0) {
+ let offset = this.variantOffsets.get(variant)
+ if (offset === undefined) {
+ throw new Error(`Cannot find offset for unknown variant ${variant}`)
+ }
+
+ return {
+ ...this.create('variants'),
+ variants: offset << BigInt(index),
+ }
+ }
+
+ /**
+ * @param {RuleOffset} rule
+ * @param {RuleOffset} variant
+ * @param {VariantOption} options
+ * @returns {RuleOffset}
+ */
+ applyVariantOffset(rule, variant, options) {
+ options.variant = variant.variants
+
+ return {
+ ...rule,
+ layer: 'variants',
+ parentLayer: rule.layer === 'variants' ? rule.parentLayer : rule.layer,
+ variants: rule.variants | variant.variants,
+ options: options.sort ? [].concat(options, rule.options) : rule.options,
+
+ // TODO: Technically this is wrong. We should be handling parallel index on a per variant basis.
+ // We'll take the max of all the parallel indexes for now.
+ // @ts-ignore
+ parallelIndex: max([rule.parallelIndex, variant.parallelIndex]),
+ }
+ }
+
+ /**
+ * @param {RuleOffset} offset
+ * @param {number} parallelIndex
+ * @returns {RuleOffset}
+ */
+ applyParallelOffset(offset, parallelIndex) {
+ return {
+ ...offset,
+ parallelIndex: BigInt(parallelIndex),
+ }
+ }
+
+ /**
+ * Each variant gets 1 bit per function / rule registered.
+ * This is because multiple variants can be applied to a single rule and we need to know which ones are present and which ones are not.
+ * Additionally, every unique group of variants is grouped together in the stylesheet.
+ *
+ * This grouping is order-independent. For instance, we do not differentiate between `hover:focus` and `focus:hover`.
+ *
+ * @param {string[]} variants
+ * @param {(name: string) => number} getLength
+ */
+ recordVariants(variants, getLength) {
+ for (let variant of variants) {
+ this.recordVariant(variant, getLength(variant))
+ }
+ }
+
+ /**
+ * The same as `recordVariants` but for a single arbitrary variant at runtime.
+ * @param {string} variant
+ * @param {number} fnCount
+ *
+ * @returns {RuleOffset} The highest offset for this variant
+ */
+ recordVariant(variant, fnCount = 1) {
+ this.variantOffsets.set(variant, 1n << this.reservedVariantBits)
+
+ // Ensure space is reserved for each "function" in the parallel variant
+ // by offsetting the next variant by the number of parallel variants
+ // in the one we just added.
+
+ // Single functions that return parallel variants are NOT handled separately here
+ // They're offset by 1 (or the number of functions) as usual
+ // And each rule returned is tracked separately since the functions are evaluated lazily.
+ // @see `RuleOffset.parallelIndex`
+ this.reservedVariantBits += BigInt(fnCount)
+
+ return {
+ ...this.create('variants'),
+ variants: this.variantOffsets.get(variant),
+ }
+ }
+
+ /**
+ * @param {RuleOffset} a
+ * @param {RuleOffset} b
+ * @returns {bigint}
+ */
+ compare(a, b) {
+ // Sort layers together
+ if (a.layer !== b.layer) {
+ return this.layerPositions[a.layer] - this.layerPositions[b.layer]
+ }
+
+ // When sorting the `variants` layer, we need to sort based on the parent layer as well within
+ // this variants layer.
+ if (a.parentLayer !== b.parentLayer) {
+ return this.layerPositions[a.parentLayer] - this.layerPositions[b.parentLayer]
+ }
+
+ // Sort based on the sorting function
+ for (let aOptions of a.options) {
+ for (let bOptions of b.options) {
+ if (aOptions.id !== bOptions.id) continue
+ if (!aOptions.sort || !bOptions.sort) continue
+
+ let maxFnVariant = max([aOptions.variant, bOptions.variant]) ?? 0n
+
+ // Create a mask of 0s from bits 1..N where N represents the mask of the Nth bit
+ let mask = ~(maxFnVariant | (maxFnVariant - 1n))
+ let aVariantsAfterFn = a.variants & mask
+ let bVariantsAfterFn = b.variants & mask
+
+ // If the variants the same, we _can_ sort them
+ if (aVariantsAfterFn !== bVariantsAfterFn) {
+ continue
+ }
+
+ let result = aOptions.sort(
+ {
+ value: aOptions.value,
+ modifier: aOptions.modifier,
+ },
+ {
+ value: bOptions.value,
+ modifier: bOptions.modifier,
+ }
+ )
+ if (result !== 0) return result
+ }
+ }
+
+ // Sort variants in the order they were registered
+ if (a.variants !== b.variants) {
+ return a.variants - b.variants
+ }
+
+ // Make sure each rule returned by a parallel variant is sorted in ascending order
+ if (a.parallelIndex !== b.parallelIndex) {
+ return a.parallelIndex - b.parallelIndex
+ }
+
+ // Always sort arbitrary properties after other utilities
+ if (a.arbitrary !== b.arbitrary) {
+ return a.arbitrary - b.arbitrary
+ }
+
+ // Sort utilities, components, etc… in the order they were registered
+ return a.index - b.index
+ }
+
+ /**
+ * Arbitrary variants are recorded in the order they're encountered.
+ * This means that the order is not stable between environments and sets of content files.
+ *
+ * In order to make the order stable, we need to remap the arbitrary variant offsets to
+ * be in alphabetical order starting from the offset of the first arbitrary variant.
+ */
+ recalculateVariantOffsets() {
+ // Sort the variants by their name
+ let variants = Array.from(this.variantOffsets.entries())
+ .filter(([v]) => v.startsWith('['))
+ .sort(([a], [z]) => fastCompare(a, z))
+
+ // Sort the list of offsets
+ // This is not necessarily a discrete range of numbers which is why
+ // we're using sort instead of creating a range from min/max
+ let newOffsets = variants.map(([, offset]) => offset).sort((a, z) => bigSign(a - z))
+
+ // Create a map from the old offsets to the new offsets in the new sort order
+ /** @type {[bigint, bigint][]} */
+ let mapping = variants.map(([, oldOffset], i) => [oldOffset, newOffsets[i]])
+
+ // Remove any variants that will not move letting us skip
+ // remapping if everything happens to be in order
+ return mapping.filter(([a, z]) => a !== z)
+ }
+
+ /**
+ * @template T
+ * @param {[RuleOffset, T][]} list
+ * @returns {[RuleOffset, T][]}
+ */
+ remapArbitraryVariantOffsets(list) {
+ let mapping = this.recalculateVariantOffsets()
+
+ // No arbitrary variants? Nothing to do.
+ // Everyhing already in order? Nothing to do.
+ if (mapping.length === 0) {
+ return list
+ }
+
+ // Remap every variant offset in the list
+ return list.map((item) => {
+ let [offset, rule] = item
+
+ offset = {
+ ...offset,
+ variants: remapBitfield(offset.variants, mapping),
+ }
+
+ return [offset, rule]
+ })
+ }
+
+ /**
+ * @template T
+ * @param {[RuleOffset, T][]} list
+ * @returns {[RuleOffset, T][]}
+ */
+ sort(list) {
+ list = this.remapArbitraryVariantOffsets(list)
+
+ return list.sort(([a], [b]) => bigSign(this.compare(a, b)))
+ }
+}
+
+/**
+ *
+ * @param {bigint[]} nums
+ * @returns {bigint|null}
+ */
+function max(nums) {
+ let max = null
+
+ for (const num of nums) {
+ max = max ?? num
+ max = max > num ? max : num
+ }
+
+ return max
+}
+
+/**
+ * A fast ASCII order string comparison function.
+ *
+ * Using `.sort()` without a custom compare function is faster
+ * But you can only use that if you're sorting an array of
+ * only strings. If you're sorting strings inside objects
+ * or arrays, you need must use a custom compare function.
+ *
+ * @param {string} a
+ * @param {string} b
+ */
+function fastCompare(a, b) {
+ let aLen = a.length
+ let bLen = b.length
+ let minLen = aLen < bLen ? aLen : bLen
+
+ for (let i = 0; i < minLen; i++) {
+ let cmp = a.charCodeAt(i) - b.charCodeAt(i)
+ if (cmp !== 0) return cmp
+ }
+
+ return aLen - bLen
+}
diff --git a/node_modules/tailwindcss/src/lib/partitionApplyAtRules.js b/node_modules/tailwindcss/src/lib/partitionApplyAtRules.js
new file mode 100644
index 0000000..34813c6
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/partitionApplyAtRules.js
@@ -0,0 +1,52 @@
+function partitionRules(root) {
+ if (!root.walkAtRules) return
+
+ let applyParents = new Set()
+
+ root.walkAtRules('apply', (rule) => {
+ applyParents.add(rule.parent)
+ })
+
+ if (applyParents.size === 0) {
+ return
+ }
+
+ for (let rule of applyParents) {
+ let nodeGroups = []
+ let lastGroup = []
+
+ for (let node of rule.nodes) {
+ if (node.type === 'atrule' && node.name === 'apply') {
+ if (lastGroup.length > 0) {
+ nodeGroups.push(lastGroup)
+ lastGroup = []
+ }
+ nodeGroups.push([node])
+ } else {
+ lastGroup.push(node)
+ }
+ }
+
+ if (lastGroup.length > 0) {
+ nodeGroups.push(lastGroup)
+ }
+
+ if (nodeGroups.length === 1) {
+ continue
+ }
+
+ for (let group of [...nodeGroups].reverse()) {
+ let clone = rule.clone({ nodes: [] })
+ clone.append(group)
+ rule.after(clone)
+ }
+
+ rule.remove()
+ }
+}
+
+export default function expandApplyAtRules() {
+ return (root) => {
+ partitionRules(root)
+ }
+}
diff --git a/node_modules/tailwindcss/src/lib/regex.js b/node_modules/tailwindcss/src/lib/regex.js
new file mode 100644
index 0000000..5db7657
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/regex.js
@@ -0,0 +1,74 @@
+const REGEX_SPECIAL = /[\\^$.*+?()[\]{}|]/g
+const REGEX_HAS_SPECIAL = RegExp(REGEX_SPECIAL.source)
+
+/**
+ * @param {string|RegExp|Array<string|RegExp>} source
+ */
+function toSource(source) {
+ source = Array.isArray(source) ? source : [source]
+
+ source = source.map((item) => (item instanceof RegExp ? item.source : item))
+
+ return source.join('')
+}
+
+/**
+ * @param {string|RegExp|Array<string|RegExp>} source
+ */
+export function pattern(source) {
+ return new RegExp(toSource(source), 'g')
+}
+
+/**
+ * @param {string|RegExp|Array<string|RegExp>} source
+ */
+export function withoutCapturing(source) {
+ return new RegExp(`(?:${toSource(source)})`, 'g')
+}
+
+/**
+ * @param {Array<string|RegExp>} sources
+ */
+export function any(sources) {
+ return `(?:${sources.map(toSource).join('|')})`
+}
+
+/**
+ * @param {string|RegExp} source
+ */
+export function optional(source) {
+ return `(?:${toSource(source)})?`
+}
+
+/**
+ * @param {string|RegExp|Array<string|RegExp>} source
+ */
+export function zeroOrMore(source) {
+ return `(?:${toSource(source)})*`
+}
+
+/**
+ * Generate a RegExp that matches balanced brackets for a given depth
+ * We have to specify a depth because JS doesn't support recursive groups using ?R
+ *
+ * Based on https://stackoverflow.com/questions/17759004/how-to-match-string-within-parentheses-nested-in-java/17759264#17759264
+ *
+ * @param {string|RegExp|Array<string|RegExp>} source
+ */
+export function nestedBrackets(open, close, depth = 1) {
+ return withoutCapturing([
+ escape(open),
+ /[^\s]*/,
+ depth === 1
+ ? `[^${escape(open)}${escape(close)}\s]*`
+ : any([`[^${escape(open)}${escape(close)}\s]*`, nestedBrackets(open, close, depth - 1)]),
+ /[^\s]*/,
+ escape(close),
+ ])
+}
+
+export function escape(string) {
+ return string && REGEX_HAS_SPECIAL.test(string)
+ ? string.replace(REGEX_SPECIAL, '\\$&')
+ : string || ''
+}
diff --git a/node_modules/tailwindcss/src/lib/remap-bitfield.js b/node_modules/tailwindcss/src/lib/remap-bitfield.js
new file mode 100644
index 0000000..3ddaf20
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/remap-bitfield.js
@@ -0,0 +1,82 @@
+// @ts-check
+
+/**
+ * We must remap all the old bits to new bits for each set variant
+ * Only arbitrary variants are considered as those are the only
+ * ones that need to be re-sorted at this time
+ *
+ * An iterated process that removes and sets individual bits simultaneously
+ * will not work because we may have a new bit that is also a later old bit
+ * This means that we would be removing a previously set bit which we don't
+ * want to do
+ *
+ * For example (assume `bN` = `1<<N`)
+ * Given the "total" mapping `[[b1, b3], [b2, b4], [b3, b1], [b4, b2]]`
+ * The mapping is "total" because:
+ * 1. Every input and output is accounted for
+ * 2. All combinations are unique
+ * 3. No one input maps to multiple outputs and vice versa
+ * And, given an offset with all bits set:
+ * V = b1 | b2 | b3 | b4
+ *
+ * Let's explore the issue with removing and setting bits simultaneously:
+ * V & ~b1 | b3 = b2 | b3 | b4
+ * V & ~b2 | b4 = b3 | b4
+ * V & ~b3 | b1 = b1 | b4
+ * V & ~b4 | b2 = b1 | b2
+ *
+ * As you can see, we end up with the wrong result.
+ * This is because we're removing a bit that was previously set.
+ * And, thus the final result is missing b3 and b4.
+ *
+ * Now, let's explore the issue with removing the bits first:
+ * V & ~b1 = b2 | b3 | b4
+ * V & ~b2 = b3 | b4
+ * V & ~b3 = b4
+ * V & ~b4 = 0
+ *
+ * And then setting the bits:
+ * V | b3 = b3
+ * V | b4 = b3 | b4
+ * V | b1 = b1 | b3 | b4
+ * V | b2 = b1 | b2 | b3 | b4
+ *
+ * We get the correct result because we're not removing any bits that were
+ * previously set thus properly remapping the bits to the new order
+ *
+ * To collect this into a single operation that can be done simultaneously
+ * we must first create a mask for the old bits that are set and a mask for
+ * the new bits that are set. Then we can remove the old bits and set the new
+ * bits simultaneously in a "single" operation like so:
+ * OldMask = b1 | b2 | b3 | b4
+ * NewMask = b3 | b4 | b1 | b2
+ *
+ * So this:
+ * V & ~oldMask | newMask
+ *
+ * Expands to this:
+ * V & ~b1 & ~b2 & ~b3 & ~b4 | b3 | b4 | b1 | b2
+ *
+ * Which becomes this:
+ * b1 | b2 | b3 | b4
+ *
+ * Which is the correct result!
+ *
+ * @param {bigint} num
+ * @param {[bigint, bigint][]} mapping
+ */
+export function remapBitfield(num, mapping) {
+ // Create masks for the old and new bits that are set
+ let oldMask = 0n
+ let newMask = 0n
+ for (let [oldBit, newBit] of mapping) {
+ if (num & oldBit) {
+ oldMask = oldMask | oldBit
+ newMask = newMask | newBit
+ }
+ }
+
+ // Remove all old bits
+ // Set all new bits
+ return (num & ~oldMask) | newMask
+}
diff --git a/node_modules/tailwindcss/src/lib/resolveDefaultsAtRules.js b/node_modules/tailwindcss/src/lib/resolveDefaultsAtRules.js
new file mode 100644
index 0000000..389ea4b
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/resolveDefaultsAtRules.js
@@ -0,0 +1,163 @@
+import postcss from 'postcss'
+import selectorParser from 'postcss-selector-parser'
+import { flagEnabled } from '../featureFlags'
+
+let getNode = {
+ id(node) {
+ return selectorParser.attribute({
+ attribute: 'id',
+ operator: '=',
+ value: node.value,
+ quoteMark: '"',
+ })
+ },
+}
+
+function minimumImpactSelector(nodes) {
+ let rest = nodes
+ .filter((node) => {
+ // Keep non-pseudo nodes
+ if (node.type !== 'pseudo') return true
+
+ // Keep pseudo nodes that have subnodes
+ // E.g.: `:not()` contains subnodes inside the parentheses
+ if (node.nodes.length > 0) return true
+
+ // Keep pseudo `elements`
+ // This implicitly means that we ignore pseudo `classes`
+ return (
+ node.value.startsWith('::') ||
+ [':before', ':after', ':first-line', ':first-letter'].includes(node.value)
+ )
+ })
+ .reverse()
+
+ let searchFor = new Set(['tag', 'class', 'id', 'attribute'])
+
+ let splitPointIdx = rest.findIndex((n) => searchFor.has(n.type))
+ if (splitPointIdx === -1) return rest.reverse().join('').trim()
+
+ let node = rest[splitPointIdx]
+ let bestNode = getNode[node.type] ? getNode[node.type](node) : node
+
+ rest = rest.slice(0, splitPointIdx)
+
+ let combinatorIdx = rest.findIndex((n) => n.type === 'combinator' && n.value === '>')
+ if (combinatorIdx !== -1) {
+ rest.splice(0, combinatorIdx)
+ rest.unshift(selectorParser.universal())
+ }
+
+ return [bestNode, ...rest.reverse()].join('').trim()
+}
+
+export let elementSelectorParser = selectorParser((selectors) => {
+ return selectors.map((s) => {
+ let nodes = s.split((n) => n.type === 'combinator' && n.value === ' ').pop()
+ return minimumImpactSelector(nodes)
+ })
+})
+
+let cache = new Map()
+
+function extractElementSelector(selector) {
+ if (!cache.has(selector)) {
+ cache.set(selector, elementSelectorParser.transformSync(selector))
+ }
+
+ return cache.get(selector)
+}
+
+export default function resolveDefaultsAtRules({ tailwindConfig }) {
+ return (root) => {
+ let variableNodeMap = new Map()
+
+ /** @type {Set<import('postcss').AtRule>} */
+ let universals = new Set()
+
+ root.walkAtRules('defaults', (rule) => {
+ if (rule.nodes && rule.nodes.length > 0) {
+ universals.add(rule)
+ return
+ }
+
+ let variable = rule.params
+ if (!variableNodeMap.has(variable)) {
+ variableNodeMap.set(variable, new Set())
+ }
+
+ variableNodeMap.get(variable).add(rule.parent)
+
+ rule.remove()
+ })
+
+ if (flagEnabled(tailwindConfig, 'optimizeUniversalDefaults')) {
+ for (let universal of universals) {
+ /** @type {Map<string, Set<string>>} */
+ let selectorGroups = new Map()
+
+ let rules = variableNodeMap.get(universal.params) ?? []
+
+ for (let rule of rules) {
+ for (let selector of extractElementSelector(rule.selector)) {
+ // If selector contains a vendor prefix after a pseudo element or class,
+ // we consider them separately because merging the declarations into
+ // a single rule will cause browsers that do not understand the
+ // vendor prefix to throw out the whole rule
+ let selectorGroupName =
+ selector.includes(':-') || selector.includes('::-') ? selector : '__DEFAULT__'
+
+ let selectors = selectorGroups.get(selectorGroupName) ?? new Set()
+ selectorGroups.set(selectorGroupName, selectors)
+
+ selectors.add(selector)
+ }
+ }
+
+ if (flagEnabled(tailwindConfig, 'optimizeUniversalDefaults')) {
+ if (selectorGroups.size === 0) {
+ universal.remove()
+ continue
+ }
+
+ for (let [, selectors] of selectorGroups) {
+ let universalRule = postcss.rule({
+ source: universal.source,
+ })
+
+ universalRule.selectors = [...selectors]
+
+ universalRule.append(universal.nodes.map((node) => node.clone()))
+ universal.before(universalRule)
+ }
+ }
+
+ universal.remove()
+ }
+ } else if (universals.size) {
+ let universalRule = postcss.rule({
+ selectors: ['*', '::before', '::after'],
+ })
+
+ for (let universal of universals) {
+ universalRule.append(universal.nodes)
+
+ if (!universalRule.parent) {
+ universal.before(universalRule)
+ }
+
+ if (!universalRule.source) {
+ universalRule.source = universal.source
+ }
+
+ universal.remove()
+ }
+
+ let backdropRule = universalRule.clone({
+ selectors: ['::backdrop'],
+ })
+
+ universalRule.after(backdropRule)
+ }
+ }
+}
diff --git a/node_modules/tailwindcss/src/lib/setupContextUtils.js b/node_modules/tailwindcss/src/lib/setupContextUtils.js
new file mode 100644
index 0000000..59c261d
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/setupContextUtils.js
@@ -0,0 +1,1342 @@
+import fs from 'fs'
+import url from 'url'
+import postcss from 'postcss'
+import dlv from 'dlv'
+import selectorParser from 'postcss-selector-parser'
+
+import transformThemeValue from '../util/transformThemeValue'
+import parseObjectStyles from '../util/parseObjectStyles'
+import prefixSelector from '../util/prefixSelector'
+import isPlainObject from '../util/isPlainObject'
+import escapeClassName from '../util/escapeClassName'
+import nameClass, { formatClass } from '../util/nameClass'
+import { coerceValue } from '../util/pluginUtils'
+import { variantPlugins, corePlugins } from '../corePlugins'
+import * as sharedState from './sharedState'
+import { env } from './sharedState'
+import { toPath } from '../util/toPath'
+import log from '../util/log'
+import negateValue from '../util/negateValue'
+import isSyntacticallyValidPropertyValue from '../util/isSyntacticallyValidPropertyValue'
+import { generateRules, getClassNameFromSelector } from './generateRules'
+import { hasContentChanged } from './cacheInvalidation.js'
+import { Offsets } from './offsets.js'
+import { flagEnabled } from '../featureFlags.js'
+import { finalizeSelector, formatVariantSelector } from '../util/formatVariantSelector'
+
+export const INTERNAL_FEATURES = Symbol()
+
+const VARIANT_TYPES = {
+ AddVariant: Symbol.for('ADD_VARIANT'),
+ MatchVariant: Symbol.for('MATCH_VARIANT'),
+}
+
+const VARIANT_INFO = {
+ Base: 1 << 0,
+ Dynamic: 1 << 1,
+}
+
+function prefix(context, selector) {
+ let prefix = context.tailwindConfig.prefix
+ return typeof prefix === 'function' ? prefix(selector) : prefix + selector
+}
+
+function normalizeOptionTypes({ type = 'any', ...options }) {
+ let types = [].concat(type)
+
+ return {
+ ...options,
+ types: types.map((type) => {
+ if (Array.isArray(type)) {
+ return { type: type[0], ...type[1] }
+ }
+ return { type, preferOnConflict: false }
+ }),
+ }
+}
+
+function parseVariantFormatString(input) {
+ /** @type {string[]} */
+ let parts = []
+
+ // When parsing whitespace around special characters are insignificant
+ // However, _inside_ of a variant they could be
+ // Because the selector could look like this
+ // @media { &[data-name="foo bar"] }
+ // This is why we do not skip whitespace
+
+ let current = ''
+ let depth = 0
+
+ for (let idx = 0; idx < input.length; idx++) {
+ let char = input[idx]
+
+ if (char === '\\') {
+ // Escaped characters are not special
+ current += '\\' + input[++idx]
+ } else if (char === '{') {
+ // Nested rule: start
+ ++depth
+ parts.push(current.trim())
+ current = ''
+ } else if (char === '}') {
+ // Nested rule: end
+ if (--depth < 0) {
+ throw new Error(`Your { and } are unbalanced.`)
+ }
+
+ parts.push(current.trim())
+ current = ''
+ } else {
+ // Normal character
+ current += char
+ }
+ }
+
+ if (current.length > 0) {
+ parts.push(current.trim())
+ }
+
+ parts = parts.filter((part) => part !== '')
+
+ return parts
+}
+
+function insertInto(list, value, { before = [] } = {}) {
+ before = [].concat(before)
+
+ if (before.length <= 0) {
+ list.push(value)
+ return
+ }
+
+ let idx = list.length - 1
+ for (let other of before) {
+ let iidx = list.indexOf(other)
+ if (iidx === -1) continue
+ idx = Math.min(idx, iidx)
+ }
+
+ list.splice(idx, 0, value)
+}
+
+function parseStyles(styles) {
+ if (!Array.isArray(styles)) {
+ return parseStyles([styles])
+ }
+
+ return styles.flatMap((style) => {
+ let isNode = !Array.isArray(style) && !isPlainObject(style)
+ return isNode ? style : parseObjectStyles(style)
+ })
+}
+
+function getClasses(selector, mutate) {
+ let parser = selectorParser((selectors) => {
+ let allClasses = []
+
+ if (mutate) {
+ mutate(selectors)
+ }
+
+ selectors.walkClasses((classNode) => {
+ allClasses.push(classNode.value)
+ })
+
+ return allClasses
+ })
+ return parser.transformSync(selector)
+}
+
+/**
+ * Ignore everything inside a :not(...). This allows you to write code like
+ * `div:not(.foo)`. If `.foo` is never found in your code, then we used to
+ * not generated it. But now we will ignore everything inside a `:not`, so
+ * that it still gets generated.
+ *
+ * @param {selectorParser.Root} selectors
+ */
+function ignoreNot(selectors) {
+ selectors.walkPseudos((pseudo) => {
+ if (pseudo.value === ':not') {
+ pseudo.remove()
+ }
+ })
+}
+
+function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) {
+ let classes = []
+ let selectors = []
+
+ if (node.type === 'rule') {
+ // Handle normal rules
+ selectors.push(...node.selectors)
+ } else if (node.type === 'atrule') {
+ // Handle at-rules (which contains nested rules)
+ node.walkRules((rule) => selectors.push(...rule.selectors))
+ }
+
+ for (let selector of selectors) {
+ let classCandidates = getClasses(selector, ignoreNot)
+
+ // At least one of the selectors contains non-"on-demandable" candidates.
+ if (classCandidates.length === 0) {
+ state.containsNonOnDemandable = true
+ }
+
+ for (let classCandidate of classCandidates) {
+ classes.push(classCandidate)
+ }
+ }
+
+ if (depth === 0) {
+ return [state.containsNonOnDemandable || classes.length === 0, classes]
+ }
+
+ return classes
+}
+
+function withIdentifiers(styles) {
+ return parseStyles(styles).flatMap((node) => {
+ let nodeMap = new Map()
+ let [containsNonOnDemandableSelectors, candidates] = extractCandidates(node)
+
+ // If this isn't "on-demandable", assign it a universal candidate to always include it.
+ if (containsNonOnDemandableSelectors) {
+ candidates.unshift(sharedState.NOT_ON_DEMAND)
+ }
+
+ // However, it could be that it also contains "on-demandable" candidates.
+ // E.g.: `span, .foo {}`, in that case it should still be possible to use
+ // `@apply foo` for example.
+ return candidates.map((c) => {
+ if (!nodeMap.has(node)) {
+ nodeMap.set(node, node)
+ }
+ return [c, nodeMap.get(node)]
+ })
+ })
+}
+
+export function isValidVariantFormatString(format) {
+ return format.startsWith('@') || format.includes('&')
+}
+
+export function parseVariant(variant) {
+ variant = variant
+ .replace(/\n+/g, '')
+ .replace(/\s{1,}/g, ' ')
+ .trim()
+
+ let fns = parseVariantFormatString(variant)
+ .map((str) => {
+ if (!str.startsWith('@')) {
+ return ({ format }) => format(str)
+ }
+
+ let [, name, params] = /@(\S*)( .+|[({].*)?/g.exec(str)
+ return ({ wrap }) => wrap(postcss.atRule({ name, params: params?.trim() ?? '' }))
+ })
+ .reverse()
+
+ return (api) => {
+ for (let fn of fns) {
+ fn(api)
+ }
+ }
+}
+
+/**
+ *
+ * @param {any} tailwindConfig
+ * @param {any} context
+ * @param {object} param2
+ * @param {Offsets} param2.offsets
+ */
+function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offsets, classList }) {
+ function getConfigValue(path, defaultValue) {
+ return path ? dlv(tailwindConfig, path, defaultValue) : tailwindConfig
+ }
+
+ function applyConfiguredPrefix(selector) {
+ return prefixSelector(tailwindConfig.prefix, selector)
+ }
+
+ function prefixIdentifier(identifier, options) {
+ if (identifier === sharedState.NOT_ON_DEMAND) {
+ return sharedState.NOT_ON_DEMAND
+ }
+
+ if (!options.respectPrefix) {
+ return identifier
+ }
+
+ return context.tailwindConfig.prefix + identifier
+ }
+
+ function resolveThemeValue(path, defaultValue, opts = {}) {
+ let parts = toPath(path)
+ let value = getConfigValue(['theme', ...parts], defaultValue)
+ return transformThemeValue(parts[0])(value, opts)
+ }
+
+ let variantIdentifier = 0
+ let api = {
+ postcss,
+ prefix: applyConfiguredPrefix,
+ e: escapeClassName,
+ config: getConfigValue,
+ theme: resolveThemeValue,
+ corePlugins: (path) => {
+ if (Array.isArray(tailwindConfig.corePlugins)) {
+ return tailwindConfig.corePlugins.includes(path)
+ }
+
+ return getConfigValue(['corePlugins', path], true)
+ },
+ variants: () => {
+ // Preserved for backwards compatibility but not used in v3.0+
+ return []
+ },
+ addBase(base) {
+ for (let [identifier, rule] of withIdentifiers(base)) {
+ let prefixedIdentifier = prefixIdentifier(identifier, {})
+ let offset = offsets.create('base')
+
+ if (!context.candidateRuleMap.has(prefixedIdentifier)) {
+ context.candidateRuleMap.set(prefixedIdentifier, [])
+ }
+
+ context.candidateRuleMap
+ .get(prefixedIdentifier)
+ .push([{ sort: offset, layer: 'base' }, rule])
+ }
+ },
+ /**
+ * @param {string} group
+ * @param {Record<string, string | string[]>} declarations
+ */
+ addDefaults(group, declarations) {
+ const groups = {
+ [`@defaults ${group}`]: declarations,
+ }
+
+ for (let [identifier, rule] of withIdentifiers(groups)) {
+ let prefixedIdentifier = prefixIdentifier(identifier, {})
+
+ if (!context.candidateRuleMap.has(prefixedIdentifier)) {
+ context.candidateRuleMap.set(prefixedIdentifier, [])
+ }
+
+ context.candidateRuleMap
+ .get(prefixedIdentifier)
+ .push([{ sort: offsets.create('defaults'), layer: 'defaults' }, rule])
+ }
+ },
+ addComponents(components, options) {
+ let defaultOptions = {
+ preserveSource: false,
+ respectPrefix: true,
+ respectImportant: false,
+ }
+
+ options = Object.assign({}, defaultOptions, Array.isArray(options) ? {} : options)
+
+ for (let [identifier, rule] of withIdentifiers(components)) {
+ let prefixedIdentifier = prefixIdentifier(identifier, options)
+
+ classList.add(prefixedIdentifier)
+
+ if (!context.candidateRuleMap.has(prefixedIdentifier)) {
+ context.candidateRuleMap.set(prefixedIdentifier, [])
+ }
+
+ context.candidateRuleMap
+ .get(prefixedIdentifier)
+ .push([{ sort: offsets.create('components'), layer: 'components', options }, rule])
+ }
+ },
+ addUtilities(utilities, options) {
+ let defaultOptions = {
+ preserveSource: false,
+ respectPrefix: true,
+ respectImportant: true,
+ }
+
+ options = Object.assign({}, defaultOptions, Array.isArray(options) ? {} : options)
+
+ for (let [identifier, rule] of withIdentifiers(utilities)) {
+ let prefixedIdentifier = prefixIdentifier(identifier, options)
+
+ classList.add(prefixedIdentifier)
+
+ if (!context.candidateRuleMap.has(prefixedIdentifier)) {
+ context.candidateRuleMap.set(prefixedIdentifier, [])
+ }
+
+ context.candidateRuleMap
+ .get(prefixedIdentifier)
+ .push([{ sort: offsets.create('utilities'), layer: 'utilities', options }, rule])
+ }
+ },
+ matchUtilities: function (utilities, options) {
+ let defaultOptions = {
+ respectPrefix: true,
+ respectImportant: true,
+ modifiers: false,
+ }
+
+ options = normalizeOptionTypes({ ...defaultOptions, ...options })
+
+ let offset = offsets.create('utilities')
+
+ for (let identifier in utilities) {
+ let prefixedIdentifier = prefixIdentifier(identifier, options)
+ let rule = utilities[identifier]
+
+ classList.add([prefixedIdentifier, options])
+
+ function wrapped(modifier, { isOnlyPlugin }) {
+ let [value, coercedType, utilityModifier] = coerceValue(
+ options.types,
+ modifier,
+ options,
+ tailwindConfig
+ )
+
+ if (value === undefined) {
+ return []
+ }
+
+ if (!options.types.some(({ type }) => type === coercedType)) {
+ if (isOnlyPlugin) {
+ log.warn([
+ `Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`,
+ `You can safely update it to \`${identifier}-${modifier.replace(
+ coercedType + ':',
+ ''
+ )}\`.`,
+ ])
+ } else {
+ return []
+ }
+ }
+
+ if (!isSyntacticallyValidPropertyValue(value)) {
+ return []
+ }
+
+ let extras = {
+ get modifier() {
+ if (!options.modifiers) {
+ log.warn(`modifier-used-without-options-for-${identifier}`, [
+ 'Your plugin must set `modifiers: true` in its options to support modifiers.',
+ ])
+ }
+
+ return utilityModifier
+ },
+ }
+
+ let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
+
+ let ruleSets = []
+ .concat(modifiersEnabled ? rule(value, extras) : rule(value))
+ .filter(Boolean)
+ .map((declaration) => ({
+ [nameClass(identifier, modifier)]: declaration,
+ }))
+
+ return ruleSets
+ }
+
+ let withOffsets = [{ sort: offset, layer: 'utilities', options }, wrapped]
+
+ if (!context.candidateRuleMap.has(prefixedIdentifier)) {
+ context.candidateRuleMap.set(prefixedIdentifier, [])
+ }
+
+ context.candidateRuleMap.get(prefixedIdentifier).push(withOffsets)
+ }
+ },
+ matchComponents: function (components, options) {
+ let defaultOptions = {
+ respectPrefix: true,
+ respectImportant: false,
+ modifiers: false,
+ }
+
+ options = normalizeOptionTypes({ ...defaultOptions, ...options })
+
+ let offset = offsets.create('components')
+
+ for (let identifier in components) {
+ let prefixedIdentifier = prefixIdentifier(identifier, options)
+ let rule = components[identifier]
+
+ classList.add([prefixedIdentifier, options])
+
+ function wrapped(modifier, { isOnlyPlugin }) {
+ let [value, coercedType, utilityModifier] = coerceValue(
+ options.types,
+ modifier,
+ options,
+ tailwindConfig
+ )
+
+ if (value === undefined) {
+ return []
+ }
+
+ if (!options.types.some(({ type }) => type === coercedType)) {
+ if (isOnlyPlugin) {
+ log.warn([
+ `Unnecessary typehint \`${coercedType}\` in \`${identifier}-${modifier}\`.`,
+ `You can safely update it to \`${identifier}-${modifier.replace(
+ coercedType + ':',
+ ''
+ )}\`.`,
+ ])
+ } else {
+ return []
+ }
+ }
+
+ if (!isSyntacticallyValidPropertyValue(value)) {
+ return []
+ }
+
+ let extras = {
+ get modifier() {
+ if (!options.modifiers) {
+ log.warn(`modifier-used-without-options-for-${identifier}`, [
+ 'Your plugin must set `modifiers: true` in its options to support modifiers.',
+ ])
+ }
+
+ return utilityModifier
+ },
+ }
+
+ let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
+
+ let ruleSets = []
+ .concat(modifiersEnabled ? rule(value, extras) : rule(value))
+ .filter(Boolean)
+ .map((declaration) => ({
+ [nameClass(identifier, modifier)]: declaration,
+ }))
+
+ return ruleSets
+ }
+
+ let withOffsets = [{ sort: offset, layer: 'components', options }, wrapped]
+
+ if (!context.candidateRuleMap.has(prefixedIdentifier)) {
+ context.candidateRuleMap.set(prefixedIdentifier, [])
+ }
+
+ context.candidateRuleMap.get(prefixedIdentifier).push(withOffsets)
+ }
+ },
+ addVariant(variantName, variantFunctions, options = {}) {
+ variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
+ if (typeof variantFunction !== 'string') {
+ // Safelist public API functions
+ return (api = {}) => {
+ let { args, modifySelectors, container, separator, wrap, format } = api
+ let result = variantFunction(
+ Object.assign(
+ { modifySelectors, container, separator },
+ options.type === VARIANT_TYPES.MatchVariant && { args, wrap, format }
+ )
+ )
+
+ if (typeof result === 'string' && !isValidVariantFormatString(result)) {
+ throw new Error(
+ `Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`
+ )
+ }
+
+ if (Array.isArray(result)) {
+ return result
+ .filter((variant) => typeof variant === 'string')
+ .map((variant) => parseVariant(variant))
+ }
+
+ // result may be undefined with legacy variants that use APIs like `modifySelectors`
+ // result may also be a postcss node if someone was returning the result from `modifySelectors`
+ return result && typeof result === 'string' && parseVariant(result)(api)
+ }
+ }
+
+ if (!isValidVariantFormatString(variantFunction)) {
+ throw new Error(
+ `Your custom variant \`${variantName}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`
+ )
+ }
+
+ return parseVariant(variantFunction)
+ })
+
+ insertInto(variantList, variantName, options)
+ variantMap.set(variantName, variantFunctions)
+ context.variantOptions.set(variantName, options)
+ },
+ matchVariant(variant, variantFn, options) {
+ // A unique identifier that "groups" these variants together.
+ // This is for internal use only which is why it is not present in the types
+ let id = options?.id ?? ++variantIdentifier
+ let isSpecial = variant === '@'
+
+ let modifiersEnabled = flagEnabled(tailwindConfig, 'generalizedModifiers')
+
+ for (let [key, value] of Object.entries(options?.values ?? {})) {
+ if (key === 'DEFAULT') continue
+
+ api.addVariant(
+ isSpecial ? `${variant}${key}` : `${variant}-${key}`,
+ ({ args, container }) => {
+ return variantFn(
+ value,
+ modifiersEnabled ? { modifier: args?.modifier, container } : { container }
+ )
+ },
+
+ {
+ ...options,
+ value,
+ id,
+ type: VARIANT_TYPES.MatchVariant,
+ variantInfo: VARIANT_INFO.Base,
+ }
+ )
+ }
+
+ let hasDefault = 'DEFAULT' in (options?.values ?? {})
+
+ api.addVariant(
+ variant,
+ ({ args, container }) => {
+ if (args?.value === sharedState.NONE && !hasDefault) {
+ return null
+ }
+
+ return variantFn(
+ args?.value === sharedState.NONE
+ ? options.values.DEFAULT
+ : // Falling back to args if it is a string, otherwise '' for older intellisense
+ // (JetBrains) plugins.
+ args?.value ?? (typeof args === 'string' ? args : ''),
+ modifiersEnabled ? { modifier: args?.modifier, container } : { container }
+ )
+ },
+ {
+ ...options,
+ id,
+ type: VARIANT_TYPES.MatchVariant,
+ variantInfo: VARIANT_INFO.Dynamic,
+ }
+ )
+ },
+ }
+
+ return api
+}
+
+let fileModifiedMapCache = new WeakMap()
+export function getFileModifiedMap(context) {
+ if (!fileModifiedMapCache.has(context)) {
+ fileModifiedMapCache.set(context, new Map())
+ }
+ return fileModifiedMapCache.get(context)
+}
+
+function trackModified(files, fileModifiedMap) {
+ let changed = false
+ let mtimesToCommit = new Map()
+
+ for (let file of files) {
+ if (!file) continue
+
+ let parsed = url.parse(file)
+ let pathname = parsed.hash ? parsed.href.replace(parsed.hash, '') : parsed.href
+ pathname = parsed.search ? pathname.replace(parsed.search, '') : pathname
+ let newModified = fs.statSync(decodeURIComponent(pathname), { throwIfNoEntry: false })?.mtimeMs
+ if (!newModified) {
+ // It could happen that a file is passed in that doesn't exist. E.g.:
+ // postcss-cli will provide you a fake path when reading from stdin. This
+ // path then looks like /path-to-your-project/stdin In that case we just
+ // want to ignore it and don't track changes at all.
+ continue
+ }
+
+ if (!fileModifiedMap.has(file) || newModified > fileModifiedMap.get(file)) {
+ changed = true
+ }
+
+ mtimesToCommit.set(file, newModified)
+ }
+
+ return [changed, mtimesToCommit]
+}
+
+function extractVariantAtRules(node) {
+ node.walkAtRules((atRule) => {
+ if (['responsive', 'variants'].includes(atRule.name)) {
+ extractVariantAtRules(atRule)
+ atRule.before(atRule.nodes)
+ atRule.remove()
+ }
+ })
+}
+
+function collectLayerPlugins(root) {
+ let layerPlugins = []
+
+ root.each((node) => {
+ if (node.type === 'atrule' && ['responsive', 'variants'].includes(node.name)) {
+ node.name = 'layer'
+ node.params = 'utilities'
+ }
+ })
+
+ // Walk @layer rules and treat them like plugins
+ root.walkAtRules('layer', (layerRule) => {
+ extractVariantAtRules(layerRule)
+
+ if (layerRule.params === 'base') {
+ for (let node of layerRule.nodes) {
+ layerPlugins.push(function ({ addBase }) {
+ addBase(node, { respectPrefix: false })
+ })
+ }
+ layerRule.remove()
+ } else if (layerRule.params === 'components') {
+ for (let node of layerRule.nodes) {
+ layerPlugins.push(function ({ addComponents }) {
+ addComponents(node, { respectPrefix: false, preserveSource: true })
+ })
+ }
+ layerRule.remove()
+ } else if (layerRule.params === 'utilities') {
+ for (let node of layerRule.nodes) {
+ layerPlugins.push(function ({ addUtilities }) {
+ addUtilities(node, { respectPrefix: false, preserveSource: true })
+ })
+ }
+ layerRule.remove()
+ }
+ })
+
+ return layerPlugins
+}
+
+function resolvePlugins(context, root) {
+ let corePluginList = Object.entries({ ...variantPlugins, ...corePlugins })
+ .map(([name, plugin]) => {
+ if (!context.tailwindConfig.corePlugins.includes(name)) {
+ return null
+ }
+
+ return plugin
+ })
+ .filter(Boolean)
+
+ let userPlugins = context.tailwindConfig.plugins.map((plugin) => {
+ if (plugin.__isOptionsFunction) {
+ plugin = plugin()
+ }
+
+ return typeof plugin === 'function' ? plugin : plugin.handler
+ })
+
+ let layerPlugins = collectLayerPlugins(root)
+
+ // TODO: This is a workaround for backwards compatibility, since custom variants
+ // were historically sorted before screen/stackable variants.
+ let beforeVariants = [
+ variantPlugins['pseudoElementVariants'],
+ variantPlugins['pseudoClassVariants'],
+ variantPlugins['ariaVariants'],
+ variantPlugins['dataVariants'],
+ ]
+ let afterVariants = [
+ variantPlugins['supportsVariants'],
+ variantPlugins['directionVariants'],
+ variantPlugins['reducedMotionVariants'],
+ variantPlugins['prefersContrastVariants'],
+ variantPlugins['darkVariants'],
+ variantPlugins['printVariant'],
+ variantPlugins['screenVariants'],
+ variantPlugins['orientationVariants'],
+ ]
+
+ return [...corePluginList, ...beforeVariants, ...userPlugins, ...afterVariants, ...layerPlugins]
+}
+
+function registerPlugins(plugins, context) {
+ let variantList = []
+ let variantMap = new Map()
+ context.variantMap = variantMap
+
+ let offsets = new Offsets()
+ context.offsets = offsets
+
+ let classList = new Set()
+
+ let pluginApi = buildPluginApi(context.tailwindConfig, context, {
+ variantList,
+ variantMap,
+ offsets,
+ classList,
+ })
+
+ for (let plugin of plugins) {
+ if (Array.isArray(plugin)) {
+ for (let pluginItem of plugin) {
+ pluginItem(pluginApi)
+ }
+ } else {
+ plugin?.(pluginApi)
+ }
+ }
+
+ // Make sure to record bit masks for every variant
+ offsets.recordVariants(variantList, (variant) => variantMap.get(variant).length)
+
+ // Build variantMap
+ for (let [variantName, variantFunctions] of variantMap.entries()) {
+ context.variantMap.set(
+ variantName,
+ variantFunctions.map((variantFunction, idx) => [
+ offsets.forVariant(variantName, idx),
+ variantFunction,
+ ])
+ )
+ }
+
+ let safelist = (context.tailwindConfig.safelist ?? []).filter(Boolean)
+ if (safelist.length > 0) {
+ let checks = []
+
+ for (let value of safelist) {
+ if (typeof value === 'string') {
+ context.changedContent.push({ content: value, extension: 'html' })
+ continue
+ }
+
+ if (value instanceof RegExp) {
+ log.warn('root-regex', [
+ 'Regular expressions in `safelist` work differently in Tailwind CSS v3.0.',
+ 'Update your `safelist` configuration to eliminate this warning.',
+ 'https://tailwindcss.com/docs/content-configuration#safelisting-classes',
+ ])
+ continue
+ }
+
+ checks.push(value)
+ }
+
+ if (checks.length > 0) {
+ let patternMatchingCount = new Map()
+ let prefixLength = context.tailwindConfig.prefix.length
+ let checkImportantUtils = checks.some((check) => check.pattern.source.includes('!'))
+
+ for (let util of classList) {
+ let utils = Array.isArray(util)
+ ? (() => {
+ let [utilName, options] = util
+ let values = Object.keys(options?.values ?? {})
+ let classes = values.map((value) => formatClass(utilName, value))
+
+ if (options?.supportsNegativeValues) {
+ // This is the normal negated version
+ // e.g. `-inset-1` or `-tw-inset-1`
+ classes = [...classes, ...classes.map((cls) => '-' + cls)]
+
+ // This is the negated version *after* the prefix
+ // e.g. `tw--inset-1`
+ // The prefix is already attached to util name
+ // So we add the negative after the prefix
+ classes = [
+ ...classes,
+ ...classes.map(
+ (cls) => cls.slice(0, prefixLength) + '-' + cls.slice(prefixLength)
+ ),
+ ]
+ }
+
+ if (options.types.some(({ type }) => type === 'color')) {
+ classes = [
+ ...classes,
+ ...classes.flatMap((cls) =>
+ Object.keys(context.tailwindConfig.theme.opacity).map(
+ (opacity) => `${cls}/${opacity}`
+ )
+ ),
+ ]
+ }
+
+ if (checkImportantUtils && options?.respectImportant) {
+ classes = [...classes, ...classes.map((cls) => '!' + cls)]
+ }
+
+ return classes
+ })()
+ : [util]
+
+ for (let util of utils) {
+ for (let { pattern, variants = [] } of checks) {
+ // RegExp with the /g flag are stateful, so let's reset the last
+ // index pointer to reset the state.
+ pattern.lastIndex = 0
+
+ if (!patternMatchingCount.has(pattern)) {
+ patternMatchingCount.set(pattern, 0)
+ }
+
+ if (!pattern.test(util)) continue
+
+ patternMatchingCount.set(pattern, patternMatchingCount.get(pattern) + 1)
+
+ context.changedContent.push({ content: util, extension: 'html' })
+ for (let variant of variants) {
+ context.changedContent.push({
+ content: variant + context.tailwindConfig.separator + util,
+ extension: 'html',
+ })
+ }
+ }
+ }
+ }
+
+ for (let [regex, count] of patternMatchingCount.entries()) {
+ if (count !== 0) continue
+
+ log.warn([
+ `The safelist pattern \`${regex}\` doesn't match any Tailwind CSS classes.`,
+ 'Fix this pattern or remove it from your `safelist` configuration.',
+ 'https://tailwindcss.com/docs/content-configuration#safelisting-classes',
+ ])
+ }
+ }
+ }
+
+ let darkClassName = [].concat(context.tailwindConfig.darkMode ?? 'media')[1] ?? 'dark'
+
+ // A list of utilities that are used by certain Tailwind CSS utilities but
+ // that don't exist on their own. This will result in them "not existing" and
+ // sorting could be weird since you still require them in order to make the
+ // host utilities work properly. (Thanks Biology)
+ let parasiteUtilities = [
+ prefix(context, darkClassName),
+ prefix(context, 'group'),
+ prefix(context, 'peer'),
+ ]
+ context.getClassOrder = function getClassOrder(classes) {
+ // Sort classes so they're ordered in a deterministic manner
+ let sorted = [...classes].sort((a, z) => {
+ if (a === z) return 0
+ if (a < z) return -1
+ return 1
+ })
+
+ // Non-util classes won't be generated, so we default them to null
+ let sortedClassNames = new Map(sorted.map((className) => [className, null]))
+
+ // Sort all classes in order
+ // Non-tailwind classes won't be generated and will be left as `null`
+ let rules = generateRules(new Set(sorted), context, true)
+ rules = context.offsets.sort(rules)
+
+ let idx = BigInt(parasiteUtilities.length)
+
+ for (const [, rule] of rules) {
+ let candidate = rule.raws.tailwind.candidate
+
+ // When multiple rules match a candidate
+ // always take the position of the first one
+ sortedClassNames.set(candidate, sortedClassNames.get(candidate) ?? idx++)
+ }
+
+ return classes.map((className) => {
+ let order = sortedClassNames.get(className) ?? null
+ let parasiteIndex = parasiteUtilities.indexOf(className)
+
+ if (order === null && parasiteIndex !== -1) {
+ // This will make sure that it is at the very beginning of the
+ // `components` layer which technically means 'before any
+ // components'.
+ order = BigInt(parasiteIndex)
+ }
+
+ return [className, order]
+ })
+ }
+
+ // Generate a list of strings for autocompletion purposes, e.g.
+ // ['uppercase', 'lowercase', ...]
+ context.getClassList = function getClassList(options = {}) {
+ let output = []
+
+ for (let util of classList) {
+ if (Array.isArray(util)) {
+ let [utilName, utilOptions] = util
+ let negativeClasses = []
+
+ let modifiers = Object.keys(utilOptions?.modifiers ?? {})
+
+ if (utilOptions?.types?.some(({ type }) => type === 'color')) {
+ modifiers.push(...Object.keys(context.tailwindConfig.theme.opacity ?? {}))
+ }
+
+ let metadata = { modifiers }
+ let includeMetadata = options.includeMetadata && modifiers.length > 0
+
+ for (let [key, value] of Object.entries(utilOptions?.values ?? {})) {
+ // Ignore undefined and null values
+ if (value == null) {
+ continue
+ }
+
+ let cls = formatClass(utilName, key)
+ output.push(includeMetadata ? [cls, metadata] : cls)
+
+ if (utilOptions?.supportsNegativeValues && negateValue(value)) {
+ let cls = formatClass(utilName, `-${key}`)
+ negativeClasses.push(includeMetadata ? [cls, metadata] : cls)
+ }
+ }
+
+ output.push(...negativeClasses)
+ } else {
+ output.push(util)
+ }
+ }
+
+ return output
+ }
+
+ // Generate a list of available variants with meta information of the type of variant.
+ context.getVariants = function getVariants() {
+ let result = []
+ for (let [name, options] of context.variantOptions.entries()) {
+ if (options.variantInfo === VARIANT_INFO.Base) continue
+
+ result.push({
+ name,
+ isArbitrary: options.type === Symbol.for('MATCH_VARIANT'),
+ values: Object.keys(options.values ?? {}),
+ hasDash: name !== '@',
+ selectors({ modifier, value } = {}) {
+ let candidate = '__TAILWIND_PLACEHOLDER__'
+
+ let rule = postcss.rule({ selector: `.${candidate}` })
+ let container = postcss.root({ nodes: [rule.clone()] })
+
+ let before = container.toString()
+
+ let fns = (context.variantMap.get(name) ?? []).flatMap(([_, fn]) => fn)
+ let formatStrings = []
+ for (let fn of fns) {
+ let localFormatStrings = []
+
+ let api = {
+ args: { modifier, value: options.values?.[value] ?? value },
+ separator: context.tailwindConfig.separator,
+ modifySelectors(modifierFunction) {
+ // Run the modifierFunction over each rule
+ container.each((rule) => {
+ if (rule.type !== 'rule') {
+ return
+ }
+
+ rule.selectors = rule.selectors.map((selector) => {
+ return modifierFunction({
+ get className() {
+ return getClassNameFromSelector(selector)
+ },
+ selector,
+ })
+ })
+ })
+
+ return container
+ },
+ format(str) {
+ localFormatStrings.push(str)
+ },
+ wrap(wrapper) {
+ localFormatStrings.push(`@${wrapper.name} ${wrapper.params} { & }`)
+ },
+ container,
+ }
+
+ let ruleWithVariant = fn(api)
+ if (localFormatStrings.length > 0) {
+ formatStrings.push(localFormatStrings)
+ }
+
+ if (Array.isArray(ruleWithVariant)) {
+ for (let variantFunction of ruleWithVariant) {
+ localFormatStrings = []
+ variantFunction(api)
+ formatStrings.push(localFormatStrings)
+ }
+ }
+ }
+
+ // Reverse engineer the result of the `container`
+ let manualFormatStrings = []
+ let after = container.toString()
+
+ if (before !== after) {
+ // Figure out all selectors
+ container.walkRules((rule) => {
+ let modified = rule.selector
+
+ // Rebuild the base selector, this is what plugin authors would do
+ // as well. E.g.: `${variant}${separator}${className}`.
+ // However, plugin authors probably also prepend or append certain
+ // classes, pseudos, ids, ...
+ let rebuiltBase = selectorParser((selectors) => {
+ selectors.walkClasses((classNode) => {
+ classNode.value = `${name}${context.tailwindConfig.separator}${classNode.value}`
+ })
+ }).processSync(modified)
+
+ // Now that we know the original selector, the new selector, and
+ // the rebuild part in between, we can replace the part that plugin
+ // authors need to rebuild with `&`, and eventually store it in the
+ // collectedFormats. Similar to what `format('...')` would do.
+ //
+ // E.g.:
+ // variant: foo
+ // selector: .markdown > p
+ // modified (by plugin): .foo .foo\\:markdown > p
+ // rebuiltBase (internal): .foo\\:markdown > p
+ // format: .foo &
+ manualFormatStrings.push(modified.replace(rebuiltBase, '&').replace(candidate, '&'))
+ })
+
+ // Figure out all atrules
+ container.walkAtRules((atrule) => {
+ manualFormatStrings.push(`@${atrule.name} (${atrule.params}) { & }`)
+ })
+ }
+
+ let isArbitraryVariant = !(value in (options.values ?? {}))
+ let internalFeatures = options[INTERNAL_FEATURES] ?? {}
+
+ let respectPrefix = (() => {
+ if (isArbitraryVariant) return false
+ if (internalFeatures.respectPrefix === false) return false
+ return true
+ })()
+
+ formatStrings = formatStrings.map((format) =>
+ format.map((str) => ({
+ format: str,
+ respectPrefix,
+ }))
+ )
+
+ manualFormatStrings = manualFormatStrings.map((format) => ({
+ format,
+ respectPrefix,
+ }))
+
+ let opts = {
+ candidate,
+ context,
+ }
+
+ let result = formatStrings.map((formats) =>
+ finalizeSelector(`.${candidate}`, formatVariantSelector(formats, opts), opts)
+ .replace(`.${candidate}`, '&')
+ .replace('{ & }', '')
+ .trim()
+ )
+
+ if (manualFormatStrings.length > 0) {
+ result.push(
+ formatVariantSelector(manualFormatStrings, opts)
+ .toString()
+ .replace(`.${candidate}`, '&')
+ )
+ }
+
+ return result
+ },
+ })
+ }
+
+ return result
+ }
+}
+
+/**
+ * Mark as class as retroactively invalid
+ *
+ *
+ * @param {string} candidate
+ */
+function markInvalidUtilityCandidate(context, candidate) {
+ if (!context.classCache.has(candidate)) {
+ return
+ }
+
+ // Mark this as not being a real utility
+ context.notClassCache.add(candidate)
+
+ // Remove it from any candidate-specific caches
+ context.classCache.delete(candidate)
+ context.applyClassCache.delete(candidate)
+ context.candidateRuleMap.delete(candidate)
+ context.candidateRuleCache.delete(candidate)
+
+ // Ensure the stylesheet gets rebuilt
+ context.stylesheetCache = null
+}
+
+/**
+ * Mark as class as retroactively invalid
+ *
+ * @param {import('postcss').Node} node
+ */
+function markInvalidUtilityNode(context, node) {
+ let candidate = node.raws.tailwind.candidate
+
+ if (!candidate) {
+ return
+ }
+
+ for (const entry of context.ruleCache) {
+ if (entry[1].raws.tailwind.candidate === candidate) {
+ context.ruleCache.delete(entry)
+ // context.postCssNodeCache.delete(node)
+ }
+ }
+
+ markInvalidUtilityCandidate(context, candidate)
+}
+
+export function createContext(tailwindConfig, changedContent = [], root = postcss.root()) {
+ let context = {
+ disposables: [],
+ ruleCache: new Set(),
+ candidateRuleCache: new Map(),
+ classCache: new Map(),
+ applyClassCache: new Map(),
+ // Seed the not class cache with the blocklist (which is only strings)
+ notClassCache: new Set(tailwindConfig.blocklist ?? []),
+ postCssNodeCache: new Map(),
+ candidateRuleMap: new Map(),
+ tailwindConfig,
+ changedContent: changedContent,
+ variantMap: new Map(),
+ stylesheetCache: null,
+ variantOptions: new Map(),
+
+ markInvalidUtilityCandidate: (candidate) => markInvalidUtilityCandidate(context, candidate),
+ markInvalidUtilityNode: (node) => markInvalidUtilityNode(context, node),
+ }
+
+ let resolvedPlugins = resolvePlugins(context, root)
+ registerPlugins(resolvedPlugins, context)
+
+ return context
+}
+
+let contextMap = sharedState.contextMap
+let configContextMap = sharedState.configContextMap
+let contextSourcesMap = sharedState.contextSourcesMap
+
+export function getContext(
+ root,
+ result,
+ tailwindConfig,
+ userConfigPath,
+ tailwindConfigHash,
+ contextDependencies
+) {
+ let sourcePath = result.opts.from
+ let isConfigFile = userConfigPath !== null
+
+ env.DEBUG && console.log('Source path:', sourcePath)
+
+ let existingContext
+
+ if (isConfigFile && contextMap.has(sourcePath)) {
+ existingContext = contextMap.get(sourcePath)
+ } else if (configContextMap.has(tailwindConfigHash)) {
+ let context = configContextMap.get(tailwindConfigHash)
+ contextSourcesMap.get(context).add(sourcePath)
+ contextMap.set(sourcePath, context)
+
+ existingContext = context
+ }
+
+ let cssDidChange = hasContentChanged(sourcePath, root)
+
+ // If there's already a context in the cache and we don't need to
+ // reset the context, return the cached context.
+ if (existingContext) {
+ let [contextDependenciesChanged, mtimesToCommit] = trackModified(
+ [...contextDependencies],
+ getFileModifiedMap(existingContext)
+ )
+ if (!contextDependenciesChanged && !cssDidChange) {
+ return [existingContext, false, mtimesToCommit]
+ }
+ }
+
+ // If this source is in the context map, get the old context.
+ // Remove this source from the context sources for the old context,
+ // and clean up that context if no one else is using it. This can be
+ // called by many processes in rapid succession, so we check for presence
+ // first because the first process to run this code will wipe it out first.
+ if (contextMap.has(sourcePath)) {
+ let oldContext = contextMap.get(sourcePath)
+ if (contextSourcesMap.has(oldContext)) {
+ contextSourcesMap.get(oldContext).delete(sourcePath)
+ if (contextSourcesMap.get(oldContext).size === 0) {
+ contextSourcesMap.delete(oldContext)
+ for (let [tailwindConfigHash, context] of configContextMap) {
+ if (context === oldContext) {
+ configContextMap.delete(tailwindConfigHash)
+ }
+ }
+ for (let disposable of oldContext.disposables.splice(0)) {
+ disposable(oldContext)
+ }
+ }
+ }
+ }
+
+ env.DEBUG && console.log('Setting up new context...')
+
+ let context = createContext(tailwindConfig, [], root)
+
+ Object.assign(context, {
+ userConfigPath,
+ })
+
+ let [, mtimesToCommit] = trackModified([...contextDependencies], getFileModifiedMap(context))
+
+ // ---
+
+ // Update all context tracking state
+
+ configContextMap.set(tailwindConfigHash, context)
+ contextMap.set(sourcePath, context)
+
+ if (!contextSourcesMap.has(context)) {
+ contextSourcesMap.set(context, new Set())
+ }
+
+ contextSourcesMap.get(context).add(sourcePath)
+
+ return [context, true, mtimesToCommit]
+}
diff --git a/node_modules/tailwindcss/src/lib/setupTrackingContext.js b/node_modules/tailwindcss/src/lib/setupTrackingContext.js
new file mode 100644
index 0000000..70e7cb6
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/setupTrackingContext.js
@@ -0,0 +1,169 @@
+// @ts-check
+
+import fs from 'fs'
+import LRU from '@alloc/quick-lru'
+
+import hash from '../util/hashConfig'
+import resolveConfig from '../public/resolve-config'
+import resolveConfigPath from '../util/resolveConfigPath'
+import { getContext, getFileModifiedMap } from './setupContextUtils'
+import parseDependency from '../util/parseDependency'
+import { validateConfig } from '../util/validateConfig.js'
+import { parseCandidateFiles, resolvedChangedContent } from './content.js'
+import { loadConfig } from '../lib/load-config'
+import getModuleDependencies from './getModuleDependencies'
+
+let configPathCache = new LRU({ maxSize: 100 })
+
+let candidateFilesCache = new WeakMap()
+
+function getCandidateFiles(context, tailwindConfig) {
+ if (candidateFilesCache.has(context)) {
+ return candidateFilesCache.get(context)
+ }
+
+ let candidateFiles = parseCandidateFiles(context, tailwindConfig)
+
+ return candidateFilesCache.set(context, candidateFiles).get(context)
+}
+
+// Get the config object based on a path
+function getTailwindConfig(configOrPath) {
+ let userConfigPath = resolveConfigPath(configOrPath)
+
+ if (userConfigPath !== null) {
+ let [prevConfig, prevConfigHash, prevDeps, prevModified] =
+ configPathCache.get(userConfigPath) || []
+
+ let newDeps = getModuleDependencies(userConfigPath)
+
+ let modified = false
+ let newModified = new Map()
+ for (let file of newDeps) {
+ let time = fs.statSync(file).mtimeMs
+ newModified.set(file, time)
+ if (!prevModified || !prevModified.has(file) || time > prevModified.get(file)) {
+ modified = true
+ }
+ }
+
+ // It hasn't changed (based on timestamps)
+ if (!modified) {
+ return [prevConfig, userConfigPath, prevConfigHash, prevDeps]
+ }
+
+ // It has changed (based on timestamps), or first run
+ for (let file of newDeps) {
+ delete require.cache[file]
+ }
+ let newConfig = validateConfig(resolveConfig(loadConfig(userConfigPath)))
+ let newHash = hash(newConfig)
+ configPathCache.set(userConfigPath, [newConfig, newHash, newDeps, newModified])
+ return [newConfig, userConfigPath, newHash, newDeps]
+ }
+
+ // It's a plain object, not a path
+ let newConfig = resolveConfig(configOrPath?.config ?? configOrPath ?? {})
+
+ newConfig = validateConfig(newConfig)
+
+ return [newConfig, null, hash(newConfig), []]
+}
+
+// DISABLE_TOUCH = TRUE
+
+// Retrieve an existing context from cache if possible (since contexts are unique per
+// source path), or set up a new one (including setting up watchers and registering
+// plugins) then return it
+export default function setupTrackingContext(configOrPath) {
+ return ({ tailwindDirectives, registerDependency }) => {
+ return (root, result) => {
+ let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] =
+ getTailwindConfig(configOrPath)
+
+ let contextDependencies = new Set(configDependencies)
+
+ // If there are no @tailwind or @apply rules, we don't consider this CSS
+ // file or its dependencies to be dependencies of the context. Can reuse
+ // the context even if they change. We may want to think about `@layer`
+ // being part of this trigger too, but it's tough because it's impossible
+ // for a layer in one file to end up in the actual @tailwind rule in
+ // another file since independent sources are effectively isolated.
+ if (tailwindDirectives.size > 0) {
+ // Add current css file as a context dependencies.
+ contextDependencies.add(result.opts.from)
+
+ // Add all css @import dependencies as context dependencies.
+ for (let message of result.messages) {
+ if (message.type === 'dependency') {
+ contextDependencies.add(message.file)
+ }
+ }
+ }
+
+ let [context, , mTimesToCommit] = getContext(
+ root,
+ result,
+ tailwindConfig,
+ userConfigPath,
+ tailwindConfigHash,
+ contextDependencies
+ )
+
+ let fileModifiedMap = getFileModifiedMap(context)
+
+ let candidateFiles = getCandidateFiles(context, tailwindConfig)
+
+ // If there are no @tailwind or @apply rules, we don't consider this CSS file or it's
+ // dependencies to be dependencies of the context. Can reuse the context even if they change.
+ // We may want to think about `@layer` being part of this trigger too, but it's tough
+ // because it's impossible for a layer in one file to end up in the actual @tailwind rule
+ // in another file since independent sources are effectively isolated.
+ if (tailwindDirectives.size > 0) {
+ // Add template paths as postcss dependencies.
+ for (let contentPath of candidateFiles) {
+ for (let dependency of parseDependency(contentPath)) {
+ registerDependency(dependency)
+ }
+ }
+
+ let [changedContent, contentMTimesToCommit] = resolvedChangedContent(
+ context,
+ candidateFiles,
+ fileModifiedMap
+ )
+
+ for (let content of changedContent) {
+ context.changedContent.push(content)
+ }
+
+ // Add the mtimes of the content files to the commit list
+ // We can overwrite the existing values because unconditionally
+ // This is because:
+ // 1. Most of the files here won't be in the map yet
+ // 2. If they are that means it's a context dependency
+ // and we're reading this after the context. This means
+ // that the mtime we just read is strictly >= the context
+ // mtime. Unless the user / os is doing something weird
+ // in which the mtime would be going backwards. If that
+ // happens there's already going to be problems.
+ for (let [path, mtime] of contentMTimesToCommit.entries()) {
+ mTimesToCommit.set(path, mtime)
+ }
+ }
+
+ for (let file of configDependencies) {
+ registerDependency({ type: 'dependency', file })
+ }
+
+ // "commit" the new modified time for all context deps
+ // We do this here because we want content tracking to
+ // read the "old" mtime even when it's a context dependency.
+ for (let [path, mtime] of mTimesToCommit.entries()) {
+ fileModifiedMap.set(path, mtime)
+ }
+
+ return context
+ }
+ }
+}
diff --git a/node_modules/tailwindcss/src/lib/sharedState.js b/node_modules/tailwindcss/src/lib/sharedState.js
new file mode 100644
index 0000000..97bdf09
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/sharedState.js
@@ -0,0 +1,61 @@
+import pkg from '../../package.json'
+
+export const env =
+ typeof process !== 'undefined'
+ ? {
+ NODE_ENV: process.env.NODE_ENV,
+ DEBUG: resolveDebug(process.env.DEBUG),
+ ENGINE: pkg.tailwindcss.engine,
+ }
+ : {
+ NODE_ENV: 'production',
+ DEBUG: false,
+ ENGINE: pkg.tailwindcss.engine,
+ }
+
+export const contextMap = new Map()
+export const configContextMap = new Map()
+export const contextSourcesMap = new Map()
+export const sourceHashMap = new Map()
+export const NOT_ON_DEMAND = new String('*')
+
+export const NONE = Symbol('__NONE__')
+
+export function resolveDebug(debug) {
+ if (debug === undefined) {
+ return false
+ }
+
+ // Environment variables are strings, so convert to boolean
+ if (debug === 'true' || debug === '1') {
+ return true
+ }
+
+ if (debug === 'false' || debug === '0') {
+ return false
+ }
+
+ // Keep the debug convention into account:
+ // DEBUG=* -> This enables all debug modes
+ // DEBUG=projectA,projectB,projectC -> This enables debug for projectA, projectB and projectC
+ // DEBUG=projectA:* -> This enables all debug modes for projectA (if you have sub-types)
+ // DEBUG=projectA,-projectB -> This enables debug for projectA and explicitly disables it for projectB
+
+ if (debug === '*') {
+ return true
+ }
+
+ let debuggers = debug.split(',').map((d) => d.split(':')[0])
+
+ // Ignoring tailwindcss
+ if (debuggers.includes('-tailwindcss')) {
+ return false
+ }
+
+ // Including tailwindcss
+ if (debuggers.includes('tailwindcss')) {
+ return true
+ }
+
+ return false
+}
diff --git a/node_modules/tailwindcss/src/lib/substituteScreenAtRules.js b/node_modules/tailwindcss/src/lib/substituteScreenAtRules.js
new file mode 100644
index 0000000..5a45cff
--- /dev/null
+++ b/node_modules/tailwindcss/src/lib/substituteScreenAtRules.js
@@ -0,0 +1,19 @@
+import { normalizeScreens } from '../util/normalizeScreens'
+import buildMediaQuery from '../util/buildMediaQuery'
+
+export default function ({ tailwindConfig: { theme } }) {
+ return function (css) {
+ css.walkAtRules('screen', (atRule) => {
+ let screen = atRule.params
+ let screens = normalizeScreens(theme.screens)
+ let screenDefinition = screens.find(({ name }) => name === screen)
+
+ if (!screenDefinition) {
+ throw atRule.error(`No \`${screen}\` screen found.`)
+ }
+
+ atRule.name = 'media'
+ atRule.params = buildMediaQuery(screenDefinition)
+ })
+ }
+}