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 @@ +
+

+ This is text fe914826-0ae1-4507-903a-a4e5b5cb1112 + + This is link d30fe654-03f5-49d1-8cca-ecc40dd6bd52 + + + This is link e2ef44c4-ede1-4f3b-85b6-5358f926c5ab + +

+
+ Profile picture of user 761a1558-6e1d-4ac6-b228-194dfeedec83 + Profile picture of user d6eb4cbe-4a90-48cb-82e7-849f9a233b3d + + +
+ +
+

+ This is heading 3e11960f-d5c3-4bc4-a097-9137acb476e6 +

+

+ This is heading e5129e54-0bd8-4c57-8a61-b956c1daef9a +

+
    +
  1. + + This is link d2210a2b-6731-40b8-af4d-8e3970dbd503 + + Profile picture of user 3021b302-2c1a-4e3e-89a2-48426376d115 +
  2. +
  3. +
      +
    1. + +
    2. +
    3. + Profile picture of user 01366bd5-e915-4583-887a-92aaf60cc962 + +
    4. +
    + + + Profile picture of user be6b3aa8-9174-4c78-85fb-5758b5c9a9c5 +
  4. +
  5. + +
  6. +
+
+
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 @@ +
+
+ + This is text d60c61e4-6aeb-4eb3-965b-617cc507dcf8 + + This is link eadc06c7-f2d4-47ae-93f2-3895885e8d32 + + +
+ +
+
+
+
+ + +
+ + + This is link c9359bca-edb7-4f24-b13b-d2103b86bc79 + + +
+
+
    +
  1. + + This is link 1bf332ed-1def-4a8f-9e85-ba41e76809df + +
      +
        + Profile picture of user af753b29-7520-4459-a429-7627b2098bd5 +
      1. +
      2. + + This is link d5ef1507-85e8-45c4-9424-4cdd5be56b5a + +
      3. +
      +

      + This is paragraph 704fe54d-6438-41de-9147-711be237e8f6 +

      + + This is link 68b03134-2b7b-438e-815c-dc696e75398a + + + This is link 2fc751bf-98b0-4445-99d3-945c269505cd + +
      +
      +
      + This is text 347f1818-6424-42c3-8b91-fb1e77ac5800 + +

      + This is paragraph 1f671368-6b0f-4ea9-9173-a9d8d30344c6 +

      +
      + +
      +
      +

      + + This is text 5c23358e-043c-47ab-9556-41591cb3e6f8 + +

      +
        +
      1. +
          +
        1. + This is item 07691c01-d3c1-4ffd-915e-8acc347436e4 +
        2. +
        3. + This is item aa25a485-91d4-4aec-a9d2-62945198e6b4 +
        4. +
        5. + This is item 470b6f06-cdef-48a3-82ab-9b454b9f649e +
        6. +
        7. + This is item a697511b-a0db-4be7-924c-7505624eef18 +
        8. +
        +
      2. +
      3. +
          +
        • + This is item 664e4f6d-7486-4532-a400-9906e5eddaf9 +
        • +
        • + This is item d3bf58aa-18f4-484d-8a00-8d28ee2e1da3 +
        • +
        • This is item 91d5de3d-4d7f-4c3d-af53-43707ba31a56
        • +
        • This is item 3d3a9d5c-968c-4274-901c-47d8f04f74a6
        • +
        +
          +
        1. This is item fd65f1a1-a714-4188-b74d-5d83d032334f
        2. +
        3. + This is item 528b2986-d143-4ace-863d-0a4c2aba89fb +
        4. +
        +
          + +
        • + This is item d43be1d8-6315-4736-a1d7-36cfa0183c02 +
        • +
        • + This is item 9ac222ec-c50d-40d3-a11c-c3e59ca80dff +
        • +
        • + This is item d96195ec-c214-4a44-8f8f-3b5c900309b1 +
        • +
        +
      4. +
      5. +
          +
        1. + This is item 82b417bf-efe9-475c-a252-82b5f63de22d +
        2. +
        3. + This is item 63e03c0c-1e42-412e-860b-7b2b1d73c73a +
        4. +
        5. + This is item 32c1265f-8b56-479f-9a08-8145c79e4986 +
        6. +
        7. + This is item 7976e3cd-14fe-4ddd-bb28-5645d6887f7c +
        8. +
        9. + This is item 35bb085b-9d0e-44e9-8555-2d9ca920bf1e +
        10. +
        +
          +
        1. + This is item 8f25f8c8-0619-4909-9953-9cb906f952be +
        2. +
        3. + This is item 1c91d284-d14c-4fb6-ad18-969178b8d444 +
        4. +
        5. + This is item 5f03c882-eb99-4f71-8c79-ce838def0b4f +
        6. +
        +
          +
        • + This is item 35247dba-6d89-4e6f-b6f0-d30e375947e7 +
        • +
        • + This is item 0976f1fe-37d5-4597-b102-520191196251 +
        • + + +
        • This is item 6a02c64c-2d76-46da-86da-3ab89b2da3fc
        • +
        +
          +
        • + This is item dd3b32d1-6f6c-446c-8568-f4e4e667a029 +
        • +
        • + This is item 20024d36-8a63-44c8-b9d3-655053a4807d +
        • +
        +
      6. +
      + +
        +
      1. + + This is link f7bf48d9-54fc-4d7d-9876-e2cca6fffa9d + +
          +
        • This is item b3d24da5-7b2b-4cee-983e-50c71b83d80a
        • +
        • + This is item 473e4022-92fb-4ac1-a698-0144e4d61909 +
        • +
        • This is item 469e9e37-9223-41c1-a454-9a453f41ec92
        • +
        • This is item 465403a2-c93b-4d0c-b409-b4b16a5f2722
        • +
        +
          +
        • This is item d3543dcd-f381-494e-bbe8-a48c5ef75782
        • +
        • + This is item a1e5c65d-b922-41a9-8893-0425affd0cb6 +
        • +
        • This is item 04e76794-ca77-452d-b38d-953ebd7af2f2
        • +
        • + This is item a0d1e6ff-ef3c-4ae1-9298-18061fcf4b97 +
        • +
        +
      2. + +
      3. + + This is link d7e11d43-8b3c-41cc-b1d8-de8daf81e1eb + +
      4. +
      + Profile picture of user 48d840a2-d618-40a2-9269-058f12953881 +
      + + +
      +
        +
      1. +
          +
        • +
            + +
          1. This is item dcf6da3f-cd0c-42cd-bf3e-ff95f11a42b7
          2. +
          3. + This is item dee3e7ac-c875-4eec-b123-0a428625a69d +
          4. +
          5. + This is item d4befb0b-141f-4d37-b94d-8f1ab68b05be +
          6. +
          7. This is item 1b41f76d-72ac-4acb-a502-c1470961b613
          8. +
          +
            +
          1. This is item bea3b7da-238b-48f6-a914-ceca9bd81531
          2. +
          3. This is item 9a36c6dc-4a33-441e-81fc-0e20358726cb
          4. +
          5. + This is item 86ae9612-9d1e-47e6-b04f-5abce55ab608 +
          6. +
          + Profile picture of user c5c6fea2-9828-47a3-b6d0-5a5c7b6cf60c + + This is link 0a46a84b-a8f0-49ec-a2da-22fdf9626e73 + +
        • +
        • + + This is link 3e5b2529-f139-4d58-bb77-7446e17b28c3 + +
            +
          1. + This is item 3cc144e6-c381-43c1-ad90-d204647ebeba +
          2. +
          3. This is item 567414f0-1e20-4178-9d85-ea59264925ef
          4. +
          +
            +
          1. This is item e2298703-fdbd-4eed-af0e-cdb070e2706b
          2. +
          3. + This is item 35d252da-75d5-4aa5-a11f-c75050e4fef9 +
          4. +
          5. + This is item 16ee79e0-a037-40ed-90e6-dc5ed27dd9a1 +
          6. +
          +
            +
          1. + This is item 61bb31d3-a7a4-493e-9056-f279d01826b4 +
          2. +
          3. This is item 2d40465f-a65c-41fd-8a27-6c652e1b4037
          4. +
          5. This is item 5f0733bb-b419-4a71-abda-83f2286f1d5c
          6. +
          7. + This is item 71770100-a00f-4b8d-a22a-8698b94a7cb7 +
          8. +
          +
        • +
        • +
            +
          1. This is item d032389a-7f9d-443d-a99e-56a8d337ce7b
          2. +
          3. + This is item b5fa59db-df70-4817-9103-b9f5131912f1 +
          4. +
          5. This is item 519e0fd2-e4e8-46c7-9ff2-fc1fc709e8fd
          6. +
          + + This is link 093d8f89-fc9c-4fd6-adf7-69faae1459d1 + +
        • +
        + + This is link 2bf81a67-3359-4e32-bbe7-3be22ccb814e + + Profile picture of user a1d55302-8081-4857-ade1-4c2e3243c56d +
      2. +
      3. +
          +
        • + Profile picture of user 406601b8-eb71-466e-9051-997d07290a5d +
        • +
        • + Profile picture of user 8d3d66b4-edf4-4c3d-970d-9834019514ba + Profile picture of user bdf5c00e-7d03-4b4e-b4cb-4a9132600943 +
            +
          • + This is item 05b7745e-80c1-4dca-8ef6-3849e9527fd8 +
          • +
          • + This is item 9bd66bd1-30aa-469f-8873-f8a632274199 +
          • +
          • + This is item c20e3976-1e89-43b7-9004-73347641b456 +
          • +
          • + This is item e61f6a70-9fbf-4300-9403-d1414f3e857b +
          • +
          • + This is item 10fd4bba-fa7d-4417-8ef0-c149f1c36bc0 +
          • +
          +
            +
          1. + This is item f690de44-c991-4716-9f69-639d6cab5674 +
          2. +
          3. + This is item 997899af-005c-4a03-892c-4320e2e49779 +
          4. +
          5. + This is item 487a2b87-faeb-493a-a902-b9b6be3bc7ae +
          6. +
          7. This is item 751f67dc-0258-455d-8b5f-7924ad276810
          8. +
          +
        • +
        • +
            +
          1. + This is item a0234046-9f6b-4c26-b1fa-b3ab9ee3d1be +
          2. +
          3. This is item 3445c4fe-82fe-4c26-9215-70ebd987a400
          4. +
          5. + This is item 785a55d6-8356-44cd-a9f8-61b65cf1d6e0 +
          6. +
          + Profile picture of user 475128ca-34a7-4ae5-87b1-007fab13027b +
        • +
        • +
            +
          1. + This is item cd767806-2991-471b-8912-b19ce1a244ab +
          2. +
          3. + This is item a3b760d7-4d24-4995-aa57-b4539f9d9099 +
          4. +
          5. + This is item 8bb797af-3568-4d4e-858c-f0556180142b +
          6. +
          7. This is item 39f09b91-8da8-4415-a8dd-6875bf44529f
          8. +
          9. + This is item 2fc0cc16-33a8-4ff9-bc51-afcc0018856f +
          10. +
          +
        • +
        +
      4. +
      5. +
          +
        • + This is link 666a56ff-1492-49ea-be55-cfa03d4b465b +
            +
          1. + This is item 3d8cfd9e-92d6-455c-862d-cae54535cbe6 +
          2. +
          3. + This is item b38dfe93-6090-4e18-90f6-7d01fa966730 +
          4. +
          5. + This is item 23cf4321-a4d0-49ec-8b76-f309bda558df +
          6. +
          7. + This is item f9aa0163-3cec-44e2-b2d0-1b069aed4cc3 +
          8. +
          +
            +
          1. + This is item 8cd3166b-11e2-4510-acfc-e7164291ed60 +
          2. +
          3. This is item cc319df9-9621-4a81-a2cf-08e416d924f4
          4. +
          5. + This is item aca77c09-4b37-4793-8062-e27d2080fe3e +
          6. +
          7. + This is item fb350f75-3b62-41f4-8b47-af0a0fc6b13d +
          8. +
          9. + This is item 6ae0363e-b38d-4f40-8c7f-10eab45f8312 +
          10. +
          +
        • +
        • + + This is link 02711a81-0962-41ee-b004-295c9444da0d + +
            +
          • + This is item 71d26b75-6b13-4e57-86b3-23ee061c52d6 +
          • +
          • This is item a054d31d-18d0-4782-b0f2-0c777d1cab3a
          • +
          +
        • +
        + Profile picture of user 2aa2a0f9-585f-479d-99ff-78cac697f407 +
      6. +
      7. + This is link 83e61d6f-2754-44e3-82d8-f1dfed04a37b +
          +
        1. + Profile picture of user c7fa150f-e78f-454d-bde1-65f0c8fff72f + + This is link f3f00510-6ff9-4c9d-a655-8a4b97e8edfa + + + + This is link 2b4d5045-366f-4f97-aba6-aa8fbfdf0859 + +
        2. +
        3. + Profile picture of user 2f0ab145-954c-4bc2-9c21-9d206011d46b +
            +
          • + This is item 96eb027b-135a-48e9-afc5-a72fe54c2bc6 +
          • +
          • + This is item 157260d3-6dc1-4c21-98ac-bbc8ea3fb381 +
          • +
          • + This is item 6f1f76b9-62de-47c5-84eb-2f5860438f5e +
          • +
          • + This is item 1dff0ac0-c446-4e13-9d4a-50de18b3596f +
          • +
          • This is item 157bdf66-c20c-4c0f-b008-697a56bd9a38
          • +
          +
        4. +
        5. +
            +
          1. This is item 4153d60e-c196-443d-9e91-4d0d21f02007
          2. +
          3. + This is item 63085d0e-ae97-41cb-b2be-2b3c3c8c3dce +
          4. +
          + Profile picture of user 326da026-4632-45be-b6a2-62c1f8f9cc6d +
        6. +
        + +
          +
        • + + This is link 25c6d6fc-f94f-4580-a36f-2a44998b9733 + +
            +
          • + This is item c44c43b8-1d10-4251-aee9-a77385d4ce37 +
          • +
          • + This is item fe8fa393-b198-41d1-825a-7eb12e31eaad +
          • +
          • + This is item bb6f17f7-7f7e-4ec1-ab6e-58dee2a10e53 +
          • +
          • + This is item b10811ae-f86e-4cfb-b2ed-61e9d99864b3 +
          • +
          +
            +
          1. + This is item a3f9e46b-c779-48ca-adf0-d169abd7bc5f +
          2. +
          3. + This is item e3a4b25f-0303-43bc-99f3-965c074b0397 +
          4. +
          +
            +
          • + This is item e931813f-7668-4238-aeda-357a7c9443aa +
          • +
          • This is item a92e9e71-2ea2-44ff-a5d7-0cac09e171f7
          • +
          • This is item a02d64a6-a7e2-4ad3-9e53-c6465be2eb9a
          • +
          • + This is item 07ee939e-9f39-41a4-9e47-080460185bf1 +
          • +
          • + This is item 8c3d9f5d-05ae-4f65-aa64-e2aaeb381d6d +
          • +
          +
        • +
        • +
            +
          • + This is item 9faca138-9bd0-49d4-82d1-b6de3b5b697d +
          • +
          • + This is item 598778a7-b5d3-426f-b43b-4aa40999329c +
          • +
          • + This is item f6755a82-3875-42d6-8a2b-c502338c5e1d +
          • +
          • + This is item 75e1ed85-f225-4a06-a44d-3a1cb66d49e7 +
          • +
          +
            +
          • This is item ef3b02d8-9349-44b4-8d89-148526e07fca
          • +
          • + This is item a8b8f0c2-901c-479e-bec3-4f6a6b5fdf82 +
          • +
          • This is item 240be924-8da5-48a8-99cb-c7809ed6c48f
          • +
          + + This is link 948945c0-2254-4178-9e96-5a14ee275940 + +
        • +
        +
      8. +
      9. +
          +
        1. +
            +
          • + This is item 924e179f-5079-4843-bd5e-51b2a7b855f9 +
          • +
          • + This is item 7199569e-c052-4517-9658-65b02f027df8 +
          • +
          • + This is item 70b5684c-8ebd-4c00-be2a-e86cd4fd6919 +
          • +
          +
            +
          • + This is item 32d42d3e-8d3a-4718-930f-24f16063efc7 +
          • +
          • + This is item c29ecf8a-b16f-4778-9397-d31c646b54c7 +
          • +
          • + This is item f8739f11-3a74-4f7a-ad40-12e78d700cd5 +
          • +
          • This is item 75beed10-0db5-470b-acf6-60f144ca5db1
          • +
          • + This is item f35cb374-5553-49fd-a402-fa60025a297c +
          • +
          +
        2. +
        3. + Profile picture of user a7c517ca-0899-4a2a-8fcf-ba1fc2fb66b5 +
            +
          1. This is item 699b5cda-8940-4809-9b98-050fb4b11c22
          2. +
          3. + This is item aac912f7-6acf-47ea-8d9a-deffb164bfb4 +
          4. +
          +
            +
          1. + This is item 5f574f89-a3f3-4594-9a09-14d5838f8b53 +
          2. +
          3. + This is item d66c13ac-be53-41f1-ab7a-028d47cf4d07 +
          4. +
          +
            + +
          • This is item b8fc0089-7ca7-4eb0-8722-1662da683fb4
          • +
          • + This is item 0bc0a83b-cf91-45e9-871b-07529726f312 +
          • +
          • + This is item 6cd91658-d2e0-4f1f-b002-7ae34ae7eecc +
          • +
          • This is item 5eb4fd90-ab71-4984-ba8d-a6a2a7529fa6
          • +
          +
        4. +
        5. + Profile picture of user d41483d2-7d26-4ef8-98de-d93e5b13dc15 +
            +
          1. + This is item 733ed128-8da0-4086-a8c0-a8d2ad178651 +
          2. +
          3. This is item 10e418dd-d680-489e-819e-20eebe47ca13
          4. +
          + + This is link aa6c77e5-ce06-4b50-8563-3c5335277659 + +
        6. +
        7. + + This is link 55e050ea-be7f-4126-a907-387f4b5cd09a + + + This is link 40058854-4895-4f26-8eea-c10c4b2cb928 + + + This is link 85c6399c-5ef2-4f08-af2b-f37a14617345 + +
        8. +
        +
      10. +
      + + Profile picture of user 6feb5bbe-a224-4b1f-b3c0-f33c7d23b6a3 + Profile picture of user ce98532f-31d6-46dc-ae9b-9a3cb3890eb3 +

      + This is heading 1410e2cf-01ca-452f-9c12-ba3db01acaf4 +

      +
      + +
      +
      + + +

      This is heading f8a5e5dc-f6b2-483b-8ff2-0ac1a20f235d

      +
      + + This is link 1166bd3e-162d-456f-a80f-42b5d7299732 + + Profile picture of user ccad3d15-1a11-4d87-93f3-0c26d0c3cff8 +

      This is heading 9334ab2a-6eff-4bfc-b2a3-0df5a3308e7b

      +
      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 @@ +
      + This is link 3ba81bb0-79d3-4665-85c9-b5afb8f0c095 + + + + +
      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 @@ +
      +

      + This is text 836e86d8-98b4-4cc0-a1e8-d56d2960c916 + + This is link 9e4bd8d6-5f33-4b19-83ae-1025bf2ee87d + +

      +
        +
      1. + Profile picture of user a0e842b8-d3ae-4ef0-9b48-058444b57fe3 + + This is link 30477fc0-bc3f-4002-a52c-7c23684d5a25 + +
          +
        1. +
            +
          1. + + This is link c8408f7d-789a-4741-8da5-6c06558648f0 + + Profile picture of user 5e352ecc-7e2b-45bd-9c40-be5f325d28ea +
          2. +
          3. + Profile picture of user e5b4fa11-f581-4d3c-a47d-19ed195fd84b + Profile picture of user 587f340d-38db-4961-82a0-cc57b710c6c6 +
          4. +
          5. +
              +
            1. + This is item c057ce5b-9c22-4373-8a90-894e3191b1f7 +
            2. +
            3. + This is item 49196c3d-7bac-456c-ad29-53fa40dc230b +
            4. +
            + + This is link 061e3624-7c72-4d2c-9dc3-5ea37fc9f5f5 + +
          6. +
          7. +
              +
            1. + This is item 395207e4-33bb-42a2-8f61-58a7f521868c +
            2. +
            3. + This is item 5f11bf0c-36bd-45aa-977c-bd438a89b0bc +
            4. +
            5. + This is item 0e1cd426-b4ed-4cd3-8887-948b4a0da354 +
            6. +
            + +
              +
            • + This is item 0715564d-b88f-403d-ab11-67a2a6e86e18 +
            • +
            • + This is item b3ffbcc2-4e3b-452d-8087-1469ed896687 +
            • +
            +
          8. +
          +
            +
          • +
              +
            1. + This is item cb579a90-0215-406f-a53a-cbd8cb9331ce +
            2. +
            3. + This is item 0ecc60ca-98a1-472c-ae9f-aea43fe215ab +
            4. +
            5. + This is item 61215cdf-5505-4ae1-8e3a-b334d5b3b07c +
            6. +
            7. This is item 2a1b833b-b584-45a3-abff-8ad46d5854b9
            8. + +
            + Profile picture of user e6b6607c-0d3d-4971-a913-6b1d823a9837 +
              +
            1. This is item f5ba4434-ae7e-45ad-952e-47cf0ffdfd2a
            2. +
            3. This is item 340e1a85-cd78-4018-9a18-684cba5a81b5
            4. +
            5. This is item 53e5348e-d341-4eba-82dc-d9dc77dc7681
            6. +
            7. + This is item 8fc1cc12-27cb-4e7e-98da-19b8968be762 +
            8. +
            9. + This is item 50541d5a-8191-4c08-857c-86b6d3d6ec14 +
            10. +
            +
          • +
          • +
              +
            1. This is item 9bad4fce-bc73-47da-b830-3669e5535db9
            2. +
            3. + This is item 90531b39-cf93-4e52-a3d4-8f66448b00e4 +
            4. +
            5. + This is item 97306b2c-8c16-4d89-ac20-c869372c6618 +
            6. +
            7. + This is item 78a4c873-89c4-44f5-9139-b722276f3371 +
            8. +
            9. + This is item 510e3438-17dd-4c0e-abcb-e536c33fe39a +
            10. +
            + + This is link 45438ba5-842a-4435-8f1d-bdcaf227cba8 + + Profile picture of user e3a2df48-c057-47cf-ba5d-c36ce5daff6a +
          • +
          + Profile picture of user 97267d42-8a97-499b-9e00-9d9cfa6da07e +
        2. +
        3. +
            +
          • + This is link 10c68f64-3291-43b2-9368-178533d0af8f + + This is link 3bc959ab-5ecd-4cb8-b281-8fef1c49def3 + + This is link 967b2db6-c38f-465f-a256-043db89719d1 +
              +
            1. + This is item 64f7e4a6-8102-448b-82eb-176bbbcd8b5b +
            2. +
            3. This is item cea7356f-b8b9-4bb1-8bf3-b466027c2b73
            4. +
            5. This is item 1d561a0c-b820-4f1e-8f7f-c5acea51e676
            6. +
            7. This is item c59908ef-a8aa-4ac9-a562-aeb5498901ff
            8. +
            +
          • +
          • +
              +
            • + This is item 989954ab-2b75-4bd1-a988-d96160530009 +
            • +
            • This is item a266e290-5eb4-4ccd-a4e1-7cb3b578499e
            • +
            • + This is item 602983e8-a1fc-4452-b02e-6224f9ea1002 +
            • +
            +
          • +
          • + Profile picture of user fd6a3e7e-6f06-4e61-9ac0-46fab81b14bb +
              +
            1. This is item b1c61ed4-2740-48a7-a905-8315ecead010
            2. +
            3. + This is item 184eaf1c-248f-4f1b-becd-fd9ff1afe7e1 +
            4. +
            5. + This is item d18a7d9f-0957-409c-bb81-6fbdbd7915d6 +
            6. +
            7. + This is item c0804123-0c73-4ace-92d6-14c2f86c59f4 +
            8. +
            9. This is item 2cf99604-4831-45a0-b996-ce5aee4c2edd
            10. +
            +
              +
            • + This is item d781aba5-0df3-49c3-914f-923056e061cb +
            • +
            • This is item b53b6af3-2dff-4241-8623-6f46876e1d0a
            • +
            • + This is item 309f238a-402b-457d-ac39-f3213233fa44 +
            • +
            +
          • +
          +
            +
          • + + This is link 7fee9a30-c948-4413-b008-07ccdef01f6a + +
          • +
          • + + This is link cfb757dd-9ad7-4fd1-b4bb-05d8c03f89e7 + + Profile picture of user 5f0c4f37-7d80-4b81-aa03-4a58c9bd8bd0 +
              +
            1. + This is item e81c40f9-879f-450f-97fc-e20e0d64cbcb +
            2. +
            3. + This is item 5e582e0c-4296-4044-b337-9e972e2ad643 +
            4. +
            5. + This is item 0d449017-fc20-4fd5-978c-5273836c7bc4 +
            6. +
            + +
          • +
          • + Profile picture of user 958da973-a646-449c-95d3-0d74b837f30b +
          • +
          • +
              +
            • + This is item bbb1b755-ec35-45c8-b353-f2e7c41030bf +
            • +
            • + This is item aa45ba7d-d709-4656-ae61-5ae5ebc1ae00 +
            • +
            • + This is item 02b590ec-ac10-48b2-87c1-86f5a33d46cf +
            • +
            • + This is item 50cdcd16-49ca-4fa7-9006-0cd2f1028326 +
            • +
            + Profile picture of user 5623c03d-a5d0-45fc-bf68-d67a2e44e634 +
          • +
          +
            +
          1. +
              +
            • + This is item 7b8736e5-bc5c-402d-9fc1-81001075e626 +
            • +
            • + This is item 8de83aa7-e4c7-41fb-8b56-0dae0ea9d19d +
            • +
            + Profile picture of user 98b62066-927a-40af-b29e-80f9beb8d845 + Profile picture of user 6b95579e-24e7-46de-9e7d-5747d477691c +
          2. +
          3. +
              +
            1. This is item 37d8f011-5c8b-4310-b856-f7d3369d4249
            2. +
            3. + This is item b4760918-68e1-49ca-884c-c95db35a06c3 +
            4. +
            5. + This is item 17d3fcb9-ebde-4d33-8551-ff391207fd01 +
            6. +
            7. + This is item 4af8fee1-8328-4bad-b005-0c5148c624d0 +
            8. +
            9. + This is item 9fefc0af-9275-4a57-8369-a12cafeeadf4 +
            10. +
            +
          4. +
          5. + + This is link 28f60217-408b-462f-985b-1c5274960125 + +
              +
            • + This is item a5d486f8-75e1-4bd1-997a-d9bb608b7b58 +
            • +
            • + This is item f9b8ddfb-00b4-4e8a-81d1-425fc607e6ad +
            • +
            • This is item 133e5f91-cf58-4cae-a28f-f5eec3325881
            • +
            • + This is item d93da5f0-aae5-40d1-8bae-8a06d4ee1391 +
            • +
            • This is item e42f0a9c-eab6-4d4c-af19-186577a6197b
            • +
            +
          6. +
          +
        4. +
        5. + + This is link 6f4e2534-d8a3-4e13-aedb-0d637f22bbe8 + +
        6. +
        + Profile picture of user 42a741ec-a46f-4e64-bf60-ae292daba13b +
      2. +
      3. +
          +
        • + Profile picture of user f568f97e-22fb-4a97-82e2-7c2079b1dea8 +
        • +
        • + + This is link e0937178-6d82-4da0-9ca7-c9b4825ff798 + + Profile picture of user 9b0b0356-eeb0-4fb7-b860-f6c8b363d303 +
            +
          1. + + This is link 58676856-1c90-4050-8e95-7b9474cb92cc + + + This is link 9a0d716c-6110-40dd-8e51-800a2a613187 + +
              +
            • + This is item a14fc367-fd50-40ab-b66e-67570fe93857 +
            • +
            • + This is item b5a87c1b-2099-428c-898a-1f8730b9e583 +
            • +
            • + This is item 8bf0e268-d4d9-4256-aa29-3f4bcc33a85b +
            • +
            • + This is item e050379b-819f-4f8d-985f-638222c2471b +
            • +
            • + This is item 5a793a0a-2871-4e96-9bb0-8ee894ecf819 +
            • +
            + + This is link 419bab39-4570-4fba-bd03-0e771e351578 + +
          2. +
          3. + Profile picture of user eb95f7b7-a0b5-434b-b05d-6792544073e3 + Profile picture of user 775195f0-c4d3-4ce0-9153-3f7e4a165423 +
          4. +
          5. +
              +
            • + This is item 70332add-e165-4964-b2de-c708c2d344a1 +
            • +
            • + This is item 53ae5347-7931-4527-8cc8-ddd4ef713d2e +
            • +
            • + This is item 65a8238e-275e-4ed0-878d-ccf9cc64d211 +
            • +
            + + This is link b5203c36-000c-4c7b-9a57-dbd98964cee8 + +
              +
            • + This is item 64856127-4d03-4244-9f6a-c6bb010874a3 +
            • +
            • + This is item 54cc09e6-1641-4654-9d19-dc9d2043d834 +
            • +
            • + This is item 5255680b-ed2a-47fd-a350-9c5c18189adb +
            • +
            • This is item 78d39c9c-97cb-4625-98e4-55a1a9104afe
            • +
            +
          6. +
          7. +
              + +
            1. + This is item dfd99a59-7d22-4ba2-94f3-3a92f89fa886 +
            2. +
            3. + This is item e1b7890a-9a3c-49c9-b7d2-60a4a5de9476 +
            4. +
            5. + This is item f7d9c6b5-4a57-4443-bf2a-5dc0603b89e4 +
            6. +
            +
              +
            • + This is item 144207ad-7633-4eb8-8a9f-783156937897 +
            • +
            • + This is item 45cbaa7d-22eb-4fc4-86c1-5956f03324a5 +
            • +
            • This is item b5eecd7f-0811-43d8-8c45-72d8a52fc9be
            • +
            + Profile picture of user 8c703ccd-ca1c-4f08-b514-99eb940a5720 + + This is link a5619c84-8bb8-4396-8ddf-a14ab872fec8 + +
          8. +
          9. + Profile picture of user e73c41b0-bb7b-4bb7-a0fe-a0b8a4bdf05a +
              +
            • + This is item eebbbde8-c3e6-49b4-929b-65ea75acea6d +
            • +
            • + This is item ec11b5af-e161-457e-9287-fa712a1028e1 +
            • +
            • This is item 24c31676-0530-4a89-9f19-0f940fbf11a7
            • +
            • + This is item 6de989fe-1001-4f70-be86-e09ad36d0757 +
            • +
            • This is item 4da5552b-9f59-4dd9-a871-027b3d51b0ec
            • +
            + + This is link 3c640db0-c0f0-4c2d-aba4-2dfabdc10b0a + +
          10. +
          +
            +
          • +
              +
            • This is item 01df3a76-c945-46d8-9256-686b96d33b57
            • +
            • This is item 798722c0-a16f-4570-8426-86c78b49f5b6
            • +
            • + This is item 5b52281f-5c22-4610-87f9-495d2820f535 +
            • +
            • This is item 1b522ca1-d1fe-4342-b3cf-05a518a0d327
            • +
            +
              +
            • This is item c7fffe7c-ddca-49ef-88c4-1275d796043d
            • +
            • + This is item a6f6058e-9c37-4753-b667-87d4666230d2 +
            • +
            • This is item 7de0a12e-091a-436f-aba3-1f31d348b4e3
            • +
            • + This is item e901c9d4-73a0-49cf-bea0-b938b47ef960 +
            • +
            + + This is link de20ea7c-c436-4a16-8697-e6116d62e702 + +
          • +
          • + + This is link 1b62aa0a-17e0-4eb1-8e13-e12c212ac1d1 + +
          • +
          +
        • +
        • + + This is link 1bb8befc-8e92-498a-8450-22f8f7350ae3 + + Profile picture of user e59b75b6-f39f-47a4-9526-69fdf1b74951 +
            +
          1. +
              +
            • This is item 4fb57671-f456-4b76-bea6-7dac6a1230b2
            • +
            • + This is item 860851f6-7242-4b45-a0d5-9094c58b74db +
            • +
            +
              +
            • This is item 366c314c-f9a3-4766-8db0-73a5a3205821
            • +
            • + This is item c29817dd-62a7-4d87-8aae-946241a0e602 +
            • +
            • + This is item 11edda55-1024-4883-b805-f1acec8018d9 +
            • +
            • + This is item b27c71f7-c949-43de-9bf0-059984bd1b5a +
            • +
            +
          2. +
          3. + Profile picture of user 56494d73-ed7e-43cd-bc19-79540aa301b1 +
          4. +
          5. + + This is link d3c84b88-828f-4b61-8dbc-88406df468cf + +
          6. +
          +
            +
          • +
              +
            • + This is item 416632a2-e5c1-4d83-8c7c-1629f73d3201 +
            • +
            • + This is item 547ae974-2d98-4f0f-9dbb-63157a3be51a +
            • +
            • + This is item 8b0fadce-db0a-4cb0-b835-ef9048976840 +
            • +
            • This is item f9658882-5766-4c8b-8f96-f60b6ba5689b
            • +
            + Profile picture of user 0a65d7ab-4c74-46bb-aafe-527f5010c776 +
              +
            1. + This is item d194ef9b-ddc8-4758-9828-7d6549efaaee +
            2. +
            3. + This is item 86529bed-e772-46fd-a678-e46261f5de10 +
            4. +
            5. + This is item 1f8a1969-4baa-401e-a6c2-7a3c1cd40617 +
            6. +
            7. This is item 094e63c1-065f-450f-92ef-f05c58b494bb
            8. +
            + + This is link 33aee684-c2b4-4275-8484-b73f175b6d0b + +
          • +
          • + + This is link aa8532c7-e4cf-4cc4-9e3b-3a2596f481a0 + +
          • +
          • + Profile picture of user f454c7ec-b4d4-49c3-9f73-0dd9468d9cd5 + + This is link 33520f96-7abc-4988-a09c-3479eacae08c + +
              +
            1. This is item 76213aa9-0b4b-4594-a343-29fedf823483
            2. +
            3. + This is item 500e0ba3-4eea-4652-882c-d285975c65b2 +
            4. +
            5. + This is item 0d50da62-6d17-4ede-ae24-92dffbd6de53 +
            6. +
            +
              +
            1. + This is item e090f68b-9911-4ed3-82cb-91da095236ec +
            2. +
            3. This is item 40780782-a3bf-44f8-9c80-3117ed333483
            4. +
            +
          • +
          • +
              +
            • + This is item f0a6fd4d-3234-4f94-b9d3-a29c26214742 +
            • +
            • This is item cd2cf691-ceb2-48cb-b4b9-8fbaae2d5aaa
            • +
            • This is item 82b87855-4bbb-4132-b59b-b46dd580ff64
            • +
            • + This is item 7dedd7a0-991d-42cc-a620-a4f5915fc685 +
            • +
            +
              +
            • + This is item 03aecbb6-4ff8-47a1-aba2-b47d308e43f5 +
            • +
            • + This is item c7d51352-4b2d-42d6-8bf2-2529116c7756 +
            • +
            • + This is item 9119dcdc-50a3-47f9-bdbb-ee88c95be73d +
            • +
            • + This is item 50cf49c0-111d-47b7-a787-73aef9eec9f9 +
            • +
            • This is item adb1e090-00bf-4f20-8b5f-d65f03523246
            • +
            +
          • +
          +
        • +
        • +
            +
          1. +
              + +
            1. + This is item ece6b694-40e4-49e1-b870-d70e9d98a3eb +
            2. +
            + + + This is link 69e13912-ad10-40a4-8761-d9e72a60c5cf + + + This is link 05271b64-a3a0-42eb-a836-a46161f863f6 + +
          2. +
          3. +
              +
            • This is item 6b4fb5b7-0599-404d-a64b-2a325c920910
            • +
            • + This is item b82e6800-c239-4b14-b54e-50fb7378a4ba +
            • +
            • + This is item c7101118-bf8d-4ec1-96e6-e8047cd34c12 +
            • +
            +
              +
            • + This is item 1b7093fe-f0ca-4170-9aa3-facae456e232 +
            • + +
            • + This is item 93a1ae96-702b-47e2-ac93-a5d7fd275ea9 +
            • +
            • + This is item d2e82235-9f23-4d4b-bdc5-8e42f7ac26b4 +
            • +
            +
              +
            1. + This is item 0b3925d5-5234-4206-9338-53d6da8fb093 +
            2. +
            3. + This is item d3b18b1a-d391-4b93-b8f1-8d6d1472b321 +
            4. +
            5. + This is item 4266f80c-84c3-4aeb-8b76-782043847e25 +
            6. +
            7. This is item bcf0f594-af3b-4211-bb37-3ad1bba89293
            8. +
            +
              +
            1. This is item 77b0a821-f933-4b9b-9222-9cce8365207e
            2. +
            3. + This is item 3b995b79-6f10-4cb3-bbfe-77e35fe5a8a4 +
            4. +
            +
          4. +
          5. +
              +
            1. + This is item 67d5d9c3-8397-49d1-a231-02da836ce4d4 +
            2. +
            3. + This is item 1a340dfd-216d-4e9e-b115-e45c3ebf811c +
            4. +
            5. + This is item 63df0cf3-3d65-4062-99ad-a30b41dc8904 +
            6. +
            + + This is link d57787d9-fba9-4670-87c6-47c431db5350 + +
          6. +
          7. + Profile picture of user 5af06942-50a7-4956-92a9-b152eac252e3 +
              +
            • + This is item 4d8eef66-e20d-433c-b2b9-7fc10ca28d21 +
            • +
            • + This is item 2da73d83-c62a-4904-b98b-e45b95bf9fd5 +
            • + +
            • This is item d086ad53-f39d-411f-986a-0b2c5db001d9
            • +
            • + This is item ce434f3c-11cd-4127-83ce-80b96955b67c +
            • +
            +
          8. +
          +
        • +
        +
      4. +
      5. + + This is link a454857e-44c5-4361-b89d-aa05a85e8435 + + Profile picture of user 56f662ca-63f5-42e9-b367-51a6f1d398fe + + This is link f409a5a0-d473-4011-8334-900443e0f18f + + + This is link 268b4b30-93e8-4f5e-a151-a32833ffe426 + +
      6. +
      7. + Profile picture of user 5430339c-3ce1-4bac-8f74-dc14b61a05bd + + This is link c2c9e114-f9b2-4a5e-8011-28be79a68c71 + +
          +
        • +
            +
          1. + + This is link e45f7944-8675-4fe6-9b82-791950e51983 + +
              +
            • + This is item d0df2580-e6ab-4585-bc13-22c65b489a50 +
            • +
            • This is item a8d5f399-d996-49ef-beb9-e6bd52ef4d33
            • +
            • + This is item cdac711c-5694-4dab-b18b-0d244e0dd63a +
            • +
            • + This is item d2bd1abd-31ee-42bf-a96d-0f34bbc66367 +
            • +
            • + This is item b4dfa43a-baba-4b58-b6b0-f959951c85f1 +
            • +
            +
          2. +
          3. + Profile picture of user daa0f908-37e5-4794-bd58-bf22b0671b4b +
              +
            • + This is item 0c48a48e-40d7-44b4-85ab-f6196279bf43 +
            • +
            • This is item 23379794-c1aa-4876-90e8-32ad1ea7577a
            • +
            • This is item 5064ae15-6411-4f51-a6ec-598be92967a3
            • +
            • This is item 0031da59-a7cd-493d-92af-7a62c259580a
            • +
            +
              +
            • + This is item 7203d4e0-2b5c-4a41-ba62-9c9719b2d6b4 +
            • +
            • + This is item 2fb5ef84-f456-4eef-b6a4-0676646cbadd +
            • +
            • + This is item 23141800-9858-438b-8571-36e6eb91d82b +
            • +
            +
          4. +
          5. + + This is link 9cc104fd-e415-40f9-b763-9e82158fcc9b + +
              +
            • + This is item e8d74548-6565-475e-9a8c-d1f5e55105df +
            • +
            • + This is item c9c52cdf-7244-4432-a788-2e6dfa26f6de +
            • +
            • This is item 66bbe44c-810f-443d-b239-4376389c466d
            • +
            • This is item 10e43390-0ddd-46cf-b721-a1d6dac4101e
            • +
            • + This is item 46ceca10-6552-42b3-9517-08fb49c75a71 +
            • +
            +
              +
            • This is item f600916c-6408-48d6-be1a-fc38a66439e9
            • +
            • This is item b2b6c2c5-0d2e-4e64-94a2-f021584719c3
            • +
            • This is item 0b179919-1d19-420d-b8f3-4ff8baa40e10
            • +
            • + This is item cf003bc1-a051-4ae6-b665-687e194c7f30 +
            • +
            • + This is item d92285fd-d158-4898-9d71-15c6e613de58 +
            • +
            + Profile picture of user 556bf043-22e9-48c4-b617-4d33c8f1cd96 +
          6. +
          7. +
              + +
            • + This is item 680e9155-9cc1-4009-8c36-9573c195b706 +
            • +
            +
          8. +
          + + This is link 3ca462d2-05e5-4cf2-b8a8-a7a4ec66b8bc + +
            +
          • +
              +
            • + This is item 03e1b234-575b-4357-98b4-a9b6f96b5560 +
            • +
            • + This is item f9257907-d82d-46e8-a653-1ae85e6f12cc +
            • +
            + + This is link 10509934-570d-4d14-aebf-254b9b9bf001 + + + This is link ebf14a51-271f-4b8a-9430-def203b04b0e + +
              +
            • + This is item b1309e2b-47b3-40d3-9bd0-641826900e41 +
            • +
            • + This is item b33d3bf7-dfc2-4889-b814-70ad48cb8bcc +
            • +
            • This is item 2b18c2ed-e08a-4eb2-8dba-3b34cb550aa2
            • +
            • + This is item 9e57e9eb-6d6f-4a34-87e9-2f24c5436245 +
            • +
            • + This is item 8569574e-e2ca-4141-897a-379f66dd74cc +
            • +
            +
          • +
          • + Profile picture of user f73aab2b-19fd-4afb-ab46-c25dc9870a88 + + This is link 88253cf6-de69-4709-809b-4481667484de + + Profile picture of user 65cf0c13-0e08-4088-98b4-8cdf2602e754 +
              +
            • This is item 2b3de75d-c11f-44cf-83b9-d3fba1fbfbe4
            • +
            • + This is item 1d18392c-2253-41cd-9474-2619c9a54b7c +
            • +
            • + This is item 5393b275-6d7e-48a1-94a8-2394cef905b0 +
            • +
            • + This is item 400fb703-342a-4115-be65-590b56b8fa50 +
            • +
            • This is item d55df31c-7841-494e-aeab-5c39c2743757
            • +
            +
          • +
          +
        • +
        • + Profile picture of user 5f60a1c1-9e3f-4928-87ec-4787e070f888 + + This is link d1d99f68-5536-4945-be50-66b1d1ce5342 + + Profile picture of user e23e4396-e2ea-4c9b-b476-52dd36bdd7eb +
        • +
        + + This is link 5717a254-b665-4b24-b123-5f08e45f79ca + +
      8. +
      9. +
          +
        • + + This is link ff60b32d-58b1-4453-bd6d-1d1390a83184 + +
            +
          1. +
              +
            1. This is item 972b4b57-82bb-4f0a-a0dd-00af80a28193
            2. +
            3. + This is item 27a5dc22-62e4-4ba2-90be-93a7e28d9b93 +
            4. +
            + + This is link c136f069-3287-4aa7-a34a-c12a071774e0 + +
              +
            1. + This is item 57bb1b4d-fac3-42b7-b56a-d1195136251a +
            2. +
            3. This is item 35cf058d-d111-4f14-b6fb-c03e7356f1be
            4. +
            5. + This is item 048a85de-2f65-4cf1-9e9f-d8d1c9296ac8 +
            6. +
            +
              +
            • This is item 0bb622c2-5f6d-449a-8295-42a95dd4d7e7
            • +
            • + This is item fc46a1a8-7628-42cb-b71b-d4126bb29d62 +
            • +
            +
          2. +
          3. + + This is link 29eb45be-4033-4a2f-a7f4-776a6c4e5d85 + + + This is link cbb38cd9-9ba7-41ac-b360-b0660b8d9c5e + +
          4. +
          5. +
              +
            • This is item 2c2450e7-17fe-4318-9b17-7b8c0a45422c
            • +
            • + This is item 5a0461ac-0adc-49d0-9e41-264fbe5b4911 +
            • +
            +
          6. +
          + +
            +
          1. + Profile picture of user 8d2ee06c-66ec-43a8-b164-58729e086aac + Profile picture of user 7ab72bf8-330a-4bfc-bce7-a4e591fe17f0 +
              +
            1. + This is item 9cbcdb21-bfe5-49d8-9a30-48822a962513 +
            2. +
            3. + This is item 118feca9-6f06-44bb-af28-5ce1c6857a77 +
            4. +
            5. This is item 532b3a46-c994-4062-9de8-c33c52ddff64
            6. +
            +
              +
            1. + This is item 410af27d-f8fb-4e8d-a19a-254a04b9a2e4 +
            2. +
            3. + This is item 802b289c-e3d7-4a9a-b307-3bc5b8fb9fa8 +
            4. +
            5. + This is item fb061d7f-1209-4392-bde8-ed8e7d9c1b58 +
            6. +
            7. + This is item 1e1467eb-5f97-4ad0-9176-e5dc0b622ddb +
            8. +
            9. + This is item 08bfacab-6582-4d66-89a9-14a5dc2a6e13 +
            10. +
            +
          2. +
          3. +
              +
            1. + This is item 607255cd-3b5b-45a0-9e49-2042ceb1d62d +
            2. + +
            3. This is item 219dfc57-42b0-4525-b9e4-af068808bfbc
            4. +
            +
          4. +
          5. + Profile picture of user 39be8647-ca40-4048-8f59-358b350c4cc9 +
          6. +
          7. +
              +
            • + This is item 878e5303-4dfd-4999-a321-5975fc4bdcbe +
            • +
            • + This is item 737593ce-1283-47aa-b995-9a95752e5e33 +
            • +
            • + This is item 69f1612b-7dff-49ae-b421-1f11a84506fd +
            • +
            + + This is link ac7e9b94-d486-445c-8351-1845d379ad54 + +
          8. +
          9. +
              +
            1. + This is item 159a0e92-7304-474f-9c2a-9996a712a416 +
            2. +
            3. This is item 09b3acba-a146-4eeb-897c-16d1e985a382
            4. +
            5. + This is item b241a536-8e2f-4815-b565-d90aca2a2a5c +
            6. +
            + Profile picture of user 02635db4-e4fe-4829-b45c-a2b7693562af +
              +
            • This is item 0ec9a2de-2d46-4942-b27b-a5c00957abb9
            • +
            • + This is item b784f4eb-5a56-479a-8341-1353d90080fa +
            • +
            • + This is item 66fcef9c-156b-4709-bbec-0a42c347b39a +
            • +
            • + This is item 805f9916-1d0b-4210-88fa-4fb494708b22 +
            • +
            +
          10. +
          +
        • +
        • +
            +
          • +
              +
            1. This is item 8e35c97c-0e04-4ef7-9e12-17d364903426
            2. +
            3. + This is item 64ffb17b-cb90-4e8f-8cf8-ce1e8f27918e +
            4. +
            +
          • +
          • +
              +
            1. This is item c06503d0-fda9-4b87-ab19-f534ba28f0cc
            2. +
            3. + This is item ad90924c-bdae-49d2-b4cc-e3de5811657c +
            4. +
            5. + This is item a93b49e5-561b-4e44-8dfb-cf7d2ae642cf +
            6. +
            + Profile picture of user a0a2aee6-7184-4f36-aedc-7334f739bd25 + + This is link 5e06acd9-d479-4695-bcf0-d4666f64f0be + + Profile picture of user e09d31c5-5dc8-4814-9044-d8992c0664fb +
          • + +
          • +
              +
            1. + This is item 189fa4fa-ca21-4d73-b295-383da320ece6 +
            2. +
            3. + This is item 571fb48f-57f8-47dd-826e-925e7b646d84 +
            4. +
            5. + This is item b7da10be-e360-4e28-8ffb-30870f97c7a6 +
            6. +
            +
              +
            • + This is item eeca141c-f2fd-4834-9ed7-5993b0cb65e6 +
            • +
            • This is item 1058035d-b1b3-4efb-873e-0486070b4d7d
            • +
            • This is item 6f271226-cb1f-467e-87b2-e16d9b070f97
            • +
            + + This is link 36819225-12b5-4c11-b2ee-404856bd9878 + +
              +
            1. + This is item e38d5c9c-940f-4878-8da5-a65feafac7ab +
            2. +
            3. + This is item dfc8489e-a4dd-48f7-943b-fba2fabe2ca8 +
            4. +
            5. + This is item e3e26f1c-08e2-4b62-b73e-2d349c5de1bf +
            6. +
            7. + This is item 9f9719ed-a737-4930-a65e-6474781c64fd +
            8. +
            +
          • +
          • + Profile picture of user 1ce02e65-a0f1-4305-9a12-23afd01582cd +
              +
            • + This is item 232d51b9-094b-475c-ad53-89e32bcea259 +
            • +
            • + This is item c1c154fa-3a2d-4109-94d1-b12ba6f0991a +
            • +
            • + This is item 1061b106-4316-4580-b6ff-26e6c0ebb3fe +
            • +
            • This is item 5ec8657f-7020-476e-b43f-86594f4aecce
            • +
            + Profile picture of user 76903155-ca94-4f32-8b5c-33f495e0a291 +
              +
            1. + This is item 34cba3ff-d465-461c-a7bb-4b7d4881e7e2 +
            2. +
            3. + This is item 658e812c-66f0-4c8a-9332-8f2b17f7f592 +
            4. +
            5. + This is item 635e9312-3baf-45f0-a1cf-9615ae21b6e8 +
            6. +
            7. + This is item 311b639e-309f-4ec1-b302-7bb23cd1c71f +
            8. +
            +
          • +
          +
            +
          1. + + This is link 516ad276-f409-459b-9daa-8a7ba3562946 + +
              +
            • + This is item 1ac42b53-615a-4940-aee3-e57aeb34ebdf +
            • +
            • + This is item 8c9c434a-c624-4ee0-b582-3f41ad4340ff +
            • +
            • This is item 9ad8cc2a-afe3-46e6-a519-dde03abe7910
            • +
            • This is item 9902400a-5f06-4ad3-aebc-39d46eb8e3ff
            • +
            +
              +
            • + This is item 460badc8-198f-4d36-bee7-180e75240f27 +
            • +
            • This is item bfeba06a-b690-4d92-8e8b-5198e0f66142
            • +
            • + This is item 9e4dbcc4-4afe-44e0-8b58-f97beb15f526 +
            • +
            • This is item 45486da8-367b-4725-82bd-0da52c7026a4
            • +
            • + This is item f4361c20-472a-4867-80c3-21deddfbeb3d +
            • +
            +
              +
            • + This is item 269f6c03-ccd1-4122-b452-f3d0aa63cb53 +
            • +
            • This is item eb36dd6a-b1f6-4c81-b8b2-e9eff1af9c53
            • +
            +
          2. +
          3. +
              +
            • + This is item d7f1babf-f7de-4e67-9c56-938e31f94fb7 +
            • +
            • + This is item e432dabd-9a91-4277-813a-e196e7c79a2e +
            • +
            • + This is item bf28a174-8700-4c44-8eba-3e1e7abb672c +
            • +
            • + This is item 1d215cde-6de7-4ec6-8ddd-4db2d01b961c +
            • +
            • + This is item 55f92724-681b-4d48-9284-f6ed038c8b5c +
            • +
            +
              +
            1. + This is item 01c29adb-8234-48e8-bea6-22589ea8aaf4 +
            2. +
            3. + This is item 15c0dae2-5be4-4c0d-9827-5fd6a1a857ce +
            4. +
            5. + This is item a857ecee-7d18-4918-a0e3-d6a46748bb2a +
            6. +
            7. + This is item 2c14b1c7-4932-4f30-8c06-6de7a40114f6 +
            8. +
            9. + This is item a526cd9a-8b0f-43bf-a9e8-809538684e2a +
            10. +
            +
              +
            1. + This is item a945d582-2e27-4338-b3bd-2742e7f3b4ef +
            2. +
            3. + This is item d574e4a5-3e93-4c27-adc9-01a4ec8f7683 +
            4. +
            5. + This is item 3c000c0e-900e-4086-9e1c-fda41eff8c81 +
            6. +
            7. + This is item 296b5cc4-2d95-411a-91ec-78e506f0aeaf +
            8. +
            9. This is item e7329e7a-270e-4245-ae59-d2448ba4dd46
            10. +
            +
          4. +
          5. + Profile picture of user 439a9dce-52c7-4958-a6f5-21f0c899527c +
          6. +
          7. +
              +
            1. + This is item 3f7452fd-97aa-4f16-89c5-3c3d5c3e4b67 +
            2. +
            3. + This is item 108c3718-b94d-4aa4-a478-905631d04b0d +
            4. +
            5. This is item 29796ba9-c9f4-43ff-a148-a05d6593c4bc
            6. +
            + + This is link f4fa01df-4632-4eee-a4cf-9159c954412d + +
              +
            1. + This is item aef28e78-2f27-4389-902d-cef41dc6ae94 +
            2. +
            3. This is item ae8744e7-6b3c-4a1f-b26e-2d84807850ce
            4. +
            5. This is item 19f756c8-d168-4ec0-bc18-c6dafd02c99d
            6. +
            +
              +
            • + This is item a87cb38a-6c5e-4ccc-9c09-4377c9b34638 +
            • +
            • + This is item 764c1216-5c36-42e0-b52d-47faf61ba368 +
            • +
            +
          8. +
          9. +
              +
            • This is item 2ba85ffa-e465-4003-8cc2-28669af7be40
            • +
            • + This is item 7db5ffaf-cdf6-404d-986e-5f5a07a880d3 +
            • +
            • + This is item 72cc9ff6-3017-4571-a33e-09d41a7859f5 +
            • +
            • + This is item f5c5967b-cdae-4e04-b8fd-8e87ed9889f8 +
            • +
            +
          10. +
          +
        • +
        • + + This is link 5527db70-73d8-49eb-ad67-b29747b327af + +
            +
          1. + + This is link 7454bed8-7222-42ce-b419-2cf460982321 + + + This is link dcec0cf7-a859-4bdf-bcb0-af5ceb9f6a2b + +
              +
            1. This is item 66938766-65e7-4e0e-a511-339f0a33cb72
            2. +
            3. + This is item cc78f6e4-f2d1-427f-b508-f460ed6c1520 +
            4. +
            5. + This is item 88beaef2-5a33-4678-a525-bd9ef965fdef +
            6. +
            7. + This is item 94982416-f7d6-4399-88b7-e8f03ba29486 +
            8. +
            +
          2. +
          3. + Profile picture of user 999d59e1-3d4c-4a9b-9ead-5411bbbe8c0d + Profile picture of user 563e71df-a830-40fd-8ae2-17243ef47846 +
              +
            1. + This is item a13dcac9-bb50-4b00-9886-39ae13aa1901 +
            2. +
            3. + This is item e24cc336-44d0-45be-aa60-bb67f04e0e29 +
            4. +
            +
          4. +
          5. + Profile picture of user 347bbd2d-9dd1-4aad-a09e-08be08bd9046 +
              +
            1. + This is item f650924f-9eba-49aa-ae11-f6c9f72e9325 +
            2. +
            3. This is item f64a2440-63c7-4240-aaf3-8b6bc964bb1e
            4. +
            5. + This is item 5a42ee1a-dc80-4398-a445-efea6a9f630e +
            6. +
            7. + This is item 7b2d127a-66c6-4093-9b3e-4af7f79ad835 +
            8. +
            +
          6. +
          7. + Profile picture of user 0e0f6b2a-a613-4029-9593-8b72507766ba + +
              +
            1. + This is item 8fc349c6-21fc-49bb-bd83-433fcb5b5a98 +
            2. +
            3. + This is item 21e4ef21-a014-49b5-8132-31414db75c20 +
            4. + +
            +
          8. +
          + Profile picture of user b6ae2012-c398-42f8-a0f1-c8d13847f09b +
        • +
        + Profile picture of user 5ba8da39-2c11-4665-9997-a5c220143192 +
      10. +
      +
      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 @@ +
      + This is link 01c5a0d6-f1d4-4d79-9b17-baac0a7ab391 + +
      + + + +

      + + This is link 49239d36-d46d-424a-9600-701b1cdeb0cb + + + This is link c2f9aed6-a254-4aaa-b175-790352f1d6f1 + +

      + + +
      + +
      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 @@ +
      + + This is link afe0b95d-703b-4471-b828-7fee034e18a0 + +
      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 @@ +
      + + Profile picture of user 8825a6f0-3a41-44b8-92ec-abcd0af79bfb + + 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 @@ +
      + +
      + + +
      + + This is text 24221d94-bb69-4fd8-9100-2e0bd6662c87 + +
      +

      + This is heading 5d33bc2a-b185-422d-b55d-c17aac47f121 +

      +
      +
        +
      • + + This is link bc8a59bc-26e4-4074-8cee-fcfeae3352a1 + +
          +
        • +
            +
          1. + This is item 693d006d-417c-4e44-97ad-0e76a6509439 +
          2. +
          3. + This is item 8540dd39-382c-4965-a778-78ea1d7e21ca +
          4. + +
          5. + This is item 8d6b675e-0808-4121-8358-f254cce4949f +
          6. +
          7. + This is item 6571937e-3b09-493e-9819-880375d64c61 +
          8. +
          +
            +
          • + This is item cd8a916c-489a-405c-8204-3c7b8bfbe21a +
          • +
          • + This is item fe037e16-beee-4df5-8d56-27bc7c88b861 +
          • +
          • This is item d2c23001-7ecd-4d90-a04a-dffeb9b5e7bd
          • +
          • + This is item f71e889a-bba0-44ee-b8c6-7994ca3feadd +
          • +
          +
        • +
        • + Profile picture of user 72b68307-816c-47bc-8f62-8058a5581811 + + This is link f4f9814c-a3b7-40ab-9001-7b50926725e8 + +
        • +
        • + Profile picture of user 804da6b2-b21b-4a9c-98d6-23c6f59be6b1 +
            +
          1. This is item 488c7c4e-0c2c-4e2a-bedc-3c5ca0a682d8
          2. +
          3. This is item 2b59ac91-bf4f-4288-b3e1-74274b93711f
          4. +
          5. This is item 32b280f2-10de-4c69-ad88-c2df0bd0569a
          6. +
          + + This is link 701be519-d46e-4227-9a64-20b967687002 + + + This is link b50f4b2d-2574-47bc-b2d6-eb870a2afd71 + +
        • +
        • +
            +
          • + This is item 5707a843-bd7d-444e-bf92-e63923c6b0eb +
          • +
          • + This is item 32cbf474-92b1-4fc6-8ae4-8d4a239f9403 +
          • +
          • + This is item 9522936d-9611-4b39-8242-d3ac14572760 +
          • +
          + Profile picture of user 9ee96d5b-e135-44df-9b0e-2a005135a305 +
            +
          1. + This is item 63066c52-90a5-4852-8583-0d504fc75fc3 +
          2. +
          3. This is item 26ffbbca-d601-4e02-a032-c2ccb5f9de57
          4. +
          5. + This is item 81c48939-6eff-476b-baf7-865e09b63b1b +
          6. + +
          +
        • +
        +
          +
        • +
            +
          • + This is item e6cd16be-928b-4129-9296-e7a8252a0404 +
          • +
          • + This is item 699c4ae4-8e51-421c-acc2-8154878f97ac +
          • +
          • This is item b2d30a67-7cd2-4a0d-8dba-19b1492022ff
          • +
          • + This is item d75c4478-7a96-42de-97cd-0026f7db1c00 +
          • +
          +
            +
          • + This is item 8458c078-b26b-43f6-a246-c19286b82514 +
          • +
          • + This is item b1446e9f-603d-49f9-90d7-03966010b3b2 +
          • +
          • + This is item 05f1a3cd-94c9-4414-a2c4-7b945385479b +
          • +
          + Profile picture of user cc90d0b5-a2d0-4636-8827-6ef0351ed134 + + This is link 3075addc-d1f5-4255-84fd-edf691d8893a + +
        • +
        • +
            +
          • + This is item 2cc3ecc3-32ce-4877-9088-c2b0aad37024 +
          • +
          • + This is item cbdd7446-64cb-485e-9374-5011cfdef78a +
          • +
          +
            +
          • + This is item aa6719fc-b2b3-422f-8c31-4c9a339ef6de +
          • + +
          • + This is item 8c96cfa6-6fd2-41eb-a1b1-ead600396d16 +
          • +
          • This is item e11064a1-075d-4abe-bd2c-de8f276ec81d
          • +
          +
        • +
        • + Profile picture of user e35196e7-2786-42b4-96d0-023246295df7 + + This is link fb96df76-d670-4228-bc8f-097a1b131200 + +
            +
          1. + This is item e8920343-7762-49d2-93ec-7c650d39a4c2 +
          2. +
          3. + This is item feef74d5-f076-41e5-9873-ce28c10b8bdd +
          4. +
          5. + This is item bd83b50b-3a59-4c25-844d-4bafba3f7009 +
          6. +
          7. + This is item 5b23946b-05c3-4e20-81d2-3a2cd8836522 +
          8. +
          9. + This is item b8889163-1b40-4f1e-8602-b50e3554fa82 +
          10. +
          +
        • +
        +
      • +
      • +
          +
        1. +
            +
          1. + This is item 5813df3f-67b8-42ad-9940-c4e72ab9077a +
          2. +
          3. + This is item d5b742ac-97bd-4e57-b0d4-65f6371f27ef +
          4. +
          5. + This is item 1d32bc11-41a6-439b-9231-fc3dbd55ebcd +
          6. +
          + + This is link 2453be29-0759-43ca-a80d-6218376ded5f + +
            +
          1. This is item 5467e9de-481d-4427-bf96-21b69ecb0702
          2. +
          3. + This is item b8e42792-6876-4a13-9311-c8f3621478a5 +
          4. +
          5. + This is item c2e09ff4-945e-45b0-9729-190d272322da +
          6. +
          7. This is item 564507ad-8cc8-40d1-ae33-9cacaaeca1f1
          8. +
          9. + This is item eb277196-6a61-44ff-8caf-c20fd0f4281b +
          10. +
          + Profile picture of user 9aac5adb-ddd1-47f9-87c4-7004b29cb1f1 +
        2. +
        3. +
            +
          • + This is item 0ba3007e-dad0-4510-9bf6-1cc142283ea8 +
          • +
          • + This is item b6e71161-e922-44fe-8a10-d2d9f6be8344 +
          • +
          +
            +
          1. + This is item 323670ab-6780-4a25-a1af-378926293081 +
          2. +
          3. This is item 1eec22df-6fd6-4c86-b417-8f4683f3a969
          4. +
          5. + This is item 825ca49f-589c-4b97-bf02-5f4edee7b3c4 +
          6. +
          + + This is link adfc3c22-efe1-4c16-8fe3-17267aa07e7f + +
            +
          1. This is item 136caf51-9340-4b93-ad61-dba2f5a4c6c0
          2. +
          3. + This is item 8aed2aa5-bef5-4f88-be8f-8629c46a0c07 +
          4. +
          +
        4. +
        5. +
            +
          1. + This is item a15f4d46-a55a-44d7-a4f4-2fa3311e3028 +
          2. +
          3. This is item a2446b4e-47c5-42ab-9827-dfb04c7fea38
          4. +
          5. + This is item c35eae4f-7f90-4c5e-985d-da93b98f97c2 +
          6. +
          7. + This is item f4961c8e-7d54-4f97-8e4d-887ee496cf5d +
          8. +
          + + This is link fc7f315c-2827-42c1-aaec-981a64040dfe + + Profile picture of user 9e400cbb-01ee-4f8c-96b3-e1703a95612f +
        6. +
        7. + + This is link 060007bf-65ff-446b-bd2a-712538b318e5 + +
            +
          • This is item f84cf1f7-1875-4d23-8980-dd8dcf5a2ffd
          • +
          • This is item ed01e20a-4e98-401a-a057-1ba5442a454d
          • +
          • + This is item 1ff84e80-ad9a-405b-a255-8d004d908d10 +
          • +
          • + This is item 16d64dab-15e5-4143-8737-ce12060aee17 +
          • +
          +
            +
          • This is item a8bf6c28-c727-4377-ae04-e85d2d91c0b0
          • +
          • This is item 9cbb3e55-f9c6-40db-a17c-963c31073806
          • +
          • + This is item d913a2a7-dcbe-45b2-ab11-6475de76eb7e +
          • +
          • + This is item 76f0258f-d0ac-4303-9049-76bcab5974c1 +
          • +
          • This is item 9429b060-d901-4d1b-831b-d175cfbc62c0
          • +
          +
            +
          1. + This is item 8894dcea-5326-4d85-9e76-8134ea2f779b +
          2. +
          3. + This is item ddfdb4d2-e754-485c-9106-5430116227e9 +
          4. +
          +
        8. +
        9. +
            +
          • + This is item 0a1f9df5-bf6f-48d3-858a-655766f9b593 +
          • +
          • + This is item d247cd63-387d-4886-b8f1-8e325d7c8805 +
          • + +
          • + This is item a46433ec-55e9-4e61-b08e-97e881fd4d4c +
          • +
          + + This is link 8dcf8de6-3aae-48cb-9ac9-854e6f3f762d + + Profile picture of user e45be800-b792-4389-935b-94eb6ef77b88 + + This is link db68407c-90d6-4fba-b9cc-4b500f6abb72 + +
        10. +
        +
          +
        1. +
            +
          1. + This is item e23bfb73-7c53-4c20-9272-9eddac5b1eb2 +
          2. +
          3. This is item f7c0fec6-2908-44ae-9ecb-17c3b34c0b06
          4. +
          +
            +
          1. + This is item 52e95eab-0179-41d6-a05e-dedd8ecf2b7f +
          2. +
          3. This is item 6b71818e-b6fe-4afe-b147-d5fc44c330ba
          4. + + +
          +
            +
          • + This is item 9f08ad1f-7c5a-46be-ba13-75a99b23184b +
          • +
          • This is item 78429ef9-0157-415b-af5c-948aa223d484
          • +
          • This is item 5765c708-ee5c-4695-ad7c-61bd07a599af
          • +
          • This is item 1389803c-5043-4936-80bb-88d0fd6240bd
          • +
          • + This is item 825d5e55-cc34-4431-ba1c-298977a5520d +
          • +
          +
        2. +
        3. +
            +
          1. + This is item fb8d394d-68a4-43fe-b00f-2a6d32bfa2d2 +
          2. +
          3. + This is item 5b4fb812-14da-495d-81be-bc76f52deefd +
          4. +
          5. This is item 105a4a77-4ef5-46cb-bebd-678111d26631
          6. +
          7. + This is item 12931611-03c7-492b-b52c-efca42592600 +
          8. +
          9. + This is item 23acf155-a467-4b83-9beb-6f61e617887d +
          10. +
          + + This is link 433081d7-06a0-487d-95de-bcea62310c88 + +
        4. +
        +
      • +
      • + Profile picture of user f8a6675f-0d50-45a9-98fe-f84a763f0fd9 +
          +
        • +
            +
          1. + This is item 4edfc0f2-be48-48cb-bda7-54f1bc38a2cb +
          2. +
          3. This is item c9225386-0bdc-4916-8ed8-17dde429dc9a
          4. + +
          5. This is item ff35553c-d3cc-4fee-bd54-3bfa5e803822
          6. +
          +
        • +
        • +
            +
          • + This is item c1d2769f-f4fc-4f58-8c5e-474a12741d7a +
          • +
          • + This is item 229c3677-c8d6-40c9-bfdb-8bd793390aea +
          • +
          +
            +
          1. This is item 97049a8f-b905-4310-ae15-60fc4e0a24e6
          2. + +
          3. This is item a23469ca-cbb7-4f90-8257-d91f60f35856
          4. +
          5. + This is item a2cc1674-6132-46df-9e37-a903741952af +
          6. +
          +
            +
          1. + This is item f2d4e4dc-c8c1-4160-ae8f-369304e281cb +
          2. +
          3. + This is item a797b5e7-22dc-4f8e-8339-93433062c0a5 +
          4. +
          + +
        • +
        • + + This is link efef93ac-c91f-4848-ab50-52f9cd2390dc + +
            +
          • + This is item fd518bad-0c90-42a8-b5d4-b109892310cf +
          • +
          • This is item c5279e23-48bd-423d-8373-652e22a1ceae
          • +
          • + This is item da69af84-15a5-45da-9be2-dd83d41e3890 +
          • +
          +
            +
          • This is item f8454342-de20-44ef-82d4-67405bf52279
          • +
          • This is item eadb097a-a49e-456e-866b-e9294be447b9
          • +
          • + This is item eee50dfc-7bc5-4eb3-9e07-7c2def0291cf +
          • +
          +
        • +
        • + + This is link 72c48bb3-665a-47dc-a724-4a58eb316182 + + Profile picture of user 56190809-8655-40fc-9796-a69de7755e56 +
            +
          • + This is item 0130e52f-bae1-4378-b349-1f171e2b6a12 +
          • +
          • + This is item 14579923-68e2-4f38-a467-9406afc562cd +
          • +
          • This is item 14b2a269-b6de-4c63-b632-e610d0c4377a
          • +
          • + This is item 0ab9da94-2772-4445-8f38-ebc2bfe6b719 +
          • +
          • This is item ef187746-f640-4430-8538-d5a7f4ccbbf4
          • +
          +
        • +
        +
          +
        • +
            +
          1. + This is item 7d863aa3-3c42-4cea-8bfd-3727b1732500 +
          2. +
          3. + This is item 2e5ed598-8235-47f1-a25b-ebddc473262f +
          4. +
          5. This is item bac9381b-d0a7-42ed-a7d9-62d339e99de3
          6. +
          +
        • +
        • + Profile picture of user 13ee422c-ee54-4e6e-aaf9-09f9c32f29f4 + + This is link 9e259fd8-71a4-4b00-9b70-b353fb86342e + +
        • +
        • + + Profile picture of user 9e5c63aa-d2a7-4b8e-8bc0-f1da09b8ea0d +
        • +
        • +
            +
          1. This is item d330ab08-2049-43e3-b5b6-438e442240be
          2. + +
          3. + This is item 92b399f1-6b83-41c9-8b9f-1e68b97614aa +
          4. +
          5. This is item dbe4d232-41b5-4acd-b5ce-f3488baa582e
          6. +
          7. + This is item 5a340e20-3c38-4781-98cf-d9c915950b42 +
          8. +
          +
            +
          • + This is item 59b9d368-0f29-48ea-8ea9-754bd6577575 +
          • +
          • + This is item 4e001101-9716-4ac8-955a-6797ee72b2c5 +
          • +
          +
            +
          1. + This is item 2d8d6b81-a5df-495c-a4d2-add24a2f36fe +
          2. +
          3. + This is item 5b908588-6f88-41cd-831b-4f980267cf9a +
          4. +
          +
            +
          1. This is item 5e446ea1-e21d-40cf-8618-09db19dea1d2
          2. +
          3. This is item 880ac8a6-a3ec-42c0-9687-03334343a3f6
          4. +
          5. This is item 11fb3678-dfc8-47bb-8f8d-10952b482227
          6. +
          +
        • +
        +
      • +
      +
      + + This is text 84a258b5-b9ca-4b73-bc0f-eb41c7b40e15 + + + + This is text e1ba7431-52ae-49b1-9f0a-608fcc1e27ce + +
      +

      + This is paragraph a4c8ae07-7ae6-46b8-83a6-0c896ffa3cd3 +

      +
      + +
      +
      +

      This is heading 4fb53825-6907-4a33-9e20-578e1029c887

      + + +
      +
      + +

      + This is heading 3634d4e3-8751-4f22-93b4-da67eebb3b05 +

      + + +
      + + + + This is link aad5bb80-144e-421d-977d-f3d187d22b2e + + This is text 25671534-232f-4b19-a0ef-f0b389de26a0 +
      +
      +
        +
      1. + +
          +
        • + Profile picture of user d780b28a-59f7-41ff-ad18-30aadfd35d44 +
            +
          • This is item e2c194b0-0f42-4e46-af20-71085b154ef8
          • +
          • + This is item e16e778b-dd33-4467-8f36-7e6e63ea35e0 +
          • +
          • + This is item 0475e277-fd09-42d7-9725-3449a1833307 +
          • +
          • + This is item 4722d042-5e29-4e36-b0b0-9e43329ee693 +
          • +
          + Profile picture of user 1204c8dd-13dd-45bc-b4e3-a1e476290357 + + This is link d791afc0-cef6-4ce8-9b66-0195af5b9ea5 + +
        • +
        • + Profile picture of user 3dd3714c-faf1-454c-a6b9-be06b6d47192 + Profile picture of user c3c148cf-3019-4254-a0c3-f73bb121caf9 + Profile picture of user 93013497-22d7-432b-be10-dc8f0a9d503b + + This is link acbc2a94-44ab-4a4b-820b-d6711bc2e374 + +
        • +
        • +
            +
          • + This is item 724f131c-3667-4037-b48a-54a5b11c964a +
          • +
          • + This is item fe54d9d4-550a-443c-82c4-c8f3bb683d6d +
          • +
          +
        • +
        • +
            +
          • + This is item 43376b98-e01a-4304-b401-88f2e8fa5774 +
          • +
          • + This is item 6536af3b-cc07-4daf-8aeb-50a1c0906145 +
          • +
          • + This is item 53f28a1e-55f1-4474-9afe-ee6d5586a79d +
          • +
          +
            +
          • This is item 96a607bf-bf65-48a9-a592-ac2bc08dd77b
          • +
          • This is item 42c30884-cc7e-46ee-9c24-cb1c60986a90
          • +
          • + This is item 8aad1010-9d41-441e-89ab-01c9f652e83a +
          • +
          + Profile picture of user 79b71af1-baed-474f-93be-09c8e1f13b82 +
        • +
        • + Profile picture of user 259d53d5-eb0c-440c-a235-f05e6f3534bc +
        • +
        +
          +
        1. + + This is link 13d5b4f3-51fd-4a05-80dd-8138be83e9ad + +
        2. +
        3. + Profile picture of user 71252e8c-acc5-4bbd-bbf6-d756b683ce2b + + This is link 552d5a72-779e-4f90-be9f-3ceeadfefcfe + +
        4. +
        5. + + This is link 1e2cd04b-6d8b-4a52-9642-e524ef12745d + +
        6. +
        7. +
            +
          • + This is item 8c1f2b68-ec28-4fe4-98d6-14a0303c6782 +
          • +
          • + This is item 18925a0d-3a6b-440e-ad92-abace9442f78 +
          • +
          • + This is item e0cacfc3-c7d7-4558-b043-268e9ff61274 +
          • +
          • + This is item f45a2400-f08e-43cc-ae87-6a9ecf5449c5 +
          • +
          +
            +
          • + This is item 9a676bff-0d21-4fa4-99c4-eb1717439e88 +
          • +
          • This is item 0393f640-dfa0-453c-be34-69dbf36937b8
          • +
          +
            +
          • This is item a7f6e04b-1d75-4f01-9da9-dd664427e83f
          • +
          • + This is item 14757b31-bf98-4486-966c-e7ec8fba30dc +
          • +
          • + This is item c5fc3f2b-2466-4396-bd5d-b39271bd4716 +
          • +
          +
        8. +
        +
      2. +
      3. + +
      4. +
      5. + + This is link d2599ecf-b5a1-4887-af51-0a1932277ef2 + + Profile picture of user 08b2d468-83df-4c19-b98c-017f13473d9d +
          +
        1. + Profile picture of user 6571c381-e1db-48af-8cef-3727e81795eb +
            +
          1. + This is item 4a76062e-f6ab-4b8d-b4e1-1dcb17e4531e +
          2. +
          3. + This is item a67f9538-8c13-4147-a405-218b07232377 +
          4. +
          +
        2. +
        3. +
            +
          • + This is item 0165d9c7-b39c-4db2-b307-a22545c1534c +
          • +
          • + This is item 66370802-d29e-4760-a066-0b58ca86f226 +
          • +
          +
            +
          1. + This is item 29977110-8d73-47e3-b243-d0bbdfb82651 +
          2. +
          3. + This is item 2aa424b1-e0d7-4995-92e8-546869bd9705 +
          4. +
          5. + This is item eaba2295-68c7-4a65-a093-754f298d0f57 +
          6. +
          + Profile picture of user a7f9cd91-ee03-4bdb-a93a-2edb1a399e66 + + This is link bde76eb0-1e26-4afe-9b8d-d92dacc6688d + +
        4. +
        5. +
            +
          • + This is item 114b2be6-0e4f-40c7-abd6-d7f6843a3cb5 +
          • +
          • + This is item e5754c30-c7ae-4631-930c-95383def0a75 +
          • +
          +
            +
          • + This is item 9ce5c3dd-6371-4b36-ac5f-3f312f17535f +
          • +
          • + This is item 2ff10843-13ff-4734-be13-ca5d7d339dec +
          • +
          • + This is item 31cb981a-5f2b-4288-9111-80075b57b52a +
          • +
          + + This is link 78d7a8b5-a738-4455-aa84-8bf3decadad2 + +
            +
          1. This is item 329126e4-a11f-40a6-924e-5945f80cb287
          2. +
          3. + This is item 7f163f33-797b-4e5f-b396-829c33676717 +
          4. +
          5. + This is item 82cc7709-199a-447c-ad81-dccecc6e19a5 +
          6. +
          7. + This is item 347a2ce5-75da-498f-80f9-952ee124ebb4 +
          8. +
          +
        6. +
        7. +
            +
          1. + This is item b1b91632-a2d2-4f30-8a35-c6d9874d2b2d +
          2. +
          3. + This is item 367fa66c-936e-48e3-9cbe-b1e2bc7c8f96 +
          4. +
          5. + This is item 391cbdae-105b-4a64-884a-886fd1abc756 +
          6. +
          7. + This is item db8acafa-d350-4757-8603-5e5cce824414 +
          8. +
          + Profile picture of user 429411aa-e9da-41ae-b2d9-6cc3eb9f3b5e + Profile picture of user 1128aa4e-67cc-400c-96ff-a71851cc0493 +
            +
          • + This is item 1e78bb79-0846-4a98-ae25-b3fd325ffdb9 +
          • +
          • + This is item e562f003-b36c-478b-9ff8-41c3db844dce +
          • +
          • + This is item ea103fd1-0fbb-4443-8739-890937166f7e +
          • +
          +
        8. +
        + Profile picture of user 2b62b56c-21ae-4c84-b639-0b093cbcba0a +
      6. +
      +
        +
      1. + Profile picture of user 3613fdc6-d257-47e9-913a-91b358607c14 + Profile picture of user 2f515104-ea87-4cf3-88c2-19634520ca0a + This is link d3be9080-4676-4809-b745-c5d8879c47c2 +
      2. +
      3. + Profile picture of user 97d9415f-27f8-4ffc-b4dc-eb9672f586fc +
      4. +
      5. + + This is link 0408d7b2-a595-4fd5-9ab0-b302cc6e7418 + + +
      6. +
      7. +
          +
        1. +
            +
          • + This is item 114829a6-a705-4104-8818-76b08982b678 +
          • +
          • + This is item cdb275b7-c27e-4af9-822a-b00687fb5479 +
          • +
          • This is item 248fd6c7-e955-40de-b6b0-865a2e988cd2
          • +
          + + This is link 95194aba-9177-4020-9832-d3c65d839e63 + +
            +
          • + This is item d88c898c-286d-4d87-bf6d-472c7ec111f9 +
          • +
          • This is item cba62412-5c11-43ea-b3fa-caa49c0246f9
          • +
          • + This is item 9cd30f2d-448e-41f9-8c6b-cf58a615297a +
          • +
          +
            +
          1. + This is item a28dd7de-34e4-46ba-81db-6eb59b69d981 +
          2. +
          3. This is item 006b90b3-bfcf-4168-bba8-04f42fb84720
          4. +
          5. This is item c804d57d-9793-4836-a13b-f7248a6d7aef
          6. +
          +
        2. +
        3. + + This is link 7bf56a82-847e-4f1c-a0ca-32610961f0e2 + +
        4. +
        +
          +
        1. + + This is link 8d501524-fd5c-46c2-8100-5aae8f16b819 + + + This is link 1f61391e-0760-47e2-aaa7-1364e485331d + +
            +
          1. + This is item fab91026-b8b8-4373-9e4c-7735987940d9 +
          2. +
          3. + This is item 2e02d6fe-0d26-4aa8-8664-1b8395a11230 +
          4. +
          5. + This is item 7644c78e-9a6a-42ae-89de-e53ea7be3f32 +
          6. +
          +
            +
          • + This is item 72e61f48-0d25-4920-8e72-1a52c2a750a5 +
          • +
          • This is item 337d9aef-6a85-46c6-958b-34e984315f6c
          • +
          • + This is item bcd6fc22-536d-4098-9810-f9287e012b46 +
          • + +
          +
        2. +
        3. + +
            +
          • This is item 3810607b-9173-4570-9493-aad902f2bef2
          • +
          • + This is item e6712919-16a8-406f-ab00-73499cfd72aa +
          • +
          +
            +
          • + This is item d5c9b17f-ffea-4758-9da4-1e82867dd893 +
          • +
          • This is item 4eca4e3d-b5c1-443e-953f-3dceea5749c3
          • +
          • + This is item 409d34fd-317a-4cf0-ab53-1eccc82dcdcc +
          • +
          +
        4. +
        5. + Profile picture of user 602e6b5b-511a-4b28-8493-b60a6fd75cff +
        6. +
        7. + Profile picture of user 7abcf285-084c-4541-a381-e30f48ffaa20 + Profile picture of user 0534350c-0095-4f9e-a37f-6fcec406c581 +
        8. +
        + + This is link c12be547-da06-4a92-a8c5-957ac0c0f3c1 + +
      8. +
      9. +
          +
        1. + + This is link aae63805-2525-4f31-8558-e242f1fcd26b + +
        2. +
        3. +
            +
          1. This is item 671e0f8c-e3f6-4813-a393-5ec420cad226
          2. +
          3. + This is item 3ff28496-0c3e-46c7-a03d-32ee1fd35f9f +
          4. +
          5. + This is item 5f970434-6fdb-43c0-8bf8-356b53d04774 +
          6. +
          +
            +
          1. + This is item af5094cc-7572-48b0-8685-308da8f4b1c4 +
          2. +
          3. + This is item 3de52000-35be-4fef-b8de-02a77a86540f +
          4. +
          5. + This is item a463d1ac-02b1-4f33-8bba-44d5de36c236 +
          6. +
          + + This is link 4b31b90a-8d78-4e0c-8fee-3ca3209504c5 + + + This is link 4bf9c53f-2980-494c-ba71-e549c27d2e17 + +
        4. +
        5. + Profile picture of user eb438e00-ba60-4754-80e0-096e5b68d525 +
        6. +
        7. +
            +
          1. This is item 54668f06-61f1-41da-bbe8-78c81096d40e
          2. +
          3. + This is item 48c32f11-9004-4fc2-8093-f302143b058f +
          4. +
          5. This is item 85830811-dfae-4786-9155-bfd645a60547
          6. + +
          +
        8. +
        + + This is link 3a3031cc-7999-4973-ad65-afd82aebcb43 + + Profile picture of user b1315998-80e4-427a-83e9-443f4b7d585b + +
      10. +
      + + This is link 6354f18e-547d-491e-8175-9a92b0a8a94b + + + +
      + Profile picture of user 7a7e459e-242a-4d25-94c8-55ba28325a19 + + This is link 0179323a-dba5-4f74-8224-9ffc3f2bc303 + + + This is link 59887750-572f-4c76-a829-b253fa34cfcf + +
        +
      1. + +
          +
        • +
            +
              +
            • + +
            + + This is link 0837b434-12c8-41a9-a8eb-0a313bd9c46b + +
          1. +
          2. + +
              + +
            1. + + + This is link 2614d94b-3d5e-4262-897c-28559a915aea + +
            2. +
            3. + + This is link 8fa5f04b-e268-4880-9b3a-494ca3c046bf + +
            4. +
            5. +
                + Profile picture of user 567d51bf-d083-46db-8eb4-0f9788010153 +
              1. +
              2. +
                  +
                    + + This is link fea48002-54fc-405f-b419-3db070403087 + +
                      +
                    1. +
                    + + This is link 9293c169-559c-4794-9f67-a6a2485dc75c + +
                  • +
                  • + Profile picture of user 0b915945-71ce-42ed-b638-018b677a87b6 +
                  • +
                  +
                  +

                  + This is text 495dd7d9-9f9b-480a-b331-8c411d853427 + + This is text 0533e18e-49dc-4d1c-a54f-e0bd121f042e + +

                  +
                  + Profile picture of user 3229ae0c-7e85-4163-a5bb-0eae924b3bf1 +
                    +
                  1. +
                      + Profile picture of user e80623b6-64e5-4b17-a323-cca8b7a08d13 + + This is link a1ec646f-49b2-4e0c-826d-85f6d1e48e4a + + Profile picture of user f27f4d43-d818-4173-b2d1-4daf346cc442 +
                    • +
                    • + Profile picture of user 6ddad74b-9773-4d3b-ab83-d1f437028140 + + This is link 33a4a214-40ce-49f4-b2ac-4df4e98eebb3 + + Profile picture of user b06eabce-8402-4a5f-926a-eb7409a060e3 + Profile picture of user 4f7ab64e-e3a7-4918-886b-917e7733f25c +
                    • + +
                    • + Profile picture of user 3cc6a489-6ce6-4fa7-9c58-a5702f07ad16 +
                        + + This is link 1feeab52-35d9-42f7-a710-85f661ce7ca7 + + Profile picture of user 5cabcc27-35e9-47c3-9738-c10e4f74bd3a +
                      1. +
                      2. +
                          +
                        • +
                        +
                        + + +

                        + + This is text 5b96273a-c758-4ae4-a19b-4d416b21d8c7 + + + This is link 2c2990be-368e-4aa6-8f11-6b24888fbcf3 + +

                        + + +
                        +
                        +
                        + +
                        +
                        +
                        +
                        +

                        + This is heading b1c1981a-61e6-480a-939d-424eb639dfff +

                        +
                        +
                        + Profile picture of user ce70952c-6830-4b53-b638-bc81064d38a9 + +
                          +
                          + This is text fd9fbaa9-86e3-4ffe-954a-5be14de18d54 +
                          + +
                          + +
                            +
                          1. + + This is link 8aa64400-12e8-41eb-b6e6-b26356ad3bfe + +
                          2. +
                          3. +
                              +
                            • + This is item e2131780-3b9c-4578-9918-a463d2361bc0 +
                            • +
                            • + This is item 5ec4c3aa-6159-4e3e-b6a6-771fab824ee8 +
                            • +
                            • This is item 25651510-b575-4293-93e8-92c11296c663
                            • +
                            +
                              +
                            • + This is item b0e9dc7d-a031-41be-a4b5-5cd8f219462c +
                            • +
                            • This is item 7a7548c7-f04e-4723-b51c-851de4417829
                            • +
                            • + This is item d6a6296e-e06b-407f-b5d2-9148b014c4fa +
                            • +
                            • + This is item cab8d7c8-2c34-4d20-a7d2-c72500d2b360 +
                            • +
                            • This is item 88f9eb9a-8c93-46f8-8b5d-81667db0ce7e
                            • +
                            +
                              +
                            1. This is item 0b1d6523-32f0-4270-bc82-094f37c191e0
                            2. +
                            3. + This is item f807b468-b8ff-49d6-a2a6-91d615364ce4 +
                            4. +
                            +
                          4. +
                          5. +
                              +
                            1. This is item 5651f9dc-57e0-46f4-bab8-1af15baef811
                            2. +
                            3. This is item c392f166-a536-4b62-b289-5f0222dff24c
                            4. +
                            5. + This is item 2f37915b-e155-4c73-b754-eb49a391bb69 +
                            6. +
                            +
                          6. +
                          +

                          This is paragraph 290a7a29-9bf3-4364-a2ab-b898457fb3dd

                          +
                          +
                          +
                          +
                          + + +
                          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(` + [ + "The quick", + "brown fox", + "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`Hello world`, + ) + + 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/*']