├── LICENSE.md ├── composer.json └── src ├── Contracts ├── TailwindMergeContract.php └── ValidatorContract.php ├── Factory.php ├── Support ├── Arr.php ├── ClassMap.php ├── Collection.php ├── Config.php ├── Str.php ├── Stringable.php └── TailwindClassParser.php ├── TailwindMerge.php ├── Validators ├── AnyValueValidator.php ├── ArbitraryImageValidator.php ├── ArbitraryLengthValidator.php ├── ArbitraryNumberValidator.php ├── ArbitraryPositionValidator.php ├── ArbitraryShadowValidator.php ├── ArbitrarySizeValidator.php ├── ArbitraryValueValidator.php ├── Concerns │ └── ValidatesArbitraryValue.php ├── IntegerValidator.php ├── LengthValidator.php ├── NumberValidator.php ├── PercentValidator.php └── TshirtSizeValidator.php └── ValueObjects ├── ClassPartObject.php ├── ClassValidatorObject.php ├── ParsedClass.php └── ThemeGetter.php /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Sandro Gehri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gehrisandro/tailwind-merge-php", 3 | "description": "TailwindMerge for PHP merges multiple Tailwind CSS classes by automatically resolving conflicts between them", 4 | "keywords": ["php", "tailwindcss", "merge", "classes"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Sandro Gehri", 9 | "email": "sandrogehri@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^8.1.0", 14 | "psr/simple-cache": "^3.0" 15 | }, 16 | "require-dev": { 17 | "laravel/pint": "^1.13.8", 18 | "nunomaduro/collision": "^7.10", 19 | "pestphp/pest": "^v2.24.0", 20 | "pestphp/pest-plugin-arch": "^2.6", 21 | "pestphp/pest-plugin-mock": "^2.0.0", 22 | "pestphp/pest-plugin-type-coverage": "^2.8", 23 | "phpstan/phpstan": "^1.10.55", 24 | "rector/rector": "^1.0.5", 25 | "symfony/var-dumper": "^6.4.2" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "TailwindMerge\\": "src/" 30 | }, 31 | "files": [ 32 | "src/TailwindMerge.php" 33 | ] 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "Tests\\": "tests/" 38 | } 39 | }, 40 | "minimum-stability": "dev", 41 | "prefer-stable": true, 42 | "config": { 43 | "sort-packages": true, 44 | "preferred-install": "dist", 45 | "allow-plugins": { 46 | "pestphp/pest-plugin": true 47 | } 48 | }, 49 | "scripts": { 50 | "refactor:lint": "pint -v", 51 | "refactor:rector": "rector", 52 | "test:lint": "pint --test -v", 53 | "test:refactor": "rector --dry-run", 54 | "test:types": "phpstan analyse --ansi", 55 | "test:type-coverage": "pest --type-coverage --min=100", 56 | "test:pest": "pest --colors=always", 57 | "test": [ 58 | "@test:lint", 59 | "@test:refactor", 60 | "@test:types", 61 | "@test:type-coverage", 62 | "@test:pest" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Contracts/TailwindMergeContract.php: -------------------------------------------------------------------------------- 1 | > ...$args 9 | */ 10 | public function merge(...$args): string; 11 | } 12 | -------------------------------------------------------------------------------- /src/Contracts/ValidatorContract.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | private array $additionalConfiguration = []; 14 | 15 | private ?CacheInterface $cache = null; 16 | 17 | /** 18 | * Override the default configuration. 19 | * 20 | * @param array $configuration 21 | */ 22 | public function withConfiguration(array $configuration): self 23 | { 24 | $this->additionalConfiguration = $configuration; 25 | 26 | return $this; 27 | } 28 | 29 | public function withCache(CacheInterface $cache): self 30 | { 31 | $this->cache = $cache; 32 | 33 | return $this; 34 | } 35 | 36 | /** 37 | * Creates a new TailwindMerge instance. 38 | */ 39 | public function make(): TailwindMerge 40 | { 41 | Config::setAdditionalConfig($this->additionalConfiguration); 42 | $config = Config::getMergedConfig(); 43 | 44 | return new TailwindMerge($config, $this->cache); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Support/Arr.php: -------------------------------------------------------------------------------- 1 | $array 9 | * @return array 10 | */ 11 | public static function flatten(array $array, int $depth = PHP_INT_MAX): array 12 | { 13 | $result = []; 14 | 15 | foreach ($array as $item) { 16 | $item = $item instanceof Collection ? $item->all() : $item; 17 | 18 | if (! is_array($item)) { 19 | $result[] = $item; 20 | } else { 21 | $values = $depth === 1 22 | ? array_values($item) 23 | : static::flatten($item, $depth - 1); 24 | 25 | foreach ($values as $value) { 26 | $result[] = $value; 27 | } 28 | } 29 | } 30 | 31 | return $result; 32 | } 33 | 34 | /** 35 | * @template TKey 36 | * @template TValue 37 | * @template TMapWithKeysKey of array-key 38 | * @template TMapWithKeysValue 39 | * 40 | * @param array $array 41 | * @param callable(TValue, TKey): array $callback 42 | * @return array 43 | */ 44 | public static function mapWithKeys(array $array, callable $callback): array 45 | { 46 | $result = []; 47 | 48 | foreach ($array as $key => $value) { 49 | $assoc = $callback($value, $key); 50 | 51 | foreach ($assoc as $mapKey => $mapValue) { 52 | $result[$mapKey] = $mapValue; 53 | } 54 | } 55 | 56 | return $result; 57 | } 58 | 59 | /** 60 | * @template TKey 61 | * @template TValue 62 | * @template TMapValue 63 | * 64 | * @param array $array 65 | * @param callable(TValue, TKey): TMapValue $callback 66 | * @return array 67 | */ 68 | public static function map(array $array, callable $callback): array 69 | { 70 | $keys = array_keys($array); 71 | 72 | $items = array_map($callback, $array, $keys); 73 | 74 | return array_combine($keys, $items); 75 | } 76 | 77 | /** 78 | * @template TKey 79 | * @template TValue 80 | * 81 | * @param array $array 82 | * @param callable(TValue, TKey): bool $callback 83 | * @return ?TValue 84 | */ 85 | public static function first(array $array, callable $callback): mixed 86 | { 87 | foreach ($array as $key => $value) { 88 | if ($callback($value, $key)) { 89 | return $value; 90 | } 91 | } 92 | 93 | return null; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Support/ClassMap.php: -------------------------------------------------------------------------------- 1 | , classGroups: array,conflictingClassGroups: array>, conflictingClassGroupModifiers: array>} $config 15 | */ 16 | public static function create(array $config): ClassPartObject 17 | { 18 | $theme = $config['theme']; 19 | $prefix = $config['prefix']; 20 | 21 | $classMap = new ClassPartObject(); 22 | 23 | $prefixedClassGroupEntries = self::getPrefixedClassGroupEntries( 24 | $config['classGroups'], 25 | $prefix, 26 | ); 27 | 28 | foreach ($prefixedClassGroupEntries as $classGroupId => $classGroup) { 29 | self::processClassesRecursively($classGroup, $classMap, $classGroupId, $theme); 30 | } 31 | 32 | return $classMap; 33 | } 34 | 35 | /** 36 | * @param array $classGroupEntries 37 | * @return array 38 | */ 39 | private static function getPrefixedClassGroupEntries(array $classGroupEntries, ?string $prefix): array 40 | { 41 | if (! $prefix) { 42 | return $classGroupEntries; 43 | } 44 | 45 | // @phpstan-ignore-next-line 46 | return Collection::make($classGroupEntries)->mapWithKeys(function (array $classGroup, string $classGroupId) use ($prefix): array { 47 | $prefixedClassGroup = Collection::make($classGroup)->map(function (string|array $classDefinition) use ($prefix): string|array { 48 | if (is_string($classDefinition)) { 49 | return $prefix.$classDefinition; 50 | } 51 | 52 | if (is_array($classDefinition)) { 53 | return Collection::make($classDefinition)->mapWithKeys(fn (array $value, string $key): array => [$prefix.$key => $value])->all(); 54 | } 55 | 56 | // return $classDefinition; 57 | })->all(); 58 | 59 | return [$classGroupId => $prefixedClassGroup]; 60 | })->all(); 61 | } 62 | 63 | public static function processClassesRecursively(array $classGroup, ClassPartObject $classPartObject, string $classGroupId, array $theme): void 64 | { 65 | foreach ($classGroup as $classDefinition) { 66 | if (is_string($classDefinition)) { 67 | $classPartObjectToEdit = $classDefinition === '' ? $classPartObject : self::getPart($classPartObject, $classDefinition); 68 | $classPartObjectToEdit->classGroupId = $classGroupId; 69 | 70 | continue; 71 | } 72 | 73 | if (self::isThemeGetter($classDefinition)) { 74 | self::processClassesRecursively( 75 | $classDefinition->get($theme), 76 | $classPartObject, 77 | $classGroupId, 78 | $theme, 79 | ); 80 | 81 | continue; 82 | } 83 | 84 | if (is_callable($classDefinition)) { 85 | $classPartObject->validators[] = new ClassValidatorObject( 86 | classGroupId: $classGroupId, 87 | validator: $classDefinition, 88 | ); 89 | 90 | continue; 91 | } 92 | 93 | foreach ($classDefinition as $key => $classGroup) { 94 | self::processClassesRecursively( 95 | $classGroup, 96 | self::getPart($classPartObject, $key), 97 | $classGroupId, 98 | $theme, 99 | ); 100 | } 101 | } 102 | } 103 | 104 | private static function isThemeGetter(ThemeGetter|array|callable $classDefinition): bool 105 | { 106 | return $classDefinition instanceof ThemeGetter; 107 | } 108 | 109 | private static function getPart(ClassPartObject $classPartObject, string $path): ClassPartObject 110 | { 111 | $currentClassPartObject = $classPartObject; 112 | 113 | foreach (explode(self::CLASS_PART_SEPARATOR, $path) as $pathPart) { 114 | if (! isset($currentClassPartObject->nextPart[$pathPart])) { 115 | $currentClassPartObject->nextPart[$pathPart] = new ClassPartObject(); 116 | } 117 | 118 | $currentClassPartObject = $currentClassPartObject->nextPart[$pathPart]; 119 | } 120 | 121 | return $currentClassPartObject; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Support/Collection.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | protected array $items = []; 17 | 18 | /** 19 | * @param self|array $items 20 | * @return void 21 | */ 22 | public function __construct(self|array $items = []) 23 | { 24 | if ($items instanceof self) { 25 | $items = $items->all(); 26 | } 27 | 28 | $this->items = $items; 29 | } 30 | 31 | /** 32 | * @template TMakeKey of array-key 33 | * @template TMakeValue 34 | * 35 | * @param array $items 36 | * @return self 37 | */ 38 | public static function make(array $items = []): self 39 | { 40 | return new self($items); 41 | } 42 | 43 | public function contains(string $key): bool 44 | { 45 | return in_array($key, $this->items); 46 | } 47 | 48 | /** 49 | * @return self 50 | */ 51 | public function flatten(int $depth = PHP_INT_MAX): self 52 | { 53 | return new self(Arr::flatten($this->items, $depth)); 54 | } 55 | 56 | public function join(string $glue): string 57 | { 58 | return implode($glue, $this->items); 59 | } 60 | 61 | /** 62 | * @template TMapWithKeysKey of array-key 63 | * @template TMapWithKeysValue 64 | * 65 | * @param callable(TValue, TKey): array $callback 66 | * @return self 67 | */ 68 | public function mapWithKeys(callable $callback): self 69 | { 70 | return new self(Arr::mapWithKeys($this->items, $callback)); // @phpstan-ignore-line 71 | } 72 | 73 | /** 74 | * @return self 75 | */ 76 | public function sort(): self 77 | { 78 | $items = $this->items; 79 | 80 | asort($items); 81 | 82 | return new self($items); 83 | } 84 | 85 | /** 86 | * @return self 87 | */ 88 | public function values(): self 89 | { 90 | return new self(array_values($this->items)); 91 | } 92 | 93 | /** 94 | * @return array 95 | */ 96 | public function toArray(): array 97 | { 98 | return $this->map(fn (mixed $value): mixed => $value instanceof Collection ? $value->toArray() : $value)->all(); 99 | } 100 | 101 | /** 102 | * @template TMapValue 103 | * 104 | * @param callable(TValue, TKey): TMapValue $callback 105 | * @return self 106 | */ 107 | public function map(callable $callback): self 108 | { 109 | return new self(Arr::map($this->items, $callback)); // @phpstan-ignore-line 110 | } 111 | 112 | /** 113 | * @return array 114 | */ 115 | public function all(): array 116 | { 117 | return $this->items; 118 | } 119 | 120 | /** 121 | * @param TValue $item 122 | * @return $this 123 | */ 124 | public function add(mixed $item): self 125 | { 126 | $this->items[] = $item; 127 | 128 | return $this; 129 | } 130 | 131 | /** 132 | * @param (callable(TValue, TKey): bool)|null $callback 133 | * @return ?TValue 134 | */ 135 | public function first(?callable $callback = null): mixed 136 | { 137 | return Arr::first($this->items, $callback); // @phpstan-ignore-line 138 | } 139 | 140 | /** 141 | * @param array|self $source 142 | * @return self 143 | */ 144 | public function concat(array|self $source): self 145 | { 146 | $result = new self($this); 147 | 148 | if ($source instanceof self) { 149 | $source = $source->all(); 150 | } 151 | 152 | foreach ($source as $item) { 153 | $result->push($item); 154 | } 155 | 156 | return $result; 157 | } 158 | 159 | /** 160 | * @param TValue ...$values 161 | * @return $this 162 | */ 163 | public function push(...$values): self 164 | { 165 | foreach ($values as $value) { 166 | $this->items[] = $value; 167 | } 168 | 169 | return $this; 170 | } 171 | 172 | /** 173 | * @return self 174 | */ 175 | public function reverse(): self 176 | { 177 | return new self(array_reverse($this->items, true)); 178 | } 179 | 180 | /** 181 | * @return self 182 | */ 183 | public function filter(): self 184 | { 185 | return new self(array_filter($this->items)); 186 | } 187 | 188 | public function dd(): void 189 | { 190 | dd($this->items); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/Support/Config.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | private static array $additionalConfig = []; 26 | 27 | /** 28 | * @return array 29 | */ 30 | public static function getMergedConfig(): array 31 | { 32 | $config = self::getDefaultConfig(); 33 | 34 | foreach (self::$additionalConfig as $key => $additionalConfig) { 35 | $config[$key] = self::mergePropertyRecursively($config, $key, $additionalConfig); 36 | } 37 | 38 | return $config; 39 | } 40 | 41 | private static function mergePropertyRecursively(array $baseConfig, string $mergeKey, array|bool|float|int|string|null $mergeValue): array|bool|float|int|string|null 42 | { 43 | if (! array_key_exists($mergeKey, $baseConfig)) { 44 | return $mergeValue; 45 | } 46 | if (is_string($mergeValue)) { 47 | return $mergeValue; 48 | } 49 | if (is_numeric($mergeValue)) { 50 | return $mergeValue; 51 | } 52 | if (is_bool($mergeValue)) { 53 | return $mergeValue; 54 | } 55 | if ($mergeValue === null) { 56 | return $mergeValue; 57 | } 58 | if (is_array($mergeValue) && array_is_list($mergeValue) && is_array($baseConfig[$mergeKey]) && array_is_list($baseConfig[$mergeKey])) { 59 | return [...$baseConfig[$mergeKey], ...$mergeValue]; 60 | } 61 | 62 | if (is_array($mergeValue) && ! array_is_list($mergeValue) /* && is_array($baseConfig[$mergeKey]) && array_is_list($baseConfig[$mergeKey]) */) { 63 | if ($baseConfig[$mergeKey] === null) { 64 | return $mergeValue; 65 | } 66 | 67 | foreach ($mergeValue as $key => $value) { 68 | $baseConfig[$mergeKey][$key] = self::mergePropertyRecursively($baseConfig[$mergeKey], $key, $value); 69 | } 70 | } 71 | 72 | return $baseConfig[$mergeKey]; 73 | } 74 | 75 | /** 76 | * @param array $config 77 | */ 78 | public static function setAdditionalConfig(array $config): void 79 | { 80 | self::$additionalConfig = $config; 81 | } 82 | 83 | /** 84 | * @return array{cacheSize: int, prefix: ?string, theme: array, classGroups: array,conflictingClassGroups: array>, conflictingClassGroupModifiers: array>} 85 | */ 86 | public static function getDefaultConfig(): array 87 | { 88 | $colors = self::fromTheme('colors'); 89 | $spacing = self::fromTheme('spacing'); 90 | $blur = self::fromTheme('blur'); 91 | $brightness = self::fromTheme('brightness'); 92 | $borderColor = self::fromTheme('borderColor'); 93 | $borderRadius = self::fromTheme('borderRadius'); 94 | $borderSpacing = self::fromTheme('borderSpacing'); 95 | $borderWidth = self::fromTheme('borderWidth'); 96 | $contrast = self::fromTheme('contrast'); 97 | $grayscale = self::fromTheme('grayscale'); 98 | $hueRotate = self::fromTheme('hueRotate'); 99 | $invert = self::fromTheme('invert'); 100 | $gap = self::fromTheme('gap'); 101 | $gradientColorStops = self::fromTheme('gradientColorStops'); 102 | $gradientColorStopPositions = self::fromTheme('gradientColorStopPositions'); 103 | $inset = self::fromTheme('inset'); 104 | $margin = self::fromTheme('margin'); 105 | $opacity = self::fromTheme('opacity'); 106 | $padding = self::fromTheme('padding'); 107 | $saturate = self::fromTheme('saturate'); 108 | $scale = self::fromTheme('scale'); 109 | $sepia = self::fromTheme('sepia'); 110 | $skew = self::fromTheme('skew'); 111 | $space = self::fromTheme('space'); 112 | $translate = self::fromTheme('translate'); 113 | 114 | return [ 115 | 'cacheSize' => 500, 116 | 'prefix' => null, 117 | 'theme' => [ 118 | 'colors' => [AnyValueValidator::validate(...)], 119 | 'spacing' => [LengthValidator::validate(...), ArbitraryLengthValidator::validate(...)], 120 | 'blur' => ['none', '', TshirtSizeValidator::validate(...), ArbitraryValueValidator::validate(...)], 121 | 'brightness' => self::getNumber(), 122 | 'borderColor' => [$colors], 123 | 'borderRadius' => ['none', '', 'full', TshirtSizeValidator::validate(...), ArbitraryValueValidator::validate(...)], 124 | 'borderSpacing' => self::getSpacingWithArbitrary($spacing), 125 | 'borderWidth' => self::getLengthWithEmptyAndArbitrary(), 126 | 'contrast' => self::getNumber(), 127 | 'grayscale' => self::getZeroAndEmpty(), 128 | 'hueRotate' => self::getNumberAndArbitrary(), 129 | 'invert' => self::getZeroAndEmpty(), 130 | 'gap' => self::getSpacingWithArbitrary($spacing), 131 | 'gradientColorStops' => [$colors], 132 | 'gradientColorStopPositions' => [PercentValidator::validate(...), ArbitraryLengthValidator::validate(...)], 133 | 'inset' => self::getSpacingWithAutoAndArbitrary($spacing), 134 | 'margin' => self::getSpacingWithAutoAndArbitrary($spacing), 135 | 'opacity' => self::getNumber(), 136 | 'padding' => self::getSpacingWithArbitrary($spacing), 137 | 'saturate' => self::getNumber(), 138 | 'scale' => self::getNumber(), 139 | 'sepia' => self::getZeroAndEmpty(), 140 | 'skew' => self::getNumberAndArbitrary(), 141 | 'space' => self::getSpacingWithArbitrary($spacing), 142 | 'translate' => self::getSpacingWithArbitrary($spacing), 143 | ], 144 | 'classGroups' => [ 145 | // Layout 146 | /** 147 | * Aspect Ratio 148 | * 149 | * @see https://tailwindcss.com/docs/aspect-ratio 150 | */ 151 | 'aspect' => [['aspect' => ['auto', 'square', 'video', ArbitraryValueValidator::validate(...)]]], 152 | /** 153 | * Container 154 | * 155 | * @see https://tailwindcss.com/docs/container 156 | */ 157 | 'container' => ['container'], 158 | /** 159 | * Columns 160 | * 161 | * @see https://tailwindcss.com/docs/columns 162 | */ 163 | 'columns' => [['columns' => [TshirtSizeValidator::validate(...)]]], 164 | /** 165 | * Break After 166 | * 167 | * @see https://tailwindcss.com/docs/break-after 168 | */ 169 | 'break-after' => [['break-after' => self::getBreaks()]], 170 | /** 171 | * Break Before 172 | * 173 | * @see https://tailwindcss.com/docs/break-before 174 | */ 175 | 'break-before' => [['break-before' => self::getBreaks()]], 176 | /** 177 | * Break Inside 178 | * 179 | * @see https://tailwindcss.com/docs/break-inside 180 | */ 181 | 'break-inside' => [['break-inside' => ['auto', 'avoid', 'avoid-page', 'avoid-column']]], 182 | /** 183 | * Box Decoration Break 184 | * 185 | * @see https://tailwindcss.com/docs/box-decoration-break 186 | */ 187 | 'box-decoration' => [['box-decoration' => ['slice', 'clone']]], 188 | /** 189 | * Box Sizing 190 | * 191 | * @see https://tailwindcss.com/docs/box-sizing 192 | */ 193 | 'box' => [['box' => ['border', 'content']]], 194 | /** 195 | * Display 196 | * 197 | * @see https://tailwindcss.com/docs/display 198 | */ 199 | 'display' => [ 200 | 'block', 201 | 'inline-block', 202 | 'inline', 203 | 'flex', 204 | 'inline-flex', 205 | 'table', 206 | 'inline-table', 207 | 'table-caption', 208 | 'table-cell', 209 | 'table-column', 210 | 'table-column-group', 211 | 'table-footer-group', 212 | 'table-header-group', 213 | 'table-row-group', 214 | 'table-row', 215 | 'flow-root', 216 | 'grid', 217 | 'inline-grid', 218 | 'contents', 219 | 'list-item', 220 | 'hidden', 221 | ], 222 | /** 223 | * Floats 224 | * 225 | * @see https://tailwindcss.com/docs/float 226 | */ 227 | 'float' => [['float' => ['right', 'left', 'none', 'start', 'end']]], 228 | /** 229 | * Clear 230 | * 231 | * @see https://tailwindcss.com/docs/clear 232 | */ 233 | 'clear' => [['clear' => ['left', 'right', 'both', 'none', 'start', 'end']]], 234 | /** 235 | * Isolation 236 | * 237 | * @see https://tailwindcss.com/docs/isolation 238 | */ 239 | 'isolation' => ['isolate', 'isolation-auto'], 240 | /** 241 | * Object Fit 242 | * 243 | * @see https://tailwindcss.com/docs/object-fit 244 | */ 245 | 'object-fit' => [['object' => ['contain', 'cover', 'fill', 'none', 'scale-down']]], 246 | /** 247 | * Object Position 248 | * 249 | * @see https://tailwindcss.com/docs/object-position 250 | */ 251 | 'object-position' => [['object' => [...self::getPositions(), ArbitraryValueValidator::validate(...)]]], 252 | /** 253 | * Overflow 254 | * 255 | * @see https://tailwindcss.com/docs/overflow 256 | */ 257 | 'overflow' => [['overflow' => self::getOverflow()]], 258 | /** 259 | * Overflow X 260 | * 261 | * @see https://tailwindcss.com/docs/overflow 262 | */ 263 | 'overflow-x' => [['overflow-x' => self::getOverflow()]], 264 | /** 265 | * Overflow Y 266 | * 267 | * @see https://tailwindcss.com/docs/overflow 268 | */ 269 | 'overflow-y' => [['overflow-y' => self::getOverflow()]], 270 | /** 271 | * Overscroll Behavior 272 | * 273 | * @see https://tailwindcss.com/docs/overscroll-behavior 274 | */ 275 | 'overscroll' => [['overscroll' => self::getOverscroll()]], 276 | /** 277 | * Overscroll Behavior X 278 | * 279 | * @see https://tailwindcss.com/docs/overscroll-behavior 280 | */ 281 | 'overscroll-x' => [['overscroll-x' => self::getOverscroll()]], 282 | /** 283 | * Overscroll Behavior Y 284 | * 285 | * @see https://tailwindcss.com/docs/overscroll-behavior 286 | */ 287 | 'overscroll-y' => [['overscroll-y' => self::getOverscroll()]], 288 | /** 289 | * Position 290 | * 291 | * @see https://tailwindcss.com/docs/position 292 | */ 293 | 'position' => ['static', 'fixed', 'absolute', 'relative', 'sticky'], 294 | /** 295 | * Top / Right / Bottom / Left 296 | * 297 | * @see https://tailwindcss.com/docs/top-right-bottom-left 298 | */ 299 | 'inset' => [['inset' => [$inset]]], 300 | /** 301 | * Right / Left 302 | * 303 | * @see https://tailwindcss.com/docs/top-right-bottom-left 304 | */ 305 | 'inset-x' => [['inset-x' => [$inset]]], 306 | /** 307 | * Top / Bottom 308 | * 309 | * @see https://tailwindcss.com/docs/top-right-bottom-left 310 | */ 311 | 'inset-y' => [['inset-y' => [$inset]]], 312 | /** 313 | * Start 314 | * 315 | * @see https://tailwindcss.com/docs/top-right-bottom-left 316 | */ 317 | 'start' => [['start' => [$inset]]], 318 | /** 319 | * End 320 | * 321 | * @see https://tailwindcss.com/docs/top-right-bottom-left 322 | */ 323 | 'end' => [['end' => [$inset]]], 324 | /** 325 | * Top 326 | * 327 | * @see https://tailwindcss.com/docs/top-right-bottom-left 328 | */ 329 | 'top' => [['top' => [$inset]]], 330 | /** 331 | * Right 332 | * 333 | * @see https://tailwindcss.com/docs/top-right-bottom-left 334 | */ 335 | 'right' => [['right' => [$inset]]], 336 | /** 337 | * Bottom 338 | * 339 | * @see https://tailwindcss.com/docs/top-right-bottom-left 340 | */ 341 | 'bottom' => [['bottom' => [$inset]]], 342 | /** 343 | * Left 344 | * 345 | * @see https://tailwindcss.com/docs/top-right-bottom-left 346 | */ 347 | 'left' => [['left' => [$inset]]], 348 | /** 349 | * Visibility 350 | * 351 | * @see https://tailwindcss.com/docs/visibility 352 | */ 353 | 'visibility' => ['visible', 'invisible', 'collapse'], 354 | /** 355 | * Z-Index 356 | * 357 | * @see https://tailwindcss.com/docs/z-index 358 | */ 359 | 'z' => [['z' => ['auto', IntegerValidator::validate(...), ArbitraryValueValidator::validate(...)]]], 360 | // Flexbox and Grid 361 | /** 362 | * Flex Basis 363 | * 364 | * @see https://tailwindcss.com/docs/flex-basis 365 | */ 366 | 'basis' => [['basis' => self::getSpacingWithAutoAndArbitrary($space)]], 367 | /** 368 | * Flex Direction 369 | * 370 | * @see https://tailwindcss.com/docs/flex-direction 371 | */ 372 | 'flex-direction' => [['flex' => ['row', 'row-reverse', 'col', 'col-reverse']]], 373 | /** 374 | * Flex Wrap 375 | * 376 | * @see https://tailwindcss.com/docs/flex-wrap 377 | */ 378 | 'flex-wrap' => [['flex' => ['wrap', 'wrap-reverse', 'nowrap']]], 379 | /** 380 | * Flex 381 | * 382 | * @see https://tailwindcss.com/docs/flex 383 | */ 384 | 'flex' => [['flex' => ['1', 'auto', 'initial', 'none', ArbitraryValueValidator::validate(...)]]], 385 | /** 386 | * Flex Grow 387 | * 388 | * @see https://tailwindcss.com/docs/flex-grow 389 | */ 390 | 'grow' => [['grow' => self::getZeroAndEmpty()]], 391 | /** 392 | * Flex Shrink 393 | * 394 | * @see https://tailwindcss.com/docs/flex-shrink 395 | */ 396 | 'shrink' => [['shrink' => self::getZeroAndEmpty()]], 397 | /** 398 | * Order 399 | * 400 | * @see https://tailwindcss.com/docs/order 401 | */ 402 | 'order' => [['order' => ['first', 'last', 'none', IntegerValidator::validate(...), ArbitraryValueValidator::validate(...)]]], 403 | /** 404 | * Grid Template Columns 405 | * 406 | * @see https://tailwindcss.com/docs/grid-template-columns 407 | */ 408 | 'grid-cols' => [['grid-cols' => [AnyValueValidator::validate(...)]]], 409 | /** 410 | * Grid Column Start / End 411 | * 412 | * @see https://tailwindcss.com/docs/grid-column 413 | */ 414 | 'col-start-end' => [['col' => ['auto', ['span' => ['full', IntegerValidator::validate(...), ArbitraryValueValidator::validate(...)]], ArbitraryValueValidator::validate(...)]]], 415 | /** 416 | * Grid Column Start 417 | * 418 | * @see https://tailwindcss.com/docs/grid-column 419 | */ 420 | 'col-start' => [['col-start' => self::getNumberWithAutoAndArbitrary()]], 421 | /** 422 | * Grid Column End 423 | * 424 | * @see https://tailwindcss.com/docs/grid-column 425 | */ 426 | 'col-end' => [['col-end' => self::getNumberWithAutoAndArbitrary()]], 427 | /** 428 | * Grid Template Rows 429 | * 430 | * @see https://tailwindcss.com/docs/grid-template-rows 431 | */ 432 | 'grid-rows' => [['grid-rows' => [AnyValueValidator::validate(...)]]], 433 | /** 434 | * Grid Row Start / End 435 | * 436 | * @see https://tailwindcss.com/docs/grid-row 437 | */ 438 | 'row-start-end' => [['row' => ['auto', ['span' => [IntegerValidator::validate(...), ArbitraryValueValidator::validate(...)]], ArbitraryValueValidator::validate(...)]]], 439 | /** 440 | * Grid Row Start 441 | * 442 | * @see https://tailwindcss.com/docs/grid-row 443 | */ 444 | 'row-start' => [['row-start' => self::getNumberWithAutoAndArbitrary()]], 445 | /** 446 | * Grid Row End 447 | * 448 | * @see https://tailwindcss.com/docs/grid-row 449 | */ 450 | 'row-end' => [['row-end' => self::getNumberWithAutoAndArbitrary()]], 451 | /** 452 | * Grid Auto Flow 453 | * 454 | * @see https://tailwindcss.com/docs/grid-auto-flow 455 | */ 456 | 'grid-flow' => [['grid-flow' => ['row', 'col', 'dense', 'row-dense', 'col-dense']]], 457 | /** 458 | * Grid Auto Columns 459 | * 460 | * @see https://tailwindcss.com/docs/grid-auto-columns 461 | */ 462 | 'auto-cols' => [['auto-cols' => ['auto', 'min', 'max', 'fr', ArbitraryValueValidator::validate(...)]]], 463 | /** 464 | * Grid Auto Rows 465 | * 466 | * @see https://tailwindcss.com/docs/grid-auto-rows 467 | */ 468 | 'auto-rows' => [['auto-rows' => ['auto', 'min', 'max', 'fr', ArbitraryValueValidator::validate(...)]]], 469 | /** 470 | * Gap 471 | * 472 | * @see https://tailwindcss.com/docs/gap 473 | */ 474 | 'gap' => [['gap' => [$gap]]], 475 | /** 476 | * Gap X 477 | * 478 | * @see https://tailwindcss.com/docs/gap 479 | */ 480 | 'gap-x' => [['gap-x' => [$gap]]], 481 | /** 482 | * Gap Y 483 | * 484 | * @see https://tailwindcss.com/docs/gap 485 | */ 486 | 'gap-y' => [['gap-y' => [$gap]]], 487 | /** 488 | * Justify Content 489 | * 490 | * @see https://tailwindcss.com/docs/justify-content 491 | */ 492 | 'justify-content' => [['justify' => ['normal', ...self::getAlign()]]], 493 | /** 494 | * Justify Items 495 | * 496 | * @see https://tailwindcss.com/docs/justify-items 497 | */ 498 | 'justify-items' => [['justify-items' => ['start', 'end', 'center', 'stretch']]], 499 | /** 500 | * Justify Self 501 | * 502 | * @see https://tailwindcss.com/docs/justify-self 503 | */ 504 | 'justify-self' => [['justify-self' => ['auto', 'start', 'end', 'center', 'stretch']]], 505 | /** 506 | * Align Content 507 | * 508 | * @see https://tailwindcss.com/docs/align-content 509 | */ 510 | 'align-content' => [['content' => ['normal', ...self::getAlign(), 'baseline']]], 511 | /** 512 | * Align Items 513 | * 514 | * @see https://tailwindcss.com/docs/align-items 515 | */ 516 | 'align-items' => [['items' => ['start', 'end', 'center', 'baseline', 'stretch']]], 517 | /** 518 | * Align Self 519 | * 520 | * @see https://tailwindcss.com/docs/align-self 521 | */ 522 | 'align-self' => [['self' => ['auto', 'start', 'end', 'center', 'stretch', 'baseline']]], 523 | /** 524 | * Place Content 525 | * 526 | * @see https://tailwindcss.com/docs/place-content 527 | */ 528 | 'place-content' => [['place-content' => [...self::getAlign(), 'baseline']]], 529 | /** 530 | * Place Items 531 | * 532 | * @see https://tailwindcss.com/docs/place-items 533 | */ 534 | 'place-items' => [['place-items' => ['start', 'end', 'center', 'baseline', 'stretch']]], 535 | /** 536 | * Place Self 537 | * 538 | * @see https://tailwindcss.com/docs/place-self 539 | */ 540 | 'place-self' => [['place-self' => ['auto', 'start', 'end', 'center', 'stretch']]], 541 | // Spacing 542 | /** 543 | * Padding 544 | * 545 | * @see https://tailwindcss.com/docs/padding 546 | */ 547 | 'p' => [['p' => [$padding]]], 548 | /** 549 | * Padding X 550 | * 551 | * @see https://tailwindcss.com/docs/padding 552 | */ 553 | 'px' => [['px' => [$padding]]], 554 | /** 555 | * Padding Y 556 | * 557 | * @see https://tailwindcss.com/docs/padding 558 | */ 559 | 'py' => [['py' => [$padding]]], 560 | /** 561 | * Padding Start 562 | * 563 | * @see https://tailwindcss.com/docs/padding 564 | */ 565 | 'ps' => [['ps' => [$padding]]], 566 | /** 567 | * Padding End 568 | * 569 | * @see https://tailwindcss.com/docs/padding 570 | */ 571 | 'pe' => [['pe' => [$padding]]], 572 | /** 573 | * Padding Top 574 | * 575 | * @see https://tailwindcss.com/docs/padding 576 | */ 577 | 'pt' => [['pt' => [$padding]]], 578 | /** 579 | * Padding Right 580 | * 581 | * @see https://tailwindcss.com/docs/padding 582 | */ 583 | 'pr' => [['pr' => [$padding]]], 584 | /** 585 | * Padding Bottom 586 | * 587 | * @see https://tailwindcss.com/docs/padding 588 | */ 589 | 'pb' => [['pb' => [$padding]]], 590 | /** 591 | * Padding Left 592 | * 593 | * @see https://tailwindcss.com/docs/padding 594 | */ 595 | 'pl' => [['pl' => [$padding]]], 596 | /** 597 | * Margin 598 | * 599 | * @see https://tailwindcss.com/docs/margin 600 | */ 601 | 'm' => [['m' => [$margin]]], 602 | /** 603 | * Margin X 604 | * 605 | * @see https://tailwindcss.com/docs/margin 606 | */ 607 | 'mx' => [['mx' => [$margin]]], 608 | /** 609 | * Margin Y 610 | * 611 | * @see https://tailwindcss.com/docs/margin 612 | */ 613 | 'my' => [['my' => [$margin]]], 614 | /** 615 | * Margin Start 616 | * 617 | * @see https://tailwindcss.com/docs/margin 618 | */ 619 | 'ms' => [['ms' => [$margin]]], 620 | /** 621 | * Margin End 622 | * 623 | * @see https://tailwindcss.com/docs/margin 624 | */ 625 | 'me' => [['me' => [$margin]]], 626 | /** 627 | * Margin Top 628 | * 629 | * @see https://tailwindcss.com/docs/margin 630 | */ 631 | 'mt' => [['mt' => [$margin]]], 632 | /** 633 | * Margin Right 634 | * 635 | * @see https://tailwindcss.com/docs/margin 636 | */ 637 | 'mr' => [['mr' => [$margin]]], 638 | /** 639 | * Margin Bottom 640 | * 641 | * @see https://tailwindcss.com/docs/margin 642 | */ 643 | 'mb' => [['mb' => [$margin]]], 644 | /** 645 | * Margin Left 646 | * 647 | * @see https://tailwindcss.com/docs/margin 648 | */ 649 | 'ml' => [['ml' => [$margin]]], 650 | /** 651 | * Space Between X 652 | * 653 | * @see https://tailwindcss.com/docs/space 654 | */ 655 | 'space-x' => [['space-x' => [$space]]], 656 | /** 657 | * Space Between X Reverse 658 | * 659 | * @see https://tailwindcss.com/docs/space 660 | */ 661 | 'space-x-reverse' => ['space-x-reverse'], 662 | /** 663 | * Space Between Y 664 | * 665 | * @see https://tailwindcss.com/docs/space 666 | */ 667 | 'space-y' => [['space-y' => [$space]]], 668 | /** 669 | * Space Between Y Reverse 670 | * 671 | * @see https://tailwindcss.com/docs/space 672 | */ 673 | 'space-y-reverse' => ['space-y-reverse'], 674 | // Sizing 675 | /** 676 | * Width 677 | * 678 | * @see https://tailwindcss.com/docs/width 679 | */ 680 | 'w' => [ 681 | [ 682 | 'w' => [ 683 | 'auto', 684 | 'min', 685 | 'max', 686 | 'fit', 687 | 'svw', 688 | 'lvw', 689 | 'dvw', 690 | ArbitraryValueValidator::validate(...), 691 | $spacing, 692 | ], 693 | ], 694 | ], 695 | /** 696 | * Min-Width 697 | * 698 | * @see https://tailwindcss.com/docs/min-width 699 | */ 700 | 'min-w' => [['min-w' => ['min', 'max', 'fit', ArbitraryValueValidator::validate(...), LengthValidator::validate(...)]]], 701 | /** 702 | * Max-Width 703 | * 704 | * @see https://tailwindcss.com/docs/max-width 705 | */ 706 | 'max-w' => [ 707 | [ 708 | 'max-w' => [ 709 | ArbitraryValueValidator::validate(...), 710 | $spacing, 711 | 'none', 712 | 'full', 713 | 'min', 714 | 'max', 715 | 'fit', 716 | 'prose', 717 | ['screen' => [TshirtSizeValidator::validate(...)]], 718 | TshirtSizeValidator::validate(...), 719 | ], 720 | ], 721 | ], 722 | /** 723 | * Height 724 | * 725 | * @see https://tailwindcss.com/docs/height 726 | */ 727 | 'h' => [ 728 | [ 729 | 'h' => [ 730 | ArbitraryValueValidator::validate(...), 731 | $spacing, 732 | 'auto', 733 | 'min', 734 | 'max', 735 | 'fit', 736 | 'svh', 737 | 'lvh', 738 | 'dvh', 739 | ], 740 | ], 741 | ], 742 | /** 743 | * Min-Height 744 | * 745 | * @see https://tailwindcss.com/docs/min-height 746 | */ 747 | 'min-h' => [ 748 | ['min-h' => [ArbitraryValueValidator::validate(...), $spacing, 'min', 'max', 'fit', 'svh', 'lvh', 'dvh']], 749 | ], 750 | /** 751 | * Max-Height 752 | * 753 | * @see https://tailwindcss.com/docs/max-height 754 | */ 755 | 'max-h' => [ 756 | ['max-h' => [ArbitraryValueValidator::validate(...), $spacing, 'min', 'max', 'fit', 'svh', 'lvh', 'dvh']], 757 | ], 758 | /** 759 | * Size 760 | * 761 | * @see https://tailwindcss.com/docs/size 762 | */ 763 | 'size' => [['size' => [ArbitraryValueValidator::validate(...), $spacing, 'auto', 'min', 'max', 'fit']]], 764 | // Typography 765 | /** 766 | * Font Size 767 | * 768 | * @see https://tailwindcss.com/docs/font-size 769 | */ 770 | 'font-size' => [['text' => ['base', TshirtSizeValidator::validate(...), ArbitraryLengthValidator::validate(...)]]], 771 | /** 772 | * Font Smoothing 773 | * 774 | * @see https://tailwindcss.com/docs/font-smoothing 775 | */ 776 | 'font-smoothing' => ['antialiased', 'subpixel-antialiased'], 777 | /** 778 | * Font Style 779 | * 780 | * @see https://tailwindcss.com/docs/font-style 781 | */ 782 | 'font-style' => ['italic', 'not-italic'], 783 | /** 784 | * Font Weight 785 | * 786 | * @see https://tailwindcss.com/docs/font-weight 787 | */ 788 | 'font-weight' => [ 789 | [ 790 | 'font' => [ 791 | 'thin', 792 | 'extralight', 793 | 'light', 794 | 'normal', 795 | 'medium', 796 | 'semibold', 797 | 'bold', 798 | 'extrabold', 799 | 'black', 800 | ArbitraryNumberValidator::validate(...), 801 | ], 802 | ], 803 | ], 804 | /** 805 | * Font Family 806 | * 807 | * @see https://tailwindcss.com/docs/font-family 808 | */ 809 | 'font-family' => [['font' => [AnyValueValidator::validate(...)]]], 810 | /** 811 | * Font Variant Numeric 812 | * 813 | * @see https://tailwindcss.com/docs/font-variant-numeric 814 | */ 815 | 'fvn-normal' => ['normal-nums'], 816 | /** 817 | * Font Variant Numeric 818 | * 819 | * @see https://tailwindcss.com/docs/font-variant-numeric 820 | */ 821 | 'fvn-ordinal' => ['ordinal'], 822 | /** 823 | * Font Variant Numeric 824 | * 825 | * @see https://tailwindcss.com/docs/font-variant-numeric 826 | */ 827 | 'fvn-slashed-zero' => ['slashed-zero'], 828 | /** 829 | * Font Variant Numeric 830 | * 831 | * @see https://tailwindcss.com/docs/font-variant-numeric 832 | */ 833 | 'fvn-figure' => ['lining-nums', 'oldstyle-nums'], 834 | /** 835 | * Font Variant Numeric 836 | * 837 | * @see https://tailwindcss.com/docs/font-variant-numeric 838 | */ 839 | 'fvn-spacing' => ['proportional-nums', 'tabular-nums'], 840 | /** 841 | * Font Variant Numeric 842 | * 843 | * @see https://tailwindcss.com/docs/font-variant-numeric 844 | */ 845 | 'fvn-fraction' => ['diagonal-fractions', 'stacked-fractons'], 846 | /** 847 | * Letter Spacing 848 | * 849 | * @see https://tailwindcss.com/docs/letter-spacing 850 | */ 851 | 'tracking' => [ 852 | [ 853 | 'tracking' => [ 854 | 'tighter', 855 | 'tight', 856 | 'normal', 857 | 'wide', 858 | 'wider', 859 | 'widest', 860 | ArbitraryValueValidator::validate(...), 861 | ], 862 | ], 863 | ], 864 | /** 865 | * Line Clamp 866 | * 867 | * @see https://tailwindcss.com/docs/line-clamp 868 | */ 869 | 'line-clamp' => [['line-clamp' => ['none', NumberValidator::validate(...), ArbitraryNumberValidator::validate(...)]]], 870 | /** 871 | * Line Height 872 | * 873 | * @see https://tailwindcss.com/docs/line-height 874 | */ 875 | 'leading' => [ 876 | ['leading' => [ 877 | 'none', 878 | 'tight', 879 | 'snug', 880 | 'normal', 881 | 'relaxed', 882 | 'loose', 883 | LengthValidator::validate(...), 884 | ArbitraryValueValidator::validate(...), 885 | ]], 886 | ], 887 | /** 888 | * List Style Image 889 | * 890 | * @see https://tailwindcss.com/docs/list-style-image 891 | */ 892 | 'list-image' => [['list-image' => ['none', ArbitraryValueValidator::validate(...)]]], 893 | /** 894 | * List Style Type 895 | * 896 | * @see https://tailwindcss.com/docs/list-style-type 897 | */ 898 | 'list-style-type' => [['list' => ['none', 'disc', 'decimal', ArbitraryValueValidator::validate(...)]]], 899 | /** 900 | * List Style Position 901 | * 902 | * @see https://tailwindcss.com/docs/list-style-position 903 | */ 904 | 'list-style-position' => [['list' => ['inside', 'outside']]], 905 | /** 906 | * Placeholder Color 907 | * 908 | * @deprecated since Tailwind CSS v3.0.0 909 | * @see https://tailwindcss.com/docs/placeholder-color 910 | */ 911 | 'placeholder-color' => [['placeholder' => [$colors]]], 912 | /** 913 | * Placeholder Opacity 914 | * 915 | * @see https://tailwindcss.com/docs/placeholder-opacity 916 | */ 917 | 'placeholder-opacity' => [['placeholder-opacity' => [$opacity]]], 918 | /** 919 | * Text Alignment 920 | * 921 | * @see https://tailwindcss.com/docs/text-align 922 | */ 923 | 'text-alignment' => [['text' => ['left', 'center', 'right', 'justify', 'start', 'end']]], 924 | /** 925 | * Text Color 926 | * 927 | * @see https://tailwindcss.com/docs/text-color 928 | */ 929 | 'text-color' => [['text' => [$colors]]], 930 | /** 931 | * Text Opacity 932 | * 933 | * @see https://tailwindcss.com/docs/text-opacity 934 | */ 935 | 'text-opacity' => [['text-opacity' => [$opacity]]], 936 | /** 937 | * Text Decoration 938 | * 939 | * @see https://tailwindcss.com/docs/text-decoration 940 | */ 941 | 'text-decoration' => ['underline', 'overline', 'line-through', 'no-underline'], 942 | /** 943 | * Text Decoration Style 944 | * 945 | * @see https://tailwindcss.com/docs/text-decoration-style 946 | */ 947 | 'text-decoration-style' => [['decoration' => [...self::getLineStyles(), 'wavy']]], 948 | /** 949 | * Text Decoration Thickness 950 | * 951 | * @see https://tailwindcss.com/docs/text-decoration-thickness 952 | */ 953 | 'text-decoration-thickness' => [['decoration' => ['auto', 'from-font', LengthValidator::validate(...), ArbitraryLengthValidator::validate(...)]]], 954 | /** 955 | * Text Underline Offset 956 | * 957 | * @see https://tailwindcss.com/docs/text-underline-offset 958 | */ 959 | 'underline-offset' => [['underline-offset' => ['auto', LengthValidator::validate(...), ArbitraryValueValidator::validate(...)]]], 960 | /** 961 | * Text Decoration Color 962 | * 963 | * @see https://tailwindcss.com/docs/text-decoration-color 964 | */ 965 | 'text-decoration-color' => [['decoration' => [$colors]]], 966 | /** 967 | * Text Transform 968 | * 969 | * @see https://tailwindcss.com/docs/text-transform 970 | */ 971 | 'text-transform' => ['uppercase', 'lowercase', 'capitalize', 'normal-case'], 972 | /** 973 | * Text Overflow 974 | * 975 | * @see https://tailwindcss.com/docs/text-overflow 976 | */ 977 | 'text-overflow' => ['truncate', 'text-ellipsis', 'text-clip'], 978 | /** 979 | * Text Wrap 980 | * 981 | * @see https://tailwindcss.com/docs/text-wrap 982 | */ 983 | 'text-wrap' => [['text' => ['wrap', 'nowrap', 'balance', 'pretty']]], 984 | /** 985 | * Text Indent 986 | * 987 | * @see https://tailwindcss.com/docs/text-indent 988 | */ 989 | 'indent' => [['indent' => self::getSpacingWithArbitrary($spacing)]], 990 | /** 991 | * Vertical Alignment 992 | * 993 | * @see https://tailwindcss.com/docs/vertical-align 994 | */ 995 | 'vertical-align' => [ 996 | [ 997 | 'align' => [ 998 | 'baseline', 999 | 'top', 1000 | 'middle', 1001 | 'bottom', 1002 | 'text-top', 1003 | 'text-bottom', 1004 | 'sub', 1005 | 'super', 1006 | ArbitraryValueValidator::validate(...), 1007 | ], 1008 | ], 1009 | ], 1010 | /** 1011 | * Whitespace 1012 | * 1013 | * @see https://tailwindcss.com/docs/whitespace 1014 | */ 1015 | 'whitespace' => [ 1016 | ['whitespace' => ['normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap', 'break-spaces']], 1017 | ], 1018 | /** 1019 | * Word Break 1020 | * 1021 | * @see https://tailwindcss.com/docs/word-break 1022 | */ 1023 | 'break' => [['break' => ['normal', 'words', 'all', 'keep']]], 1024 | /** 1025 | * Hyphens 1026 | * 1027 | * @see https://tailwindcss.com/docs/hyphens 1028 | */ 1029 | 'hyphens' => [['hyphens' => ['none', 'manual', 'auto']]], 1030 | /** 1031 | * Content 1032 | * 1033 | * @see https://tailwindcss.com/docs/content 1034 | */ 1035 | 'content' => [['content' => ['none', ArbitraryValueValidator::validate(...)]]], 1036 | // Backgrounds 1037 | /** 1038 | * Background Attachment 1039 | * 1040 | * @see https://tailwindcss.com/docs/background-attachment 1041 | */ 1042 | 'bg-attachment' => [['bg' => ['fixed', 'local', 'scroll']]], 1043 | /** 1044 | * Background Clip 1045 | * 1046 | * @see https://tailwindcss.com/docs/background-clip 1047 | */ 1048 | 'bg-clip' => [['bg-clip' => ['border', 'padding', 'content', 'text']]], 1049 | /** 1050 | * Background Opacity 1051 | * 1052 | * @deprecated since Tailwind CSS v3.0.0 1053 | * @see https://tailwindcss.com/docs/background-opacity 1054 | */ 1055 | 'bg-opacity' => [['bg-opacity' => [$opacity]]], 1056 | /** 1057 | * Background Origin 1058 | * 1059 | * @see https://tailwindcss.com/docs/background-origin 1060 | */ 1061 | 'bg-origin' => [['bg-origin' => ['border', 'padding', 'content']]], 1062 | /** 1063 | * Background Position 1064 | * 1065 | * @see https://tailwindcss.com/docs/background-position 1066 | */ 1067 | 'bg-position' => [['bg' => [...self::getPositions(), ArbitraryPositionValidator::validate(...)]]], 1068 | /** 1069 | * Background Repeat 1070 | * 1071 | * @see https://tailwindcss.com/docs/background-repeat 1072 | */ 1073 | 'bg-repeat' => [['bg' => ['no-repeat', ['repeat' => ['', 'x', 'y', 'round', 'space']]]]], 1074 | /** 1075 | * Background Size 1076 | * 1077 | * @see https://tailwindcss.com/docs/background-size 1078 | */ 1079 | 'bg-size' => [['bg' => ['auto', 'cover', 'contain', ArbitrarySizeValidator::validate(...)]]], 1080 | /** 1081 | * Background Image 1082 | * 1083 | * @see https://tailwindcss.com/docs/background-image 1084 | */ 1085 | 'bg-image' => [ 1086 | [ 1087 | 'bg' => [ 1088 | 'none', 1089 | ['gradient-to' => ['t', 'tr', 'r', 'br', 'b', 'bl', 'l', 'tl']], 1090 | ArbitraryImageValidator::validate(...), 1091 | ], 1092 | ], 1093 | ], 1094 | /** 1095 | * Background Color 1096 | * 1097 | * @see https://tailwindcss.com/docs/background-color 1098 | */ 1099 | 'bg-color' => [['bg' => [$colors]]], 1100 | /** 1101 | * Gradient Color Stops From Position 1102 | * 1103 | * @see https://tailwindcss.com/docs/gradient-color-stops 1104 | */ 1105 | 'gradient-from-pos' => [['from' => [$gradientColorStopPositions]]], 1106 | /** 1107 | * Gradient Color Stops Via Position 1108 | * 1109 | * @see https://tailwindcss.com/docs/gradient-color-stops 1110 | */ 1111 | 'gradient-via-pos' => [['via' => [$gradientColorStopPositions]]], 1112 | /** 1113 | * Gradient Color Stops To Position 1114 | * 1115 | * @see https://tailwindcss.com/docs/gradient-color-stops 1116 | */ 1117 | 'gradient-to-pos' => [['to' => [$gradientColorStopPositions]]], 1118 | /** 1119 | * Gradient Color Stops From 1120 | * 1121 | * @see https://tailwindcss.com/docs/gradient-color-stops 1122 | */ 1123 | 'gradient-from' => [['from' => [$gradientColorStops]]], 1124 | /** 1125 | * Gradient Color Stops Via 1126 | * 1127 | * @see https://tailwindcss.com/docs/gradient-color-stops 1128 | */ 1129 | 'gradient-via' => [['via' => [$gradientColorStops]]], 1130 | /** 1131 | * Gradient Color Stops To 1132 | * 1133 | * @see https://tailwindcss.com/docs/gradient-color-stops 1134 | */ 1135 | 'gradient-to' => [['to' => [$gradientColorStops]]], 1136 | // Borders 1137 | /** 1138 | * Border Radius 1139 | * 1140 | * @see https://tailwindcss.com/docs/border-radius 1141 | */ 1142 | 'rounded' => [['rounded' => [$borderRadius]]], 1143 | /** 1144 | * Border Radius Start 1145 | * 1146 | * @see https://tailwindcss.com/docs/border-radius 1147 | */ 1148 | 'rounded-s' => [['rounded-s' => [$borderRadius]]], 1149 | /** 1150 | * Border Radius End 1151 | * 1152 | * @see https://tailwindcss.com/docs/border-radius 1153 | */ 1154 | 'rounded-e' => [['rounded-e' => [$borderRadius]]], 1155 | /** 1156 | * Border Radius Top 1157 | * 1158 | * @see https://tailwindcss.com/docs/border-radius 1159 | */ 1160 | 'rounded-t' => [['rounded-t' => [$borderRadius]]], 1161 | /** 1162 | * Border Radius Right 1163 | * 1164 | * @see https://tailwindcss.com/docs/border-radius 1165 | */ 1166 | 'rounded-r' => [['rounded-r' => [$borderRadius]]], 1167 | /** 1168 | * Border Radius Bottom 1169 | * 1170 | * @see https://tailwindcss.com/docs/border-radius 1171 | */ 1172 | 'rounded-b' => [['rounded-b' => [$borderRadius]]], 1173 | /** 1174 | * Border Radius Left 1175 | * 1176 | * @see https://tailwindcss.com/docs/border-radius 1177 | */ 1178 | 'rounded-l' => [['rounded-l' => [$borderRadius]]], 1179 | /** 1180 | * Border Radius Start Start 1181 | * 1182 | * @see https://tailwindcss.com/docs/border-radius 1183 | */ 1184 | 'rounded-ss' => [['rounded-ss' => [$borderRadius]]], 1185 | /** 1186 | * Border Radius Start End 1187 | * 1188 | * @see https://tailwindcss.com/docs/border-radius 1189 | */ 1190 | 'rounded-se' => [['rounded-se' => [$borderRadius]]], 1191 | /** 1192 | * Border Radius End End 1193 | * 1194 | * @see https://tailwindcss.com/docs/border-radius 1195 | */ 1196 | 'rounded-ee' => [['rounded-ee' => [$borderRadius]]], 1197 | /** 1198 | * Border Radius End Start 1199 | * 1200 | * @see https://tailwindcss.com/docs/border-radius 1201 | */ 1202 | 'rounded-es' => [['rounded-es' => [$borderRadius]]], 1203 | /** 1204 | * Border Radius Top Left 1205 | * 1206 | * @see https://tailwindcss.com/docs/border-radius 1207 | */ 1208 | 'rounded-tl' => [['rounded-tl' => [$borderRadius]]], 1209 | /** 1210 | * Border Radius Top Right 1211 | * 1212 | * @see https://tailwindcss.com/docs/border-radius 1213 | */ 1214 | 'rounded-tr' => [['rounded-tr' => [$borderRadius]]], 1215 | /** 1216 | * Border Radius Bottom Right 1217 | * 1218 | * @see https://tailwindcss.com/docs/border-radius 1219 | */ 1220 | 'rounded-br' => [['rounded-br' => [$borderRadius]]], 1221 | /** 1222 | * Border Radius Bottom Left 1223 | * 1224 | * @see https://tailwindcss.com/docs/border-radius 1225 | */ 1226 | 'rounded-bl' => [['rounded-bl' => [$borderRadius]]], 1227 | /** 1228 | * Border Width 1229 | * 1230 | * @see https://tailwindcss.com/docs/border-width 1231 | */ 1232 | 'border-w' => [['border' => [$borderWidth]]], 1233 | /** 1234 | * Border Width X 1235 | * 1236 | * @see https://tailwindcss.com/docs/border-width 1237 | */ 1238 | 'border-w-x' => [['border-x' => [$borderWidth]]], 1239 | /** 1240 | * Border Width Y 1241 | * 1242 | * @see https://tailwindcss.com/docs/border-width 1243 | */ 1244 | 'border-w-y' => [['border-y' => [$borderWidth]]], 1245 | /** 1246 | * Border Width Start 1247 | * 1248 | * @see https://tailwindcss.com/docs/border-width 1249 | */ 1250 | 'border-w-s' => [['border-s' => [$borderWidth]]], 1251 | /** 1252 | * Border Width End 1253 | * 1254 | * @see https://tailwindcss.com/docs/border-width 1255 | */ 1256 | 'border-w-e' => [['border-e' => [$borderWidth]]], 1257 | /** 1258 | * Border Width Top 1259 | * 1260 | * @see https://tailwindcss.com/docs/border-width 1261 | */ 1262 | 'border-w-t' => [['border-t' => [$borderWidth]]], 1263 | /** 1264 | * Border Width Right 1265 | * 1266 | * @see https://tailwindcss.com/docs/border-width 1267 | */ 1268 | 'border-w-r' => [['border-r' => [$borderWidth]]], 1269 | /** 1270 | * Border Width Bottom 1271 | * 1272 | * @see https://tailwindcss.com/docs/border-width 1273 | */ 1274 | 'border-w-b' => [['border-b' => [$borderWidth]]], 1275 | /** 1276 | * Border Width Left 1277 | * 1278 | * @see https://tailwindcss.com/docs/border-width 1279 | */ 1280 | 'border-w-l' => [['border-l' => [$borderWidth]]], 1281 | /** 1282 | * Border Opacity 1283 | * 1284 | * @see https://tailwindcss.com/docs/border-opacity 1285 | */ 1286 | 'border-opacity' => [['border-opacity' => [$opacity]]], 1287 | /** 1288 | * Border Style 1289 | * 1290 | * @see https://tailwindcss.com/docs/border-style 1291 | */ 1292 | 'border-style' => [['border' => [...self::getLineStyles(), 'hidden']]], 1293 | /** 1294 | * Divide Width X 1295 | * 1296 | * @see https://tailwindcss.com/docs/divide-width 1297 | */ 1298 | 'divide-x' => [['divide-x' => [$borderWidth]]], 1299 | /** 1300 | * Divide Width X Reverse 1301 | * 1302 | * @see https://tailwindcss.com/docs/divide-width 1303 | */ 1304 | 'divide-x-reverse' => ['divide-x-reverse'], 1305 | /** 1306 | * Divide Width Y 1307 | * 1308 | * @see https://tailwindcss.com/docs/divide-width 1309 | */ 1310 | 'divide-y' => [['divide-y' => [$borderWidth]]], 1311 | /** 1312 | * Divide Width Y Reverse 1313 | * 1314 | * @see https://tailwindcss.com/docs/divide-width 1315 | */ 1316 | 'divide-y-reverse' => ['divide-y-reverse'], 1317 | /** 1318 | * Divide Opacity 1319 | * 1320 | * @see https://tailwindcss.com/docs/divide-opacity 1321 | */ 1322 | 'divide-opacity' => [['divide-opacity' => [$opacity]]], 1323 | /** 1324 | * Divide Style 1325 | * 1326 | * @see https://tailwindcss.com/docs/divide-style 1327 | */ 1328 | 'divide-style' => [['divide' => self::getLineStyles()]], 1329 | /** 1330 | * Border Color 1331 | * 1332 | * @see https://tailwindcss.com/docs/border-color 1333 | */ 1334 | 'border-color' => [['border' => [$borderColor]]], 1335 | /** 1336 | * Border Color X 1337 | * 1338 | * @see https://tailwindcss.com/docs/border-color 1339 | */ 1340 | 'border-color-x' => [['border-x' => [$borderColor]]], 1341 | /** 1342 | * Border Color Y 1343 | * 1344 | * @see https://tailwindcss.com/docs/border-color 1345 | */ 1346 | 'border-color-y' => [['border-y' => [$borderColor]]], 1347 | /** 1348 | * Border Color Top 1349 | * 1350 | * @see https://tailwindcss.com/docs/border-color 1351 | */ 1352 | 'border-color-t' => [['border-t' => [$borderColor]]], 1353 | /** 1354 | * Border Color Right 1355 | * 1356 | * @see https://tailwindcss.com/docs/border-color 1357 | */ 1358 | 'border-color-r' => [['border-r' => [$borderColor]]], 1359 | /** 1360 | * Border Color Bottom 1361 | * 1362 | * @see https://tailwindcss.com/docs/border-color 1363 | */ 1364 | 'border-color-b' => [['border-b' => [$borderColor]]], 1365 | /** 1366 | * Border Color Left 1367 | * 1368 | * @see https://tailwindcss.com/docs/border-color 1369 | */ 1370 | 'border-color-l' => [['border-l' => [$borderColor]]], 1371 | /** 1372 | * Divide Color 1373 | * 1374 | * @see https://tailwindcss.com/docs/divide-color 1375 | */ 1376 | 'divide-color' => [['divide' => [$borderColor]]], 1377 | /** 1378 | * Outline Style 1379 | * 1380 | * @see https://tailwindcss.com/docs/outline-style 1381 | */ 1382 | 'outline-style' => [['outline' => ['', ...self::getLineStyles()]]], 1383 | /** 1384 | * Outline Offset 1385 | * 1386 | * @see https://tailwindcss.com/docs/outline-offset 1387 | */ 1388 | 'outline-offset' => [['outline-offset' => [LengthValidator::validate(...), ArbitraryValueValidator::validate(...)]]], 1389 | /** 1390 | * Outline Width 1391 | * 1392 | * @see https://tailwindcss.com/docs/outline-width 1393 | */ 1394 | 'outline-w' => [['outline' => [LengthValidator::validate(...), ArbitraryLengthValidator::validate(...)]]], 1395 | /** 1396 | * Outline Color 1397 | * 1398 | * @see https://tailwindcss.com/docs/outline-color 1399 | */ 1400 | 'outline-color' => [['outline' => [$colors]]], 1401 | /** 1402 | * Ring Width 1403 | * 1404 | * @see https://tailwindcss.com/docs/ring-width 1405 | */ 1406 | 'ring-w' => [['ring' => self::getLengthWithEmptyAndArbitrary()]], 1407 | /** 1408 | * Ring Width Inset 1409 | * 1410 | * @see https://tailwindcss.com/docs/ring-width 1411 | */ 1412 | 'ring-w-inset' => ['ring-inset'], 1413 | /** 1414 | * Ring Color 1415 | * 1416 | * @see https://tailwindcss.com/docs/ring-color 1417 | */ 1418 | 'ring-color' => [['ring' => [$colors]]], 1419 | /** 1420 | * Ring Opacity 1421 | * 1422 | * @see https://tailwindcss.com/docs/ring-opacity 1423 | */ 1424 | 'ring-opacity' => [['ring-opacity' => [$opacity]]], 1425 | /** 1426 | * Ring Offset Width 1427 | * 1428 | * @see https://tailwindcss.com/docs/ring-offset-width 1429 | */ 1430 | 'ring-offset-w' => [['ring-offset' => [LengthValidator::validate(...), ArbitraryLengthValidator::validate(...)]]], 1431 | /** 1432 | * Ring Offset Color 1433 | * 1434 | * @see https://tailwindcss.com/docs/ring-offset-color 1435 | */ 1436 | 'ring-offset-color' => [['ring-offset' => [$colors]]], 1437 | // Effects 1438 | /** 1439 | * Box Shadow 1440 | * 1441 | * @see https://tailwindcss.com/docs/box-shadow 1442 | */ 1443 | 'shadow' => [['shadow' => ['', 'inner', 'none', TshirtSizeValidator::validate(...), ArbitraryShadowValidator::validate(...)]]], 1444 | /** 1445 | * Box Shadow Color 1446 | * 1447 | * @see https://tailwindcss.com/docs/box-shadow-color 1448 | */ 1449 | 'shadow-color' => [['shadow' => [AnyValueValidator::validate(...)]]], 1450 | /** 1451 | * Opacity 1452 | * 1453 | * @see https://tailwindcss.com/docs/opacity 1454 | */ 1455 | 'opacity' => [['opacity' => [$opacity]]], 1456 | /** 1457 | * Mix Blend Mode 1458 | * 1459 | * @see https://tailwindcss.com/docs/mix-blend-mode 1460 | */ 1461 | 'mix-blend' => [['mix-blend' => self::getBlendModes()]], 1462 | /** 1463 | * Background Blend Mode 1464 | * 1465 | * @see https://tailwindcss.com/docs/background-blend-mode 1466 | */ 1467 | 'bg-blend' => [['bg-blend' => self::getBlendModes()]], 1468 | // Filters 1469 | /** 1470 | * Filter 1471 | * 1472 | * @deprecated since Tailwind CSS v3.0.0 1473 | * @see https://tailwindcss.com/docs/filter 1474 | */ 1475 | 'filter' => [['filter' => ['', 'none']]], 1476 | /** 1477 | * Blur 1478 | * 1479 | * @see https://tailwindcss.com/docs/blur 1480 | */ 1481 | 'blur' => [['blur' => [$blur]]], 1482 | /** 1483 | * Brightness 1484 | * 1485 | * @see https://tailwindcss.com/docs/brightness 1486 | */ 1487 | 'brightness' => [['brightness' => [$brightness]]], 1488 | /** 1489 | * Contrast 1490 | * 1491 | * @see https://tailwindcss.com/docs/contrast 1492 | */ 1493 | 'contrast' => [['contrast' => [$contrast]]], 1494 | /** 1495 | * Drop Shadow 1496 | * 1497 | * @see https://tailwindcss.com/docs/drop-shadow 1498 | */ 1499 | 'drop-shadow' => [['drop-shadow' => ['', 'none', TshirtSizeValidator::validate(...), ArbitraryValueValidator::validate(...)]]], 1500 | /** 1501 | * Grayscale 1502 | * 1503 | * @see https://tailwindcss.com/docs/grayscale 1504 | */ 1505 | 'grayscale' => [['grayscale' => [$grayscale]]], 1506 | /** 1507 | * Hue Rotate 1508 | * 1509 | * @see https://tailwindcss.com/docs/hue-rotate 1510 | */ 1511 | 'hue-rotate' => [['hue-rotate' => [$hueRotate]]], 1512 | /** 1513 | * Invert 1514 | * 1515 | * @see https://tailwindcss.com/docs/invert 1516 | */ 1517 | 'invert' => [['invert' => [$invert]]], 1518 | /** 1519 | * Saturate 1520 | * 1521 | * @see https://tailwindcss.com/docs/saturate 1522 | */ 1523 | 'saturate' => [['saturate' => [$saturate]]], 1524 | /** 1525 | * Sepia 1526 | * 1527 | * @see https://tailwindcss.com/docs/sepia 1528 | */ 1529 | 'sepia' => [['sepia' => [$sepia]]], 1530 | /** 1531 | * Backdrop Filter 1532 | * 1533 | * @deprecated since Tailwind CSS v3.0.0 1534 | * @see https://tailwindcss.com/docs/backdrop-filter 1535 | */ 1536 | 'backdrop-filter' => [['backdrop-filter' => ['', 'none']]], 1537 | /** 1538 | * Backdrop Blur 1539 | * 1540 | * @see https://tailwindcss.com/docs/backdrop-blur 1541 | */ 1542 | 'backdrop-blur' => [['backdrop-blur' => [$blur]]], 1543 | /** 1544 | * Backdrop Brightness 1545 | * 1546 | * @see https://tailwindcss.com/docs/backdrop-brightness 1547 | */ 1548 | 'backdrop-brightness' => [['backdrop-brightness' => [$brightness]]], 1549 | /** 1550 | * Backdrop Contrast 1551 | * 1552 | * @see https://tailwindcss.com/docs/backdrop-contrast 1553 | */ 1554 | 'backdrop-contrast' => [['backdrop-contrast' => [$contrast]]], 1555 | /** 1556 | * Backdrop Grayscale 1557 | * 1558 | * @see https://tailwindcss.com/docs/backdrop-grayscale 1559 | */ 1560 | 'backdrop-grayscale' => [['backdrop-grayscale' => [$grayscale]]], 1561 | /** 1562 | * Backdrop Hue Rotate 1563 | * 1564 | * @see https://tailwindcss.com/docs/backdrop-hue-rotate 1565 | */ 1566 | 'backdrop-hue-rotate' => [['backdrop-hue-rotate' => [$hueRotate]]], 1567 | /** 1568 | * Backdrop Invert 1569 | * 1570 | * @see https://tailwindcss.com/docs/backdrop-invert 1571 | */ 1572 | 'backdrop-invert' => [['backdrop-invert' => [$invert]]], 1573 | /** 1574 | * Backdrop Opacity 1575 | * 1576 | * @see https://tailwindcss.com/docs/backdrop-opacity 1577 | */ 1578 | 'backdrop-opacity' => [['backdrop-opacity' => [$opacity]]], 1579 | /** 1580 | * Backdrop Saturate 1581 | * 1582 | * @see https://tailwindcss.com/docs/backdrop-saturate 1583 | */ 1584 | 'backdrop-saturate' => [['backdrop-saturate' => [$saturate]]], 1585 | /** 1586 | * Backdrop Sepia 1587 | * 1588 | * @see https://tailwindcss.com/docs/backdrop-sepia 1589 | */ 1590 | 'backdrop-sepia' => [['backdrop-sepia' => [$sepia]]], 1591 | // Tables 1592 | /** 1593 | * Border Collapse 1594 | * 1595 | * @see https://tailwindcss.com/docs/border-collapse 1596 | */ 1597 | 'border-collapse' => [['border' => ['collapse', 'separate']]], 1598 | /** 1599 | * Border Spacing 1600 | * 1601 | * @see https://tailwindcss.com/docs/border-spacing 1602 | */ 1603 | 'border-spacing' => [['border-spacing' => [$borderSpacing]]], 1604 | /** 1605 | * Border Spacing X 1606 | * 1607 | * @see https://tailwindcss.com/docs/border-spacing 1608 | */ 1609 | 'border-spacing-x' => [['border-spacing-x' => [$borderSpacing]]], 1610 | /** 1611 | * Border Spacing Y 1612 | * 1613 | * @see https://tailwindcss.com/docs/border-spacing 1614 | */ 1615 | 'border-spacing-y' => [['border-spacing-y' => [$borderSpacing]]], 1616 | /** 1617 | * Table Layout 1618 | * 1619 | * @see https://tailwindcss.com/docs/table-layout 1620 | */ 1621 | 'table-layout' => [['table' => ['auto', 'fixed']]], 1622 | /** 1623 | * Caption Side 1624 | * 1625 | * @see https://tailwindcss.com/docs/caption-side 1626 | */ 1627 | 'caption' => [['caption' => ['top', 'bottom']]], 1628 | // Transitions and Animation 1629 | /** 1630 | * Tranisition Property 1631 | * 1632 | * @see https://tailwindcss.com/docs/transition-property 1633 | */ 1634 | 'transition' => [ 1635 | [ 1636 | 'transition' => [ 1637 | 'none', 1638 | 'all', 1639 | '', 1640 | 'colors', 1641 | 'opacity', 1642 | 'shadow', 1643 | 'transform', 1644 | ArbitraryValueValidator::validate(...), 1645 | ], 1646 | ], 1647 | ], 1648 | /** 1649 | * Transition Duration 1650 | * 1651 | * @see https://tailwindcss.com/docs/transition-duration 1652 | */ 1653 | 'duration' => [['duration' => self::getNumberAndArbitrary()]], 1654 | /** 1655 | * Transition Timing Function 1656 | * 1657 | * @see https://tailwindcss.com/docs/transition-timing-function 1658 | */ 1659 | 'ease' => [['ease' => ['linear', 'in', 'out', 'in-out', ArbitraryValueValidator::validate(...)]]], 1660 | /** 1661 | * Transition Delay 1662 | * 1663 | * @see https://tailwindcss.com/docs/transition-delay 1664 | */ 1665 | 'delay' => [['delay' => self::getNumberAndArbitrary()]], 1666 | /** 1667 | * Animation 1668 | * 1669 | * @see https://tailwindcss.com/docs/animation 1670 | */ 1671 | 'animate' => [['animate' => ['none', 'spin', 'ping', 'pulse', 'bounce', ArbitraryValueValidator::validate(...)]]], 1672 | // Transforms 1673 | /** 1674 | * Transform 1675 | * 1676 | * @see https://tailwindcss.com/docs/transform 1677 | */ 1678 | 'transform' => [['transform' => ['', 'gpu', 'none']]], 1679 | /** 1680 | * Scale 1681 | * 1682 | * @see https://tailwindcss.com/docs/scale 1683 | */ 1684 | 'scale' => [['scale' => [$scale]]], 1685 | /** 1686 | * Scale X 1687 | * 1688 | * @see https://tailwindcss.com/docs/scale 1689 | */ 1690 | 'scale-x' => [['scale-x' => [$scale]]], 1691 | /** 1692 | * Scale Y 1693 | * 1694 | * @see https://tailwindcss.com/docs/scale 1695 | */ 1696 | 'scale-y' => [['scale-y' => [$scale]]], 1697 | /** 1698 | * Rotate 1699 | * 1700 | * @see https://tailwindcss.com/docs/rotate 1701 | */ 1702 | 'rotate' => [['rotate' => [IntegerValidator::validate(...), ArbitraryValueValidator::validate(...)]]], 1703 | /** 1704 | * Translate X 1705 | * 1706 | * @see https://tailwindcss.com/docs/translate 1707 | */ 1708 | 'translate-x' => [['translate-x' => [$translate]]], 1709 | /** 1710 | * Translate Y 1711 | * 1712 | * @see https://tailwindcss.com/docs/translate 1713 | */ 1714 | 'translate-y' => [['translate-y' => [$translate]]], 1715 | /** 1716 | * Skew X 1717 | * 1718 | * @see https://tailwindcss.com/docs/skew 1719 | */ 1720 | 'skew-x' => [['skew-x' => [$skew]]], 1721 | /** 1722 | * Skew Y 1723 | * 1724 | * @see https://tailwindcss.com/docs/skew 1725 | */ 1726 | 'skew-y' => [['skew-y' => [$skew]]], 1727 | /** 1728 | * Transform Origin 1729 | * 1730 | * @see https://tailwindcss.com/docs/transform-origin 1731 | */ 1732 | 'transform-origin' => [ 1733 | [ 1734 | 'origin' => [ 1735 | 'center', 1736 | 'top', 1737 | 'top-right', 1738 | 'right', 1739 | 'bottom-right', 1740 | 'bottom', 1741 | 'bottom-left', 1742 | 'left', 1743 | 'top-left', 1744 | ArbitraryValueValidator::validate(...), 1745 | ], 1746 | ], 1747 | ], 1748 | // Interactivity 1749 | /** 1750 | * Accent Color 1751 | * 1752 | * @see https://tailwindcss.com/docs/accent-color 1753 | */ 1754 | 'accent' => [['accent' => ['auto', $colors]]], 1755 | /** 1756 | * Appearance 1757 | * 1758 | * @see https://tailwindcss.com/docs/appearance 1759 | */ 1760 | 'appearance' => [['appearance' => ['none', 'auto']]], 1761 | /** 1762 | * Cursor 1763 | * 1764 | * @see https://tailwindcss.com/docs/cursor 1765 | */ 1766 | 'cursor' => [ 1767 | [ 1768 | 'cursor' => [ 1769 | 'auto', 1770 | 'default', 1771 | 'pointer', 1772 | 'wait', 1773 | 'text', 1774 | 'move', 1775 | 'help', 1776 | 'not-allowed', 1777 | 'none', 1778 | 'context-menu', 1779 | 'progress', 1780 | 'cell', 1781 | 'crosshair', 1782 | 'vertical-text', 1783 | 'alias', 1784 | 'copy', 1785 | 'no-drop', 1786 | 'grab', 1787 | 'grabbing', 1788 | 'all-scroll', 1789 | 'col-resize', 1790 | 'row-resize', 1791 | 'n-resize', 1792 | 'e-resize', 1793 | 's-resize', 1794 | 'w-resize', 1795 | 'ne-resize', 1796 | 'nw-resize', 1797 | 'se-resize', 1798 | 'sw-resize', 1799 | 'ew-resize', 1800 | 'ns-resize', 1801 | 'nesw-resize', 1802 | 'nwse-resize', 1803 | 'zoom-in', 1804 | 'zoom-out', 1805 | ArbitraryValueValidator::validate(...), 1806 | ], 1807 | ], 1808 | ], 1809 | /** 1810 | * Caret Color 1811 | * 1812 | * @see https://tailwindcss.com/docs/just-in-time-mode#caret-color-utilities 1813 | */ 1814 | 'caret-color' => [['caret' => [$colors]]], 1815 | /** 1816 | * Pointer Events 1817 | * 1818 | * @see https://tailwindcss.com/docs/pointer-events 1819 | */ 1820 | 'pointer-events' => [['pointer-events' => ['none', 'auto']]], 1821 | /** 1822 | * Resize 1823 | * 1824 | * @see https://tailwindcss.com/docs/resize 1825 | */ 1826 | 'resize' => [['resize' => ['none', 'y', 'x', '']]], 1827 | /** 1828 | * Scroll Behavior 1829 | * 1830 | * @see https://tailwindcss.com/docs/scroll-behavior 1831 | */ 1832 | 'scroll-behavior' => [['scroll' => ['auto', 'smooth']]], 1833 | /** 1834 | * Scroll Margin 1835 | * 1836 | * @see https://tailwindcss.com/docs/scroll-margin 1837 | */ 1838 | 'scroll-m' => [['scroll-m' => self::getSpacingWithArbitrary($spacing)]], 1839 | /** 1840 | * Scroll Margin X 1841 | * 1842 | * @see https://tailwindcss.com/docs/scroll-margin 1843 | */ 1844 | 'scroll-mx' => [['scroll-mx' => self::getSpacingWithArbitrary($spacing)]], 1845 | /** 1846 | * Scroll Margin Y 1847 | * 1848 | * @see https://tailwindcss.com/docs/scroll-margin 1849 | */ 1850 | 'scroll-my' => [['scroll-my' => self::getSpacingWithArbitrary($spacing)]], 1851 | /** 1852 | * Scroll Margin Start 1853 | * 1854 | * @see https://tailwindcss.com/docs/scroll-margin 1855 | */ 1856 | 'scroll-ms' => [['scroll-ms' => self::getSpacingWithArbitrary($spacing)]], 1857 | /** 1858 | * Scroll Margin End 1859 | * 1860 | * @see https://tailwindcss.com/docs/scroll-margin 1861 | */ 1862 | 'scroll-me' => [['scroll-me' => self::getSpacingWithArbitrary($spacing)]], 1863 | /** 1864 | * Scroll Margin Top 1865 | * 1866 | * @see https://tailwindcss.com/docs/scroll-margin 1867 | */ 1868 | 'scroll-mt' => [['scroll-mt' => self::getSpacingWithArbitrary($spacing)]], 1869 | /** 1870 | * Scroll Margin Right 1871 | * 1872 | * @see https://tailwindcss.com/docs/scroll-margin 1873 | */ 1874 | 'scroll-mr' => [['scroll-mr' => self::getSpacingWithArbitrary($spacing)]], 1875 | /** 1876 | * Scroll Margin Bottom 1877 | * 1878 | * @see https://tailwindcss.com/docs/scroll-margin 1879 | */ 1880 | 'scroll-mb' => [['scroll-mb' => self::getSpacingWithArbitrary($spacing)]], 1881 | /** 1882 | * Scroll Margin Left 1883 | * 1884 | * @see https://tailwindcss.com/docs/scroll-margin 1885 | */ 1886 | 'scroll-ml' => [['scroll-ml' => self::getSpacingWithArbitrary($spacing)]], 1887 | /** 1888 | * Scroll Padding 1889 | * 1890 | * @see https://tailwindcss.com/docs/scroll-padding 1891 | */ 1892 | 'scroll-p' => [['scroll-p' => self::getSpacingWithArbitrary($spacing)]], 1893 | /** 1894 | * Scroll Padding X 1895 | * 1896 | * @see https://tailwindcss.com/docs/scroll-padding 1897 | */ 1898 | 'scroll-px' => [['scroll-px' => self::getSpacingWithArbitrary($spacing)]], 1899 | /** 1900 | * Scroll Padding Y 1901 | * 1902 | * @see https://tailwindcss.com/docs/scroll-padding 1903 | */ 1904 | 'scroll-py' => [['scroll-py' => self::getSpacingWithArbitrary($spacing)]], 1905 | /** 1906 | * Scroll Padding Start 1907 | * 1908 | * @see https://tailwindcss.com/docs/scroll-padding 1909 | */ 1910 | 'scroll-ps' => [['scroll-ps' => self::getSpacingWithArbitrary($spacing)]], 1911 | /** 1912 | * Scroll Padding End 1913 | * 1914 | * @see https://tailwindcss.com/docs/scroll-padding 1915 | */ 1916 | 'scroll-pe' => [['scroll-pe' => self::getSpacingWithArbitrary($spacing)]], 1917 | /** 1918 | * Scroll Padding Top 1919 | * 1920 | * @see https://tailwindcss.com/docs/scroll-padding 1921 | */ 1922 | 'scroll-pt' => [['scroll-pt' => self::getSpacingWithArbitrary($spacing)]], 1923 | /** 1924 | * Scroll Padding Right 1925 | * 1926 | * @see https://tailwindcss.com/docs/scroll-padding 1927 | */ 1928 | 'scroll-pr' => [['scroll-pr' => self::getSpacingWithArbitrary($spacing)]], 1929 | /** 1930 | * Scroll Padding Bottom 1931 | * 1932 | * @see https://tailwindcss.com/docs/scroll-padding 1933 | */ 1934 | 'scroll-pb' => [['scroll-pb' => self::getSpacingWithArbitrary($spacing)]], 1935 | /** 1936 | * Scroll Padding Left 1937 | * 1938 | * @see https://tailwindcss.com/docs/scroll-padding 1939 | */ 1940 | 'scroll-pl' => [['scroll-pl' => self::getSpacingWithArbitrary($spacing)]], 1941 | /** 1942 | * Scroll Snap Align 1943 | * 1944 | * @see https://tailwindcss.com/docs/scroll-snap-align 1945 | */ 1946 | 'snap-align' => [['snap' => ['start', 'end', 'center', 'align-none']]], 1947 | /** 1948 | * Scroll Snap Stop 1949 | * 1950 | * @see https://tailwindcss.com/docs/scroll-snap-stop 1951 | */ 1952 | 'snap-stop' => [['snap' => ['normal', 'always']]], 1953 | /** 1954 | * Scroll Snap Type 1955 | * 1956 | * @see https://tailwindcss.com/docs/scroll-snap-type 1957 | */ 1958 | 'snap-type' => [['snap' => ['none', 'x', 'y', 'both']]], 1959 | /** 1960 | * Scroll Snap Type Strictness 1961 | * 1962 | * @see https://tailwindcss.com/docs/scroll-snap-type 1963 | */ 1964 | 'snap-strictness' => [['snap' => ['mandatory', 'proximity']]], 1965 | /** 1966 | * Touch Action 1967 | * 1968 | * @see https://tailwindcss.com/docs/touch-action 1969 | */ 1970 | 'touch' => [ 1971 | [ 1972 | 'touch' => [ 1973 | 'auto', 1974 | 'none', 1975 | 'manipulation', 1976 | ], 1977 | ], 1978 | ], 1979 | /** 1980 | * Touch Action X 1981 | * 1982 | * @see https://tailwindcss.com/docs/touch-action 1983 | */ 1984 | 'touch-x' => [ 1985 | [ 1986 | 'touch-pan' => ['x', 'left', 'right'], 1987 | ], 1988 | ], 1989 | /** 1990 | * Touch Action Y 1991 | * 1992 | * @see https://tailwindcss.com/docs/touch-action 1993 | */ 1994 | 'touch-y' => [ 1995 | [ 1996 | 'touch-pan' => ['y', 'up', 'down'], 1997 | ], 1998 | ], 1999 | /** 2000 | * Touch Action Pinch Zoom 2001 | * 2002 | * @see https://tailwindcss.com/docs/touch-action 2003 | */ 2004 | 'touch-pz' => ['touch-pinch-zoom'], 2005 | /** 2006 | * User Select 2007 | * 2008 | * @see https://tailwindcss.com/docs/user-select 2009 | */ 2010 | 'select' => [['select' => ['none', 'text', 'all', 'auto']]], 2011 | /** 2012 | * Will Change 2013 | * 2014 | * @see https://tailwindcss.com/docs/will-change 2015 | */ 2016 | 'will-change' => [ 2017 | ['will-change' => ['auto', 'scroll', 'contents', 'transform', ArbitraryValueValidator::validate(...)]], 2018 | ], 2019 | // SVG 2020 | /** 2021 | * Fill 2022 | * 2023 | * @see https://tailwindcss.com/docs/fill 2024 | */ 2025 | 'fill' => [['fill' => [$colors, 'none']]], 2026 | /** 2027 | * Stroke Width 2028 | * 2029 | * @see https://tailwindcss.com/docs/stroke-width 2030 | */ 2031 | 'stroke-w' => [['stroke' => [LengthValidator::validate(...), ArbitraryLengthValidator::validate(...), ArbitraryNumberValidator::validate(...)]]], 2032 | /** 2033 | * Stroke 2034 | * 2035 | * @see https://tailwindcss.com/docs/stroke 2036 | */ 2037 | 'stroke' => [['stroke' => [$colors, 'none']]], 2038 | // Accessibility 2039 | /** 2040 | * Screen Readers 2041 | * 2042 | * @see https://tailwindcss.com/docs/screen-readers 2043 | */ 2044 | 'sr' => ['sr-only', 'not-sr-only'], 2045 | /** 2046 | * Forced Color Adjust 2047 | * 2048 | * @see https://tailwindcss.com/docs/forced-color-adjust 2049 | */ 2050 | 'forced-color-adjust' => [['forced-color-adjust' => ['auto', 'none']]], 2051 | ], 2052 | 'conflictingClassGroups' => [ 2053 | 'overflow' => ['overflow-x', 'overflow-y'], 2054 | 'overscroll' => ['overscroll-x', 'overscroll-y'], 2055 | 'inset' => ['inset-x', 'inset-y', 'start', 'end', 'top', 'right', 'bottom', 'left'], 2056 | 'inset-x' => ['right', 'left'], 2057 | 'inset-y' => ['top', 'bottom'], 2058 | 'flex' => ['basis', 'grow', 'shrink'], 2059 | 'gap' => ['gap-x', 'gap-y'], 2060 | 'p' => ['px', 'py', 'ps', 'pe', 'pt', 'pr', 'pb', 'pl'], 2061 | 'px' => ['pr', 'pl'], 2062 | 'py' => ['pt', 'pb'], 2063 | 'm' => ['mx', 'my', 'ms', 'me', 'mt', 'mr', 'mb', 'ml'], 2064 | 'mx' => ['mr', 'ml'], 2065 | 'my' => ['mt', 'mb'], 2066 | 'size' => ['w', 'h'], 2067 | 'font-size' => ['leading'], 2068 | 'fvn-normal' => [ 2069 | 'fvn-ordinal', 2070 | 'fvn-slashed-zero', 2071 | 'fvn-figure', 2072 | 'fvn-spacing', 2073 | 'fvn-fraction', 2074 | ], 2075 | 'fvn-ordinal' => ['fvn-normal'], 2076 | 'fvn-slashed-zero' => ['fvn-normal'], 2077 | 'fvn-figure' => ['fvn-normal'], 2078 | 'fvn-spacing' => ['fvn-normal'], 2079 | 'fvn-fraction' => ['fvn-normal'], 2080 | 'line-clamp' => ['display', 'overflow'], 2081 | 'rounded' => [ 2082 | 'rounded-s', 2083 | 'rounded-e', 2084 | 'rounded-t', 2085 | 'rounded-r', 2086 | 'rounded-b', 2087 | 'rounded-l', 2088 | 'rounded-ss', 2089 | 'rounded-se', 2090 | 'rounded-ee', 2091 | 'rounded-es', 2092 | 'rounded-tl', 2093 | 'rounded-tr', 2094 | 'rounded-br', 2095 | 'rounded-bl', 2096 | ], 2097 | 'rounded-s' => ['rounded-ss', 'rounded-es'], 2098 | 'rounded-e' => ['rounded-se', 'rounded-ee'], 2099 | 'rounded-t' => ['rounded-tl', 'rounded-tr'], 2100 | 'rounded-r' => ['rounded-tr', 'rounded-br'], 2101 | 'rounded-b' => ['rounded-br', 'rounded-bl'], 2102 | 'rounded-l' => ['rounded-tl', 'rounded-bl'], 2103 | 'border-spacing' => ['border-spacing-x', 'border-spacing-y'], 2104 | 'border-w' => [ 2105 | 'border-w-s', 2106 | 'border-w-e', 2107 | 'border-w-t', 2108 | 'border-w-r', 2109 | 'border-w-b', 2110 | 'border-w-l', 2111 | ], 2112 | 'border-w-x' => ['border-w-r', 'border-w-l'], 2113 | 'border-w-y' => ['border-w-t', 'border-w-b'], 2114 | 'border-color' => [ 2115 | 'border-color-t', 2116 | 'border-color-r', 2117 | 'border-color-b', 2118 | 'border-color-l', 2119 | ], 2120 | 'border-color-x' => ['border-color-r', 'border-color-l'], 2121 | 'border-color-y' => ['border-color-t', 'border-color-b'], 2122 | 'scroll-m' => [ 2123 | 'scroll-mx', 2124 | 'scroll-my', 2125 | 'scroll-ms', 2126 | 'scroll-me', 2127 | 'scroll-mt', 2128 | 'scroll-mr', 2129 | 'scroll-mb', 2130 | 'scroll-ml', 2131 | ], 2132 | 'scroll-mx' => ['scroll-mr', 'scroll-ml'], 2133 | 'scroll-my' => ['scroll-mt', 'scroll-mb'], 2134 | 'scroll-p' => [ 2135 | 'scroll-px', 2136 | 'scroll-py', 2137 | 'scroll-ps', 2138 | 'scroll-pe', 2139 | 'scroll-pt', 2140 | 'scroll-pr', 2141 | 'scroll-pb', 2142 | 'scroll-pl', 2143 | ], 2144 | 'scroll-px' => ['scroll-pr', 'scroll-pl'], 2145 | 'scroll-py' => ['scroll-pt', 'scroll-pb'], 2146 | 'touch' => ['touch-x', 'touch-y', 'touch-pz'], 2147 | 'touch-x' => ['touch'], 2148 | 'touch-y' => ['touch'], 2149 | 'touch-pz' => ['touch'], 2150 | ], 2151 | 'conflictingClassGroupModifiers' => [ 2152 | 'font-size' => ['leading'], 2153 | ], 2154 | ]; 2155 | } 2156 | 2157 | public static function fromTheme(string $key): ThemeGetter 2158 | { 2159 | return new ThemeGetter($key); 2160 | } 2161 | 2162 | /** 2163 | * @return array 2164 | */ 2165 | private static function getNumber(): array 2166 | { 2167 | return [ 2168 | NumberValidator::validate(...), 2169 | ArbitraryNumberValidator::validate(...), 2170 | ]; 2171 | } 2172 | 2173 | /** 2174 | * @return array 2175 | */ 2176 | private static function getLengthWithEmptyAndArbitrary(): array 2177 | { 2178 | return [ 2179 | '', 2180 | LengthValidator::validate(...), 2181 | ArbitraryLengthValidator::validate(...), 2182 | ]; 2183 | } 2184 | 2185 | /** 2186 | * @return array 2187 | */ 2188 | private static function getZeroAndEmpty(): array 2189 | { 2190 | return [ 2191 | '', 2192 | '0', 2193 | ArbitraryValueValidator::validate(...), 2194 | ]; 2195 | } 2196 | 2197 | /** 2198 | * @return array 2199 | */ 2200 | private static function getNumberAndArbitrary(): array 2201 | { 2202 | return [NumberValidator::validate(...), ArbitraryValueValidator::validate(...)]; 2203 | } 2204 | 2205 | /** 2206 | * @return array 2207 | */ 2208 | private static function getSpacingWithAutoAndArbitrary(ThemeGetter $spacing): array 2209 | { 2210 | return [ 2211 | 'auto', 2212 | ArbitraryValueValidator::validate(...), 2213 | $spacing, 2214 | ]; 2215 | } 2216 | 2217 | /** 2218 | * @return array 2219 | */ 2220 | private static function getSpacingWithArbitrary(ThemeGetter $spacing): array 2221 | { 2222 | return [ 2223 | ArbitraryValueValidator::validate(...), 2224 | $spacing, 2225 | ]; 2226 | } 2227 | 2228 | /** 2229 | * @return array 2230 | */ 2231 | private static function getBreaks(): array 2232 | { 2233 | return [ 2234 | 'auto', 2235 | 'avoid', 2236 | 'all', 2237 | 'avoid-page', 2238 | 'page', 2239 | 'left', 2240 | 'right', 2241 | 'column', 2242 | ]; 2243 | } 2244 | 2245 | /** 2246 | * @return array 2247 | */ 2248 | private static function getPositions(): array 2249 | { 2250 | return [ 2251 | 'bottom', 2252 | 'center', 2253 | 'left', 2254 | 'left-bottom', 2255 | 'left-top', 2256 | 'right', 2257 | 'right-bottom', 2258 | 'right-top', 2259 | 'top', 2260 | ]; 2261 | } 2262 | 2263 | /** 2264 | * @return array 2265 | */ 2266 | private static function getOverflow(): array 2267 | { 2268 | return [ 2269 | 'auto', 2270 | 'hidden', 2271 | 'clip', 2272 | 'visible', 2273 | 'scroll', 2274 | ]; 2275 | } 2276 | 2277 | /** 2278 | * @return array 2279 | */ 2280 | private static function getOverscroll(): array 2281 | { 2282 | return [ 2283 | 'auto', 2284 | 'contain', 2285 | 'none', 2286 | ]; 2287 | } 2288 | 2289 | /** 2290 | * @return array 2291 | */ 2292 | private static function getNumberWithAutoAndArbitrary(): array 2293 | { 2294 | return [ 2295 | 'auto', 2296 | NumberValidator::validate(...), 2297 | ArbitraryValueValidator::validate(...), 2298 | ]; 2299 | } 2300 | 2301 | /** 2302 | * @return array 2303 | */ 2304 | private static function getAlign(): array 2305 | { 2306 | return [ 2307 | 'start', 2308 | 'end', 2309 | 'center', 2310 | 'between', 2311 | 'around', 2312 | 'evenly', 2313 | 'stretch', 2314 | ]; 2315 | } 2316 | 2317 | /** 2318 | * @return array 2319 | */ 2320 | private static function getLineStyles(): array 2321 | { 2322 | return [ 2323 | 'solid', 2324 | 'dashed', 2325 | 'dotted', 2326 | 'double', 2327 | 'none', 2328 | ]; 2329 | } 2330 | 2331 | /** 2332 | * @return array 2333 | */ 2334 | private static function getBlendModes(): array 2335 | { 2336 | return [ 2337 | 'normal', 2338 | 'multiply', 2339 | 'screen', 2340 | 'overlay', 2341 | 'darken', 2342 | 'lighten', 2343 | 'color-dodge', 2344 | 'color-burn', 2345 | 'hard-light', 2346 | 'soft-light', 2347 | 'difference', 2348 | 'exclusion', 2349 | 'hue', 2350 | 'saturation', 2351 | 'color', 2352 | 'luminosity', 2353 | 'plus-lighter', 2354 | ]; 2355 | } 2356 | } 2357 | -------------------------------------------------------------------------------- /src/Support/Str.php: -------------------------------------------------------------------------------- 1 | value, $characters)); 14 | } 15 | 16 | /** 17 | * @return Collection 18 | */ 19 | public function split(string $pattern, int $limit = -1, int $flags = 0): Collection 20 | { 21 | $segments = preg_split($pattern, $this->value, $limit, $flags); 22 | 23 | return $segments === [] || $segments === false ? Collection::make() : Collection::make($segments); 24 | } 25 | 26 | public function substr(int $start, ?int $length = null, string $encoding = 'UTF-8'): self 27 | { 28 | return new self(Str::substr($this->value, $start, $length, $encoding)); 29 | } 30 | 31 | public function toString(): string 32 | { 33 | return $this->value; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Support/TailwindClassParser.php: -------------------------------------------------------------------------------- 1 | , classGroups: array,conflictingClassGroups: array>, conflictingClassGroupModifiers: array>} $config 21 | */ 22 | public function __construct(array $configuration) 23 | { 24 | $this->classMap = ClassMap::create($configuration); 25 | } 26 | 27 | /** 28 | * @param array $classParts 29 | */ 30 | private static function getGroupRecursive(array $classParts, ClassPartObject $classPartObject): ?string 31 | { 32 | if ($classParts === []) { 33 | return $classPartObject->classGroupId; 34 | } 35 | 36 | $currentClassPart = $classParts[0] ?? null; 37 | $nextClassPartObject = $classPartObject->nextPart[$currentClassPart] ?? null; 38 | $classGroupFromNextClassPart = $nextClassPartObject !== null 39 | ? self::getGroupRecursive(array_slice($classParts, 1), $nextClassPartObject) 40 | : null; 41 | 42 | if ($classGroupFromNextClassPart) { 43 | return $classGroupFromNextClassPart; 44 | } 45 | 46 | if ($classPartObject->validators === []) { 47 | return null; 48 | } 49 | 50 | $classRest = implode(self::CLASS_PART_SEPARATOR, $classParts); 51 | 52 | return Collection::make($classPartObject->validators)->first(fn (ClassValidatorObject $validator) => ($validator->validator)($classRest))?->classGroupId; 53 | } 54 | 55 | public function parse(string $class): ParsedClass 56 | { 57 | [ 58 | 'modifiers' => $modifiers, 59 | 'hasImportantModifier' => $hasImportantModifier, 60 | 'baseClassName' => $baseClassName, 61 | 'maybePostfixModifierPosition' => $maybePostfixModifierPosition 62 | ] = $this->splitModifiers($class); 63 | 64 | $classGroupId = $this->getClassGroupId($maybePostfixModifierPosition ? Str::substr($baseClassName, 0, $maybePostfixModifierPosition) : $baseClassName); 65 | 66 | $hasPostfixModifier = $maybePostfixModifierPosition !== null; 67 | 68 | // TODO 69 | // if (!classGroupId) { 70 | // if (!maybePostfixModifierPosition) { 71 | // return { 72 | // isTailwindClass: false as const, 73 | // originalClassName, 74 | // } 75 | // } 76 | // 77 | // classGroupId = getClassGroupId(baseClassName) 78 | // 79 | // if (!classGroupId) { 80 | // return { 81 | // isTailwindClass: false as const, 82 | // originalClassName, 83 | // } 84 | // } 85 | // 86 | // hasPostfixModifier = false 87 | // } 88 | 89 | $variantModifier = implode(':', $this->sortModifiers($modifiers)); 90 | 91 | $modifierId = $hasImportantModifier ? $variantModifier.self::IMPORTANT_MODIFIER : $variantModifier; 92 | 93 | return new ParsedClass( 94 | modifiers: $modifiers, 95 | hasImportantModifier: $hasImportantModifier, 96 | hasPostfixModifier: $hasPostfixModifier, 97 | modifierId: $modifierId, 98 | classGroupId: $classGroupId, 99 | baseClassName: $baseClassName, 100 | originalClassName: $class, 101 | ); 102 | } 103 | 104 | private function getClassGroupId(string $class): string 105 | { 106 | $classParts = explode(self::CLASS_PART_SEPARATOR, $class); 107 | 108 | // Classes like `-inset-1` produce an empty string as first classPart. We assume that classes for negative values are used correctly and remove it from classParts. 109 | if ($classParts[0] === '' && count($classParts) !== 1) { 110 | array_shift($classParts); 111 | } 112 | 113 | return self::getGroupRecursive($classParts, $this->classMap) ?: $this->getGroupIdForArbitraryProperty($class); 114 | } 115 | 116 | private function getGroupIdForArbitraryProperty(string $className): string 117 | { 118 | if (Str::match(self::ARBITRARY_PROPERTY_REGEX, $className) !== '' && Str::match(self::ARBITRARY_PROPERTY_REGEX, $className) !== '0') { 119 | $arbitraryPropertyClassName = Str::match(self::ARBITRARY_PROPERTY_REGEX, $className); 120 | $property = Str::before($arbitraryPropertyClassName, ':'); 121 | 122 | if ($property !== '' && $property !== '0') { 123 | // I use two dots here because one dot is used as prefix for class groups in plugins 124 | return 'arbitrary..'.$property; 125 | } 126 | } 127 | 128 | // TODO: not sure here 129 | return $className; 130 | } 131 | 132 | /** 133 | * @return array{modifiers: array, hasImportantModifier: bool, baseClassName: string, maybePostfixModifierPosition: int|null} 134 | */ 135 | private function splitModifiers(string $className): array 136 | { 137 | $separator = isset(Config::getMergedConfig()['separator']) && is_string(Config::getMergedConfig()['separator']) ? Config::getMergedConfig()['separator'] : ':'; 138 | $isSeparatorSingleCharacter = strlen($separator) === 1; 139 | $firstSeparatorCharacter = $separator[0]; 140 | $separatorLength = strlen($separator); 141 | 142 | $modifiers = []; 143 | 144 | $bracketDepth = 0; 145 | $modifierStart = 0; 146 | $postfixModifierPosition = null; 147 | 148 | for ($index = 0; $index < strlen($className); $index++) { 149 | $currentCharacter = $className[$index]; 150 | 151 | if ($bracketDepth === 0) { 152 | if ( 153 | $currentCharacter === $firstSeparatorCharacter && 154 | ($isSeparatorSingleCharacter || 155 | Str::substr($className, $index, $separatorLength) === $separator) 156 | ) { 157 | $modifiers[] = Str::substr($className, $modifierStart, $index - $modifierStart); 158 | $modifierStart = $index + $separatorLength; 159 | 160 | continue; 161 | } 162 | 163 | if ($currentCharacter === '/') { 164 | $postfixModifierPosition = $index; 165 | 166 | continue; 167 | } 168 | } 169 | 170 | if ($currentCharacter === '[') { 171 | $bracketDepth++; 172 | } elseif ($currentCharacter === ']') { 173 | $bracketDepth--; 174 | } 175 | } 176 | 177 | $baseClassNameWithImportantModifier = 178 | $modifiers === [] ? $className : Str::substr($className, $modifierStart); 179 | $hasImportantModifier = 180 | Str::startsWith($baseClassNameWithImportantModifier, self::IMPORTANT_MODIFIER); 181 | $baseClassName = $hasImportantModifier 182 | ? Str::substr($baseClassNameWithImportantModifier, 1) 183 | : $baseClassNameWithImportantModifier; 184 | 185 | $maybePostfixModifierPosition = $postfixModifierPosition && $postfixModifierPosition > $modifierStart 186 | ? $postfixModifierPosition - $modifierStart 187 | : null; 188 | 189 | return [ 190 | 'modifiers' => $modifiers, 191 | 'hasImportantModifier' => $hasImportantModifier, 192 | 'baseClassName' => $baseClassName, 193 | 'maybePostfixModifierPosition' => $maybePostfixModifierPosition, 194 | ]; 195 | } 196 | 197 | /** 198 | * @param array $modifiers 199 | * @return array 200 | */ 201 | private function sortModifiers(array $modifiers): array 202 | { 203 | if (count($modifiers) <= 1) { 204 | return $modifiers; 205 | } 206 | 207 | /** 208 | * @var Collection $sortedModifiers 209 | */ 210 | $sortedModifiers = Collection::make(); 211 | $unsortedModifiers = Collection::make(); 212 | 213 | foreach ($modifiers as $modifier) { 214 | $isArbitraryVariant = $modifier[0] === '['; 215 | 216 | if ($isArbitraryVariant) { 217 | $sortedModifiers = $sortedModifiers->concat([...$unsortedModifiers->sort()->all(), $modifier]); 218 | $unsortedModifiers = Collection::make(); 219 | } else { 220 | $unsortedModifiers->add($modifier); 221 | } 222 | } 223 | 224 | $sortedModifiers = $sortedModifiers->concat($unsortedModifiers->sort()); 225 | 226 | return $sortedModifiers->all(); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/TailwindMerge.php: -------------------------------------------------------------------------------- 1 | make(); 19 | } 20 | 21 | /** 22 | * Creates a new factory instance 23 | */ 24 | public static function factory(): Factory 25 | { 26 | return new Factory(); 27 | } 28 | 29 | /** 30 | * @param array $configuration 31 | */ 32 | public function __construct( 33 | private readonly array $configuration, 34 | private readonly ?CacheInterface $cache = null, 35 | ) { 36 | } 37 | 38 | /** 39 | * @param string|array> ...$args 40 | */ 41 | public function merge(...$args): string 42 | { 43 | $input = Collection::make($args)->flatten()->join(' '); 44 | 45 | return $this->withCache($input, function (string $input): string { 46 | $conflictingClassGroups = []; 47 | 48 | $parser = new TailwindClassParser($this->configuration); 49 | 50 | return Str::of($input) 51 | ->trim() 52 | ->split('/\s+/') 53 | ->map(fn (string $class): ParsedClass => $parser->parse($class)) // @phpstan-ignore-line 54 | ->reverse() 55 | ->map(function (ParsedClass $class) use (&$conflictingClassGroups): ?string { 56 | $classId = $class->modifierId.$class->classGroupId; 57 | 58 | if (array_key_exists($classId, $conflictingClassGroups)) { 59 | return null; 60 | } 61 | 62 | $conflictingClassGroups[$classId] = true; 63 | 64 | foreach ($this->getConflictingClassGroupIds($class->classGroupId, $class->hasPostfixModifier) as $group) { 65 | $conflictingClassGroups[$class->modifierId.$group] = true; 66 | } 67 | 68 | return $class->originalClassName; 69 | }) 70 | ->reverse() 71 | ->filter() 72 | ->join(' '); 73 | }); 74 | } 75 | 76 | /** 77 | * @return array 78 | */ 79 | private function getConflictingClassGroupIds(string $classGroupId, bool $hasPostfixModifier): array 80 | { 81 | $conflicts = Config::getMergedConfig()['conflictingClassGroups'][$classGroupId] ?? []; 82 | 83 | if ($hasPostfixModifier && isset(Config::getMergedConfig()['conflictingClassGroupModifiers'][$classGroupId])) { 84 | return [...$conflicts, ...Config::getMergedConfig()['conflictingClassGroupModifiers'][$classGroupId]]; 85 | } 86 | 87 | return $conflicts; 88 | } 89 | 90 | private function withCache(string $input, \Closure $callback): string 91 | { 92 | if (! $this->cache instanceof CacheInterface) { 93 | return $callback($input); 94 | } 95 | 96 | $key = hash('xxh3', 'tailwind-merge-'.$input); 97 | 98 | if ($this->cache->has($key)) { 99 | $cachedValue = $this->cache->get($key); 100 | 101 | if (is_string($cachedValue)) { 102 | return $cachedValue; 103 | } 104 | } 105 | 106 | $mergedClasses = $callback($input); 107 | 108 | $this->cache->set($key, $mergedClasses); 109 | 110 | return $mergedClasses; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Validators/AnyValueValidator.php: -------------------------------------------------------------------------------- 1 | false); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Validators/ArbitraryShadowValidator.php: -------------------------------------------------------------------------------- 1 | false); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Validators/ArbitraryValueValidator.php: -------------------------------------------------------------------------------- 1 | $labels 12 | */ 13 | protected static function getIsArbitraryValue(string $value, string|array $labels, callable $isLengthOnly): bool 14 | { 15 | $labels = is_string($labels) ? [$labels] : $labels; 16 | 17 | preg_match('/^\[(?:([a-z-]+):)?(.+)\]$/i', $value, $result); 18 | 19 | if ($result !== []) { 20 | if ($result[1] !== '' && $result[1] !== '0') { 21 | return in_array($result[1], $labels); 22 | } 23 | 24 | return $isLengthOnly($result[2] ?? null); 25 | } 26 | 27 | return false; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Validators/IntegerValidator.php: -------------------------------------------------------------------------------- 1 | contains($value)) { 21 | return true; 22 | } 23 | 24 | return Str::hasMatch(self::FRACTION_REGEX, $value); 25 | } 26 | 27 | /** 28 | * @return Collection 29 | */ 30 | private static function stringLengths(): Collection 31 | { 32 | return Collection::make(['px', 'full', 'screen']); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Validators/NumberValidator.php: -------------------------------------------------------------------------------- 1 | substr(0, -1)->toString()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Validators/TshirtSizeValidator.php: -------------------------------------------------------------------------------- 1 | $nextPart 9 | * @param array $validators 10 | */ 11 | public function __construct( 12 | public array $nextPart = [], 13 | public array $validators = [], 14 | public ?string $classGroupId = null, 15 | ) { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ValueObjects/ClassValidatorObject.php: -------------------------------------------------------------------------------- 1 | $modifiers 9 | */ 10 | public function __construct( 11 | public array $modifiers, 12 | public bool $hasImportantModifier, 13 | public bool $hasPostfixModifier, 14 | public string $modifierId, 15 | public string $classGroupId, 16 | public string $baseClassName, 17 | public string $originalClassName, 18 | ) { 19 | // 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ValueObjects/ThemeGetter.php: -------------------------------------------------------------------------------- 1 | > $theme 14 | * @return array 15 | */ 16 | public function get(array $theme): array 17 | { 18 | return $theme[$this->key] ?? []; 19 | } 20 | } 21 | --------------------------------------------------------------------------------