diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 000000000..d1f4d598a
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,81 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+
+ strategy:
+ matrix:
+ node-version: [20]
+
+ steps:
+ - uses: actions/checkout@v3
+ - uses: pnpm/action-setup@v3
+ with:
+ version: ^8.15.0
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v3
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'pnpm'
+
+ # Cargo already skips downloading dependencies if they already exist
+ - name: Cache cargo
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ target/
+ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+
+ # Cache the `oxide` Rust build
+ - name: Cache oxide build
+ uses: actions/cache@v3
+ with:
+ path: |
+ ./oxide/target/
+ ./oxide/crates/node/*.node
+ ./oxide/crates/node/index.js
+ ./oxide/crates/node/index.d.ts
+ key: ${{ runner.os }}-oxide-${{ hashFiles('./oxide/crates/**/*') }}
+
+ - name: Install dependencies
+ run: pnpm install
+
+ - name: Install Playwright Browsers
+ run: npx playwright install --with-deps
+
+ - name: Build
+ run: pnpm run build
+
+ - name: Lint
+ run: pnpm run lint
+
+ - name: Test
+ run: pnpm run test
+
+ - name: Run Playwright tests
+ run: npm run test:ui
+
+ - uses: actions/upload-artifact@v3
+ if: always()
+ with:
+ name: playwright-report
+ path: packages/tailwindcss/playwright-report/
+ retention-days: 30
+
+ - name: Bench
+ run: pnpm run bench
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 000000000..5763712fb
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,214 @@
+name: Release
+
+on:
+ workflow_dispatch:
+ inputs:
+ release_channel:
+ description: 'Release channel'
+ required: false
+ default: 'internal'
+ type: string
+
+permissions:
+ contents: read
+
+env:
+ APP_NAME: tailwindcss-oxide
+ NODE_VERSION: 20
+ PNPM_VERSION: ^8.15.0
+ OXIDE_LOCATION: ./oxide/crates/node
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ include:
+ # Windows
+ - os: windows-latest
+ target: x86_64-pc-windows-msvc
+ # macOS
+ - os: macos-latest
+ target: x86_64-apple-darwin
+ strip: strip -x # Must use -x on macOS. This produces larger results on linux.
+ - os: macos-latest
+ target: aarch64-apple-darwin
+ page-size: 14
+ strip: strip -x # Must use -x on macOS. This produces larger results on linux.
+ # Linux
+ - os: ubuntu-latest
+ target: x86_64-unknown-linux-gnu
+ strip: strip
+ container:
+ image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
+ - os: ubuntu-latest
+ target: aarch64-unknown-linux-gnu
+ strip: llvm-strip
+ container:
+ image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
+ - os: ubuntu-latest
+ target: armv7-unknown-linux-gnueabihf
+ strip: llvm-strip
+ container:
+ image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-zig
+ - os: ubuntu-latest
+ target: aarch64-unknown-linux-musl
+ strip: aarch64-linux-musl-strip
+ download: true
+ container:
+ image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
+ - os: ubuntu-latest
+ target: x86_64-unknown-linux-musl
+ strip: strip
+ download: true
+ container:
+ image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
+
+ name: Build ${{ matrix.target }} (OXIDE)
+ runs-on: ${{ matrix.os }}
+ container: ${{ matrix.container }}
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v3
+ - uses: pnpm/action-setup@v3
+ with:
+ version: ${{ env.PNPM_VERSION }}
+
+ - name: Use Node.js ${{ env.NODE_VERSION }}
+ uses: actions/setup-node@v3
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'pnpm'
+
+ # Cargo already skips downloading dependencies if they already exist
+ - name: Cache cargo
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ target/
+ key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+
+ # Cache the `oxide` Rust build
+ - name: Cache oxide build
+ uses: actions/cache@v3
+ with:
+ path: |
+ ./oxide/target/
+ ./oxide/crates/node/*.node
+ ./oxide/crates/node/index.js
+ ./oxide/crates/node/index.d.ts
+ key: ${{ runner.os }}-${{ matrix.target }}-oxide-${{ hashFiles('./oxide/crates/**/*') }}
+
+ - name: Install Node.JS
+ uses: actions/setup-node@v3
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+
+ - name: Setup cross compile toolchain
+ if: ${{ matrix.setup }}
+ run: ${{ matrix.setup }}
+
+ - name: Install Rust (Stable)
+ if: ${{ matrix.download }}
+ run: |
+ rustup default stable
+
+ - name: Setup rust target
+ run: rustup target add ${{ matrix.target }}
+
+ - name: Install dependencies
+ run: pnpm install --ignore-scripts --filter=!./playgrounds/*
+
+ - name: Build release
+ run: pnpm run build --filter ${{ env.OXIDE_LOCATION }}
+ env:
+ RUST_TARGET: ${{ matrix.target }}
+ JEMALLOC_SYS_WITH_LG_PAGE: ${{ matrix.page-size }}
+
+ - name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034
+ if: ${{ matrix.strip }}
+ run: ${{ matrix.strip }} ${{ env.OXIDE_LOCATION }}/*.node
+
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: bindings-${{ matrix.target }}
+ path: ${{ env.OXIDE_LOCATION }}/*.node
+
+ release:
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ name: Build and release Tailwind CSS
+
+ needs:
+ - build
+
+ steps:
+ - uses: actions/checkout@v3
+ - uses: pnpm/action-setup@v3
+ with:
+ version: ${{ env.PNPM_VERSION }}
+
+ - name: Use Node.js ${{ env.NODE_VERSION }}
+ uses: actions/setup-node@v3
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'pnpm'
+ registry-url: 'https://registry.npmjs.org'
+
+ # Cargo already skips downloading dependencies if they already exist
+ - name: Cache cargo
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ target/
+ key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+
+ # Cache the `oxide` Rust build
+ - name: Cache oxide build
+ uses: actions/cache@v3
+ with:
+ path: |
+ ./oxide/target/
+ ./oxide/crates/node/*.node
+ ./oxide/crates/node/index.js
+ ./oxide/crates/node/index.d.ts
+ key: ${{ runner.os }}-${{ matrix.target }}-oxide-${{ hashFiles('./oxide/crates/**/*') }}
+
+ - name: Install dependencies
+ run: pnpm install --ignore-scripts --filter=!./playgrounds/*
+
+ - name: Build Tailwind CSS
+ run: pnpm run build
+
+ - name: Download artifacts
+ uses: actions/download-artifact@v3
+ with:
+ path: ${{ env.OXIDE_LOCATION }}
+
+ - name: Move artifacts
+ run: |
+ cd ${{ env.OXIDE_LOCATION }}
+ cp bindings-x86_64-pc-windows-msvc/* ./npm/win32-x64-msvc/
+ cp bindings-x86_64-apple-darwin/* ./npm/darwin-x64/
+ cp bindings-aarch64-apple-darwin/* ./npm/darwin-arm64/
+ cp bindings-aarch64-unknown-linux-gnu/* ./npm/linux-arm64-gnu/
+ cp bindings-aarch64-unknown-linux-musl/* ./npm/linux-arm64-musl/
+ cp bindings-armv7-unknown-linux-gnueabihf/* ./npm/linux-arm-gnueabihf/
+ cp bindings-x86_64-unknown-linux-gnu/* ./npm/linux-x64-gnu/
+ cp bindings-x86_64-unknown-linux-musl/* ./npm/linux-x64-musl/
+
+ - name: Lock pre-release versions
+ run: node ./scripts/lock-pre-release-versions.mjs
+
+ - name: Publish
+ run: pnpm --recursive publish --tag ${{ inputs.release_channel }} --no-git-checks
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..092494627
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+dist/
+coverage/
+.turbo
+test-results/
+playwright-report/
+blob-report/
+playwright/.cache/
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 000000000..ded82e2f6
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+auto-install-peers = true
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 000000000..0333c2dc5
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,8 @@
+coverage/
+node_modules/
+pnpm-lock.yaml
+oxide/target/
+oxide/crates/node/index.d.ts
+oxide/crates/node/index.js
+.next
+.fingerprint
diff --git a/oxide/.gitignore b/oxide/.gitignore
new file mode 100644
index 000000000..2f7896d1d
--- /dev/null
+++ b/oxide/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/oxide/Cargo.lock b/oxide/Cargo.lock
new file mode 100644
index 000000000..7404bde1d
--- /dev/null
+++ b/oxide/Cargo.lock
@@ -0,0 +1,1200 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84"
+
+[[package]]
+name = "bstr"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5"
+dependencies = [
+ "memchr",
+ "once_cell",
+ "regex-automata",
+ "serde",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "cast"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "2.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
+dependencies = [
+ "bitflags 1.3.2",
+ "textwrap",
+ "unicode-width",
+]
+
+[[package]]
+name = "convert_case"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "criterion"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
+dependencies = [
+ "atty",
+ "cast",
+ "clap",
+ "criterion-plot",
+ "csv",
+ "itertools",
+ "lazy_static",
+ "num-traits",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_cbor",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876"
+dependencies = [
+ "cast",
+ "itertools",
+]
+
+[[package]]
+name = "crossbeam"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c"
+dependencies = [
+ "cfg-if",
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-epoch",
+ "crossbeam-queue",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "csv"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b015497079b9a9d69c02ad25de6c0a6edef051ea6360a327d0bd05802ef64ad"
+dependencies = [
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ctor"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "990a40740adf249724a6000c0fc4bd574712f50bb17c2d6f6cec837ae2f0ee75"
+dependencies = [
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "either"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
+
+[[package]]
+name = "errno"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "fastrand"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "globset"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
+dependencies = [
+ "aho-corasick 0.7.20",
+ "bstr",
+ "fnv",
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "globwalk"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
+dependencies = [
+ "bitflags 1.3.2",
+ "ignore",
+ "walkdir",
+]
+
+[[package]]
+name = "half"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+
+[[package]]
+name = "ignore"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
+dependencies = [
+ "globset",
+ "lazy_static",
+ "log",
+ "memchr",
+ "regex",
+ "same-file",
+ "thread_local",
+ "walkdir",
+ "winapi-util",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi 0.3.1",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+
+[[package]]
+name = "js-sys"
+version = "0.3.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.144"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
+
+[[package]]
+name = "libloading"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
+[[package]]
+name = "log"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de"
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "memoffset"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "napi"
+version = "2.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7f0a2e93526dd9c8c522d72a4d0c88678be8966fabe9fb8f2947fde6339b682"
+dependencies = [
+ "bitflags 2.3.1",
+ "ctor",
+ "napi-derive",
+ "napi-sys",
+ "once_cell",
+]
+
+[[package]]
+name = "napi-build"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e"
+
+[[package]]
+name = "napi-derive"
+version = "2.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da1c6a8fa84d549aa8708fcd062372bf8ec6e849de39016ab921067d21bde367"
+dependencies = [
+ "cfg-if",
+ "convert_case",
+ "napi-derive-backend",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "napi-derive-backend"
+version = "1.0.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20bbc7c69168d06a848f925ec5f0e0997f98e8c8d4f2cc30157f0da51c009e17"
+dependencies = [
+ "convert_case",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "semver",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "napi-sys"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "166b5ef52a3ab5575047a9fe8d4a030cdd0f63c96f071cd6907674453b07bae3"
+dependencies = [
+ "libloading",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
+dependencies = [
+ "hermit-abi 0.2.6",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.17.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b"
+
+[[package]]
+name = "oorandom"
+version = "11.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "plotters"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97"
+dependencies = [
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rayon"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-utils",
+ "num_cpus",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "regex"
+version = "1.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390"
+dependencies = [
+ "aho-corasick 1.0.1",
+ "memchr",
+ "regex-syntax 0.7.2",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.29",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
+[[package]]
+name = "regex-syntax"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
+
+[[package]]
+name = "rustix"
+version = "0.37.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
+dependencies = [
+ "bitflags 1.3.2",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "semver"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
+
+[[package]]
+name = "serde"
+version = "1.0.163"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2"
+
+[[package]]
+name = "serde_cbor"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
+dependencies = [
+ "half",
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.163"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.96"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tailwind-oxide"
+version = "0.0.0"
+dependencies = [
+ "napi",
+ "napi-build",
+ "napi-derive",
+ "rayon",
+ "tailwindcss-core",
+]
+
+[[package]]
+name = "tailwindcss-core"
+version = "0.1.0"
+dependencies = [
+ "bstr",
+ "criterion",
+ "crossbeam",
+ "fxhash",
+ "globwalk",
+ "ignore",
+ "lazy_static",
+ "log",
+ "rayon",
+ "tempfile",
+ "tracing",
+ "tracing-subscriber",
+ "walkdir",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "redox_syscall",
+ "rustix",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "walkdir"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93"
+
+[[package]]
+name = "web-sys"
+version = "0.3.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.0",
+ "windows_aarch64_msvc 0.48.0",
+ "windows_i686_gnu 0.48.0",
+ "windows_i686_msvc 0.48.0",
+ "windows_x86_64_gnu 0.48.0",
+ "windows_x86_64_gnullvm 0.48.0",
+ "windows_x86_64_msvc 0.48.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
diff --git a/oxide/Cargo.toml b/oxide/Cargo.toml
new file mode 100644
index 000000000..6d2d5a3c5
--- /dev/null
+++ b/oxide/Cargo.toml
@@ -0,0 +1,6 @@
+[workspace]
+resolver = "2"
+members = ["crates/*"]
+
+[profile.release]
+lto = true
diff --git a/oxide/README.md b/oxide/README.md
new file mode 100644
index 000000000..fa0a1e39d
--- /dev/null
+++ b/oxide/README.md
@@ -0,0 +1 @@
+## Tailwind CSS Oxide
diff --git a/oxide/crates/core/Cargo.toml b/oxide/crates/core/Cargo.toml
new file mode 100644
index 000000000..8f9f26432
--- /dev/null
+++ b/oxide/crates/core/Cargo.toml
@@ -0,0 +1,29 @@
+[package]
+name = "tailwindcss-core"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+bstr = "1.0.1"
+globwalk = "0.8.1"
+log = "0.4"
+rayon = "1.5.3"
+fxhash = "0.2.1"
+crossbeam = "0.8.2"
+tracing = { version = "0.1.37", features = [] }
+tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
+walkdir = "2.3.3"
+ignore = "0.4.20"
+lazy_static = "1.4.0"
+
+[dev-dependencies]
+criterion = { version = "0.3", features = ['html_reports'] }
+tempfile = "3.5.0"
+
+[[bench]]
+name = "parse_candidates"
+harness = false
+
+[[bench]]
+name = "scan_files"
+harness = false
diff --git a/oxide/crates/core/benches/fixtures/template-000.html b/oxide/crates/core/benches/fixtures/template-000.html
new file mode 100644
index 000000000..8d9a823f1
--- /dev/null
+++ b/oxide/crates/core/benches/fixtures/template-000.html
@@ -0,0 +1,2203 @@
+
diff --git a/oxide/crates/core/benches/fixtures/template-001.html b/oxide/crates/core/benches/fixtures/template-001.html
new file mode 100644
index 000000000..d05b12e6c
--- /dev/null
+++ b/oxide/crates/core/benches/fixtures/template-001.html
@@ -0,0 +1,4400 @@
+
diff --git a/oxide/crates/core/benches/fixtures/template-002.html b/oxide/crates/core/benches/fixtures/template-002.html
new file mode 100644
index 000000000..de34f32b5
--- /dev/null
+++ b/oxide/crates/core/benches/fixtures/template-002.html
@@ -0,0 +1,9027 @@
+
diff --git a/oxide/crates/core/benches/fixtures/template-003.html b/oxide/crates/core/benches/fixtures/template-003.html
new file mode 100644
index 000000000..e135c0429
--- /dev/null
+++ b/oxide/crates/core/benches/fixtures/template-003.html
@@ -0,0 +1,1797 @@
+
diff --git a/oxide/crates/core/benches/fixtures/template-004.html b/oxide/crates/core/benches/fixtures/template-004.html
new file mode 100644
index 000000000..5305278fe
--- /dev/null
+++ b/oxide/crates/core/benches/fixtures/template-004.html
@@ -0,0 +1,1935 @@
+
diff --git a/oxide/crates/core/benches/fixtures/template-005.html b/oxide/crates/core/benches/fixtures/template-005.html
new file mode 100644
index 000000000..c1a4ce491
--- /dev/null
+++ b/oxide/crates/core/benches/fixtures/template-005.html
@@ -0,0 +1,5 @@
+
diff --git a/oxide/crates/core/benches/fixtures/template-006.html b/oxide/crates/core/benches/fixtures/template-006.html
new file mode 100644
index 000000000..41d8beed6
--- /dev/null
+++ b/oxide/crates/core/benches/fixtures/template-006.html
@@ -0,0 +1,552 @@
+
+
+

+
+ This is text 3d190171-53b3-4393-99ab-9bedfc964141
+
+
diff --git a/oxide/crates/core/benches/fixtures/template-007.html b/oxide/crates/core/benches/fixtures/template-007.html
new file mode 100644
index 000000000..8d80eb929
--- /dev/null
+++ b/oxide/crates/core/benches/fixtures/template-007.html
@@ -0,0 +1,6 @@
+
+
+
diff --git a/oxide/crates/core/benches/fixtures/template-008.html b/oxide/crates/core/benches/fixtures/template-008.html
new file mode 100644
index 000000000..cb2a02f9e
--- /dev/null
+++ b/oxide/crates/core/benches/fixtures/template-008.html
@@ -0,0 +1,6 @@
+
+
This is text cd32f5df-af14-4332-9fd3-0e5589f8d457
+
+ This is link 05289860-74df-49a7-91dd-1a3a0d215bc3
+
+
diff --git a/oxide/crates/core/benches/fixtures/template-009.html b/oxide/crates/core/benches/fixtures/template-009.html
new file mode 100644
index 000000000..7c1c29a35
--- /dev/null
+++ b/oxide/crates/core/benches/fixtures/template-009.html
@@ -0,0 +1,5 @@
+
+
+ This is text bec24672-f081-47d1-8130-cc06506974b5
+
+
diff --git a/oxide/crates/core/benches/fixtures/template-010.html b/oxide/crates/core/benches/fixtures/template-010.html
new file mode 100644
index 000000000..978745d41
--- /dev/null
+++ b/oxide/crates/core/benches/fixtures/template-010.html
@@ -0,0 +1,3348 @@
+
diff --git a/oxide/crates/core/benches/parse_candidates.rs b/oxide/crates/core/benches/parse_candidates.rs
new file mode 100644
index 000000000..12e476864
--- /dev/null
+++ b/oxide/crates/core/benches/parse_candidates.rs
@@ -0,0 +1,46 @@
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+use tailwindcss_core::parser::Extractor;
+
+pub fn criterion_benchmark(c: &mut Criterion) {
+ fn parse(input: &[u8]) {
+ // _ = Extractor::all(black_box(input), ExtractorOptions { preserve_spaces_in_arbitrary: false });
+ Extractor::unique(black_box(input), Default::default());
+ }
+
+ c.bench_function("scan_files (simple)", |b| b.iter(|| parse(b"underline")));
+
+ c.bench_function("scan_files (with variant)", |b| {
+ b.iter(|| parse(b"hover:underline"))
+ });
+
+ c.bench_function("scan_files (with stacked variants)", |b| {
+ b.iter(|| parse(b"focus:hover:underline"))
+ });
+
+ c.bench_function("scan_files (with arbitrary values)", |b| {
+ b.iter(|| parse(b"p-[20px]"))
+ });
+
+ c.bench_function("scan_files (with variant and arbitrary values)", |b| {
+ b.iter(|| parse(b"hover:p-[20px]"))
+ });
+
+ c.bench_function("scan_files (real world)", |b| {
+ b.iter(|| parse(include_bytes!("./fixtures/template-000.html")))
+ });
+
+ let mut group = c.benchmark_group("sample-size-example");
+ group.sample_size(10);
+
+ group.bench_function("scan_files (fast space skipping)", |b| {
+ let count = 10_000;
+ let crazy1 = format!("{}underline", " ".repeat(count));
+ let crazy2 = crazy1.repeat(count);
+ let crazy3 = crazy2.as_bytes();
+
+ b.iter(|| parse(black_box(crazy3)))
+ });
+}
+
+criterion_group!(benches, criterion_benchmark);
+criterion_main!(benches);
diff --git a/oxide/crates/core/benches/scan_files.rs b/oxide/crates/core/benches/scan_files.rs
new file mode 100644
index 000000000..83af3c720
--- /dev/null
+++ b/oxide/crates/core/benches/scan_files.rs
@@ -0,0 +1,63 @@
+use criterion::{criterion_group, criterion_main, Criterion};
+use std::path::PathBuf;
+use tailwindcss_core::{scan_files, ChangedContent, Parsing, IO};
+
+pub fn criterion_benchmark(c: &mut Criterion) {
+ // current_dir will be set to ./crates/core
+ let fixtures_path = std::env::current_dir()
+ .unwrap()
+ .join("benches")
+ .join("fixtures");
+
+ let mut all_files: Vec<(u64, PathBuf)> = std::fs::read_dir(fixtures_path)
+ .unwrap()
+ .filter_map(Result::ok)
+ .map(|dir_entry| dir_entry.path())
+ .filter(|path| path.is_file())
+ .filter(|path| match path.extension() {
+ Some(ext) => ext == "html",
+ _ => false,
+ })
+ .map(|path| (path.metadata().unwrap().len(), path))
+ .collect();
+
+ // Let's sort them first so that we are working with the same files in the same order every
+ // time.
+ all_files.sort_by(|a, b| a.0.cmp(&b.0));
+
+ // Let's work with the first middle X items (so that we can skip outliers and work with more
+ // interesting files)
+ let amount = 300;
+ let mut files: Vec<_> = all_files
+ .iter()
+ .skip((all_files.len() - amount) / 2) // Skip the first X, so that we can use the middle
+ // {amount} files.
+ .take(amount)
+ .map(|(_, path)| path)
+ .collect();
+
+ // Two (or more) files can technically have the exact same size, but the order is random, so
+ // now that we are scoped to the middle X files, let's sort these alphabetically to guarantee
+ // the same order in our benchmarks.
+ files.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
+
+ let changed_content: Vec = files
+ .into_iter()
+ .map(|file| ChangedContent {
+ file: Some(file.to_path_buf()),
+ content: None,
+ })
+ .collect();
+
+ c.bench_function("scan_files", |b| {
+ b.iter(|| {
+ scan_files(
+ changed_content.clone(),
+ Parsing::Parallel as u8 | IO::Parallel as u8,
+ )
+ })
+ });
+}
+
+criterion_group!(benches, criterion_benchmark);
+criterion_main!(benches);
diff --git a/oxide/crates/core/fuzz/.gitignore b/oxide/crates/core/fuzz/.gitignore
new file mode 100644
index 000000000..1a45eee77
--- /dev/null
+++ b/oxide/crates/core/fuzz/.gitignore
@@ -0,0 +1,4 @@
+target
+corpus
+artifacts
+coverage
diff --git a/oxide/crates/core/fuzz/Cargo.lock b/oxide/crates/core/fuzz/Cargo.lock
new file mode 100644
index 000000000..4b87adb77
--- /dev/null
+++ b/oxide/crates/core/fuzz/Cargo.lock
@@ -0,0 +1,531 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "arbitrary"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29d47fbf90d5149a107494b15a7dc8d69b351be2db3bb9691740e88ec17fd880"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bstr"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
+dependencies = [
+ "memchr",
+ "regex-automata 0.4.5",
+ "serde",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "cc"
+version = "1.0.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574"
+dependencies = [
+ "jobserver",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "crossbeam"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-epoch",
+ "crossbeam-queue",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
+
+[[package]]
+name = "either"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "globset"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "log",
+ "regex-automata 0.4.5",
+ "regex-syntax 0.8.2",
+]
+
+[[package]]
+name = "globwalk"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
+dependencies = [
+ "bitflags",
+ "ignore",
+ "walkdir",
+]
+
+[[package]]
+name = "ignore"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
+dependencies = [
+ "crossbeam-deque",
+ "globset",
+ "log",
+ "memchr",
+ "regex-automata 0.4.5",
+ "same-file",
+ "walkdir",
+ "winapi-util",
+]
+
+[[package]]
+name = "jobserver"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
+
+[[package]]
+name = "libfuzzer-sys"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8fff891139ee62800da71b7fd5b508d570b9ad95e614a53c6f453ca08366038"
+dependencies = [
+ "arbitrary",
+ "cc",
+ "once_cell",
+]
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata 0.1.10",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rayon"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "regex"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
+dependencies = [
+ "regex-syntax 0.6.28",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.28",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax 0.8.2",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.196"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.196"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
+
+[[package]]
+name = "syn"
+version = "2.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tailwindcss-core"
+version = "0.1.0"
+dependencies = [
+ "bstr",
+ "crossbeam",
+ "fxhash",
+ "globwalk",
+ "ignore",
+ "lazy_static",
+ "log",
+ "rayon",
+ "tracing",
+ "tracing-subscriber",
+ "walkdir",
+]
+
+[[package]]
+name = "tailwindcss-core-fuzz"
+version = "0.0.0"
+dependencies = [
+ "libfuzzer-sys",
+ "tailwindcss-core",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "walkdir"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/oxide/crates/core/fuzz/Cargo.toml b/oxide/crates/core/fuzz/Cargo.toml
new file mode 100644
index 000000000..221bb40d4
--- /dev/null
+++ b/oxide/crates/core/fuzz/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "tailwindcss-core-fuzz"
+version = "0.0.0"
+publish = false
+edition = "2021"
+
+[package.metadata]
+cargo-fuzz = true
+
+[dependencies]
+libfuzzer-sys = "0.4"
+
+[dependencies.tailwindcss-core]
+path = ".."
+
+# Prevent this from interfering with workspaces
+[workspace]
+members = ["."]
+
+[profile.release]
+debug = 1
+
+[[bin]]
+name = "parsing"
+path = "fuzz_targets/parsing.rs"
+test = false
+doc = false
diff --git a/oxide/crates/core/fuzz/fuzz_targets/parsing.rs b/oxide/crates/core/fuzz/fuzz_targets/parsing.rs
new file mode 100644
index 000000000..ac7a9fd79
--- /dev/null
+++ b/oxide/crates/core/fuzz/fuzz_targets/parsing.rs
@@ -0,0 +1,31 @@
+#![no_main]
+
+use libfuzzer_sys::fuzz_target;
+use std::path::PathBuf;
+use tailwindcss_core::candidate::scan_files;
+use tailwindcss_core::candidate::Candidate;
+use tailwindcss_core::location::Location;
+
+// fuzz_target!(|data: &[u8]| {
+// if let Ok(s) = std::str::from_utf8(data) {
+// let _ = parse_candidate_strings(s, false);
+// }
+// });
+
+fuzz_target!(|data: &[u8]| {
+ if let Ok(s) = std::str::from_utf8(data) {
+ let _ = scan_files(s, false)
+ .into_iter()
+ .map(|(c, _)| {
+ Candidate::new(
+ c,
+ Location {
+ file: PathBuf::new(),
+ start: (0, 1),
+ end: (0, 1),
+ },
+ )
+ })
+ .collect::>();
+ }
+});
diff --git a/oxide/crates/core/src/cache.rs b/oxide/crates/core/src/cache.rs
new file mode 100644
index 000000000..df0918b5f
--- /dev/null
+++ b/oxide/crates/core/src/cache.rs
@@ -0,0 +1,57 @@
+use std::{path::PathBuf, time::SystemTime};
+use std::fs::{self};
+use fxhash::{FxHashMap, FxHashSet};
+
+/// A cache to manage the list of candidates and the last modified time of files
+/// in the project. This is used to avoid recompiling files that haven't changed.
+#[derive(Default)]
+pub struct Cache {
+ mtimes: FxHashMap,
+ candidates: FxHashSet,
+}
+
+impl Cache {
+ pub fn clear(&mut self) {
+ self.mtimes.clear();
+ self.candidates.clear();
+ }
+
+ pub fn add_candidates(&mut self, additional_candidates: Vec) {
+ self.candidates.extend(additional_candidates);
+ }
+
+ pub fn get_candidates(&self) -> Vec {
+ let mut result = vec![];
+ result.extend(self.candidates.iter().cloned());
+ result.sort();
+ result
+ }
+
+ pub fn find_modified_files<'a>(&mut self, paths: &'a Vec) -> Vec<&'a PathBuf> {
+ // Get a list of the files that have been modified since the last time we checked
+ let mut modified: Vec<&PathBuf> = vec![];
+
+ for path in paths {
+ let curr = fs::metadata(path)
+ .and_then(|m| m.modified())
+ .unwrap_or(SystemTime::now());
+
+ let prev = self.mtimes.insert(path.clone(), curr);
+
+ match prev {
+ // Only add the file to the modified list if the mod time has changed
+ Some(prev) if prev != curr => {
+ modified.push(path);
+ },
+
+ // If the file was already in the cache then we don't need to do anything
+ Some(_) => (),
+
+ // If the file didn't exist before then it's been modified
+ None => modified.push(path),
+ }
+ }
+
+ modified
+ }
+}
diff --git a/oxide/crates/core/src/cursor.rs b/oxide/crates/core/src/cursor.rs
new file mode 100644
index 000000000..0c422c6ff
--- /dev/null
+++ b/oxide/crates/core/src/cursor.rs
@@ -0,0 +1,159 @@
+use std::{ascii::escape_default, fmt::Display};
+
+#[derive(Debug, Clone)]
+pub struct Cursor<'a> {
+ // The input we're scanning
+ pub input: &'a [u8],
+
+ // The location of the cursor in the input
+ pub pos: usize,
+
+ /// Is the cursor at the start of the input
+ pub at_start: bool,
+
+ /// Is the cursor at the end of the input
+ pub at_end: bool,
+
+ /// The previously consumed character
+ /// If `at_start` is true, this will be NUL
+ pub prev: u8,
+
+ /// The current character
+ pub curr: u8,
+
+ /// The upcoming character (if any)
+ /// If `at_end` is true, this will be NUL
+ pub next: u8,
+}
+
+impl<'a> Cursor<'a> {
+ pub fn new(input: &'a [u8]) -> Self {
+ let mut cursor = Self {
+ input,
+ pos: 0,
+ at_start: true,
+ at_end: false,
+ prev: 0x00,
+ curr: 0x00,
+ next: 0x00,
+ };
+ cursor.move_to(0);
+ cursor
+ }
+
+ pub fn rewind_by(&mut self, amount: usize) {
+ self.move_to(self.pos.saturating_sub(amount));
+ }
+
+ pub fn advance_by(&mut self, amount: usize) {
+ self.move_to(self.pos.saturating_add(amount));
+ }
+
+ pub fn move_to(&mut self, pos: usize) {
+ let len = self.input.len();
+ let pos = pos.clamp(0, len);
+
+ self.pos = pos;
+ self.at_start = pos == 0;
+ self.at_end = pos + 1 >= len;
+
+ self.prev = if pos > 0 { self.input[pos - 1] } else { 0x00 };
+ self.curr = if pos < len { self.input[pos] } else { 0x00 };
+ self.next = if pos + 1 < len {
+ self.input[pos + 1]
+ } else {
+ 0x00
+ };
+ }
+}
+
+impl<'a> Display for Cursor<'a> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let len = self.input.len().to_string();
+
+ let pos = format!("{: >len_count$}", self.pos, len_count = len.len());
+ write!(f, "{}/{} ", pos, len)?;
+
+ if self.at_start {
+ write!(f, "S ")?;
+ } else if self.at_end {
+ write!(f, "E ")?;
+ } else {
+ write!(f, "M ")?;
+ }
+
+ fn to_str(c: u8) -> String {
+ if c == 0x00 {
+ "NUL".into()
+ } else {
+ format!("{:?}", escape_default(c).to_string())
+ }
+ }
+
+ write!(
+ f,
+ "[{} {} {}]",
+ to_str(self.prev),
+ to_str(self.curr),
+ to_str(self.next)
+ )
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_cursor() {
+ let mut cursor = Cursor::new(b"hello world");
+ assert_eq!(cursor.pos, 0);
+ assert!(cursor.at_start);
+ assert!(!cursor.at_end);
+ assert_eq!(cursor.prev, 0x00);
+ assert_eq!(cursor.curr, b'h');
+ assert_eq!(cursor.next, b'e');
+
+ cursor.advance_by(1);
+ assert_eq!(cursor.pos, 1);
+ assert!(!cursor.at_start);
+ assert!(!cursor.at_end);
+ assert_eq!(cursor.prev, b'h');
+ assert_eq!(cursor.curr, b'e');
+ assert_eq!(cursor.next, b'l');
+
+ // Advancing too far should stop at the end
+ cursor.advance_by(10);
+ assert_eq!(cursor.pos, 11);
+ assert!(!cursor.at_start);
+ assert!(cursor.at_end);
+ assert_eq!(cursor.prev, b'd');
+ assert_eq!(cursor.curr, 0x00);
+ assert_eq!(cursor.next, 0x00);
+
+ // Can't advance past the end
+ cursor.advance_by(1);
+ assert_eq!(cursor.pos, 11);
+ assert!(!cursor.at_start);
+ assert!(cursor.at_end);
+ assert_eq!(cursor.prev, b'd');
+ assert_eq!(cursor.curr, 0x00);
+ assert_eq!(cursor.next, 0x00);
+
+ cursor.rewind_by(1);
+ assert_eq!(cursor.pos, 10);
+ assert!(!cursor.at_start);
+ assert!(cursor.at_end);
+ assert_eq!(cursor.prev, b'l');
+ assert_eq!(cursor.curr, b'd');
+ assert_eq!(cursor.next, 0x00);
+
+ cursor.rewind_by(10);
+ assert_eq!(cursor.pos, 0);
+ assert!(cursor.at_start);
+ assert!(!cursor.at_end);
+ assert_eq!(cursor.prev, 0x00);
+ assert_eq!(cursor.curr, b'h');
+ assert_eq!(cursor.next, b'e');
+ }
+}
diff --git a/oxide/crates/core/src/fast_skip.rs b/oxide/crates/core/src/fast_skip.rs
new file mode 100644
index 000000000..488e61bd6
--- /dev/null
+++ b/oxide/crates/core/src/fast_skip.rs
@@ -0,0 +1,89 @@
+use crate::cursor::Cursor;
+
+const STRIDE: usize = 16;
+type Mask = [bool; STRIDE];
+
+#[inline(always)]
+pub fn fast_skip(cursor: &Cursor) -> Option {
+ // If we don't have enough bytes left to check then bail early
+ if cursor.pos + STRIDE >= cursor.input.len() {
+ return None;
+ }
+
+ if !cursor.curr.is_ascii_whitespace() {
+ return None;
+ }
+
+ let mut offset = 1;
+
+ // SAFETY: We've already checked (indirectly) that this index is valid
+ let remaining = unsafe { cursor.input.get_unchecked(cursor.pos..) };
+
+ // NOTE: This loop uses primitives designed to be auto-vectorized
+ // Do not change this loop without benchmarking the results
+ // And checking the generated assembly using godbolt.org
+ for (i, chunk) in remaining.chunks_exact(STRIDE).enumerate() {
+ let value = load(chunk);
+ let is_whitespace = is_ascii_whitespace(value);
+ let is_all_whitespace = all_true(is_whitespace);
+
+ if is_all_whitespace {
+ offset = (i + 1) * STRIDE;
+ } else {
+ break;
+ }
+ }
+
+ Some(cursor.pos + offset)
+}
+
+#[inline(always)]
+fn load(input: &[u8]) -> [u8; STRIDE] {
+ let mut value = [0u8; STRIDE];
+ value.copy_from_slice(input);
+ value
+}
+
+#[inline(always)]
+fn eq(input: [u8; STRIDE], val: u8) -> Mask {
+ let mut res = [false; STRIDE];
+ for n in 0..STRIDE {
+ res[n] = input[n] == val
+ }
+ res
+}
+
+#[inline(always)]
+fn or(a: [bool; STRIDE], b: [bool; STRIDE]) -> [bool; STRIDE] {
+ let mut res = [false; STRIDE];
+ for n in 0..STRIDE {
+ res[n] = a[n] | b[n];
+ }
+ res
+}
+
+#[inline(always)]
+fn all_true(a: [bool; STRIDE]) -> bool {
+ let mut res = true;
+ for item in a.iter().take(STRIDE) {
+ res &= item;
+ }
+ res
+}
+
+#[inline(always)]
+fn is_ascii_whitespace(value: [u8; STRIDE]) -> [bool; STRIDE] {
+ let whitespace_1 = eq(value, b'\t');
+ let whitespace_2 = eq(value, b'\n');
+ let whitespace_3 = eq(value, b'\x0C');
+ let whitespace_4 = eq(value, b'\r');
+ let whitespace_5 = eq(value, b' ');
+
+ or(
+ or(
+ or(or(whitespace_1, whitespace_2), whitespace_3),
+ whitespace_4,
+ ),
+ whitespace_5,
+ )
+}
diff --git a/oxide/crates/core/src/fixtures/binary-extensions.txt b/oxide/crates/core/src/fixtures/binary-extensions.txt
new file mode 100644
index 000000000..e4e7af7cf
--- /dev/null
+++ b/oxide/crates/core/src/fixtures/binary-extensions.txt
@@ -0,0 +1,264 @@
+3dm
+3ds
+3g2
+3gp
+7z
+DS_Store
+a
+aac
+adp
+ai
+aif
+aiff
+alz
+ape
+apk
+appimage
+ar
+arj
+asf
+au
+avi
+avif
+bak
+baml
+bh
+bin
+bk
+bmp
+btif
+bz2
+bzip2
+cab
+caf
+cgm
+class
+cmx
+cpio
+cr2
+cur
+dat
+dcm
+deb
+dex
+djvu
+dll
+dmg
+dng
+doc
+docm
+docx
+dot
+dotm
+dra
+dsk
+dts
+dtshd
+dvb
+dwg
+dxf
+ecelp4800
+ecelp7470
+ecelp9600
+egg
+eol
+eot
+epub
+exe
+f4v
+fbs
+fh
+fla
+flac
+flatpak
+fli
+flv
+fpx
+fst
+fvt
+g3
+gh
+gif
+graffle
+gz
+gzip
+h261
+h263
+h264
+icns
+ico
+ief
+img
+ipa
+iso
+jar
+jpeg
+jpg
+jpgv
+jpm
+jxr
+key
+ktx
+lha
+lib
+lockb
+lvp
+lz
+lzh
+lzma
+lzo
+m3u
+m4a
+m4v
+mar
+mdi
+mht
+mid
+midi
+mj2
+mka
+mkv
+mmr
+mng
+mobi
+mov
+movie
+mp3
+mp4
+mp4a
+mpeg
+mpg
+mpga
+mxu
+nef
+npx
+numbers
+nupkg
+o
+odp
+ods
+odt
+oga
+ogg
+ogv
+otf
+ott
+pages
+pbm
+pcx
+pdb
+pdf
+pea
+pgm
+pic
+png
+pnm
+pot
+potm
+potx
+ppa
+ppam
+ppm
+pps
+ppsm
+ppsx
+ppt
+pptm
+pptx
+psd
+pya
+pyc
+pyo
+pyv
+qt
+rar
+ras
+raw
+resources
+rgb
+rip
+rlc
+rmf
+rmvb
+rpm
+rtf
+rz
+s3m
+s7z
+scpt
+sgi
+shar
+sil
+sketch
+slk
+smv
+snap
+snk
+so
+sqlite
+sqlite
+sqlite3
+sqlite3
+stl
+sub
+suo
+swf
+tar
+tbz
+tbz2
+tga
+tgz
+thmx
+tif
+tiff
+tlz
+ttc
+ttf
+txz
+udf
+uvh
+uvi
+uvm
+uvp
+uvs
+uvu
+viv
+vob
+war
+wav
+wax
+wbmp
+wdp
+weba
+webm
+webp
+whl
+wim
+wm
+wma
+wmv
+wmx
+woff
+woff2
+wrm
+wvx
+xbm
+xif
+xla
+xlam
+xls
+xlsb
+xlsm
+xlsx
+xlt
+xltm
+xltx
+xm
+xmind
+xpi
+xpm
+xwd
+xz
+z
+zip
+zipx
diff --git a/oxide/crates/core/src/fixtures/ignored-extensions.txt b/oxide/crates/core/src/fixtures/ignored-extensions.txt
new file mode 100644
index 000000000..f147c24fe
--- /dev/null
+++ b/oxide/crates/core/src/fixtures/ignored-extensions.txt
@@ -0,0 +1,6 @@
+css
+less
+lock
+sass
+scss
+styl
diff --git a/oxide/crates/core/src/fixtures/ignored-files.txt b/oxide/crates/core/src/fixtures/ignored-files.txt
new file mode 100644
index 000000000..45d4ced87
--- /dev/null
+++ b/oxide/crates/core/src/fixtures/ignored-files.txt
@@ -0,0 +1,3 @@
+package-lock.json
+pnpm-lock.yaml
+bun.lockb
diff --git a/oxide/crates/core/src/fixtures/template-extensions.txt b/oxide/crates/core/src/fixtures/template-extensions.txt
new file mode 100644
index 000000000..c059eff3a
--- /dev/null
+++ b/oxide/crates/core/src/fixtures/template-extensions.txt
@@ -0,0 +1,57 @@
+# HTML
+html
+pug
+
+# JS
+astro
+cjs
+cts
+jade
+js
+jsx
+mjs
+mts
+svelte
+ts
+tsx
+vue
+
+# Markdown
+md
+mdx
+
+# ASP
+aspx
+razor
+
+# Handlebars
+handlebars
+hbs
+mustache
+
+# PHP
+php
+twig
+
+# Ruby
+erb
+haml
+liquid
+rb
+rhtml
+slim
+
+# Elixir / Phoenix
+eex
+heex
+
+# Nunjucks
+njk
+nunjucks
+
+# Python
+py
+tpl
+
+# Rust
+rs
diff --git a/oxide/crates/core/src/glob.rs b/oxide/crates/core/src/glob.rs
new file mode 100644
index 000000000..6d688f421
--- /dev/null
+++ b/oxide/crates/core/src/glob.rs
@@ -0,0 +1,456 @@
+use std::iter;
+use std::path::{Path, PathBuf};
+
+pub fn fast_glob(
+ base_path: &Path,
+ patterns: &Vec,
+) -> Result, std::io::Error> {
+ Ok(get_fast_patterns(base_path, patterns)
+ .into_iter()
+ .flat_map(|(base_path, patterns)| {
+ globwalk::GlobWalkerBuilder::from_patterns(base_path, &patterns)
+ .follow_links(true)
+ .build()
+ .unwrap()
+ .filter_map(Result::ok)
+ .map(|file| file.path().to_path_buf())
+ }))
+}
+
+/// This function attempts to optimize the glob patterns to improve performance. The problem is
+/// that if you run the following command:
+/// ```sh
+/// tailwind --pwd ./project --content "{pages,components}/**/*.js"
+/// ```
+/// Then the globwalk library will scan every single file and folder in the `./project` folder,
+/// then it will check if the file matches the glob pattern and keep it if it does. This is very
+/// slow, because if you have vendor folders (like node_modules), then this will take a while...
+///
+/// Instead, we will optimize the pattern, and move as many directories as possible to the base
+/// path. This will allow us to scope the globwalk library to only scan the directories that we
+/// care about.
+///
+/// This means, that the following command:
+/// ```sh
+/// tailwind --pwd ./project --content "{pages,components}/**/*.js"
+/// ```
+///
+/// Will now conceptually do this instead behind the scenes:
+/// ```sh
+/// tailwind --pwd ./project/pages --content "**/*.js"
+/// tailwind --pwd ./project/components --content "**/*.js"
+/// ```
+fn get_fast_patterns(base_path: &Path, patterns: &Vec) -> Vec<(PathBuf, Vec)> {
+ let mut optimized_patterns: Vec<(PathBuf, Vec)> = vec![];
+
+ for pattern in patterns {
+ let is_negated = pattern.starts_with('!');
+ let mut pattern = pattern.clone();
+ if is_negated {
+ pattern.remove(0);
+ }
+
+ let mut folders = pattern.split('/').collect::>();
+
+ if folders.len() <= 1 {
+ // No paths we can simplify, so let's use it as-is.
+ optimized_patterns.push((base_path.to_path_buf(), vec![pattern]));
+ } else {
+ // We do have folders because `/` exists. Let's try to simplify the globs!
+ // Safety: We know that the length is greater than 1, so we can safely unwrap.
+ let file_pattern = folders.pop().unwrap();
+ let all_folders = folders.clone();
+ let mut temp_paths = vec![base_path.to_path_buf()];
+
+ let mut bail = false;
+
+ for (i, folder) in folders.into_iter().enumerate() {
+ // There is a wildcard in the folder, so we have to bail now... 😢 But this also
+ // means that we can skip looking at the rest of the folders, so there is at least
+ // this small optimization we can apply!
+ if folder.contains('*') {
+ // Get all the remaining folders, attach the existing file_pattern so that this
+ // can now be the final pattern we use.
+ let mut remaining_folders = all_folders[i..].to_vec();
+ remaining_folders.push(file_pattern);
+
+ let pattern = remaining_folders.join("/");
+ for path in &temp_paths {
+ optimized_patterns.push((path.to_path_buf(), vec![pattern.to_string()]));
+ }
+
+ bail = true;
+ break;
+ }
+
+ // The folder is very likely using an expandable pattern which we can expand!
+ if folder.contains('{') && folder.contains('}') {
+ let branches = expand_braces(folder);
+
+ let existing_paths = temp_paths;
+ temp_paths = branches
+ .iter()
+ .flat_map(|branch| {
+ existing_paths
+ .clone()
+ .into_iter()
+ .map(|path| path.join(branch))
+ .collect::>()
+ })
+ .collect::>();
+ }
+ // The folder should just be a simple folder name without any glob magic. We should
+ // be able to safely add it to the existing paths.
+ else {
+ temp_paths = temp_paths
+ .into_iter()
+ .map(|path| path.join(folder))
+ .collect();
+ }
+ }
+
+ // As long as we didn't bail, we can now add the current expanded patterns to the
+ // optimized patterns.
+ if !bail {
+ for path in &temp_paths {
+ optimized_patterns.push((path.to_path_buf(), vec![file_pattern.to_string()]));
+ }
+ }
+ }
+
+ // Ensure that we re-add all the `!` signs to the patterns.
+ if is_negated {
+ for (_, patterns) in &mut optimized_patterns {
+ for pattern in patterns {
+ pattern.insert(0, '!');
+ }
+ }
+ }
+ }
+
+ optimized_patterns
+}
+
+/// Given this input: a-{b,c}-d-{e,f}
+/// We will get:
+/// [
+/// a-b-d-e
+/// a-b-d-f
+/// a-c-d-e
+/// a-c-d-f
+/// ]
+/// TODO: There is probably a way nicer way of doing this, but this works for now.
+fn expand_braces(input: &str) -> Vec {
+ let mut result: Vec = vec![];
+
+ let mut in_braces = false;
+ let mut last_char: char = '\0';
+
+ let mut current = String::new();
+
+ // Given the input: a-{b,c}-d-{e,f}-g
+ // The template will look like this: ["a-", "-d-", "g"].
+ let mut template: Vec = vec![];
+
+ // The branches will look like this: [["b", "c"], ["e", "f"]].
+ let mut branches: Vec> = vec![];
+
+ for (i, c) in input.char_indices() {
+ let is_escaped = i > 0 && last_char == '\\';
+ last_char = c;
+
+ match c {
+ '{' if !is_escaped => {
+ // Ensure that when a new set of braces is opened, that we at least have 1
+ // template.
+ if template.is_empty() {
+ template.push(String::new());
+ }
+
+ in_braces = true;
+ branches.push(vec![]);
+ template.push(String::new());
+ }
+ '}' if !is_escaped => {
+ in_braces = false;
+ if let Some(last) = branches.last_mut() {
+ last.push(current.clone());
+ }
+ current.clear();
+ }
+ ',' if !is_escaped && in_braces => {
+ if let Some(last) = branches.last_mut() {
+ last.push(current.clone());
+ }
+ current.clear();
+ }
+ _ if in_braces => current.push(c),
+ _ => {
+ if template.is_empty() {
+ template.push(String::new());
+ }
+
+ if let Some(last) = template.last_mut() {
+ last.push(c);
+ }
+ }
+ };
+ }
+
+ // Ensure we have a string that we can start adding information too.
+ if !template.is_empty() && !branches.is_empty() {
+ result.push("".to_string());
+ }
+
+ // Let's try to generate everything!
+ for (i, template) in template.into_iter().enumerate() {
+ // Append current template string to all existing results.
+ result = result.into_iter().map(|x| x + &template).collect();
+
+ // Get the results, and copy it for every single branch.
+ if let Some(branches) = branches.get(i) {
+ result = branches
+ .iter()
+ .flat_map(|branch| {
+ result
+ .clone()
+ .into_iter()
+ .map(|x| x + branch)
+ .collect::>()
+ })
+ .collect::>();
+ }
+ }
+
+ result
+}
+
+#[cfg(test)]
+mod tests {
+ use super::get_fast_patterns;
+ use std::path::PathBuf;
+
+ #[test]
+ fn it_should_keep_globs_that_start_with_file_wildcards_as_is() {
+ let actual = get_fast_patterns(&PathBuf::from("/projects"), &vec!["*.html".to_string()]);
+ let expected = vec![(PathBuf::from("/projects"), vec!["*.html".to_string()])];
+
+ assert_eq!(actual, expected,);
+ }
+
+ #[test]
+ fn it_should_keep_globs_that_start_with_folder_wildcards_as_is() {
+ let actual = get_fast_patterns(&PathBuf::from("/projects"), &vec!["**/*.html".to_string()]);
+ let expected = vec![(PathBuf::from("/projects"), vec!["**/*.html".to_string()])];
+
+ assert_eq!(actual, expected,);
+ }
+
+ #[test]
+ fn it_should_move_the_starting_folder_to_the_path() {
+ let actual = get_fast_patterns(
+ &PathBuf::from("/projects"),
+ &vec!["example/*.html".to_string()],
+ );
+ let expected = vec![(
+ PathBuf::from("/projects/example"),
+ vec!["*.html".to_string()],
+ )];
+
+ assert_eq!(actual, expected,);
+ }
+
+ #[test]
+ fn it_should_move_the_starting_folders_to_the_path() {
+ let actual = get_fast_patterns(
+ &PathBuf::from("/projects"),
+ &vec!["example/other/*.html".to_string()],
+ );
+ let expected = vec![(
+ PathBuf::from("/projects/example/other"),
+ vec!["*.html".to_string()],
+ )];
+
+ assert_eq!(actual, expected,);
+ }
+
+ #[test]
+ fn it_should_branch_expandable_folders() {
+ let actual = get_fast_patterns(
+ &PathBuf::from("/projects"),
+ &vec!["{foo,bar}/*.html".to_string()],
+ );
+ let expected = vec![
+ (PathBuf::from("/projects/foo"), vec!["*.html".to_string()]),
+ (PathBuf::from("/projects/bar"), vec!["*.html".to_string()]),
+ ];
+
+ assert_eq!(actual, expected,);
+ }
+
+ #[test]
+ fn it_should_expand_multiple_expansions_in_the_same_folder() {
+ let actual = get_fast_patterns(
+ &PathBuf::from("/projects"),
+ &vec!["a-{b,c}-d-{e,f}-g/*.html".to_string()],
+ );
+ let expected = vec![
+ (
+ PathBuf::from("/projects/a-b-d-e-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/a-c-d-e-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/a-b-d-f-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/a-c-d-f-g"),
+ vec!["*.html".to_string()],
+ ),
+ ];
+
+ assert_eq!(actual, expected,);
+ }
+
+ #[test]
+ fn multiple_expansions_per_folder_starting_at_the_root() {
+ let actual = get_fast_patterns(
+ &PathBuf::from("/projects"),
+ &vec!["{a,b}-c-{d,e}-f/{b,c}-d-{e,f}-g/*.html".to_string()],
+ );
+ let expected = vec![
+ (
+ PathBuf::from("/projects/a-c-d-f/b-d-e-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/b-c-d-f/b-d-e-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/a-c-e-f/b-d-e-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/b-c-e-f/b-d-e-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/a-c-d-f/c-d-e-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/b-c-d-f/c-d-e-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/a-c-e-f/c-d-e-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/b-c-e-f/c-d-e-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/a-c-d-f/b-d-f-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/b-c-d-f/b-d-f-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/a-c-e-f/b-d-f-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/b-c-e-f/b-d-f-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/a-c-d-f/c-d-f-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/b-c-d-f/c-d-f-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/a-c-e-f/c-d-f-g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/b-c-e-f/c-d-f-g"),
+ vec!["*.html".to_string()],
+ ),
+ ];
+
+ assert_eq!(actual, expected,);
+ }
+
+ #[test]
+ fn it_should_stop_expanding_once_we_hit_a_wildcard() {
+ let actual = get_fast_patterns(
+ &PathBuf::from("/projects"),
+ &vec!["{foo,bar}/example/**/{baz,qux}/*.html".to_string()],
+ );
+ let expected = vec![
+ (
+ PathBuf::from("/projects/foo/example"),
+ vec!["**/{baz,qux}/*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/bar/example"),
+ vec!["**/{baz,qux}/*.html".to_string()],
+ ),
+ ];
+
+ assert_eq!(actual, expected,);
+ }
+
+ #[test]
+ fn it_should_keep_the_negation_symbol_for_all_new_patterns() {
+ let actual = get_fast_patterns(
+ &PathBuf::from("/projects"),
+ &vec!["!{foo,bar}/*.html".to_string()],
+ );
+ let expected = vec![
+ (PathBuf::from("/projects/foo"), vec!["!*.html".to_string()]),
+ (PathBuf::from("/projects/bar"), vec!["!*.html".to_string()]),
+ ];
+
+ assert_eq!(actual, expected,);
+ }
+
+ #[test]
+ fn it_should_expand_a_complex_example() {
+ let actual = get_fast_patterns(
+ &PathBuf::from("/projects"),
+ &vec!["a/{b,c}/d/{e,f}/g/*.html".to_string()],
+ );
+ let expected = vec![
+ (
+ PathBuf::from("/projects/a/b/d/e/g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/a/c/d/e/g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/a/b/d/f/g"),
+ vec!["*.html".to_string()],
+ ),
+ (
+ PathBuf::from("/projects/a/c/d/f/g"),
+ vec!["*.html".to_string()],
+ ),
+ ];
+
+ assert_eq!(actual, expected,);
+ }
+}
diff --git a/oxide/crates/core/src/lib.rs b/oxide/crates/core/src/lib.rs
new file mode 100644
index 000000000..d5ca06be7
--- /dev/null
+++ b/oxide/crates/core/src/lib.rs
@@ -0,0 +1,489 @@
+use crate::parser::Extractor;
+use cache::Cache;
+use fxhash::FxHashSet;
+use ignore::DirEntry;
+use ignore::WalkBuilder;
+use lazy_static::lazy_static;
+use rayon::prelude::*;
+use std::cmp::Ordering;
+use std::path::Path;
+use std::path::PathBuf;
+use std::sync::Mutex;
+use tracing::event;
+use walkdir::WalkDir;
+
+pub mod cache;
+pub mod cursor;
+pub mod fast_skip;
+pub mod glob;
+pub mod parser;
+
+fn init_tracing() {
+ if !*SHOULD_TRACE {
+ return;
+ }
+
+ _ = tracing_subscriber::fmt()
+ .with_max_level(tracing::Level::INFO)
+ .with_span_events(tracing_subscriber::fmt::format::FmtSpan::ACTIVE)
+ .compact()
+ .try_init();
+}
+
+#[derive(Debug, Clone)]
+pub struct ChangedContent {
+ pub file: Option,
+ pub content: Option,
+}
+
+#[derive(Debug, Clone)]
+pub struct ScanOptions {
+ pub base: String,
+ pub globs: bool,
+}
+
+#[derive(Debug, Clone)]
+pub struct ScanResult {
+ pub candidates: Vec,
+ pub files: Vec,
+ pub globs: Vec,
+}
+
+#[derive(Debug, Clone)]
+pub struct GlobEntry {
+ pub base: String,
+ pub glob: String,
+}
+
+pub fn clear_cache() {
+ let mut cache = GLOBAL_CACHE.lock().unwrap();
+ cache.clear();
+}
+
+pub fn scan_dir(opts: ScanOptions) -> ScanResult {
+ init_tracing();
+
+ let root = Path::new(&opts.base);
+
+ let (files, dirs) = resolve_files(root);
+
+ let globs = if opts.globs {
+ resolve_globs(root, dirs)
+ } else {
+ vec![]
+ };
+
+ let mut cache = GLOBAL_CACHE.lock().unwrap();
+
+ let modified_files = cache.find_modified_files(&files);
+
+ let files = files.iter().map(|x| x.display().to_string()).collect();
+
+ if !modified_files.is_empty() {
+ let content: Vec<_> = modified_files
+ .into_iter()
+ .map(|file| ChangedContent {
+ file: Some(file.clone()),
+ content: None,
+ })
+ .collect();
+
+ let candidates = scan_files(content, IO::Parallel as u8 | Parsing::Parallel as u8);
+ cache.add_candidates(candidates);
+ }
+
+ ScanResult {
+ candidates: cache.get_candidates(),
+ files,
+ globs,
+ }
+}
+
+#[tracing::instrument(skip(root))]
+fn resolve_globs(root: &Path, dirs: Vec) -> Vec {
+ let allowed_paths = FxHashSet::from_iter(dirs);
+
+ // A list of directory names where we can't use globs, but we should track each file
+ // individually instead. This is because these directories are often used for both source and
+ // destination files.
+ let mut forced_static_directories = vec![root.join("public")];
+
+ // A list of known extensions + a list of extensions we found in the project.
+ let mut found_extensions = FxHashSet::from_iter(
+ include_str!("fixtures/template-extensions.txt")
+ .trim()
+ .lines()
+ .filter(|x| !x.starts_with('#')) // Drop commented lines
+ .filter(|x| !x.is_empty()) // Drop empty lines
+ .map(|x| x.to_string()),
+ );
+
+ // All root directories.
+ let mut root_directories = FxHashSet::from_iter(vec![root.to_path_buf()]);
+
+ // All directories where we can safely use deeply nested globs to watch all files.
+ // In other comments we refer to these as "deep glob directories" or similar.
+ //
+ // E.g.: `./src/**/*.{html,js}`
+ let mut deep_globable_directories: FxHashSet = FxHashSet::default();
+
+ // All directories where we can only use shallow globs to watch all direct files but not
+ // folders.
+ // In other comments we refer to these as "shallow glob directories" or similar.
+ //
+ // E.g.: `./src/*/*.{html,js}`
+ let mut shallow_globable_directories: FxHashSet = FxHashSet::default();
+
+ // Collect all valid paths from the root. This will already filter out ignored files, unknown
+ // extensions and binary files.
+ let mut it = WalkDir::new(root)
+ // Sorting to make sure that we always see the directories before the files. Also sorting
+ // alphabetically by default.
+ .sort_by(
+ |a, z| match (a.file_type().is_dir(), z.file_type().is_dir()) {
+ (true, false) => Ordering::Less,
+ (false, true) => Ordering::Greater,
+ _ => a.file_name().cmp(z.file_name()),
+ },
+ )
+ .into_iter();
+
+ loop {
+ // We are only interested in valid entries
+ let entry = match it.next() {
+ Some(Ok(entry)) => entry,
+ _ => break,
+ };
+
+ // Ignore known directories that we don't want to traverse into.
+ if entry.file_type().is_dir() && entry.file_name() == ".git" {
+ it.skip_current_dir();
+ continue;
+ }
+
+ if entry.file_type().is_dir() {
+ // If we are in a directory where we know that we can't use any globs, then we have to
+ // track each file individually.
+ if forced_static_directories.contains(&entry.path().to_path_buf()) {
+ forced_static_directories.push(entry.path().to_path_buf());
+ root_directories.insert(entry.path().to_path_buf());
+ continue;
+ }
+
+ // If we are in a directory where the parent is a forced static directory, then this
+ // will become a forced static directory as well.
+ if forced_static_directories.contains(&entry.path().parent().unwrap().to_path_buf()) {
+ forced_static_directories.push(entry.path().to_path_buf());
+ root_directories.insert(entry.path().to_path_buf());
+ continue;
+ }
+
+ // If we are in a directory, and the directory is git ignored, then we don't have to
+ // descent into the directory. However, we have to make sure that we mark the _parent_
+ // directory as a shallow glob directory because using deep globs from any of the
+ // parent directories will include this ignored directory which should not be the case.
+ //
+ // Another important part is that if one of the ignored directories is a deep glob
+ // directory, then all of its parents (until the root) should be marked as shallow glob
+ // directories as well.
+ if !allowed_paths.contains(&entry.path().to_path_buf()) {
+ let mut parent = entry.path().parent();
+ while let Some(parent_path) = parent {
+ // If the parent is already marked as a valid deep glob directory, then we have
+ // to mark it as a shallow glob directory instead, because we won't be able to
+ // use deep globs for this directory anymore.
+ if deep_globable_directories.contains(parent_path) {
+ deep_globable_directories.remove(parent_path);
+ shallow_globable_directories.insert(parent_path.to_path_buf());
+ }
+
+ // If we reached the root, then we can stop.
+ if parent_path == root {
+ break;
+ }
+
+ // Mark the parent directory as a shallow glob directory and continue with its
+ // parent.
+ shallow_globable_directories.insert(parent_path.to_path_buf());
+ parent = parent_path.parent();
+ }
+
+ it.skip_current_dir();
+ continue;
+ }
+
+ // If we are in a directory that is not git ignored, then we can mark this directory as
+ // a valid deep glob directory. This is only necessary if any of its parents aren't
+ // marked as deep glob directories already.
+ let mut found_deep_glob_parent = false;
+ let mut parent = entry.path().parent();
+ while let Some(parent_path) = parent {
+ // If we reached the root, then we can stop.
+ if parent_path == root {
+ break;
+ }
+
+ // If the parent is already marked as a deep glob directory, then we can stop
+ // because this glob will match the current directory already.
+ if deep_globable_directories.contains(parent_path) {
+ found_deep_glob_parent = true;
+ break;
+ }
+
+ parent = parent_path.parent();
+ }
+
+ // If we didn't find a deep glob directory parent, then we can mark this directory as a
+ // deep glob directory (unless it is the root).
+ if !found_deep_glob_parent && entry.path() != root {
+ deep_globable_directories.insert(entry.path().to_path_buf());
+ }
+ }
+
+ // Handle allowed content paths
+ if is_allowed_content_path(entry.path())
+ && allowed_paths.contains(&entry.path().to_path_buf())
+ {
+ let path = entry.path();
+
+ // Collect the extension for future use when building globs.
+ if let Some(extension) = path.extension().and_then(|x| x.to_str()) {
+ found_extensions.insert(extension.to_string());
+ }
+ }
+ }
+
+ let extension_list = found_extensions.into_iter().collect::>().join(",");
+
+ // Build the globs for all globable directories.
+ let shallow_globs = shallow_globable_directories.iter().map(|path| GlobEntry {
+ base: path.display().to_string(),
+ glob: format!("*/*.{{{}}}", extension_list),
+ });
+
+ let deep_globs = deep_globable_directories.iter().map(|path| GlobEntry {
+ base: path.display().to_string(),
+ glob: format!("**/*.{{{}}}", extension_list),
+ });
+
+ shallow_globs.chain(deep_globs).collect::>()
+}
+
+#[tracing::instrument(skip(root))]
+fn resolve_files(root: &Path) -> (Vec, Vec) {
+ let mut files: Vec = vec![];
+ let mut dirs: Vec = vec![];
+
+ for entry in resolve_allowed_paths(root) {
+ let Some(file_type) = entry.file_type() else {
+ continue;
+ };
+
+ if file_type.is_file() {
+ files.push(entry.into_path());
+ } else if file_type.is_dir() {
+ dirs.push(entry.into_path());
+ }
+ }
+
+ (files, dirs)
+}
+
+#[tracing::instrument(skip(root))]
+pub fn resolve_allowed_paths(root: &Path) -> impl Iterator- {
+ WalkBuilder::new(root)
+ .hidden(false)
+ .filter_entry(|entry| match entry.file_type() {
+ Some(file_type) if file_type.is_dir() => match entry.file_name().to_str() {
+ Some(dir) => !IGNORED_CONTENT_DIRS.contains(&dir),
+ None => false,
+ },
+ Some(file_type) if file_type.is_file() || file_type.is_symlink() => {
+ is_allowed_content_path(entry.path())
+ }
+ _ => false,
+ })
+ .build()
+ .filter_map(Result::ok)
+}
+
+lazy_static! {
+ static ref BINARY_EXTENSIONS: Vec<&'static str> =
+ include_str!("fixtures/binary-extensions.txt")
+ .trim()
+ .lines()
+ .collect::>();
+ static ref IGNORED_EXTENSIONS: Vec<&'static str> =
+ include_str!("fixtures/ignored-extensions.txt")
+ .trim()
+ .lines()
+ .collect::>();
+ static ref IGNORED_FILES: Vec<&'static str> = include_str!("fixtures/ignored-files.txt")
+ .trim()
+ .lines()
+ .collect::>();
+ static ref IGNORED_CONTENT_DIRS: Vec<&'static str> = vec![".git"];
+ static ref SHOULD_TRACE: bool = {
+ matches!(std::env::var("DEBUG"), Ok(value) if value.eq("*") || value.eq("1") || value.eq("true") || value.contains("tailwind"))
+ };
+
+ /// Track file modification times and cache candidates. This cache lives for the lifetime of
+ /// the process and simply adds candidates when files are modified. Since candidates aren't
+ /// removed, incremental builds may contain extra candidates.
+ static ref GLOBAL_CACHE: Mutex = {
+ Mutex::new(Cache::default())
+ };
+}
+
+pub fn is_allowed_content_path(path: &Path) -> bool {
+ let path = PathBuf::from(path);
+
+ // Skip known ignored files
+ if path
+ .file_name()
+ .unwrap()
+ .to_str()
+ .map(|s| IGNORED_FILES.contains(&s))
+ .unwrap_or(false)
+ {
+ return false;
+ }
+
+ // Skip known ignored extensions
+ path.extension()
+ .map(|s| s.to_str().unwrap_or_default())
+ .map(|ext| !IGNORED_EXTENSIONS.contains(&ext) && !BINARY_EXTENSIONS.contains(&ext))
+ .unwrap_or(false)
+}
+
+#[derive(Debug)]
+pub enum IO {
+ Sequential = 0b0001,
+ Parallel = 0b0010,
+}
+
+impl From for IO {
+ fn from(item: u8) -> Self {
+ match item & 0b0011 {
+ 0b0001 => IO::Sequential,
+ 0b0010 => IO::Parallel,
+ _ => unimplemented!("Unknown 'IO' strategy"),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub enum Parsing {
+ Sequential = 0b0100,
+ Parallel = 0b1000,
+}
+
+impl From for Parsing {
+ fn from(item: u8) -> Self {
+ match item & 0b1100 {
+ 0b0100 => Parsing::Sequential,
+ 0b1000 => Parsing::Parallel,
+ _ => unimplemented!("Unknown 'Parsing' strategy"),
+ }
+ }
+}
+
+#[tracing::instrument(skip(input, options))]
+pub fn scan_files(input: Vec, options: u8) -> Vec {
+ match (IO::from(options), Parsing::from(options)) {
+ (IO::Sequential, Parsing::Sequential) => parse_all_blobs_sync(read_all_files_sync(input)),
+ (IO::Sequential, Parsing::Parallel) => parse_all_blobs(read_all_files_sync(input)),
+ (IO::Parallel, Parsing::Sequential) => parse_all_blobs_sync(read_all_files(input)),
+ (IO::Parallel, Parsing::Parallel) => parse_all_blobs(read_all_files(input)),
+ }
+}
+
+#[tracing::instrument(skip(changed_content))]
+fn read_all_files(changed_content: Vec) -> Vec> {
+ event!(
+ tracing::Level::INFO,
+ "Reading {:?} file(s)",
+ changed_content.len()
+ );
+
+ changed_content
+ .into_par_iter()
+ .map(|c| match (c.file, c.content) {
+ (Some(file), None) => match std::fs::read(file) {
+ Ok(content) => content,
+ Err(e) => {
+ event!(tracing::Level::ERROR, "Failed to read file: {:?}", e);
+ Default::default()
+ }
+ },
+ (None, Some(content)) => content.into_bytes(),
+ _ => Default::default(),
+ })
+ .collect()
+}
+
+#[tracing::instrument(skip(changed_content))]
+fn read_all_files_sync(changed_content: Vec) -> Vec> {
+ event!(
+ tracing::Level::INFO,
+ "Reading {:?} file(s)",
+ changed_content.len()
+ );
+
+ changed_content
+ .into_iter()
+ .map(|c| match (c.file, c.content) {
+ (Some(file), None) => std::fs::read(file).unwrap(),
+ (None, Some(content)) => content.into_bytes(),
+ _ => Default::default(),
+ })
+ .collect()
+}
+
+#[tracing::instrument(skip(blobs))]
+fn parse_all_blobs(blobs: Vec>) -> Vec {
+ let input: Vec<_> = blobs.iter().map(|blob| &blob[..]).collect();
+ let input = &input[..];
+
+ let mut result: Vec = input
+ .par_iter()
+ .map(|input| Extractor::unique(input, Default::default()))
+ .reduce(Default::default, |mut a, b| {
+ a.extend(b);
+ a
+ })
+ .into_iter()
+ .map(|s| {
+ // SAFETY: When we parsed the candidates, we already guaranteed that the byte slices
+ // are valid, therefore we don't have to re-check here when we want to convert it back
+ // to a string.
+ unsafe { String::from_utf8_unchecked(s.to_vec()) }
+ })
+ .collect();
+ result.sort();
+ result
+}
+
+#[tracing::instrument(skip(blobs))]
+fn parse_all_blobs_sync(blobs: Vec>) -> Vec {
+ let input: Vec<_> = blobs.iter().map(|blob| &blob[..]).collect();
+ let input = &input[..];
+
+ let mut result: Vec = input
+ .iter()
+ .map(|input| Extractor::unique(input, Default::default()))
+ .fold(FxHashSet::default(), |mut a, b| {
+ a.extend(b);
+ a
+ })
+ .into_iter()
+ .map(|s| {
+ // SAFETY: When we parsed the candidates, we already guaranteed that the byte slices
+ // are valid, therefore we don't have to re-check here when we want to convert it back
+ // to a string.
+ unsafe { String::from_utf8_unchecked(s.to_vec()) }
+ })
+ .collect();
+ result.sort();
+ result
+}
diff --git a/oxide/crates/core/src/parser.rs b/oxide/crates/core/src/parser.rs
new file mode 100644
index 000000000..ad612f902
--- /dev/null
+++ b/oxide/crates/core/src/parser.rs
@@ -0,0 +1,1427 @@
+use crate::{cursor::Cursor, fast_skip::fast_skip};
+use bstr::ByteSlice;
+use fxhash::FxHashSet;
+use tracing::trace;
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum ParseAction<'a> {
+ Consume,
+ Skip,
+ RestartAt(usize),
+
+ SingleCandidate(&'a [u8]),
+ MultipleCandidates(Vec<&'a [u8]>),
+ Done,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum Bracketing<'a> {
+ Included(&'a [u8]),
+ Wrapped(&'a [u8]),
+ None,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub struct SplitCandidate<'a> {
+ variant: &'a [u8],
+ utility: &'a [u8],
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum ValidationResult {
+ Invalid,
+ Valid,
+ Restart,
+}
+
+#[derive(Default)]
+pub struct ExtractorOptions {
+ pub preserve_spaces_in_arbitrary: bool,
+}
+
+pub struct Extractor<'a> {
+ opts: ExtractorOptions,
+
+ input: &'a [u8],
+ cursor: Cursor<'a>,
+
+ idx_start: usize,
+ idx_end: usize,
+ idx_last: usize,
+ idx_arbitrary_start: usize,
+
+ in_arbitrary: bool,
+ in_candidate: bool,
+ in_escape: bool,
+
+ discard_next: bool,
+
+ quote_stack: Vec,
+ bracket_stack: Vec,
+}
+
+impl<'a> Extractor<'a> {
+ pub fn all(input: &'a [u8], opts: ExtractorOptions) -> Vec<&'a [u8]> {
+ Self::new(input, opts).flatten().collect()
+ }
+
+ pub fn unique(input: &'a [u8], opts: ExtractorOptions) -> FxHashSet<&'a [u8]> {
+ let mut candidates: FxHashSet<&[u8]> = Default::default();
+ candidates.reserve(100);
+ candidates.extend(Self::new(input, opts).flatten());
+ candidates
+ }
+
+ pub fn unique_ord(input: &'a [u8], opts: ExtractorOptions) -> Vec<&'a [u8]> {
+ // This is an inefficient way to get an ordered, unique
+ // list as a Vec but it is only meant for testing.
+ let mut candidates = Self::all(input, opts);
+ let mut unique_list = FxHashSet::default();
+ unique_list.reserve(candidates.len());
+ candidates.retain(|c| unique_list.insert(*c));
+
+ candidates
+ }
+}
+
+impl<'a> Extractor<'a> {
+ pub fn new(input: &'a [u8], opts: ExtractorOptions) -> Self {
+ Self {
+ opts,
+ input,
+ cursor: Cursor::new(input),
+
+ idx_start: 0,
+ idx_end: 0,
+ idx_arbitrary_start: 0,
+
+ in_arbitrary: false,
+ in_candidate: false,
+ in_escape: false,
+
+ discard_next: false,
+
+ idx_last: input.len(),
+ quote_stack: Vec::with_capacity(8),
+ bracket_stack: Vec::with_capacity(8),
+ }
+ }
+}
+
+/// Helpers
+impl<'a> Extractor<'a> {
+ #[inline(always)]
+ fn in_quotes(&self) -> bool {
+ !self.quote_stack.is_empty()
+ }
+
+ #[inline(always)]
+ fn get_current_candidate(&mut self) -> ParseAction<'a> {
+ if self.discard_next {
+ return ParseAction::Skip;
+ }
+
+ let mut candidate = &self.input[self.idx_start..=self.idx_end];
+
+ // The bracket stack is not empty, which means that we are dealing with unbalanced
+ // brackets.
+ if !self.bracket_stack.is_empty() {
+ return ParseAction::Skip;
+ }
+
+ while !candidate.is_empty() {
+ match Extractor::is_valid_candidate_string(candidate) {
+ ValidationResult::Valid => return ParseAction::SingleCandidate(candidate),
+ ValidationResult::Restart => return ParseAction::RestartAt(self.idx_start + 1),
+ _ => {}
+ }
+
+ match candidate.split_last() {
+ // At this point the candidate is technically invalid, however it can be that it
+ // has a few dangling characters attached to it. For example, think about a
+ // JavaScript object:
+ //
+ // ```js
+ // { underline: true }
+ // ```
+ //
+ // The candidate at this point will be `underline:`, which is invalid. However, we
+ // can assume in this case that the `:` should not be there, and therefore we can
+ // try to slice it off and retry the validation.
+ Some((b':' | b'/' | b'.', head)) => {
+ candidate = head;
+ }
+
+ // It could also be that we have the candidate is nested inside of bracket or quote
+ // pairs. In this case we want to retrieve the inner part and try to validate that
+ // inner part instead. For example, in a JavaScript array:
+ //
+ // ```js
+ // let myClasses = ["underline"]
+ // ```
+ //
+ // The `underline` is nested inside of quotes and in square brackets. Let's try to
+ // get the inner part and validate that instead.
+ _ => match Self::slice_surrounding(candidate) {
+ Some(shorter) if shorter != candidate => {
+ candidate = shorter;
+ }
+ _ => break,
+ },
+ }
+ }
+
+ ParseAction::Consume
+ }
+
+ #[inline(always)]
+ fn split_candidate(candidate: &'a [u8]) -> SplitCandidate {
+ let mut brackets = 0;
+ let mut idx_end = 0;
+
+ for (n, c) in candidate.iter().enumerate() {
+ match c {
+ b'[' => brackets += 1,
+ b']' if brackets > 0 => brackets -= 1,
+ b':' if brackets == 0 => idx_end = n + 1,
+ _ => {}
+ }
+ }
+
+ SplitCandidate {
+ variant: &candidate[0..idx_end],
+ utility: &candidate[idx_end..],
+ }
+ }
+
+ #[inline(always)]
+ fn contains_in_constrained(candidate: &'a [u8], bytes: Vec) -> bool {
+ let mut brackets = 0;
+
+ for c in candidate {
+ match c {
+ b'[' => brackets += 1,
+ b']' if brackets > 0 => brackets -= 1,
+ _ if brackets == 0 && bytes.contains(c) => return true,
+ _ => {}
+ }
+ }
+
+ false
+ }
+
+ #[inline(always)]
+ fn is_valid_candidate_string(candidate: &'a [u8]) -> ValidationResult {
+ // Reject candidates that start with a capital letter
+ if candidate[0].is_ascii_uppercase() {
+ return ValidationResult::Invalid;
+ }
+
+ // Rejects candidates that end with "-" or "_"
+ if candidate.ends_with(b"-") || candidate.ends_with(b"_") {
+ return ValidationResult::Invalid;
+ }
+
+ // Reject candidates that are single camelCase words, e.g.: `useEffect`
+ if candidate.iter().all(|c| c.is_ascii_alphanumeric())
+ && candidate
+ .iter()
+ .any(|c| c.is_ascii_uppercase() || c.is_ascii_digit())
+ {
+ return ValidationResult::Invalid;
+ }
+
+ // Reject candidates that look like SVG path data, e.g.: `m32.368 m7.5`
+ if !candidate.contains(&b'-')
+ && !candidate.contains(&b':')
+ && candidate.iter().any(|c| c == &b'.' || c.is_ascii_digit())
+ {
+ return ValidationResult::Invalid;
+ }
+
+ // Reject candidates that look like version constraints or email addresses, e.g.: `next@latest`, `bob@example.com`
+ if candidate
+ .iter()
+ .all(|c| c.is_ascii_alphanumeric() || c == &b'.' || c == &b'-' || c == &b'@')
+ && candidate[1..].contains(&b'@')
+ {
+ return ValidationResult::Invalid;
+ }
+
+ // Reject candidates that look like URLs
+ if candidate.starts_with(b"http://") || candidate.starts_with(b"https://") {
+ return ValidationResult::Invalid;
+ }
+
+ // Reject candidates that look short markdown links, e.g.: `[https://example.com]`
+ if candidate.starts_with(b"[http://") || candidate.starts_with(b"[https://") {
+ return ValidationResult::Invalid;
+ }
+
+ // Reject candidates that look like imports with path aliases, e.g.: `@/components/button`
+ if candidate.len() > 1 && candidate[1] == b'/' {
+ return ValidationResult::Invalid;
+ }
+
+ // Reject candidates that look like paths, e.g.: `app/assets/stylesheets`
+ if !candidate.contains(&b':') && !candidate.contains(&b'[') {
+ let mut count = 0;
+ for c in candidate {
+ if c == &b'/' {
+ count += 1;
+ }
+ if count > 1 {
+ return ValidationResult::Invalid;
+ }
+ }
+ }
+
+ let split_candidate = Extractor::split_candidate(candidate);
+
+ let mut offset = 0;
+ let mut offset_end = 0;
+ let utility = &split_candidate.utility;
+ let original_utility = &utility;
+
+ // Some special cases that we can ignore while validating
+ if utility.starts_with(b"!-") {
+ offset += 2;
+ } else if utility.starts_with(b"!") || utility.starts_with(b"-") {
+ offset += 1;
+ } else if utility.ends_with(b"!") {
+ offset_end += 1;
+ }
+
+ // These are allowed in arbitrary values and in variants but nowhere else
+ if Extractor::contains_in_constrained(utility, vec![b'<', b'>']) {
+ return ValidationResult::Restart;
+ }
+
+ // It's an arbitrary property
+ if utility.starts_with(b"[")
+ && utility.ends_with(b"]")
+ && (utility.starts_with(b"['")
+ || utility.starts_with(b"[\"")
+ || utility.starts_with(b"[`"))
+ {
+ return ValidationResult::Restart;
+ }
+
+ // Pluck out the part that we are interested in.
+ let utility = &utility[offset..(utility.len() - offset_end)];
+
+ // Validations
+ // We should have _something_
+ if utility.is_empty() {
+ return ValidationResult::Invalid;
+ }
+
+ // = b'0' && utility[0] <= b'9' && !utility.contains(&b':') {
+ return ValidationResult::Invalid;
+ }
+
+ // In case of an arbitrary property, we should have at least this structure: [a:b]
+ if utility.starts_with(b"[") && utility.ends_with(b"]") {
+ // [a:b] is at least 5 characters long
+ if utility.len() < 5 {
+ return ValidationResult::Invalid;
+ }
+
+ // Now that we validated that the candidate is technically fine, let's ensure that it
+ // doesn't start with a `-` because that would make it invalid for arbitrary properties.
+ if original_utility.starts_with(b"-") || original_utility.starts_with(b"!-") {
+ return ValidationResult::Invalid;
+ }
+
+ // Make sure an arbitrary property/value pair is valid, otherwise
+ // we may generate invalid CSS that will cause tools like PostCSS
+ // to crash when trying to parse the generated CSS.
+ if !Self::validate_arbitrary_property(utility) {
+ return ValidationResult::Invalid;
+ }
+
+ // The ':` must be preceded by a-Z0-9 because it represents a property name.
+ let colon = utility.find(":").unwrap();
+
+ if !utility
+ .chars()
+ .nth(colon - 1)
+ .map_or_else(|| false, |c| c.is_ascii_alphanumeric())
+ {
+ return ValidationResult::Invalid;
+ }
+
+ let property = &utility[1..colon];
+
+ // The property must match /^[a-zA-Z-][a-zA-Z0-9-_]+$/
+ if !property[0].is_ascii_alphabetic() && property[0] != b'-' {
+ return ValidationResult::Invalid;
+ }
+
+ if !property
+ .iter()
+ .all(|c| c.is_ascii_alphanumeric() || c == &b'-' || c == &b'_')
+ {
+ return ValidationResult::Invalid;
+ }
+ }
+
+ ValidationResult::Valid
+ }
+
+ /**
+ * Make sure an arbitrary property/value pair is valid, otherwise
+ * PostCSS may crash when trying to parse the generated CSS.
+ *
+ * `input` - the full candidate string, including the brackets
+ */
+ fn validate_arbitrary_property(candidate: &[u8]) -> bool {
+ if !candidate.starts_with(b"[") || !candidate.ends_with(b"]") {
+ return false;
+ }
+ let property = &candidate[1..candidate.len() - 1];
+ let is_custom_property = property.starts_with(b"--");
+ let Some(colon_pos) = property.find(b":") else {
+ return false;
+ };
+ if is_custom_property {
+ return true;
+ }
+
+ let mut stack = vec![];
+ let mut iter = property[colon_pos + 1..].iter();
+ while let Some(c) = iter.next() {
+ match c {
+ // The value portion cannot contain unquoted colons.
+ // E.g. `[foo::bar]` leads to "foo::bar; which errors because of the `:`.
+ b':' | b'{' | b'}' if stack.is_empty() => {
+ return false;
+ }
+
+ b'\'' => {
+ if let Some(b'\'') = stack.last() {
+ _ = stack.pop()
+ } else {
+ stack.push(b'\'')
+ }
+ }
+ b'"' => {
+ if let Some(b'"') = stack.last() {
+ _ = stack.pop()
+ } else {
+ stack.push(b'"')
+ }
+ }
+
+ // Skip escaped characters.
+ b'\\' => {
+ iter.next();
+ }
+
+ _ => {}
+ }
+ }
+
+ true
+ }
+
+ #[inline(always)]
+ fn parse_escaped(&mut self) -> ParseAction<'a> {
+ // If this character is escaped, we don't care about it.
+ // It gets consumed.
+ trace!("Escape::Consume");
+
+ self.in_escape = false;
+
+ ParseAction::Consume
+ }
+
+ #[inline(always)]
+ fn parse_arbitrary(&mut self) -> ParseAction<'a> {
+ // In this we could technically use memchr 6 times (then looped) to find the indexes / bounds of arbitrary valuesq
+ if self.in_escape {
+ return self.parse_escaped();
+ }
+
+ match self.cursor.curr {
+ b'\\' => {
+ // The `\` character is used to escape characters in arbitrary content _and_ to prevent the starting of arbitrary content
+ trace!("Arbitrary::Escape");
+ self.in_escape = true;
+ }
+
+ b'(' => self.bracket_stack.push(self.cursor.curr),
+ b')' => match self.bracket_stack.last() {
+ Some(&b'(') => {
+ self.bracket_stack.pop();
+ }
+
+ // Last bracket is different compared to what we expect, therefore we are not in a
+ // valid arbitrary value.
+ _ if !self.in_quotes() => return ParseAction::Skip,
+
+ // We're probably in quotes or nested brackets, so we keep going
+ _ => {}
+ },
+
+ // Make sure the brackets are balanced
+ b'[' => self.bracket_stack.push(self.cursor.curr),
+ b']' => match self.bracket_stack.last() {
+ // We've ended a nested bracket
+ Some(&b'[') => {
+ self.bracket_stack.pop();
+ }
+
+ // This is the last bracket meaning the end of arbitrary content
+ _ if !self.in_quotes() => {
+ if matches!(self.cursor.next, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9') {
+ return ParseAction::Consume;
+ }
+
+ trace!("Arbitrary::End\t");
+ self.in_arbitrary = false;
+
+ if self.cursor.pos - self.idx_arbitrary_start == 1 {
+ // We have an empty arbitrary value, which is not allowed
+ return ParseAction::Skip;
+ }
+ }
+
+ // We're probably in quotes or nested brackets, so we keep going
+ _ => {}
+ },
+
+ // Arbitrary values sometimes contain quotes
+ // These can "escape" the arbitrary value mode
+ // switching of `[` and `]` characters
+ b'"' | b'\'' | b'`' => match self.quote_stack.last() {
+ Some(&last_quote) if last_quote == self.cursor.curr => {
+ trace!("Quote::End\t");
+ self.quote_stack.pop();
+ }
+ _ => {
+ trace!("Quote::Start\t");
+ self.quote_stack.push(self.cursor.curr);
+ }
+ },
+
+ b' ' if !self.opts.preserve_spaces_in_arbitrary => {
+ trace!("Arbitrary::SkipAndEndEarly\t");
+
+ // Restart the parser ahead of the arbitrary value
+ // It may pick up more candidates
+ return ParseAction::RestartAt(self.idx_arbitrary_start + 1);
+ }
+
+ // Arbitrary values allow any character inside them
+ // Except spaces unless you are in loose mode
+ _ => {
+ trace!("Arbitrary::Consume\t");
+ // No need to move the end index because either the arbitrary value will end properly OR we'll hit invalid characters
+ }
+ }
+
+ ParseAction::Consume
+ }
+
+ #[inline(always)]
+ fn parse_start(&mut self) -> ParseAction<'a> {
+ match self.cursor.curr {
+ // Enter arbitrary value mode
+ b'[' => {
+ trace!("Arbitrary::Start\t");
+ self.in_arbitrary = true;
+ self.idx_arbitrary_start = self.cursor.pos;
+
+ ParseAction::Consume
+ }
+
+ // Allowed first characters.
+ b'@' | b'!' | b'-' | b'<' | b'>' | b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b'*' => {
+ // TODO: A bunch of characters that we currently support but maybe we only want it behind
+ // a flag. E.g.: ` ParseAction::Skip,
+ }
+ }
+
+ #[inline(always)]
+ fn parse_continue(&mut self) -> ParseAction<'a> {
+ match self.cursor.curr {
+ // Enter arbitrary value mode
+ b'[' if matches!(
+ self.cursor.prev,
+ b'@' | b'-' | b' ' | b':' | b'/' | b'!' | b'\0'
+ ) =>
+ {
+ trace!("Arbitrary::Start\t");
+ self.in_arbitrary = true;
+ self.idx_arbitrary_start = self.cursor.pos;
+ }
+
+ // Can't enter arbitrary value mode
+ // This can't be a candidate
+ b'[' => {
+ trace!("Arbitrary::Skip_Start\t");
+
+ return ParseAction::Skip;
+ }
+
+ // A % can only appear at the end of the candidate itself. It can also only be after a
+ // digit 0-9. This covers the following cases:
+ // - from-15%
+ b'%' if self.cursor.prev.is_ascii_digit() => {
+ return match (self.cursor.at_end, self.cursor.next) {
+ // End of string == end of candidate == okay
+ (true, _) => ParseAction::Consume,
+
+ // Looks like the end of a candidate == okay
+ (_, b' ' | b'\'' | b'"' | b'`') => ParseAction::Consume,
+
+ // Otherwise, not a valid character in a candidate
+ _ => ParseAction::Skip,
+ };
+ }
+ b'%' => return ParseAction::Skip,
+
+ // < and > can only be part of a variant and only be the first or last character
+ b'<' | b'>' | b'*' => {
+ // Can only be the first or last character
+ //
+ // E.g.:
+ //
+ // - :underline
+ // ^
+ if self.cursor.pos == self.idx_start || self.cursor.pos == self.idx_last {
+ trace!("Candidate::Consume\t");
+ }
+ // If it is in the middle, it can only be part of a stacked variant
+ // - dark::underline
+ // ^
+ else if self.cursor.prev == b':' || self.cursor.next == b':' {
+ trace!("Candidate::Consume\t");
+ } else {
+ return ParseAction::Skip;
+ }
+ }
+
+ // Allowed characters in the candidate itself
+ // None of these can come after a closing bracket `]`
+ b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' | b'@'
+ if self.cursor.prev != b']' =>
+ {
+ /* TODO: The `b'@'` is necessary for custom separators like _@, maybe we can handle this in a better way... */
+ trace!("Candidate::Consume\t");
+ }
+
+ // A dot (.) can only appear in the candidate itself (not the arbitrary part), if the previous
+ // and next characters are both digits. This covers the following cases:
+ // - p-1.5
+ b'.' if self.cursor.prev.is_ascii_digit() => match self.cursor.next {
+ next if next.is_ascii_digit() => {
+ trace!("Candidate::Consume\t");
+ }
+ _ => return ParseAction::Skip,
+ },
+
+ // Allowed characters in the candidate itself
+ // These MUST NOT appear at the end of the candidate
+ b'/' | b':' if !self.cursor.at_end => {
+ trace!("Candidate::Consume\t");
+ }
+
+ // The important character `!`, is allowed at the end of the candidate
+ b'!' => {
+ trace!("Candidate::Consume\t");
+ }
+
+ _ => return ParseAction::Skip,
+ }
+
+ ParseAction::Consume
+ }
+
+ #[inline(always)]
+ fn can_be_candidate(&mut self) -> bool {
+ self.in_candidate
+ && !self.in_arbitrary
+ && (0..=127).contains(&self.cursor.curr)
+ && (self.idx_start == 0 || self.input[self.idx_start - 1] <= 127)
+ }
+
+ #[inline(always)]
+ fn handle_skip(&mut self) {
+ // In all other cases, we skip characters and reset everything so we can make new candidates
+ trace!("Characters::Skip\t");
+ self.idx_start = self.cursor.pos;
+ self.idx_end = self.cursor.pos;
+ self.in_candidate = false;
+ self.in_arbitrary = false;
+ self.in_escape = false;
+ }
+
+ #[inline(always)]
+ fn parse_char(&mut self) -> ParseAction<'a> {
+ if self.in_arbitrary {
+ self.parse_arbitrary()
+ } else if self.in_candidate {
+ self.parse_continue()
+ } else if self.parse_start() == ParseAction::Consume {
+ self.in_candidate = true;
+ self.idx_start = self.cursor.pos;
+ self.idx_end = self.cursor.pos;
+
+ ParseAction::Consume
+ } else {
+ ParseAction::Skip
+ }
+ }
+
+ #[inline(always)]
+ fn yield_candidate(&mut self) -> ParseAction<'a> {
+ if self.can_be_candidate() {
+ self.get_current_candidate()
+ } else {
+ ParseAction::Consume
+ }
+ }
+
+ #[inline(always)]
+ fn restart(&mut self, pos: usize) {
+ trace!("Parser::Restart\t{}", pos);
+
+ self.idx_start = pos;
+ self.idx_end = pos;
+ self.idx_arbitrary_start = 0;
+
+ self.in_arbitrary = false;
+ self.in_candidate = false;
+ self.in_escape = false;
+
+ self.discard_next = false;
+
+ self.quote_stack.clear();
+ self.bracket_stack.clear();
+ self.cursor.move_to(pos);
+ }
+
+ #[inline(always)]
+ fn without_surrounding(&self) -> Bracketing<'a> {
+ let range = self.idx_start..=self.idx_end;
+ let clipped = &self.input[range];
+
+ Self::slice_surrounding(clipped)
+ .map(Bracketing::Included)
+ .or_else(|| {
+ if self.idx_start == 0 || self.idx_end + 1 == self.idx_last {
+ return None;
+ }
+
+ let range = self.idx_start - 1..=self.idx_end + 1;
+ let clipped = &self.input[range];
+ Self::slice_surrounding(clipped).map(Bracketing::Wrapped)
+ })
+ .unwrap_or(Bracketing::None)
+ }
+
+ #[inline(always)]
+ fn is_balanced(input: &[u8]) -> bool {
+ let mut depth = 0isize;
+
+ for n in input {
+ match n {
+ b'[' | b'{' | b'(' => depth += 1,
+ b']' | b'}' | b')' => depth -= 1,
+ _ => continue,
+ }
+
+ if depth < 0 {
+ return false;
+ }
+ }
+
+ depth == 0
+ }
+
+ #[inline(always)]
+ fn slice_surrounding(input: &[u8]) -> Option<&[u8]> {
+ let mut prev = None;
+ let mut input = input;
+
+ loop {
+ let leading = input.first().unwrap_or(&0x00);
+ let trailing = input.last().unwrap_or(&0x00);
+
+ let needed = matches!(
+ (leading, trailing),
+ (b'(', b')')
+ | (b'{', b'}')
+ | (b'[', b']')
+ | (b'"', b'"')
+ | (b'`', b'`')
+ | (b'\'', b'\'')
+ );
+
+ if needed {
+ prev = Some(input);
+ input = &input[1..input.len() - 1];
+ continue;
+ }
+ if Self::is_balanced(input) && prev.is_some() {
+ return Some(input);
+ }
+ return prev;
+ }
+ }
+
+ #[inline(always)]
+ fn parse_and_yield(&mut self) -> ParseAction<'a> {
+ trace!("Cursor {}", self.cursor);
+
+ // Fast skipping of invalid characters
+ let can_skip_whitespace = false; // if self.opts.preserve_spaces_in_arbitrary { !self.in_arbitrary } else { true };
+ if can_skip_whitespace {
+ if let Some(pos) = fast_skip(&self.cursor) {
+ trace!("FastSkip::Restart\t{}", pos);
+ return ParseAction::RestartAt(pos);
+ }
+ }
+
+ let action = self.parse_char();
+
+ match action {
+ ParseAction::RestartAt(_) => return action,
+ ParseAction::Consume => {
+ self.idx_end = self.cursor.pos;
+
+ // If we're still consuming characters, we keep going
+ // Only exception is if we've hit the end of the input
+ if !self.cursor.at_end {
+ return action;
+ }
+ }
+ _ => {}
+ }
+
+ let action = self.yield_candidate();
+
+ match (&action, self.cursor.curr) {
+ (ParseAction::RestartAt(_), _) => action,
+ (_, 0x00) => ParseAction::Done,
+ (ParseAction::SingleCandidate(candidate), _) => self.generate_slices(candidate),
+ _ => ParseAction::RestartAt(self.cursor.pos + 1),
+ }
+ }
+
+ /// Peek inside `[]`, `{}`, and `()` pairs
+ /// to look for an additional candidate
+ #[inline(always)]
+ fn generate_slices(&mut self, candidate: &'a [u8]) -> ParseAction<'a> {
+ match self.without_surrounding() {
+ Bracketing::None => ParseAction::SingleCandidate(candidate),
+ Bracketing::Included(sliceable) if sliceable == candidate => {
+ ParseAction::SingleCandidate(candidate)
+ }
+ Bracketing::Included(sliceable) | Bracketing::Wrapped(sliceable) => {
+ let parts = vec![candidate, sliceable];
+ let parts = parts
+ .into_iter()
+ .filter(|v| !v.is_empty())
+ .collect::>();
+
+ ParseAction::MultipleCandidates(parts)
+ }
+ }
+ }
+}
+
+impl<'a> Iterator for Extractor<'a> {
+ type Item = Vec<&'a [u8]>;
+
+ fn next(&mut self) -> Option {
+ if self.cursor.at_end {
+ return None;
+ }
+
+ loop {
+ let result = self.parse_and_yield();
+
+ // Cursor control
+ match result {
+ ParseAction::RestartAt(pos) => self.restart(pos),
+ _ => self.cursor.advance_by(1),
+ }
+
+ // Candidate state control
+ match result {
+ ParseAction::SingleCandidate(_) => self.handle_skip(),
+ ParseAction::MultipleCandidates(_) => self.handle_skip(),
+ _ => {}
+ }
+
+ // Iterator results
+ return match result {
+ ParseAction::SingleCandidate(candidate) => Some(vec![candidate]),
+ ParseAction::MultipleCandidates(candidates) => Some(candidates),
+ ParseAction::Done => None,
+ _ => continue,
+ };
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ fn _please_trace() {
+ tracing_subscriber::fmt()
+ .with_max_level(tracing::Level::TRACE)
+ .with_span_events(tracing_subscriber::fmt::format::FmtSpan::ACTIVE)
+ .compact()
+ .init();
+ }
+
+ fn run(input: &str, loose: bool) -> Vec<&str> {
+ Extractor::unique_ord(
+ input.as_bytes(),
+ ExtractorOptions {
+ preserve_spaces_in_arbitrary: loose,
+ },
+ )
+ .into_iter()
+ .map(|s| unsafe { std::str::from_utf8_unchecked(s) })
+ .collect()
+ }
+
+ #[test]
+ fn it_can_parse_simple_candidates() {
+ let candidates = run("underline", false);
+ assert_eq!(candidates, vec!["underline"]);
+ }
+
+ #[test]
+ fn it_can_parse_multiple_simple_utilities() {
+ let candidates = run("font-bold underline", false);
+ assert_eq!(candidates, vec!["font-bold", "underline"]);
+ }
+
+ #[test]
+ fn it_can_parse_simple_candidates_with_variants() {
+ let candidates = run("hover:underline", false);
+ assert_eq!(candidates, vec!["hover:underline"]);
+ }
+
+ #[test]
+ fn it_can_parse_start_variants() {
+ let candidates = run("*:underline", false);
+ assert_eq!(candidates, vec!["*:underline"]);
+
+ let candidates = run("hover:*:underline", false);
+ assert_eq!(candidates, vec!["hover:*:underline"]);
+ }
+
+ #[test]
+ fn it_can_parse_simple_candidates_with_stacked_variants() {
+ let candidates = run("focus:hover:underline", false);
+ assert_eq!(candidates, vec!["focus:hover:underline"]);
+ }
+
+ #[test]
+ fn it_can_parse_utilities_with_arbitrary_values() {
+ let candidates = run("m-[2px]", false);
+ assert_eq!(candidates, vec!["m-[2px]"]);
+ }
+
+ #[test]
+ fn it_throws_away_arbitrary_values_that_are_unbalanced() {
+ let candidates = run("m-[calc(100px*2]", false);
+ assert!(candidates.is_empty());
+ }
+
+ #[test]
+ fn it_can_parse_utilities_with_arbitrary_values_and_variants() {
+ let candidates = run("hover:m-[2px]", false);
+ assert_eq!(candidates, vec!["hover:m-[2px]"]);
+ }
+
+ #[test]
+ fn it_can_parse_arbitrary_variants() {
+ let candidates = run("[@media(min-width:200px)]:underline", false);
+ assert_eq!(candidates, vec!["[@media(min-width:200px)]:underline"]);
+ }
+
+ #[test]
+ fn it_can_parse_matched_variants() {
+ let candidates = run("group-[&:hover]:underline", false);
+ assert_eq!(candidates, vec!["group-[&:hover]:underline"]);
+ }
+
+ #[test]
+ fn it_should_not_keep_spaces() {
+ let candidates = run("bg-[rgba(0, 0, 0)]", false);
+
+ assert_eq!(candidates, vec!["rgba"]);
+ }
+
+ #[test]
+ fn it_should_keep_spaces_in_loose_mode() {
+ let candidates = run("bg-[rgba(0, 0, 0)]", true);
+ assert_eq!(candidates, vec!["bg-[rgba(0, 0, 0)]"]);
+ }
+
+ #[test]
+ fn it_should_keep_important_arbitrary_properties_legacy() {
+ let candidates = run("![foo:bar]", false);
+ assert_eq!(candidates, vec!["![foo:bar]"]);
+ }
+
+ #[test]
+ fn it_should_keep_important_arbitrary_properties() {
+ let candidates = run("[foo:bar]!", false);
+ assert_eq!(candidates, vec!["[foo:bar]!"]);
+ }
+
+ #[test]
+ fn it_should_keep_important_arbitrary_values() {
+ let candidates = run("w-[calc(var(--size)/2)]!", false);
+ assert_eq!(candidates, vec!["w-[calc(var(--size)/2)]!"]);
+ }
+
+ #[test]
+ fn it_should_keep_important_candidates_legacy() {
+ let candidates = run("!w-4", false);
+ assert_eq!(candidates, vec!["!w-4"]);
+ }
+
+ #[test]
+ fn it_should_keep_important_candidates() {
+ let candidates = run("w-4!", false);
+ assert_eq!(candidates, vec!["w-4!"]);
+ }
+
+ #[test]
+ fn it_should_not_allow_for_bogus_candidates() {
+ let candidates = run("[0]", false);
+ assert!(candidates.is_empty());
+
+ let candidates = run("[something]", false);
+ assert_eq!(candidates, vec!["something"]);
+
+ let candidates = run(" [feature(slice_as_chunks)]", false);
+ assert_eq!(candidates, vec!["feature(slice_as_chunks)"]);
+
+ let candidates = run("![feature(slice_as_chunks)]", false);
+ assert!(candidates.is_empty());
+
+ let candidates = run("-[feature(slice_as_chunks)]", false);
+ assert!(candidates.is_empty());
+
+ let candidates = run("!-[feature(slice_as_chunks)]", false);
+ assert!(candidates.is_empty());
+
+ let candidates = run("-[foo:bar]", false);
+ assert!(candidates.is_empty());
+
+ let candidates = run("!-[foo:bar]", false);
+ assert!(candidates.is_empty());
+ }
+
+ #[test]
+ fn it_should_keep_candidates_with_brackets_in_arbitrary_values_inside_quotes() {
+ let candidates = run("content-['hello_[_]_world']", false);
+ assert_eq!(candidates, vec!["content-['hello_[_]_world']"]);
+ }
+
+ #[test]
+ fn it_should_ignore_leading_spaces() {
+ let candidates = run(" backdrop-filter-none", false);
+ assert_eq!(candidates, vec!["backdrop-filter-none"]);
+ }
+
+ #[test]
+ fn it_should_ignore_trailing_spaces() {
+ let candidates = run("backdrop-filter-none ", false);
+ assert_eq!(candidates, vec!["backdrop-filter-none"]);
+ }
+
+ #[test]
+ fn it_should_keep_classes_before_an_ending_newline() {
+ let candidates = run("backdrop-filter-none\n", false);
+ assert_eq!(candidates, vec!["backdrop-filter-none"]);
+ }
+
+ #[test]
+ fn it_should_parse_out_the_correct_classes_from_tailwind_tests() {
+ // From: tests/arbitrary-variants.test.js
+ let candidates = run(
+ r#"
+
+
+
+
+ "#,
+ false,
+ );
+ // TODO: it should not include additional (separate) classes: class, hover:, foo: bar, underline
+ // TODO: Double check the expectations based on above information
+ assert_eq!(
+ candidates,
+ vec![
+ "div",
+ "class",
+ r#"dark:lg:hover:[&>*]:underline"#,
+ r#"[&_.foo\_\_bar]:hover:underline"#,
+ r#"hover:[&_.foo\_\_bar]:underline"#
+ ]
+ );
+ }
+
+ #[test]
+ fn potential_candidates_are_skipped_when_hitting_impossible_characters() {
+ let candidates = run("
A new software update is available. See what’s new in version 2.0.4.
", false);
+ assert_eq!(
+ candidates,
+ vec![
+ "p",
+ "class",
+ "text-sm",
+ "text-blue-700",
+ // "A", // Uppercase first letter is not allowed
+ "new",
+ "software",
+ "update",
+ "is",
+ "available",
+ // "See", // Uppercase first letter is not allowed
+ // "what", // what is dropped because it is followed by the fancy: ’
+ // "s", // s is dropped because it is preceded by the fancy: ’
+ // "new", // Already seen
+ "in",
+ "version",
+ ]
+ );
+ }
+
+ #[test]
+ fn ignores_arbitrary_property_ish_things() {
+ // FIXME: () are only valid in an arbitrary
+ let candidates = run(" [feature(slice_as_chunks)]", false);
+ assert_eq!(candidates, vec!["feature(slice_as_chunks)",]);
+ }
+
+ #[test]
+ fn foo_bar() {
+ // w[…] is not a valid pattern for part of candidate
+ // but @[] is (specifically in the context of a variant)
+
+ let candidates = run("%w[text-[#bada55]]", false);
+ assert_eq!(candidates, vec!["w", "text-[#bada55]"]);
+ }
+
+ #[test]
+ fn crash_001() {
+ let candidates = run("Aҿɿ[~5", false);
+ assert!(candidates.is_empty());
+ }
+
+ #[test]
+ fn crash_002() {
+ let candidates = run("", false);
+ assert!(candidates.is_empty());
+ }
+
+ #[test]
+ fn bad_001() {
+ let candidates = run("[杛杛]/", false);
+ assert_eq!(candidates, vec!["杛杛"]);
+ }
+
+ #[test]
+ fn bad_002() {
+ let candidates = run(r"[\]\\\:[]", false);
+ assert!(candidates.is_empty());
+ }
+
+ #[test]
+ fn bad_003() {
+ // TODO: This seems… wrong
+ let candidates = run(r"[:]", false);
+ assert_eq!(candidates, vec!["", ":"]);
+ }
+
+ #[test]
+ fn classes_in_js_arrays() {
+ let candidates = run(
+ r#"let classes = ['bg-black', 'hover:px-0.5', 'text-[13px]', '[--my-var:1_/_2]', '[.foo_&]:px-[0]', '[.foo_&]:[color:red]']">"#,
+ false,
+ );
+ assert_eq!(
+ candidates,
+ vec![
+ "let",
+ "classes",
+ "bg-black",
+ "hover:px-0.5",
+ "text-[13px]",
+ "[--my-var:1_/_2]",
+ "--my-var:1_/_2",
+ "[.foo_&]:px-[0]",
+ "[.foo_&]:[color:red]",
+ ]
+ );
+ }
+
+ #[test]
+ fn classes_in_js_arrays_without_spaces() {
+ let candidates = run(
+ r#"let classes = ['bg-black','hover:px-0.5','text-[13px]','[--my-var:1_/_2]','[.foo_&]:px-[0]','[.foo_&]:[color:red]']">"#,
+ false,
+ );
+ assert_eq!(
+ candidates,
+ vec![
+ "let",
+ "classes",
+ "bg-black",
+ "hover:px-0.5",
+ "text-[13px]",
+ "[--my-var:1_/_2]",
+ "--my-var:1_/_2",
+ "[.foo_&]:px-[0]",
+ "[.foo_&]:[color:red]",
+ ]
+ );
+ }
+
+ #[test]
+ fn classes_as_object_keys() {
+ let candidates = run(
+ r#""#,
+ false,
+ );
+ assert_eq!(
+ candidates,
+ vec!["div", "underline", "active", "px-1.5", "online"]
+ );
+ }
+
+ #[test]
+ fn multiple_nested_candidates() {
+ let candidates = run(r#"{color:red}"#, false);
+ assert_eq!(candidates, vec!["color:red"]);
+ }
+
+ #[test]
+ fn percent_ended_candidates() {
+ let candidates = run(
+ r#""#,
+ false,
+ );
+ assert_eq!(
+ candidates,
+ vec!["should", "work", "underline", "from-50%", "flex",]
+ );
+ }
+
+ #[test]
+ fn candidate_cannot_start_with_uppercase_character() {
+ let candidates = run(r#""#, false);
+ assert_eq!(candidates, vec!["div", "class", "foo", "baz"]);
+ }
+
+ #[test]
+ fn candidate_cannot_end_with_a_dash() {
+ let candidates = run(r#""#, false);
+ assert_eq!(candidates, vec!["div", "class", "foo", "baz"]);
+ }
+
+ #[test]
+ fn candidate_cannot_end_with_an_underscore() {
+ let candidates = run(r#""#, false);
+ assert_eq!(candidates, vec!["div", "class", "foo", "baz"]);
+ }
+
+ #[test]
+ fn candidate_cannot_be_a_single_camelcase_word() {
+ let candidates = run(r#""#, false);
+ assert_eq!(candidates, vec!["div", "class", "foo", "baz"]);
+ }
+
+ #[test]
+ fn candidate_cannot_be_svg_path_data() {
+ let candidates = run(r#""#, false);
+ assert_eq!(candidates, vec!["path", "d"]);
+ }
+
+ #[test]
+ fn candidate_cannot_be_email_or_version_constraint() {
+ let candidates = run(r#" next@latest"#, false);
+ assert_eq!(candidates, vec!["div", "class", "@container/dialog"]);
+ }
+
+ #[test]
+ fn candidate_cannot_be_a_url() {
+ let candidates = run(
+ r#"Our website is https://example.com or http://example.com if you want a virus"#,
+ false,
+ );
+ assert_eq!(
+ candidates,
+ vec!["website", "is", "com", "or", "if", "you", "want", "a", "virus"]
+ );
+ }
+
+ #[test]
+ fn candidate_cannot_be_a_paths_with_aliases() {
+ let candidates = run(r#"import potato from '@/potato';"#, false);
+ assert_eq!(candidates, vec!["import", "potato", "from"]);
+ }
+
+ #[test]
+ fn candidate_cannot_be_a_path() {
+ let candidates = run(
+ r#"import potato from 'some/path/to/something';
+ import banana from '@/banana';"#,
+ false,
+ );
+ assert_eq!(candidates, vec!["import", "potato", "from", "banana"]);
+ }
+
+ #[test]
+ fn ruby_percent_formatted_strings() {
+ let candidates = run(r#"%w[hover:flex]"#, false);
+ assert_eq!(candidates, vec!["w", "hover:flex"]);
+ }
+
+ #[test]
+ fn urls_in_arbitrary_values_are_ok() {
+ let candidates = run(r#"
"#, false);
+ assert_eq!(
+ candidates,
+ vec!["div", "class", "bg-[url('/img/hero-pattern.svg')]"]
+ );
+ }
+
+ #[test]
+ fn colon_in_arbitrary_property_value() {
+ let candidates = run("[color::] #[test::foo]", false);
+ assert!(candidates
+ .iter()
+ .all(|candidate| !candidate.starts_with('[')));
+ }
+
+ #[test]
+ fn braces_in_arbitrary_property_value() {
+ let candidates = run("[color:${foo}] #[test:{foo}]", false);
+ assert!(candidates
+ .iter()
+ .all(|candidate| !candidate.starts_with('[')));
+ }
+
+ #[test]
+ fn quoted_colon_in_arbitrary_property_value() {
+ let candidates = run("[content:'bar:bar'] [content:\"bar:bar\"]", false);
+ assert!(candidates
+ .iter()
+ .any(|candidate| candidate == &"[content:'bar:bar']"));
+ assert!(candidates
+ .iter()
+ .any(|candidate| candidate == &"[content:\"bar:bar\"]"));
+ }
+
+ #[test]
+ fn quoted_braces_in_arbitrary_property_value() {
+ let candidates = run("[content:'{bar}'] [content:\"{bar}\"]", false);
+ assert!(candidates
+ .iter()
+ .any(|candidate| candidate == &"[content:'{bar}']"));
+ assert!(candidates
+ .iter()
+ .any(|candidate| candidate == &"[content:\"{bar}\"]"));
+ }
+
+ #[test]
+ fn colon_in_custom_property_value() {
+ let candidates = run("[--foo:bar:bar]", false);
+ assert!(candidates
+ .iter()
+ .any(|candidate| candidate == &"[--foo:bar:bar]"));
+ }
+
+ #[test]
+ fn braces_in_custom_property_value() {
+ let candidates = run("[--foo:{bar}]", false);
+ assert!(candidates
+ .iter()
+ .any(|candidate| candidate == &"[--foo:{bar}]"));
+ }
+
+ #[test]
+ fn candidate_slicing() {
+ let result = Extractor::slice_surrounding(&b".foo_&]:px-[0"[..])
+ .map(std::str::from_utf8)
+ .transpose()
+ .unwrap();
+ assert_eq!(result, None);
+
+ let result = Extractor::slice_surrounding(&b"[.foo_&]:px-[0]"[..])
+ .map(std::str::from_utf8)
+ .transpose()
+ .unwrap();
+ assert_eq!(result, Some("[.foo_&]:px-[0]"));
+
+ let result = Extractor::slice_surrounding(&b"{[.foo_&]:px-[0]}"[..])
+ .map(std::str::from_utf8)
+ .transpose()
+ .unwrap();
+ assert_eq!(result, Some("[.foo_&]:px-[0]"));
+
+ let result = Extractor::slice_surrounding(&b"![foo:bar]"[..])
+ .map(std::str::from_utf8)
+ .transpose()
+ .unwrap();
+ assert_eq!(result, None);
+
+ let result = Extractor::slice_surrounding(&b"[\"pt-1.5\"]"[..])
+ .map(std::str::from_utf8)
+ .transpose()
+ .unwrap();
+ assert_eq!(result, Some("pt-1.5"));
+
+ let count = 1_000;
+ let crazy = format!("{}[.foo_&]:px-[0]{}", "[".repeat(count), "]".repeat(count));
+
+ let result = Extractor::slice_surrounding(crazy.as_bytes())
+ .map(std::str::from_utf8)
+ .transpose()
+ .unwrap();
+ assert_eq!(result, Some("[.foo_&]:px-[0]"));
+ }
+}
diff --git a/oxide/crates/core/tests/auto_content.rs b/oxide/crates/core/tests/auto_content.rs
new file mode 100644
index 000000000..6c0217079
--- /dev/null
+++ b/oxide/crates/core/tests/auto_content.rs
@@ -0,0 +1,305 @@
+#[cfg(test)]
+mod auto_content {
+ use std::fs;
+ use std::process::Command;
+
+ use tailwindcss_core::*;
+ use tempfile::tempdir;
+
+ fn scan(paths_with_content: &[(&str, Option<&str>)]) -> (Vec
, Vec) {
+ // Create a temporary working directory
+ let dir = tempdir().unwrap().into_path();
+
+ // Initialize this directory as a git repository
+ let _ = Command::new("git").arg("init").current_dir(&dir).output();
+
+ // Create the necessary files
+ for (path, contents) in paths_with_content {
+ let path = dir.join(path);
+ let parent = path.parent().unwrap();
+ if !parent.exists() {
+ fs::create_dir_all(parent).unwrap();
+ }
+
+ match contents {
+ Some(contents) => fs::write(path, contents).unwrap(),
+ None => fs::write(path, "").unwrap(),
+ }
+ }
+
+ let base = format!("{}", dir.display());
+
+ // Resolve all content paths for the (temporary) current working directory
+ let result = scan_dir(ScanOptions {
+ base: base.clone(),
+ globs: true,
+ });
+
+ let mut paths: Vec<_> = result
+ .files
+ .into_iter()
+ .map(|x| x.replace(&format!("{}/", &base), ""))
+ .collect();
+
+ for glob in result.globs {
+ paths.push(format!("{}/{}", glob.base, glob.glob));
+ }
+
+ paths = paths
+ .into_iter()
+ .map(|x| {
+ let parent_dir = format!("{}/", &base.to_string());
+ x.replace(&parent_dir, "")
+ })
+ .collect();
+
+ // Sort the output for easier comparison (depending on internal datastructure the order
+ // _could_ be random)
+ paths.sort();
+
+ (paths, result.candidates)
+ }
+
+ fn test(paths_with_content: &[(&str, Option<&str>)]) -> Vec {
+ scan(paths_with_content).0
+ }
+
+ #[test]
+ fn it_should_work_with_a_set_of_root_files() {
+ let globs = test(&[
+ ("index.html", None),
+ ("a.html", None),
+ ("b.html", None),
+ ("c.html", None),
+ ]);
+ assert_eq!(globs, vec!["a.html", "b.html", "c.html", "index.html"]);
+ }
+
+ #[test]
+ fn it_should_work_with_a_set_of_root_files_and_ignore_ignored_files() {
+ let globs = test(&[
+ (".gitignore", Some("b.html")),
+ ("index.html", None),
+ ("a.html", None),
+ ("b.html", None),
+ ("c.html", None),
+ ]);
+ assert_eq!(globs, vec!["a.html", "c.html", "index.html"]);
+ }
+
+ #[test]
+ fn it_should_list_all_files_in_the_public_folder_explicitly() {
+ let globs = test(&[
+ ("index.html", None),
+ ("public/a.html", None),
+ ("public/b.html", None),
+ ("public/c.html", None),
+ ]);
+ assert_eq!(
+ globs,
+ vec![
+ "index.html",
+ "public/a.html",
+ "public/b.html",
+ "public/c.html",
+ ]
+ );
+ }
+
+ #[test]
+ fn it_should_list_nested_folders_explicitly_in_the_public_folder() {
+ let globs = test(&[
+ ("index.html", None),
+ ("public/a.html", None),
+ ("public/b.html", None),
+ ("public/c.html", None),
+ ("public/nested/a.html", None),
+ ("public/nested/b.html", None),
+ ("public/nested/c.html", None),
+ ("public/nested/again/a.html", None),
+ ("public/very/deeply/nested/a.html", None),
+ ]);
+ assert_eq!(
+ globs,
+ vec![
+ "index.html",
+ "public/a.html",
+ "public/b.html",
+ "public/c.html",
+ "public/nested/a.html",
+ "public/nested/again/a.html",
+ "public/nested/b.html",
+ "public/nested/c.html",
+ "public/very/deeply/nested/a.html",
+ ]
+ );
+ }
+
+ #[test]
+ fn it_should_list_all_files_in_the_public_folder_explicitly_except_ignored_files() {
+ let globs = test(&[
+ (".gitignore", Some("public/b.html\na.html")),
+ ("index.html", None),
+ ("public/a.html", None),
+ ("public/b.html", None),
+ ("public/c.html", None),
+ ]);
+ assert_eq!(globs, vec!["index.html", "public/c.html",]);
+ }
+
+ #[test]
+ fn it_should_use_a_glob_for_top_level_folders() {
+ let globs = test(&[
+ ("index.html", None),
+ ("src/a.html", None),
+ ("src/b.html", None),
+ ("src/c.html", None),
+ ]);
+ assert_eq!(globs, vec![
+ "index.html",
+ "src/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
+ "src/a.html",
+ "src/b.html",
+ "src/c.html"
+ ]);
+ }
+
+ #[test]
+ fn it_should_ignore_binary_files() {
+ let globs = test(&[
+ ("index.html", None),
+ ("a.mp4", None),
+ ("b.png", None),
+ ("c.lock", None),
+ ]);
+ assert_eq!(globs, vec!["index.html"]);
+ }
+
+ #[test]
+ fn it_should_ignore_known_extensions() {
+ let globs = test(&[
+ ("index.html", None),
+ ("a.css", None),
+ ("b.sass", None),
+ ("c.less", None),
+ ]);
+ assert_eq!(globs, vec!["index.html"]);
+ }
+
+ #[test]
+ fn it_should_ignore_known_files() {
+ let globs = test(&[
+ ("index.html", None),
+ ("package-lock.json", None),
+ ("yarn.lock", None),
+ ]);
+ assert_eq!(globs, vec!["index.html"]);
+ }
+
+ #[test]
+ fn it_should_ignore_and_expand_nested_ignored_folders() {
+ let globs = test(&[
+ // Explicitly listed root files
+ ("foo.html", None),
+ ("bar.html", None),
+ ("baz.html", None),
+ // Nested folder A, using glob
+ ("nested-a/foo.html", None),
+ ("nested-a/bar.html", None),
+ ("nested-a/baz.html", None),
+ // Nested folder B, with deeply nested files, using glob
+ ("nested-b/deeply-nested/foo.html", None),
+ ("nested-b/deeply-nested/bar.html", None),
+ ("nested-b/deeply-nested/baz.html", None),
+ // Nested folder C, with ignored sub-folder
+ ("nested-c/foo.html", None),
+ ("nested-c/bar.html", None),
+ ("nested-c/baz.html", None),
+ // Ignored folder
+ ("nested-c/.gitignore", Some("ignored-folder/")),
+ ("nested-c/ignored-folder/foo.html", None),
+ ("nested-c/ignored-folder/bar.html", None),
+ ("nested-c/ignored-folder/baz.html", None),
+ // Deeply nested, without issues
+ ("nested-c/sibling-folder/foo.html", None),
+ ("nested-c/sibling-folder/bar.html", None),
+ ("nested-c/sibling-folder/baz.html", None),
+ // Nested folder D, with deeply nested ignored folder
+ ("nested-d/foo.html", None),
+ ("nested-d/bar.html", None),
+ ("nested-d/baz.html", None),
+ ("nested-d/.gitignore", Some("deep/")),
+ ("nested-d/very/deeply/nested/deep/foo.html", None),
+ ("nested-d/very/deeply/nested/deep/bar.html", None),
+ ("nested-d/very/deeply/nested/deep/baz.html", None),
+ ("nested-d/very/deeply/nested/foo.html", None),
+ ("nested-d/very/deeply/nested/bar.html", None),
+ ("nested-d/very/deeply/nested/baz.html", None),
+ ("nested-d/very/deeply/nested/directory/foo.html", None),
+ ("nested-d/very/deeply/nested/directory/bar.html", None),
+ ("nested-d/very/deeply/nested/directory/baz.html", None),
+ ("nested-d/very/deeply/nested/directory/again/foo.html", None),
+ ]);
+
+ assert_eq!(
+ globs,
+ vec![
+ "bar.html",
+ "baz.html",
+ "foo.html",
+ "nested-a/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
+ "nested-a/bar.html",
+ "nested-a/baz.html",
+ "nested-a/foo.html",
+ "nested-b/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
+ "nested-b/deeply-nested/bar.html",
+ "nested-b/deeply-nested/baz.html",
+ "nested-b/deeply-nested/foo.html",
+ "nested-c/*/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
+ "nested-c/bar.html",
+ "nested-c/baz.html",
+ "nested-c/foo.html",
+ "nested-c/sibling-folder/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
+ "nested-c/sibling-folder/bar.html",
+ "nested-c/sibling-folder/baz.html",
+ "nested-c/sibling-folder/foo.html",
+ "nested-d/*/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
+ "nested-d/bar.html",
+ "nested-d/baz.html",
+ "nested-d/foo.html",
+ "nested-d/very/*/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
+ "nested-d/very/deeply/*/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
+ "nested-d/very/deeply/nested/*/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
+ "nested-d/very/deeply/nested/bar.html",
+ "nested-d/very/deeply/nested/baz.html",
+ "nested-d/very/deeply/nested/directory/**/*.{py,tpl,js,vue,php,mjs,cts,jsx,tsx,rhtml,slim,handlebars,twig,rs,njk,svelte,liquid,pug,md,ts,heex,mts,astro,nunjucks,rb,eex,haml,cjs,html,hbs,jade,aspx,razor,erb,mustache,mdx}",
+ "nested-d/very/deeply/nested/directory/again/foo.html",
+ "nested-d/very/deeply/nested/directory/bar.html",
+ "nested-d/very/deeply/nested/directory/baz.html",
+ "nested-d/very/deeply/nested/directory/foo.html",
+ "nested-d/very/deeply/nested/foo.html"
+ ]
+ );
+ }
+
+ #[test]
+ fn it_should_scan_for_utilities() {
+ let mut ignores = String::new();
+ ignores.push_str("# md:font-bold\n");
+ ignores.push_str("foo.html\n");
+
+ let candidates = scan(&[
+ // The gitignore file is used to filter out files but not scanned for candidates
+ (".gitignore", Some(&ignores)),
+ // A file that should definitely be scanned
+ ("index.html", Some("font-bold md:flex")),
+ // A file that should definitely not be scanned
+ ("foo.jpg", Some("xl:font-bold")),
+ // A file that is ignored
+ ("foo.html", Some("lg:font-bold")),
+ ])
+ .1;
+
+ assert_eq!(candidates, vec!["font-bold", "md:flex"]);
+ }
+}
diff --git a/oxide/crates/node/.cargo/config.toml b/oxide/crates/node/.cargo/config.toml
new file mode 100644
index 000000000..6890aa910
--- /dev/null
+++ b/oxide/crates/node/.cargo/config.toml
@@ -0,0 +1,7 @@
+[target.aarch64-unknown-linux-gnu]
+linker = "aarch64-linux-gnu-gcc"
+[target.aarch64-unknown-linux-musl]
+linker = "aarch64-linux-musl-gcc"
+rustflags = ["-C", "target-feature=-crt-static"]
+[target.armv7-unknown-linux-gnueabihf]
+linker = "arm-linux-gnueabihf-gcc"
\ No newline at end of file
diff --git a/oxide/crates/node/.gitignore b/oxide/crates/node/.gitignore
new file mode 100644
index 000000000..2cb3cf980
--- /dev/null
+++ b/oxide/crates/node/.gitignore
@@ -0,0 +1,201 @@
+# Created by https://www.toptal.com/developers/gitignore/api/node
+# Edit at https://www.toptal.com/developers/gitignore?templates=node
+
+### Node ###
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# Next.js build output
+.next
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# End of https://www.toptal.com/developers/gitignore/api/node
+
+# Created by https://www.toptal.com/developers/gitignore/api/macos
+# Edit at https://www.toptal.com/developers/gitignore?templates=macos
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### macOS Patch ###
+# iCloud generated files
+*.icloud
+
+# End of https://www.toptal.com/developers/gitignore/api/macos
+
+# Created by https://www.toptal.com/developers/gitignore/api/windows
+# Edit at https://www.toptal.com/developers/gitignore?templates=windows
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.toptal.com/developers/gitignore/api/windows
+
+#Added by cargo
+
+/target
+Cargo.lock
+
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/sdks
+!.yarn/versions
+
+*.node
+
+# Generated
+index.d.ts
+index.js
diff --git a/oxide/crates/node/.npmignore b/oxide/crates/node/.npmignore
new file mode 100644
index 000000000..ec144db2a
--- /dev/null
+++ b/oxide/crates/node/.npmignore
@@ -0,0 +1,13 @@
+target
+Cargo.lock
+.cargo
+.github
+npm
+.eslintrc
+.prettierignore
+rustfmt.toml
+yarn.lock
+*.node
+.yarn
+__test__
+renovate.json
diff --git a/oxide/crates/node/Cargo.toml b/oxide/crates/node/Cargo.toml
new file mode 100644
index 000000000..0ded64922
--- /dev/null
+++ b/oxide/crates/node/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+edition = "2021"
+name = "tailwind-oxide"
+version = "0.0.0"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
+napi = { version = "2.13.1", default-features = false, features = ["napi4"] }
+napi-derive = "2.13.0"
+tailwindcss-core = { path = "../core" }
+rayon = "1.5.3"
+
+[build-dependencies]
+napi-build = "2.0.1"
+
diff --git a/oxide/crates/node/build.rs b/oxide/crates/node/build.rs
new file mode 100644
index 000000000..1f866b6a3
--- /dev/null
+++ b/oxide/crates/node/build.rs
@@ -0,0 +1,5 @@
+extern crate napi_build;
+
+fn main() {
+ napi_build::setup();
+}
diff --git a/oxide/crates/node/npm/darwin-arm64/README.md b/oxide/crates/node/npm/darwin-arm64/README.md
new file mode 100644
index 000000000..1ab04aa37
--- /dev/null
+++ b/oxide/crates/node/npm/darwin-arm64/README.md
@@ -0,0 +1,3 @@
+# `@tailwindcss/oxide-darwin-arm64`
+
+This is the **aarch64-apple-darwin** binary for `@tailwindcss/oxide`
diff --git a/oxide/crates/node/npm/darwin-arm64/package.json b/oxide/crates/node/npm/darwin-arm64/package.json
new file mode 100644
index 000000000..5d81bed72
--- /dev/null
+++ b/oxide/crates/node/npm/darwin-arm64/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@tailwindcss/oxide-darwin-arm64",
+ "version": "0.0.0-oxide.4",
+ "os": [
+ "darwin"
+ ],
+ "cpu": [
+ "arm64"
+ ],
+ "main": "tailwindcss-oxide.darwin-arm64.node",
+ "files": [
+ "tailwindcss-oxide.darwin-arm64.node"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+}
diff --git a/oxide/crates/node/npm/darwin-x64/README.md b/oxide/crates/node/npm/darwin-x64/README.md
new file mode 100644
index 000000000..ad5f78fed
--- /dev/null
+++ b/oxide/crates/node/npm/darwin-x64/README.md
@@ -0,0 +1,3 @@
+# `@tailwindcss/oxide-darwin-x64`
+
+This is the **x86_64-apple-darwin** binary for `@tailwindcss/oxide`
diff --git a/oxide/crates/node/npm/darwin-x64/package.json b/oxide/crates/node/npm/darwin-x64/package.json
new file mode 100644
index 000000000..0a8ba15f8
--- /dev/null
+++ b/oxide/crates/node/npm/darwin-x64/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@tailwindcss/oxide-darwin-x64",
+ "version": "0.0.0-oxide.4",
+ "os": [
+ "darwin"
+ ],
+ "cpu": [
+ "x64"
+ ],
+ "main": "tailwindcss-oxide.darwin-x64.node",
+ "files": [
+ "tailwindcss-oxide.darwin-x64.node"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+}
diff --git a/oxide/crates/node/npm/freebsd-x64/README.md b/oxide/crates/node/npm/freebsd-x64/README.md
new file mode 100644
index 000000000..197c70957
--- /dev/null
+++ b/oxide/crates/node/npm/freebsd-x64/README.md
@@ -0,0 +1,3 @@
+# `@tailwindcss/oxide-freebsd-x64`
+
+This is the **x86_64-unknown-freebsd** binary for `@tailwindcss/oxide`
diff --git a/oxide/crates/node/npm/freebsd-x64/package.json b/oxide/crates/node/npm/freebsd-x64/package.json
new file mode 100644
index 000000000..142a35a0e
--- /dev/null
+++ b/oxide/crates/node/npm/freebsd-x64/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@tailwindcss/oxide-freebsd-x64",
+ "version": "0.0.0-oxide.4",
+ "os": [
+ "freebsd"
+ ],
+ "cpu": [
+ "x64"
+ ],
+ "main": "tailwindcss-oxide.freebsd-x64.node",
+ "files": [
+ "tailwindcss-oxide.freebsd-x64.node"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+}
diff --git a/oxide/crates/node/npm/linux-arm-gnueabihf/README.md b/oxide/crates/node/npm/linux-arm-gnueabihf/README.md
new file mode 100644
index 000000000..70e61fa37
--- /dev/null
+++ b/oxide/crates/node/npm/linux-arm-gnueabihf/README.md
@@ -0,0 +1,3 @@
+# `@tailwindcss/oxide-linux-arm-gnueabihf`
+
+This is the **armv7-unknown-linux-gnueabihf** binary for `@tailwindcss/oxide`
diff --git a/oxide/crates/node/npm/linux-arm-gnueabihf/package.json b/oxide/crates/node/npm/linux-arm-gnueabihf/package.json
new file mode 100644
index 000000000..e5d353fc9
--- /dev/null
+++ b/oxide/crates/node/npm/linux-arm-gnueabihf/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@tailwindcss/oxide-linux-arm-gnueabihf",
+ "version": "0.0.0-oxide.4",
+ "os": [
+ "linux"
+ ],
+ "cpu": [
+ "arm"
+ ],
+ "main": "tailwindcss-oxide.linux-arm-gnueabihf.node",
+ "files": [
+ "tailwindcss-oxide.linux-arm-gnueabihf.node"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+}
diff --git a/oxide/crates/node/npm/linux-arm64-gnu/README.md b/oxide/crates/node/npm/linux-arm64-gnu/README.md
new file mode 100644
index 000000000..ef048da21
--- /dev/null
+++ b/oxide/crates/node/npm/linux-arm64-gnu/README.md
@@ -0,0 +1,3 @@
+# `@tailwindcss/oxide-linux-arm64-gnu`
+
+This is the **aarch64-unknown-linux-gnu** binary for `@tailwindcss/oxide`
diff --git a/oxide/crates/node/npm/linux-arm64-gnu/package.json b/oxide/crates/node/npm/linux-arm64-gnu/package.json
new file mode 100644
index 000000000..25979b199
--- /dev/null
+++ b/oxide/crates/node/npm/linux-arm64-gnu/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "@tailwindcss/oxide-linux-arm64-gnu",
+ "version": "0.0.0-oxide.4",
+ "os": [
+ "linux"
+ ],
+ "cpu": [
+ "arm64"
+ ],
+ "main": "tailwindcss-oxide.linux-arm64-gnu.node",
+ "files": [
+ "tailwindcss-oxide.linux-arm64-gnu.node"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "libc": [
+ "glibc"
+ ]
+}
diff --git a/oxide/crates/node/npm/linux-arm64-musl/README.md b/oxide/crates/node/npm/linux-arm64-musl/README.md
new file mode 100644
index 000000000..ebc7e2b8c
--- /dev/null
+++ b/oxide/crates/node/npm/linux-arm64-musl/README.md
@@ -0,0 +1,3 @@
+# `@tailwindcss/oxide-linux-arm64-musl`
+
+This is the **aarch64-unknown-linux-musl** binary for `@tailwindcss/oxide`
diff --git a/oxide/crates/node/npm/linux-arm64-musl/package.json b/oxide/crates/node/npm/linux-arm64-musl/package.json
new file mode 100644
index 000000000..a34ee1f8a
--- /dev/null
+++ b/oxide/crates/node/npm/linux-arm64-musl/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "@tailwindcss/oxide-linux-arm64-musl",
+ "version": "0.0.0-oxide.4",
+ "os": [
+ "linux"
+ ],
+ "cpu": [
+ "arm64"
+ ],
+ "main": "tailwindcss-oxide.linux-arm64-musl.node",
+ "files": [
+ "tailwindcss-oxide.linux-arm64-musl.node"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "libc": [
+ "musl"
+ ]
+}
diff --git a/oxide/crates/node/npm/linux-x64-gnu/README.md b/oxide/crates/node/npm/linux-x64-gnu/README.md
new file mode 100644
index 000000000..f129c115f
--- /dev/null
+++ b/oxide/crates/node/npm/linux-x64-gnu/README.md
@@ -0,0 +1,3 @@
+# `@tailwindcss/oxide-linux-x64-gnu`
+
+This is the **x86_64-unknown-linux-gnu** binary for `@tailwindcss/oxide`
diff --git a/oxide/crates/node/npm/linux-x64-gnu/package.json b/oxide/crates/node/npm/linux-x64-gnu/package.json
new file mode 100644
index 000000000..d38572a7c
--- /dev/null
+++ b/oxide/crates/node/npm/linux-x64-gnu/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "@tailwindcss/oxide-linux-x64-gnu",
+ "version": "0.0.0-oxide.4",
+ "os": [
+ "linux"
+ ],
+ "cpu": [
+ "x64"
+ ],
+ "main": "tailwindcss-oxide.linux-x64-gnu.node",
+ "files": [
+ "tailwindcss-oxide.linux-x64-gnu.node"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "libc": [
+ "glibc"
+ ]
+}
diff --git a/oxide/crates/node/npm/linux-x64-musl/README.md b/oxide/crates/node/npm/linux-x64-musl/README.md
new file mode 100644
index 000000000..5661d1c91
--- /dev/null
+++ b/oxide/crates/node/npm/linux-x64-musl/README.md
@@ -0,0 +1,3 @@
+# `@tailwindcss/oxide-linux-x64-musl`
+
+This is the **x86_64-unknown-linux-musl** binary for `@tailwindcss/oxide`
diff --git a/oxide/crates/node/npm/linux-x64-musl/package.json b/oxide/crates/node/npm/linux-x64-musl/package.json
new file mode 100644
index 000000000..7ce86b54f
--- /dev/null
+++ b/oxide/crates/node/npm/linux-x64-musl/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "@tailwindcss/oxide-linux-x64-musl",
+ "version": "0.0.0-oxide.4",
+ "os": [
+ "linux"
+ ],
+ "cpu": [
+ "x64"
+ ],
+ "main": "tailwindcss-oxide.linux-x64-musl.node",
+ "files": [
+ "tailwindcss-oxide.linux-x64-musl.node"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "libc": [
+ "musl"
+ ]
+}
diff --git a/oxide/crates/node/npm/win32-x64-msvc/README.md b/oxide/crates/node/npm/win32-x64-msvc/README.md
new file mode 100644
index 000000000..bb1c4ac9e
--- /dev/null
+++ b/oxide/crates/node/npm/win32-x64-msvc/README.md
@@ -0,0 +1,3 @@
+# `@tailwindcss/oxide-win32-x64-msvc`
+
+This is the **x86_64-pc-windows-msvc** binary for `@tailwindcss/oxide`
diff --git a/oxide/crates/node/npm/win32-x64-msvc/package.json b/oxide/crates/node/npm/win32-x64-msvc/package.json
new file mode 100644
index 000000000..1583c7948
--- /dev/null
+++ b/oxide/crates/node/npm/win32-x64-msvc/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@tailwindcss/oxide-win32-x64-msvc",
+ "version": "0.0.0-oxide.4",
+ "os": [
+ "win32"
+ ],
+ "cpu": [
+ "x64"
+ ],
+ "main": "tailwindcss-oxide.win32-x64-msvc.node",
+ "files": [
+ "tailwindcss-oxide.win32-x64-msvc.node"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+}
diff --git a/oxide/crates/node/package.json b/oxide/crates/node/package.json
new file mode 100644
index 000000000..bddcbf987
--- /dev/null
+++ b/oxide/crates/node/package.json
@@ -0,0 +1,49 @@
+{
+ "name": "@tailwindcss/oxide",
+ "version": "0.0.0-oxide.4",
+ "main": "index.js",
+ "types": "index.d.ts",
+ "napi": {
+ "name": "tailwindcss-oxide",
+ "triples": {
+ "additional": [
+ "aarch64-apple-darwin",
+ "aarch64-unknown-linux-gnu",
+ "aarch64-unknown-linux-musl",
+ "armv7-unknown-linux-gnueabihf",
+ "x86_64-unknown-linux-musl",
+ "x86_64-unknown-freebsd",
+ "i686-pc-windows-msvc"
+ ]
+ }
+ },
+ "license": "MIT",
+ "devDependencies": {
+ "@napi-rs/cli": "^2.17.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "files": [
+ "index.js",
+ "index.d.ts"
+ ],
+ "scripts": {
+ "artifacts": "npx napi artifacts",
+ "build": "npx napi build --platform --release --no-const-enum",
+ "dev": "cargo watch --quiet --shell 'npm run build'",
+ "build:debug": "npx napi build --platform --no-const-enum",
+ "version": "npx napi version"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-darwin-arm64": "workspace:*",
+ "@tailwindcss/oxide-darwin-x64": "workspace:*",
+ "@tailwindcss/oxide-freebsd-x64": "workspace:*",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "workspace:*",
+ "@tailwindcss/oxide-linux-arm64-gnu": "workspace:*",
+ "@tailwindcss/oxide-linux-arm64-musl": "workspace:*",
+ "@tailwindcss/oxide-linux-x64-gnu": "workspace:*",
+ "@tailwindcss/oxide-linux-x64-musl": "workspace:*",
+ "@tailwindcss/oxide-win32-x64-msvc": "workspace:*"
+ }
+}
diff --git a/oxide/crates/node/rustfmt.toml b/oxide/crates/node/rustfmt.toml
new file mode 100644
index 000000000..cab5731ed
--- /dev/null
+++ b/oxide/crates/node/rustfmt.toml
@@ -0,0 +1,2 @@
+tab_spaces = 2
+edition = "2021"
diff --git a/oxide/crates/node/src/lib.rs b/oxide/crates/node/src/lib.rs
new file mode 100644
index 000000000..2658cdb22
--- /dev/null
+++ b/oxide/crates/node/src/lib.rs
@@ -0,0 +1,89 @@
+use napi::bindgen_prelude::{FromNapiValue, ToNapiValue};
+use std::path::PathBuf;
+
+#[macro_use]
+extern crate napi_derive;
+
+#[derive(Debug, Clone)]
+#[napi(object)]
+pub struct ChangedContent {
+ pub file: Option,
+ pub content: Option,
+ pub extension: String,
+}
+
+impl From for tailwindcss_core::ChangedContent {
+ fn from(changed_content: ChangedContent) -> Self {
+ tailwindcss_core::ChangedContent {
+ file: changed_content.file.map(PathBuf::from),
+ content: changed_content.content,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+#[napi(object)]
+pub struct ScanResult {
+ pub globs: Vec,
+ pub files: Vec,
+ pub candidates: Vec,
+}
+
+#[derive(Debug, Clone)]
+#[napi(object)]
+pub struct GlobEntry {
+ pub base: String,
+ pub glob: String,
+}
+
+#[derive(Debug, Clone)]
+#[napi(object)]
+pub struct ScanOptions {
+ pub base: String,
+ pub globs: Option,
+}
+
+#[napi]
+pub fn clear_cache() {
+ tailwindcss_core::clear_cache();
+}
+
+#[napi]
+pub fn scan_dir(args: ScanOptions) -> ScanResult {
+ let result = tailwindcss_core::scan_dir(tailwindcss_core::ScanOptions {
+ base: args.base,
+ globs: args.globs.unwrap_or(false),
+ });
+
+ ScanResult {
+ files: result.files,
+ candidates: result.candidates,
+ globs: result
+ .globs
+ .into_iter()
+ .map(|g| GlobEntry {
+ base: g.base,
+ glob: g.glob,
+ })
+ .collect(),
+ }
+}
+
+#[derive(Debug)]
+#[napi]
+pub enum IO {
+ Sequential = 0b0001,
+ Parallel = 0b0010,
+}
+
+#[derive(Debug)]
+#[napi]
+pub enum Parsing {
+ Sequential = 0b0100,
+ Parallel = 0b1000,
+}
+
+#[napi]
+pub fn scan_files(input: Vec, strategy: u8) -> Vec {
+ tailwindcss_core::scan_files(input.into_iter().map(Into::into).collect(), strategy)
+}
diff --git a/oxide/package.json b/oxide/package.json
new file mode 100644
index 000000000..6372db6c0
--- /dev/null
+++ b/oxide/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "tailwindcss-oxide",
+ "private": true,
+ "version": "0.1.0",
+ "workspaces": [
+ "node"
+ ],
+ "scripts": {
+ "test": "cargo test",
+ "install:cargo": "cargo install cargo-watch cargo-fuzz",
+ "build": "cargo build --release",
+ "build:node": "npm --prefix ./crates/node run build",
+ "dev": "cargo watch --clear --quiet -x 'run --quiet'",
+ "dev:node": "cargo watch --clear --quiet --shell 'npm --prefix ./crates/node run build:debug'",
+ "fuzz": "cd ./crates/core; cargo fuzz run parsing; cd -",
+ "bench": "cargo bench",
+ "postbench": "open ./target/criterion/report/index.html"
+ },
+ "license": "MIT"
+}
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..6687e06db
--- /dev/null
+++ b/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "@tailwindcss/root",
+ "private": true,
+ "version": "1.0.0",
+ "prettier": {
+ "semi": false,
+ "singleQuote": true,
+ "printWidth": 100,
+ "plugins": [
+ "prettier-plugin-organize-imports"
+ ],
+ "overrides": [
+ {
+ "files": [
+ "tsconfig.json"
+ ],
+ "options": {
+ "parser": "jsonc"
+ }
+ }
+ ]
+ },
+ "scripts": {
+ "format": "prettier --write .",
+ "lint": "prettier --check . && turbo lint",
+ "build": "turbo build --filter=!./playgrounds/*",
+ "dev": "turbo dev --filter=!./playgrounds/*",
+ "test": "pnpm test --prefix=oxide && vitest run",
+ "test:ui": "pnpm run --filter=tailwindcss test:ui",
+ "tdd": "vitest",
+ "bench": "vitest bench",
+ "version-packages": "node ./scripts/version-packages.mjs",
+ "vite": "pnpm run --filter=vite-playground dev",
+ "nextjs": "pnpm run --filter=nextjs-playground dev"
+ },
+ "license": "MIT",
+ "devDependencies": {
+ "@playwright/test": "^1.41.2",
+ "@types/node": "^20.11.19",
+ "@vitest/coverage-v8": "^1.2.1",
+ "prettier": "^3.2.5",
+ "prettier-plugin-organize-imports": "^3.2.4",
+ "tsup": "^8.0.1",
+ "turbo": "^1.12.4",
+ "typescript": "^5.3.3",
+ "vitest": "^1.1.3"
+ }
+}
diff --git a/packages/@tailwindcss-postcss/package.json b/packages/@tailwindcss-postcss/package.json
new file mode 100644
index 000000000..435f99b56
--- /dev/null
+++ b/packages/@tailwindcss-postcss/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "@tailwindcss/postcss",
+ "version": "0.0.0-oxide.4",
+ "description": "PostCSS plugin for Tailwind CSS, a utility-first CSS framework for rapidly building custom user interfaces",
+ "license": "MIT",
+ "repository": "https://github.com/tailwindlabs/tailwindcss.git",
+ "bugs": "https://github.com/tailwindlabs/tailwindcss/issues",
+ "homepage": "https://tailwindcss.com",
+ "scripts": {
+ "lint": "tsc --noEmit",
+ "build": "tsup-node ./src/index.ts --format cjs --dts --cjsInterop --splitting",
+ "dev": "pnpm run build -- --watch"
+ },
+ "files": [
+ "dist/"
+ ],
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "require": "./dist/index.js"
+ }
+ },
+ "dependencies": {
+ "@tailwindcss/oxide": "workspace:^",
+ "postcss-import": "^16.0.0",
+ "tailwindcss": "workspace:^"
+ },
+ "devDependencies": {
+ "@types/node": "^20.11.17",
+ "@types/postcss-import": "^14.0.3",
+ "postcss": "8.4.24"
+ }
+}
diff --git a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap
new file mode 100644
index 000000000..fd246450b
--- /dev/null
+++ b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap
@@ -0,0 +1,650 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = `
+"@layer theme {
+ :root {
+ --default-transition-duration: .15s;
+ --default-transition-timing-function: var(--transition-timing-function-in-out);
+ --default-font-family: var(--font-family-sans);
+ --default-font-feature-settings: var(--font-family-sans--font-feature-settings);
+ --default-font-variation-settings: var(--font-family-sans--font-variation-settings);
+ --default-mono-font-family: var(--font-family-mono);
+ --default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
+ --default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
+ --breakpoint-sm: 640px;
+ --breakpoint-md: 768px;
+ --breakpoint-lg: 1024px;
+ --breakpoint-xl: 1280px;
+ --breakpoint-2xl: 1536px;
+ --color-black: #000;
+ --color-white: #fff;
+ --color-slate-50: #f8fafc;
+ --color-slate-100: #f1f5f9;
+ --color-slate-200: #e2e8f0;
+ --color-slate-300: #cbd5e1;
+ --color-slate-400: #94a3b8;
+ --color-slate-500: #64748b;
+ --color-slate-600: #475569;
+ --color-slate-700: #334155;
+ --color-slate-800: #1e293b;
+ --color-slate-900: #0f172a;
+ --color-slate-950: #020617;
+ --color-gray-50: #f9fafb;
+ --color-gray-100: #f3f4f6;
+ --color-gray-200: #e5e7eb;
+ --color-gray-300: #d1d5db;
+ --color-gray-400: #9ca3af;
+ --color-gray-500: #6b7280;
+ --color-gray-600: #4b5563;
+ --color-gray-700: #374151;
+ --color-gray-800: #1f2937;
+ --color-gray-900: #111827;
+ --color-gray-950: #030712;
+ --color-zinc-50: #fafafa;
+ --color-zinc-100: #f4f4f5;
+ --color-zinc-200: #e4e4e7;
+ --color-zinc-300: #d4d4d8;
+ --color-zinc-400: #a1a1aa;
+ --color-zinc-500: #71717a;
+ --color-zinc-600: #52525b;
+ --color-zinc-700: #3f3f46;
+ --color-zinc-800: #27272a;
+ --color-zinc-900: #18181b;
+ --color-zinc-950: #09090b;
+ --color-neutral-50: #fafafa;
+ --color-neutral-100: #f5f5f5;
+ --color-neutral-200: #e5e5e5;
+ --color-neutral-300: #d4d4d4;
+ --color-neutral-400: #a3a3a3;
+ --color-neutral-500: #737373;
+ --color-neutral-600: #525252;
+ --color-neutral-700: #404040;
+ --color-neutral-800: #262626;
+ --color-neutral-900: #171717;
+ --color-neutral-950: #0a0a0a;
+ --color-stone-50: #fafaf9;
+ --color-stone-100: #f5f5f4;
+ --color-stone-200: #e7e5e4;
+ --color-stone-300: #d6d3d1;
+ --color-stone-400: #a8a29e;
+ --color-stone-500: #78716c;
+ --color-stone-600: #57534e;
+ --color-stone-700: #44403c;
+ --color-stone-800: #292524;
+ --color-stone-900: #1c1917;
+ --color-stone-950: #0c0a09;
+ --color-red-50: #fef2f2;
+ --color-red-100: #fee2e2;
+ --color-red-200: #fecaca;
+ --color-red-300: #fca5a5;
+ --color-red-400: #f87171;
+ --color-red-500: #ef4444;
+ --color-red-600: #dc2626;
+ --color-red-700: #b91c1c;
+ --color-red-800: #991b1b;
+ --color-red-900: #7f1d1d;
+ --color-red-950: #450a0a;
+ --color-orange-50: #fff7ed;
+ --color-orange-100: #ffedd5;
+ --color-orange-200: #fed7aa;
+ --color-orange-300: #fdba74;
+ --color-orange-400: #fb923c;
+ --color-orange-500: #f97316;
+ --color-orange-600: #ea580c;
+ --color-orange-700: #c2410c;
+ --color-orange-800: #9a3412;
+ --color-orange-900: #7c2d12;
+ --color-orange-950: #431407;
+ --color-amber-50: #fffbeb;
+ --color-amber-100: #fef3c7;
+ --color-amber-200: #fde68a;
+ --color-amber-300: #fcd34d;
+ --color-amber-400: #fbbf24;
+ --color-amber-500: #f59e0b;
+ --color-amber-600: #d97706;
+ --color-amber-700: #b45309;
+ --color-amber-800: #92400e;
+ --color-amber-900: #78350f;
+ --color-amber-950: #451a03;
+ --color-yellow-50: #fefce8;
+ --color-yellow-100: #fef9c3;
+ --color-yellow-200: #fef08a;
+ --color-yellow-300: #fde047;
+ --color-yellow-400: #facc15;
+ --color-yellow-500: #eab308;
+ --color-yellow-600: #ca8a04;
+ --color-yellow-700: #a16207;
+ --color-yellow-800: #854d0e;
+ --color-yellow-900: #713f12;
+ --color-yellow-950: #422006;
+ --color-lime-50: #f7fee7;
+ --color-lime-100: #ecfccb;
+ --color-lime-200: #d9f99d;
+ --color-lime-300: #bef264;
+ --color-lime-400: #a3e635;
+ --color-lime-500: #84cc16;
+ --color-lime-600: #65a30d;
+ --color-lime-700: #4d7c0f;
+ --color-lime-800: #3f6212;
+ --color-lime-900: #365314;
+ --color-lime-950: #1a2e05;
+ --color-green-50: #f0fdf4;
+ --color-green-100: #dcfce7;
+ --color-green-200: #bbf7d0;
+ --color-green-300: #86efac;
+ --color-green-400: #4ade80;
+ --color-green-500: #22c55e;
+ --color-green-600: #16a34a;
+ --color-green-700: #15803d;
+ --color-green-800: #166534;
+ --color-green-900: #14532d;
+ --color-green-950: #052e16;
+ --color-emerald-50: #ecfdf5;
+ --color-emerald-100: #d1fae5;
+ --color-emerald-200: #a7f3d0;
+ --color-emerald-300: #6ee7b7;
+ --color-emerald-400: #34d399;
+ --color-emerald-500: #10b981;
+ --color-emerald-600: #059669;
+ --color-emerald-700: #047857;
+ --color-emerald-800: #065f46;
+ --color-emerald-900: #064e3b;
+ --color-emerald-950: #022c22;
+ --color-teal-50: #f0fdfa;
+ --color-teal-100: #ccfbf1;
+ --color-teal-200: #99f6e4;
+ --color-teal-300: #5eead4;
+ --color-teal-400: #2dd4bf;
+ --color-teal-500: #14b8a6;
+ --color-teal-600: #0d9488;
+ --color-teal-700: #0f766e;
+ --color-teal-800: #115e59;
+ --color-teal-900: #134e4a;
+ --color-teal-950: #042f2e;
+ --color-cyan-50: #ecfeff;
+ --color-cyan-100: #cffafe;
+ --color-cyan-200: #a5f3fc;
+ --color-cyan-300: #67e8f9;
+ --color-cyan-400: #22d3ee;
+ --color-cyan-500: #06b6d4;
+ --color-cyan-600: #0891b2;
+ --color-cyan-700: #0e7490;
+ --color-cyan-800: #155e75;
+ --color-cyan-900: #164e63;
+ --color-cyan-950: #083344;
+ --color-sky-50: #f0f9ff;
+ --color-sky-100: #e0f2fe;
+ --color-sky-200: #bae6fd;
+ --color-sky-300: #7dd3fc;
+ --color-sky-400: #38bdf8;
+ --color-sky-500: #0ea5e9;
+ --color-sky-600: #0284c7;
+ --color-sky-700: #0369a1;
+ --color-sky-800: #075985;
+ --color-sky-900: #0c4a6e;
+ --color-sky-950: #082f49;
+ --color-blue-50: #eff6ff;
+ --color-blue-100: #dbeafe;
+ --color-blue-200: #bfdbfe;
+ --color-blue-300: #93c5fd;
+ --color-blue-400: #60a5fa;
+ --color-blue-500: #3b82f6;
+ --color-blue-600: #2563eb;
+ --color-blue-700: #1d4ed8;
+ --color-blue-800: #1e40af;
+ --color-blue-900: #1e3a8a;
+ --color-blue-950: #172554;
+ --color-indigo-50: #eef2ff;
+ --color-indigo-100: #e0e7ff;
+ --color-indigo-200: #c7d2fe;
+ --color-indigo-300: #a5b4fc;
+ --color-indigo-400: #818cf8;
+ --color-indigo-500: #6366f1;
+ --color-indigo-600: #4f46e5;
+ --color-indigo-700: #4338ca;
+ --color-indigo-800: #3730a3;
+ --color-indigo-900: #312e81;
+ --color-indigo-950: #1e1b4b;
+ --color-violet-50: #f5f3ff;
+ --color-violet-100: #ede9fe;
+ --color-violet-200: #ddd6fe;
+ --color-violet-300: #c4b5fd;
+ --color-violet-400: #a78bfa;
+ --color-violet-500: #8b5cf6;
+ --color-violet-600: #7c3aed;
+ --color-violet-700: #6d28d9;
+ --color-violet-800: #5b21b6;
+ --color-violet-900: #4c1d95;
+ --color-violet-950: #2e1065;
+ --color-purple-50: #faf5ff;
+ --color-purple-100: #f3e8ff;
+ --color-purple-200: #e9d5ff;
+ --color-purple-300: #d8b4fe;
+ --color-purple-400: #c084fc;
+ --color-purple-500: #a855f7;
+ --color-purple-600: #9333ea;
+ --color-purple-700: #7e22ce;
+ --color-purple-800: #6b21a8;
+ --color-purple-900: #581c87;
+ --color-purple-950: #3b0764;
+ --color-fuchsia-50: #fdf4ff;
+ --color-fuchsia-100: #fae8ff;
+ --color-fuchsia-200: #f5d0fe;
+ --color-fuchsia-300: #f0abfc;
+ --color-fuchsia-400: #e879f9;
+ --color-fuchsia-500: #d946ef;
+ --color-fuchsia-600: #c026d3;
+ --color-fuchsia-700: #a21caf;
+ --color-fuchsia-800: #86198f;
+ --color-fuchsia-900: #701a75;
+ --color-fuchsia-950: #4a044e;
+ --color-pink-50: #fdf2f8;
+ --color-pink-100: #fce7f3;
+ --color-pink-200: #fbcfe8;
+ --color-pink-300: #f9a8d4;
+ --color-pink-400: #f472b6;
+ --color-pink-500: #ec4899;
+ --color-pink-600: #db2777;
+ --color-pink-700: #be185d;
+ --color-pink-800: #9d174d;
+ --color-pink-900: #831843;
+ --color-pink-950: #500724;
+ --color-rose-50: #fff1f2;
+ --color-rose-100: #ffe4e6;
+ --color-rose-200: #fecdd3;
+ --color-rose-300: #fda4af;
+ --color-rose-400: #fb7185;
+ --color-rose-500: #f43f5e;
+ --color-rose-600: #e11d48;
+ --color-rose-700: #be123c;
+ --color-rose-800: #9f1239;
+ --color-rose-900: #881337;
+ --color-rose-950: #4c0519;
+ --animate-spin: spin 1s linear infinite;
+ --animate-ping: ping 1s cubic-bezier(0, 0, .2, 1) infinite;
+ --animate-pulse: pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;
+ --animate-bounce: bounce 1s infinite;
+ --blur: 8px;
+ --blur-sm: 4px;
+ --blur-md: 12px;
+ --blur-lg: 16px;
+ --blur-xl: 24px;
+ --blur-2xl: 40px;
+ --blur-3xl: 64px;
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ --radius-md: .375rem;
+ --radius-lg: .5rem;
+ --radius-xl: .75rem;
+ --radius-2xl: 1rem;
+ --radius-3xl: 1.5rem;
+ --shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
+ --shadow-xs: 0 1px #0000000d;
+ --shadow-sm: 0 1px 2px 0 #0000000d;
+ --shadow-md: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
+ --shadow-lg: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
+ --shadow-xl: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a;
+ --shadow-2xl: 0 25px 50px -12px #00000040;
+ --shadow-inner: inset 0 2px 4px 0 #0000000d;
+ --inset-shadow-xs: inset 0 1px #0000000d;
+ --inset-shadow-sm: inset 0 1px 1px #0000000d;
+ --inset-shadow: inset 0 2px 4px #0000000d;
+ --drop-shadow: 0 1px 2px #0000001a, 0 1px 1px #0000000f;
+ --drop-shadow-sm: 0 1px 1px #0000000d;
+ --drop-shadow-md: 0 4px 3px #00000012, 0 2px 2px #0000000f;
+ --drop-shadow-lg: 0 10px 8px #0000000a, 0 4px 3px #0000001a;
+ --drop-shadow-xl: 0 20px 13px #00000008, 0 8px 5px #00000014;
+ --drop-shadow-2xl: 0 25px 25px #00000026;
+ --spacing-px: 1px;
+ --spacing-0: 0px;
+ --spacing-0_5: .125rem;
+ --spacing-1: .25rem;
+ --spacing-1_5: .375rem;
+ --spacing-2: .5rem;
+ --spacing-2_5: .625rem;
+ --spacing-3: .75rem;
+ --spacing-3_5: .875rem;
+ --spacing-4: 1rem;
+ --spacing-5: 1.25rem;
+ --spacing-6: 1.5rem;
+ --spacing-7: 1.75rem;
+ --spacing-8: 2rem;
+ --spacing-9: 2.25rem;
+ --spacing-10: 2.5rem;
+ --spacing-11: 2.75rem;
+ --spacing-12: 3rem;
+ --spacing-14: 3.5rem;
+ --spacing-16: 4rem;
+ --spacing-20: 5rem;
+ --spacing-24: 6rem;
+ --spacing-28: 7rem;
+ --spacing-32: 8rem;
+ --spacing-36: 9rem;
+ --spacing-40: 10rem;
+ --spacing-44: 11rem;
+ --spacing-48: 12rem;
+ --spacing-52: 13rem;
+ --spacing-56: 14rem;
+ --spacing-60: 15rem;
+ --spacing-64: 16rem;
+ --spacing-72: 18rem;
+ --spacing-80: 20rem;
+ --spacing-96: 24rem;
+ --width-3xs: 16rem;
+ --width-2xs: 18rem;
+ --width-xs: 20rem;
+ --width-sm: 24rem;
+ --width-md: 28rem;
+ --width-lg: 32rem;
+ --width-xl: 36rem;
+ --width-2xl: 42rem;
+ --width-3xl: 48rem;
+ --width-4xl: 56rem;
+ --width-5xl: 64rem;
+ --width-6xl: 72rem;
+ --width-7xl: 80rem;
+ --width-prose: 65ch;
+ --font-family-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ --font-family-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
+ --font-family-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ --font-size-xs: .75rem;
+ --font-size-xs--line-height: 1rem;
+ --font-size-sm: .875rem;
+ --font-size-sm--line-height: 1.25rem;
+ --font-size-base: 1rem;
+ --font-size-base--line-height: 1.5rem;
+ --font-size-lg: 1.125rem;
+ --font-size-lg--line-height: 1.75rem;
+ --font-size-xl: 1.25rem;
+ --font-size-xl--line-height: 1.75rem;
+ --font-size-2xl: 1.5rem;
+ --font-size-2xl--line-height: 2rem;
+ --font-size-3xl: 1.875rem;
+ --font-size-3xl--line-height: 2.25rem;
+ --font-size-4xl: 2.25rem;
+ --font-size-4xl--line-height: 2.5rem;
+ --font-size-5xl: 3rem;
+ --font-size-5xl--line-height: 1;
+ --font-size-6xl: 3.75rem;
+ --font-size-6xl--line-height: 1;
+ --font-size-7xl: 4.5rem;
+ --font-size-7xl--line-height: 1;
+ --font-size-8xl: 6rem;
+ --font-size-8xl--line-height: 1;
+ --font-size-9xl: 8rem;
+ --font-size-9xl--line-height: 1;
+ --letter-spacing-tighter: -.05em;
+ --letter-spacing-tight: -.025em;
+ --letter-spacing-normal: 0em;
+ --letter-spacing-wide: .025em;
+ --letter-spacing-wider: .05em;
+ --letter-spacing-widest: .1em;
+ --line-height-none: 1;
+ --line-height-tight: 1.25;
+ --line-height-snug: 1.375;
+ --line-height-normal: 1.5;
+ --line-height-relaxed: 1.625;
+ --line-height-loose: 2;
+ --line-height-3: .75rem;
+ --line-height-4: 1rem;
+ --line-height-5: 1.25rem;
+ --line-height-6: 1.5rem;
+ --line-height-7: 1.75rem;
+ --line-height-8: 2rem;
+ --line-height-9: 2.25rem;
+ --line-height-10: 2.5rem;
+ --transition-timing-function: cubic-bezier(.4, 0, .2, 1);
+ --transition-timing-function-linear: linear;
+ --transition-timing-function-in: cubic-bezier(.4, 0, 1, 1);
+ --transition-timing-function-out: cubic-bezier(0, 0, .2, 1);
+ --transition-timing-function-in-out: cubic-bezier(.4, 0, .2, 1);
+ }
+}
+
+@layer base {
+ *, :after, :before, ::backdrop {
+ box-sizing: border-box;
+ }
+
+ ::file-selector-button {
+ box-sizing: border-box;
+ }
+
+ * {
+ margin: 0;
+ }
+
+ html, :host {
+ -webkit-text-size-adjust: 100%;
+ tab-size: 4;
+ line-height: 1.5;
+ font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
+ font-feature-settings: var(--default-font-feature-settings, normal);
+ font-variation-settings: var(--default-font-variation-settings, normal);
+ -webkit-tap-highlight-color: transparent;
+ }
+
+ body {
+ line-height: inherit;
+ }
+
+ hr {
+ color: inherit;
+ border: 0 solid;
+ border-top-width: 1px;
+ height: 0;
+ }
+
+ abbr:where([title]) {
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+ }
+
+ h1, h2, h3, h4, h5, h6 {
+ font-size: inherit;
+ font-weight: inherit;
+ }
+
+ a {
+ color: inherit;
+ -webkit-text-decoration: inherit;
+ -webkit-text-decoration: inherit;
+ text-decoration: inherit;
+ }
+
+ b, strong {
+ font-weight: bolder;
+ }
+
+ code, kbd, samp, pre {
+ font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
+ font-feature-settings: var(--default-mono-font-feature-settings, normal);
+ font-variation-settings: var(--default-mono-font-variation-settings, normal);
+ font-size: 1em;
+ }
+
+ small {
+ font-size: 80%;
+ }
+
+ sub, sup {
+ vertical-align: baseline;
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ }
+
+ sub {
+ bottom: -.25em;
+ }
+
+ sup {
+ top: -.5em;
+ }
+
+ table {
+ text-indent: 0;
+ border-color: inherit;
+ border-collapse: collapse;
+ }
+
+ button, input, optgroup, select, textarea {
+ font: inherit;
+ font-feature-settings: inherit;
+ font-variation-settings: inherit;
+ color: inherit;
+ background: none;
+ border: 1px solid;
+ padding: 0;
+ }
+
+ ::file-selector-button {
+ font: inherit;
+ font-feature-settings: inherit;
+ font-variation-settings: inherit;
+ color: inherit;
+ background: none;
+ border: 1px solid;
+ padding: 0;
+ }
+
+ button, input:where([type="button"], [type="reset"], [type="submit"]) {
+ appearance: button;
+ border: 0;
+ }
+
+ ::file-selector-button {
+ appearance: button;
+ border: 0;
+ }
+
+ :-moz-focusring {
+ outline: auto;
+ }
+
+ :-moz-ui-invalid {
+ box-shadow: none;
+ }
+
+ progress {
+ vertical-align: baseline;
+ }
+
+ ::-webkit-inner-spin-button {
+ height: auto;
+ }
+
+ ::-webkit-outer-spin-button {
+ height: auto;
+ }
+
+ ::-webkit-search-decoration {
+ -webkit-appearance: none;
+ }
+
+ summary {
+ display: list-item;
+ }
+
+ fieldset {
+ border: 0;
+ padding: 0;
+ }
+
+ legend {
+ padding: 0;
+ }
+
+ ol, ul, menu {
+ padding: 0;
+ list-style: none;
+ }
+
+ dialog {
+ padding: 0;
+ }
+
+ textarea {
+ resize: vertical;
+ }
+
+ ::placeholder {
+ opacity: 1;
+ color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ :disabled {
+ cursor: default;
+ }
+
+ img, svg, video, canvas, audio, iframe, embed, object {
+ vertical-align: middle;
+ display: block;
+ }
+
+ img, video {
+ max-width: 100%;
+ height: auto;
+ }
+
+ [hidden] {
+ display: none !important;
+ }
+}
+
+@layer components;
+
+@layer utilities {
+ .text-2xl {
+ font-size: 1.5rem;
+ line-height: 2rem;
+ }
+
+ .text-black\\/50 {
+ color: #00000080;
+ }
+
+ .underline {
+ text-decoration-line: underline;
+ }
+
+ @media (width >= 1536px) {
+ .\\32 xl\\:font-bold {
+ font-weight: 700;
+ }
+ }
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes ping {
+ 75%, 100% {
+ opacity: 0;
+ transform: scale(2);
+ }
+}
+
+@keyframes pulse {
+ 50% {
+ opacity: .5;
+ }
+}
+
+@keyframes bounce {
+ 0%, 100% {
+ animation-timing-function: cubic-bezier(.8, 0, 1, 1);
+ transform: translateY(-25%);
+ }
+
+ 50% {
+ animation-timing-function: cubic-bezier(0, 0, .2, 1);
+ transform: none;
+ }
+}"
+`;
diff --git a/packages/@tailwindcss-postcss/src/fixtures/example-project/index.html b/packages/@tailwindcss-postcss/src/fixtures/example-project/index.html
new file mode 100644
index 000000000..30d3c6684
--- /dev/null
+++ b/packages/@tailwindcss-postcss/src/fixtures/example-project/index.html
@@ -0,0 +1 @@
+
diff --git a/packages/@tailwindcss-postcss/src/fixtures/example-project/src/index.js b/packages/@tailwindcss-postcss/src/fixtures/example-project/src/index.js
new file mode 100644
index 000000000..8258dcbd3
--- /dev/null
+++ b/packages/@tailwindcss-postcss/src/fixtures/example-project/src/index.js
@@ -0,0 +1 @@
+const className = 'text-2xl text-black/50'
diff --git a/packages/@tailwindcss-postcss/src/index.test.ts b/packages/@tailwindcss-postcss/src/index.test.ts
new file mode 100644
index 000000000..799b96dac
--- /dev/null
+++ b/packages/@tailwindcss-postcss/src/index.test.ts
@@ -0,0 +1,130 @@
+import { unlink, writeFile } from 'node:fs/promises'
+import postcss from 'postcss'
+import { afterEach, beforeEach, describe, expect, test } from 'vitest'
+import tailwindcss from './index'
+
+// We give this file path to PostCSS for processing.
+// This file doesn't exist, but the path is used to resolve imports.
+// We place it in packages/ because Vitest runs in the monorepo root,
+// and packages/tailwindcss must be a sub-folder for
+// @import 'tailwindcss' to work.
+const INPUT_CSS_PATH = `${__dirname}/fixtures/example-project/input.css`
+
+const css = String.raw
+
+beforeEach(async () => {
+ const { clearCache } = await import('@tailwindcss/oxide')
+ clearCache()
+})
+
+test("`@import 'tailwindcss'` is replaced with the generated CSS", async () => {
+ let processor = postcss([tailwindcss({ base: `${__dirname}/fixtures/example-project` })])
+
+ let result = await processor.process(`@import 'tailwindcss'`, { from: INPUT_CSS_PATH })
+
+ expect(result.css.trim()).toMatchSnapshot()
+
+ // Check for dependency messages
+ expect(result.messages).toContainEqual({
+ type: 'dependency',
+ file: expect.stringMatching(/index.html$/g),
+ parent: expect.any(String),
+ plugin: expect.any(String),
+ })
+ expect(result.messages).toContainEqual({
+ type: 'dependency',
+ file: expect.stringMatching(/index.js$/g),
+ parent: expect.any(String),
+ plugin: expect.any(String),
+ })
+ expect(result.messages).toContainEqual({
+ type: 'dir-dependency',
+ dir: expect.stringMatching(/example-project\/src$/g),
+ glob: expect.stringMatching(/^\*\*\/\*/g),
+ parent: expect.any(String),
+ plugin: expect.any(String),
+ })
+})
+
+test('output is optimized by Lightning CSS', async () => {
+ let processor = postcss([tailwindcss({ base: `${__dirname}/fixtures/example-project` })])
+
+ // `@apply` is used because Lightning is skipped if neither `@tailwind` nor
+ // `@apply` is used.
+ let result = await processor.process(
+ css`
+ @layer utilities {
+ .foo {
+ @apply text-[black];
+ }
+ }
+
+ @layer utilities {
+ .bar {
+ color: red;
+ }
+ }
+ `,
+ { from: INPUT_CSS_PATH },
+ )
+
+ expect(result.css.trim()).toMatchInlineSnapshot(`
+ "@layer utilities {
+ .foo {
+ color: #000;
+ }
+
+ .bar {
+ color: red;
+ }
+ }"
+ `)
+})
+
+test('@apply can be used without emitting the theme in the CSS file', async () => {
+ let processor = postcss([tailwindcss({ base: `${__dirname}/fixtures/example-project` })])
+
+ // `@apply` is used because Lightning is skipped if neither `@tailwind` nor
+ // `@apply` is used.
+ let result = await processor.process(
+ css`
+ @import 'tailwindcss/theme.css' reference;
+ .foo {
+ @apply text-red-500;
+ }
+ `,
+ { from: INPUT_CSS_PATH },
+ )
+
+ expect(result.css.trim()).toMatchInlineSnapshot(`
+ ".foo {
+ color: #ef4444;
+ }"
+ `)
+})
+
+describe('processing without specifying a base path', () => {
+ let filepath = `${process.cwd()}/my-test-file.html`
+
+ beforeEach(() =>
+ writeFile(filepath, ``),
+ )
+ afterEach(() => unlink(filepath))
+
+ test('the current working directory is used by default', async () => {
+ let processor = postcss([tailwindcss()])
+
+ let result = await processor.process(`@import "tailwindcss"`, { from: INPUT_CSS_PATH })
+
+ expect(result.css).toContain(
+ ".md\\:\\[\\&\\:hover\\]\\:content-\\[\\'testing_default_base_path\\'\\]",
+ )
+
+ expect(result.messages).toContainEqual({
+ type: 'dependency',
+ file: expect.stringMatching(/my-test-file.html$/g),
+ parent: expect.any(String),
+ plugin: expect.any(String),
+ })
+ })
+})
diff --git a/packages/@tailwindcss-postcss/src/index.ts b/packages/@tailwindcss-postcss/src/index.ts
new file mode 100644
index 000000000..95f2f940a
--- /dev/null
+++ b/packages/@tailwindcss-postcss/src/index.ts
@@ -0,0 +1,82 @@
+import { scanDir } from '@tailwindcss/oxide'
+import postcss, { type AcceptedPlugin, type PluginCreator } from 'postcss'
+import postcssImport from 'postcss-import'
+import { compile, optimizeCss } from 'tailwindcss'
+
+type PluginOptions = {
+ // The base directory to scan for class candidates.
+ base?: string
+}
+
+function tailwindcss(opts: PluginOptions = {}): AcceptedPlugin {
+ let base = opts.base ?? process.cwd()
+
+ return {
+ postcssPlugin: 'tailwindcss-v4',
+ plugins: [
+ // We need to run `postcss-import` first to handle `@import` rules.
+ postcssImport(),
+
+ (root, result) => {
+ let hasApply = false
+ let hasTailwind = false
+
+ root.walkAtRules((rule) => {
+ if (rule.name === 'apply') {
+ hasApply = true
+ } else if (rule.name === 'tailwind') {
+ hasApply = true
+ hasTailwind = true
+ // If we've found `@tailwind` then we already
+ // know we have to run a "full" build
+ return false
+ }
+ })
+
+ // Do nothing if neither `@tailwind` nor `@apply` is used
+ if (!hasTailwind && !hasApply) return
+
+ function replaceCss(css: string) {
+ root.removeAll()
+ root.append(postcss.parse(optimizeCss(css), result.opts))
+ }
+
+ // No `@tailwind` means we don't have to look for candidates
+ if (!hasTailwind) {
+ replaceCss(compile(root.toString(), []))
+ return
+ }
+
+ // Look for candidates used to generate the CSS
+ let { candidates, files, globs } = scanDir({ base, globs: true })
+
+ // Add all found files as direct dependencies
+ for (let file of files) {
+ result.messages.push({
+ type: 'dependency',
+ plugin: 'tailwindcss-v4',
+ file,
+ parent: result.opts.from,
+ })
+ }
+
+ // Register dependencies so changes in `base` cause a rebuild while
+ // giving tools like Vite or Parcel a glob that can be used to limit
+ // the files that cause a rebuild to only those that match it.
+ for (let { base, glob } of globs) {
+ result.messages.push({
+ type: 'dir-dependency',
+ plugin: 'tailwindcss-v4',
+ dir: base,
+ glob,
+ parent: result.opts.from,
+ })
+ }
+
+ replaceCss(compile(root.toString(), candidates))
+ },
+ ],
+ }
+}
+
+export default Object.assign(tailwindcss, { postcss: true }) as PluginCreator
diff --git a/packages/@tailwindcss-postcss/tsconfig.json b/packages/@tailwindcss-postcss/tsconfig.json
new file mode 100644
index 000000000..6ae022f65
--- /dev/null
+++ b/packages/@tailwindcss-postcss/tsconfig.json
@@ -0,0 +1,3 @@
+{
+ "extends": "../tsconfig.base.json",
+}
diff --git a/packages/@tailwindcss-vite/package.json b/packages/@tailwindcss-vite/package.json
new file mode 100644
index 000000000..0aefe1752
--- /dev/null
+++ b/packages/@tailwindcss-vite/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "@tailwindcss/vite",
+ "version": "0.0.0-oxide.4",
+ "description": "A utility-first CSS framework for rapidly building custom user interfaces.",
+ "license": "MIT",
+ "repository": "https://github.com/tailwindlabs/tailwindcss.git",
+ "bugs": "https://github.com/tailwindlabs/tailwindcss/issues",
+ "homepage": "https://tailwindcss.com",
+ "scripts": {
+ "build": "tsup-node ./src/index.ts --format esm --dts",
+ "dev": "pnpm run build -- --watch"
+ },
+ "files": [
+ "dist/"
+ ],
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.mts",
+ "import": "./dist/index.mjs"
+ }
+ },
+ "dependencies": {
+ "@tailwindcss/oxide": "workspace:^",
+ "tailwindcss": "workspace:^"
+ },
+ "devDependencies": {
+ "@types/node": "^20.11.17",
+ "vite": "^5.0.11"
+ }
+}
diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts
new file mode 100644
index 000000000..166b39a52
--- /dev/null
+++ b/packages/@tailwindcss-vite/src/index.ts
@@ -0,0 +1,177 @@
+import { IO, Parsing, scanFiles } from '@tailwindcss/oxide'
+import path from 'path'
+import { compile, optimizeCss } from 'tailwindcss'
+import type { Plugin, Update, ViteDevServer } from 'vite'
+
+export default function tailwindcss(): Plugin[] {
+ let server: ViteDevServer | null = null
+ let candidates = new Set()
+ let cssModules = new Set()
+ let minify = false
+
+ function isCssFile(id: string) {
+ let [filename] = id.split('?', 2)
+ let extension = path.extname(filename).slice(1)
+ return extension === 'css'
+ }
+
+ // Trigger update to all css modules
+ function updateCssModules() {
+ // If we're building then we don't need to update anything
+ if (!server) return
+
+ let updates: Update[] = []
+ for (let id of cssModules) {
+ let cssModule = server.moduleGraph.getModuleById(id)
+ if (!cssModule) {
+ console.log('Could not find css module', id)
+ continue
+ }
+
+ server.moduleGraph.invalidateModule(cssModule)
+ updates.push({
+ type: `${cssModule.type}-update`,
+ path: cssModule.url,
+ acceptedPath: cssModule.url,
+ timestamp: Date.now(),
+ })
+ }
+
+ if (updates.length > 0) {
+ server.hot.send({ type: 'update', updates })
+ }
+ }
+
+ function scan(src: string, extension: string) {
+ let updated = false
+ // Parse all candidates given the resolved files
+ for (let candidate of scanFiles(
+ [{ content: src, extension }],
+ IO.Sequential | Parsing.Sequential,
+ )) {
+ // On an initial or full build, updated becomes true immediately so we
+ // won't be making extra checks.
+ if (!updated) {
+ if (candidates.has(candidate)) continue
+ updated = true
+ }
+ candidates.add(candidate)
+ }
+ return updated
+ }
+
+ function generateCss(css: string) {
+ return optimizeCss(compile(css, Array.from(candidates)), { minify })
+ }
+
+ // In dev mode, there isn't a hook to signal that we've seen all files. We use
+ // a timer, resetting it on each file seen, and trigger CSS generation when we
+ // haven't seen any new files after a timeout. If this triggers too early,
+ // there will be a FOOC and but CSS will regenerate after we've seen more files.
+ let initialScan = (() => {
+ // If too short, we're more likely to trigger a FOOC and generate CSS
+ // multiple times. If too long, we delay dev builds.
+ let delayInMs = 50
+
+ let timer: ReturnType
+ let resolve: () => void
+ let resolved = false
+
+ return {
+ tick() {
+ if (resolved) return
+ timer && clearTimeout(timer)
+ timer = setTimeout(resolve, delayInMs)
+ },
+
+ complete: new Promise((_resolve) => {
+ resolve = () => {
+ resolved = true
+ _resolve()
+ }
+ }),
+ }
+ })()
+
+ return [
+ {
+ // Step 1: Scan source files for candidates
+ name: '@tailwindcss/vite:scan',
+ enforce: 'pre',
+
+ configureServer(_server) {
+ server = _server
+ },
+
+ async configResolved(config) {
+ minify = config.build.cssMinify !== false
+ },
+
+ // Scan index.html for candidates
+ transformIndexHtml(html) {
+ initialScan.tick()
+ let updated = scan(html, 'html')
+
+ // In dev mode, if the generated CSS contains a URL that causes the
+ // browser to load a page (e.g. an URL to a missing image), triggering a
+ // CSS update will cause an infinite loop. We only trigger if the
+ // candidates have been updated.
+ if (server && updated) {
+ updateCssModules()
+ }
+ },
+
+ // Scan all other files for candidates
+ transform(src, id) {
+ initialScan.tick()
+ if (id.includes('/.vite/')) return
+ let [filename] = id.split('?', 2)
+ let extension = path.extname(filename).slice(1)
+ if (extension === '' || extension === 'css') return
+
+ scan(src, extension)
+
+ if (server) {
+ updateCssModules()
+ }
+ },
+ },
+
+ {
+ // Step 2 (dev mode): Generate CSS
+ name: '@tailwindcss/vite:generate:serve',
+ apply: 'serve',
+ async transform(src, id) {
+ if (!isCssFile(id) || !src.includes('@tailwind')) return
+
+ cssModules.add(id)
+
+ // For the initial load we must wait for all source files to be scanned
+ await initialScan.complete
+
+ return { code: generateCss(src) }
+ },
+ },
+
+ {
+ // Step 2 (full build): Generate CSS
+ name: '@tailwindcss/vite:generate:build',
+ enforce: 'post',
+ apply: 'build',
+ generateBundle(_options, bundle) {
+ for (let id in bundle) {
+ let item = bundle[id]
+ if (item.type !== 'asset') continue
+ if (!isCssFile(id)) continue
+ let rawSource = item.source
+ let source =
+ rawSource instanceof Uint8Array ? new TextDecoder().decode(rawSource) : rawSource
+
+ if (source.includes('@tailwind')) {
+ item.source = generateCss(source)
+ }
+ }
+ },
+ },
+ ] satisfies Plugin[]
+}
diff --git a/packages/@tailwindcss-vite/tsconfig.json b/packages/@tailwindcss-vite/tsconfig.json
new file mode 100644
index 000000000..6ae022f65
--- /dev/null
+++ b/packages/@tailwindcss-vite/tsconfig.json
@@ -0,0 +1,3 @@
+{
+ "extends": "../tsconfig.base.json",
+}
diff --git a/packages/tailwindcss/index.css b/packages/tailwindcss/index.css
new file mode 100644
index 000000000..1f5f16181
--- /dev/null
+++ b/packages/tailwindcss/index.css
@@ -0,0 +1,5 @@
+@layer theme, base, components, utilities;
+
+@import './theme.css' layer(theme);
+@import './preflight.css' layer(base);
+@import './utilities.css' layer(utilities);
diff --git a/packages/tailwindcss/package.json b/packages/tailwindcss/package.json
new file mode 100644
index 000000000..47c2c588c
--- /dev/null
+++ b/packages/tailwindcss/package.json
@@ -0,0 +1,67 @@
+{
+ "name": "tailwindcss",
+ "version": "0.0.0-oxide.4",
+ "description": "A utility-first CSS framework for rapidly building custom user interfaces.",
+ "license": "MIT",
+ "repository": "https://github.com/tailwindlabs/tailwindcss.git",
+ "bugs": "https://github.com/tailwindlabs/tailwindcss/issues",
+ "homepage": "https://tailwindcss.com",
+ "scripts": {
+ "lint": "tsc --noEmit",
+ "build": "tsup-node --env.NODE_ENV production",
+ "dev": "tsup-node --env.NODE_ENV development --watch",
+ "test:ui": "playwright test"
+ },
+ "bin": {
+ "tailwindcss": "./dist/cli.js"
+ },
+ "exports": {
+ ".": {
+ "style": "./index.css",
+ "types": "./src/index.ts",
+ "require": "./dist/lib.js",
+ "import": "./src/index.ts"
+ },
+ "./package.json": "./package.json",
+ "./index.css": "./index.css",
+ "./preflight.css": "./preflight.css",
+ "./theme.css": "./theme.css",
+ "./utilities.css": "./utilities.css"
+ },
+ "publishConfig": {
+ "exports": {
+ ".": {
+ "types": "./dist/lib.d.mts",
+ "style": "./index.css",
+ "require": "./dist/lib.js",
+ "import": "./dist/lib.mjs"
+ },
+ "./package.json": "./package.json",
+ "./index.css": "./index.css",
+ "./preflight.css": "./preflight.css",
+ "./theme.css": "./theme.css",
+ "./utilities.css": "./utilities.css"
+ }
+ },
+ "style": "index.css",
+ "files": [
+ "dist",
+ "index.css",
+ "preflight.css",
+ "theme.css",
+ "utilities.css"
+ ],
+ "dependencies": {
+ "@parcel/watcher": "^2.4.1",
+ "lightningcss": "^1.24.0",
+ "mri": "^1.2.0",
+ "picocolors": "^1.0.0",
+ "postcss": "8.4.24",
+ "postcss-import": "^16.0.0"
+ },
+ "devDependencies": {
+ "@tailwindcss/oxide": "workspace:^",
+ "@types/node": "^20.10.8",
+ "@types/postcss-import": "^14.0.3"
+ }
+}
diff --git a/packages/tailwindcss/playwright.config.ts b/packages/tailwindcss/playwright.config.ts
new file mode 100644
index 000000000..1022c63fe
--- /dev/null
+++ b/packages/tailwindcss/playwright.config.ts
@@ -0,0 +1,72 @@
+import { defineConfig, devices } from '@playwright/test'
+
+/**
+ * Read environment variables from file.
+ * https://github.com/motdotla/dotenv
+ */
+// require('dotenv').config();
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+export default defineConfig({
+ testDir: './tests',
+ /* Run tests in files in parallel */
+ fullyParallel: true,
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
+ forbidOnly: !!process.env.CI,
+ /* Retry on CI only */
+ retries: process.env.CI ? 2 : 0,
+ /* Opt out of parallel tests on CI. */
+ workers: process.env.CI ? 1 : undefined,
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
+ reporter: 'html',
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
+ use: {
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ // baseURL: 'http://127.0.0.1:3000',
+
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
+ trace: 'on-first-retry',
+ },
+
+ /* Configure projects for major browsers */
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+
+ {
+ name: 'webkit',
+ use: { ...devices['Desktop Safari'] },
+ },
+
+ /* Test against mobile viewports. */
+ // {
+ // name: 'Mobile Chrome',
+ // use: { ...devices['Pixel 5'] },
+ // },
+ // {
+ // name: 'Mobile Safari',
+ // use: { ...devices['iPhone 12'] },
+ // },
+
+ /* Test against branded browsers. */
+ // {
+ // name: 'Microsoft Edge',
+ // use: { ...devices['Desktop Edge'], channel: 'msedge' },
+ // },
+ // {
+ // name: 'Google Chrome',
+ // use: { ...devices['Desktop Chrome'], channel: 'chrome' },
+ // },
+ ],
+
+ /* Run your local dev server before starting the tests */
+ // webServer: {
+ // command: 'npm run start',
+ // url: 'http://127.0.0.1:3000',
+ // reuseExistingServer: !process.env.CI,
+ // },
+})
diff --git a/packages/tailwindcss/preflight.css b/packages/tailwindcss/preflight.css
new file mode 100644
index 000000000..9590f6200
--- /dev/null
+++ b/packages/tailwindcss/preflight.css
@@ -0,0 +1,355 @@
+/*
+ Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
+*/
+
+*,
+::after,
+::before,
+::backdrop,
+::file-selector-button {
+ box-sizing: border-box;
+}
+
+/*
+ Remove any default margins.
+*/
+
+* {
+ margin: 0;
+}
+
+/*
+ 1. Use a consistent sensible line-height in all browsers.
+ 2. Prevent adjustments of font size after orientation changes in iOS.
+ 3. Use a more readable tab size.
+ 4. Use the user's configured `sans` font-family by default.
+ 5. Use the user's configured `sans` font-feature-settings by default.
+ 6. Use the user's configured `sans` font-variation-settings by default.
+ 7. Disable tap highlights on iOS.
+*/
+
+html,
+:host {
+ line-height: 1.5; /* 1 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+ tab-size: 4; /* 3 */
+ font-family: var(
+ --default-font-family,
+ ui-sans-serif,
+ system-ui,
+ sans-serif,
+ 'Apple Color Emoji',
+ 'Segoe UI Emoji',
+ 'Segoe UI Symbol',
+ 'Noto Color Emoji'
+ ); /* 4 */
+ font-feature-settings: var(--default-font-feature-settings, normal); /* 5 */
+ font-variation-settings: var(--default-font-variation-settings, normal); /* 6 */
+ -webkit-tap-highlight-color: transparent; /* 7 */
+}
+
+/*
+ Inherit line-height from `html` so users can set them as a class directly on the `html` element.
+*/
+
+body {
+ line-height: inherit;
+}
+
+/*
+ 1. Add the correct height in Firefox.
+ 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
+ 3. Reset the default border style to a 1px solid border.
+*/
+
+hr {
+ height: 0; /* 1 */
+ color: inherit; /* 2 */
+ border: 0 solid; /* 3 */
+ border-top-width: 1px; /* 3 */
+}
+
+/*
+ Add the correct text decoration in Chrome, Edge, and Safari.
+*/
+
+abbr:where([title]) {
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+}
+
+/*
+ Remove the default font size and weight for headings.
+*/
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-size: inherit;
+ font-weight: inherit;
+}
+
+/*
+ Reset links to optimize for opt-in styling instead of opt-out.
+*/
+
+a {
+ color: inherit;
+ -webkit-text-decoration: inherit;
+ text-decoration: inherit;
+}
+
+/*
+ Add the correct font weight in Edge and Safari.
+*/
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/*
+ 1. Use the user's configured `mono` font-family by default.
+ 2. Use the user's configured `mono` font-feature-settings by default.
+ 3. Use the user's configured `mono` font-variation-settings by default.
+ 4. Correct the odd `em` font sizing in all browsers.
+*/
+
+code,
+kbd,
+samp,
+pre {
+ font-family: var(
+ --default-mono-font-family,
+ ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ Monaco,
+ Consolas,
+ 'Liberation Mono',
+ 'Courier New',
+ monospace
+ ); /* 4 */
+ font-feature-settings: var(--default-mono-font-feature-settings, normal); /* 5 */
+ font-variation-settings: var(--default-mono-font-variation-settings, normal); /* 6 */
+ font-size: 1em; /* 4 */
+}
+
+/*
+ Add the correct font size in all browsers.
+*/
+
+small {
+ font-size: 80%;
+}
+
+/*
+ Prevent `sub` and `sup` elements from affecting the line height in all browsers.
+*/
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/*
+ 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
+ 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
+ 3. Remove gaps between table borders by default.
+*/
+
+table {
+ text-indent: 0; /* 1 */
+ border-color: inherit; /* 2 */
+ border-collapse: collapse; /* 3 */
+}
+
+/*
+ 1. Inherit the font styles in all browsers.
+ 2. Reset the default inset border style to solid.
+ 3. Remove the default background color.
+ 4. Remove default padding.
+*/
+
+button,
+input,
+optgroup,
+select,
+textarea,
+::file-selector-button {
+ font: inherit; /* 1 */
+ font-feature-settings: inherit; /* 1 */
+ font-variation-settings: inherit; /* 1 */
+ color: inherit; /* 1 */
+ border: 1px solid; /* 2 */
+ background: transparent; /* 3 */
+ padding: 0; /* 4 */
+}
+
+/*
+ 1. Correct the inability to style the border radius in iOS Safari.
+ 2. Make borders opt-in.
+*/
+button,
+input:where([type='button'], [type='reset'], [type='submit']),
+::file-selector-button {
+ appearance: button; /* 1 */
+ border: 0; /* 2 */
+}
+
+/*
+ Use the modern Firefox focus style for all focusable elements.
+*/
+
+:-moz-focusring {
+ outline: auto;
+}
+
+/*
+ Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
+*/
+
+:-moz-ui-invalid {
+ box-shadow: none;
+}
+
+/*
+ Add the correct vertical alignment in Chrome and Firefox.
+*/
+
+progress {
+ vertical-align: baseline;
+}
+
+/*
+ Correct the cursor style of increment and decrement buttons in Safari.
+*/
+
+::-webkit-inner-spin-button,
+::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/*
+ Remove the inner padding in Chrome and Safari on macOS.
+*/
+
+::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/*
+ Add the correct display in Chrome and Safari.
+*/
+
+summary {
+ display: list-item;
+}
+
+/*
+ Remove the default border and spacing for fieldset and legend elements.
+*/
+
+fieldset {
+ border: 0;
+ padding: 0;
+}
+
+legend {
+ padding: 0;
+}
+
+/*
+ Make lists unstyled by default.
+*/
+
+ol,
+ul,
+menu {
+ list-style: none;
+ padding: 0;
+}
+
+/*
+ Remove the default padding from dialog elements.
+*/
+
+dialog {
+ padding: 0;
+}
+
+/*
+ Prevent resizing textareas horizontally by default.
+*/
+
+textarea {
+ resize: vertical;
+}
+
+/*
+ 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
+ 2. Set the default placeholder color to a semi-transparent version of the current text color.
+*/
+
+::placeholder {
+ opacity: 1; /* 1 */
+ color: color-mix(in srgb, currentColor 50%, transparent); /* 2 */
+}
+
+/*
+ Make sure disabled buttons don't get the pointer cursor.
+*/
+
+:disabled {
+ cursor: default;
+}
+
+/*
+ 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
+ 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
+ This can trigger a poorly considered lint error in some tools but is included by design.
+*/
+
+img,
+svg,
+video,
+canvas,
+audio,
+iframe,
+embed,
+object {
+ display: block; /* 1 */
+ vertical-align: middle; /* 2 */
+}
+
+/*
+ Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
+*/
+
+img,
+video {
+ max-width: 100%;
+ height: auto;
+}
+
+/*
+ Make elements with the HTML hidden attribute stay hidden by default.
+*/
+
+[hidden] {
+ display: none !important;
+}
diff --git a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap
new file mode 100644
index 000000000..f8f0e8685
--- /dev/null
+++ b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap
@@ -0,0 +1,523 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using the default theme 1`] = `
+":root {
+ --default-transition-duration: .15s;
+ --default-transition-timing-function: var(--transition-timing-function-in-out);
+ --default-font-family: var(--font-family-sans);
+ --default-font-feature-settings: var(--font-family-sans--font-feature-settings);
+ --default-font-variation-settings: var(--font-family-sans--font-variation-settings);
+ --default-mono-font-family: var(--font-family-mono);
+ --default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
+ --default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
+ --breakpoint-sm: 640px;
+ --breakpoint-md: 768px;
+ --breakpoint-lg: 1024px;
+ --breakpoint-xl: 1280px;
+ --breakpoint-2xl: 1536px;
+ --color-black: #000;
+ --color-white: #fff;
+ --color-slate-50: #f8fafc;
+ --color-slate-100: #f1f5f9;
+ --color-slate-200: #e2e8f0;
+ --color-slate-300: #cbd5e1;
+ --color-slate-400: #94a3b8;
+ --color-slate-500: #64748b;
+ --color-slate-600: #475569;
+ --color-slate-700: #334155;
+ --color-slate-800: #1e293b;
+ --color-slate-900: #0f172a;
+ --color-slate-950: #020617;
+ --color-gray-50: #f9fafb;
+ --color-gray-100: #f3f4f6;
+ --color-gray-200: #e5e7eb;
+ --color-gray-300: #d1d5db;
+ --color-gray-400: #9ca3af;
+ --color-gray-500: #6b7280;
+ --color-gray-600: #4b5563;
+ --color-gray-700: #374151;
+ --color-gray-800: #1f2937;
+ --color-gray-900: #111827;
+ --color-gray-950: #030712;
+ --color-zinc-50: #fafafa;
+ --color-zinc-100: #f4f4f5;
+ --color-zinc-200: #e4e4e7;
+ --color-zinc-300: #d4d4d8;
+ --color-zinc-400: #a1a1aa;
+ --color-zinc-500: #71717a;
+ --color-zinc-600: #52525b;
+ --color-zinc-700: #3f3f46;
+ --color-zinc-800: #27272a;
+ --color-zinc-900: #18181b;
+ --color-zinc-950: #09090b;
+ --color-neutral-50: #fafafa;
+ --color-neutral-100: #f5f5f5;
+ --color-neutral-200: #e5e5e5;
+ --color-neutral-300: #d4d4d4;
+ --color-neutral-400: #a3a3a3;
+ --color-neutral-500: #737373;
+ --color-neutral-600: #525252;
+ --color-neutral-700: #404040;
+ --color-neutral-800: #262626;
+ --color-neutral-900: #171717;
+ --color-neutral-950: #0a0a0a;
+ --color-stone-50: #fafaf9;
+ --color-stone-100: #f5f5f4;
+ --color-stone-200: #e7e5e4;
+ --color-stone-300: #d6d3d1;
+ --color-stone-400: #a8a29e;
+ --color-stone-500: #78716c;
+ --color-stone-600: #57534e;
+ --color-stone-700: #44403c;
+ --color-stone-800: #292524;
+ --color-stone-900: #1c1917;
+ --color-stone-950: #0c0a09;
+ --color-red-50: #fef2f2;
+ --color-red-100: #fee2e2;
+ --color-red-200: #fecaca;
+ --color-red-300: #fca5a5;
+ --color-red-400: #f87171;
+ --color-red-500: #ef4444;
+ --color-red-600: #dc2626;
+ --color-red-700: #b91c1c;
+ --color-red-800: #991b1b;
+ --color-red-900: #7f1d1d;
+ --color-red-950: #450a0a;
+ --color-orange-50: #fff7ed;
+ --color-orange-100: #ffedd5;
+ --color-orange-200: #fed7aa;
+ --color-orange-300: #fdba74;
+ --color-orange-400: #fb923c;
+ --color-orange-500: #f97316;
+ --color-orange-600: #ea580c;
+ --color-orange-700: #c2410c;
+ --color-orange-800: #9a3412;
+ --color-orange-900: #7c2d12;
+ --color-orange-950: #431407;
+ --color-amber-50: #fffbeb;
+ --color-amber-100: #fef3c7;
+ --color-amber-200: #fde68a;
+ --color-amber-300: #fcd34d;
+ --color-amber-400: #fbbf24;
+ --color-amber-500: #f59e0b;
+ --color-amber-600: #d97706;
+ --color-amber-700: #b45309;
+ --color-amber-800: #92400e;
+ --color-amber-900: #78350f;
+ --color-amber-950: #451a03;
+ --color-yellow-50: #fefce8;
+ --color-yellow-100: #fef9c3;
+ --color-yellow-200: #fef08a;
+ --color-yellow-300: #fde047;
+ --color-yellow-400: #facc15;
+ --color-yellow-500: #eab308;
+ --color-yellow-600: #ca8a04;
+ --color-yellow-700: #a16207;
+ --color-yellow-800: #854d0e;
+ --color-yellow-900: #713f12;
+ --color-yellow-950: #422006;
+ --color-lime-50: #f7fee7;
+ --color-lime-100: #ecfccb;
+ --color-lime-200: #d9f99d;
+ --color-lime-300: #bef264;
+ --color-lime-400: #a3e635;
+ --color-lime-500: #84cc16;
+ --color-lime-600: #65a30d;
+ --color-lime-700: #4d7c0f;
+ --color-lime-800: #3f6212;
+ --color-lime-900: #365314;
+ --color-lime-950: #1a2e05;
+ --color-green-50: #f0fdf4;
+ --color-green-100: #dcfce7;
+ --color-green-200: #bbf7d0;
+ --color-green-300: #86efac;
+ --color-green-400: #4ade80;
+ --color-green-500: #22c55e;
+ --color-green-600: #16a34a;
+ --color-green-700: #15803d;
+ --color-green-800: #166534;
+ --color-green-900: #14532d;
+ --color-green-950: #052e16;
+ --color-emerald-50: #ecfdf5;
+ --color-emerald-100: #d1fae5;
+ --color-emerald-200: #a7f3d0;
+ --color-emerald-300: #6ee7b7;
+ --color-emerald-400: #34d399;
+ --color-emerald-500: #10b981;
+ --color-emerald-600: #059669;
+ --color-emerald-700: #047857;
+ --color-emerald-800: #065f46;
+ --color-emerald-900: #064e3b;
+ --color-emerald-950: #022c22;
+ --color-teal-50: #f0fdfa;
+ --color-teal-100: #ccfbf1;
+ --color-teal-200: #99f6e4;
+ --color-teal-300: #5eead4;
+ --color-teal-400: #2dd4bf;
+ --color-teal-500: #14b8a6;
+ --color-teal-600: #0d9488;
+ --color-teal-700: #0f766e;
+ --color-teal-800: #115e59;
+ --color-teal-900: #134e4a;
+ --color-teal-950: #042f2e;
+ --color-cyan-50: #ecfeff;
+ --color-cyan-100: #cffafe;
+ --color-cyan-200: #a5f3fc;
+ --color-cyan-300: #67e8f9;
+ --color-cyan-400: #22d3ee;
+ --color-cyan-500: #06b6d4;
+ --color-cyan-600: #0891b2;
+ --color-cyan-700: #0e7490;
+ --color-cyan-800: #155e75;
+ --color-cyan-900: #164e63;
+ --color-cyan-950: #083344;
+ --color-sky-50: #f0f9ff;
+ --color-sky-100: #e0f2fe;
+ --color-sky-200: #bae6fd;
+ --color-sky-300: #7dd3fc;
+ --color-sky-400: #38bdf8;
+ --color-sky-500: #0ea5e9;
+ --color-sky-600: #0284c7;
+ --color-sky-700: #0369a1;
+ --color-sky-800: #075985;
+ --color-sky-900: #0c4a6e;
+ --color-sky-950: #082f49;
+ --color-blue-50: #eff6ff;
+ --color-blue-100: #dbeafe;
+ --color-blue-200: #bfdbfe;
+ --color-blue-300: #93c5fd;
+ --color-blue-400: #60a5fa;
+ --color-blue-500: #3b82f6;
+ --color-blue-600: #2563eb;
+ --color-blue-700: #1d4ed8;
+ --color-blue-800: #1e40af;
+ --color-blue-900: #1e3a8a;
+ --color-blue-950: #172554;
+ --color-indigo-50: #eef2ff;
+ --color-indigo-100: #e0e7ff;
+ --color-indigo-200: #c7d2fe;
+ --color-indigo-300: #a5b4fc;
+ --color-indigo-400: #818cf8;
+ --color-indigo-500: #6366f1;
+ --color-indigo-600: #4f46e5;
+ --color-indigo-700: #4338ca;
+ --color-indigo-800: #3730a3;
+ --color-indigo-900: #312e81;
+ --color-indigo-950: #1e1b4b;
+ --color-violet-50: #f5f3ff;
+ --color-violet-100: #ede9fe;
+ --color-violet-200: #ddd6fe;
+ --color-violet-300: #c4b5fd;
+ --color-violet-400: #a78bfa;
+ --color-violet-500: #8b5cf6;
+ --color-violet-600: #7c3aed;
+ --color-violet-700: #6d28d9;
+ --color-violet-800: #5b21b6;
+ --color-violet-900: #4c1d95;
+ --color-violet-950: #2e1065;
+ --color-purple-50: #faf5ff;
+ --color-purple-100: #f3e8ff;
+ --color-purple-200: #e9d5ff;
+ --color-purple-300: #d8b4fe;
+ --color-purple-400: #c084fc;
+ --color-purple-500: #a855f7;
+ --color-purple-600: #9333ea;
+ --color-purple-700: #7e22ce;
+ --color-purple-800: #6b21a8;
+ --color-purple-900: #581c87;
+ --color-purple-950: #3b0764;
+ --color-fuchsia-50: #fdf4ff;
+ --color-fuchsia-100: #fae8ff;
+ --color-fuchsia-200: #f5d0fe;
+ --color-fuchsia-300: #f0abfc;
+ --color-fuchsia-400: #e879f9;
+ --color-fuchsia-500: #d946ef;
+ --color-fuchsia-600: #c026d3;
+ --color-fuchsia-700: #a21caf;
+ --color-fuchsia-800: #86198f;
+ --color-fuchsia-900: #701a75;
+ --color-fuchsia-950: #4a044e;
+ --color-pink-50: #fdf2f8;
+ --color-pink-100: #fce7f3;
+ --color-pink-200: #fbcfe8;
+ --color-pink-300: #f9a8d4;
+ --color-pink-400: #f472b6;
+ --color-pink-500: #ec4899;
+ --color-pink-600: #db2777;
+ --color-pink-700: #be185d;
+ --color-pink-800: #9d174d;
+ --color-pink-900: #831843;
+ --color-pink-950: #500724;
+ --color-rose-50: #fff1f2;
+ --color-rose-100: #ffe4e6;
+ --color-rose-200: #fecdd3;
+ --color-rose-300: #fda4af;
+ --color-rose-400: #fb7185;
+ --color-rose-500: #f43f5e;
+ --color-rose-600: #e11d48;
+ --color-rose-700: #be123c;
+ --color-rose-800: #9f1239;
+ --color-rose-900: #881337;
+ --color-rose-950: #4c0519;
+ --animate-spin: spin 1s linear infinite;
+ --animate-ping: ping 1s cubic-bezier(0, 0, .2, 1) infinite;
+ --animate-pulse: pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;
+ --animate-bounce: bounce 1s infinite;
+ --blur: 8px;
+ --blur-sm: 4px;
+ --blur-md: 12px;
+ --blur-lg: 16px;
+ --blur-xl: 24px;
+ --blur-2xl: 40px;
+ --blur-3xl: 64px;
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ --radius-md: .375rem;
+ --radius-lg: .5rem;
+ --radius-xl: .75rem;
+ --radius-2xl: 1rem;
+ --radius-3xl: 1.5rem;
+ --shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
+ --shadow-xs: 0 1px #0000000d;
+ --shadow-sm: 0 1px 2px 0 #0000000d;
+ --shadow-md: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
+ --shadow-lg: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
+ --shadow-xl: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a;
+ --shadow-2xl: 0 25px 50px -12px #00000040;
+ --shadow-inner: inset 0 2px 4px 0 #0000000d;
+ --inset-shadow-xs: inset 0 1px #0000000d;
+ --inset-shadow-sm: inset 0 1px 1px #0000000d;
+ --inset-shadow: inset 0 2px 4px #0000000d;
+ --drop-shadow: 0 1px 2px #0000001a, 0 1px 1px #0000000f;
+ --drop-shadow-sm: 0 1px 1px #0000000d;
+ --drop-shadow-md: 0 4px 3px #00000012, 0 2px 2px #0000000f;
+ --drop-shadow-lg: 0 10px 8px #0000000a, 0 4px 3px #0000001a;
+ --drop-shadow-xl: 0 20px 13px #00000008, 0 8px 5px #00000014;
+ --drop-shadow-2xl: 0 25px 25px #00000026;
+ --spacing-px: 1px;
+ --spacing-0: 0px;
+ --spacing-0_5: .125rem;
+ --spacing-1: .25rem;
+ --spacing-1_5: .375rem;
+ --spacing-2: .5rem;
+ --spacing-2_5: .625rem;
+ --spacing-3: .75rem;
+ --spacing-3_5: .875rem;
+ --spacing-4: 1rem;
+ --spacing-5: 1.25rem;
+ --spacing-6: 1.5rem;
+ --spacing-7: 1.75rem;
+ --spacing-8: 2rem;
+ --spacing-9: 2.25rem;
+ --spacing-10: 2.5rem;
+ --spacing-11: 2.75rem;
+ --spacing-12: 3rem;
+ --spacing-14: 3.5rem;
+ --spacing-16: 4rem;
+ --spacing-20: 5rem;
+ --spacing-24: 6rem;
+ --spacing-28: 7rem;
+ --spacing-32: 8rem;
+ --spacing-36: 9rem;
+ --spacing-40: 10rem;
+ --spacing-44: 11rem;
+ --spacing-48: 12rem;
+ --spacing-52: 13rem;
+ --spacing-56: 14rem;
+ --spacing-60: 15rem;
+ --spacing-64: 16rem;
+ --spacing-72: 18rem;
+ --spacing-80: 20rem;
+ --spacing-96: 24rem;
+ --width-3xs: 16rem;
+ --width-2xs: 18rem;
+ --width-xs: 20rem;
+ --width-sm: 24rem;
+ --width-md: 28rem;
+ --width-lg: 32rem;
+ --width-xl: 36rem;
+ --width-2xl: 42rem;
+ --width-3xl: 48rem;
+ --width-4xl: 56rem;
+ --width-5xl: 64rem;
+ --width-6xl: 72rem;
+ --width-7xl: 80rem;
+ --width-prose: 65ch;
+ --font-family-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ --font-family-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
+ --font-family-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ --font-size-xs: .75rem;
+ --font-size-xs--line-height: 1rem;
+ --font-size-sm: .875rem;
+ --font-size-sm--line-height: 1.25rem;
+ --font-size-base: 1rem;
+ --font-size-base--line-height: 1.5rem;
+ --font-size-lg: 1.125rem;
+ --font-size-lg--line-height: 1.75rem;
+ --font-size-xl: 1.25rem;
+ --font-size-xl--line-height: 1.75rem;
+ --font-size-2xl: 1.5rem;
+ --font-size-2xl--line-height: 2rem;
+ --font-size-3xl: 1.875rem;
+ --font-size-3xl--line-height: 2.25rem;
+ --font-size-4xl: 2.25rem;
+ --font-size-4xl--line-height: 2.5rem;
+ --font-size-5xl: 3rem;
+ --font-size-5xl--line-height: 1;
+ --font-size-6xl: 3.75rem;
+ --font-size-6xl--line-height: 1;
+ --font-size-7xl: 4.5rem;
+ --font-size-7xl--line-height: 1;
+ --font-size-8xl: 6rem;
+ --font-size-8xl--line-height: 1;
+ --font-size-9xl: 8rem;
+ --font-size-9xl--line-height: 1;
+ --letter-spacing-tighter: -.05em;
+ --letter-spacing-tight: -.025em;
+ --letter-spacing-normal: 0em;
+ --letter-spacing-wide: .025em;
+ --letter-spacing-wider: .05em;
+ --letter-spacing-widest: .1em;
+ --line-height-none: 1;
+ --line-height-tight: 1.25;
+ --line-height-snug: 1.375;
+ --line-height-normal: 1.5;
+ --line-height-relaxed: 1.625;
+ --line-height-loose: 2;
+ --line-height-3: .75rem;
+ --line-height-4: 1rem;
+ --line-height-5: 1.25rem;
+ --line-height-6: 1.5rem;
+ --line-height-7: 1.75rem;
+ --line-height-8: 2rem;
+ --line-height-9: 2.25rem;
+ --line-height-10: 2.5rem;
+ --transition-timing-function: cubic-bezier(.4, 0, .2, 1);
+ --transition-timing-function-linear: linear;
+ --transition-timing-function-in: cubic-bezier(.4, 0, 1, 1);
+ --transition-timing-function-out: cubic-bezier(0, 0, .2, 1);
+ --transition-timing-function-in-out: cubic-bezier(.4, 0, .2, 1);
+}
+
+.w-4 {
+ width: 1rem;
+}
+
+.bg-red-500 {
+ background-color: #ef4444;
+}
+
+.shadow {
+ --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
+ --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+}
+
+@media (width >= 640px) {
+ .sm\\:flex {
+ display: flex;
+ }
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes ping {
+ 75%, 100% {
+ opacity: 0;
+ transform: scale(2);
+ }
+}
+
+@keyframes pulse {
+ 50% {
+ opacity: .5;
+ }
+}
+
+@keyframes bounce {
+ 0%, 100% {
+ animation-timing-function: cubic-bezier(.8, 0, 1, 1);
+ transform: translateY(-25%);
+ }
+
+ 50% {
+ animation-timing-function: cubic-bezier(0, 0, .2, 1);
+ transform: none;
+ }
+}
+
+@property --tw-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+
+@property --tw-shadow-colored {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+
+@property --tw-inset-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+
+@property --tw-inset-shadow-colored {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+
+@property --tw-ring-color {
+ syntax: "*";
+ inherits: false
+}
+
+@property --tw-ring-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+
+@property --tw-inset-ring-color {
+ syntax: "*";
+ inherits: false
+}
+
+@property --tw-inset-ring-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+
+@property --tw-ring-inset {
+ syntax: "*";
+ inherits: false
+}
+
+@property --tw-ring-offset-width {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+}
+
+@property --tw-ring-offset-color {
+ syntax: "*";
+ inherits: false;
+ initial-value: #fff;
+}
+
+@property --tw-ring-offset-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}"
+`;
diff --git a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap
new file mode 100644
index 000000000..bbb2c5f18
--- /dev/null
+++ b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap
@@ -0,0 +1,2234 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`getClassList 1`] = `
+[
+ "-bottom-0.5",
+ "-bottom-1",
+ "-bottom-3",
+ "-bottom-4",
+ "-end-0.5",
+ "-end-1",
+ "-end-3",
+ "-end-4",
+ "-indent-0.5",
+ "-indent-1",
+ "-indent-3",
+ "-indent-4",
+ "-inset-0.5",
+ "-inset-1",
+ "-inset-3",
+ "-inset-4",
+ "-inset-x-0.5",
+ "-inset-x-1",
+ "-inset-x-3",
+ "-inset-x-4",
+ "-inset-y-0.5",
+ "-inset-y-1",
+ "-inset-y-3",
+ "-inset-y-4",
+ "-left-0.5",
+ "-left-1",
+ "-left-3",
+ "-left-4",
+ "-m-0.5",
+ "-m-1",
+ "-m-3",
+ "-m-4",
+ "-mb-0.5",
+ "-mb-1",
+ "-mb-3",
+ "-mb-4",
+ "-me-0.5",
+ "-me-1",
+ "-me-3",
+ "-me-4",
+ "-ml-0.5",
+ "-ml-1",
+ "-ml-3",
+ "-ml-4",
+ "-mr-0.5",
+ "-mr-1",
+ "-mr-3",
+ "-mr-4",
+ "-ms-0.5",
+ "-ms-1",
+ "-ms-3",
+ "-ms-4",
+ "-mt-0.5",
+ "-mt-1",
+ "-mt-3",
+ "-mt-4",
+ "-mx-0.5",
+ "-mx-1",
+ "-mx-3",
+ "-mx-4",
+ "-my-0.5",
+ "-my-1",
+ "-my-3",
+ "-my-4",
+ "-order-1",
+ "-order-10",
+ "-order-11",
+ "-order-12",
+ "-order-2",
+ "-order-3",
+ "-order-4",
+ "-order-5",
+ "-order-6",
+ "-order-7",
+ "-order-8",
+ "-order-9",
+ "-right-0.5",
+ "-right-1",
+ "-right-3",
+ "-right-4",
+ "-rotate-0",
+ "-rotate-1",
+ "-rotate-12",
+ "-rotate-180",
+ "-rotate-2",
+ "-rotate-3",
+ "-rotate-45",
+ "-rotate-6",
+ "-rotate-90",
+ "-scale-0",
+ "-scale-100",
+ "-scale-105",
+ "-scale-110",
+ "-scale-125",
+ "-scale-150",
+ "-scale-200",
+ "-scale-50",
+ "-scale-75",
+ "-scale-90",
+ "-scale-95",
+ "-scale-x-0",
+ "-scale-x-100",
+ "-scale-x-105",
+ "-scale-x-110",
+ "-scale-x-125",
+ "-scale-x-150",
+ "-scale-x-200",
+ "-scale-x-50",
+ "-scale-x-75",
+ "-scale-x-90",
+ "-scale-x-95",
+ "-scale-y-0",
+ "-scale-y-100",
+ "-scale-y-105",
+ "-scale-y-110",
+ "-scale-y-125",
+ "-scale-y-150",
+ "-scale-y-200",
+ "-scale-y-50",
+ "-scale-y-75",
+ "-scale-y-90",
+ "-scale-y-95",
+ "-scroll-m-0.5",
+ "-scroll-m-1",
+ "-scroll-m-3",
+ "-scroll-m-4",
+ "-scroll-mb-0.5",
+ "-scroll-mb-1",
+ "-scroll-mb-3",
+ "-scroll-mb-4",
+ "-scroll-me-0.5",
+ "-scroll-me-1",
+ "-scroll-me-3",
+ "-scroll-me-4",
+ "-scroll-ml-0.5",
+ "-scroll-ml-1",
+ "-scroll-ml-3",
+ "-scroll-ml-4",
+ "-scroll-mr-0.5",
+ "-scroll-mr-1",
+ "-scroll-mr-3",
+ "-scroll-mr-4",
+ "-scroll-ms-0.5",
+ "-scroll-ms-1",
+ "-scroll-ms-3",
+ "-scroll-ms-4",
+ "-scroll-mt-0.5",
+ "-scroll-mt-1",
+ "-scroll-mt-3",
+ "-scroll-mt-4",
+ "-scroll-mx-0.5",
+ "-scroll-mx-1",
+ "-scroll-mx-3",
+ "-scroll-mx-4",
+ "-scroll-my-0.5",
+ "-scroll-my-1",
+ "-scroll-my-3",
+ "-scroll-my-4",
+ "-scroll-p-0.5",
+ "-scroll-p-1",
+ "-scroll-p-3",
+ "-scroll-p-4",
+ "-scroll-pb-0.5",
+ "-scroll-pb-1",
+ "-scroll-pb-3",
+ "-scroll-pb-4",
+ "-scroll-pe-0.5",
+ "-scroll-pe-1",
+ "-scroll-pe-3",
+ "-scroll-pe-4",
+ "-scroll-pl-0.5",
+ "-scroll-pl-1",
+ "-scroll-pl-3",
+ "-scroll-pl-4",
+ "-scroll-pr-0.5",
+ "-scroll-pr-1",
+ "-scroll-pr-3",
+ "-scroll-pr-4",
+ "-scroll-ps-0.5",
+ "-scroll-ps-1",
+ "-scroll-ps-3",
+ "-scroll-ps-4",
+ "-scroll-pt-0.5",
+ "-scroll-pt-1",
+ "-scroll-pt-3",
+ "-scroll-pt-4",
+ "-scroll-px-0.5",
+ "-scroll-px-1",
+ "-scroll-px-3",
+ "-scroll-px-4",
+ "-scroll-py-0.5",
+ "-scroll-py-1",
+ "-scroll-py-3",
+ "-scroll-py-4",
+ "-skew-0",
+ "-skew-1",
+ "-skew-12",
+ "-skew-2",
+ "-skew-3",
+ "-skew-6",
+ "-skew-x-0",
+ "-skew-x-1",
+ "-skew-x-12",
+ "-skew-x-2",
+ "-skew-x-3",
+ "-skew-x-6",
+ "-skew-y-0",
+ "-skew-y-1",
+ "-skew-y-12",
+ "-skew-y-2",
+ "-skew-y-3",
+ "-skew-y-6",
+ "-space-x-0.5",
+ "-space-x-1",
+ "-space-x-3",
+ "-space-x-4",
+ "-space-y-0.5",
+ "-space-y-1",
+ "-space-y-3",
+ "-space-y-4",
+ "-start-0.5",
+ "-start-1",
+ "-start-3",
+ "-start-4",
+ "-top-0.5",
+ "-top-1",
+ "-top-3",
+ "-top-4",
+ "-translate-0.5",
+ "-translate-1",
+ "-translate-3",
+ "-translate-4",
+ "-translate-x-0.5",
+ "-translate-x-1",
+ "-translate-x-3",
+ "-translate-x-4",
+ "-translate-y-0.5",
+ "-translate-y-1",
+ "-translate-y-3",
+ "-translate-y-4",
+ "-underline-offset-0",
+ "-underline-offset-1",
+ "-underline-offset-2",
+ "-underline-offset-4",
+ "-underline-offset-8",
+ "-z-0",
+ "-z-10",
+ "-z-20",
+ "-z-30",
+ "-z-40",
+ "-z-50",
+ "@container-normal",
+ "absolute",
+ "accent-current",
+ "accent-transparent",
+ "align-baseline",
+ "align-bottom",
+ "align-middle",
+ "align-sub",
+ "align-super",
+ "align-text-bottom",
+ "align-text-top",
+ "align-top",
+ "animate-none",
+ "antialiased",
+ "appearance-auto",
+ "appearance-none",
+ "aspect-auto",
+ "aspect-square",
+ "aspect-video",
+ "auto-cols-auto",
+ "auto-cols-fr",
+ "auto-cols-max",
+ "auto-cols-min",
+ "auto-rows-auto",
+ "auto-rows-fr",
+ "auto-rows-max",
+ "auto-rows-min",
+ "backdrop-brightness-0",
+ "backdrop-brightness-100",
+ "backdrop-brightness-105",
+ "backdrop-brightness-110",
+ "backdrop-brightness-125",
+ "backdrop-brightness-150",
+ "backdrop-brightness-200",
+ "backdrop-brightness-50",
+ "backdrop-brightness-75",
+ "backdrop-brightness-90",
+ "backdrop-brightness-95",
+ "backdrop-contrast-0",
+ "backdrop-contrast-100",
+ "backdrop-contrast-125",
+ "backdrop-contrast-150",
+ "backdrop-contrast-200",
+ "backdrop-contrast-50",
+ "backdrop-contrast-75",
+ "backdrop-grayscale",
+ "backdrop-grayscale-0",
+ "backdrop-grayscale-100",
+ "backdrop-grayscale-25",
+ "backdrop-grayscale-50",
+ "backdrop-grayscale-75",
+ "backdrop-hue-rotate-0",
+ "backdrop-hue-rotate-15",
+ "backdrop-hue-rotate-180",
+ "backdrop-hue-rotate-30",
+ "backdrop-hue-rotate-60",
+ "backdrop-hue-rotate-90",
+ "backdrop-invert",
+ "backdrop-invert-0",
+ "backdrop-invert-100",
+ "backdrop-invert-25",
+ "backdrop-invert-50",
+ "backdrop-invert-75",
+ "backdrop-opacity-0",
+ "backdrop-opacity-10",
+ "backdrop-opacity-100",
+ "backdrop-opacity-15",
+ "backdrop-opacity-20",
+ "backdrop-opacity-25",
+ "backdrop-opacity-30",
+ "backdrop-opacity-35",
+ "backdrop-opacity-40",
+ "backdrop-opacity-45",
+ "backdrop-opacity-5",
+ "backdrop-opacity-50",
+ "backdrop-opacity-55",
+ "backdrop-opacity-60",
+ "backdrop-opacity-65",
+ "backdrop-opacity-70",
+ "backdrop-opacity-75",
+ "backdrop-opacity-80",
+ "backdrop-opacity-85",
+ "backdrop-opacity-90",
+ "backdrop-opacity-95",
+ "backdrop-saturate-0",
+ "backdrop-saturate-100",
+ "backdrop-saturate-150",
+ "backdrop-saturate-200",
+ "backdrop-saturate-50",
+ "backdrop-sepia",
+ "backdrop-sepia-0",
+ "backdrop-sepia-100",
+ "backdrop-sepia-50",
+ "basis-0.5",
+ "basis-1",
+ "basis-3",
+ "basis-4",
+ "basis-4",
+ "basis-auto",
+ "basis-full",
+ "bg-auto",
+ "bg-blend-color",
+ "bg-blend-color-burn",
+ "bg-blend-color-dodge",
+ "bg-blend-darken",
+ "bg-blend-difference",
+ "bg-blend-exclusion",
+ "bg-blend-hard-light",
+ "bg-blend-hue",
+ "bg-blend-lighten",
+ "bg-blend-luminosity",
+ "bg-blend-multiply",
+ "bg-blend-normal",
+ "bg-blend-overlay",
+ "bg-blend-saturation",
+ "bg-blend-screen",
+ "bg-blend-soft-light",
+ "bg-bottom",
+ "bg-center",
+ "bg-clip-border",
+ "bg-clip-content",
+ "bg-clip-padding",
+ "bg-clip-text",
+ "bg-contain",
+ "bg-cover",
+ "bg-current",
+ "bg-fixed",
+ "bg-gradient-to-b",
+ "bg-gradient-to-bl",
+ "bg-gradient-to-br",
+ "bg-gradient-to-l",
+ "bg-gradient-to-r",
+ "bg-gradient-to-t",
+ "bg-gradient-to-tl",
+ "bg-gradient-to-tr",
+ "bg-inherit",
+ "bg-left",
+ "bg-left-bottom",
+ "bg-left-top",
+ "bg-local",
+ "bg-no-repeat",
+ "bg-none",
+ "bg-origin-border",
+ "bg-origin-content",
+ "bg-origin-padding",
+ "bg-repeat",
+ "bg-repeat-x",
+ "bg-repeat-y",
+ "bg-right",
+ "bg-right-bottom",
+ "bg-right-top",
+ "bg-round",
+ "bg-scroll",
+ "bg-space",
+ "bg-top",
+ "bg-transparent",
+ "bg-transparent",
+ "block",
+ "border",
+ "border-b",
+ "border-b-current",
+ "border-b-transparent",
+ "border-collapse",
+ "border-current",
+ "border-dashed",
+ "border-dotted",
+ "border-double",
+ "border-e",
+ "border-e-current",
+ "border-e-transparent",
+ "border-hidden",
+ "border-l",
+ "border-l-current",
+ "border-l-transparent",
+ "border-none",
+ "border-r",
+ "border-r-current",
+ "border-r-transparent",
+ "border-s",
+ "border-s-current",
+ "border-s-transparent",
+ "border-separate",
+ "border-solid",
+ "border-spacing-0.5",
+ "border-spacing-1",
+ "border-spacing-3",
+ "border-spacing-4",
+ "border-spacing-x-0.5",
+ "border-spacing-x-1",
+ "border-spacing-x-3",
+ "border-spacing-x-4",
+ "border-spacing-y-0.5",
+ "border-spacing-y-1",
+ "border-spacing-y-3",
+ "border-spacing-y-4",
+ "border-t",
+ "border-t-current",
+ "border-t-transparent",
+ "border-transparent",
+ "border-x",
+ "border-x-current",
+ "border-x-transparent",
+ "border-y",
+ "border-y-current",
+ "border-y-transparent",
+ "bottom-0.5",
+ "bottom-1",
+ "bottom-3",
+ "bottom-4",
+ "bottom-auto",
+ "bottom-full",
+ "box-border",
+ "box-content",
+ "box-decoration-clone",
+ "box-decoration-slice",
+ "break-after-all",
+ "break-after-auto",
+ "break-after-avoid",
+ "break-after-avoid-page",
+ "break-after-column",
+ "break-after-left",
+ "break-after-page",
+ "break-after-right",
+ "break-all",
+ "break-before-all",
+ "break-before-auto",
+ "break-before-avoid",
+ "break-before-avoid-page",
+ "break-before-column",
+ "break-before-left",
+ "break-before-page",
+ "break-before-right",
+ "break-inside-auto",
+ "break-inside-avoid",
+ "break-inside-avoid-column",
+ "break-inside-avoid-page",
+ "break-keep",
+ "break-normal",
+ "break-words",
+ "brightness-0",
+ "brightness-100",
+ "brightness-105",
+ "brightness-110",
+ "brightness-125",
+ "brightness-150",
+ "brightness-200",
+ "brightness-50",
+ "brightness-75",
+ "brightness-90",
+ "brightness-95",
+ "capitalize",
+ "caption-bottom",
+ "caption-top",
+ "caret-current",
+ "caret-transparent",
+ "clear-both",
+ "clear-end",
+ "clear-left",
+ "clear-none",
+ "clear-right",
+ "clear-start",
+ "col-auto",
+ "col-end-1",
+ "col-end-10",
+ "col-end-11",
+ "col-end-12",
+ "col-end-13",
+ "col-end-2",
+ "col-end-3",
+ "col-end-4",
+ "col-end-5",
+ "col-end-6",
+ "col-end-7",
+ "col-end-8",
+ "col-end-9",
+ "col-end-auto",
+ "col-span-1",
+ "col-span-10",
+ "col-span-11",
+ "col-span-12",
+ "col-span-2",
+ "col-span-3",
+ "col-span-4",
+ "col-span-5",
+ "col-span-6",
+ "col-span-7",
+ "col-span-8",
+ "col-span-9",
+ "col-span-full",
+ "col-start-1",
+ "col-start-10",
+ "col-start-11",
+ "col-start-12",
+ "col-start-13",
+ "col-start-2",
+ "col-start-3",
+ "col-start-4",
+ "col-start-5",
+ "col-start-6",
+ "col-start-7",
+ "col-start-8",
+ "col-start-9",
+ "col-start-auto",
+ "collapse",
+ "columns-1",
+ "columns-10",
+ "columns-11",
+ "columns-12",
+ "columns-2",
+ "columns-3",
+ "columns-4",
+ "columns-4",
+ "columns-5",
+ "columns-6",
+ "columns-7",
+ "columns-8",
+ "columns-9",
+ "columns-auto",
+ "contain-content",
+ "contain-inline-size",
+ "contain-layout",
+ "contain-none",
+ "contain-paint",
+ "contain-size",
+ "contain-strict",
+ "contain-style",
+ "content-around",
+ "content-baseline",
+ "content-between",
+ "content-center",
+ "content-end",
+ "content-evenly",
+ "content-none",
+ "content-normal",
+ "content-start",
+ "content-stretch",
+ "contents",
+ "contrast-0",
+ "contrast-100",
+ "contrast-125",
+ "contrast-150",
+ "contrast-200",
+ "contrast-50",
+ "contrast-75",
+ "cursor-alias",
+ "cursor-all-scroll",
+ "cursor-auto",
+ "cursor-cell",
+ "cursor-col-resize",
+ "cursor-context-menu",
+ "cursor-copy",
+ "cursor-crosshair",
+ "cursor-default",
+ "cursor-e-resize",
+ "cursor-ew-resize",
+ "cursor-grab",
+ "cursor-grabbing",
+ "cursor-help",
+ "cursor-move",
+ "cursor-n-resize",
+ "cursor-ne-resize",
+ "cursor-nesw-resize",
+ "cursor-no-drop",
+ "cursor-none",
+ "cursor-not-allowed",
+ "cursor-ns-resize",
+ "cursor-nw-resize",
+ "cursor-nwse-resize",
+ "cursor-pointer",
+ "cursor-progress",
+ "cursor-row-resize",
+ "cursor-s-resize",
+ "cursor-se-resize",
+ "cursor-sw-resize",
+ "cursor-text",
+ "cursor-vertical-text",
+ "cursor-w-resize",
+ "cursor-wait",
+ "cursor-zoom-in",
+ "cursor-zoom-out",
+ "decoration-0",
+ "decoration-1",
+ "decoration-2",
+ "decoration-auto",
+ "decoration-clone",
+ "decoration-current",
+ "decoration-dashed",
+ "decoration-dotted",
+ "decoration-double",
+ "decoration-from-font",
+ "decoration-slice",
+ "decoration-solid",
+ "decoration-transparent",
+ "decoration-wavy",
+ "delay-100",
+ "delay-1000",
+ "delay-150",
+ "delay-200",
+ "delay-300",
+ "delay-500",
+ "delay-700",
+ "delay-75",
+ "diagonal-fractions",
+ "divide-current",
+ "divide-dashed",
+ "divide-dotted",
+ "divide-double",
+ "divide-none",
+ "divide-solid",
+ "divide-transparent",
+ "divide-x",
+ "divide-x-0",
+ "divide-x-2",
+ "divide-x-4",
+ "divide-x-8",
+ "divide-x-reverse",
+ "divide-y",
+ "divide-y-0",
+ "divide-y-2",
+ "divide-y-4",
+ "divide-y-8",
+ "divide-y-reverse",
+ "duration-100",
+ "duration-1000",
+ "duration-150",
+ "duration-200",
+ "duration-300",
+ "duration-500",
+ "duration-700",
+ "duration-75",
+ "end-0.5",
+ "end-1",
+ "end-3",
+ "end-4",
+ "end-auto",
+ "end-full",
+ "fill-current",
+ "fill-transparent",
+ "fixed",
+ "flex-auto",
+ "flex-col",
+ "flex-col-reverse",
+ "flex-initial",
+ "flex-none",
+ "flex-nowrap",
+ "flex-row",
+ "flex-row-reverse",
+ "flex-wrap",
+ "flex-wrap-reverse",
+ "float-end",
+ "float-left",
+ "float-none",
+ "float-right",
+ "float-start",
+ "flow-root",
+ "font-black",
+ "font-bold",
+ "font-extrabold",
+ "font-extralight",
+ "font-light",
+ "font-medium",
+ "font-normal",
+ "font-semibold",
+ "font-thin",
+ "forced-color-adjust-auto",
+ "forced-color-adjust-none",
+ "from-0%",
+ "from-10%",
+ "from-100%",
+ "from-15%",
+ "from-20%",
+ "from-25%",
+ "from-30%",
+ "from-35%",
+ "from-40%",
+ "from-45%",
+ "from-5%",
+ "from-50%",
+ "from-55%",
+ "from-60%",
+ "from-65%",
+ "from-70%",
+ "from-75%",
+ "from-80%",
+ "from-85%",
+ "from-90%",
+ "from-95%",
+ "from-current",
+ "from-transparent",
+ "gap-0.5",
+ "gap-1",
+ "gap-3",
+ "gap-4",
+ "gap-x-0.5",
+ "gap-x-1",
+ "gap-x-3",
+ "gap-x-4",
+ "gap-y-0.5",
+ "gap-y-1",
+ "gap-y-3",
+ "gap-y-4",
+ "grayscale",
+ "grayscale-0",
+ "grayscale-100",
+ "grayscale-25",
+ "grayscale-50",
+ "grayscale-75",
+ "grid",
+ "grid-cols-1",
+ "grid-cols-10",
+ "grid-cols-11",
+ "grid-cols-12",
+ "grid-cols-2",
+ "grid-cols-3",
+ "grid-cols-4",
+ "grid-cols-5",
+ "grid-cols-6",
+ "grid-cols-7",
+ "grid-cols-8",
+ "grid-cols-9",
+ "grid-cols-none",
+ "grid-cols-subgrid",
+ "grid-flow-col",
+ "grid-flow-col-dense",
+ "grid-flow-dense",
+ "grid-flow-row",
+ "grid-flow-row-dense",
+ "grid-rows-1",
+ "grid-rows-10",
+ "grid-rows-11",
+ "grid-rows-12",
+ "grid-rows-2",
+ "grid-rows-3",
+ "grid-rows-4",
+ "grid-rows-5",
+ "grid-rows-6",
+ "grid-rows-7",
+ "grid-rows-8",
+ "grid-rows-9",
+ "grid-rows-none",
+ "grid-rows-subgrid",
+ "grow",
+ "grow-0",
+ "h-0.5",
+ "h-1",
+ "h-3",
+ "h-4",
+ "h-auto",
+ "h-dvh",
+ "h-fit",
+ "h-full",
+ "h-lvh",
+ "h-max",
+ "h-min",
+ "h-screen",
+ "h-svh",
+ "hidden",
+ "hue-rotate-0",
+ "hue-rotate-15",
+ "hue-rotate-180",
+ "hue-rotate-30",
+ "hue-rotate-60",
+ "hue-rotate-90",
+ "hyphens-auto",
+ "hyphens-manual",
+ "hyphens-none",
+ "indent-0.5",
+ "indent-1",
+ "indent-3",
+ "indent-4",
+ "inline",
+ "inline-block",
+ "inline-flex",
+ "inline-grid",
+ "inline-table",
+ "inset-0.5",
+ "inset-1",
+ "inset-3",
+ "inset-4",
+ "inset-auto",
+ "inset-full",
+ "inset-ring",
+ "inset-ring-0",
+ "inset-ring-1",
+ "inset-ring-2",
+ "inset-ring-4",
+ "inset-ring-8",
+ "inset-ring-current",
+ "inset-ring-transparent",
+ "inset-shadow",
+ "inset-shadow-current",
+ "inset-shadow-transparent",
+ "inset-x-0.5",
+ "inset-x-1",
+ "inset-x-3",
+ "inset-x-4",
+ "inset-x-auto",
+ "inset-x-full",
+ "inset-y-0.5",
+ "inset-y-1",
+ "inset-y-3",
+ "inset-y-4",
+ "inset-y-auto",
+ "inset-y-full",
+ "invert",
+ "invert-0",
+ "invert-100",
+ "invert-25",
+ "invert-50",
+ "invert-75",
+ "invisible",
+ "isolate",
+ "isolation-auto",
+ "italic",
+ "items-baseline",
+ "items-center",
+ "items-end",
+ "items-start",
+ "items-stretch",
+ "justify-around",
+ "justify-baseline",
+ "justify-between",
+ "justify-center",
+ "justify-end",
+ "justify-evenly",
+ "justify-items-center",
+ "justify-items-end",
+ "justify-items-normal",
+ "justify-items-start",
+ "justify-items-stretch",
+ "justify-normal",
+ "justify-self-auto",
+ "justify-self-center",
+ "justify-self-end",
+ "justify-self-start",
+ "justify-self-stretch",
+ "justify-start",
+ "justify-stretch",
+ "left-0.5",
+ "left-1",
+ "left-3",
+ "left-4",
+ "left-auto",
+ "left-full",
+ "line-clamp-1",
+ "line-clamp-2",
+ "line-clamp-3",
+ "line-clamp-4",
+ "line-clamp-5",
+ "line-clamp-6",
+ "line-clamp-none",
+ "line-through",
+ "lining-nums",
+ "list-decimal",
+ "list-disc",
+ "list-image-none",
+ "list-inside",
+ "list-item",
+ "list-none",
+ "list-outside",
+ "lowercase",
+ "m-0.5",
+ "m-1",
+ "m-3",
+ "m-4",
+ "m-auto",
+ "max-h-0.5",
+ "max-h-1",
+ "max-h-3",
+ "max-h-4",
+ "max-h-dvh",
+ "max-h-fit",
+ "max-h-full",
+ "max-h-lvh",
+ "max-h-max",
+ "max-h-min",
+ "max-h-none",
+ "max-h-screen",
+ "max-h-svh",
+ "max-w-0.5",
+ "max-w-1",
+ "max-w-3",
+ "max-w-4",
+ "max-w-4",
+ "max-w-fit",
+ "max-w-full",
+ "max-w-max",
+ "max-w-min",
+ "max-w-none",
+ "mb-0.5",
+ "mb-1",
+ "mb-3",
+ "mb-4",
+ "mb-auto",
+ "me-0.5",
+ "me-1",
+ "me-3",
+ "me-4",
+ "me-auto",
+ "min-h-0.5",
+ "min-h-1",
+ "min-h-3",
+ "min-h-4",
+ "min-h-auto",
+ "min-h-dvh",
+ "min-h-fit",
+ "min-h-full",
+ "min-h-lvh",
+ "min-h-max",
+ "min-h-min",
+ "min-h-screen",
+ "min-h-svh",
+ "min-w-0.5",
+ "min-w-1",
+ "min-w-3",
+ "min-w-4",
+ "min-w-4",
+ "min-w-auto",
+ "min-w-fit",
+ "min-w-full",
+ "min-w-max",
+ "min-w-min",
+ "mix-blend-color",
+ "mix-blend-color-burn",
+ "mix-blend-color-dodge",
+ "mix-blend-darken",
+ "mix-blend-difference",
+ "mix-blend-exclusion",
+ "mix-blend-hard-light",
+ "mix-blend-hue",
+ "mix-blend-lighten",
+ "mix-blend-luminosity",
+ "mix-blend-multiply",
+ "mix-blend-normal",
+ "mix-blend-overlay",
+ "mix-blend-plus-darker",
+ "mix-blend-plus-lighter",
+ "mix-blend-saturation",
+ "mix-blend-screen",
+ "mix-blend-soft-light",
+ "ml-0.5",
+ "ml-1",
+ "ml-3",
+ "ml-4",
+ "ml-auto",
+ "mr-0.5",
+ "mr-1",
+ "mr-3",
+ "mr-4",
+ "mr-auto",
+ "ms-0.5",
+ "ms-1",
+ "ms-3",
+ "ms-4",
+ "ms-auto",
+ "mt-0.5",
+ "mt-1",
+ "mt-3",
+ "mt-4",
+ "mt-auto",
+ "mx-0.5",
+ "mx-1",
+ "mx-3",
+ "mx-4",
+ "mx-auto",
+ "my-0.5",
+ "my-1",
+ "my-3",
+ "my-4",
+ "my-auto",
+ "no-underline",
+ "normal-case",
+ "normal-nums",
+ "not-italic",
+ "not-sr-only",
+ "object-bottom",
+ "object-center",
+ "object-contain",
+ "object-cover",
+ "object-fill",
+ "object-left",
+ "object-left-bottom",
+ "object-left-top",
+ "object-none",
+ "object-right",
+ "object-right-bottom",
+ "object-right-top",
+ "object-scale-down",
+ "object-top",
+ "oldstyle-nums",
+ "opacity-0",
+ "opacity-10",
+ "opacity-100",
+ "opacity-15",
+ "opacity-20",
+ "opacity-25",
+ "opacity-30",
+ "opacity-35",
+ "opacity-40",
+ "opacity-45",
+ "opacity-5",
+ "opacity-50",
+ "opacity-55",
+ "opacity-60",
+ "opacity-65",
+ "opacity-70",
+ "opacity-75",
+ "opacity-80",
+ "opacity-85",
+ "opacity-90",
+ "opacity-95",
+ "order-1",
+ "order-10",
+ "order-11",
+ "order-12",
+ "order-2",
+ "order-3",
+ "order-4",
+ "order-5",
+ "order-6",
+ "order-7",
+ "order-8",
+ "order-9",
+ "order-first",
+ "order-last",
+ "order-none",
+ "ordinal",
+ "origin-bottom",
+ "origin-bottom-left",
+ "origin-bottom-right",
+ "origin-center",
+ "origin-left",
+ "origin-right",
+ "origin-top",
+ "origin-top-left",
+ "origin-top-right",
+ "outline",
+ "outline-0",
+ "outline-1",
+ "outline-2",
+ "outline-4",
+ "outline-8",
+ "outline-current",
+ "outline-dashed",
+ "outline-dotted",
+ "outline-double",
+ "outline-none",
+ "outline-offset-0",
+ "outline-offset-1",
+ "outline-offset-2",
+ "outline-offset-4",
+ "outline-offset-8",
+ "outline-solid",
+ "outline-transparent",
+ "overflow-auto",
+ "overflow-clip",
+ "overflow-hidden",
+ "overflow-scroll",
+ "overflow-visible",
+ "overflow-x-auto",
+ "overflow-x-clip",
+ "overflow-x-hidden",
+ "overflow-x-scroll",
+ "overflow-x-visible",
+ "overflow-y-auto",
+ "overflow-y-clip",
+ "overflow-y-hidden",
+ "overflow-y-scroll",
+ "overflow-y-visible",
+ "overline",
+ "overscroll-auto",
+ "overscroll-contain",
+ "overscroll-none",
+ "overscroll-x-auto",
+ "overscroll-x-contain",
+ "overscroll-x-none",
+ "overscroll-y-auto",
+ "overscroll-y-contain",
+ "overscroll-y-none",
+ "p-0.5",
+ "p-1",
+ "p-3",
+ "p-4",
+ "pb-0.5",
+ "pb-1",
+ "pb-3",
+ "pb-4",
+ "pe-0.5",
+ "pe-1",
+ "pe-3",
+ "pe-4",
+ "pl-0.5",
+ "pl-1",
+ "pl-3",
+ "pl-4",
+ "place-content-around",
+ "place-content-baseline",
+ "place-content-between",
+ "place-content-center",
+ "place-content-end",
+ "place-content-evenly",
+ "place-content-start",
+ "place-content-stretch",
+ "place-items-baseline",
+ "place-items-center",
+ "place-items-end",
+ "place-items-start",
+ "place-items-stretch",
+ "place-self-auto",
+ "place-self-center",
+ "place-self-end",
+ "place-self-start",
+ "place-self-stretch",
+ "placeholder-current",
+ "placeholder-transparent",
+ "pointer-events-auto",
+ "pointer-events-none",
+ "pr-0.5",
+ "pr-1",
+ "pr-3",
+ "pr-4",
+ "proportional-nums",
+ "ps-0.5",
+ "ps-1",
+ "ps-3",
+ "ps-4",
+ "pt-0.5",
+ "pt-1",
+ "pt-3",
+ "pt-4",
+ "px-0.5",
+ "px-1",
+ "px-3",
+ "px-4",
+ "py-0.5",
+ "py-1",
+ "py-3",
+ "py-4",
+ "relative",
+ "resize-both",
+ "resize-none",
+ "resize-x",
+ "resize-y",
+ "right-0.5",
+ "right-1",
+ "right-3",
+ "right-4",
+ "right-auto",
+ "right-full",
+ "ring",
+ "ring-0",
+ "ring-1",
+ "ring-2",
+ "ring-4",
+ "ring-8",
+ "ring-current",
+ "ring-inset",
+ "ring-offset-0",
+ "ring-offset-1",
+ "ring-offset-2",
+ "ring-offset-4",
+ "ring-offset-8",
+ "ring-offset-current",
+ "ring-offset-transparent",
+ "ring-transparent",
+ "rotate-0",
+ "rotate-1",
+ "rotate-12",
+ "rotate-180",
+ "rotate-2",
+ "rotate-3",
+ "rotate-45",
+ "rotate-6",
+ "rotate-90",
+ "row-auto",
+ "row-end-1",
+ "row-end-10",
+ "row-end-11",
+ "row-end-12",
+ "row-end-13",
+ "row-end-2",
+ "row-end-3",
+ "row-end-4",
+ "row-end-5",
+ "row-end-6",
+ "row-end-7",
+ "row-end-8",
+ "row-end-9",
+ "row-end-auto",
+ "row-span-1",
+ "row-span-10",
+ "row-span-11",
+ "row-span-12",
+ "row-span-2",
+ "row-span-3",
+ "row-span-4",
+ "row-span-5",
+ "row-span-6",
+ "row-span-7",
+ "row-span-8",
+ "row-span-9",
+ "row-span-full",
+ "row-start-1",
+ "row-start-10",
+ "row-start-11",
+ "row-start-12",
+ "row-start-13",
+ "row-start-2",
+ "row-start-3",
+ "row-start-4",
+ "row-start-5",
+ "row-start-6",
+ "row-start-7",
+ "row-start-8",
+ "row-start-9",
+ "row-start-auto",
+ "saturate-0",
+ "saturate-100",
+ "saturate-150",
+ "saturate-200",
+ "saturate-50",
+ "scale-0",
+ "scale-100",
+ "scale-105",
+ "scale-110",
+ "scale-125",
+ "scale-150",
+ "scale-200",
+ "scale-50",
+ "scale-75",
+ "scale-90",
+ "scale-95",
+ "scale-x-0",
+ "scale-x-100",
+ "scale-x-105",
+ "scale-x-110",
+ "scale-x-125",
+ "scale-x-150",
+ "scale-x-200",
+ "scale-x-50",
+ "scale-x-75",
+ "scale-x-90",
+ "scale-x-95",
+ "scale-y-0",
+ "scale-y-100",
+ "scale-y-105",
+ "scale-y-110",
+ "scale-y-125",
+ "scale-y-150",
+ "scale-y-200",
+ "scale-y-50",
+ "scale-y-75",
+ "scale-y-90",
+ "scale-y-95",
+ "scroll-auto",
+ "scroll-m-0.5",
+ "scroll-m-1",
+ "scroll-m-3",
+ "scroll-m-4",
+ "scroll-mb-0.5",
+ "scroll-mb-1",
+ "scroll-mb-3",
+ "scroll-mb-4",
+ "scroll-me-0.5",
+ "scroll-me-1",
+ "scroll-me-3",
+ "scroll-me-4",
+ "scroll-ml-0.5",
+ "scroll-ml-1",
+ "scroll-ml-3",
+ "scroll-ml-4",
+ "scroll-mr-0.5",
+ "scroll-mr-1",
+ "scroll-mr-3",
+ "scroll-mr-4",
+ "scroll-ms-0.5",
+ "scroll-ms-1",
+ "scroll-ms-3",
+ "scroll-ms-4",
+ "scroll-mt-0.5",
+ "scroll-mt-1",
+ "scroll-mt-3",
+ "scroll-mt-4",
+ "scroll-mx-0.5",
+ "scroll-mx-1",
+ "scroll-mx-3",
+ "scroll-mx-4",
+ "scroll-my-0.5",
+ "scroll-my-1",
+ "scroll-my-3",
+ "scroll-my-4",
+ "scroll-p-0.5",
+ "scroll-p-1",
+ "scroll-p-3",
+ "scroll-p-4",
+ "scroll-pb-0.5",
+ "scroll-pb-1",
+ "scroll-pb-3",
+ "scroll-pb-4",
+ "scroll-pe-0.5",
+ "scroll-pe-1",
+ "scroll-pe-3",
+ "scroll-pe-4",
+ "scroll-pl-0.5",
+ "scroll-pl-1",
+ "scroll-pl-3",
+ "scroll-pl-4",
+ "scroll-pr-0.5",
+ "scroll-pr-1",
+ "scroll-pr-3",
+ "scroll-pr-4",
+ "scroll-ps-0.5",
+ "scroll-ps-1",
+ "scroll-ps-3",
+ "scroll-ps-4",
+ "scroll-pt-0.5",
+ "scroll-pt-1",
+ "scroll-pt-3",
+ "scroll-pt-4",
+ "scroll-px-0.5",
+ "scroll-px-1",
+ "scroll-px-3",
+ "scroll-px-4",
+ "scroll-py-0.5",
+ "scroll-py-1",
+ "scroll-py-3",
+ "scroll-py-4",
+ "scroll-smooth",
+ "select-all",
+ "select-auto",
+ "select-none",
+ "select-text",
+ "self-auto",
+ "self-baseline",
+ "self-center",
+ "self-end",
+ "self-start",
+ "self-stretch",
+ "sepia",
+ "sepia-0",
+ "sepia-100",
+ "sepia-50",
+ "shadow",
+ "shadow-current",
+ "shadow-transparent",
+ "shrink",
+ "shrink-0",
+ "size-0.5",
+ "size-1",
+ "size-3",
+ "size-4",
+ "size-auto",
+ "size-fit",
+ "size-full",
+ "size-max",
+ "size-min",
+ "skew-0",
+ "skew-1",
+ "skew-12",
+ "skew-2",
+ "skew-3",
+ "skew-6",
+ "skew-x-0",
+ "skew-x-1",
+ "skew-x-12",
+ "skew-x-2",
+ "skew-x-3",
+ "skew-x-6",
+ "skew-y-0",
+ "skew-y-1",
+ "skew-y-12",
+ "skew-y-2",
+ "skew-y-3",
+ "skew-y-6",
+ "slashed-zero",
+ "snap-align-none",
+ "snap-always",
+ "snap-both",
+ "snap-center",
+ "snap-end",
+ "snap-mandatory",
+ "snap-none",
+ "snap-normal",
+ "snap-proximity",
+ "snap-start",
+ "snap-x",
+ "snap-y",
+ "space-x-0.5",
+ "space-x-1",
+ "space-x-3",
+ "space-x-4",
+ "space-x-reverse",
+ "space-y-0.5",
+ "space-y-1",
+ "space-y-3",
+ "space-y-4",
+ "space-y-reverse",
+ "sr-only",
+ "stacked-fractions",
+ "start-0.5",
+ "start-1",
+ "start-3",
+ "start-4",
+ "start-auto",
+ "start-full",
+ "static",
+ "sticky",
+ "stroke-0",
+ "stroke-1",
+ "stroke-2",
+ "stroke-3",
+ "stroke-current",
+ "stroke-none",
+ "stroke-transparent",
+ "subpixel-antialiased",
+ "table",
+ "table-auto",
+ "table-caption",
+ "table-cell",
+ "table-column",
+ "table-column-group",
+ "table-fixed",
+ "table-footer-group",
+ "table-header-group",
+ "table-row",
+ "table-row-group",
+ "tabular-nums",
+ "text-balance",
+ "text-center",
+ "text-clip",
+ "text-current",
+ "text-ellipsis",
+ "text-end",
+ "text-justify",
+ "text-left",
+ "text-nowrap",
+ "text-pretty",
+ "text-right",
+ "text-start",
+ "text-transparent",
+ "text-wrap",
+ "text-xs",
+ "to-0%",
+ "to-10%",
+ "to-100%",
+ "to-15%",
+ "to-20%",
+ "to-25%",
+ "to-30%",
+ "to-35%",
+ "to-40%",
+ "to-45%",
+ "to-5%",
+ "to-50%",
+ "to-55%",
+ "to-60%",
+ "to-65%",
+ "to-70%",
+ "to-75%",
+ "to-80%",
+ "to-85%",
+ "to-90%",
+ "to-95%",
+ "to-current",
+ "to-transparent",
+ "top-0.5",
+ "top-1",
+ "top-3",
+ "top-4",
+ "top-auto",
+ "top-full",
+ "touch-auto",
+ "touch-manipulation",
+ "touch-none",
+ "touch-pan-down",
+ "touch-pan-left",
+ "touch-pan-right",
+ "touch-pan-up",
+ "touch-pan-x",
+ "touch-pan-y",
+ "touch-pinch-zoom",
+ "transform",
+ "transform-cpu",
+ "transform-gpu",
+ "transform-none",
+ "transition",
+ "transition-all",
+ "transition-colors",
+ "transition-none",
+ "transition-opacity",
+ "transition-shadow",
+ "transition-transform",
+ "translate-0.5",
+ "translate-1",
+ "translate-3",
+ "translate-4",
+ "translate-full",
+ "translate-x-0.5",
+ "translate-x-1",
+ "translate-x-3",
+ "translate-x-4",
+ "translate-x-full",
+ "translate-y-0.5",
+ "translate-y-1",
+ "translate-y-3",
+ "translate-y-4",
+ "translate-y-full",
+ "truncate",
+ "underline",
+ "underline-offset-0",
+ "underline-offset-1",
+ "underline-offset-2",
+ "underline-offset-4",
+ "underline-offset-8",
+ "underline-offset-auto",
+ "uppercase",
+ "via-0%",
+ "via-10%",
+ "via-100%",
+ "via-15%",
+ "via-20%",
+ "via-25%",
+ "via-30%",
+ "via-35%",
+ "via-40%",
+ "via-45%",
+ "via-5%",
+ "via-50%",
+ "via-55%",
+ "via-60%",
+ "via-65%",
+ "via-70%",
+ "via-75%",
+ "via-80%",
+ "via-85%",
+ "via-90%",
+ "via-95%",
+ "via-current",
+ "via-none",
+ "via-transparent",
+ "visible",
+ "w-0.5",
+ "w-1",
+ "w-3",
+ "w-4",
+ "w-4",
+ "w-auto",
+ "w-dvw",
+ "w-fit",
+ "w-full",
+ "w-lvw",
+ "w-max",
+ "w-min",
+ "w-screen",
+ "w-svw",
+ "whitespace-break-spaces",
+ "whitespace-normal",
+ "whitespace-nowrap",
+ "whitespace-pre",
+ "whitespace-pre-line",
+ "whitespace-pre-wrap",
+ "will-change-auto",
+ "will-change-contents",
+ "will-change-scroll",
+ "will-change-transform",
+ "z-0",
+ "z-10",
+ "z-20",
+ "z-30",
+ "z-40",
+ "z-50",
+ "z-auto",
+]
+`;
+
+exports[`getVariants 1`] = `
+[
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "force",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "*",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": true,
+ "name": "not",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": true,
+ "name": "group",
+ "selectors": [Function],
+ "values": [
+ "not",
+ "group",
+ "peer",
+ "first",
+ "last",
+ "only",
+ "odd",
+ "even",
+ "first-of-type",
+ "last-of-type",
+ "only-of-type",
+ "visited",
+ "target",
+ "open",
+ "default",
+ "checked",
+ "indeterminate",
+ "placeholder-shown",
+ "autofill",
+ "optional",
+ "required",
+ "valid",
+ "invalid",
+ "in-range",
+ "out-of-range",
+ "read-only",
+ "empty",
+ "focus-within",
+ "hover",
+ "focus",
+ "focus-visible",
+ "active",
+ "enabled",
+ "disabled",
+ "has",
+ "aria",
+ "data",
+ "ltr",
+ "rtl",
+ ],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": true,
+ "name": "peer",
+ "selectors": [Function],
+ "values": [
+ "not",
+ "group",
+ "peer",
+ "first",
+ "last",
+ "only",
+ "odd",
+ "even",
+ "first-of-type",
+ "last-of-type",
+ "only-of-type",
+ "visited",
+ "target",
+ "open",
+ "default",
+ "checked",
+ "indeterminate",
+ "placeholder-shown",
+ "autofill",
+ "optional",
+ "required",
+ "valid",
+ "invalid",
+ "in-range",
+ "out-of-range",
+ "read-only",
+ "empty",
+ "focus-within",
+ "hover",
+ "focus",
+ "focus-visible",
+ "active",
+ "enabled",
+ "disabled",
+ "has",
+ "aria",
+ "data",
+ "ltr",
+ "rtl",
+ ],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "first-letter",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "first-line",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "marker",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "selection",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "file",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "placeholder",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "backdrop",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "before",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "after",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "first",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "last",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "only",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "odd",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "even",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "first-of-type",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "last-of-type",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "only-of-type",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "visited",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "target",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "open",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "default",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "checked",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "indeterminate",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "placeholder-shown",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "autofill",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "optional",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "required",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "valid",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "invalid",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "in-range",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "out-of-range",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "read-only",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "empty",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "focus-within",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "hover",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "focus",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "focus-visible",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "active",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "enabled",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "disabled",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": true,
+ "name": "has",
+ "selectors": [Function],
+ "values": [
+ "not",
+ "group",
+ "peer",
+ "first",
+ "last",
+ "only",
+ "odd",
+ "even",
+ "first-of-type",
+ "last-of-type",
+ "only-of-type",
+ "visited",
+ "target",
+ "open",
+ "default",
+ "checked",
+ "indeterminate",
+ "placeholder-shown",
+ "autofill",
+ "optional",
+ "required",
+ "valid",
+ "invalid",
+ "in-range",
+ "out-of-range",
+ "read-only",
+ "empty",
+ "focus-within",
+ "hover",
+ "focus",
+ "focus-visible",
+ "active",
+ "enabled",
+ "disabled",
+ "has",
+ "aria",
+ "data",
+ "ltr",
+ "rtl",
+ ],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": true,
+ "name": "aria",
+ "selectors": [Function],
+ "values": [
+ "busy",
+ "checked",
+ "disabled",
+ "expanded",
+ "hidden",
+ "pressed",
+ "readonly",
+ "required",
+ "selected",
+ ],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": true,
+ "name": "data",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": true,
+ "name": "supports",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "motion-safe",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "motion-reduce",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "contrast-more",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "contrast-less",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": true,
+ "name": "max",
+ "selectors": [Function],
+ "values": [
+ "sm",
+ ],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "sm",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": true,
+ "name": "min",
+ "selectors": [Function],
+ "values": [
+ "sm",
+ ],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": true,
+ "name": "@max",
+ "selectors": [Function],
+ "values": [
+ "4",
+ ],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": true,
+ "name": "@",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": true,
+ "name": "@min",
+ "selectors": [Function],
+ "values": [
+ "4",
+ ],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "portrait",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "landscape",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "ltr",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "rtl",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "dark",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "print",
+ "selectors": [Function],
+ "values": [],
+ },
+ {
+ "hasDash": true,
+ "isArbitrary": false,
+ "name": "forced-colors",
+ "selectors": [Function],
+ "values": [],
+ },
+]
+`;
diff --git a/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap b/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap
new file mode 100644
index 000000000..fcd970cf0
--- /dev/null
+++ b/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap
@@ -0,0 +1,1024 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`border-* 1`] = `
+":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --color-red-500: #ef4444;
+}
+
+.border {
+ border-style: var(--tw-border-style);
+ border-width: 1px;
+}
+
+.border-0 {
+ border-style: var(--tw-border-style);
+ border-width: 0;
+}
+
+.border-123 {
+ border-style: var(--tw-border-style);
+ border-width: 123px;
+}
+
+.border-2 {
+ border-style: var(--tw-border-style);
+ border-width: 2px;
+}
+
+.border-4 {
+ border-style: var(--tw-border-style);
+ border-width: 4px;
+}
+
+.border-\\[12px\\] {
+ border-style: var(--tw-border-style);
+ border-width: 12px;
+}
+
+.border-\\[length\\:--my-width\\], .border-\\[line-width\\:--my-width\\] {
+ border-style: var(--tw-border-style);
+ border-width: var(--my-width);
+}
+
+.border-\\[medium\\] {
+ border-style: var(--tw-border-style);
+ border-width: medium;
+}
+
+.border-\\[thick\\] {
+ border-style: var(--tw-border-style);
+ border-width: thick;
+}
+
+.border-\\[thin\\] {
+ border-style: var(--tw-border-style);
+ border-width: thin;
+}
+
+.border-\\[\\#0088cc\\] {
+ border-color: #08c;
+}
+
+.border-\\[\\#0088cc\\]\\/50 {
+ border-color: #0088cc80;
+}
+
+.border-\\[--my-color\\] {
+ border-color: var(--my-color);
+}
+
+.border-\\[--my-color\\]\\/50 {
+ border-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-\\[color\\:--my-color\\] {
+ border-color: var(--my-color);
+}
+
+.border-\\[color\\:--my-color\\]\\/50 {
+ border-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-current {
+ border-color: currentColor;
+}
+
+.border-current\\/50 {
+ border-color: color-mix(in srgb, currentColor 50%, transparent);
+}
+
+.border-red-500 {
+ border-color: #ef4444;
+}
+
+.border-red-500\\/50 {
+ border-color: #ef444480;
+}
+
+.border-transparent {
+ border-color: #0000;
+}
+
+@property --tw-border-style {
+ syntax: "";
+ inherits: false;
+ initial-value: solid;
+}"
+`;
+
+exports[`border-b-* 1`] = `
+":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --color-red-500: #ef4444;
+}
+
+.border-b {
+ border-style: var(--tw-border-style);
+ border-bottom-width: 1px;
+}
+
+.border-b-0 {
+ border-style: var(--tw-border-style);
+ border-bottom-width: 0;
+}
+
+.border-b-123 {
+ border-style: var(--tw-border-style);
+ border-bottom-width: 123px;
+}
+
+.border-b-2 {
+ border-style: var(--tw-border-style);
+ border-bottom-width: 2px;
+}
+
+.border-b-4 {
+ border-style: var(--tw-border-style);
+ border-bottom-width: 4px;
+}
+
+.border-b-\\[12px\\] {
+ border-style: var(--tw-border-style);
+ border-bottom-width: 12px;
+}
+
+.border-b-\\[length\\:--my-width\\], .border-b-\\[line-width\\:--my-width\\] {
+ border-style: var(--tw-border-style);
+ border-bottom-width: var(--my-width);
+}
+
+.border-b-\\[medium\\] {
+ border-style: var(--tw-border-style);
+ border-bottom-width: medium;
+}
+
+.border-b-\\[thick\\] {
+ border-style: var(--tw-border-style);
+ border-bottom-width: thick;
+}
+
+.border-b-\\[thin\\] {
+ border-style: var(--tw-border-style);
+ border-bottom-width: thin;
+}
+
+.border-b-\\[\\#0088cc\\] {
+ border-bottom-color: #08c;
+}
+
+.border-b-\\[\\#0088cc\\]\\/50 {
+ border-bottom-color: #0088cc80;
+}
+
+.border-b-\\[--my-color\\] {
+ border-bottom-color: var(--my-color);
+}
+
+.border-b-\\[--my-color\\]\\/50 {
+ border-bottom-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-b-\\[color\\:--my-color\\] {
+ border-bottom-color: var(--my-color);
+}
+
+.border-b-\\[color\\:--my-color\\]\\/50 {
+ border-bottom-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-b-current {
+ border-bottom-color: currentColor;
+}
+
+.border-b-current\\/50 {
+ border-bottom-color: color-mix(in srgb, currentColor 50%, transparent);
+}
+
+.border-b-red-500 {
+ border-bottom-color: #ef4444;
+}
+
+.border-b-red-500\\/50 {
+ border-bottom-color: #ef444480;
+}
+
+.border-b-transparent {
+ border-bottom-color: #0000;
+}
+
+@property --tw-border-style {
+ syntax: "";
+ inherits: false;
+ initial-value: solid;
+}"
+`;
+
+exports[`border-e-* 1`] = `
+":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --color-red-500: #ef4444;
+}
+
+.border-e {
+ border-style: var(--tw-border-style);
+ border-inline-end-width: 1px;
+}
+
+.border-e-0 {
+ border-style: var(--tw-border-style);
+ border-inline-end-width: 0;
+}
+
+.border-e-123 {
+ border-style: var(--tw-border-style);
+ border-inline-end-width: 123px;
+}
+
+.border-e-2 {
+ border-style: var(--tw-border-style);
+ border-inline-end-width: 2px;
+}
+
+.border-e-4 {
+ border-style: var(--tw-border-style);
+ border-inline-end-width: 4px;
+}
+
+.border-e-\\[12px\\] {
+ border-style: var(--tw-border-style);
+ border-inline-end-width: 12px;
+}
+
+.border-e-\\[length\\:--my-width\\], .border-e-\\[line-width\\:--my-width\\] {
+ border-style: var(--tw-border-style);
+ border-inline-end-width: var(--my-width);
+}
+
+.border-e-\\[medium\\] {
+ border-style: var(--tw-border-style);
+ border-inline-end-width: medium;
+}
+
+.border-e-\\[thick\\] {
+ border-style: var(--tw-border-style);
+ border-inline-end-width: thick;
+}
+
+.border-e-\\[thin\\] {
+ border-style: var(--tw-border-style);
+ border-inline-end-width: thin;
+}
+
+.border-e-\\[\\#0088cc\\] {
+ border-inline-end-color: #08c;
+}
+
+.border-e-\\[\\#0088cc\\]\\/50 {
+ border-inline-end-color: #0088cc80;
+}
+
+.border-e-\\[--my-color\\] {
+ border-inline-end-color: var(--my-color);
+}
+
+.border-e-\\[--my-color\\]\\/50 {
+ border-inline-end-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-e-\\[color\\:--my-color\\] {
+ border-inline-end-color: var(--my-color);
+}
+
+.border-e-\\[color\\:--my-color\\]\\/50 {
+ border-inline-end-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-e-current {
+ border-inline-end-color: currentColor;
+}
+
+.border-e-current\\/50 {
+ border-inline-end-color: color-mix(in srgb, currentColor 50%, transparent);
+}
+
+.border-e-red-500 {
+ border-inline-end-color: #ef4444;
+}
+
+.border-e-red-500\\/50 {
+ border-inline-end-color: #ef444480;
+}
+
+.border-e-transparent {
+ border-inline-end-color: #0000;
+}
+
+@property --tw-border-style {
+ syntax: "";
+ inherits: false;
+ initial-value: solid;
+}"
+`;
+
+exports[`border-l-* 1`] = `
+":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --color-red-500: #ef4444;
+}
+
+.border-l {
+ border-style: var(--tw-border-style);
+ border-left-width: 1px;
+}
+
+.border-l-0 {
+ border-style: var(--tw-border-style);
+ border-left-width: 0;
+}
+
+.border-l-123 {
+ border-style: var(--tw-border-style);
+ border-left-width: 123px;
+}
+
+.border-l-2 {
+ border-style: var(--tw-border-style);
+ border-left-width: 2px;
+}
+
+.border-l-4 {
+ border-style: var(--tw-border-style);
+ border-left-width: 4px;
+}
+
+.border-l-\\[12px\\] {
+ border-style: var(--tw-border-style);
+ border-left-width: 12px;
+}
+
+.border-l-\\[length\\:--my-width\\], .border-l-\\[line-width\\:--my-width\\] {
+ border-style: var(--tw-border-style);
+ border-left-width: var(--my-width);
+}
+
+.border-l-\\[medium\\] {
+ border-style: var(--tw-border-style);
+ border-left-width: medium;
+}
+
+.border-l-\\[thick\\] {
+ border-style: var(--tw-border-style);
+ border-left-width: thick;
+}
+
+.border-l-\\[thin\\] {
+ border-style: var(--tw-border-style);
+ border-left-width: thin;
+}
+
+.border-l-\\[\\#0088cc\\] {
+ border-left-color: #08c;
+}
+
+.border-l-\\[\\#0088cc\\]\\/50 {
+ border-left-color: #0088cc80;
+}
+
+.border-l-\\[--my-color\\] {
+ border-left-color: var(--my-color);
+}
+
+.border-l-\\[--my-color\\]\\/50 {
+ border-left-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-l-\\[color\\:--my-color\\] {
+ border-left-color: var(--my-color);
+}
+
+.border-l-\\[color\\:--my-color\\]\\/50 {
+ border-left-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-l-current {
+ border-left-color: currentColor;
+}
+
+.border-l-current\\/50 {
+ border-left-color: color-mix(in srgb, currentColor 50%, transparent);
+}
+
+.border-l-red-500 {
+ border-left-color: #ef4444;
+}
+
+.border-l-red-500\\/50 {
+ border-left-color: #ef444480;
+}
+
+.border-l-transparent {
+ border-left-color: #0000;
+}
+
+@property --tw-border-style {
+ syntax: "";
+ inherits: false;
+ initial-value: solid;
+}"
+`;
+
+exports[`border-r-* 1`] = `
+":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --color-red-500: #ef4444;
+}
+
+.border-r {
+ border-style: var(--tw-border-style);
+ border-right-width: 1px;
+}
+
+.border-r-0 {
+ border-style: var(--tw-border-style);
+ border-right-width: 0;
+}
+
+.border-r-123 {
+ border-style: var(--tw-border-style);
+ border-right-width: 123px;
+}
+
+.border-r-2 {
+ border-style: var(--tw-border-style);
+ border-right-width: 2px;
+}
+
+.border-r-4 {
+ border-style: var(--tw-border-style);
+ border-right-width: 4px;
+}
+
+.border-r-\\[12px\\] {
+ border-style: var(--tw-border-style);
+ border-right-width: 12px;
+}
+
+.border-r-\\[length\\:--my-width\\], .border-r-\\[line-width\\:--my-width\\] {
+ border-style: var(--tw-border-style);
+ border-right-width: var(--my-width);
+}
+
+.border-r-\\[medium\\] {
+ border-style: var(--tw-border-style);
+ border-right-width: medium;
+}
+
+.border-r-\\[thick\\] {
+ border-style: var(--tw-border-style);
+ border-right-width: thick;
+}
+
+.border-r-\\[thin\\] {
+ border-style: var(--tw-border-style);
+ border-right-width: thin;
+}
+
+.border-r-\\[\\#0088cc\\] {
+ border-right-color: #08c;
+}
+
+.border-r-\\[\\#0088cc\\]\\/50 {
+ border-right-color: #0088cc80;
+}
+
+.border-r-\\[--my-color\\] {
+ border-right-color: var(--my-color);
+}
+
+.border-r-\\[--my-color\\]\\/50 {
+ border-right-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-r-\\[color\\:--my-color\\] {
+ border-right-color: var(--my-color);
+}
+
+.border-r-\\[color\\:--my-color\\]\\/50 {
+ border-right-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-r-current {
+ border-right-color: currentColor;
+}
+
+.border-r-current\\/50 {
+ border-right-color: color-mix(in srgb, currentColor 50%, transparent);
+}
+
+.border-r-red-500 {
+ border-right-color: #ef4444;
+}
+
+.border-r-red-500\\/50 {
+ border-right-color: #ef444480;
+}
+
+.border-r-transparent {
+ border-right-color: #0000;
+}
+
+@property --tw-border-style {
+ syntax: "";
+ inherits: false;
+ initial-value: solid;
+}"
+`;
+
+exports[`border-s-* 1`] = `
+":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --color-red-500: #ef4444;
+}
+
+.border-s {
+ border-style: var(--tw-border-style);
+ border-inline-start-width: 1px;
+}
+
+.border-s-0 {
+ border-style: var(--tw-border-style);
+ border-inline-start-width: 0;
+}
+
+.border-s-123 {
+ border-style: var(--tw-border-style);
+ border-inline-start-width: 123px;
+}
+
+.border-s-2 {
+ border-style: var(--tw-border-style);
+ border-inline-start-width: 2px;
+}
+
+.border-s-4 {
+ border-style: var(--tw-border-style);
+ border-inline-start-width: 4px;
+}
+
+.border-s-\\[12px\\] {
+ border-style: var(--tw-border-style);
+ border-inline-start-width: 12px;
+}
+
+.border-s-\\[length\\:--my-width\\], .border-s-\\[line-width\\:--my-width\\] {
+ border-style: var(--tw-border-style);
+ border-inline-start-width: var(--my-width);
+}
+
+.border-s-\\[medium\\] {
+ border-style: var(--tw-border-style);
+ border-inline-start-width: medium;
+}
+
+.border-s-\\[thick\\] {
+ border-style: var(--tw-border-style);
+ border-inline-start-width: thick;
+}
+
+.border-s-\\[thin\\] {
+ border-style: var(--tw-border-style);
+ border-inline-start-width: thin;
+}
+
+.border-s-\\[\\#0088cc\\] {
+ border-inline-start-color: #08c;
+}
+
+.border-s-\\[\\#0088cc\\]\\/50 {
+ border-inline-start-color: #0088cc80;
+}
+
+.border-s-\\[--my-color\\] {
+ border-inline-start-color: var(--my-color);
+}
+
+.border-s-\\[--my-color\\]\\/50 {
+ border-inline-start-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-s-\\[color\\:--my-color\\] {
+ border-inline-start-color: var(--my-color);
+}
+
+.border-s-\\[color\\:--my-color\\]\\/50 {
+ border-inline-start-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-s-current {
+ border-inline-start-color: currentColor;
+}
+
+.border-s-current\\/50 {
+ border-inline-start-color: color-mix(in srgb, currentColor 50%, transparent);
+}
+
+.border-s-red-500 {
+ border-inline-start-color: #ef4444;
+}
+
+.border-s-red-500\\/50 {
+ border-inline-start-color: #ef444480;
+}
+
+.border-s-transparent {
+ border-inline-start-color: #0000;
+}
+
+@property --tw-border-style {
+ syntax: "";
+ inherits: false;
+ initial-value: solid;
+}"
+`;
+
+exports[`border-t-* 1`] = `
+":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --color-red-500: #ef4444;
+}
+
+.border-t {
+ border-style: var(--tw-border-style);
+ border-top-width: 1px;
+}
+
+.border-t-0 {
+ border-style: var(--tw-border-style);
+ border-top-width: 0;
+}
+
+.border-t-123 {
+ border-style: var(--tw-border-style);
+ border-top-width: 123px;
+}
+
+.border-t-2 {
+ border-style: var(--tw-border-style);
+ border-top-width: 2px;
+}
+
+.border-t-4 {
+ border-style: var(--tw-border-style);
+ border-top-width: 4px;
+}
+
+.border-t-\\[12px\\] {
+ border-style: var(--tw-border-style);
+ border-top-width: 12px;
+}
+
+.border-t-\\[length\\:--my-width\\], .border-t-\\[line-width\\:--my-width\\] {
+ border-style: var(--tw-border-style);
+ border-top-width: var(--my-width);
+}
+
+.border-t-\\[medium\\] {
+ border-style: var(--tw-border-style);
+ border-top-width: medium;
+}
+
+.border-t-\\[thick\\] {
+ border-style: var(--tw-border-style);
+ border-top-width: thick;
+}
+
+.border-t-\\[thin\\] {
+ border-style: var(--tw-border-style);
+ border-top-width: thin;
+}
+
+.border-t-\\[\\#0088cc\\] {
+ border-top-color: #08c;
+}
+
+.border-t-\\[\\#0088cc\\]\\/50 {
+ border-top-color: #0088cc80;
+}
+
+.border-t-\\[--my-color\\] {
+ border-top-color: var(--my-color);
+}
+
+.border-t-\\[--my-color\\]\\/50 {
+ border-top-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-t-\\[color\\:--my-color\\] {
+ border-top-color: var(--my-color);
+}
+
+.border-t-\\[color\\:--my-color\\]\\/50 {
+ border-top-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-t-current {
+ border-top-color: currentColor;
+}
+
+.border-t-current\\/50 {
+ border-top-color: color-mix(in srgb, currentColor 50%, transparent);
+}
+
+.border-t-red-500 {
+ border-top-color: #ef4444;
+}
+
+.border-t-red-500\\/50 {
+ border-top-color: #ef444480;
+}
+
+.border-t-transparent {
+ border-top-color: #0000;
+}
+
+@property --tw-border-style {
+ syntax: "";
+ inherits: false;
+ initial-value: solid;
+}"
+`;
+
+exports[`border-x-* 1`] = `
+":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --color-red-500: #ef4444;
+}
+
+.border-x {
+ border-style: var(--tw-border-style);
+ border-left-width: 1px;
+ border-right-width: 1px;
+}
+
+.border-x-0 {
+ border-style: var(--tw-border-style);
+ border-left-width: 0;
+ border-right-width: 0;
+}
+
+.border-x-123 {
+ border-style: var(--tw-border-style);
+ border-left-width: 123px;
+ border-right-width: 123px;
+}
+
+.border-x-2 {
+ border-style: var(--tw-border-style);
+ border-left-width: 2px;
+ border-right-width: 2px;
+}
+
+.border-x-4 {
+ border-style: var(--tw-border-style);
+ border-left-width: 4px;
+ border-right-width: 4px;
+}
+
+.border-x-\\[12px\\] {
+ border-style: var(--tw-border-style);
+ border-left-width: 12px;
+ border-right-width: 12px;
+}
+
+.border-x-\\[length\\:--my-width\\], .border-x-\\[line-width\\:--my-width\\] {
+ border-style: var(--tw-border-style);
+ border-left-width: var(--my-width);
+ border-right-width: var(--my-width);
+}
+
+.border-x-\\[medium\\] {
+ border-style: var(--tw-border-style);
+ border-left-width: medium;
+ border-right-width: medium;
+}
+
+.border-x-\\[thick\\] {
+ border-style: var(--tw-border-style);
+ border-left-width: thick;
+ border-right-width: thick;
+}
+
+.border-x-\\[thin\\] {
+ border-style: var(--tw-border-style);
+ border-left-width: thin;
+ border-right-width: thin;
+}
+
+.border-x-\\[\\#0088cc\\] {
+ border-left-color: #08c;
+ border-right-color: #08c;
+}
+
+.border-x-\\[\\#0088cc\\]\\/50 {
+ border-left-color: #0088cc80;
+ border-right-color: #0088cc80;
+}
+
+.border-x-\\[--my-color\\] {
+ border-left-color: var(--my-color);
+ border-right-color: var(--my-color);
+}
+
+.border-x-\\[--my-color\\]\\/50 {
+ border-left-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ border-right-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-x-\\[color\\:--my-color\\] {
+ border-left-color: var(--my-color);
+ border-right-color: var(--my-color);
+}
+
+.border-x-\\[color\\:--my-color\\]\\/50 {
+ border-left-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ border-right-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-x-current {
+ border-left-color: currentColor;
+ border-right-color: currentColor;
+}
+
+.border-x-current\\/50 {
+ border-left-color: color-mix(in srgb, currentColor 50%, transparent);
+ border-right-color: color-mix(in srgb, currentColor 50%, transparent);
+}
+
+.border-x-red-500 {
+ border-left-color: #ef4444;
+ border-right-color: #ef4444;
+}
+
+.border-x-red-500\\/50 {
+ border-left-color: #ef444480;
+ border-right-color: #ef444480;
+}
+
+.border-x-transparent {
+ border-left-color: #0000;
+ border-right-color: #0000;
+}
+
+@property --tw-border-style {
+ syntax: "";
+ inherits: false;
+ initial-value: solid;
+}"
+`;
+
+exports[`border-y-* 1`] = `
+":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --color-red-500: #ef4444;
+}
+
+.border-y {
+ border-style: var(--tw-border-style);
+ border-top-width: 1px;
+ border-bottom-width: 1px;
+}
+
+.border-y-0 {
+ border-style: var(--tw-border-style);
+ border-top-width: 0;
+ border-bottom-width: 0;
+}
+
+.border-y-123 {
+ border-style: var(--tw-border-style);
+ border-top-width: 123px;
+ border-bottom-width: 123px;
+}
+
+.border-y-2 {
+ border-style: var(--tw-border-style);
+ border-top-width: 2px;
+ border-bottom-width: 2px;
+}
+
+.border-y-4 {
+ border-style: var(--tw-border-style);
+ border-top-width: 4px;
+ border-bottom-width: 4px;
+}
+
+.border-y-\\[12px\\] {
+ border-style: var(--tw-border-style);
+ border-top-width: 12px;
+ border-bottom-width: 12px;
+}
+
+.border-y-\\[length\\:--my-width\\], .border-y-\\[line-width\\:--my-width\\] {
+ border-style: var(--tw-border-style);
+ border-top-width: var(--my-width);
+ border-bottom-width: var(--my-width);
+}
+
+.border-y-\\[medium\\] {
+ border-style: var(--tw-border-style);
+ border-top-width: medium;
+ border-bottom-width: medium;
+}
+
+.border-y-\\[thick\\] {
+ border-style: var(--tw-border-style);
+ border-top-width: thick;
+ border-bottom-width: thick;
+}
+
+.border-y-\\[thin\\] {
+ border-style: var(--tw-border-style);
+ border-top-width: thin;
+ border-bottom-width: thin;
+}
+
+.border-y-\\[\\#0088cc\\] {
+ border-top-color: #08c;
+ border-bottom-color: #08c;
+}
+
+.border-y-\\[\\#0088cc\\]\\/50 {
+ border-top-color: #0088cc80;
+ border-bottom-color: #0088cc80;
+}
+
+.border-y-\\[--my-color\\] {
+ border-top-color: var(--my-color);
+ border-bottom-color: var(--my-color);
+}
+
+.border-y-\\[--my-color\\]\\/50 {
+ border-top-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ border-bottom-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-y-\\[color\\:--my-color\\] {
+ border-top-color: var(--my-color);
+ border-bottom-color: var(--my-color);
+}
+
+.border-y-\\[color\\:--my-color\\]\\/50 {
+ border-top-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ border-bottom-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+}
+
+.border-y-current {
+ border-top-color: currentColor;
+ border-bottom-color: currentColor;
+}
+
+.border-y-current\\/50 {
+ border-top-color: color-mix(in srgb, currentColor 50%, transparent);
+ border-bottom-color: color-mix(in srgb, currentColor 50%, transparent);
+}
+
+.border-y-red-500 {
+ border-top-color: #ef4444;
+ border-bottom-color: #ef4444;
+}
+
+.border-y-red-500\\/50 {
+ border-top-color: #ef444480;
+ border-bottom-color: #ef444480;
+}
+
+.border-y-transparent {
+ border-top-color: #0000;
+ border-bottom-color: #0000;
+}
+
+@property --tw-border-style {
+ syntax: "";
+ inherits: false;
+ initial-value: solid;
+}"
+`;
diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts
new file mode 100644
index 000000000..ce9cadd7b
--- /dev/null
+++ b/packages/tailwindcss/src/ast.ts
@@ -0,0 +1,122 @@
+export type Rule = {
+ kind: 'rule'
+ selector: string
+ nodes: AstNode[]
+}
+
+export type Declaration = {
+ kind: 'declaration'
+ property: string
+ value: string
+ important: boolean
+}
+
+export type Comment = {
+ kind: 'comment'
+ value: string
+}
+
+export type AstNode = Rule | Declaration | Comment
+
+export function rule(selector: string, nodes: AstNode[]): Rule {
+ return {
+ kind: 'rule',
+ selector,
+ nodes,
+ }
+}
+
+export function decl(property: string, value: string): Declaration {
+ return {
+ kind: 'declaration',
+ property,
+ value,
+ important: false,
+ }
+}
+
+export function comment(value: string): Comment {
+ return {
+ kind: 'comment',
+ value: value,
+ }
+}
+
+export function walk(
+ ast: AstNode[],
+ visit: (
+ node: AstNode,
+ utils: {
+ replaceWith(newNode: AstNode | AstNode[]): void
+ },
+ ) => void | false,
+) {
+ for (let i = 0; i < ast.length; i++) {
+ let node = ast[i]
+ let shouldContinue = visit(node, {
+ replaceWith(newNode) {
+ ast.splice(i, 1, ...(Array.isArray(newNode) ? newNode : [newNode]))
+ // We want to visit the newly replaced node(s), which start at the current
+ // index (i). By decrementing the index here, the next loop will process
+ // this position (containing the replaced node) again.
+ i--
+ },
+ })
+
+ if (shouldContinue === false) return
+
+ if (node.kind === 'rule') {
+ walk(node.nodes, visit)
+ }
+ }
+}
+
+export function toCss(ast: AstNode[]) {
+ let atRoots: string[] = []
+ return ast
+ .map(function stringify(node: AstNode): string {
+ let css = ''
+
+ // Rule
+ if (node.kind === 'rule') {
+ // Pull out `@at-root` rules to append later
+ if (node.selector === '@at-root') {
+ for (let child of node.nodes) {
+ atRoots.push(stringify(child))
+ }
+ return css
+ }
+
+ // Print at-rules without nodes with a `;` instead of an empty block.
+ //
+ // E.g.:
+ //
+ // ```css
+ // @layer base, components, utilities;
+ // ```
+ if (node.selector[0] === '@' && node.nodes.length === 0) {
+ return `${node.selector};`
+ }
+
+ css += `${node.selector}{`
+ for (let child of node.nodes) {
+ css += stringify(child)
+ }
+ css += '}'
+ }
+
+ // Comment
+ else if (node.kind === 'comment') {
+ css += `/*${node.value}*/\n`
+ }
+
+ // Declaration
+ else if (node.property !== '--tw-sort' && node.value !== undefined && node.value !== null) {
+ css += `${node.property}:${node.value}${node.important ? '!important' : ''};`
+ }
+
+ return css
+ })
+ .concat(atRoots)
+ .join('\n')
+}
diff --git a/packages/tailwindcss/src/candidate.bench.ts b/packages/tailwindcss/src/candidate.bench.ts
new file mode 100644
index 000000000..f10e95d09
--- /dev/null
+++ b/packages/tailwindcss/src/candidate.bench.ts
@@ -0,0 +1,24 @@
+import { scanDir } from '@tailwindcss/oxide'
+import { bench } from 'vitest'
+import { parseCandidate, parseVariant } from './candidate'
+import { buildDesignSystem } from './design-system'
+import { Theme } from './theme'
+import { DefaultMap } from './utils/default-map'
+
+// FOLDER=path/to/folder vitest bench
+const root = process.env.FOLDER || process.cwd()
+
+// Auto content detection
+const result = scanDir({ base: root, globs: true })
+
+const designSystem = buildDesignSystem(new Theme())
+
+bench('parseCandidate', () => {
+ for (let candidate of result.candidates) {
+ parseCandidate(
+ candidate,
+ designSystem.utilities,
+ new DefaultMap((variant, map) => parseVariant(variant, designSystem.variants, map)),
+ )
+ }
+})
diff --git a/packages/tailwindcss/src/candidate.test.ts b/packages/tailwindcss/src/candidate.test.ts
new file mode 100644
index 000000000..672e8cbb4
--- /dev/null
+++ b/packages/tailwindcss/src/candidate.test.ts
@@ -0,0 +1,1052 @@
+import { expect, it } from 'vitest'
+import { parseCandidate, parseVariant } from './candidate'
+import { Utilities } from './utilities'
+import { DefaultMap } from './utils/default-map'
+import { Variants } from './variants'
+
+function run(
+ candidate: string,
+ { utilities, variants }: { utilities?: Utilities; variants?: Variants } = {},
+) {
+ utilities ??= new Utilities()
+ variants ??= new Variants()
+
+ let parsedVariants = new DefaultMap((variant, map) => {
+ return parseVariant(variant, variants!, map)
+ })
+
+ return parseCandidate(candidate, utilities, parsedVariants)
+}
+
+it('should skip unknown utilities', () => {
+ expect(run('unknown-utility')).toEqual(null)
+})
+
+it('should skip unknown variants', () => {
+ expect(run('unknown-variant:flex')).toEqual(null)
+})
+
+it('should parse a simple utility', () => {
+ let utilities = new Utilities()
+ utilities.static('flex', () => [])
+
+ expect(run('flex', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "static",
+ "negative": false,
+ "root": "flex",
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a simple utility that should be important', () => {
+ let utilities = new Utilities()
+ utilities.static('flex', () => [])
+
+ expect(run('flex!', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": true,
+ "kind": "static",
+ "negative": false,
+ "root": "flex",
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a simple utility that can be negative', () => {
+ let utilities = new Utilities()
+ utilities.functional('translate-x', () => [])
+
+ expect(run('-translate-x-4', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "functional",
+ "modifier": null,
+ "negative": true,
+ "root": "translate-x",
+ "value": {
+ "fraction": null,
+ "kind": "named",
+ "value": "4",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a simple utility with a variant', () => {
+ let utilities = new Utilities()
+ utilities.static('flex', () => [])
+
+ let variants = new Variants()
+ variants.static('hover', () => {})
+
+ expect(run('hover:flex', { utilities, variants })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "static",
+ "negative": false,
+ "root": "flex",
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "static",
+ "root": "hover",
+ },
+ ],
+ }
+ `)
+})
+
+it('should parse a simple utility with stacked variants', () => {
+ let utilities = new Utilities()
+ utilities.static('flex', () => [])
+
+ let variants = new Variants()
+ variants.static('hover', () => {})
+ variants.static('focus', () => {})
+
+ expect(run('focus:hover:flex', { utilities, variants })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "static",
+ "negative": false,
+ "root": "flex",
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "static",
+ "root": "focus",
+ },
+ {
+ "compounds": true,
+ "kind": "static",
+ "root": "hover",
+ },
+ ],
+ }
+ `)
+})
+
+it('should parse a simple utility with an arbitrary variant', () => {
+ let utilities = new Utilities()
+ utilities.static('flex', () => [])
+
+ expect(run('[&_p]:flex', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "static",
+ "negative": false,
+ "root": "flex",
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "arbitrary",
+ "selector": "& p",
+ },
+ ],
+ }
+ `)
+})
+
+it('should parse a simple utility with a parameterized variant', () => {
+ let utilities = new Utilities()
+ utilities.static('flex', () => [])
+
+ let variants = new Variants()
+ variants.functional('data', () => {})
+
+ expect(run('data-[disabled]:flex', { utilities, variants })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "static",
+ "negative": false,
+ "root": "flex",
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "functional",
+ "modifier": null,
+ "root": "data",
+ "value": {
+ "kind": "arbitrary",
+ "value": "disabled",
+ },
+ },
+ ],
+ }
+ `)
+})
+
+it('should parse compound variants with an arbitrary value as an arbitrary variant', () => {
+ let utilities = new Utilities()
+ utilities.static('flex', () => [])
+
+ let variants = new Variants()
+ variants.compound('group', () => {})
+
+ expect(run('group-[&_p]/parent-name:flex', { utilities, variants })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "static",
+ "negative": false,
+ "root": "flex",
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "compound",
+ "modifier": {
+ "kind": "named",
+ "value": "parent-name",
+ },
+ "root": "group",
+ "variant": {
+ "compounds": true,
+ "kind": "arbitrary",
+ "selector": "& p",
+ },
+ },
+ ],
+ }
+ `)
+})
+
+it('should parse a simple utility with a parameterized variant and a modifier', () => {
+ let utilities = new Utilities()
+ utilities.static('flex', () => [])
+
+ let variants = new Variants()
+ variants.compound('group', () => {})
+ variants.functional('aria', () => {})
+
+ expect(run('group-aria-[disabled]/parent-name:flex', { utilities, variants }))
+ .toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "static",
+ "negative": false,
+ "root": "flex",
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "compound",
+ "modifier": {
+ "kind": "named",
+ "value": "parent-name",
+ },
+ "root": "group",
+ "variant": {
+ "compounds": true,
+ "kind": "functional",
+ "modifier": null,
+ "root": "aria",
+ "value": {
+ "kind": "arbitrary",
+ "value": "disabled",
+ },
+ },
+ },
+ ],
+ }
+ `)
+})
+
+it('should parse compound group with itself group-group-*', () => {
+ let utilities = new Utilities()
+ utilities.static('flex', () => [])
+
+ let variants = new Variants()
+ variants.static('hover', () => {})
+ variants.compound('group', () => {})
+
+ expect(run('group-group-group-hover/parent-name:flex', { utilities, variants }))
+ .toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "static",
+ "negative": false,
+ "root": "flex",
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "compound",
+ "modifier": {
+ "kind": "named",
+ "value": "parent-name",
+ },
+ "root": "group",
+ "variant": {
+ "compounds": true,
+ "kind": "compound",
+ "modifier": null,
+ "root": "group",
+ "variant": {
+ "compounds": true,
+ "kind": "compound",
+ "modifier": null,
+ "root": "group",
+ "variant": {
+ "compounds": true,
+ "kind": "static",
+ "root": "hover",
+ },
+ },
+ },
+ },
+ ],
+ }
+ `)
+})
+
+it('should parse a simple utility with an arbitrary media variant', () => {
+ let utilities = new Utilities()
+ utilities.static('flex', () => [])
+
+ expect(run('[@media(width>=123px)]:flex', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "static",
+ "negative": false,
+ "root": "flex",
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "arbitrary",
+ "selector": "@media(width>=123px)",
+ },
+ ],
+ }
+ `)
+})
+
+it('should skip arbitrary variants where @media and other arbitrary variants are combined', () => {
+ let utilities = new Utilities()
+ utilities.static('flex', () => [])
+
+ expect(run('[@media(width>=123px){&:hover}]:flex', { utilities })).toMatchInlineSnapshot(`null`)
+})
+
+it('should parse a utility with a modifier', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-red-500/50', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "functional",
+ "modifier": {
+ "kind": "named",
+ "value": "50",
+ },
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "fraction": "500/50",
+ "kind": "named",
+ "value": "red-500",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a utility with an arbitrary modifier', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-red-500/[50%]', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "functional",
+ "modifier": {
+ "dashedIdent": null,
+ "kind": "arbitrary",
+ "value": "50%",
+ },
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "fraction": null,
+ "kind": "named",
+ "value": "red-500",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a utility with a modifier that is important', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-red-500/50!', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": true,
+ "kind": "functional",
+ "modifier": {
+ "kind": "named",
+ "value": "50",
+ },
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "fraction": "500/50",
+ "kind": "named",
+ "value": "red-500",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a utility with a modifier and a variant', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ let variants = new Variants()
+ variants.static('hover', () => {})
+
+ expect(run('hover:bg-red-500/50', { utilities, variants })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "functional",
+ "modifier": {
+ "kind": "named",
+ "value": "50",
+ },
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "fraction": "500/50",
+ "kind": "named",
+ "value": "red-500",
+ },
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "static",
+ "root": "hover",
+ },
+ ],
+ }
+ `)
+})
+
+it('should parse a utility with an arbitrary value', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-[#0088cc]', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "functional",
+ "modifier": null,
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "dashedIdent": null,
+ "dataType": null,
+ "kind": "arbitrary",
+ "value": "#0088cc",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a utility with an arbitrary value including a typehint', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-[color:var(--value)]', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "functional",
+ "modifier": null,
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "dashedIdent": null,
+ "dataType": "color",
+ "kind": "arbitrary",
+ "value": "var(--value)",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a utility with an arbitrary value with a modifier', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-[#0088cc]/50', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "functional",
+ "modifier": {
+ "kind": "named",
+ "value": "50",
+ },
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "dashedIdent": null,
+ "dataType": null,
+ "kind": "arbitrary",
+ "value": "#0088cc",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a utility with an arbitrary value with an arbitrary modifier', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-[#0088cc]/[50%]', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "functional",
+ "modifier": {
+ "dashedIdent": null,
+ "kind": "arbitrary",
+ "value": "50%",
+ },
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "dashedIdent": null,
+ "dataType": null,
+ "kind": "arbitrary",
+ "value": "#0088cc",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a utility with an arbitrary value that is important', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-[#0088cc]!', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": true,
+ "kind": "functional",
+ "modifier": null,
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "dashedIdent": null,
+ "dataType": null,
+ "kind": "arbitrary",
+ "value": "#0088cc",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a utility with an implicit variable as the arbitrary value', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-[--value]', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "functional",
+ "modifier": null,
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "dashedIdent": "--value",
+ "dataType": null,
+ "kind": "arbitrary",
+ "value": "var(--value)",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a utility with an implicit variable as the arbitrary value that is important', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-[--value]!', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": true,
+ "kind": "functional",
+ "modifier": null,
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "dashedIdent": "--value",
+ "dataType": null,
+ "kind": "arbitrary",
+ "value": "var(--value)",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a utility with an explicit variable as the arbitrary value', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-[var(--value)]', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "functional",
+ "modifier": null,
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "dashedIdent": null,
+ "dataType": null,
+ "kind": "arbitrary",
+ "value": "var(--value)",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a utility with an explicit variable as the arbitrary value that is important', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-[var(--value)]!', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": true,
+ "kind": "functional",
+ "modifier": null,
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "dashedIdent": null,
+ "dataType": null,
+ "kind": "arbitrary",
+ "value": "var(--value)",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a utility with an implicit variable as the modifier', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-red-500/[--value]', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "functional",
+ "modifier": {
+ "dashedIdent": "--value",
+ "kind": "arbitrary",
+ "value": "var(--value)",
+ },
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "fraction": null,
+ "kind": "named",
+ "value": "red-500",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a utility with an implicit variable as the modifier that is important', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-red-500/[--value]!', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": true,
+ "kind": "functional",
+ "modifier": {
+ "dashedIdent": "--value",
+ "kind": "arbitrary",
+ "value": "var(--value)",
+ },
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "fraction": null,
+ "kind": "named",
+ "value": "red-500",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a utility with an explicit variable as the modifier', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-red-500/[var(--value)]', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "functional",
+ "modifier": {
+ "dashedIdent": null,
+ "kind": "arbitrary",
+ "value": "var(--value)",
+ },
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "fraction": null,
+ "kind": "named",
+ "value": "red-500",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a utility with an explicit variable as the modifier that is important', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-red-500/[var(--value)]!', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": true,
+ "kind": "functional",
+ "modifier": {
+ "dashedIdent": null,
+ "kind": "arbitrary",
+ "value": "var(--value)",
+ },
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "fraction": null,
+ "kind": "named",
+ "value": "red-500",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse a static variant starting with @', () => {
+ let utilities = new Utilities()
+ utilities.static('flex', () => [])
+
+ let variants = new Variants()
+ variants.static('@lg', () => {})
+
+ expect(run('@lg:flex', { utilities, variants })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "static",
+ "negative": false,
+ "root": "flex",
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "static",
+ "root": "@lg",
+ },
+ ],
+ }
+ `)
+})
+
+it('should parse a functional variant with a modifier', () => {
+ let utilities = new Utilities()
+ utilities.static('flex', () => [])
+
+ let variants = new Variants()
+ variants.functional('foo', () => {})
+
+ expect(run('foo-bar/50:flex', { utilities, variants })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "static",
+ "negative": false,
+ "root": "flex",
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "functional",
+ "modifier": {
+ "kind": "named",
+ "value": "50",
+ },
+ "root": "foo",
+ "value": {
+ "kind": "named",
+ "value": "bar",
+ },
+ },
+ ],
+ }
+ `)
+})
+
+it('should parse a functional variant starting with @', () => {
+ let utilities = new Utilities()
+ utilities.static('flex', () => [])
+
+ let variants = new Variants()
+ variants.functional('@', () => {})
+
+ expect(run('@lg:flex', { utilities, variants })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "static",
+ "negative": false,
+ "root": "flex",
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "functional",
+ "modifier": null,
+ "root": "@",
+ "value": {
+ "kind": "named",
+ "value": "lg",
+ },
+ },
+ ],
+ }
+ `)
+})
+
+it('should parse a functional variant starting with @ and a modifier', () => {
+ let utilities = new Utilities()
+ utilities.static('flex', () => [])
+
+ let variants = new Variants()
+ variants.functional('@', () => {})
+
+ expect(run('@lg/name:flex', { utilities, variants })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "static",
+ "negative": false,
+ "root": "flex",
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "functional",
+ "modifier": {
+ "kind": "named",
+ "value": "name",
+ },
+ "root": "@",
+ "value": {
+ "kind": "named",
+ "value": "lg",
+ },
+ },
+ ],
+ }
+ `)
+})
+
+it('should replace `_` with ` `', () => {
+ let utilities = new Utilities()
+ utilities.functional('content', () => [])
+
+ expect(run('content-["hello_world"]', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "functional",
+ "modifier": null,
+ "negative": false,
+ "root": "content",
+ "value": {
+ "dashedIdent": null,
+ "dataType": null,
+ "kind": "arbitrary",
+ "value": ""hello world"",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should not replace `\\_` with ` ` (when it is escaped)', () => {
+ let utilities = new Utilities()
+ utilities.functional('content', () => [])
+
+ expect(run('content-["hello\\_world"]', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "functional",
+ "modifier": null,
+ "negative": false,
+ "root": "content",
+ "value": {
+ "dashedIdent": null,
+ "dataType": null,
+ "kind": "arbitrary",
+ "value": ""hello_world"",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should not replace `_` inside of `url()`', () => {
+ let utilities = new Utilities()
+ utilities.functional('bg', () => [])
+
+ expect(run('bg-[url(https://example.com/some_page)]', { utilities })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "functional",
+ "modifier": null,
+ "negative": false,
+ "root": "bg",
+ "value": {
+ "dashedIdent": null,
+ "dataType": null,
+ "kind": "arbitrary",
+ "value": "url(https://example.com/some_page)",
+ },
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse arbitrary properties', () => {
+ expect(run('[color:red]')).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "arbitrary",
+ "modifier": null,
+ "property": "color",
+ "value": "red",
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse arbitrary properties with a modifier', () => {
+ expect(run('[color:red]/50')).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "arbitrary",
+ "modifier": {
+ "kind": "named",
+ "value": "50",
+ },
+ "property": "color",
+ "value": "red",
+ "variants": [],
+ }
+ `)
+})
+
+it('should skip arbitrary properties that start with an uppercase letter', () => {
+ expect(run('[Color:red]')).toMatchInlineSnapshot(`null`)
+})
+
+it('should skip arbitrary properties that do not have a property and value', () => {
+ expect(run('[color]')).toMatchInlineSnapshot(`null`)
+})
+
+it('should parse arbitrary properties that are important', () => {
+ expect(run('[color:red]!')).toMatchInlineSnapshot(`
+ {
+ "important": true,
+ "kind": "arbitrary",
+ "modifier": null,
+ "property": "color",
+ "value": "red",
+ "variants": [],
+ }
+ `)
+})
+
+it('should parse arbitrary properties with a variant', () => {
+ let variants = new Variants()
+ variants.static('hover', () => {})
+
+ expect(run('hover:[color:red]', { variants })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "arbitrary",
+ "modifier": null,
+ "property": "color",
+ "value": "red",
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "static",
+ "root": "hover",
+ },
+ ],
+ }
+ `)
+})
+
+it('should parse arbitrary properties with stacked variants', () => {
+ let variants = new Variants()
+ variants.static('hover', () => {})
+ variants.static('focus', () => {})
+
+ expect(run('focus:hover:[color:red]', { variants })).toMatchInlineSnapshot(`
+ {
+ "important": false,
+ "kind": "arbitrary",
+ "modifier": null,
+ "property": "color",
+ "value": "red",
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "static",
+ "root": "focus",
+ },
+ {
+ "compounds": true,
+ "kind": "static",
+ "root": "hover",
+ },
+ ],
+ }
+ `)
+})
+
+it('should parse arbitrary properties that are important and using stacked arbitrary variants', () => {
+ expect(run('[@media(width>=123px)]:[&_p]:[color:red]!')).toMatchInlineSnapshot(`
+ {
+ "important": true,
+ "kind": "arbitrary",
+ "modifier": null,
+ "property": "color",
+ "value": "red",
+ "variants": [
+ {
+ "compounds": true,
+ "kind": "arbitrary",
+ "selector": "@media(width>=123px)",
+ },
+ {
+ "compounds": true,
+ "kind": "arbitrary",
+ "selector": "& p",
+ },
+ ],
+ }
+ `)
+})
+
+it('should not parse compound group with a non-compoundable variant', () => {
+ expect(run('group-*:flex')).toMatchInlineSnapshot(`null`)
+})
diff --git a/packages/tailwindcss/src/candidate.ts b/packages/tailwindcss/src/candidate.ts
new file mode 100644
index 000000000..e1ce018ec
--- /dev/null
+++ b/packages/tailwindcss/src/candidate.ts
@@ -0,0 +1,634 @@
+import { decodeArbitraryValue } from './utils/decode-arbitrary-value'
+import { segment } from './utils/segment'
+
+type ArbitraryUtilityValue = {
+ kind: 'arbitrary'
+
+ /**
+ * bg-[color:--my-color]
+ * ^^^^^
+ */
+ dataType: string | null
+
+ /**
+ * bg-[#0088cc]
+ * ^^^^^^^
+ * bg-[--my_variable]
+ * var(^^^^^^^^^^^^^)
+ */
+ value: string
+
+ /**
+ * bg-[--my_variable]
+ * ^^^^^^^^^^^^^
+ */
+ dashedIdent: string | null
+}
+
+export type NamedUtilityValue = {
+ kind: 'named'
+
+ /**
+ * bg-red-500
+ * ^^^^^^^
+ *
+ * w-1/2
+ * ^
+ */
+ value: string
+
+ /**
+ * w-1/2
+ * ^^^
+ */
+ fraction: string | null
+}
+
+type ArbitraryModifier = {
+ kind: 'arbitrary'
+
+ /**
+ * bg-red-500/[50%]
+ * ^^^
+ */
+ value: string
+
+ /**
+ * bg-red-500/[--my_variable]
+ * ^^^^^^^^^^^^^
+ */
+ dashedIdent: string | null
+}
+
+type NamedModifier = {
+ kind: 'named'
+
+ /**
+ * bg-red-500/50
+ * ^^
+ */
+ value: string
+}
+
+export type CandidateModifier = ArbitraryModifier | NamedModifier
+
+type ArbitraryVariantValue = {
+ kind: 'arbitrary'
+ value: string
+}
+
+type NamedVariantValue = {
+ kind: 'named'
+ value: string
+}
+
+export type Variant =
+ /**
+ * Arbitrary variants are variants that take a selector and generate a variant
+ * on the fly.
+ *
+ * E.g.: `[&_p]`
+ */
+ | {
+ kind: 'arbitrary'
+ selector: string
+
+ // If true, it can be applied as a child of a compound variant
+ compounds: boolean
+ }
+
+ /**
+ * Static variants are variants that don't take any arguments.
+ *
+ * E.g.: `hover`
+ */
+ | {
+ kind: 'static'
+ root: string
+
+ // If true, it can be applied as a child of a compound variant
+ compounds: boolean
+ }
+
+ /**
+ * Functional variants are variants that can take an argument. The argument is
+ * either a named variant value or an arbitrary variant value.
+ *
+ * E.g.:
+ *
+ * - `aria-disabled`
+ * - `aria-[disabled]`
+ * - `@container-size` -> @container, with named value `size`
+ * - `@container-[inline-size]` -> @container, with arbitrary variant value `inline-size`
+ * - `@container` -> @container, with no value
+ */
+ | {
+ kind: 'functional'
+ root: string
+ value: ArbitraryVariantValue | NamedVariantValue | null
+ modifier: ArbitraryModifier | NamedModifier | null
+
+ // If true, it can be applied as a child of a compound variant
+ compounds: boolean
+ }
+
+ /**
+ * Compound variants are variants that take another variant as an argument.
+ *
+ * E.g.:
+ *
+ * - `has-[&_p]`
+ * - `group-*`
+ * - `peer-*`
+ */
+ | {
+ kind: 'compound'
+ root: string
+ modifier: ArbitraryModifier | NamedModifier | null
+ variant: Variant
+
+ // If true, it can be applied as a child of a compound variant
+ compounds: boolean
+ }
+
+export type Candidate =
+ /**
+ * Arbitrary candidates are candidates that register utilities on the fly with
+ * a property and a value.
+ *
+ * E.g.:
+ *
+ * - `[color:red]`
+ * - `[color:red]/50`
+ * - `[color:red]/50!`
+ */
+ | {
+ kind: 'arbitrary'
+ property: string
+ value: string
+ modifier: ArbitraryModifier | NamedModifier | null
+ variants: Variant[]
+ important: boolean
+ }
+
+ /**
+ * Static candidates are candidates that don't take any arguments.
+ *
+ * E.g.:
+ *
+ * - `underline`
+ * - `flex`
+ */
+ | {
+ kind: 'static'
+ root: string
+ variants: Variant[]
+ negative: boolean
+ important: boolean
+ }
+
+ /**
+ * Functional candidates are candidates that can take an argument.
+ *
+ * E.g.:
+ *
+ * - `bg-red-500`
+ * - `bg-[#0088cc]`
+ * - `w-1/2`
+ */
+ | {
+ kind: 'functional'
+ root: string
+ value: ArbitraryUtilityValue | NamedUtilityValue | null
+ modifier: ArbitraryModifier | NamedModifier | null
+ variants: Variant[]
+ negative: boolean
+ important: boolean
+ }
+
+export function parseCandidate(
+ input: string,
+ utilities: {
+ has: (value: string) => boolean
+ kind: (root: string) => Omit
+ },
+ parsedVariants: { get: (value: string) => Variant | null },
+): Candidate | null {
+ // hover:focus:underline
+ // ^^^^^ ^^^^^^ -> Variants
+ // ^^^^^^^^^ -> Base
+ let rawVariants = segment(input, ':')
+
+ // Safety: At this point it is safe to use TypeScript's non-null assertion
+ // operator because even if the `input` was an empty string, splitting an
+ // empty string by `:` will always result in an array with at least one
+ // element.
+ let base = rawVariants.pop()!
+
+ let parsedCandidateVariants: Variant[] = []
+
+ for (let variant of rawVariants) {
+ let parsedVariant = parsedVariants.get(variant)
+ if (parsedVariant === null) return null
+
+ // Variants are applied left-to-right meaning that any representing pseudo-
+ // elements must come first. This is because they cannot have anything
+ // after them in a selector. The problem with this is that it's common for
+ // users to write them in the wrong order, for example:
+ //
+ // `dark:before:underline` (wrong)
+ // `before:dark:underline` (right)
+ //
+ // Add pseudo-element variants to the front, making both examples above
+ // function identically which allows users to not care about the order.
+ switch (variant) {
+ case 'after':
+ case 'backdrop':
+ case 'before':
+ case 'first-letter':
+ case 'first-line':
+ case 'marker':
+ case 'placeholder':
+ case 'selection':
+ parsedCandidateVariants.unshift(parsedVariant)
+ break
+ default:
+ parsedCandidateVariants.push(parsedVariant)
+ }
+ }
+
+ let state = {
+ important: false,
+ negative: false,
+ }
+
+ // Candidates that end with an exclamation mark are the important version with
+ // higher specificity of the non-important candidate, e.g. `mx-4!`.
+ if (base[base.length - 1] === '!') {
+ state.important = true
+ base = base.slice(0, -1)
+ }
+
+ // Arbitrary properties
+ if (base[0] === '[') {
+ let [baseWithoutModifier, modifierSegment = null] = segment(base, '/')
+ if (baseWithoutModifier[baseWithoutModifier.length - 1] !== ']') return null
+
+ // The property part of the arbitrary property can only start with a-z
+ // lowercase or a dash `-` in case of vendor prefixes such as `-webkit-`
+ // or `-moz-`.
+ //
+ // Otherwise, it is an invalid candidate, and skip continue parsing.
+ let charCode = baseWithoutModifier.charCodeAt(1)
+ if (charCode !== 45 && !(charCode >= 97 && charCode <= 122)) return null
+
+ baseWithoutModifier = baseWithoutModifier.slice(1, -1)
+
+ // Arbitrary properties consist of a property and a value separated by a
+ // `:`. If the `:` cannot be found, then it is an invalid candidate, and we
+ // can skip continue parsing.
+ //
+ // Since the property and the value should be separated by a `:`, we can
+ // also verify that the colon is not the first or last character in the
+ // candidate, because that would make it invalid as well.
+ let idx = baseWithoutModifier.indexOf(':')
+ if (idx === -1 || idx === 0 || idx === baseWithoutModifier.length - 1) return null
+
+ let property = baseWithoutModifier.slice(0, idx)
+ let value = decodeArbitraryValue(baseWithoutModifier.slice(idx + 1))
+
+ return {
+ kind: 'arbitrary',
+ property,
+ value,
+ modifier: modifierSegment === null ? null : parseModifier(modifierSegment),
+ variants: parsedCandidateVariants,
+ important: state.important,
+ }
+ }
+
+ // Candidates that start with a dash are the negative versions of another
+ // candidate, e.g. `-mx-4`.
+ if (base[0] === '-') {
+ state.negative = true
+ base = base.slice(1)
+ }
+
+ let [root, value] = findRoot(base, utilities)
+
+ let modifierSegment: string | null = null
+
+ // If the root is null, but it contains a `/`, then it could be that we are
+ // dealing with a functional utility that contains a modifier but doesn't
+ // contain a value.
+ //
+ // E.g.: `@container/parent`
+ if (root === null && base.includes('/')) {
+ let [rootWithoutModifier, rootModifierSegment = null] = segment(base, '/')
+
+ modifierSegment = rootModifierSegment
+
+ // Try to find the root and value, without the modifier present
+ ;[root, value] = findRoot(rootWithoutModifier, utilities)
+ }
+
+ // If there's no root, the candidate isn't a valid class and can be discarded.
+ if (root === null) return null
+
+ let kind = utilities.kind(root)
+
+ if (kind === 'static') {
+ if (value !== null) return null
+
+ return {
+ kind: 'static',
+ root,
+ variants: parsedCandidateVariants,
+ negative: state.negative,
+ important: state.important,
+ }
+ }
+
+ let candidate: Candidate = {
+ kind: 'functional',
+ root,
+ modifier: modifierSegment === null ? null : parseModifier(modifierSegment),
+ value: null,
+ variants: parsedCandidateVariants,
+ negative: state.negative,
+ important: state.important,
+ }
+
+ if (value === null) return candidate
+
+ {
+ // Extract a modifier if present, e.g. `text-xl/9` or `bg-red-500/[14%]`
+ let [valueWithoutModifier, modifierSegment = null] = segment(value, '/')
+
+ if (modifierSegment !== null) {
+ candidate.modifier = parseModifier(modifierSegment)
+ }
+
+ let startArbitraryIdx = valueWithoutModifier.indexOf('[')
+ let valueIsArbitrary = startArbitraryIdx !== -1
+
+ if (valueIsArbitrary) {
+ let arbitraryValue = valueWithoutModifier.slice(startArbitraryIdx + 1, -1)
+
+ // Extract an explicit typehint if present, e.g. `bg-[color:var(--my-var)])`
+ let typehint = ''
+ for (let i = 0; i < arbitraryValue.length; i++) {
+ let code = arbitraryValue.charCodeAt(i)
+
+ // If we hit a ":", we're at the end of a typehint.
+ if (code === 58 /* ':' */) {
+ typehint = arbitraryValue.slice(0, i)
+ arbitraryValue = arbitraryValue.slice(i + 1)
+ break
+ }
+
+ // Keep iterating as long as we've only seen valid typehint characters.
+ if (code === 45 /* '-' */ || (code >= 97 && code <= 122) /* [a-z] */) {
+ continue
+ }
+
+ // If we see any other character, there's no typehint so break early.
+ break
+ }
+
+ // If an arbitrary value looks like a CSS variable, we automatically wrap
+ // it with `var(...)`.
+ //
+ // But since some CSS properties accept a `` as a value
+ // directly (e.g. `scroll-timeline-name`), we also store the original
+ // value in case the utility matcher is interested in it without
+ // `var(...)`.
+ let dashedIdent: string | null = null
+ if (arbitraryValue[0] === '-' && arbitraryValue[1] === '-') {
+ dashedIdent = arbitraryValue
+ arbitraryValue = `var(${arbitraryValue})`
+ } else {
+ arbitraryValue = decodeArbitraryValue(arbitraryValue)
+ }
+
+ candidate.value = {
+ kind: 'arbitrary',
+ dataType: typehint || null,
+ value: arbitraryValue,
+ dashedIdent,
+ }
+ } else {
+ // Some utilities support fractions as values, e.g. `w-1/2`. Since it's
+ // ambiguous whether the slash signals a modifier or not, we store the
+ // fraction separately in case the utility matcher is interested in it.
+ let fraction =
+ modifierSegment === null || candidate.modifier?.kind === 'arbitrary'
+ ? null
+ : value.slice(valueWithoutModifier.lastIndexOf('-') + 1)
+
+ candidate.value = {
+ kind: 'named',
+ value: valueWithoutModifier,
+ fraction,
+ }
+ }
+ }
+
+ return candidate
+}
+
+function parseModifier(modifier: string): CandidateModifier {
+ if (modifier[0] === '[' && modifier[modifier.length - 1] === ']') {
+ let arbitraryValue = modifier.slice(1, -1)
+
+ // If an arbitrary value looks like a CSS variable, we automatically wrap
+ // it with `var(...)`.
+ //
+ // But since some CSS properties accept a `` as a value
+ // directly (e.g. `scroll-timeline-name`), we also store the original
+ // value in case the utility matcher is interested in it without
+ // `var(...)`.
+ let dashedIdent: string | null = null
+ if (arbitraryValue[0] === '-' && arbitraryValue[1] === '-') {
+ dashedIdent = arbitraryValue
+ arbitraryValue = `var(${arbitraryValue})`
+ } else {
+ arbitraryValue = decodeArbitraryValue(arbitraryValue)
+ }
+
+ return {
+ kind: 'arbitrary',
+ value: arbitraryValue,
+ dashedIdent,
+ }
+ }
+
+ return {
+ kind: 'named',
+ value: modifier,
+ }
+}
+
+export function parseVariant(
+ variant: string,
+ variants: {
+ has: (value: string) => boolean
+ kind: (root: string) => Omit
+ compounds: (root: string) => boolean
+ },
+ parsedVariants: { get: (value: string) => Variant | null },
+): Variant | null {
+ // Arbitrary variants
+ if (variant[0] === '[' && variant[variant.length - 1] === ']') {
+ /**
+ * TODO: Breaking change
+ *
+ * @deprecated Arbitrary variants containing at-rules with other selectors
+ * are deprecated. Use stacked variants instead.
+ *
+ * Before:
+ * - `[@media(width>=123px){&:hover}]:`
+ *
+ * After:
+ * - `[@media(width>=123px)]:[&:hover]:`
+ * - `[@media(width>=123px)]:hover:`
+ */
+ if (variant[1] === '@' && variant.includes('&')) return null
+
+ let selector = decodeArbitraryValue(variant.slice(1, -1))
+
+ if (selector[0] !== '@') {
+ // Ensure `&` is always present by wrapping the selector in `&:is(…)`
+ //
+ // E.g.:
+ //
+ // - `[p]:flex`
+ if (!selector.includes('&')) {
+ selector = `&:is(${selector})`
+ }
+ }
+
+ return {
+ kind: 'arbitrary',
+ selector,
+ compounds: true,
+ }
+ }
+
+ // Static, functional and compound variants
+ {
+ // group-hover/group-name
+ // ^^^^^^^^^^^ -> Variant without modifier
+ // ^^^^^^^^^^ -> Modifier
+ let [variantWithoutModifier, modifier = null, additionalModifier] = segment(variant, '/')
+
+ // If there's more than one modifier, the variant is invalid.
+ //
+ // E.g.:
+ //
+ // - `group-hover/foo/bar`
+ if (additionalModifier) return null
+
+ let [root, value] = findRoot(variantWithoutModifier, variants)
+
+ // Variant is invalid, therefore the candidate is invalid and we can skip
+ // continue parsing it.
+ if (root === null) return null
+
+ switch (variants.kind(root)) {
+ case 'static': {
+ if (value !== null) return null
+
+ return {
+ kind: 'static',
+ root,
+ compounds: variants.compounds(root),
+ }
+ }
+
+ case 'functional': {
+ if (value === null) return null
+
+ if (value[0] === '[' && value[value.length - 1] === ']') {
+ return {
+ kind: 'functional',
+ root,
+ modifier: modifier === null ? null : parseModifier(modifier),
+ value: {
+ kind: 'arbitrary',
+ value: decodeArbitraryValue(value.slice(1, -1)),
+ },
+ compounds: variants.compounds(root),
+ }
+ }
+
+ return {
+ kind: 'functional',
+ root,
+ modifier: modifier === null ? null : parseModifier(modifier),
+ value: { kind: 'named', value },
+ compounds: variants.compounds(root),
+ }
+ }
+
+ case 'compound': {
+ if (value === null) return null
+
+ let subVariant = parsedVariants.get(value)
+ if (subVariant === null) return null
+ if (subVariant.compounds === false) return null
+
+ return {
+ kind: 'compound',
+ root,
+ modifier: modifier === null ? null : { kind: 'named', value: modifier },
+ variant: subVariant,
+ compounds: variants.compounds(root),
+ }
+ }
+ }
+ }
+
+ return null
+}
+
+function findRoot(
+ input: string,
+ lookup: { has: (input: string) => boolean },
+): [string | null, string | null] {
+ // If the lookup has an exact match, then that's the root.
+ if (lookup.has(input)) return [input, null]
+
+ // Otherwise test every permutation of the input by iteratively removing
+ // everything after the last dash.
+ let idx = input.lastIndexOf('-')
+ if (idx === -1) {
+ // Variants starting with `@` are special because they don't need a `-`
+ // after the `@` (E.g.: `@-lg` should be written as `@lg`).
+ if (input[0] === '@' && lookup.has('@')) {
+ return ['@', input.slice(1)]
+ }
+
+ return [null, null]
+ }
+
+ // Determine the root and value by testing permutations of the incoming input
+ // against the lookup table.
+ //
+ // In case of a candidate like `bg-red-500`, this looks like:
+ //
+ // `bg-red-500` -> No match
+ // `bg-red` -> No match
+ // `bg` -> Match
+ do {
+ let maybeRoot = input.slice(0, idx)
+
+ if (lookup.has(maybeRoot)) {
+ return [maybeRoot, input.slice(idx + 1)]
+ }
+
+ idx = input.lastIndexOf('-', idx - 1)
+ } while (idx > 0)
+
+ return [null, null]
+}
diff --git a/packages/tailwindcss/src/cli/commands/build/index.ts b/packages/tailwindcss/src/cli/commands/build/index.ts
new file mode 100644
index 000000000..acc323bce
--- /dev/null
+++ b/packages/tailwindcss/src/cli/commands/build/index.ts
@@ -0,0 +1,244 @@
+import watcher from '@parcel/watcher'
+import {
+ IO,
+ Parsing,
+ clearCache,
+ scanDir,
+ scanFiles,
+ type ChangedContent,
+} from '@tailwindcss/oxide'
+import { existsSync } from 'node:fs'
+import fs from 'node:fs/promises'
+import path from 'node:path'
+import postcss from 'postcss'
+import atImport from 'postcss-import'
+import { compile, optimizeCss } from '../../..'
+import type { Arg, Result } from '../../utils/args'
+import {
+ eprintln,
+ formatDuration,
+ header,
+ highlight,
+ println,
+ relative,
+} from '../../utils/renderer'
+import { resolve } from '../../utils/resolve'
+import { drainStdin, outputFile } from './utils'
+
+const css = String.raw
+
+export function options() {
+ return {
+ '--input': {
+ type: 'string',
+ description: 'Input file',
+ alias: '-i',
+ },
+ '--output': {
+ type: 'string',
+ description: 'Output file',
+ alias: '-o',
+ },
+ '--watch': {
+ type: 'boolean | string',
+ description: 'Watch for changes and rebuild as needed',
+ alias: '-w',
+ },
+ '--minify': {
+ type: 'boolean',
+ description: 'Minify the output',
+ alias: '-m',
+ },
+ '--cwd': {
+ type: 'string',
+ description: 'The current working directory',
+ default: '.',
+ },
+ } satisfies Arg
+}
+
+export async function handle(args: Result>) {
+ let base = path.resolve(args['--cwd'])
+
+ // Resolve the output as an absolute path.
+ if (args['--output']) {
+ args['--output'] = path.resolve(base, args['--output'])
+ }
+
+ // Resolve the input as an absolute path. If the input is a `-`, then we don't
+ // need to resolve it because this is a flag to indicate that we want to use
+ // `stdin` instead.
+ if (args['--input'] && args['--input'] !== '-') {
+ args['--input'] = path.resolve(base, args['--input'])
+
+ // Ensure the provided `--input` exists.
+ if (!existsSync(args['--input'])) {
+ eprintln(header())
+ eprintln()
+ eprintln(`Specified input file ${highlight(relative(args['--input']))} does not exist.`)
+ process.exit(1)
+ }
+ }
+
+ let start = process.hrtime.bigint()
+ let { candidates } = scanDir({ base })
+
+ // Resolve the input
+ let [input, cssImportPaths] = await handleImports(
+ args['--input']
+ ? args['--input'] === '-'
+ ? await drainStdin()
+ : await fs.readFile(args['--input'], 'utf-8')
+ : css`
+ @import '${resolve('tailwindcss/index.css')}';
+ `,
+ args['--input'] ?? base,
+ )
+
+ // Compile the input
+ let result = optimizeCss(compile(input, candidates), {
+ file: args['--input'] ?? 'input.css',
+ minify: args['--minify'],
+ })
+
+ // Write the output
+ if (args['--output']) {
+ await outputFile(args['--output'], result)
+ } else {
+ println(result)
+ }
+
+ let end = process.hrtime.bigint()
+ eprintln(header())
+ eprintln()
+ eprintln(`Done in ${formatDuration(end - start)}`)
+
+ // Watch for changes
+ if (args['--watch']) {
+ await watcher.subscribe(base, async (err, events) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+
+ try {
+ // If the only change happened to the output file, then we don't want to
+ // trigger a rebuild because that will result in an infinite loop.
+ if (events.length === 1 && events[0].path === args['--output']) return
+
+ let changedFiles: ChangedContent[] = []
+ let rebuildStrategy: 'incremental' | 'full' = 'incremental'
+
+ for (let event of events) {
+ // Track new and updated files for incremental rebuilds.
+ if (event.type === 'create' || event.type === 'update') {
+ changedFiles.push({
+ file: event.path,
+ extension: path.extname(event.path).slice(1),
+ } satisfies ChangedContent)
+ }
+
+ // If one of the changed files is related to the input CSS files, then
+ // we need to do a full rebuild because the theme might have changed.
+ if (cssImportPaths.includes(event.path)) {
+ rebuildStrategy = 'full'
+
+ // No need to check the rest of the events, because we already know we
+ // need to do a full rebuild.
+ break
+ }
+ }
+
+ // Re-compile the input
+ let start = process.hrtime.bigint()
+
+ // Scan the entire `base` directory for full rebuilds.
+ if (rebuildStrategy === 'full') {
+ // Clear the cache because we need to re-scan the entire directory.
+ clearCache()
+
+ // Re-scan the directory to get the new `candidates`.
+ candidates = scanDir({ base }).candidates
+ }
+
+ // Scan changed files only for incremental rebuilds.
+ else if (rebuildStrategy === 'incremental') {
+ let uniqueCandidates = new Set(candidates)
+ for (let candidate of scanFiles(changedFiles, IO.Sequential | Parsing.Sequential)) {
+ uniqueCandidates.add(candidate)
+ }
+ candidates = Array.from(uniqueCandidates)
+ }
+
+ // Resolve the input
+ if (rebuildStrategy === 'full') {
+ // Collect the new `input` and `cssImportPaths`.
+ ;[input, cssImportPaths] = await handleImports(
+ args['--input']
+ ? await fs.readFile(args['--input'], 'utf-8')
+ : css`
+ @import '${resolve('tailwindcss/index.css')}';
+ `,
+ args['--input'] ?? base,
+ )
+ }
+
+ // Compile the input
+ let result = optimizeCss(compile(input, candidates), {
+ file: args['--input'] ?? 'input.css',
+ minify: args['--minify'],
+ })
+
+ // Write the output
+ if (args['--output']) {
+ await outputFile(args['--output'], result)
+ } else {
+ println(result)
+ }
+
+ let end = process.hrtime.bigint()
+ eprintln(`Done in ${formatDuration(end - start)}`)
+ } catch (err) {
+ // Catch any errors and print them to stderr, but don't exit the process
+ // and keep watching.
+ if (err instanceof Error) {
+ eprintln(err.toString())
+ }
+ }
+ })
+
+ // Abort the watcher if `stdin` is closed to avoid zombie processes. You can
+ // disable this behavior with `--watch=always`.
+ if (args['--watch'] !== 'always') {
+ process.stdin.on('end', () => {
+ process.exit(0)
+ })
+ }
+
+ // Keep the process running
+ process.stdin.resume()
+ }
+}
+
+function handleImports(
+ input: string,
+ file: string,
+): [css: string, paths: string[]] | Promise<[css: string, paths: string[]]> {
+ // TODO: Should we implement this ourselves instead of relying on PostCSS?
+ //
+ // Relevant specification:
+ // - CSS Import Resolve: https://csstools.github.io/css-import-resolve/
+
+ if (!input.includes('@import')) return [input, []]
+
+ return postcss()
+ .use(atImport())
+ .process(input, { from: file })
+ .then((result) => [
+ result.css,
+
+ // Use `result.messages` to get the imported files. This also includes the
+ // current file itself.
+ result.messages.filter((msg) => msg.type === 'postcss-import').map((msg) => msg.file),
+ ])
+}
diff --git a/packages/tailwindcss/src/cli/commands/build/utils.ts b/packages/tailwindcss/src/cli/commands/build/utils.ts
new file mode 100644
index 000000000..b41a74358
--- /dev/null
+++ b/packages/tailwindcss/src/cli/commands/build/utils.ts
@@ -0,0 +1,26 @@
+import fs from 'node:fs/promises'
+import path from 'node:path'
+
+export function drainStdin() {
+ return new Promise((resolve, reject) => {
+ let result = ''
+ process.stdin.on('data', (chunk) => {
+ result += chunk
+ })
+ process.stdin.on('end', () => resolve(result))
+ process.stdin.on('error', (err) => reject(err))
+ })
+}
+
+export async function outputFile(file: string, contents: string) {
+ try {
+ let currentContents = await fs.readFile(file, 'utf8')
+ if (currentContents === contents) return // Skip writing the file
+ } catch {}
+
+ // Ensure the parent directories exist
+ await fs.mkdir(path.dirname(file), { recursive: true })
+
+ // Write the file
+ await fs.writeFile(file, contents, 'utf8')
+}
diff --git a/packages/tailwindcss/src/cli/commands/help/index.ts b/packages/tailwindcss/src/cli/commands/help/index.ts
new file mode 100644
index 000000000..20ee78208
--- /dev/null
+++ b/packages/tailwindcss/src/cli/commands/help/index.ts
@@ -0,0 +1,170 @@
+import pc from 'picocolors'
+import type { Arg } from '../../utils/args'
+import { UI, header, highlight, indent, println, wordWrap } from '../../utils/renderer'
+
+export function help({
+ invalid,
+ usage,
+ options,
+}: {
+ invalid?: string
+ usage?: string[]
+ options?: Arg
+}) {
+ // Available terminal width
+ let width = process.stdout.columns
+
+ // Render header
+ println(header())
+
+ // Render the invalid command
+ if (invalid) {
+ println()
+ println(`${pc.dim('Invalid command:')} ${invalid}`)
+ }
+
+ // Render usage
+ if (usage && usage.length > 0) {
+ println()
+ println(pc.dim('Usage:'))
+ for (let [idx, example] of usage.entries()) {
+ // Split the usage example into the command and its options. This allows
+ // us to wrap the options based on the available width of the terminal.
+ let command = example.slice(0, example.indexOf('['))
+ let options = example.slice(example.indexOf('['))
+
+ // Make the options dimmed, to make them stand out less than the command
+ // itself.
+ options = options.replace(/\[.*?\]/g, (option) => pc.dim(option))
+
+ // The space between the command and the options.
+ let space = 1
+
+ // Wrap the options based on the available width of the terminal.
+ let lines = wordWrap(options, width - UI.indent - command.length - space)
+
+ // Print an empty line between the usage examples if we need to split due
+ // to width constraints. This ensures that the usage examples are visually
+ // separated.
+ //
+ // E.g.: when enough space is available
+ //
+ // ```
+ // Usage:
+ // tailwindcss build [--input input.css] [--output output.css] [--watch] [options...]
+ // tailwindcss other [--watch] [options...]
+ // ```
+ //
+ // E.g.: when not enough space is available
+ //
+ // ```
+ // Usage:
+ // tailwindcss build [--input input.css] [--output output.css]
+ // [--watch] [options...]
+ //
+ // tailwindcss other [--watch] [options...]
+ // ```
+ if (lines.length > 1 && idx !== 0) {
+ println()
+ }
+
+ // Print the usage examples based on available width of the terminal.
+ //
+ // E.g.: when enough space is available
+ //
+ // ```
+ // Usage:
+ // tailwindcss [--input input.css] [--output output.css] [--watch] [options...]
+ // ```
+ //
+ // E.g.: when not enough space is available
+ //
+ // ```
+ // Usage:
+ // tailwindcss [--input input.css] [--output output.css]
+ // [--watch] [options...]
+ // ```
+ //
+ // > Note how the second line is indented to align with the first line.
+ println(indent(`${command}${lines.shift()}`))
+ for (let line of lines) {
+ println(indent(line, command.length))
+ }
+ }
+ }
+
+ // Render options
+ if (options) {
+ // Track the max alias length, this is used to indent the options that don't
+ // have an alias such that everything is aligned properly.
+ let maxAliasLength = 0
+ for (let { alias } of Object.values(options)) {
+ if (alias) {
+ maxAliasLength = Math.max(maxAliasLength, alias.length)
+ }
+ }
+
+ // The option strings, which are the combination of the `alias` and the
+ // `flag`, with the correct spacing.
+ let optionStrings: string[] = []
+
+ // Track the max option length, which is the longest combination of an
+ // `alias` followed by `, ` and followed by the `flag`.
+ let maxOptionLength = 0
+
+ for (let [flag, { alias }] of Object.entries(options)) {
+ // The option string, which is the combination of the alias and the flag
+ // but already properly indented based on the other aliases to ensure
+ // everything is aligned properly.
+ let option = [
+ alias ? `${alias.padStart(maxAliasLength)}` : alias,
+ alias ? flag : ' '.repeat(maxAliasLength + 2 /* `, `.length */) + flag,
+ ]
+ .filter(Boolean)
+ .join(', ')
+
+ optionStrings.push(option)
+ maxOptionLength = Math.max(maxOptionLength, option.length)
+ }
+
+ println()
+ println(pc.dim('Options:'))
+
+ // The minimum amount of dots between the option and the description.
+ let minimumGap = 8
+
+ for (let { description, default: defaultValue = null } of Object.values(options)) {
+ // The option to render
+ let option = optionStrings.shift() as string
+
+ // The amount of dots to show between the option and the description.
+ let dotCount = minimumGap + (maxOptionLength - option.length)
+
+ // To account for the space before and after the dots.
+ let spaces = 2
+
+ // The available width remaining for the description.
+ let availableWidth = width - option.length - dotCount - spaces - UI.indent
+
+ // Wrap the description and the default value (if present), based on the
+ // available width.
+ let lines = wordWrap(
+ defaultValue !== null
+ ? `${description} ${pc.dim(`[default:\u202F${highlight(`${defaultValue}`)}]`)}`
+ : description,
+ availableWidth,
+ )
+
+ // Print the option, the spacer dots and the start of the description.
+ println(
+ indent(`${pc.blue(option)} ${pc.dim(pc.gray('\u00B7')).repeat(dotCount)} ${lines.shift()}`),
+ )
+
+ // Print the remaining lines of the description, indenting them to align
+ // with the start of the description.
+ for (let line of lines) {
+ println(indent(`${' '.repeat(option.length + dotCount + spaces)}${line}`))
+ }
+ }
+ }
+}
diff --git a/packages/tailwindcss/src/cli/index.ts b/packages/tailwindcss/src/cli/index.ts
new file mode 100644
index 000000000..3622a858c
--- /dev/null
+++ b/packages/tailwindcss/src/cli/index.ts
@@ -0,0 +1,45 @@
+#!/usr/bin/env node
+
+import { args, type Arg } from './utils/args'
+
+import * as build from './commands/build'
+import { help } from './commands/help'
+
+const sharedOptions = {
+ '--help': { type: 'boolean', description: 'Display usage information', alias: '-h' },
+} satisfies Arg
+
+const shared = args(sharedOptions)
+const command = shared._[0]
+
+// Right now we don't support any sub-commands. Let's show the help message
+// instead.
+if (command) {
+ help({
+ invalid: command,
+ usage: ['tailwindcss [options]'],
+ options: { ...build.options(), ...sharedOptions },
+ })
+ process.exit(1)
+}
+
+// Display main help message if no command is being used.
+//
+// E.g.:
+//
+// - `tailwindcss` // should show the help message
+//
+// E.g.: implicit `build` command
+//
+// - `tailwindcss -o output.css` // should run the build command, not show the help message
+// - `tailwindcss > output.css` // should run the build command, not show the help message
+if ((process.stdout.isTTY && !process.argv.slice(2).includes('-o')) || shared['--help']) {
+ help({
+ usage: ['tailwindcss [--input input.css] [--output output.css] [--watch] [options…]'],
+ options: { ...build.options(), ...sharedOptions },
+ })
+ process.exit(0)
+}
+
+// Handle the build command
+build.handle(args(build.options()))
diff --git a/packages/tailwindcss/src/cli/utils/args.test.ts b/packages/tailwindcss/src/cli/utils/args.test.ts
new file mode 100644
index 000000000..51d2c4747
--- /dev/null
+++ b/packages/tailwindcss/src/cli/utils/args.test.ts
@@ -0,0 +1,123 @@
+import { expect, it } from 'vitest'
+import { args, type Arg } from './args'
+
+it('should be possible to parse a single argument', () => {
+ expect(
+ args(
+ {
+ '--input': { type: 'string', description: 'Input file' },
+ },
+ ['--input', 'input.css'],
+ ),
+ ).toMatchInlineSnapshot(`
+ {
+ "--input": "input.css",
+ "_": [],
+ }
+ `)
+})
+
+it('should fallback to the default value if no flag is passed', () => {
+ expect(
+ args(
+ {
+ '--input': { type: 'string', description: 'Input file', default: 'input.css' },
+ },
+ ['--other'],
+ ),
+ ).toMatchInlineSnapshot(`
+ {
+ "--input": "input.css",
+ "_": [],
+ }
+ `)
+})
+
+it('should fallback to null if no flag is passed and no default value is provided', () => {
+ expect(
+ args(
+ {
+ '--input': { type: 'string', description: 'Input file' },
+ },
+ ['--other'],
+ ),
+ ).toMatchInlineSnapshot(`
+ {
+ "--input": null,
+ "_": [],
+ }
+ `)
+})
+
+it('should be possible to parse a single argument using the shorthand alias', () => {
+ expect(
+ args(
+ {
+ '--input': { type: 'string', description: 'Input file', alias: '-i' },
+ },
+ ['-i', 'input.css'],
+ ),
+ ).toMatchInlineSnapshot(`
+ {
+ "--input": "input.css",
+ "_": [],
+ }
+ `)
+})
+
+it('should convert the incoming value to the correct type', () => {
+ expect(
+ args(
+ {
+ '--input': { type: 'string', description: 'Input file' },
+ '--watch': { type: 'boolean', description: 'Watch mode' },
+ '--retries': { type: 'number', description: 'Amount of retries' },
+ },
+ ['--input', 'input.css', '--watch', '--retries', '3'],
+ ),
+ ).toMatchInlineSnapshot(`
+ {
+ "--input": "input.css",
+ "--retries": 3,
+ "--watch": true,
+ "_": [],
+ }
+ `)
+})
+
+it('should be possible to provide multiple types, and convert the value to that type', () => {
+ let options = {
+ '--retries': { type: 'boolean | number | string', description: 'Retries' },
+ } satisfies Arg
+
+ expect(args(options, ['--retries'])).toMatchInlineSnapshot(`
+ {
+ "--retries": true,
+ "_": [],
+ }
+ `)
+ expect(args(options, ['--retries', 'true'])).toMatchInlineSnapshot(`
+ {
+ "--retries": true,
+ "_": [],
+ }
+ `)
+ expect(args(options, ['--retries', 'false'])).toMatchInlineSnapshot(`
+ {
+ "--retries": false,
+ "_": [],
+ }
+ `)
+ expect(args(options, ['--retries', '5'])).toMatchInlineSnapshot(`
+ {
+ "--retries": 5,
+ "_": [],
+ }
+ `)
+ expect(args(options, ['--retries', 'indefinitely'])).toMatchInlineSnapshot(`
+ {
+ "--retries": "indefinitely",
+ "_": [],
+ }
+ `)
+})
diff --git a/packages/tailwindcss/src/cli/utils/args.ts b/packages/tailwindcss/src/cli/utils/args.ts
new file mode 100644
index 000000000..81bd847d5
--- /dev/null
+++ b/packages/tailwindcss/src/cli/utils/args.ts
@@ -0,0 +1,160 @@
+import parse from 'mri'
+
+// Definition of the arguments for a command in the CLI.
+export type Arg = {
+ [key: `--${string}`]: {
+ type: keyof Types
+ description: string
+ alias?: `-${string}`
+ default?: Types[keyof Types]
+ }
+}
+
+// Each argument will have a type and we want to convert the incoming raw string
+// based value to the correct type. We can't use pure TypeScript types because
+// these don't exist at runtime. Instead, we define a string-based type that
+// maps to a TypeScript type.
+type Types = {
+ boolean: boolean
+ number: number | null
+ string: string | null
+ 'boolean | string': boolean | string | null
+ 'number | string': number | string | null
+ 'boolean | number': boolean | number | null
+ 'boolean | number | string': boolean | number | string | null
+}
+
+// Convert the `Arg` type to a type that can be used at runtime.
+//
+// E.g.:
+//
+// Arg:
+// ```
+// { '--input': { type: 'string', description: 'Input file', alias: '-i' } }
+// ```
+//
+// Command:
+// ```
+// ./tailwindcss -i input.css
+// ./tailwindcss --input input.css
+// ```
+//
+// Result type:
+// ```
+// {
+// _: string[], // All non-flag arguments
+// '--input': string | null // The `--input` flag will be filled with `null`, if the flag is not used.
+// // The `null` type will not be there if `default` is provided.
+// }
+// ```
+//
+// Result runtime object:
+// ```
+// {
+// _: [],
+// '--input': 'input.css'
+// }
+// ```
+export type Result = {
+ [K in keyof T]: T[K] extends { type: keyof Types; default?: any }
+ ? undefined extends T[K]['default']
+ ? Types[T[K]['type']]
+ : NonNullable
+ : never
+} & {
+ // All non-flag arguments
+ _: string[]
+}
+
+export function args(options: T, argv = process.argv.slice(2)): Result {
+ let parsed = parse(argv)
+
+ let result: { _: string[]; [key: string]: unknown } = {
+ _: parsed._,
+ }
+
+ for (let [
+ flag,
+ { type, alias, default: defaultValue = type === 'boolean' ? false : null },
+ ] of Object.entries(options)) {
+ // Start with the default value
+ result[flag] = defaultValue
+
+ // Try to find the `alias`, and map it to long form `flag`
+ if (alias) {
+ let key = alias.slice(1)
+ if (parsed[key] !== undefined) {
+ result[flag] = convert(parsed[key], type)
+ }
+ }
+
+ // Try to find the long form `flag`
+ {
+ let key = flag.slice(2)
+ if (parsed[key] !== undefined) {
+ result[flag] = convert(parsed[key], type)
+ }
+ }
+ }
+
+ return result as Result
+}
+
+// ---
+
+type ArgumentType = string | boolean
+
+// Try to convert the raw incoming `value` (which will be a string or a boolean,
+// this is coming from `mri`'s parse function'), to the correct type based on
+// the `type` of the argument.
+function convert(value: string | boolean, type: T) {
+ switch (type) {
+ case 'string':
+ return convertString(value)
+ case 'boolean':
+ return convertBoolean(value)
+ case 'number':
+ return convertNumber(value)
+ case 'boolean | string':
+ return convertBoolean(value) ?? convertString(value)
+ case 'number | string':
+ return convertNumber(value) ?? convertString(value)
+ case 'boolean | number':
+ return convertBoolean(value) ?? convertNumber(value)
+ case 'boolean | number | string':
+ return convertBoolean(value) ?? convertNumber(value) ?? convertString(value)
+ default:
+ throw new Error(`Unhandled type: ${type}`)
+ }
+}
+
+function convertBoolean(value: ArgumentType) {
+ if (value === true || value === false) {
+ return value
+ }
+
+ if (value === 'true') {
+ return true
+ }
+
+ if (value === 'false') {
+ return false
+ }
+}
+
+function convertNumber(value: ArgumentType) {
+ if (typeof value === 'number') {
+ return value
+ }
+
+ {
+ let valueAsNumber = Number(value)
+ if (!Number.isNaN(valueAsNumber)) {
+ return valueAsNumber
+ }
+ }
+}
+
+function convertString(value: ArgumentType) {
+ return `${value}`
+}
diff --git a/packages/tailwindcss/src/cli/utils/format-ns.test.ts b/packages/tailwindcss/src/cli/utils/format-ns.test.ts
new file mode 100644
index 000000000..a5382d217
--- /dev/null
+++ b/packages/tailwindcss/src/cli/utils/format-ns.test.ts
@@ -0,0 +1,28 @@
+import { expect, it } from 'vitest'
+import { formatNanoseconds } from './format-ns'
+
+it.each([
+ [0, '0ns'],
+ [1, '1ns'],
+ [999, '999ns'],
+ [1000, '1µs'],
+ [1001, '1µs'],
+ [999999, '999µs'],
+ [1000000, '1ms'],
+ [1000001, '1ms'],
+ [999999999, '999ms'],
+ [1000000000, '1s'],
+ [1000000001, '1s'],
+ [59999999999, '59s'],
+ [60000000000, '1m'],
+ [60000000001, '1m'],
+ [3599999999999n, '59m'],
+ [3600000000000n, '1h'],
+ [3600000000001n, '1h'],
+ [86399999999999n, '23h'],
+ [86400000000000n, '1d'],
+ [86400000000001n, '1d'],
+ [8640000000000000n, '100d'],
+])('should format %s nanoseconds as %s', (ns, expected) => {
+ expect(formatNanoseconds(ns)).toBe(expected)
+})
diff --git a/packages/tailwindcss/src/cli/utils/format-ns.ts b/packages/tailwindcss/src/cli/utils/format-ns.ts
new file mode 100644
index 000000000..39889d401
--- /dev/null
+++ b/packages/tailwindcss/src/cli/utils/format-ns.ts
@@ -0,0 +1,23 @@
+export function formatNanoseconds(input: bigint | number) {
+ let ns = typeof input === 'number' ? BigInt(input) : input
+
+ if (ns < 1_000n) return `${ns}ns`
+ ns /= 1_000n
+
+ if (ns < 1_000n) return `${ns}µs`
+ ns /= 1_000n
+
+ if (ns < 1_000n) return `${ns}ms`
+ ns /= 1_000n
+
+ if (ns < 60n) return `${ns}s`
+ ns /= 60n
+
+ if (ns < 60n) return `${ns}m`
+ ns /= 60n
+
+ if (ns < 24n) return `${ns}h`
+ ns /= 24n
+
+ return `${ns}d`
+}
diff --git a/packages/tailwindcss/src/cli/utils/renderer.test.ts b/packages/tailwindcss/src/cli/utils/renderer.test.ts
new file mode 100644
index 000000000..d234be40f
--- /dev/null
+++ b/packages/tailwindcss/src/cli/utils/renderer.test.ts
@@ -0,0 +1,63 @@
+import path from 'path'
+import { describe, expect, it } from 'vitest'
+import { relative, wordWrap } from './renderer'
+
+describe('relative', () => {
+ it('should print an absolute path relative to the current working directory', () => {
+ expect(relative(path.resolve('index.css'))).toMatchInlineSnapshot(`"./index.css"`)
+ })
+
+ it('should prefer the shortest value by default', () => {
+ // Shortest between absolute and relative paths
+ expect(relative('index.css')).toMatchInlineSnapshot(`"index.css"`)
+ })
+
+ it('should be possible to override the current working directory', () => {
+ expect(relative('../utils/index.css', '..')).toMatchInlineSnapshot(`"./utils/index.css"`)
+ })
+
+ it('should be possible to always prefer the relative path', () => {
+ expect(
+ relative('index.css', process.cwd(), { preferAbsoluteIfShorter: false }),
+ ).toMatchInlineSnapshot(`"./index.css"`)
+ })
+})
+
+describe('word wrap', () => {
+ it('should wrap a sentence', () => {
+ expect(wordWrap('The quick brown fox jumps over the lazy dog', 10)).toMatchInlineSnapshot(`
+ [
+ "The quick",
+ "brown fox",
+ "jumps over",
+ "the lazy",
+ "dog",
+ ]
+ `)
+ expect(wordWrap('The quick brown fox jumps over the lazy dog', 30)).toMatchInlineSnapshot(`
+ [
+ "The quick brown fox jumps over",
+ "the lazy dog",
+ ]
+ `)
+ })
+
+ it('should wrap a sentence with ANSI escape codes', () => {
+ // The ANSI escape codes are not counted in the length, but they should
+ // still be rendered correctly.
+ expect(
+ wordWrap(
+ '\x1B[31mThe\x1B[39m \x1B[32mquick\x1B[39m \x1B[34mbrown\x1B[39m \x1B[35mfox\x1B[39m jumps over the lazy dog',
+ 10,
+ ),
+ ).toMatchInlineSnapshot(`
+ [
+ "[31mThe[39m [32mquick[39m",
+ "[34mbrown[39m [35mfox[39m",
+ "jumps over",
+ "the lazy",
+ "dog",
+ ]
+ `)
+ })
+})
diff --git a/packages/tailwindcss/src/cli/utils/renderer.ts b/packages/tailwindcss/src/cli/utils/renderer.ts
new file mode 100644
index 000000000..9cb2761d4
--- /dev/null
+++ b/packages/tailwindcss/src/cli/utils/renderer.ts
@@ -0,0 +1,98 @@
+import path from 'node:path'
+import pc from 'picocolors'
+import { version } from '../../../package.json'
+import { formatNanoseconds } from './format-ns'
+
+export const UI = {
+ indent: 2,
+}
+
+export function header() {
+ return `${pc.italic(pc.bold(pc.blue('\u2248')))} tailwindcss ${pc.blue(`v${version}`)}`
+}
+
+export function highlight(file: string) {
+ return `${pc.dim(pc.blue('`'))}${pc.blue(file)}${pc.dim(pc.blue('`'))}`
+}
+
+/**
+ * Convert an `absolute` path to a `relative` path from the current working
+ * directory.
+ */
+export function relative(
+ to: string,
+ from = process.cwd(),
+ { preferAbsoluteIfShorter = true } = {},
+) {
+ let result = path.relative(from, to)
+ if (!result.startsWith('..')) {
+ result = `.${path.sep}${result}`
+ }
+
+ if (preferAbsoluteIfShorter && result.length > to.length) {
+ return to
+ }
+
+ return result
+}
+
+/**
+ * Wrap `text` into multiple lines based on the `width`.
+ */
+export function wordWrap(text: string, width: number) {
+ let words = text.split(' ')
+ let lines = []
+
+ let line = ''
+ let lineLength = 0
+ for (let word of words) {
+ let wordLength = clearAnsiEscapes(word).length
+
+ if (lineLength + wordLength + 1 > width) {
+ lines.push(line)
+ line = ''
+ lineLength = 0
+ }
+
+ line += (lineLength ? ' ' : '') + word
+ lineLength += wordLength + (lineLength ? 1 : 0)
+ }
+
+ if (lineLength) {
+ lines.push(line)
+ }
+
+ return lines
+}
+
+const ESCAPE = /((?:\x9B|\x1B\[)[0-?]*[ -\/]*[@-~])/g
+function clearAnsiEscapes(input: string) {
+ return input.replace(ESCAPE, '')
+}
+
+/**
+ * Format a duration in nanoseconds to a more human readable format.
+ */
+export function formatDuration(ns: bigint) {
+ let formatted = formatNanoseconds(ns)
+
+ if (ns <= 50 * 1e6) return pc.green(formatted)
+ if (ns <= 300 * 1e6) return pc.blue(formatted)
+ if (ns <= 1000 * 1e6) return pc.yellow(formatted)
+
+ return pc.red(formatted)
+}
+
+export function indent(value: string, offset = 0) {
+ return `${' '.repeat(offset + UI.indent)}${value}`
+}
+
+// Rust inspired functions to print to the console:
+
+export function eprintln(value = '') {
+ process.stderr.write(`${value}\n`)
+}
+
+export function println(value = '') {
+ process.stdout.write(`${value}\n`)
+}
diff --git a/packages/tailwindcss/src/cli/utils/resolve.ts b/packages/tailwindcss/src/cli/utils/resolve.ts
new file mode 100644
index 000000000..96a63b297
--- /dev/null
+++ b/packages/tailwindcss/src/cli/utils/resolve.ts
@@ -0,0 +1,4 @@
+import { createRequire } from 'node:module'
+
+export const resolve =
+ typeof require?.resolve !== 'undefined' ? require.resolve : createRequire(import.meta.url).resolve
diff --git a/packages/tailwindcss/src/compile.ts b/packages/tailwindcss/src/compile.ts
new file mode 100644
index 000000000..b91c19aca
--- /dev/null
+++ b/packages/tailwindcss/src/compile.ts
@@ -0,0 +1,251 @@
+import { rule, type AstNode, type Rule } from './ast'
+import { parseCandidate, parseVariant, type Candidate, type Variant } from './candidate'
+import { type DesignSystem } from './design-system'
+import GLOBAL_PROPERTY_ORDER from './property-order'
+import { DefaultMap } from './utils/default-map'
+import { escape } from './utils/escape'
+import type { Variants } from './variants'
+
+export function compileCandidates(
+ rawCandidates: string[],
+ designSystem: DesignSystem,
+ { throwOnInvalidCandidate = false } = {},
+) {
+ // Ensure the candidates are sorted alphabetically
+ rawCandidates.sort()
+
+ let nodeSorting = new Map<
+ AstNode,
+ { properties: number[]; variants: bigint; candidate: string }
+ >()
+ let astNodes: AstNode[] = []
+
+ // A lazy map implementation that will return the variant if it exists. If it
+ // doesn't exist yet, the raw string variant will be parsed and added to the
+ // map.
+ let parsedVariants: DefaultMap = new DefaultMap((variant, map) => {
+ return parseVariant(variant, designSystem.variants, map)
+ })
+
+ let candidates = new Map()
+
+ // Parse candidates and variants
+ for (let rawCandidate of rawCandidates) {
+ let candidate = parseCandidate(rawCandidate, designSystem.utilities, parsedVariants)
+ if (candidate === null) {
+ if (throwOnInvalidCandidate) {
+ throw new Error(`Cannot apply unknown utility class: ${rawCandidate}`)
+ }
+ continue // Bail, invalid candidate
+ }
+ candidates.set(candidate, rawCandidate)
+ }
+
+ // Sort the variants
+ let variants = Array.from(parsedVariants.values()).sort((a, z) => {
+ return designSystem.variants.compare(a, z)
+ })
+
+ // Create the AST
+ next: for (let [candidate, rawCandidate] of candidates) {
+ let nodes: AstNode[] = []
+
+ // Handle arbitrary properties
+ if (candidate.kind === 'arbitrary') {
+ let compileFn = designSystem.utilities.getArbitrary()
+
+ // Build the node
+ let compiledNodes = compileFn(candidate)
+ if (compiledNodes === undefined) {
+ if (throwOnInvalidCandidate) {
+ throw new Error(`Cannot apply unknown utility class: ${rawCandidate}`)
+ }
+ continue next
+ }
+
+ nodes = compiledNodes
+ }
+
+ // Handle named utilities
+ else if (candidate.kind === 'static' || candidate.kind === 'functional') {
+ // Safety: At this point it is safe to use TypeScript's non-null assertion
+ // operator because if the `candidate.root` didn't exist, `parseCandidate`
+ // would have returned `null` and we would have returned early resulting
+ // in not hitting this code path.
+ let { compileFn } = designSystem.utilities.get(candidate.root)!
+
+ // Build the node
+ let compiledNodes = compileFn(candidate)
+ if (compiledNodes === undefined) {
+ if (throwOnInvalidCandidate) {
+ throw new Error(`Cannot apply unknown utility class: ${rawCandidate}`)
+ }
+ continue next
+ }
+
+ nodes = compiledNodes
+ }
+
+ let propertySort = getPropertySort(nodes)
+
+ if (candidate.important) {
+ applyImportant(nodes)
+ }
+
+ let node: Rule = {
+ kind: 'rule',
+ selector: `.${escape(rawCandidate)}`,
+ nodes,
+ }
+
+ let variantOrder = 0n
+ for (let variant of candidate.variants) {
+ let result = applyVariant(node, variant, designSystem.variants)
+
+ // When the variant results in `null`, it means that the variant cannot be
+ // applied to the rule. Discard the candidate and continue to the next
+ // one.
+ if (result === null) {
+ if (throwOnInvalidCandidate) {
+ throw new Error(`Cannot apply unknown utility class: ${rawCandidate}`)
+ }
+ continue next
+ }
+
+ // Track the variant order which is a number with each bit representing a
+ // variant. This allows us to sort the rules based on the order of
+ // variants used.
+ variantOrder |= 1n << BigInt(variants.indexOf(variant))
+ }
+
+ nodeSorting.set(node, {
+ properties: propertySort,
+ variants: variantOrder,
+ candidate: rawCandidate,
+ })
+ astNodes.push(node)
+ }
+
+ astNodes.sort((a, z) => {
+ // Safety: At this point it is safe to use TypeScript's non-null assertion
+ // operator because if the ast nodes didn't exist, we introduced a bug
+ // above, but there is no need to re-check just to be sure. If this relied
+ // on pure user input, then we would need to check for its existence.
+ let aSorting = nodeSorting.get(a)!
+ let zSorting = nodeSorting.get(z)!
+
+ // Sort by variant order first
+ if (aSorting.variants - zSorting.variants !== 0n) {
+ return Number(aSorting.variants - zSorting.variants)
+ }
+
+ // Find the first property that is different between the two rules
+ let offset = 0
+ while (
+ aSorting.properties.length < offset &&
+ zSorting.properties.length < offset &&
+ aSorting.properties[offset] === zSorting.properties[offset]
+ ) {
+ offset += 1
+ }
+
+ return (
+ // Sort by lowest property index first
+ (aSorting.properties[offset] ?? Infinity) - (zSorting.properties[offset] ?? Infinity) ||
+ // Sort by most properties first, then by least properties
+ zSorting.properties.length - aSorting.properties.length
+ )
+ })
+
+ return {
+ astNodes,
+ nodeSorting,
+ }
+}
+
+export function applyVariant(node: Rule, variant: Variant, variants: Variants): null | void {
+ if (variant.kind === 'arbitrary') {
+ node.nodes = [rule(variant.selector, node.nodes)]
+ return
+ }
+
+ // Safety: At this point it is safe to use TypeScript's non-null assertion
+ // operator because if the `candidate.root` didn't exist, `parseCandidate`
+ // would have returned `null` and we would have returned early resulting in
+ // not hitting this code path.
+ let { applyFn } = variants.get(variant.root)!
+
+ if (variant.kind === 'compound') {
+ let result = applyVariant(node, variant.variant, variants)
+ if (result === null) return null
+
+ for (let child of node.nodes) {
+ // Only some variants wrap children in rules. For example, the `force`
+ // variant is a noop on the AST. And the `has` variant modifies the
+ // selector rather than the children.
+ //
+ // This means `child` may be a declaration and we don't want to apply the
+ // variant to it. This also means the entire variant as a whole is not
+ // applicable to the rule and should generate nothing.
+ if (child.kind !== 'rule') return null
+
+ let result = applyFn(child as Rule, variant)
+ if (result === null) return null
+ }
+ return
+ }
+
+ // All other variants
+ let result = applyFn(node, variant)
+ if (result === null) return null
+}
+
+function applyImportant(ast: AstNode[]): void {
+ for (let node of ast) {
+ // Skip any `@at-root` rules — we don't want to make the contents of things
+ // like `@keyframes` or `@property` important.
+ if (node.kind === 'rule' && node.selector === '@at-root') {
+ continue
+ }
+
+ if (node.kind === 'declaration') {
+ node.important = true
+ } else if (node.kind === 'rule') {
+ applyImportant(node.nodes)
+ }
+ }
+}
+
+function getPropertySort(nodes: AstNode[]) {
+ // Determine sort order based on properties used
+ let propertySort = new Set()
+ let q: AstNode[] = nodes.slice()
+
+ while (q.length > 0) {
+ // Safety: At this point it is safe to use TypeScript's non-null assertion
+ // operator because we guarded against `q.length > 0` above.
+ let node = q.shift()!
+ if (node.kind === 'declaration') {
+ if (node.property === '--tw-sort') {
+ let idx = GLOBAL_PROPERTY_ORDER.indexOf(node.value)
+ if (idx !== -1) {
+ propertySort.add(idx)
+ break
+ }
+ }
+
+ let idx = GLOBAL_PROPERTY_ORDER.indexOf(node.property)
+ if (idx !== -1) propertySort.add(idx)
+ } else if (node.kind === 'rule') {
+ // Don't consider properties within `@at-root` when determining the sort
+ // order for a rule.
+ if (node.selector === '@at-root') continue
+
+ for (let child of node.nodes) {
+ q.push(child)
+ }
+ }
+ }
+
+ return Array.from(propertySort).sort((a, z) => a - z)
+}
diff --git a/packages/tailwindcss/src/css-parser.test.ts b/packages/tailwindcss/src/css-parser.test.ts
new file mode 100644
index 000000000..9d4b26d86
--- /dev/null
+++ b/packages/tailwindcss/src/css-parser.test.ts
@@ -0,0 +1,995 @@
+import { describe, expect, it } from 'vitest'
+import { parse } from './css-parser'
+
+const css = String.raw
+
+describe('comments', () => {
+ it('should parse a comment and ignore it', () => {
+ expect(
+ parse(css`
+ /*Hello, world!*/
+ `),
+ ).toEqual([])
+ })
+
+ it('should parse a comment with an escaped ending and ignore it', () => {
+ expect(
+ parse(css`
+ /*Hello, \*\/ world!*/
+ `),
+ ).toEqual([])
+ })
+
+ it('should parse a comment inside of a selector and ignore it', () => {
+ expect(
+ parse(css`
+ .foo {
+ /*Example comment*/
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '.foo',
+ nodes: [],
+ },
+ ])
+ })
+
+ it('should remove comments in between selectors while maintaining the correct whitespace', () => {
+ expect(
+ parse(css`
+ .foo/*.bar*/.baz {
+ }
+ .foo/*.bar*//*.baz*/.qux
+ {
+ }
+ .foo/*.bar*/ /*.baz*/.qux {
+ /* ^ whitespace */
+ }
+ .foo /*.bar*/.baz {
+ /*^ whitespace */
+ }
+ .foo/*.bar*/ .baz {
+ /* ^ whitespace */
+ }
+ .foo/*.bar*/
+ .baz {
+ }
+ `),
+ ).toEqual([
+ { kind: 'rule', selector: '.foo.baz', nodes: [] },
+ { kind: 'rule', selector: '.foo.qux', nodes: [] },
+ { kind: 'rule', selector: '.foo .qux', nodes: [] },
+ { kind: 'rule', selector: '.foo .baz', nodes: [] },
+ { kind: 'rule', selector: '.foo .baz', nodes: [] },
+ { kind: 'rule', selector: '.foo .baz', nodes: [] },
+ ])
+ })
+
+ it('should collect license comments', () => {
+ expect(
+ parse(css`
+ /*! License #1 */
+ /*!
+ * License #2
+ */
+ `),
+ ).toEqual([
+ { kind: 'comment', value: '! License #1 ' },
+ {
+ kind: 'comment',
+ value: `!
+ * License #2
+ `,
+ },
+ ])
+ })
+
+ it('should hoist all license comments', () => {
+ expect(
+ parse(css`
+ /*! License #1 */
+ .foo {
+ color: red; /*! License #1.5 */
+ }
+ /*! License #2 */
+ .bar {
+ /*! License #2.5 */
+ color: blue;
+ }
+ /*! License #3 */
+ `),
+ ).toEqual([
+ { kind: 'comment', value: '! License #1 ' },
+ { kind: 'comment', value: '! License #1.5 ' },
+ { kind: 'comment', value: '! License #2 ' },
+ { kind: 'comment', value: '! License #2.5 ' },
+ { kind: 'comment', value: '! License #3 ' },
+ {
+ kind: 'rule',
+ selector: '.foo',
+ nodes: [{ kind: 'declaration', property: 'color', value: 'red', important: false }],
+ },
+ {
+ kind: 'rule',
+ selector: '.bar',
+ nodes: [{ kind: 'declaration', property: 'color', value: 'blue', important: false }],
+ },
+ ])
+ })
+
+ it('should handle comments before element selectors', () => {
+ expect(
+ parse(css`
+ .dark /* comment */p {
+ color: black;
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '.dark p',
+ nodes: [
+ {
+ kind: 'declaration',
+ property: 'color',
+ value: 'black',
+ important: false,
+ },
+ ],
+ },
+ ])
+ })
+})
+
+describe('declarations', () => {
+ it('should parse a simple declaration', () => {
+ expect(
+ parse(css`
+ color: red;
+ `),
+ ).toEqual([{ kind: 'declaration', property: 'color', value: 'red', important: false }])
+ })
+
+ it('should parse declarations with strings', () => {
+ expect(
+ parse(css`
+ content: 'Hello, world!';
+ `),
+ ).toEqual([
+ { kind: 'declaration', property: 'content', value: "'Hello, world!'", important: false },
+ ])
+ })
+
+ it('should parse declarations with nested strings', () => {
+ expect(
+ parse(css`
+ content: 'Good, "monday", morning!';
+ `),
+ ).toEqual([
+ {
+ kind: 'declaration',
+ property: 'content',
+ value: `'Good, "monday", morning!'`,
+ important: false,
+ },
+ ])
+ })
+
+ it('should parse declarations with nested strings that are not balanced', () => {
+ expect(
+ parse(css`
+ content: "It's a beautiful day!";
+ `),
+ ).toEqual([
+ {
+ kind: 'declaration',
+ property: 'content',
+ value: `"It's a beautiful day!"`,
+ important: false,
+ },
+ ])
+ })
+
+ it('should parse declarations with with strings and escaped string endings', () => {
+ expect(
+ parse(css`
+ content: 'These are not the end "\' of the string';
+ `),
+ ).toEqual([
+ {
+ kind: 'declaration',
+ property: 'content',
+ value: `'These are not the end \"\\' of the string'`,
+ important: false,
+ },
+ ])
+ })
+
+ describe('important', () => {
+ it('should parse declarations with `!important`', () => {
+ expect(
+ parse(css`
+ width: 123px !important;
+ `),
+ ).toEqual([
+ {
+ kind: 'declaration',
+ property: 'width',
+ value: '123px',
+ important: true,
+ },
+ ])
+ })
+
+ it('should parse declarations with `!important` when there is a trailing comment', () => {
+ expect(
+ parse(css`
+ width: 123px !important /* Very important */;
+ `),
+ ).toEqual([
+ {
+ kind: 'declaration',
+ property: 'width',
+ value: '123px',
+ important: true,
+ },
+ ])
+ })
+ })
+
+ describe('Custom properties', () => {
+ it('should parse a custom property', () => {
+ expect(
+ parse(css`
+ --foo: bar;
+ `),
+ ).toEqual([{ kind: 'declaration', property: '--foo', value: 'bar', important: false }])
+ })
+
+ it('should parse a minified custom property', () => {
+ expect(parse(':root{--foo:bar;}')).toEqual([
+ {
+ kind: 'rule',
+ selector: ':root',
+ nodes: [
+ {
+ kind: 'declaration',
+ property: '--foo',
+ value: 'bar',
+ important: false,
+ },
+ ],
+ },
+ ])
+ })
+
+ it('should parse a minified custom property with no semicolon ', () => {
+ expect(parse(':root{--foo:bar}')).toEqual([
+ {
+ kind: 'rule',
+ selector: ':root',
+ nodes: [
+ {
+ kind: 'declaration',
+ property: '--foo',
+ value: 'bar',
+ important: false,
+ },
+ ],
+ },
+ ])
+ })
+
+ it('should parse a custom property with a missing ending `;`', () => {
+ expect(
+ parse(`
+ --foo: bar
+ `),
+ ).toEqual([{ kind: 'declaration', property: '--foo', value: 'bar', important: false }])
+ })
+
+ it('should parse a custom property with a missing ending `;` and `!important`', () => {
+ expect(
+ parse(`
+ --foo: bar !important
+ `),
+ ).toEqual([{ kind: 'declaration', property: '--foo', value: 'bar', important: true }])
+ })
+
+ it('should parse a custom property with an embedded programming language', () => {
+ expect(
+ parse(css`
+ --foo: if(x > 5) this.width = 10;
+ `),
+ ).toEqual([
+ {
+ kind: 'declaration',
+ property: '--foo',
+ value: 'if(x > 5) this.width = 10',
+ important: false,
+ },
+ ])
+ })
+
+ it('should parse a custom property with an empty block as the value', () => {
+ expect(parse('--foo: {};')).toEqual([
+ {
+ kind: 'declaration',
+ property: '--foo',
+ value: '{}',
+ important: false,
+ },
+ ])
+ })
+
+ it('should parse a custom property with a block including nested "css"', () => {
+ expect(
+ parse(css`
+ --foo: {
+ background-color: red;
+ /* A comment */
+ content: 'Hello, world!';
+ };
+ `),
+ ).toEqual([
+ {
+ kind: 'declaration',
+ property: '--foo',
+ value: `{
+ background-color: red;
+ /* A comment */
+ content: 'Hello, world!';
+ }`,
+ important: false,
+ },
+ ])
+ })
+
+ it('should parse a custom property with a block including nested "css" and comments with end characters inside them', () => {
+ expect(
+ parse(css`
+ --foo: {
+ background-color: red;
+ /* A comment ; */
+ content: 'Hello, world!';
+ };
+ --bar: {
+ background-color: red;
+ /* A comment } */
+ content: 'Hello, world!';
+ };
+ `),
+ ).toEqual([
+ {
+ kind: 'declaration',
+ property: '--foo',
+ value: `{
+ background-color: red;
+ /* A comment ; */
+ content: 'Hello, world!';
+ }`,
+ important: false,
+ },
+ {
+ kind: 'declaration',
+ property: '--bar',
+ value: `{
+ background-color: red;
+ /* A comment } */
+ content: 'Hello, world!';
+ }`,
+ important: false,
+ },
+ ])
+ })
+
+ it('should parse a custom property with escaped characters in the value', () => {
+ expect(
+ parse(css`
+ --foo: This is not the end \;, but this is;
+ `),
+ ).toEqual([
+ {
+ kind: 'declaration',
+ property: '--foo',
+ value: 'This is not the end \\;, but this is',
+ important: false,
+ },
+ ])
+ })
+
+ it('should parse a custom property with escaped characters inside a comment in the value', () => {
+ expect(
+ parse(css`
+ --foo: /* This is not the end \; this is also not the end ; */ but this is;
+ `),
+ ).toEqual([
+ {
+ kind: 'declaration',
+ property: '--foo',
+ value: '/* This is not the end \\; this is also not the end ; */ but this is',
+ important: false,
+ },
+ ])
+ })
+
+ it('should parse empty custom properties', () => {
+ expect(
+ parse(css`
+ --foo: ;
+ `),
+ ).toEqual([{ kind: 'declaration', property: '--foo', value: '', important: false }])
+ })
+
+ it('should parse custom properties with `!important`', () => {
+ expect(
+ parse(css`
+ --foo: bar !important;
+ `),
+ ).toEqual([{ kind: 'declaration', property: '--foo', value: 'bar', important: true }])
+ })
+ })
+
+ it('should parse multiple declarations', () => {
+ expect(
+ parse(css`
+ color: red;
+ background-color: blue;
+ `),
+ ).toEqual([
+ { kind: 'declaration', property: 'color', value: 'red', important: false },
+ { kind: 'declaration', property: 'background-color', value: 'blue', important: false },
+ ])
+ })
+
+ /**
+ *
+ */
+ it('should correctly parse comments with `:` inside of them', () => {
+ expect(
+ parse(css`
+ color/* color: #f00; */: red;
+ font-weight:/* font-size: 12px */ bold;
+
+ .foo {
+ background-color/* background-color: #f00; */: red;
+ }
+ `),
+ ).toEqual([
+ { kind: 'declaration', property: 'color', value: 'red', important: false },
+ { kind: 'declaration', property: 'font-weight', value: 'bold', important: false },
+ {
+ kind: 'rule',
+ selector: '.foo',
+ nodes: [
+ {
+ kind: 'declaration',
+ property: 'background-color',
+ value: 'red',
+ important: false,
+ },
+ ],
+ },
+ ])
+ })
+
+ it('should parse mutlti-line declarations', () => {
+ expect(
+ parse(css`
+ .foo {
+ grid-template-areas:
+ 'header header header'
+ 'sidebar main main'
+ 'footer footer footer';
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '.foo',
+ nodes: [
+ {
+ kind: 'declaration',
+ property: 'grid-template-areas',
+ value: "'header header header' 'sidebar main main' 'footer footer footer'",
+ important: false,
+ },
+ ],
+ },
+ ])
+ })
+})
+
+describe('selectors', () => {
+ it('should parse a simple selector', () => {
+ expect(
+ parse(css`
+ .foo {
+ }
+ `),
+ ).toEqual([{ kind: 'rule', selector: '.foo', nodes: [] }])
+ })
+
+ it('should parse selectors with escaped characters', () => {
+ expect(
+ parse(css`
+ .hover\:foo:hover {
+ }
+ .\32 xl\:foo {
+ }
+ `),
+ ).toEqual([
+ { kind: 'rule', selector: '.hover\\:foo:hover', nodes: [] },
+ { kind: 'rule', selector: '.\\32 xl\\:foo', nodes: [] },
+ ])
+ })
+
+ it('should parse multiple simple selectors', () => {
+ expect(
+ parse(css`
+ .foo,
+ .bar {
+ }
+ `),
+ ).toEqual([{ kind: 'rule', selector: '.foo, .bar', nodes: [] }])
+ })
+
+ it('should parse multiple declarations inside of a selector', () => {
+ expect(
+ parse(css`
+ .foo {
+ color: red;
+ font-size: 16px;
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '.foo',
+ nodes: [
+ { kind: 'declaration', property: 'color', value: 'red', important: false },
+ { kind: 'declaration', property: 'font-size', value: '16px', important: false },
+ ],
+ },
+ ])
+ })
+
+ it('should parse rules with declarations that end with a missing `;`', () => {
+ expect(
+ parse(`
+ .foo {
+ color: red;
+ font-size: 16px
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '.foo',
+ nodes: [
+ { kind: 'declaration', property: 'color', value: 'red', important: false },
+ { kind: 'declaration', property: 'font-size', value: '16px', important: false },
+ ],
+ },
+ ])
+ })
+
+ it('should parse rules with declarations that end with a missing `;` and `!important`', () => {
+ expect(
+ parse(`
+ .foo {
+ color: red;
+ font-size: 16px !important
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '.foo',
+ nodes: [
+ { kind: 'declaration', property: 'color', value: 'red', important: false },
+ { kind: 'declaration', property: 'font-size', value: '16px', important: true },
+ ],
+ },
+ ])
+ })
+})
+
+describe('at-rules', () => {
+ it('should parse an at-rule without a block', () => {
+ expect(
+ parse(css`
+ @charset "UTF-8";
+ `),
+ ).toEqual([{ kind: 'rule', selector: '@charset "UTF-8"', nodes: [] }])
+ })
+
+ it("should parse an at-rule without a block or semicolon when it's the last rule in a block", () => {
+ expect(
+ parse(`
+ @layer utilities {
+ @tailwind utilities
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '@layer utilities',
+ nodes: [{ kind: 'rule', selector: '@tailwind utilities', nodes: [] }],
+ },
+ ])
+ })
+
+ it('should parse a nested at-rule without a block', () => {
+ expect(
+ parse(css`
+ @layer utilities {
+ @charset "UTF-8";
+ }
+
+ .foo {
+ @apply font-bold hover:text-red-500;
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '@layer utilities',
+ nodes: [{ kind: 'rule', selector: '@charset "UTF-8"', nodes: [] }],
+ },
+ {
+ kind: 'rule',
+ selector: '.foo',
+ nodes: [{ kind: 'rule', selector: '@apply font-bold hover:text-red-500', nodes: [] }],
+ },
+ ])
+ })
+
+ it('should parse custom at-rules without a block', () => {
+ expect(
+ parse(css`
+ @tailwind;
+ @tailwind base;
+ `),
+ ).toEqual([
+ { kind: 'rule', selector: '@tailwind', nodes: [] },
+ { kind: 'rule', selector: '@tailwind base', nodes: [] },
+ ])
+ })
+
+ it('should parse (nested) media queries', () => {
+ expect(
+ parse(css`
+ @media (width >= 600px) {
+ .foo {
+ color: red;
+ @media (width >= 800px) {
+ color: blue;
+ }
+ @media (width >= 1000px) {
+ color: green;
+ }
+ }
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '@media (width >= 600px)',
+ nodes: [
+ {
+ kind: 'rule',
+ selector: '.foo',
+ nodes: [
+ { kind: 'declaration', property: 'color', value: 'red', important: false },
+ {
+ kind: 'rule',
+ selector: '@media (width >= 800px)',
+ nodes: [
+ { kind: 'declaration', property: 'color', value: 'blue', important: false },
+ ],
+ },
+ {
+ kind: 'rule',
+ selector: '@media (width >= 1000px)',
+ nodes: [
+ { kind: 'declaration', property: 'color', value: 'green', important: false },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ])
+ })
+
+ it('should parse at-rules that span multiple lines', () => {
+ expect(
+ parse(css`
+ .foo {
+ @apply hover:text-red-100
+ sm:hover:text-red-200
+ md:hover:text-red-300
+ lg:hover:text-red-400
+ xl:hover:text-red-500;
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ nodes: [
+ {
+ kind: 'rule',
+ nodes: [],
+ selector:
+ '@apply hover:text-red-100 sm:hover:text-red-200 md:hover:text-red-300 lg:hover:text-red-400 xl:hover:text-red-500',
+ },
+ ],
+ selector: '.foo',
+ },
+ ])
+ })
+})
+
+describe('nesting', () => {
+ it('should parse nested rules', () => {
+ expect(
+ parse(css`
+ .foo {
+ .bar {
+ .baz {
+ color: red;
+ }
+ }
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '.foo',
+ nodes: [
+ {
+ kind: 'rule',
+ selector: '.bar',
+ nodes: [
+ {
+ kind: 'rule',
+ selector: '.baz',
+ nodes: [{ kind: 'declaration', property: 'color', value: 'red', important: false }],
+ },
+ ],
+ },
+ ],
+ },
+ ])
+ })
+
+ it('should parse nested selector with `&`', () => {
+ expect(
+ parse(css`
+ .foo {
+ color: red;
+
+ &:hover {
+ color: blue;
+ }
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '.foo',
+ nodes: [
+ { kind: 'declaration', property: 'color', value: 'red', important: false },
+ {
+ kind: 'rule',
+ selector: '&:hover',
+ nodes: [{ kind: 'declaration', property: 'color', value: 'blue', important: false }],
+ },
+ ],
+ },
+ ])
+ })
+
+ it('should parse nested sibling selectors', () => {
+ expect(
+ parse(css`
+ .foo {
+ .bar {
+ color: red;
+ }
+
+ .baz {
+ color: blue;
+ }
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '.foo',
+ nodes: [
+ {
+ kind: 'rule',
+ selector: '.bar',
+ nodes: [{ kind: 'declaration', property: 'color', value: 'red', important: false }],
+ },
+ {
+ kind: 'rule',
+ selector: '.baz',
+ nodes: [{ kind: 'declaration', property: 'color', value: 'blue', important: false }],
+ },
+ ],
+ },
+ ])
+ })
+
+ it('should parse nested sibling selectors and sibling declarations', () => {
+ expect(
+ parse(css`
+ .foo {
+ font-weight: bold;
+ text-declaration-line: underline;
+
+ .bar {
+ color: red;
+ }
+
+ --in-between: 1;
+
+ .baz {
+ color: blue;
+ }
+
+ --at-the-end: 2;
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '.foo',
+ nodes: [
+ { kind: 'declaration', property: 'font-weight', value: 'bold', important: false },
+ {
+ kind: 'declaration',
+ property: 'text-declaration-line',
+ value: 'underline',
+ important: false,
+ },
+ {
+ kind: 'rule',
+ selector: '.bar',
+ nodes: [{ kind: 'declaration', property: 'color', value: 'red', important: false }],
+ },
+ { kind: 'declaration', property: '--in-between', value: '1', important: false },
+ {
+ kind: 'rule',
+ selector: '.baz',
+ nodes: [{ kind: 'declaration', property: 'color', value: 'blue', important: false }],
+ },
+ { kind: 'declaration', property: '--at-the-end', value: '2', important: false },
+ ],
+ },
+ ])
+ })
+})
+
+describe('complex', () => {
+ it('should parse complex examples', () => {
+ expect(
+ parse(css`
+ @custom \{ {
+ foo: bar;
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '@custom \\{',
+ nodes: [{ kind: 'declaration', property: 'foo', value: 'bar', important: false }],
+ },
+ ])
+ })
+
+ it('should parse minified nested CSS', () => {
+ expect(
+ parse('.foo{color:red;@media(width>=600px){.bar{color:blue;font-weight:bold}}}'),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '.foo',
+ nodes: [
+ { kind: 'declaration', property: 'color', value: 'red', important: false },
+ {
+ kind: 'rule',
+ selector: '@media(width>=600px)',
+ nodes: [
+ {
+ kind: 'rule',
+ selector: '.bar',
+ nodes: [
+ { kind: 'declaration', property: 'color', value: 'blue', important: false },
+ { kind: 'declaration', property: 'font-weight', value: 'bold', important: false },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ])
+ })
+
+ it('should ignore everything inside of comments', () => {
+ expect(
+ parse(css`
+ .foo:has(.bar /* instead \*\/ of .baz { */) {
+ color: red;
+ }
+ `),
+ ).toEqual([
+ {
+ kind: 'rule',
+ selector: '.foo:has(.bar )',
+ nodes: [{ kind: 'declaration', property: 'color', value: 'red', important: false }],
+ },
+ ])
+ })
+})
+
+describe('errors', () => {
+ it('should error when curly brackets are unbalanced (opening)', () => {
+ expect(() =>
+ parse(`
+ .foo {
+ color: red;
+ }
+
+ .bar
+ /* ^ Missing opening { */
+ color: blue;
+ }
+ `),
+ ).toThrowErrorMatchingInlineSnapshot(`[Error: Missing opening {]`)
+ })
+
+ it('should error when curly brackets are unbalanced (closing)', () => {
+ expect(() =>
+ parse(`
+ .foo {
+ color: red;
+ }
+
+ .bar {
+ color: blue;
+
+ /* ^ Missing closing } */
+ `),
+ ).toThrowErrorMatchingInlineSnapshot(`[Error: Missing closing } at .bar]`)
+ })
+
+ it('should error when an unterminated string is used', () => {
+ expect(() =>
+ parse(css`
+ .foo {
+ content: "Hello world!
+ /* ^ missing " */
+ font-weight: bold;
+ }
+ `),
+ ).toThrowErrorMatchingInlineSnapshot(`[Error: Unterminated string: "Hello world!"]`)
+ })
+
+ it('should error when an unterminated string is used with a `;`', () => {
+ expect(() =>
+ parse(css`
+ .foo {
+ content: "Hello world!;
+ /* ^ missing " */
+ font-weight: bold;
+ }
+ `),
+ ).toThrowErrorMatchingInlineSnapshot(`[Error: Unterminated string: "Hello world!;"]`)
+ })
+})
diff --git a/packages/tailwindcss/src/css-parser.ts b/packages/tailwindcss/src/css-parser.ts
new file mode 100644
index 000000000..b6978d8b0
--- /dev/null
+++ b/packages/tailwindcss/src/css-parser.ts
@@ -0,0 +1,426 @@
+import { comment, rule, type AstNode, type Comment, type Declaration, type Rule } from './ast'
+
+export function parse(input: string) {
+ let ast: AstNode[] = []
+ let licenseComments: Comment[] = []
+
+ let stack: (Rule | null)[] = []
+
+ let parent = null as Rule | null
+ let node = null as AstNode | null
+
+ let current = ''
+ let closingBracketStack = ''
+
+ for (let i = 0; i < input.length; i++) {
+ let char = input[i]
+
+ // Current character is a `\` therefore the next character is escaped,
+ // consume it together with the next character and continue.
+ //
+ // E.g.:
+ //
+ // ```css
+ // .hover\:foo:hover {}
+ // ^
+ // ```
+ //
+ if (char === '\\') {
+ current += input.slice(i, i + 2)
+ i += 1
+ }
+
+ // Start of a comment.
+ //
+ // E.g.:
+ //
+ // ```css
+ // /* Example */
+ // ^^^^^^^^^^^^^
+ // .foo {
+ // color: red; /* Example */
+ // ^^^^^^^^^^^^^
+ // }
+ // .bar {
+ // color: /* Example */ red;
+ // ^^^^^^^^^^^^^
+ // }
+ // ```
+ else if (char === '/' && input[i + 1] === '*') {
+ let start = i
+
+ for (let j = i + 2; j < input.length; j++) {
+ // Current character is a `\` therefore the next character is escaped.
+ if (input[j] === '\\') {
+ j += 1
+ }
+
+ // End of the comment
+ else if (input[j] === '*' && input[j + 1] === '/') {
+ i = j + 1
+ break
+ }
+ }
+
+ let commentString = input.slice(start, i + 1)
+
+ // Collect all license comments so that we can hoist them to the top of
+ // the AST.
+ if (commentString[2] === '!') {
+ licenseComments.push(comment(commentString.slice(2, -2)))
+ }
+ }
+
+ // Start of a string.
+ else if (char === '"' || char === "'") {
+ let start = i
+
+ // We need to ensure that the closing quote is the same as the opening
+ // quote.
+ //
+ // E.g.:
+ //
+ // ```css
+ // .foo {
+ // content: "This is a string with a 'quote' in it";
+ // ^ ^ -> These are not the end of the string.
+ // }
+ // ```
+ for (let j = i + 1; j < input.length; j++) {
+ // Current character is a `\` therefore the next character is escaped.
+ if (input[j] === '\\') {
+ j += 1
+ }
+
+ // End of the string.
+ else if (input[j] === char) {
+ i = j
+ break
+ }
+
+ // End of the line without ending the string but with a `;` at the end.
+ //
+ // E.g.:
+ //
+ // ```css
+ // .foo {
+ // content: "This is a string with a;
+ // ^ Missing "
+ // }
+ // ```
+ else if (input[j] === ';' && input[j + 1] === '\n') {
+ throw new Error(`Unterminated string: ${input.slice(start, j + 1) + char}`)
+ }
+
+ // End of the line without ending the string.
+ //
+ // E.g.:
+ //
+ // ```css
+ // .foo {
+ // content: "This is a string with a
+ // ^ Missing "
+ // }
+ // ```
+ else if (input[j] === '\n') {
+ throw new Error(`Unterminated string: ${input.slice(start, j) + char}`)
+ }
+ }
+
+ // Adjust `current` to include the string.
+ current += input.slice(start, i + 1)
+ }
+
+ // Skip whitespace if the next character is also whitespace. This allows us
+ // to reduce the amount of whitespace in the AST.
+ else if (
+ (char === ' ' || char === '\n' || char === '\t') &&
+ (input[i + 1] === ' ' || input[i + 1] === '\n' || input[i + 1] === '\t')
+ ) {
+ continue
+ }
+
+ // Start of a custom property.
+ //
+ // Custom properties are very permissive and can contain almost any
+ // character, even `;` and `}`. Therefore we have to make sure that we are
+ // at the correct "end" of the custom property by making sure everything is
+ // balanced.
+ else if (char === '-' && input[i + 1] === '-' && current.length === 0) {
+ let closingBracketStack = ''
+
+ let start = i
+ let colonIdx = -1
+
+ for (let j = i + 2; j < input.length; j++) {
+ // Current character is a `\` therefore the next character is escaped.
+ if (input[j] === '\\') {
+ j += 1
+ }
+
+ // Start of a comment.
+ else if (input[j] === '/' && input[j + 1] === '*') {
+ for (let k = j + 2; k < input.length; k++) {
+ // Current character is a `\` therefore the next character is escaped.
+ if (input[k] === '\\') {
+ k += 1
+ }
+
+ // End of the comment
+ else if (input[k] === '*' && input[k + 1] === '/') {
+ j = k + 1
+ break
+ }
+ }
+ }
+
+ // End of the "property" of the property-value pair.
+ else if (colonIdx === -1 && input[j] === ':') {
+ colonIdx = current.length + j - start
+ }
+
+ // End of the custom property.
+ else if (input[j] === ';' && closingBracketStack.length === 0) {
+ current += input.slice(start, j)
+ i = j
+ break
+ }
+
+ // Start of a block.
+ else if (input[j] === '(') {
+ closingBracketStack += ')'
+ } else if (input[j] === '[') {
+ closingBracketStack += ']'
+ } else if (input[j] === '{') {
+ closingBracketStack += '}'
+ }
+
+ // End of the custom property if didn't use a `;` to end the custom
+ // property.
+ //
+ // E.g.:
+ //
+ // ```css
+ // .foo {
+ // --custom: value
+ // ^
+ // }
+ // ```
+ else if ((input[j] === '}' || input.length - 1 === j) && closingBracketStack.length === 0) {
+ i = j - 1
+ current += input.slice(start, j)
+ break
+ }
+
+ // End of a block.
+ else if (input[j] === ')' || input[j] === ']' || input[j] === '}') {
+ if (
+ closingBracketStack.length > 0 &&
+ input[j] === closingBracketStack[closingBracketStack.length - 1]
+ ) {
+ closingBracketStack = closingBracketStack.slice(0, -1)
+ }
+ }
+ }
+
+ let declaration = parseDeclaration(current, colonIdx)
+ if (parent) {
+ parent.nodes.push(declaration)
+ } else {
+ ast.push(declaration)
+ }
+
+ current = ''
+ }
+
+ // End of a body-less at-rule.
+ //
+ // E.g.:
+ //
+ // ```css
+ // @charset "UTF-8";
+ // ^
+ // ```
+ else if (char === ';' && current[0] === '@') {
+ node = rule(current, [])
+
+ // At-rule is nested inside of a rule, attach it to the parent.
+ if (parent) {
+ parent.nodes.push(node)
+ }
+
+ // We are the root node which means we are done with the current node.
+ else {
+ ast.push(node)
+ }
+
+ // Reset the state for the next node.
+ current = ''
+ node = null
+ }
+
+ // End of a declaration.
+ //
+ // E.g.:
+ //
+ // ```css
+ // .foo {
+ // color: red;
+ // ^
+ // }
+ // ```
+ //
+ else if (char === ';') {
+ let declaration = parseDeclaration(current)
+ if (parent) {
+ parent.nodes.push(declaration)
+ } else {
+ ast.push(declaration)
+ }
+
+ current = ''
+ }
+
+ // Start of a block.
+ else if (char === '{') {
+ closingBracketStack += '}'
+
+ // At this point `current` should resemble a selector or an at-rule.
+ node = rule(current.trim(), [])
+
+ // Attach the rule to the parent in case it's nested.
+ if (parent) {
+ parent.nodes.push(node)
+ }
+
+ // Push the current node to the stack and make it the new parent.
+ stack.push(parent)
+ parent = node
+
+ // Reset the state for the next node.
+ current = ''
+ node = null
+ }
+
+ // End of a block.
+ else if (char === '}') {
+ if (closingBracketStack === '') {
+ throw new Error('Missing opening {')
+ }
+
+ closingBracketStack = closingBracketStack.slice(0, -1)
+
+ // When we hit a `}` and `current` is filled in, then it means that we did
+ // not complete the previous node yet. This means that we hit a
+ // declaration without a `;` at the end.
+ if (current.length > 0) {
+ // This can happen for nested at-rules.
+ //
+ // E.g.:
+ //
+ // ```css
+ // @layer foo {
+ // @tailwind utilities
+ // ^
+ // }
+ // ```
+ if (current[0] === '@') {
+ node = rule(current.trim(), [])
+
+ // At-rule is nested inside of a rule, attach it to the parent.
+ if (parent) {
+ parent.nodes.push(node)
+ }
+
+ // We are the root node which means we are done with the current node.
+ else {
+ ast.push(node)
+ }
+
+ // Reset the state for the next node.
+ current = ''
+ node = null
+ }
+
+ // But it can also happen for declarations.
+ //
+ // E.g.:
+ //
+ // ```css
+ // .foo {
+ // color: red
+ // ^
+ // }
+ // ```
+ else {
+ // Split `current` into a `property` and a `value`. At this point the
+ // comments are already removed which means that we don't have to worry
+ // about `:` inside of comments.
+ let colonIdx = current.indexOf(':')
+
+ // Attach the declaration to the parent.
+ if (parent) {
+ let importantIdx = current.indexOf('!important', colonIdx + 1)
+ parent.nodes.push({
+ kind: 'declaration',
+ property: current.slice(0, colonIdx).trim(),
+ value: current
+ .slice(colonIdx + 1, importantIdx === -1 ? current.length : importantIdx)
+ .trim(),
+ important: importantIdx !== -1,
+ } satisfies Declaration)
+ }
+ }
+ }
+
+ // We are done with the current node, which means we can go up one level
+ // in the stack.
+ let grandParent = stack.pop() ?? null
+
+ // We are the root node which means we are done and contine with the next
+ // node.
+ if (grandParent === null && parent) {
+ ast.push(parent)
+ }
+
+ // Go up one level in the stack.
+ parent = grandParent
+
+ // Reset the state for the next node.
+ current = ''
+ node = null
+ }
+
+ // Any other character is part of the current node.
+ else {
+ // Skip whitespace at the start of a new node.
+ if (current.length === 0 && (char === ' ' || char === '\n' || char === '\t')) {
+ continue
+ }
+
+ current += char
+ }
+ }
+
+ // When we are done parsing then everything should be balanced. If we still
+ // have a leftover `parent`, then it means that we have an unterminated block.
+ if (closingBracketStack.length > 0 && parent) {
+ throw new Error(`Missing closing } at ${parent.selector}`)
+ }
+
+ if (licenseComments.length > 0) {
+ return (licenseComments as AstNode[]).concat(ast)
+ }
+
+ return ast
+}
+
+function parseDeclaration(current: string, colonIdx: number = current.indexOf(':')): Declaration {
+ let importantIdx = current.indexOf('!important', colonIdx + 1)
+ return {
+ kind: 'declaration',
+ property: current.slice(0, colonIdx).trim(),
+ value: current.slice(colonIdx + 1, importantIdx === -1 ? current.length : importantIdx).trim(),
+ important: importantIdx !== -1,
+ }
+}
diff --git a/packages/tailwindcss/src/design-system.ts b/packages/tailwindcss/src/design-system.ts
new file mode 100644
index 000000000..190c5e7e2
--- /dev/null
+++ b/packages/tailwindcss/src/design-system.ts
@@ -0,0 +1,51 @@
+import { toCss } from './ast'
+import { compileCandidates } from './compile'
+import { getClassList, getVariants, type ClassEntry, type VariantEntry } from './intellisense'
+import { getClassOrder } from './sort'
+import type { Theme } from './theme'
+import { Utilities, createUtilities } from './utilities'
+import { Variants, createVariants } from './variants'
+
+export type DesignSystem = {
+ theme: Theme
+ utilities: Utilities
+ variants: Variants
+
+ candidatesToCss(classes: string[]): (string | null)[]
+ getClassOrder(classes: string[]): [string, bigint | null][]
+ getClassList(): ClassEntry[]
+ getVariants(): VariantEntry[]
+}
+
+export function buildDesignSystem(theme: Theme): DesignSystem {
+ return {
+ theme,
+ utilities: createUtilities(theme),
+ variants: createVariants(theme),
+
+ candidatesToCss(classes: string[]) {
+ let result: (string | null)[] = []
+
+ for (let className of classes) {
+ let { astNodes } = compileCandidates([className], this, { throwOnInvalidCandidate: false })
+ if (astNodes.length === 0) {
+ result.push(null)
+ } else {
+ result.push(toCss(astNodes))
+ }
+ }
+
+ return result
+ },
+
+ getClassOrder(classes) {
+ return getClassOrder(this, classes)
+ },
+ getClassList() {
+ return getClassList(this)
+ },
+ getVariants() {
+ return getVariants(this)
+ },
+ }
+}
diff --git a/packages/tailwindcss/src/index.bench.ts b/packages/tailwindcss/src/index.bench.ts
new file mode 100644
index 000000000..1953aa195
--- /dev/null
+++ b/packages/tailwindcss/src/index.bench.ts
@@ -0,0 +1,18 @@
+import { scanDir } from '@tailwindcss/oxide'
+import { bench } from 'vitest'
+import { compile } from '.'
+
+// FOLDER=path/to/folder vitest bench
+const root = process.env.FOLDER || process.cwd()
+const css = String.raw
+
+bench('compile', async () => {
+ let { candidates } = scanDir({ base: root, globs: true })
+
+ compile(
+ css`
+ @tailwind utilities;
+ `,
+ candidates,
+ )
+})
diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts
new file mode 100644
index 000000000..aaaa6a058
--- /dev/null
+++ b/packages/tailwindcss/src/index.test.ts
@@ -0,0 +1,903 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import { describe, expect, it, test } from 'vitest'
+import { compileCss, run } from './test-utils/run'
+
+const css = String.raw
+
+describe('compiling CSS', () => {
+ test('`@tailwind utilities` is replaced with the generated utility classes', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-black: #000;
+ --breakpoint-md: 768px;
+ }
+
+ @layer utilities {
+ @tailwind utilities;
+ }
+ `,
+ ['flex', 'md:grid', 'hover:underline', 'dark:bg-black'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-black: #000;
+ --breakpoint-md: 768px;
+ }
+
+ @layer utilities {
+ .flex {
+ display: flex;
+ }
+
+ .hover\\:underline:hover {
+ text-decoration-line: underline;
+ }
+
+ @media (width >= 768px) {
+ .md\\:grid {
+ display: grid;
+ }
+ }
+
+ @media (prefers-color-scheme: dark) {
+ .dark\\:bg-black {
+ background-color: #000;
+ }
+ }
+ }"
+ `)
+ })
+
+ test('`@tailwind utilities` is only processed once', () => {
+ expect(
+ compileCss(
+ css`
+ @tailwind utilities;
+ @tailwind utilities;
+ `,
+ ['flex', 'grid'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ".flex {
+ display: flex;
+ }
+
+ .grid {
+ display: grid;
+ }
+
+ @tailwind utilities;"
+ `)
+ })
+
+ test('`@tailwind utilities` is replaced by utilities using the default theme', () => {
+ let defaultTheme = fs.readFileSync(path.resolve(__dirname, '..', 'theme.css'), 'utf-8')
+
+ expect(
+ compileCss(
+ css`
+ ${defaultTheme}
+ @tailwind utilities;
+ `,
+ ['bg-red-500', 'w-4', 'sm:flex', 'shadow'],
+ ),
+ ).toMatchSnapshot()
+ })
+})
+
+describe('arbitrary prpoerties', () => {
+ it('should generate arbitrary properties', () => {
+ expect(run(['[color:red]'])).toMatchInlineSnapshot(`
+ ".\\[color\\:red\\] {
+ color: red;
+ }"
+ `)
+ })
+
+ it('should generate arbitrary properties with modifiers', () => {
+ expect(run(['[color:red]/50'])).toMatchInlineSnapshot(`
+ ".\\[color\\:red\\]\\/50 {
+ color: #ff000080;
+ }"
+ `)
+ })
+
+ it('should not generate arbitrary properties with invalid modifiers', () => {
+ expect(run(['[color:red]/not-a-percentage'])).toMatchInlineSnapshot(`""`)
+ })
+
+ it('should generate arbitrary properties with variables and with modifiers', () => {
+ expect(run(['[color:var(--my-color)]/50'])).toMatchInlineSnapshot(`
+ ".\\[color\\:var\\(--my-color\\)\\]\\/50 {
+ color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ }"
+ `)
+ })
+})
+
+describe('@apply', () => {
+ it('should replace @apply with the correct result', () => {
+ expect(
+ compileCss(css`
+ @theme {
+ --color-red-200: #fecaca;
+ --color-red-500: #ef4444;
+ --color-blue-500: #3b82f6;
+ --color-green-200: #bbf7d0;
+ --color-green-500: #22c55e;
+ --breakpoint-md: 768px;
+ --animate-spin: spin 1s linear infinite;
+
+ @keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }
+ }
+
+ @tailwind utilities;
+
+ .foo {
+ @apply underline bg-red-500 hover:bg-blue-500 md:bg-green-500 animate-spin translate-x-full;
+
+ &:hover:focus {
+ @apply bg-red-200 md:bg-green-200;
+ }
+ }
+ `),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-200: #fecaca;
+ --color-red-500: #ef4444;
+ --color-blue-500: #3b82f6;
+ --color-green-200: #bbf7d0;
+ --color-green-500: #22c55e;
+ --breakpoint-md: 768px;
+ --animate-spin: spin 1s linear infinite;
+ }
+
+ .foo {
+ --tw-translate-x: 100%;
+ translate: var(--tw-translate-x) var(--tw-translate-y);
+ background-color: #ef4444;
+ text-decoration-line: underline;
+ animation: 1s linear infinite spin;
+ }
+
+ .foo:hover {
+ background-color: #3b82f6;
+ }
+
+ @media (width >= 768px) {
+ .foo {
+ background-color: #22c55e;
+ }
+ }
+
+ .foo:hover:focus {
+ background-color: #fecaca;
+ }
+
+ @media (width >= 768px) {
+ .foo:hover:focus {
+ background-color: #bbf7d0;
+ }
+ }
+
+ @keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }
+
+ @property --tw-translate-x {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }
+
+ @property --tw-translate-y {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }"
+ `)
+ })
+
+ it('should @apply in order the utilities would be sorted in if they were used in HTML', () => {
+ expect(
+ compileCss(css`
+ @tailwind utilities;
+
+ .foo {
+ @apply content-["a"] content-["b"];
+ }
+
+ .bar {
+ @apply content-["b"] content-["a"];
+ }
+ `),
+ ).toMatchInlineSnapshot(`
+ ".foo, .bar {
+ --tw-content: "b";
+ content: var(--tw-content);
+ content: var(--tw-content);
+ }
+
+ @property --tw-content {
+ syntax: "*";
+ inherits: false;
+ initial-value: "";
+ }"
+ `)
+ })
+
+ it('should error when using @apply with a utility that does not exist', () => {
+ expect(() =>
+ compileCss(css`
+ @tailwind utilities;
+
+ .foo {
+ @apply bg-not-found;
+ }
+ `),
+ ).toThrowErrorMatchingInlineSnapshot(
+ `[Error: Cannot apply unknown utility class: bg-not-found]`,
+ )
+ })
+
+ it('should error when using @apply with a variant that does not exist', () => {
+ expect(() =>
+ compileCss(css`
+ @tailwind utilities;
+
+ .foo {
+ @apply hocus:bg-red-500;
+ }
+ `),
+ ).toThrowErrorMatchingInlineSnapshot(
+ `[Error: Cannot apply unknown utility class: hocus:bg-red-500]`,
+ )
+ })
+})
+
+describe('arbitrary variants', () => {
+ it('should generate arbitrary variants', () => {
+ expect(run(['[&_p]:flex'])).toMatchInlineSnapshot(`
+ ".\\[\\&_p\\]\\:flex p {
+ display: flex;
+ }"
+ `)
+ })
+
+ it('should generate arbitrary at-rule variants', () => {
+ expect(run(['[@media(width>=123px)]:flex'])).toMatchInlineSnapshot(`
+ "@media (width >= 123px) {
+ .\\[\\@media\\(width\\>\\=123px\\)\\]\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+ })
+})
+
+describe('variant stacking', () => {
+ it('should stack simple variants', () => {
+ expect(run(['focus:hover:flex'])).toMatchInlineSnapshot(`
+ ".focus\\:hover\\:flex:hover:focus {
+ display: flex;
+ }"
+ `)
+ })
+
+ it('should stack arbitrary variants and simple variants', () => {
+ expect(run(['[&_p]:hover:flex'])).toMatchInlineSnapshot(`
+ ".\\[\\&_p\\]\\:hover\\:flex:hover p {
+ display: flex;
+ }"
+ `)
+ })
+
+ it('should stack multiple arbitrary variants', () => {
+ expect(run(['[&_p]:[@media(width>=123px)]:flex'])).toMatchInlineSnapshot(`
+ "@media (width >= 123px) {
+ .\\[\\&_p\\]\\:\\[\\@media\\(width\\>\\=123px\\)\\]\\:flex p {
+ display: flex;
+ }
+ }"
+ `)
+ })
+
+ it('pseudo element variants are re-ordered', () => {
+ expect(run(['before:hover:flex', 'hover:before:flex'])).toMatchInlineSnapshot(`
+ ".before\\:hover\\:flex:hover:before {
+ content: var(--tw-content);
+ display: flex;
+ }
+
+ .hover\\:before\\:flex:hover:before {
+ content: var(--tw-content);
+ display: flex;
+ }
+
+ @property --tw-content {
+ syntax: "*";
+ inherits: false;
+ initial-value: "";
+ }"
+ `)
+ })
+})
+
+describe('important', () => {
+ it('should generate an important utility', () => {
+ expect(run(['underline!'])).toMatchInlineSnapshot(`
+ ".underline\\! {
+ text-decoration-line: underline !important;
+ }"
+ `)
+ })
+
+ it('should not mark declarations inside of @keyframes as important', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --animate-spin: spin 1s linear infinite;
+
+ @keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }
+ }
+ @tailwind utilities;
+ `,
+ ['animate-spin!'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --animate-spin: spin 1s linear infinite;
+ }
+
+ .animate-spin\\! {
+ animation: 1s linear infinite spin !important;
+ }
+
+ @keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }"
+ `)
+ })
+
+ it('should generate an important arbitrary property utility', () => {
+ expect(run(['[color:red]!'])).toMatchInlineSnapshot(`
+ ".\\[color\\:red\\]\\! {
+ color: red !important;
+ }"
+ `)
+ })
+})
+
+describe('sorting', () => {
+ it('should sort utilities based on their property order', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-1: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['pointer-events-none', 'flex', 'p-1', 'px-1', 'pl-1'].sort(() => Math.random() - 0.5),
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-1: .25rem;
+ }
+
+ .pointer-events-none {
+ pointer-events: none;
+ }
+
+ .flex {
+ display: flex;
+ }
+
+ .p-1 {
+ padding: .25rem;
+ }
+
+ .px-1 {
+ padding-left: .25rem;
+ padding-right: .25rem;
+ }
+
+ .pl-1 {
+ padding-left: .25rem;
+ }"
+ `)
+ })
+
+ it('should sort based on amount of properties', () => {
+ expect(run(['text-clip', 'truncate', 'overflow-scroll'])).toMatchInlineSnapshot(`
+ ".truncate {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+
+ .overflow-scroll {
+ overflow: scroll;
+ }
+
+ .text-clip {
+ text-overflow: clip;
+ }"
+ `)
+ })
+
+ /**
+ * Space utilities are implemented using margin, but they act more like a
+ * polyfill for gap. This means that they should be sorted near gap, not
+ * margin.
+ */
+ it('should sort utilities with a custom internal --tw-sort correctly', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-0: 0px;
+ --spacing-2: 0.5rem;
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['mx-0', 'gap-4', 'space-x-2'].sort(() => Math.random() - 0.5),
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-0: 0px;
+ --spacing-2: .5rem;
+ --spacing-4: 1rem;
+ }
+
+ .mx-0 {
+ margin-left: 0;
+ margin-right: 0;
+ }
+
+ .gap-4 {
+ gap: 1rem;
+ }
+
+ :where(.space-x-2 > :not([hidden]) ~ :not([hidden])) {
+ margin-inline-start: calc(.5rem * calc(1 - var(--tw-space-x-reverse)));
+ margin-inline-end: calc(.5rem * var(--tw-space-x-reverse));
+ }
+
+ @property --tw-space-x-reverse {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }"
+ `)
+ })
+
+ it('should move variants to the end while sorting', () => {
+ expect(
+ run(
+ ['pointer-events-none', 'flex', 'hover:flex', 'focus:pointer-events-none'].sort(
+ () => Math.random() - 0.5,
+ ),
+ ),
+ ).toMatchInlineSnapshot(`
+ ".pointer-events-none {
+ pointer-events: none;
+ }
+
+ .flex {
+ display: flex;
+ }
+
+ .hover\\:flex:hover {
+ display: flex;
+ }
+
+ .focus\\:pointer-events-none:focus {
+ pointer-events: none;
+ }"
+ `)
+ })
+
+ /**
+ * Every variant should be sorted by its position in the variant list. Every
+ * combination of variants that exist before the current variant should always
+ * be sorted before the current variant.
+ *
+ * Given the following list of variants:
+ * 1. `hover`
+ * 2. `focus`
+ * 3. `disabled`
+ *
+ * This means that `hover` should be before `focus`, `focus` should be before
+ * `disabled`. This also means that the combination of `hover` and `focus`
+ * (stacked variants) should be before `disabled` because all used variants
+ * are defined before the `disabled` variant.
+ */
+ it('should sort variants and stacked variants by variant position', () => {
+ expect(
+ run(
+ ['flex', 'hover:flex', 'focus:flex', 'disabled:flex', 'hover:focus:flex'].sort(
+ () => Math.random() - 0.5,
+ ),
+ ),
+ ).toMatchInlineSnapshot(`
+ ".flex {
+ display: flex;
+ }
+
+ .hover\\:flex:hover {
+ display: flex;
+ }
+
+ .focus\\:flex:focus {
+ display: flex;
+ }
+
+ .hover\\:focus\\:flex:focus:hover {
+ display: flex;
+ }
+
+ .disabled\\:flex:disabled {
+ display: flex;
+ }"
+ `)
+ })
+
+ // TODO: Extend this test with user-defined variants to ensure they are sorted
+ // correctly.
+ it('should order group-* and peer-* variants based on the sort order of the group and peer variant but also based on the variant they are wrapping', () => {
+ expect(
+ run(
+ [
+ 'hover:flex',
+
+ 'group-hover:flex',
+ 'group-focus:flex',
+
+ 'peer-hover:flex',
+ 'peer-focus:flex',
+
+ 'group-hover:peer-hover:flex',
+ 'group-hover:peer-focus:flex',
+ 'peer-hover:group-hover:flex',
+ 'peer-hover:group-focus:flex',
+ 'group-focus:peer-hover:flex',
+ 'group-focus:peer-focus:flex',
+ 'peer-focus:group-hover:flex',
+ 'peer-focus:group-focus:flex',
+ ].sort(() => Math.random() - 0.5),
+ ),
+ ).toMatchInlineSnapshot(`
+ ".group-hover\\:flex:is(:where(.group):hover *) {
+ display: flex;
+ }
+
+ .group-focus\\:flex:is(:where(.group):focus *) {
+ display: flex;
+ }
+
+ .peer-hover\\:flex:is(:where(.peer):hover ~ *) {
+ display: flex;
+ }
+
+ .group-hover\\:peer-hover\\:flex:is(:where(.peer):hover ~ *):is(:where(.group):hover *) {
+ display: flex;
+ }
+
+ .peer-hover\\:group-hover\\:flex:is(:where(.group):hover *):is(:where(.peer):hover ~ *) {
+ display: flex;
+ }
+
+ .group-focus\\:peer-hover\\:flex:is(:where(.peer):hover ~ *):is(:where(.group):focus *) {
+ display: flex;
+ }
+
+ .peer-hover\\:group-focus\\:flex:is(:where(.group):focus *):is(:where(.peer):hover ~ *) {
+ display: flex;
+ }
+
+ .peer-focus\\:flex:is(:where(.peer):focus ~ *) {
+ display: flex;
+ }
+
+ .group-hover\\:peer-focus\\:flex:is(:where(.peer):focus ~ *):is(:where(.group):hover *) {
+ display: flex;
+ }
+
+ .peer-focus\\:group-hover\\:flex:is(:where(.group):hover *):is(:where(.peer):focus ~ *) {
+ display: flex;
+ }
+
+ .group-focus\\:peer-focus\\:flex:is(:where(.peer):focus ~ *):is(:where(.group):focus *) {
+ display: flex;
+ }
+
+ .peer-focus\\:group-focus\\:flex:is(:where(.group):focus *):is(:where(.peer):focus ~ *) {
+ display: flex;
+ }
+
+ .hover\\:flex:hover {
+ display: flex;
+ }"
+ `)
+ })
+})
+
+// Parsing theme values from CSS
+describe('Parsing themes values from CSS', () => {
+ test('Can read values from `@theme`', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #f00;
+ }
+ @tailwind utilities;
+ `,
+ ['accent-red-500'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: red;
+ }
+
+ .accent-red-500 {
+ accent-color: red;
+ }"
+ `)
+ })
+
+ test('Later values from `@theme` override earlier ones', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #f00;
+ --color-red-500: #f10;
+ }
+ @tailwind utilities;
+ `,
+ ['accent-red-500'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #f10;
+ }
+
+ .accent-red-500 {
+ accent-color: #f10;
+ }"
+ `)
+ })
+
+ test('Multiple `@theme` blocks are merged', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #f00;
+ }
+ @theme {
+ --color-blue-500: #00f;
+ }
+ @tailwind utilities;
+ `,
+ ['accent-red-500', 'accent-blue-500'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: red;
+ --color-blue-500: #00f;
+ }
+
+ .accent-blue-500 {
+ accent-color: #00f;
+ }
+
+ .accent-red-500 {
+ accent-color: red;
+ }"
+ `)
+ })
+
+ test('`@theme` values with escaped forward slashes map to unescaped slashes in candidate values', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ /* Cursed but we want this to work */
+ --width-1\/2: 75%;
+ --width-75\%: 50%;
+ }
+ @tailwind utilities;
+ `,
+ ['w-1/2', 'w-75%'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --width-1\\/2: 75%;
+ --width-75\\%: 50%;
+ }
+
+ .w-1\\/2 {
+ width: 75%;
+ }
+
+ .w-75\\% {
+ width: 50%;
+ }"
+ `)
+ })
+
+ test('`@keyframes` in `@theme` are hoisted', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red: red;
+ --animate-foo: foo 1s infinite;
+
+ @keyframes foo {
+ to {
+ opacity: 1;
+ }
+ }
+
+ --font-size-lg: 20px;
+ }
+ @tailwind utilities;
+ `,
+ ['accent-red', 'text-lg'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red: red;
+ --animate-foo: foo 1s infinite;
+ --font-size-lg: 20px;
+ }
+
+ .text-lg {
+ font-size: 20px;
+ }
+
+ .accent-red {
+ accent-color: red;
+ }
+
+ @keyframes foo {
+ to {
+ opacity: 1;
+ }
+ }"
+ `)
+ })
+
+ test('`@theme` values can be unset', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red: #f00;
+ --color-blue: #00f;
+ --font-size-sm: 13px;
+ --font-size-md: 16px;
+ }
+ @theme {
+ --color-*: initial;
+ --font-size-md: initial;
+ }
+ @theme {
+ --color-green: #0f0;
+ }
+ @tailwind utilities;
+ `,
+ ['accent-red', 'accent-blue', 'accent-green', 'text-sm', 'text-md'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --font-size-sm: 13px;
+ --color-green: #0f0;
+ }
+
+ .text-sm {
+ font-size: 13px;
+ }
+
+ .accent-green {
+ accent-color: #0f0;
+ }"
+ `)
+ })
+
+ test('all `@theme` values can be unset at once', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red: #f00;
+ --color-blue: #00f;
+ --font-size-sm: 13px;
+ --font-size-md: 16px;
+ }
+ @theme {
+ --*: initial;
+ }
+ @theme {
+ --color-green: #0f0;
+ }
+ @tailwind utilities;
+ `,
+ ['accent-red', 'accent-blue', 'accent-green', 'text-sm', 'text-md'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-green: #0f0;
+ }
+
+ .accent-green {
+ accent-color: #0f0;
+ }"
+ `)
+ })
+
+ test('unused keyframes are removed when an animation is unset', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --animate-foo: foo 1s infinite;
+ --animate-foobar: foobar 1s infinite;
+
+ @keyframes foo {
+ to {
+ opacity: 1;
+ }
+ }
+
+ @keyframes foobar {
+ to {
+ opacity: 0;
+ }
+ }
+ }
+ @theme {
+ --animate-foo: initial;
+ }
+ @tailwind utilities;
+ `,
+ ['animate-foo', 'animate-foobar'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --animate-foobar: foobar 1s infinite;
+ }
+
+ .animate-foobar {
+ animation: 1s infinite foobar;
+ }
+
+ @keyframes foobar {
+ to {
+ opacity: 0;
+ }
+ }"
+ `)
+ })
+})
diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts
new file mode 100644
index 000000000..e0e3750ab
--- /dev/null
+++ b/packages/tailwindcss/src/index.ts
@@ -0,0 +1,198 @@
+import { Features, transform } from 'lightningcss'
+import { version } from '../package.json'
+import { comment, decl, rule, toCss, walk, type AstNode, type Rule } from './ast'
+import { compileCandidates } from './compile'
+import * as CSS from './css-parser'
+import { buildDesignSystem } from './design-system'
+import { Theme } from './theme'
+
+export function compile(css: string, rawCandidates: string[]) {
+ let ast = CSS.parse(css)
+
+ if (process.env.NODE_ENV !== 'test') {
+ ast.unshift(comment(`! tailwindcss v${version} | MIT License | https://tailwindcss.com `))
+ }
+
+ // Find all `@theme` declarations
+ let theme = new Theme()
+ let firstThemeRule: Rule | null = null
+ let keyframesRules: Rule[] = []
+
+ walk(ast, (node, { replaceWith }) => {
+ if (node.kind !== 'rule') return
+ if (node.selector !== '@theme') return
+
+ // Record all custom properties in the `@theme` declaration
+ walk([node], (node, { replaceWith }) => {
+ // Collect `@keyframes` rules to re-insert with theme variables later,
+ // since the `@theme` rule itself will be removed.
+ if (node.kind === 'rule' && node.selector.startsWith('@keyframes ')) {
+ keyframesRules.push(node)
+ replaceWith([])
+ return
+ }
+
+ if (node.kind !== 'declaration') return
+ if (!node.property.startsWith('--')) return
+
+ theme.add(node.property, node.value)
+ })
+
+ // Keep a reference to the first `@theme` rule to update with the full theme
+ // later, and delete any other `@theme` rules.
+ if (!firstThemeRule) {
+ firstThemeRule = node
+ } else {
+ replaceWith([])
+ }
+ })
+
+ // Output final set of theme variables at the position of the first `@theme`
+ // rule.
+ if (firstThemeRule) {
+ firstThemeRule = firstThemeRule as Rule
+ firstThemeRule.selector = ':root'
+
+ let nodes = []
+
+ for (let [key, value] of theme.entries()) {
+ nodes.push(decl(key, value))
+ }
+
+ if (keyframesRules.length > 0) {
+ let animationParts = [...theme.namespace('--animate').values()].flatMap((animation) =>
+ animation.split(' '),
+ )
+
+ for (let keyframesRule of keyframesRules) {
+ // Remove any keyframes that aren't used by an animation variable.
+ let keyframesName = keyframesRule.selector.slice(11) // `@keyframes `.length
+ if (!animationParts.includes(keyframesName)) {
+ continue
+ }
+
+ // Wrap `@keyframes` in `@at-root` so they are hoisted out of `:root`
+ // when printing.
+ nodes.push(
+ Object.assign(keyframesRule, {
+ selector: '@at-root',
+ nodes: [rule(keyframesRule.selector, keyframesRule.nodes)],
+ }),
+ )
+ }
+ }
+ firstThemeRule.nodes = nodes
+ }
+
+ let designSystem = buildDesignSystem(theme)
+
+ // Find `@tailwind utilities` and replace it with the actual generated utility
+ // class CSS.
+ walk(ast, (node, { replaceWith }) => {
+ if (node.kind === 'rule' && node.selector === '@tailwind utilities') {
+ replaceWith(compileCandidates(rawCandidates, designSystem).astNodes)
+ // Stop walking after finding `@tailwind utilities` to avoid walking all
+ // of the generated CSS. This means `@tailwind utilities` can only appear
+ // once per file but that's the intended usage at this point in time.
+ return false
+ }
+ })
+
+ // Replace `@apply` rules with the actual utility classes.
+ if (css.includes('@apply')) {
+ walk(ast, (node, { replaceWith }) => {
+ if (node.kind === 'rule' && node.selector[0] === '@' && node.selector.startsWith('@apply')) {
+ let candidates = node.selector
+ .slice(7 /* Ignore `@apply ` when parsing the selector */)
+ .split(/\s+/g)
+
+ // Replace the `@apply` rule with the actual utility classes
+ {
+ // Parse the candidates to an AST that we can replace the `@apply` rule with.
+ let candidateAst = compileCandidates(candidates, designSystem, {
+ throwOnInvalidCandidate: true,
+ }).astNodes
+
+ // Collect the nodes to insert in place of the `@apply` rule. When a
+ // rule was used, we want to insert its children instead of the rule
+ // because we don't want the wrapping selector.
+ let newNodes: AstNode[] = []
+ for (let candidateNode of candidateAst) {
+ if (candidateNode.kind === 'rule' && candidateNode.selector[0] !== '@') {
+ for (let child of candidateNode.nodes) {
+ newNodes.push(child)
+ }
+ } else {
+ newNodes.push(candidateNode)
+ }
+ }
+
+ replaceWith(newNodes)
+ }
+ }
+ })
+ }
+
+ // Drop instances of `@media reference`
+ //
+ // We allow importing a theme as a reference so users can define the theme for
+ // the current CSS file without duplicating the theme vars in the final CSS.
+ // This is useful for users who use `@apply` in Vue SFCs and in CSS modules.
+ //
+ // The syntax is derived from `@import "tailwindcss/theme" reference` which
+ // turns into `@media reference { … }` in the final CSS.
+ if (css.includes('@media reference')) {
+ walk(ast, (node, { replaceWith }) => {
+ if (node.kind === 'rule' && node.selector === '@media reference') {
+ replaceWith([])
+ }
+ })
+ }
+
+ return toCss(ast)
+}
+
+export function optimizeCss(
+ input: string,
+ { file = 'input.css', minify = false }: { file?: string; minify?: boolean } = {},
+) {
+ return transform({
+ filename: file,
+ code: Buffer.from(input),
+ minify,
+ sourceMap: false,
+ drafts: {
+ customMedia: true,
+ },
+ nonStandard: {
+ deepSelectorCombinator: true,
+ },
+ include: Features.Nesting,
+ exclude: Features.LogicalProperties,
+ targets: {
+ safari: (16 << 16) | (4 << 8),
+ },
+ errorRecovery: true,
+ }).code.toString()
+}
+
+export function __unstable__loadDesignSystem(css: string) {
+ // Find all `@theme` declarations
+ let theme = new Theme()
+ let ast = CSS.parse(css)
+
+ walk(ast, (node) => {
+ if (node.kind !== 'rule') return
+ if (node.selector !== '@theme') return
+
+ // Record all custom properties in the `@theme` declaration
+ walk([node], (node) => {
+ if (node.kind !== 'declaration') return
+ if (!node.property.startsWith('--')) return
+
+ theme.add(node.property, node.value)
+ })
+ })
+
+ return buildDesignSystem(theme)
+}
diff --git a/packages/tailwindcss/src/intellisense.test.ts b/packages/tailwindcss/src/intellisense.test.ts
new file mode 100644
index 000000000..a7cce5618
--- /dev/null
+++ b/packages/tailwindcss/src/intellisense.test.ts
@@ -0,0 +1,83 @@
+import { expect, test } from 'vitest'
+import { buildDesignSystem } from './design-system'
+import { Theme } from './theme'
+
+function loadDesignSystem() {
+ return buildDesignSystem(
+ new Theme(
+ new Map([
+ ['--spacing-0_5', '0.125rem'],
+ ['--spacing-1', '0.25rem'],
+ ['--spacing-3', '0.75rem'],
+ ['--spacing-4', '1rem'],
+ ['--width-4', '1rem'],
+ ['--colors-red-500', 'red'],
+ ['--colors-blue-500', 'blue'],
+ ['--breakpoint-sm', '640px'],
+ ['--font-size-xs', '0.75rem'],
+ ['--font-size-xs--line-height', '1rem'],
+ ]),
+ ),
+ )
+}
+
+test('getClassList', () => {
+ let design = loadDesignSystem()
+ let classList = design.getClassList()
+ let classNames = classList.map(([name]) => name)
+
+ expect(classNames).toMatchSnapshot()
+})
+
+test('Theme values with underscores are converted back to deciaml points', () => {
+ let design = loadDesignSystem()
+ let classes = design.getClassList()
+
+ expect(classes).toContainEqual(['inset-0.5', { modifiers: [] }])
+})
+
+test('getVariants', () => {
+ let design = loadDesignSystem()
+ let variants = design.getVariants()
+
+ expect(variants).toMatchSnapshot()
+})
+
+test('getVariants compound', () => {
+ let design = loadDesignSystem()
+ let variants = design.getVariants()
+ let group = variants.find((v) => v.name === 'group')!
+
+ let list = [
+ // A selector-based variant
+ group.selectors({ value: 'hover' }),
+
+ // A selector-based variant with a modifier
+ group.selectors({ value: 'hover', modifier: 'sidebar' }),
+
+ // A nested, compound, selector-based variant
+ group.selectors({ value: 'group-hover' }),
+
+ // This variant produced an at rule
+ group.selectors({ value: 'sm' }),
+
+ // This variant does not exist
+ group.selectors({ value: 'md' }),
+ ]
+
+ expect(list).toEqual([
+ ['&:is(:where(.group):hover *)'],
+ ['&:is(:where(.group\\/sidebar):hover *)'],
+ ['&:is(:where(.group):is(:where(.group):hover *) *)'],
+ [],
+ [],
+ ])
+})
+
+test('The variant `has-force` does not crash', () => {
+ let design = loadDesignSystem()
+ let variants = design.getVariants()
+ let has = variants.find((v) => v.name === 'has')!
+
+ expect(has.selectors({ value: 'force' })).toMatchInlineSnapshot(`[]`)
+})
diff --git a/packages/tailwindcss/src/intellisense.ts b/packages/tailwindcss/src/intellisense.ts
new file mode 100644
index 000000000..8b67641c6
--- /dev/null
+++ b/packages/tailwindcss/src/intellisense.ts
@@ -0,0 +1,136 @@
+import { decl, rule } from './ast'
+import { parseVariant, type Variant } from './candidate'
+import { applyVariant } from './compile'
+import type { DesignSystem } from './design-system'
+import { DefaultMap } from './utils/default-map'
+
+interface ClassMetadata {
+ modifiers: string[]
+}
+
+export type ClassEntry = [string, ClassMetadata]
+
+export function getClassList(design: DesignSystem): ClassEntry[] {
+ let list: [string, ClassMetadata][] = []
+
+ for (let [utility, fn] of design.utilities.entries()) {
+ if (typeof utility !== 'string') {
+ continue
+ }
+
+ // Static utilities only work as-is
+ if (fn.kind === 'static') {
+ list.push([utility, { modifiers: [] }])
+ continue
+ }
+
+ // Functional utilities have their own list of completions
+ let completions = design.utilities.getCompletions(utility)
+
+ for (let group of completions) {
+ for (let value of group.values) {
+ let name = value === null ? utility : `${utility}-${value}`
+
+ list.push([name, { modifiers: group.modifiers }])
+
+ if (group.supportsNegative) {
+ list.push([`-${name}`, { modifiers: group.modifiers }])
+ }
+ }
+ }
+ }
+
+ list.sort((a, b) => a[0].localeCompare(b[0]))
+
+ return list
+}
+
+interface SelectorOptions {
+ modifier?: string
+ value?: string
+}
+
+export interface VariantEntry {
+ name: string
+ isArbitrary: boolean
+ values: string[]
+ hasDash: boolean
+ selectors: (options: SelectorOptions) => string[]
+}
+
+export function getVariants(design: DesignSystem) {
+ let list: VariantEntry[] = []
+ let parsedVariants = new DefaultMap((variant, map) =>
+ parseVariant(variant, design.variants, map),
+ )
+
+ for (let [root, variant] of design.variants.entries()) {
+ if (variant.kind === 'arbitrary') continue
+
+ let values = design.variants.getCompletions(root)
+
+ function selectors({ value, modifier }: SelectorOptions = {}) {
+ let name = root
+ if (value) name += `-${value}`
+ if (modifier) name += `/${modifier}`
+
+ let variant = parsedVariants.get(name)
+
+ if (!variant) return []
+
+ // Apply the variant to a placeholder rule
+ let node = rule('.__placeholder__', [decl('color', 'red')])
+
+ // If the rule produces no nodes it means the variant does not apply
+ if (applyVariant(node, variant, design.variants) === null) {
+ return []
+ }
+
+ // Now look at the selector(s) inside the rule
+ let selectors: string[] = []
+
+ for (let child of node.nodes) {
+ if (child.kind === 'rule') {
+ selectors.push(child.selector)
+ }
+ }
+
+ return selectors
+ }
+
+ switch (variant.kind) {
+ case 'static': {
+ list.push({
+ name: root,
+ values,
+ isArbitrary: false,
+ hasDash: true,
+ selectors,
+ })
+ break
+ }
+ case 'functional': {
+ list.push({
+ name: root,
+ values,
+ isArbitrary: true,
+ hasDash: true,
+ selectors,
+ })
+ break
+ }
+ case 'compound': {
+ list.push({
+ name: root,
+ values,
+ isArbitrary: true,
+ hasDash: true,
+ selectors,
+ })
+ break
+ }
+ }
+ }
+
+ return list
+}
diff --git a/packages/tailwindcss/src/node.d.ts b/packages/tailwindcss/src/node.d.ts
new file mode 100644
index 000000000..a5073f833
--- /dev/null
+++ b/packages/tailwindcss/src/node.d.ts
@@ -0,0 +1,5 @@
+declare namespace NodeJS {
+ interface ProcessEnv {
+ NODE_ENV: 'development' | 'production' | 'test'
+ }
+}
diff --git a/packages/tailwindcss/src/property-order.ts b/packages/tailwindcss/src/property-order.ts
new file mode 100644
index 000000000..8fa1d94fd
--- /dev/null
+++ b/packages/tailwindcss/src/property-order.ts
@@ -0,0 +1,334 @@
+export default [
+ 'container-type',
+
+ 'pointer-events',
+ 'visibility',
+ 'position',
+
+ // How do we make `inset-x-0` come before `top-0`?
+ 'inset',
+ 'inset-inline',
+ 'inset-block',
+ 'inset-inline-start',
+ 'inset-inline-end',
+ 'top',
+ 'right',
+ 'bottom',
+ 'left',
+
+ 'isolation',
+ 'z-index',
+ 'order',
+ 'grid-column',
+ 'grid-column-start',
+ 'grid-column-end',
+ 'grid-row',
+ 'grid-row-start',
+ 'grid-row-end',
+ 'float',
+ 'clear',
+
+ // How do we make `mx-0` come before `mt-0`?
+ // Idea: `margin-x` property that we compile away with a Visitor plugin?
+ 'margin',
+ 'margin-inline',
+ 'margin-block',
+ 'margin-inline-start',
+ 'margin-inline-end',
+ 'margin-top',
+ 'margin-right',
+ 'margin-bottom',
+ 'margin-left',
+
+ 'box-sizing',
+ 'display',
+ 'aspect-ratio',
+
+ 'height',
+ 'max-height',
+ 'min-height',
+ 'width',
+ 'max-width',
+ 'min-width',
+
+ 'flex',
+ 'flex-shrink',
+ 'flex-grow',
+ 'flex-basis',
+
+ 'table-layout',
+ 'caption-side',
+ 'border-collapse',
+
+ // There's no `border-spacing-x` property, we use variables, how to sort?
+ 'border-spacing',
+ // '--tw-border-spacing-x',
+ // '--tw-border-spacing-y',
+
+ 'transform-origin',
+
+ // '--tw-translate-x',
+ // '--tw-translate-y',
+ 'translate',
+ 'rotate',
+ // '--tw-rotate',
+ '--tw-skew-x',
+ '--tw-skew-y',
+ 'scale',
+ // '--tw-scale-x',
+ // '--tw-scale-y',
+ 'transform',
+
+ 'animation',
+
+ 'cursor',
+
+ 'touch-action',
+ '--tw-pan-x',
+ '--tw-pan-y',
+ '--tw-pinch-zoom',
+
+ 'resize',
+
+ 'scroll-snap-type',
+ '--tw-scroll-snap-strictness',
+ 'scroll-snap-align',
+ 'scroll-snap-stop',
+ 'scroll-margin',
+ 'scroll-margin-inline-start',
+ 'scroll-margin-inline-end',
+ 'scroll-margin-top',
+ 'scroll-margin-right',
+ 'scroll-margin-bottom',
+ 'scroll-margin-left',
+
+ 'scroll-padding',
+ 'scroll-padding-inline-start',
+ 'scroll-padding-inline-end',
+ 'scroll-padding-top',
+ 'scroll-padding-right',
+ 'scroll-padding-bottom',
+ 'scroll-padding-left',
+
+ 'list-style-position',
+ 'list-style-type',
+ 'list-style-image',
+
+ 'appearance',
+
+ 'columns',
+ 'break-before',
+ 'break-inside',
+ 'break-after',
+
+ 'grid-auto-columns',
+ 'grid-auto-flow',
+ 'grid-auto-rows',
+ 'grid-template-columns',
+ 'grid-template-rows',
+
+ 'flex-direction',
+ 'flex-wrap',
+ 'place-content',
+ 'place-items',
+ 'align-content',
+ 'align-items',
+ 'justify-content',
+ 'justify-items',
+ 'gap',
+ 'column-gap',
+ 'row-gap',
+ '--tw-space-x-reverse',
+ '--tw-space-y-reverse',
+
+ // Is there a more "real" property we could use for this?
+ 'divide-x-width',
+ 'divide-y-width',
+ '--tw-divide-y-reverse',
+ 'divide-style',
+ 'divide-color',
+ '--tw-divide-opacity',
+
+ 'place-self',
+ 'align-self',
+ 'justify-self',
+
+ 'overflow',
+ 'overflow-x',
+ 'overflow-y',
+
+ 'overscroll-behavior',
+ 'overscroll-behavior-x',
+ 'overscroll-behavior-y',
+
+ 'scroll-behavior',
+
+ 'text-overflow',
+ 'hyphens',
+ 'white-space',
+
+ 'text-wrap',
+ 'overflow-wrap',
+ 'work-break',
+
+ 'border-radius',
+ 'border-start-radius', // Not real
+ 'border-end-radius', // Not real
+ 'border-top-radius', // Not real
+ 'border-right-radius', // Not real
+ 'border-bottom-radius', // Not real
+ 'border-left-radius', // Not real
+ 'border-start-start-radius',
+ 'border-start-end-radius',
+ 'border-end-end-radius',
+ 'border-end-start-radius',
+ 'border-top-left-radius',
+ 'border-top-right-radius',
+ 'border-bottom-right-radius',
+ 'border-bottom-left-radius',
+
+ 'border-width',
+ 'border-inline-width', // Not real
+ 'border-inline-start-width',
+ 'border-inline-end-width',
+ 'border-top-width',
+ 'border-right-width',
+ 'border-bottom-width',
+ 'border-left-width',
+
+ 'border-style',
+ 'border-color',
+ 'border-x-color', // Not real
+ 'border-y-color', // Not real
+ 'border-inline-start-color',
+ 'border-inline-end-color',
+ 'border-top-color',
+ 'border-right-color',
+ 'border-bottom-color',
+ 'border-left-color',
+
+ '--tw-border-opacity',
+
+ 'background-color',
+ '--tw-bg-opacity',
+
+ 'background-image',
+ '--tw-gradient-stops',
+ '--tw-gradient-via-stops',
+ '--tw-gradient-from',
+ '--tw-gradient-from-position',
+ '--tw-gradient-via',
+ '--tw-gradient-via-position',
+ '--tw-gradient-to',
+ '--tw-gradient-to-position',
+
+ 'box-decoration-break',
+
+ 'background-size',
+ 'background-attachment',
+ 'background-clip',
+ 'background-position',
+ 'background-repeat',
+ 'background-origin',
+
+ 'fill',
+ 'stroke',
+ 'stroke-width',
+
+ 'object-fit',
+ 'object-position',
+
+ 'padding',
+ 'padding-inline',
+ 'padding-block',
+ 'padding-inline-start',
+ 'padding-inline-end',
+ 'padding-top',
+ 'padding-right',
+ 'padding-bottom',
+ 'padding-left',
+
+ 'text-align',
+ 'text-indent',
+ 'vertical-align',
+
+ 'font-family',
+ 'font-size',
+ 'font-weight',
+ 'text-transform',
+ 'font-style',
+ 'font-variant-numeric',
+ 'line-height',
+ 'letter-spacing',
+ 'color',
+ '--tw-text-opacity',
+ 'text-decoration-line',
+ 'text-decoration-color',
+ 'text-decoration-style',
+ 'text-decoration-thickness',
+ 'text-underline-offset',
+ '-webkit-font-smoothing',
+
+ 'placeholder-color', // Not real
+ '--tw-placeholder-opacity',
+
+ 'caret-color',
+ 'accent-color',
+
+ 'opacity',
+
+ 'background-blend-mode',
+ 'mix-blend-mode',
+
+ 'box-shadow',
+ '--tw-shadow',
+ '--tw-shadow-color',
+ '--tw-ring-shadow',
+ '--tw-ring-color',
+ '--tw-inset-shadow',
+ '--tw-inset-shadow-color',
+ '--tw-inset-ring-shadow',
+ '--tw-inset-ring-color',
+ '--tw-ring-opacity',
+ '--tw-ring-offset-width',
+ '--tw-ring-offset-color',
+
+ 'outline',
+ 'outline-width',
+ 'outline-offset',
+ 'outline-color',
+
+ '--tw-blur',
+ '--tw-brightness',
+ '--tw-contast',
+ '--tw-drop-shadow',
+ '--tw-grayscale',
+ '--tw-hue-rotate',
+ '--tw-invert',
+ '--tw-saturate',
+ '--tw-sepia',
+ 'filter',
+
+ '--tw-backdrop-blur',
+ '--tw-backdrop-brightness',
+ '--tw-backdrop-contast',
+ '--tw-backdrop-grayscale',
+ '--tw-backdrop-hue-rotate',
+ '--tw-backdrop-invert',
+ '--tw-backdrop-opacity',
+ '--tw-backdrop-saturate',
+ '--tw-backdrop-sepia',
+ 'backdrop-filter',
+
+ 'transition-property',
+ 'transition-delay',
+ 'transition-duration',
+ 'transition-timing-function',
+
+ 'will-change',
+ 'contain',
+
+ 'content',
+
+ 'forced-color-adjust',
+]
diff --git a/packages/tailwindcss/src/sort.bench.ts b/packages/tailwindcss/src/sort.bench.ts
new file mode 100644
index 000000000..dce0132d3
--- /dev/null
+++ b/packages/tailwindcss/src/sort.bench.ts
@@ -0,0 +1,25 @@
+import { bench } from 'vitest'
+import { buildDesignSystem } from './design-system'
+import { Theme } from './theme'
+
+const input = 'a-class px-3 p-1 b-class py-3 bg-red-500 bg-blue-500'.split(' ')
+const emptyDesign = buildDesignSystem(new Theme())
+const simpleDesign = buildDesignSystem(
+ new Theme(
+ new Map([
+ ['--spacing-1', '0.25rem'],
+ ['--spacing-3', '0.75rem'],
+ ['--spacing-4', '1rem'],
+ ['--color-red-500', 'red'],
+ ['--color-blue-500', 'blue'],
+ ]),
+ ),
+)
+
+bench('getClassOrder (empty theme)', () => {
+ emptyDesign.getClassOrder(input)
+})
+
+bench('getClassOrder (simple theme)', () => {
+ simpleDesign.getClassOrder(input)
+})
diff --git a/packages/tailwindcss/src/sort.test.ts b/packages/tailwindcss/src/sort.test.ts
new file mode 100644
index 000000000..82ebd85db
--- /dev/null
+++ b/packages/tailwindcss/src/sort.test.ts
@@ -0,0 +1,129 @@
+import { expect, test } from 'vitest'
+import { type DesignSystem } from './design-system'
+import { __unstable__loadDesignSystem } from './index'
+
+function loadDesign() {
+ return __unstable__loadDesignSystem(`
+ @theme {
+ --spacing-1: 0.25rem;
+ --spacing-3: 0.75rem;
+ --spacing-4: 1rem;
+ --color-red-500: red;
+ --color-blue-500: blue;
+ }
+ `)
+}
+
+const table = [
+ // Utitlies
+ ['px-3 p-1 py-3', 'p-1 py-3 px-3'],
+
+ // Utilities with variants
+ ['px-3 focus:hover:p-3 hover:p-1 py-3', 'py-3 px-3 hover:p-1 focus:hover:p-3'],
+
+ // Utitlies with important
+ ['px-3 py-4! p-1', 'p-1 py-4! px-3'],
+ ['py-4! px-3 p-1', 'p-1 py-4! px-3'],
+
+ // User CSS order is the same and moved to the front
+ ['b p-1 a', 'b a p-1'],
+ ['hover:b focus:p-1 a', 'hover:b a focus:p-1'],
+
+ // Add special treatment for `group`, `peer`, and `dark`
+ // ['peer a underline', 'a peer underline'],
+ // ['group a underline', 'a group underline'],
+ // ['dark a underline', 'a dark underline'],
+] as const
+
+test.each(table)('sorts classes: "%s" -> "%s"', (input, expected) => {
+ expect(sortClasses(input, loadDesign())).toEqual(expected)
+})
+
+test.skip('group, peer, and dark have their own order', () => {
+ let input = shuffle(['group', 'peer', 'dark']).join(' ')
+ expect(sortClasses(input, loadDesign())).toEqual('dark group peer')
+})
+
+test('can sort classes deterministically across multiple class lists', () => {
+ let classes = [
+ [
+ 'a-class px-3 p-1 b-class py-3 bg-red-500 bg-blue-500',
+ 'a-class b-class bg-blue-500 bg-red-500 p-1 py-3 px-3',
+ ],
+ [
+ 'px-3 b-class p-1 py-3 bg-blue-500 a-class bg-red-500',
+ 'b-class a-class bg-blue-500 bg-red-500 p-1 py-3 px-3',
+ ],
+ ]
+
+ // Shared design
+ let design = loadDesign()
+ for (const [input, output] of classes) {
+ expect(sortClasses(input, design)).toEqual(output)
+ }
+
+ // Fresh design
+ for (const [input, output] of classes) {
+ expect(sortClasses(input, loadDesign())).toEqual(output)
+ }
+})
+
+test('sorts arbitrary values across one or more class lists consistently', () => {
+ let classes = [
+ ['[--fg:#fff]', '[--fg:#fff]'],
+ ['[--bg:#111] [--bg_hover:#000] [--fg:#fff]', '[--bg:#111] [--bg_hover:#000] [--fg:#fff]'],
+ ]
+
+ // Shared design
+ let design = loadDesign()
+ for (const [input, output] of classes) {
+ expect(sortClasses(input, design)).toEqual(output)
+ }
+
+ // Fresh design
+ for (const [input, output] of classes) {
+ expect(sortClasses(input, loadDesign())).toEqual(output)
+ }
+})
+
+function sortClasses(input: string, design: DesignSystem) {
+ return defaultSort(design.getClassOrder(input.split(' ')))
+}
+
+/**
+ * This is a function that the prettier-plugin-tailwindcss would use. It would
+ * do the actual sorting based on the classes and order we return from `getClassOrder`.
+ *
+ * This way the actual sorting logic is done in the plugin which allows you to
+ * put unknown classes at the end for example.
+ */
+function defaultSort(arrayOfTuples: [string, bigint | null][]): string {
+ return arrayOfTuples
+ .sort(([, a], [, z]) => {
+ if (a === z) return 0
+ if (a === null) return -1
+ if (z === null) return 1
+ return bigSign(a - z)
+ })
+ .map(([className]) => className)
+ .join(' ')
+}
+
+function bigSign(value: bigint) {
+ if (value > 0n) {
+ return 1
+ } else if (value === 0n) {
+ return 0
+ } else {
+ return -1
+ }
+}
+
+function shuffle(arr: T[]): T[] {
+ for (let i = arr.length - 1; i > 0; i--) {
+ let j = Math.round(Math.random() * i)
+ ;[arr[i], arr[j]] = [arr[j], arr[i]]
+ }
+
+ return arr
+}
diff --git a/packages/tailwindcss/src/sort.ts b/packages/tailwindcss/src/sort.ts
new file mode 100644
index 000000000..b3434ed09
--- /dev/null
+++ b/packages/tailwindcss/src/sort.ts
@@ -0,0 +1,32 @@
+import { compileCandidates } from './compile'
+import type { DesignSystem } from './design-system'
+
+export function getClassOrder(design: DesignSystem, classes: string[]): [string, bigint | null][] {
+ // Generate a sorted AST
+ let { astNodes, nodeSorting } = compileCandidates(Array.from(classes), design, {
+ throwOnInvalidCandidate: false,
+ })
+
+ // Map class names to their order in the AST
+ // `null` indicates a non-Tailwind class
+ let sorted = new Map(classes.map((className) => [className, null]))
+
+ // Assign each class a unique, sorted number
+ let idx = 0n
+
+ for (let node of astNodes) {
+ let candidate = nodeSorting.get(node)?.candidate
+ if (!candidate) continue
+
+ // When multiple rules match a candidate
+ // always take the position of the first one
+ sorted.set(candidate, sorted.get(candidate) ?? idx++)
+ }
+
+ // Pair classes with their assigned sorting number
+ return classes.map((className) => [
+ //
+ className,
+ sorted.get(className) ?? null,
+ ])
+}
diff --git a/packages/tailwindcss/src/test-utils/run.ts b/packages/tailwindcss/src/test-utils/run.ts
new file mode 100644
index 000000000..8df6feab2
--- /dev/null
+++ b/packages/tailwindcss/src/test-utils/run.ts
@@ -0,0 +1,9 @@
+import { compile, optimizeCss } from '..'
+
+export function compileCss(css: string, candidates: string[] = []) {
+ return optimizeCss(compile(css, candidates)).trim()
+}
+
+export function run(candidates: string[]) {
+ return optimizeCss(compile('@tailwind utilities;', candidates)).trim()
+}
diff --git a/packages/tailwindcss/src/theme.ts b/packages/tailwindcss/src/theme.ts
new file mode 100644
index 000000000..68cecbc16
--- /dev/null
+++ b/packages/tailwindcss/src/theme.ts
@@ -0,0 +1,245 @@
+import { escape } from './utils/escape'
+
+export class Theme {
+ constructor(private values: Map = new Map()) {}
+
+ add(key: string, value: string): void {
+ if (key.endsWith('-*')) {
+ if (value !== 'initial') {
+ throw new Error(`Invalid theme value \`${value}\` for namespace \`${key}\``)
+ }
+ if (key === '--*') {
+ this.values.clear()
+ } else {
+ this.clearNamespace(key.slice(0, -2))
+ }
+ }
+
+ if (value === 'initial') {
+ this.values.delete(key)
+ } else {
+ this.values.set(key, value)
+ }
+ }
+
+ keysInNamespaces(themeKeys: ThemeKey[]): string[] {
+ let keys: string[] = []
+
+ for (let prefix of themeKeys) {
+ let namespace = `${prefix}-`
+
+ for (let key of this.values.keys()) {
+ if (key.startsWith(namespace)) {
+ if (key.indexOf('--', 2) !== -1) {
+ continue
+ }
+
+ keys.push(key.slice(namespace.length))
+ }
+ }
+ }
+
+ return keys
+ }
+
+ get(themeKeys: ThemeKey[]): string | null {
+ for (let key of themeKeys) {
+ let value = this.values.get(key)
+ if (value) {
+ return value
+ }
+ }
+
+ return null
+ }
+
+ entries() {
+ return this.values.entries()
+ }
+
+ clearNamespace(namespace: string) {
+ for (let key of this.values.keys()) {
+ if (key.startsWith(namespace)) {
+ this.values.delete(key)
+ }
+ }
+ }
+
+ resolveKey(candidateValue: string, themeKeys: ThemeKey[]): string | null {
+ for (let key of themeKeys) {
+ let themeKey = escape(`${key}-${candidateValue.replaceAll('.', '_')}`)
+
+ if (this.values.has(themeKey)) {
+ return themeKey
+ }
+ }
+
+ return null
+ }
+
+ resolve(candidateValue: string, themeKeys: ThemeKey[]): string | null {
+ let themeKey = this.resolveKey(candidateValue, themeKeys)
+
+ if (!themeKey) return null
+
+ return this.values.get(themeKey)!
+ }
+
+ resolveWith(
+ candidateValue: string,
+ themeKeys: ThemeKey[],
+ nestedKeys: `--${string}`[] = [],
+ ): [string, Record] | null {
+ let themeKey = this.resolveKey(candidateValue, themeKeys)
+
+ if (!themeKey) return null
+
+ let extra = {} as Record
+ for (let name of nestedKeys) {
+ let nestedValue = this.values.get(`${themeKey}${name}`)
+ if (nestedValue) {
+ extra[name] = nestedValue
+ }
+ }
+
+ return [this.values.get(themeKey)!, extra]
+ }
+
+ namespace(namespace: string) {
+ let values = new Map()
+ let prefix = `${namespace}-`
+
+ for (let [key, value] of this.values) {
+ if (key === namespace) {
+ values.set(null, value)
+ } else if (key.startsWith(prefix)) {
+ values.set(key.slice(prefix.length), value)
+ }
+ }
+
+ return values
+ }
+}
+
+export type ThemeKey =
+ | '--accent-color'
+ | '--animate'
+ | '--aspect-ratio'
+ | '--backdrop-blur'
+ | '--backdrop-brightness'
+ | '--backdrop-contrast'
+ | '--backdrop-grayscale'
+ | '--backdrop-hue-rotate'
+ | '--backdrop-invert'
+ | '--backdrop-opacity'
+ | '--backdrop-saturate'
+ | '--backdrop-sepia'
+ | '--background-color'
+ | '--background-image'
+ | '--blur'
+ | '--border-color'
+ | '--border-spacing'
+ | '--border-width'
+ | '--box-shadow-color'
+ | '--breakpoint'
+ | '--brightness'
+ | '--caret-color'
+ | '--color'
+ | '--columns'
+ | '--contrast'
+ | '--cursor'
+ | '--default-border-width'
+ | '--default-ring-color'
+ | '--default-ring-width'
+ | '--divide-width'
+ | '--divide-color'
+ | '--drop-shadow'
+ | '--fill'
+ | '--flex-basis'
+ | '--font-family'
+ | '--font-size'
+ | '--font-weight'
+ | '--gap'
+ | '--gradient-color-stop-positions'
+ | '--grayscale'
+ | '--grid-auto-columns'
+ | '--grid-auto-rows'
+ | '--grid-column'
+ | '--grid-column-end'
+ | '--grid-column-start'
+ | '--grid-row'
+ | '--grid-row-end'
+ | '--grid-row-start'
+ | '--grid-template-columns'
+ | '--grid-template-rows'
+ | '--height'
+ | '--hue-rotate'
+ | '--inset'
+ | '--inset-shadow'
+ | '--invert'
+ | '--letter-spacing'
+ | '--line-height'
+ | '--line-clamp'
+ | '--list-style-image'
+ | '--list-style-type'
+ | '--margin'
+ | '--max-height'
+ | '--max-width'
+ | '--min-height'
+ | '--min-width'
+ | '--object-position'
+ | '--opacity'
+ | '--order'
+ | '--outline-color'
+ | '--outline-width'
+ | '--outline-offset'
+ | '--padding'
+ | '--placeholder-color'
+ | '--radius'
+ | '--ring-color'
+ | '--ring-offset-color'
+ | '--ring-offset-width'
+ | '--ring-width'
+ | '--rotate'
+ | '--saturate'
+ | '--scale'
+ | '--scroll-margin'
+ | '--scroll-padding'
+ | '--sepia'
+ | '--shadow'
+ | '--size'
+ | '--skew'
+ | '--space'
+ | '--spacing'
+ | '--stroke'
+ | '--stroke-width'
+ | '--text-color'
+ | '--text-decoration-color'
+ | '--text-decoration-thickness'
+ | '--text-indent'
+ | '--text-underline-offset'
+ | '--transform-origin'
+ | '--transition-delay'
+ | '--transition-duration'
+ | '--transition-property'
+ | '--transition-timing-function'
+ | '--translate'
+ | '--width'
+ | '--z-index'
+
+export type ColorThemeKey =
+ | '--color'
+ | '--accent-color'
+ | '--background-color'
+ | '--border-color'
+ | '--box-shadow-color'
+ | '--caret-color'
+ | '--divide-color'
+ | '--fill'
+ | '--outline-color'
+ | '--placeholder-color'
+ | '--ring-color'
+ | '--ring-offset-color'
+ | '--stroke'
+ | '--text-color'
+ | '--text-decoration-color'
diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts
new file mode 100644
index 000000000..f3667a0d8
--- /dev/null
+++ b/packages/tailwindcss/src/utilities.test.ts
@@ -0,0 +1,11798 @@
+import { expect, test } from 'vitest'
+import { compileCss, run } from './test-utils/run'
+
+const css = String.raw
+
+test('sr-only', () => {
+ expect(run(['sr-only'])).toMatchInlineSnapshot(`
+ ".sr-only {
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border-width: 0;
+ width: 1px;
+ height: 1px;
+ margin: -1px;
+ padding: 0;
+ position: absolute;
+ overflow: hidden;
+ }"
+ `)
+ expect(run(['-sr-only', 'sr-only-[--value]'])).toEqual('')
+})
+
+test('not-sr-only', () => {
+ expect(run(['not-sr-only'])).toMatchInlineSnapshot(`
+ ".not-sr-only {
+ clip: auto;
+ white-space: normal;
+ width: auto;
+ height: auto;
+ margin: 0;
+ padding: 0;
+ position: static;
+ overflow: visible;
+ }"
+ `)
+ expect(run(['-not-sr-only', 'not-sr-only-[--value]'])).toEqual('')
+})
+
+test('pointer-events', () => {
+ expect(run(['pointer-events-none', 'pointer-events-auto'])).toMatchInlineSnapshot(`
+ ".pointer-events-auto {
+ pointer-events: auto;
+ }
+
+ .pointer-events-none {
+ pointer-events: none;
+ }"
+ `)
+ expect(run(['-pointer-events-none', '-pointer-events-auto', 'pointer-events-[--value]'])).toEqual(
+ '',
+ )
+})
+
+test('visibility', () => {
+ expect(run(['visible', 'invisible', 'collapse'])).toMatchInlineSnapshot(`
+ ".collapse {
+ visibility: collapse;
+ }
+
+ .invisible {
+ visibility: hidden;
+ }
+
+ .visible {
+ visibility: visible;
+ }"
+ `)
+ expect(run(['-visible', '-invisible', '-collapse'])).toEqual('')
+})
+
+test('position', () => {
+ expect(run(['static', 'fixed', 'absolute', 'relative', 'sticky'])).toMatchInlineSnapshot(`
+ ".absolute {
+ position: absolute;
+ }
+
+ .fixed {
+ position: fixed;
+ }
+
+ .relative {
+ position: relative;
+ }
+
+ .static {
+ position: static;
+ }
+
+ .sticky {
+ position: sticky;
+ }"
+ `)
+ expect(run(['-static', '-fixed', '-absolute', '-relative', '-sticky'])).toEqual('')
+})
+
+test('inset', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'inset-auto',
+ '-inset-full',
+ 'inset-full',
+ 'inset-3/4',
+ 'inset-4',
+ '-inset-4',
+ 'inset-[4px]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-inset-4 {
+ inset: -1rem;
+ }
+
+ .-inset-full {
+ inset: -100%;
+ }
+
+ .inset-3\\/4 {
+ inset: 75%;
+ }
+
+ .inset-4 {
+ inset: 1rem;
+ }
+
+ .inset-\\[4px\\] {
+ inset: 4px;
+ }
+
+ .inset-auto {
+ inset: auto;
+ }
+
+ .inset-full {
+ inset: 100%;
+ }"
+ `)
+ expect(run(['inset'])).toEqual('')
+})
+
+test('inset-x', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'inset-x-auto',
+ 'inset-x-full',
+ '-inset-x-full',
+ 'inset-x-3/4',
+ 'inset-x-4',
+ '-inset-x-4',
+ 'inset-x-[4px]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-inset-x-4 {
+ left: -1rem;
+ right: -1rem;
+ }
+
+ .-inset-x-full {
+ left: -100%;
+ right: -100%;
+ }
+
+ .inset-x-3\\/4 {
+ left: 75%;
+ right: 75%;
+ }
+
+ .inset-x-4 {
+ left: 1rem;
+ right: 1rem;
+ }
+
+ .inset-x-\\[4px\\] {
+ left: 4px;
+ right: 4px;
+ }
+
+ .inset-x-auto {
+ left: auto;
+ right: auto;
+ }
+
+ .inset-x-full {
+ left: 100%;
+ right: 100%;
+ }"
+ `)
+ expect(run(['inset-x'])).toEqual('')
+})
+
+test('inset-y', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'inset-y-auto',
+ 'inset-y-full',
+ '-inset-y-full',
+ 'inset-y-3/4',
+ 'inset-y-4',
+ '-inset-y-4',
+ 'inset-y-[4px]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-inset-y-4 {
+ top: -1rem;
+ bottom: -1rem;
+ }
+
+ .-inset-y-full {
+ top: -100%;
+ bottom: -100%;
+ }
+
+ .inset-y-3\\/4 {
+ top: 75%;
+ bottom: 75%;
+ }
+
+ .inset-y-4 {
+ top: 1rem;
+ bottom: 1rem;
+ }
+
+ .inset-y-\\[4px\\] {
+ top: 4px;
+ bottom: 4px;
+ }
+
+ .inset-y-auto {
+ top: auto;
+ bottom: auto;
+ }
+
+ .inset-y-full {
+ top: 100%;
+ bottom: 100%;
+ }"
+ `)
+ expect(run(['inset-y'])).toEqual('')
+})
+
+test('start', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'start-auto',
+ '-start-full',
+ 'start-full',
+ 'start-3/4',
+ 'start-4',
+ '-start-4',
+ 'start-[4px]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-start-4 {
+ inset-inline-start: -1rem;
+ }
+
+ .-start-full {
+ inset-inline-start: -100%;
+ }
+
+ .start-3\\/4 {
+ inset-inline-start: 75%;
+ }
+
+ .start-4 {
+ inset-inline-start: 1rem;
+ }
+
+ .start-\\[4px\\] {
+ inset-inline-start: 4px;
+ }
+
+ .start-auto {
+ inset-inline-start: auto;
+ }
+
+ .start-full {
+ inset-inline-start: 100%;
+ }"
+ `)
+ expect(run(['start'])).toEqual('')
+})
+
+test('end', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['end-auto', '-end-full', 'end-full', 'end-3/4', 'end-4', '-end-4', 'end-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-end-4 {
+ inset-inline-end: -1rem;
+ }
+
+ .-end-full {
+ inset-inline-end: -100%;
+ }
+
+ .end-3\\/4 {
+ inset-inline-end: 75%;
+ }
+
+ .end-4 {
+ inset-inline-end: 1rem;
+ }
+
+ .end-\\[4px\\] {
+ inset-inline-end: 4px;
+ }
+
+ .end-auto {
+ inset-inline-end: auto;
+ }
+
+ .end-full {
+ inset-inline-end: 100%;
+ }"
+ `)
+ expect(run(['end'])).toEqual('')
+})
+
+test('top', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+
+ ['top-auto', '-top-full', 'top-full', 'top-3/4', 'top-4', '-top-4', 'top-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-top-4 {
+ top: -1rem;
+ }
+
+ .-top-full {
+ top: -100%;
+ }
+
+ .top-3\\/4 {
+ top: 75%;
+ }
+
+ .top-4 {
+ top: 1rem;
+ }
+
+ .top-\\[4px\\] {
+ top: 4px;
+ }
+
+ .top-auto {
+ top: auto;
+ }
+
+ .top-full {
+ top: 100%;
+ }"
+ `)
+ expect(run(['top'])).toEqual('')
+})
+
+test('right', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'right-auto',
+ '-right-full',
+ 'right-full',
+ 'right-3/4',
+ 'right-4',
+ '-right-4',
+ 'right-[4px]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-right-4 {
+ right: -1rem;
+ }
+
+ .-right-full {
+ right: -100%;
+ }
+
+ .right-3\\/4 {
+ right: 75%;
+ }
+
+ .right-4 {
+ right: 1rem;
+ }
+
+ .right-\\[4px\\] {
+ right: 4px;
+ }
+
+ .right-auto {
+ right: auto;
+ }
+
+ .right-full {
+ right: 100%;
+ }"
+ `)
+ expect(run(['right'])).toEqual('')
+})
+
+test('bottom', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'bottom-auto',
+ '-bottom-full',
+ 'bottom-full',
+ 'bottom-3/4',
+ 'bottom-4',
+ '-bottom-4',
+ 'bottom-[4px]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-bottom-4 {
+ bottom: -1rem;
+ }
+
+ .-bottom-full {
+ bottom: -100%;
+ }
+
+ .bottom-3\\/4 {
+ bottom: 75%;
+ }
+
+ .bottom-4 {
+ bottom: 1rem;
+ }
+
+ .bottom-\\[4px\\] {
+ bottom: 4px;
+ }
+
+ .bottom-auto {
+ bottom: auto;
+ }
+
+ .bottom-full {
+ bottom: 100%;
+ }"
+ `)
+ expect(run(['bottom'])).toEqual('')
+})
+
+test('left', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['left-auto', '-left-full', 'left-full', 'left-3/4', 'left-4', '-left-4', 'left-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-left-4 {
+ left: -1rem;
+ }
+
+ .-left-full {
+ left: -100%;
+ }
+
+ .left-3\\/4 {
+ left: 75%;
+ }
+
+ .left-4 {
+ left: 1rem;
+ }
+
+ .left-\\[4px\\] {
+ left: 4px;
+ }
+
+ .left-auto {
+ left: auto;
+ }
+
+ .left-full {
+ left: 100%;
+ }"
+ `)
+ expect(run(['left'])).toEqual('')
+})
+
+test('isolation', () => {
+ expect(run(['isolate', 'isolation-auto'])).toMatchInlineSnapshot(`
+ ".isolate {
+ isolation: isolate;
+ }
+
+ .isolation-auto {
+ isolation: auto;
+ }"
+ `)
+ expect(run(['-isolate', '-isolation-auto'])).toEqual('')
+})
+
+test('z-index', () => {
+ expect(run(['z-auto', 'z-10', '-z-10', 'z-[123]', '-z-[--value]'])).toMatchInlineSnapshot(`
+ ".-z-10 {
+ z-index: calc(10 * -1);
+ }
+
+ .-z-\\[--value\\] {
+ z-index: calc(var(--value) * -1);
+ }
+
+ .z-10 {
+ z-index: 10;
+ }
+
+ .z-\\[123\\] {
+ z-index: 123;
+ }
+
+ .z-auto {
+ z-index: auto;
+ }"
+ `)
+ expect(run(['z', '-z-auto'])).toEqual('')
+})
+
+test('order', () => {
+ expect(
+ run([
+ 'order-4',
+ '-order-4',
+ 'order-[123]',
+ '-order-[--value]',
+ 'order-first',
+ 'order-last',
+ 'order-none',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".-order-4 {
+ order: calc(4 * -1);
+ }
+
+ .-order-\\[--value\\] {
+ order: calc(var(--value) * -1);
+ }
+
+ .order-4 {
+ order: 4;
+ }
+
+ .order-\\[123\\] {
+ order: 123;
+ }
+
+ .order-first {
+ order: calc(-infinity);
+ }
+
+ .order-last {
+ order: calc(infinity);
+ }
+
+ .order-none {
+ order: 0;
+ }"
+ `)
+ expect(run(['order', '-order-first', '-order-last', '-order-none'])).toEqual('')
+})
+
+test('col', () => {
+ expect(
+ run([
+ 'col-auto',
+ 'col-span-4',
+ 'col-span-17',
+ 'col-span-full',
+ 'col-[span_123/span_123]',
+ 'col-span-[--my-variable]',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".col-\\[span_123\\/span_123\\] {
+ grid-column: span 123 / span 123;
+ }
+
+ .col-auto {
+ grid-column: auto;
+ }
+
+ .col-span-17 {
+ grid-column: span 17 / span 17;
+ }
+
+ .col-span-4 {
+ grid-column: span 4 / span 4;
+ }
+
+ .col-span-\\[--my-variable\\] {
+ grid-column: span var(--my-variable) / span var(--my-variable);
+ }
+
+ .col-span-full {
+ grid-column: 1 / -1;
+ }"
+ `)
+ expect(run(['col', 'col-span', '-col-span-4'])).toEqual('')
+})
+
+test('col-start', () => {
+ expect(run(['col-start-auto', 'col-start-4', 'col-start-99', 'col-start-[123]']))
+ .toMatchInlineSnapshot(`
+ ".col-start-4 {
+ grid-column-start: 4;
+ }
+
+ .col-start-99 {
+ grid-column-start: 99;
+ }
+
+ .col-start-\\[123\\] {
+ grid-column-start: 123;
+ }
+
+ .col-start-auto {
+ grid-column-start: auto;
+ }"
+ `)
+ expect(run(['col-start', '-col-start-4'])).toEqual('')
+})
+
+test('col-end', () => {
+ expect(run(['col-end-auto', 'col-end-4', 'col-end-99', 'col-end-[123]'])).toMatchInlineSnapshot(`
+ ".col-end-4 {
+ grid-column-end: 4;
+ }
+
+ .col-end-99 {
+ grid-column-end: 99;
+ }
+
+ .col-end-\\[123\\] {
+ grid-column-end: 123;
+ }
+
+ .col-end-auto {
+ grid-column-end: auto;
+ }"
+ `)
+ expect(run(['col-end', '-col-end-4'])).toEqual('')
+})
+
+test('row', () => {
+ expect(
+ run([
+ 'row-auto',
+ 'row-span-4',
+ 'row-span-17',
+ 'row-span-full',
+ 'row-[span_123/span_123]',
+ 'row-span-[--my-variable]',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".row-\\[span_123\\/span_123\\] {
+ grid-row: span 123 / span 123;
+ }
+
+ .row-auto {
+ grid-row: auto;
+ }
+
+ .row-span-17 {
+ grid-row: span 17 / span 17;
+ }
+
+ .row-span-4 {
+ grid-row: span 4 / span 4;
+ }
+
+ .row-span-\\[--my-variable\\] {
+ grid-row: span var(--my-variable) / span var(--my-variable);
+ }
+
+ .row-span-full {
+ grid-row: 1 / -1;
+ }"
+ `)
+ expect(run(['row', 'row-span', '-row-span-4'])).toEqual('')
+})
+
+test('row-start', () => {
+ expect(run(['row-start-auto', 'row-start-4', 'row-start-99', 'row-start-[123]']))
+ .toMatchInlineSnapshot(`
+ ".row-start-4 {
+ grid-row-start: 4;
+ }
+
+ .row-start-99 {
+ grid-row-start: 99;
+ }
+
+ .row-start-\\[123\\] {
+ grid-row-start: 123;
+ }
+
+ .row-start-auto {
+ grid-row-start: auto;
+ }"
+ `)
+ expect(run(['row-start', '-row-start-4'])).toEqual('')
+})
+
+test('row-end', () => {
+ expect(run(['row-end-auto', 'row-end-4', 'row-end-99', 'row-end-[123]'])).toMatchInlineSnapshot(`
+ ".row-end-4 {
+ grid-row-end: 4;
+ }
+
+ .row-end-99 {
+ grid-row-end: 99;
+ }
+
+ .row-end-\\[123\\] {
+ grid-row-end: 123;
+ }
+
+ .row-end-auto {
+ grid-row-end: auto;
+ }"
+ `)
+ expect(run(['row-end', '-row-end-4'])).toEqual('')
+})
+
+test('float', () => {
+ expect(run(['float-start', 'float-end', 'float-right', 'float-left', 'float-none']))
+ .toMatchInlineSnapshot(`
+ ".float-end {
+ float: end;
+ }
+
+ .float-left {
+ float: left;
+ }
+
+ .float-none {
+ float: none;
+ }
+
+ .float-right {
+ float: right;
+ }
+
+ .float-start {
+ float: start;
+ }"
+ `)
+ expect(
+ run(['float', '-float-start', '-float-end', '-float-right', '-float-left', '-float-none']),
+ ).toEqual('')
+})
+
+test('clear', () => {
+ expect(run(['clear-start', 'clear-end', 'clear-right', 'clear-left', 'clear-both', 'clear-none']))
+ .toMatchInlineSnapshot(`
+ ".clear-both {
+ clear: both;
+ }
+
+ .clear-end {
+ clear: end;
+ }
+
+ .clear-left {
+ clear: left;
+ }
+
+ .clear-none {
+ clear: none;
+ }
+
+ .clear-right {
+ clear: right;
+ }
+
+ .clear-start {
+ clear: start;
+ }"
+ `)
+ expect(
+ run([
+ 'clear',
+ '-clear-start',
+ '-clear-end',
+ '-clear-right',
+ '-clear-left',
+ '-clear-both',
+ '-clear-none',
+ ]),
+ ).toEqual('')
+})
+
+test('margin', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['m-auto', 'm-4', 'm-[4px]', '-m-4', '-m-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-m-4 {
+ margin: -1rem;
+ }
+
+ .-m-\\[--value\\] {
+ margin: calc(var(--value) * -1);
+ }
+
+ .m-4 {
+ margin: 1rem;
+ }
+
+ .m-\\[4px\\] {
+ margin: 4px;
+ }
+
+ .m-auto {
+ margin: auto;
+ }"
+ `)
+ expect(run(['m'])).toEqual('')
+})
+
+test('margin-x', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['mx-auto', 'mx-4', 'mx-[4px]', '-mx-4', '-mx-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-mx-4 {
+ margin-left: -1rem;
+ margin-right: -1rem;
+ }
+
+ .-mx-\\[--value\\] {
+ margin-left: calc(var(--value) * -1);
+ margin-right: calc(var(--value) * -1);
+ }
+
+ .mx-4 {
+ margin-left: 1rem;
+ margin-right: 1rem;
+ }
+
+ .mx-\\[4px\\] {
+ margin-left: 4px;
+ margin-right: 4px;
+ }
+
+ .mx-auto {
+ margin-left: auto;
+ margin-right: auto;
+ }"
+ `)
+ expect(run(['mx'])).toEqual('')
+})
+
+test('margin-y', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['my-auto', 'my-4', 'my-[4px]', '-my-4', '-my-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-my-4 {
+ margin-top: -1rem;
+ margin-bottom: -1rem;
+ }
+
+ .-my-\\[--value\\] {
+ margin-top: calc(var(--value) * -1);
+ margin-bottom: calc(var(--value) * -1);
+ }
+
+ .my-4 {
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+ }
+
+ .my-\\[4px\\] {
+ margin-top: 4px;
+ margin-bottom: 4px;
+ }
+
+ .my-auto {
+ margin-top: auto;
+ margin-bottom: auto;
+ }"
+ `)
+ expect(run(['my'])).toEqual('')
+})
+
+test('margin-top', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['mt-auto', 'mt-4', 'mt-[4px]', '-mt-4', '-mt-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-mt-4 {
+ margin-top: -1rem;
+ }
+
+ .-mt-\\[--value\\] {
+ margin-top: calc(var(--value) * -1);
+ }
+
+ .mt-4 {
+ margin-top: 1rem;
+ }
+
+ .mt-\\[4px\\] {
+ margin-top: 4px;
+ }
+
+ .mt-auto {
+ margin-top: auto;
+ }"
+ `)
+ expect(run(['mt'])).toEqual('')
+})
+
+test('margin-inline-start', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['ms-auto', 'ms-4', 'ms-[4px]', '-ms-4', '-ms-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-ms-4 {
+ margin-inline-start: -1rem;
+ }
+
+ .-ms-\\[--value\\] {
+ margin-inline-start: calc(var(--value) * -1);
+ }
+
+ .ms-4 {
+ margin-inline-start: 1rem;
+ }
+
+ .ms-\\[4px\\] {
+ margin-inline-start: 4px;
+ }
+
+ .ms-auto {
+ margin-inline-start: auto;
+ }"
+ `)
+ expect(run(['ms'])).toEqual('')
+})
+
+test('margin-inline-end', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['me-auto', 'me-4', 'me-[4px]', '-me-4', '-me-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-me-4 {
+ margin-inline-end: -1rem;
+ }
+
+ .-me-\\[--value\\] {
+ margin-inline-end: calc(var(--value) * -1);
+ }
+
+ .me-4 {
+ margin-inline-end: 1rem;
+ }
+
+ .me-\\[4px\\] {
+ margin-inline-end: 4px;
+ }
+
+ .me-auto {
+ margin-inline-end: auto;
+ }"
+ `)
+ expect(run(['me'])).toEqual('')
+})
+
+test('margin-right', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['mr-auto', 'mr-4', 'mr-[4px]', '-mr-4', '-mr-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-mr-4 {
+ margin-right: -1rem;
+ }
+
+ .-mr-\\[--value\\] {
+ margin-right: calc(var(--value) * -1);
+ }
+
+ .mr-4 {
+ margin-right: 1rem;
+ }
+
+ .mr-\\[4px\\] {
+ margin-right: 4px;
+ }
+
+ .mr-auto {
+ margin-right: auto;
+ }"
+ `)
+ expect(run(['mr'])).toEqual('')
+})
+
+test('margin-bottom', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['mb-auto', 'mb-4', 'mb-[4px]', '-mb-4', '-mb-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-mb-4 {
+ margin-bottom: -1rem;
+ }
+
+ .-mb-\\[--value\\] {
+ margin-bottom: calc(var(--value) * -1);
+ }
+
+ .mb-4 {
+ margin-bottom: 1rem;
+ }
+
+ .mb-\\[4px\\] {
+ margin-bottom: 4px;
+ }
+
+ .mb-auto {
+ margin-bottom: auto;
+ }"
+ `)
+ expect(run(['mb'])).toEqual('')
+})
+
+test('margin-left', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['ml-auto', 'ml-4', 'ml-[4px]', '-ml-4', '-ml-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-ml-4 {
+ margin-left: -1rem;
+ }
+
+ .-ml-\\[--value\\] {
+ margin-left: calc(var(--value) * -1);
+ }
+
+ .ml-4 {
+ margin-left: 1rem;
+ }
+
+ .ml-\\[4px\\] {
+ margin-left: 4px;
+ }
+
+ .ml-auto {
+ margin-left: auto;
+ }"
+ `)
+ expect(run(['ml'])).toEqual('')
+})
+
+test('margin sort order', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['mb-4', 'me-4', 'mx-4', 'ml-4', 'ms-4', 'm-4', 'mr-4', 'mt-4', 'my-4'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .m-4 {
+ margin: 1rem;
+ }
+
+ .mx-4 {
+ margin-left: 1rem;
+ margin-right: 1rem;
+ }
+
+ .my-4 {
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+ }
+
+ .ms-4 {
+ margin-inline-start: 1rem;
+ }
+
+ .me-4 {
+ margin-inline-end: 1rem;
+ }
+
+ .mt-4 {
+ margin-top: 1rem;
+ }
+
+ .mr-4 {
+ margin-right: 1rem;
+ }
+
+ .mb-4 {
+ margin-bottom: 1rem;
+ }
+
+ .ml-4 {
+ margin-left: 1rem;
+ }"
+ `)
+ expect(run(['m'])).toEqual('')
+})
+
+test('box-sizing', () => {
+ expect(run(['box-border', 'box-content'])).toMatchInlineSnapshot(`
+ ".box-border {
+ box-sizing: border-box;
+ }
+
+ .box-content {
+ box-sizing: content-box;
+ }"
+ `)
+ expect(run(['box', '-box-border', '-box-content'])).toEqual('')
+})
+
+test('line-clamp', () => {
+ expect(run(['line-clamp-4', 'line-clamp-99', 'line-clamp-[123]', 'line-clamp-none']))
+ .toMatchInlineSnapshot(`
+ ".line-clamp-4 {
+ overlow: hidden;
+ -webkit-line-clamp: 4;
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+ }
+
+ .line-clamp-99 {
+ overlow: hidden;
+ -webkit-line-clamp: 99;
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+ }
+
+ .line-clamp-\\[123\\] {
+ overlow: hidden;
+ -webkit-line-clamp: 123;
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+ }
+
+ .line-clamp-none {
+ overlow: visible;
+ -webkit-box-orient: horizonal;
+ -webkit-line-clamp: none;
+ display: block;
+ }"
+ `)
+ expect(run(['line-clamp', '-line-clamp-4', '-line-clamp-[123]', '-line-clamp-none'])).toEqual('')
+})
+
+test('display', () => {
+ expect(
+ run([
+ 'block',
+ 'inline-block',
+ 'inline',
+ 'flex',
+ 'inline-flex',
+ 'table',
+ 'inline-table',
+ 'table-caption',
+ 'table-cell',
+ 'table-column',
+ 'table-column-group',
+ 'table-footer-group',
+ 'table-header-group',
+ 'table-row-group',
+ 'table-row',
+ 'flow-root',
+ 'grid',
+ 'inline-grid',
+ 'contents',
+ 'list-item',
+ 'hidden',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".block {
+ display: block;
+ }
+
+ .contents {
+ display: contents;
+ }
+
+ .flex {
+ display: flex;
+ }
+
+ .flow-root {
+ display: flow-root;
+ }
+
+ .grid {
+ display: grid;
+ }
+
+ .hidden {
+ display: none;
+ }
+
+ .inline {
+ display: inline;
+ }
+
+ .inline-block {
+ display: inline-block;
+ }
+
+ .inline-flex {
+ display: inline-flex;
+ }
+
+ .inline-grid {
+ display: inline-grid;
+ }
+
+ .inline-table {
+ display: inline-table;
+ }
+
+ .list-item {
+ display: list-item;
+ }
+
+ .table {
+ display: table;
+ }
+
+ .table-caption {
+ display: table-caption;
+ }
+
+ .table-cell {
+ display: table-cell;
+ }
+
+ .table-column {
+ display: table-column;
+ }
+
+ .table-column-group {
+ display: table-column-group;
+ }
+
+ .table-footer-group {
+ display: table-footer-group;
+ }
+
+ .table-header-group {
+ display: table-header-group;
+ }
+
+ .table-row {
+ display: table-row;
+ }
+
+ .table-row-group {
+ display: table-row-group;
+ }"
+ `)
+ expect(
+ run([
+ '-block',
+ '-inline-block',
+ '-inline',
+ '-flex',
+ '-inline-flex',
+ '-table',
+ '-inline-table',
+ '-table-caption',
+ '-table-cell',
+ '-table-column',
+ '-table-column-group',
+ '-table-footer-group',
+ '-table-header-group',
+ '-table-row-group',
+ '-table-row',
+ '-flow-root',
+ '-grid',
+ '-inline-grid',
+ '-contents',
+ '-list-item',
+ '-hidden',
+ ]),
+ ).toEqual('')
+})
+
+test('aspect-ratio', () => {
+ expect(run(['aspect-video', 'aspect-[10/9]', 'aspect-4/3'])).toMatchInlineSnapshot(`
+ ".aspect-4\\/3 {
+ aspect-ratio: 4 / 3;
+ }
+
+ .aspect-\\[10\\/9\\] {
+ aspect-ratio: 10 / 9;
+ }
+
+ .aspect-video {
+ aspect-ratio: 16 / 9;
+ }"
+ `)
+ expect(run(['aspect', 'aspect-potato', '-aspect-video', '-aspect-[10/9]'])).toEqual('')
+})
+
+test('size', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'size-auto',
+ 'size-full',
+ 'size-min',
+ 'size-max',
+ 'size-fit',
+ 'size-4',
+ 'size-1/2',
+ 'size-[4px]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .size-1\\/2 {
+ width: 50%;
+ height: 50%;
+ }
+
+ .size-4 {
+ width: 1rem;
+ height: 1rem;
+ }
+
+ .size-\\[4px\\] {
+ width: 4px;
+ height: 4px;
+ }
+
+ .size-auto {
+ width: auto;
+ height: auto;
+ }
+
+ .size-fit {
+ width: fit-content;
+ height: fit-content;
+ }
+
+ .size-full {
+ width: 100%;
+ height: 100%;
+ }
+
+ .size-max {
+ width: max-content;
+ height: max-content;
+ }
+
+ .size-min {
+ width: min-content;
+ height: min-content;
+ }"
+ `)
+ expect(run(['size', '-size-4', '-size-1/2', '-size-[4px]'])).toEqual('')
+})
+
+test('width', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ --width-xl: 36rem;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'w-full',
+ 'w-auto',
+ 'w-screen',
+ 'w-svw',
+ 'w-lvw',
+ 'w-dvw',
+ 'w-min',
+ 'w-max',
+ 'w-fit',
+ 'w-4',
+ 'w-xl',
+ 'w-1/2',
+ 'w-[4px]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ --width-xl: 36rem;
+ }
+
+ .w-1\\/2 {
+ width: 50%;
+ }
+
+ .w-4 {
+ width: 1rem;
+ }
+
+ .w-\\[4px\\] {
+ width: 4px;
+ }
+
+ .w-auto {
+ width: auto;
+ }
+
+ .w-dvw {
+ width: 100dvw;
+ }
+
+ .w-fit {
+ width: fit-content;
+ }
+
+ .w-full {
+ width: 100%;
+ }
+
+ .w-lvw {
+ width: 100lvw;
+ }
+
+ .w-max {
+ width: max-content;
+ }
+
+ .w-min {
+ width: min-content;
+ }
+
+ .w-screen {
+ width: 100vw;
+ }
+
+ .w-svw {
+ width: 100svw;
+ }
+
+ .w-xl {
+ width: 36rem;
+ }"
+ `)
+ expect(run(['w', '-w-4', '-w-1/2', '-w-[4px]'])).toEqual('')
+})
+
+test('min-width', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ --width-xl: 36rem;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'min-w-auto',
+ 'min-w-full',
+ 'min-w-min',
+ 'min-w-max',
+ 'min-w-fit',
+ 'min-w-4',
+ 'min-w-xl',
+ 'min-w-[4px]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ --width-xl: 36rem;
+ }
+
+ .min-w-4 {
+ min-width: 1rem;
+ }
+
+ .min-w-\\[4px\\] {
+ min-width: 4px;
+ }
+
+ .min-w-auto {
+ min-width: auto;
+ }
+
+ .min-w-fit {
+ min-width: fit-content;
+ }
+
+ .min-w-full {
+ min-width: 100%;
+ }
+
+ .min-w-max {
+ min-width: max-content;
+ }
+
+ .min-w-min {
+ min-width: min-content;
+ }
+
+ .min-w-xl {
+ min-width: 36rem;
+ }"
+ `)
+ expect(run(['min-w', '-min-w-4', '-min-w-[4px]'])).toEqual('')
+})
+
+test('max-width', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ --width-xl: 36rem;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'max-w-none',
+ 'max-w-full',
+ 'max-w-max',
+ 'max-w-max',
+ 'max-w-fit',
+ 'max-w-4',
+ 'max-w-xl',
+ 'max-w-[4px]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ --width-xl: 36rem;
+ }
+
+ .max-w-4 {
+ max-width: 1rem;
+ }
+
+ .max-w-\\[4px\\] {
+ max-width: 4px;
+ }
+
+ .max-w-fit {
+ max-width: fit-content;
+ }
+
+ .max-w-full {
+ max-width: 100%;
+ }
+
+ .max-w-max {
+ max-width: max-content;
+ }
+
+ .max-w-none {
+ max-width: none;
+ }
+
+ .max-w-xl {
+ max-width: 36rem;
+ }"
+ `)
+ expect(run(['max-w', '-max-w-4', '-max-w-[4px]'])).toEqual('')
+})
+
+test('height', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'h-full',
+ 'h-auto',
+ 'h-screen',
+ 'h-svh',
+ 'h-lvh',
+ 'h-dvh',
+ 'h-min',
+ 'h-max',
+ 'h-fit',
+ 'h-4',
+ 'h-1/2',
+ 'h-[4px]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .h-1\\/2 {
+ height: 50%;
+ }
+
+ .h-4 {
+ height: 1rem;
+ }
+
+ .h-\\[4px\\] {
+ height: 4px;
+ }
+
+ .h-auto {
+ height: auto;
+ }
+
+ .h-dvh {
+ height: 100dvh;
+ }
+
+ .h-fit {
+ height: fit-content;
+ }
+
+ .h-full {
+ height: 100%;
+ }
+
+ .h-lvh {
+ height: 100lvh;
+ }
+
+ .h-max {
+ height: max-content;
+ }
+
+ .h-min {
+ height: min-content;
+ }
+
+ .h-screen {
+ height: 100vh;
+ }
+
+ .h-svh {
+ height: 100svh;
+ }"
+ `)
+ expect(run(['h', '-h-4', '-h-1/2', '-h-[4px]'])).toEqual('')
+})
+
+test('min-height', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'min-h-auto',
+ 'min-h-full',
+ 'min-h-screen',
+ 'min-h-svh',
+ 'min-h-lvh',
+ 'min-h-dvh',
+ 'min-h-min',
+ 'min-h-max',
+ 'min-h-fit',
+ 'min-h-4',
+ 'min-h-[4px]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .min-h-4 {
+ min-height: 1rem;
+ }
+
+ .min-h-\\[4px\\] {
+ min-height: 4px;
+ }
+
+ .min-h-auto {
+ min-height: auto;
+ }
+
+ .min-h-dvh {
+ min-height: 100dvh;
+ }
+
+ .min-h-fit {
+ min-height: fit-content;
+ }
+
+ .min-h-full {
+ min-height: 100%;
+ }
+
+ .min-h-lvh {
+ min-height: 100lvh;
+ }
+
+ .min-h-max {
+ min-height: max-content;
+ }
+
+ .min-h-min {
+ min-height: min-content;
+ }
+
+ .min-h-screen {
+ min-height: 100vh;
+ }
+
+ .min-h-svh {
+ min-height: 100svh;
+ }"
+ `)
+ expect(run(['min-h', '-min-h-4', '-min-h-[4px]'])).toEqual('')
+})
+
+test('max-height', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'max-h-none',
+ 'max-h-full',
+ 'max-h-screen',
+ 'max-h-svh',
+ 'max-h-lvh',
+ 'max-h-dvh',
+ 'max-h-min',
+ 'max-h-max',
+ 'max-h-fit',
+ 'max-h-4',
+ 'max-h-[4px]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .max-h-4 {
+ max-height: 1rem;
+ }
+
+ .max-h-\\[4px\\] {
+ max-height: 4px;
+ }
+
+ .max-h-dvh {
+ max-height: 100dvh;
+ }
+
+ .max-h-fit {
+ max-height: fit-content;
+ }
+
+ .max-h-full {
+ max-height: 100%;
+ }
+
+ .max-h-lvh {
+ max-height: 100lvh;
+ }
+
+ .max-h-max {
+ max-height: max-content;
+ }
+
+ .max-h-min {
+ max-height: min-content;
+ }
+
+ .max-h-none {
+ max-height: none;
+ }
+
+ .max-h-screen {
+ max-height: 100vh;
+ }
+
+ .max-h-svh {
+ max-height: 100svh;
+ }"
+ `)
+ expect(run(['max-h', '-max-h-4', '-max-h-[4px]'])).toEqual('')
+})
+
+test('flex', () => {
+ expect(
+ run(['flex-1', 'flex-99', 'flex-1/2', 'flex-auto', 'flex-initial', 'flex-none', 'flex-[123]']),
+ ).toMatchInlineSnapshot(`
+ ".flex-1 {
+ flex: 1;
+ }
+
+ .flex-1\\/2 {
+ flex: 50%;
+ }
+
+ .flex-99 {
+ flex: 99;
+ }
+
+ .flex-\\[123\\] {
+ flex: 123;
+ }
+
+ .flex-auto {
+ flex: auto;
+ }
+
+ .flex-initial {
+ flex: 0 auto;
+ }
+
+ .flex-none {
+ flex: none;
+ }"
+ `)
+ expect(run(['-flex-1', '-flex-auto', '-flex-initial', '-flex-none', '-flex-[123]'])).toEqual('')
+})
+
+test('flex-shrink', () => {
+ expect(run(['shrink', 'shrink-0', 'shrink-[123]'])).toMatchInlineSnapshot(`
+ ".shrink {
+ flex-shrink: 1;
+ }
+
+ .shrink-0 {
+ flex-shrink: 0;
+ }
+
+ .shrink-\\[123\\] {
+ flex-shrink: 123;
+ }"
+ `)
+ expect(run(['-shrink', '-shrink-0', '-shrink-[123]'])).toEqual('')
+})
+
+test('flex-grow', () => {
+ expect(run(['grow', 'grow-0', 'grow-[123]'])).toMatchInlineSnapshot(`
+ ".grow {
+ flex-grow: 1;
+ }
+
+ .grow-0 {
+ flex-grow: 0;
+ }
+
+ .grow-\\[123\\] {
+ flex-grow: 123;
+ }"
+ `)
+ expect(run(['-grow', '-grow-0', '-grow-[123]'])).toEqual('')
+})
+
+test('flex-basis', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --width-xl: 36rem;
+ }
+ @tailwind utilities;
+ `,
+ ['basis-auto', 'basis-full', 'basis-xl', 'basis-11/12', 'basis-[123px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --width-xl: 36rem;
+ }
+
+ .basis-11\\/12 {
+ flex-basis: 91.6667%;
+ }
+
+ .basis-\\[123px\\] {
+ flex-basis: 123px;
+ }
+
+ .basis-auto {
+ flex-basis: auto;
+ }
+
+ .basis-full {
+ flex-basis: 100%;
+ }
+
+ .basis-xl {
+ flex-basis: 36rem;
+ }"
+ `)
+ expect(run(['basis', '-basis-full', '-basis-[123px]'])).toEqual('')
+})
+
+test('table-layout', () => {
+ expect(run(['table-auto', 'table-fixed'])).toMatchInlineSnapshot(`
+ ".table-auto {
+ table-layout: auto;
+ }
+
+ .table-fixed {
+ table-layout: fixed;
+ }"
+ `)
+ expect(run(['-table-auto', '-table-fixed'])).toEqual('')
+})
+
+test('caption-side', () => {
+ expect(run(['caption-top', 'caption-bottom'])).toMatchInlineSnapshot(`
+ ".caption-bottom {
+ caption-side: bottom;
+ }
+
+ .caption-top {
+ caption-side: top;
+ }"
+ `)
+ expect(run(['-caption-top', '-caption-bottom'])).toEqual('')
+})
+
+test('border-collapse', () => {
+ expect(run(['border-collapse', 'border-separate'])).toMatchInlineSnapshot(`
+ ".border-collapse {
+ border-collapse: collapse;
+ }
+
+ .border-separate {
+ border-collapse: separate;
+ }"
+ `)
+ expect(run(['-border-collapse', '-border-separate'])).toEqual('')
+})
+
+test('border-spacing', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-1: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['border-spacing-1', 'border-spacing-[123px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-1: .25rem;
+ }
+
+ .border-spacing-1 {
+ --tw-border-spacing-x: .25rem;
+ --tw-border-spacing-y: .25rem;
+ border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
+ }
+
+ .border-spacing-\\[123px\\] {
+ --tw-border-spacing-x: 123px;
+ --tw-border-spacing-y: 123px;
+ border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
+ }
+
+ @property --tw-border-spacing-x {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }
+
+ @property --tw-border-spacing-y {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }"
+ `)
+ expect(run(['border-spacing', '-border-spacing-1', '-border-spacing-[123px]'])).toEqual('')
+})
+
+test('border-spacing-x', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-1: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['border-spacing-x-1', 'border-spacing-x-[123px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-1: .25rem;
+ }
+
+ .border-spacing-x-1 {
+ --tw-border-spacing-x: .25rem;
+ border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
+ }
+
+ .border-spacing-x-\\[123px\\] {
+ --tw-border-spacing-x: 123px;
+ border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
+ }
+
+ @property --tw-border-spacing-x {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }
+
+ @property --tw-border-spacing-y {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }"
+ `)
+ expect(run(['border-spacing-x', '-border-spacing-x-1', '-border-spacing-x-[123px]'])).toEqual('')
+})
+
+test('border-spacing-y', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-1: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['border-spacing-y-1', 'border-spacing-y-[123px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-1: .25rem;
+ }
+
+ .border-spacing-y-1 {
+ --tw-border-spacing-y: .25rem;
+ border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
+ }
+
+ .border-spacing-y-\\[123px\\] {
+ --tw-border-spacing-y: 123px;
+ border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
+ }
+
+ @property --tw-border-spacing-x {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }
+
+ @property --tw-border-spacing-y {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }"
+ `)
+ expect(run(['border-spacing-x', '-border-spacing-y-1', '-border-spacing-y-[123px]'])).toEqual('')
+})
+
+test('origin', () => {
+ expect(
+ run([
+ 'origin-center',
+ 'origin-top',
+ 'origin-top-right',
+ 'origin-right',
+ 'origin-bottom-right',
+ 'origin-bottom',
+ 'origin-bottom-left',
+ 'origin-left',
+ 'origin-top-left',
+ 'origin-[50px_100px]',
+ 'origin-[--value]',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".origin-\\[--value\\] {
+ transform-origin: var(--value);
+ }
+
+ .origin-\\[50px_100px\\] {
+ transform-origin: 50px 100px;
+ }
+
+ .origin-bottom {
+ transform-origin: bottom;
+ }
+
+ .origin-bottom-left {
+ transform-origin: 0 100%;
+ }
+
+ .origin-bottom-right {
+ transform-origin: 100% 100%;
+ }
+
+ .origin-center {
+ transform-origin: center;
+ }
+
+ .origin-left {
+ transform-origin: 0;
+ }
+
+ .origin-right {
+ transform-origin: 100%;
+ }
+
+ .origin-top {
+ transform-origin: top;
+ }
+
+ .origin-top-left {
+ transform-origin: 0 0;
+ }
+
+ .origin-top-right {
+ transform-origin: 100% 0;
+ }"
+ `)
+ expect(run(['-origin-center', '-origin-[--value]'])).toEqual('')
+})
+
+test('translate', () => {
+ expect(
+ run([
+ 'translate-1/2',
+ 'translate-full',
+ '-translate-full',
+ 'translate-[123px]',
+ '-translate-[--value]',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".-translate-\\[--value\\] {
+ --tw-translate-x: calc(var(--value) * -1);
+ --tw-translate-y: calc(var(--value) * -1);
+ translate: var(--tw-translate-x) var(--tw-translate-y);
+ }
+
+ .-translate-full {
+ --tw-translate-x: -100%;
+ --tw-translate-y: -100%;
+ translate: var(--tw-translate-x) var(--tw-translate-y);
+ }
+
+ .translate-1\\/2 {
+ --tw-translate-x: calc(1 / 2 * 100%);
+ --tw-translate-y: calc(1 / 2 * 100%);
+ translate: var(--tw-translate-x) var(--tw-translate-y);
+ }
+
+ .translate-\\[123px\\] {
+ --tw-translate-x: 123px;
+ --tw-translate-y: 123px;
+ translate: var(--tw-translate-x) var(--tw-translate-y);
+ }
+
+ .translate-full {
+ --tw-translate-x: 100%;
+ --tw-translate-y: 100%;
+ translate: var(--tw-translate-x) var(--tw-translate-y);
+ }
+
+ @property --tw-translate-x {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }
+
+ @property --tw-translate-y {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }"
+ `)
+ expect(run(['translate'])).toEqual('')
+})
+
+test('translate-x', () => {
+ expect(run(['translate-x-full', '-translate-x-full', '-translate-x-[--value]']))
+ .toMatchInlineSnapshot(`
+ ".-translate-x-\\[--value\\] {
+ --tw-translate-x: calc(var(--value) * -1);
+ translate: var(--tw-translate-x) var(--tw-translate-y);
+ }
+
+ .-translate-x-full {
+ --tw-translate-x: -100%;
+ translate: var(--tw-translate-x) var(--tw-translate-y);
+ }
+
+ .translate-x-full {
+ --tw-translate-x: 100%;
+ translate: var(--tw-translate-x) var(--tw-translate-y);
+ }
+
+ @property --tw-translate-x {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }
+
+ @property --tw-translate-y {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }"
+ `)
+ expect(run(['translate-x'])).toEqual('')
+})
+
+test('translate-y', () => {
+ expect(run(['translate-y-full', '-translate-y-full', '-translate-y-[--value]']))
+ .toMatchInlineSnapshot(`
+ ".-translate-y-\\[--value\\] {
+ --tw-translate-y: calc(var(--value) * -1);
+ translate: var(--tw-translate-x) var(--tw-translate-y);
+ }
+
+ .-translate-y-full {
+ --tw-translate-y: -100%;
+ translate: var(--tw-translate-x) var(--tw-translate-y);
+ }
+
+ .translate-y-full {
+ --tw-translate-y: 100%;
+ translate: var(--tw-translate-x) var(--tw-translate-y);
+ }
+
+ @property --tw-translate-x {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }
+
+ @property --tw-translate-y {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }"
+ `)
+ expect(run(['translate-y'])).toEqual('')
+})
+
+test('rotate', () => {
+ expect(run(['rotate-45', '-rotate-45', 'rotate-[123deg]'])).toMatchInlineSnapshot(`
+ ".-rotate-45 {
+ rotate: -45deg;
+ }
+
+ .rotate-45 {
+ rotate: 45deg;
+ }
+
+ .rotate-\\[123deg\\] {
+ rotate: 123deg;
+ }"
+ `)
+ expect(run(['rotate'])).toEqual('')
+})
+
+test('skew', () => {
+ expect(run(['skew-6', '-skew-6', 'skew-[123deg]'])).toMatchInlineSnapshot(`
+ ".-skew-6 {
+ --tw-skew-x: calc(6deg * -1);
+ --tw-skew-y: calc(6deg * -1);
+ transform: skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y));
+ }
+
+ .skew-6 {
+ --tw-skew-x: 6deg;
+ --tw-skew-y: 6deg;
+ transform: skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y));
+ }
+
+ .skew-\\[123deg\\] {
+ --tw-skew-x: 123deg;
+ --tw-skew-y: 123deg;
+ transform: skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y));
+ }
+
+ @property --tw-skew-x {
+ syntax: "";
+ inherits: false;
+ initial-value: 0deg;
+ }
+
+ @property --tw-skew-y {
+ syntax: "";
+ inherits: false;
+ initial-value: 0deg;
+ }"
+ `)
+ expect(run(['skew'])).toEqual('')
+})
+
+test('skew-x', () => {
+ expect(run(['skew-x-6', '-skew-x-6', 'skew-x-[123deg]'])).toMatchInlineSnapshot(`
+ ".-skew-x-6 {
+ --tw-skew-x: calc(6deg * -1);
+ transform: skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y));
+ }
+
+ .skew-x-6 {
+ --tw-skew-x: 6deg;
+ transform: skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y));
+ }
+
+ .skew-x-\\[123deg\\] {
+ --tw-skew-x: 123deg;
+ transform: skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y));
+ }
+
+ @property --tw-skew-x {
+ syntax: "";
+ inherits: false;
+ initial-value: 0deg;
+ }
+
+ @property --tw-skew-y {
+ syntax: "";
+ inherits: false;
+ initial-value: 0deg;
+ }"
+ `)
+ expect(run(['skew-x'])).toEqual('')
+})
+
+test('skew-y', () => {
+ expect(run(['skew-y-6', '-skew-y-6', 'skew-y-[123deg]'])).toMatchInlineSnapshot(`
+ ".-skew-y-6 {
+ --tw-skew-y: calc(6deg * -1);
+ transform: skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y));
+ }
+
+ .skew-y-6 {
+ --tw-skew-y: 6deg;
+ transform: skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y));
+ }
+
+ .skew-y-\\[123deg\\] {
+ --tw-skew-y: 123deg;
+ transform: skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y));
+ }
+
+ @property --tw-skew-x {
+ syntax: "";
+ inherits: false;
+ initial-value: 0deg;
+ }
+
+ @property --tw-skew-y {
+ syntax: "";
+ inherits: false;
+ initial-value: 0deg;
+ }"
+ `)
+ expect(run(['skew-y'])).toEqual('')
+})
+
+test('scale', () => {
+ expect(run(['scale-50', '-scale-50', 'scale-[123deg]'])).toMatchInlineSnapshot(`
+ ".-scale-50 {
+ --tw-scale-x: calc(50% * -1);
+ --tw-scale-y: calc(50% * -1);
+ scale: var(--tw-scale-x) var(--tw-scale-y);
+ }
+
+ .scale-50 {
+ --tw-scale-x: 50%;
+ --tw-scale-y: 50%;
+ scale: var(--tw-scale-x) var(--tw-scale-y);
+ }
+
+ .scale-\\[123deg\\] {
+ --tw-scale-x: 123deg;
+ --tw-scale-y: 123deg;
+ scale: var(--tw-scale-x) var(--tw-scale-y);
+ }
+
+ @property --tw-scale-x {
+ syntax: "";
+ inherits: false;
+ initial-value: 1;
+ }
+
+ @property --tw-scale-y {
+ syntax: "";
+ inherits: false;
+ initial-value: 1;
+ }"
+ `)
+ expect(run(['scale'])).toEqual('')
+})
+
+test('scale-x', () => {
+ expect(run(['scale-x-50', '-scale-x-50', 'scale-x-[123deg]'])).toMatchInlineSnapshot(`
+ ".-scale-x-50 {
+ --tw-scale-x: calc(50% * -1);
+ scale: var(--tw-scale-x) var(--tw-scale-y);
+ }
+
+ .scale-x-50 {
+ --tw-scale-x: 50%;
+ scale: var(--tw-scale-x) var(--tw-scale-y);
+ }
+
+ .scale-x-\\[123deg\\] {
+ --tw-scale-x: 123deg;
+ scale: var(--tw-scale-x) var(--tw-scale-y);
+ }
+
+ @property --tw-scale-x {
+ syntax: "";
+ inherits: false;
+ initial-value: 1;
+ }
+
+ @property --tw-scale-y {
+ syntax: "";
+ inherits: false;
+ initial-value: 1;
+ }"
+ `)
+ expect(run(['scale-x'])).toEqual('')
+})
+
+test('scale-y', () => {
+ expect(run(['scale-y-50', '-scale-y-50', 'scale-y-[123deg]'])).toMatchInlineSnapshot(`
+ ".-scale-y-50 {
+ --tw-scale-y: calc(50% * -1);
+ scale: var(--tw-scale-x) var(--tw-scale-y);
+ }
+
+ .scale-y-50 {
+ --tw-scale-y: 50%;
+ scale: var(--tw-scale-x) var(--tw-scale-y);
+ }
+
+ .scale-y-\\[123deg\\] {
+ --tw-scale-y: 123deg;
+ scale: var(--tw-scale-x) var(--tw-scale-y);
+ }
+
+ @property --tw-scale-x {
+ syntax: "";
+ inherits: false;
+ initial-value: 1;
+ }
+
+ @property --tw-scale-y {
+ syntax: "";
+ inherits: false;
+ initial-value: 1;
+ }"
+ `)
+ expect(run(['scale-y'])).toEqual('')
+})
+
+test('transform', () => {
+ expect(run(['transform', 'transform-cpu', 'transform-gpu', 'transform-none']))
+ .toMatchInlineSnapshot(`
+ ".transform-none {
+ transform: none;
+ }
+
+ .transform {
+ transform: skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y));
+ }
+
+ .transform-cpu {
+ transform: translate(0);
+ }
+
+ .transform-gpu {
+ transform: translate(0, 0, 0);
+ }
+
+ @property --tw-skew-x {
+ syntax: "";
+ inherits: false;
+ initial-value: 0deg;
+ }
+
+ @property --tw-skew-y {
+ syntax: "";
+ inherits: false;
+ initial-value: 0deg;
+ }"
+ `)
+ expect(run(['-transform', '-transform-cpu', '-transform-gpu', '-transform-none'])).toEqual('')
+})
+
+test('cursor', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --cursor-custom: url(/my-cursor.png);
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'cursor-auto',
+ 'cursor-default',
+ 'cursor-pointer',
+ 'cursor-wait',
+ 'cursor-text',
+ 'cursor-move',
+ 'cursor-help',
+ 'cursor-not-allowed',
+ 'cursor-none',
+ 'cursor-context-menu',
+ 'cursor-progress',
+ 'cursor-cell',
+ 'cursor-crosshair',
+ 'cursor-vertical-text',
+ 'cursor-alias',
+ 'cursor-copy',
+ 'cursor-no-drop',
+ 'cursor-grab',
+ 'cursor-grabbing',
+ 'cursor-all-scroll',
+ 'cursor-col-resize',
+ 'cursor-row-resize',
+ 'cursor-n-resize',
+ 'cursor-e-resize',
+ 'cursor-s-resize',
+ 'cursor-w-resize',
+ 'cursor-ne-resize',
+ 'cursor-nw-resize',
+ 'cursor-se-resize',
+ 'cursor-sw-resize',
+ 'cursor-ew-resize',
+ 'cursor-ns-resize',
+ 'cursor-nesw-resize',
+ 'cursor-nwse-resize',
+ 'cursor-zoom-in',
+ 'cursor-zoom-out',
+ 'cursor-[--value]',
+ 'cursor-custom',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --cursor-custom: url("/my-cursor.png");
+ }
+
+ .cursor-\\[--value\\] {
+ cursor: var(--value);
+ }
+
+ .cursor-alias {
+ cursor: alias;
+ }
+
+ .cursor-all-scroll {
+ cursor: all-scroll;
+ }
+
+ .cursor-auto {
+ cursor: auto;
+ }
+
+ .cursor-cell {
+ cursor: cell;
+ }
+
+ .cursor-col-resize {
+ cursor: col-resize;
+ }
+
+ .cursor-context-menu {
+ cursor: context-menu;
+ }
+
+ .cursor-copy {
+ cursor: copy;
+ }
+
+ .cursor-crosshair {
+ cursor: crosshair;
+ }
+
+ .cursor-custom {
+ cursor: url("/my-cursor.png");
+ }
+
+ .cursor-default {
+ cursor: default;
+ }
+
+ .cursor-e-resize {
+ cursor: e-resize;
+ }
+
+ .cursor-ew-resize {
+ cursor: ew-resize;
+ }
+
+ .cursor-grab {
+ cursor: grab;
+ }
+
+ .cursor-grabbing {
+ cursor: grabbing;
+ }
+
+ .cursor-help {
+ cursor: help;
+ }
+
+ .cursor-move {
+ cursor: move;
+ }
+
+ .cursor-n-resize {
+ cursor: n-resize;
+ }
+
+ .cursor-ne-resize {
+ cursor: ne-resize;
+ }
+
+ .cursor-nesw-resize {
+ cursor: nesw-resize;
+ }
+
+ .cursor-no-drop {
+ cursor: no-drop;
+ }
+
+ .cursor-none {
+ cursor: none;
+ }
+
+ .cursor-not-allowed {
+ cursor: not-allowed;
+ }
+
+ .cursor-ns-resize {
+ cursor: ns-resize;
+ }
+
+ .cursor-nw-resize {
+ cursor: nw-resize;
+ }
+
+ .cursor-nwse-resize {
+ cursor: nwse-resize;
+ }
+
+ .cursor-pointer {
+ cursor: pointer;
+ }
+
+ .cursor-progress {
+ cursor: progress;
+ }
+
+ .cursor-row-resize {
+ cursor: row-resize;
+ }
+
+ .cursor-s-resize {
+ cursor: s-resize;
+ }
+
+ .cursor-se-resize {
+ cursor: se-resize;
+ }
+
+ .cursor-sw-resize {
+ cursor: sw-resize;
+ }
+
+ .cursor-text {
+ cursor: text;
+ }
+
+ .cursor-vertical-text {
+ cursor: vertical-text;
+ }
+
+ .cursor-w-resize {
+ cursor: w-resize;
+ }
+
+ .cursor-wait {
+ cursor: wait;
+ }
+
+ .cursor-zoom-in {
+ cursor: zoom-in;
+ }
+
+ .cursor-zoom-out {
+ cursor: zoom-out;
+ }"
+ `)
+ expect(
+ run([
+ 'cursor',
+ '-cursor-auto',
+ '-cursor-default',
+ '-cursor-pointer',
+ '-cursor-wait',
+ '-cursor-text',
+ '-cursor-move',
+ '-cursor-help',
+ '-cursor-not-allowed',
+ '-cursor-none',
+ '-cursor-context-menu',
+ '-cursor-progress',
+ '-cursor-cell',
+ '-cursor-crosshair',
+ '-cursor-vertical-text',
+ '-cursor-alias',
+ '-cursor-copy',
+ '-cursor-no-drop',
+ '-cursor-grab',
+ '-cursor-grabbing',
+ '-cursor-all-scroll',
+ '-cursor-col-resize',
+ '-cursor-row-resize',
+ '-cursor-n-resize',
+ '-cursor-e-resize',
+ '-cursor-s-resize',
+ '-cursor-w-resize',
+ '-cursor-ne-resize',
+ '-cursor-nw-resize',
+ '-cursor-se-resize',
+ '-cursor-sw-resize',
+ '-cursor-ew-resize',
+ '-cursor-ns-resize',
+ '-cursor-nesw-resize',
+ '-cursor-nwse-resize',
+ '-cursor-zoom-in',
+ '-cursor-zoom-out',
+ '-cursor-[--value]',
+ '-cursor-custom',
+ ]),
+ ).toEqual('')
+})
+
+test('touch-action', () => {
+ expect(run(['touch-auto', 'touch-none', 'touch-manipulation'])).toMatchInlineSnapshot(`
+ ".touch-auto {
+ touch-action: auto;
+ }
+
+ .touch-manipulation {
+ touch-action: manipulation;
+ }
+
+ .touch-none {
+ touch-action: none;
+ }"
+ `)
+ expect(run(['-touch-auto', '-touch-none', '-touch-manipulation'])).toEqual('')
+})
+
+test('touch-pan', () => {
+ expect(
+ run([
+ 'touch-pan-x',
+ 'touch-pan-left',
+ 'touch-pan-right',
+ 'touch-pan-y',
+ 'touch-pan-up',
+ 'touch-pan-down',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".touch-pan-down {
+ --tw-pan-y: pan-down;
+ touch-action: var(--tw-pan-x, ) var(--tw-pan-y, ) var(--tw-pinch-zoom, );
+ }
+
+ .touch-pan-left {
+ --tw-pan-x: pan-left;
+ touch-action: var(--tw-pan-x, ) var(--tw-pan-y, ) var(--tw-pinch-zoom, );
+ }
+
+ .touch-pan-right {
+ --tw-pan-x: pan-right;
+ touch-action: var(--tw-pan-x, ) var(--tw-pan-y, ) var(--tw-pinch-zoom, );
+ }
+
+ .touch-pan-up {
+ --tw-pan-y: pan-up;
+ touch-action: var(--tw-pan-x, ) var(--tw-pan-y, ) var(--tw-pinch-zoom, );
+ }
+
+ .touch-pan-x {
+ --tw-pan-x: pan-x;
+ touch-action: var(--tw-pan-x, ) var(--tw-pan-y, ) var(--tw-pinch-zoom, );
+ }
+
+ .touch-pan-y {
+ --tw-pan-y: pan-y;
+ touch-action: var(--tw-pan-x, ) var(--tw-pan-y, ) var(--tw-pinch-zoom, );
+ }
+
+ @property --tw-pan-x {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-pan-y {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-pinch-zoom {
+ syntax: "*";
+ inherits: false
+ }"
+ `)
+ expect(
+ run([
+ '-touch-pan-x',
+ '-touch-pan-left',
+ '-touch-pan-right',
+ '-touch-pan-y',
+ '-touch-pan-up',
+ '-touch-pan-down',
+ ]),
+ ).toEqual('')
+})
+
+test('touch-pinch-zoom', () => {
+ expect(run(['touch-pinch-zoom'])).toMatchInlineSnapshot(`
+ ".touch-pinch-zoom {
+ --tw-pinch-zoom: pinch-zoom;
+ touch-action: var(--tw-pan-x, ) var(--tw-pan-y, ) var(--tw-pinch-zoom, );
+ }
+
+ @property --tw-pan-x {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-pan-y {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-pinch-zoom {
+ syntax: "*";
+ inherits: false
+ }"
+ `)
+ expect(run(['-touch-pinch-zoom'])).toEqual('')
+})
+
+test('select', () => {
+ expect(run(['select-none', 'select-text', 'select-all', 'select-auto'])).toMatchInlineSnapshot(`
+ ".select-all {
+ -webkit-user-select: all;
+ user-select: all;
+ }
+
+ .select-auto {
+ -webkit-user-select: auto;
+ user-select: auto;
+ }
+
+ .select-none {
+ -webkit-user-select: none;
+ user-select: none;
+ }
+
+ .select-text {
+ -webkit-user-select: text;
+ user-select: text;
+ }"
+ `)
+ expect(run(['-select-none', '-select-text', '-select-all', '-select-auto'])).toEqual('')
+})
+
+test('resize', () => {
+ expect(run(['resize-none', 'resize-both', 'resize-x', 'resize-y'])).toMatchInlineSnapshot(`
+ ".resize-both {
+ resize: both;
+ }
+
+ .resize-none {
+ resize: none;
+ }
+
+ .resize-x {
+ resize: horizontal;
+ }
+
+ .resize-y {
+ resize: vertical;
+ }"
+ `)
+ expect(run(['-resize-none', '-resize-both', '-resize-x', '-resize-y'])).toEqual('')
+})
+
+test('scroll-snap-type', () => {
+ expect(run(['snap-none', 'snap-x', 'snap-y', 'snap-both'])).toMatchInlineSnapshot(`
+ ".snap-both {
+ scroll-snap-type: both var(--tw-scroll-snap-strictness);
+ }
+
+ .snap-none {
+ scroll-snap-type: none;
+ }
+
+ .snap-x {
+ scroll-snap-type: x var(--tw-scroll-snap-strictness);
+ }
+
+ .snap-y {
+ scroll-snap-type: y var(--tw-scroll-snap-strictness);
+ }
+
+ @property --tw-scroll-snap-strictness {
+ syntax: "*";
+ inherits: false;
+ initial-value: proximity;
+ }"
+ `)
+ expect(run(['-snap-none', '-snap-x', '-snap-y', '-snap-both'])).toEqual('')
+})
+
+test('--tw-scroll-snap-strictness', () => {
+ expect(run(['snap-mandatory', 'snap-proximity'])).toMatchInlineSnapshot(`
+ ".snap-mandatory {
+ --tw-scroll-snap-strictness: mandatory;
+ }
+
+ .snap-proximity {
+ --tw-scroll-snap-strictness: proximity;
+ }
+
+ @property --tw-scroll-snap-strictness {
+ syntax: "*";
+ inherits: false;
+ initial-value: proximity;
+ }"
+ `)
+ expect(run(['-snap-mandatory', '-snap-proximity'])).toEqual('')
+})
+
+test('scroll-snap-align', () => {
+ expect(run(['snap-align-none', 'snap-start', 'snap-end', 'snap-center'])).toMatchInlineSnapshot(`
+ ".snap-align-none {
+ scroll-snap-align: none;
+ }
+
+ .snap-center {
+ scroll-snap-align: center;
+ }
+
+ .snap-end {
+ scroll-snap-align: end;
+ }
+
+ .snap-start {
+ scroll-snap-align: start;
+ }"
+ `)
+ expect(run(['-snap-align-none', '-snap-start', '-snap-end', '-snap-center'])).toEqual('')
+})
+
+test('scroll-snap-stop', () => {
+ expect(run(['snap-normal', 'snap-always'])).toMatchInlineSnapshot(`
+ ".snap-always {
+ scroll-snap-stop: always;
+ }
+
+ .snap-normal {
+ scroll-snap-stop: normal;
+ }"
+ `)
+ expect(run(['-snap-normal', '-snap-always'])).toEqual('')
+})
+
+test('scroll-m', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-m-4', 'scroll-m-[4px]', '-scroll-m-4', '-scroll-m-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-m-4 {
+ scroll-margin: -1rem;
+ }
+
+ .-scroll-m-\\[--value\\] {
+ scroll-margin: calc(var(--value) * -1);
+ }
+
+ .scroll-m-4 {
+ scroll-margin: 1rem;
+ }
+
+ .scroll-m-\\[4px\\] {
+ scroll-margin: 4px;
+ }"
+ `)
+ expect(run(['scroll-m'])).toEqual('')
+})
+
+test('scroll-mx', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-mx-4', 'scroll-mx-[4px]', '-scroll-mx-4', '-scroll-mx-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-mx-4 {
+ scroll-margin-left: -1rem;
+ scroll-margin-right: -1rem;
+ }
+
+ .-scroll-mx-\\[--value\\] {
+ scroll-margin-left: calc(var(--value) * -1);
+ scroll-margin-right: calc(var(--value) * -1);
+ }
+
+ .scroll-mx-4 {
+ scroll-margin-left: 1rem;
+ scroll-margin-right: 1rem;
+ }
+
+ .scroll-mx-\\[4px\\] {
+ scroll-margin-left: 4px;
+ scroll-margin-right: 4px;
+ }"
+ `)
+ expect(run(['scroll-mx'])).toEqual('')
+})
+
+test('scroll-my', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-my-4', 'scroll-my-[4px]', '-scroll-my-4', '-scroll-my-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-my-4 {
+ scroll-margin-top: -1rem;
+ scroll-margin-bottom: -1rem;
+ }
+
+ .-scroll-my-\\[--value\\] {
+ scroll-margin-top: calc(var(--value) * -1);
+ scroll-margin-bottom: calc(var(--value) * -1);
+ }
+
+ .scroll-my-4 {
+ scroll-margin-top: 1rem;
+ scroll-margin-bottom: 1rem;
+ }
+
+ .scroll-my-\\[4px\\] {
+ scroll-margin-top: 4px;
+ scroll-margin-bottom: 4px;
+ }"
+ `)
+ expect(run(['scroll-my'])).toEqual('')
+})
+
+test('scroll-ms', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-ms-4', 'scroll-ms-[4px]', '-scroll-ms-4', '-scroll-ms-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-ms-4 {
+ scroll-margin-inline-start: -1rem;
+ }
+
+ .-scroll-ms-\\[--value\\] {
+ scroll-margin-inline-start: calc(var(--value) * -1);
+ }
+
+ .scroll-ms-4 {
+ scroll-margin-inline-start: 1rem;
+ }
+
+ .scroll-ms-\\[4px\\] {
+ scroll-margin-inline-start: 4px;
+ }"
+ `)
+ expect(run(['scroll-ms'])).toEqual('')
+})
+
+test('scroll-me', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-me-4', 'scroll-me-[4px]', '-scroll-me-4', '-scroll-me-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-me-4 {
+ scroll-margin-inline-end: -1rem;
+ }
+
+ .-scroll-me-\\[--value\\] {
+ scroll-margin-inline-end: calc(var(--value) * -1);
+ }
+
+ .scroll-me-4 {
+ scroll-margin-inline-end: 1rem;
+ }
+
+ .scroll-me-\\[4px\\] {
+ scroll-margin-inline-end: 4px;
+ }"
+ `)
+ expect(run(['scroll-me'])).toEqual('')
+})
+
+test('scroll-mt', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-mt-4', 'scroll-mt-[4px]', '-scroll-mt-4', '-scroll-mt-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-mt-4 {
+ scroll-margin-top: -1rem;
+ }
+
+ .-scroll-mt-\\[--value\\] {
+ scroll-margin-top: calc(var(--value) * -1);
+ }
+
+ .scroll-mt-4 {
+ scroll-margin-top: 1rem;
+ }
+
+ .scroll-mt-\\[4px\\] {
+ scroll-margin-top: 4px;
+ }"
+ `)
+ expect(run(['scroll-mt'])).toEqual('')
+})
+
+test('scroll-mr', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-mr-4', 'scroll-mr-[4px]', '-scroll-mr-4', '-scroll-mr-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-mr-4 {
+ scroll-margin-right: -1rem;
+ }
+
+ .-scroll-mr-\\[--value\\] {
+ scroll-margin-right: calc(var(--value) * -1);
+ }
+
+ .scroll-mr-4 {
+ scroll-margin-right: 1rem;
+ }
+
+ .scroll-mr-\\[4px\\] {
+ scroll-margin-right: 4px;
+ }"
+ `)
+ expect(run(['scroll-mr'])).toEqual('')
+})
+
+test('scroll-mb', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-mb-4', 'scroll-mb-[4px]', '-scroll-mb-4', '-scroll-mb-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-mb-4 {
+ scroll-margin-bottom: -1rem;
+ }
+
+ .-scroll-mb-\\[--value\\] {
+ scroll-margin-bottom: calc(var(--value) * -1);
+ }
+
+ .scroll-mb-4 {
+ scroll-margin-bottom: 1rem;
+ }
+
+ .scroll-mb-\\[4px\\] {
+ scroll-margin-bottom: 4px;
+ }"
+ `)
+ expect(run(['scroll-mb'])).toEqual('')
+})
+
+test('scroll-ml', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-ml-4', 'scroll-ml-[4px]', '-scroll-ml-4', '-scroll-ml-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-ml-4 {
+ scroll-margin-left: -1rem;
+ }
+
+ .-scroll-ml-\\[--value\\] {
+ scroll-margin-left: calc(var(--value) * -1);
+ }
+
+ .scroll-ml-4 {
+ scroll-margin-left: 1rem;
+ }
+
+ .scroll-ml-\\[4px\\] {
+ scroll-margin-left: 4px;
+ }"
+ `)
+ expect(run(['scroll-ml'])).toEqual('')
+})
+
+test('scroll-p', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-p-4', 'scroll-p-[4px]', '-scroll-p-4', '-scroll-p-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-p-4 {
+ scroll-padding: -1rem;
+ }
+
+ .-scroll-p-\\[--value\\] {
+ scroll-padding: calc(var(--value) * -1);
+ }
+
+ .scroll-p-4 {
+ scroll-padding: 1rem;
+ }
+
+ .scroll-p-\\[4px\\] {
+ scroll-padding: 4px;
+ }"
+ `)
+ expect(run(['scroll-p'])).toEqual('')
+})
+
+test('scroll-px', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-px-4', 'scroll-px-[4px]', '-scroll-px-4', '-scroll-px-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-px-4 {
+ scroll-padding-left: -1rem;
+ scroll-padding-right: -1rem;
+ }
+
+ .-scroll-px-\\[--value\\] {
+ scroll-padding-left: calc(var(--value) * -1);
+ scroll-padding-right: calc(var(--value) * -1);
+ }
+
+ .scroll-px-4 {
+ scroll-padding-left: 1rem;
+ scroll-padding-right: 1rem;
+ }
+
+ .scroll-px-\\[4px\\] {
+ scroll-padding-left: 4px;
+ scroll-padding-right: 4px;
+ }"
+ `)
+ expect(run(['scroll-px'])).toEqual('')
+})
+
+test('scroll-py', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-py-4', 'scroll-py-[4px]', '-scroll-py-4', '-scroll-py-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-py-4 {
+ scroll-padding-top: -1rem;
+ scroll-padding-bottom: -1rem;
+ }
+
+ .-scroll-py-\\[--value\\] {
+ scroll-padding-top: calc(var(--value) * -1);
+ scroll-padding-bottom: calc(var(--value) * -1);
+ }
+
+ .scroll-py-4 {
+ scroll-padding-top: 1rem;
+ scroll-padding-bottom: 1rem;
+ }
+
+ .scroll-py-\\[4px\\] {
+ scroll-padding-top: 4px;
+ scroll-padding-bottom: 4px;
+ }"
+ `)
+ expect(run(['scroll-py'])).toEqual('')
+})
+
+test('scroll-ps', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-ps-4', 'scroll-ps-[4px]', '-scroll-ps-4', '-scroll-ps-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-ps-4 {
+ scroll-padding-inline-start: -1rem;
+ }
+
+ .-scroll-ps-\\[--value\\] {
+ scroll-padding-inline-start: calc(var(--value) * -1);
+ }
+
+ .scroll-ps-4 {
+ scroll-padding-inline-start: 1rem;
+ }
+
+ .scroll-ps-\\[4px\\] {
+ scroll-padding-inline-start: 4px;
+ }"
+ `)
+ expect(run(['scroll-ps'])).toEqual('')
+})
+
+test('scroll-pe', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-pe-4', 'scroll-pe-[4px]', '-scroll-pe-4', '-scroll-pe-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-pe-4 {
+ scroll-padding-inline-end: -1rem;
+ }
+
+ .-scroll-pe-\\[--value\\] {
+ scroll-padding-inline-end: calc(var(--value) * -1);
+ }
+
+ .scroll-pe-4 {
+ scroll-padding-inline-end: 1rem;
+ }
+
+ .scroll-pe-\\[4px\\] {
+ scroll-padding-inline-end: 4px;
+ }"
+ `)
+ expect(run(['scroll-pe'])).toEqual('')
+})
+
+test('scroll-pt', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-pt-4', 'scroll-pt-[4px]', '-scroll-pt-4', '-scroll-pt-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-pt-4 {
+ scroll-padding-top: -1rem;
+ }
+
+ .-scroll-pt-\\[--value\\] {
+ scroll-padding-top: calc(var(--value) * -1);
+ }
+
+ .scroll-pt-4 {
+ scroll-padding-top: 1rem;
+ }
+
+ .scroll-pt-\\[4px\\] {
+ scroll-padding-top: 4px;
+ }"
+ `)
+ expect(run(['scroll-pt'])).toEqual('')
+})
+
+test('scroll-pr', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-pr-4', 'scroll-pr-[4px]', '-scroll-pr-4', '-scroll-pr-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-pr-4 {
+ scroll-padding-right: -1rem;
+ }
+
+ .-scroll-pr-\\[--value\\] {
+ scroll-padding-right: calc(var(--value) * -1);
+ }
+
+ .scroll-pr-4 {
+ scroll-padding-right: 1rem;
+ }
+
+ .scroll-pr-\\[4px\\] {
+ scroll-padding-right: 4px;
+ }"
+ `)
+ expect(run(['scroll-pr'])).toEqual('')
+})
+
+test('scroll-pb', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-pb-4', 'scroll-pb-[4px]', '-scroll-pb-4', '-scroll-pb-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-pb-4 {
+ scroll-padding-bottom: -1rem;
+ }
+
+ .-scroll-pb-\\[--value\\] {
+ scroll-padding-bottom: calc(var(--value) * -1);
+ }
+
+ .scroll-pb-4 {
+ scroll-padding-bottom: 1rem;
+ }
+
+ .scroll-pb-\\[4px\\] {
+ scroll-padding-bottom: 4px;
+ }"
+ `)
+ expect(run(['scroll-pb'])).toEqual('')
+})
+
+test('scroll-pl', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['scroll-pl-4', 'scroll-pl-[4px]', '-scroll-pl-4', '-scroll-pl-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .-scroll-pl-4 {
+ scroll-padding-left: -1rem;
+ }
+
+ .-scroll-pl-\\[--value\\] {
+ scroll-padding-left: calc(var(--value) * -1);
+ }
+
+ .scroll-pl-4 {
+ scroll-padding-left: 1rem;
+ }
+
+ .scroll-pl-\\[4px\\] {
+ scroll-padding-left: 4px;
+ }"
+ `)
+ expect(run(['scroll-pl'])).toEqual('')
+})
+
+test('list-style-position', () => {
+ expect(run(['list-inside', 'list-outside'])).toMatchInlineSnapshot(`
+ ".list-inside {
+ list-style-position: inside;
+ }
+
+ .list-outside {
+ list-style-position: outside;
+ }"
+ `)
+ expect(run(['-list-inside', '-list-outside'])).toEqual('')
+})
+
+test('list', () => {
+ expect(run(['list-none', 'list-disc', 'list-decimal', 'list-[--value]'])).toMatchInlineSnapshot(`
+ ".list-\\[--value\\] {
+ list-style-type: var(--value);
+ }
+
+ .list-decimal {
+ list-style-type: decimal;
+ }
+
+ .list-disc {
+ list-style-type: disc;
+ }
+
+ .list-none {
+ list-style-type: none;
+ }"
+ `)
+ expect(run(['-list-none', '-list-disc', '-list-decimal', '-list-[--value]'])).toEqual('')
+})
+
+test('list-image', () => {
+ expect(run(['list-image-none', 'list-image-[--value]'])).toMatchInlineSnapshot(`
+ ".list-image-\\[--value\\] {
+ list-style-image: var(--value);
+ }
+
+ .list-image-none {
+ list-style-image: none;
+ }"
+ `)
+ expect(run(['list-image', '-list-image-none', '-list-image-[--value]'])).toEqual('')
+})
+
+test('appearance', () => {
+ expect(run(['appearance-none', 'appearance-auto'])).toMatchInlineSnapshot(`
+ ".appearance-auto {
+ appearance: auto;
+ }
+
+ .appearance-none {
+ appearance: none;
+ }"
+ `)
+ expect(run(['appearance', '-appearance-none', '-appearance-auto'])).toEqual('')
+})
+
+test('columns', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --width-3xs: 16rem;
+ --width-7xl: 80rem;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'columns-auto',
+ 'columns-3xs',
+ 'columns-7xl',
+ 'columns-4',
+ 'columns-99',
+ 'columns-[123]',
+ 'columns-[--value]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --width-3xs: 16rem;
+ --width-7xl: 80rem;
+ }
+
+ .columns-3xs {
+ columns: 16rem;
+ }
+
+ .columns-4 {
+ columns: 4;
+ }
+
+ .columns-7xl {
+ columns: 80rem;
+ }
+
+ .columns-99 {
+ columns: 99;
+ }
+
+ .columns-\\[--value\\] {
+ columns: var(--value);
+ }
+
+ .columns-\\[123\\] {
+ columns: 123;
+ }
+
+ .columns-auto {
+ columns: auto;
+ }"
+ `)
+ expect(run(['columns', '-columns-4', '-columns-[123]', '-columns-[--value]'])).toEqual('')
+})
+
+test('break-before', () => {
+ expect(
+ run([
+ 'break-before-auto',
+ 'break-before-avoid',
+ 'break-before-all',
+ 'break-before-avoid-page',
+ 'break-before-page',
+ 'break-before-left',
+ 'break-before-right',
+ 'break-before-column',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".break-before-all {
+ break-before: all;
+ }
+
+ .break-before-auto {
+ break-before: auto;
+ }
+
+ .break-before-avoid {
+ break-before: avoid;
+ }
+
+ .break-before-avoid-page {
+ break-before: avoid-page;
+ }
+
+ .break-before-column {
+ break-before: column;
+ }
+
+ .break-before-left {
+ break-before: left;
+ }
+
+ .break-before-page {
+ break-before: page;
+ }
+
+ .break-before-right {
+ break-before: right;
+ }"
+ `)
+ expect(
+ run([
+ 'break-before',
+ '-break-before-auto',
+ '-break-before-avoid',
+ '-break-before-all',
+ '-break-before-avoid-page',
+ '-break-before-page',
+ '-break-before-left',
+ '-break-before-right',
+ '-break-before-column',
+ ]),
+ ).toEqual('')
+})
+
+test('break-inside', () => {
+ expect(
+ run([
+ 'break-inside-auto',
+ 'break-inside-avoid',
+ 'break-inside-avoid-page',
+ 'break-inside-avoid-column',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".break-inside-auto {
+ break-inside: auto;
+ }
+
+ .break-inside-avoid {
+ break-inside: avoid;
+ }
+
+ .break-inside-avoid-column {
+ break-inside: avoid-column;
+ }
+
+ .break-inside-avoid-page {
+ break-inside: avoid-page;
+ }"
+ `)
+ expect(
+ run([
+ 'break-inside',
+ '-break-inside-auto',
+ '-break-inside-avoid',
+ '-break-inside-avoid-page',
+ '-break-inside-avoid-column',
+ ]),
+ ).toEqual('')
+})
+
+test('break-after', () => {
+ expect(
+ run([
+ 'break-after-auto',
+ 'break-after-avoid',
+ 'break-after-all',
+ 'break-after-avoid-page',
+ 'break-after-page',
+ 'break-after-left',
+ 'break-after-right',
+ 'break-after-column',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".break-after-all {
+ break-after: all;
+ }
+
+ .break-after-auto {
+ break-after: auto;
+ }
+
+ .break-after-avoid {
+ break-after: avoid;
+ }
+
+ .break-after-avoid-page {
+ break-after: avoid-page;
+ }
+
+ .break-after-column {
+ break-after: column;
+ }
+
+ .break-after-left {
+ break-after: left;
+ }
+
+ .break-after-page {
+ break-after: page;
+ }
+
+ .break-after-right {
+ break-after: right;
+ }"
+ `)
+ expect(
+ run([
+ 'break-after',
+ '-break-after-auto',
+ '-break-after-avoid',
+ '-break-after-all',
+ '-break-after-avoid-page',
+ '-break-after-page',
+ '-break-after-left',
+ '-break-after-right',
+ '-break-after-column',
+ ]),
+ ).toEqual('')
+})
+
+test('auto-cols', () => {
+ expect(
+ run(['auto-cols-auto', 'auto-cols-min', 'auto-cols-max', 'auto-cols-fr', 'auto-cols-[2fr]']),
+ ).toMatchInlineSnapshot(`
+ ".auto-cols-\\[2fr\\] {
+ grid-auto-columns: 2fr;
+ }
+
+ .auto-cols-auto {
+ grid-auto-columns: auto;
+ }
+
+ .auto-cols-fr {
+ grid-auto-columns: minmax(0, 1fr);
+ }
+
+ .auto-cols-max {
+ grid-auto-columns: max-content;
+ }
+
+ .auto-cols-min {
+ grid-auto-columns: min-content;
+ }"
+ `)
+ expect(run(['auto-cols', '-auto-cols-auto', '-auto-cols-[2fr]'])).toEqual('')
+})
+
+test('grid-flow', () => {
+ expect(
+ run([
+ 'grid-flow-row',
+ 'grid-flow-col',
+ 'grid-flow-dense',
+ 'grid-flow-row-dense',
+ 'grid-flow-col-dense',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".grid-flow-col {
+ grid-auto-flow: column;
+ }
+
+ .grid-flow-col-dense {
+ grid-auto-flow: column dense;
+ }
+
+ .grid-flow-dense {
+ grid-auto-flow: dense;
+ }
+
+ .grid-flow-row {
+ grid-auto-flow: row;
+ }
+
+ .grid-flow-row-dense {
+ grid-auto-flow: row dense;
+ }"
+ `)
+ expect(
+ run([
+ 'grid-flow',
+ '-grid-flow-row',
+ '-grid-flow-col',
+ '-grid-flow-dense',
+ '-grid-flow-row-dense',
+ '-grid-flow-col-dense',
+ ]),
+ ).toEqual('')
+})
+
+test('auto-rows', () => {
+ expect(
+ run(['auto-rows-auto', 'auto-rows-min', 'auto-rows-max', 'auto-rows-fr', 'auto-rows-[2fr]']),
+ ).toMatchInlineSnapshot(`
+ ".auto-rows-\\[2fr\\] {
+ grid-auto-rows: 2fr;
+ }
+
+ .auto-rows-auto {
+ grid-auto-rows: auto;
+ }
+
+ .auto-rows-fr {
+ grid-auto-rows: minmax(0, 1fr);
+ }
+
+ .auto-rows-max {
+ grid-auto-rows: max-content;
+ }
+
+ .auto-rows-min {
+ grid-auto-rows: min-content;
+ }"
+ `)
+ expect(run(['auto-rows', '-auto-rows-auto', '-auto-rows-[2fr]'])).toEqual('')
+})
+
+test('grid-cols', () => {
+ expect(
+ run(['grid-cols-none', 'grid-cols-subgrid', 'grid-cols-12', 'grid-cols-99', 'grid-cols-[123]']),
+ ).toMatchInlineSnapshot(`
+ ".grid-cols-12 {
+ grid-template-columns: repeat(12, minmax(0, 1fr));
+ }
+
+ .grid-cols-99 {
+ grid-template-columns: repeat(99, minmax(0, 1fr));
+ }
+
+ .grid-cols-\\[123\\] {
+ grid-template-columns: 123px;
+ }
+
+ .grid-cols-none {
+ grid-template-columns: none;
+ }
+
+ .grid-cols-subgrid {
+ grid-template-columns: subgrid;
+ }"
+ `)
+ expect(
+ run([
+ 'grid-cols',
+ '-grid-cols-none',
+ '-grid-cols-subgrid',
+ '-grid-cols-12',
+ '-grid-cols-[123]',
+ ]),
+ ).toEqual('')
+})
+
+test('grid-rows', () => {
+ expect(
+ run(['grid-rows-none', 'grid-rows-subgrid', 'grid-rows-12', 'grid-rows-99', 'grid-rows-[123]']),
+ ).toMatchInlineSnapshot(`
+ ".grid-rows-12 {
+ grid-template-rows: repeat(12, minmax(0, 1fr));
+ }
+
+ .grid-rows-99 {
+ grid-template-rows: repeat(99, minmax(0, 1fr));
+ }
+
+ .grid-rows-\\[123\\] {
+ grid-template-rows: 123px;
+ }
+
+ .grid-rows-none {
+ grid-template-rows: none;
+ }
+
+ .grid-rows-subgrid {
+ grid-template-rows: subgrid;
+ }"
+ `)
+ expect(
+ run([
+ 'grid-rows',
+ '-grid-rows-none',
+ '-grid-rows-subgrid',
+ '-grid-rows-12',
+ '-grid-rows-[123]',
+ ]),
+ ).toEqual('')
+})
+
+test('flex-direction', () => {
+ expect(run(['flex-row', 'flex-row-reverse', 'flex-col', 'flex-col-reverse']))
+ .toMatchInlineSnapshot(`
+ ".flex-col {
+ flex-direction: column;
+ }
+
+ .flex-col-reverse {
+ flex-direction: column-reverse;
+ }
+
+ .flex-row {
+ flex-direction: row;
+ }
+
+ .flex-row-reverse {
+ flex-direction: row-reverse;
+ }"
+ `)
+ expect(run(['-flex-row', '-flex-row-reverse', '-flex-col', '-flex-col-reverse'])).toEqual('')
+})
+
+test('flex-wrap', () => {
+ expect(run(['flex-wrap', 'flex-wrap-reverse', 'flex-nowrap'])).toMatchInlineSnapshot(`
+ ".flex-nowrap {
+ flex-wrap: nowrap;
+ }
+
+ .flex-wrap {
+ flex-wrap: wrap;
+ }
+
+ .flex-wrap-reverse {
+ flex-wrap: wrap-reverse;
+ }"
+ `)
+ expect(run(['-flex-wrap', '-flex-wrap-reverse', '-flex-nowrap'])).toEqual('')
+})
+
+test('place-content', () => {
+ expect(
+ run([
+ 'place-content-center',
+ 'place-content-start',
+ 'place-content-end',
+ 'place-content-between',
+ 'place-content-around',
+ 'place-content-evenly',
+ 'place-content-baseline',
+ 'place-content-stretch',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".place-content-around {
+ place-content: around;
+ }
+
+ .place-content-baseline {
+ place-content: baseline start;
+ }
+
+ .place-content-between {
+ place-content: between;
+ }
+
+ .place-content-center {
+ place-content: center;
+ }
+
+ .place-content-end {
+ place-content: end;
+ }
+
+ .place-content-evenly {
+ place-content: evenly;
+ }
+
+ .place-content-start {
+ place-content: start;
+ }
+
+ .place-content-stretch {
+ place-content: stretch;
+ }"
+ `)
+ expect(
+ run([
+ 'place-content',
+ '-place-content-center',
+ '-place-content-start',
+ '-place-content-end',
+ '-place-content-between',
+ '-place-content-around',
+ '-place-content-evenly',
+ '-place-content-baseline',
+ '-place-content-stretch',
+ ]),
+ ).toEqual('')
+})
+
+test('place-items', () => {
+ expect(
+ run([
+ 'place-items-start',
+ 'place-items-end',
+ 'place-items-center',
+ 'place-items-baseline',
+ 'place-items-stretch',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".place-items-baseline {
+ place-items: baseline;
+ }
+
+ .place-items-center {
+ place-items: center;
+ }
+
+ .place-items-end {
+ place-items: end;
+ }
+
+ .place-items-start {
+ place-items: start;
+ }
+
+ .place-items-stretch {
+ place-items: stretch stretch;
+ }"
+ `)
+ expect(
+ run([
+ 'place-items',
+ '-place-items-start',
+ '-place-items-end',
+ '-place-items-center',
+ '-place-items-baseline',
+ '-place-items-stretch',
+ ]),
+ ).toEqual('')
+})
+
+test('align-content', () => {
+ expect(
+ run([
+ 'content-normal',
+ 'content-center',
+ 'content-start',
+ 'content-end',
+ 'content-between',
+ 'content-around',
+ 'content-evenly',
+ 'content-baseline',
+ 'content-stretch',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".content-around {
+ align-content: space-around;
+ }
+
+ .content-baseline {
+ align-content: baseline;
+ }
+
+ .content-between {
+ align-content: space-between;
+ }
+
+ .content-center {
+ align-content: center;
+ }
+
+ .content-end {
+ align-content: flex-end;
+ }
+
+ .content-evenly {
+ align-content: space-evenly;
+ }
+
+ .content-normal {
+ align-content: normal;
+ }
+
+ .content-start {
+ align-content: flex-start;
+ }
+
+ .content-stretch {
+ align-content: stretch;
+ }"
+ `)
+ expect(
+ run([
+ 'content',
+ '-content-normal',
+ '-content-center',
+ '-content-start',
+ '-content-end',
+ '-content-between',
+ '-content-around',
+ '-content-evenly',
+ '-content-baseline',
+ '-content-stretch',
+ ]),
+ ).toEqual('')
+})
+
+test('items', () => {
+ expect(run(['items-start', 'items-end', 'items-center', 'items-baseline', 'items-stretch']))
+ .toMatchInlineSnapshot(`
+ ".items-baseline {
+ align-items: baseline;
+ }
+
+ .items-center {
+ align-items: center;
+ }
+
+ .items-end {
+ align-items: flex-end;
+ }
+
+ .items-start {
+ align-items: flex-start;
+ }
+
+ .items-stretch {
+ align-items: stretch;
+ }"
+ `)
+ expect(
+ run([
+ 'items',
+ '-items-start',
+ '-items-end',
+ '-items-center',
+ '-items-baseline',
+ '-items-stretch',
+ ]),
+ ).toEqual('')
+})
+
+test('justify', () => {
+ expect(
+ run([
+ 'justify-normal',
+ 'justify-start',
+ 'justify-end',
+ 'justify-center',
+ 'justify-between',
+ 'justify-around',
+ 'justify-evenly',
+ 'justify-stretch',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".justify-around {
+ justify-content: space-around;
+ }
+
+ .justify-between {
+ justify-content: space-between;
+ }
+
+ .justify-center {
+ justify-content: center;
+ }
+
+ .justify-end {
+ justify-content: flex-end;
+ }
+
+ .justify-evenly {
+ justify-content: space-evenly;
+ }
+
+ .justify-normal {
+ justify-content: normal;
+ }
+
+ .justify-start {
+ justify-content: flex-start;
+ }
+
+ .justify-stretch {
+ justify-content: stretch;
+ }"
+ `)
+ expect(
+ run([
+ 'justify',
+ '-justify-normal',
+ '-justify-start',
+ '-justify-end',
+ '-justify-center',
+ '-justify-between',
+ '-justify-around',
+ '-justify-evenly',
+ '-justify-stretch',
+ ]),
+ ).toEqual('')
+})
+
+test('justify-items', () => {
+ expect(
+ run([
+ 'justify-items-start',
+ 'justify-items-end',
+ 'justify-items-center',
+ 'justify-items-stretch',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".justify-items-center {
+ justify-items: center;
+ }
+
+ .justify-items-end {
+ justify-items: end;
+ }
+
+ .justify-items-start {
+ justify-items: start;
+ }
+
+ .justify-items-stretch {
+ justify-items: stretch;
+ }"
+ `)
+ expect(
+ run([
+ 'justify-items',
+ '-justify-items-start',
+ '-justify-items-end',
+ '-justify-items-center',
+ '-justify-items-stretch',
+ ]),
+ ).toEqual('')
+})
+
+test('gap', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['gap-4', 'gap-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .gap-4 {
+ gap: 1rem;
+ }
+
+ .gap-\\[4px\\] {
+ gap: 4px;
+ }"
+ `)
+ expect(run(['gap', '-gap-4', '-gap-[4px]'])).toEqual('')
+})
+
+test('gap-x', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['gap-x-4', 'gap-x-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .gap-x-4 {
+ column-gap: 1rem;
+ }
+
+ .gap-x-\\[4px\\] {
+ column-gap: 4px;
+ }"
+ `)
+ expect(run(['gap-x', '-gap-x-4', '-gap-x-[4px]'])).toEqual('')
+})
+
+test('gap-y', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['gap-y-4', 'gap-y-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .gap-y-4 {
+ row-gap: 1rem;
+ }
+
+ .gap-y-\\[4px\\] {
+ row-gap: 4px;
+ }"
+ `)
+ expect(run(['gap-y', '-gap-y-4', '-gap-y-[4px]'])).toEqual('')
+})
+
+test('space-x', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['space-x-4', 'space-x-[4px]', '-space-x-4'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ :where(.-space-x-4 > :not([hidden]) ~ :not([hidden])) {
+ margin-inline-start: calc(calc(1rem * -1) * calc(1 - var(--tw-space-x-reverse)));
+ margin-inline-end: calc(calc(1rem * -1) * var(--tw-space-x-reverse));
+ }
+
+ :where(.space-x-4 > :not([hidden]) ~ :not([hidden])) {
+ margin-inline-start: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
+ margin-inline-end: calc(1rem * var(--tw-space-x-reverse));
+ }
+
+ :where(.space-x-\\[4px\\] > :not([hidden]) ~ :not([hidden])) {
+ margin-inline-start: calc(4px * calc(1 - var(--tw-space-x-reverse)));
+ margin-inline-end: calc(4px * var(--tw-space-x-reverse));
+ }
+
+ @property --tw-space-x-reverse {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }"
+ `)
+ expect(run(['space-x'])).toEqual('')
+})
+
+test('space-y', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['space-y-4', 'space-y-[4px]', '-space-y-4'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ :where(.-space-y-4 > :not([hidden]) ~ :not([hidden])) {
+ margin-bottom: calc(calc(1rem * -1) * var(--tw-space-y-reverse));
+ margin-top: calc(calc(1rem * -1) * calc(1 - var(--tw-space-y-reverse)));
+ }
+
+ :where(.space-y-4 > :not([hidden]) ~ :not([hidden])) {
+ margin-bottom: calc(1rem * var(--tw-space-y-reverse));
+ margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
+ }
+
+ :where(.space-y-\\[4px\\] > :not([hidden]) ~ :not([hidden])) {
+ margin-bottom: calc(4px * var(--tw-space-y-reverse));
+ margin-top: calc(4px * calc(1 - var(--tw-space-y-reverse)));
+ }
+
+ @property --tw-space-y-reverse {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }"
+ `)
+ expect(run(['space-y'])).toEqual('')
+})
+
+test('space-x-reverse', () => {
+ expect(run(['space-x-reverse'])).toMatchInlineSnapshot(`
+ ":where(.space-x-reverse > :not([hidden]) ~ :not([hidden])) {
+ --tw-space-x-reverse: 1;
+ }
+
+ @property --tw-space-x-reverse {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }"
+ `)
+ expect(run(['-space-x-reverse'])).toEqual('')
+})
+
+test('space-y-reverse', () => {
+ expect(run(['space-y-reverse'])).toMatchInlineSnapshot(`
+ ":where(.space-y-reverse > :not([hidden]) ~ :not([hidden])) {
+ --tw-space-y-reverse: 1;
+ }
+
+ @property --tw-space-y-reverse {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }"
+ `)
+ expect(run(['-space-y-reverse'])).toEqual('')
+})
+
+test('divide-x', () => {
+ expect(
+ compileCss(
+ css`
+ @tailwind utilities;
+ `,
+ ['divide-x', 'divide-x-4', 'divide-x-123', 'divide-x-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":where(.divide-x > :not([hidden]) ~ :not([hidden])) {
+ border-style: var(--tw-border-style);
+ border-inline-end-width: calc(1px * var(--tw-divide-x-reverse));
+ border-inline-start-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
+ }
+
+ :where(.divide-x-123 > :not([hidden]) ~ :not([hidden])) {
+ border-style: var(--tw-border-style);
+ border-inline-end-width: calc(123px * var(--tw-divide-x-reverse));
+ border-inline-start-width: calc(123px * calc(1 - var(--tw-divide-x-reverse)));
+ }
+
+ :where(.divide-x-4 > :not([hidden]) ~ :not([hidden])) {
+ border-style: var(--tw-border-style);
+ border-inline-end-width: calc(4px * var(--tw-divide-x-reverse));
+ border-inline-start-width: calc(4px * calc(1 - var(--tw-divide-x-reverse)));
+ }
+
+ :where(.divide-x-\\[4px\\] > :not([hidden]) ~ :not([hidden])) {
+ border-style: var(--tw-border-style);
+ border-inline-end-width: calc(4px * var(--tw-divide-x-reverse));
+ border-inline-start-width: calc(4px * calc(1 - var(--tw-divide-x-reverse)));
+ }
+
+ @property --tw-divide-x-reverse {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }
+
+ @property --tw-border-style {
+ syntax: "";
+ inherits: false;
+ initial-value: solid;
+ }"
+ `)
+ expect(run(['-divide-x', '-divide-x-4', '-divide-x-123'])).toEqual('')
+})
+
+test('divide-x with custom default border width', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --default-border-width: 2px;
+ }
+ @tailwind utilities;
+ `,
+ ['divide-x'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --default-border-width: 2px;
+ }
+
+ :where(.divide-x > :not([hidden]) ~ :not([hidden])) {
+ border-style: var(--tw-border-style);
+ border-inline-end-width: calc(2px * var(--tw-divide-x-reverse));
+ border-inline-start-width: calc(2px * calc(1 - var(--tw-divide-x-reverse)));
+ }
+
+ @property --tw-divide-x-reverse {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }
+
+ @property --tw-border-style {
+ syntax: "";
+ inherits: false;
+ initial-value: solid;
+ }"
+ `)
+})
+
+test('divide-y', () => {
+ expect(
+ compileCss(
+ css`
+ @tailwind utilities;
+ `,
+ ['divide-y', 'divide-y-4', 'divide-y-123', 'divide-y-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":where(.divide-y > :not([hidden]) ~ :not([hidden])) {
+ border-style: var(--tw-border-style);
+ border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
+ border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
+ }
+
+ :where(.divide-y-123 > :not([hidden]) ~ :not([hidden])) {
+ border-style: var(--tw-border-style);
+ border-bottom-width: calc(123px * var(--tw-divide-y-reverse));
+ border-top-width: calc(123px * calc(1 - var(--tw-divide-y-reverse)));
+ }
+
+ :where(.divide-y-4 > :not([hidden]) ~ :not([hidden])) {
+ border-style: var(--tw-border-style);
+ border-bottom-width: calc(4px * var(--tw-divide-y-reverse));
+ border-top-width: calc(4px * calc(1 - var(--tw-divide-y-reverse)));
+ }
+
+ :where(.divide-y-\\[4px\\] > :not([hidden]) ~ :not([hidden])) {
+ border-style: var(--tw-border-style);
+ border-bottom-width: calc(4px * var(--tw-divide-y-reverse));
+ border-top-width: calc(4px * calc(1 - var(--tw-divide-y-reverse)));
+ }
+
+ @property --tw-divide-y-reverse {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }
+
+ @property --tw-border-style {
+ syntax: "";
+ inherits: false;
+ initial-value: solid;
+ }"
+ `)
+ expect(run(['-divide-y', '-divide-y-4', '-divide-y-123'])).toEqual('')
+})
+
+test('divide-y with custom default border width', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --default-border-width: 2px;
+ }
+ @tailwind utilities;
+ `,
+ ['divide-y'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --default-border-width: 2px;
+ }
+
+ :where(.divide-y > :not([hidden]) ~ :not([hidden])) {
+ border-style: var(--tw-border-style);
+ border-bottom-width: calc(2px * var(--tw-divide-y-reverse));
+ border-top-width: calc(2px * calc(1 - var(--tw-divide-y-reverse)));
+ }
+
+ @property --tw-divide-y-reverse {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }
+
+ @property --tw-border-style {
+ syntax: "";
+ inherits: false;
+ initial-value: solid;
+ }"
+ `)
+})
+
+test('divide-x-reverse', () => {
+ expect(run(['divide-x-reverse'])).toMatchInlineSnapshot(`
+ ":where(.divide-x-reverse > :not([hidden]) ~ :not([hidden])) {
+ --tw-divide-x-reverse: 1;
+ }
+
+ @property --tw-divide-x-reverse {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }"
+ `)
+ expect(run(['-divide-x-reverse'])).toEqual('')
+})
+
+test('divide-y-reverse', () => {
+ expect(run(['divide-y-reverse'])).toMatchInlineSnapshot(`
+ ":where(.divide-y-reverse > :not([hidden]) ~ :not([hidden])) {
+ --tw-divide-y-reverse: 1;
+ }
+
+ @property --tw-divide-y-reverse {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }"
+ `)
+ expect(run(['-divide-y-reverse'])).toEqual('')
+})
+
+test('divide-style', () => {
+ expect(run(['divide-solid', 'divide-dashed', 'divide-dotted', 'divide-double', 'divide-none']))
+ .toMatchInlineSnapshot(`
+ ":where(.divide-dashed > :not([hidden]) ~ :not([hidden])) {
+ --tw-border-style: dashed;
+ border-style: dashed;
+ }
+
+ :where(.divide-dotted > :not([hidden]) ~ :not([hidden])) {
+ --tw-border-style: dotted;
+ border-style: dotted;
+ }
+
+ :where(.divide-double > :not([hidden]) ~ :not([hidden])) {
+ --tw-border-style: double;
+ border-style: double;
+ }
+
+ :where(.divide-none > :not([hidden]) ~ :not([hidden])) {
+ --tw-border-style: none;
+ border-style: none;
+ }
+
+ :where(.divide-solid > :not([hidden]) ~ :not([hidden])) {
+ --tw-border-style: solid;
+ border-style: solid;
+ }"
+ `)
+ expect(
+ run([
+ 'divide',
+ '-divide-solid',
+ '-divide-dashed',
+ '-divide-dotted',
+ '-divide-double',
+ '-divide-none',
+ ]),
+ ).toEqual('')
+})
+
+test('accent', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'accent-red-500',
+ 'accent-red-500/50',
+ 'accent-red-500/[0.5]',
+ 'accent-red-500/[50%]',
+ 'accent-current',
+ 'accent-current/50',
+ 'accent-current/[0.5]',
+ 'accent-current/[50%]',
+ 'accent-transparent',
+ 'accent-[#0088cc]',
+ 'accent-[#0088cc]/50',
+ 'accent-[#0088cc]/[0.5]',
+ 'accent-[#0088cc]/[50%]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ }
+
+ .accent-\\[\\#0088cc\\] {
+ accent-color: #08c;
+ }
+
+ .accent-\\[\\#0088cc\\]\\/50, .accent-\\[\\#0088cc\\]\\/\\[0\\.5\\], .accent-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ accent-color: #0088cc80;
+ }
+
+ .accent-current {
+ accent-color: currentColor;
+ }
+
+ .accent-current\\/50, .accent-current\\/\\[0\\.5\\], .accent-current\\/\\[50\\%\\] {
+ accent-color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ .accent-red-500 {
+ accent-color: #ef4444;
+ }
+
+ .accent-red-500\\/50, .accent-red-500\\/\\[0\\.5\\], .accent-red-500\\/\\[50\\%\\] {
+ accent-color: #ef444480;
+ }
+
+ .accent-transparent {
+ accent-color: #0000;
+ }"
+ `)
+ expect(
+ run([
+ 'accent',
+ '-accent-red-500',
+ '-accent-red-500/50',
+ '-accent-red-500/[0.5]',
+ '-accent-red-500/[50%]',
+ '-accent-current',
+ '-accent-current/50',
+ '-accent-current/[0.5]',
+ '-accent-current/[50%]',
+ '-accent-transparent',
+ '-accent-[#0088cc]',
+ '-accent-[#0088cc]/50',
+ '-accent-[#0088cc]/[0.5]',
+ '-accent-[#0088cc]/[50%]',
+ ]),
+ ).toEqual('')
+})
+
+test('caret', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'caret-red-500',
+ 'caret-red-500/50',
+ 'caret-red-500/[0.5]',
+ 'caret-red-500/[50%]',
+ 'caret-current',
+ 'caret-current/50',
+ 'caret-current/[0.5]',
+ 'caret-current/[50%]',
+ 'caret-transparent',
+ 'caret-[#0088cc]',
+ 'caret-[#0088cc]/50',
+ 'caret-[#0088cc]/[0.5]',
+ 'caret-[#0088cc]/[50%]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ }
+
+ .caret-\\[\\#0088cc\\] {
+ caret-color: #08c;
+ }
+
+ .caret-\\[\\#0088cc\\]\\/50, .caret-\\[\\#0088cc\\]\\/\\[0\\.5\\], .caret-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ caret-color: #0088cc80;
+ }
+
+ .caret-current {
+ caret-color: currentColor;
+ }
+
+ .caret-current\\/50, .caret-current\\/\\[0\\.5\\], .caret-current\\/\\[50\\%\\] {
+ caret-color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ .caret-red-500 {
+ caret-color: #ef4444;
+ }
+
+ .caret-red-500\\/50, .caret-red-500\\/\\[0\\.5\\], .caret-red-500\\/\\[50\\%\\] {
+ caret-color: #ef444480;
+ }
+
+ .caret-transparent {
+ caret-color: #0000;
+ }"
+ `)
+ expect(
+ run([
+ 'caret',
+ '-caret-red-500',
+ '-caret-red-500/50',
+ '-caret-red-500/[0.5]',
+ '-caret-red-500/[50%]',
+ '-caret-current',
+ '-caret-current/50',
+ '-caret-current/[0.5]',
+ '-caret-current/[50%]',
+ '-caret-transparent',
+ '-caret-[#0088cc]',
+ '-caret-[#0088cc]/50',
+ '-caret-[#0088cc]/[0.5]',
+ '-caret-[#0088cc]/[50%]',
+ ]),
+ ).toEqual('')
+})
+
+test('divide-color', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'divide-red-500',
+ 'divide-red-500/50',
+ 'divide-red-500/[0.5]',
+ 'divide-red-500/[50%]',
+ 'divide-current',
+ 'divide-current/50',
+ 'divide-current/[0.5]',
+ 'divide-current/[50%]',
+ 'divide-transparent',
+ 'divide-[#0088cc]',
+ 'divide-[#0088cc]/50',
+ 'divide-[#0088cc]/[0.5]',
+ 'divide-[#0088cc]/[50%]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ }
+
+ :where(.divide-\\[\\#0088cc\\] > :not([hidden]) ~ :not([hidden])) {
+ border-color: #08c;
+ }
+
+ :where(.divide-\\[\\#0088cc\\]\\/50 > :not([hidden]) ~ :not([hidden])) {
+ border-color: #0088cc80;
+ }
+
+ :where(.divide-\\[\\#0088cc\\]\\/\\[0\\.5\\] > :not([hidden]) ~ :not([hidden])) {
+ border-color: #0088cc80;
+ }
+
+ :where(.divide-\\[\\#0088cc\\]\\/\\[50\\%\\] > :not([hidden]) ~ :not([hidden])) {
+ border-color: #0088cc80;
+ }
+
+ :where(.divide-current > :not([hidden]) ~ :not([hidden])) {
+ border-color: currentColor;
+ }
+
+ :where(.divide-current\\/50 > :not([hidden]) ~ :not([hidden])) {
+ border-color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ :where(.divide-current\\/\\[0\\.5\\] > :not([hidden]) ~ :not([hidden])) {
+ border-color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ :where(.divide-current\\/\\[50\\%\\] > :not([hidden]) ~ :not([hidden])) {
+ border-color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ :where(.divide-red-500 > :not([hidden]) ~ :not([hidden])) {
+ border-color: #ef4444;
+ }
+
+ :where(.divide-red-500\\/50 > :not([hidden]) ~ :not([hidden])) {
+ border-color: #ef444480;
+ }
+
+ :where(.divide-red-500\\/\\[0\\.5\\] > :not([hidden]) ~ :not([hidden])) {
+ border-color: #ef444480;
+ }
+
+ :where(.divide-red-500\\/\\[50\\%\\] > :not([hidden]) ~ :not([hidden])) {
+ border-color: #ef444480;
+ }
+
+ :where(.divide-transparent > :not([hidden]) ~ :not([hidden])) {
+ border-color: #0000;
+ }"
+ `)
+ expect(
+ run([
+ 'divide',
+ '-divide-red-500',
+ '-divide-red-500/50',
+ '-divide-red-500/[0.5]',
+ '-divide-red-500/[50%]',
+ '-divide-current',
+ '-divide-current/50',
+ '-divide-current/[0.5]',
+ '-divide-current/[50%]',
+ '-divide-transparent',
+ '-divide-[#0088cc]',
+ '-divide-[#0088cc]/50',
+ '-divide-[#0088cc]/[0.5]',
+ '-divide-[#0088cc]/[50%]',
+ ]),
+ ).toMatchInlineSnapshot(`""`)
+})
+
+test('place-self', () => {
+ expect(
+ run([
+ 'place-self-auto',
+ 'place-self-start',
+ 'place-self-end',
+ 'place-self-center',
+ 'place-self-stretch',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".place-self-auto {
+ place-self: auto;
+ }
+
+ .place-self-center {
+ place-self: center;
+ }
+
+ .place-self-end {
+ place-self: end;
+ }
+
+ .place-self-start {
+ place-self: start;
+ }
+
+ .place-self-stretch {
+ place-self: stretch stretch;
+ }"
+ `)
+ expect(
+ run([
+ 'place-self',
+ '-place-self-auto',
+ '-place-self-start',
+ '-place-self-end',
+ '-place-self-center',
+ '-place-self-stretch',
+ ]),
+ ).toEqual('')
+})
+
+test('self', () => {
+ expect(
+ run(['self-auto', 'self-start', 'self-end', 'self-center', 'self-stretch', 'self-baseline']),
+ ).toMatchInlineSnapshot(`
+ ".self-auto {
+ align-self: auto;
+ }
+
+ .self-baseline {
+ align-self: baseline;
+ }
+
+ .self-center {
+ align-self: center;
+ }
+
+ .self-end {
+ align-self: flex-end;
+ }
+
+ .self-start {
+ align-self: flex-start;
+ }
+
+ .self-stretch {
+ align-self: stretch;
+ }"
+ `)
+ expect(
+ run([
+ 'self',
+ '-self-auto',
+ '-self-start',
+ '-self-end',
+ '-self-center',
+ '-self-stretch',
+ '-self-baseline',
+ ]),
+ ).toEqual('')
+})
+
+test('justify-self', () => {
+ expect(
+ run([
+ 'justify-self-auto',
+ 'justify-self-start',
+ 'justify-self-end',
+ 'justify-self-center',
+ 'justify-self-stretch',
+ 'justify-self-baseline',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".justify-self-auto {
+ justify-self: auto;
+ }
+
+ .justify-self-center {
+ justify-self: center;
+ }
+
+ .justify-self-end {
+ justify-self: flex-end;
+ }
+
+ .justify-self-start {
+ justify-self: flex-start;
+ }
+
+ .justify-self-stretch {
+ justify-self: stretch;
+ }"
+ `)
+ expect(
+ run([
+ 'justify-self',
+ '-justify-self-auto',
+ '-justify-self-start',
+ '-justify-self-end',
+ '-justify-self-center',
+ '-justify-self-stretch',
+ '-justify-self-baseline',
+ ]),
+ ).toEqual('')
+})
+
+test('overflow', () => {
+ expect(
+ run([
+ 'overflow-auto',
+ 'overflow-hidden',
+ 'overflow-clip',
+ 'overflow-visible',
+ 'overflow-scroll',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".overflow-auto {
+ overflow: auto;
+ }
+
+ .overflow-clip {
+ overflow: clip;
+ }
+
+ .overflow-hidden {
+ overflow: hidden;
+ }
+
+ .overflow-scroll {
+ overflow: scroll;
+ }
+
+ .overflow-visible {
+ overflow: visible;
+ }"
+ `)
+ expect(
+ run([
+ 'overflow',
+ '-overflow-auto',
+ '-overflow-hidden',
+ '-overflow-clip',
+ '-overflow-visible',
+ '-overflow-scroll',
+ ]),
+ ).toEqual('')
+})
+
+test('overflow-x', () => {
+ expect(
+ run([
+ 'overflow-x-auto',
+ 'overflow-x-hidden',
+ 'overflow-x-clip',
+ 'overflow-x-visible',
+ 'overflow-x-scroll',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".overflow-x-auto {
+ overflow-x: auto;
+ }
+
+ .overflow-x-clip {
+ overflow-x: clip;
+ }
+
+ .overflow-x-hidden {
+ overflow-x: hidden;
+ }
+
+ .overflow-x-scroll {
+ overflow-x: scroll;
+ }
+
+ .overflow-x-visible {
+ overflow-x: visible;
+ }"
+ `)
+ expect(
+ run([
+ 'overflow-x',
+ '-overflow-x-auto',
+ '-overflow-x-hidden',
+ '-overflow-x-clip',
+ '-overflow-x-visible',
+ '-overflow-x-scroll',
+ ]),
+ ).toEqual('')
+})
+
+test('overflow-y', () => {
+ expect(
+ run([
+ 'overflow-y-auto',
+ 'overflow-y-hidden',
+ 'overflow-y-clip',
+ 'overflow-y-visible',
+ 'overflow-y-scroll',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".overflow-y-auto {
+ overflow-y: auto;
+ }
+
+ .overflow-y-clip {
+ overflow-y: clip;
+ }
+
+ .overflow-y-hidden {
+ overflow-y: hidden;
+ }
+
+ .overflow-y-scroll {
+ overflow-y: scroll;
+ }
+
+ .overflow-y-visible {
+ overflow-y: visible;
+ }"
+ `)
+ expect(
+ run([
+ 'overflow-y',
+ '-overflow-y-auto',
+ '-overflow-y-hidden',
+ '-overflow-y-clip',
+ '-overflow-y-visible',
+ '-overflow-y-scroll',
+ ]),
+ ).toEqual('')
+})
+
+test('overscroll', () => {
+ expect(run(['overscroll-auto', 'overscroll-contain', 'overscroll-none'])).toMatchInlineSnapshot(`
+ ".overscroll-auto {
+ overscroll-behavior: auto;
+ }
+
+ .overscroll-contain {
+ overscroll-behavior: contain;
+ }
+
+ .overscroll-none {
+ overscroll-behavior: none;
+ }"
+ `)
+ expect(
+ run(['overscroll', '-overscroll-auto', '-overscroll-contain', '-overscroll-none']),
+ ).toEqual('')
+})
+
+test('overscroll-x', () => {
+ expect(run(['overscroll-x-auto', 'overscroll-x-contain', 'overscroll-x-none']))
+ .toMatchInlineSnapshot(`
+ ".overscroll-x-auto {
+ overscroll-behavior-x: auto;
+ }
+
+ .overscroll-x-contain {
+ overscroll-behavior-x: contain;
+ }
+
+ .overscroll-x-none {
+ overscroll-behavior-x: none;
+ }"
+ `)
+ expect(
+ run(['overscroll-x', '-overscroll-x-auto', '-overscroll-x-contain', '-overscroll-x-none']),
+ ).toEqual('')
+})
+
+test('overscroll-y', () => {
+ expect(run(['overscroll-y-auto', 'overscroll-y-contain', 'overscroll-y-none']))
+ .toMatchInlineSnapshot(`
+ ".overscroll-y-auto {
+ overscroll-behavior-y: auto;
+ }
+
+ .overscroll-y-contain {
+ overscroll-behavior-y: contain;
+ }
+
+ .overscroll-y-none {
+ overscroll-behavior-y: none;
+ }"
+ `)
+ expect(
+ run(['overscroll-y', '-overscroll-y-auto', '-overscroll-y-contain', '-overscroll-y-none']),
+ ).toEqual('')
+})
+
+test('scroll-behavior', () => {
+ expect(run(['scroll-auto', 'scroll-smooth'])).toMatchInlineSnapshot(`
+ ".scroll-auto {
+ scroll-behavior: auto;
+ }
+
+ .scroll-smooth {
+ scroll-behavior: smooth;
+ }"
+ `)
+ expect(run(['scroll', '-scroll-auto', '-scroll-smooth'])).toEqual('')
+})
+
+test('truncate', () => {
+ expect(run(['truncate'])).toMatchInlineSnapshot(`
+ ".truncate {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }"
+ `)
+ expect(run(['-truncate'])).toEqual('')
+})
+
+test('text-overflow', () => {
+ expect(run(['text-ellipsis', 'text-clip'])).toMatchInlineSnapshot(`
+ ".text-clip {
+ text-overflow: clip;
+ }
+
+ .text-ellipsis {
+ text-overflow: ellipsis;
+ }"
+ `)
+ expect(run(['-text-ellipsis', '-text-clip'])).toEqual('')
+})
+
+test('hyphens', () => {
+ expect(run(['hyphens-none', 'hyphens-manual', 'hyphens-auto'])).toMatchInlineSnapshot(`
+ ".hyphens-auto {
+ -webkit-hyphens: auto;
+ hyphens: auto;
+ }
+
+ .hyphens-manual {
+ -webkit-hyphens: manual;
+ hyphens: manual;
+ }
+
+ .hyphens-none {
+ -webkit-hyphens: none;
+ hyphens: none;
+ }"
+ `)
+ expect(run(['hyphens', '-hyphens-none', '-hyphens-manual', '-hyphens-auto'])).toEqual('')
+})
+
+test('whitespace', () => {
+ expect(
+ run([
+ 'whitespace-normal',
+ 'whitespace-nowrap',
+ 'whitespace-pre',
+ 'whitespace-pre-line',
+ 'whitespace-pre-wrap',
+ 'whitespace-break-spaces',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".whitespace-break-spaces {
+ white-space: break-spaces;
+ }
+
+ .whitespace-normal {
+ white-space: normal;
+ }
+
+ .whitespace-nowrap {
+ white-space: nowrap;
+ }
+
+ .whitespace-pre {
+ white-space: pre;
+ }
+
+ .whitespace-pre-line {
+ white-space: pre-line;
+ }
+
+ .whitespace-pre-wrap {
+ white-space: pre-wrap;
+ }"
+ `)
+ expect(
+ run([
+ 'whitespace',
+ '-whitespace-normal',
+ '-whitespace-nowrap',
+ '-whitespace-pre',
+ '-whitespace-pre-line',
+ '-whitespace-pre-wrap',
+ '-whitespace-break-spaces',
+ ]),
+ ).toEqual('')
+})
+
+test('text-wrap', () => {
+ expect(run(['text-wrap', 'text-nowrap', 'text-balance', 'text-pretty'])).toMatchInlineSnapshot(`
+ ".text-balance {
+ text-wrap: balance;
+ }
+
+ .text-nowrap {
+ text-wrap: nowrap;
+ }
+
+ .text-pretty {
+ text-wrap: pretty;
+ }
+
+ .text-wrap {
+ text-wrap: wrap;
+ }"
+ `)
+ expect(run(['-text-wrap', '-text-nowrap', '-text-balance', '-text-pretty'])).toEqual('')
+})
+
+test('overflow-wrap', () => {
+ expect(run(['break-normal', 'break-words', 'break-all', 'break-keep'])).toMatchInlineSnapshot(`
+ ".break-normal {
+ overflow-wrap: normal;
+ word-break: normal;
+ }
+
+ .break-words {
+ overflow-wrap: break-word;
+ }
+
+ .break-all {
+ word-break: break-all;
+ }
+
+ .break-keep {
+ word-break: break-keep;
+ }"
+ `)
+ expect(run(['-break-normal', '-break-words', '-break-all', '-break-keep'])).toEqual('')
+})
+
+test('rounded', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['rounded', 'rounded-full', 'rounded-none', 'rounded-sm', 'rounded-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ }
+
+ .rounded {
+ border-radius: .25rem;
+ }
+
+ .rounded-\\[4px\\] {
+ border-radius: 4px;
+ }
+
+ .rounded-full {
+ border-radius: 9999px;
+ }
+
+ .rounded-none {
+ border-radius: 0;
+ }
+
+ .rounded-sm {
+ border-radius: .125rem;
+ }"
+ `)
+ expect(
+ run(['-rounded', '-rounded-full', '-rounded-none', '-rounded-sm', '-rounded-[4px]']),
+ ).toEqual('')
+})
+
+test('rounded-s', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['rounded-s', 'rounded-s-full', 'rounded-s-none', 'rounded-s-sm', 'rounded-s-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ }
+
+ .rounded-s {
+ border-start-start-radius: .25rem;
+ border-end-start-radius: .25rem;
+ }
+
+ .rounded-s-\\[4px\\] {
+ border-start-start-radius: 4px;
+ border-end-start-radius: 4px;
+ }
+
+ .rounded-s-full {
+ border-start-start-radius: 9999px;
+ border-end-start-radius: 9999px;
+ }
+
+ .rounded-s-none {
+ border-start-start-radius: 0;
+ border-end-start-radius: 0;
+ }
+
+ .rounded-s-sm {
+ border-start-start-radius: .125rem;
+ border-end-start-radius: .125rem;
+ }"
+ `)
+ expect(
+ run(['-rounded-s', '-rounded-s-full', '-rounded-s-none', '-rounded-s-sm', '-rounded-s-[4px]']),
+ ).toEqual('')
+})
+
+test('rounded-e', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['rounded-e', 'rounded-e-full', 'rounded-e-none', 'rounded-e-sm', 'rounded-e-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ }
+
+ .rounded-e {
+ border-start-end-radius: .25rem;
+ border-end-end-radius: .25rem;
+ }
+
+ .rounded-e-\\[4px\\] {
+ border-start-end-radius: 4px;
+ border-end-end-radius: 4px;
+ }
+
+ .rounded-e-full {
+ border-start-end-radius: 9999px;
+ border-end-end-radius: 9999px;
+ }
+
+ .rounded-e-none {
+ border-start-end-radius: 0;
+ border-end-end-radius: 0;
+ }
+
+ .rounded-e-sm {
+ border-start-end-radius: .125rem;
+ border-end-end-radius: .125rem;
+ }"
+ `)
+ expect(
+ run(['-rounded-e', '-rounded-e-full', '-rounded-e-none', '-rounded-e-sm', '-rounded-e-[4px]']),
+ ).toEqual('')
+})
+
+test('rounded-t', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['rounded-t', 'rounded-t-full', 'rounded-t-none', 'rounded-t-sm', 'rounded-t-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ }
+
+ .rounded-t {
+ border-top-left-radius: .25rem;
+ border-top-right-radius: .25rem;
+ }
+
+ .rounded-t-\\[4px\\] {
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ }
+
+ .rounded-t-full {
+ border-top-left-radius: 9999px;
+ border-top-right-radius: 9999px;
+ }
+
+ .rounded-t-none {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ }
+
+ .rounded-t-sm {
+ border-top-left-radius: .125rem;
+ border-top-right-radius: .125rem;
+ }"
+ `)
+ expect(
+ run(['-rounded-t', '-rounded-t-full', '-rounded-t-none', '-rounded-t-sm', '-rounded-t-[4px]']),
+ ).toEqual('')
+})
+
+test('rounded-r', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['rounded-r', 'rounded-r-full', 'rounded-r-none', 'rounded-r-sm', 'rounded-r-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ }
+
+ .rounded-r {
+ border-top-right-radius: .25rem;
+ border-bottom-right-radius: .25rem;
+ }
+
+ .rounded-r-\\[4px\\] {
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ }
+
+ .rounded-r-full {
+ border-top-right-radius: 9999px;
+ border-bottom-right-radius: 9999px;
+ }
+
+ .rounded-r-none {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ }
+
+ .rounded-r-sm {
+ border-top-right-radius: .125rem;
+ border-bottom-right-radius: .125rem;
+ }"
+ `)
+ expect(
+ run(['-rounded-r', '-rounded-r-full', '-rounded-r-none', '-rounded-r-sm', '-rounded-r-[4px]']),
+ ).toEqual('')
+})
+
+test('rounded-b', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['rounded-b', 'rounded-b-full', 'rounded-b-none', 'rounded-b-sm', 'rounded-b-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ }
+
+ .rounded-b {
+ border-bottom-right-radius: .25rem;
+ border-bottom-left-radius: .25rem;
+ }
+
+ .rounded-b-\\[4px\\] {
+ border-bottom-right-radius: 4px;
+ border-bottom-left-radius: 4px;
+ }
+
+ .rounded-b-full {
+ border-bottom-right-radius: 9999px;
+ border-bottom-left-radius: 9999px;
+ }
+
+ .rounded-b-none {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ }
+
+ .rounded-b-sm {
+ border-bottom-right-radius: .125rem;
+ border-bottom-left-radius: .125rem;
+ }"
+ `)
+ expect(
+ run(['-rounded-b', '-rounded-b-full', '-rounded-b-none', '-rounded-b-sm', '-rounded-b-[4px]']),
+ ).toEqual('')
+})
+
+test('rounded-l', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['rounded-l', 'rounded-l-full', 'rounded-l-none', 'rounded-l-sm', 'rounded-l-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ }
+
+ .rounded-l {
+ border-top-left-radius: .25rem;
+ border-bottom-left-radius: .25rem;
+ }
+
+ .rounded-l-\\[4px\\] {
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ }
+
+ .rounded-l-full {
+ border-top-left-radius: 9999px;
+ border-bottom-left-radius: 9999px;
+ }
+
+ .rounded-l-none {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ }
+
+ .rounded-l-sm {
+ border-top-left-radius: .125rem;
+ border-bottom-left-radius: .125rem;
+ }"
+ `)
+ expect(
+ run(['-rounded-l', '-rounded-l-full', '-rounded-l-none', '-rounded-l-sm', '-rounded-l-[4px]']),
+ ).toEqual('')
+})
+
+test('rounded-ss', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['rounded-ss', 'rounded-ss-full', 'rounded-ss-none', 'rounded-ss-sm', 'rounded-ss-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ }
+
+ .rounded-ss {
+ border-start-start-radius: .25rem;
+ }
+
+ .rounded-ss-\\[4px\\] {
+ border-start-start-radius: 4px;
+ }
+
+ .rounded-ss-full {
+ border-start-start-radius: 9999px;
+ }
+
+ .rounded-ss-none {
+ border-start-start-radius: 0;
+ }
+
+ .rounded-ss-sm {
+ border-start-start-radius: .125rem;
+ }"
+ `)
+ expect(
+ run([
+ '-rounded-ss',
+ '-rounded-ss-full',
+ '-rounded-ss-none',
+ '-rounded-ss-sm',
+ '-rounded-ss-[4px]',
+ ]),
+ ).toEqual('')
+})
+
+test('rounded-se', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['rounded-se', 'rounded-se-full', 'rounded-se-none', 'rounded-se-sm', 'rounded-se-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ }
+
+ .rounded-se {
+ border-start-end-radius: .25rem;
+ }
+
+ .rounded-se-\\[4px\\] {
+ border-start-end-radius: 4px;
+ }
+
+ .rounded-se-full {
+ border-start-end-radius: 9999px;
+ }
+
+ .rounded-se-none {
+ border-start-end-radius: 0;
+ }
+
+ .rounded-se-sm {
+ border-start-end-radius: .125rem;
+ }"
+ `)
+ expect(
+ run([
+ '-rounded-se',
+ '-rounded-se-full',
+ '-rounded-se-none',
+ '-rounded-se-sm',
+ '-rounded-se-[4px]',
+ ]),
+ ).toEqual('')
+})
+
+test('rounded-ee', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['rounded-ee', 'rounded-ee-full', 'rounded-ee-none', 'rounded-ee-sm', 'rounded-ee-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ }
+
+ .rounded-ee {
+ border-end-end-radius: .25rem;
+ }
+
+ .rounded-ee-\\[4px\\] {
+ border-end-end-radius: 4px;
+ }
+
+ .rounded-ee-full {
+ border-end-end-radius: 9999px;
+ }
+
+ .rounded-ee-none {
+ border-end-end-radius: 0;
+ }
+
+ .rounded-ee-sm {
+ border-end-end-radius: .125rem;
+ }"
+ `)
+ expect(
+ run([
+ '-rounded-ee',
+ '-rounded-ee-full',
+ '-rounded-ee-none',
+ '-rounded-ee-sm',
+ '-rounded-ee-[4px]',
+ ]),
+ ).toEqual('')
+})
+
+test('rounded-es', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['rounded-es', 'rounded-es-full', 'rounded-es-none', 'rounded-es-sm', 'rounded-es-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ }
+
+ .rounded-es {
+ border-end-start-radius: .25rem;
+ }
+
+ .rounded-es-\\[4px\\] {
+ border-end-start-radius: 4px;
+ }
+
+ .rounded-es-full {
+ border-end-start-radius: 9999px;
+ }
+
+ .rounded-es-none {
+ border-end-start-radius: 0;
+ }
+
+ .rounded-es-sm {
+ border-end-start-radius: .125rem;
+ }"
+ `)
+ expect(
+ run([
+ '-rounded-es',
+ '-rounded-es-full',
+ '-rounded-es-none',
+ '-rounded-es-sm',
+ '-rounded-es-[4px]',
+ ]),
+ ).toEqual('')
+})
+
+test('rounded-tl', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['rounded-tl', 'rounded-tl-full', 'rounded-tl-none', 'rounded-tl-sm', 'rounded-tl-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ }
+
+ .rounded-tl {
+ border-top-left-radius: .25rem;
+ }
+
+ .rounded-tl-\\[4px\\] {
+ border-top-left-radius: 4px;
+ }
+
+ .rounded-tl-full {
+ border-top-left-radius: 9999px;
+ }
+
+ .rounded-tl-none {
+ border-top-left-radius: 0;
+ }
+
+ .rounded-tl-sm {
+ border-top-left-radius: .125rem;
+ }"
+ `)
+ expect(
+ run([
+ '-rounded-tl',
+ '-rounded-tl-full',
+ '-rounded-tl-none',
+ '-rounded-tl-sm',
+ '-rounded-tl-[4px]',
+ ]),
+ ).toEqual('')
+})
+
+test('rounded-tr', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['rounded-tr', 'rounded-tr-full', 'rounded-tr-none', 'rounded-tr-sm', 'rounded-tr-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ }
+
+ .rounded-tr {
+ border-top-right-radius: .25rem;
+ }
+
+ .rounded-tr-\\[4px\\] {
+ border-top-right-radius: 4px;
+ }
+
+ .rounded-tr-full {
+ border-top-right-radius: 9999px;
+ }
+
+ .rounded-tr-none {
+ border-top-right-radius: 0;
+ }
+
+ .rounded-tr-sm {
+ border-top-right-radius: .125rem;
+ }"
+ `)
+ expect(
+ run([
+ '-rounded-tr',
+ '-rounded-tr-full',
+ '-rounded-tr-none',
+ '-rounded-tr-sm',
+ '-rounded-tr-[4px]',
+ ]),
+ ).toEqual('')
+})
+
+test('rounded-br', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['rounded-br', 'rounded-br-full', 'rounded-br-none', 'rounded-br-sm', 'rounded-br-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ }
+
+ .rounded-br {
+ border-bottom-right-radius: .25rem;
+ }
+
+ .rounded-br-\\[4px\\] {
+ border-bottom-right-radius: 4px;
+ }
+
+ .rounded-br-full {
+ border-bottom-right-radius: 9999px;
+ }
+
+ .rounded-br-none {
+ border-bottom-right-radius: 0;
+ }
+
+ .rounded-br-sm {
+ border-bottom-right-radius: .125rem;
+ }"
+ `)
+ expect(
+ run([
+ '-rounded-br',
+ '-rounded-br-full',
+ '-rounded-br-none',
+ '-rounded-br-sm',
+ '-rounded-br-[4px]',
+ ]),
+ ).toEqual('')
+})
+
+test('rounded-bl', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ }
+ @tailwind utilities;
+ `,
+ ['rounded-bl', 'rounded-bl-full', 'rounded-bl-none', 'rounded-bl-sm', 'rounded-bl-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: .125rem;
+ --radius: .25rem;
+ }
+
+ .rounded-bl {
+ border-bottom-left-radius: .25rem;
+ }
+
+ .rounded-bl-\\[4px\\] {
+ border-bottom-left-radius: 4px;
+ }
+
+ .rounded-bl-full {
+ border-bottom-left-radius: 9999px;
+ }
+
+ .rounded-bl-none {
+ border-bottom-left-radius: 0;
+ }
+
+ .rounded-bl-sm {
+ border-bottom-left-radius: .125rem;
+ }"
+ `)
+ expect(
+ run([
+ '-rounded-bl',
+ '-rounded-bl-full',
+ '-rounded-bl-none',
+ '-rounded-bl-sm',
+ '-rounded-bl-[4px]',
+ ]),
+ ).toEqual('')
+})
+
+test('border-style', () => {
+ expect(
+ run([
+ 'border-solid',
+ 'border-dashed',
+ 'border-dotted',
+ 'border-double',
+ 'border-hidden',
+ 'border-none',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".border-dashed {
+ --tw-border-style: dashed;
+ border-style: dashed;
+ }
+
+ .border-dotted {
+ --tw-border-style: dotted;
+ border-style: dotted;
+ }
+
+ .border-double {
+ --tw-border-style: double;
+ border-style: double;
+ }
+
+ .border-hidden {
+ --tw-border-style: hidden;
+ border-style: hidden;
+ }
+
+ .border-none {
+ --tw-border-style: none;
+ border-style: none;
+ }
+
+ .border-solid {
+ --tw-border-style: solid;
+ border-style: solid;
+ }"
+ `)
+ expect(
+ run([
+ '-border-solid',
+ '-border-dashed',
+ '-border-dotted',
+ '-border-double',
+ '-border-hidden',
+ '-border-none',
+ ]),
+ ).toEqual('')
+})
+
+// All border utilities are generated in the same way
+// so we can test them all at once with a loop
+const prefixes = [
+ 'border',
+ 'border-x',
+ 'border-y',
+ 'border-s',
+ 'border-e',
+ 'border-t',
+ 'border-r',
+ 'border-b',
+ 'border-l',
+]
+
+for (let prefix of prefixes) {
+ test(`${prefix}-*`, () => {
+ let classes = []
+
+ // Width
+ classes.push(prefix)
+ classes.push(`${prefix}-0`)
+ classes.push(`${prefix}-2`)
+ classes.push(`${prefix}-4`)
+ classes.push(`${prefix}-123`)
+
+ // Inference: Width
+ classes.push(`${prefix}-[thin]`)
+ classes.push(`${prefix}-[medium]`)
+ classes.push(`${prefix}-[thick]`)
+ classes.push(`${prefix}-[12px]`)
+ classes.push(`${prefix}-[length:--my-width]`)
+ classes.push(`${prefix}-[line-width:--my-width]`)
+
+ // Color
+ classes.push(`${prefix}-red-500`)
+ classes.push(`${prefix}-red-500/50`)
+ classes.push(`${prefix}-[#0088cc]`)
+ classes.push(`${prefix}-[#0088cc]/50`)
+ classes.push(`${prefix}-current`)
+ classes.push(`${prefix}-current/50`)
+ classes.push(`${prefix}-transparent`)
+
+ // Inference: Color
+ classes.push(`${prefix}-[--my-color]`)
+ classes.push(`${prefix}-[--my-color]/50`)
+ classes.push(`${prefix}-[color:--my-color]`)
+ classes.push(`${prefix}-[color:--my-color]/50`)
+
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ classes,
+ ),
+ ).toMatchSnapshot()
+
+ // No border utilities can ever be negative
+ expect(run(classes.map((cls) => `-${cls}`))).toEqual('')
+ })
+}
+
+test('border with custom default border width', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --default-border-width: 2px;
+ }
+ @tailwind utilities;
+ `,
+ ['border'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --default-border-width: 2px;
+ }
+
+ .border {
+ border-style: var(--tw-border-style);
+ border-width: 2px;
+ }
+
+ @property --tw-border-style {
+ syntax: "";
+ inherits: false;
+ initial-value: solid;
+ }"
+ `)
+})
+
+test('bg', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ [
+ // background-color
+ 'bg-inherit',
+ 'bg-red-500',
+ 'bg-red-500/50',
+ 'bg-red-500/[0.5]',
+ 'bg-red-500/[50%]',
+ 'bg-current',
+ 'bg-current/50',
+ 'bg-current/[0.5]',
+ 'bg-current/[50%]',
+ 'bg-transparent',
+ 'bg-[#0088cc]',
+ 'bg-[#0088cc]/50',
+ 'bg-[#0088cc]/[0.5]',
+ 'bg-[#0088cc]/[50%]',
+ 'bg-[--some-var]',
+ 'bg-[--some-var]/50',
+ 'bg-[--some-var]/[0.5]',
+ 'bg-[--some-var]/[50%]',
+ 'bg-[color:--some-var]',
+ 'bg-[color:--some-var]/50',
+ 'bg-[color:--some-var]/[0.5]',
+ 'bg-[color:--some-var]/[50%]',
+
+ // background-image
+ 'bg-none',
+ 'bg-gradient-to-t',
+ 'bg-gradient-to-tr',
+ 'bg-gradient-to-r',
+ 'bg-gradient-to-br',
+ 'bg-gradient-to-b',
+ 'bg-gradient-to-bl',
+ 'bg-gradient-to-l',
+ 'bg-gradient-to-tl',
+ 'bg-[url(/image.png)]',
+ 'bg-[url:--my-url]',
+ 'bg-[linear-gradient(to_bottom,red,blue)]',
+ 'bg-[image:--my-gradient]',
+
+ // background-size
+ 'bg-auto',
+ 'bg-cover',
+ 'bg-contain',
+ 'bg-[cover]',
+ 'bg-[contain]',
+ 'bg-[size:120px_120px]',
+
+ // background-attachment
+ 'bg-fixed',
+ 'bg-local',
+ 'bg-scroll',
+
+ // background-position
+ 'bg-center',
+ 'bg-top',
+ 'bg-right-top',
+ 'bg-right-bottom',
+ 'bg-bottom',
+ 'bg-left-bottom',
+ 'bg-left',
+ 'bg-left-top',
+ 'bg-[50%]',
+ 'bg-[120px]',
+ 'bg-[120px_120px]',
+ 'bg-[position:120px_120px]',
+
+ // background-repeat
+ 'bg-repeat',
+ 'bg-no-repeat',
+ 'bg-repeat-x',
+ 'bg-repeat-y',
+ 'bg-round',
+ 'bg-space',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ }
+
+ .bg-\\[\\#0088cc\\] {
+ background-color: #08c;
+ }
+
+ .bg-\\[\\#0088cc\\]\\/50, .bg-\\[\\#0088cc\\]\\/\\[0\\.5\\], .bg-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ background-color: #0088cc80;
+ }
+
+ .bg-\\[--some-var\\] {
+ background-color: var(--some-var);
+ }
+
+ .bg-\\[--some-var\\]\\/50, .bg-\\[--some-var\\]\\/\\[0\\.5\\], .bg-\\[--some-var\\]\\/\\[50\\%\\] {
+ background-color: color-mix(in srgb, var(--some-var) 50%, transparent);
+ }
+
+ .bg-\\[color\\:--some-var\\] {
+ background-color: var(--some-var);
+ }
+
+ .bg-\\[color\\:--some-var\\]\\/50, .bg-\\[color\\:--some-var\\]\\/\\[0\\.5\\], .bg-\\[color\\:--some-var\\]\\/\\[50\\%\\] {
+ background-color: color-mix(in srgb, var(--some-var) 50%, transparent);
+ }
+
+ .bg-current {
+ background-color: currentColor;
+ }
+
+ .bg-current\\/50, .bg-current\\/\\[0\\.5\\], .bg-current\\/\\[50\\%\\] {
+ background-color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ .bg-inherit {
+ background-color: inherit;
+ }
+
+ .bg-red-500 {
+ background-color: #ef4444;
+ }
+
+ .bg-red-500\\/50, .bg-red-500\\/\\[0\\.5\\], .bg-red-500\\/\\[50\\%\\] {
+ background-color: #ef444480;
+ }
+
+ .bg-transparent {
+ background-color: #0000;
+ }
+
+ .bg-\\[image\\:--my-gradient\\] {
+ background-image: var(--my-gradient);
+ }
+
+ .bg-\\[linear-gradient\\(to_bottom\\,red\\,blue\\)\\] {
+ background-image: linear-gradient(red, #00f);
+ }
+
+ .bg-\\[url\\(\\/image\\.png\\)\\] {
+ background-image: url("/image.png");
+ }
+
+ .bg-\\[url\\:--my-url\\] {
+ background-image: var(--my-url);
+ }
+
+ .bg-gradient-to-b {
+ background-image: linear-gradient(to bottom, var(--tw-gradient-stops, ));
+ }
+
+ .bg-gradient-to-bl {
+ background-image: linear-gradient(to bottom left, var(--tw-gradient-stops, ));
+ }
+
+ .bg-gradient-to-br {
+ background-image: linear-gradient(to bottom right, var(--tw-gradient-stops, ));
+ }
+
+ .bg-gradient-to-l {
+ background-image: linear-gradient(to left, var(--tw-gradient-stops, ));
+ }
+
+ .bg-gradient-to-r {
+ background-image: linear-gradient(to right, var(--tw-gradient-stops, ));
+ }
+
+ .bg-gradient-to-t {
+ background-image: linear-gradient(to top, var(--tw-gradient-stops, ));
+ }
+
+ .bg-gradient-to-tl {
+ background-image: linear-gradient(to top left, var(--tw-gradient-stops, ));
+ }
+
+ .bg-gradient-to-tr {
+ background-image: linear-gradient(to top right, var(--tw-gradient-stops, ));
+ }
+
+ .bg-none {
+ background-image: none;
+ }
+
+ .bg-\\[contain\\] {
+ background-size: contain;
+ }
+
+ .bg-\\[cover\\] {
+ background-size: cover;
+ }
+
+ .bg-\\[size\\:120px_120px\\] {
+ background-size: 120px 120px;
+ }
+
+ .bg-auto {
+ background-size: auto;
+ }
+
+ .bg-contain {
+ background-size: contain;
+ }
+
+ .bg-cover {
+ background-size: cover;
+ }
+
+ .bg-fixed {
+ background-attachment: fixed;
+ }
+
+ .bg-local {
+ background-attachment: local;
+ }
+
+ .bg-scroll {
+ background-attachment: scroll;
+ }
+
+ .bg-\\[120px\\] {
+ background-position: 120px;
+ }
+
+ .bg-\\[120px_120px\\] {
+ background-position: 120px 120px;
+ }
+
+ .bg-\\[50\\%\\] {
+ background-position: 50%;
+ }
+
+ .bg-\\[position\\:120px_120px\\] {
+ background-position: 120px 120px;
+ }
+
+ .bg-bottom {
+ background-position: bottom;
+ }
+
+ .bg-center {
+ background-position: center;
+ }
+
+ .bg-left {
+ background-position: 0;
+ }
+
+ .bg-left-bottom {
+ background-position: left-bottom;
+ }
+
+ .bg-left-top {
+ background-position: left-top;
+ }
+
+ .bg-right-bottom {
+ background-position: right-bottom;
+ }
+
+ .bg-right-top {
+ background-position: right-top;
+ }
+
+ .bg-top {
+ background-position: top;
+ }
+
+ .bg-no-repeat {
+ background-repeat: no-repeat;
+ }
+
+ .bg-repeat {
+ background-repeat: repeat;
+ }
+
+ .bg-repeat-x {
+ background-repeat: repeat-x;
+ }
+
+ .bg-repeat-y {
+ background-repeat: repeat-y;
+ }
+
+ .bg-round {
+ background-repeat: round;
+ }
+
+ .bg-space {
+ background-repeat: space;
+ }"
+ `)
+ expect(
+ run([
+ 'bg',
+ // background-color
+ '-bg-inherit',
+ '-bg-red-500',
+ '-bg-red-500/50',
+ '-bg-red-500/[0.5]',
+ '-bg-red-500/[50%]',
+ '-bg-current',
+ '-bg-current/50',
+ '-bg-current/[0.5]',
+ '-bg-current/[50%]',
+ '-bg-transparent',
+ '-bg-[#0088cc]',
+ '-bg-[#0088cc]/50',
+ '-bg-[#0088cc]/[0.5]',
+ '-bg-[#0088cc]/[50%]',
+
+ // background-image
+ '-bg-none',
+ '-bg-gradient-to-br',
+
+ // background-size
+ '-bg-auto',
+ '-bg-cover',
+ '-bg-contain',
+
+ // background-attachment
+ '-bg-fixed',
+ '-bg-local',
+ '-bg-scroll',
+
+ // background-position
+ '-bg-center',
+ '-bg-top',
+ '-bg-right-top',
+ '-bg-right-bottom',
+ '-bg-bottom',
+ '-bg-left-bottom',
+ '-bg-left',
+ '-bg-left-top',
+
+ // background-repeat
+ '-bg-repeat',
+ '-bg-no-repeat',
+ '-bg-repeat-x',
+ '-bg-repeat-y',
+ '-bg-round',
+ '-bg-space',
+ ]),
+ ).toEqual('')
+
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --opacity-half: 0.5;
+ --opacity-custom: var(--custom-opacity);
+ }
+ @tailwind utilities;
+ `,
+ ['bg-current/half', 'bg-current/custom'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --opacity-half: .5;
+ --opacity-custom: var(--custom-opacity);
+ }
+
+ .bg-current\\/custom {
+ background-color: color-mix(in srgb, currentColor var(--custom-opacity), transparent);
+ }
+
+ .bg-current\\/half {
+ background-color: color-mix(in srgb, currentColor 50%, transparent);
+ }"
+ `)
+})
+
+test('from', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ [
+ // --tw-gradient-from
+ 'from-red-500',
+ 'from-red-500/50',
+ 'from-red-500/[0.5]',
+ 'from-red-500/[50%]',
+ 'from-current',
+ 'from-current/50',
+ 'from-current/[0.5]',
+ 'from-current/[50%]',
+ 'from-transparent',
+ 'from-[#0088cc]',
+ 'from-[#0088cc]/50',
+ 'from-[#0088cc]/[0.5]',
+ 'from-[#0088cc]/[50%]',
+ 'from-[--my-color]',
+ 'from-[--my-color]/50',
+ 'from-[--my-color]/[0.5]',
+ 'from-[--my-color]/[50%]',
+ 'from-[color:--my-color]',
+ 'from-[color:--my-color]/50',
+ 'from-[color:--my-color]/[0.5]',
+ 'from-[color:--my-color]/[50%]',
+
+ // --tw-gradient-from-position
+ 'from-0%',
+ 'from-5%',
+ 'from-100%',
+ 'from-[50%]',
+ 'from-[50px]',
+ 'from-[length:--my-position]',
+ 'from-[percentage:--my-position]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ }
+
+ .from-\\[\\#0088cc\\] {
+ --tw-gradient-from: #08c;
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .from-\\[\\#0088cc\\]\\/50, .from-\\[\\#0088cc\\]\\/\\[0\\.5\\], .from-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ --tw-gradient-from: #0088cc80;
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .from-\\[--my-color\\] {
+ --tw-gradient-from: var(--my-color);
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .from-\\[--my-color\\]\\/50, .from-\\[--my-color\\]\\/\\[0\\.5\\], .from-\\[--my-color\\]\\/\\[50\\%\\] {
+ --tw-gradient-from: color-mix(in srgb, var(--my-color) 50%, transparent);
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .from-\\[color\\:--my-color\\] {
+ --tw-gradient-from: var(--my-color);
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .from-\\[color\\:--my-color\\]\\/50, .from-\\[color\\:--my-color\\]\\/\\[0\\.5\\], .from-\\[color\\:--my-color\\]\\/\\[50\\%\\] {
+ --tw-gradient-from: color-mix(in srgb, var(--my-color) 50%, transparent);
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .from-current {
+ --tw-gradient-from: currentColor;
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .from-current\\/50, .from-current\\/\\[0\\.5\\], .from-current\\/\\[50\\%\\] {
+ --tw-gradient-from: color-mix(in srgb, currentColor 50%, transparent);
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .from-red-500 {
+ --tw-gradient-from: #ef4444;
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .from-red-500\\/50, .from-red-500\\/\\[0\\.5\\], .from-red-500\\/\\[50\\%\\] {
+ --tw-gradient-from: #ef444480;
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .from-transparent {
+ --tw-gradient-from: transparent;
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .from-0\\% {
+ --tw-gradient-from-position: 0%;
+ }
+
+ .from-100\\% {
+ --tw-gradient-from-position: 100%;
+ }
+
+ .from-5\\% {
+ --tw-gradient-from-position: 5%;
+ }
+
+ .from-\\[50\\%\\] {
+ --tw-gradient-from-position: 50%;
+ }
+
+ .from-\\[50px\\] {
+ --tw-gradient-from-position: 50px;
+ }
+
+ .from-\\[length\\:--my-position\\], .from-\\[percentage\\:--my-position\\] {
+ --tw-gradient-from-position: var(--my-position);
+ }
+
+ @property --tw-gradient-from {
+ syntax: "";
+ inherits: false;
+ initial-value: #0000;
+ }
+
+ @property --tw-gradient-to {
+ syntax: "";
+ inherits: false;
+ initial-value: #0000;
+ }
+
+ @property --tw-gradient-via {
+ syntax: "";
+ inherits: false;
+ initial-value: #0000;
+ }
+
+ @property --tw-gradient-stops {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-gradient-via-stops {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-gradient-from-position {
+ syntax: "";
+ inherits: false;
+ initial-value: 0%;
+ }
+
+ @property --tw-gradient-via-position {
+ syntax: "";
+ inherits: false;
+ initial-value: 50%;
+ }
+
+ @property --tw-gradient-to-position {
+ syntax: "";
+ inherits: false;
+ initial-value: 100%;
+ }"
+ `)
+ expect(
+ run([
+ 'from',
+ 'from-123',
+
+ // --tw-gradient-from
+ '-from-red-500',
+ '-from-red-500/50',
+ '-from-red-500/[0.5]',
+ '-from-red-500/[50%]',
+ '-from-current',
+ '-from-current/50',
+ '-from-current/[0.5]',
+ '-from-current/[50%]',
+ '-from-transparent',
+ '-from-[#0088cc]',
+ '-from-[#0088cc]/50',
+ '-from-[#0088cc]/[0.5]',
+ '-from-[#0088cc]/[50%]',
+
+ // --tw-gradient-from-position
+ '-from-0%',
+ '-from-5%',
+ '-from-100%',
+ ]),
+ ).toEqual('')
+})
+
+test('via', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ [
+ // --tw-gradient-stops
+ 'via-red-500',
+ 'via-red-500/50',
+ 'via-red-500/[0.5]',
+ 'via-red-500/[50%]',
+ 'via-current',
+ 'via-current/50',
+ 'via-current/[0.5]',
+ 'via-current/[50%]',
+ 'via-transparent',
+ 'via-[#0088cc]',
+ 'via-[#0088cc]/50',
+ 'via-[#0088cc]/[0.5]',
+ 'via-[#0088cc]/[50%]',
+ 'via-[--my-color]',
+ 'via-[--my-color]/50',
+ 'via-[--my-color]/[0.5]',
+ 'via-[--my-color]/[50%]',
+ 'via-[color:--my-color]',
+ 'via-[color:--my-color]/50',
+ 'via-[color:--my-color]/[0.5]',
+ 'via-[color:--my-color]/[50%]',
+
+ // --tw-gradient-via-position
+ 'via-0%',
+ 'via-5%',
+ 'via-100%',
+ 'via-[50%]',
+ 'via-[50px]',
+ 'via-[length:--my-position]',
+ 'via-[percentage:--my-position]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ }
+
+ .via-\\[\\#0088cc\\] {
+ --tw-gradient-via: #08c;
+ --tw-gradient-via-stops: var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-via-stops);
+ }
+
+ .via-\\[\\#0088cc\\]\\/50, .via-\\[\\#0088cc\\]\\/\\[0\\.5\\], .via-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ --tw-gradient-via: #0088cc80;
+ --tw-gradient-via-stops: var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-via-stops);
+ }
+
+ .via-\\[--my-color\\] {
+ --tw-gradient-via: var(--my-color);
+ --tw-gradient-via-stops: var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-via-stops);
+ }
+
+ .via-\\[--my-color\\]\\/50, .via-\\[--my-color\\]\\/\\[0\\.5\\], .via-\\[--my-color\\]\\/\\[50\\%\\] {
+ --tw-gradient-via: color-mix(in srgb, var(--my-color) 50%, transparent);
+ --tw-gradient-via-stops: var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-via-stops);
+ }
+
+ .via-\\[color\\:--my-color\\] {
+ --tw-gradient-via: var(--my-color);
+ --tw-gradient-via-stops: var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-via-stops);
+ }
+
+ .via-\\[color\\:--my-color\\]\\/50, .via-\\[color\\:--my-color\\]\\/\\[0\\.5\\], .via-\\[color\\:--my-color\\]\\/\\[50\\%\\] {
+ --tw-gradient-via: color-mix(in srgb, var(--my-color) 50%, transparent);
+ --tw-gradient-via-stops: var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-via-stops);
+ }
+
+ .via-current {
+ --tw-gradient-via: currentColor;
+ --tw-gradient-via-stops: var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-via-stops);
+ }
+
+ .via-current\\/50, .via-current\\/\\[0\\.5\\], .via-current\\/\\[50\\%\\] {
+ --tw-gradient-via: color-mix(in srgb, currentColor 50%, transparent);
+ --tw-gradient-via-stops: var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-via-stops);
+ }
+
+ .via-red-500 {
+ --tw-gradient-via: #ef4444;
+ --tw-gradient-via-stops: var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-via-stops);
+ }
+
+ .via-red-500\\/50, .via-red-500\\/\\[0\\.5\\], .via-red-500\\/\\[50\\%\\] {
+ --tw-gradient-via: #ef444480;
+ --tw-gradient-via-stops: var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-via-stops);
+ }
+
+ .via-transparent {
+ --tw-gradient-via: transparent;
+ --tw-gradient-via-stops: var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-via-stops);
+ }
+
+ .via-0\\% {
+ --tw-gradient-via-position: 0%;
+ }
+
+ .via-100\\% {
+ --tw-gradient-via-position: 100%;
+ }
+
+ .via-5\\% {
+ --tw-gradient-via-position: 5%;
+ }
+
+ .via-\\[50\\%\\] {
+ --tw-gradient-via-position: 50%;
+ }
+
+ .via-\\[50px\\] {
+ --tw-gradient-via-position: 50px;
+ }
+
+ .via-\\[length\\:--my-position\\], .via-\\[percentage\\:--my-position\\] {
+ --tw-gradient-via-position: var(--my-position);
+ }
+
+ @property --tw-gradient-from {
+ syntax: "";
+ inherits: false;
+ initial-value: #0000;
+ }
+
+ @property --tw-gradient-to {
+ syntax: "";
+ inherits: false;
+ initial-value: #0000;
+ }
+
+ @property --tw-gradient-via {
+ syntax: "";
+ inherits: false;
+ initial-value: #0000;
+ }
+
+ @property --tw-gradient-stops {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-gradient-via-stops {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-gradient-from-position {
+ syntax: "";
+ inherits: false;
+ initial-value: 0%;
+ }
+
+ @property --tw-gradient-via-position {
+ syntax: "";
+ inherits: false;
+ initial-value: 50%;
+ }
+
+ @property --tw-gradient-to-position {
+ syntax: "";
+ inherits: false;
+ initial-value: 100%;
+ }"
+ `)
+ expect(
+ run([
+ 'via',
+ // --tw-gradient-stops
+ '-via-red-500',
+ '-via-red-500/50',
+ '-via-red-500/[0.5]',
+ '-via-red-500/[50%]',
+ '-via-current',
+ '-via-current/50',
+ '-via-current/[0.5]',
+ '-via-current/[50%]',
+ '-via-transparent',
+ '-via-[#0088cc]',
+ '-via-[#0088cc]/50',
+ '-via-[#0088cc]/[0.5]',
+ '-via-[#0088cc]/[50%]',
+
+ // --tw-gradient-via-position
+ '-via-0%',
+ '-via-5%',
+ '-via-100%',
+ ]),
+ ).toEqual('')
+})
+
+test('to', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ [
+ // --tw-gradient-to
+ 'to-red-500',
+ 'to-red-500/50',
+ 'to-red-500/[0.5]',
+ 'to-red-500/[50%]',
+ 'to-current',
+ 'to-current/50',
+ 'to-current/[0.5]',
+ 'to-current/[50%]',
+ 'to-transparent',
+ 'to-[#0088cc]',
+ 'to-[#0088cc]/50',
+ 'to-[#0088cc]/[0.5]',
+ 'to-[#0088cc]/[50%]',
+ 'to-[--my-color]',
+ 'to-[--my-color]/50',
+ 'to-[--my-color]/[0.5]',
+ 'to-[--my-color]/[50%]',
+ 'to-[color:--my-color]',
+ 'to-[color:--my-color]/50',
+ 'to-[color:--my-color]/[0.5]',
+ 'to-[color:--my-color]/[50%]',
+
+ // --tw-gradient-to-position
+ 'to-0%',
+ 'to-5%',
+ 'to-100%',
+ 'to-[50%]',
+ 'to-[50px]',
+ 'to-[length:--my-position]',
+ 'to-[percentage:--my-position]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ }
+
+ .to-\\[\\#0088cc\\] {
+ --tw-gradient-to: #08c;
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .to-\\[\\#0088cc\\]\\/50, .to-\\[\\#0088cc\\]\\/\\[0\\.5\\], .to-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ --tw-gradient-to: #0088cc80;
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .to-\\[--my-color\\] {
+ --tw-gradient-to: var(--my-color);
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .to-\\[--my-color\\]\\/50, .to-\\[--my-color\\]\\/\\[0\\.5\\], .to-\\[--my-color\\]\\/\\[50\\%\\] {
+ --tw-gradient-to: color-mix(in srgb, var(--my-color) 50%, transparent);
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .to-\\[color\\:--my-color\\] {
+ --tw-gradient-to: var(--my-color);
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .to-\\[color\\:--my-color\\]\\/50, .to-\\[color\\:--my-color\\]\\/\\[0\\.5\\], .to-\\[color\\:--my-color\\]\\/\\[50\\%\\] {
+ --tw-gradient-to: color-mix(in srgb, var(--my-color) 50%, transparent);
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .to-current {
+ --tw-gradient-to: currentColor;
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .to-current\\/50, .to-current\\/\\[0\\.5\\], .to-current\\/\\[50\\%\\] {
+ --tw-gradient-to: color-mix(in srgb, currentColor 50%, transparent);
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .to-red-500 {
+ --tw-gradient-to: #ef4444;
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .to-red-500\\/50, .to-red-500\\/\\[0\\.5\\], .to-red-500\\/\\[50\\%\\] {
+ --tw-gradient-to: #ef444480;
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .to-transparent {
+ --tw-gradient-to: transparent;
+ --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
+ }
+
+ .to-0\\% {
+ --tw-gradient-to-position: 0%;
+ }
+
+ .to-100\\% {
+ --tw-gradient-to-position: 100%;
+ }
+
+ .to-5\\% {
+ --tw-gradient-to-position: 5%;
+ }
+
+ .to-\\[50\\%\\] {
+ --tw-gradient-to-position: 50%;
+ }
+
+ .to-\\[50px\\] {
+ --tw-gradient-to-position: 50px;
+ }
+
+ .to-\\[length\\:--my-position\\], .to-\\[percentage\\:--my-position\\] {
+ --tw-gradient-to-position: var(--my-position);
+ }
+
+ @property --tw-gradient-from {
+ syntax: "";
+ inherits: false;
+ initial-value: #0000;
+ }
+
+ @property --tw-gradient-to {
+ syntax: "";
+ inherits: false;
+ initial-value: #0000;
+ }
+
+ @property --tw-gradient-via {
+ syntax: "";
+ inherits: false;
+ initial-value: #0000;
+ }
+
+ @property --tw-gradient-stops {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-gradient-via-stops {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-gradient-from-position {
+ syntax: "";
+ inherits: false;
+ initial-value: 0%;
+ }
+
+ @property --tw-gradient-via-position {
+ syntax: "";
+ inherits: false;
+ initial-value: 50%;
+ }
+
+ @property --tw-gradient-to-position {
+ syntax: "";
+ inherits: false;
+ initial-value: 100%;
+ }"
+ `)
+ expect(
+ run([
+ 'to',
+ // --tw-gradient-to
+ '-to-red-500',
+ '-to-red-500/50',
+ '-to-red-500/[0.5]',
+ '-to-red-500/[50%]',
+ '-to-current',
+ '-to-current/50',
+ '-to-current/[0.5]',
+ '-to-current/[50%]',
+ '-to-transparent',
+ '-to-[#0088cc]',
+ '-to-[#0088cc]/50',
+ '-to-[#0088cc]/[0.5]',
+ '-to-[#0088cc]/[50%]',
+
+ // --tw-gradient-to-position
+ '-to-0%',
+ '-to-5%',
+ '-to-100%',
+ ]),
+ ).toEqual('')
+})
+
+// @deprecated
+test('decoration-slice', () => {
+ expect(run(['decoration-slice', 'decoration-clone'])).toMatchInlineSnapshot(`
+ ".decoration-clone {
+ -webkit-box-decoration-break: clone;
+ box-decoration-break: clone;
+ }
+
+ .decoration-slice {
+ -webkit-box-decoration-break: slice;
+ box-decoration-break: slice;
+ }"
+ `)
+ expect(run(['decoration', '-decoration-slice', '-decoration-clone'])).toEqual('')
+})
+
+test('box-decoration', () => {
+ expect(run(['box-decoration-slice', 'box-decoration-clone'])).toMatchInlineSnapshot(`
+ ".box-decoration-clone {
+ -webkit-box-decoration-break: clone;
+ box-decoration-break: clone;
+ }
+
+ .box-decoration-slice {
+ -webkit-box-decoration-break: slice;
+ box-decoration-break: slice;
+ }"
+ `)
+ expect(run(['box', 'box-decoration', '-box-decoration-slice', '-box-decoration-clone'])).toEqual(
+ '',
+ )
+})
+
+test('bg-clip', () => {
+ expect(run(['bg-clip-border', 'bg-clip-padding', 'bg-clip-content', 'bg-clip-text']))
+ .toMatchInlineSnapshot(`
+ ".bg-clip-border {
+ background-clip: border-box;
+ }
+
+ .bg-clip-content {
+ background-clip: content-box;
+ }
+
+ .bg-clip-padding {
+ background-clip: padding-box;
+ }
+
+ .bg-clip-text {
+ background-clip: text;
+ }"
+ `)
+ expect(
+ run(['bg-clip', '-bg-clip-border', '-bg-clip-padding', '-bg-clip-content', '-bg-clip-text']),
+ ).toEqual('')
+})
+
+test('bg-origin', () => {
+ expect(run(['bg-origin-border', 'bg-origin-padding', 'bg-origin-content']))
+ .toMatchInlineSnapshot(`
+ ".bg-origin-border {
+ background-origin: border-box;
+ }
+
+ .bg-origin-content {
+ background-origin: content-box;
+ }
+
+ .bg-origin-padding {
+ background-origin: padding-box;
+ }"
+ `)
+ expect(
+ run(['bg-origin', '-bg-origin-border', '-bg-origin-padding', '-bg-origin-content']),
+ ).toEqual('')
+})
+
+test('bg-blend', () => {
+ expect(
+ run([
+ 'bg-blend-normal',
+ 'bg-blend-multiply',
+ 'bg-blend-screen',
+ 'bg-blend-overlay',
+ 'bg-blend-darken',
+ 'bg-blend-lighten',
+ 'bg-blend-color-dodge',
+ 'bg-blend-color-burn',
+ 'bg-blend-hard-light',
+ 'bg-blend-soft-light',
+ 'bg-blend-difference',
+ 'bg-blend-exclusion',
+ 'bg-blend-hue',
+ 'bg-blend-saturation',
+ 'bg-blend-color',
+ 'bg-blend-luminosity',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".bg-blend-color {
+ background-blend-mode: color;
+ }
+
+ .bg-blend-color-burn {
+ background-blend-mode: color-burn;
+ }
+
+ .bg-blend-color-dodge {
+ background-blend-mode: color-dodge;
+ }
+
+ .bg-blend-darken {
+ background-blend-mode: darken;
+ }
+
+ .bg-blend-difference {
+ background-blend-mode: difference;
+ }
+
+ .bg-blend-exclusion {
+ background-blend-mode: exclusion;
+ }
+
+ .bg-blend-hard-light {
+ background-blend-mode: hard-light;
+ }
+
+ .bg-blend-hue {
+ background-blend-mode: hue;
+ }
+
+ .bg-blend-lighten {
+ background-blend-mode: lighten;
+ }
+
+ .bg-blend-luminosity {
+ background-blend-mode: luminosity;
+ }
+
+ .bg-blend-multiply {
+ background-blend-mode: multiply;
+ }
+
+ .bg-blend-normal {
+ background-blend-mode: normal;
+ }
+
+ .bg-blend-overlay {
+ background-blend-mode: overlay;
+ }
+
+ .bg-blend-saturation {
+ background-blend-mode: saturation;
+ }
+
+ .bg-blend-screen {
+ background-blend-mode: screen;
+ }
+
+ .bg-blend-soft-light {
+ background-blend-mode: soft-light;
+ }"
+ `)
+ expect(
+ run([
+ 'bg-blend',
+ '-bg-blend-normal',
+ '-bg-blend-multiply',
+ '-bg-blend-screen',
+ '-bg-blend-overlay',
+ '-bg-blend-darken',
+ '-bg-blend-lighten',
+ '-bg-blend-color-dodge',
+ '-bg-blend-color-burn',
+ '-bg-blend-hard-light',
+ '-bg-blend-soft-light',
+ '-bg-blend-difference',
+ '-bg-blend-exclusion',
+ '-bg-blend-hue',
+ '-bg-blend-saturation',
+ '-bg-blend-color',
+ '-bg-blend-luminosity',
+ ]),
+ ).toEqual('')
+})
+
+test('mix-blend', () => {
+ expect(
+ run([
+ 'mix-blend-normal',
+ 'mix-blend-multiply',
+ 'mix-blend-screen',
+ 'mix-blend-overlay',
+ 'mix-blend-darken',
+ 'mix-blend-lighten',
+ 'mix-blend-color-dodge',
+ 'mix-blend-color-burn',
+ 'mix-blend-hard-light',
+ 'mix-blend-soft-light',
+ 'mix-blend-difference',
+ 'mix-blend-exclusion',
+ 'mix-blend-hue',
+ 'mix-blend-saturation',
+ 'mix-blend-color',
+ 'mix-blend-luminosity',
+ 'mix-blend-plus-darker',
+ 'mix-blend-plus-lighter',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".mix-blend-color {
+ mix-blend-mode: color;
+ }
+
+ .mix-blend-color-burn {
+ mix-blend-mode: color-burn;
+ }
+
+ .mix-blend-color-dodge {
+ mix-blend-mode: color-dodge;
+ }
+
+ .mix-blend-darken {
+ mix-blend-mode: darken;
+ }
+
+ .mix-blend-difference {
+ mix-blend-mode: difference;
+ }
+
+ .mix-blend-exclusion {
+ mix-blend-mode: exclusion;
+ }
+
+ .mix-blend-hard-light {
+ mix-blend-mode: hard-light;
+ }
+
+ .mix-blend-hue {
+ mix-blend-mode: hue;
+ }
+
+ .mix-blend-lighten {
+ mix-blend-mode: lighten;
+ }
+
+ .mix-blend-luminosity {
+ mix-blend-mode: luminosity;
+ }
+
+ .mix-blend-multiply {
+ mix-blend-mode: multiply;
+ }
+
+ .mix-blend-normal {
+ mix-blend-mode: normal;
+ }
+
+ .mix-blend-overlay {
+ mix-blend-mode: overlay;
+ }
+
+ .mix-blend-plus-darker {
+ mix-blend-mode: plus-darker;
+ }
+
+ .mix-blend-plus-lighter {
+ mix-blend-mode: plus-lighter;
+ }
+
+ .mix-blend-saturation {
+ mix-blend-mode: saturation;
+ }
+
+ .mix-blend-screen {
+ mix-blend-mode: screen;
+ }
+
+ .mix-blend-soft-light {
+ mix-blend-mode: soft-light;
+ }"
+ `)
+ expect(
+ run([
+ 'mix-blend',
+ '-mix-blend-normal',
+ '-mix-blend-multiply',
+ '-mix-blend-screen',
+ '-mix-blend-overlay',
+ '-mix-blend-darken',
+ '-mix-blend-lighten',
+ '-mix-blend-color-dodge',
+ '-mix-blend-color-burn',
+ '-mix-blend-hard-light',
+ '-mix-blend-soft-light',
+ '-mix-blend-difference',
+ '-mix-blend-exclusion',
+ '-mix-blend-hue',
+ '-mix-blend-saturation',
+ '-mix-blend-color',
+ '-mix-blend-luminosity',
+ '-mix-blend-plus-lighter',
+ ]),
+ ).toEqual('')
+})
+
+test('fill', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'fill-red-500',
+ 'fill-red-500/50',
+ 'fill-red-500/[0.5]',
+ 'fill-red-500/[50%]',
+ 'fill-current',
+ 'fill-current/50',
+ 'fill-current/[0.5]',
+ 'fill-current/[50%]',
+ 'fill-transparent',
+ 'fill-[#0088cc]',
+ 'fill-[#0088cc]/50',
+ 'fill-[#0088cc]/[0.5]',
+ 'fill-[#0088cc]/[50%]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ }
+
+ .fill-\\[\\#0088cc\\] {
+ fill: #08c;
+ }
+
+ .fill-\\[\\#0088cc\\]\\/50, .fill-\\[\\#0088cc\\]\\/\\[0\\.5\\], .fill-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ fill: #0088cc80;
+ }
+
+ .fill-current {
+ fill: currentColor;
+ }
+
+ .fill-current\\/50, .fill-current\\/\\[0\\.5\\], .fill-current\\/\\[50\\%\\] {
+ fill: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ .fill-red-500 {
+ fill: #ef4444;
+ }
+
+ .fill-red-500\\/50, .fill-red-500\\/\\[0\\.5\\], .fill-red-500\\/\\[50\\%\\] {
+ fill: #ef444480;
+ }
+
+ .fill-transparent {
+ fill: #0000;
+ }"
+ `)
+ expect(
+ run([
+ 'fill',
+ '-fill-red-500',
+ '-fill-red-500/50',
+ '-fill-red-500/[0.5]',
+ '-fill-red-500/[50%]',
+ '-fill-current',
+ '-fill-current/50',
+ '-fill-current/[0.5]',
+ '-fill-current/[50%]',
+ '-fill-transparent',
+ '-fill-[#0088cc]',
+ '-fill-[#0088cc]/50',
+ '-fill-[#0088cc]/[0.5]',
+ '-fill-[#0088cc]/[50%]',
+ ]),
+ ).toEqual('')
+})
+
+test('stroke', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ [
+ // Color
+ 'stroke-red-500',
+ 'stroke-red-500/50',
+ 'stroke-red-500/[0.5]',
+ 'stroke-red-500/[50%]',
+ 'stroke-current',
+ 'stroke-current/50',
+ 'stroke-current/[0.5]',
+ 'stroke-current/[50%]',
+ 'stroke-transparent',
+ 'stroke-[#0088cc]',
+ 'stroke-[#0088cc]/50',
+ 'stroke-[#0088cc]/[0.5]',
+ 'stroke-[#0088cc]/[50%]',
+ 'stroke-[--my-color]',
+ 'stroke-[--my-color]/50',
+ 'stroke-[--my-color]/[0.5]',
+ 'stroke-[--my-color]/[50%]',
+ 'stroke-[color:--my-color]',
+ 'stroke-[color:--my-color]/50',
+ 'stroke-[color:--my-color]/[0.5]',
+ 'stroke-[color:--my-color]/[50%]',
+ 'stroke-none',
+
+ // Width
+ 'stroke-0',
+ 'stroke-1',
+ 'stroke-2',
+ 'stroke-[1.5]',
+ 'stroke-[12px]',
+ 'stroke-[50%]',
+ 'stroke-[number:--my-width]',
+ 'stroke-[length:--my-width]',
+ 'stroke-[percentage:--my-width]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ }
+
+ .stroke-\\[\\#0088cc\\] {
+ stroke: #08c;
+ }
+
+ .stroke-\\[\\#0088cc\\]\\/50, .stroke-\\[\\#0088cc\\]\\/\\[0\\.5\\], .stroke-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ stroke: #0088cc80;
+ }
+
+ .stroke-\\[--my-color\\] {
+ stroke: var(--my-color);
+ }
+
+ .stroke-\\[--my-color\\]\\/50, .stroke-\\[--my-color\\]\\/\\[0\\.5\\], .stroke-\\[--my-color\\]\\/\\[50\\%\\] {
+ stroke: color-mix(in srgb, var(--my-color) 50%, transparent);
+ }
+
+ .stroke-\\[color\\:--my-color\\] {
+ stroke: var(--my-color);
+ }
+
+ .stroke-\\[color\\:--my-color\\]\\/50, .stroke-\\[color\\:--my-color\\]\\/\\[0\\.5\\], .stroke-\\[color\\:--my-color\\]\\/\\[50\\%\\] {
+ stroke: color-mix(in srgb, var(--my-color) 50%, transparent);
+ }
+
+ .stroke-current {
+ stroke: currentColor;
+ }
+
+ .stroke-current\\/50, .stroke-current\\/\\[0\\.5\\], .stroke-current\\/\\[50\\%\\] {
+ stroke: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ .stroke-none {
+ stroke: none;
+ }
+
+ .stroke-red-500 {
+ stroke: #ef4444;
+ }
+
+ .stroke-red-500\\/50, .stroke-red-500\\/\\[0\\.5\\], .stroke-red-500\\/\\[50\\%\\] {
+ stroke: #ef444480;
+ }
+
+ .stroke-transparent {
+ stroke: #0000;
+ }
+
+ .stroke-0 {
+ stroke-width: 0;
+ }
+
+ .stroke-1 {
+ stroke-width: 1px;
+ }
+
+ .stroke-2 {
+ stroke-width: 2px;
+ }
+
+ .stroke-\\[1\\.5\\] {
+ stroke-width: 1.5px;
+ }
+
+ .stroke-\\[12px\\] {
+ stroke-width: 12px;
+ }
+
+ .stroke-\\[50\\%\\] {
+ stroke-width: 50%;
+ }
+
+ .stroke-\\[length\\:--my-width\\], .stroke-\\[number\\:--my-width\\], .stroke-\\[percentage\\:--my-width\\] {
+ stroke-width: var(--my-width);
+ }"
+ `)
+ expect(
+ run([
+ 'stroke',
+ // Color
+ '-stroke-red-500',
+ '-stroke-red-500/50',
+ '-stroke-red-500/[0.5]',
+ '-stroke-red-500/[50%]',
+ '-stroke-current',
+ '-stroke-current/50',
+ '-stroke-current/[0.5]',
+ '-stroke-current/[50%]',
+ '-stroke-transparent',
+ '-stroke-[#0088cc]',
+ '-stroke-[#0088cc]/50',
+ '-stroke-[#0088cc]/[0.5]',
+ '-stroke-[#0088cc]/[50%]',
+
+ // Width
+ '-stroke-0',
+ ]),
+ ).toEqual('')
+})
+
+test('object', () => {
+ expect(
+ run([
+ // object-fit
+ 'object-contain',
+ 'object-cover',
+ 'object-fill',
+ 'object-none',
+ 'object-scale-down',
+
+ // object-position
+ 'object-[--value]',
+ 'object-bottom',
+ 'object-center',
+ 'object-left',
+ 'object-left-bottom',
+ 'object-left-top',
+ 'object-right',
+ 'object-right-bottom',
+ 'object-right-top',
+ 'object-top',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".object-contain {
+ object-fit: contain;
+ }
+
+ .object-cover {
+ object-fit: cover;
+ }
+
+ .object-fill {
+ object-fit: fill;
+ }
+
+ .object-none {
+ object-fit: none;
+ }
+
+ .object-scale-down {
+ object-fit: scale-down;
+ }
+
+ .object-\\[--value\\] {
+ object-position: var(--value);
+ }
+
+ .object-bottom {
+ object-position: bottom;
+ }
+
+ .object-center {
+ object-position: center;
+ }
+
+ .object-left {
+ object-position: left;
+ }
+
+ .object-left-bottom {
+ object-position: left bottom;
+ }
+
+ .object-left-top {
+ object-position: left top;
+ }
+
+ .object-right {
+ object-position: right;
+ }
+
+ .object-right-bottom {
+ object-position: right bottom;
+ }
+
+ .object-right-top {
+ object-position: right top;
+ }
+
+ .object-top {
+ object-position: top;
+ }"
+ `)
+ expect(
+ run([
+ 'object',
+ // object-fit
+ '-object-contain',
+ '-object-cover',
+ '-object-fill',
+ '-object-none',
+ '-object-scale-down',
+
+ // object-position
+ '-object-[--value]',
+ '-object-bottom',
+ ]),
+ ).toEqual('')
+})
+
+test('p', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['p-4', 'p-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .p-4 {
+ padding: 1rem;
+ }
+
+ .p-\\[4px\\] {
+ padding: 4px;
+ }"
+ `)
+ expect(run(['p', '-p-4', '-p-[4px]'])).toEqual('')
+})
+
+test('px', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['px-4', 'px-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .px-4 {
+ padding-left: 1rem;
+ padding-right: 1rem;
+ }
+
+ .px-\\[4px\\] {
+ padding-left: 4px;
+ padding-right: 4px;
+ }"
+ `)
+ expect(run(['px', '-px-4', '-px-[4px]'])).toEqual('')
+})
+
+test('py', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['py-4', 'py-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .py-4 {
+ padding-top: 1rem;
+ padding-bottom: 1rem;
+ }
+
+ .py-\\[4px\\] {
+ padding-top: 4px;
+ padding-bottom: 4px;
+ }"
+ `)
+ expect(run(['py', '-py-4', '-py-[4px]'])).toEqual('')
+})
+
+test('pt', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['pt-4', 'pt-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .pt-4 {
+ padding-top: 1rem;
+ }
+
+ .pt-\\[4px\\] {
+ padding-top: 4px;
+ }"
+ `)
+ expect(run(['pt', '-pt-4', '-pt-[4px]'])).toEqual('')
+})
+
+test('ps', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['ps-4', 'ps-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .ps-4 {
+ padding-inline-start: 1rem;
+ }
+
+ .ps-\\[4px\\] {
+ padding-inline-start: 4px;
+ }"
+ `)
+ expect(run(['ps', '-ps-4', '-ps-[4px]'])).toEqual('')
+})
+
+test('pe', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['pe-4', 'pe-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .pe-4 {
+ padding-inline-end: 1rem;
+ }
+
+ .pe-\\[4px\\] {
+ padding-inline-end: 4px;
+ }"
+ `)
+ expect(run(['pe', '-pe-4', '-pe-[4px]'])).toEqual('')
+})
+
+test('pr', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['pr-4', 'pr-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .pr-4 {
+ padding-right: 1rem;
+ }
+
+ .pr-\\[4px\\] {
+ padding-right: 4px;
+ }"
+ `)
+ expect(run(['pr', '-pr-4', '-pr-[4px]'])).toEqual('')
+})
+
+test('pb', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['pb-4', 'pb-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .pb-4 {
+ padding-bottom: 1rem;
+ }
+
+ .pb-\\[4px\\] {
+ padding-bottom: 4px;
+ }"
+ `)
+ expect(run(['pb', '-pb-4', '-pb-[4px]'])).toEqual('')
+})
+
+test('pl', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --spacing-4: 1rem;
+ }
+ @tailwind utilities;
+ `,
+ ['pl-4', 'pl-[4px]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --spacing-4: 1rem;
+ }
+
+ .pl-4 {
+ padding-left: 1rem;
+ }
+
+ .pl-\\[4px\\] {
+ padding-left: 4px;
+ }"
+ `)
+ expect(run(['pl', '-pl-4', '-pl-[4px]'])).toEqual('')
+})
+
+test('text-align', () => {
+ expect(run(['text-left', 'text-center', 'text-right', 'text-justify', 'text-start', 'text-end']))
+ .toMatchInlineSnapshot(`
+ ".text-center {
+ text-align: center;
+ }
+
+ .text-end {
+ text-align: end;
+ }
+
+ .text-justify {
+ text-align: justify;
+ }
+
+ .text-left {
+ text-align: left;
+ }
+
+ .text-right {
+ text-align: right;
+ }
+
+ .text-start {
+ text-align: start;
+ }"
+ `)
+ expect(
+ run(['-text-left', '-text-center', '-text-right', '-text-justify', '-text-start', '-text-end']),
+ ).toEqual('')
+})
+
+test('indent', () => {
+ expect(run(['indent-[4px]', '-indent-[4px]'])).toMatchInlineSnapshot(`
+ ".-indent-\\[4px\\] {
+ text-indent: -4px;
+ }
+
+ .indent-\\[4px\\] {
+ text-indent: 4px;
+ }"
+ `)
+ expect(run(['indent'])).toEqual('')
+})
+
+test('align', () => {
+ expect(
+ run([
+ 'align-baseline',
+ 'align-top',
+ 'align-middle',
+ 'align-bottom',
+ 'align-text-top',
+ 'align-text-bottom',
+ 'align-sub',
+ 'align-super',
+
+ 'align-[--value]',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".align-\\[--value\\] {
+ vertical-align: var(--value);
+ }
+
+ .align-baseline {
+ vertical-align: baseline;
+ }
+
+ .align-bottom {
+ vertical-align: bottom;
+ }
+
+ .align-middle {
+ vertical-align: middle;
+ }
+
+ .align-sub {
+ vertical-align: sub;
+ }
+
+ .align-super {
+ vertical-align: super;
+ }
+
+ .align-text-bottom {
+ vertical-align: text-bottom;
+ }
+
+ .align-text-top {
+ vertical-align: text-top;
+ }
+
+ .align-top {
+ vertical-align: top;
+ }"
+ `)
+ expect(
+ run([
+ 'align',
+ '-align-baseline',
+ '-align-top',
+ '-align-middle',
+ '-align-bottom',
+ '-align-text-top',
+ '-align-text-bottom',
+ '-align-sub',
+ '-align-super',
+
+ '-align-[--value]',
+ ]),
+ ).toEqual('')
+})
+
+test('font', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --font-family-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
+ 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
+ }
+ @tailwind utilities;
+ `,
+ [
+ // font-family
+ 'font-sans',
+ 'font-["arial_rounded"]',
+ 'font-[ui-sans-serif]',
+ 'font-[--my-family]',
+ 'font-[family-name:--my-family]',
+ 'font-[generic-name:--my-family]',
+
+ // font-weight
+ 'font-bold',
+ 'font-[100]',
+ 'font-[number:--my-weight]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --font-family-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ }
+
+ .font-\\[\\"arial_rounded\\"\\] {
+ font-family: arial rounded;
+ }
+
+ .font-\\[family-name\\:--my-family\\], .font-\\[generic-name\\:--my-family\\] {
+ font-family: var(--my-family);
+ }
+
+ .font-\\[ui-sans-serif\\] {
+ font-family: ui-sans-serif;
+ }
+
+ .font-sans {
+ font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
+ }
+
+ .font-\\[--my-family\\] {
+ font-weight: var(--my-family);
+ }
+
+ .font-\\[100\\] {
+ font-weight: 100;
+ }
+
+ .font-\\[number\\:--my-weight\\] {
+ font-weight: var(--my-weight);
+ }
+
+ .font-bold {
+ font-weight: 700;
+ }"
+ `)
+ expect(
+ run([
+ 'font',
+ // font-family
+ '-font-sans',
+
+ // font-weight
+ '-font-bold',
+ ]),
+ ).toEqual('')
+})
+
+test('text-transform', () => {
+ expect(run(['uppercase', 'lowercase', 'capitalize', 'normal-case'])).toMatchInlineSnapshot(`
+ ".capitalize {
+ text-transform: capitalize;
+ }
+
+ .lowercase {
+ text-transform: lowercase;
+ }
+
+ .normal-case {
+ text-transform: none;
+ }
+
+ .uppercase {
+ text-transform: uppercase;
+ }"
+ `)
+ expect(run(['-uppercase', '-lowercase', '-capitalize', '-normal-case'])).toEqual('')
+})
+
+test('font-style', () => {
+ expect(run(['italic', 'not-italic'])).toMatchInlineSnapshot(`
+ ".italic {
+ font-style: italic;
+ }
+
+ .not-italic {
+ font-style: normal;
+ }"
+ `)
+ expect(run(['-italic', '-not-italic'])).toEqual('')
+})
+
+test('text-decoration-line', () => {
+ expect(run(['underline', 'overline', 'line-through', 'no-underline'])).toMatchInlineSnapshot(`
+ ".line-through {
+ text-decoration-line: line-through;
+ }
+
+ .no-underline {
+ text-decoration-line: none;
+ }
+
+ .overline {
+ text-decoration-line: overline;
+ }
+
+ .underline {
+ text-decoration-line: underline;
+ }"
+ `)
+ expect(run(['-underline', '-overline', '-line-through', '-no-underline'])).toEqual('')
+})
+
+test('placeholder', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'placeholder-red-500',
+ 'placeholder-red-500/50',
+ 'placeholder-red-500/[0.5]',
+ 'placeholder-red-500/[50%]',
+ 'placeholder-current',
+ 'placeholder-current/50',
+ 'placeholder-current/[0.5]',
+ 'placeholder-current/[50%]',
+ 'placeholder-transparent',
+ 'placeholder-[#0088cc]',
+ 'placeholder-[#0088cc]/50',
+ 'placeholder-[#0088cc]/[0.5]',
+ 'placeholder-[#0088cc]/[50%]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ }
+
+ .placeholder-\\[\\#0088cc\\]::placeholder {
+ color: #08c;
+ }
+
+ .placeholder-\\[\\#0088cc\\]\\/50::placeholder {
+ color: #0088cc80;
+ }
+
+ .placeholder-\\[\\#0088cc\\]\\/\\[0\\.5\\]::placeholder {
+ color: #0088cc80;
+ }
+
+ .placeholder-\\[\\#0088cc\\]\\/\\[50\\%\\]::placeholder {
+ color: #0088cc80;
+ }
+
+ .placeholder-current::placeholder {
+ color: currentColor;
+ }
+
+ .placeholder-current\\/50::placeholder {
+ color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ .placeholder-current\\/\\[0\\.5\\]::placeholder {
+ color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ .placeholder-current\\/\\[50\\%\\]::placeholder {
+ color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ .placeholder-red-500::placeholder {
+ color: #ef4444;
+ }
+
+ .placeholder-red-500\\/50::placeholder {
+ color: #ef444480;
+ }
+
+ .placeholder-red-500\\/\\[0\\.5\\]::placeholder {
+ color: #ef444480;
+ }
+
+ .placeholder-red-500\\/\\[50\\%\\]::placeholder {
+ color: #ef444480;
+ }
+
+ .placeholder-transparent::placeholder {
+ color: #0000;
+ }"
+ `)
+ expect(
+ run([
+ 'placeholder',
+ '-placeholder-red-500',
+ '-placeholder-red-500/50',
+ '-placeholder-red-500/[0.5]',
+ '-placeholder-red-500/[50%]',
+ '-placeholder-current',
+ '-placeholder-current/50',
+ '-placeholder-current/[0.5]',
+ '-placeholder-current/[50%]',
+ '-placeholder-transparent',
+ '-placeholder-[#0088cc]',
+ '-placeholder-[#0088cc]/50',
+ '-placeholder-[#0088cc]/[0.5]',
+ '-placeholder-[#0088cc]/[50%]',
+ ]),
+ ).toEqual('')
+})
+
+test('decoration', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ [
+ // text-decoration-color
+ 'decoration-red-500',
+ 'decoration-red-500/50',
+ 'decoration-red-500/[0.5]',
+ 'decoration-red-500/[50%]',
+ 'decoration-current',
+ 'decoration-current/50',
+ 'decoration-current/[0.5]',
+ 'decoration-current/[50%]',
+ 'decoration-transparent',
+ 'decoration-[#0088cc]',
+ 'decoration-[#0088cc]/50',
+ 'decoration-[#0088cc]/[0.5]',
+ 'decoration-[#0088cc]/[50%]',
+ 'decoration-[--my-color]',
+ 'decoration-[--my-color]/50',
+ 'decoration-[--my-color]/[0.5]',
+ 'decoration-[--my-color]/[50%]',
+ 'decoration-[color:--my-color]',
+ 'decoration-[color:--my-color]/50',
+ 'decoration-[color:--my-color]/[0.5]',
+ 'decoration-[color:--my-color]/[50%]',
+
+ // text-decoration-style
+ 'decoration-solid',
+ 'decoration-double',
+ 'decoration-dotted',
+ 'decoration-dashed',
+ 'decoration-wavy',
+
+ // text-decoration-thickness
+ 'decoration-auto',
+ 'decoration-from-font',
+ 'decoration-0',
+ 'decoration-1',
+ 'decoration-2',
+ 'decoration-4',
+ 'decoration-123',
+ 'decoration-[12px]',
+ 'decoration-[50%]',
+ 'decoration-[length:--my-thickness]',
+ 'decoration-[percentage:--my-thickness]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ }
+
+ .decoration-\\[\\#0088cc\\] {
+ text-decoration-color: #08c;
+ }
+
+ .decoration-\\[\\#0088cc\\]\\/50, .decoration-\\[\\#0088cc\\]\\/\\[0\\.5\\], .decoration-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ text-decoration-color: #0088cc80;
+ }
+
+ .decoration-\\[--my-color\\] {
+ -webkit-text-decoration-color: var(--my-color);
+ text-decoration-color: var(--my-color);
+ }
+
+ .decoration-\\[--my-color\\]\\/50, .decoration-\\[--my-color\\]\\/\\[0\\.5\\], .decoration-\\[--my-color\\]\\/\\[50\\%\\] {
+ -webkit-text-decoration-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ text-decoration-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ }
+
+ .decoration-\\[color\\:--my-color\\] {
+ -webkit-text-decoration-color: var(--my-color);
+ text-decoration-color: var(--my-color);
+ }
+
+ .decoration-\\[color\\:--my-color\\]\\/50, .decoration-\\[color\\:--my-color\\]\\/\\[0\\.5\\], .decoration-\\[color\\:--my-color\\]\\/\\[50\\%\\] {
+ -webkit-text-decoration-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ text-decoration-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ }
+
+ .decoration-current {
+ text-decoration-color: currentColor;
+ }
+
+ .decoration-current\\/50, .decoration-current\\/\\[0\\.5\\], .decoration-current\\/\\[50\\%\\] {
+ -webkit-text-decoration-color: color-mix(in srgb, currentColor 50%, transparent);
+ text-decoration-color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ .decoration-red-500 {
+ text-decoration-color: #ef4444;
+ }
+
+ .decoration-red-500\\/50, .decoration-red-500\\/\\[0\\.5\\], .decoration-red-500\\/\\[50\\%\\] {
+ text-decoration-color: #ef444480;
+ }
+
+ .decoration-transparent {
+ text-decoration-color: #0000;
+ }
+
+ .decoration-dashed {
+ text-decoration-style: dashed;
+ }
+
+ .decoration-dotted {
+ text-decoration-style: dotted;
+ }
+
+ .decoration-double {
+ text-decoration-style: double;
+ }
+
+ .decoration-solid {
+ text-decoration-style: solid;
+ }
+
+ .decoration-wavy {
+ text-decoration-style: wavy;
+ }
+
+ .decoration-0 {
+ text-decoration-thickness: 0;
+ }
+
+ .decoration-1 {
+ text-decoration-thickness: 1px;
+ }
+
+ .decoration-123 {
+ text-decoration-thickness: 123px;
+ }
+
+ .decoration-2 {
+ text-decoration-thickness: 2px;
+ }
+
+ .decoration-4 {
+ text-decoration-thickness: 4px;
+ }
+
+ .decoration-\\[12px\\] {
+ text-decoration-thickness: 12px;
+ }
+
+ .decoration-\\[50\\%\\] {
+ text-decoration-thickness: calc(1em / 2);
+ }
+
+ .decoration-\\[length\\:--my-thickness\\], .decoration-\\[percentage\\:--my-thickness\\] {
+ text-decoration-thickness: var(--my-thickness);
+ }
+
+ .decoration-auto {
+ text-decoration-thickness: auto;
+ }
+
+ .decoration-from-font {
+ text-decoration-thickness: from-font;
+ }"
+ `)
+ expect(
+ run([
+ 'decoration',
+ // text-decoration-color
+ '-decoration-red-500',
+ '-decoration-red-500/50',
+ '-decoration-red-500/[0.5]',
+ '-decoration-red-500/[50%]',
+ '-decoration-current',
+ '-decoration-current/50',
+ '-decoration-current/[0.5]',
+ '-decoration-current/[50%]',
+ '-decoration-transparent',
+ '-decoration-[#0088cc]',
+ '-decoration-[#0088cc]/50',
+ '-decoration-[#0088cc]/[0.5]',
+ '-decoration-[#0088cc]/[50%]',
+
+ // text-decoration-style
+ '-decoration-solid',
+ '-decoration-double',
+ '-decoration-dotted',
+ '-decoration-dashed',
+ '-decoration-wavy',
+
+ // text-decoration-thickness
+ '-decoration-auto',
+ '-decoration-from-font',
+ '-decoration-0',
+ '-decoration-1',
+ '-decoration-2',
+ '-decoration-4',
+ '-decoration-123',
+ ]),
+ ).toEqual('')
+})
+
+test('animate', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --animate-spin: spin 1s linear infinite;
+ }
+ @tailwind utilities;
+ `,
+ ['animate-spin', 'animate-none', 'animate-[bounce_1s_infinite]', 'animate-not-found'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --animate-spin: spin 1s linear infinite;
+ }
+
+ .animate-\\[bounce_1s_infinite\\] {
+ animation: 1s infinite bounce;
+ }
+
+ .animate-none {
+ animation: none;
+ }
+
+ .animate-spin {
+ animation: 1s linear infinite spin;
+ }"
+ `)
+ expect(
+ run([
+ 'animate',
+ '-animate-spin',
+ '-animate-none',
+ '-animate-[bounce_1s_infinite]',
+ '-animate-not-found',
+ ]),
+ ).toEqual('')
+})
+
+test('filter', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --blur-xl: 24px;
+ --drop-shadow: 0 1px 2px rgb(0 0 0 / 0.1), 0 1px 1px rgb(0 0 0 / 0.06);
+ --drop-shadow-xl: 0 20px 13px rgb(0 0 0 / 0.03), 0 8px 5px rgb(0 0 0 / 0.08);
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'filter',
+ 'filter-none',
+ 'filter-[--value]',
+ 'blur-xl',
+ 'blur-[4px]',
+ 'brightness-50',
+ 'brightness-[1.23]',
+ 'contrast-50',
+ 'contrast-[1.23]',
+ 'grayscale',
+ 'grayscale-0',
+ 'grayscale-[--value]',
+ 'hue-rotate-15',
+ 'hue-rotate-[45deg]',
+ 'invert',
+ 'invert-0',
+ 'invert-[--value]',
+ 'drop-shadow-xl',
+ 'drop-shadow-[0_0_red]',
+ 'saturate-0',
+ 'saturate-[1.75]',
+ 'saturate-[--value]',
+ 'sepia',
+ 'sepia-0',
+ 'sepia-[50%]',
+ 'sepia-[--value]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --blur-xl: 24px;
+ --drop-shadow: 0 1px 2px #0000001a, 0 1px 1px #0000000f;
+ --drop-shadow-xl: 0 20px 13px #00000008, 0 8px 5px #00000014;
+ }
+
+ .blur-\\[4px\\] {
+ --tw-blur: blur(4px);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .blur-xl {
+ --tw-blur: blur(24px);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .brightness-50 {
+ --tw-brightness: brightness(50%);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .brightness-\\[1\\.23\\] {
+ --tw-brightness: brightness(1.23);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .drop-shadow-\\[0_0_red\\] {
+ --tw-drop-shadow: drop-shadow(0 0 red);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .drop-shadow-xl {
+ --tw-drop-shadow: drop-shadow(0 20px 13px #00000008) drop-shadow(0 8px 5px #00000014);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .grayscale {
+ --tw-grayscale: grayscale(100%);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .grayscale-0 {
+ --tw-grayscale: grayscale(0%);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .grayscale-\\[--value\\] {
+ --tw-grayscale: grayscale(var(--value));
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .hue-rotate-15 {
+ --tw-hue-rotate: hue-rotate(15deg);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .hue-rotate-\\[45deg\\] {
+ --tw-hue-rotate: hue-rotate(45deg);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .invert {
+ --tw-invert: invert(100%);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .invert-0 {
+ --tw-invert: invert(0%);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .invert-\\[--value\\] {
+ --tw-invert: invert(var(--value));
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .saturate-0 {
+ --tw-saturate: saturate(0%);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .saturate-\\[--value\\] {
+ --tw-saturate: saturate(var(--value));
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .saturate-\\[1\\.75\\] {
+ --tw-saturate: saturate(1.75);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .sepia {
+ --tw-sepia: sepia(100%);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .sepia-0 {
+ --tw-sepia: sepia(0%);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .sepia-\\[--value\\] {
+ --tw-sepia: sepia(var(--value));
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .sepia-\\[50\\%\\] {
+ --tw-sepia: sepia(50%);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .contrast-50 {
+ --tw-contrast: contrast(50%);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .contrast-\\[1\\.23\\] {
+ --tw-contrast: contrast(1.23);
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .filter {
+ filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
+ }
+
+ .filter-\\[--value\\] {
+ filter: var(--value);
+ }
+
+ .filter-none {
+ filter: none;
+ }
+
+ @property --tw-blur {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-brightness {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-contrast {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-grayscale {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-hue-rotate {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-invert {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-opacity {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-saturate {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-sepia {
+ syntax: "*";
+ inherits: false
+ }"
+ `)
+ expect(
+ run([
+ '-filter',
+ '-filter-none',
+ '-filter-[--value]',
+ '-blur-xl',
+ '-blur-[4px]',
+ '-brightness-50',
+ '-brightness-[1.23]',
+ '-contrast-50',
+ '-contrast-[1.23]',
+ '-grayscale',
+ '-grayscale-0',
+ '-grayscale-[--value]',
+ '-hue-rotate-15',
+ '-hue-rotate-[45deg]',
+ '-invert',
+ '-invert-0',
+ '-invert-[--value]',
+ '-drop-shadow-xl',
+ '-drop-shadow-[0_0_red]',
+ '-saturate-0',
+ '-saturate-[1.75]',
+ '-saturate-[--value]',
+ '-sepia',
+ '-sepia-0',
+ '-sepia-[50%]',
+ '-sepia-[--value]',
+ ]),
+ ).toEqual('')
+})
+
+test('backdrop-filter', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --blur-xl: 24px;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'backdrop-filter',
+ 'backdrop-filter-none',
+ 'backdrop-filter-[--value]',
+ 'backdrop-blur-xl',
+ 'backdrop-blur-[4px]',
+ 'backdrop-brightness-50',
+ 'backdrop-brightness-[1.23]',
+ 'backdrop-contrast-50',
+ 'backdrop-contrast-[1.23]',
+ 'backdrop-grayscale',
+ 'backdrop-grayscale-0',
+ 'backdrop-grayscale-[--value]',
+ 'backdrop-hue-rotate-15',
+ 'backdrop-hue-rotate-[45deg]',
+ 'backdrop-invert',
+ 'backdrop-invert-0',
+ 'backdrop-invert-[--value]',
+ 'backdrop-opacity-50',
+ 'backdrop-opacity-71',
+ 'backdrop-opacity-[0.5]',
+ 'backdrop-saturate-0',
+ 'backdrop-saturate-[1.75]',
+ 'backdrop-saturate-[--value]',
+ 'backdrop-sepia',
+ 'backdrop-sepia-0',
+ 'backdrop-sepia-[50%]',
+ 'backdrop-sepia-[--value]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --blur-xl: 24px;
+ }
+
+ .backdrop-blur-\\[4px\\] {
+ --tw-backdrop-blur: blur(4px);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-blur-xl {
+ --tw-backdrop-blur: blur(24px);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-brightness-50 {
+ --tw-backdrop-brightness: brightness(50%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-brightness-\\[1\\.23\\] {
+ --tw-backdrop-brightness: brightness(1.23);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-grayscale {
+ --tw-backdrop-grayscale: grayscale(100%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-grayscale-0 {
+ --tw-backdrop-grayscale: grayscale(0%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-grayscale-\\[--value\\] {
+ --tw-backdrop-grayscale: grayscale(var(--value));
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-hue-rotate-15 {
+ --tw-backdrop-hue-rotate: hue-rotate(15deg);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-hue-rotate-\\[45deg\\] {
+ --tw-backdrop-hue-rotate: hue-rotate(45deg);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-invert {
+ --tw-backdrop-invert: invert(100%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-invert-0 {
+ --tw-backdrop-invert: invert(0%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-invert-\\[--value\\] {
+ --tw-backdrop-invert: invert(var(--value));
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-opacity-50 {
+ --tw-backdrop-opacity: opacity(50%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-opacity-71 {
+ --tw-backdrop-opacity: opacity(71%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-opacity-\\[0\\.5\\] {
+ --tw-backdrop-opacity: opacity(.5);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-saturate-0 {
+ --tw-backdrop-saturate: saturate(0%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-saturate-\\[--value\\] {
+ --tw-backdrop-saturate: saturate(var(--value));
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-saturate-\\[1\\.75\\] {
+ --tw-backdrop-saturate: saturate(1.75);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-sepia {
+ --tw-backdrop-sepia: sepia(100%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-sepia-0 {
+ --tw-backdrop-sepia: sepia(0%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-sepia-\\[--value\\] {
+ --tw-backdrop-sepia: sepia(var(--value));
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-sepia-\\[50\\%\\] {
+ --tw-backdrop-sepia: sepia(50%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-contrast-50 {
+ --tw-backdrop-contrast: contrast(50%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-contrast-\\[1\\.23\\] {
+ --tw-backdrop-contrast: contrast(1.23);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-filter {
+ -webkit-backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ backdrop-filter: var(--tw-backdrop-blur, ) var(--tw-backdrop-brightness, ) var(--tw-backdrop-contrast, ) var(--tw-backdrop-grayscale, ) var(--tw-backdrop-hue-rotate, ) var(--tw-backdrop-invert, ) var(--tw-backdrop-opacity, ) var(--tw-backdrop-saturate, ) var(--tw-backdrop-sepia, );
+ }
+
+ .backdrop-filter-\\[--value\\] {
+ -webkit-backdrop-filter: var(--value);
+ backdrop-filter: var(--value);
+ }
+
+ .backdrop-filter-none {
+ -webkit-backdrop-filter: none;
+ backdrop-filter: none;
+ }
+
+ @property --tw-backdrop-blur {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-backdrop-brightness {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-backdrop-contrast {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-backdrop-grayscale {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-backdrop-hue-rotate {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-backdrop-invert {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-backdrop-opacity {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-backdrop-saturate {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-backdrop-sepia {
+ syntax: "*";
+ inherits: false
+ }"
+ `)
+ expect(
+ run([
+ '-backdrop-filter',
+ '-backdrop-filter-none',
+ '-backdrop-filter-[--value]',
+ '-backdrop-blur-xl',
+ '-backdrop-blur-[4px]',
+ '-backdrop-brightness-50',
+ '-backdrop-brightness-[1.23]',
+ '-backdrop-contrast-50',
+ '-backdrop-contrast-[1.23]',
+ '-backdrop-grayscale',
+ '-backdrop-grayscale-0',
+ '-backdrop-grayscale-[--value]',
+ '-backdrop-hue-rotate-15',
+ '-backdrop-hue-rotate-[45deg]',
+ '-backdrop-invert',
+ '-backdrop-invert-0',
+ '-backdrop-invert-[--value]',
+ '-backdrop-opacity-50',
+ '-backdrop-opacity-[0.5]',
+ '-backdrop-saturate-0',
+ '-backdrop-saturate-[1.75]',
+ '-backdrop-saturate-[--value]',
+ '-backdrop-sepia',
+ '-backdrop-sepia-0',
+ '-backdrop-sepia-[50%]',
+ '-backdrop-sepia-[--value]',
+ ]),
+ ).toEqual('')
+})
+
+test('transition', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --transition-property: color, background-color, border-color, text-decoration-color, fill,
+ stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+ --transition-property-opacity: opacity;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'transition',
+ 'transition-none',
+ 'transition-all',
+ 'transition-transform',
+ 'transition-shadow',
+ 'transition-colors',
+ 'transition-opacity',
+ 'transition-[--value]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+ --transition-property-opacity: opacity;
+ }
+
+ .transition {
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, -webkit-backdrop-filter, backdrop-filter;
+ transition-timing-function: var(--default-transition-timing-function);
+ transition-duration: var(--default-transition-duration);
+ }
+
+ .transition-\\[--value\\] {
+ transition-property: var(--value);
+ transition-timing-function: var(--default-transition-timing-function);
+ transition-duration: var(--default-transition-duration);
+ }
+
+ .transition-all {
+ transition-property: all;
+ transition-timing-function: var(--default-transition-timing-function);
+ transition-duration: var(--default-transition-duration);
+ }
+
+ .transition-colors {
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
+ transition-timing-function: var(--default-transition-timing-function);
+ transition-duration: var(--default-transition-duration);
+ }
+
+ .transition-opacity {
+ transition-property: opacity;
+ transition-timing-function: var(--default-transition-timing-function);
+ transition-duration: var(--default-transition-duration);
+ }
+
+ .transition-shadow {
+ transition-property: box-shadow;
+ transition-timing-function: var(--default-transition-timing-function);
+ transition-duration: var(--default-transition-duration);
+ }
+
+ .transition-transform {
+ transition-property: transform, translate, scale, rotate;
+ transition-timing-function: var(--default-transition-timing-function);
+ transition-duration: var(--default-transition-duration);
+ }
+
+ .transition-none {
+ transition-property: none;
+ }"
+ `)
+ expect(
+ run([
+ '-transition',
+ '-transition-none',
+ '-transition-all',
+ '-transition-opacity',
+ '-transition-[--value]',
+ ]),
+ ).toEqual('')
+})
+
+test('delay', () => {
+ expect(run(['delay-123', 'delay-200', 'delay-[300ms]'])).toMatchInlineSnapshot(`
+ ".delay-123 {
+ transition-delay: .123s;
+ }
+
+ .delay-200 {
+ transition-delay: .2s;
+ }
+
+ .delay-\\[300ms\\] {
+ transition-delay: .3s;
+ }"
+ `)
+ expect(run(['delay', '-delay-200', '-delay-[300ms]'])).toEqual('')
+})
+
+test('duration', () => {
+ expect(run(['duration-123', 'duration-200', 'duration-[300ms]'])).toMatchInlineSnapshot(`
+ ".duration-123 {
+ transition-duration: .123s;
+ }
+
+ .duration-200 {
+ transition-duration: .2s;
+ }
+
+ .duration-\\[300ms\\] {
+ transition-duration: .3s;
+ }"
+ `)
+ expect(run(['duration', '-duration-200', '-duration-[300ms]'])).toEqual('')
+})
+
+test('ease', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --transition-timing-function-in: cubic-bezier(0.4, 0, 1, 1);
+ --transition-timing-function-out: cubic-bezier(0, 0, 0.2, 1);
+ }
+ @tailwind utilities;
+ `,
+ ['ease-in', 'ease-out', 'ease-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --transition-timing-function-in: cubic-bezier(.4, 0, 1, 1);
+ --transition-timing-function-out: cubic-bezier(0, 0, .2, 1);
+ }
+
+ .ease-\\[--value\\] {
+ transition-timing-function: var(--value);
+ }
+
+ .ease-in {
+ transition-timing-function: cubic-bezier(.4, 0, 1, 1);
+ }
+
+ .ease-out {
+ transition-timing-function: cubic-bezier(0, 0, .2, 1);
+ }"
+ `)
+ expect(run(['-ease-in', '-ease-out', '-ease-[--value]'])).toEqual('')
+})
+
+test('will-change', () => {
+ expect(
+ run([
+ 'will-change-auto',
+ 'will-change-contents',
+ 'will-change-transform',
+ 'will-change-scroll',
+ 'will-change-[--value]',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".will-change-\\[--value\\] {
+ will-change: var(--value);
+ }
+
+ .will-change-auto {
+ will-change: auto;
+ }
+
+ .will-change-contents {
+ will-change: contents;
+ }
+
+ .will-change-scroll {
+ will-change: scroll-position;
+ }
+
+ .will-change-transform {
+ will-change: transform;
+ }"
+ `)
+ expect(
+ run([
+ 'will-change',
+ '-will-change-auto',
+ '-will-change-contents',
+ '-will-change-transform',
+ '-will-change-scroll',
+ '-will-change-[--value]',
+ ]),
+ ).toEqual('')
+})
+
+test('contain', () => {
+ expect(
+ run([
+ 'contain-none',
+ 'contain-content',
+ 'contain-strict',
+ 'contain-size',
+ 'contain-inline-size',
+ 'contain-layout',
+ 'contain-paint',
+ 'contain-style',
+ 'contain-[unset]',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".contain-\\[unset\\] {
+ contain: unset;
+ }
+
+ .contain-content {
+ contain: content;
+ }
+
+ .contain-inline-size {
+ --tw-contain-size: inline-size;
+ contain: var(--tw-contain-size) var(--tw-contain-layout) var(--tw-contain-paint) var(--tw-contain-style);
+ }
+
+ .contain-layout {
+ --tw-contain-layout: layout;
+ contain: var(--tw-contain-size) var(--tw-contain-layout) var(--tw-contain-paint) var(--tw-contain-style);
+ }
+
+ .contain-none {
+ contain: none;
+ }
+
+ .contain-paint {
+ --tw-contain-paint: paint;
+ contain: var(--tw-contain-size) var(--tw-contain-layout) var(--tw-contain-paint) var(--tw-contain-style);
+ }
+
+ .contain-size {
+ --tw-contain-size: size;
+ contain: var(--tw-contain-size) var(--tw-contain-layout) var(--tw-contain-paint) var(--tw-contain-style);
+ }
+
+ .contain-strict {
+ contain: strict;
+ }
+
+ .contain-style {
+ --tw-contain-style: style;
+ contain: var(--tw-contain-size) var(--tw-contain-layout) var(--tw-contain-paint) var(--tw-contain-style);
+ }
+
+ @property --tw-contain-size {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-contain-layout {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-contain-paint {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-contain-style {
+ syntax: "*";
+ inherits: false
+ }"
+ `)
+})
+
+test('content', () => {
+ expect(run(['content-["hello_world"]'])).toMatchInlineSnapshot(`
+ ".content-\\[\\"hello_world\\"\\] {
+ --tw-content: "hello world";
+ content: var(--tw-content);
+ }
+
+ @property --tw-content {
+ syntax: "*";
+ inherits: false;
+ initial-value: "";
+ }"
+ `)
+ expect(run(['content', '-content-["hello_world"]'])).toEqual('')
+})
+
+test('forced-color-adjust', () => {
+ expect(run(['forced-color-adjust-none', 'forced-color-adjust-auto'])).toMatchInlineSnapshot(`
+ ".forced-color-adjust-auto {
+ forced-color-adjust: auto;
+ }
+
+ .forced-color-adjust-none {
+ forced-color-adjust: none;
+ }"
+ `)
+ expect(
+ run([
+ 'forced',
+ 'forced-color',
+ 'forced-color-adjust',
+ '-forced-color-adjust-none',
+ '-forced-color-adjust-auto',
+ ]),
+ ).toEqual('')
+})
+
+test('leading', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --line-height-none: 1;
+ --line-height-6: 1.5rem;
+ }
+ @tailwind utilities;
+ `,
+ ['leading-none', 'leading-6', 'leading-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --line-height-none: 1;
+ --line-height-6: 1.5rem;
+ }
+
+ .leading-6 {
+ line-height: 1.5rem;
+ }
+
+ .leading-\\[--value\\] {
+ line-height: var(--value);
+ }
+
+ .leading-none {
+ line-height: 1;
+ }"
+ `)
+ expect(run(['leading', '-leading-none', '-leading-6', '-leading-[--value]'])).toEqual('')
+})
+
+test('tracking', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --letter-spacing-normal: 0em;
+ --letter-spacing-wide: 0.025em;
+ }
+ @tailwind utilities;
+ `,
+ ['tracking-normal', 'tracking-wide', 'tracking-[--value]', '-tracking-[--value]'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --letter-spacing-normal: 0em;
+ --letter-spacing-wide: .025em;
+ }
+
+ .-tracking-\\[--value\\] {
+ letter-spacing: calc(var(--value) * -1);
+ }
+
+ .tracking-\\[--value\\] {
+ letter-spacing: var(--value);
+ }
+
+ .tracking-normal {
+ letter-spacing: 0;
+ }
+
+ .tracking-wide {
+ letter-spacing: .025em;
+ }"
+ `)
+ expect(run(['tracking'])).toEqual('')
+})
+
+test('font-smoothing', () => {
+ expect(run(['antialiased', 'subpixel-antialiased'])).toMatchInlineSnapshot(`
+ ".antialiased {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ }
+
+ .subpixel-antialiased {
+ -webkit-font-smoothing: auto;
+ -moz-osx-font-smoothing: auto;
+ }"
+ `)
+ expect(run(['-antialiased', '-subpixel-antialiased'])).toEqual('')
+})
+
+test('font-variant-numeric', () => {
+ expect(
+ run([
+ 'normal-nums',
+ 'ordinal',
+ 'slashed-zero',
+ 'lining-nums',
+ 'oldstyle-nums',
+ 'proportional-nums',
+ 'tabular-nums',
+ 'diagonal-fractions',
+ 'stacked-fractions',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".diagonal-fractions {
+ --tw-numeric-fraction: diagonal-fractions;
+ font-variant-numeric: var(--tw-ordinal, ) var(--tw-slashed-zero, ) var(--tw-numeric-figure, ) var(--tw-numeric-spacing, ) var(--tw-numeric-fraction, );
+ }
+
+ .lining-nums {
+ --tw-numeric-figure: lining-nums;
+ font-variant-numeric: var(--tw-ordinal, ) var(--tw-slashed-zero, ) var(--tw-numeric-figure, ) var(--tw-numeric-spacing, ) var(--tw-numeric-fraction, );
+ }
+
+ .normal-nums {
+ font-variant-numeric: normal;
+ }
+
+ .oldstyle-nums {
+ --tw-numeric-figure: oldstyle-nums;
+ font-variant-numeric: var(--tw-ordinal, ) var(--tw-slashed-zero, ) var(--tw-numeric-figure, ) var(--tw-numeric-spacing, ) var(--tw-numeric-fraction, );
+ }
+
+ .ordinal {
+ --tw-ordinal: ordinal;
+ font-variant-numeric: var(--tw-ordinal, ) var(--tw-slashed-zero, ) var(--tw-numeric-figure, ) var(--tw-numeric-spacing, ) var(--tw-numeric-fraction, );
+ }
+
+ .proportional-nums {
+ --tw-numeric-spacing: proportional-nums;
+ font-variant-numeric: var(--tw-ordinal, ) var(--tw-slashed-zero, ) var(--tw-numeric-figure, ) var(--tw-numeric-spacing, ) var(--tw-numeric-fraction, );
+ }
+
+ .slashed-zero {
+ --tw-slashed-zero: slashed-zero;
+ font-variant-numeric: var(--tw-ordinal, ) var(--tw-slashed-zero, ) var(--tw-numeric-figure, ) var(--tw-numeric-spacing, ) var(--tw-numeric-fraction, );
+ }
+
+ .stacked-fractions {
+ --tw-numeric-fraction: stacked-fractions;
+ font-variant-numeric: var(--tw-ordinal, ) var(--tw-slashed-zero, ) var(--tw-numeric-figure, ) var(--tw-numeric-spacing, ) var(--tw-numeric-fraction, );
+ }
+
+ .tabular-nums {
+ --tw-numeric-spacing: tabular-nums;
+ font-variant-numeric: var(--tw-ordinal, ) var(--tw-slashed-zero, ) var(--tw-numeric-figure, ) var(--tw-numeric-spacing, ) var(--tw-numeric-fraction, );
+ }
+
+ @property --tw-ordinal {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-slashed-zero {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-numeric-figure {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-numeric-spacing {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-numeric-fraction {
+ syntax: "*";
+ inherits: false
+ }"
+ `)
+ expect(
+ run([
+ '-normal-nums',
+ '-ordinal',
+ '-slashed-zero',
+ '-lining-nums',
+ '-oldstyle-nums',
+ '-proportional-nums',
+ '-tabular-nums',
+ '-diagonal-fractions',
+ '-stacked-fractions',
+ ]),
+ ).toEqual('')
+})
+
+test('outline', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'outline',
+
+ // outline-style
+ 'outline-none',
+ 'outline-solid',
+ 'outline-dashed',
+ 'outline-dotted',
+ 'outline-double',
+
+ // outline-color
+ 'outline-red-500',
+ 'outline-red-500/50',
+ 'outline-red-500/[0.5]',
+ 'outline-red-500/[50%]',
+ 'outline-current',
+ 'outline-current/50',
+ 'outline-current/[0.5]',
+ 'outline-current/[50%]',
+ 'outline-transparent',
+ 'outline-[#0088cc]',
+ 'outline-[#0088cc]/50',
+ 'outline-[#0088cc]/[0.5]',
+ 'outline-[#0088cc]/[50%]',
+ 'outline-[black]',
+ 'outline-[black]/50',
+ 'outline-[black]/[0.5]',
+ 'outline-[black]/[50%]',
+ 'outline-[--value]',
+ 'outline-[--value]/50',
+ 'outline-[--value]/[0.5]',
+ 'outline-[--value]/[50%]',
+ 'outline-[color:--value]',
+ 'outline-[color:--value]/50',
+ 'outline-[color:--value]/[0.5]',
+ 'outline-[color:--value]/[50%]',
+
+ // outline-width
+ 'outline-0',
+ 'outline-[1.5]',
+ 'outline-[12px]',
+ 'outline-[50%]',
+ 'outline-[number:--my-width]',
+ 'outline-[length:--my-width]',
+ 'outline-[percentage:--my-width]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ }
+
+ .outline-none {
+ outline-offset: 2px;
+ outline: 2px solid #0000;
+ }
+
+ .outline {
+ outline-style: var(--tw-outline-style);
+ outline-width: 1px;
+ }
+
+ .outline-0 {
+ outline-style: var(--tw-outline-style);
+ outline-width: 0;
+ }
+
+ .outline-\\[1\\.5\\] {
+ outline-style: var(--tw-outline-style);
+ outline-width: 1.5px;
+ }
+
+ .outline-\\[12px\\] {
+ outline-style: var(--tw-outline-style);
+ outline-width: 12px;
+ }
+
+ .outline-\\[50\\%\\] {
+ outline-style: var(--tw-outline-style);
+ outline-width: 50%;
+ }
+
+ .outline-\\[length\\:--my-width\\], .outline-\\[number\\:--my-width\\], .outline-\\[percentage\\:--my-width\\] {
+ outline-style: var(--tw-outline-style);
+ outline-width: var(--my-width);
+ }
+
+ .outline-\\[\\#0088cc\\] {
+ outline-color: #08c;
+ }
+
+ .outline-\\[\\#0088cc\\]\\/50, .outline-\\[\\#0088cc\\]\\/\\[0\\.5\\], .outline-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ outline-color: #0088cc80;
+ }
+
+ .outline-\\[--value\\] {
+ outline-color: var(--value);
+ }
+
+ .outline-\\[--value\\]\\/50, .outline-\\[--value\\]\\/\\[0\\.5\\], .outline-\\[--value\\]\\/\\[50\\%\\] {
+ outline-color: color-mix(in srgb, var(--value) 50%, transparent);
+ }
+
+ .outline-\\[black\\] {
+ outline-color: #000;
+ }
+
+ .outline-\\[black\\]\\/50, .outline-\\[black\\]\\/\\[0\\.5\\], .outline-\\[black\\]\\/\\[50\\%\\] {
+ outline-color: #00000080;
+ }
+
+ .outline-\\[color\\:--value\\] {
+ outline-color: var(--value);
+ }
+
+ .outline-\\[color\\:--value\\]\\/50, .outline-\\[color\\:--value\\]\\/\\[0\\.5\\], .outline-\\[color\\:--value\\]\\/\\[50\\%\\] {
+ outline-color: color-mix(in srgb, var(--value) 50%, transparent);
+ }
+
+ .outline-current {
+ outline-color: currentColor;
+ }
+
+ .outline-current\\/50, .outline-current\\/\\[0\\.5\\], .outline-current\\/\\[50\\%\\] {
+ outline-color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ .outline-red-500 {
+ outline-color: #ef4444;
+ }
+
+ .outline-red-500\\/50, .outline-red-500\\/\\[0\\.5\\], .outline-red-500\\/\\[50\\%\\] {
+ outline-color: #ef444480;
+ }
+
+ .outline-transparent {
+ outline-color: #0000;
+ }
+
+ .outline-dashed {
+ --tw-outline-style: dashed;
+ outline-style: dashed;
+ }
+
+ .outline-dotted {
+ --tw-outline-style: dotted;
+ outline-style: dotted;
+ }
+
+ .outline-double {
+ --tw-outline-style: double;
+ outline-style: double;
+ }
+
+ .outline-solid {
+ --tw-outline-style: solid;
+ outline-style: solid;
+ }
+
+ @property --tw-outline-style {
+ syntax: "";
+ inherits: false;
+ initial-value: solid;
+ }"
+ `)
+ expect(
+ run([
+ '-outline',
+
+ // outline-style
+ '-outline-none',
+ '-outline-dashed',
+ '-outline-dotted',
+ '-outline-double',
+
+ // outline-color
+ '-outline-red-500',
+ '-outline-red-500/50',
+ '-outline-red-500/[0.5]',
+ '-outline-red-500/[50%]',
+ '-outline-current',
+ '-outline-current/50',
+ '-outline-current/[0.5]',
+ '-outline-current/[50%]',
+ '-outline-transparent',
+ '-outline-[#0088cc]',
+ '-outline-[#0088cc]/50',
+ '-outline-[#0088cc]/[0.5]',
+ '-outline-[#0088cc]/[50%]',
+ '-outline-[black]',
+
+ // outline-width
+ '-outline-0',
+ ]),
+ ).toEqual('')
+})
+
+test('outline-offset', () => {
+ expect(
+ run([
+ 'outline-offset-4',
+ '-outline-offset-4',
+ 'outline-offset-[--value]',
+ '-outline-offset-[--value]',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".-outline-offset-4 {
+ outline-offset: calc(4px * -1);
+ }
+
+ .-outline-offset-\\[--value\\] {
+ outline-offset: calc(var(--value) * -1);
+ }
+
+ .outline-offset-4 {
+ outline-offset: 4px;
+ }
+
+ .outline-offset-\\[--value\\] {
+ outline-offset: var(--value);
+ }"
+ `)
+ expect(run(['outline-offset'])).toEqual('')
+})
+
+test('opacity', () => {
+ expect(run(['opacity-15', 'opacity-[--value]'])).toMatchInlineSnapshot(`
+ ".opacity-15 {
+ opacity: .15;
+ }
+
+ .opacity-\\[--value\\] {
+ opacity: var(--value);
+ }"
+ `)
+ expect(run(['opacity', '-opacity-15', '-opacity-[--value]'])).toEqual('')
+})
+
+test('underline-offset', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'underline-offset-auto',
+ 'underline-offset-4',
+ '-underline-offset-4',
+ 'underline-offset-123',
+ '-underline-offset-123',
+ 'underline-offset-[--value]',
+ '-underline-offset-[--value]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ".-underline-offset-123 {
+ text-underline-offset: calc(123px * -1);
+ }
+
+ .-underline-offset-4 {
+ text-underline-offset: calc(4px * -1);
+ }
+
+ .-underline-offset-\\[--value\\] {
+ text-underline-offset: calc(var(--value) * -1);
+ }
+
+ .underline-offset-123 {
+ text-underline-offset: 123px;
+ }
+
+ .underline-offset-4 {
+ text-underline-offset: 4px;
+ }
+
+ .underline-offset-\\[--value\\] {
+ text-underline-offset: var(--value);
+ }
+
+ .underline-offset-auto {
+ text-underline-offset: auto;
+ }"
+ `)
+ expect(run(['underline-offset', '-underline-offset-auto'])).toEqual('')
+})
+
+test('text', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ --line-height-6: 1.5rem;
+ --font-size-sm: 0.875rem;
+ --font-size-sm--line-height: 1.25rem;
+ --line-height-9: 2.25rem;
+ }
+ @tailwind utilities;
+ `,
+ [
+ // color
+ 'text-red-500',
+ 'text-red-500/50',
+ 'text-red-500/[0.5]',
+ 'text-red-500/[50%]',
+ 'text-current',
+ 'text-current/50',
+ 'text-current/[0.5]',
+ 'text-current/[50%]',
+ 'text-transparent',
+ 'text-[#0088cc]',
+ 'text-[#0088cc]/50',
+ 'text-[#0088cc]/[0.5]',
+ 'text-[#0088cc]/[50%]',
+
+ 'text-[--my-color]',
+ 'text-[--my-color]/50',
+ 'text-[--my-color]/[0.5]',
+ 'text-[--my-color]/[50%]',
+ 'text-[color:--my-color]',
+ 'text-[color:--my-color]/50',
+ 'text-[color:--my-color]/[0.5]',
+ 'text-[color:--my-color]/[50%]',
+
+ // font-size / line-height / letter-spacing / font-weight
+ 'text-sm',
+ 'text-sm/6',
+ 'text-sm/[4px]',
+ 'text-[12px]',
+ 'text-[12px]/6',
+ 'text-[50%]',
+ 'text-[50%]/6',
+ 'text-[xx-large]',
+ 'text-[xx-large]/6',
+ 'text-[larger]',
+ 'text-[larger]/6',
+ 'text-[length:--my-size]',
+ 'text-[percentage:--my-size]',
+ 'text-[absolute-size:--my-size]',
+ 'text-[relative-size:--my-size]',
+ 'text-[clamp(1rem,2rem,3rem)]',
+ 'text-[clamp(1rem,var(--size),3rem)]',
+ 'text-[clamp(1rem,var(--size),3rem)]/9',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ --line-height-6: 1.5rem;
+ --font-size-sm: .875rem;
+ --font-size-sm--line-height: 1.25rem;
+ --line-height-9: 2.25rem;
+ }
+
+ .text-sm {
+ font-size: .875rem;
+ line-height: 1.25rem;
+ }
+
+ .text-\\[12px\\]\\/6 {
+ font-size: 12px;
+ line-height: 1.5rem;
+ }
+
+ .text-\\[50\\%\\]\\/6 {
+ font-size: 50%;
+ line-height: 1.5rem;
+ }
+
+ .text-\\[clamp\\(1rem\\,var\\(--size\\)\\,3rem\\)\\]\\/9 {
+ font-size: clamp(1rem, var(--size), 3rem);
+ line-height: 2.25rem;
+ }
+
+ .text-\\[larger\\]\\/6 {
+ font-size: larger;
+ line-height: 1.5rem;
+ }
+
+ .text-\\[xx-large\\]\\/6 {
+ font-size: xx-large;
+ line-height: 1.5rem;
+ }
+
+ .text-sm\\/6 {
+ font-size: .875rem;
+ line-height: 1.5rem;
+ }
+
+ .text-sm\\/\\[4px\\] {
+ font-size: .875rem;
+ line-height: 4px;
+ }
+
+ .text-\\[12px\\] {
+ font-size: 12px;
+ }
+
+ .text-\\[50\\%\\] {
+ font-size: 50%;
+ }
+
+ .text-\\[absolute-size\\:--my-size\\] {
+ font-size: var(--my-size);
+ }
+
+ .text-\\[clamp\\(1rem\\,2rem\\,3rem\\)\\] {
+ font-size: 2rem;
+ }
+
+ .text-\\[clamp\\(1rem\\,var\\(--size\\)\\,3rem\\)\\] {
+ font-size: clamp(1rem, var(--size), 3rem);
+ }
+
+ .text-\\[larger\\] {
+ font-size: larger;
+ }
+
+ .text-\\[length\\:--my-size\\], .text-\\[percentage\\:--my-size\\], .text-\\[relative-size\\:--my-size\\] {
+ font-size: var(--my-size);
+ }
+
+ .text-\\[xx-large\\] {
+ font-size: xx-large;
+ }
+
+ .text-\\[\\#0088cc\\] {
+ color: #08c;
+ }
+
+ .text-\\[\\#0088cc\\]\\/50, .text-\\[\\#0088cc\\]\\/\\[0\\.5\\], .text-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ color: #0088cc80;
+ }
+
+ .text-\\[--my-color\\] {
+ color: var(--my-color);
+ }
+
+ .text-\\[--my-color\\]\\/50, .text-\\[--my-color\\]\\/\\[0\\.5\\], .text-\\[--my-color\\]\\/\\[50\\%\\] {
+ color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ }
+
+ .text-\\[color\\:--my-color\\] {
+ color: var(--my-color);
+ }
+
+ .text-\\[color\\:--my-color\\]\\/50, .text-\\[color\\:--my-color\\]\\/\\[0\\.5\\], .text-\\[color\\:--my-color\\]\\/\\[50\\%\\] {
+ color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ }
+
+ .text-current {
+ color: currentColor;
+ }
+
+ .text-current\\/50, .text-current\\/\\[0\\.5\\], .text-current\\/\\[50\\%\\] {
+ color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ .text-red-500 {
+ color: #ef4444;
+ }
+
+ .text-red-500\\/50, .text-red-500\\/\\[0\\.5\\], .text-red-500\\/\\[50\\%\\] {
+ color: #ef444480;
+ }
+
+ .text-transparent {
+ color: #0000;
+ }"
+ `)
+ expect(
+ run([
+ 'text',
+ // color
+ '-text-red-500',
+ '-text-red-500/50',
+ '-text-red-500/[0.5]',
+ '-text-red-500/[50%]',
+ '-text-current',
+ '-text-current/50',
+ '-text-current/[0.5]',
+ '-text-current/[50%]',
+ '-text-transparent',
+ '-text-[#0088cc]',
+ '-text-[#0088cc]/50',
+ '-text-[#0088cc]/[0.5]',
+ '-text-[#0088cc]/[50%]',
+
+ // font-size / line-height / letter-spacing / font-weight
+ '-text-sm',
+ '-text-sm/6',
+ '-text-sm/[4px]',
+ ]),
+ ).toEqual('')
+})
+
+test('shadow', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
+ }
+ @tailwind utilities;
+ `,
+ [
+ // Shadows
+ 'shadow',
+ 'shadow-xl',
+ 'shadow-none',
+ 'shadow-[12px_12px_#0088cc]',
+ 'shadow-[10px_10px]',
+ 'shadow-[--value]',
+ 'shadow-[shadow:--value]',
+
+ // Colors
+ 'shadow-red-500',
+ 'shadow-red-500/50',
+ 'shadow-red-500/[0.5]',
+ 'shadow-red-500/[50%]',
+ 'shadow-current',
+ 'shadow-current/50',
+ 'shadow-current/[0.5]',
+ 'shadow-current/[50%]',
+ 'shadow-transparent',
+ 'shadow-[#0088cc]',
+ 'shadow-[#0088cc]/50',
+ 'shadow-[#0088cc]/[0.5]',
+ 'shadow-[#0088cc]/[50%]',
+ 'shadow-[color:--value]',
+ 'shadow-[color:--value]/50',
+ 'shadow-[color:--value]/[0.5]',
+ 'shadow-[color:--value]/[50%]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ --shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
+ --shadow-xl: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a;
+ }
+
+ .shadow {
+ --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
+ --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .shadow-\\[--value\\] {
+ --tw-shadow: var(--value);
+ --tw-shadow-colored: var(--value);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .shadow-\\[10px_10px\\] {
+ --tw-shadow: 10px 10px;
+ --tw-shadow-colored: 10px 10px;
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .shadow-\\[12px_12px_\\#0088cc\\] {
+ --tw-shadow: 12px 12px #08c;
+ --tw-shadow-colored: 12px 12px var(--tw-shadow-color);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .shadow-\\[shadow\\:--value\\] {
+ --tw-shadow: var(--value);
+ --tw-shadow-colored: var(--value);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .shadow-none {
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .shadow-xl {
+ --tw-shadow: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a;
+ --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .shadow-\\[\\#0088cc\\] {
+ --tw-shadow-color: #08c;
+ --tw-shadow: var(--tw-shadow-colored);
+ }
+
+ .shadow-\\[\\#0088cc\\]\\/50, .shadow-\\[\\#0088cc\\]\\/\\[0\\.5\\], .shadow-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ --tw-shadow-color: #0088cc80;
+ --tw-shadow: var(--tw-shadow-colored);
+ }
+
+ .shadow-\\[color\\:--value\\] {
+ --tw-shadow-color: var(--value);
+ --tw-shadow: var(--tw-shadow-colored);
+ }
+
+ .shadow-\\[color\\:--value\\]\\/50, .shadow-\\[color\\:--value\\]\\/\\[0\\.5\\], .shadow-\\[color\\:--value\\]\\/\\[50\\%\\] {
+ --tw-shadow-color: color-mix(in srgb, var(--value) 50%, transparent);
+ --tw-shadow: var(--tw-shadow-colored);
+ }
+
+ .shadow-current {
+ --tw-shadow-color: currentColor;
+ --tw-shadow: var(--tw-shadow-colored);
+ }
+
+ .shadow-current\\/50, .shadow-current\\/\\[0\\.5\\], .shadow-current\\/\\[50\\%\\] {
+ --tw-shadow-color: color-mix(in srgb, currentColor 50%, transparent);
+ --tw-shadow: var(--tw-shadow-colored);
+ }
+
+ .shadow-red-500 {
+ --tw-shadow-color: #ef4444;
+ --tw-shadow: var(--tw-shadow-colored);
+ }
+
+ .shadow-red-500\\/50, .shadow-red-500\\/\\[0\\.5\\], .shadow-red-500\\/\\[50\\%\\] {
+ --tw-shadow-color: #ef444480;
+ --tw-shadow: var(--tw-shadow-colored);
+ }
+
+ .shadow-transparent {
+ --tw-shadow-color: transparent;
+ --tw-shadow: var(--tw-shadow-colored);
+ }
+
+ @property --tw-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-shadow-colored {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-inset-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-inset-shadow-colored {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-ring-color {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-ring-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-inset-ring-color {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-inset-ring-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-ring-inset {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-ring-offset-width {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }
+
+ @property --tw-ring-offset-color {
+ syntax: "*";
+ inherits: false;
+ initial-value: #fff;
+ }
+
+ @property --tw-ring-offset-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }"
+ `)
+ expect(
+ run([
+ '-shadow-xl',
+ '-shadow-none',
+ '-shadow-red-500',
+ '-shadow-red-500/50',
+ '-shadow-red-500/[0.5]',
+ '-shadow-red-500/[50%]',
+ '-shadow-current',
+ '-shadow-current/50',
+ '-shadow-current/[0.5]',
+ '-shadow-current/[50%]',
+ '-shadow-transparent',
+ '-shadow-[#0088cc]',
+ '-shadow-[#0088cc]/50',
+ '-shadow-[#0088cc]/[0.5]',
+ '-shadow-[#0088cc]/[50%]',
+ '-shadow-[--value]',
+ ]),
+ ).toEqual('')
+})
+
+test('inset-shadow', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ --inset-shadow: inset 0 2px 4px rgb(0 0 0 / 0.05);
+ --inset-shadow-sm: inset 0 1px 1px rgb(0 0 0 / 0.05);
+ }
+ @tailwind utilities;
+ `,
+ [
+ // Shadows
+ 'inset-shadow',
+ 'inset-shadow-sm',
+ 'inset-shadow-none',
+ 'inset-shadow-[12px_12px_#0088cc]',
+ 'inset-shadow-[10px_10px]',
+ 'inset-shadow-[--value]',
+ 'inset-shadow-[shadow:--value]',
+
+ // Colors
+ 'inset-shadow-red-500',
+ 'inset-shadow-red-500/50',
+ 'inset-shadow-red-500/[0.5]',
+ 'inset-shadow-red-500/[50%]',
+ 'inset-shadow-current',
+ 'inset-shadow-current/50',
+ 'inset-shadow-current/[0.5]',
+ 'inset-shadow-current/[50%]',
+ 'inset-shadow-transparent',
+ 'inset-shadow-[#0088cc]',
+ 'inset-shadow-[#0088cc]/50',
+ 'inset-shadow-[#0088cc]/[0.5]',
+ 'inset-shadow-[#0088cc]/[50%]',
+ 'inset-shadow-[color:--value]',
+ 'inset-shadow-[color:--value]/50',
+ 'inset-shadow-[color:--value]/[0.5]',
+ 'inset-shadow-[color:--value]/[50%]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ --inset-shadow: inset 0 2px 4px #0000000d;
+ --inset-shadow-sm: inset 0 1px 1px #0000000d;
+ }
+
+ .inset-shadow {
+ --tw-inset-shadow: inset 0 2px 4px #0000000d;
+ --tw-inset-shadow-colored: inset 0 2px 4px var(--tw-inset-shadow-color);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .inset-shadow-\\[--value\\] {
+ --tw-inset-shadow: inset var(--value);
+ --tw-inset-shadow-colored: inset var(--value);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .inset-shadow-\\[10px_10px\\] {
+ --tw-inset-shadow: inset 10px 10px;
+ --tw-inset-shadow-colored: inset 10px 10px;
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .inset-shadow-\\[12px_12px_\\#0088cc\\] {
+ --tw-inset-shadow: inset 12px 12px #08c;
+ --tw-inset-shadow-colored: inset 12px 12px var(--tw-inset-shadow-color);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .inset-shadow-\\[shadow\\:--value\\] {
+ --tw-inset-shadow: inset var(--value);
+ --tw-inset-shadow-colored: inset var(--value);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .inset-shadow-none {
+ --tw-inset-shadow: 0 0 #0000;
+ --tw-inset-shadow-colored: 0 0 #0000;
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .inset-shadow-sm {
+ --tw-inset-shadow: inset 0 1px 1px #0000000d;
+ --tw-inset-shadow-colored: inset 0 1px 1px var(--tw-inset-shadow-color);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .inset-shadow-\\[\\#0088cc\\] {
+ --tw-inset-shadow-color: #08c;
+ --tw-inset-shadow: var(--tw-inset-shadow-colored);
+ }
+
+ .inset-shadow-\\[\\#0088cc\\]\\/50, .inset-shadow-\\[\\#0088cc\\]\\/\\[0\\.5\\], .inset-shadow-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ --tw-inset-shadow-color: #0088cc80;
+ --tw-inset-shadow: var(--tw-inset-shadow-colored);
+ }
+
+ .inset-shadow-\\[color\\:--value\\] {
+ --tw-inset-shadow-color: var(--value);
+ --tw-inset-shadow: var(--tw-inset-shadow-colored);
+ }
+
+ .inset-shadow-\\[color\\:--value\\]\\/50, .inset-shadow-\\[color\\:--value\\]\\/\\[0\\.5\\], .inset-shadow-\\[color\\:--value\\]\\/\\[50\\%\\] {
+ --tw-inset-shadow-color: color-mix(in srgb, var(--value) 50%, transparent);
+ --tw-inset-shadow: var(--tw-inset-shadow-colored);
+ }
+
+ .inset-shadow-current {
+ --tw-inset-shadow-color: currentColor;
+ --tw-inset-shadow: var(--tw-inset-shadow-colored);
+ }
+
+ .inset-shadow-current\\/50, .inset-shadow-current\\/\\[0\\.5\\], .inset-shadow-current\\/\\[50\\%\\] {
+ --tw-inset-shadow-color: color-mix(in srgb, currentColor 50%, transparent);
+ --tw-inset-shadow: var(--tw-inset-shadow-colored);
+ }
+
+ .inset-shadow-red-500 {
+ --tw-inset-shadow-color: #ef4444;
+ --tw-inset-shadow: var(--tw-inset-shadow-colored);
+ }
+
+ .inset-shadow-red-500\\/50, .inset-shadow-red-500\\/\\[0\\.5\\], .inset-shadow-red-500\\/\\[50\\%\\] {
+ --tw-inset-shadow-color: #ef444480;
+ --tw-inset-shadow: var(--tw-inset-shadow-colored);
+ }
+
+ .inset-shadow-transparent {
+ --tw-inset-shadow-color: transparent;
+ --tw-inset-shadow: var(--tw-inset-shadow-colored);
+ }
+
+ @property --tw-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-shadow-colored {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-inset-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-inset-shadow-colored {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-ring-color {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-ring-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-inset-ring-color {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-inset-ring-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-ring-inset {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-ring-offset-width {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }
+
+ @property --tw-ring-offset-color {
+ syntax: "*";
+ inherits: false;
+ initial-value: #fff;
+ }
+
+ @property --tw-ring-offset-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }"
+ `)
+ expect(
+ run([
+ '-inset-shadow-sm',
+ '-inset-shadow-none',
+ '-inset-shadow-red-500',
+ '-inset-shadow-red-500/50',
+ '-inset-shadow-red-500/[0.5]',
+ '-inset-shadow-red-500/[50%]',
+ '-inset-shadow-current',
+ '-inset-shadow-current/50',
+ '-inset-shadow-current/[0.5]',
+ '-inset-shadow-current/[50%]',
+ '-inset-shadow-transparent',
+ '-inset-shadow-[#0088cc]',
+ '-inset-shadow-[#0088cc]/50',
+ '-inset-shadow-[#0088cc]/[0.5]',
+ '-inset-shadow-[#0088cc]/[50%]',
+ '-inset-shadow-[--value]',
+ ]),
+ ).toEqual('')
+})
+
+test('ring', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ [
+ // ring color
+ 'ring-inset',
+ 'ring-red-500',
+ 'ring-red-500/50',
+ 'ring-red-500/[0.5]',
+ 'ring-red-500/[50%]',
+ 'ring-current',
+ 'ring-current/50',
+ 'ring-current/[0.5]',
+ 'ring-current/[50%]',
+ 'ring-transparent',
+ 'ring-[#0088cc]',
+ 'ring-[#0088cc]/50',
+ 'ring-[#0088cc]/[0.5]',
+ 'ring-[#0088cc]/[50%]',
+ 'ring-[--my-color]',
+ 'ring-[--my-color]/50',
+ 'ring-[--my-color]/[0.5]',
+ 'ring-[--my-color]/[50%]',
+ 'ring-[color:--my-color]',
+ 'ring-[color:--my-color]/50',
+ 'ring-[color:--my-color]/[0.5]',
+ 'ring-[color:--my-color]/[50%]',
+
+ // ring width
+ 'ring',
+ 'ring-0',
+ 'ring-1',
+ 'ring-2',
+ 'ring-4',
+ 'ring-[12px]',
+ 'ring-[length:--my-width]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ }
+
+ .ring {
+ --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .ring-0 {
+ --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .ring-1 {
+ --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .ring-2 {
+ --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .ring-4 {
+ --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .ring-\\[12px\\] {
+ --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(12px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .ring-\\[length\\:--my-width\\] {
+ --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(var(--my-width) + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .ring-\\[\\#0088cc\\] {
+ --tw-ring-color: #08c;
+ }
+
+ .ring-\\[\\#0088cc\\]\\/50, .ring-\\[\\#0088cc\\]\\/\\[0\\.5\\], .ring-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ --tw-ring-color: #0088cc80;
+ }
+
+ .ring-\\[--my-color\\] {
+ --tw-ring-color: var(--my-color);
+ }
+
+ .ring-\\[--my-color\\]\\/50, .ring-\\[--my-color\\]\\/\\[0\\.5\\], .ring-\\[--my-color\\]\\/\\[50\\%\\] {
+ --tw-ring-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ }
+
+ .ring-\\[color\\:--my-color\\] {
+ --tw-ring-color: var(--my-color);
+ }
+
+ .ring-\\[color\\:--my-color\\]\\/50, .ring-\\[color\\:--my-color\\]\\/\\[0\\.5\\], .ring-\\[color\\:--my-color\\]\\/\\[50\\%\\] {
+ --tw-ring-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ }
+
+ .ring-current {
+ --tw-ring-color: currentColor;
+ }
+
+ .ring-current\\/50, .ring-current\\/\\[0\\.5\\], .ring-current\\/\\[50\\%\\] {
+ --tw-ring-color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ .ring-red-500 {
+ --tw-ring-color: #ef4444;
+ }
+
+ .ring-red-500\\/50, .ring-red-500\\/\\[0\\.5\\], .ring-red-500\\/\\[50\\%\\] {
+ --tw-ring-color: #ef444480;
+ }
+
+ .ring-transparent {
+ --tw-ring-color: transparent;
+ }
+
+ .ring-inset {
+ --tw-ring-inset: inset;
+ }
+
+ @property --tw-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-shadow-colored {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-inset-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-inset-shadow-colored {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-ring-color {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-ring-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-inset-ring-color {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-inset-ring-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-ring-inset {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-ring-offset-width {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }
+
+ @property --tw-ring-offset-color {
+ syntax: "*";
+ inherits: false;
+ initial-value: #fff;
+ }
+
+ @property --tw-ring-offset-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }"
+ `)
+ expect(
+ run([
+ // ring color
+ '-ring-inset',
+ '-ring-red-500',
+ '-ring-red-500/50',
+ '-ring-red-500/[0.5]',
+ '-ring-red-500/[50%]',
+ '-ring-current',
+ '-ring-current/50',
+ '-ring-current/[0.5]',
+ '-ring-current/[50%]',
+ '-ring-transparent',
+ '-ring-[#0088cc]',
+ '-ring-[#0088cc]/50',
+ '-ring-[#0088cc]/[0.5]',
+ '-ring-[#0088cc]/[50%]',
+
+ // ring width
+ '-ring',
+ '-ring-0',
+ '-ring-1',
+ '-ring-2',
+ '-ring-4',
+ ]),
+ ).toEqual('')
+})
+
+test('inset-ring', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ [
+ // ring color
+ 'inset-ring-red-500',
+ 'inset-ring-red-500/50',
+ 'inset-ring-red-500/[0.5]',
+ 'inset-ring-red-500/[50%]',
+ 'inset-ring-current',
+ 'inset-ring-current/50',
+ 'inset-ring-current/[0.5]',
+ 'inset-ring-current/[50%]',
+ 'inset-ring-transparent',
+ 'inset-ring-[#0088cc]',
+ 'inset-ring-[#0088cc]/50',
+ 'inset-ring-[#0088cc]/[0.5]',
+ 'inset-ring-[#0088cc]/[50%]',
+ 'inset-ring-[--my-color]',
+ 'inset-ring-[--my-color]/50',
+ 'inset-ring-[--my-color]/[0.5]',
+ 'inset-ring-[--my-color]/[50%]',
+ 'inset-ring-[color:--my-color]',
+ 'inset-ring-[color:--my-color]/50',
+ 'inset-ring-[color:--my-color]/[0.5]',
+ 'inset-ring-[color:--my-color]/[50%]',
+
+ // ring width
+ 'inset-ring',
+ 'inset-ring-0',
+ 'inset-ring-1',
+ 'inset-ring-2',
+ 'inset-ring-4',
+ 'inset-ring-[12px]',
+ 'inset-ring-[length:--my-width]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ }
+
+ .inset-ring {
+ --tw-inset-ring-shadow: inset 0 0 0 1px var(--tw-inset-ring-color, currentColor);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .inset-ring-0 {
+ --tw-inset-ring-shadow: inset 0 0 0 0px var(--tw-inset-ring-color, currentColor);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .inset-ring-1 {
+ --tw-inset-ring-shadow: inset 0 0 0 1px var(--tw-inset-ring-color, currentColor);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .inset-ring-2 {
+ --tw-inset-ring-shadow: inset 0 0 0 2px var(--tw-inset-ring-color, currentColor);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .inset-ring-4 {
+ --tw-inset-ring-shadow: inset 0 0 0 4px var(--tw-inset-ring-color, currentColor);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .inset-ring-\\[12px\\] {
+ --tw-inset-ring-shadow: inset 0 0 0 12px var(--tw-inset-ring-color, currentColor);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .inset-ring-\\[length\\:--my-width\\] {
+ --tw-inset-ring-shadow: inset 0 0 0 var(--my-width) var(--tw-inset-ring-color, currentColor);
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+
+ .inset-ring-\\[\\#0088cc\\] {
+ --tw-inset-ring-color: #08c;
+ }
+
+ .inset-ring-\\[\\#0088cc\\]\\/50, .inset-ring-\\[\\#0088cc\\]\\/\\[0\\.5\\], .inset-ring-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ --tw-inset-ring-color: #0088cc80;
+ }
+
+ .inset-ring-\\[--my-color\\] {
+ --tw-inset-ring-color: var(--my-color);
+ }
+
+ .inset-ring-\\[--my-color\\]\\/50, .inset-ring-\\[--my-color\\]\\/\\[0\\.5\\], .inset-ring-\\[--my-color\\]\\/\\[50\\%\\] {
+ --tw-inset-ring-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ }
+
+ .inset-ring-\\[color\\:--my-color\\] {
+ --tw-inset-ring-color: var(--my-color);
+ }
+
+ .inset-ring-\\[color\\:--my-color\\]\\/50, .inset-ring-\\[color\\:--my-color\\]\\/\\[0\\.5\\], .inset-ring-\\[color\\:--my-color\\]\\/\\[50\\%\\] {
+ --tw-inset-ring-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ }
+
+ .inset-ring-current {
+ --tw-inset-ring-color: currentColor;
+ }
+
+ .inset-ring-current\\/50, .inset-ring-current\\/\\[0\\.5\\], .inset-ring-current\\/\\[50\\%\\] {
+ --tw-inset-ring-color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ .inset-ring-red-500 {
+ --tw-inset-ring-color: #ef4444;
+ }
+
+ .inset-ring-red-500\\/50, .inset-ring-red-500\\/\\[0\\.5\\], .inset-ring-red-500\\/\\[50\\%\\] {
+ --tw-inset-ring-color: #ef444480;
+ }
+
+ .inset-ring-transparent {
+ --tw-inset-ring-color: transparent;
+ }
+
+ @property --tw-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-shadow-colored {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-inset-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-inset-shadow-colored {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-ring-color {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-ring-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-inset-ring-color {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-inset-ring-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }
+
+ @property --tw-ring-inset {
+ syntax: "*";
+ inherits: false
+ }
+
+ @property --tw-ring-offset-width {
+ syntax: "";
+ inherits: false;
+ initial-value: 0;
+ }
+
+ @property --tw-ring-offset-color {
+ syntax: "*";
+ inherits: false;
+ initial-value: #fff;
+ }
+
+ @property --tw-ring-offset-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+ }"
+ `)
+ expect(
+ run([
+ // ring color
+ '-inset-ring-red-500',
+ '-inset-ring-red-500/50',
+ '-inset-ring-red-500/[0.5]',
+ '-inset-ring-red-500/[50%]',
+ '-inset-ring-current',
+ '-inset-ring-current/50',
+ '-inset-ring-current/[0.5]',
+ '-inset-ring-current/[50%]',
+ '-inset-ring-transparent',
+ '-inset-ring-[#0088cc]',
+ '-inset-ring-[#0088cc]/50',
+ '-inset-ring-[#0088cc]/[0.5]',
+ '-inset-ring-[#0088cc]/[50%]',
+
+ // ring width
+ '-inset-ring',
+ '-inset-ring-0',
+ '-inset-ring-1',
+ '-inset-ring-2',
+ '-inset-ring-4',
+ ]),
+ ).toEqual('')
+})
+
+test('ring-offset', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --color-red-500: #ef4444;
+ }
+ @tailwind utilities;
+ `,
+ [
+ // ring color
+ 'ring-offset-inset',
+ 'ring-offset-red-500',
+ 'ring-offset-red-500/50',
+ 'ring-offset-red-500/[0.5]',
+ 'ring-offset-red-500/[50%]',
+ 'ring-offset-current',
+ 'ring-offset-current/50',
+ 'ring-offset-current/[0.5]',
+ 'ring-offset-current/[50%]',
+ 'ring-offset-transparent',
+ 'ring-offset-[#0088cc]',
+ 'ring-offset-[#0088cc]/50',
+ 'ring-offset-[#0088cc]/[0.5]',
+ 'ring-offset-[#0088cc]/[50%]',
+
+ 'ring-offset-[--my-color]',
+ 'ring-offset-[--my-color]/50',
+ 'ring-offset-[--my-color]/[0.5]',
+ 'ring-offset-[--my-color]/[50%]',
+ 'ring-offset-[color:--my-color]',
+ 'ring-offset-[color:--my-color]/50',
+ 'ring-offset-[color:--my-color]/[0.5]',
+ 'ring-offset-[color:--my-color]/[50%]',
+
+ // ring width
+ 'ring-offset-0',
+ 'ring-offset-1',
+ 'ring-offset-2',
+ 'ring-offset-4',
+ 'ring-offset-[12px]',
+ 'ring-offset-[length:--my-width]',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --color-red-500: #ef4444;
+ }
+
+ .ring-offset-0 {
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-shadow: var(--tw-ring-inset, ) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ }
+
+ .ring-offset-1 {
+ --tw-ring-offset-width: 1px;
+ --tw-ring-offset-shadow: var(--tw-ring-inset, ) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ }
+
+ .ring-offset-2 {
+ --tw-ring-offset-width: 2px;
+ --tw-ring-offset-shadow: var(--tw-ring-inset, ) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ }
+
+ .ring-offset-4 {
+ --tw-ring-offset-width: 4px;
+ --tw-ring-offset-shadow: var(--tw-ring-inset, ) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ }
+
+ .ring-offset-\\[12px\\] {
+ --tw-ring-offset-width: 12px;
+ --tw-ring-offset-shadow: var(--tw-ring-inset, ) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ }
+
+ .ring-offset-\\[length\\:--my-width\\] {
+ --tw-ring-offset-width: var(--my-width);
+ --tw-ring-offset-shadow: var(--tw-ring-inset, ) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ }
+
+ .ring-offset-\\[\\#0088cc\\] {
+ --tw-ring-offset-color: #08c;
+ }
+
+ .ring-offset-\\[\\#0088cc\\]\\/50, .ring-offset-\\[\\#0088cc\\]\\/\\[0\\.5\\], .ring-offset-\\[\\#0088cc\\]\\/\\[50\\%\\] {
+ --tw-ring-offset-color: #0088cc80;
+ }
+
+ .ring-offset-\\[--my-color\\] {
+ --tw-ring-offset-color: var(--my-color);
+ }
+
+ .ring-offset-\\[--my-color\\]\\/50, .ring-offset-\\[--my-color\\]\\/\\[0\\.5\\], .ring-offset-\\[--my-color\\]\\/\\[50\\%\\] {
+ --tw-ring-offset-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ }
+
+ .ring-offset-\\[color\\:--my-color\\] {
+ --tw-ring-offset-color: var(--my-color);
+ }
+
+ .ring-offset-\\[color\\:--my-color\\]\\/50, .ring-offset-\\[color\\:--my-color\\]\\/\\[0\\.5\\], .ring-offset-\\[color\\:--my-color\\]\\/\\[50\\%\\] {
+ --tw-ring-offset-color: color-mix(in srgb, var(--my-color) 50%, transparent);
+ }
+
+ .ring-offset-current {
+ --tw-ring-offset-color: currentColor;
+ }
+
+ .ring-offset-current\\/50, .ring-offset-current\\/\\[0\\.5\\], .ring-offset-current\\/\\[50\\%\\] {
+ --tw-ring-offset-color: color-mix(in srgb, currentColor 50%, transparent);
+ }
+
+ .ring-offset-red-500 {
+ --tw-ring-offset-color: #ef4444;
+ }
+
+ .ring-offset-red-500\\/50, .ring-offset-red-500\\/\\[0\\.5\\], .ring-offset-red-500\\/\\[50\\%\\] {
+ --tw-ring-offset-color: #ef444480;
+ }
+
+ .ring-offset-transparent {
+ --tw-ring-offset-color: transparent;
+ }"
+ `)
+ expect(
+ run([
+ 'ring-offset',
+ // ring color
+ '-ring-offset-inset',
+ '-ring-offset-red-500',
+ '-ring-offset-red-500/50',
+ '-ring-offset-red-500/[0.5]',
+ '-ring-offset-red-500/[50%]',
+ '-ring-offset-current',
+ '-ring-offset-current/50',
+ '-ring-offset-current/[0.5]',
+ '-ring-offset-current/[50%]',
+ '-ring-offset-transparent',
+ '-ring-offset-[#0088cc]',
+ '-ring-offset-[#0088cc]/50',
+ '-ring-offset-[#0088cc]/[0.5]',
+ '-ring-offset-[#0088cc]/[50%]',
+
+ // ring width
+ '-ring-offset-0',
+ '-ring-offset-1',
+ '-ring-offset-2',
+ '-ring-offset-4',
+ ]),
+ ).toEqual('')
+})
+
+test('@container', () => {
+ expect(
+ run([
+ '@container',
+ '@container-normal',
+ '@container/sidebar',
+ '@container-normal/sidebar',
+ '@container-[size]',
+ '@container-[size]/sidebar',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".\\@container {
+ container-type: inline-size;
+ }
+
+ .\\@container-\\[size\\] {
+ container-type: size;
+ }
+
+ .\\@container-\\[size\\]\\/sidebar {
+ container: sidebar / size;
+ }
+
+ .\\@container-normal {
+ container-type: normal;
+ }
+
+ .\\@container-normal\\/sidebar {
+ container: sidebar;
+ }
+
+ .\\@container\\/sidebar {
+ container: sidebar / inline-size;
+ }"
+ `)
+ expect(
+ run([
+ '-@container',
+ '-@container-normal',
+ '-@container/sidebar',
+ '-@container-normal/sidebar',
+ '-@container-[size]',
+ '-@container-[size]/sidebar',
+ ]),
+ ).toEqual('')
+})
diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts
new file mode 100644
index 000000000..d93a7c10f
--- /dev/null
+++ b/packages/tailwindcss/src/utilities.ts
@@ -0,0 +1,4304 @@
+import { decl, rule, type AstNode, type Rule } from './ast'
+import type { Candidate, CandidateModifier, NamedUtilityValue } from './candidate'
+import type { ColorThemeKey, Theme, ThemeKey } from './theme'
+import { inferDataType } from './utils/infer-data-type'
+import { replaceShadowColors } from './utils/replace-shadow-colors'
+import { segment } from './utils/segment'
+
+const ARBITRARY_VARIANT = Symbol('ARBITRARY_VARIANT')
+
+type CompileFn = (
+ value: Extract,
+) => AstNode[] | undefined
+
+interface SuggestionGroup {
+ supportsNegative?: boolean
+ values: (string | null)[]
+ modifiers: string[]
+}
+
+type SuggestionDefinition =
+ | string
+ | {
+ supportsNegative?: boolean
+ values?: string[]
+ modifiers?: string[]
+ valueThemeKeys?: ThemeKey[]
+ modifierThemeKeys?: ThemeKey[]
+ hasDefaultValue?: boolean
+ }
+
+export class Utilities {
+ private utilities = new Map<
+ string | symbol,
+ {
+ kind: Candidate['kind']
+ compileFn: CompileFn
+ }
+ >()
+
+ private completions = new Map SuggestionGroup[]>()
+
+ static(name: string, compileFn: CompileFn<'static'>) {
+ this.set(name, { kind: 'static', compileFn: compileFn })
+ }
+
+ functional(name: string, compileFn: CompileFn<'functional'>) {
+ this.set(name, { kind: 'functional', compileFn: compileFn })
+ }
+
+ arbitrary(compileFn: CompileFn<'arbitrary'>) {
+ this.set(ARBITRARY_VARIANT, { kind: 'arbitrary', compileFn: compileFn })
+ }
+
+ has(name: string) {
+ return this.utilities.has(name)
+ }
+
+ get(name: string | symbol) {
+ return this.utilities.get(name)
+ }
+
+ kind(name: string) {
+ return this.utilities.get(name)!.kind
+ }
+
+ getCompletions(name: string): SuggestionGroup[] {
+ return this.completions.get(name)?.() ?? []
+ }
+
+ suggest(name: string, groups: () => SuggestionGroup[]) {
+ // TODO: We are calling this multiple times on purpose but ideally only ever
+ // once per utility root.
+ this.completions.set(name, groups)
+ }
+
+ keys() {
+ return this.utilities.keys()
+ }
+
+ entries() {
+ return this.utilities.entries()
+ }
+
+ getArbitrary() {
+ return this.get(ARBITRARY_VARIANT)!.compileFn
+ }
+
+ private set(
+ name: string | symbol,
+ { kind, compileFn }: { kind: T; compileFn: CompileFn },
+ ) {
+ // In test mode, throw an error if we accidentally override another utility
+ // by mistake when implementing a new utility that shares the same root
+ // without realizing the definitions need to be merged.
+ if (process.env.NODE_ENV === 'test') {
+ if (this.utilities.has(name)) {
+ throw new Error(`Duplicate utility prefix [${name.toString()}]`)
+ }
+ }
+
+ this.utilities.set(name, {
+ kind,
+ compileFn: compileFn,
+ })
+ }
+}
+
+function atRoot(rules: Rule[]) {
+ return rule('@at-root', rules)
+}
+
+function property(ident: string, initialValue?: string, syntax?: string) {
+ return rule(`@property ${ident}`, [
+ decl('syntax', syntax ? `"${syntax}"` : `"*"`),
+ decl('inherits', 'false'),
+
+ // If there's no initial value, it's important that we omit it rather than
+ // use an empty value. Safari currently doesn't support an empty
+ // `initial-value` properly, so we have to design how we use things around
+ // the guaranteed invalid value instead, which is how `initial-value`
+ // behaves when omitted.
+ ...(initialValue ? [decl('initial-value', initialValue)] : []),
+ ])
+}
+
+/**
+ * Apply opacity to a color using `color-mix`.
+ */
+function withAlpha(value: string, alpha: string): string {
+ if (alpha === null) return value
+
+ // Convert numeric values (like `0.5`) to percentages (like `50%`) so they
+ // work properly with `color-mix`. Assume anything that isn't a number is
+ // safe to pass through as-is, like `var(--my-opacity)`.
+ let alphaAsNumber = Number(alpha)
+ if (!Number.isNaN(alphaAsNumber)) {
+ alpha = `${alphaAsNumber * 100}%`
+ }
+
+ return `color-mix(in srgb, ${value} ${alpha}, transparent)`
+}
+
+/**
+ * Resolve a color value + optional opacity modifier to a final color.
+ */
+function asColor(value: string, modifier: CandidateModifier | null, theme: Theme): string | null {
+ if (!modifier) return value
+
+ if (modifier.kind === 'arbitrary') {
+ return withAlpha(value, modifier.value)
+ }
+
+ // Check if the modifier exists in the `opacity` theme configuration and use
+ // that value if so.
+ let alpha = theme.resolve(modifier.value, ['--opacity'])
+ if (alpha) {
+ return withAlpha(value, alpha)
+ }
+
+ if (Number.isNaN(Number(modifier.value))) {
+ return null
+ }
+
+ // The modifier is a bare value like `50`, so convert that to `50%`.
+ return withAlpha(value, `${modifier.value}%`)
+}
+
+/**
+ * Negate a numeric value — literals get simplified by Lightning CSS.
+ */
+function withNegative(
+ value: string,
+ candidate: Extract,
+) {
+ return candidate.negative ? `calc(${value} * -1)` : value
+}
+
+/**
+ * Finds a color in the theme under one of the given theme keys that matches `candidateValue`.
+ *
+ * The values `transparent` and `current` are special-cased as they are universal and don't need to
+ * be resolved from the theme.
+ */
+function resolveThemeColor(
+ candidate: Extract,
+ theme: Theme,
+ themeKeys: T[],
+) {
+ if (process.env.NODE_ENV === 'test') {
+ if (!candidate.value) {
+ throw new Error('resolveThemeColor must be called with a named candidate')
+ }
+
+ if (candidate.value.kind !== 'named') {
+ throw new Error('resolveThemeColor must be called with a named value')
+ }
+ }
+
+ let value: string | null = null
+
+ switch (candidate.value!.value) {
+ case 'transparent': {
+ value = 'transparent'
+ break
+ }
+ case 'current': {
+ value = 'currentColor'
+ break
+ }
+ default: {
+ value = theme.resolve(candidate.value!.value, themeKeys)
+ break
+ }
+ }
+
+ return value ? asColor(value, candidate.modifier, theme) : null
+}
+
+export function createUtilities(theme: Theme) {
+ let utilities = new Utilities()
+
+ utilities.arbitrary((candidate) => {
+ let value: string | null = candidate.value
+
+ // Assumption: If an arbitrary property has a modifier, then we assume it
+ // is an opacity modifier.
+ if (candidate.modifier) {
+ value = asColor(value, candidate.modifier, theme)
+ }
+
+ if (value === null) return
+
+ return [decl(candidate.property, value)]
+ })
+
+ /**
+ * Register list of suggestions for a class
+ */
+ function suggest(classRoot: string, defns: () => SuggestionDefinition[]) {
+ function* resolve(themeKeys: ThemeKey[]) {
+ for (let value of theme.keysInNamespaces(themeKeys)) {
+ yield value.replaceAll('_', '.')
+ }
+ }
+
+ utilities.suggest(classRoot, () => {
+ let groups: SuggestionGroup[] = []
+
+ for (let defn of defns()) {
+ if (typeof defn === 'string') {
+ groups.push({ values: [defn], modifiers: [] })
+ continue
+ }
+
+ let values: (string | null)[] = [
+ ...(defn.values ?? []),
+ ...resolve(defn.valueThemeKeys ?? []),
+ ]
+ let modifiers = [...(defn.modifiers ?? []), ...resolve(defn.modifierThemeKeys ?? [])]
+
+ if (defn.hasDefaultValue) {
+ values.unshift(null)
+ }
+
+ groups.push({ supportsNegative: defn.supportsNegative, values, modifiers })
+ }
+
+ return groups
+ })
+ }
+
+ /**
+ * Register a static utility class like `justify-center`.
+ */
+ function staticUtility(className: string, declarations: ([string, string] | (() => AstNode))[]) {
+ utilities.static(className, (candidate) => {
+ if (candidate.negative) return
+
+ return declarations.map((node) => {
+ return typeof node === 'function' ? node() : decl(node[0], node[1])
+ })
+ })
+ }
+
+ type UtilityDescription = {
+ supportsNegative?: boolean
+ supportsFractions?: boolean
+ themeKeys: ThemeKey[]
+ defaultValue?: string | null
+ handleBareValue?: (value: NamedUtilityValue) => string | null
+ handle: (value: string) => AstNode[] | undefined
+ }
+
+ /**
+ * Register a functional utility class like `max-w-*` that maps to tokens in the
+ * user's theme.
+ */
+ function functionalUtility(classRoot: string, desc: UtilityDescription) {
+ utilities.functional(classRoot, (candidate) => {
+ // If the class candidate has a negative prefix (like `-mx-2`) but this
+ // utility doesn't support negative values (like the `width` utility),
+ // don't generate any rules.
+ if (candidate.negative && !desc.supportsNegative) return
+
+ let value: string | null = null
+
+ if (!candidate.value) {
+ // If the candidate has no value segment (like `shadow`), use the
+ // `defaultValue` or the `DEFAULT` value in the theme. No utility will
+ // ever support both of these — `defaultValue` is for things like
+ // `grayscale` whereas the `DEFAULT` in theme is for things like
+ // `shadow` or `blur`.
+ value = desc.defaultValue ?? theme.get(desc.themeKeys)
+ } else if (candidate.value.kind === 'arbitrary') {
+ value = candidate.value.value
+ } else {
+ value = theme.resolve(candidate.value.fraction ?? candidate.value.value, desc.themeKeys)
+
+ // Automatically handle things like `w-1/2` without requiring `1/2` to
+ // exist as a theme value.
+ if (value === null && desc.supportsFractions && candidate.value.fraction) {
+ value = `calc(${candidate.value.fraction} * 100%)`
+ }
+
+ // If there is still no value but the utility supports bare values, then
+ // use the bare candidate value as the value.
+ if (value === null && desc.handleBareValue) {
+ value = desc.handleBareValue(candidate.value)
+ }
+ }
+
+ // If there is no value, don't generate any rules.
+ if (value === null) return
+
+ // Negate the value if the candidate has a negative prefix.
+ return desc.handle(withNegative(value, candidate))
+ })
+
+ suggest(classRoot, () => [
+ {
+ supportsNegative: desc.supportsNegative,
+ valueThemeKeys: desc.themeKeys,
+ hasDefaultValue: desc.defaultValue !== undefined && desc.defaultValue !== null,
+ },
+ ])
+ }
+
+ type ColorUtilityDescription = {
+ themeKeys: ColorThemeKey[]
+ handle: (value: string) => AstNode[] | undefined
+ }
+
+ /**
+ * Register a functional utility class that represents a color.
+ * Such utilities gain automatic support for color opacity modifiers.
+ */
+ function colorUtility(classRoot: string, desc: ColorUtilityDescription) {
+ utilities.functional(classRoot, (candidate) => {
+ // Color utilities have to have a value, like the `red-500` in
+ // `bg-red-500`, otherwise they would be static utilities.
+ if (!candidate.value) return
+
+ // Color utilities never support negative values as there's no sensible
+ // way to negate a color.
+ if (candidate.negative) return
+
+ // Find the actual CSS value that the candidate value maps to.
+ let value: string | null = null
+
+ if (candidate.value.kind === 'arbitrary') {
+ value = candidate.value.value
+
+ // Apply an opacity modifier to the value if appropriate.
+ value = asColor(value, candidate.modifier, theme)
+ } else {
+ value = resolveThemeColor(candidate, theme, desc.themeKeys)
+ }
+
+ // If the candidate value (like the `red-500` in `bg-red-500`) doesn't
+ // resolve to an actual value, don't generate any rules.
+ if (value === null) return
+
+ return desc.handle(value)
+ })
+
+ suggest(classRoot, () => [
+ {
+ values: ['current', 'transparent'],
+ valueThemeKeys: desc.themeKeys,
+ modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
+ modifierThemeKeys: ['--opacity'],
+ },
+ ])
+ }
+
+ /**
+ * ----------------
+ * Utility matchers
+ * ----------------
+ */
+
+ staticUtility('sr-only', [
+ ['position', 'absolute'],
+ ['width', '1px'],
+ ['height', '1px'],
+ ['padding', '0'],
+ ['margin', '-1px'],
+ ['overflow', 'hidden'],
+ ['clip', 'rect(0, 0, 0, 0)'],
+ ['white-space', 'nowrap'],
+ ['border-width', '0'],
+ ])
+ staticUtility('not-sr-only', [
+ ['position', 'static'],
+ ['width', 'auto'],
+ ['height', 'auto'],
+ ['padding', '0'],
+ ['margin', '0'],
+ ['overflow', 'visible'],
+ ['clip', 'auto'],
+ ['white-space', 'normal'],
+ ])
+
+ /**
+ * @css `pointer-events`
+ */
+ staticUtility('pointer-events-none', [['pointer-events', 'none']])
+ staticUtility('pointer-events-auto', [['pointer-events', 'auto']])
+
+ /**
+ * @css `visibility`
+ */
+ staticUtility('visible', [['visibility', 'visible']])
+ staticUtility('invisible', [['visibility', 'hidden']])
+ staticUtility('collapse', [['visibility', 'collapse']])
+
+ /**
+ * @css `position`
+ */
+ staticUtility('static', [['position', 'static']])
+ staticUtility('fixed', [['position', 'fixed']])
+ staticUtility('absolute', [['position', 'absolute']])
+ staticUtility('relative', [['position', 'relative']])
+ staticUtility('sticky', [['position', 'sticky']])
+
+ /**
+ * @css `inset`
+ */
+ staticUtility('inset-auto', [['inset', 'auto']])
+ utilities.static('inset-full', (candidate) => {
+ let value = candidate.negative ? '-100%' : '100%'
+ return [decl('inset', value)]
+ })
+ functionalUtility('inset', {
+ supportsNegative: true,
+ supportsFractions: true,
+ themeKeys: ['--inset', '--spacing'],
+ handle: (value) => [decl('inset', value)],
+ })
+
+ staticUtility('inset-x-auto', [
+ ['--tw-sort', 'inset-inline'],
+ ['right', 'auto'],
+ ['left', 'auto'],
+ ])
+
+ utilities.static('inset-x-full', (candidate) => {
+ let value = candidate.negative ? '-100%' : '100%'
+ return [decl('--tw-sort', 'inset-inline'), decl('right', value), decl('left', value)]
+ })
+ functionalUtility('inset-x', {
+ supportsNegative: true,
+ supportsFractions: true,
+ themeKeys: ['--inset', '--spacing'],
+ handle: (value) => [
+ decl('--tw-sort', 'inset-inline'),
+ decl('right', value),
+ decl('left', value),
+ ],
+ })
+
+ staticUtility('inset-y-auto', [
+ ['--tw-sort', 'inset-block'],
+ ['top', 'auto'],
+ ['bottom', 'auto'],
+ ])
+ utilities.static('inset-y-full', (candidate) => {
+ let value = candidate.negative ? '-100%' : '100%'
+ return [decl('--tw-sort', 'inset-block'), decl('top', value), decl('bottom', value)]
+ })
+ functionalUtility('inset-y', {
+ supportsNegative: true,
+ supportsFractions: true,
+ themeKeys: ['--inset', '--spacing'],
+ handle: (value) => [
+ decl('--tw-sort', 'inset-block'),
+ decl('top', value),
+ decl('bottom', value),
+ ],
+ })
+
+ staticUtility('start-auto', [['inset-inline-start', 'auto']])
+ utilities.static('start-full', (candidate) => {
+ let value = candidate.negative ? '-100%' : '100%'
+ return [decl('inset-inline-start', value)]
+ })
+ functionalUtility('start', {
+ supportsNegative: true,
+ supportsFractions: true,
+ themeKeys: ['--inset', '--spacing'],
+ handle: (value) => [decl('inset-inline-start', value)],
+ })
+
+ staticUtility('end-auto', [['inset-inline-end', 'auto']])
+ utilities.static('end-full', (candidate) => {
+ let value = candidate.negative ? '-100%' : '100%'
+ return [decl('inset-inline-end', value)]
+ })
+ functionalUtility('end', {
+ supportsNegative: true,
+ supportsFractions: true,
+ themeKeys: ['--inset', '--spacing'],
+ handle: (value) => [decl('inset-inline-end', value)],
+ })
+
+ staticUtility('top-auto', [['top', 'auto']])
+ utilities.static('top-full', (candidate) => {
+ let value = candidate.negative ? '-100%' : '100%'
+ return [decl('top', value)]
+ })
+ functionalUtility('top', {
+ supportsNegative: true,
+ supportsFractions: true,
+ themeKeys: ['--inset', '--spacing'],
+ handle: (value) => [decl('top', value)],
+ })
+
+ staticUtility('right-auto', [['right', 'auto']])
+ utilities.static('right-full', (candidate) => {
+ let value = candidate.negative ? '-100%' : '100%'
+ return [decl('right', value)]
+ })
+ functionalUtility('right', {
+ supportsNegative: true,
+ supportsFractions: true,
+ themeKeys: ['--inset', '--spacing'],
+ handle: (value) => [decl('right', value)],
+ })
+
+ staticUtility('bottom-auto', [['bottom', 'auto']])
+ utilities.static('bottom-full', (candidate) => {
+ let value = candidate.negative ? '-100%' : '100%'
+ return [decl('bottom', value)]
+ })
+ functionalUtility('bottom', {
+ supportsNegative: true,
+ supportsFractions: true,
+ themeKeys: ['--inset', '--spacing'],
+ handle: (value) => [decl('bottom', value)],
+ })
+
+ staticUtility('left-auto', [['left', 'auto']])
+ utilities.static('left-full', (candidate) => {
+ let value = candidate.negative ? '-100%' : '100%'
+ return [decl('left', value)]
+ })
+ functionalUtility('left', {
+ supportsNegative: true,
+ supportsFractions: true,
+ themeKeys: ['--inset', '--spacing'],
+ handle: (value) => [decl('left', value)],
+ })
+
+ /**
+ * @css `isolation`
+ */
+ staticUtility('isolate', [['isolation', 'isolate']])
+ staticUtility('isolation-auto', [['isolation', 'auto']])
+
+ /**
+ * @css `z-index`
+ */
+ staticUtility('z-auto', [['z-index', 'auto']])
+ functionalUtility('z', {
+ supportsNegative: true,
+ handleBareValue: ({ value }) => value,
+ themeKeys: ['--z-index'],
+ handle: (value) => [decl('z-index', value)],
+ })
+
+ suggest('z', () => [
+ {
+ supportsNegative: true,
+ values: ['0', '10', '20', '30', '40', '50'],
+ valueThemeKeys: ['--z-index'],
+ },
+ ])
+
+ /**
+ * @css `order`
+ */
+ staticUtility('order-first', [['order', 'calc(-infinity)']])
+ staticUtility('order-last', [['order', 'calc(infinity)']])
+ staticUtility('order-none', [['order', '0']])
+ functionalUtility('order', {
+ supportsNegative: true,
+ handleBareValue: ({ value }) => value,
+ themeKeys: ['--order'],
+ handle: (value) => [decl('order', value)],
+ })
+
+ suggest('order', () => [
+ {
+ supportsNegative: true,
+ values: Array.from({ length: 12 }, (_, i) => `${i + 1}`),
+ valueThemeKeys: ['--order'],
+ },
+ ])
+
+ /**
+ * @css `grid-column`
+ */
+ staticUtility('col-auto', [['grid-column', 'auto']])
+ functionalUtility('col', {
+ themeKeys: ['--grid-column'],
+ handle: (value) => [decl('grid-column', value)],
+ })
+ staticUtility('col-span-full', [['grid-column', '1 / -1']])
+ functionalUtility('col-span', {
+ themeKeys: [],
+ handleBareValue: ({ value }) => value,
+ handle: (value) => [decl('grid-column', `span ${value} / span ${value}`)],
+ })
+
+ /**
+ * @css `grid-column-start`
+ */
+ staticUtility('col-start-auto', [['grid-column-start', 'auto']])
+ functionalUtility('col-start', {
+ handleBareValue: ({ value }) => value,
+ themeKeys: ['--grid-column-start'],
+ handle: (value) => [decl('grid-column-start', value)],
+ })
+
+ /**
+ * @css `grid-column-end`
+ */
+ staticUtility('col-end-auto', [['grid-column-end', 'auto']])
+ functionalUtility('col-end', {
+ handleBareValue: ({ value }) => value,
+ themeKeys: ['--grid-column-end'],
+ handle: (value) => [decl('grid-column-end', value)],
+ })
+
+ suggest('col-span', () => [
+ {
+ values: Array.from({ length: 12 }, (_, i) => `${i + 1}`),
+ valueThemeKeys: [],
+ },
+ ])
+
+ suggest('col-start', () => [
+ {
+ values: Array.from({ length: 13 }, (_, i) => `${i + 1}`),
+ valueThemeKeys: ['--grid-column-start'],
+ },
+ ])
+
+ suggest('col-end', () => [
+ {
+ values: Array.from({ length: 13 }, (_, i) => `${i + 1}`),
+ valueThemeKeys: ['--grid-column-end'],
+ },
+ ])
+
+ /**
+ * @css `grid-row`
+ */
+ staticUtility('row-auto', [['grid-row', 'auto']])
+ functionalUtility('row', {
+ themeKeys: ['--grid-row'],
+ handle: (value) => [decl('grid-row', value)],
+ })
+ staticUtility('row-span-full', [['grid-row', '1 / -1']])
+ functionalUtility('row-span', {
+ themeKeys: [],
+ handleBareValue: ({ value }) => value,
+ handle: (value) => [decl('grid-row', `span ${value} / span ${value}`)],
+ })
+
+ /**
+ * @css `grid-row-start`
+ */
+ staticUtility('row-start-auto', [['grid-row-start', 'auto']])
+ functionalUtility('row-start', {
+ handleBareValue: ({ value }) => value,
+ themeKeys: ['--grid-row-start'],
+ handle: (value) => [decl('grid-row-start', value)],
+ })
+
+ /**
+ * @css `grid-row-end`
+ */
+ staticUtility('row-end-auto', [['grid-row-end', 'auto']])
+ functionalUtility('row-end', {
+ handleBareValue: ({ value }) => value,
+ themeKeys: ['--grid-row-end'],
+ handle: (value) => [decl('grid-row-end', value)],
+ })
+
+ suggest('row-span', () => [
+ {
+ values: Array.from({ length: 12 }, (_, i) => `${i + 1}`),
+ valueThemeKeys: [],
+ },
+ ])
+
+ suggest('row-start', () => [
+ {
+ values: Array.from({ length: 13 }, (_, i) => `${i + 1}`),
+ valueThemeKeys: ['--grid-row-start'],
+ },
+ ])
+
+ suggest('row-end', () => [
+ {
+ values: Array.from({ length: 13 }, (_, i) => `${i + 1}`),
+ valueThemeKeys: ['--grid-row-end'],
+ },
+ ])
+
+ /**
+ * @css `float`
+ */
+ staticUtility('float-start', [['float', 'start']])
+ staticUtility('float-end', [['float', 'end']])
+ staticUtility('float-right', [['float', 'right']])
+ staticUtility('float-left', [['float', 'left']])
+ staticUtility('float-none', [['float', 'none']])
+
+ /**
+ * @css `clear`
+ */
+ staticUtility('clear-start', [['clear', 'start']])
+ staticUtility('clear-end', [['clear', 'end']])
+ staticUtility('clear-right', [['clear', 'right']])
+ staticUtility('clear-left', [['clear', 'left']])
+ staticUtility('clear-both', [['clear', 'both']])
+ staticUtility('clear-none', [['clear', 'none']])
+
+ /**
+ * @css `margin`
+ */
+ for (let [namespace, properties, sort] of [
+ ['m', ['margin']],
+ ['mx', ['margin-left', 'margin-right'], 'margin-inline'],
+ ['my', ['margin-top', 'margin-bottom'], 'margin-block'],
+ ['ms', ['margin-inline-start']],
+ ['me', ['margin-inline-end']],
+ ['mt', ['margin-top']],
+ ['mr', ['margin-right']],
+ ['mb', ['margin-bottom']],
+ ['ml', ['margin-left']],
+ ] as const) {
+ staticUtility(
+ `${namespace}-auto`,
+ properties.map((property) => [property, 'auto']),
+ )
+ functionalUtility(namespace, {
+ supportsNegative: true,
+ themeKeys: ['--margin', '--spacing'],
+ handle: (value) => [
+ ...(sort ? [decl('--tw-sort', sort)] : []),
+ ...properties.map((property) => decl(property, value)),
+ ],
+ })
+ }
+
+ /**
+ * @css `box-sizing`
+ */
+ staticUtility('box-border', [['box-sizing', 'border-box']])
+ staticUtility('box-content', [['box-sizing', 'content-box']])
+
+ /**
+ * @css `line-clamp`
+ */
+ staticUtility('line-clamp-none', [
+ ['overlow', 'visible'],
+ ['display', 'block'],
+ ['-webkit-box-orient', 'horizonal'],
+ ['-webkit-line-clamp', 'none'],
+ ])
+ functionalUtility('line-clamp', {
+ themeKeys: ['--line-clamp'],
+ handleBareValue: ({ value }) => value,
+ handle: (value) => [
+ decl('overlow', 'hidden'),
+ decl('display', '-webkit-box'),
+ decl('-webkit-box-orient', 'vertical'),
+ decl('-webkit-line-clamp', value),
+ ],
+ })
+
+ suggest('line-clamp', () => [
+ {
+ values: ['1', '2', '3', '4', '5', '6'],
+ valueThemeKeys: ['--line-clamp'],
+ },
+ ])
+
+ /**
+ * @css `display`
+ */
+ staticUtility('block', [['display', 'block']])
+ staticUtility('inline-block', [['display', 'inline-block']])
+ staticUtility('inline', [['display', 'inline']])
+ staticUtility('hidden', [['display', 'none']])
+ // flex is registered below
+ staticUtility('inline-flex', [['display', 'inline-flex']])
+ staticUtility('table', [['display', 'table']])
+ staticUtility('inline-table', [['display', 'inline-table']])
+ staticUtility('table-caption', [['display', 'table-caption']])
+ staticUtility('table-cell', [['display', 'table-cell']])
+ staticUtility('table-column', [['display', 'table-column']])
+ staticUtility('table-column-group', [['display', 'table-column-group']])
+ staticUtility('table-footer-group', [['display', 'table-footer-group']])
+ staticUtility('table-header-group', [['display', 'table-header-group']])
+ staticUtility('table-row-group', [['display', 'table-row-group']])
+ staticUtility('table-row', [['display', 'table-row']])
+ staticUtility('flow-root', [['display', 'flow-root']])
+ staticUtility('grid', [['display', 'grid']])
+ staticUtility('inline-grid', [['display', 'inline-grid']])
+ staticUtility('contents', [['display', 'contents']])
+ staticUtility('list-item', [['display', 'list-item']])
+
+ /**
+ * @css `aspect-ratio`
+ */
+ staticUtility('aspect-auto', [['aspect-ratio', 'auto']])
+ staticUtility('aspect-square', [['aspect-ratio', '1 / 1']])
+ staticUtility('aspect-video', [['aspect-ratio', '16 / 9']])
+ functionalUtility('aspect', {
+ themeKeys: ['--aspect-ratio'],
+ handleBareValue: ({ fraction }) => fraction,
+ handle: (value) => [decl('aspect-ratio', value)],
+ })
+
+ /**
+ * @css `size`
+ * @css `width`
+ * @css `height`
+ */
+ for (let [key, value] of [
+ ['auto', 'auto'],
+ ['full', '100%'],
+ ['min', 'min-content'],
+ ['max', 'max-content'],
+ ['fit', 'fit-content'],
+ ]) {
+ staticUtility(`size-${key}`, [
+ ['--tw-sort', 'size'],
+ ['width', value],
+ ['height', value],
+ ])
+ }
+ functionalUtility('size', {
+ supportsFractions: true,
+ themeKeys: ['--size', '--spacing'],
+ handle: (value) => [decl('--tw-sort', 'size'), decl('width', value), decl('height', value)],
+ })
+
+ /**
+ * @css `width`
+ */
+ for (let [key, value] of [
+ ['auto', 'auto'],
+ ['full', '100%'],
+ ['screen', '100vw'],
+ ['svw', '100svw'],
+ ['lvw', '100lvw'],
+ ['dvw', '100dvw'],
+ ['min', 'min-content'],
+ ['max', 'max-content'],
+ ['fit', 'fit-content'],
+ ]) {
+ staticUtility(`w-${key}`, [['width', value]])
+ }
+ functionalUtility('w', {
+ supportsFractions: true,
+ themeKeys: ['--width', '--spacing'],
+ handle: (value) => [decl('width', value)],
+ })
+
+ /**
+ * @css `min-width`
+ */
+ for (let [key, value] of [
+ ['auto', 'auto'],
+ ['full', '100%'],
+ ['min', 'min-content'],
+ ['max', 'max-content'],
+ ['fit', 'fit-content'],
+ ]) {
+ staticUtility(`min-w-${key}`, [['min-width', value]])
+ }
+ functionalUtility('min-w', {
+ themeKeys: ['--min-width', '--width', '--spacing'],
+ handle: (value) => [decl('min-width', value)],
+ })
+
+ /**
+ * @css `max-width`
+ */
+ for (let [key, value] of [
+ ['none', 'none'],
+ ['full', '100%'],
+ ['min', 'min-content'],
+ ['max', 'max-content'],
+ ['fit', 'fit-content'],
+ ]) {
+ staticUtility(`max-w-${key}`, [['max-width', value]])
+ }
+ functionalUtility('max-w', {
+ themeKeys: ['--max-width', '--width', '--spacing'],
+ handle: (value) => [decl('max-width', value)],
+ })
+
+ /**
+ * @css `height`
+ */
+ for (let [key, value] of [
+ ['auto', 'auto'],
+ ['full', '100%'],
+ ['screen', '100vh'],
+ ['svh', '100svh'],
+ ['lvh', '100lvh'],
+ ['dvh', '100dvh'],
+ ['min', 'min-content'],
+ ['max', 'max-content'],
+ ['fit', 'fit-content'],
+ ]) {
+ staticUtility(`h-${key}`, [['height', value]])
+ }
+ functionalUtility('h', {
+ supportsFractions: true,
+ themeKeys: ['--height', '--spacing'],
+ handle: (value) => [decl('height', value)],
+ })
+
+ /**
+ * @css `min-height`
+ */
+ for (let [key, value] of [
+ ['auto', 'auto'],
+ ['full', '100%'],
+ ['screen', '100vh'],
+ ['svh', '100svh'],
+ ['lvh', '100lvh'],
+ ['dvh', '100dvh'],
+ ['min', 'min-content'],
+ ['max', 'max-content'],
+ ['fit', 'fit-content'],
+ ]) {
+ staticUtility(`min-h-${key}`, [['min-height', value]])
+ }
+ functionalUtility('min-h', {
+ themeKeys: ['--min-height', '--spacing'],
+ handle: (value) => [decl('min-height', value)],
+ })
+
+ /**
+ * @css `max-height`
+ */
+ for (let [key, value] of [
+ ['none', 'none'],
+ ['full', '100%'],
+ ['screen', '100vh'],
+ ['svh', '100svh'],
+ ['lvh', '100lvh'],
+ ['dvh', '100dvh'],
+ ['min', 'min-content'],
+ ['max', 'max-content'],
+ ['fit', 'fit-content'],
+ ]) {
+ staticUtility(`max-h-${key}`, [['max-height', value]])
+ }
+ functionalUtility('max-h', {
+ themeKeys: ['--max-height', '--spacing'],
+ handle: (value) => [decl('max-height', value)],
+ })
+
+ /**
+ * @css `flex`
+ */
+ staticUtility('flex-auto', [['flex', 'auto']])
+ staticUtility('flex-initial', [['flex', '0 auto']])
+ staticUtility('flex-none', [['flex', 'none']])
+
+ // The `flex` utility generates `display: flex` but utilities like `flex-1`
+ // generate `flex: 1`. Our `functionalUtility` helper can't handle two properties
+ // using the same namespace, so we handle this one manually.
+ utilities.functional('flex', (candidate) => {
+ if (candidate.negative) return
+
+ if (!candidate.value) {
+ return [decl('display', 'flex')]
+ }
+
+ if (candidate.value.kind === 'arbitrary') {
+ return [decl('flex', candidate.value.value)]
+ }
+
+ if (candidate.value.fraction) {
+ return [decl('flex', `calc(${candidate.value.fraction} * 100%)`)]
+ }
+
+ return [decl('flex', candidate.value.value)]
+ })
+
+ /**
+ * @css `flex-shrink`
+ */
+ functionalUtility('shrink', {
+ defaultValue: '1',
+ themeKeys: [],
+ handleBareValue: ({ value }) => value,
+ handle: (value) => [decl('flex-shrink', value)],
+ })
+
+ /**
+ * @css `flex-grow`
+ */
+ functionalUtility('grow', {
+ defaultValue: '1',
+ themeKeys: [],
+ handleBareValue: ({ value }) => value,
+ handle: (value) => [decl('flex-grow', value)],
+ })
+
+ suggest('shrink', () => [
+ {
+ values: ['0'],
+ valueThemeKeys: [],
+ hasDefaultValue: true,
+ },
+ ])
+
+ suggest('grow', () => [
+ {
+ values: ['0'],
+ valueThemeKeys: [],
+ hasDefaultValue: true,
+ },
+ ])
+
+ /**
+ * @css `flex-basis`
+ */
+ staticUtility('basis-auto', [['flex-basis', 'auto']])
+ staticUtility('basis-full', [['flex-basis', '100%']])
+ functionalUtility('basis', {
+ supportsFractions: true,
+ themeKeys: ['--flex-basis', '--width', '--spacing'],
+ handle: (value) => [decl('flex-basis', value)],
+ })
+
+ /**
+ * @css `table-layout`
+ */
+ staticUtility('table-auto', [['table-layout', 'auto']])
+ staticUtility('table-fixed', [['table-layout', 'fixed']])
+
+ /**
+ * @css `caption-side`
+ */
+ staticUtility('caption-top', [['caption-side', 'top']])
+ staticUtility('caption-bottom', [['caption-side', 'bottom']])
+
+ /**
+ * @css `border-collapse`
+ */
+ staticUtility('border-collapse', [['border-collapse', 'collapse']])
+ staticUtility('border-separate', [['border-collapse', 'separate']])
+
+ let borderSpacingProperties = () =>
+ atRoot([
+ property('--tw-border-spacing-x', '0', ''),
+ property('--tw-border-spacing-y', '0', ''),
+ ])
+
+ /**
+ * @css `border-spacing`
+ */
+ functionalUtility('border-spacing', {
+ themeKeys: ['--border-spacing', '--spacing'],
+ handle: (value) => [
+ borderSpacingProperties(),
+ decl('--tw-border-spacing-x', value),
+ decl('--tw-border-spacing-y', value),
+ decl('border-spacing', 'var(--tw-border-spacing-x) var(--tw-border-spacing-y)'),
+ ],
+ })
+
+ /**
+ * @css `border-spacing`
+ */
+ functionalUtility('border-spacing-x', {
+ themeKeys: ['--border-spacing', '--spacing'],
+ handle: (value) => [
+ borderSpacingProperties(),
+ decl('--tw-border-spacing-x', value),
+ decl('border-spacing', 'var(--tw-border-spacing-x) var(--tw-border-spacing-y)'),
+ ],
+ })
+
+ /**
+ * @css `border-spacing`
+ */
+ functionalUtility('border-spacing-y', {
+ themeKeys: ['--border-spacing', '--spacing'],
+ handle: (value) => [
+ borderSpacingProperties(),
+ decl('--tw-border-spacing-y', value),
+ decl('border-spacing', 'var(--tw-border-spacing-x) var(--tw-border-spacing-y)'),
+ ],
+ })
+
+ /**
+ * @css `transform-origin`
+ */
+ staticUtility('origin-center', [['transform-origin', 'center']])
+ staticUtility('origin-top', [['transform-origin', 'top']])
+ staticUtility('origin-top-right', [['transform-origin', 'top right']])
+ staticUtility('origin-right', [['transform-origin', 'right']])
+ staticUtility('origin-bottom-right', [['transform-origin', 'bottom right']])
+ staticUtility('origin-bottom', [['transform-origin', 'bottom']])
+ staticUtility('origin-bottom-left', [['transform-origin', 'bottom left']])
+ staticUtility('origin-left', [['transform-origin', 'left']])
+ staticUtility('origin-top-left', [['transform-origin', 'top left']])
+ functionalUtility('origin', {
+ themeKeys: ['--transform-origin'],
+ handle: (value) => [decl('transform-origin', value)],
+ })
+
+ let translateProperties = () =>
+ atRoot([
+ property('--tw-translate-x', '0', ''),
+ property('--tw-translate-y', '0', ''),
+ ])
+
+ /**
+ * @css `translate`
+ */
+ utilities.static('translate-full', (candidate) => {
+ let value = candidate.negative ? '-100%' : '100%'
+
+ return [
+ translateProperties(),
+ decl('--tw-translate-x', value),
+ decl('--tw-translate-y', value),
+ decl('translate', 'var(--tw-translate-x) var(--tw-translate-y)'),
+ ]
+ })
+ functionalUtility('translate', {
+ supportsNegative: true,
+ supportsFractions: true,
+ themeKeys: ['--translate', '--spacing'],
+ handle: (value) => [
+ translateProperties(),
+ decl('--tw-translate-x', value),
+ decl('--tw-translate-y', value),
+ decl('translate', 'var(--tw-translate-x) var(--tw-translate-y)'),
+ ],
+ })
+
+ /**
+ * @css `translate`
+ */
+ utilities.static('translate-x-full', (candidate) => {
+ let value = candidate.negative ? '-100%' : '100%'
+
+ return [
+ translateProperties(),
+ decl('--tw-translate-x', value),
+ decl('translate', 'var(--tw-translate-x) var(--tw-translate-y)'),
+ ]
+ })
+ functionalUtility('translate-x', {
+ supportsNegative: true,
+ supportsFractions: true,
+ themeKeys: ['--translate', '--spacing'],
+ handle: (value) => [
+ translateProperties(),
+ decl('--tw-translate-x', value),
+ decl('translate', 'var(--tw-translate-x) var(--tw-translate-y)'),
+ ],
+ })
+
+ /**
+ * @css `translate`
+ */
+ utilities.static('translate-y-full', (candidate) => {
+ let value = candidate.negative ? '-100%' : '100%'
+
+ return [
+ translateProperties(),
+ decl('--tw-translate-y', value),
+ decl('translate', 'var(--tw-translate-x) var(--tw-translate-y)'),
+ ]
+ })
+ functionalUtility('translate-y', {
+ supportsNegative: true,
+ supportsFractions: true,
+ themeKeys: ['--translate', '--spacing'],
+ handle: (value) => [
+ translateProperties(),
+ decl('--tw-translate-y', value),
+ decl('translate', 'var(--tw-translate-x) var(--tw-translate-y)'),
+ ],
+ })
+
+ /**
+ * @css `rotate`
+ */
+ functionalUtility('rotate', {
+ supportsNegative: true,
+ themeKeys: ['--rotate'],
+ handleBareValue: ({ value }) => `${value}deg`,
+ handle: (value) => [decl('rotate', value)],
+ })
+
+ suggest('rotate', () => [
+ {
+ supportsNegative: true,
+ values: ['0', '1', '2', '3', '6', '12', '45', '90', '180'],
+ valueThemeKeys: ['--rotate'],
+ },
+ ])
+
+ let skewProperties = () =>
+ atRoot([property('--tw-skew-x', '0deg', ''), property('--tw-skew-y', '0deg', '')])
+
+ /**
+ * @css `transform`
+ * @css `skew()`
+ */
+ functionalUtility('skew', {
+ supportsNegative: true,
+ themeKeys: ['--skew'],
+ handleBareValue: ({ value }) => `${value}deg`,
+ handle: (value) => [
+ skewProperties(),
+ decl('--tw-skew-x', value),
+ decl('--tw-skew-y', value),
+ decl('transform', 'skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))'),
+ ],
+ })
+
+ /**
+ * @css `transform`
+ * @css `skew()`
+ */
+ functionalUtility('skew-x', {
+ supportsNegative: true,
+ themeKeys: ['--skew'],
+ handleBareValue: ({ value }) => `${value}deg`,
+ handle: (value) => [
+ skewProperties(),
+ decl('--tw-skew-x', value),
+ decl('transform', 'skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))'),
+ ],
+ })
+
+ /**
+ * @css `transform`
+ * @css `skew()`
+ */
+ functionalUtility('skew-y', {
+ supportsNegative: true,
+ themeKeys: ['--skew'],
+ handleBareValue: ({ value }) => `${value}deg`,
+ handle: (value) => [
+ skewProperties(),
+ decl('--tw-skew-y', value),
+ decl('transform', 'skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))'),
+ ],
+ })
+
+ suggest('skew', () => [
+ {
+ supportsNegative: true,
+ values: ['0', '1', '2', '3', '6', '12'],
+ valueThemeKeys: ['--skew'],
+ },
+ ])
+
+ suggest('skew-x', () => [
+ {
+ supportsNegative: true,
+ values: ['0', '1', '2', '3', '6', '12'],
+ valueThemeKeys: ['--skew'],
+ },
+ ])
+
+ suggest('skew-y', () => [
+ {
+ supportsNegative: true,
+ values: ['0', '1', '2', '3', '6', '12'],
+ valueThemeKeys: ['--skew'],
+ },
+ ])
+
+ let scaleProperties = () =>
+ atRoot([property('--tw-scale-x', '1', ''), property('--tw-scale-y', '1', '')])
+
+ /**
+ * @css `scale`
+ */
+ functionalUtility('scale', {
+ supportsNegative: true,
+ themeKeys: ['--scale'],
+ handleBareValue: ({ value }) => `${value}%`,
+ handle: (value) => [
+ scaleProperties(),
+ decl('--tw-scale-x', value),
+ decl('--tw-scale-y', value),
+ decl('scale', 'var(--tw-scale-x) var(--tw-scale-y)'),
+ ],
+ })
+
+ /**
+ * @css `scale`
+ */
+ functionalUtility('scale-x', {
+ supportsNegative: true,
+ themeKeys: ['--scale'],
+ handleBareValue: ({ value }) => `${value}%`,
+ handle: (value) => [
+ scaleProperties(),
+ decl('--tw-scale-x', value),
+ decl('scale', 'var(--tw-scale-x) var(--tw-scale-y)'),
+ ],
+ })
+
+ /**
+ * @css `scale`
+ */
+ functionalUtility('scale-y', {
+ supportsNegative: true,
+ themeKeys: ['--scale'],
+ handleBareValue: ({ value }) => `${value}%`,
+ handle: (value) => [
+ scaleProperties(),
+ decl('--tw-scale-y', value),
+ decl('scale', 'var(--tw-scale-x) var(--tw-scale-y)'),
+ ],
+ })
+
+ suggest('scale', () => [
+ {
+ supportsNegative: true,
+ values: ['0', '50', '75', '90', '95', '100', '105', '110', '125', '150', '200'],
+ valueThemeKeys: ['--scale'],
+ },
+ ])
+
+ suggest('scale-x', () => [
+ {
+ supportsNegative: true,
+ values: ['0', '50', '75', '90', '95', '100', '105', '110', '125', '150', '200'],
+ valueThemeKeys: ['--scale'],
+ },
+ ])
+
+ suggest('scale-y', () => [
+ {
+ supportsNegative: true,
+ values: ['0', '50', '75', '90', '95', '100', '105', '110', '125', '150', '200'],
+ valueThemeKeys: ['--scale'],
+ },
+ ])
+
+ /**
+ * @css `transform`
+ */
+ staticUtility('transform', [
+ skewProperties,
+ ['transform', 'skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))'],
+ ])
+
+ staticUtility('transform-cpu', [['transform', 'translate(0,0)']])
+ staticUtility('transform-gpu', [['transform', 'translate(0,0,0)']])
+ staticUtility('transform-none', [
+ ['translate', 'none'],
+ ['rotate', 'none'],
+ ['scale', 'none'],
+ ['transform', 'none'],
+ ])
+
+ /**
+ * @css `cursor`
+ */
+ for (let value of [
+ 'auto',
+ 'default',
+ 'pointer',
+ 'wait',
+ 'text',
+ 'move',
+ 'help',
+ 'not-allowed',
+ 'none',
+ 'context-menu',
+ 'progress',
+ 'cell',
+ 'crosshair',
+ 'vertical-text',
+ 'alias',
+ 'copy',
+ 'no-drop',
+ 'grab',
+ 'grabbing',
+ 'all-scroll',
+ 'col-resize',
+ 'row-resize',
+ 'n-resize',
+ 'e-resize',
+ 's-resize',
+ 'w-resize',
+ 'ne-resize',
+ 'nw-resize',
+ 'se-resize',
+ 'sw-resize',
+ 'ew-resize',
+ 'ns-resize',
+ 'nesw-resize',
+ 'nwse-resize',
+ 'zoom-in',
+ 'zoom-out',
+ ]) {
+ staticUtility(`cursor-${value}`, [['cursor', value]])
+ }
+
+ functionalUtility('cursor', {
+ themeKeys: ['--cursor'],
+ handle: (value) => [decl('cursor', value)],
+ })
+
+ /**
+ * @css `touch-action`
+ */
+ for (let value of ['auto', 'none', 'manipulation']) {
+ staticUtility(`touch-${value}`, [['touch-action', value]])
+ }
+
+ let touchProperties = () =>
+ atRoot([property('--tw-pan-x'), property('--tw-pan-y'), property('--tw-pinch-zoom')])
+
+ for (let value of ['x', 'left', 'right']) {
+ staticUtility(`touch-pan-${value}`, [
+ touchProperties,
+ ['--tw-pan-x', `pan-${value}`],
+ ['touch-action', 'var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)'],
+ ])
+ }
+
+ for (let value of ['y', 'up', 'down']) {
+ staticUtility(`touch-pan-${value}`, [
+ touchProperties,
+ ['--tw-pan-y', `pan-${value}`],
+ ['touch-action', 'var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)'],
+ ])
+ }
+
+ staticUtility('touch-pinch-zoom', [
+ touchProperties,
+ ['--tw-pinch-zoom', `pinch-zoom`],
+ ['touch-action', 'var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)'],
+ ])
+
+ /**
+ * @css `user-select`
+ */
+ for (let value of ['none', 'text', 'all', 'auto']) {
+ staticUtility(`select-${value}`, [
+ ['-webkit-user-select', value],
+ ['user-select', value],
+ ])
+ }
+
+ /**
+ * @css `resize`
+ */
+ staticUtility('resize-none', [['resize', 'none']])
+ staticUtility('resize-both', [['resize', 'both']])
+ staticUtility('resize-x', [['resize', 'horizontal']])
+ staticUtility('resize-y', [['resize', 'vertical']])
+
+ /**
+ * @css `scroll-snap-type`
+ */
+ staticUtility('snap-none', [['scroll-snap-type', 'none']])
+
+ let snapProperties = () => atRoot([property('--tw-scroll-snap-strictness', 'proximity', '*')])
+
+ for (let value of ['x', 'y', 'both']) {
+ staticUtility(`snap-${value}`, [
+ snapProperties,
+ ['scroll-snap-type', `${value} var(--tw-scroll-snap-strictness)`],
+ ])
+ }
+
+ staticUtility('snap-mandatory', [snapProperties, ['--tw-scroll-snap-strictness', 'mandatory']])
+
+ staticUtility('snap-proximity', [snapProperties, ['--tw-scroll-snap-strictness', 'proximity']])
+
+ staticUtility('snap-align-none', [['scroll-snap-align', 'none']])
+ staticUtility('snap-start', [['scroll-snap-align', 'start']])
+ staticUtility('snap-end', [['scroll-snap-align', 'end']])
+ staticUtility('snap-center', [['scroll-snap-align', 'center']])
+
+ staticUtility('snap-normal', [['scroll-snap-stop', 'normal']])
+ staticUtility('snap-always', [['scroll-snap-stop', 'always']])
+
+ functionalUtility('scroll-m', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-margin', '--spacing'],
+ handle: (value) => [decl('scroll-margin', value)],
+ })
+
+ functionalUtility('scroll-mx', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-margin', '--spacing'],
+ handle: (value) => [decl('scroll-margin-left', value), decl('scroll-margin-right', value)],
+ })
+
+ functionalUtility('scroll-my', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-margin', '--spacing'],
+ handle: (value) => [decl('scroll-margin-top', value), decl('scroll-margin-bottom', value)],
+ })
+
+ functionalUtility('scroll-ms', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-margin', '--spacing'],
+ handle: (value) => [decl('scroll-margin-inline-start', value)],
+ })
+
+ functionalUtility('scroll-me', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-margin', '--spacing'],
+ handle: (value) => [decl('scroll-margin-inline-end', value)],
+ })
+
+ functionalUtility('scroll-mt', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-margin', '--spacing'],
+ handle: (value) => [decl('scroll-margin-top', value)],
+ })
+
+ functionalUtility('scroll-mr', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-margin', '--spacing'],
+ handle: (value) => [decl('scroll-margin-right', value)],
+ })
+
+ functionalUtility('scroll-mb', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-margin', '--spacing'],
+ handle: (value) => [decl('scroll-margin-bottom', value)],
+ })
+
+ functionalUtility('scroll-ml', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-margin', '--spacing'],
+ handle: (value) => [decl('scroll-margin-left', value)],
+ })
+
+ // scroll-padding
+ functionalUtility('scroll-p', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-padding', '--spacing'],
+ handle: (value) => [decl('scroll-padding', value)],
+ })
+
+ functionalUtility('scroll-px', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-padding', '--spacing'],
+ handle: (value) => [decl('scroll-padding-left', value), decl('scroll-padding-right', value)],
+ })
+
+ functionalUtility('scroll-py', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-padding', '--spacing'],
+ handle: (value) => [decl('scroll-padding-top', value), decl('scroll-padding-bottom', value)],
+ })
+
+ functionalUtility('scroll-ps', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-padding', '--spacing'],
+ handle: (value) => [decl('scroll-padding-inline-start', value)],
+ })
+
+ functionalUtility('scroll-pe', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-padding', '--spacing'],
+ handle: (value) => [decl('scroll-padding-inline-end', value)],
+ })
+
+ functionalUtility('scroll-pt', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-padding', '--spacing'],
+ handle: (value) => [decl('scroll-padding-top', value)],
+ })
+
+ functionalUtility('scroll-pr', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-padding', '--spacing'],
+ handle: (value) => [decl('scroll-padding-right', value)],
+ })
+
+ functionalUtility('scroll-pb', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-padding', '--spacing'],
+ handle: (value) => [decl('scroll-padding-bottom', value)],
+ })
+
+ functionalUtility('scroll-pl', {
+ supportsNegative: true,
+ themeKeys: ['--scroll-padding', '--spacing'],
+ handle: (value) => [decl('scroll-padding-left', value)],
+ })
+
+ staticUtility('list-inside', [['list-style-position', 'inside']])
+ staticUtility('list-outside', [['list-style-position', 'outside']])
+
+ /**
+ * @css `list-style-type`
+ */
+ staticUtility('list-none', [['list-style-type', 'none']])
+ staticUtility('list-disc', [['list-style-type', 'disc']])
+ staticUtility('list-decimal', [['list-style-type', 'decimal']])
+ functionalUtility('list', {
+ themeKeys: ['--list-style-type'],
+ handle: (value) => [decl('list-style-type', value)],
+ })
+
+ // list-image-*
+
+ staticUtility('list-image-none', [['list-style-image', 'none']])
+ functionalUtility('list-image', {
+ themeKeys: ['--list-style-image'],
+ handle: (value) => [decl('list-style-image', value)],
+ })
+
+ staticUtility('appearance-none', [['appearance', 'none']])
+ staticUtility('appearance-auto', [['appearance', 'auto']])
+
+ // columns-*
+ staticUtility('columns-auto', [['columns', 'auto']])
+
+ functionalUtility('columns', {
+ themeKeys: ['--columns', '--width'],
+ handleBareValue: ({ value }) => value,
+ handle: (value) => [decl('columns', value)],
+ })
+
+ suggest('columns', () => [
+ {
+ values: Array.from({ length: 12 }, (_, i) => `${i + 1}`),
+ valueThemeKeys: ['--columns', '--width'],
+ },
+ ])
+
+ for (let value of ['auto', 'avoid', 'all', 'avoid-page', 'page', 'left', 'right', 'column']) {
+ staticUtility(`break-before-${value}`, [['break-before', value]])
+ }
+
+ for (let value of ['auto', 'avoid', 'avoid-page', 'avoid-column']) {
+ staticUtility(`break-inside-${value}`, [['break-inside', value]])
+ }
+
+ for (let value of ['auto', 'avoid', 'all', 'avoid-page', 'page', 'left', 'right', 'column']) {
+ staticUtility(`break-after-${value}`, [['break-after', value]])
+ }
+
+ staticUtility('grid-flow-row', [['grid-auto-flow', 'row']])
+ staticUtility('grid-flow-col', [['grid-auto-flow', 'column']])
+ staticUtility('grid-flow-dense', [['grid-auto-flow', 'dense']])
+ staticUtility('grid-flow-row-dense', [['grid-auto-flow', 'row dense']])
+ staticUtility('grid-flow-col-dense', [['grid-auto-flow', 'column dense']])
+
+ staticUtility('auto-cols-auto', [['grid-auto-columns', 'auto']])
+ staticUtility('auto-cols-min', [['grid-auto-columns', 'min-content']])
+ staticUtility('auto-cols-max', [['grid-auto-columns', 'max-content']])
+ staticUtility('auto-cols-fr', [['grid-auto-columns', 'minmax(0, 1fr)']])
+ functionalUtility('auto-cols', {
+ themeKeys: ['--grid-auto-columns'],
+ handle: (value) => [decl('grid-auto-columns', value)],
+ })
+
+ staticUtility('auto-rows-auto', [['grid-auto-rows', 'auto']])
+ staticUtility('auto-rows-min', [['grid-auto-rows', 'min-content']])
+ staticUtility('auto-rows-max', [['grid-auto-rows', 'max-content']])
+ staticUtility('auto-rows-fr', [['grid-auto-rows', 'minmax(0, 1fr)']])
+ functionalUtility('auto-rows', {
+ themeKeys: ['--grid-auto-rows'],
+ handle: (value) => [decl('grid-auto-rows', value)],
+ })
+
+ staticUtility('grid-cols-none', [['grid-template-columns', 'none']])
+ staticUtility('grid-cols-subgrid', [['grid-template-columns', 'subgrid']])
+ functionalUtility('grid-cols', {
+ themeKeys: ['--grid-template-columns'],
+ handleBareValue: ({ value }) => `repeat(${value}, minmax(0, 1fr))`,
+ handle: (value) => [decl('grid-template-columns', value)],
+ })
+
+ staticUtility('grid-rows-none', [['grid-template-rows', 'none']])
+ staticUtility('grid-rows-subgrid', [['grid-template-rows', 'subgrid']])
+ functionalUtility('grid-rows', {
+ themeKeys: ['--grid-template-rows'],
+ handleBareValue: ({ value }) => `repeat(${value}, minmax(0, 1fr))`,
+ handle: (value) => [decl('grid-template-rows', value)],
+ })
+
+ suggest('grid-cols', () => [
+ {
+ values: Array.from({ length: 12 }, (_, i) => `${i + 1}`),
+ valueThemeKeys: ['--grid-template-columns'],
+ },
+ ])
+
+ suggest('grid-rows', () => [
+ {
+ values: Array.from({ length: 12 }, (_, i) => `${i + 1}`),
+ valueThemeKeys: ['--grid-template-rows'],
+ },
+ ])
+
+ staticUtility('flex-row', [['flex-direction', 'row']])
+ staticUtility('flex-row-reverse', [['flex-direction', 'row-reverse']])
+ staticUtility('flex-col', [['flex-direction', 'column']])
+ staticUtility('flex-col-reverse', [['flex-direction', 'column-reverse']])
+
+ staticUtility('flex-wrap', [['flex-wrap', 'wrap']])
+ staticUtility('flex-nowrap', [['flex-wrap', 'nowrap']])
+ staticUtility('flex-wrap-reverse', [['flex-wrap', 'wrap-reverse']])
+
+ staticUtility('place-content-center', [['place-content', 'center']])
+ staticUtility('place-content-start', [['place-content', 'start']])
+ staticUtility('place-content-end', [['place-content', 'end']])
+ staticUtility('place-content-between', [['place-content', 'between']])
+ staticUtility('place-content-around', [['place-content', 'around']])
+ staticUtility('place-content-evenly', [['place-content', 'evenly']])
+ staticUtility('place-content-baseline', [['place-content', 'baseline']])
+ staticUtility('place-content-stretch', [['place-content', 'stretch']])
+
+ staticUtility('place-items-center', [['place-items', 'center']])
+ staticUtility('place-items-start', [['place-items', 'start']])
+ staticUtility('place-items-end', [['place-items', 'end']])
+ staticUtility('place-items-baseline', [['place-items', 'baseline']])
+ staticUtility('place-items-stretch', [['place-items', 'stretch']])
+
+ staticUtility('content-normal', [['align-content', 'normal']])
+ staticUtility('content-center', [['align-content', 'center']])
+ staticUtility('content-start', [['align-content', 'flex-start']])
+ staticUtility('content-end', [['align-content', 'flex-end']])
+ staticUtility('content-between', [['align-content', 'space-between']])
+ staticUtility('content-around', [['align-content', 'space-around']])
+ staticUtility('content-evenly', [['align-content', 'space-evenly']])
+ staticUtility('content-baseline', [['align-content', 'baseline']])
+ staticUtility('content-stretch', [['align-content', 'stretch']])
+
+ staticUtility('items-center', [['align-items', 'center']])
+ staticUtility('items-start', [['align-items', 'flex-start']])
+ staticUtility('items-end', [['align-items', 'flex-end']])
+ staticUtility('items-baseline', [['align-items', 'baseline']])
+ staticUtility('items-stretch', [['align-items', 'stretch']])
+
+ staticUtility('justify-normal', [['justify-content', 'normal']])
+ staticUtility('justify-center', [['justify-content', 'center']])
+ staticUtility('justify-start', [['justify-content', 'flex-start']])
+ staticUtility('justify-end', [['justify-content', 'flex-end']])
+ staticUtility('justify-between', [['justify-content', 'space-between']])
+ staticUtility('justify-around', [['justify-content', 'space-around']])
+ staticUtility('justify-evenly', [['justify-content', 'space-evenly']])
+ staticUtility('justify-baseline', [['justify-content', 'baseline']])
+ staticUtility('justify-stretch', [['justify-content', 'stretch']])
+
+ staticUtility('justify-items-normal', [['justify-items', 'normal']])
+ staticUtility('justify-items-center', [['justify-items', 'center']])
+ staticUtility('justify-items-start', [['justify-items', 'start']])
+ staticUtility('justify-items-end', [['justify-items', 'end']])
+ staticUtility('justify-items-stretch', [['justify-items', 'stretch']])
+
+ functionalUtility('gap', {
+ themeKeys: ['--gap', '--spacing'],
+ handle: (value) => [decl('gap', value)],
+ })
+
+ functionalUtility('gap-x', {
+ themeKeys: ['--gap', '--spacing'],
+ handle: (value) => [decl('column-gap', value)],
+ })
+
+ functionalUtility('gap-y', {
+ themeKeys: ['--gap', '--spacing'],
+ handle: (value) => [decl('row-gap', value)],
+ })
+
+ functionalUtility('space-x', {
+ supportsNegative: true,
+ themeKeys: ['--space', '--spacing'],
+ handle: (value) => [
+ atRoot([property('--tw-space-x-reverse', '0', '')]),
+
+ rule(':where(& > :not([hidden]) ~ :not([hidden]))', [
+ decl('--tw-sort', 'row-gap'),
+ decl('margin-inline-end', `calc(${value} * var(--tw-space-x-reverse))`),
+ decl('margin-inline-start', `calc(${value} * calc(1 - var(--tw-space-x-reverse)))`),
+ ]),
+ ],
+ })
+
+ functionalUtility('space-y', {
+ supportsNegative: true,
+ themeKeys: ['--space', '--spacing'],
+ handle: (value) => [
+ atRoot([property('--tw-space-y-reverse', '0', '')]),
+
+ rule(':where(& > :not([hidden]) ~ :not([hidden]))', [
+ decl('--tw-sort', 'column-gap'),
+ decl('margin-bottom', `calc(${value} * var(--tw-space-y-reverse))`),
+ decl('margin-top', `calc(${value} * calc(1 - var(--tw-space-y-reverse)))`),
+ ]),
+ ],
+ })
+
+ staticUtility('space-x-reverse', [
+ () => atRoot([property('--tw-space-x-reverse', '0', '')]),
+ () =>
+ rule(':where(& > :not([hidden]) ~ :not([hidden]))', [
+ decl('--tw-sort', 'row-gap'),
+ decl('--tw-space-x-reverse', '1'),
+ ]),
+ ])
+
+ staticUtility('space-y-reverse', [
+ () => atRoot([property('--tw-space-y-reverse', '0', '')]),
+ () =>
+ rule(':where(& > :not([hidden]) ~ :not([hidden]))', [
+ decl('--tw-sort', 'column-gap'),
+ decl('--tw-space-y-reverse', '1'),
+ ]),
+ ])
+
+ colorUtility('accent', {
+ themeKeys: ['--accent-color', '--color'],
+ handle: (value) => [decl('accent-color', value)],
+ })
+
+ colorUtility('caret', {
+ themeKeys: ['--caret-color', '--color'],
+ handle: (value) => [decl('caret-color', value)],
+ })
+
+ colorUtility('divide', {
+ themeKeys: ['--divide-color', '--color'],
+ handle: (value) => [
+ rule(':where(& > :not([hidden]) ~ :not([hidden]))', [
+ decl('--tw-sort', 'divide-color'),
+ decl('border-color', value),
+ ]),
+ ],
+ })
+
+ staticUtility('place-self-auto', [['place-self', 'auto']])
+ staticUtility('place-self-start', [['place-self', 'start']])
+ staticUtility('place-self-end', [['place-self', 'end']])
+ staticUtility('place-self-center', [['place-self', 'center']])
+ staticUtility('place-self-stretch', [['place-self', 'stretch']])
+
+ staticUtility('self-auto', [['align-self', 'auto']])
+ staticUtility('self-start', [['align-self', 'flex-start']])
+ staticUtility('self-end', [['align-self', 'flex-end']])
+ staticUtility('self-center', [['align-self', 'center']])
+ staticUtility('self-stretch', [['align-self', 'stretch']])
+ staticUtility('self-baseline', [['align-self', 'baseline']])
+
+ staticUtility('justify-self-auto', [['justify-self', 'auto']])
+ staticUtility('justify-self-start', [['justify-self', 'flex-start']])
+ staticUtility('justify-self-end', [['justify-self', 'flex-end']])
+ staticUtility('justify-self-center', [['justify-self', 'center']])
+ staticUtility('justify-self-stretch', [['justify-self', 'stretch']])
+
+ for (let value of ['auto', 'hidden', 'clip', 'visible', 'scroll']) {
+ staticUtility(`overflow-${value}`, [['overflow', value]])
+ staticUtility(`overflow-x-${value}`, [['overflow-x', value]])
+ staticUtility(`overflow-y-${value}`, [['overflow-y', value]])
+ }
+
+ for (let value of ['auto', 'contain', 'none']) {
+ staticUtility(`overscroll-${value}`, [['overscroll-behavior', value]])
+ staticUtility(`overscroll-x-${value}`, [['overscroll-behavior-x', value]])
+ staticUtility(`overscroll-y-${value}`, [['overscroll-behavior-y', value]])
+ }
+
+ staticUtility('scroll-auto', [['scroll-behavior', 'auto']])
+ staticUtility('scroll-smooth', [['scroll-behavior', 'smooth']])
+
+ staticUtility('truncate', [
+ ['overflow', 'hidden'],
+ ['text-overflow', 'ellipsis'],
+ ['white-space', 'nowrap'],
+ ])
+
+ staticUtility('text-ellipsis', [['text-overflow', 'ellipsis']])
+ staticUtility('text-clip', [['text-overflow', 'clip']])
+
+ staticUtility('hyphens-none', [
+ ['-webkit-hyphens', 'none'],
+ ['hyphens', 'none'],
+ ])
+ staticUtility('hyphens-manual', [
+ ['-webkit-hyphens', 'manual'],
+ ['hyphens', 'manual'],
+ ])
+ staticUtility('hyphens-auto', [
+ ['-webkit-hyphens', 'auto'],
+ ['hyphens', 'auto'],
+ ])
+
+ staticUtility('whitespace-normal', [['white-space', 'normal']])
+ staticUtility('whitespace-nowrap', [['white-space', 'nowrap']])
+ staticUtility('whitespace-pre', [['white-space', 'pre']])
+ staticUtility('whitespace-pre-line', [['white-space', 'pre-line']])
+ staticUtility('whitespace-pre-wrap', [['white-space', 'pre-wrap']])
+ staticUtility('whitespace-break-spaces', [['white-space', 'break-spaces']])
+
+ staticUtility('text-wrap', [['text-wrap', 'wrap']])
+ staticUtility('text-nowrap', [['text-wrap', 'nowrap']])
+ staticUtility('text-balance', [['text-wrap', 'balance']])
+ staticUtility('text-pretty', [['text-wrap', 'pretty']])
+ staticUtility('break-normal', [
+ ['overflow-wrap', 'normal'],
+ ['word-break', 'normal'],
+ ])
+ staticUtility('break-words', [['overflow-wrap', 'break-word']])
+ staticUtility('break-all', [['word-break', 'break-all']])
+ staticUtility('break-keep', [['word-break', 'break-keep']])
+
+ // rounded-*
+ functionalUtility('rounded', {
+ themeKeys: ['--radius'],
+ handle: (value) => [decl('border-radius', value)],
+ })
+
+ functionalUtility('rounded-s', {
+ themeKeys: ['--radius'],
+ handle: (value) => [
+ decl('border-start-start-radius', value),
+ decl('border-end-start-radius', value),
+ ],
+ })
+
+ functionalUtility('rounded-e', {
+ themeKeys: ['--radius'],
+ handle: (value) => [
+ decl('border-start-end-radius', value),
+ decl('border-end-end-radius', value),
+ ],
+ })
+
+ functionalUtility('rounded-t', {
+ themeKeys: ['--radius'],
+ handle: (value) => [
+ decl('border-top-left-radius', value),
+ decl('border-top-right-radius', value),
+ ],
+ })
+
+ functionalUtility('rounded-r', {
+ themeKeys: ['--radius'],
+ handle: (value) => [
+ decl('border-top-right-radius', value),
+ decl('border-bottom-right-radius', value),
+ ],
+ })
+
+ functionalUtility('rounded-b', {
+ themeKeys: ['--radius'],
+ handle: (value) => [
+ decl('border-bottom-right-radius', value),
+ decl('border-bottom-left-radius', value),
+ ],
+ })
+
+ functionalUtility('rounded-l', {
+ themeKeys: ['--radius'],
+ handle: (value) => [
+ decl('border-top-left-radius', value),
+ decl('border-bottom-left-radius', value),
+ ],
+ })
+
+ functionalUtility('rounded-ss', {
+ themeKeys: ['--radius'],
+ handle: (value) => [decl('border-start-start-radius', value)],
+ })
+
+ functionalUtility('rounded-se', {
+ themeKeys: ['--radius'],
+ handle: (value) => [decl('border-start-end-radius', value)],
+ })
+
+ functionalUtility('rounded-ee', {
+ themeKeys: ['--radius'],
+ handle: (value) => [decl('border-end-end-radius', value)],
+ })
+
+ functionalUtility('rounded-es', {
+ themeKeys: ['--radius'],
+ handle: (value) => [decl('border-end-start-radius', value)],
+ })
+
+ functionalUtility('rounded-tl', {
+ themeKeys: ['--radius'],
+ handle: (value) => [decl('border-top-left-radius', value)],
+ })
+
+ functionalUtility('rounded-tr', {
+ themeKeys: ['--radius'],
+ handle: (value) => [decl('border-top-right-radius', value)],
+ })
+
+ functionalUtility('rounded-br', {
+ themeKeys: ['--radius'],
+ handle: (value) => [decl('border-bottom-right-radius', value)],
+ })
+
+ functionalUtility('rounded-bl', {
+ themeKeys: ['--radius'],
+ handle: (value) => [decl('border-bottom-left-radius', value)],
+ })
+
+ staticUtility('border-solid', [
+ ['--tw-border-style', 'solid'],
+ ['border-style', 'solid'],
+ ])
+ staticUtility('border-dashed', [
+ ['--tw-border-style', 'dashed'],
+ ['border-style', 'dashed'],
+ ])
+ staticUtility('border-dotted', [
+ ['--tw-border-style', 'dotted'],
+ ['border-style', 'dotted'],
+ ])
+ staticUtility('border-double', [
+ ['--tw-border-style', 'double'],
+ ['border-style', 'double'],
+ ])
+ staticUtility('border-hidden', [
+ ['--tw-border-style', 'hidden'],
+ ['border-style', 'hidden'],
+ ])
+ staticUtility('border-none', [
+ ['--tw-border-style', 'none'],
+ ['border-style', 'none'],
+ ])
+
+ {
+ // border-* (color)
+ type BorderDescription = {
+ width: (value: string) => AstNode[] | undefined
+ color: (value: string) => AstNode[] | undefined
+ }
+
+ let borderProperties = () => {
+ return atRoot([property('--tw-border-style', 'solid', '')])
+ }
+
+ function borderSideUtility(classRoot: string, desc: BorderDescription) {
+ utilities.functional(classRoot, (candidate) => {
+ if (candidate.negative) return
+
+ if (!candidate.value) {
+ let value = theme.get(['--default-border-width']) ?? '1px'
+ let decls = desc.width(value)
+ if (!decls) return
+ return [borderProperties(), decl('border-style', 'var(--tw-border-style)'), ...decls]
+ }
+
+ if (candidate.value.kind === 'arbitrary') {
+ let value: string | null = candidate.value.value
+ let type =
+ candidate.value.dataType ?? inferDataType(value, ['color', 'line-width', 'length'])
+
+ switch (type) {
+ case 'line-width':
+ case 'length': {
+ let decls = desc.width(value)
+ if (!decls) return
+ return [borderProperties(), decl('border-style', 'var(--tw-border-style)'), ...decls]
+ }
+ default: {
+ value = asColor(value, candidate.modifier, theme)
+ if (value === null) return
+
+ return desc.color(value)
+ }
+ }
+ }
+
+ // `border-color` property
+ {
+ let value = resolveThemeColor(candidate, theme, ['--border-color', '--color'])
+ if (value) {
+ return desc.color(value)
+ }
+ }
+
+ // `border-width` property
+ {
+ let value = theme.resolve(candidate.value.value, ['--border-width'])
+ if (value) {
+ let decls = desc.width(value)
+ if (!decls) return
+ return [borderProperties(), decl('border-style', 'var(--tw-border-style)'), ...decls]
+ }
+
+ if (!Number.isNaN(Number(candidate.value.value))) {
+ let decls = desc.width(`${candidate.value.value}px`)
+ if (!decls) return
+ return [borderProperties(), decl('border-style', 'var(--tw-border-style)'), ...decls]
+ }
+ }
+ })
+
+ suggest(classRoot, () => [
+ {
+ values: ['current', 'transparent'],
+ valueThemeKeys: ['--border-color', '--color'],
+ modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
+ modifierThemeKeys: ['--opacity'],
+ hasDefaultValue: true,
+ },
+ {
+ values: [],
+ valueThemeKeys: ['--border-width'],
+ },
+ ])
+ }
+
+ borderSideUtility('border', {
+ width: (value) => [decl('border-width', value)],
+ color: (value) => [decl('border-color', value)],
+ })
+
+ borderSideUtility('border-x', {
+ width: (value) => [decl('border-left-width', value), decl('border-right-width', value)],
+ color: (value) => [decl('border-left-color', value), decl('border-right-color', value)],
+ })
+
+ borderSideUtility('border-y', {
+ width: (value) => [decl('border-top-width', value), decl('border-bottom-width', value)],
+ color: (value) => [decl('border-top-color', value), decl('border-bottom-color', value)],
+ })
+
+ borderSideUtility('border-s', {
+ width: (value) => [decl('border-inline-start-width', value)],
+ color: (value) => [decl('border-inline-start-color', value)],
+ })
+
+ borderSideUtility('border-e', {
+ width: (value) => [decl('border-inline-end-width', value)],
+ color: (value) => [decl('border-inline-end-color', value)],
+ })
+
+ borderSideUtility('border-t', {
+ width: (value) => [decl('border-top-width', value)],
+ color: (value) => [decl('border-top-color', value)],
+ })
+
+ borderSideUtility('border-r', {
+ width: (value) => [decl('border-right-width', value)],
+ color: (value) => [decl('border-right-color', value)],
+ })
+
+ borderSideUtility('border-b', {
+ width: (value) => [decl('border-bottom-width', value)],
+ color: (value) => [decl('border-bottom-color', value)],
+ })
+
+ borderSideUtility('border-l', {
+ width: (value) => [decl('border-left-width', value)],
+ color: (value) => [decl('border-left-color', value)],
+ })
+
+ functionalUtility('divide-x', {
+ defaultValue: theme.get(['--default-border-width']) ?? '1px',
+ themeKeys: ['--divide-width', '--border-width'],
+ handleBareValue: ({ value }) => `${value}px`,
+ handle: (value) => [
+ atRoot([property('--tw-divide-x-reverse', '0', '')]),
+
+ rule(':where(& > :not([hidden]) ~ :not([hidden]))', [
+ decl('--tw-sort', 'divide-x-width'),
+ borderProperties(),
+ decl('border-style', 'var(--tw-border-style)'),
+ decl('border-inline-end-width', `calc(${value} * var(--tw-divide-x-reverse))`),
+ decl(
+ 'border-inline-start-width',
+ `calc(${value} * calc(1 - var(--tw-divide-x-reverse)))`,
+ ),
+ ]),
+ ],
+ })
+
+ functionalUtility('divide-y', {
+ defaultValue: theme.get(['--default-border-width']) ?? '1px',
+ themeKeys: ['--divide-width', '--border-width'],
+ handleBareValue: ({ value }) => `${value}px`,
+ handle: (value) => [
+ atRoot([property('--tw-divide-y-reverse', '0', '')]),
+
+ rule(':where(& > :not([hidden]) ~ :not([hidden]))', [
+ decl('--tw-sort', 'divide-y-width'),
+ borderProperties(),
+ decl('border-style', 'var(--tw-border-style)'),
+ decl('border-bottom-width', `calc(${value} * var(--tw-divide-y-reverse))`),
+ decl('border-top-width', `calc(${value} * calc(1 - var(--tw-divide-y-reverse)))`),
+ ]),
+ ],
+ })
+
+ suggest('divide-x', () => [
+ {
+ values: ['0', '2', '4', '8'],
+ valueThemeKeys: ['--divide-width', '--border-width'],
+ hasDefaultValue: true,
+ },
+ ])
+
+ suggest('divide-y', () => [
+ {
+ values: ['0', '2', '4', '8'],
+ valueThemeKeys: ['--divide-width', '--border-width'],
+ hasDefaultValue: true,
+ },
+ ])
+
+ staticUtility('divide-x-reverse', [
+ () => atRoot([property('--tw-divide-x-reverse', '0', '')]),
+ () =>
+ rule(':where(& > :not([hidden]) ~ :not([hidden]))', [decl('--tw-divide-x-reverse', '1')]),
+ ])
+
+ staticUtility('divide-y-reverse', [
+ () => atRoot([property('--tw-divide-y-reverse', '0', '')]),
+ () =>
+ rule(':where(& > :not([hidden]) ~ :not([hidden]))', [decl('--tw-divide-y-reverse', '1')]),
+ ])
+
+ for (let value of ['solid', 'dashed', 'dotted', 'double', 'none']) {
+ staticUtility(`divide-${value}`, [
+ () =>
+ rule(':where(& > :not([hidden]) ~ :not([hidden]))', [
+ decl('--tw-sort', 'divide-style'),
+ decl('--tw-border-style', value),
+ decl('border-style', value),
+ ]),
+ ])
+ }
+ }
+
+ staticUtility('bg-inherit', [['background-color', 'inherit']])
+ staticUtility('bg-transparent', [['background-color', 'transparent']])
+
+ staticUtility('bg-auto', [['background-size', 'auto']])
+ staticUtility('bg-cover', [['background-size', 'cover']])
+ staticUtility('bg-contain', [['background-size', 'contain']])
+
+ staticUtility('bg-fixed', [['background-attachment', 'fixed']])
+ staticUtility('bg-local', [['background-attachment', 'local']])
+ staticUtility('bg-scroll', [['background-attachment', 'scroll']])
+
+ staticUtility('bg-center', [['background-position', 'center']])
+ staticUtility('bg-top', [['background-position', 'top']])
+ staticUtility('bg-right-top', [['background-position', 'right-top']])
+ staticUtility('bg-right', [['background-position', 'right']])
+ staticUtility('bg-right-bottom', [['background-position', 'right-bottom']])
+ staticUtility('bg-bottom', [['background-position', 'bottom']])
+ staticUtility('bg-left-bottom', [['background-position', 'left-bottom']])
+ staticUtility('bg-left', [['background-position', 'left']])
+ staticUtility('bg-left-top', [['background-position', 'left-top']])
+
+ staticUtility('bg-repeat', [['background-repeat', 'repeat']])
+ staticUtility('bg-no-repeat', [['background-repeat', 'no-repeat']])
+ staticUtility('bg-repeat-x', [['background-repeat', 'repeat-x']])
+ staticUtility('bg-repeat-y', [['background-repeat', 'repeat-y']])
+ staticUtility('bg-round', [['background-repeat', 'round']])
+ staticUtility('bg-space', [['background-repeat', 'space']])
+
+ staticUtility('bg-none', [['background-image', 'none']])
+
+ for (let [value, direction] of [
+ ['t', 'top'],
+ ['tr', 'top right'],
+ ['r', 'right'],
+ ['br', 'bottom right'],
+ ['b', 'bottom'],
+ ['bl', 'bottom left'],
+ ['l', 'left'],
+ ['tl', 'top left'],
+ ]) {
+ staticUtility(`bg-gradient-to-${value}`, [
+ ['background-image', `linear-gradient(to ${direction}, var(--tw-gradient-stops,))`],
+ ])
+ }
+
+ utilities.functional('bg', (candidate) => {
+ if (candidate.negative || !candidate.value) return
+
+ // Arbitrary values
+ if (candidate.value.kind === 'arbitrary') {
+ let value: string | null = candidate.value.value
+ let type =
+ candidate.value.dataType ??
+ inferDataType(value, [
+ 'image',
+ 'color',
+ 'length',
+ 'percentage',
+ 'position',
+ 'bg-size',
+ 'url',
+ ])
+
+ switch (type) {
+ case 'length':
+ case 'percentage':
+ case 'position': {
+ return [decl('background-position', value)]
+ }
+ case 'size':
+ case 'bg-size': {
+ return [decl('background-size', value)]
+ }
+ case 'image':
+ case 'url': {
+ return [decl('background-image', value)]
+ }
+ default: {
+ value = asColor(value, candidate.modifier, theme)
+ if (value === null) return
+
+ return [decl('background-color', value)]
+ }
+ }
+ }
+
+ // `background-color` property
+ {
+ let value = resolveThemeColor(candidate, theme, ['--background-color', '--color'])
+ if (value) {
+ return [decl('background-color', value)]
+ }
+ }
+
+ // `background-image` property
+ {
+ let value = theme.resolve(candidate.value.value, ['--background-image'])
+ if (value) {
+ return [decl('background-image', value)]
+ }
+ }
+ })
+
+ suggest('bg', () => [
+ {
+ values: ['current', 'transparent'],
+ valueThemeKeys: ['--background-color', '--color'],
+ modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
+ modifierThemeKeys: ['--opacity'],
+ },
+ {
+ values: [],
+ valueThemeKeys: ['--background-image'],
+ },
+ ])
+
+ let gradientStopProperties = () => {
+ return atRoot([
+ property('--tw-gradient-from', '#0000', ''),
+ property('--tw-gradient-to', '#0000', ''),
+ property('--tw-gradient-from', 'transparent', ''),
+ property('--tw-gradient-via', 'transparent', ''),
+ property('--tw-gradient-to', 'transparent', ''),
+ property('--tw-gradient-stops'),
+ property('--tw-gradient-via-stops'),
+ property('--tw-gradient-from-position', '0%', ''),
+ property('--tw-gradient-via-position', '50%', ''),
+ property('--tw-gradient-to-position', '100%', ''),
+ ])
+ }
+
+ type GradientStopDescription = {
+ color: (value: string) => AstNode[] | undefined
+ position: (value: string) => AstNode[] | undefined
+ }
+
+ function gradientStopUtility(classRoot: string, desc: GradientStopDescription) {
+ utilities.functional(classRoot, (candidate) => {
+ if (candidate.negative || !candidate.value) return
+
+ // Arbitrary values
+ if (candidate.value.kind === 'arbitrary') {
+ let value: string | null = candidate.value.value
+ let type =
+ candidate.value.dataType ?? inferDataType(value, ['color', 'length', 'percentage'])
+
+ switch (type) {
+ case 'length':
+ case 'percentage': {
+ return desc.position(value)
+ }
+ default: {
+ value = asColor(value, candidate.modifier, theme)
+ if (value === null) return
+
+ return desc.color(value)
+ }
+ }
+ }
+
+ // Known values: Color Stops
+ {
+ let value = resolveThemeColor(candidate, theme, ['--background-color', '--color'])
+ if (value) {
+ return desc.color(value)
+ }
+ }
+
+ // Known values: Positions
+ {
+ let value = theme.resolve(candidate.value.value, ['--gradient-color-stop-positions'])
+ if (value) {
+ return desc.position(value)
+ } else if (candidate.value.value[candidate.value.value.length - 1] === '%') {
+ return desc.position(candidate.value.value)
+ }
+ }
+ })
+
+ suggest(classRoot, () => [
+ {
+ values: ['current', 'transparent'],
+ valueThemeKeys: ['--background-color', '--color'],
+ modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
+ modifierThemeKeys: ['--opacity'],
+ },
+ {
+ values: Array.from({ length: 21 }, (_, index) => `${index * 5}%`),
+ valueThemeKeys: ['--gradient-color-stop-positions'],
+ },
+ ])
+ }
+
+ gradientStopUtility('from', {
+ color: (value) => [
+ gradientStopProperties(),
+ decl('--tw-sort', '--tw-gradient-from'),
+ decl('--tw-gradient-from', value),
+ decl(
+ '--tw-gradient-stops',
+ 'var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))',
+ ),
+ ],
+ position: (value) => [gradientStopProperties(), decl('--tw-gradient-from-position', value)],
+ })
+ staticUtility('via-none', [['--tw-gradient-via-stops', 'initial']])
+ gradientStopUtility('via', {
+ color: (value) => [
+ gradientStopProperties(),
+ decl('--tw-sort', '--tw-gradient-via'),
+ decl('--tw-gradient-via', value),
+ decl(
+ '--tw-gradient-via-stops',
+ 'var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position)',
+ ),
+ decl('--tw-gradient-stops', 'var(--tw-gradient-via-stops)'),
+ ],
+ position: (value) => [gradientStopProperties(), decl('--tw-gradient-via-position', value)],
+ })
+ gradientStopUtility('to', {
+ color: (value) => [
+ gradientStopProperties(),
+ decl('--tw-sort', '--tw-gradient-to'),
+ decl('--tw-gradient-to', value),
+ decl(
+ '--tw-gradient-stops',
+ 'var(--tw-gradient-via-stops, var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))',
+ ),
+ ],
+ position: (value) => [gradientStopProperties(), decl('--tw-gradient-to-position', value)],
+ })
+
+ /**
+ * @css `box-decoration-break`
+ * @deprecated
+ */
+ staticUtility('decoration-slice', [
+ ['-webkit-box-decoration-break', 'slice'],
+ ['box-decoration-break', 'slice'],
+ ])
+ /**
+ * @deprecated
+ */
+ staticUtility('decoration-clone', [
+ ['-webkit-box-decoration-break', 'clone'],
+ ['box-decoration-break', 'clone'],
+ ])
+
+ staticUtility('box-decoration-slice', [
+ ['-webkit-box-decoration-break', 'slice'],
+ ['box-decoration-break', 'slice'],
+ ])
+ staticUtility('box-decoration-clone', [
+ ['-webkit-box-decoration-break', 'clone'],
+ ['box-decoration-break', 'clone'],
+ ])
+
+ /**
+ * @css `background-clip`
+ */
+ staticUtility('bg-clip-text', [['background-clip', 'text']])
+ staticUtility('bg-clip-border', [['background-clip', 'border-box']])
+ staticUtility('bg-clip-padding', [['background-clip', 'padding-box']])
+ staticUtility('bg-clip-content', [['background-clip', 'content-box']])
+
+ staticUtility('bg-origin-border', [['background-origin', 'border-box']])
+ staticUtility('bg-origin-padding', [['background-origin', 'padding-box']])
+ staticUtility('bg-origin-content', [['background-origin', 'content-box']])
+
+ for (let value of [
+ 'normal',
+ 'multiply',
+ 'screen',
+ 'overlay',
+ 'darken',
+ 'lighten',
+ 'color-dodge',
+ 'color-burn',
+ 'hard-light',
+ 'soft-light',
+ 'difference',
+ 'exclusion',
+ 'hue',
+ 'saturation',
+ 'color',
+ 'luminosity',
+ ]) {
+ staticUtility(`bg-blend-${value}`, [['background-blend-mode', value]])
+ staticUtility(`mix-blend-${value}`, [['mix-blend-mode', value]])
+ }
+
+ staticUtility('mix-blend-plus-darker', [['mix-blend-mode', 'plus-darker']])
+ staticUtility('mix-blend-plus-lighter', [['mix-blend-mode', 'plus-lighter']])
+
+ utilities.functional('fill', (candidate) => {
+ if (candidate.negative || !candidate.value) return
+
+ if (candidate.value.kind === 'arbitrary') {
+ let value = asColor(candidate.value.value, candidate.modifier, theme)
+ if (value === null) return
+ return [decl('fill', value)]
+ }
+
+ let value = resolveThemeColor(candidate, theme, ['--fill', '--color'])
+ if (value) {
+ return [decl('fill', value)]
+ }
+ })
+
+ suggest('fill', () => [
+ {
+ values: ['current', 'transparent'],
+ valueThemeKeys: ['--fill', '--color'],
+ modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
+ modifierThemeKeys: ['--opacity'],
+ },
+ ])
+
+ staticUtility('stroke-none', [['stroke', 'none']])
+ utilities.functional('stroke', (candidate) => {
+ if (candidate.negative || !candidate.value) return
+
+ if (candidate.value.kind === 'arbitrary') {
+ let value: string | null = candidate.value.value
+ let type =
+ candidate.value.dataType ??
+ inferDataType(value, ['color', 'number', 'length', 'percentage'])
+
+ switch (type) {
+ case 'number':
+ case 'length':
+ case 'percentage': {
+ return [decl('stroke-width', value)]
+ }
+ default: {
+ value = asColor(candidate.value.value, candidate.modifier, theme)
+ if (value === null) return
+
+ return [decl('stroke', value)]
+ }
+ }
+ }
+
+ {
+ let value = resolveThemeColor(candidate, theme, ['--stroke', '--color'])
+ if (value) {
+ return [decl('stroke', value)]
+ }
+ }
+
+ {
+ let value = theme.resolve(candidate.value.value, ['--stroke-width'])
+ if (value) {
+ return [decl('stroke-width', value)]
+ } else if (!Number.isNaN(Number(candidate.value.value))) {
+ return [decl('stroke-width', candidate.value.value)]
+ }
+ }
+ })
+
+ suggest('stroke', () => [
+ {
+ values: ['current', 'transparent'],
+ valueThemeKeys: ['--stroke', '--color'],
+ modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
+ modifierThemeKeys: ['--opacity'],
+ },
+ {
+ values: ['0', '1', '2', '3'],
+ valueThemeKeys: ['--stroke-width'],
+ },
+ ])
+
+ staticUtility('object-contain', [['object-fit', 'contain']])
+ staticUtility('object-cover', [['object-fit', 'cover']])
+ staticUtility('object-fill', [['object-fit', 'fill']])
+ staticUtility('object-none', [['object-fit', 'none']])
+ staticUtility('object-scale-down', [['object-fit', 'scale-down']])
+
+ staticUtility('object-bottom', [['object-position', 'bottom']])
+ staticUtility('object-center', [['object-position', 'center']])
+ staticUtility('object-left', [['object-position', 'left']])
+ staticUtility('object-left-bottom', [['object-position', 'left bottom']])
+ staticUtility('object-left-top', [['object-position', 'left top']])
+ staticUtility('object-right', [['object-position', 'right']])
+ staticUtility('object-right-bottom', [['object-position', 'right bottom']])
+ staticUtility('object-right-top', [['object-position', 'right top']])
+ staticUtility('object-top', [['object-position', 'top']])
+ functionalUtility('object', {
+ themeKeys: ['--object-position'],
+ handle: (value) => [decl('object-position', value)],
+ })
+
+ functionalUtility('p', {
+ themeKeys: ['--padding', '--spacing'],
+ handle: (value) => [decl('padding', value)],
+ })
+
+ functionalUtility('px', {
+ themeKeys: ['--padding', '--spacing'],
+ handle: (value) => [decl('padding-left', value), decl('padding-right', value)],
+ })
+
+ functionalUtility('py', {
+ themeKeys: ['--padding', '--spacing'],
+ handle: (value) => [decl('padding-top', value), decl('padding-bottom', value)],
+ })
+
+ functionalUtility('pt', {
+ themeKeys: ['--padding', '--spacing'],
+ handle: (value) => [decl('padding-top', value)],
+ })
+ functionalUtility('ps', {
+ themeKeys: ['--padding', '--spacing'],
+ handle: (value) => [decl('padding-inline-start', value)],
+ })
+ functionalUtility('pe', {
+ themeKeys: ['--padding', '--spacing'],
+ handle: (value) => [decl('padding-inline-end', value)],
+ })
+ functionalUtility('pr', {
+ themeKeys: ['--padding', '--spacing'],
+ handle: (value) => [decl('padding-right', value)],
+ })
+ functionalUtility('pb', {
+ themeKeys: ['--padding', '--spacing'],
+ handle: (value) => [decl('padding-bottom', value)],
+ })
+ functionalUtility('pl', {
+ themeKeys: ['--padding', '--spacing'],
+ handle: (value) => [decl('padding-left', value)],
+ })
+
+ staticUtility('text-left', [['text-align', 'left']])
+ staticUtility('text-center', [['text-align', 'center']])
+ staticUtility('text-right', [['text-align', 'right']])
+ staticUtility('text-justify', [['text-align', 'justify']])
+ staticUtility('text-start', [['text-align', 'start']])
+ staticUtility('text-end', [['text-align', 'end']])
+
+ functionalUtility('indent', {
+ supportsNegative: true,
+ themeKeys: ['--text-indent', '--spacing'],
+ handle: (value) => [decl('text-indent', value)],
+ })
+
+ staticUtility('align-baseline', [['vertical-align', 'baseline']])
+ staticUtility('align-top', [['vertical-align', 'top']])
+ staticUtility('align-middle', [['vertical-align', 'middle']])
+ staticUtility('align-bottom', [['vertical-align', 'bottom']])
+ staticUtility('align-text-top', [['vertical-align', 'text-top']])
+ staticUtility('align-text-bottom', [['vertical-align', 'text-bottom']])
+ staticUtility('align-sub', [['vertical-align', 'sub']])
+ staticUtility('align-super', [['vertical-align', 'super']])
+
+ functionalUtility('align', {
+ themeKeys: [],
+ handle: (value) => [decl('vertical-align', value)],
+ })
+
+ utilities.functional('font', (candidate) => {
+ if (candidate.negative || !candidate.value) return
+
+ if (candidate.value.kind === 'arbitrary') {
+ let value = candidate.value.value
+ let type =
+ candidate.value.dataType ?? inferDataType(value, ['number', 'generic-name', 'family-name'])
+
+ switch (type) {
+ case 'generic-name':
+ case 'family-name': {
+ return [decl('font-family', value)]
+ }
+ default: {
+ return [decl('font-weight', value)]
+ }
+ }
+ }
+
+ {
+ let value = theme.resolveWith(
+ candidate.value.value,
+ ['--font-family'],
+ ['--font-feature-settings', '--font-variation-settings'],
+ )
+ if (value) {
+ let [families, options = {}] = value
+
+ return [
+ decl('font-family', families),
+ decl('font-feature-settings', options['--font-feature-settings']),
+ decl('font-variation-settings', options['--font-variation-settings']),
+ ]
+ }
+ }
+
+ {
+ let value = theme.resolve(candidate.value.value, ['--font-weight'])
+ if (value) {
+ return [decl('font-weight', value)]
+ }
+
+ switch (candidate.value.value) {
+ case 'thin':
+ value = '100'
+ break
+ case 'extralight':
+ value = '200'
+ break
+ case 'light':
+ value = '300'
+ break
+ case 'normal':
+ value = '400'
+ break
+ case 'medium':
+ value = '500'
+ break
+ case 'semibold':
+ value = '600'
+ break
+ case 'bold':
+ value = '700'
+ break
+ case 'extrabold':
+ value = '800'
+ break
+ case 'black':
+ value = '900'
+ break
+ }
+
+ if (value) {
+ return [decl('font-weight', value)]
+ }
+ }
+ })
+
+ suggest('font', () => [
+ {
+ values: [],
+ valueThemeKeys: ['--font-family'],
+ },
+ {
+ values: [
+ 'thin',
+ 'extralight',
+ 'light',
+ 'normal',
+ 'medium',
+ 'semibold',
+ 'bold',
+ 'extrabold',
+ 'black',
+ ],
+ valueThemeKeys: ['--font-weight'],
+ },
+ ])
+
+ staticUtility('uppercase', [['text-transform', 'uppercase']])
+ staticUtility('lowercase', [['text-transform', 'lowercase']])
+ staticUtility('capitalize', [['text-transform', 'capitalize']])
+ staticUtility('normal-case', [['text-transform', 'none']])
+
+ staticUtility('italic', [['font-style', 'italic']])
+ staticUtility('not-italic', [['font-style', 'normal']])
+ staticUtility('underline', [['text-decoration-line', 'underline']])
+ staticUtility('overline', [['text-decoration-line', 'overline']])
+ staticUtility('line-through', [['text-decoration-line', 'line-through']])
+ staticUtility('no-underline', [['text-decoration-line', 'none']])
+
+ colorUtility('placeholder', {
+ themeKeys: ['--background-color', '--color'],
+ handle: (value) => [
+ rule('&::placeholder', [decl('--tw-sort', 'placeholder-color'), decl('color', value)]),
+ ],
+ })
+
+ /**
+ * @css `text-decoration-style`
+ */
+ staticUtility('decoration-solid', [['text-decoration-style', 'solid']])
+ staticUtility('decoration-double', [['text-decoration-style', 'double']])
+ staticUtility('decoration-dotted', [['text-decoration-style', 'dotted']])
+ staticUtility('decoration-dashed', [['text-decoration-style', 'dashed']])
+ staticUtility('decoration-wavy', [['text-decoration-style', 'wavy']])
+
+ /**
+ * @css `text-decoration-thickness`
+ */
+ staticUtility('decoration-auto', [['text-decoration-thickness', 'auto']])
+ staticUtility('decoration-from-font', [['text-decoration-thickness', 'from-font']])
+
+ utilities.functional('decoration', (candidate) => {
+ if (candidate.negative || !candidate.value) return
+
+ if (candidate.value.kind === 'arbitrary') {
+ let value: string | null = candidate.value.value
+ let type = candidate.value.dataType ?? inferDataType(value, ['color', 'length', 'percentage'])
+
+ switch (type) {
+ case 'length':
+ case 'percentage': {
+ return [decl('text-decoration-thickness', value)]
+ }
+ default: {
+ value = asColor(value, candidate.modifier, theme)
+ if (value === null) return
+
+ return [decl('text-decoration-color', value)]
+ }
+ }
+ }
+
+ // `text-decoration-thickness` property
+ {
+ let value = theme.resolve(candidate.value.value, ['--text-decoration-thickness'])
+ if (value) {
+ return [decl('text-decoration-thickness', value)]
+ }
+
+ if (!Number.isNaN(Number(candidate.value.value))) {
+ return [decl('text-decoration-thickness', `${candidate.value.value}px`)]
+ }
+ }
+
+ // `text-decoration-color` property
+ {
+ let value = resolveThemeColor(candidate, theme, ['--text-decoration-color', '--color'])
+ if (value) {
+ return [decl('text-decoration-color', value)]
+ }
+ }
+ })
+
+ suggest('decoration', () => [
+ {
+ values: ['current', 'transparent'],
+ valueThemeKeys: ['--text-decoration-color', '--color'],
+ modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
+ modifierThemeKeys: ['--opacity'],
+ },
+ {
+ values: ['0', '1', '2'],
+ valueThemeKeys: ['--text-decoration-thickness'],
+ },
+ ])
+
+ staticUtility('animate-none', [['animation', 'none']])
+ functionalUtility('animate', {
+ themeKeys: ['--animate'],
+ handle: (value) => [decl('animation', value)],
+ })
+
+ {
+ let cssFilterValue = [
+ 'var(--tw-blur,)',
+ 'var(--tw-brightness,)',
+ 'var(--tw-contrast,)',
+ 'var(--tw-grayscale,)',
+ 'var(--tw-hue-rotate,)',
+ 'var(--tw-invert,)',
+ 'var(--tw-saturate,)',
+ 'var(--tw-sepia,)',
+ 'var(--tw-drop-shadow,)',
+ ].join(' ')
+
+ let cssBackdropFilterValue = [
+ 'var(--tw-backdrop-blur,)',
+ 'var(--tw-backdrop-brightness,)',
+ 'var(--tw-backdrop-contrast,)',
+ 'var(--tw-backdrop-grayscale,)',
+ 'var(--tw-backdrop-hue-rotate,)',
+ 'var(--tw-backdrop-invert,)',
+ 'var(--tw-backdrop-opacity,)',
+ 'var(--tw-backdrop-saturate,)',
+ 'var(--tw-backdrop-sepia,)',
+ ].join(' ')
+
+ let filterProperties = () => {
+ return atRoot([
+ property('--tw-blur'),
+ property('--tw-brightness'),
+ property('--tw-contrast'),
+ property('--tw-grayscale'),
+ property('--tw-hue-rotate'),
+ property('--tw-invert'),
+ property('--tw-opacity'),
+ property('--tw-saturate'),
+ property('--tw-sepia'),
+ ])
+ }
+
+ let backdropFilterProperties = () => {
+ return atRoot([
+ property('--tw-backdrop-blur'),
+ property('--tw-backdrop-brightness'),
+ property('--tw-backdrop-contrast'),
+ property('--tw-backdrop-grayscale'),
+ property('--tw-backdrop-hue-rotate'),
+ property('--tw-backdrop-invert'),
+ property('--tw-backdrop-opacity'),
+ property('--tw-backdrop-saturate'),
+ property('--tw-backdrop-sepia'),
+ ])
+ }
+
+ utilities.functional('filter', (candidate) => {
+ if (candidate.negative) return
+
+ if (candidate.value === null) {
+ return [filterProperties(), decl('filter', cssFilterValue)]
+ }
+
+ if (candidate.value.kind === 'arbitrary') {
+ return [decl('filter', candidate.value.value)]
+ }
+
+ switch (candidate.value.value) {
+ case 'none':
+ return [decl('filter', 'none')]
+ }
+ })
+
+ utilities.functional('backdrop-filter', (candidate) => {
+ if (candidate.negative) return
+
+ if (candidate.value === null) {
+ return [
+ backdropFilterProperties(),
+ decl('-webkit-backdrop-filter', cssBackdropFilterValue),
+ decl('backdrop-filter', cssBackdropFilterValue),
+ ]
+ }
+
+ if (candidate.value.kind === 'arbitrary') {
+ return [
+ decl('-webkit-backdrop-filter', candidate.value.value),
+ decl('backdrop-filter', candidate.value.value),
+ ]
+ }
+
+ switch (candidate.value.value) {
+ case 'none':
+ return [decl('-webkit-backdrop-filter', 'none'), decl('backdrop-filter', 'none')]
+ }
+ })
+
+ functionalUtility('blur', {
+ themeKeys: ['--blur'],
+ handle: (value) => [
+ filterProperties(),
+ decl('--tw-blur', `blur(${value})`),
+ decl('filter', cssFilterValue),
+ ],
+ })
+
+ functionalUtility('backdrop-blur', {
+ themeKeys: ['--backdrop-blur', '--blur'],
+ handle: (value) => [
+ backdropFilterProperties(),
+ decl('--tw-backdrop-blur', `blur(${value})`),
+ decl('-webkit-backdrop-filter', cssBackdropFilterValue),
+ decl('backdrop-filter', cssBackdropFilterValue),
+ ],
+ })
+
+ functionalUtility('brightness', {
+ themeKeys: ['--brightness'],
+ handleBareValue: ({ value }) => `${value}%`,
+ handle: (value) => [
+ filterProperties(),
+ decl('--tw-brightness', `brightness(${value})`),
+ decl('filter', cssFilterValue),
+ ],
+ })
+
+ functionalUtility('backdrop-brightness', {
+ themeKeys: ['--backdrop-brightness', '--brightness'],
+ handleBareValue: ({ value }) => `${value}%`,
+ handle: (value) => [
+ backdropFilterProperties(),
+ decl('--tw-backdrop-brightness', `brightness(${value})`),
+ decl('-webkit-backdrop-filter', cssBackdropFilterValue),
+ decl('backdrop-filter', cssBackdropFilterValue),
+ ],
+ })
+
+ suggest('brightness', () => [
+ {
+ values: ['0', '50', '75', '90', '95', '100', '105', '110', '125', '150', '200'],
+ valueThemeKeys: ['--brightness'],
+ },
+ ])
+
+ suggest('backdrop-brightness', () => [
+ {
+ values: ['0', '50', '75', '90', '95', '100', '105', '110', '125', '150', '200'],
+ valueThemeKeys: ['--backdrop-brightness', '--brightness'],
+ },
+ ])
+
+ functionalUtility('contrast', {
+ themeKeys: ['--contrast'],
+ handleBareValue: ({ value }) => `${value}%`,
+ handle: (value) => [
+ filterProperties(),
+ decl('--tw-contrast', `contrast(${value})`),
+ decl('filter', cssFilterValue),
+ ],
+ })
+
+ functionalUtility('backdrop-contrast', {
+ themeKeys: ['--backdrop-contrast', '--contrast'],
+ handleBareValue: ({ value }) => `${value}%`,
+ handle: (value) => [
+ backdropFilterProperties(),
+ decl('--tw-backdrop-contrast', `contrast(${value})`),
+ decl('-webkit-backdrop-filter', cssBackdropFilterValue),
+ decl('backdrop-filter', cssBackdropFilterValue),
+ ],
+ })
+
+ suggest('contrast', () => [
+ {
+ values: ['0', '50', '75', '100', '125', '150', '200'],
+ valueThemeKeys: ['--contrast'],
+ },
+ ])
+
+ suggest('backdrop-contrast', () => [
+ {
+ values: ['0', '50', '75', '100', '125', '150', '200'],
+ valueThemeKeys: ['--backdrop-contrast', '--contrast'],
+ },
+ ])
+
+ functionalUtility('grayscale', {
+ themeKeys: ['--grayscale'],
+ handleBareValue: ({ value }) => `${value}%`,
+ defaultValue: '100%',
+ handle: (value) => [
+ filterProperties(),
+ decl('--tw-grayscale', `grayscale(${value})`),
+ decl('filter', cssFilterValue),
+ ],
+ })
+
+ functionalUtility('backdrop-grayscale', {
+ themeKeys: ['--backdrop-grayscale', '--grayscale'],
+ handleBareValue: ({ value }) => `${value}%`,
+ defaultValue: '100%',
+ handle: (value) => [
+ backdropFilterProperties(),
+ decl('--tw-backdrop-grayscale', `grayscale(${value})`),
+ decl('-webkit-backdrop-filter', cssBackdropFilterValue),
+ decl('backdrop-filter', cssBackdropFilterValue),
+ ],
+ })
+
+ suggest('grayscale', () => [
+ {
+ values: ['0', '25', '50', '75', '100'],
+ valueThemeKeys: ['--grayscale'],
+ hasDefaultValue: true,
+ },
+ ])
+
+ suggest('backdrop-grayscale', () => [
+ {
+ values: ['0', '25', '50', '75', '100'],
+ valueThemeKeys: ['--backdrop-grayscale', '--grayscale'],
+ hasDefaultValue: true,
+ },
+ ])
+
+ functionalUtility('hue-rotate', {
+ themeKeys: ['--hue-rotate'],
+ handleBareValue: ({ value }) => `${value}deg`,
+ handle: (value) => [
+ filterProperties(),
+ decl('--tw-hue-rotate', `hue-rotate(${value})`),
+ decl('filter', cssFilterValue),
+ ],
+ })
+
+ functionalUtility('backdrop-hue-rotate', {
+ themeKeys: ['--backdrop-hue-rotate', '--hue-rotate'],
+ handleBareValue: ({ value }) => `${value}deg`,
+ handle: (value) => [
+ backdropFilterProperties(),
+ decl('--tw-backdrop-hue-rotate', `hue-rotate(${value})`),
+ decl('-webkit-backdrop-filter', cssBackdropFilterValue),
+ decl('backdrop-filter', cssBackdropFilterValue),
+ ],
+ })
+
+ suggest('hue-rotate', () => [
+ {
+ values: ['0', '15', '30', '60', '90', '180'],
+ valueThemeKeys: ['--hue-rotate'],
+ },
+ ])
+
+ suggest('backdrop-hue-rotate', () => [
+ {
+ values: ['0', '15', '30', '60', '90', '180'],
+ valueThemeKeys: ['--backdrop-hue-rotate', '--hue-rotate'],
+ },
+ ])
+
+ functionalUtility('invert', {
+ themeKeys: ['--invert'],
+ handleBareValue: ({ value }) => `${value}%`,
+ defaultValue: '100%',
+ handle: (value) => [
+ filterProperties(),
+ decl('--tw-invert', `invert(${value})`),
+ decl('filter', cssFilterValue),
+ ],
+ })
+
+ functionalUtility('backdrop-invert', {
+ themeKeys: ['--backdrop-invert', '--invert'],
+ handleBareValue: ({ value }) => `${value}%`,
+ defaultValue: '100%',
+ handle: (value) => [
+ backdropFilterProperties(),
+ decl('--tw-backdrop-invert', `invert(${value})`),
+ decl('-webkit-backdrop-filter', cssBackdropFilterValue),
+ decl('backdrop-filter', cssBackdropFilterValue),
+ ],
+ })
+
+ suggest('invert', () => [
+ {
+ values: ['0', '25', '50', '75', '100'],
+ valueThemeKeys: ['--invert'],
+ hasDefaultValue: true,
+ },
+ ])
+
+ suggest('backdrop-invert', () => [
+ {
+ values: ['0', '25', '50', '75', '100'],
+ valueThemeKeys: ['--backdrop-invert', '--invert'],
+ hasDefaultValue: true,
+ },
+ ])
+
+ functionalUtility('saturate', {
+ themeKeys: ['--saturate'],
+ handleBareValue: ({ value }) => `${value}%`,
+ handle: (value) => [
+ filterProperties(),
+ decl('--tw-saturate', `saturate(${value})`),
+ decl('filter', cssFilterValue),
+ ],
+ })
+
+ functionalUtility('backdrop-saturate', {
+ themeKeys: ['--backdrop-saturate', '--saturate'],
+ handleBareValue: ({ value }) => `${value}%`,
+ handle: (value) => [
+ backdropFilterProperties(),
+ decl('--tw-backdrop-saturate', `saturate(${value})`),
+ decl('-webkit-backdrop-filter', cssBackdropFilterValue),
+ decl('backdrop-filter', cssBackdropFilterValue),
+ ],
+ })
+
+ suggest('saturate', () => [
+ {
+ values: ['0', '50', '100', '150', '200'],
+ valueThemeKeys: ['--saturate'],
+ },
+ ])
+
+ suggest('backdrop-saturate', () => [
+ {
+ values: ['0', '50', '100', '150', '200'],
+ valueThemeKeys: ['--backdrop-saturate', '--saturate'],
+ },
+ ])
+
+ functionalUtility('sepia', {
+ themeKeys: ['--sepia'],
+ handleBareValue: ({ value }) => `${value}%`,
+ defaultValue: '100%',
+ handle: (value) => [
+ filterProperties(),
+ decl('--tw-sepia', `sepia(${value})`),
+ decl('filter', cssFilterValue),
+ ],
+ })
+
+ functionalUtility('backdrop-sepia', {
+ themeKeys: ['--backdrop-sepia', '--sepia'],
+ handleBareValue: ({ value }) => `${value}%`,
+ defaultValue: '100%',
+ handle: (value) => [
+ backdropFilterProperties(),
+ decl('--tw-backdrop-sepia', `sepia(${value})`),
+ decl('-webkit-backdrop-filter', cssBackdropFilterValue),
+ decl('backdrop-filter', cssBackdropFilterValue),
+ ],
+ })
+
+ suggest('sepia', () => [
+ {
+ values: ['0', '50', '100'],
+ valueThemeKeys: ['--sepia'],
+ hasDefaultValue: true,
+ },
+ ])
+
+ suggest('backdrop-sepia', () => [
+ {
+ values: ['0', '50', '100'],
+ valueThemeKeys: ['--backdrop-sepia', '--sepia'],
+ hasDefaultValue: true,
+ },
+ ])
+
+ functionalUtility('drop-shadow', {
+ themeKeys: ['--drop-shadow'],
+ handle: (value) => [
+ filterProperties(),
+ decl(
+ '--tw-drop-shadow',
+ segment(value, ',')
+ .map((v) => `drop-shadow(${v})`)
+ .join(' '),
+ ),
+ decl('filter', cssFilterValue),
+ ],
+ })
+
+ functionalUtility('backdrop-opacity', {
+ themeKeys: ['--backdrop-opacity', '--opacity'],
+ handleBareValue: ({ value }) => `${value}%`,
+ handle: (value) => [
+ backdropFilterProperties(),
+ decl('--tw-backdrop-opacity', `opacity(${value})`),
+ decl('-webkit-backdrop-filter', cssBackdropFilterValue),
+ decl('backdrop-filter', cssBackdropFilterValue),
+ ],
+ })
+
+ suggest('backdrop-opacity', () => [
+ {
+ values: Array.from({ length: 21 }, (_, i) => `${i * 5}`),
+ valueThemeKeys: ['--backdrop-opacity', '--opacity'],
+ },
+ ])
+ }
+
+ {
+ let defaultTimingFunction = 'var(--default-transition-timing-function)'
+ let defaultDuration = 'var(--default-transition-duration)'
+
+ staticUtility('transition-none', [['transition-property', 'none']])
+ staticUtility('transition-all', [
+ ['transition-property', 'all'],
+ ['transition-timing-function', defaultTimingFunction],
+ ['transition-duration', defaultDuration],
+ ])
+ staticUtility('transition-colors', [
+ [
+ 'transition-property',
+ 'color, background-color, border-color, text-decoration-color, fill, stroke',
+ ],
+ ['transition-timing-function', defaultTimingFunction],
+ ['transition-duration', defaultDuration],
+ ])
+ staticUtility('transition-opacity', [
+ ['transition-property', 'opacity'],
+ ['transition-timing-function', defaultTimingFunction],
+ ['transition-duration', defaultDuration],
+ ])
+ staticUtility('transition-shadow', [
+ ['transition-property', 'box-shadow'],
+ ['transition-timing-function', defaultTimingFunction],
+ ['transition-duration', defaultDuration],
+ ])
+ staticUtility('transition-transform', [
+ ['transition-property', 'transform, translate, scale, rotate'],
+ ['transition-timing-function', defaultTimingFunction],
+ ['transition-duration', defaultDuration],
+ ])
+
+ functionalUtility('transition', {
+ defaultValue:
+ 'color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter',
+ themeKeys: ['--transition-property'],
+ handle: (value) => [
+ decl('transition-property', value),
+ decl('transition-timing-function', defaultTimingFunction),
+ decl('transition-duration', defaultDuration),
+ ],
+ })
+
+ functionalUtility('delay', {
+ handleBareValue: ({ value }) => `${value}ms`,
+ themeKeys: ['--transition-delay'],
+ handle: (value) => [decl('transition-delay', value)],
+ })
+
+ utilities.functional('duration', (candidate) => {
+ // This utility doesn't support negative values.
+ if (candidate.negative) return
+
+ // This utility doesn't support `DEFAULT` values.
+ if (!candidate.value) return
+
+ // Find the actual CSS value that the candidate value maps to.
+ let value: string | null = null
+
+ if (candidate.value.kind === 'arbitrary') {
+ value = candidate.value.value
+ } else {
+ value = theme.resolve(candidate.value.fraction ?? candidate.value.value, [
+ '--transition-duration',
+ ])
+
+ if (value === null && !Number.isNaN(Number(candidate.value.value))) {
+ value = `${candidate.value.value}ms`
+ }
+ }
+
+ // If the candidate value (like the `sm` in `max-w-sm`) doesn't resolve to
+ // an actual value, don't generate any rules.
+ if (value === null) return
+
+ return [decl('transition-duration', value)]
+ })
+
+ suggest('delay', () => [
+ {
+ values: ['75', '100', '150', '200', '300', '500', '700', '1000'],
+ valueThemeKeys: ['--transition-delay'],
+ },
+ ])
+
+ suggest('duration', () => [
+ {
+ values: ['75', '100', '150', '200', '300', '500', '700', '1000'],
+ valueThemeKeys: ['--transition-duration'],
+ },
+ ])
+ }
+
+ functionalUtility('ease', {
+ themeKeys: ['--transition-timing-function'],
+ handle: (value) => [decl('transition-timing-function', value)],
+ })
+
+ staticUtility('will-change-auto', [['will-change', 'auto']])
+ staticUtility('will-change-scroll', [['will-change', 'scroll-position']])
+ staticUtility('will-change-contents', [['will-change', 'contents']])
+ staticUtility('will-change-transform', [['will-change', 'transform']])
+ functionalUtility('will-change', {
+ themeKeys: [],
+ handle: (value) => [decl('will-change', value)],
+ })
+
+ staticUtility('content-none', [['content', 'none']])
+ functionalUtility('content', {
+ themeKeys: [],
+ handle: (value) => [
+ atRoot([property('--tw-content', '""')]),
+ decl('--tw-content', value),
+ decl('content', 'var(--tw-content)'),
+ ],
+ })
+
+ {
+ let cssContainValue =
+ 'var(--tw-contain-size) var(--tw-contain-layout) var(--tw-contain-paint) var(--tw-contain-style)'
+ let cssContainProperties = () => {
+ return atRoot([
+ property('--tw-contain-size'),
+ property('--tw-contain-layout'),
+ property('--tw-contain-paint'),
+ property('--tw-contain-style'),
+ ])
+ }
+
+ staticUtility('contain-none', [['contain', 'none']])
+ staticUtility('contain-content', [['contain', 'content']])
+ staticUtility('contain-strict', [['contain', 'strict']])
+
+ staticUtility('contain-size', [
+ cssContainProperties,
+ ['--tw-contain-size', 'size'],
+ ['contain', cssContainValue],
+ ])
+
+ staticUtility('contain-inline-size', [
+ cssContainProperties,
+ ['--tw-contain-size', 'inline-size'],
+ ['contain', cssContainValue],
+ ])
+
+ staticUtility('contain-layout', [
+ cssContainProperties,
+ ['--tw-contain-layout', 'layout'],
+ ['contain', cssContainValue],
+ ])
+
+ staticUtility('contain-paint', [
+ cssContainProperties,
+ ['--tw-contain-paint', 'paint'],
+ ['contain', cssContainValue],
+ ])
+
+ staticUtility('contain-style', [
+ cssContainProperties,
+ ['--tw-contain-style', 'style'],
+ ['contain', cssContainValue],
+ ])
+
+ functionalUtility('contain', {
+ themeKeys: [],
+ handle: (value) => [decl('contain', value)],
+ })
+ }
+
+ staticUtility('forced-color-adjust-none', [['forced-color-adjust', 'none']])
+ staticUtility('forced-color-adjust-auto', [['forced-color-adjust', 'auto']])
+
+ functionalUtility('leading', {
+ themeKeys: ['--line-height'],
+ handle: (value) => [decl('line-height', value)],
+ })
+
+ functionalUtility('tracking', {
+ supportsNegative: true,
+ themeKeys: ['--letter-spacing'],
+ handle: (value) => [decl('letter-spacing', value)],
+ })
+
+ staticUtility('antialiased', [
+ ['-webkit-font-smoothing', 'antialiased'],
+ ['-moz-osx-font-smoothing', 'grayscale'],
+ ])
+
+ staticUtility('subpixel-antialiased', [
+ ['-webkit-font-smoothing', 'auto'],
+ ['-moz-osx-font-smoothing', 'auto'],
+ ])
+
+ {
+ let cssFontVariantNumericValue =
+ 'var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)'
+ let fontVariantNumericProperties = () => {
+ return atRoot([
+ property('--tw-ordinal'),
+ property('--tw-slashed-zero'),
+ property('--tw-numeric-figure'),
+ property('--tw-numeric-spacing'),
+ property('--tw-numeric-fraction'),
+ ])
+ }
+
+ staticUtility('normal-nums', [['font-variant-numeric', 'normal']])
+
+ staticUtility('ordinal', [
+ fontVariantNumericProperties,
+ ['--tw-ordinal', 'ordinal'],
+ ['font-variant-numeric', cssFontVariantNumericValue],
+ ])
+
+ staticUtility('slashed-zero', [
+ fontVariantNumericProperties,
+ ['--tw-slashed-zero', 'slashed-zero'],
+ ['font-variant-numeric', cssFontVariantNumericValue],
+ ])
+
+ staticUtility('lining-nums', [
+ fontVariantNumericProperties,
+ ['--tw-numeric-figure', 'lining-nums'],
+ ['font-variant-numeric', cssFontVariantNumericValue],
+ ])
+
+ staticUtility('oldstyle-nums', [
+ fontVariantNumericProperties,
+ ['--tw-numeric-figure', 'oldstyle-nums'],
+ ['font-variant-numeric', cssFontVariantNumericValue],
+ ])
+
+ staticUtility('proportional-nums', [
+ fontVariantNumericProperties,
+ ['--tw-numeric-spacing', 'proportional-nums'],
+ ['font-variant-numeric', cssFontVariantNumericValue],
+ ])
+
+ staticUtility('tabular-nums', [
+ fontVariantNumericProperties,
+ ['--tw-numeric-spacing', 'tabular-nums'],
+ ['font-variant-numeric', cssFontVariantNumericValue],
+ ])
+
+ staticUtility('diagonal-fractions', [
+ fontVariantNumericProperties,
+ ['--tw-numeric-fraction', 'diagonal-fractions'],
+ ['font-variant-numeric', cssFontVariantNumericValue],
+ ])
+
+ staticUtility('stacked-fractions', [
+ fontVariantNumericProperties,
+ ['--tw-numeric-fraction', 'stacked-fractions'],
+ ['font-variant-numeric', cssFontVariantNumericValue],
+ ])
+ }
+
+ {
+ let outlineProperties = () => {
+ return atRoot([property('--tw-outline-style', 'solid', '')])
+ }
+
+ staticUtility('outline-none', [
+ ['outline', '2px solid transparent'],
+ ['outline-offset', '2px'],
+ ])
+
+ /**
+ * @css `outline-style`
+ */
+ staticUtility('outline-solid', [
+ ['--tw-outline-style', 'solid'],
+ ['outline-style', 'solid'],
+ ])
+ staticUtility('outline-dashed', [
+ ['--tw-outline-style', 'dashed'],
+ ['outline-style', 'dashed'],
+ ])
+ staticUtility('outline-dotted', [
+ ['--tw-outline-style', 'dotted'],
+ ['outline-style', 'dotted'],
+ ])
+ staticUtility('outline-double', [
+ ['--tw-outline-style', 'double'],
+ ['outline-style', 'double'],
+ ])
+
+ utilities.functional('outline', (candidate) => {
+ if (candidate.negative) return
+
+ if (candidate.value === null) {
+ return [
+ outlineProperties(),
+ decl('outline-style', 'var(--tw-outline-style)'),
+ decl('outline-width', '1px'),
+ ]
+ }
+
+ if (candidate.value.kind === 'arbitrary') {
+ let value: string | null = candidate.value.value
+ let type =
+ candidate.value.dataType ??
+ inferDataType(value, ['color', 'length', 'number', 'percentage'])
+
+ switch (type) {
+ case 'length':
+ case 'number':
+ case 'percentage': {
+ return [
+ outlineProperties(),
+ decl('outline-style', 'var(--tw-outline-style)'),
+ decl('outline-width', value),
+ ]
+ }
+ default: {
+ value = asColor(value, candidate.modifier, theme)
+ if (value === null) return
+
+ return [decl('outline-color', value)]
+ }
+ }
+ }
+
+ // `outline-color` property
+ {
+ let value = resolveThemeColor(candidate, theme, ['--outline-color', '--color'])
+ if (value) {
+ return [decl('outline-color', value)]
+ }
+ }
+
+ // `outline-width` property
+ {
+ let value = theme.resolve(candidate.value.value, ['--outline-width'])
+ if (value) {
+ return [
+ outlineProperties(),
+ decl('outline-style', 'var(--tw-outline-style)'),
+ decl('outline-width', value),
+ ]
+ } else if (!Number.isNaN(Number(candidate.value.value))) {
+ return [
+ outlineProperties(),
+ decl('outline-style', 'var(--tw-outline-style)'),
+ decl('outline-width', `${candidate.value.value}px`),
+ ]
+ }
+ }
+ })
+
+ suggest('outline', () => [
+ {
+ values: ['current', 'transparent'],
+ valueThemeKeys: ['--outline-color', '--color'],
+ modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
+ modifierThemeKeys: ['--opacity'],
+ hasDefaultValue: true,
+ },
+ {
+ values: ['0', '1', '2', '4', '8'],
+ valueThemeKeys: ['--outline-width'],
+ },
+ ])
+
+ functionalUtility('outline-offset', {
+ supportsNegative: true,
+ themeKeys: ['--outline-offset'],
+ handleBareValue: ({ value }) => `${value}px`,
+ handle: (value) => [decl('outline-offset', value)],
+ })
+
+ suggest('outline-offset', () => [
+ {
+ values: ['0', '1', '2', '4', '8'],
+ valueThemeKeys: ['--outline-offset'],
+ },
+ ])
+ }
+
+ functionalUtility('opacity', {
+ themeKeys: ['--opacity'],
+ handleBareValue: ({ value }) => `${value}%`,
+ handle: (value) => [decl('opacity', value)],
+ })
+
+ suggest('opacity', () => [
+ {
+ values: Array.from({ length: 21 }, (_, i) => `${i * 5}`),
+ valueThemeKeys: ['--opacity'],
+ },
+ ])
+
+ staticUtility('underline-offset-auto', [['text-underline-offset', 'auto']])
+ functionalUtility('underline-offset', {
+ supportsNegative: true,
+ themeKeys: ['--text-underline-offset'],
+ handleBareValue: ({ value }) => `${value}px`,
+ handle: (value) => [decl('text-underline-offset', value)],
+ })
+
+ suggest('underline-offset', () => [
+ {
+ supportsNegative: true,
+ values: ['0', '1', '2', '4', '8'],
+ valueThemeKeys: ['--text-underline-offset'],
+ },
+ ])
+
+ utilities.functional('text', (candidate) => {
+ if (candidate.negative || !candidate.value) return
+
+ if (candidate.value.kind === 'arbitrary') {
+ let value: string | null = candidate.value.value
+ let type =
+ candidate.value.dataType ??
+ inferDataType(value, ['color', 'length', 'percentage', 'absolute-size', 'relative-size'])
+
+ switch (type) {
+ case 'size':
+ case 'length':
+ case 'percentage':
+ case 'absolute-size':
+ case 'relative-size': {
+ if (candidate.modifier) {
+ let modifier =
+ candidate.modifier.kind === 'arbitrary'
+ ? candidate.modifier.value
+ : theme.resolve(candidate.modifier.value, ['--line-height'])
+
+ if (modifier) {
+ return [decl('font-size', value), decl('line-height', modifier)]
+ }
+ }
+
+ return [decl('font-size', value)]
+ }
+ default: {
+ value = asColor(value, candidate.modifier, theme)
+ if (value === null) return
+
+ return [decl('color', value)]
+ }
+ }
+ }
+
+ // `color` property
+ {
+ let value = resolveThemeColor(candidate, theme, ['--text-color', '--color'])
+ if (value) {
+ return [decl('color', value)]
+ }
+ }
+
+ // `font-size` property
+ {
+ let value = theme.resolveWith(
+ candidate.value.value,
+ ['--font-size'],
+ ['--line-height', '--letter-spacing', '--font-weight'],
+ )
+ if (value) {
+ let [fontSize, options = {}] = Array.isArray(value) ? value : [value]
+
+ if (candidate.modifier) {
+ let modifier =
+ candidate.modifier.kind === 'arbitrary'
+ ? candidate.modifier.value
+ : theme.resolve(candidate.modifier.value, ['--line-height'])
+
+ let declarations = [decl('font-size', fontSize)]
+ modifier && declarations.push(decl('line-height', modifier))
+ return declarations
+ }
+
+ if (typeof options === 'string') {
+ return [decl('font-size', fontSize), decl('line-height', options)]
+ }
+
+ return [
+ decl('font-size', fontSize),
+ decl('line-height', options['--line-height']),
+ decl('letter-spacing', options['--letter-spacing']),
+ decl('font-weight', options['--font-weight']),
+ ]
+ }
+ }
+ })
+
+ suggest('text', () => [
+ {
+ values: ['current', 'transparent'],
+ valueThemeKeys: ['--text-color', '--color'],
+ modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
+ modifierThemeKeys: ['--opacity'],
+ },
+ {
+ values: [],
+ valueThemeKeys: ['--font-size'],
+ modifiers: [],
+ modifierThemeKeys: ['--line-height'],
+ },
+ ])
+
+ {
+ let cssBoxShadowValue = [
+ 'var(--tw-inset-shadow)',
+ 'var(--tw-inset-ring-shadow)',
+ 'var(--tw-ring-offset-shadow)',
+ 'var(--tw-ring-shadow)',
+ 'var(--tw-shadow)',
+ ].join(', ')
+ let nullShadow = '0 0 #0000'
+
+ let boxShadowProperties = () => {
+ return atRoot([
+ property('--tw-shadow', nullShadow),
+ property('--tw-shadow-colored', nullShadow),
+ property('--tw-inset-shadow', nullShadow),
+ property('--tw-inset-shadow-colored', nullShadow),
+ property('--tw-ring-color'),
+ property('--tw-ring-shadow', nullShadow),
+ property('--tw-inset-ring-color'),
+ property('--tw-inset-ring-shadow', nullShadow),
+
+ // Legacy
+ property('--tw-ring-inset'),
+ property('--tw-ring-offset-width', '0px', ''),
+ property('--tw-ring-offset-color', '#fff'),
+ property('--tw-ring-offset-shadow', nullShadow),
+ ])
+ }
+
+ utilities.functional('shadow', (candidate) => {
+ if (candidate.negative) return
+
+ if (!candidate.value) {
+ let value = theme.get(['--shadow'])
+ if (value === null) return
+
+ return [
+ boxShadowProperties(),
+ decl('--tw-shadow', value),
+ decl('--tw-shadow-colored', replaceShadowColors(value, 'var(--tw-shadow-color)')),
+ decl('box-shadow', cssBoxShadowValue),
+ ]
+ }
+
+ if (candidate.value.kind === 'arbitrary') {
+ let value: string | null = candidate.value.value
+ let type = candidate.value.dataType ?? inferDataType(value, ['color'])
+
+ switch (type) {
+ case 'color': {
+ value = asColor(value, candidate.modifier, theme)
+ if (value === null) return
+
+ return [
+ boxShadowProperties(),
+ decl('--tw-shadow-color', value),
+ decl('--tw-shadow', 'var(--tw-shadow-colored)'),
+ ]
+ }
+ default: {
+ return [
+ boxShadowProperties(),
+ decl('--tw-shadow', value),
+ decl('--tw-shadow-colored', replaceShadowColors(value, 'var(--tw-shadow-color)')),
+ decl('box-shadow', cssBoxShadowValue),
+ ]
+ }
+ }
+ }
+
+ switch (candidate.value.value) {
+ case 'none':
+ return [
+ boxShadowProperties(),
+ decl('--tw-shadow', nullShadow),
+ decl('--tw-shadow-colored', nullShadow),
+ decl('box-shadow', cssBoxShadowValue),
+ ]
+ }
+
+ // Shadow size
+ {
+ let value = theme.resolve(candidate.value.value, ['--shadow'])
+ if (value) {
+ return [
+ boxShadowProperties(),
+ decl('--tw-shadow', value),
+ decl('--tw-shadow-colored', replaceShadowColors(value, 'var(--tw-shadow-color)')),
+ decl('box-shadow', cssBoxShadowValue),
+ ]
+ }
+ }
+
+ // Shadow color
+ {
+ let value = resolveThemeColor(candidate, theme, ['--box-shadow-color', '--color'])
+ if (value) {
+ return [
+ boxShadowProperties(),
+ decl('--tw-shadow-color', value),
+ decl('--tw-shadow', 'var(--tw-shadow-colored)'),
+ ]
+ }
+ }
+ })
+
+ suggest('shadow', () => [
+ {
+ values: ['current', 'transparent'],
+ valueThemeKeys: ['--box-shadow-color', '--color'],
+ modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
+ modifierThemeKeys: ['--opacity'],
+ },
+ {
+ values: [],
+ valueThemeKeys: ['--shadow'],
+ hasDefaultValue: true,
+ },
+ ])
+
+ utilities.functional('inset-shadow', (candidate) => {
+ if (candidate.negative) return
+
+ if (!candidate.value) {
+ let value = theme.get(['--inset-shadow'])
+ if (value === null) return
+
+ return [
+ boxShadowProperties(),
+ decl('--tw-inset-shadow', value),
+ decl(
+ '--tw-inset-shadow-colored',
+ replaceShadowColors(value, 'var(--tw-inset-shadow-color)'),
+ ),
+ decl('box-shadow', cssBoxShadowValue),
+ ]
+ }
+
+ if (candidate.value.kind === 'arbitrary') {
+ let value: string | null = candidate.value.value
+ let type = candidate.value.dataType ?? inferDataType(value, ['color'])
+
+ switch (type) {
+ case 'color': {
+ value = asColor(value, candidate.modifier, theme)
+ if (value === null) return
+
+ return [
+ boxShadowProperties(),
+ decl('--tw-inset-shadow-color', value),
+ decl('--tw-inset-shadow', 'var(--tw-inset-shadow-colored)'),
+ ]
+ }
+ default: {
+ return [
+ boxShadowProperties(),
+ decl('--tw-inset-shadow', `inset ${value}`),
+ decl(
+ '--tw-inset-shadow-colored',
+ replaceShadowColors(`inset ${value}`, 'var(--tw-inset-shadow-color)'),
+ ),
+ decl('box-shadow', cssBoxShadowValue),
+ ]
+ }
+ }
+ }
+
+ switch (candidate.value.value) {
+ case 'none':
+ return [
+ boxShadowProperties(),
+ decl('--tw-inset-shadow', nullShadow),
+ decl('--tw-inset-shadow-colored', nullShadow),
+ decl('box-shadow', cssBoxShadowValue),
+ ]
+ }
+
+ // Shadow size
+ {
+ let value = theme.resolve(candidate.value.value, ['--inset-shadow'])
+ if (value) {
+ return [
+ boxShadowProperties(),
+ decl('--tw-inset-shadow', value),
+ decl(
+ '--tw-inset-shadow-colored',
+ replaceShadowColors(value, 'var(--tw-inset-shadow-color)'),
+ ),
+ decl('box-shadow', cssBoxShadowValue),
+ ]
+ }
+ }
+
+ // Shadow color
+ {
+ let value = resolveThemeColor(candidate, theme, ['--box-shadow-color', '--color'])
+ if (value) {
+ return [
+ boxShadowProperties(),
+ decl('--tw-inset-shadow-color', value),
+ decl('--tw-inset-shadow', 'var(--tw-inset-shadow-colored)'),
+ ]
+ }
+ }
+ })
+
+ suggest('inset-shadow', () => [
+ {
+ values: ['current', 'transparent'],
+ valueThemeKeys: ['--box-shadow-color', '--color'],
+ modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
+ modifierThemeKeys: ['--opacity'],
+ },
+ {
+ values: [],
+ valueThemeKeys: ['--shadow'],
+ hasDefaultValue: true,
+ },
+ ])
+
+ staticUtility('ring-inset', [boxShadowProperties, ['--tw-ring-inset', 'inset']])
+
+ let defaultRingColor = theme.get(['--default-ring-color']) ?? 'currentColor'
+ function ringShadowValue(value: string) {
+ return `var(--tw-ring-inset,) 0 0 0 calc(${value} + var(--tw-ring-offset-width)) var(--tw-ring-color, ${defaultRingColor})`
+ }
+ utilities.functional('ring', (candidate) => {
+ if (candidate.negative) return
+
+ if (!candidate.value) {
+ let value = theme.get(['--default-ring-width']) ?? '1px'
+ return [
+ boxShadowProperties(),
+ decl('--tw-ring-shadow', ringShadowValue(value)),
+ decl('box-shadow', cssBoxShadowValue),
+ ]
+ }
+
+ if (candidate.value.kind === 'arbitrary') {
+ let value: string | null = candidate.value.value
+ let type = candidate.value.dataType ?? inferDataType(value, ['color', 'length'])
+
+ switch (type) {
+ case 'length': {
+ return [
+ boxShadowProperties(),
+ decl('--tw-ring-shadow', ringShadowValue(value)),
+ decl('box-shadow', cssBoxShadowValue),
+ ]
+ }
+ default: {
+ value = asColor(value, candidate.modifier, theme)
+ if (value === null) return
+
+ return [decl('--tw-ring-color', value)]
+ }
+ }
+ }
+
+ // Ring color
+ {
+ let value = resolveThemeColor(candidate, theme, ['--ring-color', '--color'])
+ if (value) {
+ return [decl('--tw-ring-color', value)]
+ }
+ }
+
+ // Ring width
+ {
+ let value = theme.resolve(candidate.value.value, ['--ring-width'])
+ if (value === null && !Number.isNaN(Number(candidate.value.value))) {
+ value = `${candidate.value.value}px`
+ }
+ if (value) {
+ return [
+ boxShadowProperties(),
+ decl('--tw-ring-shadow', ringShadowValue(value)),
+ decl('box-shadow', cssBoxShadowValue),
+ ]
+ }
+ }
+ })
+
+ suggest('ring', () => [
+ {
+ values: ['current', 'transparent'],
+ valueThemeKeys: ['--ring-color', '--color'],
+ modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
+ modifierThemeKeys: ['--opacity'],
+ },
+ {
+ values: ['0', '1', '2', '4', '8'],
+ valueThemeKeys: ['--ring-width'],
+ hasDefaultValue: true,
+ },
+ ])
+
+ function insetRingShadowValue(value: string) {
+ return `inset 0 0 0 ${value} var(--tw-inset-ring-color, currentColor)`
+ }
+ utilities.functional('inset-ring', (candidate) => {
+ if (candidate.negative) return
+
+ if (!candidate.value) {
+ return [
+ boxShadowProperties(),
+ decl('--tw-inset-ring-shadow', insetRingShadowValue('1px')),
+ decl('box-shadow', cssBoxShadowValue),
+ ]
+ }
+
+ if (candidate.value.kind === 'arbitrary') {
+ let value: string | null = candidate.value.value
+ let type = candidate.value.dataType ?? inferDataType(value, ['color', 'length'])
+
+ switch (type) {
+ case 'length': {
+ return [
+ boxShadowProperties(),
+ decl('--tw-inset-ring-shadow', insetRingShadowValue(value)),
+ decl('box-shadow', cssBoxShadowValue),
+ ]
+ }
+ default: {
+ value = asColor(value, candidate.modifier, theme)
+ if (value === null) return
+
+ return [decl('--tw-inset-ring-color', value)]
+ }
+ }
+ }
+
+ // Ring color
+ {
+ let value = resolveThemeColor(candidate, theme, ['--ring-color', '--color'])
+ if (value) {
+ return [decl('--tw-inset-ring-color', value)]
+ }
+ }
+
+ // Ring width
+ {
+ let value = theme.resolve(candidate.value.value, ['--ring-width'])
+ if (value === null && !Number.isNaN(Number(candidate.value.value))) {
+ value = `${candidate.value.value}px`
+ }
+ if (value) {
+ return [
+ boxShadowProperties(),
+ decl('--tw-inset-ring-shadow', insetRingShadowValue(value)),
+ decl('box-shadow', cssBoxShadowValue),
+ ]
+ }
+ }
+ })
+
+ suggest('inset-ring', () => [
+ {
+ values: ['current', 'transparent'],
+ valueThemeKeys: ['--ring-color', '--color'],
+ modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
+ modifierThemeKeys: ['--opacity'],
+ },
+ {
+ values: ['0', '1', '2', '4', '8'],
+ valueThemeKeys: ['--ring-width'],
+ hasDefaultValue: true,
+ },
+ ])
+
+ let ringOffsetShadowValue =
+ 'var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)'
+ utilities.functional('ring-offset', (candidate) => {
+ if (candidate.negative || !candidate.value) return
+
+ if (candidate.value.kind === 'arbitrary') {
+ let value: string | null = candidate.value.value
+ let type = candidate.value.dataType ?? inferDataType(value, ['color', 'length'])
+
+ switch (type) {
+ case 'length': {
+ return [
+ decl('--tw-ring-offset-width', value),
+ decl('--tw-ring-offset-shadow', ringOffsetShadowValue),
+ ]
+ }
+ default: {
+ value = asColor(value, candidate.modifier, theme)
+ if (value === null) return
+
+ return [decl('--tw-ring-offset-color', value)]
+ }
+ }
+ }
+
+ // `--tw-ring-offset-width` property
+ {
+ let value = theme.resolve(candidate.value.value, ['--ring-offset-width'])
+ if (value) {
+ return [
+ decl('--tw-ring-offset-width', value),
+ decl('--tw-ring-offset-shadow', ringOffsetShadowValue),
+ ]
+ } else if (!Number.isNaN(Number(candidate.value.value))) {
+ return [
+ decl('--tw-ring-offset-width', `${candidate.value.value}px`),
+ decl('--tw-ring-offset-shadow', ringOffsetShadowValue),
+ ]
+ }
+ }
+
+ // `--tw-ring-offset-color` property
+ {
+ let value = resolveThemeColor(candidate, theme, ['--ring-offset-color', '--color'])
+ if (value) {
+ return [decl('--tw-ring-offset-color', value)]
+ }
+ }
+ })
+ }
+
+ suggest('ring-offset', () => [
+ {
+ values: ['current', 'transparent'],
+ valueThemeKeys: ['--ring-offset-color', '--color'],
+ modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
+ modifierThemeKeys: ['--opacity'],
+ },
+ {
+ values: ['0', '1', '2', '4', '8'],
+ valueThemeKeys: ['--ring-offset-width'],
+ },
+ ])
+
+ utilities.functional('@container', (candidate) => {
+ if (candidate.negative) return
+
+ let value: string | null = null
+ if (candidate.value === null) {
+ value = 'inline-size'
+ } else if (candidate.value.kind === 'arbitrary') {
+ value = candidate.value.value
+ } else if (candidate.value.kind === 'named' && candidate.value.value === 'normal') {
+ value = 'normal'
+ }
+
+ if (value === null) return
+
+ if (candidate.modifier) {
+ return [decl('container-type', value), decl('container-name', candidate.modifier.value)]
+ }
+
+ return [decl('container-type', value)]
+ })
+
+ suggest('@container', () => [
+ {
+ values: ['normal'],
+ valueThemeKeys: [],
+ hasDefaultValue: false,
+ },
+ ])
+
+ return utilities
+}
diff --git a/packages/tailwindcss/src/utils/decode-arbitrary-value.bench.ts b/packages/tailwindcss/src/utils/decode-arbitrary-value.bench.ts
new file mode 100644
index 000000000..701dedacc
--- /dev/null
+++ b/packages/tailwindcss/src/utils/decode-arbitrary-value.bench.ts
@@ -0,0 +1,15 @@
+import { bench } from 'vitest'
+import { decodeArbitraryValue } from './decode-arbitrary-value'
+
+const values = [
+ '#ffffff',
+ 'calc(1+2)',
+ '[content-start]_calc(100%-1px)_[content-end]_minmax(1rem,1fr)',
+ 'var(--some-value,env(safe-area-inset-top,var(--some-other-value,env(safe-area-inset))))',
+]
+
+bench('decodeArbitraryValue', () => {
+ for (let value of values) {
+ decodeArbitraryValue(value)
+ }
+})
diff --git a/packages/tailwindcss/src/utils/decode-arbitrary-value.test.ts b/packages/tailwindcss/src/utils/decode-arbitrary-value.test.ts
new file mode 100644
index 000000000..e78a25f74
--- /dev/null
+++ b/packages/tailwindcss/src/utils/decode-arbitrary-value.test.ts
@@ -0,0 +1,116 @@
+import { describe, expect, it } from 'vitest'
+import { decodeArbitraryValue } from './decode-arbitrary-value'
+
+describe('decoding arbitrary values', () => {
+ it('should replace an underscore with a space', () => {
+ expect(decodeArbitraryValue('foo_bar')).toBe('foo bar')
+ })
+
+ it('should replace multiple underscores with spaces', () => {
+ expect(decodeArbitraryValue('__foo__bar__')).toBe(' foo bar ')
+ })
+
+ it('should replace escaped underscores with a normal underscore', () => {
+ expect(decodeArbitraryValue('foo\\_bar')).toBe('foo_bar')
+ })
+
+ it('should not replace underscores in url()', () => {
+ expect(decodeArbitraryValue('url(./my_file.jpg)')).toBe('url(./my_file.jpg)')
+ })
+
+ it('should leave var(…) as is', () => {
+ expect(decodeArbitraryValue('var(--foo)')).toBe('var(--foo)')
+ expect(decodeArbitraryValue('var(--headings-h1-size)')).toBe('var(--headings-h1-size)')
+ })
+})
+
+describe('adds spaces around math operators', () => {
+ let table = [
+ // math functions like calc(…) get spaces around operators
+ ['calc(1+2)', 'calc(1 + 2)'],
+ ['calc(100%+1rem)', 'calc(100% + 1rem)'],
+ ['calc(1+calc(100%-20px))', 'calc(1 + calc(100% - 20px))'],
+ ['calc(var(--headings-h1-size)*100)', 'calc(var(--headings-h1-size) * 100)'],
+ [
+ 'calc(var(--headings-h1-size)*calc(100%+50%))',
+ 'calc(var(--headings-h1-size) * calc(100% + 50%))',
+ ],
+ ['min(1+2)', 'min(1 + 2)'],
+ ['max(1+2)', 'max(1 + 2)'],
+ ['clamp(1+2,1+3,1+4)', 'clamp(1 + 2, 1 + 3, 1 + 4)'],
+ ['var(--width, calc(100%+1rem))', 'var(--width, calc(100% + 1rem))'],
+ ['calc(1px*(7--12/24))', 'calc(1px * (7 - -12 / 24))'],
+ ['calc((7-32)/(1400-782))', 'calc((7 - 32) / (1400 - 782))'],
+ ['calc((7-3)/(1400-782))', 'calc((7 - 3) / (1400 - 782))'],
+ ['calc((7-32)/(1400-782))', 'calc((7 - 32) / (1400 - 782))'],
+ ['calc((70-3)/(1400-782))', 'calc((70 - 3) / (1400 - 782))'],
+ ['calc((70-32)/(1400-782))', 'calc((70 - 32) / (1400 - 782))'],
+ ['calc((704-3)/(1400-782))', 'calc((704 - 3) / (1400 - 782))'],
+ ['calc((704-320))', 'calc((704 - 320))'],
+ ['calc((704-320)/1)', 'calc((704 - 320) / 1)'],
+ ['calc((704-320)/(1400-782))', 'calc((704 - 320) / (1400 - 782))'],
+ ['calc(24px+-1rem)', 'calc(24px + -1rem)'],
+ ['calc(24px+(-1rem))', 'calc(24px + (-1rem))'],
+ ['calc(24px_+_-1rem)', 'calc(24px + -1rem)'],
+ ['calc(24px+(-1rem))', 'calc(24px + (-1rem))'],
+ ['calc(24px_+_(-1rem))', 'calc(24px + (-1rem))'],
+ [
+ 'calc(var(--10-10px,calc(-20px-(-30px--40px)-50px)',
+ 'calc(var(--10-10px,calc(-20px - (-30px - -40px) - 50px)',
+ ],
+ ['calc(theme(spacing.1-bar))', 'calc(theme(spacing.1-bar))'],
+ ['theme(spacing.1-bar)', 'theme(spacing.1-bar)'],
+ ['calc(theme(spacing.1-bar))', 'calc(theme(spacing.1-bar))'],
+ ['calc(1rem-theme(spacing.1-bar))', 'calc(1rem - theme(spacing.1-bar))'],
+ ['calc(theme(spacing.foo-2))', 'calc(theme(spacing.foo-2))'],
+ ['calc(theme(spacing.foo-bar))', 'calc(theme(spacing.foo-bar))'],
+
+ // A negative number immediately after a `,` should not have spaces inserted
+ ['clamp(-3px+4px,-3px+4px,-3px+4px)', 'clamp(-3px + 4px, -3px + 4px, -3px + 4px)'],
+
+ // Prevent formatting inside `var()` functions
+ ['calc(var(--foo-bar-bar)*2)', 'calc(var(--foo-bar-bar) * 2)'],
+
+ // Prevent formatting inside `env()` functions
+ ['calc(env(safe-area-inset-bottom)*2)', 'calc(env(safe-area-inset-bottom) * 2)'],
+
+ // Should format inside `calc()` nested in `env()`
+ [
+ 'env(safe-area-inset-bottom,calc(10px+20px))',
+ 'env(safe-area-inset-bottom,calc(10px + 20px))',
+ ],
+
+ [
+ 'calc(env(safe-area-inset-bottom,calc(10px+20px))+5px)',
+ 'calc(env(safe-area-inset-bottom,calc(10px + 20px)) + 5px)',
+ ],
+
+ // Prevent formatting keywords
+ ['minmax(min-content,25%)', 'minmax(min-content,25%)'],
+
+ // Prevent formatting keywords
+ [
+ 'radial-gradient(calc(1+2)),radial-gradient(calc(1+2))',
+ 'radial-gradient(calc(1 + 2)),radial-gradient(calc(1 + 2))',
+ ],
+
+ [
+ '[content-start]_calc(100%-1px)_[content-end]_minmax(1rem,1fr)',
+ '[content-start] calc(100% - 1px) [content-end] minmax(1rem,1fr)',
+ ],
+
+ // round(…) function
+ ['round(1+2,1+3)', 'round(1 + 2, 1 + 3)'],
+ ['round(to-zero,1+2,1+3)', 'round(to-zero,1 + 2, 1 + 3)'],
+
+ // Nested parens in non-math functions don't format their contents
+ ['env((safe-area-inset-bottom))', 'env((safe-area-inset-bottom))'],
+
+ // `-infinity` is a keyword and should not have spaces around the `-`
+ ['atan(1 + -infinity)', 'atan(1 + -infinity)'],
+ ]
+
+ it.each(table)('%s', (input, output) => {
+ expect(decodeArbitraryValue(input)).toBe(output)
+ })
+})
diff --git a/packages/tailwindcss/src/utils/decode-arbitrary-value.ts b/packages/tailwindcss/src/utils/decode-arbitrary-value.ts
new file mode 100644
index 000000000..f644e6639
--- /dev/null
+++ b/packages/tailwindcss/src/utils/decode-arbitrary-value.ts
@@ -0,0 +1,43 @@
+import { addWhitespaceAroundMathOperators } from './math-operators'
+
+export function decodeArbitraryValue(input: string): string {
+ // We do not want to normalize anything inside of a url() because if we
+ // replace `_` with ` `, then it will very likely break the url.
+ if (input.startsWith('url(')) {
+ return input
+ }
+
+ input = convertUnderscoresToWhitespace(input)
+ input = addWhitespaceAroundMathOperators(input)
+
+ return input
+}
+
+/**
+ * Convert `_` to ` `, except for escaped underscores `\_` they should be
+ * converted to `_` instead.
+ */
+function convertUnderscoresToWhitespace(input: string) {
+ let output = ''
+ for (let i = 0; i < input.length; i++) {
+ let char = input[i]
+
+ // Escaped underscore
+ if (char === '\\' && input[i + 1] === '_') {
+ output += '_'
+ i += 1
+ }
+
+ // Unescaped underscore
+ else if (char === '_') {
+ output += ' '
+ }
+
+ // All other characters
+ else {
+ output += char
+ }
+ }
+
+ return output
+}
diff --git a/packages/tailwindcss/src/utils/default-map.ts b/packages/tailwindcss/src/utils/default-map.ts
new file mode 100644
index 000000000..a045b828d
--- /dev/null
+++ b/packages/tailwindcss/src/utils/default-map.ts
@@ -0,0 +1,20 @@
+/**
+ * A Map that can generate default values for keys that don't exist.
+ * Generated default values are added to the map to avoid recomputation.
+ */
+export class DefaultMap extends Map {
+ constructor(private factory: (key: T, self: DefaultMap) => V) {
+ super()
+ }
+
+ get(key: T): V {
+ let value = super.get(key)
+
+ if (value === undefined) {
+ value = this.factory(key, this)
+ this.set(key, value)
+ }
+
+ return value
+ }
+}
diff --git a/packages/tailwindcss/src/utils/escape.ts b/packages/tailwindcss/src/utils/escape.ts
new file mode 100644
index 000000000..da45fb944
--- /dev/null
+++ b/packages/tailwindcss/src/utils/escape.ts
@@ -0,0 +1,73 @@
+// https://drafts.csswg.org/cssom/#serialize-an-identifier
+export function escape(value: string) {
+ if (arguments.length == 0) {
+ throw new TypeError('`CSS.escape` requires an argument.')
+ }
+ var string = String(value)
+ var length = string.length
+ var index = -1
+ var codeUnit
+ var result = ''
+ var firstCodeUnit = string.charCodeAt(0)
+
+ if (
+ // If the character is the first character and is a `-` (U+002D), and
+ // there is no second character, […]
+ length == 1 &&
+ firstCodeUnit == 0x002d
+ ) {
+ return '\\' + string
+ }
+
+ while (++index < length) {
+ codeUnit = string.charCodeAt(index)
+ // Note: there’s no need to special-case astral symbols, surrogate
+ // pairs, or lone surrogates.
+
+ // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
+ // (U+FFFD).
+ if (codeUnit == 0x0000) {
+ result += '\uFFFD'
+ continue
+ }
+
+ if (
+ // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
+ // U+007F, […]
+ (codeUnit >= 0x0001 && codeUnit <= 0x001f) ||
+ codeUnit == 0x007f ||
+ // If the character is the first character and is in the range [0-9]
+ // (U+0030 to U+0039), […]
+ (index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
+ // If the character is the second character and is in the range [0-9]
+ // (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
+ (index == 1 && codeUnit >= 0x0030 && codeUnit <= 0x0039 && firstCodeUnit == 0x002d)
+ ) {
+ // https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
+ result += '\\' + codeUnit.toString(16) + ' '
+ continue
+ }
+
+ // If the character is not handled by one of the above rules and is
+ // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
+ // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
+ // U+005A), or [a-z] (U+0061 to U+007A), […]
+ if (
+ codeUnit >= 0x0080 ||
+ codeUnit == 0x002d ||
+ codeUnit == 0x005f ||
+ (codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
+ (codeUnit >= 0x0041 && codeUnit <= 0x005a) ||
+ (codeUnit >= 0x0061 && codeUnit <= 0x007a)
+ ) {
+ // the character itself
+ result += string.charAt(index)
+ continue
+ }
+
+ // Otherwise, the escaped character.
+ // https://drafts.csswg.org/cssom/#escape-a-character
+ result += '\\' + string.charAt(index)
+ }
+ return result
+}
diff --git a/packages/tailwindcss/src/utils/infer-data-type.bench.ts b/packages/tailwindcss/src/utils/infer-data-type.bench.ts
new file mode 100644
index 000000000..82fe874bd
--- /dev/null
+++ b/packages/tailwindcss/src/utils/infer-data-type.bench.ts
@@ -0,0 +1,30 @@
+import { bench, expect, test } from 'vitest'
+import { inferDataType } from './infer-data-type'
+
+const colors = [
+ 'slateblue',
+ 'rgb(255, 255, 255)',
+ 'rgba(255, 255, 255, 1)',
+ 'hsl(0, 0%, 100%)',
+ 'hsla(0, 0%, 100%, 1)',
+ 'hwb(0, 0%, 100%)',
+ 'color(red a(1))',
+ 'lab(0 0 0)',
+ 'lch(0 0 0)',
+ 'oklab(0 0 0)',
+ 'oklch(0 0 0)',
+ 'light-dark(#fff #000)',
+ 'color-mix(#fff #000)',
+]
+
+bench('colors', () => {
+ for (let color of colors) {
+ inferDataType(color, ['color'])
+ }
+})
+
+test('colors', () => {
+ for (let color of colors) {
+ expect(inferDataType(color, ['color'])).toBe('color')
+ }
+})
diff --git a/packages/tailwindcss/src/utils/infer-data-type.ts b/packages/tailwindcss/src/utils/infer-data-type.ts
new file mode 100644
index 000000000..9025eeb3f
--- /dev/null
+++ b/packages/tailwindcss/src/utils/infer-data-type.ts
@@ -0,0 +1,285 @@
+import { isColor } from './is-color'
+import { hasMathFn } from './math-operators'
+import { segment } from './segment'
+
+type DataType =
+ | 'color'
+ | 'length'
+ | 'percentage'
+ | 'number'
+ | 'url'
+ | 'position'
+ | 'bg-size'
+ | 'line-width'
+ | 'image'
+ | 'family-name'
+ | 'generic-name'
+ | 'absolute-size'
+ | 'relative-size'
+
+const checks: Record boolean> = {
+ color: isColor,
+ length: isLength,
+ percentage: isPercentage,
+ number: isNumber,
+ url: isUrl,
+ position: isBackgroundPosition,
+ 'bg-size': isBackgroundSize,
+ 'line-width': isLineWidth,
+ image: isImage,
+ 'family-name': isFamilyName,
+ 'generic-name': isGenericName,
+ 'absolute-size': isAbsoluteSize,
+ 'relative-size': isRelativeSize,
+}
+
+/**
+ * Determine the type of `value` using syntax rules from CSS specs.
+ */
+export function inferDataType(value: string, types: DataType[]): DataType | null {
+ if (value.startsWith('var(')) return null
+
+ for (let type of types) {
+ if (checks[type]?.(value)) {
+ return type
+ }
+ }
+
+ return null
+}
+
+/* -------------------------------------------------------------------------- */
+
+const IS_URL = /^url\(.*\)$/
+
+function isUrl(value: string): boolean {
+ return IS_URL.test(value)
+}
+
+/* -------------------------------------------------------------------------- */
+
+function isLineWidth(value: string): boolean {
+ return value === 'thin' || value === 'medium' || value === 'thick'
+}
+
+/* -------------------------------------------------------------------------- */
+
+const IS_IMAGE_FN = /^(?:element|image|cross-fade|image-set)\(/
+const IS_GRADIENT_FN = /^(repeating-)?(conic|linear|radial)-gradient\(/
+
+function isImage(value: string) {
+ let count = 0
+
+ for (let part of segment(value, ',')) {
+ if (part.startsWith('var(')) continue
+
+ if (isUrl(part)) {
+ count += 1
+ continue
+ }
+
+ if (IS_GRADIENT_FN.test(part)) {
+ count += 1
+ continue
+ }
+
+ if (IS_IMAGE_FN.test(part)) {
+ count += 1
+ continue
+ }
+
+ return false
+ }
+
+ return count > 0
+}
+
+/* -------------------------------------------------------------------------- */
+
+function isGenericName(value: string): boolean {
+ return (
+ value === 'serif' ||
+ value === 'sans-serif' ||
+ value === 'monospace' ||
+ value === 'cursive' ||
+ value === 'fantasy' ||
+ value === 'system-ui' ||
+ value === 'ui-serif' ||
+ value === 'ui-sans-serif' ||
+ value === 'ui-monospace' ||
+ value === 'ui-rounded' ||
+ value === 'math' ||
+ value === 'emoji' ||
+ value === 'fangsong'
+ )
+}
+
+function isFamilyName(value: string): boolean {
+ let count = 0
+
+ for (let part of segment(value, ',')) {
+ // If it starts with a digit, then it's not a family name
+ let char = part.charCodeAt(0)
+ if (char >= 48 && char <= 57) return false
+
+ if (part.startsWith('var(')) continue
+
+ count += 1
+ }
+
+ return count > 0
+}
+
+function isAbsoluteSize(value: string): boolean {
+ return (
+ value === 'xx-small' ||
+ value === 'x-small' ||
+ value === 'small' ||
+ value === 'medium' ||
+ value === 'large' ||
+ value === 'x-large' ||
+ value === 'xx-large' ||
+ value === 'xxx-large'
+ )
+}
+
+function isRelativeSize(value: string): boolean {
+ return value === 'larger' || value === 'smaller'
+}
+
+/* -------------------------------------------------------------------------- */
+
+const HAS_NUMBER = /[+-]?\d*\.?\d+(?:[eE][+-]?\d+)?/
+
+/* -------------------------------------------------------------------------- */
+
+const IS_NUMBER = new RegExp(`^${HAS_NUMBER.source}$`)
+
+function isNumber(value: string): boolean {
+ return IS_NUMBER.test(value) || hasMathFn(value)
+}
+
+/* -------------------------------------------------------------------------- */
+
+const IS_PERCENTAGE = new RegExp(`^${HAS_NUMBER.source}%$`)
+
+function isPercentage(value: string): boolean {
+ return IS_PERCENTAGE.test(value) || hasMathFn(value)
+}
+
+/* -------------------------------------------------------------------------- */
+
+/**
+ * Please refer to MDN when updating this list:
+ * @see https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Values_and_units
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries#container_query_length_units
+ */
+const LENGTH_UNITS = [
+ 'cm',
+ 'mm',
+ 'Q',
+ 'in',
+ 'pc',
+ 'pt',
+ 'px',
+ 'em',
+ 'ex',
+ 'ch',
+ 'rem',
+ 'lh',
+ 'rlh',
+ 'vw',
+ 'vh',
+ 'vmin',
+ 'vmax',
+ 'vb',
+ 'vi',
+ 'svw',
+ 'svh',
+ 'lvw',
+ 'lvh',
+ 'dvw',
+ 'dvh',
+ 'cqw',
+ 'cqh',
+ 'cqi',
+ 'cqb',
+ 'cqmin',
+ 'cqmax',
+]
+
+const IS_LENGTH = new RegExp(`^${HAS_NUMBER.source}(${LENGTH_UNITS.join('|')})$`)
+
+function isLength(value: string): boolean {
+ return IS_LENGTH.test(value) || hasMathFn(value)
+}
+
+/* -------------------------------------------------------------------------- */
+
+function isBackgroundPosition(value: string): boolean {
+ let count = 0
+
+ for (let part of segment(value, ' ')) {
+ if (
+ part === 'center' ||
+ part === 'top' ||
+ part === 'right' ||
+ part === 'bottom' ||
+ part === 'left'
+ ) {
+ count += 1
+ continue
+ }
+
+ if (part.startsWith('var(')) continue
+
+ if (isLength(part) || isPercentage(part)) {
+ count += 1
+ continue
+ }
+
+ return false
+ }
+
+ return count > 0
+}
+
+/* -------------------------------------------------------------------------- */
+
+/**
+ * Determine if `value` is valid for `background-size`
+ *
+ * background-size =
+ * #
+ *
+ * =
+ * [ | auto ]{1,2} |
+ * cover |
+ * contain
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/background-size#formal_syntax
+ */
+function isBackgroundSize(value: string) {
+ let count = 0
+
+ for (let size of segment(value, ',')) {
+ if (size === 'cover' || size === 'contain') {
+ count += 1
+ continue
+ }
+
+ let values = segment(size, ' ')
+
+ // Sizes must have exactly one or two values
+ if (values.length !== 1 && values.length !== 2) {
+ return false
+ }
+
+ if (values.every((value) => value === 'auto' || isLength(value) || isPercentage(value))) {
+ count += 1
+ continue
+ }
+ }
+
+ return count > 0
+}
diff --git a/packages/tailwindcss/src/utils/is-color.ts b/packages/tailwindcss/src/utils/is-color.ts
new file mode 100644
index 000000000..0448556db
--- /dev/null
+++ b/packages/tailwindcss/src/utils/is-color.ts
@@ -0,0 +1,204 @@
+const NAMED_COLORS = [
+ // CSS Level 1 colors
+ 'black',
+ 'silver',
+ 'gray',
+ 'white',
+ 'maroon',
+ 'red',
+ 'purple',
+ 'fuchsia',
+ 'green',
+ 'lime',
+ 'olive',
+ 'yellow',
+ 'navy',
+ 'blue',
+ 'teal',
+ 'aqua',
+
+ // CSS Level 2/3 colors
+ 'aliceblue',
+ 'antiquewhite',
+ 'aqua',
+ 'aquamarine',
+ 'azure',
+ 'beige',
+ 'bisque',
+ 'black',
+ 'blanchedalmond',
+ 'blue',
+ 'blueviolet',
+ 'brown',
+ 'burlywood',
+ 'cadetblue',
+ 'chartreuse',
+ 'chocolate',
+ 'coral',
+ 'cornflowerblue',
+ 'cornsilk',
+ 'crimson',
+ 'cyan',
+ 'darkblue',
+ 'darkcyan',
+ 'darkgoldenrod',
+ 'darkgray',
+ 'darkgreen',
+ 'darkgrey',
+ 'darkkhaki',
+ 'darkmagenta',
+ 'darkolivegreen',
+ 'darkorange',
+ 'darkorchid',
+ 'darkred',
+ 'darksalmon',
+ 'darkseagreen',
+ 'darkslateblue',
+ 'darkslategray',
+ 'darkslategrey',
+ 'darkturquoise',
+ 'darkviolet',
+ 'deeppink',
+ 'deepskyblue',
+ 'dimgray',
+ 'dimgrey',
+ 'dodgerblue',
+ 'firebrick',
+ 'floralwhite',
+ 'forestgreen',
+ 'fuchsia',
+ 'gainsboro',
+ 'ghostwhite',
+ 'gold',
+ 'goldenrod',
+ 'gray',
+ 'green',
+ 'greenyellow',
+ 'grey',
+ 'honeydew',
+ 'hotpink',
+ 'indianred',
+ 'indigo',
+ 'ivory',
+ 'khaki',
+ 'lavender',
+ 'lavenderblush',
+ 'lawngreen',
+ 'lemonchiffon',
+ 'lightblue',
+ 'lightcoral',
+ 'lightcyan',
+ 'lightgoldenrodyellow',
+ 'lightgray',
+ 'lightgreen',
+ 'lightgrey',
+ 'lightpink',
+ 'lightsalmon',
+ 'lightseagreen',
+ 'lightskyblue',
+ 'lightslategray',
+ 'lightslategrey',
+ 'lightsteelblue',
+ 'lightyellow',
+ 'lime',
+ 'limegreen',
+ 'linen',
+ 'magenta',
+ 'maroon',
+ 'mediumaquamarine',
+ 'mediumblue',
+ 'mediumorchid',
+ 'mediumpurple',
+ 'mediumseagreen',
+ 'mediumslateblue',
+ 'mediumspringgreen',
+ 'mediumturquoise',
+ 'mediumvioletred',
+ 'midnightblue',
+ 'mintcream',
+ 'mistyrose',
+ 'moccasin',
+ 'navajowhite',
+ 'navy',
+ 'oldlace',
+ 'olive',
+ 'olivedrab',
+ 'orange',
+ 'orangered',
+ 'orchid',
+ 'palegoldenrod',
+ 'palegreen',
+ 'paleturquoise',
+ 'palevioletred',
+ 'papayawhip',
+ 'peachpuff',
+ 'peru',
+ 'pink',
+ 'plum',
+ 'powderblue',
+ 'purple',
+ 'rebeccapurple',
+ 'red',
+ 'rosybrown',
+ 'royalblue',
+ 'saddlebrown',
+ 'salmon',
+ 'sandybrown',
+ 'seagreen',
+ 'seashell',
+ 'sienna',
+ 'silver',
+ 'skyblue',
+ 'slateblue',
+ 'slategray',
+ 'slategrey',
+ 'snow',
+ 'springgreen',
+ 'steelblue',
+ 'tan',
+ 'teal',
+ 'thistle',
+ 'tomato',
+ 'turquoise',
+ 'violet',
+ 'wheat',
+ 'white',
+ 'whitesmoke',
+ 'yellow',
+ 'yellowgreen',
+
+ // Keywords
+ 'transparent',
+ 'currentcolor',
+
+ // System colors
+ 'canvas',
+ 'canvastext',
+ 'linktext',
+ 'visitedtext',
+ 'activetext',
+ 'buttonface',
+ 'buttontext',
+ 'buttonborder',
+ 'field',
+ 'fieldtext',
+ 'highlight',
+ 'highlighttext',
+ 'selecteditem',
+ 'selecteditemtext',
+ 'mark',
+ 'marktext',
+ 'graytext',
+ 'accentcolor',
+ 'accentcolortext',
+]
+
+const IS_COLOR_FN = /^(rgba?|hsla?|hwb|color|(ok)?(lab|lch)|light-dark|color-mix)\(/i
+
+export function isColor(value: string): boolean {
+ return (
+ value.charCodeAt(0) === 35 /* "#" */ ||
+ IS_COLOR_FN.test(value) ||
+ NAMED_COLORS.includes(value.toLowerCase())
+ )
+}
diff --git a/packages/tailwindcss/src/utils/math-operators.ts b/packages/tailwindcss/src/utils/math-operators.ts
new file mode 100644
index 000000000..7c4b1c473
--- /dev/null
+++ b/packages/tailwindcss/src/utils/math-operators.ts
@@ -0,0 +1,154 @@
+const mathFunctions = [
+ 'calc',
+ 'min',
+ 'max',
+ 'clamp',
+ 'mod',
+ 'rem',
+ 'sin',
+ 'cos',
+ 'tan',
+ 'asin',
+ 'acos',
+ 'atan',
+ 'atan2',
+ 'pow',
+ 'sqrt',
+ 'hypot',
+ 'log',
+ 'exp',
+ 'round',
+]
+
+export function hasMathFn(input: string) {
+ return input.indexOf('(') !== -1 && mathFunctions.some((fn) => input.includes(`${fn}(`))
+}
+
+export function addWhitespaceAroundMathOperators(input: string) {
+ // There's definitely no functions in the input, so bail early
+ if (input.indexOf('(') === -1) {
+ return input
+ }
+
+ // Bail early if there are no math functions in the input
+ if (!mathFunctions.some((fn) => input.includes(fn))) {
+ return input
+ }
+
+ let result = ''
+ let formattable: boolean[] = []
+
+ for (let i = 0; i < input.length; i++) {
+ let char = input[i]
+
+ // Determine if we're inside a math function
+ if (char === '(') {
+ result += char
+
+ // Scan backwards to determine the function name. This assumes math
+ // functions are named with lowercase alphanumeric characters.
+ let start = i
+
+ for (let j = i - 1; j >= 0; j--) {
+ let inner = input.charCodeAt(j)
+
+ if (inner >= 48 && inner <= 57) {
+ start = j // 0-9
+ } else if (inner >= 97 && inner <= 122) {
+ start = j // a-z
+ } else {
+ break
+ }
+ }
+
+ let fn = input.slice(start, i)
+
+ // This is a known math function so start formatting
+ if (mathFunctions.includes(fn)) {
+ formattable.unshift(true)
+ continue
+ }
+
+ // We've encountered nested parens inside a math function, record that and
+ // keep formatting until we've closed all parens.
+ else if (formattable[0] && fn === '') {
+ formattable.unshift(true)
+ continue
+ }
+
+ // This is not a known math function so don't format it
+ formattable.unshift(false)
+ continue
+ }
+
+ // We've exited the function so format according to the parent function's
+ // type.
+ else if (char === ')') {
+ result += char
+ formattable.shift()
+ }
+
+ // Add spaces after commas in math functions
+ else if (char === ',' && formattable[0]) {
+ result += `, `
+ continue
+ }
+
+ // Skip over consecutive whitespace
+ else if (char === ' ' && formattable[0] && result[result.length - 1] === ' ') {
+ continue
+ }
+
+ // Add whitespace around operators inside math functions
+ else if ((char === '+' || char === '*' || char === '/' || char === '-') && formattable[0]) {
+ let trimmed = result.trimEnd()
+ let prev = trimmed[trimmed.length - 1]
+
+ // If we're preceded by an operator don't add spaces
+ if (prev === '+' || prev === '*' || prev === '/' || prev === '-') {
+ result += char
+ continue
+ }
+
+ // If we're at the beginning of an argument don't add spaces
+ else if (prev === '(' || prev === ',') {
+ result += char
+ continue
+ }
+
+ // Add spaces only after the operator if we already have spaces before it
+ else if (input[i - 1] === ' ') {
+ result += `${char} `
+ }
+
+ // Add spaces around the operator
+ else {
+ result += ` ${char} `
+ }
+ }
+
+ // Skip over `to-zero` when in a math function.
+ //
+ // This is specifically to handle this value in the round(…) function:
+ //
+ // ```
+ // round(to-zero, 1px)
+ // ^^^^^^^
+ // ```
+ //
+ // This is because the first argument is optionally a keyword and `to-zero`
+ // contains a hyphen and we want to avoid adding spaces inside it.
+ else if (formattable[0] && input.startsWith('to-zero', i)) {
+ let start = i
+ i += 7
+ result += input.slice(start, i + 1)
+ }
+
+ // Handle all other characters
+ else {
+ result += char
+ }
+ }
+
+ return result
+}
diff --git a/packages/tailwindcss/src/utils/replace-shadow-colors.test.ts b/packages/tailwindcss/src/utils/replace-shadow-colors.test.ts
new file mode 100644
index 000000000..23895596c
--- /dev/null
+++ b/packages/tailwindcss/src/utils/replace-shadow-colors.test.ts
@@ -0,0 +1,37 @@
+import { expect, it } from 'vitest'
+import { replaceShadowColors } from './replace-shadow-colors'
+
+const table = [
+ {
+ input: ['var(--my-shadow)'],
+ output: 'var(--my-shadow)',
+ },
+ {
+ input: ['1px var(--my-shadow)'],
+ output: '1px var(--my-shadow)',
+ },
+ {
+ input: ['1px 1px var(--my-color)'],
+ output: '1px 1px var(--tw-shadow-color)',
+ },
+ {
+ input: ['0 0 0 var(--my-color)'],
+ output: '0 0 0 var(--tw-shadow-color)',
+ },
+ {
+ input: ['var(--my-shadow)', '1px 1px var(--my-color)', '0 0 1px var(--my-color)'],
+ output: [
+ 'var(--my-shadow)',
+ '1px 1px var(--tw-shadow-color)',
+ '0 0 1px var(--tw-shadow-color)',
+ ].join(', '),
+ },
+]
+
+it.each(table)(
+ 'should be possible to get the names for an animation: $output',
+ ({ input, output }) => {
+ let parsed = replaceShadowColors(input.join(', '), 'var(--tw-shadow-color)')
+ expect(parsed).toEqual(output)
+ },
+)
diff --git a/packages/tailwindcss/src/utils/replace-shadow-colors.ts b/packages/tailwindcss/src/utils/replace-shadow-colors.ts
new file mode 100644
index 000000000..e758955be
--- /dev/null
+++ b/packages/tailwindcss/src/utils/replace-shadow-colors.ts
@@ -0,0 +1,38 @@
+import { segment } from './segment'
+
+const KEYWORDS = new Set(['inset', 'inherit', 'initial', 'revert', 'unset'])
+const LENGTH = /^-?(\d+|\.\d+)(.*?)$/g
+
+export function replaceShadowColors(input: string, replacement: string): string {
+ for (let shadow of segment(input, ',')) {
+ let parts = segment(shadow, ' ').filter((part) => part.trim() !== '')
+ let color = null
+ let offsetX = null
+ let offsetY = null
+
+ for (let part of parts) {
+ if (KEYWORDS.has(part)) {
+ continue
+ } else if (LENGTH.test(part)) {
+ if (offsetX === null) {
+ offsetX = part
+ } else if (offsetY === null) {
+ offsetY = part
+ }
+
+ // Reset index, since the regex is stateful.
+ LENGTH.lastIndex = 0
+ } else if (color === null) {
+ color = part
+ }
+ }
+
+ // Only replace if we found an x-offset, y-offset, and color, otherwise we
+ // may be replacing the wrong part of the box-shadow.
+ if (offsetX !== null && offsetY !== null && color !== null) {
+ input = input.replace(color, replacement)
+ }
+ }
+
+ return input
+}
diff --git a/packages/tailwindcss/src/utils/segment.test.ts b/packages/tailwindcss/src/utils/segment.test.ts
new file mode 100644
index 000000000..9cfa91f86
--- /dev/null
+++ b/packages/tailwindcss/src/utils/segment.test.ts
@@ -0,0 +1,29 @@
+import { expect, it } from 'vitest'
+import { segment } from './segment'
+
+it('should result in a single segment when the separator is not present', () => {
+ expect(segment('foo', ':')).toEqual(['foo'])
+})
+
+it('should split by the separator', () => {
+ expect(segment('foo:bar:baz', ':')).toEqual(['foo', 'bar', 'baz'])
+})
+
+it('should not split inside of parens', () => {
+ expect(segment('a:(b:c):d', ':')).toEqual(['a', '(b:c)', 'd'])
+})
+
+it('should not split inside of brackets', () => {
+ expect(segment('a:[b:c]:d', ':')).toEqual(['a', '[b:c]', 'd'])
+})
+
+it('should not split inside of curlies', () => {
+ expect(segment('a:{b:c}:d', ':')).toEqual(['a', '{b:c}', 'd'])
+})
+
+it('should split by the escape sequence which is escape as well', () => {
+ expect(segment('a\\b\\c\\d', '\\')).toEqual(['a', 'b', 'c', 'd'])
+ expect(segment('a\\(b\\c)\\d', '\\')).toEqual(['a', '(b\\c)', 'd'])
+ expect(segment('a\\[b\\c]\\d', '\\')).toEqual(['a', '[b\\c]', 'd'])
+ expect(segment('a\\{b\\c}\\d', '\\')).toEqual(['a', '{b\\c}', 'd'])
+})
diff --git a/packages/tailwindcss/src/utils/segment.ts b/packages/tailwindcss/src/utils/segment.ts
new file mode 100644
index 000000000..e043d30a8
--- /dev/null
+++ b/packages/tailwindcss/src/utils/segment.ts
@@ -0,0 +1,59 @@
+/**
+ * This splits a string on a top-level character.
+ *
+ * Regex doesn't support recursion (at least not the JS-flavored version).
+ * So we have to use a tiny state machine to keep track of paren placement.
+ *
+ * Expected behavior using commas:
+ * var(--a, 0 0 1px rgb(0, 0, 0)), 0 0 1px rgb(0, 0, 0)
+ * ┬ ┬ ┬ ┬
+ * x x x ╰──────── Split because top-level
+ * ╰──────────────┴──┴───────────── Ignored b/c inside >= 1 levels of parens
+ */
+export function segment(input: string, separator: string) {
+ // Stack of characters to close open brackets. Appending to a string because
+ // it's faster than an array of strings.
+ let closingBracketStack = ''
+ let parts: string[] = []
+ let lastPos = 0
+
+ for (let idx = 0; idx < input.length; idx++) {
+ let char = input[idx]
+
+ if (closingBracketStack.length === 0 && char === separator) {
+ parts.push(input.slice(lastPos, idx))
+ lastPos = idx + 1
+ continue
+ }
+
+ switch (char) {
+ case '\\':
+ // The next character is escaped, so we skip it.
+ idx += 1
+ break
+ case '(':
+ closingBracketStack += ')'
+ break
+ case '[':
+ closingBracketStack += ']'
+ break
+ case '{':
+ closingBracketStack += '}'
+ break
+ case ')':
+ case ']':
+ case '}':
+ if (
+ closingBracketStack.length > 0 &&
+ char === closingBracketStack[closingBracketStack.length - 1]
+ ) {
+ closingBracketStack = closingBracketStack.slice(0, closingBracketStack.length - 1)
+ }
+ break
+ }
+ }
+
+ parts.push(input.slice(lastPos))
+
+ return parts
+}
diff --git a/packages/tailwindcss/src/variants.test.ts b/packages/tailwindcss/src/variants.test.ts
new file mode 100644
index 000000000..675fa6931
--- /dev/null
+++ b/packages/tailwindcss/src/variants.test.ts
@@ -0,0 +1,2067 @@
+import { expect, test } from 'vitest'
+import { compileCss, run } from './test-utils/run'
+
+const css = String.raw
+
+test('force', () => {
+ expect(run(['force:flex'])).toMatchInlineSnapshot(`
+ ".force\\:flex {
+ display: flex;
+ }"
+ `)
+})
+
+test('*', () => {
+ expect(run(['*:flex'])).toMatchInlineSnapshot(`
+ ".\\*\\:flex > * {
+ display: flex;
+ }"
+`)
+})
+
+test('first-letter', () => {
+ expect(run(['first-letter:flex'])).toMatchInlineSnapshot(`
+ ".first-letter\\:flex:first-letter {
+ display: flex;
+ }"
+ `)
+})
+
+test('first-line', () => {
+ expect(run(['first-line:flex'])).toMatchInlineSnapshot(`
+ ".first-line\\:flex:first-line {
+ display: flex;
+ }"
+ `)
+})
+
+test('marker', () => {
+ expect(run(['marker:flex'])).toMatchInlineSnapshot(`
+ ".marker\\:flex ::marker, .marker\\:flex::marker {
+ display: flex;
+ }"
+ `)
+})
+
+test('selection', () => {
+ expect(run(['selection:flex'])).toMatchInlineSnapshot(`
+ ".selection\\:flex ::selection, .selection\\:flex::selection {
+ display: flex;
+ }"
+ `)
+})
+
+test('file', () => {
+ expect(run(['file:flex'])).toMatchInlineSnapshot(`
+ ".file\\:flex::file-selector-button {
+ display: flex;
+ }"
+ `)
+})
+
+test('placeholder', () => {
+ expect(run(['placeholder:flex'])).toMatchInlineSnapshot(`
+ ".placeholder\\:flex::placeholder {
+ display: flex;
+ }"
+ `)
+})
+
+test('backdrop', () => {
+ expect(run(['backdrop:flex'])).toMatchInlineSnapshot(`
+ ".backdrop\\:flex::backdrop {
+ display: flex;
+ }"
+ `)
+})
+
+test('before', () => {
+ expect(run(['before:flex'])).toMatchInlineSnapshot(`
+ ".before\\:flex:before {
+ content: var(--tw-content);
+ display: flex;
+ }
+
+ @property --tw-content {
+ syntax: "*";
+ inherits: false;
+ initial-value: "";
+ }"
+ `)
+})
+
+test('after', () => {
+ expect(run(['after:flex'])).toMatchInlineSnapshot(`
+ ".after\\:flex:after {
+ content: var(--tw-content);
+ display: flex;
+ }
+
+ @property --tw-content {
+ syntax: "*";
+ inherits: false;
+ initial-value: "";
+ }"
+ `)
+})
+
+test('first', () => {
+ expect(run(['first:flex', 'group-first:flex', 'peer-first:flex'])).toMatchInlineSnapshot(`
+ ".group-first\\:flex:is(:where(.group):first-child *) {
+ display: flex;
+ }
+
+ .peer-first\\:flex:is(:where(.peer):first-child ~ *) {
+ display: flex;
+ }
+
+ .first\\:flex:first-child {
+ display: flex;
+ }"
+ `)
+})
+
+test('last', () => {
+ expect(run(['last:flex', 'group-last:flex', 'peer-last:flex'])).toMatchInlineSnapshot(`
+ ".group-last\\:flex:is(:where(.group):last-child *) {
+ display: flex;
+ }
+
+ .peer-last\\:flex:is(:where(.peer):last-child ~ *) {
+ display: flex;
+ }
+
+ .last\\:flex:last-child {
+ display: flex;
+ }"
+ `)
+})
+
+test('only', () => {
+ expect(run(['only:flex', 'group-only:flex', 'peer-only:flex'])).toMatchInlineSnapshot(`
+ ".group-only\\:flex:is(:where(.group):only-child *) {
+ display: flex;
+ }
+
+ .peer-only\\:flex:is(:where(.peer):only-child ~ *) {
+ display: flex;
+ }
+
+ .only\\:flex:only-child {
+ display: flex;
+ }"
+ `)
+})
+
+test('odd', () => {
+ expect(run(['odd:flex', 'group-odd:flex', 'peer-odd:flex'])).toMatchInlineSnapshot(`
+ ".group-odd\\:flex:is(:where(.group):nth-child(odd) *) {
+ display: flex;
+ }
+
+ .peer-odd\\:flex:is(:where(.peer):nth-child(odd) ~ *) {
+ display: flex;
+ }
+
+ .odd\\:flex:nth-child(odd) {
+ display: flex;
+ }"
+ `)
+})
+
+test('even', () => {
+ expect(run(['even:flex', 'group-even:flex', 'peer-even:flex'])).toMatchInlineSnapshot(`
+ ".group-even\\:flex:is(:where(.group):nth-child(2n) *) {
+ display: flex;
+ }
+
+ .peer-even\\:flex:is(:where(.peer):nth-child(2n) ~ *) {
+ display: flex;
+ }
+
+ .even\\:flex:nth-child(2n) {
+ display: flex;
+ }"
+ `)
+})
+
+test('first-of-type', () => {
+ expect(run(['first-of-type:flex', 'group-first-of-type:flex', 'peer-first-of-type:flex']))
+ .toMatchInlineSnapshot(`
+ ".group-first-of-type\\:flex:is(:where(.group):first-of-type *) {
+ display: flex;
+ }
+
+ .peer-first-of-type\\:flex:is(:where(.peer):first-of-type ~ *) {
+ display: flex;
+ }
+
+ .first-of-type\\:flex:first-of-type {
+ display: flex;
+ }"
+ `)
+})
+
+test('last-of-type', () => {
+ expect(run(['last-of-type:flex', 'group-last-of-type:flex', 'peer-last-of-type:flex']))
+ .toMatchInlineSnapshot(`
+ ".group-last-of-type\\:flex:is(:where(.group):last-of-type *) {
+ display: flex;
+ }
+
+ .peer-last-of-type\\:flex:is(:where(.peer):last-of-type ~ *) {
+ display: flex;
+ }
+
+ .last-of-type\\:flex:last-of-type {
+ display: flex;
+ }"
+ `)
+})
+
+test('only-of-type', () => {
+ expect(run(['only-of-type:flex', 'group-only-of-type:flex', 'peer-only-of-type:flex']))
+ .toMatchInlineSnapshot(`
+ ".group-only-of-type\\:flex:is(:where(.group):only-of-type *) {
+ display: flex;
+ }
+
+ .peer-only-of-type\\:flex:is(:where(.peer):only-of-type ~ *) {
+ display: flex;
+ }
+
+ .only-of-type\\:flex:only-of-type {
+ display: flex;
+ }"
+ `)
+})
+
+test('visited', () => {
+ expect(run(['visited:flex', 'group-visited:flex', 'peer-visited:flex'])).toMatchInlineSnapshot(`
+ ".group-visited\\:flex:is(:where(.group):visited *) {
+ display: flex;
+ }
+
+ .peer-visited\\:flex:is(:where(.peer):visited ~ *) {
+ display: flex;
+ }
+
+ .visited\\:flex:visited {
+ display: flex;
+ }"
+ `)
+})
+
+test('target', () => {
+ expect(run(['target:flex', 'group-target:flex', 'peer-target:flex'])).toMatchInlineSnapshot(`
+ ".group-target\\:flex:is(:where(.group):target *) {
+ display: flex;
+ }
+
+ .peer-target\\:flex:is(:where(.peer):target ~ *) {
+ display: flex;
+ }
+
+ .target\\:flex:target {
+ display: flex;
+ }"
+ `)
+})
+
+test('open', () => {
+ expect(run(['open:flex', 'group-open:flex', 'peer-open:flex'])).toMatchInlineSnapshot(`
+ ".group-open\\:flex:is(:where(.group)[open] *) {
+ display: flex;
+ }
+
+ .peer-open\\:flex:is(:where(.peer)[open] ~ *) {
+ display: flex;
+ }
+
+ .open\\:flex[open] {
+ display: flex;
+ }"
+ `)
+})
+
+test('default', () => {
+ expect(run(['default:flex', 'group-default:flex', 'peer-default:flex'])).toMatchInlineSnapshot(`
+ ".group-default\\:flex:is(:where(.group):default *) {
+ display: flex;
+ }
+
+ .peer-default\\:flex:is(:where(.peer):default ~ *) {
+ display: flex;
+ }
+
+ .default\\:flex:default {
+ display: flex;
+ }"
+ `)
+})
+
+test('checked', () => {
+ expect(run(['checked:flex', 'group-checked:flex', 'peer-checked:flex'])).toMatchInlineSnapshot(`
+ ".group-checked\\:flex:is(:where(.group):checked *) {
+ display: flex;
+ }
+
+ .peer-checked\\:flex:is(:where(.peer):checked ~ *) {
+ display: flex;
+ }
+
+ .checked\\:flex:checked {
+ display: flex;
+ }"
+ `)
+})
+
+test('indeterminate', () => {
+ expect(run(['indeterminate:flex', 'group-indeterminate:flex', 'peer-indeterminate:flex']))
+ .toMatchInlineSnapshot(`
+ ".group-indeterminate\\:flex:is(:where(.group):indeterminate *) {
+ display: flex;
+ }
+
+ .peer-indeterminate\\:flex:is(:where(.peer):indeterminate ~ *) {
+ display: flex;
+ }
+
+ .indeterminate\\:flex:indeterminate {
+ display: flex;
+ }"
+ `)
+})
+
+test('placeholder-shown', () => {
+ expect(
+ run(['placeholder-shown:flex', 'group-placeholder-shown:flex', 'peer-placeholder-shown:flex']),
+ ).toMatchInlineSnapshot(`
+ ".group-placeholder-shown\\:flex:is(:where(.group):placeholder-shown *) {
+ display: flex;
+ }
+
+ .peer-placeholder-shown\\:flex:is(:where(.peer):placeholder-shown ~ *) {
+ display: flex;
+ }
+
+ .placeholder-shown\\:flex:placeholder-shown {
+ display: flex;
+ }"
+ `)
+})
+
+test('autofill', () => {
+ expect(run(['autofill:flex', 'group-autofill:flex', 'peer-autofill:flex']))
+ .toMatchInlineSnapshot(`
+ ".group-autofill\\:flex:is(:where(.group):autofill *) {
+ display: flex;
+ }
+
+ .peer-autofill\\:flex:is(:where(.peer):autofill ~ *) {
+ display: flex;
+ }
+
+ .autofill\\:flex:autofill {
+ display: flex;
+ }"
+ `)
+})
+
+test('optional', () => {
+ expect(run(['optional:flex', 'group-optional:flex', 'peer-optional:flex']))
+ .toMatchInlineSnapshot(`
+ ".group-optional\\:flex:is(:where(.group):optional *) {
+ display: flex;
+ }
+
+ .peer-optional\\:flex:is(:where(.peer):optional ~ *) {
+ display: flex;
+ }
+
+ .optional\\:flex:optional {
+ display: flex;
+ }"
+ `)
+})
+
+test('required', () => {
+ expect(run(['required:flex', 'group-required:flex', 'peer-required:flex']))
+ .toMatchInlineSnapshot(`
+ ".group-required\\:flex:is(:where(.group):required *) {
+ display: flex;
+ }
+
+ .peer-required\\:flex:is(:where(.peer):required ~ *) {
+ display: flex;
+ }
+
+ .required\\:flex:required {
+ display: flex;
+ }"
+ `)
+})
+
+test('valid', () => {
+ expect(run(['valid:flex', 'group-valid:flex', 'peer-valid:flex'])).toMatchInlineSnapshot(`
+ ".group-valid\\:flex:is(:where(.group):valid *) {
+ display: flex;
+ }
+
+ .peer-valid\\:flex:is(:where(.peer):valid ~ *) {
+ display: flex;
+ }
+
+ .valid\\:flex:valid {
+ display: flex;
+ }"
+ `)
+})
+
+test('invalid', () => {
+ expect(run(['invalid:flex', 'group-invalid:flex', 'peer-invalid:flex'])).toMatchInlineSnapshot(`
+ ".group-invalid\\:flex:is(:where(.group):invalid *) {
+ display: flex;
+ }
+
+ .peer-invalid\\:flex:is(:where(.peer):invalid ~ *) {
+ display: flex;
+ }
+
+ .invalid\\:flex:invalid {
+ display: flex;
+ }"
+ `)
+})
+
+test('in-range', () => {
+ expect(run(['in-range:flex', 'group-in-range:flex', 'peer-in-range:flex']))
+ .toMatchInlineSnapshot(`
+ ".group-in-range\\:flex:is(:where(.group):in-range *) {
+ display: flex;
+ }
+
+ .peer-in-range\\:flex:is(:where(.peer):in-range ~ *) {
+ display: flex;
+ }
+
+ .in-range\\:flex:in-range {
+ display: flex;
+ }"
+ `)
+})
+
+test('out-of-range', () => {
+ expect(run(['out-of-range:flex', 'group-out-of-range:flex', 'peer-out-of-range:flex']))
+ .toMatchInlineSnapshot(`
+ ".group-out-of-range\\:flex:is(:where(.group):out-of-range *) {
+ display: flex;
+ }
+
+ .peer-out-of-range\\:flex:is(:where(.peer):out-of-range ~ *) {
+ display: flex;
+ }
+
+ .out-of-range\\:flex:out-of-range {
+ display: flex;
+ }"
+ `)
+})
+
+test('read-only', () => {
+ expect(run(['read-only:flex', 'group-read-only:flex', 'peer-read-only:flex']))
+ .toMatchInlineSnapshot(`
+ ".group-read-only\\:flex:is(:where(.group):read-only *) {
+ display: flex;
+ }
+
+ .peer-read-only\\:flex:is(:where(.peer):read-only ~ *) {
+ display: flex;
+ }
+
+ .read-only\\:flex:read-only {
+ display: flex;
+ }"
+ `)
+})
+
+test('empty', () => {
+ expect(run(['empty:flex', 'group-empty:flex', 'peer-empty:flex'])).toMatchInlineSnapshot(`
+ ".group-empty\\:flex:is(:where(.group):empty *) {
+ display: flex;
+ }
+
+ .peer-empty\\:flex:is(:where(.peer):empty ~ *) {
+ display: flex;
+ }
+
+ .empty\\:flex:empty {
+ display: flex;
+ }"
+ `)
+})
+
+test('focus-within', () => {
+ expect(run(['focus-within:flex', 'group-focus-within:flex', 'peer-focus-within:flex']))
+ .toMatchInlineSnapshot(`
+ ".group-focus-within\\:flex:is(:where(.group):focus-within *) {
+ display: flex;
+ }
+
+ .peer-focus-within\\:flex:is(:where(.peer):focus-within ~ *) {
+ display: flex;
+ }
+
+ .focus-within\\:flex:focus-within {
+ display: flex;
+ }"
+ `)
+})
+
+test('hover', () => {
+ expect(run(['hover:flex', 'group-hover:flex', 'peer-hover:flex'])).toMatchInlineSnapshot(`
+ ".group-hover\\:flex:is(:where(.group):hover *) {
+ display: flex;
+ }
+
+ .peer-hover\\:flex:is(:where(.peer):hover ~ *) {
+ display: flex;
+ }
+
+ .hover\\:flex:hover {
+ display: flex;
+ }"
+ `)
+})
+
+test('focus', () => {
+ expect(run(['focus:flex', 'group-focus:flex', 'peer-focus:flex'])).toMatchInlineSnapshot(`
+ ".group-focus\\:flex:is(:where(.group):focus *) {
+ display: flex;
+ }
+
+ .peer-focus\\:flex:is(:where(.peer):focus ~ *) {
+ display: flex;
+ }
+
+ .focus\\:flex:focus {
+ display: flex;
+ }"
+ `)
+})
+
+test('group-hover group-focus', () => {
+ expect(run(['group-hover:flex', 'group-focus:flex'])).toMatchInlineSnapshot(`
+ ".group-hover\\:flex:is(:where(.group):hover *) {
+ display: flex;
+ }
+
+ .group-focus\\:flex:is(:where(.group):focus *) {
+ display: flex;
+ }"
+ `)
+ expect(run(['group-focus:flex', 'group-hover:flex'])).toMatchInlineSnapshot(`
+ ".group-hover\\:flex:is(:where(.group):hover *) {
+ display: flex;
+ }
+
+ .group-focus\\:flex:is(:where(.group):focus *) {
+ display: flex;
+ }"
+ `)
+})
+
+test('focus-visible', () => {
+ expect(run(['focus-visible:flex', 'group-focus-visible:flex', 'peer-focus-visible:flex']))
+ .toMatchInlineSnapshot(`
+ ".group-focus-visible\\:flex:is(:where(.group):focus-visible *) {
+ display: flex;
+ }
+
+ .peer-focus-visible\\:flex:is(:where(.peer):focus-visible ~ *) {
+ display: flex;
+ }
+
+ .focus-visible\\:flex:focus-visible {
+ display: flex;
+ }"
+ `)
+})
+
+test('active', () => {
+ expect(run(['active:flex', 'group-active:flex', 'peer-active:flex'])).toMatchInlineSnapshot(`
+ ".group-active\\:flex:is(:where(.group):active *) {
+ display: flex;
+ }
+
+ .peer-active\\:flex:is(:where(.peer):active ~ *) {
+ display: flex;
+ }
+
+ .active\\:flex:active {
+ display: flex;
+ }"
+ `)
+})
+
+test('enabled', () => {
+ expect(run(['enabled:flex', 'group-enabled:flex', 'peer-enabled:flex'])).toMatchInlineSnapshot(`
+ ".group-enabled\\:flex:is(:where(.group):enabled *) {
+ display: flex;
+ }
+
+ .peer-enabled\\:flex:is(:where(.peer):enabled ~ *) {
+ display: flex;
+ }
+
+ .enabled\\:flex:enabled {
+ display: flex;
+ }"
+ `)
+})
+
+test('disabled', () => {
+ expect(run(['disabled:flex', 'group-disabled:flex', 'peer-disabled:flex']))
+ .toMatchInlineSnapshot(`
+ ".group-disabled\\:flex:is(:where(.group):disabled *) {
+ display: flex;
+ }
+
+ .peer-disabled\\:flex:is(:where(.peer):disabled ~ *) {
+ display: flex;
+ }
+
+ .disabled\\:flex:disabled {
+ display: flex;
+ }"
+ `)
+})
+
+test('group-[...]', () => {
+ expect(
+ run([
+ 'group-[&_p]:flex',
+ 'group-[&_p]:hover:flex',
+ 'hover:group-[&_p]:flex',
+ 'hover:group-[&_p]:hover:flex',
+ 'group-[&:hover]:group-[&_p]:flex',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".group-\\[\\&_p\\]\\:flex:is(:where(.group) p *) {
+ display: flex;
+ }
+
+ .group-\\[\\&\\:hover\\]\\:group-\\[\\&_p\\]\\:flex:is(:where(.group) p *):is(:where(.group):hover *) {
+ display: flex;
+ }
+
+ .group-\\[\\&_p\\]\\:hover\\:flex:hover:is(:where(.group) p *) {
+ display: flex;
+ }
+
+ .hover\\:group-\\[\\&_p\\]\\:flex:is(:where(.group) p *):hover {
+ display: flex;
+ }
+
+ .hover\\:group-\\[\\&_p\\]\\:hover\\:flex:hover:is(:where(.group) p *):hover {
+ display: flex;
+ }"
+ `)
+})
+
+test('group-*', () => {
+ expect(
+ run([
+ 'group-hover:flex',
+ 'group-focus:flex',
+ 'group-hover:group-focus:flex',
+ 'group-focus:group-hover:flex',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".group-hover\\:flex:is(:where(.group):hover *) {
+ display: flex;
+ }
+
+ .group-focus\\:flex:is(:where(.group):focus *) {
+ display: flex;
+ }
+
+ .group-focus\\:group-hover\\:flex:is(:where(.group):hover *):is(:where(.group):focus *) {
+ display: flex;
+ }
+
+ .group-hover\\:group-focus\\:flex:is(:where(.group):focus *):is(:where(.group):hover *) {
+ display: flex;
+ }"
+ `)
+})
+
+test('peer-[...]', () => {
+ expect(
+ run([
+ 'peer-[&_p]:flex',
+ 'peer-[&_p]:hover:flex',
+ 'hover:peer-[&_p]:flex',
+ 'hover:peer-[&_p]:focus:flex',
+ 'peer-[&:hover]:peer-[&_p]:flex',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".peer-\\[\\&_p\\]\\:flex:is(:where(.peer) p ~ *) {
+ display: flex;
+ }
+
+ .peer-\\[\\&\\:hover\\]\\:peer-\\[\\&_p\\]\\:flex:is(:where(.peer) p ~ *):is(:where(.peer):hover ~ *) {
+ display: flex;
+ }
+
+ .hover\\:peer-\\[\\&_p\\]\\:flex:is(:where(.peer) p ~ *):hover {
+ display: flex;
+ }
+
+ .peer-\\[\\&_p\\]\\:hover\\:flex:hover:is(:where(.peer) p ~ *) {
+ display: flex;
+ }
+
+ .hover\\:peer-\\[\\&_p\\]\\:focus\\:flex:focus:is(:where(.peer) p ~ *):hover {
+ display: flex;
+ }"
+ `)
+})
+
+test('peer-*', () => {
+ expect(
+ run([
+ 'peer-hover:flex',
+ 'peer-focus:flex',
+ 'peer-hover:peer-focus:flex',
+ 'peer-focus:peer-hover:flex',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".peer-hover\\:flex:is(:where(.peer):hover ~ *) {
+ display: flex;
+ }
+
+ .peer-focus\\:flex:is(:where(.peer):focus ~ *) {
+ display: flex;
+ }
+
+ .peer-focus\\:peer-hover\\:flex:is(:where(.peer):hover ~ *):is(:where(.peer):focus ~ *) {
+ display: flex;
+ }
+
+ .peer-hover\\:peer-focus\\:flex:is(:where(.peer):focus ~ *):is(:where(.peer):hover ~ *) {
+ display: flex;
+ }"
+ `)
+})
+
+test('ltr', () => {
+ expect(run(['ltr:flex'])).toMatchInlineSnapshot(`
+ ".ltr\\:flex:where([dir="ltr"], [dir="ltr"] *) {
+ display: flex;
+ }"
+ `)
+})
+
+test('rtl', () => {
+ expect(run(['rtl:flex'])).toMatchInlineSnapshot(`
+ ".rtl\\:flex:where([dir="rtl"], [dir="rtl"] *) {
+ display: flex;
+ }"
+ `)
+})
+
+test('motion-safe', () => {
+ expect(run(['motion-safe:flex'])).toMatchInlineSnapshot(`
+ "@media (prefers-reduced-motion: no-preference) {
+ .motion-safe\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('motion-reduce', () => {
+ expect(run(['motion-reduce:flex'])).toMatchInlineSnapshot(`
+ "@media (prefers-reduced-motion: reduce) {
+ .motion-reduce\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('dark', () => {
+ expect(run(['dark:flex'])).toMatchInlineSnapshot(`
+ "@media (prefers-color-scheme: dark) {
+ .dark\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('print', () => {
+ expect(run(['print:flex'])).toMatchInlineSnapshot(`
+ "@media print {
+ .print\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('default breakpoints', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ /* Breakpoints */
+ --breakpoint-sm: 640px;
+ --breakpoint-md: 768px;
+ --breakpoint-lg: 1024px;
+ --breakpoint-xl: 1280px;
+ --breakpoint-2xl: 1536px;
+ }
+ @tailwind utilities;
+ `,
+ ['sm:flex', 'md:flex', 'lg:flex', 'xl:flex', '2xl:flex'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --breakpoint-sm: 640px;
+ --breakpoint-md: 768px;
+ --breakpoint-lg: 1024px;
+ --breakpoint-xl: 1280px;
+ --breakpoint-2xl: 1536px;
+ }
+
+ @media (width >= 640px) {
+ .sm\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 768px) {
+ .md\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 1024px) {
+ .lg\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 1280px) {
+ .xl\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 1536px) {
+ .\\32 xl\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('custom breakpoint', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --breakpoint-10xl: 5000px;
+ }
+ @tailwind utilities;
+ `,
+ ['10xl:flex'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --breakpoint-10xl: 5000px;
+ }
+
+ @media (width >= 5000px) {
+ .\\31 0xl\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('max-*', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ /* Explicitly ordered in a strange way */
+ --breakpoint-sm: 640px;
+ --breakpoint-lg: 1024px;
+ --breakpoint-md: 768px;
+ }
+ @tailwind utilities;
+ `,
+ ['max-lg:flex', 'max-sm:flex', 'max-md:flex'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --breakpoint-sm: 640px;
+ --breakpoint-lg: 1024px;
+ --breakpoint-md: 768px;
+ }
+
+ @media (width < 1024px) {
+ .max-lg\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width < 768px) {
+ .max-md\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width < 640px) {
+ .max-sm\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('min-*', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ /* Explicitly ordered in a strange way */
+ --breakpoint-sm: 640px;
+ --breakpoint-lg: 1024px;
+ --breakpoint-md: 768px;
+ }
+ @tailwind utilities;
+ `,
+ ['min-lg:flex', 'min-sm:flex', 'min-md:flex'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --breakpoint-sm: 640px;
+ --breakpoint-lg: 1024px;
+ --breakpoint-md: 768px;
+ }
+
+ @media (width >= 640px) {
+ .min-sm\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 768px) {
+ .min-md\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 1024px) {
+ .min-lg\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('sorting stacked min-* and max-* variants', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ /* Explicitly ordered in a strange way */
+ --breakpoint-sm: 640px;
+ --breakpoint-lg: 1024px;
+ --breakpoint-md: 768px;
+ --breakpoint-xl: 1280px;
+ --breakpoint-xs: 280px;
+ }
+ @tailwind utilities;
+ `,
+ ['min-sm:max-xl:flex', 'min-md:max-xl:flex', 'min-xs:max-xl:flex'],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --breakpoint-sm: 640px;
+ --breakpoint-lg: 1024px;
+ --breakpoint-md: 768px;
+ --breakpoint-xl: 1280px;
+ --breakpoint-xs: 280px;
+ }
+
+ @media (width < 1280px) {
+ @media (width >= 280px) {
+ .min-xs\\:max-xl\\:flex {
+ display: flex;
+ }
+ }
+ }
+
+ @media (width < 1280px) {
+ @media (width >= 640px) {
+ .min-sm\\:max-xl\\:flex {
+ display: flex;
+ }
+ }
+ }
+
+ @media (width < 1280px) {
+ @media (width >= 768px) {
+ .min-md\\:max-xl\\:flex {
+ display: flex;
+ }
+ }
+ }"
+ `)
+})
+
+test('min, max and unprefixed breakpoints', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ /* Explicitly ordered in a strange way */
+ --breakpoint-sm: 640px;
+ --breakpoint-lg: 1024px;
+ --breakpoint-md: 768px;
+ }
+ @tailwind utilities;
+ `,
+ [
+ 'max-lg-sm-potato:flex',
+ 'min-lg-sm-potato:flex',
+ 'lg-sm-potato:flex',
+ 'max-lg:flex',
+ 'max-sm:flex',
+ 'min-lg:flex',
+ 'max-[1000px]:flex',
+ 'md:flex',
+ 'min-md:flex',
+ 'min-[700px]:flex',
+ 'max-md:flex',
+ 'min-sm:flex',
+ 'sm:flex',
+ 'lg:flex',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --breakpoint-sm: 640px;
+ --breakpoint-lg: 1024px;
+ --breakpoint-md: 768px;
+ }
+
+ @media (width < 1024px) {
+ .max-lg\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width < 1000px) {
+ .max-\\[1000px\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width < 768px) {
+ .max-md\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width < 640px) {
+ .max-sm\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 640px) {
+ .min-sm\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 640px) {
+ .sm\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 700px) {
+ .min-\\[700px\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 768px) {
+ .md\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 768px) {
+ .min-md\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 1024px) {
+ .lg\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 1024px) {
+ .min-lg\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('sorting `min` and `max` should sort by unit, then by value, then alphabetically', () => {
+ expect(
+ run([
+ 'min-[10px]:flex',
+ 'min-[12px]:flex',
+ 'min-[10em]:flex',
+ 'min-[12em]:flex',
+ 'min-[10rem]:flex',
+ 'min-[12rem]:flex',
+ 'max-[10px]:flex',
+ 'max-[12px]:flex',
+ 'max-[10em]:flex',
+ 'max-[12em]:flex',
+ 'max-[10rem]:flex',
+ 'max-[12rem]:flex',
+ 'min-[calc(1000px+12em)]:flex',
+ 'max-[calc(1000px+12em)]:flex',
+ 'min-[calc(50vh+12em)]:flex',
+ 'max-[calc(50vh+12em)]:flex',
+ 'min-[10vh]:flex',
+ 'min-[12vh]:flex',
+ 'max-[10vh]:flex',
+ 'max-[12vh]:flex',
+ ]),
+ ).toMatchInlineSnapshot(`
+ "@media (width < calc(1000px + 12em)) {
+ .max-\\[calc\\(1000px\\+12em\\)\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width < calc(50vh + 12em)) {
+ .max-\\[calc\\(50vh\\+12em\\)\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width < 12em) {
+ .max-\\[12em\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width < 10em) {
+ .max-\\[10em\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width < 12px) {
+ .max-\\[12px\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width < 10px) {
+ .max-\\[10px\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width < 12rem) {
+ .max-\\[12rem\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width < 10rem) {
+ .max-\\[10rem\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width < 12vh) {
+ .max-\\[12vh\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width < 10vh) {
+ .max-\\[10vh\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= calc(1000px + 12em)) {
+ .min-\\[calc\\(1000px\\+12em\\)\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= calc(50vh + 12em)) {
+ .min-\\[calc\\(50vh\\+12em\\)\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 10em) {
+ .min-\\[10em\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 12em) {
+ .min-\\[12em\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 10px) {
+ .min-\\[10px\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 12px) {
+ .min-\\[12px\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 10rem) {
+ .min-\\[10rem\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 12rem) {
+ .min-\\[12rem\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 10vh) {
+ .min-\\[10vh\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 12vh) {
+ .min-\\[12vh\\]\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('supports', () => {
+ expect(
+ run(['supports-gap:grid', 'supports-[display:grid]:flex', 'supports-[selector(A_>_B)]:flex']),
+ ).toMatchInlineSnapshot(`
+ "@supports (display: grid) {
+ .supports-\\[display\\:grid\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @supports selector(A > B) {
+ .supports-\\[selector\\(A_\\>_B\\)\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @supports (gap: var(--tw)) {
+ .supports-gap\\:grid {
+ display: grid;
+ }
+ }"
+ `)
+})
+
+test('not', () => {
+ expect(
+ run([
+ 'not-[:checked]:flex',
+
+ 'group-not-[:checked]:flex',
+ 'group-not-[:checked]/parent-name:flex',
+ 'group-not-checked:flex',
+
+ 'peer-not-[:checked]:flex',
+ 'peer-not-[:checked]/parent-name:flex',
+ 'peer-not-checked:flex',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".not-\\[\\:checked\\]\\:flex:not(:checked) {
+ display: flex;
+ }
+
+ .group-not-checked\\:flex:is(:where(.group):not(:checked) *) {
+ display: flex;
+ }
+
+ .group-not-\\[\\:checked\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name):not(:checked) *) {
+ display: flex;
+ }
+
+ .group-not-\\[\\:checked\\]\\:flex:is(:where(.group):not(:checked) *) {
+ display: flex;
+ }
+
+ .peer-not-checked\\:flex:is(:where(.peer):not(:checked) ~ *) {
+ display: flex;
+ }
+
+ .peer-not-\\[\\:checked\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name):not(:checked) ~ *) {
+ display: flex;
+ }
+
+ .peer-not-\\[\\:checked\\]\\:flex:is(:where(.peer):not(:checked) ~ *) {
+ display: flex;
+ }"
+ `)
+})
+
+test('has', () => {
+ expect(
+ run([
+ 'has-[:checked]:flex',
+
+ 'group-has-[:checked]:flex',
+ 'group-has-[:checked]/parent-name:flex',
+ 'group-has-checked:flex',
+
+ 'peer-has-[:checked]:flex',
+ 'peer-has-[:checked]/parent-name:flex',
+ 'peer-has-checked:flex',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".group-has-checked\\:flex:is(:where(.group):has(:checked) *) {
+ display: flex;
+ }
+
+ .group-has-\\[\\:checked\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name):has(:checked) *) {
+ display: flex;
+ }
+
+ .group-has-\\[\\:checked\\]\\:flex:is(:where(.group):has(:checked) *) {
+ display: flex;
+ }
+
+ .peer-has-checked\\:flex:is(:where(.peer):has(:checked) ~ *) {
+ display: flex;
+ }
+
+ .peer-has-\\[\\:checked\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name):has(:checked) ~ *) {
+ display: flex;
+ }
+
+ .peer-has-\\[\\:checked\\]\\:flex:is(:where(.peer):has(:checked) ~ *) {
+ display: flex;
+ }
+
+ .has-\\[\\:checked\\]\\:flex:has(:checked) {
+ display: flex;
+ }"
+ `)
+})
+
+test('aria', () => {
+ expect(
+ run([
+ 'aria-checked:flex',
+ 'aria-[invalid=spelling]:flex',
+
+ 'group-aria-[modal]:flex',
+ 'group-aria-checked:flex',
+ 'group-aria-[modal]/parent-name:flex',
+ 'group-aria-checked/parent-name:flex',
+
+ 'peer-aria-[modal]:flex',
+ 'peer-aria-checked:flex',
+ 'peer-aria-[modal]/parent-name:flex',
+ 'peer-aria-checked/parent-name:flex',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".group-aria-\\[modal\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[aria-modal] *) {
+ display: flex;
+ }
+
+ .group-aria-\\[modal\\]\\:flex:is(:where(.group)[aria-modal] *) {
+ display: flex;
+ }
+
+ .group-aria-checked\\/parent-name\\:flex:is(:where(.group\\/parent-name)[aria-checked="true"] *) {
+ display: flex;
+ }
+
+ .group-aria-checked\\:flex:is(:where(.group)[aria-checked="true"] *) {
+ display: flex;
+ }
+
+ .peer-aria-\\[modal\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[aria-modal] ~ *) {
+ display: flex;
+ }
+
+ .peer-aria-\\[modal\\]\\:flex:is(:where(.peer)[aria-modal] ~ *) {
+ display: flex;
+ }
+
+ .peer-aria-checked\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[aria-checked="true"] ~ *) {
+ display: flex;
+ }
+
+ .peer-aria-checked\\:flex:is(:where(.peer)[aria-checked="true"] ~ *) {
+ display: flex;
+ }
+
+ .aria-\\[invalid\\=spelling\\]\\:flex[aria-invalid="spelling"] {
+ display: flex;
+ }
+
+ .aria-checked\\:flex[aria-checked="true"] {
+ display: flex;
+ }"
+ `)
+})
+
+test('data', () => {
+ expect(
+ run([
+ 'data-disabled:flex',
+ 'data-[potato=salad]:flex',
+
+ 'group-data-[disabled]:flex',
+ 'group-data-[disabled]/parent-name:flex',
+
+ 'peer-data-[disabled]:flex',
+ 'peer-data-[disabled]/parent-name:flex',
+ ]),
+ ).toMatchInlineSnapshot(`
+ ".group-data-\\[disabled\\]\\/parent-name\\:flex:is(:where(.group\\/parent-name)[data-disabled] *) {
+ display: flex;
+ }
+
+ .group-data-\\[disabled\\]\\:flex:is(:where(.group)[data-disabled] *) {
+ display: flex;
+ }
+
+ .peer-data-\\[disabled\\]\\/parent-name\\:flex:is(:where(.peer\\/parent-name)[data-disabled] ~ *) {
+ display: flex;
+ }
+
+ .peer-data-\\[disabled\\]\\:flex:is(:where(.peer)[data-disabled] ~ *) {
+ display: flex;
+ }
+
+ .data-\\[potato\\=salad\\]\\:flex[data-potato="salad"] {
+ display: flex;
+ }
+
+ .data-disabled\\:flex[data-disabled] {
+ display: flex;
+ }"
+ `)
+})
+
+test('portrait', () => {
+ expect(run(['portrait:flex'])).toMatchInlineSnapshot(`
+ "@media (orientation: portrait) {
+ .portrait\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('landscape', () => {
+ expect(run(['landscape:flex'])).toMatchInlineSnapshot(`
+ "@media (orientation: landscape) {
+ .landscape\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('contrast-more', () => {
+ expect(run(['contrast-more:flex'])).toMatchInlineSnapshot(`
+ "@media (prefers-contrast: more) {
+ .contrast-more\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('contrast-less', () => {
+ expect(run(['contrast-less:flex'])).toMatchInlineSnapshot(`
+ "@media (prefers-contrast: less) {
+ .contrast-less\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('forced-colors', () => {
+ expect(run(['forced-colors:flex'])).toMatchInlineSnapshot(`
+ "@media (forced-colors: active) {
+ .forced-colors\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('container queries', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --width-lg: 1024px;
+ }
+ @tailwind utilities;
+ `,
+ [
+ '@lg:flex',
+ '@lg/name:flex',
+ '@[123px]:flex',
+ '@[456px]/name:flex',
+
+ '@min-lg:flex',
+ '@min-lg/name:flex',
+ '@min-[123px]:flex',
+ '@min-[456px]/name:flex',
+
+ '@max-lg:flex',
+ '@max-lg/name:flex',
+ '@max-[123px]:flex',
+ '@max-[456px]/name:flex',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --width-lg: 1024px;
+ }
+
+ @container name (width < 1024px) {
+ .\\@max-lg\\/name\\:flex {
+ display: flex;
+ }
+ }
+
+ @container (width < 1024px) {
+ .\\@max-lg\\:flex {
+ display: flex;
+ }
+ }
+
+ @container name (width < 456px) {
+ .\\@max-\\[456px\\]\\/name\\:flex {
+ display: flex;
+ }
+ }
+
+ @container (width < 123px) {
+ .\\@max-\\[123px\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @container (width >= 123px) {
+ .\\@\\[123px\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @container (width >= 123px) {
+ .\\@min-\\[123px\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @container name (width >= 456px) {
+ .\\@\\[456px\\]\\/name\\:flex {
+ display: flex;
+ }
+ }
+
+ @container name (width >= 456px) {
+ .\\@min-\\[456px\\]\\/name\\:flex {
+ display: flex;
+ }
+ }
+
+ @container name (width >= 1024px) {
+ .\\@lg\\/name\\:flex {
+ display: flex;
+ }
+ }
+
+ @container (width >= 1024px) {
+ .\\@lg\\:flex {
+ display: flex;
+ }
+ }
+
+ @container name (width >= 1024px) {
+ .\\@min-lg\\/name\\:flex {
+ display: flex;
+ }
+ }
+
+ @container (width >= 1024px) {
+ .\\@min-lg\\:flex {
+ display: flex;
+ }
+ }"
+ `)
+})
+
+test('variant order', () => {
+ expect(
+ compileCss(
+ css`
+ @theme {
+ --breakpoint-sm: 640px;
+ --breakpoint-md: 768px;
+ --breakpoint-lg: 1024px;
+ --breakpoint-xl: 1280px;
+ --breakpoint-2xl: 1536px;
+ }
+ @tailwind utilities;
+ `,
+ [
+ '[&_p]:flex',
+ '2xl:flex',
+ 'active:flex',
+ 'after:flex',
+ 'aria-[custom=true]:flex',
+ 'aria-busy:flex',
+ 'aria-checked:flex',
+ 'aria-disabled:flex',
+ 'aria-expanded:flex',
+ 'aria-hidden:flex',
+ 'aria-pressed:flex',
+ 'aria-readonly:flex',
+ 'aria-required:flex',
+ 'aria-selected:flex',
+ 'autofill:flex',
+ 'backdrop:flex',
+ 'before:flex',
+ 'checked:flex',
+ 'contrast-less:flex',
+ 'contrast-more:flex',
+ 'dark:flex',
+ 'data-[custom=true]:flex',
+ 'default:flex',
+ 'disabled:flex',
+ 'empty:flex',
+ 'enabled:flex',
+ 'even:flex',
+ 'file:flex',
+ 'first-letter:flex',
+ 'first-line:flex',
+ 'first-of-type:flex',
+ 'first:flex',
+ 'focus-visible:flex',
+ 'focus-within:flex',
+ 'focus:flex',
+ 'forced-colors:flex',
+ 'group-hover:flex',
+ 'has-[:hover]:flex',
+ 'hover:flex',
+ 'in-range:flex',
+ 'indeterminate:flex',
+ 'invalid:flex',
+ 'landscape:flex',
+ 'last-of-type:flex',
+ 'last:flex',
+ 'lg:flex',
+ 'ltr:flex',
+ 'marker:flex',
+ 'md:flex',
+ 'motion-reduce:flex',
+ 'motion-safe:flex',
+ 'odd:flex',
+ 'only-of-type:flex',
+ 'only:flex',
+ 'open:flex',
+ 'optional:flex',
+ 'out-of-range:flex',
+ 'peer-hover:flex',
+ 'placeholder-shown:flex',
+ 'placeholder:flex',
+ 'portrait:flex',
+ 'print:flex',
+ 'read-only:flex',
+ 'required:flex',
+ 'rtl:flex',
+ 'selection:flex',
+ 'sm:flex',
+ 'supports-[display:flex]:flex',
+ 'target:flex',
+ 'valid:flex',
+ 'visited:flex',
+ 'xl:flex',
+ ],
+ ),
+ ).toMatchInlineSnapshot(`
+ ":root {
+ --breakpoint-sm: 640px;
+ --breakpoint-md: 768px;
+ --breakpoint-lg: 1024px;
+ --breakpoint-xl: 1280px;
+ --breakpoint-2xl: 1536px;
+ }
+
+ .group-hover\\:flex:is(:where(.group):hover *) {
+ display: flex;
+ }
+
+ .peer-hover\\:flex:is(:where(.peer):hover ~ *) {
+ display: flex;
+ }
+
+ .first-letter\\:flex:first-letter {
+ display: flex;
+ }
+
+ .first-line\\:flex:first-line {
+ display: flex;
+ }
+
+ .marker\\:flex ::marker, .marker\\:flex::marker {
+ display: flex;
+ }
+
+ .selection\\:flex ::selection, .selection\\:flex::selection {
+ display: flex;
+ }
+
+ .file\\:flex::file-selector-button {
+ display: flex;
+ }
+
+ .placeholder\\:flex::placeholder {
+ display: flex;
+ }
+
+ .backdrop\\:flex::backdrop {
+ display: flex;
+ }
+
+ .before\\:flex:before {
+ content: var(--tw-content);
+ display: flex;
+ }
+
+ .after\\:flex:after {
+ content: var(--tw-content);
+ display: flex;
+ }
+
+ .first\\:flex:first-child {
+ display: flex;
+ }
+
+ .last\\:flex:last-child {
+ display: flex;
+ }
+
+ .only\\:flex:only-child {
+ display: flex;
+ }
+
+ .odd\\:flex:nth-child(odd) {
+ display: flex;
+ }
+
+ .even\\:flex:nth-child(2n) {
+ display: flex;
+ }
+
+ .first-of-type\\:flex:first-of-type {
+ display: flex;
+ }
+
+ .last-of-type\\:flex:last-of-type {
+ display: flex;
+ }
+
+ .only-of-type\\:flex:only-of-type {
+ display: flex;
+ }
+
+ .visited\\:flex:visited {
+ display: flex;
+ }
+
+ .target\\:flex:target {
+ display: flex;
+ }
+
+ .open\\:flex[open] {
+ display: flex;
+ }
+
+ .default\\:flex:default {
+ display: flex;
+ }
+
+ .checked\\:flex:checked {
+ display: flex;
+ }
+
+ .indeterminate\\:flex:indeterminate {
+ display: flex;
+ }
+
+ .placeholder-shown\\:flex:placeholder-shown {
+ display: flex;
+ }
+
+ .autofill\\:flex:autofill {
+ display: flex;
+ }
+
+ .optional\\:flex:optional {
+ display: flex;
+ }
+
+ .required\\:flex:required {
+ display: flex;
+ }
+
+ .valid\\:flex:valid {
+ display: flex;
+ }
+
+ .invalid\\:flex:invalid {
+ display: flex;
+ }
+
+ .in-range\\:flex:in-range {
+ display: flex;
+ }
+
+ .out-of-range\\:flex:out-of-range {
+ display: flex;
+ }
+
+ .read-only\\:flex:read-only {
+ display: flex;
+ }
+
+ .empty\\:flex:empty {
+ display: flex;
+ }
+
+ .focus-within\\:flex:focus-within {
+ display: flex;
+ }
+
+ .hover\\:flex:hover {
+ display: flex;
+ }
+
+ .focus\\:flex:focus {
+ display: flex;
+ }
+
+ .focus-visible\\:flex:focus-visible {
+ display: flex;
+ }
+
+ .active\\:flex:active {
+ display: flex;
+ }
+
+ .enabled\\:flex:enabled {
+ display: flex;
+ }
+
+ .disabled\\:flex:disabled {
+ display: flex;
+ }
+
+ .has-\\[\\:hover\\]\\:flex:has(:hover) {
+ display: flex;
+ }
+
+ .aria-\\[custom\\=true\\]\\:flex[aria-custom="true"] {
+ display: flex;
+ }
+
+ .aria-busy\\:flex[aria-busy="true"] {
+ display: flex;
+ }
+
+ .aria-checked\\:flex[aria-checked="true"] {
+ display: flex;
+ }
+
+ .aria-disabled\\:flex[aria-disabled="true"] {
+ display: flex;
+ }
+
+ .aria-expanded\\:flex[aria-expanded="true"] {
+ display: flex;
+ }
+
+ .aria-hidden\\:flex[aria-hidden="true"] {
+ display: flex;
+ }
+
+ .aria-pressed\\:flex[aria-pressed="true"] {
+ display: flex;
+ }
+
+ .aria-readonly\\:flex[aria-readonly="true"] {
+ display: flex;
+ }
+
+ .aria-required\\:flex[aria-required="true"] {
+ display: flex;
+ }
+
+ .aria-selected\\:flex[aria-selected="true"] {
+ display: flex;
+ }
+
+ .data-\\[custom\\=true\\]\\:flex[data-custom="true"] {
+ display: flex;
+ }
+
+ @supports (display: flex) {
+ .supports-\\[display\\:flex\\]\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (prefers-reduced-motion: no-preference) {
+ .motion-safe\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ .motion-reduce\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (prefers-contrast: more) {
+ .contrast-more\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (prefers-contrast: less) {
+ .contrast-less\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 640px) {
+ .sm\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 768px) {
+ .md\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 1024px) {
+ .lg\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 1280px) {
+ .xl\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (width >= 1536px) {
+ .\\32 xl\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (orientation: portrait) {
+ .portrait\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (orientation: landscape) {
+ .landscape\\:flex {
+ display: flex;
+ }
+ }
+
+ .ltr\\:flex:where([dir="ltr"], [dir="ltr"] *) {
+ display: flex;
+ }
+
+ .rtl\\:flex:where([dir="rtl"], [dir="rtl"] *) {
+ display: flex;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ .dark\\:flex {
+ display: flex;
+ }
+ }
+
+ @media print {
+ .print\\:flex {
+ display: flex;
+ }
+ }
+
+ @media (forced-colors: active) {
+ .forced-colors\\:flex {
+ display: flex;
+ }
+ }
+
+ .\\[\\&_p\\]\\:flex p {
+ display: flex;
+ }
+
+ @property --tw-content {
+ syntax: "*";
+ inherits: false;
+ initial-value: "";
+ }"
+ `)
+})
diff --git a/packages/tailwindcss/src/variants.ts b/packages/tailwindcss/src/variants.ts
new file mode 100644
index 000000000..3e5138c62
--- /dev/null
+++ b/packages/tailwindcss/src/variants.ts
@@ -0,0 +1,703 @@
+import { decl, rule, type Rule } from './ast'
+import { type Variant } from './candidate'
+import type { Theme } from './theme'
+import { DefaultMap } from './utils/default-map'
+
+type VariantFn = (
+ rule: Rule,
+ variant: Extract,
+) => null | void
+
+type CompareFn = (a: Variant, z: Variant) => number
+
+export class Variants {
+ private compareFns = new Map()
+ private variants = new Map<
+ string,
+ {
+ kind: Variant['kind']
+ order: number
+ applyFn: VariantFn
+ compounds: boolean
+ }
+ >()
+
+ private completions = new Map string[]>()
+
+ /**
+ * Registering a group of variants should result in the same sort number for
+ * all the variants. This is to ensure that the variants are applied in the
+ * correct order.
+ */
+ private groupOrder: null | number = null
+
+ /**
+ * Keep track of the last sort order instead of using the size of the map to
+ * avoid unnecessarily skipping order numbers.
+ */
+ private lastOrder = 0
+
+ static(name: string, applyFn: VariantFn<'static'>, { compounds }: { compounds?: boolean } = {}) {
+ this.set(name, { kind: 'static', applyFn, compounds: compounds ?? true })
+ }
+
+ functional(
+ name: string,
+ applyFn: VariantFn<'functional'>,
+ { compounds }: { compounds?: boolean } = {},
+ ) {
+ this.set(name, { kind: 'functional', applyFn, compounds: compounds ?? true })
+ }
+
+ compound(
+ name: string,
+ applyFn: VariantFn<'compound'>,
+ { compounds }: { compounds?: boolean } = {},
+ ) {
+ this.set(name, { kind: 'compound', applyFn, compounds: compounds ?? true })
+ }
+
+ group(fn: () => void, compareFn?: CompareFn) {
+ this.groupOrder = this.nextOrder()
+ if (compareFn) this.compareFns.set(this.groupOrder, compareFn)
+ fn()
+ this.groupOrder = null
+ }
+
+ has(name: string) {
+ return this.variants.has(name)
+ }
+
+ get(name: string) {
+ return this.variants.get(name)
+ }
+
+ kind(name: string) {
+ return this.variants.get(name)?.kind!
+ }
+
+ compounds(name: string) {
+ return this.variants.get(name)?.compounds!
+ }
+
+ suggest(name: string, suggestions: () => string[]) {
+ this.completions.set(name, suggestions)
+ }
+
+ getCompletions(name: string) {
+ return this.completions.get(name)?.() ?? []
+ }
+
+ compare(a: Variant | null, z: Variant | null): number {
+ if (a === z) return 0
+ if (a === null) return -1
+ if (z === null) return 1
+
+ if (a.kind === 'arbitrary' && z.kind === 'arbitrary') {
+ return a.selector.localeCompare(z.selector)
+ } else if (a.kind === 'arbitrary') {
+ return 1
+ } else if (z.kind === 'arbitrary') {
+ return -1
+ }
+
+ let aOrder = this.variants.get(a.root)!.order
+ let zOrder = this.variants.get(z.root)!.order
+
+ let orderedByVariant = aOrder - zOrder
+ if (orderedByVariant !== 0) return orderedByVariant
+
+ if (a.kind === 'compound' && z.kind === 'compound') {
+ return this.compare(a.variant, z.variant)
+ }
+
+ let compareFn = this.compareFns.get(aOrder)
+ if (compareFn === undefined) return 0
+
+ return compareFn(a, z)
+ }
+
+ keys() {
+ return this.variants.keys()
+ }
+
+ entries() {
+ return this.variants.entries()
+ }
+
+ private set(
+ name: string,
+ { kind, applyFn, compounds }: { kind: T; applyFn: VariantFn; compounds: boolean },
+ ) {
+ // In test mode, throw an error if we accidentally override another variant
+ // by mistake when implementing a new variant that shares the same root
+ // without realizing the definitions need to be merged.
+ if (process.env.NODE_ENV === 'test') {
+ if (this.variants.has(name)) {
+ throw new Error(`Duplicate variant prefix [${name}]`)
+ }
+ }
+
+ this.lastOrder = this.nextOrder()
+ this.variants.set(name, {
+ kind,
+ applyFn,
+ order: this.lastOrder,
+ compounds,
+ })
+ }
+
+ private nextOrder() {
+ return this.groupOrder ?? this.lastOrder + 1
+ }
+}
+
+export function createVariants(theme: Theme): Variants {
+ // In the future we may want to support returning a rule here if some complex
+ // variant requires it. For now pure mutation is sufficient and will be the
+ // most performant.
+ let variants = new Variants()
+
+ /**
+ * Register a static variant like `hover`.
+ */
+ function staticVariant(
+ name: string,
+ selectors: string[],
+ { compounds }: { compounds?: boolean } = {},
+ ) {
+ variants.static(
+ name,
+ (r) => {
+ r.nodes = selectors.map((selector) => rule(selector, r.nodes))
+ },
+ { compounds },
+ )
+ }
+
+ variants.static('force', () => {}, { compounds: false })
+ staticVariant('*', ['& > *'], { compounds: false })
+
+ variants.compound('not', (ruleNode) => {
+ ruleNode.selector = `&:not(${ruleNode.selector.replace('&', '*')})`
+ })
+
+ variants.compound('group', (ruleNode, variant) => {
+ // Name the group by appending the modifier to `group` class itself if
+ // present.
+ let groupSelector = variant.modifier
+ ? `:where(.group\\/${variant.modifier.value})`
+ : ':where(.group)'
+
+ // For most variants we rely entirely on CSS nesting to build-up the final
+ // selector, but there is no way to use CSS nesting to make `&` refer to
+ // just the `.group` class the way we'd need to for these variants, so we
+ // need to replace it in the selector ourselves.
+ ruleNode.selector = ruleNode.selector.replace('&', groupSelector)
+
+ // Use `:where` to make sure the specificity of group variants isn't higher
+ // than the specificity of other variants.
+ ruleNode.selector = `&:is(${ruleNode.selector} *)`
+ })
+
+ variants.suggest('group', () => {
+ return Array.from(variants.keys()).filter((name) => {
+ return variants.get(name)?.compounds ?? false
+ })
+ })
+
+ variants.compound('peer', (ruleNode, variant) => {
+ // Name the peer by appending the modifier to `peer` class itself if
+ // present.
+ let peerSelector = variant.modifier
+ ? `:where(.peer\\/${variant.modifier.value})`
+ : ':where(.peer)'
+
+ // For most variants we rely entirely on CSS nesting to build-up the final
+ // selector, but there is no way to use CSS nesting to make `&` refer to
+ // just the `.peer` class the way we'd need to for these variants, so we
+ // need to replace it in the selector ourselves.
+ ruleNode.selector = ruleNode.selector.replace('&', peerSelector)
+
+ // Use `:where` to make sure the specificity of peer variants isn't higher
+ // than the specificity of other variants.
+ ruleNode.selector = `&:is(${ruleNode.selector} ~ *)`
+ })
+
+ variants.suggest('peer', () => {
+ return Array.from(variants.keys()).filter((name) => {
+ return variants.get(name)?.compounds ?? false
+ })
+ })
+
+ staticVariant('first-letter', ['&::first-letter'], { compounds: false })
+ staticVariant('first-line', ['&::first-line'], { compounds: false })
+
+ // TODO: Remove alpha vars or no?
+ staticVariant('marker', ['& *::marker', '&::marker'], { compounds: false })
+
+ staticVariant('selection', ['& *::selection', '&::selection'], { compounds: false })
+ staticVariant('file', ['&::file-selector-button'], { compounds: false })
+ staticVariant('placeholder', ['&::placeholder'], { compounds: false })
+ staticVariant('backdrop', ['&::backdrop'], { compounds: false })
+
+ {
+ function contentProperties() {
+ return rule('@at-root', [
+ rule('@property --tw-content', [
+ decl('syntax', '"*"'),
+ decl('initial-value', '""'),
+ decl('inherits', 'false'),
+ ]),
+ ])
+ }
+ variants.static(
+ 'before',
+ (v) => {
+ v.nodes = [
+ rule('&::before', [
+ contentProperties(),
+ decl('content', 'var(--tw-content)'),
+ ...v.nodes,
+ ]),
+ ]
+ },
+ { compounds: false },
+ )
+
+ variants.static(
+ 'after',
+ (v) => {
+ v.nodes = [
+ rule('&::after', [contentProperties(), decl('content', 'var(--tw-content)'), ...v.nodes]),
+ ]
+ },
+ { compounds: false },
+ )
+ }
+
+ let pseudos: [name: string, selector: string][] = [
+ // Positional
+ ['first', '&:first-child'],
+ ['last', '&:last-child'],
+ ['only', '&:only-child'],
+ ['odd', '&:nth-child(odd)'],
+ ['even', '&:nth-child(even)'],
+ ['first-of-type', '&:first-of-type'],
+ ['last-of-type', '&:last-of-type'],
+ ['only-of-type', '&:only-of-type'],
+
+ // State
+ // TODO: Remove alpha vars or no?
+ ['visited', '&:visited'],
+
+ ['target', '&:target'],
+ ['open', '&[open]'],
+
+ // Forms
+ ['default', '&:default'],
+ ['checked', '&:checked'],
+ ['indeterminate', '&:indeterminate'],
+ ['placeholder-shown', '&:placeholder-shown'],
+ ['autofill', '&:autofill'],
+ ['optional', '&:optional'],
+ ['required', '&:required'],
+ ['valid', '&:valid'],
+ ['invalid', '&:invalid'],
+ ['in-range', '&:in-range'],
+ ['out-of-range', '&:out-of-range'],
+ ['read-only', '&:read-only'],
+
+ // Content
+ ['empty', '&:empty'],
+
+ // Interactive
+ ['focus-within', '&:focus-within'],
+ [
+ 'hover',
+ '&:hover',
+ // TODO: Update tests for this:
+ // v => {
+ // v.nodes = [
+ // rule('@media (hover: hover) and (pointer: fine)', [
+ // rule('&:hover', v.nodes),
+ // ]),
+ // ]
+ // }
+ ],
+ ['focus', '&:focus'],
+ ['focus-visible', '&:focus-visible'],
+ ['active', '&:active'],
+ ['enabled', '&:enabled'],
+ ['disabled', '&:disabled'],
+ ]
+
+ for (let [key, value] of pseudos) {
+ staticVariant(key, [value])
+ }
+
+ variants.compound('has', (ruleNode) => {
+ ruleNode.selector = `&:has(${ruleNode.selector.replace('&', '*')})`
+ })
+
+ variants.suggest('has', () => {
+ return Array.from(variants.keys()).filter((name) => {
+ return variants.get(name)?.compounds ?? false
+ })
+ })
+
+ variants.functional('aria', (ruleNode, variant) => {
+ if (variant.value === null) return null
+ if (variant.value.kind === 'arbitrary') {
+ ruleNode.nodes = [rule(`&[aria-${variant.value.value}]`, ruleNode.nodes)]
+ } else {
+ ruleNode.nodes = [rule(`&[aria-${variant.value.value}="true"]`, ruleNode.nodes)]
+ }
+ })
+
+ variants.suggest('aria', () => [
+ 'busy',
+ 'checked',
+ 'disabled',
+ 'expanded',
+ 'hidden',
+ 'pressed',
+ 'readonly',
+ 'required',
+ 'selected',
+ ])
+
+ variants.functional('data', (ruleNode, variant) => {
+ if (variant.value === null) return null
+
+ ruleNode.nodes = [rule(`&[data-${variant.value.value}]`, ruleNode.nodes)]
+ })
+
+ variants.functional(
+ 'supports',
+ (ruleNode, variant) => {
+ if (variant.value === null) return null
+
+ let value = variant.value.value
+ if (value === null) return null
+
+ // When `supports-[...]:flex` is used, with `not()`, `and()` or
+ // `selector()`, then we know that want to use this directly as the
+ // supports condition as-is.
+ if (/^\w*\s*\(/.test(value)) {
+ // Chrome has a bug where `(condition1)or(condition2)` is not valid, but
+ // `(condition1) or (condition2)` is supported.
+ let query = value.replace(/\b(and|or|not)\b/g, ' $1 ')
+
+ ruleNode.nodes = [rule(`@supports ${query}`, ruleNode.nodes)]
+ return
+ }
+
+ // When `supports-[display]` is used as a shorthand, we need to make sure
+ // that this becomes a valid CSS supports condition.
+ //
+ // E.g.: `supports-[display]` -> `@supports (display: var(--tw))`
+ if (!value.includes(':')) {
+ value = `${value}: var(--tw)`
+ }
+
+ // When `supports-[display:flex]` is used, we need to make sure that this
+ // becomes a valid CSS supports condition by wrapping it in parens.
+ //
+ // E.g.: `supports-[display:flex]` -> `@supports (display: flex)`
+ //
+ // We also have to make sure that we wrap the value in parens if the last
+ // character is a paren already for situations where we are testing
+ // against a CSS custom property.
+ //
+ // E.g.: `supports-[display]:flex` -> `@supports (display: var(--tw))`
+ if (value[0] !== '(' || value[value.length - 1] !== ')') {
+ value = `(${value})`
+ }
+
+ ruleNode.nodes = [rule(`@supports ${value}`, ruleNode.nodes)]
+ },
+ { compounds: false },
+ )
+
+ staticVariant('motion-safe', ['@media (prefers-reduced-motion: no-preference)'], {
+ compounds: false,
+ })
+ staticVariant('motion-reduce', ['@media (prefers-reduced-motion: reduce)'], { compounds: false })
+
+ staticVariant('contrast-more', ['@media (prefers-contrast: more)'], { compounds: false })
+ staticVariant('contrast-less', ['@media (prefers-contrast: less)'], { compounds: false })
+
+ {
+ // Helper to compare variants by their resolved values, this is used by the
+ // responsive variants (`sm`, `md`, ...), `min-*`, `max-*` and container
+ // queries (`@`).
+ function compareBreakpoints(
+ a: Variant,
+ z: Variant,
+ direction: 'asc' | 'desc',
+ lookup: { get(v: Variant): string | null },
+ ) {
+ if (a === z) return 0
+ let aValue = lookup.get(a)
+ if (aValue === null) return direction === 'asc' ? -1 : 1
+
+ let zValue = lookup.get(z)
+ if (zValue === null) return direction === 'asc' ? 1 : -1
+
+ if (aValue === zValue) return 0
+
+ // Assumption: when a `(` exists, we are dealing with a CSS function.
+ //
+ // E.g.: `calc(100% - 1rem)`
+ let aIsCssFunction = aValue.indexOf('(')
+ let zIsCssFunction = zValue.indexOf('(')
+
+ let aBucket =
+ aIsCssFunction === -1
+ ? // No CSS function found, bucket by unit instead
+ aValue.replace(/[\d.]+/g, '')
+ : // CSS function found, bucket by function name
+ aValue.slice(0, aIsCssFunction)
+
+ let zBucket =
+ zIsCssFunction === -1
+ ? // No CSS function found, bucket by unit
+ zValue.replace(/[\d.]+/g, '')
+ : // CSS function found, bucket by function name
+ zValue.slice(0, zIsCssFunction)
+
+ let order =
+ // Compare by bucket name
+ aBucket.localeCompare(zBucket) ||
+ // If bucket names are the same, compare by value
+ (direction === 'asc'
+ ? parseInt(aValue) - parseInt(zValue)
+ : parseInt(zValue) - parseInt(aValue))
+
+ // If the groups are the same, and the contents are not numbers, the
+ // `order` will result in `NaN`. In this case, we want to make sorting
+ // stable by falling back to a string comparison.
+ //
+ // This can happen when using CSS functions such as `calc`.
+ //
+ // E.g.:
+ //
+ // - `min-[calc(100%-1rem)]` and `min-[calc(100%-2rem)]`
+ // - `@[calc(100%-1rem)]` and `@[calc(100%-2rem)]`
+ //
+ // In this scenario, we want to alphabetically sort `calc(100%-1rem)` and
+ // `calc(100%-2rem)` to make it deterministic.
+ if (Number.isNaN(order)) {
+ return aValue.localeCompare(zValue)
+ }
+
+ return order
+ }
+
+ // Breakpoints
+ {
+ let breakpoints = theme.namespace('--breakpoint')
+ let resolvedBreakpoints = new DefaultMap((variant: Variant) => {
+ switch (variant.kind) {
+ case 'static': {
+ return breakpoints.get(variant.root) ?? null
+ }
+
+ case 'functional': {
+ if (variant.value === null) return null
+
+ let value: string | null = null
+
+ if (variant.value.kind === 'arbitrary') {
+ value = variant.value.value
+ } else if (variant.value.kind === 'named') {
+ value = theme.resolve(variant.value.value, ['--breakpoint'])
+ }
+
+ if (!value) return null
+ if (value.includes('var(')) return null
+
+ return value
+ }
+ case 'arbitrary':
+ case 'compound':
+ return null
+ }
+ })
+
+ // Max
+ variants.group(
+ () => {
+ variants.functional(
+ 'max',
+ (ruleNode, variant) => {
+ let value = resolvedBreakpoints.get(variant)
+ if (value === null) return null
+
+ ruleNode.nodes = [rule(`@media (width < ${value})`, ruleNode.nodes)]
+ },
+ { compounds: false },
+ )
+ },
+ (a, z) => compareBreakpoints(a, z, 'desc', resolvedBreakpoints),
+ )
+
+ variants.suggest(
+ 'max',
+ () => Array.from(breakpoints.keys()).filter((key) => key !== null) as string[],
+ )
+
+ // Min
+ variants.group(
+ () => {
+ // Registers breakpoint variants like `sm`, `md`, `lg`, etc.
+ for (let [key, value] of theme.namespace('--breakpoint')) {
+ if (key === null) continue
+ variants.static(
+ key,
+ (ruleNode) => {
+ ruleNode.nodes = [rule(`@media (width >= ${value})`, ruleNode.nodes)]
+ },
+ { compounds: false },
+ )
+ }
+
+ variants.functional(
+ 'min',
+ (ruleNode, variant) => {
+ let value = resolvedBreakpoints.get(variant)
+ if (value === null) return null
+
+ ruleNode.nodes = [rule(`@media (width >= ${value})`, ruleNode.nodes)]
+ },
+ { compounds: false },
+ )
+ },
+ (a, z) => compareBreakpoints(a, z, 'asc', resolvedBreakpoints),
+ )
+
+ variants.suggest(
+ 'min',
+ () => Array.from(breakpoints.keys()).filter((key) => key !== null) as string[],
+ )
+ }
+
+ {
+ let widths = theme.namespace('--width')
+
+ // Container queries
+ let resolvedWidths = new DefaultMap((variant: Variant) => {
+ switch (variant.kind) {
+ case 'functional': {
+ if (variant.value === null) return null
+
+ let value: string | null = null
+
+ if (variant.value.kind === 'arbitrary') {
+ value = variant.value.value
+ } else if (variant.value.kind === 'named') {
+ value = theme.resolve(variant.value.value, ['--width'])
+ }
+
+ if (!value) return null
+ if (value.includes('var(')) return null
+
+ return value
+ }
+ case 'static':
+ case 'arbitrary':
+ case 'compound':
+ return null
+ }
+ })
+
+ variants.group(
+ () => {
+ variants.functional(
+ '@max',
+ (ruleNode, variant) => {
+ let value = resolvedWidths.get(variant)
+ if (value === null) return null
+
+ ruleNode.nodes = [
+ rule(
+ variant.modifier
+ ? `@container ${variant.modifier.value} (width < ${value})`
+ : `@container (width < ${value})`,
+ ruleNode.nodes,
+ ),
+ ]
+ },
+ { compounds: false },
+ )
+ },
+ (a, z) => compareBreakpoints(a, z, 'desc', resolvedWidths),
+ )
+
+ variants.suggest(
+ '@max',
+ () => Array.from(widths.keys()).filter((key) => key !== null) as string[],
+ )
+
+ variants.group(
+ () => {
+ variants.functional(
+ '@',
+ (ruleNode, variant) => {
+ let value = resolvedWidths.get(variant)
+ if (value === null) return null
+
+ ruleNode.nodes = [
+ rule(
+ variant.modifier
+ ? `@container ${variant.modifier.value} (width >= ${value})`
+ : `@container (width >= ${value})`,
+ ruleNode.nodes,
+ ),
+ ]
+ },
+ { compounds: false },
+ )
+ variants.functional(
+ '@min',
+ (ruleNode, variant) => {
+ let value = resolvedWidths.get(variant)
+ if (value === null) return null
+
+ ruleNode.nodes = [
+ rule(
+ variant.modifier
+ ? `@container ${variant.modifier.value} (width >= ${value})`
+ : `@container (width >= ${value})`,
+ ruleNode.nodes,
+ ),
+ ]
+ },
+ { compounds: false },
+ )
+ },
+ (a, z) => compareBreakpoints(a, z, 'asc', resolvedWidths),
+ )
+
+ variants.suggest(
+ '@min',
+ () => Array.from(widths.keys()).filter((key) => key !== null) as string[],
+ )
+ }
+ }
+
+ staticVariant('portrait', ['@media (orientation: portrait)'], { compounds: false })
+ staticVariant('landscape', ['@media (orientation: landscape)'], { compounds: false })
+
+ staticVariant('ltr', ['&:where([dir="ltr"], [dir="ltr"] *)'])
+ staticVariant('rtl', ['&:where([dir="rtl"], [dir="rtl"] *)'])
+
+ staticVariant('dark', ['@media (prefers-color-scheme: dark)'], { compounds: false })
+
+ staticVariant('print', ['@media print'], { compounds: false })
+
+ staticVariant('forced-colors', ['@media (forced-colors: active)'], { compounds: false })
+
+ return variants
+}
diff --git a/packages/tailwindcss/tests/ui.spec.ts b/packages/tailwindcss/tests/ui.spec.ts
new file mode 100644
index 000000000..45831436c
--- /dev/null
+++ b/packages/tailwindcss/tests/ui.spec.ts
@@ -0,0 +1,245 @@
+import { expect, test, type Page } from '@playwright/test'
+import { IO, Parsing, scanFiles } from '@tailwindcss/oxide'
+import fs from 'fs'
+import path from 'path'
+import { compile, optimizeCss } from '../src'
+
+const html = String.raw
+const css = String.raw
+
+test('touch action', async ({ page }) => {
+ let { getPropertyValue } = await render(
+ page,
+ html`Hello world
`,
+ )
+
+ expect(await getPropertyValue('#x', 'touch-action')).toEqual('pan-x pan-y')
+
+ await page.locator('#x').hover()
+
+ expect([
+ // `manipulation` is an alias for `pan-x pan-y pinch-zoom` and some engines
+ // compute the combination of those three values to `manipulation` even when
+ // explicitly set as three values.
+ 'manipulation',
+ 'pan-x pan-y pinch-zoom',
+ ]).toContain(await getPropertyValue('#x', 'touch-action'))
+})
+
+for (let [classes, expected] of [
+ ['from-red-500', 'linear-gradient(to right, rgb(239, 68, 68) 0%, rgba(0, 0, 0, 0) 100%)'],
+ [
+ 'via-red-500',
+ 'linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(239, 68, 68) 50%, rgba(0, 0, 0, 0) 100%)',
+ ],
+ ['to-red-500', 'linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(239, 68, 68) 100%)'],
+ [
+ 'from-red-500 to-blue-500',
+ 'linear-gradient(to right, rgb(239, 68, 68) 0%, rgb(59, 130, 246) 100%)',
+ ],
+ [
+ 'via-red-500 to-blue-500',
+ 'linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(239, 68, 68) 50%, rgb(59, 130, 246) 100%)',
+ ],
+ [
+ 'from-red-500 via-green-500 to-blue-500',
+ 'linear-gradient(to right, rgb(239, 68, 68) 0%, rgb(34, 197, 94) 50%, rgb(59, 130, 246) 100%)',
+ ],
+]) {
+ test(`background gradient, "${classes}"`, async ({ page }) => {
+ let { getPropertyValue } = await render(
+ page,
+ html`Hello world
`,
+ )
+
+ expect(await getPropertyValue('#x', 'background-image')).toEqual(expected)
+ })
+}
+
+test('background gradient, going from 2 to 3', async ({ page }) => {
+ let { getPropertyValue } = await render(
+ page,
+ html`
+
+ Hello world
+
+ `,
+ )
+
+ expect(await getPropertyValue('#x', 'background-image')).toEqual(
+ 'linear-gradient(to right, rgb(239, 68, 68) 0%, rgb(59, 130, 246) 100%)',
+ )
+
+ await page.locator('#x').hover()
+
+ expect(await getPropertyValue('#x', 'background-image')).toEqual(
+ 'linear-gradient(to right, rgb(239, 68, 68) 0%, rgb(34, 197, 94) 50%, rgb(59, 130, 246) 100%)',
+ )
+})
+
+test('background gradient, going from 3 to 2', async ({ page }) => {
+ let { getPropertyValue } = await render(
+ page,
+ html`
+
+ Hello world
+
+ `,
+ )
+
+ expect(await getPropertyValue('#x', 'background-image')).toEqual(
+ 'linear-gradient(to right, rgb(239, 68, 68) 0%, rgb(34, 197, 94) 50%, rgb(59, 130, 246) 100%)',
+ )
+
+ await page.locator('#x').hover()
+
+ expect(await getPropertyValue('#x', 'background-image')).toEqual(
+ 'linear-gradient(to right, rgb(239, 68, 68) 0%, rgb(59, 130, 246) 100%)',
+ )
+})
+
+test("::backdrop can receive a border with just the 'border' utility", async ({ page }) => {
+ let { getPropertyValue } = await render(
+ page,
+ html``,
+ )
+
+ await page.evaluate(() => {
+ ;(document.getElementById('x') as HTMLDialogElement)!.showModal()
+ })
+
+ expect(await getPropertyValue(['#x', '::backdrop'], 'border')).toEqual('1px solid rgb(0, 0, 0)')
+})
+
+test("::first-letter can receive a border with just the 'border' utility", async ({ page }) => {
+ let { getPropertyValue } = await render(
+ page,
+ html`Hello world
`,
+ )
+
+ expect(await getPropertyValue(['#x', '::first-letter'], 'border')).toEqual(
+ '1px solid rgb(0, 0, 0)',
+ )
+})
+
+// `getComputedStyle` doesn't work in Chrome/Safari on `::file-selector-button`
+// for some reason at the moment, so we can't actually verify the computed
+// style. It does work in Firefox, so maybe we can update this one test to run
+// against Firefox and let the rest run against Chrome/Safari.
+test.skip("::file-selector-button can receive a border with just the 'border' utility", async ({
+ page,
+}) => {
+ let { getPropertyValue } = await render(
+ page,
+ html``,
+ )
+
+ expect(await getPropertyValue(['#x', '::file-selector-button'], 'border')).toEqual(
+ '1px solid rgb(0, 0, 0)',
+ )
+})
+
+test('composing shadow, inset shadow, ring, and inset ring', async ({ page }) => {
+ let { getPropertyValue } = await render(
+ page,
+ html``,
+ )
+
+ expect(await getPropertyValue('#x', 'box-shadow')).toEqual(
+ [
+ 'rgba(0, 255, 0, 0.5) 0px 2px 4px 0px inset', // inset-shadow
+ 'rgba(0, 0, 255, 0.5) 0px 0px 0px 1px inset', // inset-ring
+ 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', // ring-offset (disabled)
+ 'rgba(255, 255, 255, 0.5) 0px 0px 0px 1px', // ring
+ 'rgba(255, 0, 0, 0.5) 0px 1px 3px 0px, rgba(255, 0, 0, 0.5) 0px 1px 2px -1px', // shadow
+ ].join(', '),
+ )
+})
+
+test('outline style is optional', async ({ page }) => {
+ let { getPropertyValue } = await render(
+ page,
+ html``,
+ )
+
+ expect(await getPropertyValue('#x', 'outline')).toEqual('rgb(255, 255, 255) solid 2px')
+})
+
+test('outline style is preserved when changing outline width', async ({ page }) => {
+ let { getPropertyValue } = await render(
+ page,
+ html`
+ Hello world
+
`,
+ )
+
+ expect(await getPropertyValue('#x', 'outline')).toEqual('rgb(255, 255, 255) dotted 2px')
+
+ await page.locator('#x').hover()
+
+ expect(await getPropertyValue('#x', 'outline')).toEqual('rgb(255, 255, 255) dotted 4px')
+})
+
+test('borders can be added without a border-style utility', async ({ page }) => {
+ let { getPropertyValue } = await render(
+ page,
+ html``,
+ )
+
+ expect(await getPropertyValue('#x', 'border')).toEqual('2px solid rgb(0, 0, 0)')
+})
+
+// ---
+
+const preflight = fs.readFileSync(path.resolve(__dirname, '..', 'preflight.css'), 'utf-8')
+const defaultTheme = fs.readFileSync(path.resolve(__dirname, '..', 'theme.css'), 'utf-8')
+async function render(page: Page, content: string) {
+ await page.setContent(content)
+ await page.addStyleTag({
+ content: optimizeCss(
+ compile(
+ css`
+ @layer theme, base, components, utilities;
+ @layer theme {
+ ${defaultTheme}
+ }
+ @layer base {
+ ${preflight}
+ }
+ @layer utilities {
+ @tailwind utilities;
+ }
+ `,
+ scanFiles([{ content, extension: 'html' }], IO.Sequential | Parsing.Sequential),
+ ),
+ ),
+ })
+
+ return {
+ getPropertyValue(selector: string | [string, string], property: string) {
+ return getPropertyValue(
+ page,
+ Array.isArray(selector) ? selector : [selector, undefined],
+ property,
+ )
+ },
+ }
+}
+
+async function getPropertyValue(
+ page: Page,
+ selector: [string, string | undefined],
+ property: string,
+) {
+ return page.evaluate(
+ ([[selector, pseudo], property]) => {
+ return window
+ .getComputedStyle(document.querySelector(selector)!, pseudo)
+ .getPropertyValue(property)
+ },
+ [selector, property] as const,
+ )
+}
diff --git a/packages/tailwindcss/theme.css b/packages/tailwindcss/theme.css
new file mode 100644
index 000000000..d751f895b
--- /dev/null
+++ b/packages/tailwindcss/theme.css
@@ -0,0 +1,465 @@
+@theme {
+ /* Defaults */
+ --default-transition-duration: 150ms;
+ --default-transition-timing-function: var(--transition-timing-function-in-out);
+ --default-font-family: var(--font-family-sans);
+ --default-font-feature-settings: var(--font-family-sans--font-feature-settings);
+ --default-font-variation-settings: var(--font-family-sans--font-variation-settings);
+ --default-mono-font-family: var(--font-family-mono);
+ --default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings);
+ --default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings);
+
+ /* Breakpoints */
+ --breakpoint-sm: 640px;
+ --breakpoint-md: 768px;
+ --breakpoint-lg: 1024px;
+ --breakpoint-xl: 1280px;
+ --breakpoint-2xl: 1536px;
+
+ /* Colors */
+ --color-black: #000;
+ --color-white: #fff;
+ --color-slate-50: #f8fafc;
+ --color-slate-100: #f1f5f9;
+ --color-slate-200: #e2e8f0;
+ --color-slate-300: #cbd5e1;
+ --color-slate-400: #94a3b8;
+ --color-slate-500: #64748b;
+ --color-slate-600: #475569;
+ --color-slate-700: #334155;
+ --color-slate-800: #1e293b;
+ --color-slate-900: #0f172a;
+ --color-slate-950: #020617;
+ --color-gray-50: #f9fafb;
+ --color-gray-100: #f3f4f6;
+ --color-gray-200: #e5e7eb;
+ --color-gray-300: #d1d5db;
+ --color-gray-400: #9ca3af;
+ --color-gray-500: #6b7280;
+ --color-gray-600: #4b5563;
+ --color-gray-700: #374151;
+ --color-gray-800: #1f2937;
+ --color-gray-900: #111827;
+ --color-gray-950: #030712;
+ --color-zinc-50: #fafafa;
+ --color-zinc-100: #f4f4f5;
+ --color-zinc-200: #e4e4e7;
+ --color-zinc-300: #d4d4d8;
+ --color-zinc-400: #a1a1aa;
+ --color-zinc-500: #71717a;
+ --color-zinc-600: #52525b;
+ --color-zinc-700: #3f3f46;
+ --color-zinc-800: #27272a;
+ --color-zinc-900: #18181b;
+ --color-zinc-950: #09090b;
+ --color-neutral-50: #fafafa;
+ --color-neutral-100: #f5f5f5;
+ --color-neutral-200: #e5e5e5;
+ --color-neutral-300: #d4d4d4;
+ --color-neutral-400: #a3a3a3;
+ --color-neutral-500: #737373;
+ --color-neutral-600: #525252;
+ --color-neutral-700: #404040;
+ --color-neutral-800: #262626;
+ --color-neutral-900: #171717;
+ --color-neutral-950: #0a0a0a;
+ --color-stone-50: #fafaf9;
+ --color-stone-100: #f5f5f4;
+ --color-stone-200: #e7e5e4;
+ --color-stone-300: #d6d3d1;
+ --color-stone-400: #a8a29e;
+ --color-stone-500: #78716c;
+ --color-stone-600: #57534e;
+ --color-stone-700: #44403c;
+ --color-stone-800: #292524;
+ --color-stone-900: #1c1917;
+ --color-stone-950: #0c0a09;
+ --color-red-50: #fef2f2;
+ --color-red-100: #fee2e2;
+ --color-red-200: #fecaca;
+ --color-red-300: #fca5a5;
+ --color-red-400: #f87171;
+ --color-red-500: #ef4444;
+ --color-red-600: #dc2626;
+ --color-red-700: #b91c1c;
+ --color-red-800: #991b1b;
+ --color-red-900: #7f1d1d;
+ --color-red-950: #450a0a;
+ --color-orange-50: #fff7ed;
+ --color-orange-100: #ffedd5;
+ --color-orange-200: #fed7aa;
+ --color-orange-300: #fdba74;
+ --color-orange-400: #fb923c;
+ --color-orange-500: #f97316;
+ --color-orange-600: #ea580c;
+ --color-orange-700: #c2410c;
+ --color-orange-800: #9a3412;
+ --color-orange-900: #7c2d12;
+ --color-orange-950: #431407;
+ --color-amber-50: #fffbeb;
+ --color-amber-100: #fef3c7;
+ --color-amber-200: #fde68a;
+ --color-amber-300: #fcd34d;
+ --color-amber-400: #fbbf24;
+ --color-amber-500: #f59e0b;
+ --color-amber-600: #d97706;
+ --color-amber-700: #b45309;
+ --color-amber-800: #92400e;
+ --color-amber-900: #78350f;
+ --color-amber-950: #451a03;
+ --color-yellow-50: #fefce8;
+ --color-yellow-100: #fef9c3;
+ --color-yellow-200: #fef08a;
+ --color-yellow-300: #fde047;
+ --color-yellow-400: #facc15;
+ --color-yellow-500: #eab308;
+ --color-yellow-600: #ca8a04;
+ --color-yellow-700: #a16207;
+ --color-yellow-800: #854d0e;
+ --color-yellow-900: #713f12;
+ --color-yellow-950: #422006;
+ --color-lime-50: #f7fee7;
+ --color-lime-100: #ecfccb;
+ --color-lime-200: #d9f99d;
+ --color-lime-300: #bef264;
+ --color-lime-400: #a3e635;
+ --color-lime-500: #84cc16;
+ --color-lime-600: #65a30d;
+ --color-lime-700: #4d7c0f;
+ --color-lime-800: #3f6212;
+ --color-lime-900: #365314;
+ --color-lime-950: #1a2e05;
+ --color-green-50: #f0fdf4;
+ --color-green-100: #dcfce7;
+ --color-green-200: #bbf7d0;
+ --color-green-300: #86efac;
+ --color-green-400: #4ade80;
+ --color-green-500: #22c55e;
+ --color-green-600: #16a34a;
+ --color-green-700: #15803d;
+ --color-green-800: #166534;
+ --color-green-900: #14532d;
+ --color-green-950: #052e16;
+ --color-emerald-50: #ecfdf5;
+ --color-emerald-100: #d1fae5;
+ --color-emerald-200: #a7f3d0;
+ --color-emerald-300: #6ee7b7;
+ --color-emerald-400: #34d399;
+ --color-emerald-500: #10b981;
+ --color-emerald-600: #059669;
+ --color-emerald-700: #047857;
+ --color-emerald-800: #065f46;
+ --color-emerald-900: #064e3b;
+ --color-emerald-950: #022c22;
+ --color-teal-50: #f0fdfa;
+ --color-teal-100: #ccfbf1;
+ --color-teal-200: #99f6e4;
+ --color-teal-300: #5eead4;
+ --color-teal-400: #2dd4bf;
+ --color-teal-500: #14b8a6;
+ --color-teal-600: #0d9488;
+ --color-teal-700: #0f766e;
+ --color-teal-800: #115e59;
+ --color-teal-900: #134e4a;
+ --color-teal-950: #042f2e;
+ --color-cyan-50: #ecfeff;
+ --color-cyan-100: #cffafe;
+ --color-cyan-200: #a5f3fc;
+ --color-cyan-300: #67e8f9;
+ --color-cyan-400: #22d3ee;
+ --color-cyan-500: #06b6d4;
+ --color-cyan-600: #0891b2;
+ --color-cyan-700: #0e7490;
+ --color-cyan-800: #155e75;
+ --color-cyan-900: #164e63;
+ --color-cyan-950: #083344;
+ --color-sky-50: #f0f9ff;
+ --color-sky-100: #e0f2fe;
+ --color-sky-200: #bae6fd;
+ --color-sky-300: #7dd3fc;
+ --color-sky-400: #38bdf8;
+ --color-sky-500: #0ea5e9;
+ --color-sky-600: #0284c7;
+ --color-sky-700: #0369a1;
+ --color-sky-800: #075985;
+ --color-sky-900: #0c4a6e;
+ --color-sky-950: #082f49;
+ --color-blue-50: #eff6ff;
+ --color-blue-100: #dbeafe;
+ --color-blue-200: #bfdbfe;
+ --color-blue-300: #93c5fd;
+ --color-blue-400: #60a5fa;
+ --color-blue-500: #3b82f6;
+ --color-blue-600: #2563eb;
+ --color-blue-700: #1d4ed8;
+ --color-blue-800: #1e40af;
+ --color-blue-900: #1e3a8a;
+ --color-blue-950: #172554;
+ --color-indigo-50: #eef2ff;
+ --color-indigo-100: #e0e7ff;
+ --color-indigo-200: #c7d2fe;
+ --color-indigo-300: #a5b4fc;
+ --color-indigo-400: #818cf8;
+ --color-indigo-500: #6366f1;
+ --color-indigo-600: #4f46e5;
+ --color-indigo-700: #4338ca;
+ --color-indigo-800: #3730a3;
+ --color-indigo-900: #312e81;
+ --color-indigo-950: #1e1b4b;
+ --color-violet-50: #f5f3ff;
+ --color-violet-100: #ede9fe;
+ --color-violet-200: #ddd6fe;
+ --color-violet-300: #c4b5fd;
+ --color-violet-400: #a78bfa;
+ --color-violet-500: #8b5cf6;
+ --color-violet-600: #7c3aed;
+ --color-violet-700: #6d28d9;
+ --color-violet-800: #5b21b6;
+ --color-violet-900: #4c1d95;
+ --color-violet-950: #2e1065;
+ --color-purple-50: #faf5ff;
+ --color-purple-100: #f3e8ff;
+ --color-purple-200: #e9d5ff;
+ --color-purple-300: #d8b4fe;
+ --color-purple-400: #c084fc;
+ --color-purple-500: #a855f7;
+ --color-purple-600: #9333ea;
+ --color-purple-700: #7e22ce;
+ --color-purple-800: #6b21a8;
+ --color-purple-900: #581c87;
+ --color-purple-950: #3b0764;
+ --color-fuchsia-50: #fdf4ff;
+ --color-fuchsia-100: #fae8ff;
+ --color-fuchsia-200: #f5d0fe;
+ --color-fuchsia-300: #f0abfc;
+ --color-fuchsia-400: #e879f9;
+ --color-fuchsia-500: #d946ef;
+ --color-fuchsia-600: #c026d3;
+ --color-fuchsia-700: #a21caf;
+ --color-fuchsia-800: #86198f;
+ --color-fuchsia-900: #701a75;
+ --color-fuchsia-950: #4a044e;
+ --color-pink-50: #fdf2f8;
+ --color-pink-100: #fce7f3;
+ --color-pink-200: #fbcfe8;
+ --color-pink-300: #f9a8d4;
+ --color-pink-400: #f472b6;
+ --color-pink-500: #ec4899;
+ --color-pink-600: #db2777;
+ --color-pink-700: #be185d;
+ --color-pink-800: #9d174d;
+ --color-pink-900: #831843;
+ --color-pink-950: #500724;
+ --color-rose-50: #fff1f2;
+ --color-rose-100: #ffe4e6;
+ --color-rose-200: #fecdd3;
+ --color-rose-300: #fda4af;
+ --color-rose-400: #fb7185;
+ --color-rose-500: #f43f5e;
+ --color-rose-600: #e11d48;
+ --color-rose-700: #be123c;
+ --color-rose-800: #9f1239;
+ --color-rose-900: #881337;
+ --color-rose-950: #4c0519;
+
+ /* Animations */
+ --animate-spin: spin 1s linear infinite;
+ --animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+ --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+ --animate-bounce: bounce 1s infinite;
+
+ /* Blurs */
+ --blur: 8px;
+ --blur-sm: 4px;
+ --blur-md: 12px;
+ --blur-lg: 16px;
+ --blur-xl: 24px;
+ --blur-2xl: 40px;
+ --blur-3xl: 64px;
+
+ /* Radii */
+ --radius-none: 0px;
+ --radius-full: 9999px;
+ --radius-sm: 0.125rem;
+ --radius: 0.25rem;
+ --radius-md: 0.375rem;
+ --radius-lg: 0.5rem;
+ --radius-xl: 0.75rem;
+ --radius-2xl: 1rem;
+ --radius-3xl: 1.5rem;
+
+ /* Shadows */
+ --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ --shadow-xs: 0 1px rgb(0 0 0 / 0.05);
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+ --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
+ --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
+ --shadow-inner: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
+
+ /* Inset shadows */
+ --inset-shadow-xs: inset 0 1px rgb(0 0 0 / 0.05);
+ --inset-shadow-sm: inset 0 1px 1px rgb(0 0 0 / 0.05);
+ --inset-shadow: inset 0 2px 4px rgb(0 0 0 / 0.05);
+
+ /* Drop shadows */
+ --drop-shadow: 0 1px 2px rgb(0 0 0 / 0.1), 0 1px 1px rgb(0 0 0 / 0.06);
+ --drop-shadow-sm: 0 1px 1px rgb(0 0 0 / 0.05);
+ --drop-shadow-md: 0 4px 3px rgb(0 0 0 / 0.07), 0 2px 2px rgb(0 0 0 / 0.06);
+ --drop-shadow-lg: 0 10px 8px rgb(0 0 0 / 0.04), 0 4px 3px rgb(0 0 0 / 0.1);
+ --drop-shadow-xl: 0 20px 13px rgb(0 0 0 / 0.03), 0 8px 5px rgb(0 0 0 / 0.08);
+ --drop-shadow-2xl: 0 25px 25px rgb(0 0 0 / 0.15);
+
+ /* Spacing */
+ --spacing-px: 1px;
+ --spacing-0: 0px;
+ --spacing-0_5: 0.125rem;
+ --spacing-1: 0.25rem;
+ --spacing-1_5: 0.375rem;
+ --spacing-2: 0.5rem;
+ --spacing-2_5: 0.625rem;
+ --spacing-3: 0.75rem;
+ --spacing-3_5: 0.875rem;
+ --spacing-4: 1rem;
+ --spacing-5: 1.25rem;
+ --spacing-6: 1.5rem;
+ --spacing-7: 1.75rem;
+ --spacing-8: 2rem;
+ --spacing-9: 2.25rem;
+ --spacing-10: 2.5rem;
+ --spacing-11: 2.75rem;
+ --spacing-12: 3rem;
+ --spacing-14: 3.5rem;
+ --spacing-16: 4rem;
+ --spacing-20: 5rem;
+ --spacing-24: 6rem;
+ --spacing-28: 7rem;
+ --spacing-32: 8rem;
+ --spacing-36: 9rem;
+ --spacing-40: 10rem;
+ --spacing-44: 11rem;
+ --spacing-48: 12rem;
+ --spacing-52: 13rem;
+ --spacing-56: 14rem;
+ --spacing-60: 15rem;
+ --spacing-64: 16rem;
+ --spacing-72: 18rem;
+ --spacing-80: 20rem;
+ --spacing-96: 24rem;
+
+ /* Widths */
+ --width-3xs: 16rem;
+ --width-2xs: 18rem;
+ --width-xs: 20rem;
+ --width-sm: 24rem;
+ --width-md: 28rem;
+ --width-lg: 32rem;
+ --width-xl: 36rem;
+ --width-2xl: 42rem;
+ --width-3xl: 48rem;
+ --width-4xl: 56rem;
+ --width-5xl: 64rem;
+ --width-6xl: 72rem;
+ --width-7xl: 80rem;
+ --width-prose: 65ch;
+
+ /* Fonts */
+ --font-family-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
+ 'Segoe UI Symbol', 'Noto Color Emoji';
+ --font-family-serif: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
+ --font-family-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
+ 'Courier New', monospace;
+
+ /* Type scale */
+ --font-size-xs: 0.75rem;
+ --font-size-xs--line-height: 1rem;
+ --font-size-sm: 0.875rem;
+ --font-size-sm--line-height: 1.25rem;
+ --font-size-base: 1rem;
+ --font-size-base--line-height: 1.5rem;
+ --font-size-lg: 1.125rem;
+ --font-size-lg--line-height: 1.75rem;
+ --font-size-xl: 1.25rem;
+ --font-size-xl--line-height: 1.75rem;
+ --font-size-2xl: 1.5rem;
+ --font-size-2xl--line-height: 2rem;
+ --font-size-3xl: 1.875rem;
+ --font-size-3xl--line-height: 2.25rem;
+ --font-size-4xl: 2.25rem;
+ --font-size-4xl--line-height: 2.5rem;
+ --font-size-5xl: 3rem;
+ --font-size-5xl--line-height: 1;
+ --font-size-6xl: 3.75rem;
+ --font-size-6xl--line-height: 1;
+ --font-size-7xl: 4.5rem;
+ --font-size-7xl--line-height: 1;
+ --font-size-8xl: 6rem;
+ --font-size-8xl--line-height: 1;
+ --font-size-9xl: 8rem;
+ --font-size-9xl--line-height: 1;
+
+ /* Letter spacing */
+ --letter-spacing-tighter: -0.05em;
+ --letter-spacing-tight: -0.025em;
+ --letter-spacing-normal: 0em;
+ --letter-spacing-wide: 0.025em;
+ --letter-spacing-wider: 0.05em;
+ --letter-spacing-widest: 0.1em;
+
+ /* Line-height */
+ --line-height-none: 1;
+ --line-height-tight: 1.25;
+ --line-height-snug: 1.375;
+ --line-height-normal: 1.5;
+ --line-height-relaxed: 1.625;
+ --line-height-loose: 2;
+ --line-height-3: 0.75rem;
+ --line-height-4: 1rem;
+ --line-height-5: 1.25rem;
+ --line-height-6: 1.5rem;
+ --line-height-7: 1.75rem;
+ --line-height-8: 2rem;
+ --line-height-9: 2.25rem;
+ --line-height-10: 2.5rem;
+
+ /* Transition timing functions */
+ --transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ --transition-timing-function-linear: linear;
+ --transition-timing-function-in: cubic-bezier(0.4, 0, 1, 1);
+ --transition-timing-function-out: cubic-bezier(0, 0, 0.2, 1);
+ --transition-timing-function-in-out: cubic-bezier(0.4, 0, 0.2, 1);
+
+ @keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }
+
+ @keyframes ping {
+ 75%,
+ 100% {
+ transform: scale(2);
+ opacity: 0;
+ }
+ }
+
+ @keyframes pulse {
+ 50% {
+ opacity: 0.5;
+ }
+ }
+
+ @keyframes bounce {
+ 0%,
+ 100% {
+ transform: translateY(-25%);
+ animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
+ }
+
+ 50% {
+ transform: none;
+ animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ }
+ }
+}
diff --git a/packages/tailwindcss/tsconfig.json b/packages/tailwindcss/tsconfig.json
new file mode 100644
index 000000000..6ae022f65
--- /dev/null
+++ b/packages/tailwindcss/tsconfig.json
@@ -0,0 +1,3 @@
+{
+ "extends": "../tsconfig.base.json",
+}
diff --git a/packages/tailwindcss/tsup.config.ts b/packages/tailwindcss/tsup.config.ts
new file mode 100644
index 000000000..87fe4bbdb
--- /dev/null
+++ b/packages/tailwindcss/tsup.config.ts
@@ -0,0 +1,12 @@
+import { defineConfig } from 'tsup'
+
+export default defineConfig({
+ format: ['esm', 'cjs'],
+ clean: true,
+ treeshake: true,
+ dts: true,
+ entry: {
+ lib: 'src/index.ts',
+ cli: 'src/cli/index.ts',
+ },
+})
diff --git a/packages/tailwindcss/utilities.css b/packages/tailwindcss/utilities.css
new file mode 100644
index 000000000..65dd5f63a
--- /dev/null
+++ b/packages/tailwindcss/utilities.css
@@ -0,0 +1 @@
+@tailwind utilities;
diff --git a/packages/tailwindcss/vitest.config.ts b/packages/tailwindcss/vitest.config.ts
new file mode 100644
index 000000000..d819d1bad
--- /dev/null
+++ b/packages/tailwindcss/vitest.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vitest/config'
+
+export default defineConfig({
+ test: {
+ exclude: ['**/*.spec.?(c|m)[jt]s?(x)'],
+ },
+})
diff --git a/packages/tsconfig.base.json b/packages/tsconfig.base.json
new file mode 100644
index 000000000..32597214a
--- /dev/null
+++ b/packages/tsconfig.base.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "lib": ["ESNext", "DOM"],
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleDetection": "force",
+ "allowJs": true,
+
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "isolatedModules": true,
+ "preserveConstEnums": true,
+ "noEmit": true,
+
+ "skipLibCheck": true,
+ "strict": true,
+ "noFallthroughCasesInSwitch": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/playgrounds/nextjs/.eslintrc.json b/playgrounds/nextjs/.eslintrc.json
new file mode 100644
index 000000000..591f680e4
--- /dev/null
+++ b/playgrounds/nextjs/.eslintrc.json
@@ -0,0 +1,7 @@
+{
+ "extends": "next/core-web-vitals",
+ "rules": {
+ "react/no-unescaped-entities": "off",
+ "react/jsx-no-comment-textnodes": "off"
+ }
+}
diff --git a/playgrounds/nextjs/.gitignore b/playgrounds/nextjs/.gitignore
new file mode 100644
index 000000000..fd3dbb571
--- /dev/null
+++ b/playgrounds/nextjs/.gitignore
@@ -0,0 +1,36 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/playgrounds/nextjs/README.md b/playgrounds/nextjs/README.md
new file mode 100644
index 000000000..c4033664f
--- /dev/null
+++ b/playgrounds/nextjs/README.md
@@ -0,0 +1,36 @@
+This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+
+This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
diff --git a/playgrounds/nextjs/app/favicon.ico b/playgrounds/nextjs/app/favicon.ico
new file mode 100644
index 000000000..718d6fea4
Binary files /dev/null and b/playgrounds/nextjs/app/favicon.ico differ
diff --git a/playgrounds/nextjs/app/globals.css b/playgrounds/nextjs/app/globals.css
new file mode 100644
index 000000000..d4b507858
--- /dev/null
+++ b/playgrounds/nextjs/app/globals.css
@@ -0,0 +1 @@
+@import 'tailwindcss';
diff --git a/playgrounds/nextjs/app/layout.tsx b/playgrounds/nextjs/app/layout.tsx
new file mode 100644
index 000000000..cf832e94e
--- /dev/null
+++ b/playgrounds/nextjs/app/layout.tsx
@@ -0,0 +1,23 @@
+import type { Metadata } from 'next'
+import { Inter } from 'next/font/google'
+import './globals.css'
+
+const inter = Inter({ subsets: ['latin'] })
+
+export const metadata: Metadata = {
+ title: 'Create Next App',
+ description: 'Generated by create next app',
+}
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode
+}>) {
+ return (
+
+ {/* */}
+ {children}
+
+ )
+}
diff --git a/playgrounds/nextjs/app/page.module.css b/playgrounds/nextjs/app/page.module.css
new file mode 100644
index 000000000..e0c1bf51e
--- /dev/null
+++ b/playgrounds/nextjs/app/page.module.css
@@ -0,0 +1,228 @@
+.main {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: center;
+ padding: 6rem;
+ min-height: 100vh;
+}
+
+.description {
+ display: inherit;
+ justify-content: inherit;
+ align-items: inherit;
+ font-size: 0.85rem;
+ max-width: var(--max-width);
+ width: 100%;
+ z-index: 2;
+ font-family: var(--font-mono);
+}
+
+.description a {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.description p {
+ position: relative;
+ margin: 0;
+ padding: 1rem;
+ background-color: rgba(var(--callout-rgb), 0.5);
+ border: 1px solid rgba(var(--callout-border-rgb), 0.3);
+ border-radius: var(--border-radius);
+}
+
+.code {
+ font-weight: 700;
+ font-family: var(--font-mono);
+}
+
+.grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(25%, auto));
+ max-width: 100%;
+ width: var(--max-width);
+}
+
+.card {
+ padding: 1rem 1.2rem;
+ border-radius: var(--border-radius);
+ background: rgba(var(--card-rgb), 0);
+ border: 1px solid rgba(var(--card-border-rgb), 0);
+ transition:
+ background 200ms,
+ border 200ms;
+}
+
+.card span {
+ display: inline-block;
+ transition: transform 200ms;
+}
+
+.card h2 {
+ font-weight: 600;
+ margin-bottom: 0.7rem;
+}
+
+.card p {
+ margin: 0;
+ opacity: 0.6;
+ font-size: 0.9rem;
+ line-height: 1.5;
+ max-width: 30ch;
+ text-wrap: balance;
+}
+
+.center {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ padding: 4rem 0;
+}
+
+.center::before {
+ background: var(--secondary-glow);
+ border-radius: 50%;
+ width: 480px;
+ height: 360px;
+ margin-left: -400px;
+}
+
+.center::after {
+ background: var(--primary-glow);
+ width: 240px;
+ height: 180px;
+ z-index: -1;
+}
+
+.center::before,
+.center::after {
+ content: '';
+ left: 50%;
+ position: absolute;
+ filter: blur(45px);
+ transform: translateZ(0);
+}
+
+.logo {
+ position: relative;
+}
+/* Enable hover only on non-touch devices */
+@media (hover: hover) and (pointer: fine) {
+ .card:hover {
+ background: rgba(var(--card-rgb), 0.1);
+ border: 1px solid rgba(var(--card-border-rgb), 0.15);
+ }
+
+ .card:hover span {
+ transform: translateX(4px);
+ }
+}
+
+@media (prefers-reduced-motion) {
+ .card:hover span {
+ transform: none;
+ }
+}
+
+/* Mobile */
+@media (max-width: 700px) {
+ .content {
+ padding: 4rem;
+ }
+
+ .grid {
+ grid-template-columns: 1fr;
+ margin-bottom: 120px;
+ max-width: 320px;
+ text-align: center;
+ }
+
+ .card {
+ padding: 1rem 2.5rem;
+ }
+
+ .card h2 {
+ margin-bottom: 0.5rem;
+ }
+
+ .center {
+ padding: 8rem 0 6rem;
+ }
+
+ .center::before {
+ transform: none;
+ height: 300px;
+ }
+
+ .description {
+ font-size: 0.8rem;
+ }
+
+ .description a {
+ padding: 1rem;
+ }
+
+ .description p,
+ .description div {
+ display: flex;
+ justify-content: center;
+ position: fixed;
+ width: 100%;
+ }
+
+ .description p {
+ align-items: center;
+ inset: 0 0 auto;
+ padding: 2rem 1rem 1.4rem;
+ border-radius: 0;
+ border: none;
+ border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
+ background: linear-gradient(
+ to bottom,
+ rgba(var(--background-start-rgb), 1),
+ rgba(var(--callout-rgb), 0.5)
+ );
+ background-clip: padding-box;
+ backdrop-filter: blur(24px);
+ }
+
+ .description div {
+ align-items: flex-end;
+ pointer-events: none;
+ inset: auto 0 0;
+ padding: 2rem;
+ height: 200px;
+ background: linear-gradient(to bottom, transparent 0%, rgb(var(--background-end-rgb)) 40%);
+ z-index: 1;
+ }
+}
+
+/* Tablet and Smaller Desktop */
+@media (min-width: 701px) and (max-width: 1120px) {
+ .grid {
+ grid-template-columns: repeat(2, 50%);
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ .vercelLogo {
+ filter: invert(1);
+ }
+
+ .logo {
+ filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
+ }
+}
+
+@keyframes rotate {
+ from {
+ transform: rotate(360deg);
+ }
+ to {
+ transform: rotate(0deg);
+ }
+}
diff --git a/playgrounds/nextjs/app/page.tsx b/playgrounds/nextjs/app/page.tsx
new file mode 100644
index 000000000..3c5390442
--- /dev/null
+++ b/playgrounds/nextjs/app/page.tsx
@@ -0,0 +1,3 @@
+export default function Home() {
+ return Hello world!
+}
diff --git a/playgrounds/nextjs/next.config.mjs b/playgrounds/nextjs/next.config.mjs
new file mode 100644
index 000000000..1d6147825
--- /dev/null
+++ b/playgrounds/nextjs/next.config.mjs
@@ -0,0 +1,4 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {}
+
+export default nextConfig
diff --git a/playgrounds/nextjs/package.json b/playgrounds/nextjs/package.json
new file mode 100644
index 000000000..7e603f827
--- /dev/null
+++ b/playgrounds/nextjs/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "nextjs-playground",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@tailwindcss/postcss": "workspace:^",
+ "fast-glob": "^3.3.2",
+ "next": "14.1.0",
+ "react": "^18",
+ "react-dom": "^18",
+ "tailwindcss": "workspace:^"
+ },
+ "devDependencies": {
+ "@types/node": "^20",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "eslint": "^8",
+ "eslint-config-next": "14.1.0",
+ "typescript": "^5"
+ }
+}
diff --git a/playgrounds/nextjs/postcss.config.js b/playgrounds/nextjs/postcss.config.js
new file mode 100644
index 000000000..6c434006e
--- /dev/null
+++ b/playgrounds/nextjs/postcss.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ plugins: ['@tailwindcss/postcss'],
+}
diff --git a/playgrounds/nextjs/public/next.svg b/playgrounds/nextjs/public/next.svg
new file mode 100644
index 000000000..5174b28c5
--- /dev/null
+++ b/playgrounds/nextjs/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/playgrounds/nextjs/public/vercel.svg b/playgrounds/nextjs/public/vercel.svg
new file mode 100644
index 000000000..d2f842227
--- /dev/null
+++ b/playgrounds/nextjs/public/vercel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/playgrounds/nextjs/tsconfig.json b/playgrounds/nextjs/tsconfig.json
new file mode 100644
index 000000000..862143548
--- /dev/null
+++ b/playgrounds/nextjs/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next",
+ },
+ ],
+ "paths": {
+ "@/*": ["./*"],
+ },
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"],
+}
diff --git a/playgrounds/vite/package.json b/playgrounds/vite/package.json
new file mode 100644
index 000000000..48f7bae60
--- /dev/null
+++ b/playgrounds/vite/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "vite-playground",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "lint": "tsc --noEmit",
+ "dev": "bun --bun vite ./src --config ./vite.config.ts",
+ "build": "bun --bun vite build ./src --outDir ../dist --config ./vite.config.ts --emptyOutDir"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.48",
+ "@types/react-dom": "^18.2.18",
+ "bun": "^1.0.26",
+ "vite": "^5.0.11",
+ "vite-plugin-handlebars": "^2.0.0"
+ },
+ "dependencies": {
+ "@tailwindcss/vite": "workspace:^",
+ "tailwindcss": "workspace:^",
+ "@vitejs/plugin-react": "^4.2.1",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ }
+}
diff --git a/playgrounds/vite/src/app.css b/playgrounds/vite/src/app.css
new file mode 100644
index 000000000..d4b507858
--- /dev/null
+++ b/playgrounds/vite/src/app.css
@@ -0,0 +1 @@
+@import 'tailwindcss';
diff --git a/playgrounds/vite/src/app.tsx b/playgrounds/vite/src/app.tsx
new file mode 100644
index 000000000..ab1808bf7
--- /dev/null
+++ b/playgrounds/vite/src/app.tsx
@@ -0,0 +1,10 @@
+import { Foo } from './foo'
+
+export function App() {
+ return (
+
+
Hello World
+
+
+ )
+}
diff --git a/playgrounds/vite/src/bar.tsx b/playgrounds/vite/src/bar.tsx
new file mode 100644
index 000000000..1efa2c004
--- /dev/null
+++ b/playgrounds/vite/src/bar.tsx
@@ -0,0 +1,7 @@
+export function Bar() {
+ return (
+
+
Bar
+
+ )
+}
diff --git a/playgrounds/vite/src/foo.tsx b/playgrounds/vite/src/foo.tsx
new file mode 100644
index 000000000..60ec68b55
--- /dev/null
+++ b/playgrounds/vite/src/foo.tsx
@@ -0,0 +1,10 @@
+import { Bar } from './bar'
+
+export function Foo() {
+ return (
+
+
Foo
+
+
+ )
+}
diff --git a/playgrounds/vite/src/index.html b/playgrounds/vite/src/index.html
new file mode 100644
index 000000000..d4b60a84a
--- /dev/null
+++ b/playgrounds/vite/src/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ ≈ Playground
+
+
+
+
+
+
+
diff --git a/playgrounds/vite/src/main.tsx b/playgrounds/vite/src/main.tsx
new file mode 100644
index 000000000..b8df681f3
--- /dev/null
+++ b/playgrounds/vite/src/main.tsx
@@ -0,0 +1,9 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import { App } from './app'
+
+ReactDOM.createRoot(document.getElementById('app')!).render(
+
+
+ ,
+)
diff --git a/playgrounds/vite/src/tailwind.css b/playgrounds/vite/src/tailwind.css
new file mode 100644
index 000000000..6f0b0d649
--- /dev/null
+++ b/playgrounds/vite/src/tailwind.css
@@ -0,0 +1 @@
+@tailwind;
diff --git a/playgrounds/vite/tsconfig.json b/playgrounds/vite/tsconfig.json
new file mode 100644
index 000000000..2c0c2c0d9
--- /dev/null
+++ b/playgrounds/vite/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "lib": ["ESNext"],
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleDetection": "force",
+ "allowJs": true,
+
+ /* Bundler mode */
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "isolatedModules": true,
+ "preserveConstEnums": true,
+ "noEmit": true,
+
+ /* Linting */
+ "skipLibCheck": true,
+ "strict": true,
+ "noFallthroughCasesInSwitch": true,
+ "forceConsistentCasingInFileNames": true,
+ },
+ "include": ["**/*.ts"],
+ "exclude": ["node_modules"],
+}
diff --git a/playgrounds/vite/vite.config.ts b/playgrounds/vite/vite.config.ts
new file mode 100644
index 000000000..888ede112
--- /dev/null
+++ b/playgrounds/vite/vite.config.ts
@@ -0,0 +1,7 @@
+import tailwindcss from '@tailwindcss/vite'
+import react from '@vitejs/plugin-react'
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ plugins: [react(), tailwindcss()],
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 000000000..321182a90
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,8045 @@
+lockfileVersion: '6.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ devDependencies:
+ '@playwright/test':
+ specifier: ^1.41.2
+ version: 1.41.2
+ '@types/node':
+ specifier: ^20.11.19
+ version: 20.11.20
+ '@vitest/coverage-v8':
+ specifier: ^1.2.1
+ version: 1.2.2(vitest@1.2.2)
+ prettier:
+ specifier: ^3.2.5
+ version: 3.2.5
+ prettier-plugin-organize-imports:
+ specifier: ^3.2.4
+ version: 3.2.4(prettier@3.2.5)(typescript@5.3.3)
+ tsup:
+ specifier: ^8.0.1
+ version: 8.0.1(typescript@5.3.3)
+ turbo:
+ specifier: ^1.12.4
+ version: 1.12.4
+ typescript:
+ specifier: ^5.3.3
+ version: 5.3.3
+ vitest:
+ specifier: ^1.1.3
+ version: 1.2.2(@types/node@20.11.20)
+
+ oxide/crates/node:
+ optionalDependencies:
+ '@tailwindcss/oxide-darwin-arm64':
+ specifier: workspace:*
+ version: link:npm/darwin-arm64
+ '@tailwindcss/oxide-darwin-x64':
+ specifier: workspace:*
+ version: link:npm/darwin-x64
+ '@tailwindcss/oxide-freebsd-x64':
+ specifier: workspace:*
+ version: link:npm/freebsd-x64
+ '@tailwindcss/oxide-linux-arm-gnueabihf':
+ specifier: workspace:*
+ version: link:npm/linux-arm-gnueabihf
+ '@tailwindcss/oxide-linux-arm64-gnu':
+ specifier: workspace:*
+ version: link:npm/linux-arm64-gnu
+ '@tailwindcss/oxide-linux-arm64-musl':
+ specifier: workspace:*
+ version: link:npm/linux-arm64-musl
+ '@tailwindcss/oxide-linux-x64-gnu':
+ specifier: workspace:*
+ version: link:npm/linux-x64-gnu
+ '@tailwindcss/oxide-linux-x64-musl':
+ specifier: workspace:*
+ version: link:npm/linux-x64-musl
+ '@tailwindcss/oxide-win32-x64-msvc':
+ specifier: workspace:*
+ version: link:npm/win32-x64-msvc
+ devDependencies:
+ '@napi-rs/cli':
+ specifier: ^2.17.0
+ version: 2.18.0
+
+ oxide/crates/node/npm/darwin-arm64: {}
+
+ oxide/crates/node/npm/darwin-x64: {}
+
+ oxide/crates/node/npm/freebsd-x64: {}
+
+ oxide/crates/node/npm/linux-arm-gnueabihf: {}
+
+ oxide/crates/node/npm/linux-arm64-gnu: {}
+
+ oxide/crates/node/npm/linux-arm64-musl: {}
+
+ oxide/crates/node/npm/linux-x64-gnu: {}
+
+ oxide/crates/node/npm/linux-x64-musl: {}
+
+ oxide/crates/node/npm/win32-x64-msvc: {}
+
+ packages/@tailwindcss-postcss:
+ dependencies:
+ '@tailwindcss/oxide':
+ specifier: workspace:^
+ version: link:../../oxide/crates/node
+ postcss-import:
+ specifier: ^16.0.0
+ version: 16.0.0(postcss@8.4.24)
+ tailwindcss:
+ specifier: workspace:^
+ version: link:../tailwindcss
+ devDependencies:
+ '@types/node':
+ specifier: ^20.11.17
+ version: 20.11.17
+ '@types/postcss-import':
+ specifier: ^14.0.3
+ version: 14.0.3
+ postcss:
+ specifier: 8.4.24
+ version: 8.4.24
+
+ packages/@tailwindcss-vite:
+ dependencies:
+ '@tailwindcss/oxide':
+ specifier: workspace:^
+ version: link:../../oxide/crates/node
+ tailwindcss:
+ specifier: workspace:^
+ version: link:../tailwindcss
+ devDependencies:
+ '@types/node':
+ specifier: ^20.11.17
+ version: 20.11.17
+ vite:
+ specifier: ^5.0.11
+ version: 5.1.1(@types/node@20.11.17)
+
+ packages/tailwindcss:
+ dependencies:
+ '@parcel/watcher':
+ specifier: ^2.4.1
+ version: 2.4.1
+ lightningcss:
+ specifier: ^1.24.0
+ version: 1.24.0
+ mri:
+ specifier: ^1.2.0
+ version: 1.2.0
+ picocolors:
+ specifier: ^1.0.0
+ version: 1.0.0
+ postcss:
+ specifier: 8.4.24
+ version: 8.4.24
+ postcss-import:
+ specifier: ^16.0.0
+ version: 16.0.0(postcss@8.4.24)
+ devDependencies:
+ '@tailwindcss/oxide':
+ specifier: workspace:^
+ version: link:../../oxide/crates/node
+ '@types/node':
+ specifier: ^20.10.8
+ version: 20.11.17
+ '@types/postcss-import':
+ specifier: ^14.0.3
+ version: 14.0.3
+
+ playgrounds/nextjs:
+ dependencies:
+ '@tailwindcss/postcss':
+ specifier: workspace:^
+ version: link:../../packages/@tailwindcss-postcss
+ fast-glob:
+ specifier: ^3.3.2
+ version: 3.3.2
+ next:
+ specifier: 14.1.0
+ version: 14.1.0(react-dom@18.2.0)(react@18.2.0)
+ react:
+ specifier: ^18
+ version: 18.2.0
+ react-dom:
+ specifier: ^18
+ version: 18.2.0(react@18.2.0)
+ tailwindcss:
+ specifier: workspace:^
+ version: link:../../packages/tailwindcss
+ devDependencies:
+ '@types/node':
+ specifier: ^20
+ version: 20.11.17
+ '@types/react':
+ specifier: ^18
+ version: 18.2.55
+ '@types/react-dom':
+ specifier: ^18
+ version: 18.2.19
+ eslint:
+ specifier: ^8
+ version: 8.44.0
+ eslint-config-next:
+ specifier: 14.1.0
+ version: 14.1.0(eslint@8.44.0)(typescript@5.3.3)
+ typescript:
+ specifier: ^5
+ version: 5.3.3
+
+ playgrounds/vite:
+ dependencies:
+ '@tailwindcss/vite':
+ specifier: workspace:^
+ version: link:../../packages/@tailwindcss-vite
+ '@vitejs/plugin-react':
+ specifier: ^4.2.1
+ version: 4.2.1(vite@5.1.1)
+ react:
+ specifier: ^18.2.0
+ version: 18.2.0
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.2.0(react@18.2.0)
+ tailwindcss:
+ specifier: workspace:^
+ version: link:../../packages/tailwindcss
+ devDependencies:
+ '@types/react':
+ specifier: ^18.2.48
+ version: 18.2.55
+ '@types/react-dom':
+ specifier: ^18.2.18
+ version: 18.2.19
+ bun:
+ specifier: ^1.0.26
+ version: 1.0.26
+ vite:
+ specifier: ^5.0.11
+ version: 5.1.1(@types/node@20.11.20)
+ vite-plugin-handlebars:
+ specifier: ^2.0.0
+ version: 2.0.0(@types/node@20.11.20)
+
+packages:
+
+ /@aashutoshrathi/word-wrap@1.2.6:
+ resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
+ engines: {node: '>=0.10.0'}
+
+ /@ampproject/remapping@2.2.1:
+ resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
+ engines: {node: '>=6.0.0'}
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.3
+ '@jridgewell/trace-mapping': 0.3.22
+
+ /@babel/code-frame@7.23.5:
+ resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/highlight': 7.23.4
+ chalk: 2.4.2
+ dev: false
+
+ /@babel/compat-data@7.23.5:
+ resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==}
+ engines: {node: '>=6.9.0'}
+ dev: false
+
+ /@babel/core@7.23.9:
+ resolution: {integrity: sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@ampproject/remapping': 2.2.1
+ '@babel/code-frame': 7.23.5
+ '@babel/generator': 7.23.6
+ '@babel/helper-compilation-targets': 7.23.6
+ '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9)
+ '@babel/helpers': 7.23.9
+ '@babel/parser': 7.23.9
+ '@babel/template': 7.23.9
+ '@babel/traverse': 7.23.9
+ '@babel/types': 7.23.9
+ convert-source-map: 2.0.0
+ debug: 4.3.4
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /@babel/generator@7.23.6:
+ resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.23.9
+ '@jridgewell/gen-mapping': 0.3.3
+ '@jridgewell/trace-mapping': 0.3.22
+ jsesc: 2.5.2
+ dev: false
+
+ /@babel/helper-compilation-targets@7.23.6:
+ resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/compat-data': 7.23.5
+ '@babel/helper-validator-option': 7.23.5
+ browserslist: 4.22.3
+ lru-cache: 5.1.1
+ semver: 6.3.1
+ dev: false
+
+ /@babel/helper-environment-visitor@7.22.20:
+ resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==}
+ engines: {node: '>=6.9.0'}
+ dev: false
+
+ /@babel/helper-function-name@7.23.0:
+ resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/template': 7.23.9
+ '@babel/types': 7.23.9
+ dev: false
+
+ /@babel/helper-hoist-variables@7.22.5:
+ resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.23.9
+ dev: false
+
+ /@babel/helper-module-imports@7.22.15:
+ resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.23.9
+ dev: false
+
+ /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.9):
+ resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+ dependencies:
+ '@babel/core': 7.23.9
+ '@babel/helper-environment-visitor': 7.22.20
+ '@babel/helper-module-imports': 7.22.15
+ '@babel/helper-simple-access': 7.22.5
+ '@babel/helper-split-export-declaration': 7.22.6
+ '@babel/helper-validator-identifier': 7.22.20
+ dev: false
+
+ /@babel/helper-plugin-utils@7.22.5:
+ resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==}
+ engines: {node: '>=6.9.0'}
+ dev: false
+
+ /@babel/helper-simple-access@7.22.5:
+ resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.23.9
+ dev: false
+
+ /@babel/helper-split-export-declaration@7.22.6:
+ resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.23.9
+ dev: false
+
+ /@babel/helper-string-parser@7.23.4:
+ resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==}
+ engines: {node: '>=6.9.0'}
+
+ /@babel/helper-validator-identifier@7.22.20:
+ resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
+ engines: {node: '>=6.9.0'}
+
+ /@babel/helper-validator-option@7.23.5:
+ resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==}
+ engines: {node: '>=6.9.0'}
+ dev: false
+
+ /@babel/helpers@7.23.9:
+ resolution: {integrity: sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/template': 7.23.9
+ '@babel/traverse': 7.23.9
+ '@babel/types': 7.23.9
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /@babel/highlight@7.23.4:
+ resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-validator-identifier': 7.22.20
+ chalk: 2.4.2
+ js-tokens: 4.0.0
+ dev: false
+
+ /@babel/parser@7.23.9:
+ resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+ dependencies:
+ '@babel/types': 7.23.9
+
+ /@babel/plugin-transform-react-jsx-self@7.23.3(@babel/core@7.23.9):
+ resolution: {integrity: sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.23.9
+ '@babel/helper-plugin-utils': 7.22.5
+ dev: false
+
+ /@babel/plugin-transform-react-jsx-source@7.23.3(@babel/core@7.23.9):
+ resolution: {integrity: sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.23.9
+ '@babel/helper-plugin-utils': 7.22.5
+ dev: false
+
+ /@babel/runtime@7.23.9:
+ resolution: {integrity: sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ regenerator-runtime: 0.14.1
+
+ /@babel/template@7.23.9:
+ resolution: {integrity: sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/code-frame': 7.23.5
+ '@babel/parser': 7.23.9
+ '@babel/types': 7.23.9
+ dev: false
+
+ /@babel/traverse@7.23.9:
+ resolution: {integrity: sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/code-frame': 7.23.5
+ '@babel/generator': 7.23.6
+ '@babel/helper-environment-visitor': 7.22.20
+ '@babel/helper-function-name': 7.23.0
+ '@babel/helper-hoist-variables': 7.22.5
+ '@babel/helper-split-export-declaration': 7.22.6
+ '@babel/parser': 7.23.9
+ '@babel/types': 7.23.9
+ debug: 4.3.4
+ globals: 11.12.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /@babel/types@7.23.9:
+ resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-string-parser': 7.23.4
+ '@babel/helper-validator-identifier': 7.22.20
+ to-fast-properties: 2.0.0
+
+ /@bcoe/v8-coverage@0.2.3:
+ resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
+ dev: true
+
+ /@code-hike/lighter@0.8.2:
+ resolution: {integrity: sha512-h7PA2+90rIRQWamxeHSpcgVLs9hwhz8UW8+RG+vYIYh2Y4F2GTa4c+7S5HQH/BKTyMPv5yrSCEwhCB605gO5og==}
+ dependencies:
+ ansi-sequence-parser: 1.1.1
+ dev: false
+
+ /@esbuild/aix-ppc64@0.19.12:
+ resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [aix]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/android-arm64@0.19.12:
+ resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/android-arm@0.19.12:
+ resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/android-x64@0.19.12:
+ resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/darwin-arm64@0.19.12:
+ resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/darwin-x64@0.19.12:
+ resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/freebsd-arm64@0.19.12:
+ resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/freebsd-x64@0.19.12:
+ resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-arm64@0.19.12:
+ resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-arm@0.19.12:
+ resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-ia32@0.19.12:
+ resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-loong64@0.19.12:
+ resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-mips64el@0.19.12:
+ resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-ppc64@0.19.12:
+ resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-riscv64@0.19.12:
+ resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-s390x@0.19.12:
+ resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/linux-x64@0.19.12:
+ resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/netbsd-x64@0.19.12:
+ resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/openbsd-x64@0.19.12:
+ resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/sunos-x64@0.19.12:
+ resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/win32-arm64@0.19.12:
+ resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/win32-ia32@0.19.12:
+ resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
+ /@esbuild/win32-x64@0.19.12:
+ resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
+ /@eslint-community/eslint-utils@4.4.0(eslint@8.44.0):
+ resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+ dependencies:
+ eslint: 8.44.0
+ eslint-visitor-keys: 3.4.3
+
+ /@eslint-community/regexpp@4.10.0:
+ resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ /@eslint/eslintrc@2.1.4:
+ resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dependencies:
+ ajv: 6.12.6
+ debug: 4.3.4
+ espree: 9.6.1
+ globals: 13.24.0
+ ignore: 5.3.1
+ import-fresh: 3.3.0
+ js-yaml: 4.1.0
+ minimatch: 3.1.2
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ /@eslint/js@8.44.0:
+ resolution: {integrity: sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ /@floating-ui/core@1.6.0:
+ resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==}
+ dependencies:
+ '@floating-ui/utils': 0.2.1
+ dev: false
+
+ /@floating-ui/dom@1.6.1:
+ resolution: {integrity: sha512-iA8qE43/H5iGozC3W0YSnVSW42Vh522yyM1gj+BqRwVsTNOyr231PsXDaV04yT39PsO0QL2QpbI/M0ZaLUQgRQ==}
+ dependencies:
+ '@floating-ui/core': 1.6.0
+ '@floating-ui/utils': 0.2.1
+ dev: false
+
+ /@floating-ui/react-dom@2.0.8(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+ dependencies:
+ '@floating-ui/dom': 1.6.1
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
+ /@floating-ui/react@0.26.9(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-p86wynZJVEkEq2BBjY/8p2g3biQ6TlgT4o/3KgFKyTWoJLU1GZ8wpctwRqtkEl2tseYA+kw7dBAIDFcednfI5w==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+ dependencies:
+ '@floating-ui/react-dom': 2.0.8(react-dom@18.2.0)(react@18.2.0)
+ '@floating-ui/utils': 0.2.1
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ tabbable: 6.2.0
+ dev: false
+
+ /@floating-ui/utils@0.2.1:
+ resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==}
+ dev: false
+
+ /@headlessui/react@2.0.0-alpha.4(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-spykSTXskDUYjSFhdId97Bqclo1F9Ky2pgLmyNKdV4f7aRDncdc/mjMPx67eEWRN2xQobksaUCcnn5K/AcRXsg==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ react: ^16 || ^17 || ^18
+ react-dom: ^16 || ^17 || ^18
+ dependencies:
+ '@floating-ui/react': 0.26.9(react-dom@18.2.0)(react@18.2.0)
+ '@react-aria/focus': 3.16.0(react@18.2.0)
+ '@react-aria/interactions': 3.0.0-nightly.2584(react@18.2.0)
+ '@tanstack/react-virtual': 3.0.0-beta.60(react@18.2.0)
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
+ /@heroicons/react@2.1.1(react@18.2.0):
+ resolution: {integrity: sha512-JyyN9Lo66kirbCMuMMRPtJxtKJoIsXKS569ebHGGRKbl8s4CtUfLnyKJxteA+vIKySocO4s1SkTkGS4xtG/yEA==}
+ peerDependencies:
+ react: '>= 16'
+ dependencies:
+ react: 18.2.0
+ dev: false
+
+ /@humanwhocodes/config-array@0.11.14:
+ resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
+ engines: {node: '>=10.10.0'}
+ dependencies:
+ '@humanwhocodes/object-schema': 2.0.2
+ debug: 4.3.4
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ /@humanwhocodes/module-importer@1.0.1:
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ /@humanwhocodes/object-schema@2.0.2:
+ resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==}
+
+ /@isaacs/cliui@8.0.2:
+ resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
+ engines: {node: '>=12'}
+ dependencies:
+ string-width: 5.1.2
+ string-width-cjs: /string-width@4.2.3
+ strip-ansi: 7.1.0
+ strip-ansi-cjs: /strip-ansi@6.0.1
+ wrap-ansi: 8.1.0
+ wrap-ansi-cjs: /wrap-ansi@7.0.0
+ dev: true
+
+ /@istanbuljs/schema@0.1.3:
+ resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /@jest/schemas@29.6.3:
+ resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ dependencies:
+ '@sinclair/typebox': 0.27.8
+ dev: true
+
+ /@jridgewell/gen-mapping@0.3.3:
+ resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
+ engines: {node: '>=6.0.0'}
+ dependencies:
+ '@jridgewell/set-array': 1.1.2
+ '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/trace-mapping': 0.3.22
+
+ /@jridgewell/resolve-uri@3.1.1:
+ resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
+ engines: {node: '>=6.0.0'}
+
+ /@jridgewell/set-array@1.1.2:
+ resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
+ engines: {node: '>=6.0.0'}
+
+ /@jridgewell/sourcemap-codec@1.4.15:
+ resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
+
+ /@jridgewell/trace-mapping@0.3.22:
+ resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==}
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.1
+ '@jridgewell/sourcemap-codec': 1.4.15
+
+ /@mdx-js/loader@2.3.0(webpack@4.47.0):
+ resolution: {integrity: sha512-IqsscXh7Q3Rzb+f5DXYk0HU71PK+WuFsEhf+mSV3fOhpLcEpgsHvTQ2h0T6TlZ5gHOaBeFjkXwB52by7ypMyNg==}
+ peerDependencies:
+ webpack: '>=4'
+ dependencies:
+ '@mdx-js/mdx': 2.3.0
+ source-map: 0.7.4
+ webpack: 4.47.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /@mdx-js/mdx@2.3.0:
+ resolution: {integrity: sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==}
+ dependencies:
+ '@types/estree-jsx': 1.0.4
+ '@types/mdx': 2.0.11
+ estree-util-build-jsx: 2.2.2
+ estree-util-is-identifier-name: 2.1.0
+ estree-util-to-js: 1.2.0
+ estree-walker: 3.0.3
+ hast-util-to-estree: 2.3.3
+ markdown-extensions: 1.1.1
+ periscopic: 3.1.0
+ remark-mdx: 2.3.0
+ remark-parse: 10.0.2
+ remark-rehype: 10.1.0
+ unified: 10.1.2
+ unist-util-position-from-estree: 1.1.2
+ unist-util-stringify-position: 3.0.3
+ unist-util-visit: 4.1.2
+ vfile: 5.3.7
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /@mdx-js/react@2.3.0(react@18.2.0):
+ resolution: {integrity: sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==}
+ peerDependencies:
+ react: '>=16'
+ dependencies:
+ '@types/mdx': 2.0.11
+ '@types/react': 18.2.55
+ react: 18.2.0
+ dev: false
+
+ /@napi-rs/cli@2.18.0:
+ resolution: {integrity: sha512-lfSRT7cs3iC4L+kv9suGYQEezn5Nii7Kpu+THsYVI0tA1Vh59LH45p4QADaD7hvIkmOz79eEGtoKQ9nAkAPkzA==}
+ engines: {node: '>= 10'}
+ hasBin: true
+ dev: true
+
+ /@next/env@14.1.0:
+ resolution: {integrity: sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==}
+ dev: false
+
+ /@next/eslint-plugin-next@13.4.8:
+ resolution: {integrity: sha512-cmfVHpxWjjcETFt2WHnoFU6EmY69QcPJRlRNAooQlNe53Ke90vg1Ci/dkPffryJZaxxiRziP9bQrV8lDVCn3Fw==}
+ dependencies:
+ glob: 7.1.7
+ dev: false
+
+ /@next/eslint-plugin-next@14.1.0:
+ resolution: {integrity: sha512-x4FavbNEeXx/baD/zC/SdrvkjSby8nBn8KcCREqk6UuwvwoAPZmaV8TFCAuo/cpovBRTIY67mHhe86MQQm/68Q==}
+ dependencies:
+ glob: 10.3.10
+ dev: true
+
+ /@next/mdx@13.5.6(@mdx-js/loader@2.3.0)(@mdx-js/react@2.3.0):
+ resolution: {integrity: sha512-2AMyCrz1SxSWNUpADyLz3RbPbq0GHrchbO7Msvg7IsH8MrTw3VYaZSI1KNa6JzZIoykwtNVSEL+uBmPZi106Jw==}
+ peerDependencies:
+ '@mdx-js/loader': '>=0.15.0'
+ '@mdx-js/react': '>=0.15.0'
+ peerDependenciesMeta:
+ '@mdx-js/loader':
+ optional: true
+ '@mdx-js/react':
+ optional: true
+ dependencies:
+ '@mdx-js/loader': 2.3.0(webpack@4.47.0)
+ '@mdx-js/react': 2.3.0(react@18.2.0)
+ source-map: 0.7.4
+ dev: false
+
+ /@next/swc-darwin-arm64@14.1.0:
+ resolution: {integrity: sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@next/swc-darwin-x64@14.1.0:
+ resolution: {integrity: sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@next/swc-linux-arm64-gnu@14.1.0:
+ resolution: {integrity: sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@next/swc-linux-arm64-musl@14.1.0:
+ resolution: {integrity: sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@next/swc-linux-x64-gnu@14.1.0:
+ resolution: {integrity: sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@next/swc-linux-x64-musl@14.1.0:
+ resolution: {integrity: sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@next/swc-win32-arm64-msvc@14.1.0:
+ resolution: {integrity: sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@next/swc-win32-ia32-msvc@14.1.0:
+ resolution: {integrity: sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==}
+ engines: {node: '>= 10'}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@next/swc-win32-x64-msvc@14.1.0:
+ resolution: {integrity: sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@nodelib/fs.scandir@2.1.5:
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ /@nodelib/fs.stat@2.0.5:
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ /@nodelib/fs.walk@1.2.8:
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.17.1
+
+ /@oven/bun-darwin-aarch64@1.0.26:
+ resolution: {integrity: sha512-7FgUZz2EpMgFdEI9sA68bnHhrnrDBmCwcewpm6641HK0x2RIO1EcFl/7GKSGsD1JmbNfGqxYU16Sc+rgKFtXAg==}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@oven/bun-darwin-x64-baseline@1.0.26:
+ resolution: {integrity: sha512-ssVJJs6tAvVZKnCx5bXAXZ05nSbfLHJ4qD/XFlVYb+3aue81YhXzQpbsOr9DShv209rBp9756SqHs9KZH66cbQ==}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@oven/bun-darwin-x64@1.0.26:
+ resolution: {integrity: sha512-kWO6ThQEMm4OPfqlhFlWrDRiIFtaUl//bRJfomOHE4BABaP9bdgHjDsaxt0pUWiKP9j9YnPFXtMw91iUbTbCWg==}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@oven/bun-linux-aarch64@1.0.26:
+ resolution: {integrity: sha512-YSG6pOFMpXquGf3i4/Ta05NAlsSkiZVk5P5EAlfO1fnJANlkcu1QfD/IYvddQ5YY4aOTwlQFqA29pYxYj+2A/A==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@oven/bun-linux-x64-baseline@1.0.26:
+ resolution: {integrity: sha512-1EjD6t6BFkspGqTRqYGVSQw5eIktDsappweG+1yh/QpmhV95/vDKKoPGXCwoyeSoJIjJMl9VaApX8Fr1vpnUfA==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@oven/bun-linux-x64@1.0.26:
+ resolution: {integrity: sha512-ErEH7yBkWIt01qROMysM6ZQqz8epY+2DtQW0rfjKbaPm8JuEdZVyeA5zoKES3e2J37FUY073MdbwdzE5KPX2cQ==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@parcel/watcher-android-arm64@2.4.1:
+ resolution: {integrity: sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@parcel/watcher-darwin-arm64@2.4.1:
+ resolution: {integrity: sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@parcel/watcher-darwin-x64@2.4.1:
+ resolution: {integrity: sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@parcel/watcher-freebsd-x64@2.4.1:
+ resolution: {integrity: sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@parcel/watcher-linux-arm-glibc@2.4.1:
+ resolution: {integrity: sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@parcel/watcher-linux-arm64-glibc@2.4.1:
+ resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@parcel/watcher-linux-arm64-musl@2.4.1:
+ resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@parcel/watcher-linux-x64-glibc@2.4.1:
+ resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@parcel/watcher-linux-x64-musl@2.4.1:
+ resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@parcel/watcher-win32-arm64@2.4.1:
+ resolution: {integrity: sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@parcel/watcher-win32-ia32@2.4.1:
+ resolution: {integrity: sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@parcel/watcher-win32-x64@2.4.1:
+ resolution: {integrity: sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@parcel/watcher@2.4.1:
+ resolution: {integrity: sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==}
+ engines: {node: '>= 10.0.0'}
+ dependencies:
+ detect-libc: 1.0.3
+ is-glob: 4.0.3
+ micromatch: 4.0.5
+ node-addon-api: 7.1.0
+ optionalDependencies:
+ '@parcel/watcher-android-arm64': 2.4.1
+ '@parcel/watcher-darwin-arm64': 2.4.1
+ '@parcel/watcher-darwin-x64': 2.4.1
+ '@parcel/watcher-freebsd-x64': 2.4.1
+ '@parcel/watcher-linux-arm-glibc': 2.4.1
+ '@parcel/watcher-linux-arm64-glibc': 2.4.1
+ '@parcel/watcher-linux-arm64-musl': 2.4.1
+ '@parcel/watcher-linux-x64-glibc': 2.4.1
+ '@parcel/watcher-linux-x64-musl': 2.4.1
+ '@parcel/watcher-win32-arm64': 2.4.1
+ '@parcel/watcher-win32-ia32': 2.4.1
+ '@parcel/watcher-win32-x64': 2.4.1
+ dev: false
+
+ /@pkgjs/parseargs@0.11.0:
+ resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
+ engines: {node: '>=14'}
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@playwright/test@1.41.2:
+ resolution: {integrity: sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==}
+ engines: {node: '>=16'}
+ hasBin: true
+ dependencies:
+ playwright: 1.41.2
+ dev: true
+
+ /@react-aria/focus@3.16.0(react@18.2.0):
+ resolution: {integrity: sha512-GP6EYI07E8NKQQcXHjpIocEU0vh0oi0Vcsd+/71fKS0NnTR0TUOEeil0JuuQ9ymkmPDTu51Aaaa4FxVsuN/23A==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@react-aria/interactions': 3.20.1(react@18.2.0)
+ '@react-aria/utils': 3.23.0(react@18.2.0)
+ '@react-types/shared': 3.22.0(react@18.2.0)
+ '@swc/helpers': 0.5.6
+ clsx: 2.1.0
+ react: 18.2.0
+ dev: false
+
+ /@react-aria/interactions@3.0.0-nightly.2584(react@18.2.0):
+ resolution: {integrity: sha512-6DqYQx8XnbCfIen33uLz4kdgevrXLW6aoxsBOTY/Mzq9n0LHzbG/5H87obrOxRNVYh62RcQolo/qfqEpXZ7bVA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@react-aria/ssr': 3.9.1-nightly.4295(react@18.2.0)
+ '@react-aria/utils': 3.0.0-nightly.2584(react@18.2.0)
+ '@react-types/shared': 3.0.0-nightly.2584(react@18.2.0)
+ '@swc/helpers': 0.5.6
+ react: 18.2.0
+ dev: false
+
+ /@react-aria/interactions@3.20.1(react@18.2.0):
+ resolution: {integrity: sha512-PLNBr87+SzRhe9PvvF9qvzYeP4ofTwfKSorwmO+hjr3qoczrSXf4LRQlb27wB6hF10C7ZE/XVbUI1lj4QQrZ/g==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@react-aria/ssr': 3.9.1(react@18.2.0)
+ '@react-aria/utils': 3.23.0(react@18.2.0)
+ '@react-types/shared': 3.22.0(react@18.2.0)
+ '@swc/helpers': 0.5.6
+ react: 18.2.0
+ dev: false
+
+ /@react-aria/ssr@3.9.1(react@18.2.0):
+ resolution: {integrity: sha512-NqzkLFP8ZVI4GSorS0AYljC13QW2sc8bDqJOkBvkAt3M8gbcAXJWVRGtZBCRscki9RZF+rNlnPdg0G0jYkhJcg==}
+ engines: {node: '>= 12'}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@swc/helpers': 0.5.6
+ react: 18.2.0
+ dev: false
+
+ /@react-aria/ssr@3.9.1-nightly.4295(react@18.2.0):
+ resolution: {integrity: sha512-cv0+RaS3LJeZiSJ4pVGqSAyiyL+rieLiR3ctyoU7EwkArY1W7fI3NSkMEbNhHe4YoqqjPy1ZzAcpSA11EceiBg==}
+ engines: {node: '>= 12'}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@swc/helpers': 0.5.6
+ react: 18.2.0
+ dev: false
+
+ /@react-aria/utils@3.0.0-nightly.2584(react@18.2.0):
+ resolution: {integrity: sha512-A6NP3Yc9MMA+PiRBMTpMlx5plaiK7ejl3cppdkKiNPHtFmZrzxn6o9WHth4NToqIUkJRWHIrpTK8a/gBgVFPOg==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@react-aria/ssr': 3.9.1-nightly.4295(react@18.2.0)
+ '@react-stately/utils': 3.0.0-nightly.2584(react@18.2.0)
+ '@react-types/shared': 3.0.0-nightly.2584(react@18.2.0)
+ '@swc/helpers': 0.5.6
+ clsx: 1.2.1
+ react: 18.2.0
+ dev: false
+
+ /@react-aria/utils@3.23.0(react@18.2.0):
+ resolution: {integrity: sha512-fJA63/VU4iQNT8WUvrmll3kvToqMurD69CcgVmbQ56V7ZbvlzFi44E7BpnoaofScYLLtFWRjVdaHsohT6O/big==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@react-aria/ssr': 3.9.1(react@18.2.0)
+ '@react-stately/utils': 3.9.0(react@18.2.0)
+ '@react-types/shared': 3.22.0(react@18.2.0)
+ '@swc/helpers': 0.5.6
+ clsx: 2.1.0
+ react: 18.2.0
+ dev: false
+
+ /@react-stately/utils@3.0.0-nightly.2584(react@18.2.0):
+ resolution: {integrity: sha512-UOW2P+H3O7goB1mNEIwUdxr28CVHrKKvi+N1CQ0TGDwr+Bp6oIZK2aXE6aQluzgwZ36aRvLPW5dAoovpzTTcQQ==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@swc/helpers': 0.5.6
+ react: 18.2.0
+ dev: false
+
+ /@react-stately/utils@3.9.0(react@18.2.0):
+ resolution: {integrity: sha512-yPKFY1F88HxuZ15BG2qwAYxtpE4HnIU0Ofi4CuBE0xC6I8mwo4OQjDzi+DZjxQngM9D6AeTTD6F1V8gkozA0Gw==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ '@swc/helpers': 0.5.6
+ react: 18.2.0
+ dev: false
+
+ /@react-types/shared@3.0.0-nightly.2584(react@18.2.0):
+ resolution: {integrity: sha512-SVqvg7B3rtzN1ypQni5g6sfpUNf4wODRDtiOalBFSJ02YuaUIr7gXVjafPYIXOC1BkJbZtPun/Pv4mCwNHFNbA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ react: 18.2.0
+ dev: false
+
+ /@react-types/shared@3.22.0(react@18.2.0):
+ resolution: {integrity: sha512-yVOekZWbtSmmiThGEIARbBpnmUIuePFlLyctjvCbgJgGhz8JnEJOipLQ/a4anaWfzAgzSceQP8j/K+VOOePleA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
+ dependencies:
+ react: 18.2.0
+ dev: false
+
+ /@rollup/rollup-android-arm-eabi@4.9.6:
+ resolution: {integrity: sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-android-arm64@4.9.6:
+ resolution: {integrity: sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-darwin-arm64@4.9.6:
+ resolution: {integrity: sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-darwin-x64@4.9.6:
+ resolution: {integrity: sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-arm-gnueabihf@4.9.6:
+ resolution: {integrity: sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-arm64-gnu@4.9.6:
+ resolution: {integrity: sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-arm64-musl@4.9.6:
+ resolution: {integrity: sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-riscv64-gnu@4.9.6:
+ resolution: {integrity: sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-x64-gnu@4.9.6:
+ resolution: {integrity: sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-x64-musl@4.9.6:
+ resolution: {integrity: sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-win32-arm64-msvc@4.9.6:
+ resolution: {integrity: sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-win32-ia32-msvc@4.9.6:
+ resolution: {integrity: sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-win32-x64-msvc@4.9.6:
+ resolution: {integrity: sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
+ /@rushstack/eslint-patch@1.7.2:
+ resolution: {integrity: sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==}
+
+ /@sinclair/typebox@0.27.8:
+ resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
+ dev: true
+
+ /@sindresorhus/slugify@2.2.1:
+ resolution: {integrity: sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==}
+ engines: {node: '>=12'}
+ dependencies:
+ '@sindresorhus/transliterate': 1.6.0
+ escape-string-regexp: 5.0.0
+ dev: false
+
+ /@sindresorhus/transliterate@1.6.0:
+ resolution: {integrity: sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==}
+ engines: {node: '>=12'}
+ dependencies:
+ escape-string-regexp: 5.0.0
+ dev: false
+
+ /@swc/helpers@0.5.2:
+ resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==}
+ dependencies:
+ tslib: 2.6.2
+ dev: false
+
+ /@swc/helpers@0.5.6:
+ resolution: {integrity: sha512-aYX01Ke9hunpoCexYAgQucEpARGQ5w/cqHFrIR+e9gdKb1QWTsVJuTJ2ozQzIAxLyRQe/m+2RqzkyOOGiMKRQA==}
+ dependencies:
+ tslib: 2.6.2
+ dev: false
+
+ /@tanstack/react-virtual@3.0.0-beta.60(react@18.2.0):
+ resolution: {integrity: sha512-F0wL9+byp7lf/tH6U5LW0ZjBqs+hrMXJrj5xcIGcklI0pggvjzMNW9DdIBcyltPNr6hmHQ0wt8FDGe1n1ZAThA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ dependencies:
+ '@tanstack/virtual-core': 3.0.0-beta.60
+ react: 18.2.0
+ dev: false
+
+ /@tanstack/virtual-core@3.0.0-beta.60:
+ resolution: {integrity: sha512-QlCdhsV1+JIf0c0U6ge6SQmpwsyAT0oQaOSZk50AtEeAyQl9tQrd6qCHAslxQpgphrfe945abvKG8uYvw3hIGA==}
+ dev: false
+
+ /@types/acorn@4.0.6:
+ resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==}
+ dependencies:
+ '@types/estree': 1.0.5
+ dev: false
+
+ /@types/babel__core@7.20.5:
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+ dependencies:
+ '@babel/parser': 7.23.9
+ '@babel/types': 7.23.9
+ '@types/babel__generator': 7.6.8
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.20.5
+ dev: false
+
+ /@types/babel__generator@7.6.8:
+ resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==}
+ dependencies:
+ '@babel/types': 7.23.9
+ dev: false
+
+ /@types/babel__template@7.4.4:
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+ dependencies:
+ '@babel/parser': 7.23.9
+ '@babel/types': 7.23.9
+ dev: false
+
+ /@types/babel__traverse@7.20.5:
+ resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==}
+ dependencies:
+ '@babel/types': 7.23.9
+ dev: false
+
+ /@types/debug@4.1.12:
+ resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
+ dependencies:
+ '@types/ms': 0.7.34
+ dev: false
+
+ /@types/estree-jsx@1.0.4:
+ resolution: {integrity: sha512-5idy3hvI9lAMqsyilBM+N+boaCf1MgoefbDxN6KEO5aK17TOHwFAYT9sjxzeKAiIWRUBgLxmZ9mPcnzZXtTcRQ==}
+ dependencies:
+ '@types/estree': 1.0.5
+ dev: false
+
+ /@types/estree@1.0.5:
+ resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
+
+ /@types/hast@2.3.10:
+ resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
+ dependencies:
+ '@types/unist': 2.0.10
+ dev: false
+
+ /@types/istanbul-lib-coverage@2.0.6:
+ resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
+ dev: true
+
+ /@types/json5@0.0.29:
+ resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+
+ /@types/mdast@3.0.15:
+ resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==}
+ dependencies:
+ '@types/unist': 2.0.10
+ dev: false
+
+ /@types/mdast@4.0.3:
+ resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==}
+ dependencies:
+ '@types/unist': 3.0.2
+ dev: false
+
+ /@types/mdx@2.0.11:
+ resolution: {integrity: sha512-HM5bwOaIQJIQbAYfax35HCKxx7a3KrK3nBtIqJgSOitivTD1y3oW9P3rxY9RkXYPUk7y/AjAohfHKmFpGE79zw==}
+ dev: false
+
+ /@types/ms@0.7.34:
+ resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
+ dev: false
+
+ /@types/node@20.11.17:
+ resolution: {integrity: sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==}
+ dependencies:
+ undici-types: 5.26.5
+ dev: true
+
+ /@types/node@20.11.20:
+ resolution: {integrity: sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==}
+ dependencies:
+ undici-types: 5.26.5
+
+ /@types/node@20.3.3:
+ resolution: {integrity: sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==}
+ dev: false
+
+ /@types/postcss-import@14.0.3:
+ resolution: {integrity: sha512-raZhRVTf6Vw5+QbmQ7LOHSDML71A5rj4+EqDzAbrZPfxfoGzFxMHRCq16VlddGIZpHELw0BG4G0YE2ANkdZiIQ==}
+ dependencies:
+ postcss: 8.4.24
+ dev: true
+
+ /@types/prop-types@15.7.11:
+ resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
+
+ /@types/react-dom@18.2.19:
+ resolution: {integrity: sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==}
+ dependencies:
+ '@types/react': 18.2.55
+
+ /@types/react@18.2.55:
+ resolution: {integrity: sha512-Y2Tz5P4yz23brwm2d7jNon39qoAtMMmalOQv6+fEFt1mT+FcM3D841wDpoUvFXhaYenuROCy3FZYqdTjM7qVyA==}
+ dependencies:
+ '@types/prop-types': 15.7.11
+ '@types/scheduler': 0.16.8
+ csstype: 3.1.3
+
+ /@types/scheduler@0.16.8:
+ resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==}
+
+ /@types/unist@2.0.10:
+ resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==}
+ dev: false
+
+ /@types/unist@3.0.2:
+ resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==}
+ dev: false
+
+ /@typescript-eslint/parser@5.62.0(eslint@8.44.0)(typescript@5.3.3):
+ resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/scope-manager': 5.62.0
+ '@typescript-eslint/types': 5.62.0
+ '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3)
+ debug: 4.3.4
+ eslint: 8.44.0
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /@typescript-eslint/parser@6.21.0(eslint@8.44.0)(typescript@5.3.3):
+ resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^7.0.0 || ^8.0.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/scope-manager': 6.21.0
+ '@typescript-eslint/types': 6.21.0
+ '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3)
+ '@typescript-eslint/visitor-keys': 6.21.0
+ debug: 4.3.4
+ eslint: 8.44.0
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/scope-manager@5.62.0:
+ resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dependencies:
+ '@typescript-eslint/types': 5.62.0
+ '@typescript-eslint/visitor-keys': 5.62.0
+ dev: false
+
+ /@typescript-eslint/scope-manager@6.21.0:
+ resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dependencies:
+ '@typescript-eslint/types': 6.21.0
+ '@typescript-eslint/visitor-keys': 6.21.0
+ dev: true
+
+ /@typescript-eslint/types@5.62.0:
+ resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dev: false
+
+ /@typescript-eslint/types@6.21.0:
+ resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dev: true
+
+ /@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.3):
+ resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/types': 5.62.0
+ '@typescript-eslint/visitor-keys': 5.62.0
+ debug: 4.3.4
+ globby: 11.1.0
+ is-glob: 4.0.3
+ semver: 7.6.0
+ tsutils: 3.21.0(typescript@5.3.3)
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /@typescript-eslint/typescript-estree@6.21.0(typescript@5.3.3):
+ resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/types': 6.21.0
+ '@typescript-eslint/visitor-keys': 6.21.0
+ debug: 4.3.4
+ globby: 11.1.0
+ is-glob: 4.0.3
+ minimatch: 9.0.3
+ semver: 7.6.0
+ ts-api-utils: 1.2.1(typescript@5.3.3)
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/visitor-keys@5.62.0:
+ resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dependencies:
+ '@typescript-eslint/types': 5.62.0
+ eslint-visitor-keys: 3.4.3
+ dev: false
+
+ /@typescript-eslint/visitor-keys@6.21.0:
+ resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dependencies:
+ '@typescript-eslint/types': 6.21.0
+ eslint-visitor-keys: 3.4.3
+ dev: true
+
+ /@vitejs/plugin-react@4.2.1(vite@5.1.1):
+ resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0
+ dependencies:
+ '@babel/core': 7.23.9
+ '@babel/plugin-transform-react-jsx-self': 7.23.3(@babel/core@7.23.9)
+ '@babel/plugin-transform-react-jsx-source': 7.23.3(@babel/core@7.23.9)
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.14.0
+ vite: 5.1.1(@types/node@20.11.20)
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /@vitest/coverage-v8@1.2.2(vitest@1.2.2):
+ resolution: {integrity: sha512-IHyKnDz18SFclIEEAHb9Y4Uxx0sPKC2VO1kdDCs1BF6Ip4S8rQprs971zIsooLUn7Afs71GRxWMWpkCGZpRMhw==}
+ peerDependencies:
+ vitest: ^1.0.0
+ dependencies:
+ '@ampproject/remapping': 2.2.1
+ '@bcoe/v8-coverage': 0.2.3
+ debug: 4.3.4
+ istanbul-lib-coverage: 3.2.2
+ istanbul-lib-report: 3.0.1
+ istanbul-lib-source-maps: 4.0.1
+ istanbul-reports: 3.1.6
+ magic-string: 0.30.7
+ magicast: 0.3.3
+ picocolors: 1.0.0
+ std-env: 3.7.0
+ test-exclude: 6.0.0
+ v8-to-istanbul: 9.2.0
+ vitest: 1.2.2(@types/node@20.11.20)
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@vitest/expect@1.2.2:
+ resolution: {integrity: sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==}
+ dependencies:
+ '@vitest/spy': 1.2.2
+ '@vitest/utils': 1.2.2
+ chai: 4.4.1
+ dev: true
+
+ /@vitest/runner@1.2.2:
+ resolution: {integrity: sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==}
+ dependencies:
+ '@vitest/utils': 1.2.2
+ p-limit: 5.0.0
+ pathe: 1.1.2
+ dev: true
+
+ /@vitest/snapshot@1.2.2:
+ resolution: {integrity: sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==}
+ dependencies:
+ magic-string: 0.30.7
+ pathe: 1.1.2
+ pretty-format: 29.7.0
+ dev: true
+
+ /@vitest/spy@1.2.2:
+ resolution: {integrity: sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==}
+ dependencies:
+ tinyspy: 2.2.1
+ dev: true
+
+ /@vitest/utils@1.2.2:
+ resolution: {integrity: sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==}
+ dependencies:
+ diff-sequences: 29.6.3
+ estree-walker: 3.0.3
+ loupe: 2.3.7
+ pretty-format: 29.7.0
+ dev: true
+
+ /@webassemblyjs/ast@1.9.0:
+ resolution: {integrity: sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==}
+ dependencies:
+ '@webassemblyjs/helper-module-context': 1.9.0
+ '@webassemblyjs/helper-wasm-bytecode': 1.9.0
+ '@webassemblyjs/wast-parser': 1.9.0
+ dev: false
+
+ /@webassemblyjs/floating-point-hex-parser@1.9.0:
+ resolution: {integrity: sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==}
+ dev: false
+
+ /@webassemblyjs/helper-api-error@1.9.0:
+ resolution: {integrity: sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==}
+ dev: false
+
+ /@webassemblyjs/helper-buffer@1.9.0:
+ resolution: {integrity: sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==}
+ dev: false
+
+ /@webassemblyjs/helper-code-frame@1.9.0:
+ resolution: {integrity: sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==}
+ dependencies:
+ '@webassemblyjs/wast-printer': 1.9.0
+ dev: false
+
+ /@webassemblyjs/helper-fsm@1.9.0:
+ resolution: {integrity: sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==}
+ dev: false
+
+ /@webassemblyjs/helper-module-context@1.9.0:
+ resolution: {integrity: sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==}
+ dependencies:
+ '@webassemblyjs/ast': 1.9.0
+ dev: false
+
+ /@webassemblyjs/helper-wasm-bytecode@1.9.0:
+ resolution: {integrity: sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==}
+ dev: false
+
+ /@webassemblyjs/helper-wasm-section@1.9.0:
+ resolution: {integrity: sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==}
+ dependencies:
+ '@webassemblyjs/ast': 1.9.0
+ '@webassemblyjs/helper-buffer': 1.9.0
+ '@webassemblyjs/helper-wasm-bytecode': 1.9.0
+ '@webassemblyjs/wasm-gen': 1.9.0
+ dev: false
+
+ /@webassemblyjs/ieee754@1.9.0:
+ resolution: {integrity: sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==}
+ dependencies:
+ '@xtuc/ieee754': 1.2.0
+ dev: false
+
+ /@webassemblyjs/leb128@1.9.0:
+ resolution: {integrity: sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==}
+ dependencies:
+ '@xtuc/long': 4.2.2
+ dev: false
+
+ /@webassemblyjs/utf8@1.9.0:
+ resolution: {integrity: sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==}
+ dev: false
+
+ /@webassemblyjs/wasm-edit@1.9.0:
+ resolution: {integrity: sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==}
+ dependencies:
+ '@webassemblyjs/ast': 1.9.0
+ '@webassemblyjs/helper-buffer': 1.9.0
+ '@webassemblyjs/helper-wasm-bytecode': 1.9.0
+ '@webassemblyjs/helper-wasm-section': 1.9.0
+ '@webassemblyjs/wasm-gen': 1.9.0
+ '@webassemblyjs/wasm-opt': 1.9.0
+ '@webassemblyjs/wasm-parser': 1.9.0
+ '@webassemblyjs/wast-printer': 1.9.0
+ dev: false
+
+ /@webassemblyjs/wasm-gen@1.9.0:
+ resolution: {integrity: sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==}
+ dependencies:
+ '@webassemblyjs/ast': 1.9.0
+ '@webassemblyjs/helper-wasm-bytecode': 1.9.0
+ '@webassemblyjs/ieee754': 1.9.0
+ '@webassemblyjs/leb128': 1.9.0
+ '@webassemblyjs/utf8': 1.9.0
+ dev: false
+
+ /@webassemblyjs/wasm-opt@1.9.0:
+ resolution: {integrity: sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==}
+ dependencies:
+ '@webassemblyjs/ast': 1.9.0
+ '@webassemblyjs/helper-buffer': 1.9.0
+ '@webassemblyjs/wasm-gen': 1.9.0
+ '@webassemblyjs/wasm-parser': 1.9.0
+ dev: false
+
+ /@webassemblyjs/wasm-parser@1.9.0:
+ resolution: {integrity: sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==}
+ dependencies:
+ '@webassemblyjs/ast': 1.9.0
+ '@webassemblyjs/helper-api-error': 1.9.0
+ '@webassemblyjs/helper-wasm-bytecode': 1.9.0
+ '@webassemblyjs/ieee754': 1.9.0
+ '@webassemblyjs/leb128': 1.9.0
+ '@webassemblyjs/utf8': 1.9.0
+ dev: false
+
+ /@webassemblyjs/wast-parser@1.9.0:
+ resolution: {integrity: sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==}
+ dependencies:
+ '@webassemblyjs/ast': 1.9.0
+ '@webassemblyjs/floating-point-hex-parser': 1.9.0
+ '@webassemblyjs/helper-api-error': 1.9.0
+ '@webassemblyjs/helper-code-frame': 1.9.0
+ '@webassemblyjs/helper-fsm': 1.9.0
+ '@xtuc/long': 4.2.2
+ dev: false
+
+ /@webassemblyjs/wast-printer@1.9.0:
+ resolution: {integrity: sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==}
+ dependencies:
+ '@webassemblyjs/ast': 1.9.0
+ '@webassemblyjs/wast-parser': 1.9.0
+ '@xtuc/long': 4.2.2
+ dev: false
+
+ /@xtuc/ieee754@1.2.0:
+ resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
+ dev: false
+
+ /@xtuc/long@4.2.2:
+ resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
+ dev: false
+
+ /acorn-jsx@5.3.2(acorn@8.11.3):
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+ dependencies:
+ acorn: 8.11.3
+
+ /acorn-walk@8.3.2:
+ resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
+ engines: {node: '>=0.4.0'}
+ dev: true
+
+ /acorn@6.4.2:
+ resolution: {integrity: sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+ dev: false
+
+ /acorn@8.11.3:
+ resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ /ajv-errors@1.0.1(ajv@6.12.6):
+ resolution: {integrity: sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==}
+ peerDependencies:
+ ajv: '>=5.0.0'
+ dependencies:
+ ajv: 6.12.6
+ dev: false
+
+ /ajv-keywords@3.5.2(ajv@6.12.6):
+ resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
+ peerDependencies:
+ ajv: ^6.9.1
+ dependencies:
+ ajv: 6.12.6
+ dev: false
+
+ /ajv@6.12.6:
+ resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
+ /ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ /ansi-regex@6.0.1:
+ resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
+ engines: {node: '>=12'}
+ dev: true
+
+ /ansi-sequence-parser@1.1.1:
+ resolution: {integrity: sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==}
+ dev: false
+
+ /ansi-styles@3.2.1:
+ resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
+ engines: {node: '>=4'}
+ dependencies:
+ color-convert: 1.9.3
+ dev: false
+
+ /ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+ dependencies:
+ color-convert: 2.0.1
+
+ /ansi-styles@5.2.0:
+ resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /ansi-styles@6.2.1:
+ resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
+ engines: {node: '>=12'}
+ dev: true
+
+ /any-promise@1.3.0:
+ resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+ dev: true
+
+ /anymatch@2.0.0:
+ resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==}
+ requiresBuild: true
+ dependencies:
+ micromatch: 3.1.10
+ normalize-path: 2.1.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+ optional: true
+
+ /anymatch@3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+ requiresBuild: true
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+
+ /aproba@1.2.0:
+ resolution: {integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==}
+ dev: false
+
+ /argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ /aria-query@5.3.0:
+ resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
+ dependencies:
+ dequal: 2.0.3
+
+ /arr-diff@4.0.0:
+ resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /arr-flatten@1.1.0:
+ resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /arr-union@3.1.0:
+ resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /array-buffer-byte-length@1.0.1:
+ resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ is-array-buffer: 3.0.4
+
+ /array-includes@3.1.7:
+ resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ get-intrinsic: 1.2.4
+ is-string: 1.0.7
+
+ /array-union@2.1.0:
+ resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
+ engines: {node: '>=8'}
+
+ /array-unique@0.3.2:
+ resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /array.prototype.filter@1.0.3:
+ resolution: {integrity: sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ es-array-method-boxes-properly: 1.0.0
+ is-string: 1.0.7
+
+ /array.prototype.findlastindex@1.2.4:
+ resolution: {integrity: sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ es-errors: 1.3.0
+ es-shim-unscopables: 1.0.2
+
+ /array.prototype.flat@1.3.2:
+ resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ es-shim-unscopables: 1.0.2
+
+ /array.prototype.flatmap@1.3.2:
+ resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ es-shim-unscopables: 1.0.2
+
+ /array.prototype.tosorted@1.1.3:
+ resolution: {integrity: sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ es-errors: 1.3.0
+ es-shim-unscopables: 1.0.2
+
+ /arraybuffer.prototype.slice@1.0.3:
+ resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ array-buffer-byte-length: 1.0.1
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ es-errors: 1.3.0
+ get-intrinsic: 1.2.4
+ is-array-buffer: 3.0.4
+ is-shared-array-buffer: 1.0.2
+
+ /asn1.js@5.4.1:
+ resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==}
+ dependencies:
+ bn.js: 4.12.0
+ inherits: 2.0.4
+ minimalistic-assert: 1.0.1
+ safer-buffer: 2.1.2
+ dev: false
+
+ /assert@1.5.1:
+ resolution: {integrity: sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==}
+ dependencies:
+ object.assign: 4.1.5
+ util: 0.10.4
+ dev: false
+
+ /assertion-error@1.1.0:
+ resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
+ dev: true
+
+ /assign-symbols@1.0.0:
+ resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /ast-types-flow@0.0.8:
+ resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
+
+ /astring@1.8.6:
+ resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==}
+ hasBin: true
+ dev: false
+
+ /async-each@1.0.6:
+ resolution: {integrity: sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==}
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /asynciterator.prototype@1.0.0:
+ resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==}
+ dependencies:
+ has-symbols: 1.0.3
+
+ /atob@2.1.2:
+ resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}
+ engines: {node: '>= 4.5.0'}
+ hasBin: true
+ dev: false
+
+ /autoprefixer@10.4.14(postcss@8.4.24):
+ resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+ dependencies:
+ browserslist: 4.22.3
+ caniuse-lite: 1.0.30001585
+ fraction.js: 4.3.7
+ normalize-range: 0.1.2
+ picocolors: 1.0.0
+ postcss: 8.4.24
+ postcss-value-parser: 4.2.0
+ dev: false
+
+ /available-typed-arrays@1.0.6:
+ resolution: {integrity: sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==}
+ engines: {node: '>= 0.4'}
+
+ /axe-core@4.7.0:
+ resolution: {integrity: sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==}
+ engines: {node: '>=4'}
+
+ /axobject-query@3.2.1:
+ resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==}
+ dependencies:
+ dequal: 2.0.3
+
+ /bail@2.0.2:
+ resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
+ dev: false
+
+ /balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ /base64-js@1.5.1:
+ resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+ dev: false
+
+ /base@0.11.2:
+ resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ cache-base: 1.0.1
+ class-utils: 0.3.6
+ component-emitter: 1.3.1
+ define-property: 1.0.0
+ isobject: 3.0.1
+ mixin-deep: 1.3.2
+ pascalcase: 0.1.1
+ dev: false
+
+ /big.js@5.2.2:
+ resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==}
+ dev: false
+
+ /binary-extensions@1.13.1:
+ resolution: {integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==}
+ engines: {node: '>=0.10.0'}
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /binary-extensions@2.2.0:
+ resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
+ engines: {node: '>=8'}
+ requiresBuild: true
+
+ /bindings@1.5.0:
+ resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
+ requiresBuild: true
+ dependencies:
+ file-uri-to-path: 1.0.0
+ dev: false
+ optional: true
+
+ /bluebird@3.7.2:
+ resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
+ dev: false
+
+ /bn.js@4.12.0:
+ resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==}
+ dev: false
+
+ /bn.js@5.2.1:
+ resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==}
+ dev: false
+
+ /boolbase@1.0.0:
+ resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+ dev: false
+
+ /brace-expansion@1.1.11:
+ resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ /brace-expansion@2.0.1:
+ resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+ dependencies:
+ balanced-match: 1.0.2
+ dev: true
+
+ /braces@2.3.2:
+ resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ arr-flatten: 1.1.0
+ array-unique: 0.3.2
+ extend-shallow: 2.0.1
+ fill-range: 4.0.0
+ isobject: 3.0.1
+ repeat-element: 1.1.4
+ snapdragon: 0.8.2
+ snapdragon-node: 2.1.1
+ split-string: 3.1.0
+ to-regex: 3.0.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /braces@3.0.2:
+ resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
+ engines: {node: '>=8'}
+ dependencies:
+ fill-range: 7.0.1
+
+ /brorand@1.1.0:
+ resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==}
+ dev: false
+
+ /browserify-aes@1.2.0:
+ resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==}
+ dependencies:
+ buffer-xor: 1.0.3
+ cipher-base: 1.0.4
+ create-hash: 1.2.0
+ evp_bytestokey: 1.0.3
+ inherits: 2.0.4
+ safe-buffer: 5.2.1
+ dev: false
+
+ /browserify-cipher@1.0.1:
+ resolution: {integrity: sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==}
+ dependencies:
+ browserify-aes: 1.2.0
+ browserify-des: 1.0.2
+ evp_bytestokey: 1.0.3
+ dev: false
+
+ /browserify-des@1.0.2:
+ resolution: {integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==}
+ dependencies:
+ cipher-base: 1.0.4
+ des.js: 1.1.0
+ inherits: 2.0.4
+ safe-buffer: 5.2.1
+ dev: false
+
+ /browserify-rsa@4.1.0:
+ resolution: {integrity: sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==}
+ dependencies:
+ bn.js: 5.2.1
+ randombytes: 2.1.0
+ dev: false
+
+ /browserify-sign@4.2.2:
+ resolution: {integrity: sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==}
+ engines: {node: '>= 4'}
+ dependencies:
+ bn.js: 5.2.1
+ browserify-rsa: 4.1.0
+ create-hash: 1.2.0
+ create-hmac: 1.1.7
+ elliptic: 6.5.4
+ inherits: 2.0.4
+ parse-asn1: 5.1.6
+ readable-stream: 3.6.2
+ safe-buffer: 5.2.1
+ dev: false
+
+ /browserify-zlib@0.2.0:
+ resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==}
+ dependencies:
+ pako: 1.0.11
+ dev: false
+
+ /browserslist@4.22.3:
+ resolution: {integrity: sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+ dependencies:
+ caniuse-lite: 1.0.30001585
+ electron-to-chromium: 1.4.664
+ node-releases: 2.0.14
+ update-browserslist-db: 1.0.13(browserslist@4.22.3)
+ dev: false
+
+ /buffer-from@1.1.2:
+ resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+ dev: false
+
+ /buffer-xor@1.0.3:
+ resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==}
+ dev: false
+
+ /buffer@4.9.2:
+ resolution: {integrity: sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==}
+ dependencies:
+ base64-js: 1.5.1
+ ieee754: 1.2.1
+ isarray: 1.0.0
+ dev: false
+
+ /builtin-status-codes@3.0.0:
+ resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==}
+ dev: false
+
+ /bun@1.0.26:
+ resolution: {integrity: sha512-+aSzKvGLE9hWwCRDwvnhoMTDntCYEk5bKn16L0QGD0v630WJMPhazAMxaSJmfq6dSAtiqPpauPpmHbzwoGPjlQ==}
+ cpu: [arm64, x64]
+ os: [darwin, linux]
+ hasBin: true
+ requiresBuild: true
+ optionalDependencies:
+ '@oven/bun-darwin-aarch64': 1.0.26
+ '@oven/bun-darwin-x64': 1.0.26
+ '@oven/bun-darwin-x64-baseline': 1.0.26
+ '@oven/bun-linux-aarch64': 1.0.26
+ '@oven/bun-linux-x64': 1.0.26
+ '@oven/bun-linux-x64-baseline': 1.0.26
+ dev: true
+
+ /bundle-require@4.0.2(esbuild@0.19.12):
+ resolution: {integrity: sha512-jwzPOChofl67PSTW2SGubV9HBQAhhR2i6nskiOThauo9dzwDUgOWQScFVaJkjEfYX+UXiD+LEx8EblQMc2wIag==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ peerDependencies:
+ esbuild: '>=0.17'
+ dependencies:
+ esbuild: 0.19.12
+ load-tsconfig: 0.2.5
+ dev: true
+
+ /busboy@1.6.0:
+ resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
+ engines: {node: '>=10.16.0'}
+ dependencies:
+ streamsearch: 1.1.0
+ dev: false
+
+ /cac@6.7.14:
+ resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /cacache@12.0.4:
+ resolution: {integrity: sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==}
+ dependencies:
+ bluebird: 3.7.2
+ chownr: 1.1.4
+ figgy-pudding: 3.5.2
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ infer-owner: 1.0.4
+ lru-cache: 5.1.1
+ mississippi: 3.0.0
+ mkdirp: 0.5.6
+ move-concurrently: 1.0.1
+ promise-inflight: 1.0.1(bluebird@3.7.2)
+ rimraf: 2.7.1
+ ssri: 6.0.2
+ unique-filename: 1.1.1
+ y18n: 4.0.3
+ dev: false
+
+ /cache-base@1.0.1:
+ resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ collection-visit: 1.0.0
+ component-emitter: 1.3.1
+ get-value: 2.0.6
+ has-value: 1.0.0
+ isobject: 3.0.1
+ set-value: 2.0.1
+ to-object-path: 0.3.0
+ union-value: 1.0.1
+ unset-value: 1.0.0
+ dev: false
+
+ /call-bind@1.0.6:
+ resolution: {integrity: sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.2.4
+ set-function-length: 1.2.1
+
+ /callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ /caniuse-lite@1.0.30001585:
+ resolution: {integrity: sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==}
+ dev: false
+
+ /ccount@2.0.1:
+ resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
+ dev: false
+
+ /chai@4.4.1:
+ resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==}
+ engines: {node: '>=4'}
+ dependencies:
+ assertion-error: 1.1.0
+ check-error: 1.0.3
+ deep-eql: 4.1.3
+ get-func-name: 2.0.2
+ loupe: 2.3.7
+ pathval: 1.1.1
+ type-detect: 4.0.8
+ dev: true
+
+ /chalk@2.4.2:
+ resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
+ engines: {node: '>=4'}
+ dependencies:
+ ansi-styles: 3.2.1
+ escape-string-regexp: 1.0.5
+ supports-color: 5.5.0
+ dev: false
+
+ /chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ /character-entities-html4@2.1.0:
+ resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
+ dev: false
+
+ /character-entities-legacy@3.0.0:
+ resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
+ dev: false
+
+ /character-entities@2.0.2:
+ resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
+ dev: false
+
+ /character-reference-invalid@2.0.1:
+ resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
+ dev: false
+
+ /check-error@1.0.3:
+ resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
+ dependencies:
+ get-func-name: 2.0.2
+ dev: true
+
+ /chokidar@2.1.8:
+ resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==}
+ deprecated: Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies
+ requiresBuild: true
+ dependencies:
+ anymatch: 2.0.0
+ async-each: 1.0.6
+ braces: 2.3.2
+ glob-parent: 3.1.0
+ inherits: 2.0.4
+ is-binary-path: 1.0.1
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ path-is-absolute: 1.0.1
+ readdirp: 2.2.1
+ upath: 1.2.0
+ optionalDependencies:
+ fsevents: 1.2.13
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+ optional: true
+
+ /chokidar@3.6.0:
+ resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
+ engines: {node: '>= 8.10.0'}
+ requiresBuild: true
+ dependencies:
+ anymatch: 3.1.3
+ braces: 3.0.2
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ /chownr@1.1.4:
+ resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
+ dev: false
+
+ /chrome-trace-event@1.0.3:
+ resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==}
+ engines: {node: '>=6.0'}
+ dev: false
+
+ /cipher-base@1.0.4:
+ resolution: {integrity: sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==}
+ dependencies:
+ inherits: 2.0.4
+ safe-buffer: 5.2.1
+ dev: false
+
+ /class-utils@0.3.6:
+ resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ arr-union: 3.1.0
+ define-property: 0.2.5
+ isobject: 3.0.1
+ static-extend: 0.1.2
+ dev: false
+
+ /client-only@0.0.1:
+ resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+ dev: false
+
+ /clsx@1.2.1:
+ resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
+ engines: {node: '>=6'}
+ dev: false
+
+ /clsx@2.1.0:
+ resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==}
+ engines: {node: '>=6'}
+ dev: false
+
+ /collection-visit@1.0.0:
+ resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ map-visit: 1.0.0
+ object-visit: 1.0.1
+ dev: false
+
+ /color-convert@1.9.3:
+ resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
+ dependencies:
+ color-name: 1.1.3
+ dev: false
+
+ /color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+ dependencies:
+ color-name: 1.1.4
+
+ /color-name@1.1.3:
+ resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
+ dev: false
+
+ /color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ /comma-separated-tokens@2.0.3:
+ resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
+ dev: false
+
+ /commander@2.20.3:
+ resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
+ dev: false
+
+ /commander@4.1.1:
+ resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+ engines: {node: '>= 6'}
+ dev: true
+
+ /commondir@1.0.1:
+ resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
+ dev: false
+
+ /component-emitter@1.3.1:
+ resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
+ dev: false
+
+ /concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ /concat-stream@1.6.2:
+ resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==}
+ engines: {'0': node >= 0.8}
+ dependencies:
+ buffer-from: 1.1.2
+ inherits: 2.0.4
+ readable-stream: 2.3.8
+ typedarray: 0.0.6
+ dev: false
+
+ /console-browserify@1.2.0:
+ resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==}
+ dev: false
+
+ /constants-browserify@1.0.0:
+ resolution: {integrity: sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==}
+ dev: false
+
+ /convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ /copy-concurrently@1.0.5:
+ resolution: {integrity: sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==}
+ dependencies:
+ aproba: 1.2.0
+ fs-write-stream-atomic: 1.0.10
+ iferr: 0.1.5
+ mkdirp: 0.5.6
+ rimraf: 2.7.1
+ run-queue: 1.0.3
+ dev: false
+
+ /copy-descriptor@0.1.1:
+ resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /core-util-is@1.0.3:
+ resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
+ dev: false
+
+ /create-ecdh@4.0.4:
+ resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==}
+ dependencies:
+ bn.js: 4.12.0
+ elliptic: 6.5.4
+ dev: false
+
+ /create-hash@1.2.0:
+ resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==}
+ dependencies:
+ cipher-base: 1.0.4
+ inherits: 2.0.4
+ md5.js: 1.3.5
+ ripemd160: 2.0.2
+ sha.js: 2.4.11
+ dev: false
+
+ /create-hmac@1.1.7:
+ resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==}
+ dependencies:
+ cipher-base: 1.0.4
+ create-hash: 1.2.0
+ inherits: 2.0.4
+ ripemd160: 2.0.2
+ safe-buffer: 5.2.1
+ sha.js: 2.4.11
+ dev: false
+
+ /cross-spawn@7.0.3:
+ resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
+ engines: {node: '>= 8'}
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ /crypto-browserify@3.12.0:
+ resolution: {integrity: sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==}
+ dependencies:
+ browserify-cipher: 1.0.1
+ browserify-sign: 4.2.2
+ create-ecdh: 4.0.4
+ create-hash: 1.2.0
+ create-hmac: 1.1.7
+ diffie-hellman: 5.0.3
+ inherits: 2.0.4
+ pbkdf2: 3.1.2
+ public-encrypt: 4.0.3
+ randombytes: 2.1.0
+ randomfill: 1.0.4
+ dev: false
+
+ /css-selector-parser@1.4.1:
+ resolution: {integrity: sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g==}
+ dev: false
+
+ /csstype@3.1.3:
+ resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+
+ /cyclist@1.0.2:
+ resolution: {integrity: sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA==}
+ dev: false
+
+ /damerau-levenshtein@1.0.8:
+ resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
+
+ /debug@2.6.9:
+ resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.0.0
+ dev: false
+
+ /debug@3.2.7:
+ resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.1.2
+
+ /debug@4.3.4:
+ resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.1.2
+
+ /decode-named-character-reference@1.0.2:
+ resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
+ dependencies:
+ character-entities: 2.0.2
+ dev: false
+
+ /decode-uri-component@0.2.2:
+ resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
+ engines: {node: '>=0.10'}
+ dev: false
+
+ /deep-eql@4.1.3:
+ resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
+ engines: {node: '>=6'}
+ dependencies:
+ type-detect: 4.0.8
+ dev: true
+
+ /deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+ /define-data-property@1.1.2:
+ resolution: {integrity: sha512-SRtsSqsDbgpJBbW3pABMCOt6rQyeM8s8RiyeSN8jYG8sYmt/kGJejbydttUsnDs1tadr19tvhT4ShwMyoqAm4g==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ es-errors: 1.3.0
+ get-intrinsic: 1.2.4
+ gopd: 1.0.1
+ has-property-descriptors: 1.0.1
+
+ /define-properties@1.2.1:
+ resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ define-data-property: 1.1.2
+ has-property-descriptors: 1.0.1
+ object-keys: 1.1.1
+
+ /define-property@0.2.5:
+ resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-descriptor: 0.1.7
+ dev: false
+
+ /define-property@1.0.0:
+ resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-descriptor: 1.0.3
+ dev: false
+
+ /define-property@2.0.2:
+ resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-descriptor: 1.0.3
+ isobject: 3.0.1
+ dev: false
+
+ /dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
+ /des.js@1.1.0:
+ resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==}
+ dependencies:
+ inherits: 2.0.4
+ minimalistic-assert: 1.0.1
+ dev: false
+
+ /detect-libc@1.0.3:
+ resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
+ engines: {node: '>=0.10'}
+ hasBin: true
+ dev: false
+
+ /diff-sequences@29.6.3:
+ resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ dev: true
+
+ /diff@5.1.0:
+ resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
+ engines: {node: '>=0.3.1'}
+ dev: false
+
+ /diffie-hellman@5.0.3:
+ resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==}
+ dependencies:
+ bn.js: 4.12.0
+ miller-rabin: 4.0.1
+ randombytes: 2.1.0
+ dev: false
+
+ /dir-glob@3.0.1:
+ resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
+ engines: {node: '>=8'}
+ dependencies:
+ path-type: 4.0.0
+
+ /doctrine@2.1.0:
+ resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ esutils: 2.0.3
+
+ /doctrine@3.0.0:
+ resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
+ engines: {node: '>=6.0.0'}
+ dependencies:
+ esutils: 2.0.3
+
+ /domain-browser@1.2.0:
+ resolution: {integrity: sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==}
+ engines: {node: '>=0.4', npm: '>=1.2'}
+ dev: false
+
+ /duplexify@3.7.1:
+ resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==}
+ dependencies:
+ end-of-stream: 1.4.4
+ inherits: 2.0.4
+ readable-stream: 2.3.8
+ stream-shift: 1.0.3
+ dev: false
+
+ /eastasianwidth@0.2.0:
+ resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+ dev: true
+
+ /electron-to-chromium@1.4.664:
+ resolution: {integrity: sha512-k9VKKSkOSNPvSckZgDDl/IQx45E1quMjX8QfLzUsAs/zve8AyFDK+ByRynSP/OfEfryiKHpQeMf00z0leLCc3A==}
+ dev: false
+
+ /elliptic@6.5.4:
+ resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==}
+ dependencies:
+ bn.js: 4.12.0
+ brorand: 1.1.0
+ hash.js: 1.1.7
+ hmac-drbg: 1.0.1
+ inherits: 2.0.4
+ minimalistic-assert: 1.0.1
+ minimalistic-crypto-utils: 1.0.1
+ dev: false
+
+ /emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+ dev: true
+
+ /emoji-regex@9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+
+ /emojis-list@3.0.0:
+ resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
+ engines: {node: '>= 4'}
+ dev: false
+
+ /end-of-stream@1.4.4:
+ resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
+ dependencies:
+ once: 1.4.0
+ dev: false
+
+ /enhanced-resolve@4.5.0:
+ resolution: {integrity: sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ graceful-fs: 4.2.11
+ memory-fs: 0.5.0
+ tapable: 1.1.3
+ dev: false
+
+ /enhanced-resolve@5.15.0:
+ resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==}
+ engines: {node: '>=10.13.0'}
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.2.1
+
+ /errno@0.1.8:
+ resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
+ hasBin: true
+ dependencies:
+ prr: 1.0.1
+ dev: false
+
+ /es-abstract@1.22.3:
+ resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ array-buffer-byte-length: 1.0.1
+ arraybuffer.prototype.slice: 1.0.3
+ available-typed-arrays: 1.0.6
+ call-bind: 1.0.6
+ es-set-tostringtag: 2.0.2
+ es-to-primitive: 1.2.1
+ function.prototype.name: 1.1.6
+ get-intrinsic: 1.2.4
+ get-symbol-description: 1.0.2
+ globalthis: 1.0.3
+ gopd: 1.0.1
+ has-property-descriptors: 1.0.1
+ has-proto: 1.0.1
+ has-symbols: 1.0.3
+ hasown: 2.0.0
+ internal-slot: 1.0.7
+ is-array-buffer: 3.0.4
+ is-callable: 1.2.7
+ is-negative-zero: 2.0.2
+ is-regex: 1.1.4
+ is-shared-array-buffer: 1.0.2
+ is-string: 1.0.7
+ is-typed-array: 1.1.13
+ is-weakref: 1.0.2
+ object-inspect: 1.13.1
+ object-keys: 1.1.1
+ object.assign: 4.1.5
+ regexp.prototype.flags: 1.5.1
+ safe-array-concat: 1.1.0
+ safe-regex-test: 1.0.3
+ string.prototype.trim: 1.2.8
+ string.prototype.trimend: 1.0.7
+ string.prototype.trimstart: 1.0.7
+ typed-array-buffer: 1.0.1
+ typed-array-byte-length: 1.0.0
+ typed-array-byte-offset: 1.0.0
+ typed-array-length: 1.0.4
+ unbox-primitive: 1.0.2
+ which-typed-array: 1.1.14
+
+ /es-array-method-boxes-properly@1.0.0:
+ resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==}
+
+ /es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ /es-iterator-helpers@1.0.15:
+ resolution: {integrity: sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==}
+ dependencies:
+ asynciterator.prototype: 1.0.0
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ es-set-tostringtag: 2.0.2
+ function-bind: 1.1.2
+ get-intrinsic: 1.2.4
+ globalthis: 1.0.3
+ has-property-descriptors: 1.0.1
+ has-proto: 1.0.1
+ has-symbols: 1.0.3
+ internal-slot: 1.0.7
+ iterator.prototype: 1.1.2
+ safe-array-concat: 1.1.0
+
+ /es-set-tostringtag@2.0.2:
+ resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ get-intrinsic: 1.2.4
+ has-tostringtag: 1.0.2
+ hasown: 2.0.0
+
+ /es-shim-unscopables@1.0.2:
+ resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==}
+ dependencies:
+ hasown: 2.0.0
+
+ /es-to-primitive@1.2.1:
+ resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ is-callable: 1.2.7
+ is-date-object: 1.0.5
+ is-symbol: 1.0.4
+
+ /esbuild@0.19.12:
+ resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==}
+ engines: {node: '>=12'}
+ hasBin: true
+ requiresBuild: true
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.19.12
+ '@esbuild/android-arm': 0.19.12
+ '@esbuild/android-arm64': 0.19.12
+ '@esbuild/android-x64': 0.19.12
+ '@esbuild/darwin-arm64': 0.19.12
+ '@esbuild/darwin-x64': 0.19.12
+ '@esbuild/freebsd-arm64': 0.19.12
+ '@esbuild/freebsd-x64': 0.19.12
+ '@esbuild/linux-arm': 0.19.12
+ '@esbuild/linux-arm64': 0.19.12
+ '@esbuild/linux-ia32': 0.19.12
+ '@esbuild/linux-loong64': 0.19.12
+ '@esbuild/linux-mips64el': 0.19.12
+ '@esbuild/linux-ppc64': 0.19.12
+ '@esbuild/linux-riscv64': 0.19.12
+ '@esbuild/linux-s390x': 0.19.12
+ '@esbuild/linux-x64': 0.19.12
+ '@esbuild/netbsd-x64': 0.19.12
+ '@esbuild/openbsd-x64': 0.19.12
+ '@esbuild/sunos-x64': 0.19.12
+ '@esbuild/win32-arm64': 0.19.12
+ '@esbuild/win32-ia32': 0.19.12
+ '@esbuild/win32-x64': 0.19.12
+
+ /escalade@3.1.2:
+ resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
+ engines: {node: '>=6'}
+ dev: false
+
+ /escape-string-regexp@1.0.5:
+ resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
+ engines: {node: '>=0.8.0'}
+ dev: false
+
+ /escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ /escape-string-regexp@5.0.0:
+ resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
+ engines: {node: '>=12'}
+ dev: false
+
+ /eslint-config-next@13.4.8(eslint@8.44.0)(typescript@5.3.3):
+ resolution: {integrity: sha512-2hE0b6lHuhtHBX8VgEXi8v4G8PVrPUBMOSLCTq8qtcQ2qQOX7+uBOLK2kU4FD2qDZzyXNlhmuH+WLT5ptY4XLA==}
+ peerDependencies:
+ eslint: ^7.23.0 || ^8.0.0
+ typescript: '>=3.3.1'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@next/eslint-plugin-next': 13.4.8
+ '@rushstack/eslint-patch': 1.7.2
+ '@typescript-eslint/parser': 5.62.0(eslint@8.44.0)(typescript@5.3.3)
+ eslint: 8.44.0
+ eslint-import-resolver-node: 0.3.9
+ eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.44.0)
+ eslint-plugin-import: 2.29.1(eslint@8.44.0)
+ eslint-plugin-jsx-a11y: 6.8.0(eslint@8.44.0)
+ eslint-plugin-react: 7.33.2(eslint@8.44.0)
+ eslint-plugin-react-hooks: 4.6.0(eslint@8.44.0)
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - eslint-import-resolver-webpack
+ - supports-color
+ dev: false
+
+ /eslint-config-next@14.1.0(eslint@8.44.0)(typescript@5.3.3):
+ resolution: {integrity: sha512-SBX2ed7DoRFXC6CQSLc/SbLY9Ut6HxNB2wPTcoIWjUMd7aF7O/SIE7111L8FdZ9TXsNV4pulUDnfthpyPtbFUg==}
+ peerDependencies:
+ eslint: ^7.23.0 || ^8.0.0
+ typescript: '>=3.3.1'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@next/eslint-plugin-next': 14.1.0
+ '@rushstack/eslint-patch': 1.7.2
+ '@typescript-eslint/parser': 6.21.0(eslint@8.44.0)(typescript@5.3.3)
+ eslint: 8.44.0
+ eslint-import-resolver-node: 0.3.9
+ eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.44.0)
+ eslint-plugin-import: 2.29.1(eslint@8.44.0)
+ eslint-plugin-jsx-a11y: 6.8.0(eslint@8.44.0)
+ eslint-plugin-react: 7.33.2(eslint@8.44.0)
+ eslint-plugin-react-hooks: 4.6.0(eslint@8.44.0)
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - eslint-import-resolver-webpack
+ - supports-color
+ dev: true
+
+ /eslint-import-resolver-node@0.3.9:
+ resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
+ dependencies:
+ debug: 3.2.7
+ is-core-module: 2.13.1
+ resolve: 1.22.8
+ transitivePeerDependencies:
+ - supports-color
+
+ /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.44.0):
+ resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ eslint: '*'
+ eslint-plugin-import: '*'
+ dependencies:
+ debug: 4.3.4
+ enhanced-resolve: 5.15.0
+ eslint: 8.44.0
+ eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.44.0)
+ eslint-plugin-import: 2.29.1(eslint@8.44.0)
+ fast-glob: 3.3.2
+ get-tsconfig: 4.7.2
+ is-core-module: 2.13.1
+ is-glob: 4.0.3
+ transitivePeerDependencies:
+ - '@typescript-eslint/parser'
+ - eslint-import-resolver-node
+ - eslint-import-resolver-webpack
+ - supports-color
+ dev: false
+
+ /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.44.0):
+ resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ eslint: '*'
+ eslint-plugin-import: '*'
+ dependencies:
+ debug: 4.3.4
+ enhanced-resolve: 5.15.0
+ eslint: 8.44.0
+ eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.44.0)
+ eslint-plugin-import: 2.29.1(eslint@8.44.0)
+ fast-glob: 3.3.2
+ get-tsconfig: 4.7.2
+ is-core-module: 2.13.1
+ is-glob: 4.0.3
+ transitivePeerDependencies:
+ - '@typescript-eslint/parser'
+ - eslint-import-resolver-node
+ - eslint-import-resolver-webpack
+ - supports-color
+ dev: true
+
+ /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.44.0):
+ resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
+ dependencies:
+ '@typescript-eslint/parser': 5.62.0(eslint@8.44.0)(typescript@5.3.3)
+ debug: 3.2.7
+ eslint: 8.44.0
+ eslint-import-resolver-node: 0.3.9
+ eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.44.0)
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.44.0):
+ resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
+ dependencies:
+ '@typescript-eslint/parser': 6.21.0(eslint@8.44.0)(typescript@5.3.3)
+ debug: 3.2.7
+ eslint: 8.44.0
+ eslint-import-resolver-node: 0.3.9
+ eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.44.0)
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /eslint-module-utils@2.8.0(eslint-import-resolver-node@0.3.9)(eslint@8.44.0):
+ resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
+ dependencies:
+ debug: 3.2.7
+ eslint: 8.44.0
+ eslint-import-resolver-node: 0.3.9
+ transitivePeerDependencies:
+ - supports-color
+
+ /eslint-plugin-import@2.29.1(eslint@8.44.0):
+ resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ dependencies:
+ array-includes: 3.1.7
+ array.prototype.findlastindex: 1.2.4
+ array.prototype.flat: 1.3.2
+ array.prototype.flatmap: 1.3.2
+ debug: 3.2.7
+ doctrine: 2.1.0
+ eslint: 8.44.0
+ eslint-import-resolver-node: 0.3.9
+ eslint-module-utils: 2.8.0(eslint-import-resolver-node@0.3.9)(eslint@8.44.0)
+ hasown: 2.0.0
+ is-core-module: 2.13.1
+ is-glob: 4.0.3
+ minimatch: 3.1.2
+ object.fromentries: 2.0.7
+ object.groupby: 1.0.2
+ object.values: 1.1.7
+ semver: 6.3.1
+ tsconfig-paths: 3.15.0
+ transitivePeerDependencies:
+ - eslint-import-resolver-typescript
+ - eslint-import-resolver-webpack
+ - supports-color
+
+ /eslint-plugin-jsx-a11y@6.8.0(eslint@8.44.0):
+ resolution: {integrity: sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
+ dependencies:
+ '@babel/runtime': 7.23.9
+ aria-query: 5.3.0
+ array-includes: 3.1.7
+ array.prototype.flatmap: 1.3.2
+ ast-types-flow: 0.0.8
+ axe-core: 4.7.0
+ axobject-query: 3.2.1
+ damerau-levenshtein: 1.0.8
+ emoji-regex: 9.2.2
+ es-iterator-helpers: 1.0.15
+ eslint: 8.44.0
+ hasown: 2.0.0
+ jsx-ast-utils: 3.3.5
+ language-tags: 1.0.9
+ minimatch: 3.1.2
+ object.entries: 1.1.7
+ object.fromentries: 2.0.7
+
+ /eslint-plugin-react-hooks@4.6.0(eslint@8.44.0):
+ resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
+ dependencies:
+ eslint: 8.44.0
+
+ /eslint-plugin-react@7.33.2(eslint@8.44.0):
+ resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
+ dependencies:
+ array-includes: 3.1.7
+ array.prototype.flatmap: 1.3.2
+ array.prototype.tosorted: 1.1.3
+ doctrine: 2.1.0
+ es-iterator-helpers: 1.0.15
+ eslint: 8.44.0
+ estraverse: 5.3.0
+ jsx-ast-utils: 3.3.5
+ minimatch: 3.1.2
+ object.entries: 1.1.7
+ object.fromentries: 2.0.7
+ object.hasown: 1.1.3
+ object.values: 1.1.7
+ prop-types: 15.8.1
+ resolve: 2.0.0-next.5
+ semver: 6.3.1
+ string.prototype.matchall: 4.0.10
+
+ /eslint-scope@4.0.3:
+ resolution: {integrity: sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==}
+ engines: {node: '>=4.0.0'}
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 4.3.0
+ dev: false
+
+ /eslint-scope@7.2.2:
+ resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
+ /eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ /eslint@8.44.0:
+ resolution: {integrity: sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ hasBin: true
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.44.0)
+ '@eslint-community/regexpp': 4.10.0
+ '@eslint/eslintrc': 2.1.4
+ '@eslint/js': 8.44.0
+ '@humanwhocodes/config-array': 0.11.14
+ '@humanwhocodes/module-importer': 1.0.1
+ '@nodelib/fs.walk': 1.2.8
+ ajv: 6.12.6
+ chalk: 4.1.2
+ cross-spawn: 7.0.3
+ debug: 4.3.4
+ doctrine: 3.0.0
+ escape-string-regexp: 4.0.0
+ eslint-scope: 7.2.2
+ eslint-visitor-keys: 3.4.3
+ espree: 9.6.1
+ esquery: 1.5.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 6.0.1
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ globals: 13.24.0
+ graphemer: 1.4.0
+ ignore: 5.3.1
+ import-fresh: 3.3.0
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ is-path-inside: 3.0.3
+ js-yaml: 4.1.0
+ json-stable-stringify-without-jsonify: 1.0.1
+ levn: 0.4.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.2
+ natural-compare: 1.4.0
+ optionator: 0.9.3
+ strip-ansi: 6.0.1
+ strip-json-comments: 3.1.1
+ text-table: 0.2.0
+ transitivePeerDependencies:
+ - supports-color
+
+ /espree@9.6.1:
+ resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dependencies:
+ acorn: 8.11.3
+ acorn-jsx: 5.3.2(acorn@8.11.3)
+ eslint-visitor-keys: 3.4.3
+
+ /esquery@1.5.0:
+ resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
+ engines: {node: '>=0.10'}
+ dependencies:
+ estraverse: 5.3.0
+
+ /esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+ dependencies:
+ estraverse: 5.3.0
+
+ /estraverse@4.3.0:
+ resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
+ engines: {node: '>=4.0'}
+ dev: false
+
+ /estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+
+ /estree-util-attach-comments@2.1.1:
+ resolution: {integrity: sha512-+5Ba/xGGS6mnwFbXIuQiDPTbuTxuMCooq3arVv7gPZtYpjp+VXH/NkHAP35OOefPhNG/UGqU3vt/LTABwcHX0w==}
+ dependencies:
+ '@types/estree': 1.0.5
+ dev: false
+
+ /estree-util-build-jsx@2.2.2:
+ resolution: {integrity: sha512-m56vOXcOBuaF+Igpb9OPAy7f9w9OIkb5yhjsZuaPm7HoGi4oTOQi0h2+yZ+AtKklYFZ+rPC4n0wYCJCEU1ONqg==}
+ dependencies:
+ '@types/estree-jsx': 1.0.4
+ estree-util-is-identifier-name: 2.1.0
+ estree-walker: 3.0.3
+ dev: false
+
+ /estree-util-is-identifier-name@2.1.0:
+ resolution: {integrity: sha512-bEN9VHRyXAUOjkKVQVvArFym08BTWB0aJPppZZr0UNyAqWsLaVfAqP7hbaTJjzHifmB5ebnR8Wm7r7yGN/HonQ==}
+ dev: false
+
+ /estree-util-to-js@1.2.0:
+ resolution: {integrity: sha512-IzU74r1PK5IMMGZXUVZbmiu4A1uhiPgW5hm1GjcOfr4ZzHaMPpLNJjR7HjXiIOzi25nZDrgFTobHTkV5Q6ITjA==}
+ dependencies:
+ '@types/estree-jsx': 1.0.4
+ astring: 1.8.6
+ source-map: 0.7.4
+ dev: false
+
+ /estree-util-visit@1.2.1:
+ resolution: {integrity: sha512-xbgqcrkIVbIG+lI/gzbvd9SGTJL4zqJKBFttUl5pP27KhAjtMKbX/mQXJ7qgyXpMgVy/zvpm0xoQQaGL8OloOw==}
+ dependencies:
+ '@types/estree-jsx': 1.0.4
+ '@types/unist': 2.0.10
+ dev: false
+
+ /estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+ dependencies:
+ '@types/estree': 1.0.5
+
+ /esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
+ /events@3.3.0:
+ resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
+ engines: {node: '>=0.8.x'}
+ dev: false
+
+ /evp_bytestokey@1.0.3:
+ resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==}
+ dependencies:
+ md5.js: 1.3.5
+ safe-buffer: 5.2.1
+ dev: false
+
+ /execa@5.1.1:
+ resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
+ engines: {node: '>=10'}
+ dependencies:
+ cross-spawn: 7.0.3
+ get-stream: 6.0.1
+ human-signals: 2.1.0
+ is-stream: 2.0.1
+ merge-stream: 2.0.0
+ npm-run-path: 4.0.1
+ onetime: 5.1.2
+ signal-exit: 3.0.7
+ strip-final-newline: 2.0.0
+ dev: true
+
+ /execa@8.0.1:
+ resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
+ engines: {node: '>=16.17'}
+ dependencies:
+ cross-spawn: 7.0.3
+ get-stream: 8.0.1
+ human-signals: 5.0.0
+ is-stream: 3.0.0
+ merge-stream: 2.0.0
+ npm-run-path: 5.2.0
+ onetime: 6.0.0
+ signal-exit: 4.1.0
+ strip-final-newline: 3.0.0
+ dev: true
+
+ /expand-brackets@2.1.4:
+ resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ debug: 2.6.9
+ define-property: 0.2.5
+ extend-shallow: 2.0.1
+ posix-character-classes: 0.1.1
+ regex-not: 1.0.2
+ snapdragon: 0.8.2
+ to-regex: 3.0.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /extend-shallow@2.0.1:
+ resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-extendable: 0.1.1
+ dev: false
+
+ /extend-shallow@3.0.2:
+ resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ assign-symbols: 1.0.0
+ is-extendable: 1.0.1
+ dev: false
+
+ /extend@3.0.2:
+ resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
+ dev: false
+
+ /extglob@2.0.4:
+ resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ array-unique: 0.3.2
+ define-property: 1.0.0
+ expand-brackets: 2.1.4
+ extend-shallow: 2.0.1
+ fragment-cache: 0.2.1
+ regex-not: 1.0.2
+ snapdragon: 0.8.2
+ to-regex: 3.0.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ /fast-glob@3.3.2:
+ resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
+ engines: {node: '>=8.6.0'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.5
+
+ /fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ /fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+ /fastq@1.17.1:
+ resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
+ dependencies:
+ reusify: 1.0.4
+
+ /figgy-pudding@3.5.2:
+ resolution: {integrity: sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==}
+ deprecated: This module is no longer supported.
+ dev: false
+
+ /file-entry-cache@6.0.1:
+ resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
+ engines: {node: ^10.12.0 || >=12.0.0}
+ dependencies:
+ flat-cache: 3.2.0
+
+ /file-uri-to-path@1.0.0:
+ resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /fill-range@4.0.0:
+ resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ extend-shallow: 2.0.1
+ is-number: 3.0.0
+ repeat-string: 1.6.1
+ to-regex-range: 2.1.1
+ dev: false
+
+ /fill-range@7.0.1:
+ resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ to-regex-range: 5.0.1
+
+ /find-cache-dir@2.1.0:
+ resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==}
+ engines: {node: '>=6'}
+ dependencies:
+ commondir: 1.0.1
+ make-dir: 2.1.0
+ pkg-dir: 3.0.0
+ dev: false
+
+ /find-up@3.0.0:
+ resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
+ engines: {node: '>=6'}
+ dependencies:
+ locate-path: 3.0.0
+ dev: false
+
+ /find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
+ /flat-cache@3.2.0:
+ resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
+ engines: {node: ^10.12.0 || >=12.0.0}
+ dependencies:
+ flatted: 3.2.9
+ keyv: 4.5.4
+ rimraf: 3.0.2
+
+ /flatted@3.2.9:
+ resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==}
+
+ /flush-write-stream@1.1.1:
+ resolution: {integrity: sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==}
+ dependencies:
+ inherits: 2.0.4
+ readable-stream: 2.3.8
+ dev: false
+
+ /for-each@0.3.3:
+ resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
+ dependencies:
+ is-callable: 1.2.7
+
+ /for-in@1.0.2:
+ resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /foreground-child@3.1.1:
+ resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==}
+ engines: {node: '>=14'}
+ dependencies:
+ cross-spawn: 7.0.3
+ signal-exit: 4.1.0
+ dev: true
+
+ /fraction.js@4.3.7:
+ resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
+ dev: false
+
+ /fragment-cache@0.2.1:
+ resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ map-cache: 0.2.2
+ dev: false
+
+ /from2@2.3.0:
+ resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==}
+ dependencies:
+ inherits: 2.0.4
+ readable-stream: 2.3.8
+ dev: false
+
+ /fs-write-stream-atomic@1.0.10:
+ resolution: {integrity: sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA==}
+ dependencies:
+ graceful-fs: 4.2.11
+ iferr: 0.1.5
+ imurmurhash: 0.1.4
+ readable-stream: 2.3.8
+ dev: false
+
+ /fs.realpath@1.0.0:
+ resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
+ /fsevents@1.2.13:
+ resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==}
+ engines: {node: '>= 4.0'}
+ os: [darwin]
+ deprecated: The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2
+ requiresBuild: true
+ dependencies:
+ bindings: 1.5.0
+ nan: 2.18.0
+ dev: false
+ optional: true
+
+ /fsevents@2.3.2:
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+ requiresBuild: true
+ optional: true
+
+ /function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ /function.prototype.name@1.1.6:
+ resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ functions-have-names: 1.2.3
+
+ /functions-have-names@1.2.3:
+ resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+
+ /gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+ dev: false
+
+ /get-func-name@2.0.2:
+ resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
+ dev: true
+
+ /get-intrinsic@1.2.4:
+ resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ has-proto: 1.0.1
+ has-symbols: 1.0.3
+ hasown: 2.0.0
+
+ /get-stream@6.0.1:
+ resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /get-stream@8.0.1:
+ resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
+ engines: {node: '>=16'}
+ dev: true
+
+ /get-symbol-description@1.0.2:
+ resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ es-errors: 1.3.0
+ get-intrinsic: 1.2.4
+
+ /get-tsconfig@4.7.2:
+ resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==}
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
+ /get-value@2.0.6:
+ resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /glob-parent@3.1.0:
+ resolution: {integrity: sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==}
+ requiresBuild: true
+ dependencies:
+ is-glob: 3.1.0
+ path-dirname: 1.0.2
+ dev: false
+ optional: true
+
+ /glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+ dependencies:
+ is-glob: 4.0.3
+
+ /glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+ dependencies:
+ is-glob: 4.0.3
+
+ /glob@10.3.10:
+ resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ hasBin: true
+ dependencies:
+ foreground-child: 3.1.1
+ jackspeak: 2.3.6
+ minimatch: 9.0.3
+ minipass: 7.0.4
+ path-scurry: 1.10.1
+ dev: true
+
+ /glob@7.1.7:
+ resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==}
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.2
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+ dev: false
+
+ /glob@7.2.3:
+ resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.2
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+
+ /globals@11.12.0:
+ resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
+ engines: {node: '>=4'}
+ dev: false
+
+ /globals@13.24.0:
+ resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ type-fest: 0.20.2
+
+ /globalthis@1.0.3:
+ resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ define-properties: 1.2.1
+
+ /globby@11.1.0:
+ resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
+ engines: {node: '>=10'}
+ dependencies:
+ array-union: 2.1.0
+ dir-glob: 3.0.1
+ fast-glob: 3.3.2
+ ignore: 5.3.1
+ merge2: 1.4.1
+ slash: 3.0.0
+
+ /gopd@1.0.1:
+ resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
+ dependencies:
+ get-intrinsic: 1.2.4
+
+ /graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ /graphemer@1.4.0:
+ resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
+
+ /handlebars@4.7.8:
+ resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==}
+ engines: {node: '>=0.4.7'}
+ hasBin: true
+ dependencies:
+ minimist: 1.2.8
+ neo-async: 2.6.2
+ source-map: 0.6.1
+ wordwrap: 1.0.0
+ optionalDependencies:
+ uglify-js: 3.17.4
+ dev: true
+
+ /has-bigints@1.0.2:
+ resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
+
+ /has-flag@3.0.0:
+ resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
+ engines: {node: '>=4'}
+ dev: false
+
+ /has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ /has-property-descriptors@1.0.1:
+ resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==}
+ dependencies:
+ get-intrinsic: 1.2.4
+
+ /has-proto@1.0.1:
+ resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==}
+ engines: {node: '>= 0.4'}
+
+ /has-symbols@1.0.3:
+ resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
+ engines: {node: '>= 0.4'}
+
+ /has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-symbols: 1.0.3
+
+ /has-value@0.3.1:
+ resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ get-value: 2.0.6
+ has-values: 0.1.4
+ isobject: 2.1.0
+ dev: false
+
+ /has-value@1.0.0:
+ resolution: {integrity: sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ get-value: 2.0.6
+ has-values: 1.0.0
+ isobject: 3.0.1
+ dev: false
+
+ /has-values@0.1.4:
+ resolution: {integrity: sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /has-values@1.0.0:
+ resolution: {integrity: sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-number: 3.0.0
+ kind-of: 4.0.0
+ dev: false
+
+ /hash-base@3.1.0:
+ resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==}
+ engines: {node: '>=4'}
+ dependencies:
+ inherits: 2.0.4
+ readable-stream: 3.6.2
+ safe-buffer: 5.2.1
+ dev: false
+
+ /hash.js@1.1.7:
+ resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==}
+ dependencies:
+ inherits: 2.0.4
+ minimalistic-assert: 1.0.1
+ dev: false
+
+ /hasown@2.0.0:
+ resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ function-bind: 1.1.2
+
+ /hast-util-to-estree@2.3.3:
+ resolution: {integrity: sha512-ihhPIUPxN0v0w6M5+IiAZZrn0LH2uZomeWwhn7uP7avZC6TE7lIiEh2yBMPr5+zi1aUCXq6VoYRgs2Bw9xmycQ==}
+ dependencies:
+ '@types/estree': 1.0.5
+ '@types/estree-jsx': 1.0.4
+ '@types/hast': 2.3.10
+ '@types/unist': 2.0.10
+ comma-separated-tokens: 2.0.3
+ estree-util-attach-comments: 2.1.1
+ estree-util-is-identifier-name: 2.1.0
+ hast-util-whitespace: 2.0.1
+ mdast-util-mdx-expression: 1.3.2
+ mdast-util-mdxjs-esm: 1.3.1
+ property-information: 6.4.1
+ space-separated-tokens: 2.0.2
+ style-to-object: 0.4.4
+ unist-util-position: 4.0.4
+ zwitch: 2.0.4
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /hast-util-whitespace@2.0.1:
+ resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==}
+ dev: false
+
+ /hmac-drbg@1.0.1:
+ resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==}
+ dependencies:
+ hash.js: 1.1.7
+ minimalistic-assert: 1.0.1
+ minimalistic-crypto-utils: 1.0.1
+ dev: false
+
+ /html-escaper@2.0.2:
+ resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+ dev: true
+
+ /https-browserify@1.0.0:
+ resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==}
+ dev: false
+
+ /human-signals@2.1.0:
+ resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
+ engines: {node: '>=10.17.0'}
+ dev: true
+
+ /human-signals@5.0.0:
+ resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
+ engines: {node: '>=16.17.0'}
+ dev: true
+
+ /ieee754@1.2.1:
+ resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+ dev: false
+
+ /iferr@0.1.5:
+ resolution: {integrity: sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==}
+ dev: false
+
+ /ignore@5.3.1:
+ resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
+ engines: {node: '>= 4'}
+
+ /import-fresh@3.3.0:
+ resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
+ engines: {node: '>=6'}
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ /imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ /indent-string@5.0.0:
+ resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==}
+ engines: {node: '>=12'}
+ dev: false
+
+ /infer-owner@1.0.4:
+ resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==}
+ dev: false
+
+ /inflight@1.0.6:
+ resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+
+ /inherits@2.0.3:
+ resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==}
+ dev: false
+
+ /inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ /inline-style-parser@0.1.1:
+ resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==}
+ dev: false
+
+ /internal-slot@1.0.7:
+ resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ es-errors: 1.3.0
+ hasown: 2.0.0
+ side-channel: 1.0.5
+
+ /is-accessor-descriptor@1.0.1:
+ resolution: {integrity: sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==}
+ engines: {node: '>= 0.10'}
+ dependencies:
+ hasown: 2.0.0
+ dev: false
+
+ /is-alphabetical@2.0.1:
+ resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
+ dev: false
+
+ /is-alphanumerical@2.0.1:
+ resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
+ dependencies:
+ is-alphabetical: 2.0.1
+ is-decimal: 2.0.1
+ dev: false
+
+ /is-array-buffer@3.0.4:
+ resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ get-intrinsic: 1.2.4
+
+ /is-async-function@2.0.0:
+ resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.2
+
+ /is-bigint@1.0.4:
+ resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
+ dependencies:
+ has-bigints: 1.0.2
+
+ /is-binary-path@1.0.1:
+ resolution: {integrity: sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==}
+ engines: {node: '>=0.10.0'}
+ requiresBuild: true
+ dependencies:
+ binary-extensions: 1.13.1
+ dev: false
+ optional: true
+
+ /is-binary-path@2.1.0:
+ resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+ engines: {node: '>=8'}
+ requiresBuild: true
+ dependencies:
+ binary-extensions: 2.2.0
+
+ /is-boolean-object@1.1.2:
+ resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ has-tostringtag: 1.0.2
+
+ /is-buffer@1.1.6:
+ resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
+ dev: false
+
+ /is-buffer@2.0.5:
+ resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
+ engines: {node: '>=4'}
+ dev: false
+
+ /is-callable@1.2.7:
+ resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+ engines: {node: '>= 0.4'}
+
+ /is-core-module@2.13.1:
+ resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
+ dependencies:
+ hasown: 2.0.0
+
+ /is-data-descriptor@1.0.1:
+ resolution: {integrity: sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ hasown: 2.0.0
+ dev: false
+
+ /is-date-object@1.0.5:
+ resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.2
+
+ /is-decimal@2.0.1:
+ resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
+ dev: false
+
+ /is-descriptor@0.1.7:
+ resolution: {integrity: sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ is-accessor-descriptor: 1.0.1
+ is-data-descriptor: 1.0.1
+ dev: false
+
+ /is-descriptor@1.0.3:
+ resolution: {integrity: sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ is-accessor-descriptor: 1.0.1
+ is-data-descriptor: 1.0.1
+ dev: false
+
+ /is-extendable@0.1.1:
+ resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /is-extendable@1.0.1:
+ resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-plain-object: 2.0.4
+ dev: false
+
+ /is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ /is-finalizationregistry@1.0.2:
+ resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==}
+ dependencies:
+ call-bind: 1.0.6
+
+ /is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /is-generator-function@1.0.10:
+ resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.2
+
+ /is-glob@3.1.0:
+ resolution: {integrity: sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==}
+ engines: {node: '>=0.10.0'}
+ requiresBuild: true
+ dependencies:
+ is-extglob: 2.1.1
+ dev: false
+ optional: true
+
+ /is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-extglob: 2.1.1
+
+ /is-hexadecimal@2.0.1:
+ resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
+ dev: false
+
+ /is-map@2.0.2:
+ resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==}
+
+ /is-negative-zero@2.0.2:
+ resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
+ engines: {node: '>= 0.4'}
+
+ /is-number-object@1.0.7:
+ resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.2
+
+ /is-number@3.0.0:
+ resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ kind-of: 3.2.2
+ dev: false
+
+ /is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ /is-path-inside@3.0.3:
+ resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
+ engines: {node: '>=8'}
+
+ /is-plain-obj@4.1.0:
+ resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
+ engines: {node: '>=12'}
+ dev: false
+
+ /is-plain-object@2.0.4:
+ resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ isobject: 3.0.1
+ dev: false
+
+ /is-reference@3.0.2:
+ resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==}
+ dependencies:
+ '@types/estree': 1.0.5
+ dev: false
+
+ /is-regex@1.1.4:
+ resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ has-tostringtag: 1.0.2
+
+ /is-set@2.0.2:
+ resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==}
+
+ /is-shared-array-buffer@1.0.2:
+ resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
+ dependencies:
+ call-bind: 1.0.6
+
+ /is-stream@2.0.1:
+ resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /is-stream@3.0.0:
+ resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ dev: true
+
+ /is-string@1.0.7:
+ resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.2
+
+ /is-symbol@1.0.4:
+ resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-symbols: 1.0.3
+
+ /is-typed-array@1.1.13:
+ resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ which-typed-array: 1.1.14
+
+ /is-weakmap@2.0.1:
+ resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==}
+
+ /is-weakref@1.0.2:
+ resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
+ dependencies:
+ call-bind: 1.0.6
+
+ /is-weakset@2.0.2:
+ resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==}
+ dependencies:
+ call-bind: 1.0.6
+ get-intrinsic: 1.2.4
+
+ /is-windows@1.0.2:
+ resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /is-wsl@1.1.0:
+ resolution: {integrity: sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==}
+ engines: {node: '>=4'}
+ dev: false
+
+ /isarray@1.0.0:
+ resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
+ dev: false
+
+ /isarray@2.0.5:
+ resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+
+ /isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ /isobject@2.1.0:
+ resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ isarray: 1.0.0
+ dev: false
+
+ /isobject@3.0.1:
+ resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /istanbul-lib-coverage@3.2.2:
+ resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /istanbul-lib-report@3.0.1:
+ resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
+ engines: {node: '>=10'}
+ dependencies:
+ istanbul-lib-coverage: 3.2.2
+ make-dir: 4.0.0
+ supports-color: 7.2.0
+ dev: true
+
+ /istanbul-lib-source-maps@4.0.1:
+ resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
+ engines: {node: '>=10'}
+ dependencies:
+ debug: 4.3.4
+ istanbul-lib-coverage: 3.2.2
+ source-map: 0.6.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /istanbul-reports@3.1.6:
+ resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==}
+ engines: {node: '>=8'}
+ dependencies:
+ html-escaper: 2.0.2
+ istanbul-lib-report: 3.0.1
+ dev: true
+
+ /iterator.prototype@1.1.2:
+ resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==}
+ dependencies:
+ define-properties: 1.2.1
+ get-intrinsic: 1.2.4
+ has-symbols: 1.0.3
+ reflect.getprototypeof: 1.0.5
+ set-function-name: 2.0.1
+
+ /jackspeak@2.3.6:
+ resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
+ engines: {node: '>=14'}
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+ optionalDependencies:
+ '@pkgjs/parseargs': 0.11.0
+ dev: true
+
+ /joycon@3.1.1:
+ resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ /js-yaml@4.1.0:
+ resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+ hasBin: true
+ dependencies:
+ argparse: 2.0.1
+
+ /jsesc@2.5.2:
+ resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
+ engines: {node: '>=4'}
+ hasBin: true
+ dev: false
+
+ /json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+ /json-parse-better-errors@1.0.2:
+ resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==}
+ dev: false
+
+ /json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+ /json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+ /json5@1.0.2:
+ resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
+ hasBin: true
+ dependencies:
+ minimist: 1.2.8
+
+ /json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+ dev: false
+
+ /jsonc-parser@3.2.1:
+ resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==}
+ dev: true
+
+ /jsx-ast-utils@3.3.5:
+ resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
+ engines: {node: '>=4.0'}
+ dependencies:
+ array-includes: 3.1.7
+ array.prototype.flat: 1.3.2
+ object.assign: 4.1.5
+ object.values: 1.1.7
+
+ /keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+ dependencies:
+ json-buffer: 3.0.1
+
+ /kind-of@3.2.2:
+ resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-buffer: 1.1.6
+ dev: false
+
+ /kind-of@4.0.0:
+ resolution: {integrity: sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-buffer: 1.1.6
+ dev: false
+
+ /kind-of@6.0.3:
+ resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /kleur@4.1.5:
+ resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
+ engines: {node: '>=6'}
+ dev: false
+
+ /language-subtag-registry@0.3.22:
+ resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==}
+
+ /language-tags@1.0.9:
+ resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==}
+ engines: {node: '>=0.10'}
+ dependencies:
+ language-subtag-registry: 0.3.22
+
+ /levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ /lightningcss-darwin-arm64@1.24.0:
+ resolution: {integrity: sha512-rTNPkEiynOu4CfGdd5ZfVOQe2gd2idfQd4EfX1l2ZUUwd+2SwSdbb7cG4rlwfnZckbzCAygm85xkpekRE5/wFw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /lightningcss-darwin-x64@1.24.0:
+ resolution: {integrity: sha512-4KCeF2RJjzp9xdGY8zIH68CUtptEg8uz8PfkHvsIdrP4t9t5CIgfDBhiB8AmuO75N6SofdmZexDZIKdy9vA7Ww==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /lightningcss-freebsd-x64@1.24.0:
+ resolution: {integrity: sha512-FJAYlek1wXuVTsncNU0C6YD41q126dXcIUm97KAccMn9C4s/JfLSqGWT2gIzAblavPFkyGG2gIADTWp3uWfN1g==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /lightningcss-linux-arm-gnueabihf@1.24.0:
+ resolution: {integrity: sha512-N55K6JqzMx7C0hYUu1YmWqhkHwzEJlkQRMA6phY65noO0I1LOAvP4wBIoFWrzRE+O6zL0RmXJ2xppqyTbk3sYw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /lightningcss-linux-arm64-gnu@1.24.0:
+ resolution: {integrity: sha512-MqqUB2TpYtFWeBvvf5KExDdClU3YGLW5bHKs50uKKootcvG9KoS7wYwd5UichS+W3mYLc5yXUPGD1DNWbLiYKw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /lightningcss-linux-arm64-musl@1.24.0:
+ resolution: {integrity: sha512-5wn4d9tFwa5bS1ao9mLexYVJdh3nn09HNIipsII6ZF7z9ZA5J4dOEhMgKoeCl891axTGTUYd8Kxn+Hn3XUSYRQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /lightningcss-linux-x64-gnu@1.24.0:
+ resolution: {integrity: sha512-3j5MdTh+LSDF3o6uDwRjRUgw4J+IfDCVtdkUrJvKxL79qBLUujXY7CTe5X3IQDDLKEe/3wu49r8JKgxr0MfjbQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /lightningcss-linux-x64-musl@1.24.0:
+ resolution: {integrity: sha512-HI+rNnvaLz0o36z6Ki0gyG5igVGrJmzczxA5fznr6eFTj3cHORoR/j2q8ivMzNFR4UKJDkTWUH5LMhacwOHWBA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /lightningcss-win32-x64-msvc@1.24.0:
+ resolution: {integrity: sha512-oeije/t7OZ5N9vSs6amyW/34wIYoBCpE6HUlsSKcP2SR1CVgx9oKEM00GtQmtqNnYiMIfsSm7+ppMb4NLtD5vg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /lightningcss@1.24.0:
+ resolution: {integrity: sha512-y36QEEDVx4IM7/yIZNsZJMRREIu26WzTsauIysf5s76YeCmlSbRZS7aC97IGPuoFRnyZ5Wx43OBsQBFB5Ne7ng==}
+ engines: {node: '>= 12.0.0'}
+ dependencies:
+ detect-libc: 1.0.3
+ optionalDependencies:
+ lightningcss-darwin-arm64: 1.24.0
+ lightningcss-darwin-x64: 1.24.0
+ lightningcss-freebsd-x64: 1.24.0
+ lightningcss-linux-arm-gnueabihf: 1.24.0
+ lightningcss-linux-arm64-gnu: 1.24.0
+ lightningcss-linux-arm64-musl: 1.24.0
+ lightningcss-linux-x64-gnu: 1.24.0
+ lightningcss-linux-x64-musl: 1.24.0
+ lightningcss-win32-x64-msvc: 1.24.0
+ dev: false
+
+ /lilconfig@3.0.0:
+ resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==}
+ engines: {node: '>=14'}
+ dev: true
+
+ /lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+ dev: true
+
+ /load-tsconfig@0.2.5:
+ resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ dev: true
+
+ /loader-runner@2.4.0:
+ resolution: {integrity: sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==}
+ engines: {node: '>=4.3.0 <5.0.0 || >=5.10'}
+ dev: false
+
+ /loader-utils@1.4.2:
+ resolution: {integrity: sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==}
+ engines: {node: '>=4.0.0'}
+ dependencies:
+ big.js: 5.2.2
+ emojis-list: 3.0.0
+ json5: 1.0.2
+ dev: false
+
+ /local-pkg@0.5.0:
+ resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
+ engines: {node: '>=14'}
+ dependencies:
+ mlly: 1.5.0
+ pkg-types: 1.0.3
+ dev: true
+
+ /locate-path@3.0.0:
+ resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
+ engines: {node: '>=6'}
+ dependencies:
+ p-locate: 3.0.0
+ path-exists: 3.0.0
+ dev: false
+
+ /locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+ dependencies:
+ p-locate: 5.0.0
+
+ /lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ /lodash.sortby@4.7.0:
+ resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==}
+ dev: true
+
+ /longest-streak@3.1.0:
+ resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
+ dev: false
+
+ /loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+ dependencies:
+ js-tokens: 4.0.0
+
+ /loupe@2.3.7:
+ resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
+ dependencies:
+ get-func-name: 2.0.2
+ dev: true
+
+ /lru-cache@10.2.0:
+ resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==}
+ engines: {node: 14 || >=16.14}
+ dev: true
+
+ /lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+ dependencies:
+ yallist: 3.1.1
+ dev: false
+
+ /lru-cache@6.0.0:
+ resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
+ engines: {node: '>=10'}
+ dependencies:
+ yallist: 4.0.0
+
+ /magic-string@0.30.7:
+ resolution: {integrity: sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==}
+ engines: {node: '>=12'}
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.4.15
+ dev: true
+
+ /magicast@0.3.3:
+ resolution: {integrity: sha512-ZbrP1Qxnpoes8sz47AM0z08U+jW6TyRgZzcWy3Ma3vDhJttwMwAFDMMQFobwdBxByBD46JYmxRzeF7w2+wJEuw==}
+ dependencies:
+ '@babel/parser': 7.23.9
+ '@babel/types': 7.23.9
+ source-map-js: 1.0.2
+ dev: true
+
+ /make-dir@2.1.0:
+ resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
+ engines: {node: '>=6'}
+ dependencies:
+ pify: 4.0.1
+ semver: 5.7.2
+ dev: false
+
+ /make-dir@4.0.0:
+ resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
+ engines: {node: '>=10'}
+ dependencies:
+ semver: 7.6.0
+ dev: true
+
+ /map-cache@0.2.2:
+ resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /map-visit@1.0.0:
+ resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ object-visit: 1.0.1
+ dev: false
+
+ /markdown-extensions@1.1.1:
+ resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /markdown-table@3.0.3:
+ resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
+ dev: false
+
+ /md5.js@1.3.5:
+ resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==}
+ dependencies:
+ hash-base: 3.1.0
+ inherits: 2.0.4
+ safe-buffer: 5.2.1
+ dev: false
+
+ /mdast-util-definitions@5.1.2:
+ resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==}
+ dependencies:
+ '@types/mdast': 3.0.15
+ '@types/unist': 2.0.10
+ unist-util-visit: 4.1.2
+ dev: false
+
+ /mdast-util-find-and-replace@2.2.2:
+ resolution: {integrity: sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==}
+ dependencies:
+ '@types/mdast': 3.0.15
+ escape-string-regexp: 5.0.0
+ unist-util-is: 5.2.1
+ unist-util-visit-parents: 5.1.3
+ dev: false
+
+ /mdast-util-from-markdown@1.3.1:
+ resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==}
+ dependencies:
+ '@types/mdast': 3.0.15
+ '@types/unist': 2.0.10
+ decode-named-character-reference: 1.0.2
+ mdast-util-to-string: 3.2.0
+ micromark: 3.2.0
+ micromark-util-decode-numeric-character-reference: 1.1.0
+ micromark-util-decode-string: 1.1.0
+ micromark-util-normalize-identifier: 1.1.0
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ unist-util-stringify-position: 3.0.3
+ uvu: 0.5.6
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /mdast-util-gfm-autolink-literal@1.0.3:
+ resolution: {integrity: sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==}
+ dependencies:
+ '@types/mdast': 3.0.15
+ ccount: 2.0.1
+ mdast-util-find-and-replace: 2.2.2
+ micromark-util-character: 1.2.0
+ dev: false
+
+ /mdast-util-gfm-footnote@1.0.2:
+ resolution: {integrity: sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==}
+ dependencies:
+ '@types/mdast': 3.0.15
+ mdast-util-to-markdown: 1.5.0
+ micromark-util-normalize-identifier: 1.1.0
+ dev: false
+
+ /mdast-util-gfm-strikethrough@1.0.3:
+ resolution: {integrity: sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==}
+ dependencies:
+ '@types/mdast': 3.0.15
+ mdast-util-to-markdown: 1.5.0
+ dev: false
+
+ /mdast-util-gfm-table@1.0.7:
+ resolution: {integrity: sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==}
+ dependencies:
+ '@types/mdast': 3.0.15
+ markdown-table: 3.0.3
+ mdast-util-from-markdown: 1.3.1
+ mdast-util-to-markdown: 1.5.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /mdast-util-gfm-task-list-item@1.0.2:
+ resolution: {integrity: sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==}
+ dependencies:
+ '@types/mdast': 3.0.15
+ mdast-util-to-markdown: 1.5.0
+ dev: false
+
+ /mdast-util-gfm@2.0.2:
+ resolution: {integrity: sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==}
+ dependencies:
+ mdast-util-from-markdown: 1.3.1
+ mdast-util-gfm-autolink-literal: 1.0.3
+ mdast-util-gfm-footnote: 1.0.2
+ mdast-util-gfm-strikethrough: 1.0.3
+ mdast-util-gfm-table: 1.0.7
+ mdast-util-gfm-task-list-item: 1.0.2
+ mdast-util-to-markdown: 1.5.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /mdast-util-mdx-expression@1.3.2:
+ resolution: {integrity: sha512-xIPmR5ReJDu/DHH1OoIT1HkuybIfRGYRywC+gJtI7qHjCJp/M9jrmBEJW22O8lskDWm562BX2W8TiAwRTb0rKA==}
+ dependencies:
+ '@types/estree-jsx': 1.0.4
+ '@types/hast': 2.3.10
+ '@types/mdast': 3.0.15
+ mdast-util-from-markdown: 1.3.1
+ mdast-util-to-markdown: 1.5.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /mdast-util-mdx-jsx@2.1.4:
+ resolution: {integrity: sha512-DtMn9CmVhVzZx3f+optVDF8yFgQVt7FghCRNdlIaS3X5Bnym3hZwPbg/XW86vdpKjlc1PVj26SpnLGeJBXD3JA==}
+ dependencies:
+ '@types/estree-jsx': 1.0.4
+ '@types/hast': 2.3.10
+ '@types/mdast': 3.0.15
+ '@types/unist': 2.0.10
+ ccount: 2.0.1
+ mdast-util-from-markdown: 1.3.1
+ mdast-util-to-markdown: 1.5.0
+ parse-entities: 4.0.1
+ stringify-entities: 4.0.3
+ unist-util-remove-position: 4.0.2
+ unist-util-stringify-position: 3.0.3
+ vfile-message: 3.1.4
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /mdast-util-mdx@2.0.1:
+ resolution: {integrity: sha512-38w5y+r8nyKlGvNjSEqWrhG0w5PmnRA+wnBvm+ulYCct7nsGYhFVb0lljS9bQav4psDAS1eGkP2LMVcZBi/aqw==}
+ dependencies:
+ mdast-util-from-markdown: 1.3.1
+ mdast-util-mdx-expression: 1.3.2
+ mdast-util-mdx-jsx: 2.1.4
+ mdast-util-mdxjs-esm: 1.3.1
+ mdast-util-to-markdown: 1.5.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /mdast-util-mdxjs-esm@1.3.1:
+ resolution: {integrity: sha512-SXqglS0HrEvSdUEfoXFtcg7DRl7S2cwOXc7jkuusG472Mmjag34DUDeOJUZtl+BVnyeO1frIgVpHlNRWc2gk/w==}
+ dependencies:
+ '@types/estree-jsx': 1.0.4
+ '@types/hast': 2.3.10
+ '@types/mdast': 3.0.15
+ mdast-util-from-markdown: 1.3.1
+ mdast-util-to-markdown: 1.5.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /mdast-util-phrasing@3.0.1:
+ resolution: {integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==}
+ dependencies:
+ '@types/mdast': 3.0.15
+ unist-util-is: 5.2.1
+ dev: false
+
+ /mdast-util-to-hast@12.3.0:
+ resolution: {integrity: sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==}
+ dependencies:
+ '@types/hast': 2.3.10
+ '@types/mdast': 3.0.15
+ mdast-util-definitions: 5.1.2
+ micromark-util-sanitize-uri: 1.2.0
+ trim-lines: 3.0.1
+ unist-util-generated: 2.0.1
+ unist-util-position: 4.0.4
+ unist-util-visit: 4.1.2
+ dev: false
+
+ /mdast-util-to-markdown@1.5.0:
+ resolution: {integrity: sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==}
+ dependencies:
+ '@types/mdast': 3.0.15
+ '@types/unist': 2.0.10
+ longest-streak: 3.1.0
+ mdast-util-phrasing: 3.0.1
+ mdast-util-to-string: 3.2.0
+ micromark-util-decode-string: 1.1.0
+ unist-util-visit: 4.1.2
+ zwitch: 2.0.4
+ dev: false
+
+ /mdast-util-to-string@3.2.0:
+ resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==}
+ dependencies:
+ '@types/mdast': 3.0.15
+ dev: false
+
+ /mdast-util-to-string@4.0.0:
+ resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
+ dependencies:
+ '@types/mdast': 4.0.3
+ dev: false
+
+ /memory-fs@0.4.1:
+ resolution: {integrity: sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ==}
+ dependencies:
+ errno: 0.1.8
+ readable-stream: 2.3.8
+ dev: false
+
+ /memory-fs@0.5.0:
+ resolution: {integrity: sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==}
+ engines: {node: '>=4.3.0 <5.0.0 || >=5.10'}
+ dependencies:
+ errno: 0.1.8
+ readable-stream: 2.3.8
+ dev: false
+
+ /merge-stream@2.0.0:
+ resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+ dev: true
+
+ /merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ /micromark-core-commonmark@1.1.0:
+ resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==}
+ dependencies:
+ decode-named-character-reference: 1.0.2
+ micromark-factory-destination: 1.1.0
+ micromark-factory-label: 1.1.0
+ micromark-factory-space: 1.1.0
+ micromark-factory-title: 1.1.0
+ micromark-factory-whitespace: 1.1.0
+ micromark-util-character: 1.2.0
+ micromark-util-chunked: 1.1.0
+ micromark-util-classify-character: 1.1.0
+ micromark-util-html-tag-name: 1.2.0
+ micromark-util-normalize-identifier: 1.1.0
+ micromark-util-resolve-all: 1.1.0
+ micromark-util-subtokenize: 1.1.0
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ uvu: 0.5.6
+ dev: false
+
+ /micromark-extension-gfm-autolink-literal@1.0.5:
+ resolution: {integrity: sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==}
+ dependencies:
+ micromark-util-character: 1.2.0
+ micromark-util-sanitize-uri: 1.2.0
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ dev: false
+
+ /micromark-extension-gfm-footnote@1.1.2:
+ resolution: {integrity: sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==}
+ dependencies:
+ micromark-core-commonmark: 1.1.0
+ micromark-factory-space: 1.1.0
+ micromark-util-character: 1.2.0
+ micromark-util-normalize-identifier: 1.1.0
+ micromark-util-sanitize-uri: 1.2.0
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ uvu: 0.5.6
+ dev: false
+
+ /micromark-extension-gfm-strikethrough@1.0.7:
+ resolution: {integrity: sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==}
+ dependencies:
+ micromark-util-chunked: 1.1.0
+ micromark-util-classify-character: 1.1.0
+ micromark-util-resolve-all: 1.1.0
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ uvu: 0.5.6
+ dev: false
+
+ /micromark-extension-gfm-table@1.0.7:
+ resolution: {integrity: sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==}
+ dependencies:
+ micromark-factory-space: 1.1.0
+ micromark-util-character: 1.2.0
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ uvu: 0.5.6
+ dev: false
+
+ /micromark-extension-gfm-tagfilter@1.0.2:
+ resolution: {integrity: sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==}
+ dependencies:
+ micromark-util-types: 1.1.0
+ dev: false
+
+ /micromark-extension-gfm-task-list-item@1.0.5:
+ resolution: {integrity: sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==}
+ dependencies:
+ micromark-factory-space: 1.1.0
+ micromark-util-character: 1.2.0
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ uvu: 0.5.6
+ dev: false
+
+ /micromark-extension-gfm@2.0.3:
+ resolution: {integrity: sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==}
+ dependencies:
+ micromark-extension-gfm-autolink-literal: 1.0.5
+ micromark-extension-gfm-footnote: 1.1.2
+ micromark-extension-gfm-strikethrough: 1.0.7
+ micromark-extension-gfm-table: 1.0.7
+ micromark-extension-gfm-tagfilter: 1.0.2
+ micromark-extension-gfm-task-list-item: 1.0.5
+ micromark-util-combine-extensions: 1.1.0
+ micromark-util-types: 1.1.0
+ dev: false
+
+ /micromark-extension-mdx-expression@1.0.8:
+ resolution: {integrity: sha512-zZpeQtc5wfWKdzDsHRBY003H2Smg+PUi2REhqgIhdzAa5xonhP03FcXxqFSerFiNUr5AWmHpaNPQTBVOS4lrXw==}
+ dependencies:
+ '@types/estree': 1.0.5
+ micromark-factory-mdx-expression: 1.0.9
+ micromark-factory-space: 1.1.0
+ micromark-util-character: 1.2.0
+ micromark-util-events-to-acorn: 1.2.3
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ uvu: 0.5.6
+ dev: false
+
+ /micromark-extension-mdx-jsx@1.0.5:
+ resolution: {integrity: sha512-gPH+9ZdmDflbu19Xkb8+gheqEDqkSpdCEubQyxuz/Hn8DOXiXvrXeikOoBA71+e8Pfi0/UYmU3wW3H58kr7akA==}
+ dependencies:
+ '@types/acorn': 4.0.6
+ '@types/estree': 1.0.5
+ estree-util-is-identifier-name: 2.1.0
+ micromark-factory-mdx-expression: 1.0.9
+ micromark-factory-space: 1.1.0
+ micromark-util-character: 1.2.0
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ uvu: 0.5.6
+ vfile-message: 3.1.4
+ dev: false
+
+ /micromark-extension-mdx-md@1.0.1:
+ resolution: {integrity: sha512-7MSuj2S7xjOQXAjjkbjBsHkMtb+mDGVW6uI2dBL9snOBCbZmoNgDAeZ0nSn9j3T42UE/g2xVNMn18PJxZvkBEA==}
+ dependencies:
+ micromark-util-types: 1.1.0
+ dev: false
+
+ /micromark-extension-mdxjs-esm@1.0.5:
+ resolution: {integrity: sha512-xNRBw4aoURcyz/S69B19WnZAkWJMxHMT5hE36GtDAyhoyn/8TuAeqjFJQlwk+MKQsUD7b3l7kFX+vlfVWgcX1w==}
+ dependencies:
+ '@types/estree': 1.0.5
+ micromark-core-commonmark: 1.1.0
+ micromark-util-character: 1.2.0
+ micromark-util-events-to-acorn: 1.2.3
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ unist-util-position-from-estree: 1.1.2
+ uvu: 0.5.6
+ vfile-message: 3.1.4
+ dev: false
+
+ /micromark-extension-mdxjs@1.0.1:
+ resolution: {integrity: sha512-7YA7hF6i5eKOfFUzZ+0z6avRG52GpWR8DL+kN47y3f2KhxbBZMhmxe7auOeaTBrW2DenbbZTf1ea9tA2hDpC2Q==}
+ dependencies:
+ acorn: 8.11.3
+ acorn-jsx: 5.3.2(acorn@8.11.3)
+ micromark-extension-mdx-expression: 1.0.8
+ micromark-extension-mdx-jsx: 1.0.5
+ micromark-extension-mdx-md: 1.0.1
+ micromark-extension-mdxjs-esm: 1.0.5
+ micromark-util-combine-extensions: 1.1.0
+ micromark-util-types: 1.1.0
+ dev: false
+
+ /micromark-factory-destination@1.1.0:
+ resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==}
+ dependencies:
+ micromark-util-character: 1.2.0
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ dev: false
+
+ /micromark-factory-label@1.1.0:
+ resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==}
+ dependencies:
+ micromark-util-character: 1.2.0
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ uvu: 0.5.6
+ dev: false
+
+ /micromark-factory-mdx-expression@1.0.9:
+ resolution: {integrity: sha512-jGIWzSmNfdnkJq05c7b0+Wv0Kfz3NJ3N4cBjnbO4zjXIlxJr+f8lk+5ZmwFvqdAbUy2q6B5rCY//g0QAAaXDWA==}
+ dependencies:
+ '@types/estree': 1.0.5
+ micromark-util-character: 1.2.0
+ micromark-util-events-to-acorn: 1.2.3
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ unist-util-position-from-estree: 1.1.2
+ uvu: 0.5.6
+ vfile-message: 3.1.4
+ dev: false
+
+ /micromark-factory-space@1.1.0:
+ resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==}
+ dependencies:
+ micromark-util-character: 1.2.0
+ micromark-util-types: 1.1.0
+ dev: false
+
+ /micromark-factory-title@1.1.0:
+ resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==}
+ dependencies:
+ micromark-factory-space: 1.1.0
+ micromark-util-character: 1.2.0
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ dev: false
+
+ /micromark-factory-whitespace@1.1.0:
+ resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==}
+ dependencies:
+ micromark-factory-space: 1.1.0
+ micromark-util-character: 1.2.0
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ dev: false
+
+ /micromark-util-character@1.2.0:
+ resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==}
+ dependencies:
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ dev: false
+
+ /micromark-util-chunked@1.1.0:
+ resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==}
+ dependencies:
+ micromark-util-symbol: 1.1.0
+ dev: false
+
+ /micromark-util-classify-character@1.1.0:
+ resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==}
+ dependencies:
+ micromark-util-character: 1.2.0
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ dev: false
+
+ /micromark-util-combine-extensions@1.1.0:
+ resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==}
+ dependencies:
+ micromark-util-chunked: 1.1.0
+ micromark-util-types: 1.1.0
+ dev: false
+
+ /micromark-util-decode-numeric-character-reference@1.1.0:
+ resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==}
+ dependencies:
+ micromark-util-symbol: 1.1.0
+ dev: false
+
+ /micromark-util-decode-string@1.1.0:
+ resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==}
+ dependencies:
+ decode-named-character-reference: 1.0.2
+ micromark-util-character: 1.2.0
+ micromark-util-decode-numeric-character-reference: 1.1.0
+ micromark-util-symbol: 1.1.0
+ dev: false
+
+ /micromark-util-encode@1.1.0:
+ resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==}
+ dev: false
+
+ /micromark-util-events-to-acorn@1.2.3:
+ resolution: {integrity: sha512-ij4X7Wuc4fED6UoLWkmo0xJQhsktfNh1J0m8g4PbIMPlx+ek/4YdW5mvbye8z/aZvAPUoxgXHrwVlXAPKMRp1w==}
+ dependencies:
+ '@types/acorn': 4.0.6
+ '@types/estree': 1.0.5
+ '@types/unist': 2.0.10
+ estree-util-visit: 1.2.1
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ uvu: 0.5.6
+ vfile-message: 3.1.4
+ dev: false
+
+ /micromark-util-html-tag-name@1.2.0:
+ resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==}
+ dev: false
+
+ /micromark-util-normalize-identifier@1.1.0:
+ resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==}
+ dependencies:
+ micromark-util-symbol: 1.1.0
+ dev: false
+
+ /micromark-util-resolve-all@1.1.0:
+ resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==}
+ dependencies:
+ micromark-util-types: 1.1.0
+ dev: false
+
+ /micromark-util-sanitize-uri@1.2.0:
+ resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==}
+ dependencies:
+ micromark-util-character: 1.2.0
+ micromark-util-encode: 1.1.0
+ micromark-util-symbol: 1.1.0
+ dev: false
+
+ /micromark-util-subtokenize@1.1.0:
+ resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==}
+ dependencies:
+ micromark-util-chunked: 1.1.0
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ uvu: 0.5.6
+ dev: false
+
+ /micromark-util-symbol@1.1.0:
+ resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==}
+ dev: false
+
+ /micromark-util-types@1.1.0:
+ resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==}
+ dev: false
+
+ /micromark@3.2.0:
+ resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==}
+ dependencies:
+ '@types/debug': 4.1.12
+ debug: 4.3.4
+ decode-named-character-reference: 1.0.2
+ micromark-core-commonmark: 1.1.0
+ micromark-factory-space: 1.1.0
+ micromark-util-character: 1.2.0
+ micromark-util-chunked: 1.1.0
+ micromark-util-combine-extensions: 1.1.0
+ micromark-util-decode-numeric-character-reference: 1.1.0
+ micromark-util-encode: 1.1.0
+ micromark-util-normalize-identifier: 1.1.0
+ micromark-util-resolve-all: 1.1.0
+ micromark-util-sanitize-uri: 1.2.0
+ micromark-util-subtokenize: 1.1.0
+ micromark-util-symbol: 1.1.0
+ micromark-util-types: 1.1.0
+ uvu: 0.5.6
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /micromatch@3.1.10:
+ resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ arr-diff: 4.0.0
+ array-unique: 0.3.2
+ braces: 2.3.2
+ define-property: 2.0.2
+ extend-shallow: 3.0.2
+ extglob: 2.0.4
+ fragment-cache: 0.2.1
+ kind-of: 6.0.3
+ nanomatch: 1.2.13
+ object.pick: 1.3.0
+ regex-not: 1.0.2
+ snapdragon: 0.8.2
+ to-regex: 3.0.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /micromatch@4.0.5:
+ resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
+ engines: {node: '>=8.6'}
+ dependencies:
+ braces: 3.0.2
+ picomatch: 2.3.1
+
+ /miller-rabin@4.0.1:
+ resolution: {integrity: sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==}
+ hasBin: true
+ dependencies:
+ bn.js: 4.12.0
+ brorand: 1.1.0
+ dev: false
+
+ /mimic-fn@2.1.0:
+ resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /mimic-fn@4.0.0:
+ resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
+ engines: {node: '>=12'}
+ dev: true
+
+ /min-indent@1.0.1:
+ resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
+ engines: {node: '>=4'}
+ dev: false
+
+ /minimalistic-assert@1.0.1:
+ resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==}
+ dev: false
+
+ /minimalistic-crypto-utils@1.0.1:
+ resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==}
+ dev: false
+
+ /minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+ dependencies:
+ brace-expansion: 1.1.11
+
+ /minimatch@9.0.3:
+ resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ dependencies:
+ brace-expansion: 2.0.1
+ dev: true
+
+ /minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+ /minipass@7.0.4:
+ resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ dev: true
+
+ /mississippi@3.0.0:
+ resolution: {integrity: sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==}
+ engines: {node: '>=4.0.0'}
+ dependencies:
+ concat-stream: 1.6.2
+ duplexify: 3.7.1
+ end-of-stream: 1.4.4
+ flush-write-stream: 1.1.1
+ from2: 2.3.0
+ parallel-transform: 1.2.0
+ pump: 3.0.0
+ pumpify: 1.5.1
+ stream-each: 1.2.3
+ through2: 2.0.5
+ dev: false
+
+ /mixin-deep@1.3.2:
+ resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ for-in: 1.0.2
+ is-extendable: 1.0.1
+ dev: false
+
+ /mkdirp@0.5.6:
+ resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
+ hasBin: true
+ dependencies:
+ minimist: 1.2.8
+ dev: false
+
+ /mlly@1.5.0:
+ resolution: {integrity: sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==}
+ dependencies:
+ acorn: 8.11.3
+ pathe: 1.1.2
+ pkg-types: 1.0.3
+ ufo: 1.4.0
+ dev: true
+
+ /move-concurrently@1.0.1:
+ resolution: {integrity: sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==}
+ dependencies:
+ aproba: 1.2.0
+ copy-concurrently: 1.0.5
+ fs-write-stream-atomic: 1.0.10
+ mkdirp: 0.5.6
+ rimraf: 2.7.1
+ run-queue: 1.0.3
+ dev: false
+
+ /mri@1.2.0:
+ resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
+ engines: {node: '>=4'}
+ dev: false
+
+ /ms@2.0.0:
+ resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+ dev: false
+
+ /ms@2.1.2:
+ resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
+
+ /mz@2.7.0:
+ resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+ dependencies:
+ any-promise: 1.3.0
+ object-assign: 4.1.1
+ thenify-all: 1.6.0
+ dev: true
+
+ /nan@2.18.0:
+ resolution: {integrity: sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==}
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /nanoid@3.3.7:
+ resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ /nanomatch@1.2.13:
+ resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ arr-diff: 4.0.0
+ array-unique: 0.3.2
+ define-property: 2.0.2
+ extend-shallow: 3.0.2
+ fragment-cache: 0.2.1
+ is-windows: 1.0.2
+ kind-of: 6.0.3
+ object.pick: 1.3.0
+ regex-not: 1.0.2
+ snapdragon: 0.8.2
+ to-regex: 3.0.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+ /neo-async@2.6.2:
+ resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
+
+ /next-themes@0.2.1(next@14.1.0)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==}
+ peerDependencies:
+ next: '*'
+ react: '*'
+ react-dom: '*'
+ dependencies:
+ next: 14.1.0(react-dom@18.2.0)(react@18.2.0)
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
+ /next@14.1.0(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==}
+ engines: {node: '>=18.17.0'}
+ hasBin: true
+ peerDependencies:
+ '@opentelemetry/api': ^1.1.0
+ react: ^18.2.0
+ react-dom: ^18.2.0
+ sass: ^1.3.0
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+ sass:
+ optional: true
+ dependencies:
+ '@next/env': 14.1.0
+ '@swc/helpers': 0.5.2
+ busboy: 1.6.0
+ caniuse-lite: 1.0.30001585
+ graceful-fs: 4.2.11
+ postcss: 8.4.31
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ styled-jsx: 5.1.1(react@18.2.0)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 14.1.0
+ '@next/swc-darwin-x64': 14.1.0
+ '@next/swc-linux-arm64-gnu': 14.1.0
+ '@next/swc-linux-arm64-musl': 14.1.0
+ '@next/swc-linux-x64-gnu': 14.1.0
+ '@next/swc-linux-x64-musl': 14.1.0
+ '@next/swc-win32-arm64-msvc': 14.1.0
+ '@next/swc-win32-ia32-msvc': 14.1.0
+ '@next/swc-win32-x64-msvc': 14.1.0
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+ dev: false
+
+ /node-addon-api@7.1.0:
+ resolution: {integrity: sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==}
+ engines: {node: ^16 || ^18 || >= 20}
+ dev: false
+
+ /node-libs-browser@2.2.1:
+ resolution: {integrity: sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==}
+ dependencies:
+ assert: 1.5.1
+ browserify-zlib: 0.2.0
+ buffer: 4.9.2
+ console-browserify: 1.2.0
+ constants-browserify: 1.0.0
+ crypto-browserify: 3.12.0
+ domain-browser: 1.2.0
+ events: 3.3.0
+ https-browserify: 1.0.0
+ os-browserify: 0.3.0
+ path-browserify: 0.0.1
+ process: 0.11.10
+ punycode: 1.4.1
+ querystring-es3: 0.2.1
+ readable-stream: 2.3.8
+ stream-browserify: 2.0.2
+ stream-http: 2.8.3
+ string_decoder: 1.3.0
+ timers-browserify: 2.0.12
+ tty-browserify: 0.0.0
+ url: 0.11.3
+ util: 0.11.1
+ vm-browserify: 1.1.2
+ dev: false
+
+ /node-releases@2.0.14:
+ resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
+ dev: false
+
+ /normalize-path@2.1.1:
+ resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==}
+ engines: {node: '>=0.10.0'}
+ requiresBuild: true
+ dependencies:
+ remove-trailing-separator: 1.1.0
+ dev: false
+ optional: true
+
+ /normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+ requiresBuild: true
+
+ /normalize-range@0.1.2:
+ resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /npm-run-path@4.0.1:
+ resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
+ engines: {node: '>=8'}
+ dependencies:
+ path-key: 3.1.1
+ dev: true
+
+ /npm-run-path@5.2.0:
+ resolution: {integrity: sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ dependencies:
+ path-key: 4.0.0
+ dev: true
+
+ /nth-check@2.1.1:
+ resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+ dependencies:
+ boolbase: 1.0.0
+ dev: false
+
+ /object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ /object-copy@0.1.0:
+ resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ copy-descriptor: 0.1.1
+ define-property: 0.2.5
+ kind-of: 3.2.2
+ dev: false
+
+ /object-inspect@1.13.1:
+ resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
+
+ /object-keys@1.1.1:
+ resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+ engines: {node: '>= 0.4'}
+
+ /object-visit@1.0.1:
+ resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ isobject: 3.0.1
+ dev: false
+
+ /object.assign@4.1.5:
+ resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ has-symbols: 1.0.3
+ object-keys: 1.1.1
+
+ /object.entries@1.1.7:
+ resolution: {integrity: sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+
+ /object.fromentries@2.0.7:
+ resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+
+ /object.groupby@1.0.2:
+ resolution: {integrity: sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==}
+ dependencies:
+ array.prototype.filter: 1.0.3
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ es-errors: 1.3.0
+
+ /object.hasown@1.1.3:
+ resolution: {integrity: sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==}
+ dependencies:
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+
+ /object.pick@1.3.0:
+ resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ isobject: 3.0.1
+ dev: false
+
+ /object.values@1.1.7:
+ resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+
+ /once@1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+ dependencies:
+ wrappy: 1.0.2
+
+ /onetime@5.1.2:
+ resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
+ engines: {node: '>=6'}
+ dependencies:
+ mimic-fn: 2.1.0
+ dev: true
+
+ /onetime@6.0.0:
+ resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
+ engines: {node: '>=12'}
+ dependencies:
+ mimic-fn: 4.0.0
+ dev: true
+
+ /optionator@0.9.3:
+ resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ '@aashutoshrathi/word-wrap': 1.2.6
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ /os-browserify@0.3.0:
+ resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==}
+ dev: false
+
+ /p-limit@2.3.0:
+ resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
+ engines: {node: '>=6'}
+ dependencies:
+ p-try: 2.2.0
+ dev: false
+
+ /p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+ dependencies:
+ yocto-queue: 0.1.0
+
+ /p-limit@5.0.0:
+ resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
+ engines: {node: '>=18'}
+ dependencies:
+ yocto-queue: 1.0.0
+ dev: true
+
+ /p-locate@3.0.0:
+ resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
+ engines: {node: '>=6'}
+ dependencies:
+ p-limit: 2.3.0
+ dev: false
+
+ /p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+ dependencies:
+ p-limit: 3.1.0
+
+ /p-try@2.2.0:
+ resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
+ engines: {node: '>=6'}
+ dev: false
+
+ /pako@1.0.11:
+ resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
+ dev: false
+
+ /parallel-transform@1.2.0:
+ resolution: {integrity: sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==}
+ dependencies:
+ cyclist: 1.0.2
+ inherits: 2.0.4
+ readable-stream: 2.3.8
+ dev: false
+
+ /parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+ dependencies:
+ callsites: 3.1.0
+
+ /parse-asn1@5.1.6:
+ resolution: {integrity: sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==}
+ dependencies:
+ asn1.js: 5.4.1
+ browserify-aes: 1.2.0
+ evp_bytestokey: 1.0.3
+ pbkdf2: 3.1.2
+ safe-buffer: 5.2.1
+ dev: false
+
+ /parse-entities@4.0.1:
+ resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==}
+ dependencies:
+ '@types/unist': 2.0.10
+ character-entities: 2.0.2
+ character-entities-legacy: 3.0.0
+ character-reference-invalid: 2.0.1
+ decode-named-character-reference: 1.0.2
+ is-alphanumerical: 2.0.1
+ is-decimal: 2.0.1
+ is-hexadecimal: 2.0.1
+ dev: false
+
+ /pascalcase@0.1.1:
+ resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /path-browserify@0.0.1:
+ resolution: {integrity: sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==}
+ dev: false
+
+ /path-dirname@1.0.2:
+ resolution: {integrity: sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==}
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /path-exists@3.0.0:
+ resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
+ engines: {node: '>=4'}
+ dev: false
+
+ /path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
+ /path-is-absolute@1.0.1:
+ resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+ engines: {node: '>=0.10.0'}
+
+ /path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ /path-key@4.0.0:
+ resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
+ engines: {node: '>=12'}
+ dev: true
+
+ /path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ /path-scurry@1.10.1:
+ resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ dependencies:
+ lru-cache: 10.2.0
+ minipass: 7.0.4
+ dev: true
+
+ /path-type@4.0.0:
+ resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+ engines: {node: '>=8'}
+
+ /pathe@1.1.2:
+ resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
+ dev: true
+
+ /pathval@1.1.1:
+ resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
+ dev: true
+
+ /pbkdf2@3.1.2:
+ resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==}
+ engines: {node: '>=0.12'}
+ dependencies:
+ create-hash: 1.2.0
+ create-hmac: 1.1.7
+ ripemd160: 2.0.2
+ safe-buffer: 5.2.1
+ sha.js: 2.4.11
+ dev: false
+
+ /periscopic@3.1.0:
+ resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==}
+ dependencies:
+ '@types/estree': 1.0.5
+ estree-walker: 3.0.3
+ is-reference: 3.0.2
+ dev: false
+
+ /picocolors@1.0.0:
+ resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
+
+ /picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+
+ /pify@2.3.0:
+ resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /pify@4.0.1:
+ resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
+ engines: {node: '>=6'}
+ dev: false
+
+ /pirates@4.0.6:
+ resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
+ engines: {node: '>= 6'}
+ dev: true
+
+ /pkg-dir@3.0.0:
+ resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==}
+ engines: {node: '>=6'}
+ dependencies:
+ find-up: 3.0.0
+ dev: false
+
+ /pkg-types@1.0.3:
+ resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==}
+ dependencies:
+ jsonc-parser: 3.2.1
+ mlly: 1.5.0
+ pathe: 1.1.2
+ dev: true
+
+ /playwright-core@1.41.2:
+ resolution: {integrity: sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==}
+ engines: {node: '>=16'}
+ hasBin: true
+ dev: true
+
+ /playwright@1.41.2:
+ resolution: {integrity: sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==}
+ engines: {node: '>=16'}
+ hasBin: true
+ dependencies:
+ playwright-core: 1.41.2
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
+ /posix-character-classes@0.1.1:
+ resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /postcss-import@16.0.0(postcss@8.4.24):
+ resolution: {integrity: sha512-e77lhVvrD1I2y7dYmBv0k9ULTdArgEYZt97T4w6sFIU5uxIHvDFQlKgUUyY7v7Barj0Yf/zm5A4OquZN7jKm5Q==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ postcss: ^8.0.0
+ dependencies:
+ postcss: 8.4.24
+ postcss-value-parser: 4.2.0
+ read-cache: 1.0.0
+ resolve: 1.22.8
+ dev: false
+
+ /postcss-load-config@4.0.2:
+ resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
+ engines: {node: '>= 14'}
+ peerDependencies:
+ postcss: '>=8.0.9'
+ ts-node: '>=9.0.0'
+ peerDependenciesMeta:
+ postcss:
+ optional: true
+ ts-node:
+ optional: true
+ dependencies:
+ lilconfig: 3.0.0
+ yaml: 2.3.4
+ dev: true
+
+ /postcss-value-parser@4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+ dev: false
+
+ /postcss@8.4.24:
+ resolution: {integrity: sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==}
+ engines: {node: ^10 || ^12 || >=14}
+ dependencies:
+ nanoid: 3.3.7
+ picocolors: 1.0.0
+ source-map-js: 1.0.2
+
+ /postcss@8.4.31:
+ resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
+ engines: {node: ^10 || ^12 || >=14}
+ dependencies:
+ nanoid: 3.3.7
+ picocolors: 1.0.0
+ source-map-js: 1.0.2
+ dev: false
+
+ /postcss@8.4.35:
+ resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==}
+ engines: {node: ^10 || ^12 || >=14}
+ dependencies:
+ nanoid: 3.3.7
+ picocolors: 1.0.0
+ source-map-js: 1.0.2
+
+ /prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ /prettier-plugin-organize-imports@3.2.4(prettier@3.2.5)(typescript@5.3.3):
+ resolution: {integrity: sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==}
+ peerDependencies:
+ '@volar/vue-language-plugin-pug': ^1.0.4
+ '@volar/vue-typescript': ^1.0.4
+ prettier: '>=2.0'
+ typescript: '>=2.9'
+ peerDependenciesMeta:
+ '@volar/vue-language-plugin-pug':
+ optional: true
+ '@volar/vue-typescript':
+ optional: true
+ dependencies:
+ prettier: 3.2.5
+ typescript: 5.3.3
+ dev: true
+
+ /prettier-plugin-tailwindcss@0.5.11(prettier-plugin-organize-imports@3.2.4)(prettier@3.2.5):
+ resolution: {integrity: sha512-AvI/DNyMctyyxGOjyePgi/gqj5hJYClZ1avtQvLlqMT3uDZkRbi4HhGUpok3DRzv9z7Lti85Kdj3s3/1CeNI0w==}
+ engines: {node: '>=14.21.3'}
+ peerDependencies:
+ '@ianvs/prettier-plugin-sort-imports': '*'
+ '@prettier/plugin-pug': '*'
+ '@shopify/prettier-plugin-liquid': '*'
+ '@trivago/prettier-plugin-sort-imports': '*'
+ prettier: ^3.0
+ prettier-plugin-astro: '*'
+ prettier-plugin-css-order: '*'
+ prettier-plugin-import-sort: '*'
+ prettier-plugin-jsdoc: '*'
+ prettier-plugin-marko: '*'
+ prettier-plugin-organize-attributes: '*'
+ prettier-plugin-organize-imports: '*'
+ prettier-plugin-style-order: '*'
+ prettier-plugin-svelte: '*'
+ prettier-plugin-twig-melody: '*'
+ peerDependenciesMeta:
+ '@ianvs/prettier-plugin-sort-imports':
+ optional: true
+ '@prettier/plugin-pug':
+ optional: true
+ '@shopify/prettier-plugin-liquid':
+ optional: true
+ '@trivago/prettier-plugin-sort-imports':
+ optional: true
+ prettier-plugin-astro:
+ optional: true
+ prettier-plugin-css-order:
+ optional: true
+ prettier-plugin-import-sort:
+ optional: true
+ prettier-plugin-jsdoc:
+ optional: true
+ prettier-plugin-marko:
+ optional: true
+ prettier-plugin-organize-attributes:
+ optional: true
+ prettier-plugin-organize-imports:
+ optional: true
+ prettier-plugin-style-order:
+ optional: true
+ prettier-plugin-svelte:
+ optional: true
+ prettier-plugin-twig-melody:
+ optional: true
+ dependencies:
+ prettier: 3.2.5
+ prettier-plugin-organize-imports: 3.2.4(prettier@3.2.5)(typescript@5.3.3)
+ dev: true
+
+ /prettier@3.2.5:
+ resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
+ engines: {node: '>=14'}
+ hasBin: true
+ dev: true
+
+ /pretty-format@29.7.0:
+ resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ dependencies:
+ '@jest/schemas': 29.6.3
+ ansi-styles: 5.2.0
+ react-is: 18.2.0
+ dev: true
+
+ /process-nextick-args@2.0.1:
+ resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
+ dev: false
+
+ /process@0.11.10:
+ resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
+ engines: {node: '>= 0.6.0'}
+ dev: false
+
+ /promise-inflight@1.0.1(bluebird@3.7.2):
+ resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}
+ peerDependencies:
+ bluebird: '*'
+ peerDependenciesMeta:
+ bluebird:
+ optional: true
+ dependencies:
+ bluebird: 3.7.2
+ dev: false
+
+ /prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
+ /property-information@6.4.1:
+ resolution: {integrity: sha512-OHYtXfu5aI2sS2LWFSN5rgJjrQ4pCy8i1jubJLe2QvMF8JJ++HXTUIVWFLfXJoaOfvYYjk2SN8J2wFUWIGXT4w==}
+ dev: false
+
+ /prr@1.0.1:
+ resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
+ dev: false
+
+ /public-encrypt@4.0.3:
+ resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==}
+ dependencies:
+ bn.js: 4.12.0
+ browserify-rsa: 4.1.0
+ create-hash: 1.2.0
+ parse-asn1: 5.1.6
+ randombytes: 2.1.0
+ safe-buffer: 5.2.1
+ dev: false
+
+ /pump@2.0.1:
+ resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==}
+ dependencies:
+ end-of-stream: 1.4.4
+ once: 1.4.0
+ dev: false
+
+ /pump@3.0.0:
+ resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
+ dependencies:
+ end-of-stream: 1.4.4
+ once: 1.4.0
+ dev: false
+
+ /pumpify@1.5.1:
+ resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==}
+ dependencies:
+ duplexify: 3.7.1
+ inherits: 2.0.4
+ pump: 2.0.1
+ dev: false
+
+ /punycode@1.4.1:
+ resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
+ dev: false
+
+ /punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ /qs@6.11.2:
+ resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==}
+ engines: {node: '>=0.6'}
+ dependencies:
+ side-channel: 1.0.5
+ dev: false
+
+ /querystring-es3@0.2.1:
+ resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==}
+ engines: {node: '>=0.4.x'}
+ dev: false
+
+ /queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ /randombytes@2.1.0:
+ resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
+ dependencies:
+ safe-buffer: 5.2.1
+ dev: false
+
+ /randomfill@1.0.4:
+ resolution: {integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==}
+ dependencies:
+ randombytes: 2.1.0
+ safe-buffer: 5.2.1
+ dev: false
+
+ /react-dom@18.2.0(react@18.2.0):
+ resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
+ peerDependencies:
+ react: ^18.2.0
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.2.0
+ scheduler: 0.23.0
+ dev: false
+
+ /react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ /react-is@18.2.0:
+ resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
+ dev: true
+
+ /react-refresh@0.14.0:
+ resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /react@18.2.0:
+ resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ loose-envify: 1.4.0
+ dev: false
+
+ /read-cache@1.0.0:
+ resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+ dependencies:
+ pify: 2.3.0
+ dev: false
+
+ /readable-stream@2.3.8:
+ resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
+ dependencies:
+ core-util-is: 1.0.3
+ inherits: 2.0.4
+ isarray: 1.0.0
+ process-nextick-args: 2.0.1
+ safe-buffer: 5.1.2
+ string_decoder: 1.1.1
+ util-deprecate: 1.0.2
+ dev: false
+
+ /readable-stream@3.6.2:
+ resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
+ engines: {node: '>= 6'}
+ dependencies:
+ inherits: 2.0.4
+ string_decoder: 1.3.0
+ util-deprecate: 1.0.2
+ dev: false
+
+ /readdirp@2.2.1:
+ resolution: {integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==}
+ engines: {node: '>=0.10'}
+ requiresBuild: true
+ dependencies:
+ graceful-fs: 4.2.11
+ micromatch: 3.1.10
+ readable-stream: 2.3.8
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+ optional: true
+
+ /readdirp@3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+ requiresBuild: true
+ dependencies:
+ picomatch: 2.3.1
+
+ /redent@4.0.0:
+ resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==}
+ engines: {node: '>=12'}
+ dependencies:
+ indent-string: 5.0.0
+ strip-indent: 4.0.0
+ dev: false
+
+ /reflect.getprototypeof@1.0.5:
+ resolution: {integrity: sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ es-errors: 1.3.0
+ get-intrinsic: 1.2.4
+ globalthis: 1.0.3
+ which-builtin-type: 1.1.3
+
+ /regenerator-runtime@0.14.1:
+ resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
+
+ /regex-not@1.0.2:
+ resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ extend-shallow: 3.0.2
+ safe-regex: 1.1.0
+ dev: false
+
+ /regexp.prototype.flags@1.5.1:
+ resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ set-function-name: 2.0.1
+
+ /remark-gfm@3.0.1:
+ resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==}
+ dependencies:
+ '@types/mdast': 3.0.15
+ mdast-util-gfm: 2.0.2
+ micromark-extension-gfm: 2.0.3
+ unified: 10.1.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /remark-mdx@2.3.0:
+ resolution: {integrity: sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==}
+ dependencies:
+ mdast-util-mdx: 2.0.1
+ micromark-extension-mdxjs: 1.0.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /remark-parse@10.0.2:
+ resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==}
+ dependencies:
+ '@types/mdast': 3.0.15
+ mdast-util-from-markdown: 1.3.1
+ unified: 10.1.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /remark-rehype-wrap@0.0.3:
+ resolution: {integrity: sha512-hNZ7epC6wFvNWDyPFNTrMzhk7leJSnaGRYZ2y4/YDRalzA0M2jha27wBiKnIQvWGW8vn1bDmSYOAG2grs6uPog==}
+ dependencies:
+ unist-util-select: 4.0.3
+ dev: false
+
+ /remark-rehype@10.1.0:
+ resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==}
+ dependencies:
+ '@types/hast': 2.3.10
+ '@types/mdast': 3.0.15
+ mdast-util-to-hast: 12.3.0
+ unified: 10.1.2
+ dev: false
+
+ /remove-trailing-separator@1.1.0:
+ resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==}
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /repeat-element@1.1.4:
+ resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /repeat-string@1.6.1:
+ resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
+ engines: {node: '>=0.10'}
+ dev: false
+
+ /resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ /resolve-from@5.0.0:
+ resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
+ /resolve-url@0.2.1:
+ resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==}
+ deprecated: https://github.com/lydell/resolve-url#deprecated
+ dev: false
+
+ /resolve@1.22.8:
+ resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
+ hasBin: true
+ dependencies:
+ is-core-module: 2.13.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ /resolve@2.0.0-next.5:
+ resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
+ hasBin: true
+ dependencies:
+ is-core-module: 2.13.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ /ret@0.1.15:
+ resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==}
+ engines: {node: '>=0.12'}
+ dev: false
+
+ /reusify@1.0.4:
+ resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+ /rimraf@2.7.1:
+ resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
+ hasBin: true
+ dependencies:
+ glob: 7.2.3
+ dev: false
+
+ /rimraf@3.0.2:
+ resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+ hasBin: true
+ dependencies:
+ glob: 7.2.3
+
+ /ripemd160@2.0.2:
+ resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==}
+ dependencies:
+ hash-base: 3.1.0
+ inherits: 2.0.4
+ dev: false
+
+ /rollup@4.9.6:
+ resolution: {integrity: sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+ dependencies:
+ '@types/estree': 1.0.5
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.9.6
+ '@rollup/rollup-android-arm64': 4.9.6
+ '@rollup/rollup-darwin-arm64': 4.9.6
+ '@rollup/rollup-darwin-x64': 4.9.6
+ '@rollup/rollup-linux-arm-gnueabihf': 4.9.6
+ '@rollup/rollup-linux-arm64-gnu': 4.9.6
+ '@rollup/rollup-linux-arm64-musl': 4.9.6
+ '@rollup/rollup-linux-riscv64-gnu': 4.9.6
+ '@rollup/rollup-linux-x64-gnu': 4.9.6
+ '@rollup/rollup-linux-x64-musl': 4.9.6
+ '@rollup/rollup-win32-arm64-msvc': 4.9.6
+ '@rollup/rollup-win32-ia32-msvc': 4.9.6
+ '@rollup/rollup-win32-x64-msvc': 4.9.6
+ fsevents: 2.3.3
+
+ /run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ dependencies:
+ queue-microtask: 1.2.3
+
+ /run-queue@1.0.3:
+ resolution: {integrity: sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg==}
+ dependencies:
+ aproba: 1.2.0
+ dev: false
+
+ /sade@1.8.1:
+ resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
+ engines: {node: '>=6'}
+ dependencies:
+ mri: 1.2.0
+ dev: false
+
+ /safe-array-concat@1.1.0:
+ resolution: {integrity: sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==}
+ engines: {node: '>=0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ get-intrinsic: 1.2.4
+ has-symbols: 1.0.3
+ isarray: 2.0.5
+
+ /safe-buffer@5.1.2:
+ resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
+ dev: false
+
+ /safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+ dev: false
+
+ /safe-regex-test@1.0.3:
+ resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ es-errors: 1.3.0
+ is-regex: 1.1.4
+
+ /safe-regex@1.1.0:
+ resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==}
+ dependencies:
+ ret: 0.1.15
+ dev: false
+
+ /safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+ dev: false
+
+ /scheduler@0.23.0:
+ resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
+ dependencies:
+ loose-envify: 1.4.0
+ dev: false
+
+ /schema-utils@1.0.0:
+ resolution: {integrity: sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==}
+ engines: {node: '>= 4'}
+ dependencies:
+ ajv: 6.12.6
+ ajv-errors: 1.0.1(ajv@6.12.6)
+ ajv-keywords: 3.5.2(ajv@6.12.6)
+ dev: false
+
+ /semver@5.7.2:
+ resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
+ hasBin: true
+ dev: false
+
+ /semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ /semver@7.6.0:
+ resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==}
+ engines: {node: '>=10'}
+ hasBin: true
+ dependencies:
+ lru-cache: 6.0.0
+
+ /serialize-javascript@4.0.0:
+ resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==}
+ dependencies:
+ randombytes: 2.1.0
+ dev: false
+
+ /set-function-length@1.2.1:
+ resolution: {integrity: sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ define-data-property: 1.1.2
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.2.4
+ gopd: 1.0.1
+ has-property-descriptors: 1.0.1
+
+ /set-function-name@2.0.1:
+ resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ define-data-property: 1.1.2
+ functions-have-names: 1.2.3
+ has-property-descriptors: 1.0.1
+
+ /set-value@2.0.1:
+ resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ extend-shallow: 2.0.1
+ is-extendable: 0.1.1
+ is-plain-object: 2.0.4
+ split-string: 3.1.0
+ dev: false
+
+ /setimmediate@1.0.5:
+ resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
+ dev: false
+
+ /sha.js@2.4.11:
+ resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==}
+ hasBin: true
+ dependencies:
+ inherits: 2.0.4
+ safe-buffer: 5.2.1
+ dev: false
+
+ /shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+ dependencies:
+ shebang-regex: 3.0.0
+
+ /shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ /side-channel@1.0.5:
+ resolution: {integrity: sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ es-errors: 1.3.0
+ get-intrinsic: 1.2.4
+ object-inspect: 1.13.1
+
+ /siginfo@2.0.0:
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+ dev: true
+
+ /signal-exit@3.0.7:
+ resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+ dev: true
+
+ /signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+ dev: true
+
+ /slash@3.0.0:
+ resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
+ engines: {node: '>=8'}
+
+ /snapdragon-node@2.1.1:
+ resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ define-property: 1.0.0
+ isobject: 3.0.1
+ snapdragon-util: 3.0.1
+ dev: false
+
+ /snapdragon-util@3.0.1:
+ resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ kind-of: 3.2.2
+ dev: false
+
+ /snapdragon@0.8.2:
+ resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ base: 0.11.2
+ debug: 2.6.9
+ define-property: 0.2.5
+ extend-shallow: 2.0.1
+ map-cache: 0.2.2
+ source-map: 0.5.7
+ source-map-resolve: 0.5.3
+ use: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /source-list-map@2.0.1:
+ resolution: {integrity: sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==}
+ dev: false
+
+ /source-map-js@1.0.2:
+ resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
+ engines: {node: '>=0.10.0'}
+
+ /source-map-resolve@0.5.3:
+ resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==}
+ deprecated: See https://github.com/lydell/source-map-resolve#deprecated
+ dependencies:
+ atob: 2.1.2
+ decode-uri-component: 0.2.2
+ resolve-url: 0.2.1
+ source-map-url: 0.4.1
+ urix: 0.1.0
+ dev: false
+
+ /source-map-support@0.5.21:
+ resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
+ dependencies:
+ buffer-from: 1.1.2
+ source-map: 0.6.1
+ dev: false
+
+ /source-map-url@0.4.1:
+ resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==}
+ deprecated: See https://github.com/lydell/source-map-url#deprecated
+ dev: false
+
+ /source-map@0.5.7:
+ resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /source-map@0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+ engines: {node: '>=0.10.0'}
+
+ /source-map@0.7.4:
+ resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
+ engines: {node: '>= 8'}
+ dev: false
+
+ /source-map@0.8.0-beta.0:
+ resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
+ engines: {node: '>= 8'}
+ dependencies:
+ whatwg-url: 7.1.0
+ dev: true
+
+ /space-separated-tokens@2.0.2:
+ resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
+ dev: false
+
+ /split-string@3.1.0:
+ resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ extend-shallow: 3.0.2
+ dev: false
+
+ /ssri@6.0.2:
+ resolution: {integrity: sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==}
+ dependencies:
+ figgy-pudding: 3.5.2
+ dev: false
+
+ /stackback@0.0.2:
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+ dev: true
+
+ /static-extend@0.1.2:
+ resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ define-property: 0.2.5
+ object-copy: 0.1.0
+ dev: false
+
+ /std-env@3.7.0:
+ resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==}
+ dev: true
+
+ /stream-browserify@2.0.2:
+ resolution: {integrity: sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==}
+ dependencies:
+ inherits: 2.0.4
+ readable-stream: 2.3.8
+ dev: false
+
+ /stream-each@1.2.3:
+ resolution: {integrity: sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==}
+ dependencies:
+ end-of-stream: 1.4.4
+ stream-shift: 1.0.3
+ dev: false
+
+ /stream-http@2.8.3:
+ resolution: {integrity: sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==}
+ dependencies:
+ builtin-status-codes: 3.0.0
+ inherits: 2.0.4
+ readable-stream: 2.3.8
+ to-arraybuffer: 1.0.1
+ xtend: 4.0.2
+ dev: false
+
+ /stream-shift@1.0.3:
+ resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
+ dev: false
+
+ /streamsearch@1.1.0:
+ resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
+ engines: {node: '>=10.0.0'}
+ dev: false
+
+ /string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+ dev: true
+
+ /string-width@5.1.2:
+ resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+ engines: {node: '>=12'}
+ dependencies:
+ eastasianwidth: 0.2.0
+ emoji-regex: 9.2.2
+ strip-ansi: 7.1.0
+ dev: true
+
+ /string.prototype.matchall@4.0.10:
+ resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ get-intrinsic: 1.2.4
+ has-symbols: 1.0.3
+ internal-slot: 1.0.7
+ regexp.prototype.flags: 1.5.1
+ set-function-name: 2.0.1
+ side-channel: 1.0.5
+
+ /string.prototype.trim@1.2.8:
+ resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+
+ /string.prototype.trimend@1.0.7:
+ resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+
+ /string.prototype.trimstart@1.0.7:
+ resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==}
+ dependencies:
+ call-bind: 1.0.6
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+
+ /string_decoder@1.1.1:
+ resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
+ dependencies:
+ safe-buffer: 5.1.2
+ dev: false
+
+ /string_decoder@1.3.0:
+ resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+ dependencies:
+ safe-buffer: 5.2.1
+ dev: false
+
+ /stringify-entities@4.0.3:
+ resolution: {integrity: sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==}
+ dependencies:
+ character-entities-html4: 2.1.0
+ character-entities-legacy: 3.0.0
+ dev: false
+
+ /strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+ dependencies:
+ ansi-regex: 5.0.1
+
+ /strip-ansi@7.1.0:
+ resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
+ engines: {node: '>=12'}
+ dependencies:
+ ansi-regex: 6.0.1
+ dev: true
+
+ /strip-bom@3.0.0:
+ resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
+ engines: {node: '>=4'}
+
+ /strip-final-newline@2.0.0:
+ resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /strip-final-newline@3.0.0:
+ resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
+ engines: {node: '>=12'}
+ dev: true
+
+ /strip-indent@4.0.0:
+ resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==}
+ engines: {node: '>=12'}
+ dependencies:
+ min-indent: 1.0.1
+ dev: false
+
+ /strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+
+ /strip-literal@1.3.0:
+ resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==}
+ dependencies:
+ acorn: 8.11.3
+ dev: true
+
+ /style-to-object@0.4.4:
+ resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==}
+ dependencies:
+ inline-style-parser: 0.1.1
+ dev: false
+
+ /styled-jsx@5.1.1(react@18.2.0):
+ resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==}
+ engines: {node: '>= 12.0.0'}
+ peerDependencies:
+ '@babel/core': '*'
+ babel-plugin-macros: '*'
+ react: '>= 16.8.0 || 17.x.x || ^18.0.0-0'
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ babel-plugin-macros:
+ optional: true
+ dependencies:
+ client-only: 0.0.1
+ react: 18.2.0
+ dev: false
+
+ /sucrase@3.35.0:
+ resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ hasBin: true
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.3
+ commander: 4.1.1
+ glob: 10.3.10
+ lines-and-columns: 1.2.4
+ mz: 2.7.0
+ pirates: 4.0.6
+ ts-interface-checker: 0.1.13
+ dev: true
+
+ /supports-color@5.5.0:
+ resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
+ engines: {node: '>=4'}
+ dependencies:
+ has-flag: 3.0.0
+ dev: false
+
+ /supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+ dependencies:
+ has-flag: 4.0.0
+
+ /supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ /tabbable@6.2.0:
+ resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
+ dev: false
+
+ /tapable@1.1.3:
+ resolution: {integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==}
+ engines: {node: '>=6'}
+ dev: false
+
+ /tapable@2.2.1:
+ resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
+ engines: {node: '>=6'}
+
+ /terser-webpack-plugin@1.4.5(webpack@4.47.0):
+ resolution: {integrity: sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==}
+ engines: {node: '>= 6.9.0'}
+ peerDependencies:
+ webpack: ^4.0.0
+ dependencies:
+ cacache: 12.0.4
+ find-cache-dir: 2.1.0
+ is-wsl: 1.1.0
+ schema-utils: 1.0.0
+ serialize-javascript: 4.0.0
+ source-map: 0.6.1
+ terser: 4.8.1
+ webpack: 4.47.0
+ webpack-sources: 1.4.3
+ worker-farm: 1.7.0
+ dev: false
+
+ /terser@4.8.1:
+ resolution: {integrity: sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+ dependencies:
+ acorn: 8.11.3
+ commander: 2.20.3
+ source-map: 0.6.1
+ source-map-support: 0.5.21
+ dev: false
+
+ /test-exclude@6.0.0:
+ resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
+ engines: {node: '>=8'}
+ dependencies:
+ '@istanbuljs/schema': 0.1.3
+ glob: 7.2.3
+ minimatch: 3.1.2
+ dev: true
+
+ /text-table@0.2.0:
+ resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
+
+ /thenify-all@1.6.0:
+ resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+ engines: {node: '>=0.8'}
+ dependencies:
+ thenify: 3.3.1
+ dev: true
+
+ /thenify@3.3.1:
+ resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+ dependencies:
+ any-promise: 1.3.0
+ dev: true
+
+ /through2@2.0.5:
+ resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
+ dependencies:
+ readable-stream: 2.3.8
+ xtend: 4.0.2
+ dev: false
+
+ /timers-browserify@2.0.12:
+ resolution: {integrity: sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==}
+ engines: {node: '>=0.6.0'}
+ dependencies:
+ setimmediate: 1.0.5
+ dev: false
+
+ /tinybench@2.6.0:
+ resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==}
+ dev: true
+
+ /tinypool@0.8.2:
+ resolution: {integrity: sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==}
+ engines: {node: '>=14.0.0'}
+ dev: true
+
+ /tinyspy@2.2.1:
+ resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==}
+ engines: {node: '>=14.0.0'}
+ dev: true
+
+ /to-arraybuffer@1.0.1:
+ resolution: {integrity: sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==}
+ dev: false
+
+ /to-fast-properties@2.0.0:
+ resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
+ engines: {node: '>=4'}
+
+ /to-object-path@0.3.0:
+ resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ kind-of: 3.2.2
+ dev: false
+
+ /to-regex-range@2.1.1:
+ resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-number: 3.0.0
+ repeat-string: 1.6.1
+ dev: false
+
+ /to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+ dependencies:
+ is-number: 7.0.0
+
+ /to-regex@3.0.2:
+ resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ define-property: 2.0.2
+ extend-shallow: 3.0.2
+ regex-not: 1.0.2
+ safe-regex: 1.1.0
+ dev: false
+
+ /tr46@1.0.1:
+ resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
+ dependencies:
+ punycode: 2.3.1
+ dev: true
+
+ /tree-kill@1.2.2:
+ resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
+ hasBin: true
+ dev: true
+
+ /trim-lines@3.0.1:
+ resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
+ dev: false
+
+ /trough@2.2.0:
+ resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
+ dev: false
+
+ /ts-api-utils@1.2.1(typescript@5.3.3):
+ resolution: {integrity: sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ typescript: '>=4.2.0'
+ dependencies:
+ typescript: 5.3.3
+ dev: true
+
+ /ts-interface-checker@0.1.13:
+ resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+ dev: true
+
+ /tsconfig-paths@3.15.0:
+ resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
+ dependencies:
+ '@types/json5': 0.0.29
+ json5: 1.0.2
+ minimist: 1.2.8
+ strip-bom: 3.0.0
+
+ /tslib@1.14.1:
+ resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
+ dev: false
+
+ /tslib@2.6.2:
+ resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
+ dev: false
+
+ /tsup@8.0.1(typescript@5.3.3):
+ resolution: {integrity: sha512-hvW7gUSG96j53ZTSlT4j/KL0q1Q2l6TqGBFc6/mu/L46IoNWqLLUzLRLP1R8Q7xrJTmkDxxDoojV5uCVs1sVOg==}
+ engines: {node: '>=18'}
+ hasBin: true
+ peerDependencies:
+ '@microsoft/api-extractor': ^7.36.0
+ '@swc/core': ^1
+ postcss: ^8.4.12
+ typescript: '>=4.5.0'
+ peerDependenciesMeta:
+ '@microsoft/api-extractor':
+ optional: true
+ '@swc/core':
+ optional: true
+ postcss:
+ optional: true
+ typescript:
+ optional: true
+ dependencies:
+ bundle-require: 4.0.2(esbuild@0.19.12)
+ cac: 6.7.14
+ chokidar: 3.6.0
+ debug: 4.3.4
+ esbuild: 0.19.12
+ execa: 5.1.1
+ globby: 11.1.0
+ joycon: 3.1.1
+ postcss-load-config: 4.0.2
+ resolve-from: 5.0.0
+ rollup: 4.9.6
+ source-map: 0.8.0-beta.0
+ sucrase: 3.35.0
+ tree-kill: 1.2.2
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - supports-color
+ - ts-node
+ dev: true
+
+ /tsutils@3.21.0(typescript@5.3.3):
+ resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
+ engines: {node: '>= 6'}
+ peerDependencies:
+ typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
+ dependencies:
+ tslib: 1.14.1
+ typescript: 5.3.3
+ dev: false
+
+ /tty-browserify@0.0.0:
+ resolution: {integrity: sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==}
+ dev: false
+
+ /turbo-darwin-64@1.12.4:
+ resolution: {integrity: sha512-dBwFxhp9isTa9RS/fz2gDVk5wWhKQsPQMozYhjM7TT4jTrnYn0ZJMzr7V3B/M/T8QF65TbniW7w1gtgxQgX5Zg==}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /turbo-darwin-arm64@1.12.4:
+ resolution: {integrity: sha512-1Uo5iI6xsJ1j9ObsqxYRsa3W26mEbUe6fnj4rQYV6kDaqYD54oAMJ6hM53q9rB8JvFxwdrUXGp3PwTw9A0qqkA==}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /turbo-linux-64@1.12.4:
+ resolution: {integrity: sha512-ONg2aSqKP7LAQOg7ysmU5WpEQp4DGNxSlAiR7um+LKtbmC/UxogbR5+T+Uuq6zGuQ5kJyKjWJ4NhtvUswOqBsA==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /turbo-linux-arm64@1.12.4:
+ resolution: {integrity: sha512-9FPufkwdgfIKg/9jj87Cdtftw8o36y27/S2vLN7FTR2pp9c0MQiTBOLVYadUr1FlShupddmaMbTkXEhyt9SdrA==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /turbo-windows-64@1.12.4:
+ resolution: {integrity: sha512-2mOtxHW5Vjh/5rDVu/aFwsMzI+chs8XcEuJHlY1sYOpEymYTz+u6AXbnzRvwZFMrLKr7J7fQOGl+v96sLKbNdA==}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /turbo-windows-arm64@1.12.4:
+ resolution: {integrity: sha512-nOY5wae9qnxPOpT1fRuYO0ks6dTwpKMPV6++VkDkamFDLFHUDVM/9kmD2UTeh1yyrKnrZksbb9zmShhmfj1wog==}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /turbo@1.12.4:
+ resolution: {integrity: sha512-yUJ7elEUSToiGwFZogXpYKJpQ0BvaMbkEuQECIWtkBLcmWzlMOt6bActsIm29oN83mRU0WbzGt4e8H1KHWedhg==}
+ hasBin: true
+ optionalDependencies:
+ turbo-darwin-64: 1.12.4
+ turbo-darwin-arm64: 1.12.4
+ turbo-linux-64: 1.12.4
+ turbo-linux-arm64: 1.12.4
+ turbo-windows-64: 1.12.4
+ turbo-windows-arm64: 1.12.4
+ dev: true
+
+ /type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ prelude-ls: 1.2.1
+
+ /type-detect@4.0.8:
+ resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /type-fest@0.20.2:
+ resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
+ engines: {node: '>=10'}
+
+ /typed-array-buffer@1.0.1:
+ resolution: {integrity: sha512-RSqu1UEuSlrBhHTWC8O9FnPjOduNs4M7rJ4pRKoEjtx1zUNOPN2sSXHLDX+Y2WPbHIxbvg4JFo2DNAEfPIKWoQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ es-errors: 1.3.0
+ is-typed-array: 1.1.13
+
+ /typed-array-byte-length@1.0.0:
+ resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.6
+ for-each: 0.3.3
+ has-proto: 1.0.1
+ is-typed-array: 1.1.13
+
+ /typed-array-byte-offset@1.0.0:
+ resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ available-typed-arrays: 1.0.6
+ call-bind: 1.0.6
+ for-each: 0.3.3
+ has-proto: 1.0.1
+ is-typed-array: 1.1.13
+
+ /typed-array-length@1.0.4:
+ resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
+ dependencies:
+ call-bind: 1.0.6
+ for-each: 0.3.3
+ is-typed-array: 1.1.13
+
+ /typedarray@0.0.6:
+ resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
+ dev: false
+
+ /typescript@5.3.3:
+ resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ /ufo@1.4.0:
+ resolution: {integrity: sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==}
+ dev: true
+
+ /uglify-js@3.17.4:
+ resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==}
+ engines: {node: '>=0.8.0'}
+ hasBin: true
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /unbox-primitive@1.0.2:
+ resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
+ dependencies:
+ call-bind: 1.0.6
+ has-bigints: 1.0.2
+ has-symbols: 1.0.3
+ which-boxed-primitive: 1.0.2
+
+ /undici-types@5.26.5:
+ resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
+
+ /unified@10.1.2:
+ resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==}
+ dependencies:
+ '@types/unist': 2.0.10
+ bail: 2.0.2
+ extend: 3.0.2
+ is-buffer: 2.0.5
+ is-plain-obj: 4.1.0
+ trough: 2.2.0
+ vfile: 5.3.7
+ dev: false
+
+ /union-value@1.0.1:
+ resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ arr-union: 3.1.0
+ get-value: 2.0.6
+ is-extendable: 0.1.1
+ set-value: 2.0.1
+ dev: false
+
+ /unique-filename@1.1.1:
+ resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==}
+ dependencies:
+ unique-slug: 2.0.2
+ dev: false
+
+ /unique-slug@2.0.2:
+ resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==}
+ dependencies:
+ imurmurhash: 0.1.4
+ dev: false
+
+ /unist-util-generated@2.0.1:
+ resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==}
+ dev: false
+
+ /unist-util-is@5.2.1:
+ resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==}
+ dependencies:
+ '@types/unist': 2.0.10
+ dev: false
+
+ /unist-util-is@6.0.0:
+ resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
+ dependencies:
+ '@types/unist': 3.0.2
+ dev: false
+
+ /unist-util-position-from-estree@1.1.2:
+ resolution: {integrity: sha512-poZa0eXpS+/XpoQwGwl79UUdea4ol2ZuCYguVaJS4qzIOMDzbqz8a3erUCOmubSZkaOuGamb3tX790iwOIROww==}
+ dependencies:
+ '@types/unist': 2.0.10
+ dev: false
+
+ /unist-util-position@4.0.4:
+ resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==}
+ dependencies:
+ '@types/unist': 2.0.10
+ dev: false
+
+ /unist-util-remove-position@4.0.2:
+ resolution: {integrity: sha512-TkBb0HABNmxzAcfLf4qsIbFbaPDvMO6wa3b3j4VcEzFVaw1LBKwnW4/sRJ/atSLSzoIg41JWEdnE7N6DIhGDGQ==}
+ dependencies:
+ '@types/unist': 2.0.10
+ unist-util-visit: 4.1.2
+ dev: false
+
+ /unist-util-select@4.0.3:
+ resolution: {integrity: sha512-1074+K9VyR3NyUz3lgNtHKm7ln+jSZXtLJM4E22uVuoFn88a/Go2pX8dusrt/W+KWH1ncn8jcd8uCQuvXb/fXA==}
+ dependencies:
+ '@types/unist': 2.0.10
+ css-selector-parser: 1.4.1
+ nth-check: 2.1.1
+ zwitch: 2.0.4
+ dev: false
+
+ /unist-util-stringify-position@3.0.3:
+ resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==}
+ dependencies:
+ '@types/unist': 2.0.10
+ dev: false
+
+ /unist-util-visit-parents@5.1.3:
+ resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==}
+ dependencies:
+ '@types/unist': 2.0.10
+ unist-util-is: 5.2.1
+ dev: false
+
+ /unist-util-visit-parents@6.0.1:
+ resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
+ dependencies:
+ '@types/unist': 3.0.2
+ unist-util-is: 6.0.0
+ dev: false
+
+ /unist-util-visit@4.1.2:
+ resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==}
+ dependencies:
+ '@types/unist': 2.0.10
+ unist-util-is: 5.2.1
+ unist-util-visit-parents: 5.1.3
+ dev: false
+
+ /unist-util-visit@5.0.0:
+ resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
+ dependencies:
+ '@types/unist': 3.0.2
+ unist-util-is: 6.0.0
+ unist-util-visit-parents: 6.0.1
+ dev: false
+
+ /unset-value@1.0.0:
+ resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ has-value: 0.3.1
+ isobject: 3.0.1
+ dev: false
+
+ /upath@1.2.0:
+ resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==}
+ engines: {node: '>=4'}
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /update-browserslist-db@1.0.13(browserslist@4.22.3):
+ resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+ dependencies:
+ browserslist: 4.22.3
+ escalade: 3.1.2
+ picocolors: 1.0.0
+ dev: false
+
+ /uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+ dependencies:
+ punycode: 2.3.1
+
+ /urix@0.1.0:
+ resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==}
+ deprecated: Please see https://github.com/lydell/urix#deprecated
+ dev: false
+
+ /url@0.11.3:
+ resolution: {integrity: sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==}
+ dependencies:
+ punycode: 1.4.1
+ qs: 6.11.2
+ dev: false
+
+ /use@3.1.1:
+ resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ dev: false
+
+ /util@0.10.4:
+ resolution: {integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==}
+ dependencies:
+ inherits: 2.0.3
+ dev: false
+
+ /util@0.11.1:
+ resolution: {integrity: sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==}
+ dependencies:
+ inherits: 2.0.3
+ dev: false
+
+ /uvu@0.5.6:
+ resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==}
+ engines: {node: '>=8'}
+ hasBin: true
+ dependencies:
+ dequal: 2.0.3
+ diff: 5.1.0
+ kleur: 4.1.5
+ sade: 1.8.1
+ dev: false
+
+ /v8-to-istanbul@9.2.0:
+ resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==}
+ engines: {node: '>=10.12.0'}
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.22
+ '@types/istanbul-lib-coverage': 2.0.6
+ convert-source-map: 2.0.0
+ dev: true
+
+ /vfile-message@3.1.4:
+ resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==}
+ dependencies:
+ '@types/unist': 2.0.10
+ unist-util-stringify-position: 3.0.3
+ dev: false
+
+ /vfile@5.3.7:
+ resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==}
+ dependencies:
+ '@types/unist': 2.0.10
+ is-buffer: 2.0.5
+ unist-util-stringify-position: 3.0.3
+ vfile-message: 3.1.4
+ dev: false
+
+ /vite-node@1.2.2(@types/node@20.11.20):
+ resolution: {integrity: sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ dependencies:
+ cac: 6.7.14
+ debug: 4.3.4
+ pathe: 1.1.2
+ picocolors: 1.0.0
+ vite: 5.1.1(@types/node@20.11.20)
+ transitivePeerDependencies:
+ - '@types/node'
+ - less
+ - lightningcss
+ - sass
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ dev: true
+
+ /vite-plugin-handlebars@2.0.0(@types/node@20.11.20):
+ resolution: {integrity: sha512-+J3It0nyhPzx4nT1I+fnWH+jShTEXzm6X0Tgsggdm9IYFD7/eJ6a3ROI13HTe0CVoyaxm/fPxH5HDAKyfz7T0g==}
+ dependencies:
+ handlebars: 4.7.8
+ vite: 5.1.1(@types/node@20.11.20)
+ transitivePeerDependencies:
+ - '@types/node'
+ - less
+ - lightningcss
+ - sass
+ - stylus
+ - sugarss
+ - terser
+ dev: true
+
+ /vite@5.1.1(@types/node@20.11.17):
+ resolution: {integrity: sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || >=20.0.0
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ dependencies:
+ '@types/node': 20.11.17
+ esbuild: 0.19.12
+ postcss: 8.4.35
+ rollup: 4.9.6
+ optionalDependencies:
+ fsevents: 2.3.3
+ dev: true
+
+ /vite@5.1.1(@types/node@20.11.20):
+ resolution: {integrity: sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || >=20.0.0
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ dependencies:
+ '@types/node': 20.11.20
+ esbuild: 0.19.12
+ postcss: 8.4.35
+ rollup: 4.9.6
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ /vitest@1.2.2(@types/node@20.11.20):
+ resolution: {integrity: sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@types/node': ^18.0.0 || >=20.0.0
+ '@vitest/browser': ^1.0.0
+ '@vitest/ui': ^1.0.0
+ happy-dom: '*'
+ jsdom: '*'
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+ dependencies:
+ '@types/node': 20.11.20
+ '@vitest/expect': 1.2.2
+ '@vitest/runner': 1.2.2
+ '@vitest/snapshot': 1.2.2
+ '@vitest/spy': 1.2.2
+ '@vitest/utils': 1.2.2
+ acorn-walk: 8.3.2
+ cac: 6.7.14
+ chai: 4.4.1
+ debug: 4.3.4
+ execa: 8.0.1
+ local-pkg: 0.5.0
+ magic-string: 0.30.7
+ pathe: 1.1.2
+ picocolors: 1.0.0
+ std-env: 3.7.0
+ strip-literal: 1.3.0
+ tinybench: 2.6.0
+ tinypool: 0.8.2
+ vite: 5.1.1(@types/node@20.11.20)
+ vite-node: 1.2.2(@types/node@20.11.20)
+ why-is-node-running: 2.2.2
+ transitivePeerDependencies:
+ - less
+ - lightningcss
+ - sass
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ dev: true
+
+ /vm-browserify@1.1.2:
+ resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==}
+ dev: false
+
+ /watchpack-chokidar2@2.0.1:
+ resolution: {integrity: sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==}
+ requiresBuild: true
+ dependencies:
+ chokidar: 2.1.8
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+ optional: true
+
+ /watchpack@1.7.5:
+ resolution: {integrity: sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==}
+ dependencies:
+ graceful-fs: 4.2.11
+ neo-async: 2.6.2
+ optionalDependencies:
+ chokidar: 3.6.0
+ watchpack-chokidar2: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /webidl-conversions@4.0.2:
+ resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
+ dev: true
+
+ /webpack-sources@1.4.3:
+ resolution: {integrity: sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==}
+ dependencies:
+ source-list-map: 2.0.1
+ source-map: 0.6.1
+ dev: false
+
+ /webpack@4.47.0:
+ resolution: {integrity: sha512-td7fYwgLSrky3fI1EuU5cneU4+pbH6GgOfuKNS1tNPcfdGinGELAqsb/BP4nnvZyKSG2i/xFGU7+n2PvZA8HJQ==}
+ engines: {node: '>=6.11.5'}
+ hasBin: true
+ peerDependencies:
+ webpack-cli: '*'
+ webpack-command: '*'
+ peerDependenciesMeta:
+ webpack-cli:
+ optional: true
+ webpack-command:
+ optional: true
+ dependencies:
+ '@webassemblyjs/ast': 1.9.0
+ '@webassemblyjs/helper-module-context': 1.9.0
+ '@webassemblyjs/wasm-edit': 1.9.0
+ '@webassemblyjs/wasm-parser': 1.9.0
+ acorn: 6.4.2
+ ajv: 6.12.6
+ ajv-keywords: 3.5.2(ajv@6.12.6)
+ chrome-trace-event: 1.0.3
+ enhanced-resolve: 4.5.0
+ eslint-scope: 4.0.3
+ json-parse-better-errors: 1.0.2
+ loader-runner: 2.4.0
+ loader-utils: 1.4.2
+ memory-fs: 0.4.1
+ micromatch: 3.1.10
+ mkdirp: 0.5.6
+ neo-async: 2.6.2
+ node-libs-browser: 2.2.1
+ schema-utils: 1.0.0
+ tapable: 1.1.3
+ terser-webpack-plugin: 1.4.5(webpack@4.47.0)
+ watchpack: 1.7.5
+ webpack-sources: 1.4.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /whatwg-url@7.1.0:
+ resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
+ dependencies:
+ lodash.sortby: 4.7.0
+ tr46: 1.0.1
+ webidl-conversions: 4.0.2
+ dev: true
+
+ /which-boxed-primitive@1.0.2:
+ resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
+ dependencies:
+ is-bigint: 1.0.4
+ is-boolean-object: 1.1.2
+ is-number-object: 1.0.7
+ is-string: 1.0.7
+ is-symbol: 1.0.4
+
+ /which-builtin-type@1.1.3:
+ resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ function.prototype.name: 1.1.6
+ has-tostringtag: 1.0.2
+ is-async-function: 2.0.0
+ is-date-object: 1.0.5
+ is-finalizationregistry: 1.0.2
+ is-generator-function: 1.0.10
+ is-regex: 1.1.4
+ is-weakref: 1.0.2
+ isarray: 2.0.5
+ which-boxed-primitive: 1.0.2
+ which-collection: 1.0.1
+ which-typed-array: 1.1.14
+
+ /which-collection@1.0.1:
+ resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==}
+ dependencies:
+ is-map: 2.0.2
+ is-set: 2.0.2
+ is-weakmap: 2.0.1
+ is-weakset: 2.0.2
+
+ /which-typed-array@1.1.14:
+ resolution: {integrity: sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ available-typed-arrays: 1.0.6
+ call-bind: 1.0.6
+ for-each: 0.3.3
+ gopd: 1.0.1
+ has-tostringtag: 1.0.2
+
+ /which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+ dependencies:
+ isexe: 2.0.0
+
+ /why-is-node-running@2.2.2:
+ resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==}
+ engines: {node: '>=8'}
+ hasBin: true
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
+ dev: true
+
+ /wordwrap@1.0.0:
+ resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
+ dev: true
+
+ /worker-farm@1.7.0:
+ resolution: {integrity: sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==}
+ dependencies:
+ errno: 0.1.8
+ dev: false
+
+ /wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ dev: true
+
+ /wrap-ansi@8.1.0:
+ resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
+ engines: {node: '>=12'}
+ dependencies:
+ ansi-styles: 6.2.1
+ string-width: 5.1.2
+ strip-ansi: 7.1.0
+ dev: true
+
+ /wrappy@1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+ /xtend@4.0.2:
+ resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
+ engines: {node: '>=0.4'}
+ dev: false
+
+ /y18n@4.0.3:
+ resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
+ dev: false
+
+ /yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+ dev: false
+
+ /yallist@4.0.0:
+ resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+
+ /yaml@2.3.4:
+ resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==}
+ engines: {node: '>= 14'}
+ dev: true
+
+ /yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
+ /yocto-queue@1.0.0:
+ resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
+ engines: {node: '>=12.20'}
+ dev: true
+
+ /zwitch@2.0.4:
+ resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
+ dev: false
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 000000000..665bcbcb5
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,5 @@
+packages:
+ - 'oxide/crates/node'
+ - 'oxide/crates/node/npm/*'
+ - 'packages/*'
+ - 'playgrounds/*'
diff --git a/scripts/lock-pre-release-versions.mjs b/scripts/lock-pre-release-versions.mjs
new file mode 100644
index 000000000..35f7b0dc9
--- /dev/null
+++ b/scripts/lock-pre-release-versions.mjs
@@ -0,0 +1,60 @@
+import { exec } from 'node:child_process'
+import fs from 'node:fs/promises'
+import path from 'node:path'
+import prettier from 'prettier'
+
+exec('pnpm --silent --filter=!./playgrounds/* -r exec pwd', async (err, stdout) => {
+ if (err) {
+ console.error(err)
+ process.exit(1)
+ }
+
+ let paths = stdout
+ .trim()
+ .split('\n')
+ .map((x) => path.resolve(x, 'package.json'))
+
+ for (let path of paths) {
+ let pkg = await fs.readFile(path, 'utf8').then(JSON.parse)
+
+ // In our case, all pre-release like versions have a `-` in the version
+ // number.
+ //
+ // E.g.:
+ //
+ // - `0.0.0-development.0`
+ // - `0.0.0-insiders.{hash}`
+ if (!pkg.version?.includes('-')) continue
+
+ let shouldUpdate = false
+
+ for (let group of [
+ 'dependencies',
+ 'devDependencies',
+ 'peerDependencies',
+ 'optionalDependencies',
+ ]) {
+ for (let [name, version] of Object.entries(pkg[group] ?? {})) {
+ if (version === 'workspace:*' || !version.startsWith('workspace:')) continue
+
+ // Set the version to `workspace:*`, we don't need to know the exact
+ // version because `pnpm` will handle it for us at publishing time.
+ pkg[group][name] = 'workspace:*'
+
+ // Whether or not we should update the `package.json` file.
+ shouldUpdate = true
+ }
+ }
+
+ if (shouldUpdate) {
+ await fs.writeFile(
+ path,
+ await prettier
+ .format(JSON.stringify(pkg, null, 2), { filepath: path })
+ .then((x) => `${x.trim()}\n`),
+ )
+ }
+ }
+
+ console.log('Done.')
+})
diff --git a/scripts/version-packages.mjs b/scripts/version-packages.mjs
new file mode 100644
index 000000000..c94ec11f9
--- /dev/null
+++ b/scripts/version-packages.mjs
@@ -0,0 +1,140 @@
+import { exec, spawnSync } from 'node:child_process'
+import { randomUUID } from 'node:crypto'
+import fs from 'node:fs/promises'
+import { tmpdir } from 'node:os'
+import path from 'node:path'
+import url from 'node:url'
+import prettier from 'prettier'
+
+const __dirname = path.dirname(url.fileURLToPath(import.meta.url))
+const root = path.resolve(__dirname, '..')
+
+// The known workspace is: @tailwindcss/oxide
+// All the workspaces in `oxide/crates/node/npm/*` should always be in sync with
+// `@tailwindcss/oxide`. You can think of them as one big package, but they are
+// split into multiple packages because they are OS specific.
+const syncedWorkspaces = new Map([
+ [
+ '@tailwindcss/oxide',
+ [
+ 'oxide/crates/node/npm/darwin-arm64',
+ 'oxide/crates/node/npm/darwin-x64',
+ 'oxide/crates/node/npm/freebsd-x64',
+ 'oxide/crates/node/npm/linux-arm-gnueabihf',
+ 'oxide/crates/node/npm/linux-arm64-gnu',
+ 'oxide/crates/node/npm/linux-arm64-musl',
+ 'oxide/crates/node/npm/linux-x64-gnu',
+ 'oxide/crates/node/npm/linux-x64-musl',
+ 'oxide/crates/node/npm/win32-x64-msvc',
+ ],
+ ],
+])
+
+const inverseSyncedWorkspaces = new Map()
+
+for (let [name, paths] of syncedWorkspaces) {
+ for (let [idx, filePath] of paths.entries()) {
+ // Make sure all the paths are absolute paths
+ paths[idx] = path.resolve(root, filePath, 'package.json')
+
+ // Make sure inverse lookup table exists
+ inverseSyncedWorkspaces.set(paths[idx], name)
+ }
+}
+
+exec('pnpm --silent --filter=!./playgrounds/* -r exec pwd', async (err, stdout) => {
+ if (err) {
+ console.error(err)
+ process.exit(1)
+ }
+
+ let paths = stdout
+ .trim()
+ .split('\n')
+ .map((x) => path.resolve(x, 'package.json'))
+ // Workspaces that are in sync with another workspace should not be updated
+ // manually, they should be updated by updating the main workspace.
+ .filter((x) => !inverseSyncedWorkspaces.has(x))
+
+ let workspaces = new Map()
+
+ // Track all the workspaces
+ for (let path of paths) {
+ let pkg = await fs.readFile(path, 'utf8').then(JSON.parse)
+ workspaces.set(pkg.name, { version: pkg.version ?? '', path })
+ }
+
+ // Build the editable output
+ let lines = ['# Update the versions of the packages you want to change', '']
+ for (let [name, info] of workspaces) {
+ lines.push(`${name}: ${info.version}`)
+ }
+ let output = lines.join('\n')
+
+ // Edit the file
+ {
+ // Figure out which editor to use.
+ //
+ // In this case we still split on whitespace, because it can happen that the
+ // EDITOR env variable is configured as `code --wait`. This means that we
+ // want `code` as the editor, but `--wait` is one of the arguments.
+ let args = process.env.EDITOR.split(' ')
+ let editor = args.shift()
+
+ // Create a temporary file which will be edited
+ let filepath = path.resolve(tmpdir(), `version-${randomUUID()}.txt`)
+ await fs.writeFile(filepath, output)
+
+ // Edit the file, once the editor is closed, the file will be saved and we
+ // can read the changes
+ spawnSync(editor, [...args, filepath], {
+ stdio: 'inherit',
+ })
+
+ let newOutput = await fs.readFile(filepath, 'utf8').then((x) => x.trim().split('\n'))
+
+ // Cleanup temporary file
+ await fs.unlink(filepath)
+
+ // Update the package.json files
+ for (let line of newOutput) {
+ if (line[0] === '#') continue // Skip comment lines
+ if (line.trim() === '') continue // Skip empty lines
+
+ let [name, version = ''] = line.split(':').map((x) => x.trim())
+
+ // Figure out all the paths to the package.json files that need to be
+ // updated with the new version
+ let paths = [
+ // The package.json file of the main workspace
+ workspaces.get(name).path,
+
+ // The package.json files of the workspaces that are in sync with the
+ // main workspace
+ ...(syncedWorkspaces.get(name) ?? []),
+ ]
+
+ for (let pkgPath of paths) {
+ let pkg = await fs.readFile(pkgPath, 'utf8').then(JSON.parse)
+ let name = pkg.name
+ if (version !== '') {
+ // Ensure the version is set after the name and before everything else
+ delete pkg.name
+ delete pkg.version
+
+ // This allows us to keep the order of the keys in the package.json
+ pkg = { name, version, ...pkg }
+ }
+
+ await fs.writeFile(
+ pkgPath,
+ await prettier
+ .format(JSON.stringify(pkg, null, 2), { filepath: pkgPath })
+ .then((x) => `${x.trim()}\n`),
+ )
+ }
+ }
+ }
+
+ console.log('Done.')
+})
diff --git a/turbo.json b/turbo.json
new file mode 100644
index 000000000..006c9e553
--- /dev/null
+++ b/turbo.json
@@ -0,0 +1,44 @@
+{
+ "$schema": "https://turbo.build/schema.json",
+ "pipeline": {
+ "@tailwindcss/oxide#build": {
+ "dependsOn": ["^build"],
+ "outputs": ["./index.d.ts", "./index.js", "./*.node"],
+ "inputs": [
+ "./src/**/*",
+ "./build.rs",
+ "./package.json",
+ "./Cargo.toml",
+ "../core/src/**/*",
+ "../core/Cargo.toml",
+ "../Cargo.toml",
+ "../package.json"
+ ]
+ },
+ "@tailwindcss/oxide#dev": {
+ "dependsOn": ["^dev"],
+ "outputs": ["./index.d.ts", "./index.js", "./*.node"],
+ "inputs": [
+ "./src/**/*",
+ "./build.rs",
+ "./package.json",
+ "./Cargo.toml",
+ "../core/src/**/*",
+ "../core/Cargo.toml",
+ "../Cargo.toml",
+ "../package.json"
+ ],
+ "cache": false,
+ "persistent": true
+ },
+ "build": {
+ "dependsOn": ["^build"],
+ "outputs": ["dist/**"]
+ },
+ "lint": {},
+ "dev": {
+ "cache": false,
+ "persistent": true
+ }
+ }
+}
diff --git a/vitest.workspace.ts b/vitest.workspace.ts
new file mode 100644
index 000000000..4ccde0667
--- /dev/null
+++ b/vitest.workspace.ts
@@ -0,0 +1 @@
+export default ['packages/*']