From b1e2c8fd5cb5dfa46bc440a12eafaf56cd844b1c Mon Sep 17 00:00:00 2001 From: Philipp Tanlak Date: Mon, 24 Nov 2025 20:54:57 +0100 Subject: Docs --- .../tailwindcss/src/cli-peer-dependencies.js | 15 + node_modules/tailwindcss/src/cli.js | 7 + node_modules/tailwindcss/src/cli/build/deps.js | 56 + node_modules/tailwindcss/src/cli/build/index.js | 49 + node_modules/tailwindcss/src/cli/build/plugin.js | 444 +++ node_modules/tailwindcss/src/cli/build/utils.js | 76 + node_modules/tailwindcss/src/cli/build/watching.js | 229 ++ node_modules/tailwindcss/src/cli/help/index.js | 70 + node_modules/tailwindcss/src/cli/index.js | 216 ++ node_modules/tailwindcss/src/cli/init/index.js | 79 + node_modules/tailwindcss/src/corePluginList.js | 1 + node_modules/tailwindcss/src/corePlugins.js | 2855 ++++++++++++++++++++ node_modules/tailwindcss/src/css/LICENSE | 25 + node_modules/tailwindcss/src/css/preflight.css | 378 +++ node_modules/tailwindcss/src/featureFlags.js | 69 + node_modules/tailwindcss/src/index.js | 1 + .../tailwindcss/src/lib/cacheInvalidation.js | 52 + .../tailwindcss/src/lib/collapseAdjacentRules.js | 58 + .../src/lib/collapseDuplicateDeclarations.js | 93 + node_modules/tailwindcss/src/lib/content.js | 208 ++ .../tailwindcss/src/lib/defaultExtractor.js | 216 ++ node_modules/tailwindcss/src/lib/detectNesting.js | 47 + .../src/lib/evaluateTailwindFunctions.js | 272 ++ .../tailwindcss/src/lib/expandApplyAtRules.js | 620 +++++ .../tailwindcss/src/lib/expandTailwindAtRules.js | 297 ++ .../tailwindcss/src/lib/findAtConfigPath.js | 48 + node_modules/tailwindcss/src/lib/generateRules.js | 936 +++++++ .../tailwindcss/src/lib/getModuleDependencies.js | 79 + node_modules/tailwindcss/src/lib/load-config.ts | 31 + .../src/lib/normalizeTailwindDirectives.js | 84 + node_modules/tailwindcss/src/lib/offsets.js | 373 +++ .../tailwindcss/src/lib/partitionApplyAtRules.js | 52 + node_modules/tailwindcss/src/lib/regex.js | 74 + node_modules/tailwindcss/src/lib/remap-bitfield.js | 82 + .../tailwindcss/src/lib/resolveDefaultsAtRules.js | 163 ++ .../tailwindcss/src/lib/setupContextUtils.js | 1342 +++++++++ .../tailwindcss/src/lib/setupTrackingContext.js | 169 ++ node_modules/tailwindcss/src/lib/sharedState.js | 61 + .../tailwindcss/src/lib/substituteScreenAtRules.js | 19 + node_modules/tailwindcss/src/oxide/cli.ts | 1 + .../tailwindcss/src/oxide/cli/build/deps.ts | 91 + .../tailwindcss/src/oxide/cli/build/index.ts | 47 + .../tailwindcss/src/oxide/cli/build/plugin.ts | 442 +++ .../tailwindcss/src/oxide/cli/build/utils.ts | 74 + .../tailwindcss/src/oxide/cli/build/watching.ts | 225 ++ .../tailwindcss/src/oxide/cli/help/index.ts | 69 + node_modules/tailwindcss/src/oxide/cli/index.ts | 204 ++ .../tailwindcss/src/oxide/cli/init/index.ts | 59 + .../tailwindcss/src/oxide/postcss-plugin.ts | 1 + node_modules/tailwindcss/src/plugin.js | 107 + .../src/postcss-plugins/nesting/README.md | 42 + .../src/postcss-plugins/nesting/index.js | 13 + .../src/postcss-plugins/nesting/plugin.js | 80 + .../tailwindcss/src/processTailwindFeatures.js | 59 + node_modules/tailwindcss/src/public/colors.js | 322 +++ .../tailwindcss/src/public/create-plugin.js | 2 + .../tailwindcss/src/public/default-config.js | 4 + .../tailwindcss/src/public/default-theme.js | 4 + node_modules/tailwindcss/src/public/load-config.js | 2 + .../tailwindcss/src/public/resolve-config.js | 7 + .../tailwindcss/src/util/applyImportantSelector.js | 27 + node_modules/tailwindcss/src/util/bigSign.js | 3 + .../tailwindcss/src/util/buildMediaQuery.js | 22 + node_modules/tailwindcss/src/util/cloneDeep.js | 11 + node_modules/tailwindcss/src/util/cloneNodes.js | 28 + node_modules/tailwindcss/src/util/color.js | 88 + node_modules/tailwindcss/src/util/colorNames.js | 150 + .../tailwindcss/src/util/configurePlugins.js | 23 + node_modules/tailwindcss/src/util/createPlugin.js | 27 + .../tailwindcss/src/util/createUtilityPlugin.js | 37 + node_modules/tailwindcss/src/util/dataTypes.js | 394 +++ node_modules/tailwindcss/src/util/defaults.js | 17 + .../tailwindcss/src/util/escapeClassName.js | 8 + node_modules/tailwindcss/src/util/escapeCommas.js | 3 + .../tailwindcss/src/util/flattenColorPalette.js | 13 + .../tailwindcss/src/util/formatVariantSelector.js | 324 +++ node_modules/tailwindcss/src/util/getAllConfigs.js | 38 + node_modules/tailwindcss/src/util/hashConfig.js | 5 + .../tailwindcss/src/util/isKeyframeRule.js | 3 + node_modules/tailwindcss/src/util/isPlainObject.js | 8 + .../src/util/isSyntacticallyValidPropertyValue.js | 61 + node_modules/tailwindcss/src/util/log.js | 29 + node_modules/tailwindcss/src/util/nameClass.js | 30 + node_modules/tailwindcss/src/util/negateValue.js | 24 + .../tailwindcss/src/util/normalizeConfig.js | 301 +++ .../tailwindcss/src/util/normalizeScreens.js | 140 + .../tailwindcss/src/util/parseAnimationValue.js | 68 + .../tailwindcss/src/util/parseBoxShadowValue.js | 72 + .../tailwindcss/src/util/parseDependency.js | 44 + node_modules/tailwindcss/src/util/parseGlob.js | 24 + .../tailwindcss/src/util/parseObjectStyles.js | 19 + node_modules/tailwindcss/src/util/pluginUtils.js | 291 ++ .../tailwindcss/src/util/prefixSelector.js | 33 + .../tailwindcss/src/util/pseudoElements.js | 167 ++ .../tailwindcss/src/util/removeAlphaVariables.js | 24 + node_modules/tailwindcss/src/util/resolveConfig.js | 277 ++ .../tailwindcss/src/util/resolveConfigPath.js | 66 + node_modules/tailwindcss/src/util/responsive.js | 10 + .../tailwindcss/src/util/splitAtTopLevelOnly.js | 52 + node_modules/tailwindcss/src/util/tap.js | 4 + node_modules/tailwindcss/src/util/toColorValue.js | 3 + node_modules/tailwindcss/src/util/toPath.js | 26 + .../tailwindcss/src/util/transformThemeValue.js | 62 + .../tailwindcss/src/util/validateConfig.js | 26 + .../tailwindcss/src/util/validateFormalSyntax.js | 34 + .../tailwindcss/src/util/withAlphaVariable.js | 49 + node_modules/tailwindcss/src/value-parser/LICENSE | 22 + .../tailwindcss/src/value-parser/README.md | 3 + .../tailwindcss/src/value-parser/index.d.ts | 177 ++ node_modules/tailwindcss/src/value-parser/index.js | 28 + node_modules/tailwindcss/src/value-parser/parse.js | 303 +++ .../tailwindcss/src/value-parser/stringify.js | 41 + node_modules/tailwindcss/src/value-parser/unit.js | 118 + node_modules/tailwindcss/src/value-parser/walk.js | 18 + 114 files changed, 15676 insertions(+) create mode 100644 node_modules/tailwindcss/src/cli-peer-dependencies.js create mode 100644 node_modules/tailwindcss/src/cli.js create mode 100644 node_modules/tailwindcss/src/cli/build/deps.js create mode 100644 node_modules/tailwindcss/src/cli/build/index.js create mode 100644 node_modules/tailwindcss/src/cli/build/plugin.js create mode 100644 node_modules/tailwindcss/src/cli/build/utils.js create mode 100644 node_modules/tailwindcss/src/cli/build/watching.js create mode 100644 node_modules/tailwindcss/src/cli/help/index.js create mode 100644 node_modules/tailwindcss/src/cli/index.js create mode 100644 node_modules/tailwindcss/src/cli/init/index.js create mode 100644 node_modules/tailwindcss/src/corePluginList.js create mode 100644 node_modules/tailwindcss/src/corePlugins.js create mode 100644 node_modules/tailwindcss/src/css/LICENSE create mode 100644 node_modules/tailwindcss/src/css/preflight.css create mode 100644 node_modules/tailwindcss/src/featureFlags.js create mode 100644 node_modules/tailwindcss/src/index.js create mode 100644 node_modules/tailwindcss/src/lib/cacheInvalidation.js create mode 100644 node_modules/tailwindcss/src/lib/collapseAdjacentRules.js create mode 100644 node_modules/tailwindcss/src/lib/collapseDuplicateDeclarations.js create mode 100644 node_modules/tailwindcss/src/lib/content.js create mode 100644 node_modules/tailwindcss/src/lib/defaultExtractor.js create mode 100644 node_modules/tailwindcss/src/lib/detectNesting.js create mode 100644 node_modules/tailwindcss/src/lib/evaluateTailwindFunctions.js create mode 100644 node_modules/tailwindcss/src/lib/expandApplyAtRules.js create mode 100644 node_modules/tailwindcss/src/lib/expandTailwindAtRules.js create mode 100644 node_modules/tailwindcss/src/lib/findAtConfigPath.js create mode 100644 node_modules/tailwindcss/src/lib/generateRules.js create mode 100644 node_modules/tailwindcss/src/lib/getModuleDependencies.js create mode 100644 node_modules/tailwindcss/src/lib/load-config.ts create mode 100644 node_modules/tailwindcss/src/lib/normalizeTailwindDirectives.js create mode 100644 node_modules/tailwindcss/src/lib/offsets.js create mode 100644 node_modules/tailwindcss/src/lib/partitionApplyAtRules.js create mode 100644 node_modules/tailwindcss/src/lib/regex.js create mode 100644 node_modules/tailwindcss/src/lib/remap-bitfield.js create mode 100644 node_modules/tailwindcss/src/lib/resolveDefaultsAtRules.js create mode 100644 node_modules/tailwindcss/src/lib/setupContextUtils.js create mode 100644 node_modules/tailwindcss/src/lib/setupTrackingContext.js create mode 100644 node_modules/tailwindcss/src/lib/sharedState.js create mode 100644 node_modules/tailwindcss/src/lib/substituteScreenAtRules.js create mode 100644 node_modules/tailwindcss/src/oxide/cli.ts create mode 100644 node_modules/tailwindcss/src/oxide/cli/build/deps.ts create mode 100644 node_modules/tailwindcss/src/oxide/cli/build/index.ts create mode 100644 node_modules/tailwindcss/src/oxide/cli/build/plugin.ts create mode 100644 node_modules/tailwindcss/src/oxide/cli/build/utils.ts create mode 100644 node_modules/tailwindcss/src/oxide/cli/build/watching.ts create mode 100644 node_modules/tailwindcss/src/oxide/cli/help/index.ts create mode 100644 node_modules/tailwindcss/src/oxide/cli/index.ts create mode 100644 node_modules/tailwindcss/src/oxide/cli/init/index.ts create mode 100644 node_modules/tailwindcss/src/oxide/postcss-plugin.ts create mode 100644 node_modules/tailwindcss/src/plugin.js create mode 100644 node_modules/tailwindcss/src/postcss-plugins/nesting/README.md create mode 100644 node_modules/tailwindcss/src/postcss-plugins/nesting/index.js create mode 100644 node_modules/tailwindcss/src/postcss-plugins/nesting/plugin.js create mode 100644 node_modules/tailwindcss/src/processTailwindFeatures.js create mode 100644 node_modules/tailwindcss/src/public/colors.js create mode 100644 node_modules/tailwindcss/src/public/create-plugin.js create mode 100644 node_modules/tailwindcss/src/public/default-config.js create mode 100644 node_modules/tailwindcss/src/public/default-theme.js create mode 100644 node_modules/tailwindcss/src/public/load-config.js create mode 100644 node_modules/tailwindcss/src/public/resolve-config.js create mode 100644 node_modules/tailwindcss/src/util/applyImportantSelector.js create mode 100644 node_modules/tailwindcss/src/util/bigSign.js create mode 100644 node_modules/tailwindcss/src/util/buildMediaQuery.js create mode 100644 node_modules/tailwindcss/src/util/cloneDeep.js create mode 100644 node_modules/tailwindcss/src/util/cloneNodes.js create mode 100644 node_modules/tailwindcss/src/util/color.js create mode 100644 node_modules/tailwindcss/src/util/colorNames.js create mode 100644 node_modules/tailwindcss/src/util/configurePlugins.js create mode 100644 node_modules/tailwindcss/src/util/createPlugin.js create mode 100644 node_modules/tailwindcss/src/util/createUtilityPlugin.js create mode 100644 node_modules/tailwindcss/src/util/dataTypes.js create mode 100644 node_modules/tailwindcss/src/util/defaults.js create mode 100644 node_modules/tailwindcss/src/util/escapeClassName.js create mode 100644 node_modules/tailwindcss/src/util/escapeCommas.js create mode 100644 node_modules/tailwindcss/src/util/flattenColorPalette.js create mode 100644 node_modules/tailwindcss/src/util/formatVariantSelector.js create mode 100644 node_modules/tailwindcss/src/util/getAllConfigs.js create mode 100644 node_modules/tailwindcss/src/util/hashConfig.js create mode 100644 node_modules/tailwindcss/src/util/isKeyframeRule.js create mode 100644 node_modules/tailwindcss/src/util/isPlainObject.js create mode 100644 node_modules/tailwindcss/src/util/isSyntacticallyValidPropertyValue.js create mode 100644 node_modules/tailwindcss/src/util/log.js create mode 100644 node_modules/tailwindcss/src/util/nameClass.js create mode 100644 node_modules/tailwindcss/src/util/negateValue.js create mode 100644 node_modules/tailwindcss/src/util/normalizeConfig.js create mode 100644 node_modules/tailwindcss/src/util/normalizeScreens.js create mode 100644 node_modules/tailwindcss/src/util/parseAnimationValue.js create mode 100644 node_modules/tailwindcss/src/util/parseBoxShadowValue.js create mode 100644 node_modules/tailwindcss/src/util/parseDependency.js create mode 100644 node_modules/tailwindcss/src/util/parseGlob.js create mode 100644 node_modules/tailwindcss/src/util/parseObjectStyles.js create mode 100644 node_modules/tailwindcss/src/util/pluginUtils.js create mode 100644 node_modules/tailwindcss/src/util/prefixSelector.js create mode 100644 node_modules/tailwindcss/src/util/pseudoElements.js create mode 100644 node_modules/tailwindcss/src/util/removeAlphaVariables.js create mode 100644 node_modules/tailwindcss/src/util/resolveConfig.js create mode 100644 node_modules/tailwindcss/src/util/resolveConfigPath.js create mode 100644 node_modules/tailwindcss/src/util/responsive.js create mode 100644 node_modules/tailwindcss/src/util/splitAtTopLevelOnly.js create mode 100644 node_modules/tailwindcss/src/util/tap.js create mode 100644 node_modules/tailwindcss/src/util/toColorValue.js create mode 100644 node_modules/tailwindcss/src/util/toPath.js create mode 100644 node_modules/tailwindcss/src/util/transformThemeValue.js create mode 100644 node_modules/tailwindcss/src/util/validateConfig.js create mode 100644 node_modules/tailwindcss/src/util/validateFormalSyntax.js create mode 100644 node_modules/tailwindcss/src/util/withAlphaVariable.js create mode 100644 node_modules/tailwindcss/src/value-parser/LICENSE create mode 100644 node_modules/tailwindcss/src/value-parser/README.md create mode 100644 node_modules/tailwindcss/src/value-parser/index.d.ts create mode 100644 node_modules/tailwindcss/src/value-parser/index.js create mode 100644 node_modules/tailwindcss/src/value-parser/parse.js create mode 100644 node_modules/tailwindcss/src/value-parser/stringify.js create mode 100644 node_modules/tailwindcss/src/value-parser/unit.js create mode 100644 node_modules/tailwindcss/src/value-parser/walk.js (limited to 'node_modules/tailwindcss/src') diff --git a/node_modules/tailwindcss/src/cli-peer-dependencies.js b/node_modules/tailwindcss/src/cli-peer-dependencies.js new file mode 100644 index 0000000..6b9f986 --- /dev/null +++ b/node_modules/tailwindcss/src/cli-peer-dependencies.js @@ -0,0 +1,15 @@ +export function lazyPostcss() { + return require('postcss') +} + +export function lazyPostcssImport() { + return require('postcss-import') +} + +export function lazyAutoprefixer() { + return require('autoprefixer') +} + +export function lazyCssnano() { + return require('cssnano') +} diff --git a/node_modules/tailwindcss/src/cli.js b/node_modules/tailwindcss/src/cli.js new file mode 100644 index 0000000..4b73acc --- /dev/null +++ b/node_modules/tailwindcss/src/cli.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node + +if (__OXIDE__) { + module.exports = require('./oxide/cli') +} else { + module.exports = require('./cli/index') +} diff --git a/node_modules/tailwindcss/src/cli/build/deps.js b/node_modules/tailwindcss/src/cli/build/deps.js new file mode 100644 index 0000000..9435b92 --- /dev/null +++ b/node_modules/tailwindcss/src/cli/build/deps.js @@ -0,0 +1,56 @@ +// @ts-check + +import { + // @ts-ignore + lazyPostcss, + + // @ts-ignore + lazyPostcssImport, + + // @ts-ignore + lazyCssnano, + + // @ts-ignore + lazyAutoprefixer, +} from '../../../peers/index.js' + +/** + * @returns {import('postcss')} + */ +export function loadPostcss() { + // Try to load a local `postcss` version first + try { + return require('postcss') + } catch {} + + return lazyPostcss() +} + +export function loadPostcssImport() { + // Try to load a local `postcss-import` version first + try { + return require('postcss-import') + } catch {} + + return lazyPostcssImport() +} + +export function loadCssNano() { + let options = { preset: ['default', { cssDeclarationSorter: false }] } + + // Try to load a local `cssnano` version first + try { + return require('cssnano') + } catch {} + + return lazyCssnano()(options) +} + +export function loadAutoprefixer() { + // Try to load a local `autoprefixer` version first + try { + return require('autoprefixer') + } catch {} + + return lazyAutoprefixer() +} diff --git a/node_modules/tailwindcss/src/cli/build/index.js b/node_modules/tailwindcss/src/cli/build/index.js new file mode 100644 index 0000000..62c020e --- /dev/null +++ b/node_modules/tailwindcss/src/cli/build/index.js @@ -0,0 +1,49 @@ +// @ts-check + +import fs from 'fs' +import path from 'path' +import { resolveDefaultConfigPath } from '../../util/resolveConfigPath.js' +import { createProcessor } from './plugin.js' + +export async function build(args) { + let input = args['--input'] + let shouldWatch = args['--watch'] + + // TODO: Deprecate this in future versions + if (!input && args['_'][1]) { + console.error('[deprecation] Running tailwindcss without -i, please provide an input file.') + input = args['--input'] = args['_'][1] + } + + if (input && input !== '-' && !fs.existsSync((input = path.resolve(input)))) { + console.error(`Specified input file ${args['--input']} does not exist.`) + process.exit(9) + } + + if (args['--config'] && !fs.existsSync((args['--config'] = path.resolve(args['--config'])))) { + console.error(`Specified config file ${args['--config']} does not exist.`) + process.exit(9) + } + + // TODO: Reference the @config path here if exists + let configPath = args['--config'] ? args['--config'] : resolveDefaultConfigPath() + + let processor = await createProcessor(args, configPath) + + if (shouldWatch) { + // Abort the watcher if stdin is closed to avoid zombie processes + // You can disable this behavior with --watch=always + if (args['--watch'] !== 'always') { + process.stdin.on('end', () => process.exit(0)) + } + + process.stdin.resume() + + await processor.watch() + } else { + await processor.build().catch((e) => { + console.error(e) + process.exit(1) + }) + } +} diff --git a/node_modules/tailwindcss/src/cli/build/plugin.js b/node_modules/tailwindcss/src/cli/build/plugin.js new file mode 100644 index 0000000..6af590d --- /dev/null +++ b/node_modules/tailwindcss/src/cli/build/plugin.js @@ -0,0 +1,444 @@ +// @ts-check + +import path from 'path' +import fs from 'fs' +import postcssrc from 'postcss-load-config' +import { lilconfig } from 'lilconfig' +import loadPlugins from 'postcss-load-config/src/plugins' // Little bit scary, looking at private/internal API +import loadOptions from 'postcss-load-config/src/options' // Little bit scary, looking at private/internal API + +import tailwind from '../../processTailwindFeatures' +import { loadAutoprefixer, loadCssNano, loadPostcss, loadPostcssImport } from './deps' +import { formatNodes, drainStdin, outputFile } from './utils' +import { env } from '../../lib/sharedState' +import resolveConfig from '../../../resolveConfig.js' +import { parseCandidateFiles } from '../../lib/content.js' +import { createWatcher } from './watching.js' +import fastGlob from 'fast-glob' +import { findAtConfigPath } from '../../lib/findAtConfigPath.js' +import log from '../../util/log' +import { loadConfig } from '../../lib/load-config' +import getModuleDependencies from '../../lib/getModuleDependencies' + +/** + * + * @param {string} [customPostCssPath ] + * @returns + */ +async function loadPostCssPlugins(customPostCssPath) { + let config = customPostCssPath + ? await (async () => { + let file = path.resolve(customPostCssPath) + + // Implementation, see: https://unpkg.com/browse/postcss-load-config@3.1.0/src/index.js + // @ts-ignore + let { config = {} } = await lilconfig('postcss').load(file) + if (typeof config === 'function') { + config = config() + } else { + config = Object.assign({}, config) + } + + if (!config.plugins) { + config.plugins = [] + } + + return { + file, + plugins: loadPlugins(config, file), + options: loadOptions(config, file), + } + })() + : await postcssrc() + + let configPlugins = config.plugins + + let configPluginTailwindIdx = configPlugins.findIndex((plugin) => { + if (typeof plugin === 'function' && plugin.name === 'tailwindcss') { + return true + } + + if (typeof plugin === 'object' && plugin !== null && plugin.postcssPlugin === 'tailwindcss') { + return true + } + + return false + }) + + let beforePlugins = + configPluginTailwindIdx === -1 ? [] : configPlugins.slice(0, configPluginTailwindIdx) + let afterPlugins = + configPluginTailwindIdx === -1 + ? configPlugins + : configPlugins.slice(configPluginTailwindIdx + 1) + + return [beforePlugins, afterPlugins, config.options] +} + +function loadBuiltinPostcssPlugins() { + let postcss = loadPostcss() + let IMPORT_COMMENT = '__TAILWIND_RESTORE_IMPORT__: ' + return [ + [ + (root) => { + root.walkAtRules('import', (rule) => { + if (rule.params.slice(1).startsWith('tailwindcss/')) { + rule.after(postcss.comment({ text: IMPORT_COMMENT + rule.params })) + rule.remove() + } + }) + }, + loadPostcssImport(), + (root) => { + root.walkComments((rule) => { + if (rule.text.startsWith(IMPORT_COMMENT)) { + rule.after( + postcss.atRule({ + name: 'import', + params: rule.text.replace(IMPORT_COMMENT, ''), + }) + ) + rule.remove() + } + }) + }, + ], + [], + {}, + ] +} + +let state = { + /** @type {any} */ + context: null, + + /** @type {ReturnType | null} */ + watcher: null, + + /** @type {{content: string, extension: string}[]} */ + changedContent: [], + + /** @type {ReturnType | null} */ + configBag: null, + + contextDependencies: new Set(), + + /** @type {import('../../lib/content.js').ContentPath[]} */ + contentPaths: [], + + refreshContentPaths() { + this.contentPaths = parseCandidateFiles(this.context, this.context?.tailwindConfig) + }, + + get config() { + return this.context.tailwindConfig + }, + + get contentPatterns() { + return { + all: this.contentPaths.map((contentPath) => contentPath.pattern), + dynamic: this.contentPaths + .filter((contentPath) => contentPath.glob !== undefined) + .map((contentPath) => contentPath.pattern), + } + }, + + loadConfig(configPath, content) { + if (this.watcher && configPath) { + this.refreshConfigDependencies() + } + + let config = loadConfig(configPath) + let dependencies = getModuleDependencies(configPath) + this.configBag = { + config, + dependencies, + dispose() { + for (let file of dependencies) { + delete require.cache[require.resolve(file)] + } + }, + } + + // @ts-ignore + this.configBag.config = resolveConfig(this.configBag.config, { content: { files: [] } }) + + // Override content files if `--content` has been passed explicitly + if (content?.length > 0) { + this.configBag.config.content.files = content + } + + return this.configBag.config + }, + + refreshConfigDependencies() { + env.DEBUG && console.time('Module dependencies') + this.configBag?.dispose() + env.DEBUG && console.timeEnd('Module dependencies') + }, + + readContentPaths() { + let content = [] + + // Resolve globs from the content config + // TODO: When we make the postcss plugin async-capable this can become async + let files = fastGlob.sync(this.contentPatterns.all) + + for (let file of files) { + if (__OXIDE__) { + content.push({ + file, + extension: path.extname(file).slice(1), + }) + } else { + content.push({ + content: fs.readFileSync(path.resolve(file), 'utf8'), + extension: path.extname(file).slice(1), + }) + } + } + + // Resolve raw content in the tailwind config + let rawContent = this.config.content.files.filter((file) => { + return file !== null && typeof file === 'object' + }) + + for (let { raw: htmlContent, extension = 'html' } of rawContent) { + content.push({ content: htmlContent, extension }) + } + + return content + }, + + getContext({ createContext, cliConfigPath, root, result, content }) { + if (this.context) { + this.context.changedContent = this.changedContent.splice(0) + + return this.context + } + + env.DEBUG && console.time('Searching for config') + let configPath = findAtConfigPath(root, result) ?? cliConfigPath + env.DEBUG && console.timeEnd('Searching for config') + + env.DEBUG && console.time('Loading config') + let config = this.loadConfig(configPath, content) + env.DEBUG && console.timeEnd('Loading config') + + env.DEBUG && console.time('Creating context') + this.context = createContext(config, []) + Object.assign(this.context, { + userConfigPath: configPath, + }) + env.DEBUG && console.timeEnd('Creating context') + + env.DEBUG && console.time('Resolving content paths') + this.refreshContentPaths() + env.DEBUG && console.timeEnd('Resolving content paths') + + if (this.watcher) { + env.DEBUG && console.time('Watch new files') + this.watcher.refreshWatchedFiles() + env.DEBUG && console.timeEnd('Watch new files') + } + + for (let file of this.readContentPaths()) { + this.context.changedContent.push(file) + } + + return this.context + }, +} + +export async function createProcessor(args, cliConfigPath) { + let postcss = loadPostcss() + + let input = args['--input'] + let output = args['--output'] + let includePostCss = args['--postcss'] + let customPostCssPath = typeof args['--postcss'] === 'string' ? args['--postcss'] : undefined + + let [beforePlugins, afterPlugins, postcssOptions] = includePostCss + ? await loadPostCssPlugins(customPostCssPath) + : loadBuiltinPostcssPlugins() + + if (args['--purge']) { + log.warn('purge-flag-deprecated', [ + 'The `--purge` flag has been deprecated.', + 'Please use `--content` instead.', + ]) + + if (!args['--content']) { + args['--content'] = args['--purge'] + } + } + + let content = args['--content']?.split(/(? { + return { + postcssPlugin: 'tailwindcss', + async Once(root, { result }) { + env.DEBUG && console.time('Compiling CSS') + await tailwind(({ createContext }) => { + console.error() + console.error('Rebuilding...') + + return () => { + return state.getContext({ + createContext, + cliConfigPath, + root, + result, + content, + }) + } + })(root, result) + env.DEBUG && console.timeEnd('Compiling CSS') + }, + } + } + + tailwindPlugin.postcss = true + + let plugins = [ + ...beforePlugins, + tailwindPlugin, + !args['--minify'] && formatNodes, + ...afterPlugins, + !args['--no-autoprefixer'] && loadAutoprefixer(), + args['--minify'] && loadCssNano(), + ].filter(Boolean) + + /** @type {import('postcss').Processor} */ + // @ts-ignore + let processor = postcss(plugins) + + async function readInput() { + // Piping in data, let's drain the stdin + if (input === '-') { + return drainStdin() + } + + // Input file has been provided + if (input) { + return fs.promises.readFile(path.resolve(input), 'utf8') + } + + // No input file provided, fallback to default atrules + return '@tailwind base; @tailwind components; @tailwind utilities' + } + + async function build() { + let start = process.hrtime.bigint() + + return readInput() + .then((css) => processor.process(css, { ...postcssOptions, from: input, to: output })) + .then((result) => { + if (!state.watcher) { + return result + } + + env.DEBUG && console.time('Recording PostCSS dependencies') + for (let message of result.messages) { + if (message.type === 'dependency') { + state.contextDependencies.add(message.file) + } + } + env.DEBUG && console.timeEnd('Recording PostCSS dependencies') + + // TODO: This needs to be in a different spot + env.DEBUG && console.time('Watch new files') + state.watcher.refreshWatchedFiles() + env.DEBUG && console.timeEnd('Watch new files') + + return result + }) + .then((result) => { + if (!output) { + process.stdout.write(result.css) + return + } + + return Promise.all([ + outputFile(result.opts.to, result.css), + result.map && outputFile(result.opts.to + '.map', result.map.toString()), + ]) + }) + .then(() => { + let end = process.hrtime.bigint() + console.error() + console.error('Done in', (end - start) / BigInt(1e6) + 'ms.') + }) + .then( + () => {}, + (err) => { + // TODO: If an initial build fails we can't easily pick up any PostCSS dependencies + // that were collected before the error occurred + // The result is not stored on the error so we have to store it externally + // and pull the messages off of it here somehow + + // This results in a less than ideal DX because the watcher will not pick up + // changes to imported CSS if one of them caused an error during the initial build + // If you fix it and then save the main CSS file so there's no error + // The watcher will start watching the imported CSS files and will be + // resilient to future errors. + + if (state.watcher) { + console.error(err) + } else { + return Promise.reject(err) + } + } + ) + } + + /** + * @param {{file: string, content(): Promise, extension: string}[]} changes + */ + async function parseChanges(changes) { + return Promise.all( + changes.map(async (change) => ({ + content: await change.content(), + extension: change.extension, + })) + ) + } + + if (input !== undefined && input !== '-') { + state.contextDependencies.add(path.resolve(input)) + } + + return { + build, + watch: async () => { + state.watcher = createWatcher(args, { + state, + + /** + * @param {{file: string, content(): Promise, extension: string}[]} changes + */ + async rebuild(changes) { + let needsNewContext = changes.some((change) => { + return ( + state.configBag?.dependencies.has(change.file) || + state.contextDependencies.has(change.file) + ) + }) + + if (needsNewContext) { + state.context = null + } else { + for (let change of await parseChanges(changes)) { + state.changedContent.push(change) + } + } + + return build() + }, + }) + + await build() + }, + } +} diff --git a/node_modules/tailwindcss/src/cli/build/utils.js b/node_modules/tailwindcss/src/cli/build/utils.js new file mode 100644 index 0000000..3462a97 --- /dev/null +++ b/node_modules/tailwindcss/src/cli/build/utils.js @@ -0,0 +1,76 @@ +// @ts-check + +import fs from 'fs' +import path from 'path' + +export function indentRecursive(node, indent = 0) { + node.each && + node.each((child, i) => { + if (!child.raws.before || !child.raws.before.trim() || child.raws.before.includes('\n')) { + child.raws.before = `\n${node.type !== 'rule' && i > 0 ? '\n' : ''}${' '.repeat(indent)}` + } + child.raws.after = `\n${' '.repeat(indent)}` + indentRecursive(child, indent + 1) + }) +} + +export function formatNodes(root) { + indentRecursive(root) + if (root.first) { + root.first.raws.before = '' + } +} + +/** + * When rapidly saving files atomically a couple of situations can happen: + * - The file is missing since the external program has deleted it by the time we've gotten around to reading it from the earlier save. + * - The file is being written to by the external program by the time we're going to read it and is thus treated as busy because a lock is held. + * + * To work around this we retry reading the file a handful of times with a delay between each attempt + * + * @param {string} path + * @param {number} tries + * @returns {Promise} + * @throws {Error} If the file is still missing or busy after the specified number of tries + */ +export async function readFileWithRetries(path, tries = 5) { + for (let n = 0; n <= tries; n++) { + try { + return await fs.promises.readFile(path, 'utf8') + } catch (err) { + if (n !== tries) { + if (err.code === 'ENOENT' || err.code === 'EBUSY') { + await new Promise((resolve) => setTimeout(resolve, 10)) + + continue + } + } + + throw err + } + } +} + +export function drainStdin() { + return new Promise((resolve, reject) => { + let result = '' + process.stdin.on('data', (chunk) => { + result += chunk + }) + process.stdin.on('end', () => resolve(result)) + process.stdin.on('error', (err) => reject(err)) + }) +} + +export async function outputFile(file, newContents) { + try { + let currentContents = await fs.promises.readFile(file, 'utf8') + if (currentContents === newContents) { + return // Skip writing the file + } + } catch {} + + // Write the file + await fs.promises.mkdir(path.dirname(file), { recursive: true }) + await fs.promises.writeFile(file, newContents, 'utf8') +} diff --git a/node_modules/tailwindcss/src/cli/build/watching.js b/node_modules/tailwindcss/src/cli/build/watching.js new file mode 100644 index 0000000..b778872 --- /dev/null +++ b/node_modules/tailwindcss/src/cli/build/watching.js @@ -0,0 +1,229 @@ +// @ts-check + +import chokidar from 'chokidar' +import fs from 'fs' +import micromatch from 'micromatch' +import normalizePath from 'normalize-path' +import path from 'path' + +import { readFileWithRetries } from './utils.js' + +/** + * The core idea of this watcher is: + * 1. Whenever a file is added, changed, or renamed we queue a rebuild + * 2. Perform as few rebuilds as possible by batching them together + * 3. Coalesce events that happen in quick succession to avoid unnecessary rebuilds + * 4. Ensure another rebuild happens _if_ changed while a rebuild is in progress + */ + +/** + * + * @param {*} args + * @param {{ state, rebuild(changedFiles: any[]): Promise }} param1 + * @returns {{ + * fswatcher: import('chokidar').FSWatcher, + * refreshWatchedFiles(): void, + * }} + */ +export function createWatcher(args, { state, rebuild }) { + let shouldPoll = args['--poll'] + let shouldCoalesceWriteEvents = shouldPoll || process.platform === 'win32' + + // Polling interval in milliseconds + // Used only when polling or coalescing add/change events on Windows + let pollInterval = 10 + + let watcher = chokidar.watch([], { + // Force checking for atomic writes in all situations + // This causes chokidar to wait up to 100ms for a file to re-added after it's been unlinked + // This only works when watching directories though + atomic: true, + + usePolling: shouldPoll, + interval: shouldPoll ? pollInterval : undefined, + ignoreInitial: true, + awaitWriteFinish: shouldCoalesceWriteEvents + ? { + stabilityThreshold: 50, + pollInterval: pollInterval, + } + : false, + }) + + // A queue of rebuilds, file reads, etc… to run + let chain = Promise.resolve() + + /** + * A list of files that have been changed since the last rebuild + * + * @type {{file: string, content: () => Promise, extension: string}[]} + */ + let changedContent = [] + + /** + * A list of files for which a rebuild has already been queued. + * This is used to prevent duplicate rebuilds when multiple events are fired for the same file. + * The rebuilt file is cleared from this list when it's associated rebuild has _started_ + * This is because if the file is changed during a rebuild it won't trigger a new rebuild which it should + **/ + let pendingRebuilds = new Set() + + let _timer + let _reject + + /** + * Rebuilds the changed files and resolves when the rebuild is + * complete regardless of whether it was successful or not + */ + async function rebuildAndContinue() { + let changes = changedContent.splice(0) + + // There are no changes to rebuild so we can just do nothing + if (changes.length === 0) { + return Promise.resolve() + } + + // Clear all pending rebuilds for the about-to-be-built files + changes.forEach((change) => pendingRebuilds.delete(change.file)) + + // Resolve the promise even when the rebuild fails + return rebuild(changes).then( + () => {}, + (e) => { + console.error(e.toString()) + } + ) + } + + /** + * + * @param {*} file + * @param {(() => Promise) | null} content + * @param {boolean} skipPendingCheck + * @returns {Promise} + */ + function recordChangedFile(file, content = null, skipPendingCheck = false) { + file = path.resolve(file) + + // Applications like Vim/Neovim fire both rename and change events in succession for atomic writes + // In that case rebuild has already been queued by rename, so can be skipped in change + if (pendingRebuilds.has(file) && !skipPendingCheck) { + return Promise.resolve() + } + + // Mark that a rebuild of this file is going to happen + // It MUST happen synchronously before the rebuild is queued for this to be effective + pendingRebuilds.add(file) + + changedContent.push({ + file, + content: content ?? (() => fs.promises.readFile(file, 'utf8')), + extension: path.extname(file).slice(1), + }) + + if (_timer) { + clearTimeout(_timer) + _reject() + } + + // If a rebuild is already in progress we don't want to start another one until the 10ms timer has expired + chain = chain.then( + () => + new Promise((resolve, reject) => { + _timer = setTimeout(resolve, 10) + _reject = reject + }) + ) + + // Resolves once this file has been rebuilt (or the rebuild for this file has failed) + // This queues as many rebuilds as there are changed files + // But those rebuilds happen after some delay + // And will immediately resolve if there are no changes + chain = chain.then(rebuildAndContinue, rebuildAndContinue) + + return chain + } + + watcher.on('change', (file) => recordChangedFile(file)) + watcher.on('add', (file) => recordChangedFile(file)) + + // Restore watching any files that are "removed" + // This can happen when a file is pseudo-atomically replaced (a copy is created, overwritten, the old one is unlinked, and the new one is renamed) + // TODO: An an optimization we should allow removal when the config changes + watcher.on('unlink', (file) => { + file = normalizePath(file) + + // Only re-add the file if it's not covered by a dynamic pattern + if (!micromatch.some([file], state.contentPatterns.dynamic)) { + watcher.add(file) + } + }) + + // Some applications such as Visual Studio (but not VS Code) + // will only fire a rename event for atomic writes and not a change event + // This is very likely a chokidar bug but it's one we need to work around + // We treat this as a change event and rebuild the CSS + watcher.on('raw', (evt, filePath, meta) => { + if (evt !== 'rename' || filePath === null) { + return + } + + let watchedPath = meta.watchedPath + + // Watched path might be the file itself + // Or the directory it is in + filePath = watchedPath.endsWith(filePath) ? watchedPath : path.join(watchedPath, filePath) + + // Skip this event since the files it is for does not match any of the registered content globs + if (!micromatch.some([filePath], state.contentPatterns.all)) { + return + } + + // Skip since we've already queued a rebuild for this file that hasn't happened yet + if (pendingRebuilds.has(filePath)) { + return + } + + // We'll go ahead and add the file to the pending rebuilds list here + // It'll be removed when the rebuild starts unless the read fails + // which will be taken care of as well + pendingRebuilds.add(filePath) + + async function enqueue() { + try { + // We need to read the file as early as possible outside of the chain + // because it may be gone by the time we get to it. doing the read + // immediately increases the chance that the file is still there + let content = await readFileWithRetries(path.resolve(filePath)) + + if (content === undefined) { + return + } + + // This will push the rebuild onto the chain + // We MUST skip the rebuild check here otherwise the rebuild will never happen on Linux + // This is because the order of events and timing is different on Linux + // @ts-ignore: TypeScript isn't picking up that content is a string here + await recordChangedFile(filePath, () => content, true) + } catch { + // If reading the file fails, it's was probably a deleted temporary file + // So we can ignore it and no rebuild is needed + } + } + + enqueue().then(() => { + // If the file read fails we still need to make sure the file isn't stuck in the pending rebuilds list + pendingRebuilds.delete(filePath) + }) + }) + + return { + fswatcher: watcher, + + refreshWatchedFiles() { + watcher.add(Array.from(state.contextDependencies)) + watcher.add(Array.from(state.configBag.dependencies)) + watcher.add(state.contentPatterns.all) + }, + } +} diff --git a/node_modules/tailwindcss/src/cli/help/index.js b/node_modules/tailwindcss/src/cli/help/index.js new file mode 100644 index 0000000..ea4137a --- /dev/null +++ b/node_modules/tailwindcss/src/cli/help/index.js @@ -0,0 +1,70 @@ +// @ts-check +import packageJson from '../../../package.json' + +export function help({ message, usage, commands, options }) { + let indent = 2 + + // Render header + console.log() + console.log(`${packageJson.name} v${packageJson.version}`) + + // Render message + if (message) { + console.log() + for (let msg of message.split('\n')) { + console.log(msg) + } + } + + // Render usage + if (usage && usage.length > 0) { + console.log() + console.log('Usage:') + for (let example of usage) { + console.log(' '.repeat(indent), example) + } + } + + // Render commands + if (commands && commands.length > 0) { + console.log() + console.log('Commands:') + for (let command of commands) { + console.log(' '.repeat(indent), command) + } + } + + // Render options + if (options) { + let groupedOptions = {} + for (let [key, value] of Object.entries(options)) { + if (typeof value === 'object') { + groupedOptions[key] = { ...value, flags: [key] } + } else { + groupedOptions[value].flags.push(key) + } + } + + console.log() + console.log('Options:') + for (let { flags, description, deprecated } of Object.values(groupedOptions)) { + if (deprecated) continue + + if (flags.length === 1) { + console.log( + ' '.repeat(indent + 4 /* 4 = "-i, ".length */), + flags.slice().reverse().join(', ').padEnd(20, ' '), + description + ) + } else { + console.log( + ' '.repeat(indent), + flags.slice().reverse().join(', ').padEnd(24, ' '), + description + ) + } + } + } + + console.log() +} diff --git a/node_modules/tailwindcss/src/cli/index.js b/node_modules/tailwindcss/src/cli/index.js new file mode 100644 index 0000000..fc1497f --- /dev/null +++ b/node_modules/tailwindcss/src/cli/index.js @@ -0,0 +1,216 @@ +#!/usr/bin/env node + +import path from 'path' +import arg from 'arg' +import fs from 'fs' + +import { build } from './build' +import { help } from './help' +import { init } from './init' + +function oneOf(...options) { + return Object.assign( + (value = true) => { + for (let option of options) { + let parsed = option(value) + if (parsed === value) { + return parsed + } + } + + throw new Error('...') + }, + { manualParsing: true } + ) +} + +let commands = { + init: { + run: init, + args: { + '--esm': { type: Boolean, description: `Initialize configuration file as ESM` }, + '--ts': { type: Boolean, description: `Initialize configuration file as TypeScript` }, + '--postcss': { type: Boolean, description: `Initialize a \`postcss.config.js\` file` }, + '--full': { + type: Boolean, + description: `Include the default values for all options in the generated configuration file`, + }, + '-f': '--full', + '-p': '--postcss', + }, + }, + build: { + run: build, + args: { + '--input': { type: String, description: 'Input file' }, + '--output': { type: String, description: 'Output file' }, + '--watch': { + type: oneOf(String, Boolean), + description: 'Watch for changes and rebuild as needed', + }, + '--poll': { + type: Boolean, + description: 'Use polling instead of filesystem events when watching', + }, + '--content': { + type: String, + description: 'Content paths to use for removing unused classes', + }, + '--purge': { + type: String, + deprecated: true, + }, + '--postcss': { + type: oneOf(String, Boolean), + description: 'Load custom PostCSS configuration', + }, + '--minify': { type: Boolean, description: 'Minify the output' }, + '--config': { + type: String, + description: 'Path to a custom config file', + }, + '--no-autoprefixer': { + type: Boolean, + description: 'Disable autoprefixer', + }, + '-c': '--config', + '-i': '--input', + '-o': '--output', + '-m': '--minify', + '-w': '--watch', + '-p': '--poll', + }, + }, +} + +let sharedFlags = { + '--help': { type: Boolean, description: 'Display usage information' }, + '-h': '--help', +} + +if ( + process.stdout.isTTY /* Detect redirecting output to a file */ && + (process.argv[2] === undefined || + process.argv.slice(2).every((flag) => sharedFlags[flag] !== undefined)) +) { + help({ + usage: [ + 'tailwindcss [--input input.css] [--output output.css] [--watch] [options...]', + 'tailwindcss init [--full] [--postcss] [options...]', + ], + commands: Object.keys(commands) + .filter((command) => command !== 'build') + .map((command) => `${command} [options]`), + options: { ...commands.build.args, ...sharedFlags }, + }) + process.exit(0) +} + +let command = ((arg = '') => (arg.startsWith('-') ? undefined : arg))(process.argv[2]) || 'build' + +if (commands[command] === undefined) { + if (fs.existsSync(path.resolve(command))) { + // TODO: Deprecate this in future versions + // Check if non-existing command, might be a file. + command = 'build' + } else { + help({ + message: `Invalid command: ${command}`, + usage: ['tailwindcss [options]'], + commands: Object.keys(commands) + .filter((command) => command !== 'build') + .map((command) => `${command} [options]`), + options: sharedFlags, + }) + process.exit(1) + } +} + +// Execute command +let { args: flags, run } = commands[command] +let args = (() => { + try { + let result = arg( + Object.fromEntries( + Object.entries({ ...flags, ...sharedFlags }) + .filter(([_key, value]) => !value?.type?.manualParsing) + .map(([key, value]) => [key, typeof value === 'object' ? value.type : value]) + ), + { permissive: true } + ) + + // Manual parsing of flags to allow for special flags like oneOf(Boolean, String) + for (let i = result['_'].length - 1; i >= 0; --i) { + let flag = result['_'][i] + if (!flag.startsWith('-')) continue + + let [flagName, flagValue] = flag.split('=') + let handler = flags[flagName] + + // Resolve flagName & handler + while (typeof handler === 'string') { + flagName = handler + handler = flags[handler] + } + + if (!handler) continue + + let args = [] + let offset = i + 1 + + // --flag value syntax was used so we need to pull `value` from `args` + if (flagValue === undefined) { + // Parse args for current flag + while (result['_'][offset] && !result['_'][offset].startsWith('-')) { + args.push(result['_'][offset++]) + } + + // Cleanup manually parsed flags + args + result['_'].splice(i, 1 + args.length) + + // No args were provided, use default value defined in handler + // One arg was provided, use that directly + // Multiple args were provided so pass them all in an array + flagValue = args.length === 0 ? undefined : args.length === 1 ? args[0] : args + } else { + // Remove the whole flag from the args array + result['_'].splice(i, 1) + } + + // Set the resolved value in the `result` object + result[flagName] = handler.type(flagValue, flagName) + } + + // Ensure that the `command` is always the first argument in the `args`. + // This is important so that we don't have to check if a default command + // (build) was used or not from within each plugin. + // + // E.g.: tailwindcss input.css -> _: ['build', 'input.css'] + // E.g.: tailwindcss build input.css -> _: ['build', 'input.css'] + if (result['_'][0] !== command) { + result['_'].unshift(command) + } + + return result + } catch (err) { + if (err.code === 'ARG_UNKNOWN_OPTION') { + help({ + message: err.message, + usage: ['tailwindcss [options]'], + options: sharedFlags, + }) + process.exit(1) + } + throw err + } +})() + +if (args['--help']) { + help({ + options: { ...flags, ...sharedFlags }, + usage: [`tailwindcss ${command} [options]`], + }) + process.exit(0) +} + +run(args) diff --git a/node_modules/tailwindcss/src/cli/init/index.js b/node_modules/tailwindcss/src/cli/init/index.js new file mode 100644 index 0000000..6bd7e41 --- /dev/null +++ b/node_modules/tailwindcss/src/cli/init/index.js @@ -0,0 +1,79 @@ +// @ts-check + +import fs from 'fs' +import path from 'path' + +function isESM() { + const pkgPath = path.resolve('./package.json') + + try { + let pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) + return pkg.type && pkg.type === 'module' + } catch (err) { + return false + } +} + +export function init(args) { + let messages = [] + + let isProjectESM = args['--ts'] || args['--esm'] || isESM() + let syntax = args['--ts'] ? 'ts' : isProjectESM ? 'js' : 'cjs' + let extension = args['--ts'] ? 'ts' : 'js' + + let tailwindConfigLocation = path.resolve(args['_'][1] ?? `./tailwind.config.${extension}`) + + if (fs.existsSync(tailwindConfigLocation)) { + messages.push(`${path.basename(tailwindConfigLocation)} already exists.`) + } else { + let stubContentsFile = fs.readFileSync( + args['--full'] + ? path.resolve(__dirname, '../../../stubs/config.full.js') + : path.resolve(__dirname, '../../../stubs/config.simple.js'), + 'utf8' + ) + + let stubFile = fs.readFileSync( + path.resolve(__dirname, `../../../stubs/tailwind.config.${syntax}`), + 'utf8' + ) + + // Change colors import + stubContentsFile = stubContentsFile.replace('../colors', 'tailwindcss/colors') + + // Replace contents of {ts,js,cjs} file with the stub {simple,full}. + stubFile = + stubFile + .replace('__CONFIG__', stubContentsFile.replace('module.exports =', '').trim()) + .trim() + '\n\n' + + fs.writeFileSync(tailwindConfigLocation, stubFile, 'utf8') + + messages.push(`Created Tailwind CSS config file: ${path.basename(tailwindConfigLocation)}`) + } + + if (args['--postcss']) { + let postcssConfigLocation = path.resolve('./postcss.config.js') + if (fs.existsSync(postcssConfigLocation)) { + messages.push(`${path.basename(postcssConfigLocation)} already exists.`) + } else { + let stubFile = fs.readFileSync( + isProjectESM + ? path.resolve(__dirname, '../../../stubs/postcss.config.js') + : path.resolve(__dirname, '../../../stubs/postcss.config.cjs'), + 'utf8' + ) + + fs.writeFileSync(postcssConfigLocation, stubFile, 'utf8') + + messages.push(`Created PostCSS config file: ${path.basename(postcssConfigLocation)}`) + } + } + + if (messages.length > 0) { + console.log() + for (let message of messages) { + console.log(message) + } + } +} diff --git a/node_modules/tailwindcss/src/corePluginList.js b/node_modules/tailwindcss/src/corePluginList.js new file mode 100644 index 0000000..f172ceb --- /dev/null +++ b/node_modules/tailwindcss/src/corePluginList.js @@ -0,0 +1 @@ +export default ["preflight","container","accessibility","pointerEvents","visibility","position","inset","isolation","zIndex","order","gridColumn","gridColumnStart","gridColumnEnd","gridRow","gridRowStart","gridRowEnd","float","clear","margin","boxSizing","lineClamp","display","aspectRatio","height","maxHeight","minHeight","width","minWidth","maxWidth","flex","flexShrink","flexGrow","flexBasis","tableLayout","captionSide","borderCollapse","borderSpacing","transformOrigin","translate","rotate","skew","scale","transform","animation","cursor","touchAction","userSelect","resize","scrollSnapType","scrollSnapAlign","scrollSnapStop","scrollMargin","scrollPadding","listStylePosition","listStyleType","listStyleImage","appearance","columns","breakBefore","breakInside","breakAfter","gridAutoColumns","gridAutoFlow","gridAutoRows","gridTemplateColumns","gridTemplateRows","flexDirection","flexWrap","placeContent","placeItems","alignContent","alignItems","justifyContent","justifyItems","gap","space","divideWidth","divideStyle","divideColor","divideOpacity","placeSelf","alignSelf","justifySelf","overflow","overscrollBehavior","scrollBehavior","textOverflow","hyphens","whitespace","wordBreak","borderRadius","borderWidth","borderStyle","borderColor","borderOpacity","backgroundColor","backgroundOpacity","backgroundImage","gradientColorStops","boxDecorationBreak","backgroundSize","backgroundAttachment","backgroundClip","backgroundPosition","backgroundRepeat","backgroundOrigin","fill","stroke","strokeWidth","objectFit","objectPosition","padding","textAlign","textIndent","verticalAlign","fontFamily","fontSize","fontWeight","textTransform","fontStyle","fontVariantNumeric","lineHeight","letterSpacing","textColor","textOpacity","textDecoration","textDecorationColor","textDecorationStyle","textDecorationThickness","textUnderlineOffset","fontSmoothing","placeholderColor","placeholderOpacity","caretColor","accentColor","opacity","backgroundBlendMode","mixBlendMode","boxShadow","boxShadowColor","outlineStyle","outlineWidth","outlineOffset","outlineColor","ringWidth","ringColor","ringOpacity","ringOffsetWidth","ringOffsetColor","blur","brightness","contrast","dropShadow","grayscale","hueRotate","invert","saturate","sepia","filter","backdropBlur","backdropBrightness","backdropContrast","backdropGrayscale","backdropHueRotate","backdropInvert","backdropOpacity","backdropSaturate","backdropSepia","backdropFilter","transitionProperty","transitionDelay","transitionDuration","transitionTimingFunction","willChange","content"] \ No newline at end of file diff --git a/node_modules/tailwindcss/src/corePlugins.js b/node_modules/tailwindcss/src/corePlugins.js new file mode 100644 index 0000000..5db1fdb --- /dev/null +++ b/node_modules/tailwindcss/src/corePlugins.js @@ -0,0 +1,2855 @@ +import fs from 'fs' +import * as path from 'path' +import postcss from 'postcss' +import createUtilityPlugin from './util/createUtilityPlugin' +import buildMediaQuery from './util/buildMediaQuery' +import escapeClassName from './util/escapeClassName' +import parseAnimationValue from './util/parseAnimationValue' +import flattenColorPalette from './util/flattenColorPalette' +import withAlphaVariable, { withAlphaValue } from './util/withAlphaVariable' +import toColorValue from './util/toColorValue' +import isPlainObject from './util/isPlainObject' +import transformThemeValue from './util/transformThemeValue' +import { version as tailwindVersion } from '../package.json' +import log from './util/log' +import { + normalizeScreens, + isScreenSortable, + compareScreens, + toScreen, +} from './util/normalizeScreens' +import { formatBoxShadowValue, parseBoxShadowValue } from './util/parseBoxShadowValue' +import { removeAlphaVariables } from './util/removeAlphaVariables' +import { flagEnabled } from './featureFlags' +import { normalize } from './util/dataTypes' +import { INTERNAL_FEATURES } from './lib/setupContextUtils' + +export let variantPlugins = { + pseudoElementVariants: ({ addVariant }) => { + addVariant('first-letter', '&::first-letter') + addVariant('first-line', '&::first-line') + + addVariant('marker', [ + ({ container }) => { + removeAlphaVariables(container, ['--tw-text-opacity']) + + return '& *::marker' + }, + ({ container }) => { + removeAlphaVariables(container, ['--tw-text-opacity']) + + return '&::marker' + }, + ]) + + addVariant('selection', ['& *::selection', '&::selection']) + + addVariant('file', '&::file-selector-button') + + addVariant('placeholder', '&::placeholder') + + addVariant('backdrop', '&::backdrop') + + addVariant('before', ({ container }) => { + container.walkRules((rule) => { + let foundContent = false + rule.walkDecls('content', () => { + foundContent = true + }) + + if (!foundContent) { + rule.prepend(postcss.decl({ prop: 'content', value: 'var(--tw-content)' })) + } + }) + + return '&::before' + }) + + addVariant('after', ({ container }) => { + container.walkRules((rule) => { + let foundContent = false + rule.walkDecls('content', () => { + foundContent = true + }) + + if (!foundContent) { + rule.prepend(postcss.decl({ prop: 'content', value: 'var(--tw-content)' })) + } + }) + + return '&::after' + }) + }, + + pseudoClassVariants: ({ addVariant, matchVariant, config, prefix }) => { + let pseudoVariants = [ + // Positional + ['first', '&:first-child'], + ['last', '&:last-child'], + ['only', '&:only-child'], + ['odd', '&:nth-child(odd)'], + ['even', '&:nth-child(even)'], + 'first-of-type', + 'last-of-type', + 'only-of-type', + + // State + [ + 'visited', + ({ container }) => { + removeAlphaVariables(container, [ + '--tw-text-opacity', + '--tw-border-opacity', + '--tw-bg-opacity', + ]) + + return '&:visited' + }, + ], + 'target', + ['open', '&[open]'], + + // Forms + 'default', + 'checked', + 'indeterminate', + 'placeholder-shown', + 'autofill', + 'optional', + 'required', + 'valid', + 'invalid', + 'in-range', + 'out-of-range', + 'read-only', + + // Content + 'empty', + + // Interactive + 'focus-within', + [ + 'hover', + !flagEnabled(config(), 'hoverOnlyWhenSupported') + ? '&:hover' + : '@media (hover: hover) and (pointer: fine) { &:hover }', + ], + 'focus', + 'focus-visible', + 'active', + 'enabled', + 'disabled', + ].map((variant) => (Array.isArray(variant) ? variant : [variant, `&:${variant}`])) + + for (let [variantName, state] of pseudoVariants) { + addVariant(variantName, (ctx) => { + let result = typeof state === 'function' ? state(ctx) : state + + return result + }) + } + + let variants = { + group: (_, { modifier }) => + modifier + ? [`:merge(${prefix('.group')}\\/${escapeClassName(modifier)})`, ' &'] + : [`:merge(${prefix('.group')})`, ' &'], + peer: (_, { modifier }) => + modifier + ? [`:merge(${prefix('.peer')}\\/${escapeClassName(modifier)})`, ' ~ &'] + : [`:merge(${prefix('.peer')})`, ' ~ &'], + } + + for (let [name, fn] of Object.entries(variants)) { + matchVariant( + name, + (value = '', extra) => { + let result = normalize(typeof value === 'function' ? value(extra) : value) + if (!result.includes('&')) result = '&' + result + + let [a, b] = fn('', extra) + + let start = null + let end = null + let quotes = 0 + + for (let i = 0; i < result.length; ++i) { + let c = result[i] + if (c === '&') { + start = i + } else if (c === "'" || c === '"') { + quotes += 1 + } else if (start !== null && c === ' ' && !quotes) { + end = i + } + } + + if (start !== null && end === null) { + end = result.length + } + + // Basically this but can handle quotes: + // result.replace(/&(\S+)?/g, (_, pseudo = '') => a + pseudo + b) + + return result.slice(0, start) + a + result.slice(start + 1, end) + b + result.slice(end) + }, + { + values: Object.fromEntries(pseudoVariants), + [INTERNAL_FEATURES]: { + respectPrefix: false, + }, + } + ) + } + }, + + directionVariants: ({ addVariant }) => { + addVariant('ltr', ':is([dir="ltr"] &)') + addVariant('rtl', ':is([dir="rtl"] &)') + }, + + reducedMotionVariants: ({ addVariant }) => { + addVariant('motion-safe', '@media (prefers-reduced-motion: no-preference)') + addVariant('motion-reduce', '@media (prefers-reduced-motion: reduce)') + }, + + darkVariants: ({ config, addVariant }) => { + let [mode, className = '.dark'] = [].concat(config('darkMode', 'media')) + + if (mode === false) { + mode = 'media' + log.warn('darkmode-false', [ + 'The `darkMode` option in your Tailwind CSS configuration is set to `false`, which now behaves the same as `media`.', + 'Change `darkMode` to `media` or remove it entirely.', + 'https://tailwindcss.com/docs/upgrade-guide#remove-dark-mode-configuration', + ]) + } + + if (mode === 'class') { + addVariant('dark', `:is(${className} &)`) + } else if (mode === 'media') { + addVariant('dark', '@media (prefers-color-scheme: dark)') + } + }, + + printVariant: ({ addVariant }) => { + addVariant('print', '@media print') + }, + + screenVariants: ({ theme, addVariant, matchVariant }) => { + let rawScreens = theme('screens') ?? {} + let areSimpleScreens = Object.values(rawScreens).every((v) => typeof v === 'string') + let screens = normalizeScreens(theme('screens')) + + /** @type {Set} */ + let unitCache = new Set([]) + + /** @param {string} value */ + function units(value) { + return value.match(/(\D+)$/)?.[1] ?? '(none)' + } + + /** @param {string} value */ + function recordUnits(value) { + if (value !== undefined) { + unitCache.add(units(value)) + } + } + + /** @param {string} value */ + function canUseUnits(value) { + recordUnits(value) + + // If the cache was empty it'll become 1 because we've just added the current unit + // If the cache was not empty and the units are the same the size doesn't change + // Otherwise, if the units are different from what is already known the size will always be > 1 + return unitCache.size === 1 + } + + for (const screen of screens) { + for (const value of screen.values) { + recordUnits(value.min) + recordUnits(value.max) + } + } + + let screensUseConsistentUnits = unitCache.size <= 1 + + /** + * @typedef {import('./util/normalizeScreens').Screen} Screen + */ + + /** + * @param {'min' | 'max'} type + * @returns {Record} + */ + function buildScreenValues(type) { + return Object.fromEntries( + screens + .filter((screen) => isScreenSortable(screen).result) + .map((screen) => { + let { min, max } = screen.values[0] + + if (type === 'min' && min !== undefined) { + return screen + } else if (type === 'min' && max !== undefined) { + return { ...screen, not: !screen.not } + } else if (type === 'max' && max !== undefined) { + return screen + } else if (type === 'max' && min !== undefined) { + return { ...screen, not: !screen.not } + } + }) + .map((screen) => [screen.name, screen]) + ) + } + + /** + * @param {'min' | 'max'} type + * @returns {(a: { value: string | Screen }, z: { value: string | Screen }) => number} + */ + function buildSort(type) { + return (a, z) => compareScreens(type, a.value, z.value) + } + + let maxSort = buildSort('max') + let minSort = buildSort('min') + + /** @param {'min'|'max'} type */ + function buildScreenVariant(type) { + return (value) => { + if (!areSimpleScreens) { + log.warn('complex-screen-config', [ + 'The `min-*` and `max-*` variants are not supported with a `screens` configuration containing objects.', + ]) + + return [] + } else if (!screensUseConsistentUnits) { + log.warn('mixed-screen-units', [ + 'The `min-*` and `max-*` variants are not supported with a `screens` configuration containing mixed units.', + ]) + + return [] + } else if (typeof value === 'string' && !canUseUnits(value)) { + log.warn('minmax-have-mixed-units', [ + 'The `min-*` and `max-*` variants are not supported with a `screens` configuration containing mixed units.', + ]) + + return [] + } + + return [`@media ${buildMediaQuery(toScreen(value, type))}`] + } + } + + matchVariant('max', buildScreenVariant('max'), { + sort: maxSort, + values: areSimpleScreens ? buildScreenValues('max') : {}, + }) + + // screens and min-* are sorted together when they can be + let id = 'min-screens' + for (let screen of screens) { + addVariant(screen.name, `@media ${buildMediaQuery(screen)}`, { + id, + sort: areSimpleScreens && screensUseConsistentUnits ? minSort : undefined, + value: screen, + }) + } + + matchVariant('min', buildScreenVariant('min'), { + id, + sort: minSort, + }) + }, + + supportsVariants: ({ matchVariant, theme }) => { + matchVariant( + 'supports', + (value = '') => { + let check = normalize(value) + let isRaw = /^\w*\s*\(/.test(check) + + // Chrome has a bug where `(condtion1)or(condition2)` is not valid + // But `(condition1) or (condition2)` is supported. + check = isRaw ? check.replace(/\b(and|or|not)\b/g, ' $1 ') : check + + if (isRaw) { + return `@supports ${check}` + } + + if (!check.includes(':')) { + check = `${check}: var(--tw)` + } + + if (!(check.startsWith('(') && check.endsWith(')'))) { + check = `(${check})` + } + + return `@supports ${check}` + }, + { values: theme('supports') ?? {} } + ) + }, + + ariaVariants: ({ matchVariant, theme }) => { + matchVariant('aria', (value) => `&[aria-${normalize(value)}]`, { values: theme('aria') ?? {} }) + matchVariant( + 'group-aria', + (value, { modifier }) => + modifier + ? `:merge(.group\\/${modifier})[aria-${normalize(value)}] &` + : `:merge(.group)[aria-${normalize(value)}] &`, + { values: theme('aria') ?? {} } + ) + matchVariant( + 'peer-aria', + (value, { modifier }) => + modifier + ? `:merge(.peer\\/${modifier})[aria-${normalize(value)}] ~ &` + : `:merge(.peer)[aria-${normalize(value)}] ~ &`, + { values: theme('aria') ?? {} } + ) + }, + + dataVariants: ({ matchVariant, theme }) => { + matchVariant('data', (value) => `&[data-${normalize(value)}]`, { values: theme('data') ?? {} }) + matchVariant( + 'group-data', + (value, { modifier }) => + modifier + ? `:merge(.group\\/${modifier})[data-${normalize(value)}] &` + : `:merge(.group)[data-${normalize(value)}] &`, + { values: theme('data') ?? {} } + ) + matchVariant( + 'peer-data', + (value, { modifier }) => + modifier + ? `:merge(.peer\\/${modifier})[data-${normalize(value)}] ~ &` + : `:merge(.peer)[data-${normalize(value)}] ~ &`, + { values: theme('data') ?? {} } + ) + }, + + orientationVariants: ({ addVariant }) => { + addVariant('portrait', '@media (orientation: portrait)') + addVariant('landscape', '@media (orientation: landscape)') + }, + + prefersContrastVariants: ({ addVariant }) => { + addVariant('contrast-more', '@media (prefers-contrast: more)') + addVariant('contrast-less', '@media (prefers-contrast: less)') + }, +} + +let cssTransformValue = [ + 'translate(var(--tw-translate-x), var(--tw-translate-y))', + 'rotate(var(--tw-rotate))', + 'skewX(var(--tw-skew-x))', + 'skewY(var(--tw-skew-y))', + 'scaleX(var(--tw-scale-x))', + 'scaleY(var(--tw-scale-y))', +].join(' ') + +let cssFilterValue = [ + 'var(--tw-blur)', + 'var(--tw-brightness)', + 'var(--tw-contrast)', + 'var(--tw-grayscale)', + 'var(--tw-hue-rotate)', + 'var(--tw-invert)', + 'var(--tw-saturate)', + 'var(--tw-sepia)', + 'var(--tw-drop-shadow)', +].join(' ') + +let cssBackdropFilterValue = [ + 'var(--tw-backdrop-blur)', + 'var(--tw-backdrop-brightness)', + 'var(--tw-backdrop-contrast)', + 'var(--tw-backdrop-grayscale)', + 'var(--tw-backdrop-hue-rotate)', + 'var(--tw-backdrop-invert)', + 'var(--tw-backdrop-opacity)', + 'var(--tw-backdrop-saturate)', + 'var(--tw-backdrop-sepia)', +].join(' ') + +export let corePlugins = { + preflight: ({ addBase }) => { + let preflightStyles = postcss.parse( + fs.readFileSync(path.join(__dirname, './css/preflight.css'), 'utf8') + ) + + addBase([ + postcss.comment({ + text: `! tailwindcss v${tailwindVersion} | MIT License | https://tailwindcss.com`, + }), + ...preflightStyles.nodes, + ]) + }, + + container: (() => { + function extractMinWidths(breakpoints = []) { + return breakpoints + .flatMap((breakpoint) => breakpoint.values.map((breakpoint) => breakpoint.min)) + .filter((v) => v !== undefined) + } + + function mapMinWidthsToPadding(minWidths, screens, paddings) { + if (typeof paddings === 'undefined') { + return [] + } + + if (!(typeof paddings === 'object' && paddings !== null)) { + return [ + { + screen: 'DEFAULT', + minWidth: 0, + padding: paddings, + }, + ] + } + + let mapping = [] + + if (paddings.DEFAULT) { + mapping.push({ + screen: 'DEFAULT', + minWidth: 0, + padding: paddings.DEFAULT, + }) + } + + for (let minWidth of minWidths) { + for (let screen of screens) { + for (let { min } of screen.values) { + if (min === minWidth) { + mapping.push({ minWidth, padding: paddings[screen.name] }) + } + } + } + } + + return mapping + } + + return function ({ addComponents, theme }) { + let screens = normalizeScreens(theme('container.screens', theme('screens'))) + let minWidths = extractMinWidths(screens) + let paddings = mapMinWidthsToPadding(minWidths, screens, theme('container.padding')) + + let generatePaddingFor = (minWidth) => { + let paddingConfig = paddings.find((padding) => padding.minWidth === minWidth) + + if (!paddingConfig) { + return {} + } + + return { + paddingRight: paddingConfig.padding, + paddingLeft: paddingConfig.padding, + } + } + + let atRules = Array.from( + new Set(minWidths.slice().sort((a, z) => parseInt(a) - parseInt(z))) + ).map((minWidth) => ({ + [`@media (min-width: ${minWidth})`]: { + '.container': { + 'max-width': minWidth, + ...generatePaddingFor(minWidth), + }, + }, + })) + + addComponents([ + { + '.container': Object.assign( + { width: '100%' }, + theme('container.center', false) ? { marginRight: 'auto', marginLeft: 'auto' } : {}, + generatePaddingFor(0) + ), + }, + ...atRules, + ]) + } + })(), + + accessibility: ({ addUtilities }) => { + addUtilities({ + '.sr-only': { + position: 'absolute', + width: '1px', + height: '1px', + padding: '0', + margin: '-1px', + overflow: 'hidden', + clip: 'rect(0, 0, 0, 0)', + whiteSpace: 'nowrap', + borderWidth: '0', + }, + '.not-sr-only': { + position: 'static', + width: 'auto', + height: 'auto', + padding: '0', + margin: '0', + overflow: 'visible', + clip: 'auto', + whiteSpace: 'normal', + }, + }) + }, + + pointerEvents: ({ addUtilities }) => { + addUtilities({ + '.pointer-events-none': { 'pointer-events': 'none' }, + '.pointer-events-auto': { 'pointer-events': 'auto' }, + }) + }, + + visibility: ({ addUtilities }) => { + addUtilities({ + '.visible': { visibility: 'visible' }, + '.invisible': { visibility: 'hidden' }, + '.collapse': { visibility: 'collapse' }, + }) + }, + + position: ({ addUtilities }) => { + addUtilities({ + '.static': { position: 'static' }, + '.fixed': { position: 'fixed' }, + '.absolute': { position: 'absolute' }, + '.relative': { position: 'relative' }, + '.sticky': { position: 'sticky' }, + }) + }, + + inset: createUtilityPlugin( + 'inset', + [ + ['inset', ['inset']], + [ + ['inset-x', ['left', 'right']], + ['inset-y', ['top', 'bottom']], + ], + [ + ['start', ['inset-inline-start']], + ['end', ['inset-inline-end']], + ['top', ['top']], + ['right', ['right']], + ['bottom', ['bottom']], + ['left', ['left']], + ], + ], + { supportsNegativeValues: true } + ), + + isolation: ({ addUtilities }) => { + addUtilities({ + '.isolate': { isolation: 'isolate' }, + '.isolation-auto': { isolation: 'auto' }, + }) + }, + + zIndex: createUtilityPlugin('zIndex', [['z', ['zIndex']]], { supportsNegativeValues: true }), + order: createUtilityPlugin('order', undefined, { supportsNegativeValues: true }), + gridColumn: createUtilityPlugin('gridColumn', [['col', ['gridColumn']]]), + gridColumnStart: createUtilityPlugin('gridColumnStart', [['col-start', ['gridColumnStart']]]), + gridColumnEnd: createUtilityPlugin('gridColumnEnd', [['col-end', ['gridColumnEnd']]]), + gridRow: createUtilityPlugin('gridRow', [['row', ['gridRow']]]), + gridRowStart: createUtilityPlugin('gridRowStart', [['row-start', ['gridRowStart']]]), + gridRowEnd: createUtilityPlugin('gridRowEnd', [['row-end', ['gridRowEnd']]]), + + float: ({ addUtilities }) => { + addUtilities({ + '.float-right': { float: 'right' }, + '.float-left': { float: 'left' }, + '.float-none': { float: 'none' }, + }) + }, + + clear: ({ addUtilities }) => { + addUtilities({ + '.clear-left': { clear: 'left' }, + '.clear-right': { clear: 'right' }, + '.clear-both': { clear: 'both' }, + '.clear-none': { clear: 'none' }, + }) + }, + + margin: createUtilityPlugin( + 'margin', + [ + ['m', ['margin']], + [ + ['mx', ['margin-left', 'margin-right']], + ['my', ['margin-top', 'margin-bottom']], + ], + [ + ['ms', ['margin-inline-start']], + ['me', ['margin-inline-end']], + ['mt', ['margin-top']], + ['mr', ['margin-right']], + ['mb', ['margin-bottom']], + ['ml', ['margin-left']], + ], + ], + { supportsNegativeValues: true } + ), + + boxSizing: ({ addUtilities }) => { + addUtilities({ + '.box-border': { 'box-sizing': 'border-box' }, + '.box-content': { 'box-sizing': 'content-box' }, + }) + }, + + lineClamp: ({ matchUtilities, addUtilities, theme }) => { + matchUtilities( + { + 'line-clamp': (value) => ({ + overflow: 'hidden', + display: '-webkit-box', + '-webkit-box-orient': 'vertical', + '-webkit-line-clamp': `${value}`, + }), + }, + { values: theme('lineClamp') } + ) + + addUtilities({ + '.line-clamp-none': { + overflow: 'visible', + display: 'block', + '-webkit-box-orient': 'horizontal', + '-webkit-line-clamp': 'none', + }, + }) + }, + + display: ({ addUtilities }) => { + addUtilities({ + '.block': { display: 'block' }, + '.inline-block': { display: 'inline-block' }, + '.inline': { display: 'inline' }, + '.flex': { display: 'flex' }, + '.inline-flex': { display: 'inline-flex' }, + '.table': { display: 'table' }, + '.inline-table': { display: 'inline-table' }, + '.table-caption': { display: 'table-caption' }, + '.table-cell': { display: 'table-cell' }, + '.table-column': { display: 'table-column' }, + '.table-column-group': { display: 'table-column-group' }, + '.table-footer-group': { display: 'table-footer-group' }, + '.table-header-group': { display: 'table-header-group' }, + '.table-row-group': { display: 'table-row-group' }, + '.table-row': { display: 'table-row' }, + '.flow-root': { display: 'flow-root' }, + '.grid': { display: 'grid' }, + '.inline-grid': { display: 'inline-grid' }, + '.contents': { display: 'contents' }, + '.list-item': { display: 'list-item' }, + '.hidden': { display: 'none' }, + }) + }, + + aspectRatio: createUtilityPlugin('aspectRatio', [['aspect', ['aspect-ratio']]]), + + height: createUtilityPlugin('height', [['h', ['height']]]), + maxHeight: createUtilityPlugin('maxHeight', [['max-h', ['maxHeight']]]), + minHeight: createUtilityPlugin('minHeight', [['min-h', ['minHeight']]]), + + width: createUtilityPlugin('width', [['w', ['width']]]), + minWidth: createUtilityPlugin('minWidth', [['min-w', ['minWidth']]]), + maxWidth: createUtilityPlugin('maxWidth', [['max-w', ['maxWidth']]]), + + flex: createUtilityPlugin('flex'), + flexShrink: createUtilityPlugin('flexShrink', [ + ['flex-shrink', ['flex-shrink']], // Deprecated + ['shrink', ['flex-shrink']], + ]), + flexGrow: createUtilityPlugin('flexGrow', [ + ['flex-grow', ['flex-grow']], // Deprecated + ['grow', ['flex-grow']], + ]), + flexBasis: createUtilityPlugin('flexBasis', [['basis', ['flex-basis']]]), + + tableLayout: ({ addUtilities }) => { + addUtilities({ + '.table-auto': { 'table-layout': 'auto' }, + '.table-fixed': { 'table-layout': 'fixed' }, + }) + }, + + captionSide: ({ addUtilities }) => { + addUtilities({ + '.caption-top': { 'caption-side': 'top' }, + '.caption-bottom': { 'caption-side': 'bottom' }, + }) + }, + + borderCollapse: ({ addUtilities }) => { + addUtilities({ + '.border-collapse': { 'border-collapse': 'collapse' }, + '.border-separate': { 'border-collapse': 'separate' }, + }) + }, + + borderSpacing: ({ addDefaults, matchUtilities, theme }) => { + addDefaults('border-spacing', { + '--tw-border-spacing-x': 0, + '--tw-border-spacing-y': 0, + }) + + matchUtilities( + { + 'border-spacing': (value) => { + return { + '--tw-border-spacing-x': value, + '--tw-border-spacing-y': value, + '@defaults border-spacing': {}, + 'border-spacing': 'var(--tw-border-spacing-x) var(--tw-border-spacing-y)', + } + }, + 'border-spacing-x': (value) => { + return { + '--tw-border-spacing-x': value, + '@defaults border-spacing': {}, + 'border-spacing': 'var(--tw-border-spacing-x) var(--tw-border-spacing-y)', + } + }, + 'border-spacing-y': (value) => { + return { + '--tw-border-spacing-y': value, + '@defaults border-spacing': {}, + 'border-spacing': 'var(--tw-border-spacing-x) var(--tw-border-spacing-y)', + } + }, + }, + { values: theme('borderSpacing') } + ) + }, + + transformOrigin: createUtilityPlugin('transformOrigin', [['origin', ['transformOrigin']]]), + translate: createUtilityPlugin( + 'translate', + [ + [ + [ + 'translate-x', + [['@defaults transform', {}], '--tw-translate-x', ['transform', cssTransformValue]], + ], + [ + 'translate-y', + [['@defaults transform', {}], '--tw-translate-y', ['transform', cssTransformValue]], + ], + ], + ], + { supportsNegativeValues: true } + ), + rotate: createUtilityPlugin( + 'rotate', + [['rotate', [['@defaults transform', {}], '--tw-rotate', ['transform', cssTransformValue]]]], + { supportsNegativeValues: true } + ), + skew: createUtilityPlugin( + 'skew', + [ + [ + ['skew-x', [['@defaults transform', {}], '--tw-skew-x', ['transform', cssTransformValue]]], + ['skew-y', [['@defaults transform', {}], '--tw-skew-y', ['transform', cssTransformValue]]], + ], + ], + { supportsNegativeValues: true } + ), + scale: createUtilityPlugin( + 'scale', + [ + [ + 'scale', + [ + ['@defaults transform', {}], + '--tw-scale-x', + '--tw-scale-y', + ['transform', cssTransformValue], + ], + ], + [ + [ + 'scale-x', + [['@defaults transform', {}], '--tw-scale-x', ['transform', cssTransformValue]], + ], + [ + 'scale-y', + [['@defaults transform', {}], '--tw-scale-y', ['transform', cssTransformValue]], + ], + ], + ], + { supportsNegativeValues: true } + ), + + transform: ({ addDefaults, addUtilities }) => { + addDefaults('transform', { + '--tw-translate-x': '0', + '--tw-translate-y': '0', + '--tw-rotate': '0', + '--tw-skew-x': '0', + '--tw-skew-y': '0', + '--tw-scale-x': '1', + '--tw-scale-y': '1', + }) + + addUtilities({ + '.transform': { '@defaults transform': {}, transform: cssTransformValue }, + '.transform-cpu': { + transform: cssTransformValue, + }, + '.transform-gpu': { + transform: cssTransformValue.replace( + 'translate(var(--tw-translate-x), var(--tw-translate-y))', + 'translate3d(var(--tw-translate-x), var(--tw-translate-y), 0)' + ), + }, + '.transform-none': { transform: 'none' }, + }) + }, + + animation: ({ matchUtilities, theme, config }) => { + let prefixName = (name) => escapeClassName(config('prefix') + name) + let keyframes = Object.fromEntries( + Object.entries(theme('keyframes') ?? {}).map(([key, value]) => { + return [key, { [`@keyframes ${prefixName(key)}`]: value }] + }) + ) + + matchUtilities( + { + animate: (value) => { + let animations = parseAnimationValue(value) + + return [ + ...animations.flatMap((animation) => keyframes[animation.name]), + { + animation: animations + .map(({ name, value }) => { + if (name === undefined || keyframes[name] === undefined) { + return value + } + return value.replace(name, prefixName(name)) + }) + .join(', '), + }, + ] + }, + }, + { values: theme('animation') } + ) + }, + + cursor: createUtilityPlugin('cursor'), + + touchAction: ({ addDefaults, addUtilities }) => { + addDefaults('touch-action', { + '--tw-pan-x': ' ', + '--tw-pan-y': ' ', + '--tw-pinch-zoom': ' ', + }) + + let cssTouchActionValue = 'var(--tw-pan-x) var(--tw-pan-y) var(--tw-pinch-zoom)' + + addUtilities({ + '.touch-auto': { 'touch-action': 'auto' }, + '.touch-none': { 'touch-action': 'none' }, + '.touch-pan-x': { + '@defaults touch-action': {}, + '--tw-pan-x': 'pan-x', + 'touch-action': cssTouchActionValue, + }, + '.touch-pan-left': { + '@defaults touch-action': {}, + '--tw-pan-x': 'pan-left', + 'touch-action': cssTouchActionValue, + }, + '.touch-pan-right': { + '@defaults touch-action': {}, + '--tw-pan-x': 'pan-right', + 'touch-action': cssTouchActionValue, + }, + '.touch-pan-y': { + '@defaults touch-action': {}, + '--tw-pan-y': 'pan-y', + 'touch-action': cssTouchActionValue, + }, + '.touch-pan-up': { + '@defaults touch-action': {}, + '--tw-pan-y': 'pan-up', + 'touch-action': cssTouchActionValue, + }, + '.touch-pan-down': { + '@defaults touch-action': {}, + '--tw-pan-y': 'pan-down', + 'touch-action': cssTouchActionValue, + }, + '.touch-pinch-zoom': { + '@defaults touch-action': {}, + '--tw-pinch-zoom': 'pinch-zoom', + 'touch-action': cssTouchActionValue, + }, + '.touch-manipulation': { 'touch-action': 'manipulation' }, + }) + }, + + userSelect: ({ addUtilities }) => { + addUtilities({ + '.select-none': { 'user-select': 'none' }, + '.select-text': { 'user-select': 'text' }, + '.select-all': { 'user-select': 'all' }, + '.select-auto': { 'user-select': 'auto' }, + }) + }, + + resize: ({ addUtilities }) => { + addUtilities({ + '.resize-none': { resize: 'none' }, + '.resize-y': { resize: 'vertical' }, + '.resize-x': { resize: 'horizontal' }, + '.resize': { resize: 'both' }, + }) + }, + + scrollSnapType: ({ addDefaults, addUtilities }) => { + addDefaults('scroll-snap-type', { + '--tw-scroll-snap-strictness': 'proximity', + }) + + addUtilities({ + '.snap-none': { 'scroll-snap-type': 'none' }, + '.snap-x': { + '@defaults scroll-snap-type': {}, + 'scroll-snap-type': 'x var(--tw-scroll-snap-strictness)', + }, + '.snap-y': { + '@defaults scroll-snap-type': {}, + 'scroll-snap-type': 'y var(--tw-scroll-snap-strictness)', + }, + '.snap-both': { + '@defaults scroll-snap-type': {}, + 'scroll-snap-type': 'both var(--tw-scroll-snap-strictness)', + }, + '.snap-mandatory': { '--tw-scroll-snap-strictness': 'mandatory' }, + '.snap-proximity': { '--tw-scroll-snap-strictness': 'proximity' }, + }) + }, + + scrollSnapAlign: ({ addUtilities }) => { + addUtilities({ + '.snap-start': { 'scroll-snap-align': 'start' }, + '.snap-end': { 'scroll-snap-align': 'end' }, + '.snap-center': { 'scroll-snap-align': 'center' }, + '.snap-align-none': { 'scroll-snap-align': 'none' }, + }) + }, + + scrollSnapStop: ({ addUtilities }) => { + addUtilities({ + '.snap-normal': { 'scroll-snap-stop': 'normal' }, + '.snap-always': { 'scroll-snap-stop': 'always' }, + }) + }, + + scrollMargin: createUtilityPlugin( + 'scrollMargin', + [ + ['scroll-m', ['scroll-margin']], + [ + ['scroll-mx', ['scroll-margin-left', 'scroll-margin-right']], + ['scroll-my', ['scroll-margin-top', 'scroll-margin-bottom']], + ], + [ + ['scroll-ms', ['scroll-margin-inline-start']], + ['scroll-me', ['scroll-margin-inline-end']], + ['scroll-mt', ['scroll-margin-top']], + ['scroll-mr', ['scroll-margin-right']], + ['scroll-mb', ['scroll-margin-bottom']], + ['scroll-ml', ['scroll-margin-left']], + ], + ], + { supportsNegativeValues: true } + ), + + scrollPadding: createUtilityPlugin('scrollPadding', [ + ['scroll-p', ['scroll-padding']], + [ + ['scroll-px', ['scroll-padding-left', 'scroll-padding-right']], + ['scroll-py', ['scroll-padding-top', 'scroll-padding-bottom']], + ], + [ + ['scroll-ps', ['scroll-padding-inline-start']], + ['scroll-pe', ['scroll-padding-inline-end']], + ['scroll-pt', ['scroll-padding-top']], + ['scroll-pr', ['scroll-padding-right']], + ['scroll-pb', ['scroll-padding-bottom']], + ['scroll-pl', ['scroll-padding-left']], + ], + ]), + + listStylePosition: ({ addUtilities }) => { + addUtilities({ + '.list-inside': { 'list-style-position': 'inside' }, + '.list-outside': { 'list-style-position': 'outside' }, + }) + }, + listStyleType: createUtilityPlugin('listStyleType', [['list', ['listStyleType']]]), + listStyleImage: createUtilityPlugin('listStyleImage', [['list-image', ['listStyleImage']]]), + + appearance: ({ addUtilities }) => { + addUtilities({ + '.appearance-none': { appearance: 'none' }, + }) + }, + + columns: createUtilityPlugin('columns', [['columns', ['columns']]]), + + breakBefore: ({ addUtilities }) => { + addUtilities({ + '.break-before-auto': { 'break-before': 'auto' }, + '.break-before-avoid': { 'break-before': 'avoid' }, + '.break-before-all': { 'break-before': 'all' }, + '.break-before-avoid-page': { 'break-before': 'avoid-page' }, + '.break-before-page': { 'break-before': 'page' }, + '.break-before-left': { 'break-before': 'left' }, + '.break-before-right': { 'break-before': 'right' }, + '.break-before-column': { 'break-before': 'column' }, + }) + }, + + breakInside: ({ addUtilities }) => { + addUtilities({ + '.break-inside-auto': { 'break-inside': 'auto' }, + '.break-inside-avoid': { 'break-inside': 'avoid' }, + '.break-inside-avoid-page': { 'break-inside': 'avoid-page' }, + '.break-inside-avoid-column': { 'break-inside': 'avoid-column' }, + }) + }, + + breakAfter: ({ addUtilities }) => { + addUtilities({ + '.break-after-auto': { 'break-after': 'auto' }, + '.break-after-avoid': { 'break-after': 'avoid' }, + '.break-after-all': { 'break-after': 'all' }, + '.break-after-avoid-page': { 'break-after': 'avoid-page' }, + '.break-after-page': { 'break-after': 'page' }, + '.break-after-left': { 'break-after': 'left' }, + '.break-after-right': { 'break-after': 'right' }, + '.break-after-column': { 'break-after': 'column' }, + }) + }, + + gridAutoColumns: createUtilityPlugin('gridAutoColumns', [['auto-cols', ['gridAutoColumns']]]), + + gridAutoFlow: ({ addUtilities }) => { + addUtilities({ + '.grid-flow-row': { gridAutoFlow: 'row' }, + '.grid-flow-col': { gridAutoFlow: 'column' }, + '.grid-flow-dense': { gridAutoFlow: 'dense' }, + '.grid-flow-row-dense': { gridAutoFlow: 'row dense' }, + '.grid-flow-col-dense': { gridAutoFlow: 'column dense' }, + }) + }, + + gridAutoRows: createUtilityPlugin('gridAutoRows', [['auto-rows', ['gridAutoRows']]]), + gridTemplateColumns: createUtilityPlugin('gridTemplateColumns', [ + ['grid-cols', ['gridTemplateColumns']], + ]), + gridTemplateRows: createUtilityPlugin('gridTemplateRows', [['grid-rows', ['gridTemplateRows']]]), + + flexDirection: ({ addUtilities }) => { + addUtilities({ + '.flex-row': { 'flex-direction': 'row' }, + '.flex-row-reverse': { 'flex-direction': 'row-reverse' }, + '.flex-col': { 'flex-direction': 'column' }, + '.flex-col-reverse': { 'flex-direction': 'column-reverse' }, + }) + }, + + flexWrap: ({ addUtilities }) => { + addUtilities({ + '.flex-wrap': { 'flex-wrap': 'wrap' }, + '.flex-wrap-reverse': { 'flex-wrap': 'wrap-reverse' }, + '.flex-nowrap': { 'flex-wrap': 'nowrap' }, + }) + }, + + placeContent: ({ addUtilities }) => { + addUtilities({ + '.place-content-center': { 'place-content': 'center' }, + '.place-content-start': { 'place-content': 'start' }, + '.place-content-end': { 'place-content': 'end' }, + '.place-content-between': { 'place-content': 'space-between' }, + '.place-content-around': { 'place-content': 'space-around' }, + '.place-content-evenly': { 'place-content': 'space-evenly' }, + '.place-content-baseline': { 'place-content': 'baseline' }, + '.place-content-stretch': { 'place-content': 'stretch' }, + }) + }, + + placeItems: ({ addUtilities }) => { + addUtilities({ + '.place-items-start': { 'place-items': 'start' }, + '.place-items-end': { 'place-items': 'end' }, + '.place-items-center': { 'place-items': 'center' }, + '.place-items-baseline': { 'place-items': 'baseline' }, + '.place-items-stretch': { 'place-items': 'stretch' }, + }) + }, + + alignContent: ({ addUtilities }) => { + addUtilities({ + '.content-normal': { 'align-content': 'normal' }, + '.content-center': { 'align-content': 'center' }, + '.content-start': { 'align-content': 'flex-start' }, + '.content-end': { 'align-content': 'flex-end' }, + '.content-between': { 'align-content': 'space-between' }, + '.content-around': { 'align-content': 'space-around' }, + '.content-evenly': { 'align-content': 'space-evenly' }, + '.content-baseline': { 'align-content': 'baseline' }, + '.content-stretch': { 'align-content': 'stretch' }, + }) + }, + + alignItems: ({ addUtilities }) => { + addUtilities({ + '.items-start': { 'align-items': 'flex-start' }, + '.items-end': { 'align-items': 'flex-end' }, + '.items-center': { 'align-items': 'center' }, + '.items-baseline': { 'align-items': 'baseline' }, + '.items-stretch': { 'align-items': 'stretch' }, + }) + }, + + justifyContent: ({ addUtilities }) => { + addUtilities({ + '.justify-normal': { 'justify-content': 'normal' }, + '.justify-start': { 'justify-content': 'flex-start' }, + '.justify-end': { 'justify-content': 'flex-end' }, + '.justify-center': { 'justify-content': 'center' }, + '.justify-between': { 'justify-content': 'space-between' }, + '.justify-around': { 'justify-content': 'space-around' }, + '.justify-evenly': { 'justify-content': 'space-evenly' }, + '.justify-stretch': { 'justify-content': 'stretch' }, + }) + }, + + justifyItems: ({ addUtilities }) => { + addUtilities({ + '.justify-items-start': { 'justify-items': 'start' }, + '.justify-items-end': { 'justify-items': 'end' }, + '.justify-items-center': { 'justify-items': 'center' }, + '.justify-items-stretch': { 'justify-items': 'stretch' }, + }) + }, + + gap: createUtilityPlugin('gap', [ + ['gap', ['gap']], + [ + ['gap-x', ['columnGap']], + ['gap-y', ['rowGap']], + ], + ]), + + space: ({ matchUtilities, addUtilities, theme }) => { + matchUtilities( + { + 'space-x': (value) => { + value = value === '0' ? '0px' : value + + if (__OXIDE__) { + return { + '& > :not([hidden]) ~ :not([hidden])': { + '--tw-space-x-reverse': '0', + 'margin-inline-end': `calc(${value} * var(--tw-space-x-reverse))`, + 'margin-inline-start': `calc(${value} * calc(1 - var(--tw-space-x-reverse)))`, + }, + } + } + + return { + '& > :not([hidden]) ~ :not([hidden])': { + '--tw-space-x-reverse': '0', + 'margin-right': `calc(${value} * var(--tw-space-x-reverse))`, + 'margin-left': `calc(${value} * calc(1 - var(--tw-space-x-reverse)))`, + }, + } + }, + 'space-y': (value) => { + value = value === '0' ? '0px' : value + + return { + '& > :not([hidden]) ~ :not([hidden])': { + '--tw-space-y-reverse': '0', + 'margin-top': `calc(${value} * calc(1 - var(--tw-space-y-reverse)))`, + 'margin-bottom': `calc(${value} * var(--tw-space-y-reverse))`, + }, + } + }, + }, + { values: theme('space'), supportsNegativeValues: true } + ) + + addUtilities({ + '.space-y-reverse > :not([hidden]) ~ :not([hidden])': { '--tw-space-y-reverse': '1' }, + '.space-x-reverse > :not([hidden]) ~ :not([hidden])': { '--tw-space-x-reverse': '1' }, + }) + }, + + divideWidth: ({ matchUtilities, addUtilities, theme }) => { + matchUtilities( + { + 'divide-x': (value) => { + value = value === '0' ? '0px' : value + + if (__OXIDE__) { + return { + '& > :not([hidden]) ~ :not([hidden])': { + '@defaults border-width': {}, + '--tw-divide-x-reverse': '0', + 'border-inline-end-width': `calc(${value} * var(--tw-divide-x-reverse))`, + 'border-inline-start-width': `calc(${value} * calc(1 - var(--tw-divide-x-reverse)))`, + }, + } + } + + return { + '& > :not([hidden]) ~ :not([hidden])': { + '@defaults border-width': {}, + '--tw-divide-x-reverse': '0', + 'border-right-width': `calc(${value} * var(--tw-divide-x-reverse))`, + 'border-left-width': `calc(${value} * calc(1 - var(--tw-divide-x-reverse)))`, + }, + } + }, + 'divide-y': (value) => { + value = value === '0' ? '0px' : value + + return { + '& > :not([hidden]) ~ :not([hidden])': { + '@defaults border-width': {}, + '--tw-divide-y-reverse': '0', + 'border-top-width': `calc(${value} * calc(1 - var(--tw-divide-y-reverse)))`, + 'border-bottom-width': `calc(${value} * var(--tw-divide-y-reverse))`, + }, + } + }, + }, + { values: theme('divideWidth'), type: ['line-width', 'length', 'any'] } + ) + + addUtilities({ + '.divide-y-reverse > :not([hidden]) ~ :not([hidden])': { + '@defaults border-width': {}, + '--tw-divide-y-reverse': '1', + }, + '.divide-x-reverse > :not([hidden]) ~ :not([hidden])': { + '@defaults border-width': {}, + '--tw-divide-x-reverse': '1', + }, + }) + }, + + divideStyle: ({ addUtilities }) => { + addUtilities({ + '.divide-solid > :not([hidden]) ~ :not([hidden])': { 'border-style': 'solid' }, + '.divide-dashed > :not([hidden]) ~ :not([hidden])': { 'border-style': 'dashed' }, + '.divide-dotted > :not([hidden]) ~ :not([hidden])': { 'border-style': 'dotted' }, + '.divide-double > :not([hidden]) ~ :not([hidden])': { 'border-style': 'double' }, + '.divide-none > :not([hidden]) ~ :not([hidden])': { 'border-style': 'none' }, + }) + }, + + divideColor: ({ matchUtilities, theme, corePlugins }) => { + matchUtilities( + { + divide: (value) => { + if (!corePlugins('divideOpacity')) { + return { + ['& > :not([hidden]) ~ :not([hidden])']: { + 'border-color': toColorValue(value), + }, + } + } + + return { + ['& > :not([hidden]) ~ :not([hidden])']: withAlphaVariable({ + color: value, + property: 'border-color', + variable: '--tw-divide-opacity', + }), + } + }, + }, + { + values: (({ DEFAULT: _, ...colors }) => colors)(flattenColorPalette(theme('divideColor'))), + type: ['color', 'any'], + } + ) + }, + + divideOpacity: ({ matchUtilities, theme }) => { + matchUtilities( + { + 'divide-opacity': (value) => { + return { [`& > :not([hidden]) ~ :not([hidden])`]: { '--tw-divide-opacity': value } } + }, + }, + { values: theme('divideOpacity') } + ) + }, + + placeSelf: ({ addUtilities }) => { + addUtilities({ + '.place-self-auto': { 'place-self': 'auto' }, + '.place-self-start': { 'place-self': 'start' }, + '.place-self-end': { 'place-self': 'end' }, + '.place-self-center': { 'place-self': 'center' }, + '.place-self-stretch': { 'place-self': 'stretch' }, + }) + }, + + alignSelf: ({ addUtilities }) => { + addUtilities({ + '.self-auto': { 'align-self': 'auto' }, + '.self-start': { 'align-self': 'flex-start' }, + '.self-end': { 'align-self': 'flex-end' }, + '.self-center': { 'align-self': 'center' }, + '.self-stretch': { 'align-self': 'stretch' }, + '.self-baseline': { 'align-self': 'baseline' }, + }) + }, + + justifySelf: ({ addUtilities }) => { + addUtilities({ + '.justify-self-auto': { 'justify-self': 'auto' }, + '.justify-self-start': { 'justify-self': 'start' }, + '.justify-self-end': { 'justify-self': 'end' }, + '.justify-self-center': { 'justify-self': 'center' }, + '.justify-self-stretch': { 'justify-self': 'stretch' }, + }) + }, + + overflow: ({ addUtilities }) => { + addUtilities({ + '.overflow-auto': { overflow: 'auto' }, + '.overflow-hidden': { overflow: 'hidden' }, + '.overflow-clip': { overflow: 'clip' }, + '.overflow-visible': { overflow: 'visible' }, + '.overflow-scroll': { overflow: 'scroll' }, + '.overflow-x-auto': { 'overflow-x': 'auto' }, + '.overflow-y-auto': { 'overflow-y': 'auto' }, + '.overflow-x-hidden': { 'overflow-x': 'hidden' }, + '.overflow-y-hidden': { 'overflow-y': 'hidden' }, + '.overflow-x-clip': { 'overflow-x': 'clip' }, + '.overflow-y-clip': { 'overflow-y': 'clip' }, + '.overflow-x-visible': { 'overflow-x': 'visible' }, + '.overflow-y-visible': { 'overflow-y': 'visible' }, + '.overflow-x-scroll': { 'overflow-x': 'scroll' }, + '.overflow-y-scroll': { 'overflow-y': 'scroll' }, + }) + }, + + overscrollBehavior: ({ addUtilities }) => { + addUtilities({ + '.overscroll-auto': { 'overscroll-behavior': 'auto' }, + '.overscroll-contain': { 'overscroll-behavior': 'contain' }, + '.overscroll-none': { 'overscroll-behavior': 'none' }, + '.overscroll-y-auto': { 'overscroll-behavior-y': 'auto' }, + '.overscroll-y-contain': { 'overscroll-behavior-y': 'contain' }, + '.overscroll-y-none': { 'overscroll-behavior-y': 'none' }, + '.overscroll-x-auto': { 'overscroll-behavior-x': 'auto' }, + '.overscroll-x-contain': { 'overscroll-behavior-x': 'contain' }, + '.overscroll-x-none': { 'overscroll-behavior-x': 'none' }, + }) + }, + + scrollBehavior: ({ addUtilities }) => { + addUtilities({ + '.scroll-auto': { 'scroll-behavior': 'auto' }, + '.scroll-smooth': { 'scroll-behavior': 'smooth' }, + }) + }, + + textOverflow: ({ addUtilities }) => { + addUtilities({ + '.truncate': { overflow: 'hidden', 'text-overflow': 'ellipsis', 'white-space': 'nowrap' }, + '.overflow-ellipsis': { 'text-overflow': 'ellipsis' }, // Deprecated + '.text-ellipsis': { 'text-overflow': 'ellipsis' }, + '.text-clip': { 'text-overflow': 'clip' }, + }) + }, + + hyphens: ({ addUtilities }) => { + addUtilities({ + '.hyphens-none': { hyphens: 'none' }, + '.hyphens-manual': { hyphens: 'manual' }, + '.hyphens-auto': { hyphens: 'auto' }, + }) + }, + + whitespace: ({ addUtilities }) => { + addUtilities({ + '.whitespace-normal': { 'white-space': 'normal' }, + '.whitespace-nowrap': { 'white-space': 'nowrap' }, + '.whitespace-pre': { 'white-space': 'pre' }, + '.whitespace-pre-line': { 'white-space': 'pre-line' }, + '.whitespace-pre-wrap': { 'white-space': 'pre-wrap' }, + '.whitespace-break-spaces': { 'white-space': 'break-spaces' }, + }) + }, + + wordBreak: ({ addUtilities }) => { + addUtilities({ + '.break-normal': { 'overflow-wrap': 'normal', 'word-break': 'normal' }, + '.break-words': { 'overflow-wrap': 'break-word' }, + '.break-all': { 'word-break': 'break-all' }, + '.break-keep': { 'word-break': 'keep-all' }, + }) + }, + + borderRadius: createUtilityPlugin('borderRadius', [ + ['rounded', ['border-radius']], + [ + ['rounded-s', ['border-start-start-radius', 'border-end-start-radius']], + ['rounded-e', ['border-start-end-radius', 'border-end-end-radius']], + ['rounded-t', ['border-top-left-radius', 'border-top-right-radius']], + ['rounded-r', ['border-top-right-radius', 'border-bottom-right-radius']], + ['rounded-b', ['border-bottom-right-radius', 'border-bottom-left-radius']], + ['rounded-l', ['border-top-left-radius', 'border-bottom-left-radius']], + ], + [ + ['rounded-ss', ['border-start-start-radius']], + ['rounded-se', ['border-start-end-radius']], + ['rounded-ee', ['border-end-end-radius']], + ['rounded-es', ['border-end-start-radius']], + ['rounded-tl', ['border-top-left-radius']], + ['rounded-tr', ['border-top-right-radius']], + ['rounded-br', ['border-bottom-right-radius']], + ['rounded-bl', ['border-bottom-left-radius']], + ], + ]), + + borderWidth: createUtilityPlugin( + 'borderWidth', + [ + ['border', [['@defaults border-width', {}], 'border-width']], + [ + ['border-x', [['@defaults border-width', {}], 'border-left-width', 'border-right-width']], + ['border-y', [['@defaults border-width', {}], 'border-top-width', 'border-bottom-width']], + ], + [ + ['border-s', [['@defaults border-width', {}], 'border-inline-start-width']], + ['border-e', [['@defaults border-width', {}], 'border-inline-end-width']], + ['border-t', [['@defaults border-width', {}], 'border-top-width']], + ['border-r', [['@defaults border-width', {}], 'border-right-width']], + ['border-b', [['@defaults border-width', {}], 'border-bottom-width']], + ['border-l', [['@defaults border-width', {}], 'border-left-width']], + ], + ], + { type: ['line-width', 'length'] } + ), + + borderStyle: ({ addUtilities }) => { + addUtilities({ + '.border-solid': { 'border-style': 'solid' }, + '.border-dashed': { 'border-style': 'dashed' }, + '.border-dotted': { 'border-style': 'dotted' }, + '.border-double': { 'border-style': 'double' }, + '.border-hidden': { 'border-style': 'hidden' }, + '.border-none': { 'border-style': 'none' }, + }) + }, + + borderColor: ({ matchUtilities, theme, corePlugins }) => { + matchUtilities( + { + border: (value) => { + if (!corePlugins('borderOpacity')) { + return { + 'border-color': toColorValue(value), + } + } + + return withAlphaVariable({ + color: value, + property: 'border-color', + variable: '--tw-border-opacity', + }) + }, + }, + { + values: (({ DEFAULT: _, ...colors }) => colors)(flattenColorPalette(theme('borderColor'))), + type: ['color', 'any'], + } + ) + + matchUtilities( + { + 'border-x': (value) => { + if (!corePlugins('borderOpacity')) { + return { + 'border-left-color': toColorValue(value), + 'border-right-color': toColorValue(value), + } + } + + return withAlphaVariable({ + color: value, + property: ['border-left-color', 'border-right-color'], + variable: '--tw-border-opacity', + }) + }, + 'border-y': (value) => { + if (!corePlugins('borderOpacity')) { + return { + 'border-top-color': toColorValue(value), + 'border-bottom-color': toColorValue(value), + } + } + + return withAlphaVariable({ + color: value, + property: ['border-top-color', 'border-bottom-color'], + variable: '--tw-border-opacity', + }) + }, + }, + { + values: (({ DEFAULT: _, ...colors }) => colors)(flattenColorPalette(theme('borderColor'))), + type: ['color', 'any'], + } + ) + + matchUtilities( + { + 'border-s': (value) => { + if (!corePlugins('borderOpacity')) { + return { + 'border-inline-start-color': toColorValue(value), + } + } + + return withAlphaVariable({ + color: value, + property: 'border-inline-start-color', + variable: '--tw-border-opacity', + }) + }, + 'border-e': (value) => { + if (!corePlugins('borderOpacity')) { + return { + 'border-inline-end-color': toColorValue(value), + } + } + + return withAlphaVariable({ + color: value, + property: 'border-inline-end-color', + variable: '--tw-border-opacity', + }) + }, + 'border-t': (value) => { + if (!corePlugins('borderOpacity')) { + return { + 'border-top-color': toColorValue(value), + } + } + + return withAlphaVariable({ + color: value, + property: 'border-top-color', + variable: '--tw-border-opacity', + }) + }, + 'border-r': (value) => { + if (!corePlugins('borderOpacity')) { + return { + 'border-right-color': toColorValue(value), + } + } + + return withAlphaVariable({ + color: value, + property: 'border-right-color', + variable: '--tw-border-opacity', + }) + }, + 'border-b': (value) => { + if (!corePlugins('borderOpacity')) { + return { + 'border-bottom-color': toColorValue(value), + } + } + + return withAlphaVariable({ + color: value, + property: 'border-bottom-color', + variable: '--tw-border-opacity', + }) + }, + 'border-l': (value) => { + if (!corePlugins('borderOpacity')) { + return { + 'border-left-color': toColorValue(value), + } + } + + return withAlphaVariable({ + color: value, + property: 'border-left-color', + variable: '--tw-border-opacity', + }) + }, + }, + { + values: (({ DEFAULT: _, ...colors }) => colors)(flattenColorPalette(theme('borderColor'))), + type: ['color', 'any'], + } + ) + }, + + borderOpacity: createUtilityPlugin('borderOpacity', [ + ['border-opacity', ['--tw-border-opacity']], + ]), + + backgroundColor: ({ matchUtilities, theme, corePlugins }) => { + matchUtilities( + { + bg: (value) => { + if (!corePlugins('backgroundOpacity')) { + return { + 'background-color': toColorValue(value), + } + } + + return withAlphaVariable({ + color: value, + property: 'background-color', + variable: '--tw-bg-opacity', + }) + }, + }, + { values: flattenColorPalette(theme('backgroundColor')), type: ['color', 'any'] } + ) + }, + + backgroundOpacity: createUtilityPlugin('backgroundOpacity', [ + ['bg-opacity', ['--tw-bg-opacity']], + ]), + backgroundImage: createUtilityPlugin('backgroundImage', [['bg', ['background-image']]], { + type: ['lookup', 'image', 'url'], + }), + gradientColorStops: (() => { + function transparentTo(value) { + return withAlphaValue(value, 0, 'rgb(255 255 255 / 0)') + } + + return function ({ matchUtilities, theme, addDefaults }) { + addDefaults('gradient-color-stops', { + '--tw-gradient-from-position': ' ', + '--tw-gradient-via-position': ' ', + '--tw-gradient-to-position': ' ', + }) + + let options = { + values: flattenColorPalette(theme('gradientColorStops')), + type: ['color', 'any'], + } + + let positionOptions = { + values: theme('gradientColorStopPositions'), + type: ['length', 'percentage'], + } + + matchUtilities( + { + from: (value) => { + let transparentToValue = transparentTo(value) + + return { + '@defaults gradient-color-stops': {}, + '--tw-gradient-from': `${toColorValue(value)} var(--tw-gradient-from-position)`, + '--tw-gradient-to': `${transparentToValue} var(--tw-gradient-to-position)`, + '--tw-gradient-stops': `var(--tw-gradient-from), var(--tw-gradient-to)`, + } + }, + }, + options + ) + + matchUtilities( + { + from: (value) => { + return { + '--tw-gradient-from-position': value, + } + }, + }, + positionOptions + ) + + matchUtilities( + { + via: (value) => { + let transparentToValue = transparentTo(value) + + return { + '@defaults gradient-color-stops': {}, + '--tw-gradient-to': `${transparentToValue} var(--tw-gradient-to-position)`, + '--tw-gradient-stops': `var(--tw-gradient-from), ${toColorValue( + value + )} var(--tw-gradient-via-position), var(--tw-gradient-to)`, + } + }, + }, + options + ) + + matchUtilities( + { + via: (value) => { + return { + '--tw-gradient-via-position': value, + } + }, + }, + positionOptions + ) + + matchUtilities( + { + to: (value) => ({ + '@defaults gradient-color-stops': {}, + '--tw-gradient-to': `${toColorValue(value)} var(--tw-gradient-to-position)`, + }), + }, + options + ) + + matchUtilities( + { + to: (value) => { + return { + '--tw-gradient-to-position': value, + } + }, + }, + positionOptions + ) + } + })(), + + boxDecorationBreak: ({ addUtilities }) => { + addUtilities({ + '.decoration-slice': { 'box-decoration-break': 'slice' }, // Deprecated + '.decoration-clone': { 'box-decoration-break': 'clone' }, // Deprecated + '.box-decoration-slice': { 'box-decoration-break': 'slice' }, + '.box-decoration-clone': { 'box-decoration-break': 'clone' }, + }) + }, + + backgroundSize: createUtilityPlugin('backgroundSize', [['bg', ['background-size']]], { + type: ['lookup', 'length', 'percentage', 'size'], + }), + + backgroundAttachment: ({ addUtilities }) => { + addUtilities({ + '.bg-fixed': { 'background-attachment': 'fixed' }, + '.bg-local': { 'background-attachment': 'local' }, + '.bg-scroll': { 'background-attachment': 'scroll' }, + }) + }, + + backgroundClip: ({ addUtilities }) => { + addUtilities({ + '.bg-clip-border': { 'background-clip': 'border-box' }, + '.bg-clip-padding': { 'background-clip': 'padding-box' }, + '.bg-clip-content': { 'background-clip': 'content-box' }, + '.bg-clip-text': { 'background-clip': 'text' }, + }) + }, + + backgroundPosition: createUtilityPlugin('backgroundPosition', [['bg', ['background-position']]], { + type: ['lookup', ['position', { preferOnConflict: true }]], + }), + + backgroundRepeat: ({ addUtilities }) => { + addUtilities({ + '.bg-repeat': { 'background-repeat': 'repeat' }, + '.bg-no-repeat': { 'background-repeat': 'no-repeat' }, + '.bg-repeat-x': { 'background-repeat': 'repeat-x' }, + '.bg-repeat-y': { 'background-repeat': 'repeat-y' }, + '.bg-repeat-round': { 'background-repeat': 'round' }, + '.bg-repeat-space': { 'background-repeat': 'space' }, + }) + }, + + backgroundOrigin: ({ addUtilities }) => { + addUtilities({ + '.bg-origin-border': { 'background-origin': 'border-box' }, + '.bg-origin-padding': { 'background-origin': 'padding-box' }, + '.bg-origin-content': { 'background-origin': 'content-box' }, + }) + }, + + fill: ({ matchUtilities, theme }) => { + matchUtilities( + { + fill: (value) => { + return { fill: toColorValue(value) } + }, + }, + { values: flattenColorPalette(theme('fill')), type: ['color', 'any'] } + ) + }, + + stroke: ({ matchUtilities, theme }) => { + matchUtilities( + { + stroke: (value) => { + return { stroke: toColorValue(value) } + }, + }, + { values: flattenColorPalette(theme('stroke')), type: ['color', 'url', 'any'] } + ) + }, + + strokeWidth: createUtilityPlugin('strokeWidth', [['stroke', ['stroke-width']]], { + type: ['length', 'number', 'percentage'], + }), + + objectFit: ({ addUtilities }) => { + addUtilities({ + '.object-contain': { 'object-fit': 'contain' }, + '.object-cover': { 'object-fit': 'cover' }, + '.object-fill': { 'object-fit': 'fill' }, + '.object-none': { 'object-fit': 'none' }, + '.object-scale-down': { 'object-fit': 'scale-down' }, + }) + }, + objectPosition: createUtilityPlugin('objectPosition', [['object', ['object-position']]]), + + padding: createUtilityPlugin('padding', [ + ['p', ['padding']], + [ + ['px', ['padding-left', 'padding-right']], + ['py', ['padding-top', 'padding-bottom']], + ], + [ + ['ps', ['padding-inline-start']], + ['pe', ['padding-inline-end']], + ['pt', ['padding-top']], + ['pr', ['padding-right']], + ['pb', ['padding-bottom']], + ['pl', ['padding-left']], + ], + ]), + + textAlign: ({ addUtilities }) => { + addUtilities({ + '.text-left': { 'text-align': 'left' }, + '.text-center': { 'text-align': 'center' }, + '.text-right': { 'text-align': 'right' }, + '.text-justify': { 'text-align': 'justify' }, + '.text-start': { 'text-align': 'start' }, + '.text-end': { 'text-align': 'end' }, + }) + }, + + textIndent: createUtilityPlugin('textIndent', [['indent', ['text-indent']]], { + supportsNegativeValues: true, + }), + + verticalAlign: ({ addUtilities, matchUtilities }) => { + addUtilities({ + '.align-baseline': { 'vertical-align': 'baseline' }, + '.align-top': { 'vertical-align': 'top' }, + '.align-middle': { 'vertical-align': 'middle' }, + '.align-bottom': { 'vertical-align': 'bottom' }, + '.align-text-top': { 'vertical-align': 'text-top' }, + '.align-text-bottom': { 'vertical-align': 'text-bottom' }, + '.align-sub': { 'vertical-align': 'sub' }, + '.align-super': { 'vertical-align': 'super' }, + }) + + matchUtilities({ align: (value) => ({ 'vertical-align': value }) }) + }, + + fontFamily: ({ matchUtilities, theme }) => { + matchUtilities( + { + font: (value) => { + let [families, options = {}] = + Array.isArray(value) && isPlainObject(value[1]) ? value : [value] + let { fontFeatureSettings, fontVariationSettings } = options + + return { + 'font-family': Array.isArray(families) ? families.join(', ') : families, + ...(fontFeatureSettings === undefined + ? {} + : { 'font-feature-settings': fontFeatureSettings }), + ...(fontVariationSettings === undefined + ? {} + : { 'font-variation-settings': fontVariationSettings }), + } + }, + }, + { + values: theme('fontFamily'), + type: ['lookup', 'generic-name', 'family-name'], + } + ) + }, + + fontSize: ({ matchUtilities, theme }) => { + matchUtilities( + { + text: (value, { modifier }) => { + let [fontSize, options] = Array.isArray(value) ? value : [value] + + if (modifier) { + return { + 'font-size': fontSize, + 'line-height': modifier, + } + } + + let { lineHeight, letterSpacing, fontWeight } = isPlainObject(options) + ? options + : { lineHeight: options } + + return { + 'font-size': fontSize, + ...(lineHeight === undefined ? {} : { 'line-height': lineHeight }), + ...(letterSpacing === undefined ? {} : { 'letter-spacing': letterSpacing }), + ...(fontWeight === undefined ? {} : { 'font-weight': fontWeight }), + } + }, + }, + { + values: theme('fontSize'), + modifiers: theme('lineHeight'), + type: ['absolute-size', 'relative-size', 'length', 'percentage'], + } + ) + }, + + fontWeight: createUtilityPlugin('fontWeight', [['font', ['fontWeight']]], { + type: ['lookup', 'number', 'any'], + }), + + textTransform: ({ addUtilities }) => { + addUtilities({ + '.uppercase': { 'text-transform': 'uppercase' }, + '.lowercase': { 'text-transform': 'lowercase' }, + '.capitalize': { 'text-transform': 'capitalize' }, + '.normal-case': { 'text-transform': 'none' }, + }) + }, + + fontStyle: ({ addUtilities }) => { + addUtilities({ + '.italic': { 'font-style': 'italic' }, + '.not-italic': { 'font-style': 'normal' }, + }) + }, + + fontVariantNumeric: ({ addDefaults, addUtilities }) => { + let cssFontVariantNumericValue = + 'var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)' + + addDefaults('font-variant-numeric', { + '--tw-ordinal': ' ', + '--tw-slashed-zero': ' ', + '--tw-numeric-figure': ' ', + '--tw-numeric-spacing': ' ', + '--tw-numeric-fraction': ' ', + }) + + addUtilities({ + '.normal-nums': { 'font-variant-numeric': 'normal' }, + '.ordinal': { + '@defaults font-variant-numeric': {}, + '--tw-ordinal': 'ordinal', + 'font-variant-numeric': cssFontVariantNumericValue, + }, + '.slashed-zero': { + '@defaults font-variant-numeric': {}, + '--tw-slashed-zero': 'slashed-zero', + 'font-variant-numeric': cssFontVariantNumericValue, + }, + '.lining-nums': { + '@defaults font-variant-numeric': {}, + '--tw-numeric-figure': 'lining-nums', + 'font-variant-numeric': cssFontVariantNumericValue, + }, + '.oldstyle-nums': { + '@defaults font-variant-numeric': {}, + '--tw-numeric-figure': 'oldstyle-nums', + 'font-variant-numeric': cssFontVariantNumericValue, + }, + '.proportional-nums': { + '@defaults font-variant-numeric': {}, + '--tw-numeric-spacing': 'proportional-nums', + 'font-variant-numeric': cssFontVariantNumericValue, + }, + '.tabular-nums': { + '@defaults font-variant-numeric': {}, + '--tw-numeric-spacing': 'tabular-nums', + 'font-variant-numeric': cssFontVariantNumericValue, + }, + '.diagonal-fractions': { + '@defaults font-variant-numeric': {}, + '--tw-numeric-fraction': 'diagonal-fractions', + 'font-variant-numeric': cssFontVariantNumericValue, + }, + '.stacked-fractions': { + '@defaults font-variant-numeric': {}, + '--tw-numeric-fraction': 'stacked-fractions', + 'font-variant-numeric': cssFontVariantNumericValue, + }, + }) + }, + + lineHeight: createUtilityPlugin('lineHeight', [['leading', ['lineHeight']]]), + letterSpacing: createUtilityPlugin('letterSpacing', [['tracking', ['letterSpacing']]], { + supportsNegativeValues: true, + }), + + textColor: ({ matchUtilities, theme, corePlugins }) => { + matchUtilities( + { + text: (value) => { + if (!corePlugins('textOpacity')) { + return { color: toColorValue(value) } + } + + return withAlphaVariable({ + color: value, + property: 'color', + variable: '--tw-text-opacity', + }) + }, + }, + { values: flattenColorPalette(theme('textColor')), type: ['color', 'any'] } + ) + }, + + textOpacity: createUtilityPlugin('textOpacity', [['text-opacity', ['--tw-text-opacity']]]), + + textDecoration: ({ addUtilities }) => { + addUtilities({ + '.underline': { 'text-decoration-line': 'underline' }, + '.overline': { 'text-decoration-line': 'overline' }, + '.line-through': { 'text-decoration-line': 'line-through' }, + '.no-underline': { 'text-decoration-line': 'none' }, + }) + }, + + textDecorationColor: ({ matchUtilities, theme }) => { + matchUtilities( + { + decoration: (value) => { + return { 'text-decoration-color': toColorValue(value) } + }, + }, + { values: flattenColorPalette(theme('textDecorationColor')), type: ['color', 'any'] } + ) + }, + + textDecorationStyle: ({ addUtilities }) => { + addUtilities({ + '.decoration-solid': { 'text-decoration-style': 'solid' }, + '.decoration-double': { 'text-decoration-style': 'double' }, + '.decoration-dotted': { 'text-decoration-style': 'dotted' }, + '.decoration-dashed': { 'text-decoration-style': 'dashed' }, + '.decoration-wavy': { 'text-decoration-style': 'wavy' }, + }) + }, + + textDecorationThickness: createUtilityPlugin( + 'textDecorationThickness', + [['decoration', ['text-decoration-thickness']]], + { type: ['length', 'percentage'] } + ), + + textUnderlineOffset: createUtilityPlugin( + 'textUnderlineOffset', + [['underline-offset', ['text-underline-offset']]], + { type: ['length', 'percentage', 'any'] } + ), + + fontSmoothing: ({ addUtilities }) => { + addUtilities({ + '.antialiased': { + '-webkit-font-smoothing': 'antialiased', + '-moz-osx-font-smoothing': 'grayscale', + }, + '.subpixel-antialiased': { + '-webkit-font-smoothing': 'auto', + '-moz-osx-font-smoothing': 'auto', + }, + }) + }, + + placeholderColor: ({ matchUtilities, theme, corePlugins }) => { + matchUtilities( + { + placeholder: (value) => { + if (!corePlugins('placeholderOpacity')) { + return { + '&::placeholder': { + color: toColorValue(value), + }, + } + } + + return { + '&::placeholder': withAlphaVariable({ + color: value, + property: 'color', + variable: '--tw-placeholder-opacity', + }), + } + }, + }, + { values: flattenColorPalette(theme('placeholderColor')), type: ['color', 'any'] } + ) + }, + + placeholderOpacity: ({ matchUtilities, theme }) => { + matchUtilities( + { + 'placeholder-opacity': (value) => { + return { ['&::placeholder']: { '--tw-placeholder-opacity': value } } + }, + }, + { values: theme('placeholderOpacity') } + ) + }, + + caretColor: ({ matchUtilities, theme }) => { + matchUtilities( + { + caret: (value) => { + return { 'caret-color': toColorValue(value) } + }, + }, + { values: flattenColorPalette(theme('caretColor')), type: ['color', 'any'] } + ) + }, + + accentColor: ({ matchUtilities, theme }) => { + matchUtilities( + { + accent: (value) => { + return { 'accent-color': toColorValue(value) } + }, + }, + { values: flattenColorPalette(theme('accentColor')), type: ['color', 'any'] } + ) + }, + + opacity: createUtilityPlugin('opacity', [['opacity', ['opacity']]]), + + backgroundBlendMode: ({ addUtilities }) => { + addUtilities({ + '.bg-blend-normal': { 'background-blend-mode': 'normal' }, + '.bg-blend-multiply': { 'background-blend-mode': 'multiply' }, + '.bg-blend-screen': { 'background-blend-mode': 'screen' }, + '.bg-blend-overlay': { 'background-blend-mode': 'overlay' }, + '.bg-blend-darken': { 'background-blend-mode': 'darken' }, + '.bg-blend-lighten': { 'background-blend-mode': 'lighten' }, + '.bg-blend-color-dodge': { 'background-blend-mode': 'color-dodge' }, + '.bg-blend-color-burn': { 'background-blend-mode': 'color-burn' }, + '.bg-blend-hard-light': { 'background-blend-mode': 'hard-light' }, + '.bg-blend-soft-light': { 'background-blend-mode': 'soft-light' }, + '.bg-blend-difference': { 'background-blend-mode': 'difference' }, + '.bg-blend-exclusion': { 'background-blend-mode': 'exclusion' }, + '.bg-blend-hue': { 'background-blend-mode': 'hue' }, + '.bg-blend-saturation': { 'background-blend-mode': 'saturation' }, + '.bg-blend-color': { 'background-blend-mode': 'color' }, + '.bg-blend-luminosity': { 'background-blend-mode': 'luminosity' }, + }) + }, + + mixBlendMode: ({ addUtilities }) => { + addUtilities({ + '.mix-blend-normal': { 'mix-blend-mode': 'normal' }, + '.mix-blend-multiply': { 'mix-blend-mode': 'multiply' }, + '.mix-blend-screen': { 'mix-blend-mode': 'screen' }, + '.mix-blend-overlay': { 'mix-blend-mode': 'overlay' }, + '.mix-blend-darken': { 'mix-blend-mode': 'darken' }, + '.mix-blend-lighten': { 'mix-blend-mode': 'lighten' }, + '.mix-blend-color-dodge': { 'mix-blend-mode': 'color-dodge' }, + '.mix-blend-color-burn': { 'mix-blend-mode': 'color-burn' }, + '.mix-blend-hard-light': { 'mix-blend-mode': 'hard-light' }, + '.mix-blend-soft-light': { 'mix-blend-mode': 'soft-light' }, + '.mix-blend-difference': { 'mix-blend-mode': 'difference' }, + '.mix-blend-exclusion': { 'mix-blend-mode': 'exclusion' }, + '.mix-blend-hue': { 'mix-blend-mode': 'hue' }, + '.mix-blend-saturation': { 'mix-blend-mode': 'saturation' }, + '.mix-blend-color': { 'mix-blend-mode': 'color' }, + '.mix-blend-luminosity': { 'mix-blend-mode': 'luminosity' }, + '.mix-blend-plus-lighter': { 'mix-blend-mode': 'plus-lighter' }, + }) + }, + + boxShadow: (() => { + let transformValue = transformThemeValue('boxShadow') + let defaultBoxShadow = [ + `var(--tw-ring-offset-shadow, 0 0 #0000)`, + `var(--tw-ring-shadow, 0 0 #0000)`, + `var(--tw-shadow)`, + ].join(', ') + + return function ({ matchUtilities, addDefaults, theme }) { + addDefaults(' box-shadow', { + '--tw-ring-offset-shadow': '0 0 #0000', + '--tw-ring-shadow': '0 0 #0000', + '--tw-shadow': '0 0 #0000', + '--tw-shadow-colored': '0 0 #0000', + }) + + matchUtilities( + { + shadow: (value) => { + value = transformValue(value) + + let ast = parseBoxShadowValue(value) + for (let shadow of ast) { + // Don't override color if the whole shadow is a variable + if (!shadow.valid) { + continue + } + + shadow.color = 'var(--tw-shadow-color)' + } + + return { + '@defaults box-shadow': {}, + '--tw-shadow': value === 'none' ? '0 0 #0000' : value, + '--tw-shadow-colored': value === 'none' ? '0 0 #0000' : formatBoxShadowValue(ast), + 'box-shadow': defaultBoxShadow, + } + }, + }, + { values: theme('boxShadow'), type: ['shadow'] } + ) + } + })(), + + boxShadowColor: ({ matchUtilities, theme }) => { + matchUtilities( + { + shadow: (value) => { + return { + '--tw-shadow-color': toColorValue(value), + '--tw-shadow': 'var(--tw-shadow-colored)', + } + }, + }, + { values: flattenColorPalette(theme('boxShadowColor')), type: ['color', 'any'] } + ) + }, + + outlineStyle: ({ addUtilities }) => { + addUtilities({ + '.outline-none': { + outline: '2px solid transparent', + 'outline-offset': '2px', + }, + '.outline': { 'outline-style': 'solid' }, + '.outline-dashed': { 'outline-style': 'dashed' }, + '.outline-dotted': { 'outline-style': 'dotted' }, + '.outline-double': { 'outline-style': 'double' }, + }) + }, + + outlineWidth: createUtilityPlugin('outlineWidth', [['outline', ['outline-width']]], { + type: ['length', 'number', 'percentage'], + }), + + outlineOffset: createUtilityPlugin('outlineOffset', [['outline-offset', ['outline-offset']]], { + type: ['length', 'number', 'percentage', 'any'], + supportsNegativeValues: true, + }), + + outlineColor: ({ matchUtilities, theme }) => { + matchUtilities( + { + outline: (value) => { + return { 'outline-color': toColorValue(value) } + }, + }, + { values: flattenColorPalette(theme('outlineColor')), type: ['color', 'any'] } + ) + }, + + ringWidth: ({ matchUtilities, addDefaults, addUtilities, theme, config }) => { + let ringColorDefault = (() => { + if (flagEnabled(config(), 'respectDefaultRingColorOpacity')) { + return theme('ringColor.DEFAULT') + } + + let ringOpacityDefault = theme('ringOpacity.DEFAULT', '0.5') + + if (!theme('ringColor')?.DEFAULT) { + return `rgb(147 197 253 / ${ringOpacityDefault})` + } + + return withAlphaValue( + theme('ringColor')?.DEFAULT, + ringOpacityDefault, + `rgb(147 197 253 / ${ringOpacityDefault})` + ) + })() + + addDefaults('ring-width', { + '--tw-ring-inset': ' ', + '--tw-ring-offset-width': theme('ringOffsetWidth.DEFAULT', '0px'), + '--tw-ring-offset-color': theme('ringOffsetColor.DEFAULT', '#fff'), + '--tw-ring-color': ringColorDefault, + '--tw-ring-offset-shadow': '0 0 #0000', + '--tw-ring-shadow': '0 0 #0000', + '--tw-shadow': '0 0 #0000', + '--tw-shadow-colored': '0 0 #0000', + }) + + matchUtilities( + { + ring: (value) => { + return { + '@defaults ring-width': {}, + '--tw-ring-offset-shadow': `var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)`, + '--tw-ring-shadow': `var(--tw-ring-inset) 0 0 0 calc(${value} + var(--tw-ring-offset-width)) var(--tw-ring-color)`, + 'box-shadow': [ + `var(--tw-ring-offset-shadow)`, + `var(--tw-ring-shadow)`, + `var(--tw-shadow, 0 0 #0000)`, + ].join(', '), + } + }, + }, + { values: theme('ringWidth'), type: 'length' } + ) + + addUtilities({ + '.ring-inset': { '@defaults ring-width': {}, '--tw-ring-inset': 'inset' }, + }) + }, + + ringColor: ({ matchUtilities, theme, corePlugins }) => { + matchUtilities( + { + ring: (value) => { + if (!corePlugins('ringOpacity')) { + return { + '--tw-ring-color': toColorValue(value), + } + } + + return withAlphaVariable({ + color: value, + property: '--tw-ring-color', + variable: '--tw-ring-opacity', + }) + }, + }, + { + values: Object.fromEntries( + Object.entries(flattenColorPalette(theme('ringColor'))).filter( + ([modifier]) => modifier !== 'DEFAULT' + ) + ), + type: ['color', 'any'], + } + ) + }, + + ringOpacity: (helpers) => { + let { config } = helpers + + return createUtilityPlugin('ringOpacity', [['ring-opacity', ['--tw-ring-opacity']]], { + filterDefault: !flagEnabled(config(), 'respectDefaultRingColorOpacity'), + })(helpers) + }, + ringOffsetWidth: createUtilityPlugin( + 'ringOffsetWidth', + [['ring-offset', ['--tw-ring-offset-width']]], + { type: 'length' } + ), + + ringOffsetColor: ({ matchUtilities, theme }) => { + matchUtilities( + { + 'ring-offset': (value) => { + return { + '--tw-ring-offset-color': toColorValue(value), + } + }, + }, + { values: flattenColorPalette(theme('ringOffsetColor')), type: ['color', 'any'] } + ) + }, + + blur: ({ matchUtilities, theme }) => { + matchUtilities( + { + blur: (value) => { + return { + '--tw-blur': `blur(${value})`, + '@defaults filter': {}, + filter: cssFilterValue, + } + }, + }, + { values: theme('blur') } + ) + }, + + brightness: ({ matchUtilities, theme }) => { + matchUtilities( + { + brightness: (value) => { + return { + '--tw-brightness': `brightness(${value})`, + '@defaults filter': {}, + filter: cssFilterValue, + } + }, + }, + { values: theme('brightness') } + ) + }, + + contrast: ({ matchUtilities, theme }) => { + matchUtilities( + { + contrast: (value) => { + return { + '--tw-contrast': `contrast(${value})`, + '@defaults filter': {}, + filter: cssFilterValue, + } + }, + }, + { values: theme('contrast') } + ) + }, + + dropShadow: ({ matchUtilities, theme }) => { + matchUtilities( + { + 'drop-shadow': (value) => { + return { + '--tw-drop-shadow': Array.isArray(value) + ? value.map((v) => `drop-shadow(${v})`).join(' ') + : `drop-shadow(${value})`, + '@defaults filter': {}, + filter: cssFilterValue, + } + }, + }, + { values: theme('dropShadow') } + ) + }, + + grayscale: ({ matchUtilities, theme }) => { + matchUtilities( + { + grayscale: (value) => { + return { + '--tw-grayscale': `grayscale(${value})`, + '@defaults filter': {}, + filter: cssFilterValue, + } + }, + }, + { values: theme('grayscale') } + ) + }, + + hueRotate: ({ matchUtilities, theme }) => { + matchUtilities( + { + 'hue-rotate': (value) => { + return { + '--tw-hue-rotate': `hue-rotate(${value})`, + '@defaults filter': {}, + filter: cssFilterValue, + } + }, + }, + { values: theme('hueRotate'), supportsNegativeValues: true } + ) + }, + + invert: ({ matchUtilities, theme }) => { + matchUtilities( + { + invert: (value) => { + return { + '--tw-invert': `invert(${value})`, + '@defaults filter': {}, + filter: cssFilterValue, + } + }, + }, + { values: theme('invert') } + ) + }, + + saturate: ({ matchUtilities, theme }) => { + matchUtilities( + { + saturate: (value) => { + return { + '--tw-saturate': `saturate(${value})`, + '@defaults filter': {}, + filter: cssFilterValue, + } + }, + }, + { values: theme('saturate') } + ) + }, + + sepia: ({ matchUtilities, theme }) => { + matchUtilities( + { + sepia: (value) => { + return { + '--tw-sepia': `sepia(${value})`, + '@defaults filter': {}, + filter: cssFilterValue, + } + }, + }, + { values: theme('sepia') } + ) + }, + + filter: ({ addDefaults, addUtilities }) => { + addDefaults('filter', { + '--tw-blur': ' ', + '--tw-brightness': ' ', + '--tw-contrast': ' ', + '--tw-grayscale': ' ', + '--tw-hue-rotate': ' ', + '--tw-invert': ' ', + '--tw-saturate': ' ', + '--tw-sepia': ' ', + '--tw-drop-shadow': ' ', + }) + addUtilities({ + '.filter': { '@defaults filter': {}, filter: cssFilterValue }, + '.filter-none': { filter: 'none' }, + }) + }, + + backdropBlur: ({ matchUtilities, theme }) => { + matchUtilities( + { + 'backdrop-blur': (value) => { + return { + '--tw-backdrop-blur': `blur(${value})`, + '@defaults backdrop-filter': {}, + 'backdrop-filter': cssBackdropFilterValue, + } + }, + }, + { values: theme('backdropBlur') } + ) + }, + + backdropBrightness: ({ matchUtilities, theme }) => { + matchUtilities( + { + 'backdrop-brightness': (value) => { + return { + '--tw-backdrop-brightness': `brightness(${value})`, + '@defaults backdrop-filter': {}, + 'backdrop-filter': cssBackdropFilterValue, + } + }, + }, + { values: theme('backdropBrightness') } + ) + }, + + backdropContrast: ({ matchUtilities, theme }) => { + matchUtilities( + { + 'backdrop-contrast': (value) => { + return { + '--tw-backdrop-contrast': `contrast(${value})`, + '@defaults backdrop-filter': {}, + 'backdrop-filter': cssBackdropFilterValue, + } + }, + }, + { values: theme('backdropContrast') } + ) + }, + + backdropGrayscale: ({ matchUtilities, theme }) => { + matchUtilities( + { + 'backdrop-grayscale': (value) => { + return { + '--tw-backdrop-grayscale': `grayscale(${value})`, + '@defaults backdrop-filter': {}, + 'backdrop-filter': cssBackdropFilterValue, + } + }, + }, + { values: theme('backdropGrayscale') } + ) + }, + + backdropHueRotate: ({ matchUtilities, theme }) => { + matchUtilities( + { + 'backdrop-hue-rotate': (value) => { + return { + '--tw-backdrop-hue-rotate': `hue-rotate(${value})`, + '@defaults backdrop-filter': {}, + 'backdrop-filter': cssBackdropFilterValue, + } + }, + }, + { values: theme('backdropHueRotate'), supportsNegativeValues: true } + ) + }, + + backdropInvert: ({ matchUtilities, theme }) => { + matchUtilities( + { + 'backdrop-invert': (value) => { + return { + '--tw-backdrop-invert': `invert(${value})`, + '@defaults backdrop-filter': {}, + 'backdrop-filter': cssBackdropFilterValue, + } + }, + }, + { values: theme('backdropInvert') } + ) + }, + + backdropOpacity: ({ matchUtilities, theme }) => { + matchUtilities( + { + 'backdrop-opacity': (value) => { + return { + '--tw-backdrop-opacity': `opacity(${value})`, + '@defaults backdrop-filter': {}, + 'backdrop-filter': cssBackdropFilterValue, + } + }, + }, + { values: theme('backdropOpacity') } + ) + }, + + backdropSaturate: ({ matchUtilities, theme }) => { + matchUtilities( + { + 'backdrop-saturate': (value) => { + return { + '--tw-backdrop-saturate': `saturate(${value})`, + '@defaults backdrop-filter': {}, + 'backdrop-filter': cssBackdropFilterValue, + } + }, + }, + { values: theme('backdropSaturate') } + ) + }, + + backdropSepia: ({ matchUtilities, theme }) => { + matchUtilities( + { + 'backdrop-sepia': (value) => { + return { + '--tw-backdrop-sepia': `sepia(${value})`, + '@defaults backdrop-filter': {}, + 'backdrop-filter': cssBackdropFilterValue, + } + }, + }, + { values: theme('backdropSepia') } + ) + }, + + backdropFilter: ({ addDefaults, addUtilities }) => { + addDefaults('backdrop-filter', { + '--tw-backdrop-blur': ' ', + '--tw-backdrop-brightness': ' ', + '--tw-backdrop-contrast': ' ', + '--tw-backdrop-grayscale': ' ', + '--tw-backdrop-hue-rotate': ' ', + '--tw-backdrop-invert': ' ', + '--tw-backdrop-opacity': ' ', + '--tw-backdrop-saturate': ' ', + '--tw-backdrop-sepia': ' ', + }) + addUtilities({ + '.backdrop-filter': { + '@defaults backdrop-filter': {}, + 'backdrop-filter': cssBackdropFilterValue, + }, + '.backdrop-filter-none': { 'backdrop-filter': 'none' }, + }) + }, + + transitionProperty: ({ matchUtilities, theme }) => { + let defaultTimingFunction = theme('transitionTimingFunction.DEFAULT') + let defaultDuration = theme('transitionDuration.DEFAULT') + + matchUtilities( + { + transition: (value) => { + return { + 'transition-property': value, + ...(value === 'none' + ? {} + : { + 'transition-timing-function': defaultTimingFunction, + 'transition-duration': defaultDuration, + }), + } + }, + }, + { values: theme('transitionProperty') } + ) + }, + + transitionDelay: createUtilityPlugin('transitionDelay', [['delay', ['transitionDelay']]]), + transitionDuration: createUtilityPlugin( + 'transitionDuration', + [['duration', ['transitionDuration']]], + { filterDefault: true } + ), + transitionTimingFunction: createUtilityPlugin( + 'transitionTimingFunction', + [['ease', ['transitionTimingFunction']]], + { filterDefault: true } + ), + willChange: createUtilityPlugin('willChange', [['will-change', ['will-change']]]), + content: createUtilityPlugin('content', [ + ['content', ['--tw-content', ['content', 'var(--tw-content)']]], + ]), +} diff --git a/node_modules/tailwindcss/src/css/LICENSE b/node_modules/tailwindcss/src/css/LICENSE new file mode 100644 index 0000000..a1fb039 --- /dev/null +++ b/node_modules/tailwindcss/src/css/LICENSE @@ -0,0 +1,25 @@ +MIT License + +Copyright (c) Nicolas Gallagher +Copyright (c) Jonathan Neal +Copyright (c) Sindre Sorhus (sindresorhus.com) +Copyright (c) Adam Wathan +Copyright (c) Jonathan Reinink + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/tailwindcss/src/css/preflight.css b/node_modules/tailwindcss/src/css/preflight.css new file mode 100644 index 0000000..e5e52cd --- /dev/null +++ b/node_modules/tailwindcss/src/css/preflight.css @@ -0,0 +1,378 @@ +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; /* 1 */ + border-width: 0; /* 2 */ + border-style: solid; /* 2 */ + border-color: theme('borderColor.DEFAULT', currentColor); /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +*/ + +html { + line-height: 1.5; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ + -moz-tab-size: 4; /* 3 */ + tab-size: 4; /* 3 */ + font-family: theme('fontFamily.sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); /* 4 */ + font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings', normal); /* 5 */ + font-variation-settings: theme('fontFamily.sans[1].fontVariationSettings', normal); /* 6 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; /* 1 */ + line-height: inherit; /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; /* 1 */ + color: inherit; /* 2 */ + border-top-width: 1px; /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font family by default. +2. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: theme('fontFamily.mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); /* 1 */ + font-size: 1em; /* 2 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; /* 1 */ + border-color: inherit; /* 2 */ + border-collapse: collapse; /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-feature-settings: inherit; /* 1 */ + font-variation-settings: inherit; /* 1 */ + font-size: 100%; /* 1 */ + font-weight: inherit; /* 1 */ + line-height: inherit; /* 1 */ + color: inherit; /* 1 */ + margin: 0; /* 2 */ + padding: 0; /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; /* 1 */ + background-color: transparent; /* 2 */ + background-image: none; /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::placeholder, +textarea::placeholder { + opacity: 1; /* 1 */ + color: theme('colors.gray.400', #9ca3af); /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; /* 1 */ + vertical-align: middle; /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ +[hidden] { + display: none; +} diff --git a/node_modules/tailwindcss/src/featureFlags.js b/node_modules/tailwindcss/src/featureFlags.js new file mode 100644 index 0000000..76dd373 --- /dev/null +++ b/node_modules/tailwindcss/src/featureFlags.js @@ -0,0 +1,69 @@ +import colors from 'picocolors' +import log from './util/log' + +let defaults = { + optimizeUniversalDefaults: false, + generalizedModifiers: true, + get disableColorOpacityUtilitiesByDefault() { + return __OXIDE__ + }, + get relativeContentPathsByDefault() { + return __OXIDE__ + }, +} + +let featureFlags = { + future: [ + 'hoverOnlyWhenSupported', + 'respectDefaultRingColorOpacity', + 'disableColorOpacityUtilitiesByDefault', + 'relativeContentPathsByDefault', + ], + experimental: [ + 'optimizeUniversalDefaults', + 'generalizedModifiers', + ], +} + +export function flagEnabled(config, flag) { + if (featureFlags.future.includes(flag)) { + return config.future === 'all' || (config?.future?.[flag] ?? defaults[flag] ?? false) + } + + if (featureFlags.experimental.includes(flag)) { + return ( + config.experimental === 'all' || (config?.experimental?.[flag] ?? defaults[flag] ?? false) + ) + } + + return false +} + +function experimentalFlagsEnabled(config) { + if (config.experimental === 'all') { + return featureFlags.experimental + } + + return Object.keys(config?.experimental ?? {}).filter( + (flag) => featureFlags.experimental.includes(flag) && config.experimental[flag] + ) +} + +export function issueFlagNotices(config) { + if (process.env.JEST_WORKER_ID !== undefined) { + return + } + + if (experimentalFlagsEnabled(config).length > 0) { + let changes = experimentalFlagsEnabled(config) + .map((s) => colors.yellow(s)) + .join(', ') + + log.warn('experimental-flags-enabled', [ + `You have enabled experimental features: ${changes}`, + 'Experimental features in Tailwind CSS are not covered by semver, may introduce breaking changes, and can change at any time.', + ]) + } +} + +export default featureFlags diff --git a/node_modules/tailwindcss/src/index.js b/node_modules/tailwindcss/src/index.js new file mode 100644 index 0000000..d0b329b --- /dev/null +++ b/node_modules/tailwindcss/src/index.js @@ -0,0 +1 @@ +module.exports = require('./plugin') 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} fileModifiedMap + * @returns {[{ content: string, extension: string }[], Map]} + */ +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} fileModifiedMap + * @returns {[Set, Map]} + */ +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} ApplyCache */ + +function extractClasses(node) { + /** @type {Map>} */ + 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} */ + 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