diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 63f79fa10..93e104db8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,48 +1,12 @@ # Contributing -## Requirements - -Before getting started, ensure your system has access to the following tools: - -- [Node.js](https://nodejs.org/) -- [Rustup](https://rustup.rs/) -- [pnpm](https://pnpm.io/) - -## Getting started - -```sh -# Install dependencies -pnpm install - -# Install Rust toolchain and WASM targets -rustup default stable -rustup target add wasm32-wasip1-threads - -# Build the project -pnpm build -``` - -## Development workflow - -During development, you can run tests in watch mode: - -```sh -pnpm tdd -``` - -The `playgrounds` directory contains example projects you can use to test your changes. To start the Vite playground, use: - -```sh -pnpm build && pnpm vite -``` - ## Bug fixes If you've found a bug in Tailwind that you'd like to fix, [submit a pull request](https://github.com/tailwindlabs/tailwindcss/pulls) with your changes. Include a helpful description of the problem and how your changes address it, and provide tests so we can verify the fix works as expected. ## New features -If there's a new feature you'd like to see added to Tailwind, [share your idea with us](https://github.com/tailwindlabs/tailwindcss/discussions/new?category=ideas) in our discussion forum to get it on our radar as something to consider for a future release before starting work on it. +If there's a new feature you'd like to see added to Tailwind, [share your idea with us](https://github.com/tailwindlabs/tailwindcss/discussions/new?category=ideas) in our discussion forum to get it on our radar as something to consider for a future release. **Please note that we don't often accept pull requests for new features.** Adding a new feature to Tailwind requires us to think through the entire problem ourselves to make sure we agree with the proposed API, which means the feature needs to be high on our own priority list for us to be able to give it the attention it needs. @@ -50,7 +14,7 @@ If you open a pull request for a new feature, we're likely to close it not becau ## Coding standards -Our code formatting rules are defined in the `"prettier"` section of [package.json](https://github.com/tailwindlabs/tailwindcss/blob/main/package.json). You can check your code against these standards by running: +Our code formatting rules are defined in the `"prettier"` section of [package.json](https://github.com/tailwindcss/tailwindcss/blob/main/package.json). You can check your code against these standards by running: ```sh pnpm run lint @@ -64,40 +28,10 @@ pnpm run format ## Running tests -You can run the TypeScript and Rust test suites using the following command: +You can run the test suite using the following commands: ```sh -pnpm test +pnpm build && pnpm test ``` -To run the integration tests, use: - -```sh -pnpm build && pnpm test:integrations -``` - -Additionally, some features require testing in browsers (i.e. to ensure CSS variable resolution works as expected). These can be run via: - -```sh -pnpm build && pnpm test:ui -``` - -Please ensure that all tests are passing when submitting a pull request. If you're adding new features to Tailwind CSS, always include tests. - -After a successful build, you can also use the npm package tarballs created inside the `dist/` folder to install your build in other local projects. - -## Pull request process - -When submitting a pull request: - -- Ensure the pull request title and description explain the changes you made and why you made them. -- Include a test plan section that outlines how you tested your contributions. We do not accept contributions without tests. -- Ensure all tests pass. You can add the tag `[ci-all]` in your pull request description to run the test suites across all platforms. - -When a pull request is created, Tailwind CSS maintainers will be notified automatically. - -## Communication - -- **GitHub discussions**: For feature ideas and general questions -- **GitHub issues**: For bug reports -- **GitHub pull requests**: For code contributions +Please ensure that the tests are passing when submitting a pull request. If you're adding new features to Tailwind, please include tests. diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 848c851b1..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -custom: ['https://tailwindcss.com/sponsor'] diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index aa0793aa3..ce7fafb97 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,26 +4,8 @@ **Please ask first before starting work on any significant new features.** -It's never a fun experience to have your pull request declined after investing a lot of time and effort into a new feature. To avoid this from happening, we request that contributors create a discussion to first discuss any significant new features. +It's never a fun experience to have your pull request declined after investing a lot of time and effort into a new feature. To avoid this from happening, we request that contributors create an issue to first discuss any significant new features. This includes things like adding new utilities, creating new at-rules, or adding new component examples to the documentation. -For more info, check out the contributing guide: - -https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md - ---> - -## Summary - - - -## Test plan - - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ae59e80f..e3844c305 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,18 +8,12 @@ on: permissions: contents: read -env: - NODE_VERSION: 24 - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - jobs: tests: strategy: fail-fast: false matrix: + node-version: [20] runner: - name: Windows os: windows-latest @@ -27,19 +21,17 @@ jobs: - name: Linux os: namespace-profile-default - # Playwright 1.62+ dropped WebKit support for macOS 14, and hangs - # instead of failing when launching WebKit on a macos-14 runner. - name: macOS - os: macos-15 + os: macos-14 # Exclude windows and macos from being built on feature branches - run-all: - - ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.body, '[ci-all]') || github.event.pull_request.user.login == 'depfu[bot]' }} + on-main-branch: + - ${{ github.ref == 'refs/heads/main' }} exclude: - - run-all: false + - on-main-branch: false runner: name: Windows - - run-all: false + - on-main-branch: false runner: name: macOS @@ -49,21 +41,21 @@ jobs: name: ${{ matrix.runner.name }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - persist-credentials: false - - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 - - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 with: - node-version: ${{ env.NODE_VERSION }} + node-version: ${{ matrix.node-version }} + cache: 'pnpm' # Cargo already skips downloading dependencies if they already exist - name: Cache cargo - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + uses: actions/cache@v4 with: path: | + ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ @@ -72,25 +64,17 @@ jobs: # Cache the `oxide` Rust build - name: Cache oxide build - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + uses: actions/cache@v4 with: path: | + ./target/ ./crates/node/*.node - ./crates/node/*.wasm - ./crates/node/index.d.ts ./crates/node/index.js - ./crates/node/browser.js - ./crates/node/tailwindcss-oxide.wasi-browser.js - ./crates/node/tailwindcss-oxide.wasi.cjs - ./crates/node/wasi-worker-browser.mjs - ./crates/node/wasi-worker.mjs + ./crates/node/index.d.ts key: ${{ runner.os }}-oxide-${{ hashFiles('./crates/**/*') }} - - name: Setup WASM target - run: rustup target add wasm32-wasip1-threads - - name: Install dependencies - run: pnpm install --frozen-lockfile + run: pnpm install - name: Build run: pnpm run build @@ -112,13 +96,9 @@ jobs: - name: Run Playwright tests run: npm run test:ui - notify: - if: ${{ always() && github.ref == 'refs/heads/main' && needs.tests.result == 'failure' }} - needs: tests - runs-on: ubuntu-latest - steps: - name: Notify Discord - uses: discord-actions/message@5c7149c81a83146e5d01f142be1bf87a61831c4d # v2 + if: failure() && github.ref == 'refs/heads/main' + uses: discord-actions/message@v2 with: webhookUrl: ${{ secrets.DISCORD_WEBHOOK_URL }} - message: 'The [most recent ${{ github.workflow }} workflow](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>) on the `main` branch has failed.' + message: 'The [most recent build](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>) on the `main` branch has failed.' diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 457c38424..55fac5cb1 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -8,18 +8,13 @@ on: permissions: contents: read -env: - NODE_VERSION: 24 - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - jobs: tests: strategy: fail-fast: false matrix: + node-version: [20] + runner: - name: Windows os: windows-latest @@ -35,17 +30,16 @@ jobs: - vite - cli - postcss - - oxide - - webpack + - workers # Exclude windows and macos from being built on feature branches - run-all: - - ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.body, '[ci-all]') }} + on-main-branch: + - ${{ github.ref == 'refs/heads/main' }} exclude: - - run-all: false + - on-main-branch: false runner: name: Windows - - run-all: false + - on-main-branch: false runner: name: macOS @@ -55,25 +49,21 @@ jobs: name: ${{ matrix.runner.name }} / ${{ matrix.integration }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - persist-credentials: false - - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 - - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" - - - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 with: - node-version: ${{ env.NODE_VERSION }} + node-version: ${{ matrix.node-version }} + cache: 'pnpm' # Cargo already skips downloading dependencies if they already exist - name: Cache cargo - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + uses: actions/cache@v4 with: path: | + ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ @@ -82,23 +72,15 @@ jobs: # Cache the `oxide` Rust build - name: Cache oxide build - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + uses: actions/cache@v4 with: path: | + ./target/ ./crates/node/*.node - ./crates/node/*.wasm - ./crates/node/index.d.ts ./crates/node/index.js - ./crates/node/browser.js - ./crates/node/tailwindcss-oxide.wasi-browser.js - ./crates/node/tailwindcss-oxide.wasi.cjs - ./crates/node/wasi-worker-browser.mjs - ./crates/node/wasi-worker.mjs + ./crates/node/index.d.ts key: ${{ runner.os }}-oxide-${{ hashFiles('./crates/**/*') }} - - name: Setup WASM target - run: rustup target add wasm32-wasip1-threads - - name: Install dependencies run: pnpm install @@ -113,13 +95,9 @@ jobs: env: GITHUB_WORKSPACE: ${{ github.workspace }} - notify: - if: ${{ always() && github.ref == 'refs/heads/main' && needs.tests.result == 'failure' }} - needs: tests - runs-on: ubuntu-latest - steps: - name: Notify Discord - uses: discord-actions/message@5c7149c81a83146e5d01f142be1bf87a61831c4d # v2 + if: failure() && github.ref == 'refs/heads/main' + uses: discord-actions/message@v2 with: webhookUrl: ${{ secrets.DISCORD_WEBHOOK_URL }} - message: 'The [most recent ${{ github.workflow }} workflow](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>) on the `main` branch has failed.' + message: 'The [most recent build](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>) on the `main` branch has failed.' diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 24e829afd..ce0ad15aa 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -2,29 +2,18 @@ name: Prepare Release on: workflow_dispatch: - inputs: - dry_run: - description: Skip creating the draft GitHub release - required: false - default: true - type: boolean push: tags: - 'v*' env: APP_NAME: tailwindcss-oxide - NODE_VERSION: 24 - PNPM_VERSION: '11.9.0' + NODE_VERSION: 20 OXIDE_LOCATION: ./crates/node permissions: contents: read -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - jobs: build: strategy: @@ -54,97 +43,96 @@ jobs: - os: ubuntu-latest target: x86_64-unknown-linux-gnu strip: strip - build-flags: --use-napi-cross + container: + image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian - os: ubuntu-latest target: aarch64-unknown-linux-gnu - strip: aarch64-linux-gnu-strip - build-flags: --use-napi-cross + 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: arm-linux-gnueabihf-strip - build-flags: --use-napi-cross + 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-zig: true - build-flags: -x + 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 - build-flags: -x + download: true + container: + image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine - name: Build ${{ matrix.target }} (oxide) + name: Build ${{ matrix.target }} (OXIDE) runs-on: ${{ matrix.os }} + container: ${{ matrix.container }} timeout-minutes: 15 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - persist-credentials: false - - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 - with: - version: ${{ env.PNPM_VERSION }} + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - package-manager-cache: false + cache: 'pnpm' - - name: Install gcc-arm-linux-gnueabihf - if: ${{ matrix.target == 'armv7-unknown-linux-gnueabihf' }} - run: | - sudo apt-get update - sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y - - - name: Install binutils-aarch64-linux-gnu - if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }} - run: | - sudo apt-get update - sudo apt-get install binutils-aarch64-linux-gnu -y - - - uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2 - if: ${{ contains(matrix.target, 'musl') }} + # Cargo already skips downloading dependencies if they already exist + - name: Cache cargo + uses: actions/cache@v4 with: - version: 0.14.1 - use-cache: false + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Install cargo-zigbuild - uses: taiki-e/install-action@65851e10cd6c377f11a60e600abc07cb08643468 # v2 - if: ${{ contains(matrix.target, 'musl') }} - env: - GITHUB_TOKEN: ${{ github.token }} + # Cache the `oxide` Rust build + - name: Cache oxide build + uses: actions/cache@v4 with: - tool: cargo-zigbuild + path: | + ./oxide/target/ + ./crates/node/*.node + ./crates/node/index.js + ./crates/node/index.d.ts + key: ${{ runner.os }}-${{ matrix.target }}-oxide-${{ hashFiles('./crates/**/*') }} + + - name: Install Node.JS + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - 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 --frozen-lockfile --filter=!./playgrounds/* + run: pnpm install --ignore-scripts --filter=!./playgrounds/* - name: Build release - run: pnpm run --filter ${{ env.OXIDE_LOCATION }} build:platform --target=${{ matrix.target }} ${{ matrix.build-flags }} + run: pnpm run --filter ${{ env.OXIDE_LOCATION }} build 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 || matrix.strip-zig }} - env: - STRIP_COMMAND: ${{ matrix.strip }} - STRIP_ZIG: ${{ matrix.strip-zig }} - run: | - if [ "$STRIP_ZIG" = "true" ]; then - for file in ${{ env.OXIDE_LOCATION }}/*.node; do - zig objcopy --strip-all "$file" "$file.stripped" - mv "$file.stripped" "$file" - done - exit 0 - fi - - eval "$STRIP_COMMAND ${{ env.OXIDE_LOCATION }}/*.node" + if: ${{ matrix.strip }} + run: ${{ matrix.strip }} ${{ env.OXIDE_LOCATION }}/*.node - name: Upload artifacts - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@v4 with: name: bindings-${{ matrix.target }} path: ${{ env.OXIDE_LOCATION }}/*.node @@ -154,11 +142,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - persist-credentials: false + - uses: actions/checkout@v4 - name: Build FreeBSD - uses: cross-platform-actions/action@cdc9ee69ef84a5f2e59c9058335d9c57bcb4ac86 # v0.25.0 + uses: cross-platform-actions/action@v0.25.0 env: DEBUG: napi:* RUSTUP_HOME: /usr/local/rustup @@ -174,23 +160,23 @@ jobs: shell: bash run: | sudo pkg install -y -f curl node libnghttp2 npm - sudo npm install -g pnpm@${{ env.PNPM_VERSION }} --unsafe-perm=true + sudo npm install -g pnpm@9.6.0 --unsafe-perm=true curl -sSf https://static.rust-lang.org/rustup/archive/1.27.1/x86_64-unknown-freebsd/rustup-init --output rustup-init chmod +x rustup-init ./rustup-init -y --profile minimal source "$HOME/.cargo/env" - pnpm install --ignore-scripts --frozen-lockfile --filter=!./playgrounds/* || true + pnpm install --ignore-scripts --filter=!./playgrounds/* || true echo "~~~~ rustc --version ~~~~" rustc --version echo "~~~~ node -v ~~~~" node -v echo "~~~~ pnpm --version ~~~~" pnpm --version - pnpm run --filter ${{ env.OXIDE_LOCATION }} build:platform + pnpm run --filter ${{ env.OXIDE_LOCATION }} build strip -x ${{ env.OXIDE_LOCATION }}/*.node ls -la ${{ env.OXIDE_LOCATION }} - name: Upload artifacts - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@v4 with: name: bindings-x86_64-unknown-freebsd path: ${{ env.OXIDE_LOCATION }}/*.node @@ -201,17 +187,18 @@ jobs: name: Build and release Tailwind CSS permissions: - contents: write # Required for creating releases + contents: write # for softprops/action-gh-release to create GitHub release + # https://docs.npmjs.com/generating-provenance-statements#publishing-packages-with-provenance-via-github-actions + id-token: write needs: - build - build-freebsd steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@v4 with: fetch-depth: 20 - persist-credentials: false - run: git fetch --tags -f @@ -220,25 +207,43 @@ jobs: run: | echo "TAG_NAME=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV - - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 - with: - version: ${{ env.PNPM_VERSION }} + - uses: pnpm/action-setup@v4 - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' registry-url: 'https://registry.npmjs.org' - package-manager-cache: false - - name: Setup WASM target - run: rustup target add wasm32-wasip1-threads + # Cargo already skips downloading dependencies if they already exist + - name: Cache cargo + uses: actions/cache@v4 + 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@v4 + with: + path: | + ./oxide/target/ + ./crates/node/*.node + ./crates/node/index.js + ./crates/node/index.d.ts + key: ${{ runner.os }}-${{ matrix.target }}-oxide-${{ hashFiles('./crates/**/*') }} - name: Install dependencies - run: pnpm --filter=!./playgrounds/* install --frozen-lockfile + run: pnpm --filter=!./playgrounds/* install - name: Download artifacts - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + uses: actions/download-artifact@v4 with: path: ${{ env.OXIDE_LOCATION }} @@ -276,32 +281,25 @@ jobs: echo "$RELEASE_NOTES" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - - name: Upload standalone artifacts - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + - name: Upload Standalone Artifacts + uses: actions/upload-artifact@v4 with: name: tailwindcss-standalone path: packages/@tailwindcss-standalone/dist/ - - name: Upload npm package tarballs - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - with: - name: npm-package-tarballs - path: dist/*.tgz - - name: Prepare GitHub Release - if: ${{ !inputs.dry_run }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release create "${TAG_NAME}" \ - --draft \ - --title "${TAG_NAME}" \ - --notes "${RELEASE_NOTES}" \ - packages/@tailwindcss-standalone/dist/sha256sums.txt \ - packages/@tailwindcss-standalone/dist/tailwindcss-linux-arm64 \ - packages/@tailwindcss-standalone/dist/tailwindcss-linux-arm64-musl \ - packages/@tailwindcss-standalone/dist/tailwindcss-linux-x64 \ - packages/@tailwindcss-standalone/dist/tailwindcss-linux-x64-musl \ - packages/@tailwindcss-standalone/dist/tailwindcss-macos-arm64 \ - packages/@tailwindcss-standalone/dist/tailwindcss-macos-x64 \ - packages/@tailwindcss-standalone/dist/tailwindcss-windows-x64.exe + uses: softprops/action-gh-release@v2 + with: + draft: true + tag_name: ${{ env.TAG_NAME }} + body: | + ${{ env.RELEASE_NOTES }} + files: | + packages/@tailwindcss-standalone/dist/sha256sums.txt + packages/@tailwindcss-standalone/dist/tailwindcss-linux-arm64 + packages/@tailwindcss-standalone/dist/tailwindcss-linux-arm64-musl + packages/@tailwindcss-standalone/dist/tailwindcss-linux-x64 + packages/@tailwindcss-standalone/dist/tailwindcss-linux-x64-musl + packages/@tailwindcss-standalone/dist/tailwindcss-macos-arm64 + packages/@tailwindcss-standalone/dist/tailwindcss-macos-x64 + packages/@tailwindcss-standalone/dist/tailwindcss-windows-x64.exe diff --git a/.github/workflows/release-insiders.yml b/.github/workflows/release-insiders.yml new file mode 100644 index 000000000..5ad182607 --- /dev/null +++ b/.github/workflows/release-insiders.yml @@ -0,0 +1,290 @@ +name: Release Insiders + +on: + push: + branches: [main] + +permissions: + contents: read + +env: + APP_NAME: tailwindcss-oxide + NODE_VERSION: 20 + OXIDE_LOCATION: ./crates/node + RELEASE_CHANNEL: insiders + +jobs: + build: + strategy: + matrix: + include: + # Windows + - os: windows-latest + target: x86_64-pc-windows-msvc + - os: windows-latest + target: aarch64-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. + # Android + - os: ubuntu-latest + target: aarch64-linux-android + strip: ${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip + - os: ubuntu-latest + target: armv7-linux-androideabi + strip: ${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip + # 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@v4 + - uses: pnpm/action-setup@v4 + + - name: Use Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' + + # Cargo already skips downloading dependencies if they already exist + - name: Cache cargo + uses: actions/cache@v4 + 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@v4 + with: + path: | + ./oxide/target/ + ./crates/node/*.node + ./crates/node/index.js + ./crates/node/index.d.ts + key: ${{ runner.os }}-${{ matrix.target }}-oxide-${{ hashFiles('./crates/**/*') }} + + - name: Install Node.JS + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - 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 --filter ${{ env.OXIDE_LOCATION }} build + 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@v4 + with: + name: bindings-${{ matrix.target }} + path: ${{ env.OXIDE_LOCATION }}/*.node + + build-freebsd: + name: Build x86_64-unknown-freebsd (OXIDE) + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - name: Build FreeBSD + uses: cross-platform-actions/action@v0.25.0 + env: + DEBUG: napi:* + RUSTUP_HOME: /usr/local/rustup + CARGO_HOME: /usr/local/cargo + RUSTUP_IO_THREADS: 1 + RUST_TARGET: x86_64-unknown-freebsd + with: + operating_system: freebsd + version: '14.0' + memory: 13G + cpu_count: 3 + environment_variables: 'DEBUG RUSTUP_IO_THREADS' + shell: bash + run: | + sudo pkg install -y -f curl node libnghttp2 npm + sudo npm install -g pnpm@9.6.0 --unsafe-perm=true + curl -sSf https://static.rust-lang.org/rustup/archive/1.27.1/x86_64-unknown-freebsd/rustup-init --output rustup-init + chmod +x rustup-init + ./rustup-init -y --profile minimal + source "$HOME/.cargo/env" + echo "~~~~ rustc --version ~~~~" + rustc --version + echo "~~~~ node -v ~~~~" + node -v + echo "~~~~ pnpm --version ~~~~" + pnpm --version + pnpm install --ignore-scripts --filter=!./playgrounds/* || true + pnpm run --filter ${{ env.OXIDE_LOCATION }} build + strip -x ${{ env.OXIDE_LOCATION }}/*.node + ls -la ${{ env.OXIDE_LOCATION }} + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: bindings-x86_64-unknown-freebsd + path: ${{ env.OXIDE_LOCATION }}/*.node + + release: + runs-on: macos-14 + timeout-minutes: 15 + name: Build and release Tailwind CSS insiders + + permissions: + contents: write # for softprops/action-gh-release to create GitHub release + # https://docs.npmjs.com/generating-provenance-statements#publishing-packages-with-provenance-via-github-actions + id-token: write + + needs: + - build + - build-freebsd + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 20 + + - name: Resolve version + id: vars + run: | + echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + + - uses: pnpm/action-setup@v4 + + - name: Use Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v4 + 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@v4 + 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@v4 + with: + path: | + ./oxide/target/ + ./crates/node/*.node + ./crates/node/index.js + ./crates/node/index.d.ts + key: ${{ runner.os }}-${{ matrix.target }}-oxide-${{ hashFiles('./crates/**/*') }} + + - name: Install dependencies + run: pnpm --filter=!./playgrounds/* install + + - name: Download artifacts + uses: actions/download-artifact@v4 + 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-aarch64-pc-windows-msvc/* ./npm/win32-arm64-msvc/ + cp bindings-x86_64-apple-darwin/* ./npm/darwin-x64/ + cp bindings-aarch64-apple-darwin/* ./npm/darwin-arm64/ + cp bindings-aarch64-linux-android/* ./npm/android-arm64/ + cp bindings-armv7-linux-androideabi/* ./npm/android-arm-eabi/ + 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/ + cp bindings-x86_64-unknown-freebsd/* ./npm/freebsd-x64/ + + - name: 'Version based on commit: 0.0.0-${{ env.RELEASE_CHANNEL }}.${{ env.SHA_SHORT }}' + run: pnpm run version-packages 0.0.0-${{ env.RELEASE_CHANNEL }}.${{ env.SHA_SHORT }} + + - name: Build Tailwind CSS + run: pnpm run build + + - name: Run pre-publish optimizations scripts + run: node ./scripts/pre-publish-optimizations.mjs + + - name: Lock pre-release versions + run: node ./scripts/lock-pre-release-versions.mjs + + - name: Publish + run: pnpm --recursive publish --tag ${{ env.RELEASE_CHANNEL }} --no-git-checks + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Trigger Tailwind Play update + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.TAILWIND_PLAY_TOKEN }} + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: 'tailwindlabs', + repo: 'play.tailwindcss.com', + ref: 'master', + workflow_id: 'upgrade-tailwindcss.yml' + }) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c957d3af..a936e25e3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,34 +1,18 @@ name: Release on: - push: - branches: [main] release: types: [published] workflow_dispatch: - inputs: - channel: - description: Release channel to publish - required: true - default: insiders - type: choice - options: - - insiders - - release permissions: contents: read env: APP_NAME: tailwindcss-oxide - NODE_VERSION: 24 - PNPM_VERSION: '11.9.0' + NODE_VERSION: 20 OXIDE_LOCATION: ./crates/node -concurrency: - group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} - cancel-in-progress: true - jobs: build: strategy: @@ -58,97 +42,96 @@ jobs: - os: ubuntu-latest target: x86_64-unknown-linux-gnu strip: strip - build-flags: --use-napi-cross + container: + image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian - os: ubuntu-latest target: aarch64-unknown-linux-gnu - strip: aarch64-linux-gnu-strip - build-flags: --use-napi-cross + 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: arm-linux-gnueabihf-strip - build-flags: --use-napi-cross + 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-zig: true - build-flags: -x + 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 - build-flags: -x + download: true + container: + image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine - name: Build ${{ matrix.target }} (oxide) + name: Build ${{ matrix.target }} (OXIDE) runs-on: ${{ matrix.os }} + container: ${{ matrix.container }} timeout-minutes: 15 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - persist-credentials: false - - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 - with: - version: ${{ env.PNPM_VERSION }} + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - package-manager-cache: false + cache: 'pnpm' - - name: Install gcc-arm-linux-gnueabihf - if: ${{ matrix.target == 'armv7-unknown-linux-gnueabihf' }} - run: | - sudo apt-get update - sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y - - - name: Install binutils-aarch64-linux-gnu - if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }} - run: | - sudo apt-get update - sudo apt-get install binutils-aarch64-linux-gnu -y - - - uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2 - if: ${{ contains(matrix.target, 'musl') }} + # Cargo already skips downloading dependencies if they already exist + - name: Cache cargo + uses: actions/cache@v4 with: - version: 0.14.1 - use-cache: false + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Install cargo-zigbuild - uses: taiki-e/install-action@65851e10cd6c377f11a60e600abc07cb08643468 # v2 - if: ${{ contains(matrix.target, 'musl') }} - env: - GITHUB_TOKEN: ${{ github.token }} + # Cache the `oxide` Rust build + - name: Cache oxide build + uses: actions/cache@v4 with: - tool: cargo-zigbuild + path: | + ./oxide/target/ + ./crates/node/*.node + ./crates/node/index.js + ./crates/node/index.d.ts + key: ${{ runner.os }}-${{ matrix.target }}-oxide-${{ hashFiles('./crates/**/*') }} + + - name: Install Node.JS + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - 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 --frozen-lockfile --filter=!./playgrounds/* + run: pnpm install --ignore-scripts --filter=!./playgrounds/* - name: Build release - run: pnpm run --filter ${{ env.OXIDE_LOCATION }} build:platform --target=${{ matrix.target }} ${{ matrix.build-flags }} + run: pnpm run --filter ${{ env.OXIDE_LOCATION }} build 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 || matrix.strip-zig }} - env: - STRIP_COMMAND: ${{ matrix.strip }} - STRIP_ZIG: ${{ matrix.strip-zig }} - run: | - if [ "$STRIP_ZIG" = "true" ]; then - for file in ${{ env.OXIDE_LOCATION }}/*.node; do - zig objcopy --strip-all "$file" "$file.stripped" - mv "$file.stripped" "$file" - done - exit 0 - fi - - eval "$STRIP_COMMAND ${{ env.OXIDE_LOCATION }}/*.node" + if: ${{ matrix.strip }} + run: ${{ matrix.strip }} ${{ env.OXIDE_LOCATION }}/*.node - name: Upload artifacts - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@v4 with: name: bindings-${{ matrix.target }} path: ${{ env.OXIDE_LOCATION }}/*.node @@ -158,11 +141,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - persist-credentials: false + - uses: actions/checkout@v4 - name: Build FreeBSD - uses: cross-platform-actions/action@cdc9ee69ef84a5f2e59c9058335d9c57bcb4ac86 # v0.25.0 + uses: cross-platform-actions/action@v0.25.0 env: DEBUG: napi:* RUSTUP_HOME: /usr/local/rustup @@ -178,7 +159,7 @@ jobs: shell: bash run: | sudo pkg install -y -f curl node libnghttp2 npm - sudo npm install -g pnpm@${{ env.PNPM_VERSION }} --unsafe-perm=true + sudo npm install -g pnpm@9.6.0 --unsafe-perm=true curl -sSf https://static.rust-lang.org/rustup/archive/1.27.1/x86_64-unknown-freebsd/rustup-init --output rustup-init chmod +x rustup-init ./rustup-init -y --profile minimal @@ -189,12 +170,12 @@ jobs: node -v echo "~~~~ pnpm --version ~~~~" pnpm --version - pnpm install --ignore-scripts --frozen-lockfile --filter=!./playgrounds/* || true - pnpm run --filter ${{ env.OXIDE_LOCATION }} build:platform + pnpm install --ignore-scripts --filter=!./playgrounds/* || true + pnpm run --filter ${{ env.OXIDE_LOCATION }} build strip -x ${{ env.OXIDE_LOCATION }}/*.node ls -la ${{ env.OXIDE_LOCATION }} - name: Upload artifacts - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@v4 with: name: bindings-x86_64-unknown-freebsd path: ${{ env.OXIDE_LOCATION }}/*.node @@ -202,10 +183,10 @@ jobs: release: runs-on: macos-14 timeout-minutes: 15 - name: Build and publish Tailwind CSS + name: Build and release Tailwind CSS permissions: - contents: read + contents: write # for softprops/action-gh-release to create GitHub release # https://docs.npmjs.com/generating-provenance-statements#publishing-packages-with-provenance-via-github-actions id-token: write @@ -214,51 +195,47 @@ jobs: - build-freebsd steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@v4 with: fetch-depth: 20 - persist-credentials: false - - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 - with: - version: ${{ env.PNPM_VERSION }} + - uses: pnpm/action-setup@v4 - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} + cache: 'pnpm' registry-url: 'https://registry.npmjs.org' - package-manager-cache: false - # npm trusted publishing validates the caller workflow filename, so all npm publishes live here. - # This workflow rebuilds the publish artifacts instead of depending on prepare-release.yml. - - name: Resolve release metadata - env: - INPUT_CHANNEL: ${{ github.event.inputs.channel || '' }} - run: | - if [[ "${{ github.event_name }}" == "release" || "$INPUT_CHANNEL" == "release" ]]; then - release_channel=$(node ./scripts/release-channel.js) + # Cargo already skips downloading dependencies if they already exist + - name: Cache cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }} - echo "RELEASE_KIND=release" >> $GITHUB_ENV - echo "RELEASE_CHANNEL=$release_channel" >> $GITHUB_ENV - echo "FEATURES_ENV=stable" >> $GITHUB_ENV - else - sha_short=$(git rev-parse --short HEAD) - - echo "RELEASE_KIND=insiders" >> $GITHUB_ENV - echo "RELEASE_CHANNEL=insiders" >> $GITHUB_ENV - echo "SHA_SHORT=$sha_short" >> $GITHUB_ENV - echo "INSIDERS_VERSION=0.0.0-insiders.$sha_short" >> $GITHUB_ENV - fi - - - name: Setup WASM target - run: rustup target add wasm32-wasip1-threads + # Cache the `oxide` Rust build + - name: Cache oxide build + uses: actions/cache@v4 + with: + path: | + ./oxide/target/ + ./crates/node/*.node + ./crates/node/index.js + ./crates/node/index.d.ts + key: ${{ runner.os }}-${{ matrix.target }}-oxide-${{ hashFiles('./crates/**/*') }} - name: Install dependencies - run: pnpm --filter=!./playgrounds/* install --frozen-lockfile + run: pnpm --filter=!./playgrounds/* install - name: Download artifacts - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + uses: actions/download-artifact@v4 with: path: ${{ env.OXIDE_LOCATION }} @@ -278,19 +255,10 @@ jobs: cp bindings-x86_64-unknown-linux-musl/* ./npm/linux-x64-musl/ cp bindings-x86_64-unknown-freebsd/* ./npm/freebsd-x64/ - - name: 'Version based on commit: ${{ env.INSIDERS_VERSION }}' - if: env.RELEASE_KIND == 'insiders' - run: pnpm run version-packages ${INSIDERS_VERSION} - - name: Build Tailwind CSS - if: env.RELEASE_KIND == 'insiders' - run: pnpm run build - - - name: Build Tailwind CSS - if: env.RELEASE_KIND == 'release' run: pnpm run build env: - FEATURES_ENV: ${{ env.FEATURES_ENV }} + FEATURES_ENV: stable - name: Run pre-publish optimizations scripts run: node ./scripts/pre-publish-optimizations.mjs @@ -298,61 +266,25 @@ jobs: - name: Lock pre-release versions run: node ./scripts/lock-pre-release-versions.mjs - - name: Upload npm package tarballs - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - with: - name: npm-package-tarballs - path: dist/*.tgz + - name: Calculate environment variables + run: | + echo "RELEASE_CHANNEL=$(node ./scripts/release-channel.js)" >> $GITHUB_ENV + echo "TAILWINDCSS_VERSION=$(node -e 'console.log(require(`./packages/tailwindcss/package.json`).version);')" >> $GITHUB_ENV - name: Publish - run: | - pnpm --recursive --filter="!@tailwindcss/oxide-wasm32-wasi" publish --tag ${RELEASE_CHANNEL} --no-git-checks - # The wasm package needs a special npm config that isn't read when pnpm --recursive is used - pushd crates/node/npm/wasm32-wasi; pnpm publish --tag ${RELEASE_CHANNEL} --no-git-checks --config.node-linker=hoisted; popd; + run: pnpm --recursive publish --tag ${{ env.RELEASE_CHANNEL }} --no-git-checks + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Trigger Tailwind Play update - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + if: env.RELEASE_CHANNEL == 'latest' + uses: actions/github-script@v7 with: github-token: ${{ secrets.TAILWIND_PLAY_TOKEN }} script: | await github.rest.actions.createWorkflowDispatch({ owner: 'tailwindlabs', - repo: 'upgrades', - ref: 'main', + repo: 'play.tailwindcss.com', + ref: 'master', workflow_id: 'upgrade-tailwindcss.yml' }) - - notify: - if: ${{ always() && (needs.build.result == 'failure' || needs.build-freebsd.result == 'failure' || needs.release.result == 'failure') }} - needs: - - build - - build-freebsd - - release - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - persist-credentials: false - - - name: Resolve release label - id: release - env: - INPUT_CHANNEL: ${{ github.event.inputs.channel || '' }} - RELEASE_TAG: ${{ github.event.release.tag_name || '' }} - run: | - if [[ "${{ github.event_name }}" == "release" ]]; then - tag_name="${RELEASE_TAG:-${GITHUB_REF_NAME}}" - echo "label=release ${tag_name}" >> $GITHUB_OUTPUT - elif [[ "$INPUT_CHANNEL" == "release" ]]; then - version=$(node -p "require('./packages/tailwindcss/package.json').version") - echo "label=release v${version}" >> $GITHUB_OUTPUT - else - sha_short=$(git rev-parse --short HEAD) - echo "label=insiders release 0.0.0-insiders.${sha_short}" >> $GITHUB_OUTPUT - fi - - - name: Notify Discord - uses: discord-actions/message@5c7149c81a83146e5d01f142be1bf87a61831c4d # v2 - with: - webhookUrl: ${{ secrets.DISCORD_WEBHOOK_URL }} - message: 'The [most recent ${{ github.workflow }} workflow](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>) for `${{ steps.release.outputs.label }}` has failed.' diff --git a/.gitignore b/.gitignore index bbac7d227..9d75bba27 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ playwright-report/ blob-report/ playwright/.cache/ target/ -.debug/ +.debug 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 index 4f50b932f..3de4530dd 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,5 @@ pnpm-lock.yaml target/ crates/node/index.d.ts crates/node/index.js -crates/ignore/ .next .fingerprint diff --git a/CHANGELOG.md b/CHANGELOG.md index e5e6f6f89..f97dc1f72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,580 +9,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `@tailwindcss/turbopack` package to run Tailwind CSS with Next.js ([20367](https://github.com/tailwindlabs/tailwindcss/pull/20367)) - -### Fixed - -- Ensure watch mode detects changes to symlinked `@source` files whose real paths aren't otherwise scanned ([#20356](https://github.com/tailwindlabs/tailwindcss/pull/20356)) -- Ensure custom variants using `@scope` wrap the generated utilities instead of nesting inside them ([#20369](https://github.com/tailwindlabs/tailwindcss/pull/20369)) -- Fix flattening of `@scope` at-rules ([#20369](https://github.com/tailwindlabs/tailwindcss/pull/20369)) -- Fix standalone declarations in `@scope`, wrap them in `:where(:scope)` ([#20369](https://github.com/tailwindlabs/tailwindcss/pull/20369)) -- Always emit a space for empty fallback values in CSS variables (e.g. `var(--tw-blur,)` → `var(--tw-blur, )`) ([#20373](https://github.com/tailwindlabs/tailwindcss/pull/20373)) -- Canonicalization: convert arbitrary breakpoint and container query variants to named equivalents (e.g. `max-[64rem]` → `max-lg`) ([#20380](https://github.com/tailwindlabs/tailwindcss/pull/20380)) -- Prevent `@tailwindcss/vite` from crashing on every edit under Vite's experimental `bundledDev` mode ([#20379](https://github.com/tailwindlabs/tailwindcss/pull/20379)) -- Ensure `@tailwindcss/oxide` falls back to WASM on platforms without native bindings ([#20383](https://github.com/tailwindlabs/tailwindcss/pull/20383)) -- Detect classes in Ruby percent literals using angle brackets or custom delimiters (e.g. `%w`, `%w|flex|`), including in Slim and Haml templates ([#20387](https://github.com/tailwindlabs/tailwindcss/pull/20387)) -- Preserve whitespace in `--default(…)` values in custom functional utilities (e.g. `--default(box alphabetic)` no longer becomes `boxalphabetic`) ([#20392](https://github.com/tailwindlabs/tailwindcss/pull/20392)) -- Don't scan gitignored directories (e.g. `node_modules` and `.git`) when the project uses a safelist-style `.gitignore` (e.g. `/*` followed by `!/…` negations) ([#20397](https://github.com/tailwindlabs/tailwindcss/pull/20397)) -- Ensure root `theme('…')` namespace lookups in JavaScript plugins and config files return the full namespace object instead of the value of its `DEFAULT` key ([#20399](https://github.com/tailwindlabs/tailwindcss/pull/20399)) -- Skip ignored directories entirely when computing watch globs (`scanner.globs`), instead of walking their full contents on every rebuild ([#20408](https://github.com/tailwindlabs/tailwindcss/pull/20408)) -- Oxide: drop invalid UTF-8 candidates ([#20389](https://github.com/tailwindlabs/tailwindcss/pull/20389)) -- `@tailwindcss/vite` no longer forces a full page reload for external files (e.g.: `.php` files) ([#20414](https://github.com/tailwindlabs/tailwindcss/issues/20414)) -- Canonicalization: don't merge utilities that reference different theme variables set to CSS-wide keywords like `unset` ([#20417](https://github.com/tailwindlabs/tailwindcss/pull/20417)) -- Don't generate utilities when a modifier is used that would otherwise be silently ignored (e.g. `rounded-sm/[5]`, `shadow-sm/foo`, `stroke-2/50`) ([#20419](https://github.com/tailwindlabs/tailwindcss/pull/20419)) -- Only normalize top-level `and`, `or`, and `not` keywords in `supports-[…]` variants (e.g. `selector(a: not (.foo))` → `selector(a:not(.foo))`) ([#20420](https://github.com/tailwindlabs/tailwindcss/pull/20420)) -- Don't warn about Angular's `::ng-deep` and `:host-context()` when optimizing CSS ([#20434](https://github.com/tailwindlabs/tailwindcss/pull/20434)) -- Don't generate CSS for candidates containing an empty additional modifier (e.g. `bg-red-500/50/` and `group-hover/foo//bar:flex`) ([#20466](https://github.com/tailwindlabs/tailwindcss/pull/20466)) -- Sort `min-*`, `max-*`, and container query variants with decimal values numerically (e.g. `min-[40.25rem]` before `min-[40.5rem]`) ([#20512](https://github.com/tailwindlabs/tailwindcss/pull/20512)) -- Ensure CSS comments ending with `\*/` are closed correctly instead of swallowing the CSS that follows (e.g. `/* C:\temp\*/`) ([#20508](https://github.com/tailwindlabs/tailwindcss/pull/20508)) -- Improve style invalidation performance of `group-*` and `peer-*` variants ([#20513](https://github.com/tailwindlabs/tailwindcss/pull/20513)) - -## [4.3.3] - 2026-07-16 - -### Fixed - -- Support `--watch --poll[=ms]` in `@tailwindcss/cli` when filesystem events are unreliable or unavailable ([#20297](https://github.com/tailwindlabs/tailwindcss/pull/20297)) -- Canonicalization: match arbitrary hex colors against theme colors case-insensitively (e.g. `bg-[#fff]` and `bg-[#FFF]` → `bg-white`) ([#20298](https://github.com/tailwindlabs/tailwindcss/pull/20298)) -- Prevent Preflight from overriding Firefox's native `iframe:focus-visible` outline styles ([#20292](https://github.com/tailwindlabs/tailwindcss/pull/20292)) -- Ensure `theme('colors.foo')` in JS plugins resolves correctly when both `--color-foo` and `--color-foo-bar` exist ([#20299](https://github.com/tailwindlabs/tailwindcss/pull/20299)) -- Ensure fractional opacity modifiers work with named shadow sizes like `shadow-sm/12.5`, `text-shadow-sm/12.5`, `drop-shadow-sm/12.5`, and `inset-shadow-sm/12.5` ([#20302](https://github.com/tailwindlabs/tailwindcss/pull/20302)) -- Parse selectors like `[data-foo]div` as two selectors instead of one ([#20303](https://github.com/tailwindlabs/tailwindcss/pull/20303)) -- Ensure `@tailwindcss/postcss` rebuilds when a preprocessor like Sass changes the input CSS without changing the input file on disk ([#20310](https://github.com/tailwindlabs/tailwindcss/pull/20310)) -- Ensure CSS nesting is handled even when Lightning CSS isn't run, such as in `@tailwindcss/browser` and Tailwind Play ([#20124](https://github.com/tailwindlabs/tailwindcss/pull/20124)) -- Prevent achromatic theme colors from shifting hue when mixed in polar color spaces like `oklch` ([#20314](https://github.com/tailwindlabs/tailwindcss/pull/20314)) -- Ensure `--spacing(0)` is optimized to `0px` instead of `0` so it remains a `` when used in `calc(…)` ([#20319](https://github.com/tailwindlabs/tailwindcss/pull/20319)) -- Load `@parcel/watcher` only when needed in `@tailwindcss/cli --watch` mode, so one-off builds and `--watch --poll` work when `@parcel/watcher` can't be loaded ([#20325](https://github.com/tailwindlabs/tailwindcss/pull/20325)) -- Use explicit platform fonts instead of `system-ui` and `ui-sans-serif` so CJK text respects the page's `lang` attribute on Windows ([#20318](https://github.com/tailwindlabs/tailwindcss/pull/20318)) -- Prevent `@tailwindcss/upgrade` from rewriting ignored files when run from a subdirectory ([#20329](https://github.com/tailwindlabs/tailwindcss/pull/20329)) -- Ensure earlier `@source` rules pointing to nested files are scanned when later `@source` rules point to files in parent folders ([#20335](https://github.com/tailwindlabs/tailwindcss/pull/20335)) -- Prevent `@tailwindcss/vite` from triggering full page reloads when scanned files are processed by Vite but haven't been loaded as modules yet ([#20336](https://github.com/tailwindlabs/tailwindcss/pull/20336)) - -## [4.3.2] - 2026-06-26 - -### Fixed - -- Support bare spacing values for `auto-rows-*` and `auto-cols-*` utilities (e.g. `auto-rows-12` and `auto-cols-16`) ([#20229](https://github.com/tailwindlabs/tailwindcss/pull/20229)) -- Prevent `@tailwindcss/cli` in `--watch` mode from crashing on Windows when `@source` points to a directory that doesn't exist ([#20242](https://github.com/tailwindlabs/tailwindcss/pull/20242)) -- Prevent `@tailwindcss/vite` from crashing in Deno v2.8.x when `context.parentURL` is not a valid URL ([#20245](https://github.com/tailwindlabs/tailwindcss/pull/20245)) -- Ensure `@tailwindcss/cli` in `--watch` mode rebuilds when the input CSS file changes in an ignored directory ([#20246](https://github.com/tailwindlabs/tailwindcss/pull/20246)) -- Allow `@variant` rules used in `addBase(…)` to use custom variants defined later ([#20247](https://github.com/tailwindlabs/tailwindcss/pull/20247)) -- Prevent `@tailwindcss/vite` from crashing during HMR when scanned files or directories are deleted ([#20259](https://github.com/tailwindlabs/tailwindcss/pull/20259)) -- Generate `font-size` instead of `color` declarations for `text-[--spacing(…)]` ([#20260](https://github.com/tailwindlabs/tailwindcss/pull/20260)) -- Prevent `@source` patterns from scanning unrelated sibling files and folders ([#20263](https://github.com/tailwindlabs/tailwindcss/pull/20263)) -- Extract class candidates adjacent to Template Toolkit delimiters like `%]…[%` in `.tt`, `.tt2`, and `.tx` files ([#20269](https://github.com/tailwindlabs/tailwindcss/pull/20269)) -- Extract class candidates from conditional Maud syntax like `p.text-black[condition]` ([#20269](https://github.com/tailwindlabs/tailwindcss/pull/20269)) -- Prevent `@position-try` rules from triggering unknown at-rule warnings when optimizing CSS ([#20277](https://github.com/tailwindlabs/tailwindcss/pull/20277)) -- Support class suggestions for named opacity modifiers from `--opacity` theme values ([#20287](https://github.com/tailwindlabs/tailwindcss/pull/20287)) -- Prevent type errors in `@tailwindcss/postcss` when used with newer PostCSS patch releases ([#20289](https://github.com/tailwindlabs/tailwindcss/pull/20289)) - -## [4.3.1] - 2026-06-12 - -### Added - -- Add `--silent` option to suppress output in `@tailwindcss/cli` ([#20100](https://github.com/tailwindlabs/tailwindcss/pull/20100)) - -### Fixed - -- Remove deprecation warnings by using `Module#registerHooks` instead of `Module#register` on Node 26+ ([#20028](https://github.com/tailwindlabs/tailwindcss/pull/20028)) -- Canonicalization: don't crash when plugin utilities throw for unsupported values ([#20052](https://github.com/tailwindlabs/tailwindcss/pull/20052)) -- Allow `@apply` to be used with CSS mixins ([#19427](https://github.com/tailwindlabs/tailwindcss/pull/19427)) -- Ensure `not-*` correctly negates `@container` queries, including `style(…)` queries ([#20059](https://github.com/tailwindlabs/tailwindcss/pull/20059)) -- Ensure `drop-shadow-*` color utilities work with custom shadow values containing `calc(…)` ([#20080](https://github.com/tailwindlabs/tailwindcss/pull/20080)) -- Fix 'Sourcemap is likely to be incorrect' warnings when using `@tailwindcss/vite` ([#20103](https://github.com/tailwindlabs/tailwindcss/pull/20103)) -- Ensure `@tailwindcss/webpack` can be installed in Rspack projects without requiring `webpack` as a peer dependency ([#20027](https://github.com/tailwindlabs/tailwindcss/pull/20027)) -- Canonicalization: don't suggest invalid `calc(…)` expressions (e.g. `px-[calc(1rem+0px)]` → `px-[calc(1rem+0)]`) ([#20127](https://github.com/tailwindlabs/tailwindcss/pull/20127)) -- Canonicalization: avoid suggesting large spacing-scale values for arbitrary lengths (e.g. `left-[99999px]` → `left-[99999px]`, not `left-24999.75`) ([#20130](https://github.com/tailwindlabs/tailwindcss/pull/20130)) -- Ensure `@tailwindcss/cli` in `--watch` mode recovers when a tracked dependency is deleted and restored ([#20137](https://github.com/tailwindlabs/tailwindcss/pull/20137)) -- Ensure standalone `@tailwindcss/cli` binaries are ignored when scanning for class candidates ([#20139](https://github.com/tailwindlabs/tailwindcss/pull/20139)) -- Ensure class candidates are extracted from Twig `addClass(…)` and `removeClass(…)` calls ([#20198](https://github.com/tailwindlabs/tailwindcss/pull/20198)) -- Don't crash in the Ruby or Vue preprocessors when scanning files containing invalid UTF-8 bytes ([#19588](https://github.com/tailwindlabs/tailwindcss/pull/19588)) -- Allow `@variant` to be used inside `addBase` ([#19480](https://github.com/tailwindlabs/tailwindcss/pull/19480)) -- Ensure `@source` globs with symlinks are preserved ([#20203](https://github.com/tailwindlabs/tailwindcss/pull/20203)) -- Ensure later `@source` rules can re-include files excluded by earlier `@source not` rules ([#20203](https://github.com/tailwindlabs/tailwindcss/pull/20203)) -- Upgrade: don't migrate empty class rules to invalid `@utility` rules ([#20205](https://github.com/tailwindlabs/tailwindcss/pull/20205)) -- Ensure transitions between `inset-shadow-none` and other inset shadows work correctly ([#20208](https://github.com/tailwindlabs/tailwindcss/pull/20208)) -- Ensure explicitly referenced `@source` directories are scanned even when ignored by git ([#20214](https://github.com/tailwindlabs/tailwindcss/pull/20214)) -- Ensure `@source` globs ending in `**/*` preserve dynamic path segments to avoid scanning too many files ([#20217](https://github.com/tailwindlabs/tailwindcss/pull/20217)) -- Canonicalization: don't fold `calc(…)` divisions when the result would require high precision (e.g. `w-[calc(100%/3.5)]` → `w-[calc(100%/3.5)]`, not `w-[28.571428571428573%]`) ([#20221](https://github.com/tailwindlabs/tailwindcss/pull/20221)) -- Serve ESM type declarations to ESM importers of `@tailwindcss/postcss` ([#20228](https://github.com/tailwindlabs/tailwindcss/pull/20228)) - -### Changed - -- Generate `0` instead of `calc(var(--spacing) * 0)` for spacing utilities like `m-0` and `left-0` ([#20196](https://github.com/tailwindlabs/tailwindcss/pull/20196)) -- Generate `var(--spacing)` instead of `calc(var(--spacing) * 1)` for spacing utilities like `m-1` and `left-1` ([#20196](https://github.com/tailwindlabs/tailwindcss/pull/20196)) - -## [4.3.0] - 2026-05-08 - -### Added - -- Add `@container-size` utility ([#18901](https://github.com/tailwindlabs/tailwindcss/pull/18901)) -- Add `scrollbar-{auto,thin,none}` utilities for `scrollbar-width`, and `scrollbar-thumb-*` / `scrollbar-track-*` color utilities for `scrollbar-color` ([#19981](https://github.com/tailwindlabs/tailwindcss/pull/19981), [#20019](https://github.com/tailwindlabs/tailwindcss/pull/20019)) -- Add `scrollbar-gutter-*` utilities ([#20018](https://github.com/tailwindlabs/tailwindcss/pull/20018)) -- Add `zoom-*` utilities ([#20020](https://github.com/tailwindlabs/tailwindcss/pull/20020)) -- Add `tab-*` utilities ([#20022](https://github.com/tailwindlabs/tailwindcss/pull/20022)) -- Allow using `@variant` with stacked variants (e.g. `@variant hover:focus { … }`) ([#19996](https://github.com/tailwindlabs/tailwindcss/pull/19996)) -- Allow using `@variant` with compound variants (e.g. `@variant hover, focus { … }`) ([#19996](https://github.com/tailwindlabs/tailwindcss/pull/19996)) -- Support `--default(…)` in `--value(…)` and `--modifier(…)` for functional `@utility` definitions ([#19989](https://github.com/tailwindlabs/tailwindcss/pull/19989)) - -### Fixed - -- Ensure `@plugin` resolves package JavaScript entries instead of browser CSS entries when using `@tailwindcss/vite` ([#19949](https://github.com/tailwindlabs/tailwindcss/pull/19949)) -- Fix relative `@import` and `@plugin` paths resolving from the wrong directory when using `@tailwindcss/vite` ([#19965](https://github.com/tailwindlabs/tailwindcss/pull/19965)) -- Ensure CSS files containing `@variant` are processed by `@tailwindcss/vite` ([#19966](https://github.com/tailwindlabs/tailwindcss/pull/19966)) -- Resolve imports relative to `base` when `result.opts.from` is not provided when using `@tailwindcss/postcss` ([#19980](https://github.com/tailwindlabs/tailwindcss/pull/19980)) -- Canonicalization: preserve significant `_` whitespace in arbitrary values ([#19986](https://github.com/tailwindlabs/tailwindcss/pull/19986)) -- Canonicalization: add parentheses when removing whitespace from arbitrary values would hurt readability (e.g. `w-[calc(100%---spacing(60))]` → `w-[calc(100%-(--spacing(60)))]`) ([#19986](https://github.com/tailwindlabs/tailwindcss/pull/19986)) -- Canonicalization: preserve the original unit in arbitrary values instead of normalizing to base units (e.g. `-mt-[20in]` → `mt-[-20in]`, not `mt-[-1920px]`) ([#19988](https://github.com/tailwindlabs/tailwindcss/pull/19988)) -- Canonicalization: migrate arbitrary `:has()` variants from `[&:has(…)]` to `has-[…]` ([#19991](https://github.com/tailwindlabs/tailwindcss/pull/19991)) -- Upgrade: don’t migrate inline `style` attributes (e.g. `style="flex-grow: 1"` → `style="flex-grow: 1"`, not `style="grow: 1"`) ([#19918](https://github.com/tailwindlabs/tailwindcss/pull/19918)) -- Allow multiple `@utility` definitions with the same name but different value types ([#19777](https://github.com/tailwindlabs/tailwindcss/pull/19777)) -- Export missing `PluginWithConfig` type from `tailwindcss/plugin` to fix errors when inferring plugin config types ([#19707](https://github.com/tailwindlabs/tailwindcss/pull/19707)) -- Ensure `start` and `end` legacy utilities without values do not generate CSS ([#20003](https://github.com/tailwindlabs/tailwindcss/pull/20003)) -- Ensure `--value(…)` is required in functional `@utility` definitions ([#20005](https://github.com/tailwindlabs/tailwindcss/pull/20005)) -- Canonicalization: preserve required whitespace around operators in negated arbitrary values (e.g. `-left-[(var(--a)+var(--b))]`) ([#20011](https://github.com/tailwindlabs/tailwindcss/pull/20011)) - -## [4.2.4] - 2026-04-21 - -### Fixed - -- Ensure imports in `@import` and `@plugin` still resolve correctly when using Vite aliases in `@tailwindcss/vite` ([#19947](https://github.com/tailwindlabs/tailwindcss/pull/19947)) - -## [4.2.3] - 2026-04-20 - -### Fixed - -- Canonicalization: improve canonicalization for `tracking-*` utilities by preferring non-negative utilities (e.g. `-tracking-tighter` → `tracking-wider`) ([#19827](https://github.com/tailwindlabs/tailwindcss/pull/19827)) -- Fix crash due to invalid characters in candidate (exceeding valid unicode code point range) ([#19829](https://github.com/tailwindlabs/tailwindcss/pull/19829)) -- Ensure query params in imports are considered unique resources when using `@tailwindcss/webpack` ([#19723](https://github.com/tailwindlabs/tailwindcss/pull/19723)) -- Canonicalization: collapse arbitrary values into shorthand utilities (e.g. `px-[1.2rem] py-[1.2rem]` → `p-[1.2rem]`) ([#19837](https://github.com/tailwindlabs/tailwindcss/pull/19837)) -- Canonicalization: collapse `border-{t,b}-*` into `border-y-*`, `border-{l,r}-*` into `border-x-*`, and `border-{t,r,b,l}-*` into `border-*` ([#19842](https://github.com/tailwindlabs/tailwindcss/pull/19842)) -- Canonicalization: collapse `scroll-m{t,b}-*` into `scroll-my-*`, `scroll-m{l,r}-*` into `scroll-mx-*`, and `scroll-m{t,r,b,l}-*` into `scroll-m-*` ([#19842](https://github.com/tailwindlabs/tailwindcss/pull/19842)) -- Canonicalization: collapse `scroll-p{t,b}-*` into `scroll-py-*`, `scroll-p{l,r}-*` into `scroll-px-*`, and `scroll-p{t,r,b,l}-*` into `scroll-p-*` ([#19842](https://github.com/tailwindlabs/tailwindcss/pull/19842)) -- Canonicalization: collapse `overflow-{x,y}-*` into `overflow-*` ([#19842](https://github.com/tailwindlabs/tailwindcss/pull/19842)) -- Canonicalization: collapse `overscroll-{x,y}-*` into `overscroll-*` ([#19842](https://github.com/tailwindlabs/tailwindcss/pull/19842)) -- Read from `--placeholder-color` instead of `--background-color` for `placeholder-*` utilities ([#19843](https://github.com/tailwindlabs/tailwindcss/pull/19843)) -- Upgrade: ensure files are not emptied out when killing the upgrade process while it's running ([#19846](https://github.com/tailwindlabs/tailwindcss/pull/19846)) -- Upgrade: use `config.content` when migrating from Tailwind CSS v3 to Tailwind CSS v4 ([#19846](https://github.com/tailwindlabs/tailwindcss/pull/19846)) -- Upgrade: never migrate files that are ignored by git ([#19846](https://github.com/tailwindlabs/tailwindcss/pull/19846)) -- Add `.env` and `.env.*` to default ignored content files ([#19846](https://github.com/tailwindlabs/tailwindcss/pull/19846)) -- Canonicalization: migrate `overflow-ellipsis` into `text-ellipsis` ([#19849](https://github.com/tailwindlabs/tailwindcss/pull/19849)) -- Canonicalization: migrate `start-full` → `inset-s-full`, `start-auto` → `inset-s-auto`, `start-px` → `inset-s-px`, and `start-` → `inset-s-` as well as negative versions ([#19849](https://github.com/tailwindlabs/tailwindcss/pull/19849)) -- Canonicalization: migrate `end-full` → `inset-e-full`, `end-auto` → `inset-e-auto`, `end-px` → `inset-e-px`, and `end-` → `inset-e-` as well as negative versions ([#19849](https://github.com/tailwindlabs/tailwindcss/pull/19849)) -- Canonicalization: move the `-` sign inside the arbitrary value `-left-[9rem]` → `left-[-9rem]` ([#19858](https://github.com/tailwindlabs/tailwindcss/pull/19858)) -- Canonicalization: move the `-` sign outside the arbitrary value `ml-[calc(-1*var(--width))]` → `-ml-(--width)` ([#19858](https://github.com/tailwindlabs/tailwindcss/pull/19858)) -- Improve performance when scanning JSONL / NDJSON files ([#19862](https://github.com/tailwindlabs/tailwindcss/pull/19862)) -- Support `NODE_PATH` environment variable in standalone CLI ([#19617](https://github.com/tailwindlabs/tailwindcss/pull/19617)) - -## [4.2.2] - 2026-03-18 - -### Fixed - -- Don't crash when candidates contain prototype properties like `row-constructor` ([#19725](https://github.com/tailwindlabs/tailwindcss/pull/19725)) -- Canonicalize `calc(var(--spacing)*…)` expressions into `--spacing(…)` ([#19769](https://github.com/tailwindlabs/tailwindcss/pull/19769)) -- Fix crash in canonicalization step when handling utilities containing `@property` at-rules (e.g. `shadow-sm border`) ([#19727](https://github.com/tailwindlabs/tailwindcss/pull/19727)) -- Skip full reload for server only modules scanned by client CSS when using `@tailwindcss/vite` ([#19745](https://github.com/tailwindlabs/tailwindcss/pull/19745)) -- Add support for Vite 8 in `@tailwindcss/vite` ([#19790](https://github.com/tailwindlabs/tailwindcss/pull/19790)) -- Improve canonicalization for bare values exceeding default spacing scale suggestions (e.g. `w-1234 h-1234` → `size-1234`) ([#19809](https://github.com/tailwindlabs/tailwindcss/pull/19809)) -- Fix canonicalization resulting in empty list (e.g. `w-5 h-5 size-5` → `''` instead of `size-5`) ([#19812](https://github.com/tailwindlabs/tailwindcss/pull/19812)) -- Resolve tsconfig paths to allow for `@import '@/path/to/file';` when using `@tailwindcss/vite` ([#19803](https://github.com/tailwindlabs/tailwindcss/pull/19803)) - -## [4.2.1] - 2026-02-23 - -### Fixed - -- Allow trailing dash in functional utility names for backwards compatibility ([#19696](https://github.com/tailwindlabs/tailwindcss/pull/19696)) -- Properly detect classes containing `.` characters within curly braces in MDX files ([#19711](https://github.com/tailwindlabs/tailwindcss/pull/19711)) - -## [4.2.0] - 2026-02-18 - -### Added - -- Add mauve, olive, mist, and taupe color palettes to the default theme ([#19627](https://github.com/tailwindlabs/tailwindcss/pull/19627)) -- Add `@tailwindcss/webpack` package to run Tailwind CSS as a webpack plugin ([#19610](https://github.com/tailwindlabs/tailwindcss/pull/19610)) -- Add `pbs-*` and `pbe-*` utilities for `padding-block-start` and `padding-block-end` ([#19601](https://github.com/tailwindlabs/tailwindcss/pull/19601)) -- Add `mbs-*` and `mbe-*` utilities for `margin-block-start` and `margin-block-end` ([#19601](https://github.com/tailwindlabs/tailwindcss/pull/19601)) -- Add `scroll-pbs-*` and `scroll-pbe-*` utilities for `scroll-padding-block-start` and `scroll-padding-block-end` ([#19601](https://github.com/tailwindlabs/tailwindcss/pull/19601)) -- Add `scroll-mbs-*` and `scroll-mbe-*` utilities for `scroll-margin-block-start` and `scroll-margin-block-end` ([#19601](https://github.com/tailwindlabs/tailwindcss/pull/19601)) -- Add `border-bs-*` and `border-be-*` utilities for `border-block-start` and `border-block-end` ([#19601](https://github.com/tailwindlabs/tailwindcss/pull/19601)) -- Add `inline-*`, `min-inline-*`, `max-inline-*` utilities for `inline-size`, `min-inline-size`, and `max-inline-size` ([#19612](https://github.com/tailwindlabs/tailwindcss/pull/19612)) -- Add `block-*`, `min-block-*`, `max-block-*` utilities for `block-size`, `min-block-size`, and `max-block-size` ([#19612](https://github.com/tailwindlabs/tailwindcss/pull/19612)) -- Add `inset-s-*`, `inset-e-*`, `inset-bs-*`, `inset-be-*` utilities for `inset-inline-start`, `inset-inline-end`, `inset-block-start`, and `inset-block-end` ([#19613](https://github.com/tailwindlabs/tailwindcss/pull/19613)) -- Add `font-features-*` utility for `font-feature-settings` ([#19623](https://github.com/tailwindlabs/tailwindcss/pull/19623)) - -### Fixed - -- Prevent double `@supports` wrapper for `color-mix` values ([#19450](https://github.com/tailwindlabs/tailwindcss/pull/19450)) -- Allow whitespace around `@source inline()` argument ([#19461](https://github.com/tailwindlabs/tailwindcss/pull/19461)) -- Emit comment when source maps are saved to files when using `@tailwindcss/cli` ([#19447](https://github.com/tailwindlabs/tailwindcss/pull/19447)) -- Detect utilities containing capital letters followed by numbers ([#19465](https://github.com/tailwindlabs/tailwindcss/pull/19465)) -- Fix class extraction for Rails' strict locals ([#19525](https://github.com/tailwindlabs/tailwindcss/pull/19525)) -- Align `@utility` name validation with Oxide scanner rules ([#19524](https://github.com/tailwindlabs/tailwindcss/pull/19524)) -- Fix infinite loop when using `@variant` inside `@custom-variant` ([#19633](https://github.com/tailwindlabs/tailwindcss/pull/19633)) -- Allow multiples of `.25` in `aspect-*` fractions (e.g. `aspect-8.5/11`) ([#19688](https://github.com/tailwindlabs/tailwindcss/pull/19688)) -- Ensure changes to external files listed via `@source` trigger a full page reload when using `@tailwindcss/vite` ([#19670](https://github.com/tailwindlabs/tailwindcss/pull/19670)) -- Improve performance of Oxide scanner in bigger projects by reducing file system walks ([#19632](https://github.com/tailwindlabs/tailwindcss/pull/19632)) -- Ensure import aliases in Astro v5 work without crashing when using `@tailwindcss/vite` ([#19677](https://github.com/tailwindlabs/tailwindcss/issues/19677)) -- Allow escape characters in `@utility` names to improve support with formatters such as Biome ([#19626](https://github.com/tailwindlabs/tailwindcss/pull/19626)) -- Fix incorrect canonicalization results when canonicalizing multiple times ([#19675](https://github.com/tailwindlabs/tailwindcss/pull/19675)) -- Add `.jj` to default ignored content directories ([#19687](https://github.com/tailwindlabs/tailwindcss/pull/19687)) - -### Deprecated - -- Deprecate `start-*` and `end-*` utilities in favor of `inset-s-*` and `inset-e-*` utilities ([#19613](https://github.com/tailwindlabs/tailwindcss/pull/19613)) - -## [4.1.18] - 2025-12-11 - -### Fixed - -- Ensure validation of `source(…)` happens relative to the file it is in ([#19274](https://github.com/tailwindlabs/tailwindcss/pull/19274)) -- Include filename and line numbers in CSS parse errors ([#19282](https://github.com/tailwindlabs/tailwindcss/pull/19282)) -- Skip comments in Ruby files when checking for class names ([#19243](https://github.com/tailwindlabs/tailwindcss/pull/19243)) -- Skip over arbitrary property utilities with a top-level `!` in the value ([#19243](https://github.com/tailwindlabs/tailwindcss/pull/19243)) -- Support environment API in `@tailwindcss/vite` ([#18970](https://github.com/tailwindlabs/tailwindcss/pull/18970)) -- Preserve case of theme keys from JS configs and plugins ([#19337](https://github.com/tailwindlabs/tailwindcss/pull/19337)) -- Write source maps correctly on the CLI when using `--watch` ([#19373](https://github.com/tailwindlabs/tailwindcss/pull/19373)) -- Handle special defaults (like `ringColor.DEFAULT`) in JS configs ([#19348](https://github.com/tailwindlabs/tailwindcss/pull/19348)) -- Improve backwards compatibility for `content` theme key from JS configs ([#19381](https://github.com/tailwindlabs/tailwindcss/pull/19381)) -- Upgrade: Handle `future` and `experimental` config keys ([#19344](https://github.com/tailwindlabs/tailwindcss/pull/19344)) -- Try to canonicalize any arbitrary utility to a bare value ([#19379](https://github.com/tailwindlabs/tailwindcss/pull/19379)) -- Validate candidates similarly to Oxide ([#19397](https://github.com/tailwindlabs/tailwindcss/pull/19397)) -- Canonicalization: combine `text-*` and `leading-*` classes ([#19396](https://github.com/tailwindlabs/tailwindcss/pull/19396)) -- Correctly handle duplicate CLI arguments ([#19416](https://github.com/tailwindlabs/tailwindcss/pull/19416)) -- Don’t emit color-mix fallback rules inside `@keyframes` ([#19419](https://github.com/tailwindlabs/tailwindcss/pull/19419)) -- CLI: Don't hang when output is `/dev/stdout` ([#19421](https://github.com/tailwindlabs/tailwindcss/pull/19421)) - -## [3.4.19] - 2025-12-10 - -### Fixed - -- Don’t break `sibling-*()` functions when used inside `calc(…)` ([#19335](https://github.com/tailwindlabs/tailwindcss/pull/19335)) - -## [4.1.17] - 2025-11-06 - -### Fixed - -- Substitute `@variant` inside legacy JS APIs ([#19263](https://github.com/tailwindlabs/tailwindcss/pull/19263)) -- Prevent occasional crash on Windows when loaded into a worker thread ([#19242](https://github.com/tailwindlabs/tailwindcss/pull/19242)) - -## [4.1.16] - 2025-10-23 - -### Fixed - -- Discard candidates with an empty data type ([#19172](https://github.com/tailwindlabs/tailwindcss/pull/19172)) -- Fix canonicalization of arbitrary variants with attribute selectors ([#19176](https://github.com/tailwindlabs/tailwindcss/pull/19176)) -- Fix invalid colors due to nested `&` ([#19184](https://github.com/tailwindlabs/tailwindcss/pull/19184)) -- Improve canonicalization for `& > :pseudo` and `& :pseudo` arbitrary variants ([#19178](https://github.com/tailwindlabs/tailwindcss/pull/19178)) - -## [4.1.15] - 2025-10-20 - -### Fixed - -- Fix Safari devtools rendering issue due to `color-mix` fallback ([#19069](https://github.com/tailwindlabs/tailwindcss/pull/19069)) -- Suppress Lightning CSS warnings about `:deep`, `:slotted`, and `:global` ([#19094](https://github.com/tailwindlabs/tailwindcss/pull/19094)) -- Fix resolving theme keys when starting with the name of another theme key in JS configs and plugins ([#19097](https://github.com/tailwindlabs/tailwindcss/pull/19097)) -- Allow named groups in combination with `not-*`, `has-*`, and `in-*` ([#19100](https://github.com/tailwindlabs/tailwindcss/pull/19100)) -- Prevent important utilities from affecting other utilities ([#19110](https://github.com/tailwindlabs/tailwindcss/pull/19110)) -- Don’t index into strings with the `theme(…)` function ([#19111](https://github.com/tailwindlabs/tailwindcss/pull/19111)) -- Fix parsing issue when `\t` is used in at-rules ([#19130](https://github.com/tailwindlabs/tailwindcss/pull/19130)) -- Upgrade: Canonicalize utilities containing `0` values ([#19095](https://github.com/tailwindlabs/tailwindcss/pull/19095)) -- Upgrade: Migrate deprecated `break-words` to `wrap-break-word` ([#19157](https://github.com/tailwindlabs/tailwindcss/pull/19157)) - -### Changed - -- Remove the `postinstall` script from oxide ([#19149])(https://github.com/tailwindlabs/tailwindcss/pull/19149) - -## [4.1.14] - 2025-10-01 - -### Fixed - -- Handle `'` syntax in ClojureScript when extracting classes ([#18888](https://github.com/tailwindlabs/tailwindcss/pull/18888)) -- Handle `@variant` inside `@custom-variant` ([#18885](https://github.com/tailwindlabs/tailwindcss/pull/18885)) -- Merge suggestions when using `@utility` ([#18900](https://github.com/tailwindlabs/tailwindcss/pull/18900)) -- Ensure that file system watchers created when using the CLI are always cleaned up ([#18905](https://github.com/tailwindlabs/tailwindcss/pull/18905)) -- Do not generate `grid-column` utilities when configuring `grid-column-start` or `grid-column-end` ([#18907](https://github.com/tailwindlabs/tailwindcss/pull/18907)) -- Do not generate `grid-row` utilities when configuring `grid-row-start` or `grid-row-end` ([#18907](https://github.com/tailwindlabs/tailwindcss/pull/18907)) -- Prevent duplicate CSS when overwriting a static utility with a theme key ([#18056](https://github.com/tailwindlabs/tailwindcss/pull/18056)) -- Show Lightning CSS warnings (if any) when optimizing/minifying ([#18918](https://github.com/tailwindlabs/tailwindcss/pull/18918)) -- Use `default` export condition for `@tailwindcss/vite` ([#18948](https://github.com/tailwindlabs/tailwindcss/pull/18948)) -- Re-throw errors from PostCSS nodes ([#18373](https://github.com/tailwindlabs/tailwindcss/pull/18373)) -- Detect classes in markdown inline directives ([#18967](https://github.com/tailwindlabs/tailwindcss/pull/18967)) -- Ensure files with only `@theme` produce no output when built ([#18979](https://github.com/tailwindlabs/tailwindcss/pull/18979)) -- Support Maud templates when extracting classes ([#18988](https://github.com/tailwindlabs/tailwindcss/pull/18988)) -- Upgrade: Do not migrate `variant = 'outline'` during upgrades ([#18922](https://github.com/tailwindlabs/tailwindcss/pull/18922)) -- Upgrade: Show version mismatch (if any) when running upgrade tool ([#19028](https://github.com/tailwindlabs/tailwindcss/pull/19028)) -- Upgrade: Ensure first class inside `className` is migrated ([#19031](https://github.com/tailwindlabs/tailwindcss/pull/19031)) -- Upgrade: Migrate classes inside `*ClassName` and `*Class` attributes ([#19031](https://github.com/tailwindlabs/tailwindcss/pull/19031)) - -## [3.4.18] - 2025-10-01 - -### Fixed - -- Improve support for raw `supports-[…]` queries in arbitrary values ([#13605](https://github.com/tailwindlabs/tailwindcss/pull/13605)) -- Fix `require.cache` error when loaded through a TypeScript file in Node 22.18+ ([#18665](https://github.com/tailwindlabs/tailwindcss/pull/18665)) -- Support `import.meta.resolve(…)` in configs for new enough Node.js versions ([#18938](https://github.com/tailwindlabs/tailwindcss/pull/18938)) -- Allow using newer versions of `postcss-load-config` for better ESM and TypeScript PostCSS config support with the CLI ([#18938](https://github.com/tailwindlabs/tailwindcss/pull/18938)) -- Remove irrelevant utility rules when matching important classes ([#19030](https://github.com/tailwindlabs/tailwindcss/pull/19030)) - -## [4.1.13] - 2025-09-03 - -### Changed - -- Drop warning from browser build ([#18731](https://github.com/tailwindlabs/tailwindcss/issues/18731)) -- Drop exact duplicate declarations when emitting CSS ([#18809](https://github.com/tailwindlabs/tailwindcss/issues/18809)) - -### Fixed - -- Don't transition `visibility` when using `transition` ([#18795](https://github.com/tailwindlabs/tailwindcss/pull/18795)) -- Discard matched variants with unknown named values ([#18799](https://github.com/tailwindlabs/tailwindcss/pull/18799)) -- Discard matched variants with non-string values ([#18799](https://github.com/tailwindlabs/tailwindcss/pull/18799)) -- Show suggestions for known `matchVariant` values ([#18798](https://github.com/tailwindlabs/tailwindcss/pull/18798)) -- Replace deprecated `clip` with `clip-path` in `sr-only` ([#18769](https://github.com/tailwindlabs/tailwindcss/pull/18769)) -- Hide internal fields from completions in `matchUtilities` ([#18820](https://github.com/tailwindlabs/tailwindcss/pull/18820)) -- Ignore `.vercel` folders by default (can be overridden by `@source …` rules) ([#18855](https://github.com/tailwindlabs/tailwindcss/pull/18855)) -- Consider variants starting with `@-` to be invalid (e.g. `@-2xl:flex`) ([#18869](https://github.com/tailwindlabs/tailwindcss/pull/18869)) -- Do not allow custom variants to start or end with a `-` or `_` ([#18867](https://github.com/tailwindlabs/tailwindcss/pull/18867), [#18872](https://github.com/tailwindlabs/tailwindcss/pull/18872)) -- Upgrade: Migrate `aria` theme keys to `@custom-variant` ([#18815](https://github.com/tailwindlabs/tailwindcss/pull/18815)) -- Upgrade: Migrate `data` theme keys to `@custom-variant` ([#18816](https://github.com/tailwindlabs/tailwindcss/pull/18816)) -- Upgrade: Migrate `supports` theme keys to `@custom-variant` ([#18817](https://github.com/tailwindlabs/tailwindcss/pull/18817)) - -## [4.1.12] - 2025-08-13 - -### Fixed - -- Don't consider the global important state in `@apply` ([#18404](https://github.com/tailwindlabs/tailwindcss/pull/18404)) -- Add missing suggestions for `flex-` utilities ([#18642](https://github.com/tailwindlabs/tailwindcss/pull/18642)) -- Fix trailing `)` from interfering with extraction in Clojure keywords ([#18345](https://github.com/tailwindlabs/tailwindcss/pull/18345)) -- Detect classes inside Elixir charlist, word list, and string sigils ([#18432](https://github.com/tailwindlabs/tailwindcss/pull/18432)) -- Track source locations through `@plugin` and `@config` ([#18345](https://github.com/tailwindlabs/tailwindcss/pull/18345)) -- Allow boolean values of `process.env.DEBUG` in `@tailwindcss/node` ([#18485](https://github.com/tailwindlabs/tailwindcss/pull/18485)) -- Ignore consecutive semicolons in the CSS parser ([#18532](https://github.com/tailwindlabs/tailwindcss/pull/18532)) -- Center the dropdown icon added to an input with a paired datalist by default ([#18511](https://github.com/tailwindlabs/tailwindcss/pull/18511)) -- Extract candidates in Slang templates ([#18565](https://github.com/tailwindlabs/tailwindcss/pull/18565)) -- Improve error messages when encountering invalid functional utility names ([#18568](https://github.com/tailwindlabs/tailwindcss/pull/18568)) -- Discard CSS AST objects with `false` or `undefined` properties ([#18571](https://github.com/tailwindlabs/tailwindcss/pull/18571)) -- Allow users to disable URL rebasing in `@tailwindcss/postcss` via `transformAssetUrls: false` ([#18321](https://github.com/tailwindlabs/tailwindcss/pull/18321)) -- Fix false-positive migrations in `addEventListener` and JavaScript variable names ([#18718](https://github.com/tailwindlabs/tailwindcss/pull/18718)) -- Fix Standalone CLI showing default Bun help when run via symlink on Windows ([#18723](https://github.com/tailwindlabs/tailwindcss/pull/18723)) -- Read from `--border-color-*` theme keys in `divide-*` utilities for backwards compatibility ([#18704](https://github.com/tailwindlabs/tailwindcss/pull/18704/)) -- Don't scan `.hdr` and `.exr` files for classes by default ([#18734](https://github.com/tailwindlabs/tailwindcss/pull/18734)) - -## [4.1.11] - 2025-06-26 - -### Fixed - -- Add heuristic to skip candidate migrations inside `emit(…)` ([#18330](https://github.com/tailwindlabs/tailwindcss/pull/18330)) -- Extract candidates with variants in Clojure/ClojureScript keywords ([#18338](https://github.com/tailwindlabs/tailwindcss/pull/18338)) -- Document `--watch=always` in the CLI's usage ([#18337](https://github.com/tailwindlabs/tailwindcss/pull/18337)) -- Add support for Vite 7 to `@tailwindcss/vite` ([#18384](https://github.com/tailwindlabs/tailwindcss/pull/18384)) - -## [4.1.10] - 2025-06-11 - -### Fixed - -- Fix incorrectly generated CSS when using percentages in arbitrary values with calc (e.g. `w-[calc(100%-var(--offset))]`) ([#18289](https://github.com/tailwindlabs/tailwindcss/pull/18289)) - -## [4.1.9] - 2025-06-11 - -### Fixed - -- Correctly parse custom properties with strings containing semicolons ([#18251](https://github.com/tailwindlabs/tailwindcss/pull/18251)) -- Upgrade: Migrate arbitrary modifiers without percentage signs to bare values (e.g. `/[0.16]` → `/16`) ([#18184](https://github.com/tailwindlabs/tailwindcss/pull/18184)) -- Upgrade: Migrate CSS variable shorthands where fallback value contains function call ([#18184](https://github.com/tailwindlabs/tailwindcss/pull/18184)) -- Upgrade: Migrate negative arbitrary values to negative bare values (e.g. `mb-[-32rem]` → `-mb-128`) ([#18212](https://github.com/tailwindlabs/tailwindcss/pull/18212)) -- Upgrade: Do not migrate `blur` in `wire:model.blur` ([#18216](https://github.com/tailwindlabs/tailwindcss/pull/18216)) -- Don't add spaces around CSS dashed idents when formatting math expressions ([#18220](https://github.com/tailwindlabs/tailwindcss/pull/18220)) - -## [4.1.8] - 2025-05-27 - -### Added - -- Improve error messages when `@apply` fails ([#18059](https://github.com/tailwindlabs/tailwindcss/pull/18059)) - -### Fixed - -- Upgrade: Do not migrate declarations that look like candidates in ` - `, - 'src/input.css': css` - @import 'tailwindcss'; - - .foo { - flex-shrink: 1; - } - - .bar { - @apply !underline; - } - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect(await fs.dumpFiles('./src/**/*.{css,vue}')).toMatchInlineSnapshot(` - " - --- ./src/index.vue --- - - - - - --- ./src/input.css --- - @import 'tailwindcss'; - - .foo { - flex-shrink: 1; - } - - .bar { - @apply underline!; - } - " - `) - }, -) - function withBOM(text: string): string { return '\uFEFF' + text } diff --git a/integrations/upgrade/js-config.test.ts b/integrations/upgrade/js-config.test.ts index cab0d3d62..b667a3f09 100644 --- a/integrations/upgrade/js-config.test.ts +++ b/integrations/upgrade/js-config.test.ts @@ -1,6 +1,6 @@ import path from 'node:path' import { describe } from 'vitest' -import { css, html, json, test, ts, txt } from '../utils' +import { css, html, json, test, ts } from '../utils' test( `upgrade JS config files with flat theme values, darkMode, and content fields`, @@ -30,23 +30,13 @@ test( 400: '#f87171', 500: 'red', }, - superRed: '#ff0000', steel: 'rgb(70 130 180 / )', smoke: 'rgba(245, 245, 245, var(--smoke-alpha, ))', }, - ringColor: { - DEFAULT: '#c0ffee', - }, - opacity: { - superOpaque: '0.95', - }, fontSize: { xs: ['0.75rem', { lineHeight: '1rem' }], sm: ['0.875rem', { lineHeight: '1.5rem' }], base: ['1rem', { lineHeight: '2rem' }], - lg: ['1.125rem', '2.5rem'], - xl: ['1.5rem', '3rem', 'invalid'], - '2xl': ['2rem'], }, width: { px: '1px', @@ -151,10 +141,9 @@ test( } `, 'src/index.html': html` -
-
`, 'node_modules/my-external-lib/src/template.html': html`
@@ -164,15 +153,14 @@ test( }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.{css,js,html}')).toMatchInlineSnapshot(` " --- src/index.html ---
-
+ class="[letter-spacing:var(--tracking-super-wide)] [line-height:var(--leading-super-loose)]" + >
--- src/input.css --- @import 'tailwindcss'; @@ -190,15 +178,9 @@ test( --color-red-500: #ef4444; --color-red-600: #dc2626; - --color-superRed: #ff0000; --color-steel: rgb(70 130 180); --color-smoke: rgba(245, 245, 245, var(--smoke-alpha, 1)); - --default-ring-color: #c0ffee; - - --opacity-*: initial; - --opacity-superOpaque: 95%; - --text-*: initial; --text-xs: 0.75rem; --text-xs--line-height: 1rem; @@ -206,11 +188,6 @@ test( --text-sm--line-height: 1.5rem; --text-base: 1rem; --text-base--line-height: 2rem; - --text-lg: 1.125rem; - --text-lg--line-height: 2.5rem; - --text-xl: 1.5rem; - --text-xl--line-height: 3rem; - --text-2xl: 2rem; --width-*: initial; --width-0: 0%; @@ -270,9 +247,9 @@ test( --animate-spin-clockwise: spin-clockwise 1s linear infinite; --animate-spin-counterclockwise: spin-counterclockwise 1s linear infinite; - --tracking-superWide: 0.25em; + --tracking-super-wide: 0.25em; - --leading-superLoose: 3; + --leading-super-loose: 3; @keyframes spin-clockwise { 0% { @@ -293,7 +270,7 @@ test( } /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -306,7 +283,7 @@ test( ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } @@ -322,105 +299,6 @@ test( }, ) -test( - 'skips gitignored template files even when they are explicitly referenced in `content`', - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - } - } - `, - '.gitignore': txt` - node_modules - src/ignored - `, - 'tailwind.config.ts': ts` - import { type Config } from 'tailwindcss' - - module.exports = { - content: [ - './src/migrate-me.html', - './src/ignored/do-not-migrate-me.html', - './node_modules/my-external-lib/template.html', - ], - theme: {}, - plugins: [], - } satisfies Config - `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - 'src/migrate-me.html': html` -
- `, - 'src/ignored/do-not-migrate-me.html': html` -
- `, - 'node_modules/my-external-lib/template.html': html` -
- `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect( - await fs.dumpFiles( - '{src/**/*.{css,html},node_modules/my-external-lib/template.html,.gitignore}', - ), - ).toMatchInlineSnapshot(` - " - --- .gitignore --- - node_modules - src/ignored - - --- src/input.css --- - @import 'tailwindcss'; - - @source './ignored/do-not-migrate-me.html'; - @source '../node_modules/my-external-lib/template.html'; - - /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. - */ - @layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - } - - --- src/migrate-me.html --- -
- - --- node_modules/my-external-lib/template.html --- -
- - --- src/ignored/do-not-migrate-me.html --- -
- " - `) - }, -) - test( 'upgrades JS config files with plugins', { @@ -484,7 +362,7 @@ test( }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -526,7 +404,7 @@ test( } /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -539,7 +417,7 @@ test( ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } " @@ -586,7 +464,7 @@ test( }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.{css,ts}')).toMatchInlineSnapshot(` " @@ -594,21 +472,21 @@ test( @import 'tailwindcss'; @theme { - --color-gray-50: oklch(98.5% 0 none); - --color-gray-100: oklch(97% 0 none); - --color-gray-200: oklch(92.2% 0 none); - --color-gray-300: oklch(87% 0 none); - --color-gray-400: oklch(70.8% 0 none); - --color-gray-500: oklch(55.6% 0 none); - --color-gray-600: oklch(43.9% 0 none); - --color-gray-700: oklch(37.1% 0 none); - --color-gray-800: oklch(26.9% 0 none); - --color-gray-900: oklch(20.5% 0 none); - --color-gray-950: oklch(14.5% 0 none); + --color-gray-50: oklch(0.985 0 0); + --color-gray-100: oklch(0.97 0 0); + --color-gray-200: oklch(0.922 0 0); + --color-gray-300: oklch(0.87 0 0); + --color-gray-400: oklch(0.708 0 0); + --color-gray-500: oklch(0.556 0 0); + --color-gray-600: oklch(0.439 0 0); + --color-gray-700: oklch(0.371 0 0); + --color-gray-800: oklch(0.269 0 0); + --color-gray-900: oklch(0.205 0 0); + --color-gray-950: oklch(0.145 0 0); } /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -621,7 +499,7 @@ test( ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } " @@ -672,7 +550,7 @@ test( }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -682,7 +560,7 @@ test( @config '../tailwind.config.ts'; /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -695,7 +573,7 @@ test( ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } " @@ -750,7 +628,7 @@ test( }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -760,7 +638,7 @@ test( @config '../tailwind.config.ts'; /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -773,7 +651,7 @@ test( ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } " @@ -824,7 +702,7 @@ test( }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -834,7 +712,7 @@ test( @config '../tailwind.config.ts'; /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -847,7 +725,7 @@ test( ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } " @@ -934,7 +812,7 @@ test( }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('project-{a,b}/**/*.{css,ts}')).toMatchInlineSnapshot(` " @@ -946,7 +824,7 @@ test( } /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -959,7 +837,7 @@ test( ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } @@ -971,7 +849,7 @@ test( } /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -984,7 +862,7 @@ test( ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } " @@ -1032,7 +910,7 @@ test( }, }, async ({ root, exec, fs, expect }) => { - await exec('pnpm exec upgrade', { + await exec('npx @tailwindcss/upgrade', { cwd: path.join(root, 'frontend'), }) @@ -1048,7 +926,7 @@ test( } /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -1061,253 +939,7 @@ test( ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - } - " - `) - }, -) - -test( - 'migrate aria theme keys to custom variants', - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - } - } - `, - 'tailwind.config.ts': ts` - export default { - content: { - relative: true, - files: ['./src/**/*.html'], - }, - theme: { - extend: { - aria: { - // Built-in (not really, but visible because of intellisense) - busy: 'busy="true"', - - // Automatically handled by bare values - foo: 'foo="true"', - - // Quotes are optional in CSS for these kinds of attribute - // selectors - bar: 'bar=true', - - // Not automatically handled by bare values because names differ - baz: 'qux="true"', - - // Completely custom - asc: 'sort="ascending"', - desc: 'sort="descending"', - }, - }, - }, - } - `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect(await fs.dumpFiles('src/*.css')).toMatchInlineSnapshot(` - " - --- src/input.css --- - @import 'tailwindcss'; - - @custom-variant aria-baz (&[aria-qux="true"]); - @custom-variant aria-asc (&[aria-sort="ascending"]); - @custom-variant aria-desc (&[aria-sort="descending"]); - - /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. - */ - @layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - } - " - `) - }, -) - -test( - 'migrate data theme keys to custom variants', - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - } - } - `, - 'tailwind.config.ts': ts` - export default { - content: { - relative: true, - files: ['./src/**/*.html'], - }, - theme: { - extend: { - data: { - // Automatically handled by bare values - foo: 'foo', - - // Not automatically handled by bare values because names differ - bar: 'baz', - - // Custom - checked: 'ui~="checked"', - }, - }, - }, - } - `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect(await fs.dumpFiles('src/*.css')).toMatchInlineSnapshot(` - " - --- src/input.css --- - @import 'tailwindcss'; - - @custom-variant data-bar (&[data-baz]); - @custom-variant data-checked (&[data-ui~="checked"]); - - /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. - */ - @layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - } - " - `) - }, -) - -test( - 'migrate supports theme keys to custom variants', - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - } - } - `, - 'tailwind.config.ts': ts` - export default { - content: { - relative: true, - files: ['./src/**/*.html'], - }, - theme: { - extend: { - supports: { - // Automatically handled by bare values (using CSS variable as the value) - foo: 'foo: var(--foo)', // parentheses are optional - bar: '(bar: var(--bar))', - - // Not automatically handled by bare values because names differ - foo: 'bar: var(--foo)', // parentheses are optional - bar: '(qux: var(--bar))', - - // Custom - grid: 'display: grid', - }, - }, - }, - } - `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect(await fs.dumpFiles('src/*.css')).toMatchInlineSnapshot(` - " - --- src/input.css --- - @import 'tailwindcss'; - - @custom-variant supports-foo { - @supports (bar: var(--foo)) { - @slot; - } - } - @custom-variant supports-bar { - @supports ((qux: var(--bar))) { - @slot; - } - } - @custom-variant supports-grid { - @supports (display: grid) { - @slot; - } - } - - /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. - */ - @layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } " @@ -1346,7 +978,7 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -1354,7 +986,7 @@ describe('border compatibility', () => { @import 'tailwindcss'; /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -1367,7 +999,7 @@ describe('border compatibility', () => { ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } " @@ -1408,7 +1040,7 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -1416,7 +1048,7 @@ describe('border compatibility', () => { @import 'tailwindcss'; /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -1429,7 +1061,7 @@ describe('border compatibility', () => { ::before, ::backdrop, ::file-selector-button { - border-color: oklch(62.3% 0.214 259.815); + border-color: oklch(0.623 0.214 259.815); } } " @@ -1456,7 +1088,7 @@ describe('border compatibility', () => { theme: { extend: { borderColor: ({ colors }) => ({ - DEFAULT: 'currentcolor', + DEFAULT: 'currentColor', }), }, }, @@ -1470,7 +1102,7 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -1509,7 +1141,7 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -1520,7 +1152,7 @@ describe('border compatibility', () => { @import 'tailwindcss'; /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -1533,7 +1165,7 @@ describe('border compatibility', () => { ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } " @@ -1580,7 +1212,7 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -1589,7 +1221,7 @@ describe('border compatibility', () => { @import 'tailwindcss/preflight' layer(base); /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -1602,7 +1234,7 @@ describe('border compatibility', () => { ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } @@ -1683,18 +1315,18 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.{css,html}')).toMatchInlineSnapshot(` " --- src/index.html ---
--- src/input.css --- @@ -1707,7 +1339,7 @@ describe('border compatibility', () => { } /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -1720,7 +1352,7 @@ describe('border compatibility', () => { ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } @@ -1793,18 +1425,18 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.{css,html}')).toMatchInlineSnapshot(` " --- src/index.html ---
--- src/input.css --- @@ -1821,7 +1453,7 @@ describe('border compatibility', () => { } /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -1834,7 +1466,7 @@ describe('border compatibility', () => { ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } @@ -1895,7 +1527,7 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.{css,html}')).toMatchInlineSnapshot(` " @@ -1924,7 +1556,7 @@ describe('border compatibility', () => { } /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -1937,7 +1569,7 @@ describe('border compatibility', () => { ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } " @@ -1945,243 +1577,3 @@ describe('border compatibility', () => { }, ) }) - -test( - `future and experimental keys are supported`, - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - } - } - `, - 'tailwind.config.ts': ts` - import { type Config } from 'tailwindcss' - import defaultTheme from 'tailwindcss/defaultTheme' - - module.exports = { - darkMode: 'selector', - content: ['./src/**/*.{html,js}'], - future: { - hoverOnlyWhenSupported: true, - respectDefaultRingColorOpacity: true, - disableColorOpacityUtilitiesByDefault: true, - relativeContentPathsByDefault: true, - }, - experimental: { - generalizedModifiers: true, - }, - theme: { - colors: { - red: { - 400: '#f87171', - 500: 'red', - }, - }, - }, - plugins: [], - } satisfies Config - `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` - " - --- src/input.css --- - @import 'tailwindcss'; - - @custom-variant dark (&:where(.dark, .dark *)); - - @theme { - --color-*: initial; - --color-red-400: #f87171; - --color-red-500: red; - } - - /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. - */ - @layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - } - " - `) - - expect((await fs.dumpFiles('tailwind.config.ts')).trim()).toBe('') - }, -) - -test( - `unknown future keys dont migrate the config`, - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - } - } - `, - 'tailwind.config.ts': ts` - import { type Config } from 'tailwindcss' - import defaultTheme from 'tailwindcss/defaultTheme' - - module.exports = { - darkMode: 'selector', - content: ['./src/**/*.{html,js}'], - future: { - something: true, - }, - } satisfies Config - `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` - " - --- src/input.css --- - @import 'tailwindcss'; - - @config '../tailwind.config.ts'; - - /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. - */ - @layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - } - " - `) - - expect((await fs.dumpFiles('tailwind.config.ts')).trim()).toMatchInlineSnapshot(` - "--- tailwind.config.ts --- - import { type Config } from 'tailwindcss' - import defaultTheme from 'tailwindcss/defaultTheme' - - module.exports = { - darkMode: 'selector', - content: ['./src/**/*.{html,js}'], - future: { - something: true, - }, - } satisfies Config" - `) - }, -) - -test( - `unknown experimental keys dont migrate the config`, - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - } - } - `, - 'tailwind.config.ts': ts` - import { type Config } from 'tailwindcss' - import defaultTheme from 'tailwindcss/defaultTheme' - - module.exports = { - darkMode: 'selector', - content: ['./src/**/*.{html,js}'], - experimental: { - something: true, - }, - } satisfies Config - `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` - " - --- src/input.css --- - @import 'tailwindcss'; - - @config '../tailwind.config.ts'; - - /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. - */ - @layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - } - " - `) - - expect((await fs.dumpFiles('tailwind.config.ts')).trim()).toMatchInlineSnapshot(` - "--- tailwind.config.ts --- - import { type Config } from 'tailwindcss' - import defaultTheme from 'tailwindcss/defaultTheme' - - module.exports = { - darkMode: 'selector', - content: ['./src/**/*.{html,js}'], - experimental: { - something: true, - }, - } satisfies Config" - `) - }, -) diff --git a/integrations/upgrade/upgrade-errors.test.ts b/integrations/upgrade/upgrade-errors.test.ts deleted file mode 100644 index fdc60828e..000000000 --- a/integrations/upgrade/upgrade-errors.test.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { stripVTControlCharacters } from 'node:util' -import { css, html, js, json, test } from '../utils' - -test( - 'upgrades half-upgraded v3 project to v4 (pnpm)', - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - }, - "devDependencies": { - "@tailwindcss/cli": "workspace:^" - } - } - `, - 'tailwind.config.js': js` - /** @type {import('tailwindcss').Config} */ - module.exports = { - content: ['./src/**/*.{html,js}'], - } - `, - 'src/index.html': html` -
Hello World
- `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - retry: 0, - }, - async ({ exec, expect }) => { - // Ensure we are in a git repo - await exec('git init') - await exec('git add --all') - await exec('git commit -m "before migration"') - - // Fully upgrade to v4 - await exec('pnpm exec upgrade') - - // Undo all changes to the current repo. This will bring the repo back to a - // v3 state, but the `node_modules` will now have v4 installed. - await exec('git reset --hard HEAD') - - // Re-running the upgrade should result in an error - return expect(() => { - return exec('pnpm exec upgrade', {}, { ignoreStdErr: true }).catch((e) => { - // Replacing the current version with a hardcoded `v4` to make it stable - // when we release new minor/patch versions. - return Promise.reject( - stripVTControlCharacters(e.message.replace(/\d+\.\d+\.\d+/g, '4.0.0')), - ) - }) - }).rejects.toThrowErrorMatchingInlineSnapshot(` - "Command failed: pnpm exec upgrade - ≈ tailwindcss v4.0.0 - - │ ↳ Upgrading from Tailwind CSS \`v4.0.0\` - - │ ↳ Version mismatch - │ - │ \`\`\`diff - │ - "tailwindcss": "^3" (expected version in \`package.json\`) - │ + "tailwindcss": "4.0.0" (installed version in \`node_modules\`) - │ \`\`\` - │ - │ Make sure to run \`pnpm install\` and try again. - - " - `) - }, -) - -test( - 'upgrades half-upgraded v3 project to v4 (bun)', - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - }, - "devDependencies": { - "@tailwindcss/cli": "workspace:^", - "bun": "^1.0.0" - } - } - `, - 'tailwind.config.js': js` - /** @type {import('tailwindcss').Config} */ - module.exports = { - content: ['./src/**/*.{html,js}'], - } - `, - 'src/index.html': html` -
Hello World
- `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - retry: 0, - }, - async ({ exec, expect }) => { - // Use `bun` to install dependencies - await exec('rm ./pnpm-lock.yaml') - try { - await exec('npx bun install', {}, { ignoreStdErr: true }) - } catch (e) { - // When preparing for a release, the version in `package.json` will point - // to a non-existent version because it's not published yet. - // TODO: Find a better approach to handle this and actually test it even - // on release branches. Note: the pnpm version _does_ work because of - // overrides in the package.json file. - if (`${e}`.includes('No version matching')) return - throw e - } - - // Ensure we are in a git repo - await exec('git init') - await exec('git add --all') - await exec('git commit -m "before migration"') - - // Fully upgrade to v4 - await exec('npx @tailwindcss/upgrade') - - // Undo all changes to the current repo. This will bring the repo back to a - // v3 state, but the `node_modules` will now have v4 installed. - await exec('git reset --hard HEAD') - - // Re-running the upgrade should result in an error - return expect(() => { - return exec('npx @tailwindcss/upgrade', {}, { ignoreStdErr: true }).catch((e) => { - // Replacing the current version with a hardcoded `v4` to make it stable - // when we release new minor/patch versions. - return Promise.reject( - stripVTControlCharacters(e.message.replace(/\d+\.\d+\.\d+/g, '4.0.0')), - ) - }) - }).rejects.toThrowErrorMatchingInlineSnapshot(` - "Command failed: npx @tailwindcss/upgrade - ≈ tailwindcss v4.0.0 - - │ ↳ Upgrading from Tailwind CSS \`v4.0.0\` - - │ ↳ Version mismatch - │ - │ \`\`\`diff - │ - "tailwindcss": "^3" (expected version in \`package.json\`) - │ + "tailwindcss": "4.0.0" (installed version in \`node_modules\`) - │ \`\`\` - │ - │ Make sure to run \`bun install\` and try again. - - " - `) - }, -) - -test( - 'upgrades half-upgraded v3 project to v4 (npm)', - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - }, - "devDependencies": { - "@tailwindcss/cli": "workspace:^" - } - } - `, - 'tailwind.config.js': js` - /** @type {import('tailwindcss').Config} */ - module.exports = { - content: ['./src/**/*.{html,js}'], - } - `, - 'src/index.html': html` -
Hello World
- `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - retry: 0, - }, - async ({ exec, expect }) => { - // Use `npm` to install dependencies - await exec('rm ./pnpm-lock.yaml') - await exec('rm -rf ./node_modules') - try { - await exec('npm install', {}, { ignoreStdErr: true }) - } catch (e) { - // When preparing for a release, the version in `package.json` will point - // to a non-existent version because it's not published yet. - // TODO: Find a better approach to handle this and actually test it even - // on release branches. Note: the pnpm version _does_ work because of - // overrides in the package.json file. - if (`${e}`.includes('npm error code ETARGET')) return - throw e - } - - // Ensure we are in a git repo - await exec('git init') - await exec('git add --all') - await exec('git commit -m "before migration"') - - // Fully upgrade to v4 - await exec('npx @tailwindcss/upgrade') - - // Undo all changes to the current repo. This will bring the repo back to a - // v3 state, but the `node_modules` will now have v4 installed. - await exec('git reset --hard HEAD') - - // Re-running the upgrade should result in an error - return expect(() => { - return exec('npx @tailwindcss/upgrade', {}, { ignoreStdErr: true }).catch((e) => { - // Replacing the current version with a hardcoded `v4` to make it stable - // when we release new minor/patch versions. - return Promise.reject( - stripVTControlCharacters(e.message.replace(/\d+\.\d+\.\d+/g, '4.0.0')), - ) - }) - }).rejects.toThrowErrorMatchingInlineSnapshot(` - "Command failed: npx @tailwindcss/upgrade - ≈ tailwindcss v4.0.0 - - │ ↳ Upgrading from Tailwind CSS \`v4.0.0\` - - │ ↳ Version mismatch - │ - │ \`\`\`diff - │ - "tailwindcss": "^3" (expected version in \`package.json\`) - │ + "tailwindcss": "4.0.0" (installed version in \`node_modules\`) - │ \`\`\` - │ - │ Make sure to run \`npm install\` and try again. - - " - `) - }, -) diff --git a/integrations/utils.ts b/integrations/utils.ts index 9863918d1..4558edd03 100644 --- a/integrations/utils.ts +++ b/integrations/utils.ts @@ -1,28 +1,20 @@ import dedent from 'dedent' import fastGlob from 'fast-glob' -import { exec, execFile, spawn, type ChildProcess } from 'node:child_process' +import { exec, spawn } from 'node:child_process' import fs from 'node:fs/promises' -import { createServer } from 'node:net' import { platform, tmpdir } from 'node:os' import path from 'node:path' -import { promisify, stripVTControlCharacters } from 'node:util' -import { RawSourceMap, SourceMapConsumer } from 'source-map-js' +import { stripVTControlCharacters } from 'node:util' import { test as defaultTest, type ExpectStatic } from 'vitest' -import * as Yaml from 'yaml' -import { createLineTable } from '../packages/tailwindcss/src/source-maps/line-table' import { escape } from '../packages/tailwindcss/src/utils/escape' const REPO_ROOT = path.join(__dirname, '..') -const ROOT_PNPM_WORKSPACE = Yaml.parse( - await fs.readFile(path.join(REPO_ROOT, 'pnpm-workspace.yaml'), 'utf8'), -) const PUBLIC_PACKAGES = (await fs.readdir(path.join(REPO_ROOT, 'dist'))).map((name) => name.replace('tailwindcss-', '@tailwindcss/').replace('.tgz', ''), ) interface SpawnedProcess { - dispose: () => Promise - flush: () => void + dispose: () => void onStdout: (predicate: (message: string) => boolean) => Promise onStderr: (predicate: (message: string) => boolean) => Promise } @@ -42,29 +34,24 @@ interface TestConfig { [filePath: string]: string | Uint8Array } - timeout?: number installDependencies?: boolean - retry?: number } interface TestContext { root: string expect: ExpectStatic exec(command: string, options?: ChildProcessOptions, execOptions?: ExecOptions): Promise spawn(command: string, options?: ChildProcessOptions): Promise - parseSourceMap(opts: string | SourceMapOptions): SourceMap fs: { write(filePath: string, content: string, encoding?: BufferEncoding): Promise - symlink(dst: string, src: string): Promise create(filePaths: string[]): Promise read(filePath: string): Promise - delete(filePath: string): Promise glob(pattern: string): Promise<[string, string][]> dumpFiles(pattern: string): Promise expectFileToContain( filePath: string, contents: string | RegExp | (string | RegExp)[], ): Promise - expectFileNotToContain(filePath: string, contents: string[]): Promise + expectFileNotToContain(filePath: string, contents: string | string[]): Promise } } type TestCallback = (context: TestContext) => Promise | void @@ -72,14 +59,11 @@ interface TestFlags { only?: boolean skip?: boolean debug?: boolean - concurrent?: boolean } type SpawnActor = { predicate: (message: string) => boolean; resolve: () => void } -export const IS_WINDOWS = platform() === 'win32' - -const execFileAsync = promisify(execFile) +const IS_WINDOWS = platform() === 'win32' const TEST_TIMEOUT = IS_WINDOWS ? 120000 : 60000 const ASSERTION_TIMEOUT = IS_WINDOWS ? 10000 : 5000 @@ -93,16 +77,16 @@ export function test( name: string, config: TestConfig, testCallback: TestCallback, - { only = false, skip = false, debug = false, concurrent = false }: TestFlags = {}, + { only = false, skip = false, debug = false }: TestFlags = {}, ) { return defaultTest( name, { - timeout: config.timeout ?? TEST_TIMEOUT, - retry: config.retry ?? (process.env.CI ? 2 : 0), + timeout: TEST_TIMEOUT, + retry: process.env.CI ? 2 : 0, only: only || (!process.env.CI && debug), skip, - concurrent, + concurrent: true, }, async (options) => { let rootDir = debug ? path.join(REPO_ROOT, '.debug') : TMP_ROOT @@ -119,46 +103,12 @@ export function test( let context = { root, expect: options.expect, - parseSourceMap, async exec( command: string, childProcessOptions: ChildProcessOptions = {}, execOptions: ExecOptions = {}, ) { let cwd = childProcessOptions.cwd ?? root - let originalCommand = command - - // Avoid `pnpm exec upgrade` on Windows because the upgrader runs - // `pnpm add`, which rewrites Windows `.cmd` shims while the current - // shim is still executing. - // - // Pretty sure this is a pnpm v11 bug on Windows. - if (IS_WINDOWS && command.includes('pnpm exec upgrade')) { - for (let base = cwd; ; base = path.dirname(base)) { - let upgradeBin = path.join( - base, - 'node_modules', - '@tailwindcss', - 'upgrade', - 'dist', - 'index.mjs', - ) - - try { - await fs.access(upgradeBin) - command = command.replace('pnpm exec upgrade', `node "${upgradeBin}"`) - break - } catch (error: any) { - if (error?.code !== 'ENOENT') throw error - } - - let parent = path.dirname(base) - if (parent === base) { - throw new Error(`Unable to resolve @tailwindcss/upgrade from ${cwd}`) - } - } - } - if (debug && cwd !== root) { let relative = path.relative(root, cwd) if (relative[0] !== '.') relative = `./${relative}` @@ -171,18 +121,10 @@ export function test( { cwd, ...childProcessOptions, - env: { - ...process.env, - ...childProcessOptions.env, - }, + env: childProcessOptions.env, }, (error, stdout, stderr) => { if (error) { - if (command !== originalCommand) { - error.message = error.message.replace(command, originalCommand) - let mappedError = error as any - mappedError.cmd = originalCommand - } if (execOptions.ignoreStdErr !== true) console.error(stderr) if (only || debug) { console.error(stdout) @@ -219,7 +161,6 @@ export function test( if (debug) console.log(`>& ${command}`) let child = spawn(command, { cwd, - detached: !IS_WINDOWS, shell: true, ...childProcessOptions, env: { @@ -228,26 +169,22 @@ export function test( }, }) - let disposed = false + function dispose() { + if (!child.kill()) { + child.kill('SIGKILL') + } - async function dispose() { - if (disposed) return disposePromise - disposed = true - - await killProcessTree(child) - - let timer = setTimeout(() => { - forceKillProcessTree(child) - rejectDisposal?.(new Error(`spawned process (${command}) did not exit in time`)) - }, ASSERTION_TIMEOUT) - disposePromise.then( - () => clearTimeout(timer), - () => clearTimeout(timer), + let timer = setTimeout( + () => + rejectDisposal?.(new Error(`spawned process (${command}) did not exit in time`)), + ASSERTION_TIMEOUT, ) + disposePromise.finally(() => { + clearTimeout(timer) + }) return disposePromise } disposables.push(dispose) - function onExit() { resolveDisposal?.() } @@ -316,13 +253,6 @@ export function test( return { dispose, - flush() { - stdoutActors.splice(0) - stderrActors.splice(0) - - stdoutMessages.splice(0) - stderrMessages.splice(0) - }, onStdout(predicate: (message: string) => boolean) { return new Promise((resolve) => { stdoutActors.push({ predicate, resolve }) @@ -338,7 +268,11 @@ export function test( } }, fs: { - async write(filename, content, encoding = 'utf8') { + async write( + filename: string, + content: string | Uint8Array, + encoding: BufferEncoding = 'utf8', + ): Promise { let full = path.join(root, filename) let dir = path.dirname(full) await fs.mkdir(dir, { recursive: true }) @@ -349,8 +283,6 @@ export function test( if (filename.endsWith('package.json')) { content = await overwriteVersionsInPackageJson(content) - } else if (filename.endsWith('pnpm-workspace.yaml')) { - content = overwriteVersionsInPnpmWorkspace(content) } // Ensure that files written on Windows use \r\n line ending @@ -361,19 +293,7 @@ export function test( await fs.writeFile(full, content, encoding) }, - async symlink(target, src) { - let targetAbsolute = path.join(root, target) - let targetParent = path.dirname(targetAbsolute) - await fs.mkdir(targetParent, { recursive: true }) - - let srcAbsolute = path.join(root, src) - let srcParent = path.dirname(srcAbsolute) - await fs.mkdir(srcParent, { recursive: true }) - - await fs.symlink(targetAbsolute, srcAbsolute) - }, - - async create(filenames) { + async create(filenames: string[]): Promise { for (let filename of filenames) { let full = path.join(root, filename) @@ -383,11 +303,7 @@ export function test( } }, - async delete(filename) { - await fs.unlink(path.join(root, filename)) - }, - - async read(filePath) { + async read(filePath: string) { let content = await fs.readFile(path.resolve(root, filePath), 'utf8') // Ensure that files read on Windows have \r\n line endings removed @@ -397,7 +313,7 @@ export function test( return content }, - async glob(pattern) { + async glob(pattern: string) { let files = await fastGlob(pattern, { cwd: root }) return Promise.all( files.map(async (file) => { @@ -405,12 +321,12 @@ export function test( return [ file, // Drop license comment - content.replace(/[\s\n]*\/\*![\s\S]*?\*\/[\s\n]*/g, ''), + content.replace(/[\s\n]*\/\*! tailwindcss .*? \*\/[\s\n]*/g, ''), ] }), ) }, - async dumpFiles(pattern) { + async dumpFiles(pattern: string) { let files = await context.fs.glob(pattern) return `\n${files .slice() @@ -469,44 +385,21 @@ export function test( node_modules/ ` - // Disable Git's automatic line ending conversion in these throwaway test - // repositories. Tools like pnpm write files (e.g. `pnpm-lock.yaml`) with - // LF endings, which on Windows (where `core.autocrlf=true` is common) - // makes Git emit noisy "LF will be replaced by CRLF" warnings when it - // touches those files. - config.fs['.gitattributes'] ??= txt` - * -text - ` - - // Ensure there is always a root `pnpm-workspace.yaml` so that transitive - // dependency overrides (and the build allow list) are injected into it. - // As of pnpm v10, these can no longer live in the `pnpm.overrides` field - // of `package.json`. - config.fs['pnpm-workspace.yaml'] ??= '' - for (let [filename, content] of Object.entries(config.fs)) { - if (content.toString().startsWith('symlink:')) { - // The symlink path is relative to the target destination's path - let target = path.join( - filename, - content.toString().slice('symlink:'.length), // Relative path - ) - - await context.fs.symlink(target, filename) - } else { - await context.fs.write(filename, content) - } + await context.fs.write(filename, content) } let shouldInstallDependencies = config.installDependencies ?? true try { - // Every test project gets its own root `pnpm-workspace.yaml` (see - // above). This makes the test directory its own workspace root, so even - // in debug mode — where the directory lives inside this repository's - // pnpm workspace — `pnpm install` won't attach to the parent workspace. + // In debug mode, the directory is going to be inside the pnpm workspace + // of the tailwindcss package. This means that `pnpm install` will run + // pnpm install on the workspace instead (expect if the root dir defines + // a separate workspace). We work around this by using the + // `--ignore-workspace` flag. if (shouldInstallDependencies) { - await context.exec(`pnpm install`) + let ignoreWorkspace = debug && !config.fs['pnpm-workspace.yaml'] + await context.exec(`pnpm install${ignoreWorkspace ? ' --ignore-workspace' : ''}`) } } catch (error: any) { console.error(error) @@ -518,18 +411,11 @@ export function test( let disposables: (() => Promise)[] = [] async function dispose() { - let results = await Promise.allSettled(disposables.map((dispose) => dispose())) + await Promise.all(disposables.map((dispose) => dispose())) if (!debug) { await gracefullyRemove(root) } - - let errors = results.flatMap((result) => - result.status === 'rejected' ? [result.reason] : [], - ) - if (errors.length > 0) { - throw new AggregateError(errors, 'Failed to clean up spawned processes') - } } options.onTestFinished(dispose) @@ -539,7 +425,7 @@ export function test( try { await context.exec('git init', { cwd: root }) await context.exec('git add --all', { cwd: root }) - await context.exec('git commit -m "before migration" --allow-empty', { cwd: root }) + await context.exec('git commit -m "before migration"', { cwd: root }) } catch (error: any) { console.error(error) console.error(error.stdout?.toString()) @@ -558,9 +444,6 @@ test.only = (name: string, config: TestConfig, testCallback: TestCallback) => { test.skip = (name: string, config: TestConfig, testCallback: TestCallback) => { return test(name, config, testCallback, { skip: true }) } -test.concurrent = (name: string, config: TestConfig, testCallback: TestCallback) => { - return test(name, config, testCallback, { concurrent: true }) -} test.debug = (name: string, config: TestConfig, testCallback: TestCallback) => { return test(name, config, testCallback, { debug: true }) } @@ -584,43 +467,27 @@ async function overwriteVersionsInPackageJson(content: string): Promise } } - return JSON.stringify(json, null, 2) -} - -function overwriteVersionsInPnpmWorkspace(content: string): string { - let workspace = content.trim() === '' ? {} : Yaml.parse(content) - - // Inherit information from the root workspace - workspace.allowBuilds = { - ...ROOT_PNPM_WORKSPACE.allowBuilds, - ...workspace.allowBuilds, - } - // Inject transitive dependency overwrite. This is necessary because // @tailwindcss/vite internally depends on a specific version of // @tailwindcss/oxide and we instead want to resolve it to the locally built // version. - // - // As of pnpm v10, the `pnpm.overrides` field in `package.json` is no longer - // read. Overrides have to be defined in `pnpm-workspace.yaml` instead. - workspace.overrides ||= {} + json.pnpm ||= {} + json.pnpm.overrides ||= {} for (let pkg of PUBLIC_PACKAGES) { if (pkg === 'tailwindcss') { // We want to be explicit about the `tailwindcss` package so our tests can // also import v3 without conflicting v4 tarballs. - workspace.overrides['@tailwindcss/node>tailwindcss'] = resolveVersion(pkg) - workspace.overrides['@tailwindcss/upgrade>tailwindcss'] = resolveVersion(pkg) - workspace.overrides['@tailwindcss/cli>tailwindcss'] = resolveVersion(pkg) - workspace.overrides['@tailwindcss/postcss>tailwindcss'] = resolveVersion(pkg) - workspace.overrides['@tailwindcss/turbopack>tailwindcss'] = resolveVersion(pkg) - workspace.overrides['@tailwindcss/vite>tailwindcss'] = resolveVersion(pkg) - workspace.overrides['@tailwindcss/webpack>tailwindcss'] = resolveVersion(pkg) + json.pnpm.overrides['@tailwindcss/node>tailwindcss'] = resolveVersion(pkg) + json.pnpm.overrides['@tailwindcss/upgrade>tailwindcss'] = resolveVersion(pkg) + json.pnpm.overrides['@tailwindcss/cli>tailwindcss'] = resolveVersion(pkg) + json.pnpm.overrides['@tailwindcss/postcss>tailwindcss'] = resolveVersion(pkg) + json.pnpm.overrides['@tailwindcss/vite>tailwindcss'] = resolveVersion(pkg) } else { - workspace.overrides[pkg] = resolveVersion(pkg) + json.pnpm.overrides[pkg] = resolveVersion(pkg) } } - return Yaml.stringify(workspace) + return JSON.stringify(json, null, 2) } function resolveVersion(dependency: string) { @@ -637,7 +504,6 @@ export let css = dedent export let html = dedent export let ts = dedent export let js = dedent -export let jsx = dedent export let json = dedent export let yaml = dedent export let txt = dedent @@ -670,7 +536,6 @@ export async function retryAssertion( try { return await fn() } catch (err) { - Error.captureStackTrace(err, retryAssertion) error = err await new Promise((resolve) => setTimeout(resolve, delay)) } @@ -686,7 +551,7 @@ export async function fetchStyles(base: string, path = '/'): Promise { let index = await fetch(`${base}${path}`) let html = await index.text() - let linkRegex = /]*>([\s\S]*?)<\/style>/gi let stylesheets: string[] = [] @@ -717,155 +582,13 @@ export async function fetchStyles(base: string, path = '/'): Promise { } return stylesheets.reduce((acc, css) => { - if (acc.length > 0) acc += '\n' - acc += css - return acc + return acc + '\n' + css }, '') } -export async function getRandomPort() { - return new Promise((resolve, reject) => { - let server = createServer() - server.unref() - server.on('error', reject) - server.listen(0, '127.0.0.1', () => { - let address = server.address() - server.close(() => { - if (address && typeof address === 'object') { - resolve(address.port) - } else { - reject(new Error('Unable to allocate random port')) - } - }) - }) - }) -} - -async function killProcessTree(child: ChildProcess) { - if (child.exitCode !== null || child.signalCode !== null || child.pid === undefined) { - return - } - - if (IS_WINDOWS) { - await execFileAsync('taskkill', ['/pid', String(child.pid), '/T', '/F'], { - timeout: ASSERTION_TIMEOUT, - windowsHide: true, - }).catch(() => {}) - return - } - - try { - process.kill(-child.pid, 'SIGTERM') - } catch (error: any) { - if (error?.code !== 'ESRCH') { - child.kill() - } - } -} - -function forceKillProcessTree(child: ChildProcess) { - if (child.exitCode !== null || child.signalCode !== null || child.pid === undefined) { - return - } - - if (IS_WINDOWS) { - execFile('taskkill', ['/pid', String(child.pid), '/T', '/F'], { windowsHide: true }, () => {}) - return - } - - try { - process.kill(-child.pid, 'SIGKILL') - } catch (error: any) { - if (error?.code !== 'ESRCH') { - child.kill('SIGKILL') - } - } -} - async function gracefullyRemove(dir: string) { // Skip removing the directory in CI because it can stall on Windows if (!process.env.CI) { - await fs.rm(dir, { recursive: true, force: true }).catch((error) => { - console.log(`Failed to remove ${dir}`, error) - }) - } -} - -const SOURCE_MAP_COMMENT = /^\/\*# sourceMappingURL=data:application\/json;base64,(.*) \*\/$/ - -export interface SourceMap { - at( - line: number, - column: number, - ): { - source: string | null - original: string - generated: string - } -} - -interface SourceMapOptions { - /** - * A raw source map - * - * This may be a string or an object. Strings will be decoded. - */ - map: string | object - - /** - * The content of the generated file the source map is for - */ - content: string - - /** - * The encoding of the source map - * - * Can be used to decode a base64 map (e.g. an inline source map URI) - */ - encoding?: BufferEncoding -} - -function parseSourceMap(opts: string | SourceMapOptions): SourceMap { - if (typeof opts === 'string') { - let lines = opts.trimEnd().split('\n') - let comment = lines.at(-1) ?? '' - let map = String(comment).match(SOURCE_MAP_COMMENT)?.[1] ?? null - if (!map) throw new Error('No source map comment found') - - return parseSourceMap({ - map, - content: lines.slice(0, -1).join('\n'), - encoding: 'base64', - }) - } - - let rawMap: RawSourceMap - let content = opts.content - - if (typeof opts.map === 'object') { - rawMap = opts.map as RawSourceMap - } else { - rawMap = JSON.parse(Buffer.from(opts.map, opts.encoding ?? 'utf-8').toString()) - } - - let map = new SourceMapConsumer(rawMap) - let generatedTable = createLineTable(content) - - return { - at(line: number, column: number) { - let pos = map.originalPositionFor({ line, column }) - let source = pos.source ? map.sourceContentFor(pos.source) : null - let originalTable = createLineTable(source ?? '') - let originalOffset = originalTable.findOffset(pos) - let generatedOffset = generatedTable.findOffset({ line, column }) - - return { - source: pos.source, - original: source - ? source.slice(originalOffset, originalOffset + 10).trim() + '...' - : '(none)', - generated: content.slice(generatedOffset, generatedOffset + 10).trim() + '...', - } - }, + await fs.rm(dir, { recursive: true, force: true }) } } diff --git a/integrations/vite/astro.test.ts b/integrations/vite/astro.test.ts index f828b5071..43406624c 100644 --- a/integrations/vite/astro.test.ts +++ b/integrations/vite/astro.test.ts @@ -1,4 +1,4 @@ -import { candidate, css, fetchStyles, html, js, json, retryAssertion, test, ts } from '../utils' +import { candidate, fetchStyles, html, js, json, retryAssertion, test, ts } from '../utils' test( 'dev mode', @@ -129,73 +129,3 @@ test( await fs.expectFileToContain(files[0][0], [candidate`underline`, candidate`overline`]) }, ) - -// https://github.com/tailwindlabs/tailwindcss/issues/19677 -test( - 'import aliases should work in - - -

Astro

- - - `, - 'src/styles/global.css': css`@import 'tailwindcss';`, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm astro build') - - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - - await fs.expectFileToContain(files[0][0], [candidate`underline`]) - }, -) diff --git a/integrations/vite/bundled-dev.test.ts b/integrations/vite/bundled-dev.test.ts deleted file mode 100644 index 8ba1b6b73..000000000 --- a/integrations/vite/bundled-dev.test.ts +++ /dev/null @@ -1,193 +0,0 @@ -import path from 'node:path' -import { candidate, css, html, json, retryAssertion, test, ts, txt, yaml } from '../utils' - -// Vite's experimental `bundledDev` mode invokes the `hotUpdate` hook without a -// `server`, which used to crash the plugin on every file edit. -// -// - https://github.com/tailwindlabs/tailwindcss/issues/20378 -// - https://vite.dev/blog/announcing-vite8-1#experimental-bundled-dev-mode -test( - 'dev mode (experimental `bundledDev`)', - { - fs: { - 'package.json': json`{}`, - 'pnpm-workspace.yaml': yaml` - # - packages: - - project-a - `, - 'project-a/package.json': txt` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8.1" - } - } - `, - 'project-a/vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - experimental: { - bundledDev: true, - }, - plugins: [tailwindcss()], - }) - `, - 'project-a/index.html': html` - - - - -
Hello, world!
- - `, - 'project-a/src/index.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - @source '../../project-b/src/**/*.html'; - `, - 'project-b/src/index.html': html` -
- `, - }, - }, - async ({ root, spawn, fs, expect }) => { - let process = await spawn('pnpm vite dev', { - cwd: path.join(root, 'project-a'), - env: { - // Some CI machines have an upstream rolldown bug where its file watcher - // never delivers a single event. The debug output is the only way to - // tell that apart from a real regression (see below). - DEBUG: 'vite:full-bundle-mode', - }, - }) - - // Any debug output beyond the "INITIAL:" startup lines proves rolldown's - // watcher delivered at least one file event. - let watcherAlive = false - - // `hotUpdate` errors don't kill the dev server, they are only printed to - // stderr. Track them explicitly so a crash fails the test even if the - // rebuild happens to succeed anyway. - let pluginErrors: string[] = [] - process.onStderr((message) => { - if (message.includes('vite:full-bundle-mode')) { - if (!message.includes('INITIAL:')) watcherAlive = true - } else if (message.includes('@tailwindcss/vite')) { - pluginErrors.push(message) - } - return false - }) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - // In `bundledDev` mode the stylesheet is not served separately. Instead the - // generated CSS is embedded in the bundled JS and injected at runtime, so - // extract the bundle from the served HTML. While the bundle is being built, - // Vite serves a temporary fallback page instead. - // - // The bundle can be split into multiple chunks (e.g. the HMR client runtime - // and the app itself), and the chunk containing the CSS is not always the - // first one, so fetch every referenced script and stylesheet. - async function fetchBundledStyles(): Promise { - let index = await fetch(`${url}/`) - let html = await index.text() - if (html.includes('__vite_is_fallback_page__')) { - throw new Error('Bundling still in progress') - } - - let sources = [ - ...html.matchAll(/]*\ssrc="([^"]+)"/g), - ...html.matchAll(/]*\srel="stylesheet"[^>]*\shref="([^"]+)"/g), - ].map((match) => match[1]) - if (sources.length === 0) throw new Error(`No scripts or stylesheets found in:\n\n${html}`) - - let contents = await Promise.all( - sources.map(async (src) => { - let response = await fetch(new URL(src, `${url}/`)) - return await response.text() - }), - ) - return contents.join('\n') - } - - await retryAssertion( - async () => { - let styles = await fetchBundledStyles() - expect(styles).toContain(candidate`underline`) - expect(styles).toContain(candidate`flex`) - }, - { timeout: 10_000, delay: 100 }, - ) - - // Edit a module file and a manually `@source`d file. Without a connected - // HMR websocket client (this test only uses HTTP fetches), a file change - // does not trigger an eager rebuild — it only marks the bundle as stale, - // and the next fetch kicks off the rebuild. So poll with fetches without - // touching the files again: rewriting would re-mark the bundle stale right - // before each fetch and the rebuild could never be observed. - await fs.write( - 'project-a/index.html', - html` - - - - -
Hello, world!
- - `, - ) - - await fs.write('project-b/src/index.html', html` -
- `) - - try { - await retryAssertion( - async () => { - let styles = await fetchBundledStyles() - expect(styles).toContain(candidate`underline`) - expect(styles).toContain(candidate`flex`) - expect(styles).toContain(candidate`m-2`) - expect(styles).toContain(candidate`font-bold`) - }, - { timeout: 15_000, delay: 100 }, - ) - } catch (error) { - // If rolldown's watcher delivered any file event, the updates were - // genuinely dropped somewhere along the way — a real failure. - if (watcherAlive) throw error - - // Otherwise the watcher never delivered a single event, so the updates - // can never be observed no matter how long we wait — an upstream - // rolldown bug on some CI machines, not a plugin regression. The crash - // regression this test guards is still covered: `hotUpdate` is driven - // by Vite's own (working) chokidar watcher and asserted via - // `pluginErrors` below. - // - // Skipping is only sound while the server still healthily serves the - // original bundle. If it stopped serving (crashed, or wedged on the - // fallback page), that's real breakage a silent watcher must not mask. - console.log(error) - let styles = await fetchBundledStyles() - expect(styles).toContain(candidate`underline`) - expect(styles).toContain(candidate`flex`) - console.warn( - 'Skipping update assertions: rolldown’s file watcher delivered no events on this machine.', - ) - } - - expect(pluginErrors).toEqual([]) - }, -) diff --git a/integrations/vite/config.test.ts b/integrations/vite/config.test.ts index 0ac68f5e9..3bf560e07 100644 --- a/integrations/vite/config.test.ts +++ b/integrations/vite/config.test.ts @@ -12,7 +12,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, @@ -76,7 +76,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, @@ -140,7 +140,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, @@ -219,7 +219,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, diff --git a/integrations/vite/css-modules.test.ts b/integrations/vite/css-modules.test.ts index a680ab9b7..668281cb4 100644 --- a/integrations/vite/css-modules.test.ts +++ b/integrations/vite/css-modules.test.ts @@ -14,8 +14,8 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, diff --git a/integrations/vite/html-style-blocks.test.ts b/integrations/vite/html-style-blocks.test.ts index 22bb91d78..4a86f96dc 100644 --- a/integrations/vite/html-style-blocks.test.ts +++ b/integrations/vite/html-style-blocks.test.ts @@ -12,7 +12,7 @@ test( }, "devDependencies": { "@tailwindcss/vite": "workspace:^", - "vite": "^7" + "vite": "^6" } } `, diff --git a/integrations/vite/ignored-packages.test.ts b/integrations/vite/ignored-packages.test.ts index 1efa9dc61..f7f85ff14 100644 --- a/integrations/vite/ignored-packages.test.ts +++ b/integrations/vite/ignored-packages.test.ts @@ -11,7 +11,7 @@ const WORKSPACE = { "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, diff --git a/integrations/vite/index.test.ts b/integrations/vite/index.test.ts index c346f8e37..4c64f220e 100644 --- a/integrations/vite/index.test.ts +++ b/integrations/vite/index.test.ts @@ -7,7 +7,6 @@ import { html, js, json, - jsx, retryAssertion, test, ts, @@ -34,8 +33,8 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, @@ -111,8 +110,8 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, @@ -294,113 +293,6 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { }, ) - // https://github.com/tailwindlabs/tailwindcss/issues/20346 - test( - 'dev mode + symlinked `@source` files pick up changes to the real file', - { - fs: { - 'package.json': json`{}`, - 'pnpm-workspace.yaml': yaml` - # - packages: - - project-a - - packages/* - `, - 'project-a/package.json': txt` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "repro-package": "workspace:*", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" - } - } - `, - 'project-a/vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"}, - build: { cssMinify: false }, - plugins: [tailwindcss()], - }) - `, - 'project-a/index.html': html` - - - - -
Hello, world!
- - `, - - // The `@source` points through the `node_modules` symlink that pnpm - // creates for the workspace package. Vite's file watcher ignores - // `node_modules` by default, so the symlinked path can not be watched. - // The real file (in `packages/repro-package`) has to be watched - // instead. - 'project-a/src/index.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - @source '../node_modules/repro-package/source.html'; - `, - 'packages/repro-package/package.json': json` - { - "name": "repro-package", - "private": true, - "version": "1.0.0" - } - `, - 'packages/repro-package/source.html': html` -
- Hello, world! -
- `, - }, - }, - async ({ root, spawn, fs, expect }) => { - let process = await spawn('pnpm vite dev', { - cwd: path.join(root, 'project-a'), - }) - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`underline`) - expect(styles).toContain(candidate`content-['v1']`) - }) - - await retryAssertion(async () => { - // Changes to the real file (behind the symlink) should be picked up - await fs.write( - 'packages/repro-package/source.html', - html` -
- Hello, world! -
- `, - ) - - let styles = await fetchStyles(url) - expect(styles).toContain(candidate`underline`) - expect(styles).toContain(candidate`content-['v1']`) - expect(styles).toContain(candidate`content-['v2']`) - }) - }, - ) - test( 'watch mode', { @@ -419,8 +311,8 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, @@ -580,352 +472,6 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { }, ) - describe.each([['^6'], ['7.0.8'], ['7.1.12'], ['7.3.1'], ['8.0.0']])( - 'Using Vite %s', - (version) => { - test( - 'external source file changes update the CSS', - { - fs: { - 'package.json': json`{}`, - 'pnpm-workspace.yaml': yaml` - # - packages: - - project-a - `, - 'project-a/package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "${version}" - } - } - `, - 'project-a/vite.config.ts': ts` - import fs from 'node:fs' - import path from 'node:path' - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"}, - build: { cssMinify: false }, - plugins: [tailwindcss()], - logLevel: 'info', - }) - `, - 'project-a/index.html': html` - - - - - -
- - - - `, - 'project-a/src/main.ts': jsx`import { classes } from './app'`, - 'project-a/src/app.ts': jsx`export let classes = "content-['project-a/src/app.ts']"`, - 'project-a/src/index.css': css` - @import 'tailwindcss'; - @source '../../project-b/**/*.php'; - `, - 'project-b/src/index.php': html` -
- `, - }, - }, - async ({ root, spawn, fs, expect }) => { - let process = await spawn('pnpm vite dev --debug hmr', { - cwd: path.join(root, 'project-a'), - }) - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['project-b/src/index.php']`) - }) - - // Flush all messages so that we can be sure the next messages are - // from the file changes we're about to make - process.flush() - - // Changing an external .php file hot-updates the generated CSS - { - await fs.write( - 'project-b/src/index.php', - txt`
`, - ) - - // On Vite < 7.1, Vite itself hard-invalidates watched files that - // aren't part of the module graph and reloads the page. - // - // On newer versions nothing reloads the page: the CSS hot-updates - // through the regular pipeline because the changed file is a - // watch dependency of the CSS root. - // - // Reloading the page for external template changes is the - // responsibility of the backend integration (e.g. `laravel-vite-plugin`'s `refresh` option, or `vite-plugin-full-reload`). - // - // https://github.com/tailwindlabs/tailwindcss/issues/20411 - if (version === '^6' || version === '7.0.8') { - await process.onStdout((m) => m.includes('page reload') && m.includes('index.php')) - } else { - await process.onStdout((m) => m.includes('hmr update') && m.includes('index.css')) - } - - // Ensure the styles were regenerated with the new content - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['updated:project-b/src/index.php']`) - } - }, - ) - }, - ) - - // https://github.com/tailwindlabs/tailwindcss/issues/20320 - // https://github.com/tailwindlabs/tailwindcss/issues/19903 - test( - 'editing scanned files that Vite can process as modules does not trigger a full reload', - { - fs: { - 'package.json': json`{}`, - 'pnpm-workspace.yaml': yaml` - # - packages: - - project-a - `, - 'project-a/package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^8" - } - } - `, - 'project-a/vite.config.ts': ts` - import fs from 'node:fs' - import path from 'node:path' - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"}, - build: { cssMinify: false }, - plugins: [ - tailwindcss(), - { - // A plugin that processes a custom file type into a JS - // module, similar to e.g. \`.vue\` or \`.svelte\` files - name: 'custom-file-type', - transform(code, id) { - if (id.endsWith('.custom')) { - return { code: 'export default ' + JSON.stringify(code), map: null } - } - }, - }, - { - // Log all HMR payloads to a file so the test can assert on - // them - name: 'hmr-wiretap', - configureServer(server) { - let logFile = path.resolve('hmr.log') - fs.writeFileSync(logFile, '') - for (let environment of Object.values(server.environments)) { - let send = environment.hot.send.bind(environment.hot) - environment.hot.send = (payload) => { - fs.appendFileSync(logFile, JSON.stringify(payload) + '\\n') - return send(payload) - } - } - }, - }, - ], - }) - `, - 'project-a/index.html': html` - - - - - -
- - - - `, - 'project-a/src/main.ts': ts` - import compA from './comp-a.custom' - import snippet from './snippet.php?raw' - console.log(compA, snippet) - `, - 'project-a/src/snippet.php': html` -
- `, - 'project-a/src/comp-a.custom': html` -
- `, - 'project-a/src/comp-b.custom': html` -
- `, - 'project-a/src/lazy.tsx': jsx` - export default function Lazy() { - return
- } - `, - 'project-a/src/unimported.css': css` - .unimported { - color: red; - } - `, - 'project-a/src/index.css': css` - @import 'tailwindcss'; - @source '../../project-b/**/*.php'; - `, - 'project-b/src/index.php': html` -
- `, - }, - }, - async ({ root, spawn, fs, expect }) => { - let process = await spawn('pnpm vite dev', { - cwd: path.join(root, 'project-a'), - }) - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['src/lazy.tsx']`) - expect(styles).toContain(candidate`content-['src/comp-b.custom']`) - expect(styles).toContain(candidate`content-['project-b/src/index.php']`) - }) - - // Load `main.ts`, `comp-a.custom`, and `snippet.php?raw` as real - // modules, like a browser visiting the page would - await fetch(`${url}/src/main.ts`) - await fetch(`${url}/src/comp-a.custom?import`) - await fetch(`${url}/src/snippet.php?raw`) - - // Changing a scanned `.tsx` file that is not part of the module graph - // (e.g. a lazily-loaded route that hasn't been visited yet) should not - // trigger a full reload, but new classes should still apply - // - // https://github.com/tailwindlabs/tailwindcss/issues/20320 - { - await fs.write( - 'project-a/src/lazy.tsx', - jsx` - export default function Lazy() { - return
- } - `, - ) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['updated:src/lazy.tsx']`) - }) - } - - // The same holds for a custom file type as long as some file of the - // same type was processed as a module before - { - await fs.write( - 'project-a/src/comp-b.custom', - html`
`, - ) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['updated:src/comp-b.custom']`) - }) - } - - // Changing a scanned stylesheet that is not part of the module graph - // (e.g. a component stylesheet that a framework plugin compiles into - // the component) should not trigger a full reload either - // - // https://github.com/tailwindlabs/tailwindcss/issues/19903 - { - let updates = (await fs.read('project-a/hmr.log')).split('"type":"update"').length - - await fs.write( - 'project-a/src/unimported.css', - css` - .unimported { - color: blue; - } - `, - ) - - // Wait until the change was handled and an update was pushed - await retryAssertion(async () => { - let log = await fs.read('project-a/hmr.log') - expect(log.split('"type":"update"').length).toBeGreaterThan(updates) - }) - } - - // Changing an external file (e.g. a PHP template) hot-updates the - // generated CSS but does not trigger a full reload either. Reloading the - // page for external template changes is the responsibility of the backend - // integration (e.g. `laravel-vite-plugin`'s `refresh` option, or - // `vite-plugin-full-reload`). - // - // https://github.com/tailwindlabs/tailwindcss/issues/20411 - { - let updates = (await fs.read('project-a/hmr.log')).split('"type":"update"').length - - await fs.write( - 'project-b/src/index.php', - html`
`, - ) - - await retryAssertion(async () => { - let log = await fs.read('project-a/hmr.log') - expect(log.split('"type":"update"').length).toBeGreaterThan(updates) - }) - - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['updated:project-b/src/index.php']`) - } - }, - ) - test( `source(none) disables looking at the module graph`, { @@ -944,8 +490,8 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, @@ -1030,8 +576,8 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, @@ -1132,8 +678,8 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, @@ -1178,92 +724,12 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { async ({ root, fs, exec, expect }) => { await expect(() => exec('pnpm vite build', { cwd: path.join(root, 'project-a') }, { ignoreStdErr: true }), - ).rejects.toThrow('The `source(../i-do-not-exist)` does not exist') + ).rejects.toThrowError('The `source(../i-do-not-exist)` does not exist') let files = await fs.glob('project-a/dist/**/*.css') expect(files).toHaveLength(0) }, ) - - test( - 'source(…) and `@source` are relative to the file they are in', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"}, - build: { cssMinify: false }, - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - - - - - `, - 'index.css': css` @import './project-a/src/index.css'; `, - - 'project-a/src/index.css': css` - /* Run auto-content detection in ../../project-b */ - @import 'tailwindcss/utilities' source('../../project-b'); - - /* Explicitly using node_modules in the @source allows git ignored folders */ - @source '../../project-c'; - `, - - // Project A is the current folder, but we explicitly configured - // `source(project-b)`, therefore project-a should not be included in - // the output. - 'project-a/src/index.html': html` -
- `, - - // Project B is the configured `source(…)`, therefore auto source - // detection should include known extensions and folders in the output. - 'project-b/src/index.html': html` -
- `, - - // Project C should apply auto source detection, therefore known - // extensions and folders should be included in the output. - 'project-c/src/index.html': html` -
- `, - }, - }, - async ({ fs, exec, spawn, root, expect }) => { - await exec('pnpm vite build', { cwd: root }) - - let content = await fs.dumpFiles('./dist/assets/*.css') - - expect(content).not.toContain(candidate`content-['project-a/src/index.html']`) - expect(content).toContain(candidate`content-['project-b/src/index.html']`) - expect(content).toContain(candidate`content-['project-c/src/index.html']`) - }, - ) }) test( @@ -1278,7 +744,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, @@ -1350,7 +816,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, @@ -1413,7 +879,7 @@ test( "@tailwindcss/vite": "workspace:^", "tailwindcss": "workspace:^", "plotly.js": "^3", - "vite": "^7" + "vite": "^6" } } `, @@ -1432,14 +898,6 @@ test( `, 'src/index.js': js`import Plotly from 'plotly.js/lib/core'`, - - // `plotly.js` pulls in `es5-ext`, which has a build script. As of pnpm - // v10, build scripts only run when explicitly allowed. - 'pnpm-workspace.yaml': yaml` - # - allowBuilds: - es5-ext: true - `, }, }, async ({ exec, expect, fs }) => { @@ -1456,271 +914,3 @@ test( function firstLine(str: string) { return str.split('\n')[0] } - -test( - 'optimize option: disabled', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^7" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss({ optimize: false })], - }) - `, - 'index.html': html` - - - - -
Hello, world!
- - `, - 'src/index.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - }, - }, - async ({ exec, expect, fs }) => { - await exec('pnpm vite build') - - // Should not be minified when optimize is disabled - expect((await fs.dumpFiles('dist/**/*.css')).replace(/-([-_a-zA-Z0-9]*?)\.css/g, '-.css')) - .toMatchInlineSnapshot(` - " - --- dist/assets/index-.css --- - .focus\\:text-\\[black\\]:focus { - color: black; - } - " - `) - }, -) - -test( - 'optimize option: enabled with minify disabled', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^7" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss({ optimize: { minify: false } })], - }) - `, - 'index.html': html` - - - - -
Hello, world!
- - `, - 'src/index.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - }, - }, - async ({ exec, expect, fs }) => { - await exec('pnpm vite build') - - expect((await fs.dumpFiles('dist/**/*.css')).replace(/-([-_a-zA-Z0-9]*?)\.css/g, '-.css')) - .toMatchInlineSnapshot(` - " - --- dist/assets/index-.css --- - @media (hover: hover) { - .hover\\:text-\\[black\\]:hover { - color: #000; - } - } - " - `) - }, -) - -test( - `the plugin works when using the environment API`, - { - fs: { - 'package.json': txt` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^7" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - plugins: [tailwindcss()], - builder: {}, - environments: { - server: { - build: { - cssMinify: false, - emitAssets: true, - rollupOptions: { input: './src/server.ts' }, - }, - }, - }, - }) - `, - // Has to exist or the build fails - 'index.html': html` -
- `, - 'src/server.ts': js` - // Import the stylesheet in the server build - import a from './index.css?url' - console.log(a) - `, - 'src/index.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - }, - }, - async ({ root, fs, exec, expect }) => { - await exec('pnpm vite build', { cwd: root }) - - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - let [filename] = files[0] - - await fs.expectFileToContain(filename, [candidate`content-['index.html']`]) - }, -) - -// https://github.com/tailwindlabs/tailwindcss/issues/17532 -test( - 'deleting a file should not crash Vite', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "6.2.5" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - -
- - - `, - 'src/index.css': css`@import 'tailwindcss';`, - 'src/main.js': js` - import iconUrl from './assets/icon.svg?url' - import './index.css' - - document.querySelector('#app').innerHTML = iconUrl - `, - 'src/assets/icon.svg': html` - - - - `, - }, - }, - async ({ fs, spawn, expect }) => { - let process = await spawn('pnpm vite dev') - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let [main, css] = await Promise.all([ - fetch(`${url}/src/main.js`).then((r) => r.text()), - fetch(`${url}/src/index.css`).then((r) => r.text()), - ]) - - expect(main).toContain('/src/assets/icon.svg?import&url') - expect(main).toContain('/src/index.css') - expect(css).toContain('.fill-red-500') - }) - - process.flush() - - let unexpectedError = process.onStderr((m) => { - return /error|crash|panic/i.test(m) - }) - - await fs.write( - 'src/main.js', - js` - import './index.css' - document.querySelector('#app').innerHTML = 'Hello World' - `, - ) - - await fs.delete('src/assets/icon.svg') - - let result = await Promise.race([ - unexpectedError.then(() => 'stderr'), - new Promise((resolve) => setTimeout(() => resolve('timeout'), 500)), - ]) - - expect(result).toBe('timeout') - - await retryAssertion(async () => { - let response = await fetch(`${url}/src/index.css?t=${Date.now()}`) - expect(response.status).toBe(200) - }) - }, -) diff --git a/integrations/vite/multi-root.test.ts b/integrations/vite/multi-root.test.ts index 4020aff0f..d1a1337a4 100644 --- a/integrations/vite/multi-root.test.ts +++ b/integrations/vite/multi-root.test.ts @@ -12,7 +12,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, @@ -96,7 +96,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, diff --git a/integrations/vite/nuxt.test.ts b/integrations/vite/nuxt.test.ts index 20f367020..ab14628c7 100644 --- a/integrations/vite/nuxt.test.ts +++ b/integrations/vite/nuxt.test.ts @@ -1,15 +1,4 @@ -import { - candidate, - css, - fetchStyles, - getRandomPort, - html, - json, - retryAssertion, - test, - ts, - yaml, -} from '../utils' +import { candidate, css, fetchStyles, html, json, retryAssertion, test, ts } from '../utils' const SETUP = { fs: { @@ -18,18 +7,12 @@ const SETUP = { "type": "module", "dependencies": { "@tailwindcss/vite": "workspace:^", - "nuxt": "3.16.0", - "nitropack": "2.11.0", + "nuxt": "^3.13.1", "tailwindcss": "workspace:^", "vue": "latest" } } `, - 'pnpm-workspace.yaml': yaml` - # - overrides: - nuxi: 3.28.0 - `, 'nuxt.config.ts': ts` import tailwindcss from '@tailwindcss/vite' @@ -95,8 +78,7 @@ test('build', SETUP, async ({ spawn, exec, expect }) => { await exec('pnpm nuxt build') // The Nuxt preview server does not automatically assign a free port if 3000 // is taken, so we use a random port instead. - let port = await getRandomPort() - let process = await spawn(`pnpm nuxt preview --port ${port}`, { + let process = await spawn(`pnpm nuxt preview --port 8724`, { env: { TEST: 'false', NODE_ENV: 'development', diff --git a/integrations/vite/other-transforms.test.ts b/integrations/vite/other-transforms.test.ts index d56396065..b1cb5819c 100644 --- a/integrations/vite/other-transforms.test.ts +++ b/integrations/vite/other-transforms.test.ts @@ -13,8 +13,8 @@ function createSetup(transformer: 'postcss' | 'lightningcss') { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, diff --git a/integrations/vite/preact.test.ts b/integrations/vite/preact.test.ts deleted file mode 100644 index 8e4deec2b..000000000 --- a/integrations/vite/preact.test.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { candidate, css, fetchStyles, html, json, retryAssertion, test, ts, txt } from '../utils' - -test( - 'dev mode', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "preact": "^10" - }, - "devDependencies": { - "@preact/preset-vite": "^2", - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^", - "vite": "^8" - } - } - `, - 'vite.config.ts': ts` - import fs from 'node:fs' - import path from 'node:path' - import preact from '@preact/preset-vite' - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - plugins: [ - tailwindcss(), - preact(), - { - // Log all HMR payloads to a file so the test can assert on them - name: 'hmr-wiretap', - configureServer(server) { - let logFile = path.resolve('hmr.log') - fs.writeFileSync(logFile, '') - for (let environment of Object.values(server.environments)) { - let send = environment.hot.send.bind(environment.hot) - environment.hot.send = (payload) => { - fs.appendFileSync(logFile, JSON.stringify(payload) + '\\n') - return send(payload) - } - } - }, - }, - ], - }) - `, - 'index.html': html` - - - - - -
- - - - `, - 'src/main.tsx': ts` - import { render } from 'preact' - import { App } from './app' - - render(, document.getElementById('app')!) - `, - 'src/app.tsx': ts` - import { useState } from 'preact/hooks' - - export function App() { - const [count, setCount] = useState(0) - return ( - - ) - } - `, - 'src/index.css': css`@import 'tailwindcss';`, - }, - }, - async ({ fs, spawn, expect }) => { - let process = await spawn('pnpm vite dev') - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url) - expect(styles).toContain(candidate`underline`) - }) - - // Load the component modules, like a browser visiting the page would - await fetch(`${url}/src/main.tsx`) - await fetch(`${url}/src/app.tsx`) - - // Editing a component keeps HMR intact: new classes are delivered through - // a regular update, not a full page reload (which would lose all state) - { - await fs.write( - 'src/app.tsx', - ts` - import { useState } from 'preact/hooks' - - export function App() { - const [count, setCount] = useState(0) - return ( - - ) - } - `, - ) - - await retryAssertion(async () => { - let styles = await fetchStyles(url) - expect(styles).toContain(candidate`underline`) - expect(styles).toContain(candidate`flex`) - }) - expect(await fs.read('hmr.log')).toContain('"type":"update"') - expect(await fs.read('hmr.log')).not.toContain('full-reload') - } - - // Changing a scanned file that is not part of the module graph (e.g. - // `package.json`, which package managers and other tooling write to while - // the dev server is running) should not trigger a full reload either — - // that would destroy client state. New candidates should still be picked - // up because the file is a watch dependency of the CSS root, so the CSS - // hot-updates through Vite's regular pipeline. - // - // https://github.com/tailwindlabs/tailwindcss/issues/20411 - { - await fs.write( - 'package.json', - txt` - { - "type": "module", - "description": "content-['package.json']", - "dependencies": { - "preact": "^10" - }, - "devDependencies": { - "@preact/preset-vite": "^2", - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^", - "vite": "^8" - } - } - `, - ) - - await retryAssertion(async () => { - let styles = await fetchStyles(url) - expect(styles).toContain(candidate`content-['package.json']`) - }) - expect(await fs.read('hmr.log')).not.toContain('full-reload') - } - }, -) diff --git a/integrations/vite/qwik.test.ts b/integrations/vite/qwik.test.ts deleted file mode 100644 index 42db6563a..000000000 --- a/integrations/vite/qwik.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { candidate, css, fetchStyles, json, retryAssertion, test, ts } from '../utils' - -test( - 'dev mode', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@builder.io/qwik": "^1", - "@builder.io/qwik-city": "^1", - "vite": "^5", - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - } - } - `, - 'vite.config.ts': ts` - import { defineConfig } from 'vite' - import { qwikVite } from '@builder.io/qwik/optimizer' - import { qwikCity } from '@builder.io/qwik-city/vite' - import tailwindcss from '@tailwindcss/vite' - - export default defineConfig(() => { - return { - plugins: [tailwindcss(), qwikCity(), qwikVite()], - } - }) - `, - 'src/root.tsx': ts` - import { component$ } from '@builder.io/qwik' - import { QwikCityProvider, RouterOutlet } from '@builder.io/qwik-city' - - import './global.css' - - export default component$(() => { - return ( - - - - - - - ) - }) - `, - 'src/global.css': css`@import 'tailwindcss/utilities.css';`, - 'src/entry.ssr.tsx': ts` - import { renderToStream, type RenderToStreamOptions } from '@builder.io/qwik/server' - import Root from './root' - - export default function (opts: RenderToStreamOptions) { - return renderToStream(, opts) - } - `, - 'src/routes/index.tsx': ts` - import { component$ } from '@builder.io/qwik' - - export default component$(() => { - return

Hello World!

- }) - `, - }, - }, - async ({ fs, spawn, expect }) => { - let process = await spawn('pnpm vite --mode ssr') - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - console.log(m) - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let css = await fetchStyles(url) - expect(css).toContain(candidate`underline`) - }) - - await retryAssertion(async () => { - await fs.write( - 'src/routes/index.tsx', - ts` - import { component$ } from '@builder.io/qwik' - - export default component$(() => { - return

Hello World!

- }) - `, - ) - - let css = await fetchStyles(url) - expect(css).toContain(candidate`underline`) - expect(css).toContain(candidate`flex`) - }) - }, -) diff --git a/integrations/vite/react-router.test.ts b/integrations/vite/react-router.test.ts index 98b0c928d..5b9574fd6 100644 --- a/integrations/vite/react-router.test.ts +++ b/integrations/vite/react-router.test.ts @@ -102,101 +102,13 @@ test('dev mode', { fs: WORKSPACE }, async ({ fs, spawn, expect }) => { }) }) -test( - // cf. https://github.com/remix-run/react-router/blob/00cb4d7b310663b2e84152700c05d3b503005e83/integration/vite-hmr-hdr-test.ts#L311-L318 - 'dev mode, editing a server-only loader dependency triggers HDR instead of a full reload', - { - fs: { - ...WORKSPACE, - 'package.json': json` - { - "type": "module", - "dependencies": { - "@react-router/dev": "^7", - "@react-router/node": "^7", - "@react-router/serve": "^7", - "@tailwindcss/vite": "workspace:^", - "@types/node": "^20", - "@types/react-dom": "^19", - "@types/react": "^19", - "isbot": "^5", - "react-dom": "^19", - "react-router": "^7", - "react": "^19", - "tailwindcss": "workspace:^", - "vite": "^7" - } - } - `, - 'app/routes/home.tsx': ts` - import type { Route } from './+types/home' - import { direct } from '../direct-hdr-dep' - - export async function loader() { - return { message: direct } - } - - export default function Home({ loaderData }: Route.ComponentProps) { - return ( -
-

{loaderData.message}

- -
- ) - } - `, - 'app/direct-hdr-dep.ts': ts` export const direct = 'HDR: 0' `, - }, - }, - async ({ fs, spawn, expect }) => { - let process = await spawn('pnpm react-router dev') - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - // check initial state - await retryAssertion(async () => { - let html = await (await fetch(url)).text() - expect(html).toContain('HDR: 0') - - let css = await fetchStyles(url) - expect(css).toContain(candidate`font-bold`) - }) - - // Flush stdout so we only see messages triggered by the edit below. - process.flush() - - // Edit the server-only module. The client environment watches this file - // but it only exists in the server module graph. Without the fix, the - // Tailwind CSS plugin would trigger a full page reload on the client - // instead of letting react-router handle HDR. - await fs.write('app/direct-hdr-dep.ts', ts` export const direct = 'HDR: 1' `) - - // check update - await retryAssertion(async () => { - let html = await (await fetch(url)).text() - expect(html).toContain('HDR: 1') - - let css = await fetchStyles(url) - expect(css).toContain(candidate`font-bold`) - }) - - // Assert the client receives an HMR update (not a full page reload). - await process.onStdout((m) => m.includes('(client) hmr update')) - }, -) - test('build mode', { fs: WORKSPACE }, async ({ spawn, exec, expect }) => { await exec('pnpm react-router build') let process = await spawn('pnpm react-router-serve ./build/server/index.js') let url = '' await process.onStdout((m) => { - let match = /\[react-router-serve\]\s*(http.*) \/?/.exec(m) + let match = /\[react-router-serve\]\s*(http.*)\ \/?/.exec(m) if (match) url = match[1] return url != '' }) diff --git a/integrations/vite/resolvers.test.ts b/integrations/vite/resolvers.test.ts index fc9aca5c7..6072c37da 100644 --- a/integrations/vite/resolvers.test.ts +++ b/integrations/vite/resolvers.test.ts @@ -1,661 +1,5 @@ import { describe } from 'vitest' -import { - candidate, - css, - fetchStyles, - html, - js, - json, - retryAssertion, - test, - ts, - txt, - yaml, -} from '../utils' - -test( - 'resolves tsconfig paths in production build', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'tsconfig.json': json` - { - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - } - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - resolve: { - tsconfigPaths: true, - }, - }) - `, - 'index.html': html` - - - - -
Hello, world!
- - `, - 'src/index.css': css` - @import '@/styles/base.css'; - @plugin '@/plugin.js'; - `, - 'src/styles/base.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - 'src/plugin.js': js` - export default function ({ addUtilities }) { - addUtilities({ '.custom-underline': { 'border-bottom': '1px solid green' } }) - } - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm vite build') - - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - let [filename] = files[0] - - await fs.expectFileToContain(filename, [candidate`underline`, candidate`custom-underline`]) - }, -) - -test( - 'resolves tsconfig paths in dev mode', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'tsconfig.json': json` - { - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - } - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - resolve: { - tsconfigPaths: true, - }, - }) - `, - 'index.html': html` - - - - -
Hello, world!
- - `, - 'src/index.css': css` - @import '@/styles/base.css'; - @plugin '@/plugin.js'; - `, - 'src/styles/base.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - 'src/plugin.js': js` - export default function ({ addUtilities }) { - addUtilities({ '.custom-underline': { 'border-bottom': '1px solid green' } }) - } - `, - }, - }, - async ({ spawn, expect }) => { - let process = await spawn('pnpm vite dev') - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`underline`) - expect(styles).toContain(candidate`custom-underline`) - }) - }, -) - -test( - 'resolves at-sign aliases in production build', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - import { fileURLToPath } from 'node:url' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - resolve: { - alias: [{ find: '@', replacement: fileURLToPath(new URL('.', import.meta.url)) }], - }, - }) - `, - 'index.html': html` - - - - -
Hello, world!
- - `, - 'src/index.css': css` - @import '@/src/styles/base.css'; - @plugin '@/src/plugin.js'; - `, - 'src/styles/base.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - 'src/plugin.js': js` - export default function ({ addUtilities }) { - addUtilities({ '.custom-underline': { 'border-bottom': '1px solid green' } }) - } - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm vite build') - - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - let [filename] = files[0] - - await fs.expectFileToContain(filename, [candidate`underline`, candidate`custom-underline`]) - }, -) - -test( - 'resolves package plugins in production build with at-sign aliases', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "tailwind-scrollbar": "^4.0.2", - "tailwindcss-motion": "^1.1.1", - "vite": "^8" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - import { fileURLToPath } from 'node:url' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - resolve: { - alias: [{ find: '@', replacement: fileURLToPath(new URL('.', import.meta.url)) }], - }, - }) - `, - 'index.html': html` - - - - -
Hello, world!
- - `, - 'src/index.css': css` - @import 'tailwindcss'; - @plugin 'tailwind-scrollbar'; - @plugin 'tailwindcss-motion'; - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm vite build') - - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - let [filename] = files[0] - - await fs.expectFileToContain(filename, [ - candidate`scrollbar`, - candidate`scrollbar-thumb-red-500`, - candidate`motion-preset-fade`, - ]) - }, -) - -test( - 'resolves package plugins to JS entries in production build when browser points to CSS', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "plugin-browser-css": "workspace:*", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'pnpm-workspace.yaml': yaml` - # - packages: - - packages/* - `, - 'packages/plugin-browser-css/package.json': json` - { - "name": "plugin-browser-css", - "version": "1.0.0", - "type": "module", - "main": "./index.js", - "module": "./index.js", - "browser": "./browser.css" - } - `, - 'packages/plugin-browser-css/index.js': js` - export default function ({ addUtilities }) { - addUtilities({ '.browser-css-plugin': { 'border-bottom': '1px solid green' } }) - } - `, - 'packages/plugin-browser-css/browser.css': css` - .should-not-be-loaded-as-a-plugin { - display: none; - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - - - - - -
Hello, world!
- - - `, - 'src/index.css': css` - @import 'tailwindcss'; - @plugin 'plugin-browser-css'; - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm vite build') - - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - let [filename] = files[0] - - await fs.expectFileToContain(filename, [candidate`browser-css-plugin`]) - }, -) - -test( - 'resolves package plugins to JS entries in dev mode when browser points to CSS', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "plugin-browser-css": "workspace:*", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'pnpm-workspace.yaml': yaml` - # - packages: - - packages/* - `, - 'packages/plugin-browser-css/package.json': json` - { - "name": "plugin-browser-css", - "version": "1.0.0", - "type": "module", - "main": "./index.js", - "module": "./index.js", - "browser": "./browser.css" - } - `, - 'packages/plugin-browser-css/index.js': js` - export default function ({ addUtilities }) { - addUtilities({ '.browser-css-plugin': { 'border-bottom': '1px solid green' } }) - } - `, - 'packages/plugin-browser-css/browser.css': css` - .should-not-be-loaded-as-a-plugin { - display: none; - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - - - - - -
Hello, world!
- - - `, - 'src/index.css': css` - @import 'tailwindcss'; - @plugin 'plugin-browser-css'; - `, - }, - }, - async ({ spawn, expect }) => { - let process = await spawn('pnpm vite dev') - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`browser-css-plugin`) - }) - }, -) - -test( - 'resolves package plugins in dev mode when package exports CSS files', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "daisyui": "^5", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - - - - - - - - - `, - 'src/index.css': css` - @import 'tailwindcss'; - @plugin 'daisyui'; - `, - }, - }, - async ({ spawn, expect }) => { - let process = await spawn('pnpm vite dev') - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain('.btn') - expect(styles).toContain('.btn-primary') - }) - }, -) - -test( - 'resolve relative CSS files correctly', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - - - - - - - `, - 'src/index.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - @import './themes/glow.css'; - `, - // References a file in the current folder, which names happens to match a - // file in the parent folder as well. - 'src/themes/glow.css': css`@import './entry.css';`, - 'src/themes/entry.css': css` - .do-include-me { - color: green; - } - `, - - // Never rerefenced, so should not be included - 'src/entry.css': css` - .do-not-include-me { - color: red; - } - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm vite build') - - expect( - (await fs.dumpFiles('./dist/**/*.css')).replace(/-([-_a-zA-Z0-9]*?)\.css/g, '-.css'), - ).toMatchInlineSnapshot(` - " - --- ./dist/assets/index-.css --- - .do-include-me { - color: green; - } - " - `) - }, -) - -test( - 'resolve relative JS files correctly', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - - - - - - - `, - 'src/index.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - @import './themes/glow.css'; - `, - // References a file in the current folder, which names happens to match a - // file in the parent folder as well. - 'src/themes/glow.css': css`@plugin "./my-plugin.js";`, - 'src/themes/my-plugin.js': ts` - export default function ({ addBase }) { - addBase({ '.do-include-me': { color: 'green' } }) - } - `, - - // Never rerefenced, so should not be included - 'src/my-plugin.js': css` - export default function ({ addBase }) { - addBase({ '.do-not-include-me': { 'color': 'red' } }) - } - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm vite build') - - expect( - (await fs.dumpFiles('./dist/**/*.css')).replace(/-([-_a-zA-Z0-9]*?)\.css/g, '-.css'), - ).toMatchInlineSnapshot(` - " - --- ./dist/assets/index-.css --- - @layer base { - .do-include-me { - color: green; - } - } - " - `) - }, -) +import { candidate, css, fetchStyles, html, js, retryAssertion, test, ts, txt } from '../utils' describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { test( @@ -670,7 +14,7 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} "vite": "^5.3.5" } } @@ -738,7 +82,7 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} "vite": "^5.3.5" } } diff --git a/integrations/vite/source-maps.test.ts b/integrations/vite/source-maps.test.ts deleted file mode 100644 index fe26d9a7c..000000000 --- a/integrations/vite/source-maps.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { candidate, css, fetchStyles, html, json, retryAssertion, test, ts } from '../utils' - -test( - `dev build`, - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "lightningcss": "^1", - "vite": "^7" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - plugins: [tailwindcss()], - css: { - devSourcemap: true, - }, - }) - `, - 'index.html': html` - - - - -
Hello, world!
- - `, - 'src/index.css': css` - @import 'tailwindcss/utilities'; - /* */ - `, - }, - }, - async ({ fs, spawn, expect, parseSourceMap }) => { - // Source maps only work in development mode in Vite - let process = await spawn('pnpm vite dev') - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - let styles = await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - - // Wait until we have the right CSS - expect(styles).toContain(candidate`flex`) - - return styles - }) - - // Make sure we can find a source map - let map = parseSourceMap(styles) - - expect(map.at(1, 0)).toMatchObject({ - source: null, - original: '(none)', - generated: '/*! tailwi...', - }) - - expect(map.at(2, 0)).toMatchObject({ - source: expect.stringContaining('utilities.css'), - original: '@tailwind...', - generated: '.flex {...', - }) - - expect(map.at(3, 2)).toMatchObject({ - source: expect.stringContaining('utilities.css'), - original: '@tailwind...', - generated: 'display: f...', - }) - - expect(map.at(4, 0)).toMatchObject({ - source: null, - original: '(none)', - generated: '}...', - }) - }, -) - -// https://github.com/tailwindlabs/tailwindcss/issues/19930 -test( - 'production build without Tailwind roots should not result in source map warnings', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^7" - } - } - `, - 'vite.config.ts': ts` - import { defineConfig } from 'vite' - import tailwindcss from '@tailwindcss/vite' - - export default defineConfig({ - plugins: [ - tailwindcss(), - { - name: 'inspect-source-map-chain', - enforce: 'pre', - transform(_, id) { - if (id.includes('.css')) { - // Force Rollup to collapse the sourcemap chain during this build. - this.getCombinedSourcemap() - } - }, - }, - ], - css: { - devSourcemap: true, - }, - build: { - sourcemap: true, - }, - }) - `, - 'index.html': html` - - -
Hello, world!
- - `, - 'src/index.js': ts` import './index.css' `, - 'src/index.css': css` - body { - color: red; - } - `, - }, - }, - async ({ exec, expect }) => { - let output = await exec('pnpm vite build') - - expect(output).not.toContain('Sourcemap is likely to be incorrect') - }, -) diff --git a/integrations/vite/ssr.test.ts b/integrations/vite/ssr.test.ts index a1d660816..d4d227b8e 100644 --- a/integrations/vite/ssr.test.ts +++ b/integrations/vite/ssr.test.ts @@ -1,72 +1,121 @@ -import { describe } from 'vitest' import { candidate, css, html, json, test, ts } from '../utils' -describe.each([['^5.3'], ['^6.0'], ['^7'], ['^8']])('Using Vite %s', (version) => { - test( - `SSR build`, - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "${version}" - } +const WORKSPACE = { + 'index.html': html` + +
+ + + `, + 'src/index.css': css`@import 'tailwindcss';`, + 'src/index.ts': ts` + import './index.css' + + document.querySelector('#app').innerHTML = \` +
Hello, world!
+ \` + `, + 'server.ts': ts` + import css from './src/index.css?url' + + document.querySelector('#app').innerHTML = \` + +
Hello, world!
+ \` + `, +} + +test( + 'Vite 5', + { + fs: { + 'package.json': json` + { + "type": "module", + "dependencies": { + "@tailwindcss/vite": "workspace:^", + "tailwindcss": "workspace:^" + }, + "_comment": "This test uses Vite 5.3 on purpose. Do not upgrade it to Vite 6.", + "devDependencies": { + "vite": "^5.3" } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' + } + `, + 'vite.config.ts': ts` + import tailwindcss from '@tailwindcss/vite' + import { defineConfig } from 'vite' - export default defineConfig({ - build: { - cssMinify: false, - ssrEmitAssets: true, - }, - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - -
- - - `, - 'src/index.css': css`@import 'tailwindcss';`, - 'src/index.ts': ts` - import './index.css' - - document.querySelector('#app').innerHTML = \` -
Hello, world!
- \` - `, - 'server.ts': ts` - import css from './src/index.css?url' - - document.querySelector('#app').innerHTML = \` - -
Hello, world!
- \` - `, - }, + export default defineConfig({ + build: { + cssMinify: false, + ssrEmitAssets: true, + }, + plugins: [tailwindcss()], + }) + `, + ...WORKSPACE, }, - async ({ fs, exec, expect }) => { - await exec('pnpm vite build --ssr server.ts') + }, + async ({ fs, exec, expect }) => { + await exec('pnpm vite build --ssr server.ts') - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - let [filename] = files[0] + let files = await fs.glob('dist/**/*.css') + expect(files).toHaveLength(1) + let [filename] = files[0] - await fs.expectFileToContain(filename, [ - candidate`underline`, - candidate`m-2`, - candidate`overline`, - candidate`m-3`, - ]) + await fs.expectFileToContain(filename, [ + candidate`underline`, + candidate`m-2`, + candidate`overline`, + candidate`m-3`, + ]) + }, +) + +test( + `Vite 6`, + { + fs: { + 'package.json': json` + { + "type": "module", + "dependencies": { + "@tailwindcss/vite": "workspace:^", + "tailwindcss": "workspace:^" + }, + "devDependencies": { + "vite": "^6.0" + } + } + `, + 'vite.config.ts': ts` + import tailwindcss from '@tailwindcss/vite' + import { defineConfig } from 'vite' + + export default defineConfig({ + build: { + cssMinify: false, + ssrEmitAssets: true, + }, + plugins: [tailwindcss()], + }) + `, + ...WORKSPACE, }, - ) -}) + }, + async ({ fs, exec, expect }) => { + await exec('pnpm vite build --ssr server.ts') + + let files = await fs.glob('dist/**/*.css') + expect(files).toHaveLength(1) + let [filename] = files[0] + + await fs.expectFileToContain(filename, [ + candidate`underline`, + candidate`m-2`, + candidate`overline`, + candidate`m-3`, + ]) + }, +) diff --git a/integrations/vite/svelte.test.ts b/integrations/vite/svelte.test.ts index e186c3f85..053819c73 100644 --- a/integrations/vite/svelte.test.ts +++ b/integrations/vite/svelte.test.ts @@ -14,7 +14,7 @@ test( "devDependencies": { "@sveltejs/vite-plugin-svelte": "^5", "@tailwindcss/vite": "workspace:^", - "vite": "^7" + "vite": "^6" } } `, @@ -100,8 +100,8 @@ test( await fs.expectFileToContain(files[0][0], [ candidate`underline`, - '.global{color:var(--color-green-500,oklch(72.3% .219 149.579));animation:2s ease-in-out infinite globalKeyframes}', - /\.local.svelte-.*\{color:var\(--color-red-500,oklch\(63\.7% \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/, + '.global{color:var(--color-green-500,oklch(.723 .219 149.579));animation:2s ease-in-out infinite globalKeyframes}', + /\.local.svelte-.*\{color:var\(--color-red-500\,oklch\(\.637 \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/, /@keyframes globalKeyframes\{/, /@keyframes svelte-.*-localKeyframes\{/, ]) @@ -125,7 +125,7 @@ test( "devDependencies": { "@sveltejs/vite-plugin-svelte": "^5", "@tailwindcss/vite": "workspace:^", - "vite": "^7" + "vite": "^6" } } `, @@ -213,10 +213,10 @@ test( let [, css] = files[0] expect(css).toContain(candidate`underline`) expect(css).toContain( - '.global{color:var(--color-green-500,oklch(72.3% .219 149.579));animation:2s ease-in-out infinite globalKeyframes}', + '.global{color:var(--color-green-500,oklch(.723 .219 149.579));animation:2s ease-in-out infinite globalKeyframes}', ) expect(css).toMatch( - /\.local.svelte-.*\{color:var\(--color-red-500,oklch\(63\.7% \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/, + /\.local.svelte-.*\{color:var\(--color-red-500,oklch\(\.637 \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/, ) expect(css).toMatch(/@keyframes globalKeyframes\{/) expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/) @@ -238,15 +238,15 @@ test( let [, css] = files[0] expect(css).toContain(candidate`font-bold`) expect(css).toContain( - '.global{color:var(--color-green-500,oklch(72.3% .219 149.579));animation:2s ease-in-out infinite globalKeyframes}', + '.global{color:var(--color-green-500,oklch(.723 .219 149.579));animation:2s ease-in-out infinite globalKeyframes}', ) expect(css).toMatch( - /\.local.svelte-.*\{color:var\(--color-red-500,oklch\(63\.7% \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/, + /\.local.svelte-.*\{color:var\(--color-red-500,oklch\(\.637 \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/, ) expect(css).toMatch(/@keyframes globalKeyframes\{/) expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/) expect(css).toMatch( - /\.bar.svelte-.*\{color:var\(--color-pink-500,oklch\(65\.6% \.241 354\.308\)\)\}/, + /\.bar.svelte-.*\{color:var\(--color-pink-500,oklch\(\.656 \.241 354\.308\)\)\}/, ) }) }, diff --git a/integrations/vite/sveltekit.test.ts b/integrations/vite/sveltekit.test.ts deleted file mode 100644 index 92e855da9..000000000 --- a/integrations/vite/sveltekit.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { candidate, css, html, js, json, test, ts } from '../utils' - -test( - 'sveltekit build resolves package plugins', - { - fs: { - 'package.json': json` - { - "type": "module", - "scripts": { - "prepare": "svelte-kit sync || echo ''" - }, - "dependencies": { - "svelte": "^5", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "@sveltejs/adapter-auto": "^6", - "@sveltejs/kit": "^2", - "@tailwindcss/forms": "^0.5.11", - "@tailwindcss/vite": "workspace:^", - "vite": "^8" - } - } - `, - 'svelte.config.js': js` - import adapter from '@sveltejs/adapter-auto' - - export default { - kit: { - adapter: adapter(), - }, - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { sveltekit } from '@sveltejs/kit/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss(), sveltekit()], - }) - `, - 'src/app.html': html` - - - - %sveltekit.head% - - -
%sveltekit.body%
- - - `, - 'src/routes/+layout.svelte': html` - - - - `, - 'src/routes/+page.svelte': html` -
Hello, world!
- `, - 'src/app.css': css` - @import 'tailwindcss'; - @plugin '@tailwindcss/forms'; - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm vite build') - - let files = await fs.glob('.svelte-kit/output/client/**/*.css') - expect(files.length).toBeGreaterThan(0) - - await fs.expectFileToContain(files[0][0], [candidate`form-input`]) - }, -) diff --git a/integrations/vite/url-rewriting.test.ts b/integrations/vite/url-rewriting.test.ts index 500fe6d71..4077df16d 100644 --- a/integrations/vite/url-rewriting.test.ts +++ b/integrations/vite/url-rewriting.test.ts @@ -15,9 +15,9 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} "@tailwindcss/vite": "workspace:^", - "vite": "^7" + "vite": "^6" } } `, diff --git a/integrations/vite/vue.test.ts b/integrations/vite/vue.test.ts index b04f57c6b..7b5da2904 100644 --- a/integrations/vite/vue.test.ts +++ b/integrations/vite/vue.test.ts @@ -1,5 +1,4 @@ -import { stripVTControlCharacters } from 'node:util' -import { candidate, css, fetchStyles, html, json, retryAssertion, test, ts } from '../utils' +import { candidate, html, json, test, ts } from '../utils' test( 'production build', @@ -15,7 +14,7 @@ test( "devDependencies": { "@vitejs/plugin-vue": "^5.1.2", "@tailwindcss/vite": "workspace:^", - "vite": "^7" + "vite": "^6" } } `, @@ -72,309 +71,3 @@ test( await fs.expectFileToContain(files[0][0], ['.bar{']) }, ) - -{ - const VUE_COMPONENT_COUNT = 1_000 - - let vueComponentsWithReferences = Object.fromEntries( - Array.from({ length: VUE_COMPONENT_COUNT }, (_, idx) => [ - `src/components/Component${idx}.vue`, - html` - - - - `, - ]), - ) - - let vueComponentImports = Array.from( - { length: VUE_COMPONENT_COUNT }, - (_, idx) => `import Component${idx} from './components/Component${idx}.vue'`, - ).join('\n') - - let vueComponentUsages = Array.from( - { length: VUE_COMPONENT_COUNT }, - (_, idx) => ``, - ).join('\n') - - test( - 'production build with many Vue style blocks referencing the main stylesheet', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "vue": "^3.4.37", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^5.1.2", - "@tailwindcss/vite": "workspace:^", - "vite": "^7" - } - } - `, - 'vite.config.ts': ts` - import { defineConfig } from 'vite' - import vue from '@vitejs/plugin-vue' - import tailwindcss from '@tailwindcss/vite' - - export default defineConfig({ - plugins: [vue(), tailwindcss()], - }) - `, - 'index.html': html` - - - -
- - - - `, - 'src/main.css': css`@import 'tailwindcss';`, - 'src/main.ts': ts` - import { createApp } from 'vue' - import './main.css' - import App from './App.vue' - - createApp(App).mount('#app') - `, - 'src/App.vue': html` - - - - `, - ...vueComponentsWithReferences, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm vite build') - - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - - await fs.expectFileToContain(files[0][0], [ - candidate`content-['component-0']`, - candidate`component-0`, - candidate`content-['component-99']`, - candidate`component-99`, - candidate`content-['component-999']`, - candidate`component-999`, - ]) - }, - ) -} - -test( - 'error when using `@apply` without `@reference`', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "vue": "^3.4.37", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^5.1.2", - "@tailwindcss/vite": "workspace:^", - "vite": "^7" - } - } - `, - 'vite.config.ts': ts` - import { defineConfig } from 'vite' - import vue from '@vitejs/plugin-vue' - import tailwindcss from '@tailwindcss/vite' - - export default defineConfig({ - plugins: [vue(), tailwindcss()], - }) - `, - 'index.html': html` - - - -
- - - - `, - 'src/main.ts': ts` - import { createApp } from 'vue' - import App from './App.vue' - - createApp(App).mount('#app') - `, - 'src/App.vue': html` - - - - `, - }, - }, - async ({ exec, expect }) => { - expect.assertions(1) - - try { - await exec('pnpm vite build', {}, { ignoreStdErr: true }) - } catch (error) { - let [, message] = - /error during build:([\s\S]*?)file:/g.exec( - stripVTControlCharacters(error.message.replace(/\r?\n/g, '\n')), - ) ?? [] - expect(message.trim()).toMatchInlineSnapshot( - `"[@tailwindcss/vite:generate:build] Cannot apply unknown utility class \`text-red-500\`. Are you using CSS modules or similar and missing \`@reference\`? https://tailwindcss.com/docs/functions-and-directives#reference-directive"`, - ) - } - }, -) - -// https://github.com/tailwindlabs/tailwindcss/issues/20320 -test( - 'editing a scanned `.vue` file that is not loaded as a module does not trigger a full reload', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "vue": "^3.4.37", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^6", - "@tailwindcss/vite": "workspace:^", - "vite": "^8" - } - } - `, - 'vite.config.ts': ts` - import fs from 'node:fs' - import path from 'node:path' - import { defineConfig } from 'vite' - import vue from '@vitejs/plugin-vue' - import tailwindcss from '@tailwindcss/vite' - - export default defineConfig({ - plugins: [ - vue(), - tailwindcss(), - { - // Log update and full-reload HMR payloads to a file so the - // test can assert on them. Custom events are not logged - // because \`@vitejs/plugin-vue\` sends a \`file-changed\` event - // for every file change, including changes to the log file - // itself, which would cause an infinite feedback loop. - name: 'hmr-wiretap', - configureServer(server) { - let logFile = path.resolve('hmr.log') - fs.writeFileSync(logFile, '') - for (let environment of Object.values(server.environments)) { - let send = environment.hot.send.bind(environment.hot) - environment.hot.send = (payload) => { - if (payload.type === 'update' || payload.type === 'full-reload') { - fs.appendFileSync(logFile, JSON.stringify(payload) + '\\n') - } - return send(payload) - } - } - }, - }, - ], - }) - `, - 'index.html': html` - - - - - - -
- - - - `, - 'src/index.css': css`@import 'tailwindcss';`, - 'src/main.ts': ts` - import { createApp } from 'vue' - import App from './App.vue' - - createApp(App).mount('#app') - `, - 'src/App.vue': html` - - `, - - // This file is scanned by Tailwind but never imported, so it is not - // part of the loaded module graph (e.g. a lazy route that hasn't been - // visited yet) - 'src/LazyRoute.vue': html` - - `, - }, - }, - async ({ spawn, fs, expect }) => { - let process = await spawn('pnpm vite dev') - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['src/App.vue']`) - expect(styles).toContain(candidate`content-['src/LazyRoute.vue']`) - }) - - // Load `main.ts` and `App.vue` as real modules, like a browser visiting - // the page would - await fetch(`${url}/src/main.ts`) - await fetch(`${url}/src/App.vue`) - - // Changing the scanned but unloaded `.vue` file should not trigger a - // full reload, but new classes should still apply - await fs.write( - 'src/LazyRoute.vue', - html` - - `, - ) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['updated:src/LazyRoute.vue']`) - }) - expect(await fs.read('hmr.log')).not.toContain('full-reload') - }, -) diff --git a/integrations/vitest.config.mts b/integrations/vitest.config.ts similarity index 100% rename from integrations/vitest.config.mts rename to integrations/vitest.config.ts diff --git a/integrations/webpack/index.test.ts b/integrations/webpack/index.test.ts deleted file mode 100644 index 2a91411d2..000000000 --- a/integrations/webpack/index.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { css, html, js, json, test } from '../utils' - -test( - 'webpack + PostCSS (watch)', - { - fs: { - 'package.json': json` - { - "main": "./src/index.js", - "browser": "./src/index.js", - "dependencies": { - "css-loader": "^6", - "postcss": "^8", - "postcss-loader": "^7", - "webpack": "^5", - "webpack-cli": "^5", - "mini-css-extract-plugin": "^2", - "tailwindcss": "workspace:^", - "@tailwindcss/postcss": "workspace:^" - } - } - `, - 'postcss.config.js': js` - /** @type {import('postcss-load-config').Config} */ - module.exports = { - plugins: { - '@tailwindcss/postcss': {}, - }, - } - `, - 'webpack.config.js': js` - let MiniCssExtractPlugin = require('mini-css-extract-plugin') - - module.exports = { - output: { - clean: true, - }, - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /.css$/i, - use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'], - }, - ], - }, - } - `, - 'src/index.js': js`import './index.css'`, - 'src/index.html': html` -
- `, - 'src/index.css': css` - @import 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - 'src/unrelated.module.css': css` - .module { - color: var(--color-blue-500); - } - `, - }, - }, - async ({ fs, spawn, exec, expect }) => { - // Generate the initial build so output CSS files exist on disk - await exec('pnpm webpack --mode=development') - - // NOTE: We are writing to an output CSS file which is not being ignored by - // `.gitignore` nor marked with `@source not`. This should not result in an - // infinite loop. - let process = await spawn('pnpm webpack --mode=development --watch') - await process.onStdout((m) => m.includes('compiled successfully in')) - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - :root, :host { - --color-blue-500: oklch(62.3% 0.214 259.815); - } - .flex { - display: flex; - } - " - `) - - await fs.write( - 'src/unrelated.module.css', - css` - .module { - color: var(--color-blue-500); - background-color: var(--color-red-500); - } - `, - ) - await process.onStdout((m) => m.includes('compiled successfully in')) - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - :root, :host { - --color-red-500: oklch(63.7% 0.237 25.331); - --color-blue-500: oklch(62.3% 0.214 259.815); - } - .flex { - display: flex; - } - " - `) - }, -) diff --git a/integrations/webpack/loader.test.ts b/integrations/webpack/loader.test.ts deleted file mode 100644 index 9c3d870af..000000000 --- a/integrations/webpack/loader.test.ts +++ /dev/null @@ -1,495 +0,0 @@ -import { css, html, js, json, test } from '../utils' - -test( - '@tailwindcss/webpack loader (build)', - { - fs: { - 'package.json': json` - { - "main": "./src/index.js", - "browser": "./src/index.js", - "dependencies": { - "css-loader": "^6", - "webpack": "^5", - "webpack-cli": "^5", - "mini-css-extract-plugin": "^2", - "tailwindcss": "workspace:^", - "@tailwindcss/webpack": "workspace:^" - } - } - `, - 'webpack.config.js': js` - let MiniCssExtractPlugin = require('mini-css-extract-plugin') - - module.exports = { - output: { - clean: true, - }, - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /.css$/i, - use: [MiniCssExtractPlugin.loader, 'css-loader', '@tailwindcss/webpack'], - }, - ], - }, - } - `, - 'src/index.js': js`import './index.css'`, - 'src/index.html': html` -
- `, - 'src/index.css': css` - @import 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm webpack --mode=development') - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - .flex { - display: flex; - } - " - `) - }, -) - -test( - '@tailwindcss/webpack loader (watch)', - { - fs: { - 'package.json': json` - { - "main": "./src/index.js", - "browser": "./src/index.js", - "dependencies": { - "css-loader": "^6", - "webpack": "^5", - "webpack-cli": "^5", - "mini-css-extract-plugin": "^2", - "tailwindcss": "workspace:^", - "@tailwindcss/webpack": "workspace:^" - } - } - `, - 'webpack.config.js': js` - let MiniCssExtractPlugin = require('mini-css-extract-plugin') - - module.exports = { - output: { - clean: true, - }, - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /.css$/i, - use: [MiniCssExtractPlugin.loader, 'css-loader', '@tailwindcss/webpack'], - }, - ], - }, - } - `, - 'src/index.js': js`import './index.css'`, - 'src/index.html': html` -
- `, - 'src/index.css': css` - @import 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - }, - }, - async ({ fs, spawn, exec, expect }) => { - // Generate the initial build so output CSS files exist on disk - await exec('pnpm webpack --mode=development') - - let process = await spawn('pnpm webpack --mode=development --watch') - await process.onStdout((m) => m.includes('compiled successfully in')) - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - .flex { - display: flex; - } - " - `) - - // Add a new Tailwind class to the HTML file - await fs.write('src/index.html', html` -
- `) - await process.onStdout((m) => m.includes('compiled successfully in')) - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - .flex { - display: flex; - } - .underline { - text-decoration-line: underline; - } - " - `) - }, -) - -test( - '@tailwindcss/webpack loader with @apply', - { - fs: { - 'package.json': json` - { - "main": "./src/index.js", - "browser": "./src/index.js", - "dependencies": { - "css-loader": "^6", - "webpack": "^5", - "webpack-cli": "^5", - "mini-css-extract-plugin": "^2", - "tailwindcss": "workspace:^", - "@tailwindcss/webpack": "workspace:^" - } - } - `, - 'webpack.config.js': js` - let MiniCssExtractPlugin = require('mini-css-extract-plugin') - - module.exports = { - output: { - clean: true, - }, - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /.css$/i, - use: [MiniCssExtractPlugin.loader, 'css-loader', '@tailwindcss/webpack'], - }, - ], - }, - } - `, - 'src/index.js': js`import './index.css'`, - 'src/index.css': css` - @import 'tailwindcss/theme'; - - .btn { - @apply flex items-center px-4 py-2 rounded-md; - } - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm webpack --mode=development') - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - :root, :host { - --spacing: 0.25rem; - --radius-md: 0.375rem; - } - .btn { - display: flex; - align-items: center; - border-radius: var(--radius-md); - padding-inline: calc(var(--spacing) * 4); - padding-block: calc(var(--spacing) * 2); - } - " - `) - }, -) - -test( - '@tailwindcss/webpack loader with optimization', - { - fs: { - 'package.json': json` - { - "main": "./src/index.js", - "browser": "./src/index.js", - "dependencies": { - "css-loader": "^6", - "webpack": "^5", - "webpack-cli": "^5", - "mini-css-extract-plugin": "^2", - "tailwindcss": "workspace:^", - "@tailwindcss/webpack": "workspace:^" - } - } - `, - 'webpack.config.js': js` - let MiniCssExtractPlugin = require('mini-css-extract-plugin') - - module.exports = { - output: { - clean: true, - }, - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /.css$/i, - use: [ - MiniCssExtractPlugin.loader, - 'css-loader', - { - loader: '@tailwindcss/webpack', - options: { - optimize: true, - }, - }, - ], - }, - ], - }, - } - `, - 'src/index.js': js`import './index.css'`, - 'src/index.html': html` -
- `, - 'src/index.css': css` - @import 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm webpack --mode=development') - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - .flex{display:flex} - " - `) - }, -) - -test( - '@tailwindcss/webpack loader with CSS @import', - { - fs: { - 'package.json': json` - { - "main": "./src/index.js", - "browser": "./src/index.js", - "dependencies": { - "css-loader": "^6", - "webpack": "^5", - "webpack-cli": "^5", - "mini-css-extract-plugin": "^2", - "tailwindcss": "workspace:^", - "@tailwindcss/webpack": "workspace:^" - } - } - `, - 'webpack.config.js': js` - let MiniCssExtractPlugin = require('mini-css-extract-plugin') - - module.exports = { - output: { - clean: true, - }, - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /.css$/i, - use: [MiniCssExtractPlugin.loader, 'css-loader', '@tailwindcss/webpack'], - }, - ], - }, - } - `, - 'src/index.js': js`import './index.css'`, - 'src/index.html': html` -
- `, - 'src/index.css': css` - @import './custom.css'; - @import 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - 'src/custom.css': css` - /**/ - @utility custom-util { - color: var(--color-red-500); - } - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm webpack --mode=development') - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - :root, :host { - --color-red-500: oklch(63.7% 0.237 25.331); - } - .flex { - display: flex; - } - .custom-util { - color: var(--color-red-500); - } - " - `) - }, -) - -test( - '@tailwindcss/webpack loader with @plugin', - { - fs: { - 'package.json': json` - { - "main": "./src/index.js", - "browser": "./src/index.js", - "dependencies": { - "css-loader": "^6", - "webpack": "^5", - "webpack-cli": "^5", - "mini-css-extract-plugin": "^2", - "tailwindcss": "workspace:^", - "@tailwindcss/webpack": "workspace:^" - } - } - `, - 'webpack.config.js': js` - let MiniCssExtractPlugin = require('mini-css-extract-plugin') - - module.exports = { - output: { - clean: true, - }, - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /.css$/i, - use: [MiniCssExtractPlugin.loader, 'css-loader', '@tailwindcss/webpack'], - }, - ], - }, - } - `, - 'src/index.js': js`import './index.css'`, - 'src/index.html': html` -
- `, - 'src/index.css': css` - @import 'tailwindcss/utilities'; - @plugin './plugin.js'; - `, - 'src/plugin.js': js` - export default function ({ addUtilities }) { - addUtilities({ - '.custom-underline': { - 'border-bottom': '1px solid green', - }, - }) - } - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm webpack --mode=development') - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - .custom-underline { - border-bottom: 1px solid green; - } - " - `) - }, -) - -test( - '@tailwindcss/webpack loader isolates cache by resource including query', - { - fs: { - 'package.json': json` - { - "main": "./src/index.js", - "browser": "./src/index.js", - "dependencies": { - "css-loader": "^6", - "webpack": "^5", - "webpack-cli": "^5", - "mini-css-extract-plugin": "^2", - "tailwindcss": "workspace:^", - "@tailwindcss/webpack": "workspace:^" - } - } - `, - 'webpack.config.js': js` - let MiniCssExtractPlugin = require('mini-css-extract-plugin') - let path = require('node:path') - - module.exports = { - mode: 'development', - entry: { - a: './src/a.js', - b: './src/b.js', - }, - output: { - clean: true, - }, - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /.css$/i, - use: [ - MiniCssExtractPlugin.loader, - 'css-loader', - '@tailwindcss/webpack', - path.resolve(__dirname, 'query-loader.js'), - ], - }, - ], - }, - } - `, - 'query-loader.js': js` - module.exports = function (source) { - if (this.resourceQuery.includes('a')) { - return '@import "tailwindcss/utilities"; @utility only-a {color: var(--color-red-500);}' - } - - if (this.resourceQuery.includes('b')) { - return '@import "tailwindcss/utilities"; @utility only-b {color: var(--color-blue-500);}' - } - - return source - } - `, - 'src/a.js': js`import './index.css?a'`, - 'src/b.js': js`import './index.css?b'`, - 'src/index.css': css``, - }, - }, - async ({ fs, exec }) => { - await exec('pnpm webpack --mode=development') - - await fs.expectFileToContain('dist/a.css', ['only-a', '--color-red-500']) - await fs.expectFileToContain('dist/b.css', ['only-b', '--color-blue-500']) - await fs.expectFileNotToContain('dist/a.css', ['only-b', '--color-blue-500']) - await fs.expectFileNotToContain('dist/b.css', ['only-a', '--color-red-500']) - }, -) diff --git a/integrations/oxide/workers.test.ts b/integrations/workers/index.test.ts similarity index 100% rename from integrations/oxide/workers.test.ts rename to integrations/workers/index.test.ts diff --git a/package.json b/package.json index 7d41a469e..9a2661360 100644 --- a/package.json +++ b/package.json @@ -48,17 +48,23 @@ }, "license": "MIT", "devDependencies": { - "@playwright/test": "^1.62.1", + "@playwright/test": "^1.51.0", "@types/node": "catalog:", - "postcss": "catalog:", - "postcss-import": "^16.1.1", + "postcss": "8.5.3", + "postcss-import": "^16.1.0", "prettier": "catalog:", - "prettier-plugin-embed": "^0.5.1", - "prettier-plugin-organize-imports": "^4.3.0", - "tsup": "^8.5.1", - "turbo": "^2.10.8", - "typescript": "^5.9.3", - "vitest": "^4.1.10" + "prettier-plugin-embed": "^0.5.0", + "prettier-plugin-organize-imports": "^4.0.0", + "tsup": "^8.4.0", + "turbo": "^2.4.4", + "typescript": "^5.5.4", + "vitest": "^2.0.5" }, - "packageManager": "pnpm@11.9.0" + "packageManager": "pnpm@9.6.0", + "pnpm": { + "patchedDependencies": { + "lightningcss@1.29.2": "patches/lightningcss@1.29.2.patch", + "@parcel/watcher@2.5.1": "patches/@parcel__watcher@2.5.1.patch" + } + } } diff --git a/packages/@tailwindcss-browser/README.md b/packages/@tailwindcss-browser/README.md index 5f532607d..95ec9d87d 100644 --- a/packages/@tailwindcss-browser/README.md +++ b/packages/@tailwindcss-browser/README.md @@ -13,10 +13,10 @@

- Build Status + Build Status Total Downloads - Latest Release - License + Latest Release + License

--- @@ -27,10 +27,14 @@ For full documentation, visit [tailwindcss.com](https://tailwindcss.com). ## Community -For help, discussion about best practices, or feature ideas: +For help, discussion about best practices, or any other conversation that would benefit from being searchable: -[Discuss Tailwind CSS on GitHub](https://github.com/tailwindlabs/tailwindcss/discussions) +[Discuss Tailwind CSS on GitHub](https://github.com/tailwindcss/tailwindcss/discussions) + +For chatting with others using the framework: + +[Join the Tailwind CSS Discord Server](https://discord.gg/7NF8GNe) ## Contributing -If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md) **before submitting a pull request**. +If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindcss/tailwindcss/blob/next/.github/CONTRIBUTING.md) **before submitting a pull request**. diff --git a/packages/@tailwindcss-browser/package.json b/packages/@tailwindcss-browser/package.json index 70af78c1e..96f80a684 100644 --- a/packages/@tailwindcss-browser/package.json +++ b/packages/@tailwindcss-browser/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/browser", - "version": "4.3.3", + "version": "4.0.15", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "main": "./dist/index.global.js", @@ -30,8 +30,8 @@ "access": "public" }, "devDependencies": { - "h3": "^1.15.11", - "listhen": "^1.10.1", + "h3": "^1.15.1", + "listhen": "^1.9.0", "tailwindcss": "workspace:*" } } diff --git a/packages/@tailwindcss-browser/playwright.config.ts b/packages/@tailwindcss-browser/playwright.config.ts index c6059dd82..c3ad05b91 100644 --- a/packages/@tailwindcss-browser/playwright.config.ts +++ b/packages/@tailwindcss-browser/playwright.config.ts @@ -14,7 +14,7 @@ export default defineConfig({ /* 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', { open: 'never' }]], + 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('/')`. */ @@ -30,11 +30,10 @@ export default defineConfig({ name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, - // Playwright 1.60's Windows WebKit worker hangs on shutdown after all tests - // pass, causing CI to fail with a worker force-kill error. - ...(process.platform === 'win32' - ? [] - : [{ name: 'webkit', use: { ...devices['Desktop Safari'] } }]), + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, { name: 'firefox', use: { diff --git a/packages/@tailwindcss-browser/src/index.ts b/packages/@tailwindcss-browser/src/index.ts index 49c7e4675..e7688a121 100644 --- a/packages/@tailwindcss-browser/src/index.ts +++ b/packages/@tailwindcss-browser/src/index.ts @@ -2,6 +2,12 @@ import * as tailwindcss from 'tailwindcss' import * as assets from './assets' import { Instrumentation } from './instrumentation' +// Warn users about using the browser build in production as early as possible. +// It can take time for the script to do its work so this must be at the top. +console.warn( + 'The browser build of Tailwind CSS should not be used in production. To use Tailwind CSS in production, use the Tailwind CLI, Vite plugin, or PostCSS plugin: https://tailwindcss.com/docs/installation', +) + /** * The type used by ` - // ``` - if ( - // Whitespace before the candidate - location.contents[location.start - 1]?.match(/\s/) && - // A colon followed by whitespace after the candidate - location.contents.slice(location.end, location.end + 2)?.match(/^:\s/) - ) { - // Compute all `` tag -// - All `` -// - No nested `', offset) - if (endTag === -1) return ranges - offset = endTag + 1 - - ranges.push(startTag, endTag) - } -}) - -const BACKSLASH = 0x5c -const DOUBLE_QUOTE = 0x22 -const SINGLE_QUOTE = 0x27 -const BACKTICK = 0x60 -const TAB = 0x09 -const NEWLINE = 0x0a -const FORM_FEED = 0x0c -const CARRIAGE_RETURN = 0x0d -const SPACE = 0x20 -const SLASH = 0x2f -const EQUALS = 0x3d -const GREATER_THAN = 0x3e - -function isMiddleOfString(line: string): boolean { - let currentQuote: number | null = null - - for (let i = 0; i < line.length; i++) { - let char = line.charCodeAt(i) - switch (char) { - // Escaped character, skip the next character - case BACKSLASH: - i++ - break - - case SINGLE_QUOTE: - case DOUBLE_QUOTE: - case BACKTICK: - // Found matching quote, we are outside of a string - if (currentQuote === char) { - currentQuote = null - } - - // Found a quote, we are inside a string - else if (currentQuote === null) { - currentQuote = char - } - break - } - } - - return currentQuote !== null -} - -const inlineStyleAttributeValueRanges = new DefaultMap((source: string) => { - let ranges: number[] = [] - let offset = 0 - - while (true) { - let tagStart = source.indexOf('<', offset) - if (tagStart === -1) return ranges - - let tagEnd = source.indexOf('>', tagStart + 1) - if (tagEnd === -1) return ranges - - offset = tagEnd + 1 - - for (let i = tagStart + 1; i < tagEnd; i++) { - let char = source.charCodeAt(i) - - if ( - char === SPACE || - char === TAB || - char === NEWLINE || - char === CARRIAGE_RETURN || - char === FORM_FEED - ) { - continue - } - - let start = i - while (i < tagEnd) { - let char = source.charCodeAt(i) - if ( - char === SPACE || - char === TAB || - char === NEWLINE || - char === CARRIAGE_RETURN || - char === FORM_FEED || - char === EQUALS || - char === GREATER_THAN || - char === SLASH - ) { - break - } - - i++ - } - - let attribute = source.slice(start, i).toLowerCase() - if (attribute !== 'style' && attribute !== ':style') continue - - while (i < tagEnd) { - let char = source.charCodeAt(i) - if ( - char !== SPACE && - char !== TAB && - char !== NEWLINE && - char !== CARRIAGE_RETURN && - char !== FORM_FEED - ) { - break - } - - i++ - } - - if (source[i] !== '=') continue - - i++ - while (i < tagEnd) { - let char = source.charCodeAt(i) - if ( - char !== SPACE && - char !== TAB && - char !== NEWLINE && - char !== CARRIAGE_RETURN && - char !== FORM_FEED - ) { - break - } - - i++ - } - - let quote = source[i] - if (quote !== '"' && quote !== "'") continue - - let valueStart = i + 1 - let valueEnd = source.indexOf(quote, valueStart) - if (valueEnd === -1 || valueEnd > tagEnd) break - - ranges.push(valueStart, valueEnd) - i = valueEnd - } - } -}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.ts deleted file mode 100644 index 258ba1b33..000000000 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { cloneCandidate } from '../../../../tailwindcss/src/candidate' -import { - PRE_COMPUTED_VARIANTS_KEY, - prepareDesignSystemStorage, - VARIANT_SIGNATURE_KEY, -} from '../../../../tailwindcss/src/canonicalize-candidates' -import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' -import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import type { Writable } from '../../../../tailwindcss/src/types' -import { replaceObject } from '../../../../tailwindcss/src/utils/replace-object' -import { walkVariants } from '../../utils/walk-variants' - -export function migrateArbitraryVariants( - baseDesignSystem: DesignSystem, - _userConfig: Config | null, - rawCandidate: string, -): string { - let designSystem = prepareDesignSystemStorage(baseDesignSystem) - let signatures = designSystem.storage[VARIANT_SIGNATURE_KEY].get(null) - let variants = designSystem.storage[PRE_COMPUTED_VARIANTS_KEY].get(null) - - for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) { - // We are only interested in the variants - if (readonlyCandidate.variants.length <= 0) return rawCandidate - - // The below logic makes use of mutation. Since candidates in the - // DesignSystem are cached, we can't mutate them directly. - let candidate = cloneCandidate(readonlyCandidate) as Writable - - for (let [variant] of walkVariants(candidate)) { - if (variant.kind === 'compound') continue - - let targetString = designSystem.printVariant(variant) - let targetSignature = signatures.get(targetString) - if (typeof targetSignature !== 'string') continue - - let foundVariants = variants.get(targetSignature) - if (foundVariants.length === 0) continue - - // The variant is already in a canonical form, e.g.: `min-lg` and `lg` - // produce the same CSS, but both are valid so we keep them as-is. - if (foundVariants.includes(targetString)) continue - - let foundVariant = foundVariants[0] - let parsedVariant = designSystem.parseVariant(foundVariant) - if (parsedVariant === null) continue - - replaceObject(variant, parsedVariant) - } - - return designSystem.printCandidate(candidate) - } - - return rawCandidate -} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-camelcase-in-named-value.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-camelcase-in-named-value.test.ts deleted file mode 100644 index ed5be23c0..000000000 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-camelcase-in-named-value.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { __unstable__loadDesignSystem } from '@tailwindcss/node' -import { expect, test, vi } from 'vitest' -import * as versions from '../../utils/version' -import { migrateCamelcaseInNamedValue } from './migrate-camelcase-in-named-value' -vi.spyOn(versions, 'isMajor').mockReturnValue(true) - -test.each([ - ['text-superRed', 'text-super-red'], - ['text-red/superOpaque', 'text-red/super-opaque'], - ['text-superRed/superOpaque', 'text-super-red/super-opaque'], - - // Should not migrate named values in modifiers - ['group-hover/superGroup:underline', 'group-hover/superGroup:underline'], - - ['hover:text-superRed', 'hover:text-super-red'], - ['hover:text-red/superOpaque', 'hover:text-red/super-opaque'], - ['hover:text-superRed/superOpaque', 'hover:text-super-red/super-opaque'], -])('%s => %s', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { - base: __dirname, - }) - - expect(migrateCamelcaseInNamedValue(designSystem, {}, candidate)).toEqual(result) -}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-camelcase-in-named-value.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-camelcase-in-named-value.ts deleted file mode 100644 index 9cfbc7de0..000000000 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-camelcase-in-named-value.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { cloneCandidate } from '../../../../tailwindcss/src/candidate' -import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' -import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import * as version from '../../utils/version' - -// Converts named values to use kebab-case. This is necessary because the -// upgrade tool also renames the theme values to kebab-case, so `text-superRed` -// will have its theme value renamed to `--color-super-red` and thus the utility -// will be renamed to `text-super-red`. -export function migrateCamelcaseInNamedValue( - designSystem: DesignSystem, - _userConfig: Config | null, - rawCandidate: string, -): string { - if (!version.isMajor(3)) return rawCandidate - - for (let candidate of designSystem.parseCandidate(rawCandidate)) { - if (candidate.kind !== 'functional') continue - let clone = cloneCandidate(candidate) - let didChange = false - - if ( - candidate.value && - clone.value && - candidate.value.kind === 'named' && - clone.value.kind === 'named' && - candidate.value.value.match(/[A-Z]/) - ) { - clone.value.value = camelToKebab(candidate.value.value) - didChange = true - } - - if ( - candidate.modifier && - clone.modifier && - candidate.modifier.kind === 'named' && - clone.modifier.kind === 'named' && - candidate.modifier.value.match(/[A-Z]/) - ) { - clone.modifier.value = camelToKebab(candidate.modifier.value) - didChange = true - } - - if (didChange) { - return designSystem.printCandidate(clone) - } - } - - return rawCandidate -} - -function camelToKebab(str: string): string { - return str.replace(/([A-Z])/g, '-$1').toLowerCase() -} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-canonicalize-candidate.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-canonicalize-candidate.test.ts deleted file mode 100644 index 57fe80c22..000000000 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-canonicalize-candidate.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { __unstable__loadDesignSystem } from '@tailwindcss/node' -import { expect, test, vi } from 'vitest' -import * as versions from '../../utils/version' -import { migrateCanonicalizeCandidate } from './migrate-canonicalize-candidate' -vi.spyOn(versions, 'isMajor').mockReturnValue(true) - -test.each([ - // Normalize whitespace in arbitrary properties - ['[display:flex]', '[display:flex]'], - ['[display:_flex]', '[display:flex]'], - ['[display:flex_]', '[display:flex]'], - ['[display:_flex_]', '[display:flex]'], - - // Normalize whitespace in `calc` expressions - ['w-[calc(100%-2rem)]', 'w-[calc(100%-2rem)]'], - ['w-[calc(100%_-_2rem)]', 'w-[calc(100%-2rem)]'], - - // Normalize the important modifier - ['!flex', 'flex!'], - ['flex!', 'flex!'], -])('%s => %s', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { - base: __dirname, - }) - - expect(migrateCanonicalizeCandidate(designSystem, {}, candidate)).toEqual(result) -}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-canonicalize-candidate.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-canonicalize-candidate.ts deleted file mode 100644 index 32d126467..000000000 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-canonicalize-candidate.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' -import type { DesignSystem } from '../../../../tailwindcss/src/design-system' - -// Canonicalize the value to its minimal form. This will normalize whitespace, -// and print the important modifier `!` in the correct place. -// -// E.g.: -// -// ``` -// [display:_flex_] => [display:flex] -// [display:_flex] => [display:flex] -// [display:flex_] => [display:flex] -// [display:flex] => [display:flex] -// ``` -// -export function migrateCanonicalizeCandidate( - designSystem: DesignSystem, - _userConfig: Config | null, - rawCandidate: string, -) { - for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) { - let canonicalizedCandidate = designSystem.printCandidate(readonlyCandidate) - if (canonicalizedCandidate !== rawCandidate) { - return canonicalizedCandidate - } - } - - return rawCandidate -} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.test.ts deleted file mode 100644 index 5b204386f..000000000 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { __unstable__loadDesignSystem } from '@tailwindcss/node' -import { expect, test, vi } from 'vitest' -import * as versions from '../../utils/version' -import { migrateLegacyClasses } from './migrate-legacy-classes' -vi.spyOn(versions, 'isMajor').mockReturnValue(true) - -test.each([ - ['shadow', 'shadow-sm'], - ['shadow-sm', 'shadow-xs'], - ['shadow-xs', 'shadow-2xs'], - - ['inset-shadow', 'inset-shadow-sm'], - ['inset-shadow-sm', 'inset-shadow-xs'], - ['inset-shadow-xs', 'inset-shadow-2xs'], - - ['drop-shadow', 'drop-shadow-sm'], - ['drop-shadow-sm', 'drop-shadow-xs'], - - ['rounded', 'rounded-sm'], - ['rounded-sm', 'rounded-xs'], - - ['blur', 'blur-sm'], - ['blur-sm', 'blur-xs'], - - ['backdrop-blur', 'backdrop-blur-sm'], - ['backdrop-blur-sm', 'backdrop-blur-xs'], - - ['ring', 'ring-3'], - - ['outline', 'outline-solid'], - - ['blur!', 'blur-sm!'], - ['hover:blur', 'hover:blur-sm'], - ['hover:blur!', 'hover:blur-sm!'], - - ['hover:blur-sm', 'hover:blur-xs'], - ['blur-sm!', 'blur-xs!'], - ['hover:blur-sm!', 'hover:blur-xs!'], -])('%s => %s (%#)', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { - base: __dirname, - }) - - expect(await migrateLegacyClasses(designSystem, {}, candidate)).toEqual(result) -}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.test.ts deleted file mode 100644 index b1887ae0b..000000000 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { __unstable__loadDesignSystem } from '@tailwindcss/node' -import { expect, test, vi } from 'vitest' -import type { UserConfig } from '../../../../tailwindcss/src/compat/config/types' -import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import * as versions from '../../utils/version' -import { migrateArbitraryVariants } from './migrate-arbitrary-variants' -import { migrateEmptyArbitraryValues } from './migrate-handle-empty-arbitrary-values' -import { migrateModernizeArbitraryValues } from './migrate-modernize-arbitrary-values' -import { migratePrefix } from './migrate-prefix' -vi.spyOn(versions, 'isMajor').mockReturnValue(true) - -const css = String.raw - -function migrate(designSystem: DesignSystem, userConfig: UserConfig | null, rawCandidate: string) { - for (let migration of [ - migrateEmptyArbitraryValues, - migratePrefix, - migrateModernizeArbitraryValues, - migrateArbitraryVariants, - (designSystem: DesignSystem, _: UserConfig | null, rawCandidate: string) => { - return designSystem.canonicalizeCandidates([rawCandidate]).pop() ?? rawCandidate - }, - ]) { - rawCandidate = migration(designSystem, userConfig, rawCandidate) - } - return rawCandidate -} - -test.each([ - // Some extreme examples of what happens in the wild: - ['group-[]:flex', 'in-[.group]:flex'], - ['group-[]/name:flex', 'in-[.group\\/name]:flex'], - - // Migrate `peer-[]` to a parsable `peer-[&]` instead: - ['peer-[]:flex', 'peer-[&]:flex'], - ['peer-[]/name:flex', 'peer-[&]/name:flex'], - - // These shouldn't happen in the real world (because compound variants are - // new). But this could happen once we allow codemods to run in v4+ projects. - ['has-group-[]:flex', 'has-in-[.group]:flex'], - ['has-group-[]/name:flex', 'has-in-[.group\\/name]:flex'], - ['not-group-[]:flex', 'not-in-[.group]:flex'], - ['not-group-[]/name:flex', 'not-in-[.group\\/name]:flex'], -])('%s => %s (%#)', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem( - css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - `, - { base: __dirname }, - ) - - expect(migrate(designSystem, {}, candidate)).toEqual(result) -}) - -test.each([ - // Should not prefix classes in arbitrary values - ['[.foo_&]:tw-flex', 'tw:in-[.foo]:flex'], - - // Should migrate `.group` classes - ['group-[]:tw-flex', 'tw:in-[.tw\\:group]:flex'], - ['group-[]/name:tw-flex', 'tw:in-[.tw\\:group\\/name]:flex'], - - // Migrate `peer-[]` to a parsable `peer-[&]` instead: - ['peer-[]:tw-flex', 'tw:peer-[&]:flex'], - ['peer-[]/name:tw-flex', 'tw:peer-[&]/name:flex'], - - // However, `.group` inside of an arbitrary variant should not be prefixed: - ['[.group_&]:tw-flex', 'tw:in-[.group]:flex'], - - // These shouldn't happen in the real world (because compound variants are - // new). But this could happen once we allow codemods to run in v4+ projects. - ['has-group-[]:tw-flex', 'tw:has-in-[.tw\\:group]:flex'], - ['has-group-[]/name:tw-flex', 'tw:has-in-[.tw\\:group\\/name]:flex'], - ['not-group-[]:tw-flex', 'tw:not-in-[.tw\\:group]:flex'], - ['not-group-[]/name:tw-flex', 'tw:not-in-[.tw\\:group\\/name]:flex'], -])('%s => %s (%#)', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem( - css` - @import 'tailwindcss' prefix(tw); - @theme { - --*: initial; - } - `, - { base: __dirname }, - ) - - expect(migrate(designSystem, { prefix: 'tw-' }, candidate)).toEqual(result) -}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts deleted file mode 100644 index 91718b323..000000000 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { cloneCandidate, parseCandidate } from '../../../../tailwindcss/src/candidate' -import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' -import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { replaceObject } from '../../../../tailwindcss/src/utils/replace-object' -import { walkVariants } from '../../utils/walk-variants' - -export function migrateModernizeArbitraryValues( - designSystem: DesignSystem, - _userConfig: Config | null, - rawCandidate: string, -): string { - for (let candidate of parseCandidate(rawCandidate, designSystem)) { - let clone = cloneCandidate(candidate) - let changed = false - - for (let [variant] of walkVariants(clone)) { - // Forward modifier from the root to the compound variant - if ( - variant.kind === 'compound' && - (variant.root === 'has' || variant.root === 'not' || variant.root === 'in') - ) { - if (variant.modifier !== null) { - if ('modifier' in variant.variant) { - variant.variant.modifier = variant.modifier - variant.modifier = null - } - } - } - - // Promote `group-[]:flex` to `in-[.group]:flex` - // ^^ Yes, this is empty - // Promote `group-[]/name:flex` to `in-[.group\/name]:flex` - if ( - variant.kind === 'compound' && - variant.root === 'group' && - variant.variant.kind === 'arbitrary' && - variant.variant.selector === '&' - ) { - // `group-[]` - if (variant.modifier === null) { - changed = true - replaceObject( - variant, - designSystem.parseVariant( - designSystem.theme.prefix - ? `in-[.${designSystem.theme.prefix}\\:group]` - : 'in-[.group]', - ), - ) - } - - // `group-[]/name` - else if (variant.modifier.kind === 'named') { - changed = true - replaceObject( - variant, - designSystem.parseVariant( - designSystem.theme.prefix - ? `in-[.${designSystem.theme.prefix}\\:group\\/${variant.modifier.value}]` - : `in-[.group\\/${variant.modifier.value}]`, - ), - ) - } - continue - } - } - - return changed ? designSystem.printCandidate(clone) : rawCandidate - } - - return rawCandidate -} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate.test.ts deleted file mode 100644 index 24bb3d5ef..000000000 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { __unstable__loadDesignSystem } from '@tailwindcss/node' -import { describe, expect, test, vi } from 'vitest' -import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' -import * as versions from '../../utils/version' -import { migrateCandidate as migrate } from './migrate' -vi.spyOn(versions, 'isMajor').mockReturnValue(false) - -const designSystems = new DefaultMap((base: string) => { - return new DefaultMap((input: string) => { - return __unstable__loadDesignSystem(input, { base }) - }) -}) - -const css = String.raw - -describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', (strategy) => { - let testName = '%s => %s (%#)' - if (strategy === 'with-variant') { - testName = testName.replaceAll('%s', 'focus:%s') - } else if (strategy === 'important') { - testName = testName.replaceAll('%s', '%s!') - } else if (strategy === 'prefix') { - testName = testName.replaceAll('%s', 'tw:%s') - } - - // Basic input with minimal design system to keep the tests fast - let input = css` - @import 'tailwindcss' ${strategy === 'prefix' ? 'prefix(tw)' : ''}; - @theme { - --*: initial; - --spacing: 0.25rem; - --color-red-500: red; - - /* Equivalent of blue-500/50 */ - --color-primary: color-mix(in oklab, oklch(62.3% 0.214 259.815) 50%, transparent); - } - ` - - test.each([ - // Arbitrary property to named functional utility - ['[color:red]', 'text-red-500'], - - // Promote data types to more specific utility if it exists - ['bg-(position:--my-value)', 'bg-position-(--my-value)'], - - // Promote inferred data type to more specific utility if it exists - ['bg-[123px]', 'bg-position-[123px]'], - - // Do not migrate bare values or arbitrary values to named values that are - // deprecated - ['order-[0]', 'order-0'], - ['order-0', 'order-0'], - - // Migrate deprecated named values to bare values - ['order-none', 'order-0'], - - // `-0` should not be migrated to `0`. - // - // This used to be a bug that translate `mt-[0px]` into `-mt-[0px]` because - // `-mt-[0px]` translates to `margin-top: calc(0px * -1);` and therefore we - // handle the `0px * -1` case which translates to `0px` not `-0px`. - // - // This translation is actually fine, because now, we will prefer the - // non-negative version first so we can replace `-mt-[0px]` with `mt-0`. - ['mt-[0px]', 'mt-0'], - ['-mt-[0px]', 'mt-0'], - - // Shorthand CSS Variables should be converted to the new syntax, even if - // the fallback contains functions. The fallback should also be migrated to - // the newest syntax. - ['bg-[--my-color,theme(colors.red.500)]', 'bg-(--my-color,var(--color-red-500))'], - - // Both the positive and negative versions of arbitrary utilities should be - // converted to the bare value version. - ['mb-[32rem]', 'mb-128'], - ['mb-[-32rem]', '-mb-128'], - ])(testName, async (candidate, result) => { - if (strategy === 'with-variant') { - candidate = `focus:${candidate}` - result = `focus:${result}` - } else if (strategy === 'important') { - candidate = `${candidate}!` - result = `${result}!` - } else if (strategy === 'prefix') { - // Not only do we need to prefix the candidate, we also have to make - // sure that we prefix all CSS variables. - candidate = `tw:${candidate.replaceAll('var(--', 'var(--tw-')}` - result = `tw:${result.replaceAll('var(--', 'var(--tw-')}` - } - - let designSystem = await designSystems.get(__dirname).get(input) - let migrated = await migrate(designSystem, {}, candidate) - expect(migrated).toEqual(result) - }) - - test.each([ - ['order-[0]', 'order-0'], - ['order-0', 'order-0'], - - // Do not migrate `order-none` if defined as a custom utility as it is then - // not safe to migrate to `order-0` - ['order-none', 'order-none'], - ])(`${testName} with custom implementations`, async (candidate, result) => { - if (strategy === 'with-variant') { - candidate = `focus:${candidate}` - result = `focus:${result}` - } else if (strategy === 'important') { - candidate = `${candidate}!` - result = `${result}!` - } else if (strategy === 'prefix') { - // Not only do we need to prefix the candidate, we also have to make - // sure that we prefix all CSS variables. - candidate = `tw:${candidate.replaceAll('var(--', 'var(--tw-')}` - result = `tw:${result.replaceAll('var(--', 'var(--tw-')}` - } - - let localInput = css` - ${input} - - @utility order-none { - order: none; /* imagine this exists */ - } - ` - - let designSystem = await designSystems.get(__dirname).get(localInput) - let migrated = await migrate(designSystem, {}, candidate) - expect(migrated).toEqual(result) - }) -}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts deleted file mode 100644 index 976c63a83..000000000 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts +++ /dev/null @@ -1,137 +0,0 @@ -import fs from 'node:fs/promises' -import path, { extname } from 'node:path' -import { - createSignatureOptions, - prepareDesignSystemStorage, - UTILITY_SIGNATURE_KEY, -} from '../../../../tailwindcss/src/canonicalize-candidates' -import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' -import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' -import { spliceChangesIntoString, type StringChange } from '../../utils/splice-changes-into-string' -import { writeFileSafely } from '../../utils/write-file-safely' -import { extractRawCandidates } from './candidates' -import { isSafeMigration } from './is-safe-migration' -import { migrateAutomaticVarInjection } from './migrate-automatic-var-injection' -import { migrateCamelcaseInNamedValue } from './migrate-camelcase-in-named-value' -import { migrateCanonicalizeCandidate } from './migrate-canonicalize-candidate' -import { migrateEmptyArbitraryValues } from './migrate-handle-empty-arbitrary-values' -import { migrateLegacyArbitraryValues } from './migrate-legacy-arbitrary-values' -import { migrateLegacyClasses } from './migrate-legacy-classes' -import { migrateMaxWidthScreen } from './migrate-max-width-screen' -import { migrateModernizeArbitraryValues } from './migrate-modernize-arbitrary-values' -import { migratePrefix } from './migrate-prefix' -import { migrateSimpleLegacyClasses } from './migrate-simple-legacy-classes' -import { migrateVariantOrder } from './migrate-variant-order' - -export type Migration = ( - designSystem: DesignSystem, - userConfig: Config | null, - rawCandidate: string, -) => string | Promise - -export const DEFAULT_MIGRATIONS: Migration[] = [ - migrateEmptyArbitraryValues, - migratePrefix, - migrateCanonicalizeCandidate, - migrateSimpleLegacyClasses, - migrateCamelcaseInNamedValue, - migrateLegacyClasses, - migrateMaxWidthScreen, - migrateVariantOrder, // Has to happen before migrations that modify variants - migrateAutomaticVarInjection, - migrateLegacyArbitraryValues, - migrateModernizeArbitraryValues, -] - -let migrateCached = new DefaultMap((baseDesignSystem: DesignSystem) => { - let designSystem = prepareDesignSystemStorage(baseDesignSystem) - let options = createSignatureOptions(designSystem) - - return new DefaultMap((userConfig: Config | null) => { - return new DefaultMap(async (rawCandidate) => { - let original = rawCandidate - - for (let migration of DEFAULT_MIGRATIONS) { - rawCandidate = await migration(designSystem, userConfig, rawCandidate) - } - - // Canonicalize the final migrated candidate to its final form - rawCandidate = designSystem.canonicalizeCandidates([rawCandidate]).pop()! - - // Verify that the candidate actually makes sense at all. E.g.: `duration` - // is not a valid candidate, but it will parse because `duration-` - // exists. - let signature = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options).get(rawCandidate) - if (typeof signature !== 'string') return original - - return rawCandidate - }) - }) -}) - -export async function migrateCandidate( - designSystem: DesignSystem, - userConfig: Config | null, - rawCandidate: string, - // Location is only set when migrating a candidate from a source file - location?: { - contents: string - start: number - end: number - }, -): Promise { - // Skip this migration if we think that the migration is unsafe - if (location && !isSafeMigration(rawCandidate, location, designSystem)) { - return rawCandidate - } - - return migrateCached.get(designSystem).get(userConfig).get(rawCandidate) -} - -export default async function migrateContents( - designSystem: DesignSystem, - userConfig: Config | null, - contents: string, - extension: string, -): Promise { - let candidates = await extractRawCandidates(contents, extension) - - let changes: StringChange[] = [] - - for (let { rawCandidate, start, end } of candidates) { - let migratedCandidate = await migrateCandidate(designSystem, userConfig, rawCandidate, { - contents, - start, - end, - }) - - if (migratedCandidate === rawCandidate) { - continue - } - - changes.push({ - start, - end, - replacement: migratedCandidate, - }) - } - - return spliceChangesIntoString(contents, changes) -} - -export async function migrate( - designSystem: DesignSystem, - userConfig: Config | null, - file: string, -): Promise { - let fullPath = path.isAbsolute(file) ? file : path.resolve(process.cwd(), file) - let contents = await fs.readFile(fullPath, 'utf-8') - - let migrated = await migrateContents(designSystem, userConfig, contents, extname(file)) - if (migrated === contents) return false // Nothing changed - if (migrated.trim() === '') return false // Emptied out, something went horribly wrong - - await writeFileSafely(fullPath, migrated) - return true -} diff --git a/packages/@tailwindcss-upgrade/src/index.test.ts b/packages/@tailwindcss-upgrade/src/index.test.ts index 2eae32f30..65d74937b 100644 --- a/packages/@tailwindcss-upgrade/src/index.test.ts +++ b/packages/@tailwindcss-upgrade/src/index.test.ts @@ -2,12 +2,10 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import dedent from 'dedent' import path from 'node:path' import postcss from 'postcss' -import { expect, it, vi } from 'vitest' -import { formatNodes } from './codemods/css/format-nodes' -import { migrateContents } from './codemods/css/migrate' -import { sortBuckets } from './codemods/css/sort-buckets' -import * as versions from './utils/version' -vi.spyOn(versions, 'isMajor').mockReturnValue(true) +import { expect, it } from 'vitest' +import { formatNodes } from './codemods/format-nodes' +import { sortBuckets } from './codemods/sort-buckets' +import { migrateContents } from './migrate' const css = dedent @@ -99,7 +97,7 @@ it('should migrate a stylesheet', async () => { "@import 'tailwindcss'; /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -112,7 +110,7 @@ it('should migrate a stylesheet', async () => { ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } @@ -170,7 +168,7 @@ it('should migrate a stylesheet (with imports)', async () => { @import './my-utilities.css' layer(utilities); /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -183,7 +181,7 @@ it('should migrate a stylesheet (with imports)', async () => { ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } }" `) @@ -212,7 +210,7 @@ it('should migrate a stylesheet (with preceding rules that should be wrapped in @import 'tailwindcss'; /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, + The default border color has changed to \`currentColor\` in Tailwind CSS v4, so we've added these compatibility styles to make sure everything still looks the same as it did with Tailwind CSS v3. @@ -225,7 +223,7 @@ it('should migrate a stylesheet (with preceding rules that should be wrapped in ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-gray-200, currentColor); } } diff --git a/packages/@tailwindcss-upgrade/src/index.ts b/packages/@tailwindcss-upgrade/src/index.ts index d304bbe1f..9069b4ea8 100644 --- a/packages/@tailwindcss-upgrade/src/index.ts +++ b/packages/@tailwindcss-upgrade/src/index.ts @@ -1,29 +1,30 @@ #!/usr/bin/env node -import { Scanner } from '@tailwindcss/oxide' -import { globby, isGitIgnored } from 'globby' +import { globby } from 'globby' import fs from 'node:fs/promises' import path from 'node:path' -import pc from 'picocolors' import postcss from 'postcss' -import { migrateJsConfig } from './codemods/config/migrate-js-config' -import { migratePostCSSConfig } from './codemods/config/migrate-postcss' -import { analyze as analyzeStylesheets } from './codemods/css/analyze' -import { formatNodes } from './codemods/css/format-nodes' -import { linkConfigs as linkConfigsToStylesheets } from './codemods/css/link' -import { migrate as migrateStylesheet } from './codemods/css/migrate' -import { sortBuckets } from './codemods/css/sort-buckets' -import { split as splitStylesheets } from './codemods/css/split' -import { migrate as migrateTemplate } from './codemods/template/migrate' -import { prepareConfig } from './codemods/template/prepare-config' +import { formatNodes } from './codemods/format-nodes' +import { sortBuckets } from './codemods/sort-buckets' import { help } from './commands/help' +import { + analyze as analyzeStylesheets, + linkConfigs as linkConfigsToStylesheets, + migrate as migrateStylesheet, + split as splitStylesheets, +} from './migrate' +import { migrateJsConfig } from './migrate-js-config' +import { migratePostCSSConfig } from './migrate-postcss' +import { migratePrettierPlugin } from './migrate-prettier' import { Stylesheet } from './stylesheet' +import { migrate as migrateTemplate } from './template/migrate' +import { prepareConfig } from './template/prepare-config' import { args, type Arg } from './utils/args' -import { gitRoot, isRepoDirty } from './utils/git' +import { isRepoDirty } from './utils/git' +import { hoistStaticGlobParts } from './utils/hoist-static-glob-parts' +import { getPackageVersion } from './utils/package-version' import { pkg } from './utils/packages' import { eprintln, error, header, highlight, info, relative, success } from './utils/renderer' -import * as version from './utils/version' -import { writeFileSafely } from './utils/write-file-safely' const options = { '--config': { type: 'string', description: 'Path to the configuration file', alias: '-c' }, @@ -43,9 +44,6 @@ if (flags['--help']) { async function run() { let base = process.cwd() - // Use the project's git root, instead of the `base` to ensure all - // `.gitignore` rules are being used. - let isIgnored = await isGitIgnored({ cwd: gitRoot(base) ?? base }) eprintln(header()) eprintln() @@ -63,27 +61,11 @@ async function run() { } } - info(`Upgrading from Tailwind CSS ${highlight(`v${version.installedTailwindVersion(base)}`)}`, { - prefix: '↳ ', - }) - - if (version.installedTailwindVersion(base) !== version.expectedTailwindVersion(base)) { - let pkgManager = await pkg(base).manager() - + // Require an installed `tailwindcss` version < 4 + let tailwindVersion = await getPackageVersion('tailwindcss', base) + if (tailwindVersion && Number(tailwindVersion.split('.')[0]) !== 3) { error( - [ - 'Version mismatch', - '', - pc.dim('```diff'), - `${pc.red('-')} ${`${pc.dim('"tailwindcss":')} ${`${pc.dim('"')}${pc.blue(version.expectedTailwindVersion(base))}${pc.dim('"')}`}`} (expected version in ${highlight('package.json')})`, - `${pc.green('+')} ${`${pc.dim('"tailwindcss":')} ${`${pc.dim('"')}${pc.blue(version.installedTailwindVersion(base))}${pc.dim('"')}`}`} (installed version in ${highlight('node_modules')})`, - pc.dim('```'), - '', - `Make sure to run ${highlight(`${pkgManager} install`)} and try again.`, - ].join('\n'), - { - prefix: '↳ ', - }, + `Tailwind CSS v${tailwindVersion} found. The migration tool can only be run on v3 projects.`, ) process.exit(1) } @@ -99,11 +81,8 @@ async function run() { info('Searching for CSS files in the current directory and its subdirectories…') files = await globby(['**/*.css'], { - cwd: base, absolute: true, gitignore: true, - // gitignore: true will first search for all .gitignore including node_modules folders, this makes the initial search much faster - ignore: ['**/node_modules/**'], }) } @@ -123,30 +102,26 @@ async function run() { let stylesheets = loadResults .filter((result) => result.status === 'fulfilled') .map((result) => result.value) - let originals = new Map(stylesheets.map((sheet) => [sheet, sheet.root.toString()])) // Analyze the stylesheets try { - await analyzeStylesheets(stylesheets, { base }) + await analyzeStylesheets(stylesheets) } catch (e: any) { error(`${e?.message ?? e}`, { prefix: '↳ ' }) } - // Ensure stylesheets are linked to configs. But this is only necessary when - // migrating from v3 to v4. - if (version.isMajor(3)) { - try { - await linkConfigsToStylesheets(stylesheets, { - configPath: flags['--config'], - base, - }) - } catch (e: any) { - error(`${e?.message ?? e}`, { prefix: '↳ ' }) - } + // Ensure stylesheets are linked to configs + try { + await linkConfigsToStylesheets(stylesheets, { + configPath: flags['--config'], + base, + }) + } catch (e: any) { + error(`${e?.message ?? e}`, { prefix: '↳ ' }) } // Migrate js config files, linked to stylesheets - if (stylesheets.some((sheet) => sheet.isTailwindRoot && sheet.linkedConfigPath)) { + if (stylesheets.some((sheet) => sheet.isTailwindRoot)) { info('Migrating JavaScript configuration files…') } let configBySheet = new Map>>() @@ -156,7 +131,6 @@ async function run() { >() for (let sheet of stylesheets) { if (!sheet.isTailwindRoot) continue - if (!version.isMajor(3) && !sheet.linkedConfigPath) continue let config = await prepareConfig(sheet.linkedConfigPath, { base }) configBySheet.set(sheet, config) @@ -181,6 +155,41 @@ async function run() { } } + // Migrate source files, linked to config files + if (configBySheet.size > 0) { + info('Migrating templates…') + } + { + // Template migrations + for (let config of configBySheet.values()) { + let set = new Set() + for (let globEntry of config.globs.flatMap((entry) => hoistStaticGlobParts(entry))) { + let files = await globby([globEntry.pattern], { + absolute: true, + gitignore: true, + cwd: globEntry.base, + }) + + for (let file of files) { + set.add(file) + } + } + + let files = Array.from(set) + files.sort() + + // Migrate each file + await Promise.allSettled( + files.map((file) => migrateTemplate(config.designSystem, config.userConfig, file)), + ) + + success( + `Migrated templates for configuration file: ${highlight(relative(config.configFilePath, base))}`, + { prefix: '↳ ' }, + ) + } + } + // Migrate each CSS file if (stylesheets.length > 0) { info('Migrating stylesheets…') @@ -188,33 +197,20 @@ async function run() { await Promise.all( stylesheets.map(async (sheet) => { try { - let config = configBySheet.get(sheet) - let jsConfigMigration = jsConfigMigrationBySheet.get(sheet) ?? null + let config = configBySheet.get(sheet)! + let jsConfigMigration = jsConfigMigrationBySheet.get(sheet)! if (!config) { for (let parent of sheet.ancestors()) { if (parent.isTailwindRoot) { config ??= configBySheet.get(parent)! - jsConfigMigration ??= jsConfigMigrationBySheet.get(parent) ?? null + jsConfigMigration ??= jsConfigMigrationBySheet.get(parent)! break } } } - // Prime the compiler/designSystem cache against the pre-migration AST - // so the later template-migration phase sees the same v4 state. After - // this point, `sheet.root` may be mutated by `migrateStylesheet`. - if (!version.isMajor(3) && sheet.isTailwindRoot) { - await Promise.all([sheet.compiler(), sheet.designSystem()]) - } - - await migrateStylesheet(sheet, { - newPrefix: config?.newPrefix ?? null, - designSystem: config?.designSystem ?? (await sheet.designSystem()), - userConfig: config?.userConfig ?? null, - configFilePath: config?.configFilePath ?? null, - jsConfigMigration, - }) + await migrateStylesheet(sheet, { ...config, jsConfigMigration }) } catch (e: any) { error(`${e?.message ?? e} in ${highlight(relative(sheet.file!, base))}`, { prefix: '↳ ' }) } @@ -222,39 +218,36 @@ async function run() { ) // Split up stylesheets (as needed) - if (version.isMajor(3)) { - try { - await splitStylesheets(stylesheets) - } catch (e: any) { - error(`${e?.message ?? e}`, { prefix: '↳ ' }) - } + try { + await splitStylesheets(stylesheets) + } catch (e: any) { + error(`${e?.message ?? e}`, { prefix: '↳ ' }) + } - // Cleanup `@import "…" layer(utilities)` - for (let sheet of stylesheets) { - for (let importRule of sheet.importRules) { - if (!importRule.raws.tailwind_injected_layer) continue - let importedSheet = stylesheets.find( - (sheet) => sheet.id === importRule.raws.tailwind_destination_sheet_id, - ) - if (!importedSheet) continue + // Cleanup `@import "…" layer(utilities)` + for (let sheet of stylesheets) { + for (let importRule of sheet.importRules) { + if (!importRule.raws.tailwind_injected_layer) continue + let importedSheet = stylesheets.find( + (sheet) => sheet.id === importRule.raws.tailwind_destination_sheet_id, + ) + if (!importedSheet) continue - // Only remove the `layer(…)` next to the import if any of the children - // contain `@utility`. Otherwise `@utility` will not be top-level. - if ( - !importedSheet.containsRule((node) => node.type === 'atrule' && node.name === 'utility') - ) { - continue - } - - // Make sure to remove the `layer(…)` from the `@import` at-rule - importRule.params = importRule.params.replace(/ layer\([^)]+\)/, '').trim() + // Only remove the `layer(…)` next to the import if any of the children + // contain `@utility`. Otherwise `@utility` will not be top-level. + if ( + !importedSheet.containsRule((node) => node.type === 'atrule' && node.name === 'utility') + ) { + continue } + + // Make sure to remove the `layer(…)` from the `@import` at-rule + importRule.params = importRule.params.replace(/ layer\([^)]+\)/, '').trim() } } // Format nodes for (let sheet of stylesheets) { - if (originals.get(sheet) === sheet.root.toString()) continue await postcss([sortBuckets(), formatNodes()]).process(sheet.root!, { from: sheet.file! }) } @@ -262,186 +255,31 @@ async function run() { for (let sheet of stylesheets) { if (!sheet.file) continue - await writeFileSafely(sheet.file, sheet.root.toString()) + await fs.writeFile(sheet.file, sheet.root.toString()) if (sheet.isTailwindRoot) { success(`Migrated stylesheet: ${highlight(relative(sheet.file, base))}`, { prefix: '↳ ' }) } } - - info('Updating dependencies…') - { - let pkgManager = pkg(base) - let dependencies = [ - 'tailwindcss', - '@tailwindcss/cli', - '@tailwindcss/postcss', - '@tailwindcss/vite', - '@tailwindcss/node', - '@tailwindcss/oxide', - 'prettier-plugin-tailwindcss', - ].filter((dependency) => dependency === 'tailwindcss' || pkgManager.has(dependency)) - try { - await pkgManager.add(dependencies.map((dependency) => `${dependency}@latest`)) - for (let dependency of dependencies) { - success(`Updated package: ${highlight(dependency)}`, { prefix: '↳ ' }) - } - } catch {} - } - - let tailwindRootStylesheets = stylesheets.filter((sheet) => sheet.isTailwindRoot && sheet.file) - - // Migrate source files - if (tailwindRootStylesheets.length > 0) { - info('Migrating templates…') - } - { - let seenFiles = new Set() - - // Template migrations - for (let sheet of tailwindRootStylesheets) { - let compiler = await sheet.compiler() - if (!compiler) continue - let designSystem = await sheet.designSystem() - if (!designSystem) continue - - let config = configBySheet.get(sheet) - - // Figure out the source files to migrate - let sources = (() => { - // Disable auto source detection - if (compiler.root === 'none') { - return [] - } - - // No root specified - if (compiler.root === null) { - // When coming from Tailwind CSS v3, we have to use the - // `config.sources` (which came from `config.content` originally) - if (version.isMajor(3)) { - if (config?.sources) { - return config.sources.map((source) => ({ ...source, negated: false })) - } - - // When we don't have any sources, then we have to fallback to no - // sources at all. We cannot fallback to the `**/*` pattern. - return [] - } - - // When we are upgrading a Tailwind CSS v4 and up version, we use - // the default `**/*` pattern. All custom `@source` directives will - // be attached later as sources. - return [{ base, pattern: '**/*', negated: false }] - } - - // Use the specified root - return [{ ...compiler.root, negated: false }] - })().concat(compiler.sources) - let scanner = new Scanner({ sources }) - let filesToMigrate = [] - - let ignoredPaths = new Set() - - for (let file of scanner.files) { - file = await fs.realpath(file).catch(() => file) // Ensure we are dealing with the real path, not symlinks - if (file.endsWith('.css')) continue - - // When a file is git ignored, then we don't want to migrate it even - // if it was listed in the `config.content` array or part of any - // `@source` directives. - // - // We can make this an option later to explicitly allow this, but - // this should be the default. This guarantees that: - // - // 1. Files coming from node_modules aren't touched - // 2. Generated files aren't changed (the source should update, not the target) - // 3. You can see all the changes that happened - try { - if (isIgnored(file)) { - let culprit = file - - // To prevent print all ignored files, we can also walk up the - // parent tree and log those instead _if_ they are: - // - // 1. Also git ignored - // 2. Are not going outside of the current repo - let parent = path.dirname(file) - do { - try { - if (isIgnored(parent)) { - culprit = parent - } - } catch { - // Escaping the current repo - break - } - - let nextParent = path.dirname(parent) - if (nextParent === parent) break - parent = nextParent - } while (parent) - - if (ignoredPaths.has(culprit)) continue // Already logged, skip - ignoredPaths.add(culprit) - - if (culprit === file) { - info(`Git ignored, skipping: ${highlight(relative(culprit, base))}`, { - prefix: '↳ ', - }) - } else { - info(`Git ignored folder, skipping: ${highlight(relative(culprit, base))}`, { - prefix: '↳ ', - }) - } - - continue - } - } catch (err) { - info(`Outside repository, skipping: ${highlight(relative(file, base))}`, { - prefix: '↳ ', - }) - // Skip this file when we run into errors. E.g.: when the current - // file is not part of the current git repo it will throw an error. - continue - } - - if (seenFiles.has(file)) continue - seenFiles.add(file) - filesToMigrate.push(file) - } - - // Migrate each file - let changes = 0 - await Promise.allSettled( - filesToMigrate.map(async (file) => { - let changed = await migrateTemplate(designSystem, config?.userConfig ?? null, file) - if (changed) { - changes++ - info(`Migrated ${highlight(relative(file, base))}`, { prefix: '↳ ' }) - } - }), - ) - - if (config?.configFilePath) { - success( - `Migrated templates for configuration file: ${highlight(relative(config.configFilePath, base))} (${changes} file${changes === 1 ? '' : 's'} changed)`, - { prefix: '↳ ' }, - ) - } else { - success( - `Migrated templates for: ${highlight(relative(sheet.file ?? '', base))} (${changes} file${changes === 1 ? '' : 's'} changed)`, - { prefix: '↳ ' }, - ) - } - } - } } - if (version.isMajor(3)) { + { // PostCSS config migration await migratePostCSSConfig(base) } + info('Updating dependencies…') + { + // Migrate the prettier plugin to the latest version + await migratePrettierPlugin(base) + } + + try { + // Upgrade Tailwind CSS + await pkg(base).add(['tailwindcss@latest']) + success(`Updated package: ${highlight('tailwindcss')}`, { prefix: '↳ ' }) + } catch {} + // Run all cleanup functions because we completed the migration await Promise.allSettled(cleanup.map((fn) => fn())) diff --git a/packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts b/packages/@tailwindcss-upgrade/src/migrate-js-config.ts similarity index 63% rename from packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts rename to packages/@tailwindcss-upgrade/src/migrate-js-config.ts index 6a451333e..ac0a71a15 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts +++ b/packages/@tailwindcss-upgrade/src/migrate-js-config.ts @@ -2,31 +2,27 @@ import { Scanner } from '@tailwindcss/oxide' import fs from 'node:fs/promises' import path from 'node:path' import { fileURLToPath } from 'node:url' -import { loadModule } from '../../../../@tailwindcss-node/src/compile' -import defaultTheme from '../../../../tailwindcss/dist/default-theme' -import { atRule, toCss, type AstNode } from '../../../../tailwindcss/src/ast' +import { loadModule } from '../../@tailwindcss-node/src/compile' +import defaultTheme from '../../tailwindcss/dist/default-theme' +import { atRule, toCss, type AstNode } from '../../tailwindcss/src/ast' import { keyPathToCssProperty, themeableValues, -} from '../../../../tailwindcss/src/compat/apply-config-to-theme' -import { keyframesToRules } from '../../../../tailwindcss/src/compat/apply-keyframes-to-theme' -import { - resolveConfig, - type ConfigFile, -} from '../../../../tailwindcss/src/compat/config/resolve-config' -import type { ResolvedConfig, ThemeConfig } from '../../../../tailwindcss/src/compat/config/types' -import { buildCustomContainerUtilityRules } from '../../../../tailwindcss/src/compat/container' -import { darkModePlugin } from '../../../../tailwindcss/src/compat/dark-mode' -import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' -import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { escape } from '../../../../tailwindcss/src/utils/escape' +} from '../../tailwindcss/src/compat/apply-config-to-theme' +import { keyframesToRules } from '../../tailwindcss/src/compat/apply-keyframes-to-theme' +import { resolveConfig, type ConfigFile } from '../../tailwindcss/src/compat/config/resolve-config' +import type { ResolvedConfig, ThemeConfig } from '../../tailwindcss/src/compat/config/types' +import { buildCustomContainerUtilityRules } from '../../tailwindcss/src/compat/container' +import { darkModePlugin } from '../../tailwindcss/src/compat/dark-mode' +import type { Config } from '../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../tailwindcss/src/design-system' +import { escape } from '../../tailwindcss/src/utils/escape' import { isValidOpacityValue, isValidSpacingMultiplier, -} from '../../../../tailwindcss/src/utils/infer-data-type' -import * as ValueParser from '../../../../tailwindcss/src/value-parser' -import { findStaticPlugins, type StaticPluginOptions } from '../../utils/extract-static-plugins' -import { highlight, info, relative } from '../../utils/renderer' +} from '../../tailwindcss/src/utils/infer-data-type' +import { findStaticPlugins, type StaticPluginOptions } from './utils/extract-static-plugins' +import { highlight, info, relative } from './utils/renderer' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) @@ -98,14 +94,12 @@ async function migrateTheme( designSystem: DesignSystem, unresolvedConfig: Config, base: string, -): Promise { +): Promise { // Resolve the config file without applying plugins and presets, as these are // migrated to CSS separately. let configToResolve: ConfigFile = { base, config: { ...unresolvedConfig, plugins: [], presets: undefined }, - reference: false, - src: undefined, } let { resolvedConfig, replacedThemeKeys } = resolveConfig(designSystem, [configToResolve]) @@ -115,88 +109,10 @@ async function migrateTheme( removeUnnecessarySpacingKeys(designSystem, resolvedConfig, replacedThemeKeys) - let css = '' let prevSectionKey = '' - let themeSection: string[] = [] - let keyframesCss = '' - let variants = new Map() - - // Special handling of specific theme keys: - { - if ('keyframes' in resolvedConfig.theme) { - keyframesCss += keyframesToCss(resolvedConfig.theme.keyframes) - delete resolvedConfig.theme.keyframes - } - - if ('container' in resolvedConfig.theme) { - let rules = buildCustomContainerUtilityRules(resolvedConfig.theme.container, designSystem) - if (rules.length > 0) { - // Using `theme` instead of `utility` so it sits before the `@layer - // base` with compatibility CSS. While this is technically a utility, it - // makes a bit more sense to emit this closer to the `@theme` values - // since it is needed for backwards compatibility. - css += `\n@tw-bucket theme {\n` - css += toCss([atRule('@utility', 'container', rules)]) - css += '}\n' // @tw-bucket - } - delete resolvedConfig.theme.container - } - - if ('aria' in resolvedConfig.theme) { - for (let [key, value] of Object.entries(resolvedConfig.theme.aria ?? {})) { - // Will be handled by bare values if the names match. - // E.g.: `aria-foo:flex` should produce `[aria-foo="true"]` - if (new RegExp(`^${key}=(['"]?)true\\1$`).test(`${value}`)) continue - - // Create custom variant - variants.set(`aria-${key}`, `&[aria-${value}]`) - } - delete resolvedConfig.theme.aria - } - - if ('data' in resolvedConfig.theme) { - for (let [key, value] of Object.entries(resolvedConfig.theme.data ?? {})) { - // Will be handled by bare values if the names match. - // E.g.: `data-foo:flex` should produce `[data-foo]` - if (key === value) continue - - // Create custom variant - variants.set(`data-${key}`, `&[data-${value}]`) - } - delete resolvedConfig.theme.data - } - - if ('supports' in resolvedConfig.theme) { - for (let [key, value] of Object.entries(resolvedConfig.theme.supports ?? {})) { - // Will be handled by bare values if the value of the declaration is a - // CSS variable. - let parsed = ValueParser.parse(`${value}`) - - // Unwrap the parens, e.g.: `(foo: var(--bar))` → `foo: var(--bar)` - if (parsed.length === 1 && parsed[0].kind === 'function' && parsed[0].value === '') { - parsed = parsed[0].nodes - } - - // Verify structure: `foo: var(--bar)` - // ^^^ ← must match the `key` - if ( - parsed.length === 3 && - parsed[0].kind === 'word' && - parsed[0].value === key && - parsed[2].kind === 'function' && - parsed[2].value === 'var' - ) { - continue - } - - // Create custom variant - variants.set(`supports-${key}`, `{@supports(${value}){@slot;}}`) - } - delete resolvedConfig.theme.supports - } - } - - // Convert theme values to CSS custom properties + let css = '\n@tw-bucket theme {\n' + css += `\n@theme {\n` + let containsThemeKeys = false for (let [key, value] of themeableValues(resolvedConfig.theme)) { if (typeof value !== 'string' && typeof value !== 'number') { continue @@ -231,61 +147,51 @@ async function migrateTheme( } } + if (key[0] === 'keyframes') { + continue + } + containsThemeKeys = true + let sectionKey = createSectionKey(key) if (sectionKey !== prevSectionKey) { - themeSection.push('') + css += `\n` prevSectionKey = sectionKey } + if (resetNamespaces.has(key[0]) && resetNamespaces.get(key[0]) === false) { + resetNamespaces.set(key[0], true) + let property = keyPathToCssProperty([key[0]]) + if (property !== null) { + css += ` ${escape(`--${property}`)}-*: initial;\n` + } + } + let property = keyPathToCssProperty(key) - if (property !== null) { - if ( - !property.startsWith('default-') && - resetNamespaces.has(key[0]) && - resetNamespaces.get(key[0]) === false - ) { - resetNamespaces.set(key[0], true) - let ns = keyPathToCssProperty([key[0]]) - if (ns !== null) { - themeSection.push(` ${escape(`--${ns}`)}-*: initial;`) - } - } - - themeSection.push(` ${escape(`--${property}`)}: ${value};`) + css += ` ${escape(`--${property}`)}: ${value};\n` } } - if (keyframesCss) { - themeSection.push('', keyframesCss) + if ('keyframes' in resolvedConfig.theme) { + containsThemeKeys = true + css += '\n' + keyframesToCss(resolvedConfig.theme.keyframes) } - if (themeSection.length > 0) { - css += `\n@tw-bucket theme {\n` - css += `\n@theme {\n` - css += themeSection.join('\n') + '\n' - css += '}\n' // @theme - css += '}\n' // @tw-bucket + if (!containsThemeKeys) { + return null } - if (variants.size > 0) { - css += '\n@tw-bucket custom-variant {\n' + css += '}\n' // @theme - let previousRoot = '' - for (let [name, selector] of variants) { - let root = name.split('-')[0] - if (previousRoot !== root) css += '\n' - previousRoot = root - - if (selector.startsWith('{')) { - css += `@custom-variant ${name} ${selector}\n` - } else { - css += `@custom-variant ${name} (${selector});\n` - } + if ('container' in resolvedConfig.theme) { + let rules = buildCustomContainerUtilityRules(resolvedConfig.theme.container, designSystem) + if (rules.length > 0) { + css += '\n' + toCss([atRule('@utility', 'container', rules)]) } - css += '}\n' } + css += '}\n' // @tw-bucket + return css } @@ -368,11 +274,7 @@ async function migrateContent( throw new Error('Unsupported content value: ' + pattern) } - let sourceFiles = patternSourceFiles({ - base, - pattern: pattern[0] === '!' ? pattern.slice(1) : pattern, - negated: pattern[0] === '!', - }) + let sourceFiles = patternSourceFiles({ base, pattern }) let autoContentContainsAllSourceFiles = true for (let sourceFile of sourceFiles) { @@ -407,7 +309,7 @@ function canMigrateConfig(unresolvedConfig: Config, source: string): boolean { return false } - // The file may only contain known-migratable top-level properties + // The file may only contain known-migrateable top-level properties let knownProperties = [ 'darkMode', 'content', @@ -416,8 +318,6 @@ function canMigrateConfig(unresolvedConfig: Config, source: string): boolean { 'presets', 'prefix', // Prefix is handled in the dedicated prefix migrator 'corePlugins', - 'future', - 'experimental', ] if (Object.keys(unresolvedConfig).some((key) => !knownProperties.includes(key))) { @@ -432,29 +332,6 @@ function canMigrateConfig(unresolvedConfig: Config, source: string): boolean { return false } - // If there are unknown "future" flags we should bail - if (unresolvedConfig.future && unresolvedConfig.future !== 'all') { - let knownFlags = [ - 'hoverOnlyWhenSupported', - 'respectDefaultRingColorOpacity', - 'disableColorOpacityUtilitiesByDefault', - 'relativeContentPathsByDefault', - ] - - if (Object.keys(unresolvedConfig.future).some((key) => !knownFlags.includes(key))) { - return false - } - } - - // If there are unknown "experimental" flags we should bail - if (unresolvedConfig.experimental && unresolvedConfig.experimental !== 'all') { - let knownFlags = ['generalizedModifiers'] - - if (Object.keys(unresolvedConfig.experimental).some((key) => !knownFlags.includes(key))) { - return false - } - } - // Only migrate the config file if all top-level theme keys are allowed to be // migrated if (theme && typeof theme === 'object') { @@ -471,11 +348,15 @@ const ALLOWED_THEME_KEYS = [ // Used by @tailwindcss/container-queries 'containers', ] +const BLOCKED_THEME_KEYS = ['supports', 'data', 'aria'] function onlyAllowedThemeValues(theme: ThemeConfig): boolean { for (let key of Object.keys(theme)) { if (!ALLOWED_THEME_KEYS.includes(key)) { return false } + if (BLOCKED_THEME_KEYS.includes(key)) { + return false + } } if ('screens' in theme && typeof theme.screens === 'object' && theme.screens !== null) { @@ -494,20 +375,12 @@ function keyframesToCss(keyframes: Record): string { } function autodetectedSourceFiles(base: string) { - let scanner = new Scanner({ - sources: [ - { - base, - pattern: '**/*', - negated: false, - }, - ], - }) + let scanner = new Scanner({ sources: [{ base, pattern: '**/*' }] }) scanner.scan() return scanner.files } -function patternSourceFiles(source: { base: string; pattern: string; negated: boolean }): string[] { +function patternSourceFiles(source: { base: string; pattern: string }): string[] { let scanner = new Scanner({ sources: [source] }) scanner.scan() return scanner.files diff --git a/packages/@tailwindcss-upgrade/src/codemods/config/migrate-postcss.ts b/packages/@tailwindcss-upgrade/src/migrate-postcss.ts similarity index 94% rename from packages/@tailwindcss-upgrade/src/codemods/config/migrate-postcss.ts rename to packages/@tailwindcss-upgrade/src/migrate-postcss.ts index e16f52287..0d49c2ad2 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/config/migrate-postcss.ts +++ b/packages/@tailwindcss-upgrade/src/migrate-postcss.ts @@ -1,8 +1,7 @@ import fs from 'node:fs/promises' import path from 'node:path' -import { pkg } from '../../utils/packages' -import { highlight, info, relative, success, warn } from '../../utils/renderer' -import { writeFileSafely } from '../../utils/write-file-safely' +import { pkg } from './utils/packages' +import { highlight, info, relative, success, warn } from './utils/renderer' // Migrates simple PostCSS setups. This is to cover non-dynamic config files // similar to the ones we have all over our docs: @@ -16,7 +15,6 @@ import { writeFileSafely } from '../../utils/write-file-safely' // autoprefixer: {}, // } // } -// ``` export async function migratePostCSSConfig(base: string) { let ranMigration = false let didMigrate = false @@ -51,7 +49,7 @@ export async function migratePostCSSConfig(base: string) { ranMigration = true if (result) { - await writeFileSafely( + await fs.writeFile( packageJsonPath, JSON.stringify({ ...packageJson, postcss: result?.json }, null, 2), ) @@ -77,7 +75,7 @@ export async function migratePostCSSConfig(base: string) { ranMigration = true if (result) { - await writeFileSafely(jsonConfigPath, JSON.stringify(result.json, null, 2)) + await fs.writeFile(jsonConfigPath, JSON.stringify(result.json, null, 2)) didMigrate = true didAddPostcssClient = result.didAddPostcssClient @@ -137,16 +135,16 @@ async function migratePostCSSJSConfig(configPath: string): Promise<{ didRemovePostCSSImport: boolean } | null> { function isTailwindCSSPlugin(line: string) { - return /['"]?tailwindcss['"]?: ?\{\}/.test(line) + return /['"]?tailwindcss['"]?\: ?\{\}/.test(line) } function isPostCSSImportPlugin(line: string) { - return /['"]?postcss-import['"]?: ?\{\}/.test(line) + return /['"]?postcss-import['"]?\: ?\{\}/.test(line) } function isAutoprefixerPlugin(line: string) { - return /['"]?autoprefixer['"]?: ?\{\}/.test(line) + return /['"]?autoprefixer['"]?\: ?\{\}/.test(line) } function isTailwindCSSNestingPlugin(line: string) { - return /['"]tailwindcss\/nesting['"]: ?(\{\}|['"]postcss-nesting['"])/.test(line) + return /['"]tailwindcss\/nesting['"]\: ?(\{\}|['"]postcss-nesting['"])/.test(line) } info('Migrating PostCSS configuration…') @@ -207,7 +205,7 @@ async function migratePostCSSJSConfig(configPath: string): Promise<{ newLines.push(line) } } - await writeFileSafely(configPath, newLines.join('\n')) + await fs.writeFile(configPath, newLines.join('\n')) return { didAddPostcssClient, didRemoveAutoprefixer, didRemovePostCSSImport } } diff --git a/packages/@tailwindcss-upgrade/src/migrate-prettier.ts b/packages/@tailwindcss-upgrade/src/migrate-prettier.ts new file mode 100644 index 000000000..cd56cc688 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/migrate-prettier.ts @@ -0,0 +1,15 @@ +import fs from 'node:fs/promises' +import path from 'node:path' +import { pkg } from './utils/packages' +import { highlight, success } from './utils/renderer' + +export async function migratePrettierPlugin(base: string) { + let packageJsonPath = path.resolve(base, 'package.json') + try { + let packageJson = await fs.readFile(packageJsonPath, 'utf-8') + if (packageJson.includes('prettier-plugin-tailwindcss')) { + await pkg(base).add(['prettier-plugin-tailwindcss@latest']) + success(`Updated package: ${highlight('prettier-plugin-tailwindcss')}`, { prefix: '↳ ' }) + } + } catch {} +} diff --git a/packages/@tailwindcss-upgrade/src/migrate.ts b/packages/@tailwindcss-upgrade/src/migrate.ts new file mode 100644 index 000000000..45e758496 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/migrate.ts @@ -0,0 +1,719 @@ +import { normalizePath } from '@tailwindcss/node' +import { isGitIgnored } from 'globby' +import path from 'node:path' +import postcss, { type Result } from 'postcss' +import type { Config } from '../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../tailwindcss/src/design-system' +import { DefaultMap } from '../../tailwindcss/src/utils/default-map' +import { segment } from '../../tailwindcss/src/utils/segment' +import { migrateAtApply } from './codemods/migrate-at-apply' +import { migrateAtLayerUtilities } from './codemods/migrate-at-layer-utilities' +import { migrateConfig } from './codemods/migrate-config' +import { migrateImport } from './codemods/migrate-import' +import { migrateMediaScreen } from './codemods/migrate-media-screen' +import { migrateMissingLayers } from './codemods/migrate-missing-layers' +import { migratePreflight } from './codemods/migrate-preflight' +import { migrateTailwindDirectives } from './codemods/migrate-tailwind-directives' +import { migrateThemeToVar } from './codemods/migrate-theme-to-var' +import { migrateVariantsDirective } from './codemods/migrate-variants-directive' +import type { JSConfigMigration } from './migrate-js-config' +import { Stylesheet, type StylesheetConnection, type StylesheetId } from './stylesheet' +import { detectConfigPath } from './template/prepare-config' +import { error, highlight, relative, success } from './utils/renderer' +import { resolveCssId } from './utils/resolve' +import { walk, WalkAction } from './utils/walk' + +export interface MigrateOptions { + newPrefix: string | null + designSystem: DesignSystem + userConfig: Config + configFilePath: string + jsConfigMigration: JSConfigMigration +} + +export async function migrateContents( + stylesheet: Stylesheet | string, + options: MigrateOptions, + file?: string, +) { + if (typeof stylesheet === 'string') { + stylesheet = await Stylesheet.fromString(stylesheet) + stylesheet.file = file ?? null + } + + return postcss() + .use(migrateImport()) + .use(migrateAtApply(options)) + .use(migrateMediaScreen(options)) + .use(migrateVariantsDirective()) + .use(migrateAtLayerUtilities(stylesheet)) + .use(migrateMissingLayers()) + .use(migrateTailwindDirectives(options)) + .use(migrateConfig(stylesheet, options)) + .use(migratePreflight(options)) + .use(migrateThemeToVar(options)) + .process(stylesheet.root, { from: stylesheet.file ?? undefined }) +} + +export async function migrate(stylesheet: Stylesheet, options: MigrateOptions) { + if (!stylesheet.file) { + throw new Error('Cannot migrate a stylesheet without a file path') + } + + if (!stylesheet.canMigrate) return + + await migrateContents(stylesheet, options) +} + +export async function analyze(stylesheets: Stylesheet[]) { + let isIgnored = await isGitIgnored() + let processingQueue: (() => Promise)[] = [] + let stylesheetsByFile = new DefaultMap((file) => { + // We don't want to process ignored files (like node_modules) + if (isIgnored(file)) { + return null + } + + try { + let sheet = Stylesheet.loadSync(file) + + // Mutate incoming stylesheets to include the newly discovered sheet + stylesheets.push(sheet) + + // Queue up the processing of this stylesheet + processingQueue.push(() => processor.process(sheet.root, { from: sheet.file! })) + + return sheet + } catch { + return null + } + }) + + // Step 1: Record which `@import` rules point to which stylesheets + // and which stylesheets are parents/children of each other + let processor = postcss([ + { + postcssPlugin: 'mark-import-nodes', + AtRule: { + import(node) { + // Find what the import points to + let id = node.params.match(/['"](.*)['"]/)?.[1] + if (!id) return + + let basePath = node.source?.input.file + ? path.dirname(node.source.input.file) + : process.cwd() + + // Resolve the import to a file path + let resolvedPath: string | false = false + try { + // We first try to resolve the file as relative to the current file + // to mimic the behavior of `postcss-import` since that's what was + // used to resolve imports in Tailwind CSS v3. + if (id[0] !== '.') { + try { + resolvedPath = resolveCssId(`./${id}`, basePath) + } catch {} + } + + if (!resolvedPath) { + resolvedPath = resolveCssId(id, basePath) + } + } catch (err) { + // Import is a URL, we don't want to process these, but also don't + // want to show an error message for them. + if (id.startsWith('http://') || id.startsWith('https://') || id.startsWith('//')) { + return + } + + // Something went wrong, we can't resolve the import. + error( + `Failed to resolve import: ${highlight(id)} in ${highlight(relative(node.source?.input.file!, basePath))}. Skipping.`, + { prefix: '↳ ' }, + ) + return + } + + if (!resolvedPath) return + + // Find the stylesheet pointing to the resolved path + let stylesheet = stylesheetsByFile.get(resolvedPath) + + // If it _does not_ exist in stylesheets we don't care and skip it + // this is likely because its in node_modules or a workspace package + // that we don't want to modify + if (!stylesheet) return + + // Mark the import node with the ID of the stylesheet it points to + // We will use these later to build lookup tables and modify the AST + node.raws.tailwind_destination_sheet_id = stylesheet.id + + let parent = node.source?.input.file + ? stylesheetsByFile.get(node.source.input.file) + : undefined + + let layers: string[] = [] + + for (let part of segment(node.params, ' ')) { + if (!part.startsWith('layer(')) continue + if (!part.endsWith(')')) continue + + layers.push(part.slice(6, -1).trim()) + } + + // Connect sheets together in a dependency graph + if (parent) { + let meta = { layers } + stylesheet.parents.add({ item: parent, meta }) + parent.children.add({ item: stylesheet, meta }) + } + }, + }, + }, + ]) + + // Seed the map with all the known stylesheets, and queue up the processing of + // each incoming stylesheet. + for (let sheet of stylesheets) { + if (sheet.file) { + stylesheetsByFile.set(sheet.file, sheet) + processingQueue.push(() => processor.process(sheet.root, { from: sheet.file ?? undefined })) + } + } + + // Process all the stylesheets from step 1 + while (processingQueue.length > 0) { + let task = processingQueue.shift()! + await task() + } + + // --- + + let commonPath = process.cwd() + + function pathToString(path: StylesheetConnection[]) { + let parts: string[] = [] + + for (let connection of path) { + if (!connection.item.file) continue + + let filePath = connection.item.file.replace(commonPath, '') + let layers = connection.meta.layers.join(', ') + + if (layers.length > 0) { + parts.push(`${filePath} (layers: ${layers})`) + } else { + parts.push(filePath) + } + } + + return parts.join(' <- ') + } + + let lines: string[] = [] + + for (let sheet of stylesheets) { + if (!sheet.file) continue + + let { convertiblePaths, nonConvertiblePaths } = sheet.analyzeImportPaths() + let isAmbiguous = convertiblePaths.length > 0 && nonConvertiblePaths.length > 0 + + if (!isAmbiguous) continue + + sheet.canMigrate = false + + let filePath = sheet.file.replace(commonPath, '') + + for (let path of convertiblePaths) { + lines.push(`- ${filePath} <- ${pathToString(path)}`) + } + + for (let path of nonConvertiblePaths) { + lines.push(`- ${filePath} <- ${pathToString(path)}`) + } + } + + if (lines.length === 0) { + let tailwindRootLeafs = new Set() + + for (let sheet of stylesheets) { + // If the current file already contains `@config`, then we can assume it's + // a Tailwind CSS root file. + sheet.root.walkAtRules('config', () => { + sheet.isTailwindRoot = true + return false + }) + if (sheet.isTailwindRoot) continue + + // If an `@tailwind` at-rule, or `@import "tailwindcss"` is present, + // then we can assume it's a file where Tailwind CSS might be configured. + // + // However, if 2 or more stylesheets exist with these rules that share a + // common parent, then we want to mark the common parent as the root + // stylesheet instead. + sheet.root.walkAtRules((node) => { + if ( + node.name === 'tailwind' || + (node.name === 'import' && node.params.match(/^["']tailwindcss["']/)) || + (node.name === 'import' && node.params.match(/^["']tailwindcss\/.*?["']$/)) + ) { + sheet.isTailwindRoot = true + tailwindRootLeafs.add(sheet) + } + }) + } + + // Only a single Tailwind CSS root file exists, no need to do anything else. + if (tailwindRootLeafs.size <= 1) { + return + } + + // Mark the common parent as the root file + { + // Group each sheet from tailwindRootLeafs by their common parent + let commonParents = new DefaultMap>(() => new Set()) + + // Seed common parents with leafs + for (let sheet of tailwindRootLeafs) { + commonParents.get(sheet).add(sheet) + } + + // If any 2 common parents come from the same tree, then all children of + // parent A and parent B will be moved to the parent of parent A and + // parent B. Parent A and parent B will be removed. + let repeat = true + repeat: while (repeat) { + repeat = false + + for (let [sheetA, childrenA] of commonParents) { + for (let [sheetB, childrenB] of commonParents) { + if (sheetA === sheetB) continue + + // Ancestors from self to root. Reversed order so we find the + // nearest common parent first + // + // Including self because if you compare a sheet with its parent, + // then the parent is still the common sheet between the two. In + // this case, the parent is the root file. + let ancestorsA = [sheetA].concat(Array.from(sheetA.ancestors()).reverse()) + let ancestorsB = [sheetB].concat(Array.from(sheetB.ancestors()).reverse()) + + for (let parentA of ancestorsA) { + for (let parentB of ancestorsB) { + if (parentA !== parentB) continue + + // Found the parent + let parent = parentA + + commonParents.delete(sheetA) + commonParents.delete(sheetB) + + for (let child of childrenA) { + commonParents.get(parent).add(child) + } + + for (let child of childrenB) { + commonParents.get(parent).add(child) + } + + // Found a common parent between sheet A and sheet B. We can + // stop looking for more common parents between A and B, and + // continue with the next sheet. + repeat = true + continue repeat + } + } + } + } + } + + // Mark the common parent as the Tailwind CSS root file, and remove the + // flag from each leaf. + for (let [parent, children] of commonParents) { + parent.isTailwindRoot = true + + for (let child of children) { + if (parent === child) continue + + child.isTailwindRoot = false + } + } + return + } + } + + { + let error = `You have one or more stylesheets that are imported into a utility layer and non-utility layer.\n` + error += `We cannot convert stylesheets under these conditions. Please look at the following stylesheets:\n` + + throw new Error(error + lines.join('\n')) + } +} + +export async function linkConfigs( + stylesheets: Stylesheet[], + { configPath, base }: { configPath: string | null; base: string }, +) { + let rootStylesheets = stylesheets.filter((sheet) => sheet.isTailwindRoot) + if (rootStylesheets.length === 0) { + throw new Error( + `Cannot find any CSS files that reference Tailwind CSS.\nBefore your project can be upgraded you need to create a CSS file that imports Tailwind CSS or uses ${highlight('@tailwind')}.`, + ) + } + let withoutAtConfig = rootStylesheets.filter((sheet) => { + let hasConfig = false + sheet.root.walkAtRules('config', (node) => { + let configPath = path.resolve(path.dirname(sheet.file!), node.params.slice(1, -1)) + sheet.linkedConfigPath = configPath + hasConfig = true + return false + }) + return !hasConfig + }) + + // All stylesheets have a `@config` directives + if (withoutAtConfig.length === 0) return + + // Find the config file path for each stylesheet + let configPathBySheet = new Map() + let sheetByConfigPath = new DefaultMap>(() => new Set()) + for (let sheet of withoutAtConfig) { + if (!sheet.file) continue + + let localConfigPath = configPath as string + if (configPath === null) { + localConfigPath = await detectConfigPath(path.dirname(sheet.file), base) + } else if (!path.isAbsolute(localConfigPath)) { + localConfigPath = path.resolve(base, localConfigPath) + } + + configPathBySheet.set(sheet, localConfigPath) + sheetByConfigPath.get(localConfigPath).add(sheet) + } + + let problematicStylesheets = new Set() + for (let sheets of sheetByConfigPath.values()) { + if (sheets.size > 1) { + for (let sheet of sheets) { + problematicStylesheets.add(sheet) + } + } + } + + // There are multiple "root" files without `@config` directives. Manual + // intervention is needed to link to the correct Tailwind config files. + if (problematicStylesheets.size > 1) { + for (let sheet of problematicStylesheets) { + error( + `Could not determine configuration file for: ${highlight(relative(sheet.file!, base))}\nUpdate your stylesheet to use ${highlight('@config')} to specify the correct configuration file explicitly and then run the upgrade tool again.`, + { prefix: '↳ ' }, + ) + } + + process.exit(1) + } + + let relativePath = relative + for (let [sheet, configPath] of configPathBySheet) { + try { + if (!sheet || !sheet.file) return + success( + `Linked ${highlight(relativePath(configPath, base))} to ${highlight(relativePath(sheet.file, base))}`, + { prefix: '↳ ' }, + ) + + // Link the `@config` directive to the root stylesheets + + // Track the config file path on the stylesheet itself for easy access + // without traversing the CSS ast and finding the corresponding + // `@config` later. + sheet.linkedConfigPath = configPath + + // Create a relative path from the current file to the config file. + let relative = path.relative(path.dirname(sheet.file), configPath) + + // If the path points to a file in the same directory, `path.relative` will + // remove the leading `./` and we need to add it back in order to still + // consider the path relative + if (!relative.startsWith('.') && !path.isAbsolute(relative)) { + relative = './' + relative + } + + relative = normalizePath(relative) + + // Add the `@config` directive to the root stylesheet. + { + let target = sheet.root as postcss.Root | postcss.AtRule + let atConfig = postcss.atRule({ name: 'config', params: `'${relative}'` }) + + sheet.root.walkAtRules((node) => { + if (node.name === 'tailwind' || node.name === 'import') { + target = node + } + }) + + if (target.type === 'root') { + sheet.root.prepend(atConfig) + } else if (target.type === 'atrule') { + target.after(atConfig) + } + } + } catch (e: any) { + error('Could not load the configuration file: ' + e.message, { prefix: '↳ ' }) + process.exit(1) + } + } +} + +export async function split(stylesheets: Stylesheet[]) { + let stylesheetsById = new Map() + let stylesheetsByFile = new Map() + + for (let sheet of stylesheets) { + stylesheetsById.set(sheet.id, sheet) + + if (sheet.file) { + stylesheetsByFile.set(sheet.file, sheet) + } + } + + // Keep track of sheets that contain `@utility` rules + let requiresSplit = new Set() + + for (let sheet of stylesheets) { + // Root files don't need to be split + if (sheet.isTailwindRoot) continue + + let containsUtility = false + let containsUnsafe = sheet.layers().size > 0 + + walk(sheet.root, (node) => { + if (node.type === 'atrule' && node.name === 'utility') { + containsUtility = true + } + + // Safe to keep without splitting + else if ( + // An `@import "…" layer(…)` is safe + (node.type === 'atrule' && node.name === 'import' && node.params.includes('layer(')) || + // @layer blocks are safe + (node.type === 'atrule' && node.name === 'layer') || + // Comments are safe + node.type === 'comment' + ) { + return WalkAction.Skip + } + + // Everything else is not safe, and requires a split + else { + containsUnsafe = true + } + + // We already know we need to split this sheet + if (containsUtility && containsUnsafe) { + return WalkAction.Stop + } + + return WalkAction.Skip + }) + + if (containsUtility && containsUnsafe) { + requiresSplit.add(sheet) + } + } + + // Split every imported stylesheet into two parts + let utilitySheets = new Map() + + for (let sheet of stylesheets) { + // Ignore stylesheets that were not imported + if (!sheet.file) continue + if (sheet.parents.size === 0) continue + + // Skip stylesheets that don't have utilities + // and don't have any children that have utilities + if (!requiresSplit.has(sheet)) { + if (!Array.from(sheet.descendants()).some((child) => requiresSplit.has(child))) { + continue + } + } + + let utilities = postcss.root() + + walk(sheet.root, (node) => { + if (node.type !== 'atrule') return + if (node.name !== 'utility') return + + // `append` will move this node from the original sheet + // to the new utilities sheet + utilities.append(node) + + return WalkAction.Skip + }) + + let newFileName = sheet.file.replace(/\.css$/, '.utilities.css') + + let counter = 0 + + // If we already have a utility sheet with this name, we need to rename it + while (stylesheetsByFile.has(newFileName)) { + counter += 1 + newFileName = sheet.file.replace(/\.css$/, `.utilities.${counter}.css`) + } + + let utilitySheet = await Stylesheet.fromRoot(utilities, newFileName) + + utilitySheet.extension = counter > 0 ? `.utilities.${counter}.css` : `.utilities.css` + + utilitySheets.set(sheet, utilitySheet) + stylesheetsById.set(utilitySheet.id, utilitySheet) + } + + // Make sure the utility sheets are linked to one another + for (let [normalSheet, utilitySheet] of utilitySheets) { + for (let parent of normalSheet.parents) { + let utilityParent = utilitySheets.get(parent.item) + if (!utilityParent) continue + utilitySheet.parents.add({ + item: utilityParent, + meta: parent.meta, + }) + } + + for (let child of normalSheet.children) { + let utilityChild = utilitySheets.get(child.item) + if (!utilityChild) continue + utilitySheet.children.add({ + item: utilityChild, + meta: child.meta, + }) + } + } + + for (let sheet of stylesheets) { + let utilitySheet = utilitySheets.get(sheet) + let utilityImports: Set = new Set() + + for (let node of sheet.importRules) { + let sheetId = node.raws.tailwind_destination_sheet_id as StylesheetId | undefined + + // This import rule does not point to a stylesheet + // which likely means it points to `node_modules` + if (!sheetId) continue + + let originalDestination = stylesheetsById.get(sheetId) + + // This import points to a stylesheet that no longer exists which likely + // means it was removed by the optimizer this will be cleaned up later + if (!originalDestination) continue + + let utilityDestination = utilitySheets.get(originalDestination) + + // A utility sheet doesn't exist for this import so it doesn't need + // to be processed + if (!utilityDestination) continue + + let match = node.params.match(/(['"])(.*)\1/) + if (!match) return + + let quote = match[1] + let id = match[2] + + let newFile = id.replace(/\.css$/, utilityDestination.extension!) + + // The import will just point to the new file without any media queries, + // layers, or other conditions because `@utility` MUST be top-level. + let newImport = node.clone({ + params: `${quote}${newFile}${quote}`, + raws: { + tailwind_injected_layer: node.raws.tailwind_injected_layer, + tailwind_original_params: `${quote}${id}${quote}`, + tailwind_destination_sheet_id: utilityDestination.id, + }, + }) + + if (utilitySheet) { + // If this import is intended to go into the utility sheet + // we'll collect it into a list to add later. If we don't' + // we'll end up adding them in reverse order. + utilityImports.add(newImport) + } else { + // This import will go immediately after the original import + node.after(newImport) + } + } + + // Add imports to the top of the utility sheet if necessary + if (utilitySheet && utilityImports.size > 0) { + utilitySheet.root.prepend(Array.from(utilityImports)) + } + } + + // Tracks the at rules that import a given stylesheet + let importNodes = new DefaultMap>(() => new Set()) + + for (let sheet of stylesheetsById.values()) { + for (let node of sheet.importRules) { + let sheetId = node.raws.tailwind_destination_sheet_id as StylesheetId | undefined + + // This import rule does not point to a stylesheet + if (!sheetId) continue + + let destination = stylesheetsById.get(sheetId) + + // This import rule does not point to a stylesheet that exists + // We'll remove it later + if (!destination) continue + + importNodes.get(destination).add(node) + } + } + + // At this point we've created many `{name}.utilities.css` files. + // If the original file _becomes_ empty after splitting that means that + // dedicated utility file is not required and we can move the utilities + // back to the original file. + // + // This could be done in one step but separating them makes it easier to + // reason about since the stylesheets are in a consistent state before we + // perform any cleanup tasks. + let list: Stylesheet[] = [] + + for (let sheet of stylesheets.slice()) { + for (let child of sheet.descendants()) { + list.push(child) + } + + list.push(sheet) + } + + for (let sheet of list) { + let utilitySheet = utilitySheets.get(sheet) + + // This sheet was not split so there's nothing to do + if (!utilitySheet) continue + + // This sheet did not become empty + if (!sheet.isEmpty) continue + + // We have a sheet that became empty after splitting + // 1. Replace the sheet with it's utility sheet content + sheet.root = utilitySheet.root + + // 2. Rewrite imports in parent sheets to point to the original sheet + // Ideally this wouldn't need to be _undone_ but instead only done once at the end + for (let node of importNodes.get(utilitySheet)) { + node.params = node.raws.tailwind_original_params as any + } + + // 3. Remove the original import from the non-utility sheet + for (let node of importNodes.get(sheet)) { + node.remove() + } + + // 3. Mark the utility sheet for removal + utilitySheets.delete(sheet) + } + + stylesheets.push(...utilitySheets.values()) +} diff --git a/packages/@tailwindcss-upgrade/src/stylesheet.ts b/packages/@tailwindcss-upgrade/src/stylesheet.ts index 573413262..092d9414b 100644 --- a/packages/@tailwindcss-upgrade/src/stylesheet.ts +++ b/packages/@tailwindcss-upgrade/src/stylesheet.ts @@ -1,10 +1,8 @@ -import { __unstable__loadDesignSystem, compileAst } from '@tailwindcss/node' import * as fsSync from 'node:fs' import * as fs from 'node:fs/promises' import * as path from 'node:path' import * as util from 'node:util' import * as postcss from 'postcss' -import { postCssAstToCssAst } from '../../@tailwindcss-postcss/src/ast' export type StylesheetId = string @@ -265,28 +263,6 @@ export class Stylesheet { return false } - #compiler?: Promise> | null> - #designSystem?: Promise> | null> - - async compiler(): Promise> | null> { - if (!this.isTailwindRoot) return null - if (!this.file) return null - - return (this.#compiler ??= compileAst(postCssAstToCssAst(this.root), { - base: path.dirname(this.file), - onDependency() {}, - })) - } - - async designSystem(): Promise> | null> { - if (!this.isTailwindRoot) return null - if (!this.file) return null - - return (this.#designSystem ??= __unstable__loadDesignSystem(this.root.toString(), { - base: path.dirname(this.file), - })) - } - [util.inspect.custom]() { return { ...this, diff --git a/packages/@tailwindcss-upgrade/src/template/candidates.test.ts b/packages/@tailwindcss-upgrade/src/template/candidates.test.ts new file mode 100644 index 000000000..5ec45fa81 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/candidates.test.ts @@ -0,0 +1,193 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { describe, expect, test } from 'vitest' +import { extractRawCandidates, printCandidate } from './candidates' +import { spliceChangesIntoString } from './splice-changes-into-string' + +let html = String.raw + +test('extracts candidates with positions from a template', async () => { + let content = html` +
+ +
+ ` + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + let candidates = await extractRawCandidates(content, 'html') + let validCandidates = candidates.filter( + ({ rawCandidate }) => designSystem.parseCandidate(rawCandidate).length > 0, + ) + + expect(validCandidates).toMatchInlineSnapshot(` + [ + { + "end": 28, + "rawCandidate": "bg-blue-500", + "start": 17, + }, + { + "end": 51, + "rawCandidate": "hover:focus:text-white", + "start": 29, + }, + { + "end": 63, + "rawCandidate": "[color:red]", + "start": 52, + }, + { + "end": 98, + "rawCandidate": "bg-blue-500", + "start": 87, + }, + { + "end": 109, + "rawCandidate": "text-white", + "start": 99, + }, + ] + `) +}) + +test('replaces the right positions for a candidate', async () => { + let content = html` +

🤠👋

+
+ ` + + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + let candidates = await extractRawCandidates(content, 'html') + + let candidate = candidates.find( + ({ rawCandidate }) => designSystem.parseCandidate(rawCandidate).length > 0, + )! + + let migrated = spliceChangesIntoString(content, [ + { + start: candidate.start, + end: candidate.end, + replacement: 'flex', + }, + ]) + + expect(migrated).toMatchInlineSnapshot(` + " +

🤠👋

+
+ " + `) +}) + +const candidates = [ + // Arbitrary candidates + ['[color:red]', '[color:red]'], + ['[color:red]/50', '[color:red]/50'], + ['[color:red]/[0.5]', '[color:red]/[0.5]'], + ['[color:red]/50!', '[color:red]/50!'], + ['![color:red]/50', '[color:red]/50!'], + ['[color:red]/[0.5]!', '[color:red]/[0.5]!'], + + // Static candidates + ['box-border', 'box-border'], + ['underline!', 'underline!'], + ['!underline', 'underline!'], + ['-inset-full', '-inset-full'], + + // Functional candidates + ['bg-red-500', 'bg-red-500'], + ['bg-red-500/50', 'bg-red-500/50'], + ['bg-red-500/[0.5]', 'bg-red-500/[0.5]'], + ['bg-red-500!', 'bg-red-500!'], + ['!bg-red-500', 'bg-red-500!'], + ['bg-[#0088cc]/50', 'bg-[#0088cc]/50'], + ['bg-[#0088cc]/[0.5]', 'bg-[#0088cc]/[0.5]'], + ['bg-[#0088cc]!', 'bg-[#0088cc]!'], + ['!bg-[#0088cc]', 'bg-[#0088cc]!'], + ['bg-[var(--spacing)-1px]', 'bg-[var(--spacing)-1px]'], + ['bg-[var(--spacing)_-_1px]', 'bg-[var(--spacing)-1px]'], + ['bg-[var(--_spacing)]', 'bg-(--_spacing)'], + ['bg-(--_spacing)', 'bg-(--_spacing)'], + ['bg-[var(--\_spacing)]', 'bg-(--_spacing)'], + ['bg-(--\_spacing)', 'bg-(--_spacing)'], + ['bg-[-1px_-1px]', 'bg-[-1px_-1px]'], + ['p-[round(to-zero,1px)]', 'p-[round(to-zero,1px)]'], + ['w-1/2', 'w-1/2'], + ['p-[calc((100vw-theme(maxWidth.2xl))_/_2)]', 'p-[calc((100vw-theme(maxWidth.2xl))/2)]'], + + // Keep spaces in strings + ['content-["hello_world"]', 'content-["hello_world"]'], + ['content-[____"hello_world"___]', 'content-["hello_world"]'], + + // Do not escape underscores for url() and CSS variable in var() + ['bg-[no-repeat_url(/image_13.png)]', 'bg-[no-repeat_url(/image_13.png)]'], + [ + 'bg-[var(--spacing-0_5,_var(--spacing-1_5,_3rem))]', + 'bg-(--spacing-0_5,var(--spacing-1_5,3rem))', + ], +] + +const variants = [ + ['', ''], // no variant + ['*:', '*:'], + ['focus:', 'focus:'], + ['group-focus:', 'group-focus:'], + + ['hover:focus:', 'hover:focus:'], + ['hover:group-focus:', 'hover:group-focus:'], + ['group-hover:focus:', 'group-hover:focus:'], + ['group-hover:group-focus:', 'group-hover:group-focus:'], + + ['min-[10px]:', 'min-[10px]:'], + + // Normalize spaces + ['min-[calc(1000px_+_12em)]:', 'min-[calc(1000px+12em)]:'], + ['min-[calc(1000px_+12em)]:', 'min-[calc(1000px+12em)]:'], + ['min-[calc(1000px+_12em)]:', 'min-[calc(1000px+12em)]:'], + ['min-[calc(1000px___+___12em)]:', 'min-[calc(1000px+12em)]:'], + + ['peer-[&_p]:', 'peer-[&_p]:'], + ['peer-[&_p]:hover:', 'peer-[&_p]:hover:'], + ['hover:peer-[&_p]:', 'hover:peer-[&_p]:'], + ['hover:peer-[&_p]:focus:', 'hover:peer-[&_p]:focus:'], + ['peer-[&:hover]:peer-[&_p]:', 'peer-[&:hover]:peer-[&_p]:'], + + ['[p]:', '[p]:'], + ['[_p_]:', '[p]:'], + ['has-[p]:', 'has-[p]:'], + ['has-[_p_]:', 'has-[p]:'], + + // Simplify `&:is(p)` to `p` + ['[&:is(p)]:', '[p]:'], + ['[&:is(_p_)]:', '[p]:'], + ['has-[&:is(p)]:', 'has-[p]:'], + ['has-[&:is(_p_)]:', 'has-[p]:'], +] + +let combinations: [string, string][] = [] + +for (let [inputVariant, outputVariant] of variants) { + for (let [inputCandidate, outputCandidate] of candidates) { + combinations.push([`${inputVariant}${inputCandidate}`, `${outputVariant}${outputCandidate}`]) + } +} + +describe('printCandidate()', () => { + test.each(combinations)('%s -> %s', async (candidate: string, result: string) => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + let candidates = designSystem.parseCandidate(candidate) + + // Sometimes we will have a functional and a static candidate for the same + // raw input string (e.g. `-inset-full`). Dedupe in this case. + let cleaned = new Set([...candidates].map((c) => printCandidate(designSystem, c))) + + expect([...cleaned]).toEqual([result]) + }) +}) diff --git a/packages/@tailwindcss-upgrade/src/template/candidates.ts b/packages/@tailwindcss-upgrade/src/template/candidates.ts new file mode 100644 index 000000000..e292168a5 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/candidates.ts @@ -0,0 +1,285 @@ +import { Scanner } from '@tailwindcss/oxide' +import type { Candidate, Variant } from '../../../tailwindcss/src/candidate' +import type { DesignSystem } from '../../../tailwindcss/src/design-system' +import * as ValueParser from '../../../tailwindcss/src/value-parser' + +export async function extractRawCandidates( + content: string, + extension: string = 'html', +): Promise<{ rawCandidate: string; start: number; end: number }[]> { + let scanner = new Scanner({}) + let result = scanner.getCandidatesWithPositions({ content, extension }) + + let candidates: { rawCandidate: string; start: number; end: number }[] = [] + for (let { candidate: rawCandidate, position: start } of result) { + candidates.push({ rawCandidate, start, end: start + rawCandidate.length }) + } + return candidates +} + +export function printCandidate(designSystem: DesignSystem, candidate: Candidate) { + let parts: string[] = [] + + for (let variant of candidate.variants) { + parts.unshift(printVariant(variant)) + } + + // Handle prefix + if (designSystem.theme.prefix) { + parts.unshift(designSystem.theme.prefix) + } + + let base: string = '' + + // Handle static + if (candidate.kind === 'static') { + base += candidate.root + } + + // Handle functional + if (candidate.kind === 'functional') { + base += candidate.root + + if (candidate.value) { + if (candidate.value.kind === 'arbitrary') { + if (candidate.value !== null) { + let isVarValue = isVar(candidate.value.value) + let value = isVarValue ? candidate.value.value.slice(4, -1) : candidate.value.value + let [open, close] = isVarValue ? ['(', ')'] : ['[', ']'] + + if (candidate.value.dataType) { + base += `-${open}${candidate.value.dataType}:${printArbitraryValue(value)}${close}` + } else { + base += `-${open}${printArbitraryValue(value)}${close}` + } + } + } else if (candidate.value.kind === 'named') { + base += `-${candidate.value.value}` + } + } + } + + // Handle arbitrary + if (candidate.kind === 'arbitrary') { + base += `[${candidate.property}:${printArbitraryValue(candidate.value)}]` + } + + // Handle modifier + if (candidate.kind === 'arbitrary' || candidate.kind === 'functional') { + if (candidate.modifier) { + let isVarValue = isVar(candidate.modifier.value) + let value = isVarValue ? candidate.modifier.value.slice(4, -1) : candidate.modifier.value + let [open, close] = isVarValue ? ['(', ')'] : ['[', ']'] + + if (candidate.modifier.kind === 'arbitrary') { + base += `/${open}${printArbitraryValue(value)}${close}` + } else if (candidate.modifier.kind === 'named') { + base += `/${candidate.modifier.value}` + } + } + } + + // Handle important + if (candidate.important) { + base += '!' + } + + parts.push(base) + + return parts.join(':') +} + +function printVariant(variant: Variant) { + // Handle static variants + if (variant.kind === 'static') { + return variant.root + } + + // Handle arbitrary variants + if (variant.kind === 'arbitrary') { + return `[${printArbitraryValue(simplifyArbitraryVariant(variant.selector))}]` + } + + let base: string = '' + + // Handle functional variants + if (variant.kind === 'functional') { + base += variant.root + if (variant.value) { + if (variant.value.kind === 'arbitrary') { + let isVarValue = isVar(variant.value.value) + let value = isVarValue ? variant.value.value.slice(4, -1) : variant.value.value + let [open, close] = isVarValue ? ['(', ')'] : ['[', ']'] + + base += `-${open}${printArbitraryValue(value)}${close}` + } else if (variant.value.kind === 'named') { + base += `-${variant.value.value}` + } + } + } + + // Handle compound variants + if (variant.kind === 'compound') { + base += variant.root + base += '-' + base += printVariant(variant.variant) + } + + // Handle modifiers + if (variant.kind === 'functional' || variant.kind === 'compound') { + if (variant.modifier) { + if (variant.modifier.kind === 'arbitrary') { + base += `/[${printArbitraryValue(variant.modifier.value)}]` + } else if (variant.modifier.kind === 'named') { + base += `/${variant.modifier.value}` + } + } + } + + return base +} + +function printArbitraryValue(input: string) { + let ast = ValueParser.parse(input) + + let drop = new Set() + + ValueParser.walk(ast, (node, { parent }) => { + let parentArray = parent === null ? ast : (parent.nodes ?? []) + + // Handle operators (e.g.: inside of `calc(…)`) + if ( + node.kind === 'word' && + // Operators + (node.value === '+' || node.value === '-' || node.value === '*' || node.value === '/') + ) { + let idx = parentArray.indexOf(node) ?? -1 + + // This should not be possible + if (idx === -1) return + + let previous = parentArray[idx - 1] + if (previous?.kind !== 'separator' || previous.value !== ' ') return + + let next = parentArray[idx + 1] + if (next?.kind !== 'separator' || next.value !== ' ') return + + drop.add(previous) + drop.add(next) + } + + // The value parser handles `/` as a separator in some scenarios. E.g.: + // `theme(colors.red/50%)`. Because of this, we have to handle this case + // separately. + else if (node.kind === 'separator' && node.value.trim() === '/') { + node.value = '/' + } + + // Leading and trailing whitespace + else if (node.kind === 'separator' && node.value.length > 0 && node.value.trim() === '') { + if (parentArray[0] === node || parentArray[parentArray.length - 1] === node) { + drop.add(node) + } + } + + // Whitespace around `,` separators can be removed. + // E.g.: `min(1px , 2px)` -> `min(1px,2px)` + else if (node.kind === 'separator' && node.value.trim() === ',') { + node.value = ',' + } + }) + + if (drop.size > 0) { + ValueParser.walk(ast, (node, { replaceWith }) => { + if (drop.has(node)) { + drop.delete(node) + replaceWith([]) + } + }) + } + + recursivelyEscapeUnderscores(ast) + + return ValueParser.toCss(ast) +} + +function simplifyArbitraryVariant(input: string) { + let ast = ValueParser.parse(input) + + // &:is(…) + if ( + ast.length === 3 && + // & + ast[0].kind === 'word' && + ast[0].value === '&' && + // : + ast[1].kind === 'separator' && + ast[1].value === ':' && + // is(…) + ast[2].kind === 'function' && + ast[2].value === 'is' + ) { + return ValueParser.toCss(ast[2].nodes) + } + + return input +} + +function recursivelyEscapeUnderscores(ast: ValueParser.ValueAstNode[]) { + for (let node of ast) { + switch (node.kind) { + case 'function': { + if (node.value === 'url' || node.value.endsWith('_url')) { + // Don't decode underscores in url() but do decode the function name + node.value = escapeUnderscore(node.value) + break + } + + if ( + node.value === 'var' || + node.value.endsWith('_var') || + node.value === 'theme' || + node.value.endsWith('_theme') + ) { + node.value = escapeUnderscore(node.value) + for (let i = 0; i < node.nodes.length; i++) { + recursivelyEscapeUnderscores([node.nodes[i]]) + } + break + } + + node.value = escapeUnderscore(node.value) + recursivelyEscapeUnderscores(node.nodes) + break + } + case 'separator': + node.value = escapeUnderscore(node.value) + break + case 'word': { + // Dashed idents and variables `var(--my-var)` and `--my-var` should not + // have underscores escaped + if (node.value[0] !== '-' && node.value[1] !== '-') { + node.value = escapeUnderscore(node.value) + } + break + } + default: + never(node) + } + } +} + +function isVar(value: string) { + let ast = ValueParser.parse(value) + return ast.length === 1 && ast[0].kind === 'function' && ast[0].value === 'var' +} + +function never(value: never): never { + throw new Error(`Unexpected value: ${value}`) +} + +function escapeUnderscore(value: string): string { + return value + .replaceAll('_', String.raw`\_`) // Escape underscores to keep them as-is + .replaceAll(' ', '_') // Replace spaces with underscores +} diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.test.ts new file mode 100644 index 000000000..c9b459d84 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.test.ts @@ -0,0 +1,69 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { expect, test } from 'vitest' +import { arbitraryValueToBareValue } from './arbitrary-value-to-bare-value' + +test.each([ + ['aspect-[12/34]', 'aspect-12/34'], + ['aspect-[1.2/34]', 'aspect-[1.2/34]'], + ['col-start-[7]', 'col-start-7'], + ['flex-[2]', 'flex-2'], // `flex` is implemented as static and functional utilities + + ['grid-cols-[subgrid]', 'grid-cols-subgrid'], + ['grid-rows-[subgrid]', 'grid-rows-subgrid'], + + // Only 50-200% (inclusive) are valid: + // https://developer.mozilla.org/en-US/docs/Web/CSS/font-stretch#percentage + ['font-stretch-[50%]', 'font-stretch-50%'], + ['font-stretch-[50.5%]', 'font-stretch-[50.5%]'], + ['font-stretch-[201%]', 'font-stretch-[201%]'], + ['font-stretch-[49%]', 'font-stretch-[49%]'], + // Should stay as-is + ['font-stretch-[1/2]', 'font-stretch-[1/2]'], + + // Bare value with % is valid for these utilities + ['from-[28%]', 'from-28%'], + ['via-[28%]', 'via-28%'], + ['to-[28%]', 'to-28%'], + ['from-[28.5%]', 'from-[28.5%]'], + ['via-[28.5%]', 'via-[28.5%]'], + ['to-[28.5%]', 'to-[28.5%]'], + + // This test in itself is a bit flawed because `text-[1/2]` currently + // generates something. Converting it to `text-1/2` doesn't produce anything. + ['text-[1/2]', 'text-[1/2]'], + + // Leading is special, because `leading-[123]` is the direct value of 123, but + // `leading-123` maps to `calc(--spacing(123))`. + ['leading-[1]', 'leading-none'], + ['leading-[123]', 'leading-[123]'], + + ['data-[selected]:flex', 'data-selected:flex'], + ['data-[foo=bar]:flex', 'data-[foo=bar]:flex'], + + ['supports-[gap]:flex', 'supports-gap:flex'], + ['supports-[display:grid]:flex', 'supports-[display:grid]:flex'], + + ['group-data-[selected]:flex', 'group-data-selected:flex'], + ['group-data-[foo=bar]:flex', 'group-data-[foo=bar]:flex'], + ['group-has-data-[selected]:flex', 'group-has-data-selected:flex'], + + ['aria-[selected]:flex', 'aria-[selected]:flex'], + ['aria-[selected="true"]:flex', 'aria-selected:flex'], + ['aria-[selected*="true"]:flex', 'aria-[selected*="true"]:flex'], + + ['group-aria-[selected]:flex', 'group-aria-[selected]:flex'], + ['group-aria-[selected="true"]:flex', 'group-aria-selected:flex'], + ['group-has-aria-[selected]:flex', 'group-has-aria-[selected]:flex'], + + ['max-lg:hover:data-[selected]:flex!', 'max-lg:hover:data-selected:flex!'], + [ + 'data-[selected]:aria-[selected="true"]:aspect-[12/34]', + 'data-selected:aria-selected:aspect-12/34', + ], +])('%s => %s', async (candidate, result) => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + expect(arbitraryValueToBareValue(designSystem, {}, candidate)).toEqual(result) +}) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.ts b/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.ts new file mode 100644 index 000000000..64c06a8f3 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.ts @@ -0,0 +1,203 @@ +import { parseCandidate, type Candidate, type Variant } from '../../../../tailwindcss/src/candidate' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { isPositiveInteger } from '../../../../tailwindcss/src/utils/infer-data-type' +import { segment } from '../../../../tailwindcss/src/utils/segment' +import { printCandidate } from '../candidates' + +export function arbitraryValueToBareValue( + designSystem: DesignSystem, + _userConfig: Config, + rawCandidate: string, +): string { + for (let candidate of parseCandidate(rawCandidate, designSystem)) { + let clone = structuredClone(candidate) + let changed = false + + // Convert [subgrid] to subgrid + if ( + clone.kind === 'functional' && + clone.value?.kind === 'arbitrary' && + clone.value.value === 'subgrid' && + (clone.root === 'grid-cols' || clone.root == 'grid-rows') + ) { + changed = true + clone.value = { + kind: 'named', + value: 'subgrid', + fraction: null, + } + } + + // Convert utilities that accept bare values ending in % + if ( + clone.kind === 'functional' && + clone.value?.kind === 'arbitrary' && + clone.value.dataType === null && + (clone.root === 'from' || + clone.root === 'via' || + clone.root === 'to' || + clone.root === 'font-stretch') + ) { + if (clone.value.value.endsWith('%') && isPositiveInteger(clone.value.value.slice(0, -1))) { + let percentage = parseInt(clone.value.value) + if ( + clone.root === 'from' || + clone.root === 'via' || + clone.root === 'to' || + (clone.root === 'font-stretch' && percentage >= 50 && percentage <= 200) + ) { + changed = true + clone.value = { + kind: 'named', + value: clone.value.value, + fraction: null, + } + } + } + } + + // Convert arbitrary values with positive integers to bare values + // Convert arbitrary values with fractions to bare values + else if ( + clone.kind === 'functional' && + clone.value?.kind === 'arbitrary' && + clone.value.dataType === null + ) { + if (clone.root === 'leading') { + // leading-[1] -> leading-none + if (clone.value.value === '1') { + changed = true + clone.value = { + kind: 'named', + value: 'none', + fraction: null, + } + } + + // Keep leading-[] as leading-[] + else { + continue + } + } + + let parts = segment(clone.value.value, '/') + if (parts.every((part) => isPositiveInteger(part))) { + changed = true + + let currentValue = clone.value + let currentModifier = clone.modifier + + // E.g.: `col-start-[12]` + // ^^ + if (parts.length === 1) { + clone.value = { + kind: 'named', + value: clone.value.value, + fraction: null, + } + } + + // E.g.: `aspect-[12/34]` + // ^^ ^^ + else { + clone.value = { + kind: 'named', + value: parts[0], + fraction: clone.value.value, + } + clone.modifier = { + kind: 'named', + value: parts[1], + } + } + + // Double check that the new value compiles correctly + if (designSystem.compileAstNodes(clone).length === 0) { + clone.value = currentValue + clone.modifier = currentModifier + changed = false + } + } + } + + for (let variant of variants(clone)) { + // Convert `data-[selected]` to `data-selected` + if ( + variant.kind === 'functional' && + variant.root === 'data' && + variant.value?.kind === 'arbitrary' && + !variant.value.value.includes('=') + ) { + changed = true + variant.value = { + kind: 'named', + value: variant.value.value, + } + } + + // Convert `aria-[selected="true"]` to `aria-selected` + else if ( + variant.kind === 'functional' && + variant.root === 'aria' && + variant.value?.kind === 'arbitrary' && + (variant.value.value.endsWith('=true') || + variant.value.value.endsWith('="true"') || + variant.value.value.endsWith("='true'")) + ) { + let [key, _value] = segment(variant.value.value, '=') + if ( + // aria-[foo~="true"] + key[key.length - 1] === '~' || + // aria-[foo|="true"] + key[key.length - 1] === '|' || + // aria-[foo^="true"] + key[key.length - 1] === '^' || + // aria-[foo$="true"] + key[key.length - 1] === '$' || + // aria-[foo*="true"] + key[key.length - 1] === '*' + ) { + continue + } + + changed = true + variant.value = { + kind: 'named', + value: variant.value.value.slice(0, variant.value.value.indexOf('=')), + } + } + + // Convert `supports-[gap]` to `supports-gap` + else if ( + variant.kind === 'functional' && + variant.root === 'supports' && + variant.value?.kind === 'arbitrary' && + /^[a-z-][a-z0-9-]*$/i.test(variant.value.value) + ) { + changed = true + variant.value = { + kind: 'named', + value: variant.value.value, + } + } + } + + return changed ? printCandidate(designSystem, clone) : rawCandidate + } + + return rawCandidate +} + +function* variants(candidate: Candidate) { + function* inner(variant: Variant): Iterable { + yield variant + if (variant.kind === 'compound') { + yield* inner(variant.variant) + } + } + + for (let variant of candidate.variants) { + yield* inner(variant) + } +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.test.ts similarity index 88% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.test.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.test.ts index 5da651d51..59b1523b7 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.test.ts @@ -1,6 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' -import { migrateAutomaticVarInjection } from './migrate-automatic-var-injection' +import { automaticVarInjection } from './automatic-var-injection' test.each([ // Arbitrary candidates @@ -40,11 +40,6 @@ test.each([ ['w-[--spacing(5)]', 'w-[--spacing(5)]'], ['bg-[--theme(--color-red-500)]', 'bg-[--theme(--color-red-500)]'], - // Fallback values should be included inside the `var(…)` function - ['bg-[--my-color,red]', 'bg-(--my-color,red)'], - // Fallback values can contain CSS functions - ['bg-[--my-color,theme(spacing.1)]', 'bg-(--my-color,theme(spacing.1))'], - // Some properties never had var() injection in v3. ['[scroll-timeline-name:--myTimeline]', '[scroll-timeline-name:--myTimeline]'], ['[timeline-scope:--myScope]', '[timeline-scope:--myScope]'], @@ -63,6 +58,6 @@ test.each([ base: __dirname, }) - let migrated = migrateAutomaticVarInjection(designSystem, {}, candidate) + let migrated = automaticVarInjection(designSystem, {}, candidate) expect(migrated).toEqual(result) }) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.ts b/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.ts similarity index 79% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.ts index 0ea3bd691..643d000c6 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.ts @@ -1,19 +1,18 @@ -import { cloneCandidate, type Candidate, type Variant } from '../../../../tailwindcss/src/candidate' +import { walk, WalkAction } from '../../../../tailwindcss/src/ast' +import { type Candidate, type Variant } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import type { Writable } from '../../../../tailwindcss/src/types' -import * as ValueParser from '../../../../tailwindcss/src/value-parser' -import { walk, WalkAction } from '../../../../tailwindcss/src/walk' +import { printCandidate } from '../candidates' -export function migrateAutomaticVarInjection( +export function automaticVarInjection( designSystem: DesignSystem, - _userConfig: Config | null, + _userConfig: Config, rawCandidate: string, ): string { for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) { // The below logic makes extended use of mutation. Since candidates in the // DesignSystem are cached, we can't mutate them directly. - let candidate = cloneCandidate(readonlyCandidate) as Writable + let candidate = structuredClone(readonlyCandidate) as Candidate let didChange = false @@ -67,7 +66,7 @@ export function migrateAutomaticVarInjection( } if (didChange) { - return designSystem.printCandidate(candidate) + return printCandidate(designSystem, candidate) } } return rawCandidate @@ -75,23 +74,9 @@ export function migrateAutomaticVarInjection( function injectVar(value: string): { value: string; didChange: boolean } { let didChange = false - if (value.startsWith('--')) { - // E.g.: - // - // - `--my-color` → `var(--my-color)` Convert variable - // - `--my-color,red` → `var(--my-color,red)` Convert variable with fallback - // - `--theme(color.red)` → `--theme(color.red)` Do not convert functions - // - if ( - // No `(` definitely means there is no function - !value.includes('(') || - // There could be a function call in the fallback value, but it cannot be - // top-level, so we can safely check the first part - ValueParser.parse(value)[0]?.kind !== 'function' - ) { - value = `var(${value})` - didChange = true - } + if (value.startsWith('--') && !value.includes('(')) { + value = `var(${value})` + didChange = true } else if (value.startsWith(' --')) { value = value.slice(1) didChange = true diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.test.ts new file mode 100644 index 000000000..3c42903c3 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.test.ts @@ -0,0 +1,22 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { expect, test } from 'vitest' +import { bgGradient } from './bg-gradient' + +test.each([ + ['bg-gradient-to-t', 'bg-linear-to-t'], + ['bg-gradient-to-tr', 'bg-linear-to-tr'], + ['bg-gradient-to-r', 'bg-linear-to-r'], + ['bg-gradient-to-br', 'bg-linear-to-br'], + ['bg-gradient-to-b', 'bg-linear-to-b'], + ['bg-gradient-to-bl', 'bg-linear-to-bl'], + ['bg-gradient-to-l', 'bg-linear-to-l'], + ['bg-gradient-to-tl', 'bg-linear-to-tl'], + + ['max-lg:hover:bg-gradient-to-t', 'max-lg:hover:bg-linear-to-t'], +])('%s => %s', async (candidate, result) => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + expect(bgGradient(designSystem, {}, candidate)).toEqual(result) +}) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.ts b/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.ts new file mode 100644 index 000000000..02ad5590b --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.ts @@ -0,0 +1,27 @@ +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { printCandidate } from '../candidates' + +const DIRECTIONS = ['t', 'tr', 'r', 'br', 'b', 'bl', 'l', 'tl'] + +export function bgGradient( + designSystem: DesignSystem, + _userConfig: Config, + rawCandidate: string, +): string { + for (let candidate of designSystem.parseCandidate(rawCandidate)) { + if (candidate.kind === 'static' && candidate.root.startsWith('bg-gradient-to-')) { + let direction = candidate.root.slice(15) + + if (!DIRECTIONS.includes(direction)) { + continue + } + + return printCandidate(designSystem, { + ...candidate, + root: `bg-linear-to-${direction}`, + }) + } + } + return rawCandidate +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/handle-empty-arbitrary-values.test.ts similarity index 68% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.test.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/handle-empty-arbitrary-values.test.ts index bebd43343..b451fbf86 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/handle-empty-arbitrary-values.test.ts @@ -1,9 +1,7 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' -import { expect, test, vi } from 'vitest' -import * as versions from '../../utils/version' -import { migrateEmptyArbitraryValues } from './migrate-handle-empty-arbitrary-values' -import { migratePrefix } from './migrate-prefix' -vi.spyOn(versions, 'isMajor').mockReturnValue(true) +import { expect, test } from 'vitest' +import { handleEmptyArbitraryValues } from './handle-empty-arbitrary-values' +import { prefix } from './prefix' test.each([ ['group-[]:flex', 'group-[&]:flex'], @@ -16,7 +14,7 @@ test.each([ base: __dirname, }) - expect(migrateEmptyArbitraryValues(designSystem, {}, candidate)).toEqual(result) + expect(handleEmptyArbitraryValues(designSystem, {}, candidate)).toEqual(result) }) test.each([ @@ -31,7 +29,7 @@ test.each([ }) expect( - [migrateEmptyArbitraryValues, migratePrefix].reduce( + [handleEmptyArbitraryValues, prefix].reduce( (acc, step) => step(designSystem, { prefix: 'tw-' }, acc), candidate, ), diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/template/codemods/handle-empty-arbitrary-values.ts similarity index 91% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/handle-empty-arbitrary-values.ts index 0058814d9..8f7dc5e6e 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/handle-empty-arbitrary-values.ts @@ -1,9 +1,9 @@ import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -export function migrateEmptyArbitraryValues( +export function handleEmptyArbitraryValues( designSystem: DesignSystem, - _userConfig: Config | null, + _userConfig: Config, rawCandidate: string, ): string { // We can parse the candidate, nothing to do diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/important.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/important.test.ts new file mode 100644 index 000000000..6667d1e3a --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/codemods/important.test.ts @@ -0,0 +1,68 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { expect, test } from 'vitest' +import { important } from './important' + +test.each([ + ['!flex', 'flex!'], + ['min-[calc(1000px+12em)]:!flex', 'min-[calc(1000px+12em)]:flex!'], + ['md:!block', 'md:block!'], + + // Does not change non-important candidates + ['bg-blue-500', 'bg-blue-500'], + ['min-[calc(1000px+12em)]:flex', 'min-[calc(1000px+12em)]:flex'], +])('%s => %s', async (candidate, result) => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + expect( + important(designSystem, {}, candidate, { + contents: `"${candidate}"`, + start: 1, + end: candidate.length + 1, + }), + ).toEqual(result) +}) + +test('does not match false positives', async () => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + expect( + important(designSystem, {}, '!border', { + contents: `let notBorder = !border\n`, + start: 16, + end: 16 + '!border'.length, + }), + ).toEqual('!border') +}) + +test('does not replace classes in invalid positions', async () => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + function shouldNotReplace(example: string, candidate = '!border') { + expect( + important(designSystem, {}, candidate, { + contents: example, + start: example.indexOf(candidate), + end: example.indexOf(candidate) + candidate.length, + }), + ).toEqual(candidate) + } + + shouldNotReplace(`let notBorder = !border \n`) + shouldNotReplace(`{ "foo": !border.something + ""}\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) +}) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/important.ts b/packages/@tailwindcss-upgrade/src/template/codemods/important.ts new file mode 100644 index 000000000..4f2c6871f --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/codemods/important.ts @@ -0,0 +1,49 @@ +import { parseCandidate } from '../../../../tailwindcss/src/candidate' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { printCandidate } from '../candidates' +import { isSafeMigration } from '../is-safe-migration' + +// In v3 the important modifier `!` sits in front of the utility itself, not +// before any of the variants. In v4, we want it to be at the end of the utility +// so that it's always in the same location regardless of whether you used +// variants or not. +// +// So this: +// +// !flex md:!block +// +// Should turn into: +// +// flex! md:block! +export function important( + designSystem: DesignSystem, + _userConfig: Config, + rawCandidate: string, + location?: { + contents: string + start: number + end: number + }, +): string { + nextCandidate: for (let candidate of parseCandidate(rawCandidate, designSystem)) { + if (candidate.important && candidate.raw[candidate.raw.length - 1] !== '!') { + // The important migration is one of the most broad migrations with a high + // potential of matching false positives since `!` is a valid character in + // most programming languages. Since v4 is technically backward compatible + // with v3 in that it can read `!` in the front of the utility too, we err + // on the side of caution and only migrate candidates that we are certain + // are inside of a string. + if (location && !isSafeMigration(location)) { + continue nextCandidate + } + + // The printCandidate function will already put the exclamation mark in + // the right place, so we just need to mark this candidate as requiring a + // migration. + return printCandidate(designSystem, candidate) + } + } + + return rawCandidate +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/legacy-arbitrary-values.test.ts similarity index 72% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.test.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/legacy-arbitrary-values.test.ts index 8bb193b43..974cd0ca3 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/legacy-arbitrary-values.test.ts @@ -1,6 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' -import { migrateLegacyArbitraryValues } from './migrate-legacy-arbitrary-values' +import { legacyArbitraryValues } from './legacy-arbitrary-values' test.each([ ['grid-cols-[auto,1fr]', 'grid-cols-[auto_1fr]'], @@ -11,5 +11,5 @@ test.each([ base: __dirname, }) - expect(migrateLegacyArbitraryValues(designSystem, {}, candidate)).toEqual(result) + expect(legacyArbitraryValues(designSystem, {}, candidate)).toEqual(result) }) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/template/codemods/legacy-arbitrary-values.ts similarity index 72% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/legacy-arbitrary-values.ts index 2bd7d2fd5..1c60526fd 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/legacy-arbitrary-values.ts @@ -1,15 +1,16 @@ -import { cloneCandidate, parseCandidate } from '../../../../tailwindcss/src/candidate' +import { parseCandidate } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { segment } from '../../../../tailwindcss/src/utils/segment' +import { printCandidate } from '../candidates' -export function migrateLegacyArbitraryValues( +export function legacyArbitraryValues( designSystem: DesignSystem, - _userConfig: Config | null, + _userConfig: Config, rawCandidate: string, ): string { for (let candidate of parseCandidate(rawCandidate, designSystem)) { - let clone = cloneCandidate(candidate) + let clone = structuredClone(candidate) let changed = false // Convert commas to spaces. E.g.: [auto,1fr] to [auto_1fr] @@ -22,7 +23,7 @@ export function migrateLegacyArbitraryValues( clone.value.value = segment(clone.value.value, ',').join(' ') } - return changed ? designSystem.printCandidate(clone) : rawCandidate + return changed ? printCandidate(designSystem, clone) : rawCandidate } return rawCandidate diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.test.ts new file mode 100644 index 000000000..6b8bbe4c6 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.test.ts @@ -0,0 +1,76 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { expect, test } from 'vitest' +import { legacyClasses } from './legacy-classes' + +test.each([ + ['shadow', 'shadow-sm'], + ['shadow-sm', 'shadow-xs'], + ['shadow-xs', 'shadow-2xs'], + + ['inset-shadow', 'inset-shadow-sm'], + ['inset-shadow-sm', 'inset-shadow-xs'], + ['inset-shadow-xs', 'inset-shadow-2xs'], + + ['drop-shadow', 'drop-shadow-sm'], + ['drop-shadow-sm', 'drop-shadow-xs'], + + ['rounded', 'rounded-sm'], + ['rounded-sm', 'rounded-xs'], + + ['blur', 'blur-sm'], + ['blur-sm', 'blur-xs'], + + ['backdrop-blur', 'backdrop-blur-sm'], + ['backdrop-blur-sm', 'backdrop-blur-xs'], + + ['ring', 'ring-3'], + + ['blur!', 'blur-sm!'], + ['hover:blur', 'hover:blur-sm'], + ['hover:blur!', 'hover:blur-sm!'], + + ['hover:blur-sm', 'hover:blur-xs'], + ['blur-sm!', 'blur-xs!'], + ['hover:blur-sm!', 'hover:blur-xs!'], +])('%s => %s (%#)', async (candidate, result) => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + expect(await legacyClasses(designSystem, {}, candidate)).toEqual(result) +}) + +test('does not replace classes in invalid positions', async () => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + async function shouldNotReplace(example: string, candidate = 'shadow') { + expect( + await legacyClasses(designSystem, {}, candidate, { + contents: example, + start: example.indexOf(candidate), + end: example.indexOf(candidate) + candidate.length, + }), + ).toEqual(candidate) + } + + await shouldNotReplace(`let notShadow = shadow \n`) + await shouldNotReplace(`{ "foo": shadow.something + ""}\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + + // Next.js Image placeholder cases + await shouldNotReplace(``, 'blur') + await shouldNotReplace(``, 'blur') + await shouldNotReplace(``, 'blur') +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts b/packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.ts similarity index 71% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.ts index 2b088aa2e..8dad3967c 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.ts @@ -1,12 +1,12 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import path from 'node:path' import url from 'node:url' -import { cloneCandidate, type Candidate } from '../../../../tailwindcss/src/candidate' +import type { Candidate } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' -import * as version from '../../utils/version' -import { baseCandidate } from './candidates' +import { printCandidate } from '../candidates' +import { isSafeMigration } from '../is-safe-migration' const __filename = url.fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) @@ -33,8 +33,6 @@ const LEGACY_CLASS_MAP = new Map([ ['backdrop-blur-sm', 'backdrop-blur-xs'], ['ring', 'ring-3'], - - ['outline', 'outline-solid'], ]) const THEME_KEYS = new Map([ @@ -59,7 +57,7 @@ const THEME_KEYS = new Map([ ['backdrop-blur-sm', '--backdrop-blur-sm'], ['backdrop-blur-xs', '--backdrop-blur-xs'], - ['ring', '--default-ring-width'], + ['ring', '--ring-width'], ['ring-3', '--ring-width-3'], ]) @@ -67,28 +65,26 @@ const DESIGN_SYSTEMS = new DefaultMap((base) => { return __unstable__loadDesignSystem('@import "tailwindcss";', { base }) }) -export async function migrateLegacyClasses( +export async function legacyClasses( designSystem: DesignSystem, - _userConfig: Config | null, + _userConfig: Config, rawCandidate: string, + location?: { + contents: string + start: number + end: number + }, ): Promise { - // These migrations are only safe when migrating from v3 to v4. - // - // Migrating from `rounded` to `rounded-sm` once is fine (v3 -> v4). But if we - // migrate again (v4 -> v4), then `rounded-sm` would be migrated to - // `rounded-xs` which is incorrect because we already migrated this. - if (!version.isMajor(3)) { - return rawCandidate - } - let defaultDesignSystem = await DESIGN_SYSTEMS.get(__dirname) function* migrate(rawCandidate: string) { for (let candidate of designSystem.parseCandidate(rawCandidate)) { // Create a base candidate string from the candidate. // E.g.: `hover:blur!` -> `blur` - let base = baseCandidate(candidate) - let baseCandidateString = designSystem.printCandidate(base) + let baseCandidate = structuredClone(candidate) as Candidate + baseCandidate.variants = [] + baseCandidate.important = false + let baseCandidateString = printCandidate(designSystem, baseCandidate) // Find the new base candidate string. `blur` -> `blur-sm` let newBaseCandidateString = LEGACY_CLASS_MAP.get(baseCandidateString) @@ -100,11 +96,12 @@ export async function migrateLegacyClasses( // Re-apply the variants and important flag from the original candidate. // E.g.: `hover:blur!` -> `blur` -> `blur-sm` -> `hover:blur-sm!` - let newCandidate = cloneCandidate(newBaseCandidate) as Candidate + let newCandidate = structuredClone(newBaseCandidate) as Candidate newCandidate.variants = candidate.variants newCandidate.important = candidate.important yield [ + candidate, newCandidate, THEME_KEYS.get(baseCandidateString), THEME_KEYS.get(newBaseCandidateString), @@ -112,7 +109,34 @@ export async function migrateLegacyClasses( } } - for (let [toCandidate, fromThemeKey, toThemeKey] of migrate(rawCandidate)) { + for (let [fromCandidate, toCandidate, fromThemeKey, toThemeKey] of migrate(rawCandidate)) { + // Every utility that has a simple representation (e.g.: `blur`, `radius`, + // etc.`) without variants or special characters _could_ be a potential + // problem during the migration. + let isPotentialProblematicClass = (() => { + if (fromCandidate.variants.length > 0) { + return false + } + + if (fromCandidate.kind === 'arbitrary') { + return false + } + + if (fromCandidate.kind === 'static') { + return !fromCandidate.root.includes('-') + } + + if (fromCandidate.kind === 'functional') { + return fromCandidate.value === null || !fromCandidate.root.includes('-') + } + + return false + })() + + if (location && isPotentialProblematicClass && !isSafeMigration(location)) { + continue + } + if (fromThemeKey && toThemeKey) { // Migrating something that resolves to a value in the theme. let customFrom = designSystem.resolveThemeValue(fromThemeKey, true) @@ -137,7 +161,7 @@ export async function migrateLegacyClasses( } } - return designSystem.printCandidate(toCandidate) + return printCandidate(designSystem, toCandidate) } return rawCandidate diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/max-width-screen.test.ts similarity index 74% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.test.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/max-width-screen.test.ts index 91b57b3db..3e3151cbc 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/max-width-screen.test.ts @@ -1,12 +1,12 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' -import { migrateMaxWidthScreen } from './migrate-max-width-screen' +import { maxWidthScreen } from './max-width-screen' test('converts max-w-screen-* to max-w-[theme(screens.*)] (so it will later be converted to the var injection)', async () => { let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { base: __dirname, }) - let migrated = migrateMaxWidthScreen(designSystem, {}, 'max-w-screen-md') + let migrated = maxWidthScreen(designSystem, {}, 'max-w-screen-md') expect(migrated).toMatchInlineSnapshot(`"max-w-[theme(screens.md)]"`) }) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.ts b/packages/@tailwindcss-upgrade/src/template/codemods/max-width-screen.ts similarity index 81% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/max-width-screen.ts index cc9b7e9e2..4a14e5aad 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/max-width-screen.ts @@ -1,9 +1,10 @@ import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { printCandidate } from '../candidates' -export function migrateMaxWidthScreen( +export function maxWidthScreen( designSystem: DesignSystem, - _userConfig: Config | null, + _userConfig: Config, rawCandidate: string, ): string { for (let candidate of designSystem.parseCandidate(rawCandidate)) { @@ -12,7 +13,7 @@ export function migrateMaxWidthScreen( candidate.root === 'max-w' && candidate.value?.value.startsWith('screen-') ) { - return designSystem.printCandidate({ + return printCandidate(designSystem, { ...candidate, value: { ...candidate.value, diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts new file mode 100644 index 000000000..ec38d0864 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts @@ -0,0 +1,138 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { expect, test } from 'vitest' +import { handleEmptyArbitraryValues } from './handle-empty-arbitrary-values' +import { modernizeArbitraryValues } from './modernize-arbitrary-values' +import { prefix } from './prefix' + +test.each([ + // Arbitrary variants + ['[[data-visible]]:flex', 'data-visible:flex'], + ['[&[data-visible]]:flex', 'data-visible:flex'], + ['[[data-visible]&]:flex', 'data-visible:flex'], + ['[&>[data-visible]]:flex', '*:data-visible:flex'], + ['[&_>_[data-visible]]:flex', '*:data-visible:flex'], + ['[&>*]:flex', '*:flex'], + ['[&_>_*]:flex', '*:flex'], + + ['[&_[data-visible]]:flex', '**:data-visible:flex'], + ['[&_*]:flex', '**:flex'], + + ['[&:first-child]:flex', 'first:flex'], + ['[&:not(:first-child)]:flex', 'not-first:flex'], + + // in-* variants + ['[p_&]:flex', 'in-[p]:flex'], + ['[.foo_&]:flex', 'in-[.foo]:flex'], + ['[[data-visible]_&]:flex', 'in-data-visible:flex'], + // Multiple selectors, should stay as-is + ['[[data-foo][data-bar]_&]:flex', '[[data-foo][data-bar]_&]:flex'], + // Using `>` instead of ` ` should not be transformed: + ['[figure>&]:my-0', '[figure>&]:my-0'], + // Some extreme examples of what happens in the wild: + ['group-[]:flex', 'in-[.group]:flex'], + ['group-[]/name:flex', 'in-[.group\\/name]:flex'], + + // Migrate `peer-[]` to a parsable `peer-[&]` instead: + ['peer-[]:flex', 'peer-[&]:flex'], + ['peer-[]/name:flex', 'peer-[&]/name:flex'], + + // These shouldn't happen in the real world (because compound variants are + // new). But this could happen once we allow codemods to run in v4+ projects. + ['has-group-[]:flex', 'has-in-[.group]:flex'], + ['has-group-[]/name:flex', 'has-in-[.group\\/name]:flex'], + ['not-group-[]:flex', 'not-in-[.group]:flex'], + ['not-group-[]/name:flex', 'not-in-[.group\\/name]:flex'], + + // nth-child + ['[&:nth-child(2)]:flex', 'nth-2:flex'], + ['[&:not(:nth-child(2))]:flex', 'not-nth-2:flex'], + + ['[&:nth-child(-n+3)]:flex', 'nth-[-n+3]:flex'], + ['[&:not(:nth-child(-n+3))]:flex', 'not-nth-[-n+3]:flex'], + ['[&:nth-child(-n_+_3)]:flex', 'nth-[-n+3]:flex'], + ['[&:not(:nth-child(-n_+_3))]:flex', 'not-nth-[-n+3]:flex'], + + // nth-last-child + ['[&:nth-last-child(2)]:flex', 'nth-last-2:flex'], + ['[&:not(:nth-last-child(2))]:flex', 'not-nth-last-2:flex'], + + ['[&:nth-last-child(-n+3)]:flex', 'nth-last-[-n+3]:flex'], + ['[&:not(:nth-last-child(-n+3))]:flex', 'not-nth-last-[-n+3]:flex'], + ['[&:nth-last-child(-n_+_3)]:flex', 'nth-last-[-n+3]:flex'], + ['[&:not(:nth-last-child(-n_+_3))]:flex', 'not-nth-last-[-n+3]:flex'], + + // nth-child odd/even + ['[&:nth-child(odd)]:flex', 'odd:flex'], + ['[&:not(:nth-child(odd))]:flex', 'even:flex'], + ['[&:nth-child(even)]:flex', 'even:flex'], + ['[&:not(:nth-child(even))]:flex', 'odd:flex'], + + // Keep multiple attribute selectors as-is + ['[[data-visible][data-dark]]:flex', '[[data-visible][data-dark]]:flex'], + + // Complex attribute selectors with operators, quotes and insensitivity flags + ['[[data-url*="example"]]:flex', 'data-[url*="example"]:flex'], + ['[[data-url$=".com"_i]]:flex', 'data-[url$=".com"_i]:flex'], + ['[[data-url$=.com_i]]:flex', 'data-[url$=.com_i]:flex'], + + // Attribute selector wrapped in `&:is(…)` + ['[&:is([data-visible])]:flex', 'data-visible:flex'], + + // Compound arbitrary variants + ['has-[[data-visible]]:flex', 'has-data-visible:flex'], + ['has-[&:is([data-visible])]:flex', 'has-data-visible:flex'], + ['has-[&>[data-visible]]:flex', 'has-[&>[data-visible]]:flex'], + + ['has-[[data-slot=description]]:flex', 'has-data-[slot=description]:flex'], + ['has-[&:is([data-slot=description])]:flex', 'has-data-[slot=description]:flex'], + + ['has-[[aria-visible="true"]]:flex', 'has-aria-visible:flex'], + ['has-[[aria-visible]]:flex', 'has-aria-[visible]:flex'], + + ['has-[&:not(:nth-child(even))]:flex', 'has-odd:flex'], +])('%s => %s (%#)', async (candidate, result) => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + expect( + [handleEmptyArbitraryValues, modernizeArbitraryValues].reduce( + (acc, step) => step(designSystem, {}, acc), + candidate, + ), + ).toEqual(result) +}) + +test.each([ + // Should not prefix classes in arbitrary values + ['[.foo_&]:tw-flex', 'tw:in-[.foo]:flex'], + + // Should migrate `.group` classes + ['group-[]:tw-flex', 'tw:in-[.tw\\:group]:flex'], + ['group-[]/name:tw-flex', 'tw:in-[.tw\\:group\\/name]:flex'], + + // Migrate `peer-[]` to a parsable `peer-[&]` instead: + ['peer-[]:tw-flex', 'tw:peer-[&]:flex'], + ['peer-[]/name:tw-flex', 'tw:peer-[&]/name:flex'], + + // However, `.group` inside of an arbitrary variant should not be prefixed: + ['[.group_&]:tw-flex', 'tw:in-[.group]:flex'], + + // These shouldn't happen in the real world (because compound variants are + // new). But this could happen once we allow codemods to run in v4+ projects. + ['has-group-[]:tw-flex', 'tw:has-in-[.tw\\:group]:flex'], + ['has-group-[]/name:tw-flex', 'tw:has-in-[.tw\\:group\\/name]:flex'], + ['not-group-[]:tw-flex', 'tw:not-in-[.tw\\:group]:flex'], + ['not-group-[]/name:tw-flex', 'tw:not-in-[.tw\\:group\\/name]:flex'], +])('%s => %s (%#)', async (candidate, result) => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss" prefix(tw);', { + base: __dirname, + }) + + expect( + [handleEmptyArbitraryValues, prefix, modernizeArbitraryValues].reduce( + (acc, step) => step(designSystem, { prefix: 'tw-' }, acc), + candidate, + ), + ).toEqual(result) +}) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts new file mode 100644 index 000000000..3d90351b1 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts @@ -0,0 +1,402 @@ +import SelectorParser from 'postcss-selector-parser' +import { parseCandidate, type Candidate, type Variant } from '../../../../tailwindcss/src/candidate' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { isPositiveInteger } from '../../../../tailwindcss/src/utils/infer-data-type' +import { printCandidate } from '../candidates' + +function memcpy(target: T, source: U): U { + // Clear out the target object, otherwise inspecting the final object will + // look very confusing. + for (let key in target) delete target[key] + + return Object.assign(target, source) +} + +export function modernizeArbitraryValues( + designSystem: DesignSystem, + _userConfig: Config, + rawCandidate: string, +): string { + for (let candidate of parseCandidate(rawCandidate, designSystem)) { + let clone = structuredClone(candidate) + let changed = false + + for (let [variant, parent] of variants(clone)) { + // Forward modifier from the root to the compound variant + if ( + variant.kind === 'compound' && + (variant.root === 'has' || variant.root === 'not' || variant.root === 'in') + ) { + if (variant.modifier !== null) { + if ('modifier' in variant.variant) { + variant.variant.modifier = variant.modifier + variant.modifier = null + } + } + } + + // Promote `group-[]:flex` to `in-[.group]:flex` + // ^^ Yes, this is empty + // Promote `group-[]/name:flex` to `in-[.group\/name]:flex` + if ( + variant.kind === 'compound' && + variant.root === 'group' && + variant.variant.kind === 'arbitrary' && + variant.variant.selector === '&' + ) { + // `group-[]` + if (variant.modifier === null) { + changed = true + memcpy( + variant, + designSystem.parseVariant( + designSystem.theme.prefix + ? `in-[.${designSystem.theme.prefix}\\:group]` + : 'in-[.group]', + ), + ) + } + + // `group-[]/name` + else if (variant.modifier.kind === 'named') { + changed = true + memcpy( + variant, + designSystem.parseVariant( + designSystem.theme.prefix + ? `in-[.${designSystem.theme.prefix}\\:group\\/${variant.modifier.value}]` + : `in-[.group\\/${variant.modifier.value}]`, + ), + ) + } + continue + } + + // Expecting an arbitrary variant + if (variant.kind === 'arbitrary') { + // Expecting a non-relative arbitrary variant + if (variant.relative) continue + + let ast = SelectorParser().astSync(variant.selector) + + // Expecting a single selector node + if (ast.nodes.length !== 1) continue + + // `[&>*]` can be replaced with `*` + if ( + // Only top-level, so `has-[&>*]` is not supported + parent === null && + // [&_>_*]:flex + // ^ ^ ^ + ast.nodes[0].length === 3 && + ast.nodes[0].nodes[0].type === 'nesting' && + ast.nodes[0].nodes[0].value === '&' && + ast.nodes[0].nodes[1].type === 'combinator' && + ast.nodes[0].nodes[1].value === '>' && + ast.nodes[0].nodes[2].type === 'universal' + ) { + changed = true + memcpy(variant, designSystem.parseVariant('*')) + continue + } + + // `[&_*]` can be replaced with `**` + if ( + // Only top-level, so `has-[&_*]` is not supported + parent === null && + // [&_*]:flex + // ^ ^ + ast.nodes[0].length === 3 && + ast.nodes[0].nodes[0].type === 'nesting' && + ast.nodes[0].nodes[0].value === '&' && + ast.nodes[0].nodes[1].type === 'combinator' && + ast.nodes[0].nodes[1].value === ' ' && + ast.nodes[0].nodes[2].type === 'universal' + ) { + changed = true + memcpy(variant, designSystem.parseVariant('**')) + continue + } + + // `in-*` variant. If the selector ends with ` &`, we can convert it to an + // `in-*` variant. + // + // E.g.: `[[data-visible]_&]` => `in-data-visible` + if ( + // Only top-level, so `in-[&_[data-visible]]` is not supported + parent === null && + // [[data-visible]___&]:flex + // ^^^^^^^^^^^^^^ ^ ^ + ast.nodes[0].nodes.length === 3 && + ast.nodes[0].nodes[1].type === 'combinator' && + ast.nodes[0].nodes[1].value === ' ' && + ast.nodes[0].nodes[2].type === 'nesting' + ) { + ast.nodes[0].nodes.pop() // Remove the nesting node + ast.nodes[0].nodes.pop() // Remove the combinator + + changed = true + // When handling a compound like `in-[[data-visible]]`, we will first + // handle `[[data-visible]]`, then the parent `in-*` part. This means + // that we can convert `[[data-visible]_&]` to `in-[[data-visible]]`. + // + // Later this gets converted to `in-data-visible`. + memcpy(variant, designSystem.parseVariant(`in-[${ast.toString()}]`)) + continue + } + + let prefixedVariant: Variant | null = null + + // Handling a child combinator. E.g.: `[&>[data-visible]]` => `*:data-visible` + if ( + // Only top-level, so `has-[&>[data-visible]]` is not supported + parent === null && + // [&_>_[data-visible]]:flex + // ^ ^ ^^^^^^^^^^^^^^ + ast.nodes[0].length === 3 && + ast.nodes[0].nodes[0].type === 'nesting' && + ast.nodes[0].nodes[0].value === '&' && + ast.nodes[0].nodes[1].type === 'combinator' && + ast.nodes[0].nodes[1].value === '>' && + ast.nodes[0].nodes[2].type === 'attribute' + ) { + ast.nodes[0].nodes = [ast.nodes[0].nodes[2]] + prefixedVariant = designSystem.parseVariant('*') + } + + // Handling a grand child combinator. E.g.: `[&_[data-visible]]` => `**:data-visible` + if ( + // Only top-level, so `has-[&_[data-visible]]` is not supported + parent === null && + // [&_[data-visible]]:flex + // ^ ^^^^^^^^^^^^^^ + ast.nodes[0].length === 3 && + ast.nodes[0].nodes[0].type === 'nesting' && + ast.nodes[0].nodes[0].value === '&' && + ast.nodes[0].nodes[1].type === 'combinator' && + ast.nodes[0].nodes[1].value === ' ' && + ast.nodes[0].nodes[2].type === 'attribute' + ) { + ast.nodes[0].nodes = [ast.nodes[0].nodes[2]] + prefixedVariant = designSystem.parseVariant('**') + } + + // Filter out `&`. E.g.: `&[data-foo]` => `[data-foo]` + let selectorNodes = ast.nodes[0].filter((node) => node.type !== 'nesting') + + // Expecting a single selector (normal selector or attribute selector) + if (selectorNodes.length !== 1) continue + + let target = selectorNodes[0] + if (target.type === 'pseudo' && target.value === ':is') { + // Expecting a single selector node + if (target.nodes.length !== 1) continue + + // Expecting a single attribute selector + if (target.nodes[0].nodes.length !== 1) continue + + // Unwrap the selector from inside `&:is(…)` + target = target.nodes[0].nodes[0] + } + + // Expecting a pseudo selector + if (target.type === 'pseudo') { + let targetNode = target + let compoundNot = false + if (target.value === ':not') { + compoundNot = true + if (target.nodes.length !== 1) continue + if (target.nodes[0].type !== 'selector') continue + if (target.nodes[0].nodes.length !== 1) continue + if (target.nodes[0].nodes[0].type !== 'pseudo') continue + + targetNode = target.nodes[0].nodes[0] + } + + let newVariant = ((value) => { + // + if (value === ':first-letter') return 'first-letter' + else if (value === ':first-line') return 'first-line' + // + else if (value === ':file-selector-button') return 'file' + else if (value === ':placeholder') return 'placeholder' + else if (value === ':backdrop') return 'backdrop' + // Positional + else if (value === ':first-child') return 'first' + else if (value === ':last-child') return 'last' + else if (value === ':only-child') return 'only' + else if (value === ':first-of-type') return 'first-of-type' + else if (value === ':last-of-type') return 'last-of-type' + else if (value === ':only-of-type') return 'only-of-type' + // State + else if (value === ':visited') return 'visited' + else if (value === ':target') return 'target' + // Forms + else if (value === ':default') return 'default' + else if (value === ':checked') return 'checked' + else if (value === ':indeterminate') return 'indeterminate' + else if (value === ':placeholder-shown') return 'placeholder-shown' + else if (value === ':autofill') return 'autofill' + else if (value === ':optional') return 'optional' + else if (value === ':required') return 'required' + else if (value === ':valid') return 'valid' + else if (value === ':invalid') return 'invalid' + else if (value === ':in-range') return 'in-range' + else if (value === ':out-of-range') return 'out-of-range' + else if (value === ':read-only') return 'read-only' + // Content + else if (value === ':empty') return 'empty' + // Interactive + else if (value === ':focus-within') return 'focus-within' + else if (value === ':focus') return 'focus' + else if (value === ':focus-visible') return 'focus-visible' + else if (value === ':active') return 'active' + else if (value === ':enabled') return 'enabled' + else if (value === ':disabled') return 'disabled' + // + if ( + value === ':nth-child' && + targetNode.nodes.length === 1 && + targetNode.nodes[0].nodes.length === 1 && + targetNode.nodes[0].nodes[0].type === 'tag' && + targetNode.nodes[0].nodes[0].value === 'odd' + ) { + if (compoundNot) { + compoundNot = false + return 'even' + } + return 'odd' + } + if ( + value === ':nth-child' && + targetNode.nodes.length === 1 && + targetNode.nodes[0].nodes.length === 1 && + targetNode.nodes[0].nodes[0].type === 'tag' && + targetNode.nodes[0].nodes[0].value === 'even' + ) { + if (compoundNot) { + compoundNot = false + return 'odd' + } + return 'even' + } + + for (let [selector, variantName] of [ + [':nth-child', 'nth'], + [':nth-last-child', 'nth-last'], + [':nth-of-type', 'nth-of-type'], + [':nth-last-of-type', 'nth-of-last-type'], + ]) { + if (value === selector && targetNode.nodes.length === 1) { + if ( + targetNode.nodes[0].nodes.length === 1 && + targetNode.nodes[0].nodes[0].type === 'tag' && + isPositiveInteger(targetNode.nodes[0].nodes[0].value) + ) { + return `${variantName}-${targetNode.nodes[0].nodes[0].value}` + } + + return `${variantName}-[${targetNode.nodes[0].toString()}]` + } + } + + return null + })(targetNode.value) + + if (newVariant === null) continue + + // Add `not-` prefix + if (compoundNot) newVariant = `not-${newVariant}` + + let parsed = designSystem.parseVariant(newVariant) + if (parsed === null) continue + + // Update original variant + changed = true + memcpy(variant, parsed) + } + + // Expecting an attribute selector + else if (target.type === 'attribute') { + // Attribute selectors + let attributeKey = target.attribute + let attributeValue = target.value + ? target.quoted + ? `${target.quoteMark}${target.value}${target.quoteMark}` + : target.value + : null + + // Insensitive attribute selectors. E.g.: `[data-foo="value" i]` + // ^ + if (target.insensitive && attributeValue) { + attributeValue += ' i' + } + + let operator = target.operator ?? '=' + + // Migrate `data-*` + if (attributeKey.startsWith('data-')) { + changed = true + attributeKey = attributeKey.slice(5) // Remove `data-` + memcpy(variant, { + kind: 'functional', + root: 'data', + modifier: null, + value: + attributeValue === null + ? { kind: 'named', value: attributeKey } + : { kind: 'arbitrary', value: `${attributeKey}${operator}${attributeValue}` }, + } satisfies Variant) + } + + // Migrate `aria-*` + else if (attributeKey.startsWith('aria-')) { + changed = true + attributeKey = attributeKey.slice(5) // Remove `aria-` + memcpy(variant, { + kind: 'functional', + root: 'aria', + modifier: null, + value: + attributeValue === null + ? { kind: 'arbitrary', value: attributeKey } // aria-[foo] + : operator === '=' && target.value === 'true' && !target.insensitive + ? { kind: 'named', value: attributeKey } // aria-[foo="true"] or aria-[foo='true'] or aria-[foo=true] + : { kind: 'arbitrary', value: `${attributeKey}${operator}${attributeValue}` }, // aria-[foo~="true"], aria-[foo|="true"], … + } satisfies Variant) + } + } + + if (prefixedVariant) { + let idx = clone.variants.indexOf(variant) + if (idx === -1) continue + + // Ensure we inject the prefixed variant + clone.variants.splice(idx, 1, variant, prefixedVariant) + } + } + } + + return changed ? printCandidate(designSystem, clone) : rawCandidate + } + + return rawCandidate +} + +function* variants(candidate: Candidate) { + function* inner( + variant: Variant, + parent: Extract | null = null, + ): Iterable<[Variant, Extract | null]> { + yield [variant, parent] + + if (variant.kind === 'compound') { + yield* inner(variant.variant, variant) + } + } + + for (let variant of candidate.variants) { + yield* inner(variant, null) + } +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/prefix.test.ts similarity index 75% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.test.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/prefix.test.ts index f47e181af..a7ad6a03e 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/prefix.test.ts @@ -1,8 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' -import { describe, expect, test, vi } from 'vitest' -import * as versions from '../../utils/version' -import { migratePrefix } from './migrate-prefix' -vi.spyOn(versions, 'isMajor').mockReturnValue(true) +import { describe, expect, test } from 'vitest' +import { prefix } from './prefix' describe('for projects with configured prefix', () => { test.each([ @@ -33,7 +31,7 @@ describe('for projects with configured prefix', () => { base: __dirname, }) - expect(migratePrefix(designSystem, { prefix: 'tw-' }, candidate)).toEqual(result) + expect(prefix(designSystem, { prefix: 'tw-' }, candidate)).toEqual(result) }) }) @@ -42,7 +40,7 @@ test('can handle complex prefix separators', async () => { base: __dirname, }) - expect(migratePrefix(designSystem, { prefix: 'tw__' }, 'tw__flex')).toEqual('tw:flex') + expect(prefix(designSystem, { prefix: 'tw__' }, 'tw__flex')).toEqual('tw:flex') }) describe('for projects without configured prefix', () => { @@ -51,6 +49,6 @@ describe('for projects without configured prefix', () => { base: __dirname, }) - expect(migratePrefix(designSystem, {}, 'tw-flex')).toEqual('tw-flex') + expect(prefix(designSystem, {}, 'tw-flex')).toEqual('tw-flex') }) }) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.ts b/packages/@tailwindcss-upgrade/src/template/codemods/prefix.ts similarity index 79% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/prefix.ts index 776032850..47a377239 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/prefix.ts @@ -1,34 +1,21 @@ -import { decl } from '../../../../tailwindcss/src/ast' import { parseCandidate, type Candidate } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { segment } from '../../../../tailwindcss/src/utils/segment' -import * as version from '../../utils/version' +import { printCandidate } from '../candidates' let seenDesignSystems = new WeakSet() -export function migratePrefix( +export function prefix( designSystem: DesignSystem, - userConfig: Config | null, + userConfig: Config, rawCandidate: string, ): string { if (!designSystem.theme.prefix) return rawCandidate - if (!userConfig) return rawCandidate - if (!version.isMajor(3)) return rawCandidate if (!seenDesignSystems.has(designSystem)) { - designSystem.utilities.functional('group', (value) => [ - // To ensure that `@apply group` works when computing a signature - decl('--phantom-class', 'group'), - // To ensure `group` and `group/foo` are considered different classes - decl('--phantom-modifier', value.modifier?.value), - ]) - designSystem.utilities.functional('peer', (value) => [ - // To ensure that `@apply peer` works when computing a signature - decl('--phantom-class', 'peer'), - // To ensure `peer` and `peer/foo` are considered different classes - decl('--phantom-modifier', value.modifier?.value), - ]) + designSystem.utilities.functional('group', () => null) + designSystem.utilities.functional('peer', () => null) seenDesignSystems.add(designSystem) } @@ -58,7 +45,7 @@ export function migratePrefix( if (!candidate) return rawCandidate - return designSystem.printCandidate(candidate) + return printCandidate(designSystem, candidate) } // Parses a raw candidate with v3 compatible prefix syntax. This won't match if @@ -128,7 +115,7 @@ function extractV3Base( } const VALID_PREFIX = /([a-z]+)/ -export function migratePrefixValue(prefix: string): string { +export function migratePrefix(prefix: string): string { let result = VALID_PREFIX.exec(prefix.toLocaleLowerCase()) if (!result) { console.warn( diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.test.ts similarity index 73% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.test.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.test.ts index 456671dd7..a9da9e54e 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.test.ts @@ -1,8 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' -import { expect, test, vi } from 'vitest' -import * as versions from '../../utils/version' -import { migrateSimpleLegacyClasses } from './migrate-simple-legacy-classes' -vi.spyOn(versions, 'isMajor').mockReturnValue(true) +import { expect, test } from 'vitest' +import { simpleLegacyClasses } from './simple-legacy-classes' test.each([ ['overflow-ellipsis', 'text-ellipsis'], @@ -26,5 +24,5 @@ test.each([ base: __dirname, }) - expect(migrateSimpleLegacyClasses(designSystem, {}, candidate)).toEqual(result) + expect(simpleLegacyClasses(designSystem, {}, candidate)).toEqual(result) }) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.ts b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts similarity index 56% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts index 415ed524c..1251d0f2d 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts @@ -1,10 +1,10 @@ import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import * as version from '../../utils/version' +import { printCandidate } from '../candidates' // Classes that used to exist in Tailwind CSS v3, but do not exist in Tailwind // CSS v4 anymore. -const LEGACY_CLASS_MAP: Record = { +const LEGACY_CLASS_MAP = { 'overflow-ellipsis': 'text-ellipsis', 'flex-grow': 'grow', @@ -15,33 +15,16 @@ const LEGACY_CLASS_MAP: Record = { 'decoration-clone': 'box-decoration-clone', 'decoration-slice': 'box-decoration-slice', - // Since v4.1.0 - 'bg-left-top': 'bg-top-left', - 'bg-left-bottom': 'bg-bottom-left', - 'bg-right-top': 'bg-top-right', - 'bg-right-bottom': 'bg-bottom-right', - 'object-left-top': 'object-top-left', - 'object-left-bottom': 'object-bottom-left', - 'object-right-top': 'object-top-right', - 'object-right-bottom': 'object-bottom-right', + 'outline-none': 'outline-hidden', } let seenDesignSystems = new WeakSet() -export function migrateSimpleLegacyClasses( +export function simpleLegacyClasses( designSystem: DesignSystem, - _userConfig: Config | null, + _userConfig: Config, rawCandidate: string, ): string { - // `outline-none` in v3 has the same meaning as `outline-hidden` in v4. However, - // `outline-none` in v4 _also_ exists but has a different meaning. - // - // We can only migrate `outline-none` to `outline-hidden` if we are migrating a - // v3 project to v4. - if (version.isMajor(3)) { - LEGACY_CLASS_MAP['outline-none'] = 'outline-hidden' - } - // Prepare design system with the unknown legacy classes if (!seenDesignSystems.has(designSystem)) { for (let old in LEGACY_CLASS_MAP) { @@ -52,7 +35,7 @@ export function migrateSimpleLegacyClasses( for (let candidate of designSystem.parseCandidate(rawCandidate)) { if (candidate.kind === 'static' && Object.hasOwn(LEGACY_CLASS_MAP, candidate.root)) { - return designSystem.printCandidate({ + return printCandidate(designSystem, { ...candidate, root: LEGACY_CLASS_MAP[candidate.root as keyof typeof LEGACY_CLASS_MAP], }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts new file mode 100644 index 000000000..c5131fc29 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts @@ -0,0 +1,195 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { expect, test } from 'vitest' +import { themeToVar } from './theme-to-var' + +let css = String.raw + +test.each([ + // Keep candidates that don't contain `theme(…)` or `theme(…, …)` + ['[color:red]', '[color:red]'], + + // Handle special cases around `.1` in the `theme(…)` + ['[--value:theme(spacing.1)]', '[--value:--spacing(1)]'], + ['[--value:theme(fontSize.xs.1.lineHeight)]', '[--value:var(--text-xs--line-height)]'], + ['[--value:theme(spacing[1.25])]', '[--value:--spacing(1.25)]'], + + // Should not convert invalid spacing values to calc + ['[--value:theme(spacing[1.1])]', '[--value:theme(spacing[1.1])]'], + + // Convert to `var(…)` if we can resolve the path + ['[color:theme(colors.red.500)]', '[color:var(--color-red-500)]'], // Arbitrary property + ['[color:theme(colors.red.500)]/50', '[color:var(--color-red-500)]/50'], // Arbitrary property + modifier + ['bg-[theme(colors.red.500)]', 'bg-(--color-red-500)'], // Arbitrary value + ['bg-[size:theme(spacing.4)]', 'bg-[size:--spacing(4)]'], // Arbitrary value + data type hint + + // Pretty print CSS functions preceded by an operator to prevent consecutive + // operator characters. + ['w-[calc(100dvh-theme(spacing.2))]', 'w-[calc(100dvh-(--spacing(2)))]'], + ['w-[calc(100dvh+theme(spacing.2))]', 'w-[calc(100dvh+(--spacing(2)))]'], + ['w-[calc(100dvh/theme(spacing.2))]', 'w-[calc(100dvh/(--spacing(2)))]'], + ['w-[calc(100dvh*theme(spacing.2))]', 'w-[calc(100dvh*(--spacing(2)))]'], + + // Convert to `var(…)` if we can resolve the path, but keep fallback values + ['bg-[theme(colors.red.500,red)]', 'bg-(--color-red-500,red)'], + + // Keep `theme(…)` if we can't resolve the path + ['bg-[theme(colors.foo.1000)]', 'bg-[theme(colors.foo.1000)]'], + + // Keep `theme(…)` if we can't resolve the path, but still try to convert the + // fallback value. + [ + 'bg-[theme(colors.foo.1000,theme(colors.red.500))]', + 'bg-[theme(colors.foo.1000,var(--color-red-500))]', + ], + + // Use `theme(…)` (deeply nested) inside of a `calc(…)` function + ['text-[calc(theme(fontSize.xs)*2)]', 'text-[calc(var(--text-xs)*2)]'], + + // Multiple `theme(… / …)` calls should result in modern syntax of `theme(…)` + // - Can't convert to `var(…)` because that would lose the modifier. + // - Can't convert to a candidate modifier because there are multiple + // `theme(…)` calls. + // + // If we really want to, we can make a fancy migration that tries to move it + // to a candidate modifier _if_ all `theme(…)` calls use the same modifier. + [ + '[color:theme(colors.red.500/50,theme(colors.blue.500/50))]', + '[color:--theme(--color-red-500/50,--theme(--color-blue-500/50))]', + ], + [ + '[color:theme(colors.red.500/50,theme(colors.blue.500/50))]/50', + '[color:--theme(--color-red-500/50,--theme(--color-blue-500/50))]/50', + ], + + // Convert the `theme(…)`, but try to move the inline modifier (e.g. `50%`), + // to a candidate modifier. + // Arbitrary property, with simple percentage modifier + ['[color:theme(colors.red.500/75%)]', '[color:var(--color-red-500)]/75'], + + // Arbitrary property, with numbers (0-1) without a unit + ['[color:theme(colors.red.500/.12)]', '[color:var(--color-red-500)]/12'], + ['[color:theme(colors.red.500/0.12)]', '[color:var(--color-red-500)]/12'], + + // Arbitrary property, with more complex modifier (we only allow whole numbers + // as bare modifiers). Convert the complex numbers to arbitrary values instead. + ['[color:theme(colors.red.500/12.34%)]', '[color:var(--color-red-500)]/[12.34%]'], + ['[color:theme(colors.red.500/var(--opacity))]', '[color:var(--color-red-500)]/(--opacity)'], + ['[color:theme(colors.red.500/.12345)]', '[color:var(--color-red-500)]/[12.345]'], + ['[color:theme(colors.red.500/50.25%)]', '[color:var(--color-red-500)]/[50.25%]'], + + // Arbitrary value + ['bg-[theme(colors.red.500/75%)]', 'bg-(--color-red-500)/75'], + ['bg-[theme(colors.red.500/12.34%)]', 'bg-(--color-red-500)/[12.34%]'], + + // Arbitrary property that already contains a modifier + ['[color:theme(colors.red.500/50%)]/50', '[color:--theme(--color-red-500/50%)]/50'], + + // Values that don't contain only `theme(…)` calls should not be converted to + // use a modifier since the color is not the whole value. + [ + 'shadow-[shadow:inset_0px_1px_theme(colors.white/15%)]', + 'shadow-[shadow:inset_0px_1px_--theme(--color-white/15%)]', + ], + + // Arbitrary value, where the candidate already contains a modifier + // This should still migrate the `theme(…)` syntax to the modern syntax. + ['bg-[theme(colors.red.500/50%)]/50', 'bg-[--theme(--color-red-500/50%)]/50'], + + // Variants, we can't use `var(…)` especially inside of `@media(…)`. We can + // still upgrade the `theme(…)` to the modern syntax. + ['max-[theme(screens.lg)]:flex', 'max-[--theme(--breakpoint-lg)]:flex'], + // There are no variables for `--spacing` multiples, so we can't convert this + ['max-[theme(spacing.4)]:flex', 'max-[theme(spacing.4)]:flex'], + + // This test in itself doesn't make much sense. But we need to make sure + // that this doesn't end up as the modifier in the candidate itself. + ['max-[theme(spacing.4/50)]:flex', 'max-[theme(spacing.4/50)]:flex'], + + // `theme(…)` calls in another CSS function is replaced correctly. + // Additionally we remove unnecessary whitespace. + ['grid-cols-[min(50%_,_theme(spacing.80))_auto]', 'grid-cols-[min(50%,--spacing(80))_auto]'], + + // `theme(…)` calls valid in v3, but not in v4 should still be converted. + ['[--foo:theme(transitionDuration.500)]', '[--foo:theme(transitionDuration.500)]'], + + // Renamed theme keys + ['max-w-[theme(screens.md)]', 'max-w-(--breakpoint-md)'], + ['w-[theme(maxWidth.md)]', 'w-(--container-md)'], + + // Invalid cases + ['[--foo:theme(colors.red.500/50/50)]', '[--foo:theme(colors.red.500/50/50)]'], + ['[--foo:theme(colors.red.500/50/50)]/50', '[--foo:theme(colors.red.500/50/50)]/50'], + + // Partially invalid cases + [ + '[--foo:theme(colors.red.500/50/50)_theme(colors.blue.200)]', + '[--foo:theme(colors.red.500/50/50)_var(--color-blue-200)]', + ], + [ + '[--foo:theme(colors.red.500/50/50)_theme(colors.blue.200)]/50', + '[--foo:theme(colors.red.500/50/50)_var(--color-blue-200)]/50', + ], +])('%s => %s', async (candidate, result) => { + let designSystem = await __unstable__loadDesignSystem( + css` + @import 'tailwindcss'; + `, + { + base: __dirname, + }, + ) + + expect(themeToVar(designSystem, {}, candidate)).toEqual(result) +}) + +test('extended space scale converts to var or calc', async () => { + let designSystem = await __unstable__loadDesignSystem( + css` + @import 'tailwindcss'; + @theme { + --spacing-2: 2px; + --spacing-miami: 0.875rem; + } + `, + { + base: __dirname, + }, + ) + expect(themeToVar(designSystem, {}, '[--value:theme(spacing.1)]')).toEqual( + '[--value:--spacing(1)]', + ) + expect(themeToVar(designSystem, {}, '[--value:theme(spacing.2)]')).toEqual( + '[--value:var(--spacing-2)]', + ) + expect(themeToVar(designSystem, {}, '[--value:theme(spacing.miami)]')).toEqual( + '[--value:var(--spacing-miami)]', + ) + expect(themeToVar(designSystem, {}, '[--value:theme(spacing.nyc)]')).toEqual( + '[--value:theme(spacing.nyc)]', + ) +}) + +test('custom space scale converts to var', async () => { + let designSystem = await __unstable__loadDesignSystem( + css` + @import 'tailwindcss'; + @theme { + --spacing-*: initial; + --spacing-1: 0.25rem; + --spacing-2: 0.5rem; + } + `, + { + base: __dirname, + }, + ) + expect(themeToVar(designSystem, {}, '[--value:theme(spacing.1)]')).toEqual( + '[--value:var(--spacing-1)]', + ) + expect(themeToVar(designSystem, {}, '[--value:theme(spacing.2)]')).toEqual( + '[--value:var(--spacing-2)]', + ) + expect(themeToVar(designSystem, {}, '[--value:theme(spacing.3)]')).toEqual( + '[--value:theme(spacing.3)]', + ) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts similarity index 73% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts index e566e7d6e..6248d4347 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts @@ -1,11 +1,17 @@ -import { type CandidateModifier } from '../../../../tailwindcss/src/candidate' +import { + parseCandidate, + type Candidate, + type CandidateModifier, + type Variant, +} from '../../../../tailwindcss/src/candidate' import { keyPathToCssProperty } from '../../../../tailwindcss/src/compat/apply-config-to-theme' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { isValidSpacingMultiplier } from '../../../../tailwindcss/src/utils/infer-data-type' import { segment } from '../../../../tailwindcss/src/utils/segment' import { toKeyPath } from '../../../../tailwindcss/src/utils/to-key-path' import * as ValueParser from '../../../../tailwindcss/src/value-parser' -import { walk, WalkAction } from '../../../../tailwindcss/src/walk' +import { printCandidate } from '../candidates' export const enum Convert { All = 0, @@ -13,6 +19,68 @@ export const enum Convert { MigrateThemeOnly = 1 << 1, } +export function themeToVar( + designSystem: DesignSystem, + _userConfig: Config, + rawCandidate: string, +): string { + let convert = createConverter(designSystem) + + for (let candidate of parseCandidate(rawCandidate, designSystem)) { + let clone = structuredClone(candidate) + let changed = false + + if (clone.kind === 'arbitrary') { + let [newValue, modifier] = convert( + clone.value, + clone.modifier === null ? Convert.MigrateModifier : Convert.All, + ) + if (newValue !== clone.value) { + changed = true + clone.value = newValue + + if (modifier !== null) { + clone.modifier = modifier + } + } + } else if (clone.kind === 'functional' && clone.value?.kind === 'arbitrary') { + let [newValue, modifier] = convert( + clone.value.value, + clone.modifier === null ? Convert.MigrateModifier : Convert.All, + ) + if (newValue !== clone.value.value) { + changed = true + clone.value.value = newValue + + if (modifier !== null) { + clone.modifier = modifier + } + } + } + + // Handle variants + for (let variant of variants(clone)) { + if (variant.kind === 'arbitrary') { + let [newValue] = convert(variant.selector, Convert.MigrateThemeOnly) + if (newValue !== variant.selector) { + changed = true + variant.selector = newValue + } + } else if (variant.kind === 'functional' && variant.value?.kind === 'arbitrary') { + let [newValue] = convert(variant.value.value, Convert.MigrateThemeOnly) + if (newValue !== variant.value.value) { + changed = true + variant.value.value = newValue + } + } + } + + return changed ? printCandidate(designSystem, clone) : rawCandidate + } + + return rawCandidate +} + export function createConverter(designSystem: DesignSystem, { prettyPrint = false } = {}) { function convert(input: string, options = Convert.All): [string, CandidateModifier | null] { let ast = ValueParser.parse(input) @@ -28,7 +96,7 @@ export function createConverter(designSystem: DesignSystem, { prettyPrint = fals let themeModifierCount = 0 // Analyze AST - walk(ast, (node) => { + ValueParser.walk(ast, (node) => { if (node.kind !== 'function') return if (node.value !== 'theme') return @@ -36,19 +104,19 @@ export function createConverter(designSystem: DesignSystem, { prettyPrint = fals themeUsageCount += 1 // Figure out if a modifier is used - walk(node.nodes, (child) => { + ValueParser.walk(node.nodes, (child) => { // If we see a `,`, it means that we have a fallback value if (child.kind === 'separator' && child.value.includes(',')) { - return WalkAction.Stop + return ValueParser.ValueWalkAction.Stop } // If we see a `/`, we have a modifier - else if (child.kind === 'word' && child.value === '/') { + else if (child.kind === 'separator' && child.value.trim() === '/') { themeModifierCount += 1 - return WalkAction.Stop + return ValueParser.ValueWalkAction.Stop } - return WalkAction.Skip + return ValueParser.ValueWalkAction.Skip }) }) @@ -130,10 +198,6 @@ export function createConverter(designSystem: DesignSystem, { prettyPrint = fals let variable = `--${keyPathToCssProperty(toKeyPath(path))}` as const if (!designSystem.theme.get([variable])) return null - if (designSystem.theme.prefix) { - return `--${designSystem.theme.prefix}-${variable.slice(2)}` - } - return variable } @@ -173,7 +237,7 @@ function substituteFunctionsInValue( ast: ValueParser.ValueAstNode[], handle: (value: string, fallback?: string) => string | null, ) { - walk(ast, (node, ctx) => { + ValueParser.walk(ast, (node, { parent, replaceWith }) => { if (node.kind === 'function' && node.value === 'theme') { if (node.nodes.length < 1) return @@ -211,10 +275,10 @@ function substituteFunctionsInValue( fallbackValues.length > 0 ? handle(path, ValueParser.toCss(fallbackValues)) : handle(path) if (replacement === null) return - { - let idx = ctx.index - 1 + if (parent) { + let idx = parent.nodes.indexOf(node) - 1 while (idx !== -1) { - let previous = ctx.siblings[idx] + let previous = parent.nodes[idx] // Skip the space separator if (previous.kind === 'separator' && previous.value.trim() === '') { idx -= 1 @@ -242,7 +306,7 @@ function substituteFunctionsInValue( } } - return WalkAction.Replace(ValueParser.parse(replacement)) + replaceWith(ValueParser.parse(replacement)) } }) @@ -268,3 +332,16 @@ function eventuallyUnquote(value: string) { return unquoted } + +function* variants(candidate: Candidate) { + function* inner(variant: Variant): Iterable { + yield variant + if (variant.kind === 'compound') { + yield* inner(variant.variant) + } + } + + for (let variant of candidate.variants) { + yield* inner(variant) + } +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.test.ts b/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.test.ts similarity index 88% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.test.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/variant-order.test.ts index 1b14d533f..ffceaaecb 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.test.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.test.ts @@ -1,9 +1,7 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import dedent from 'dedent' -import { expect, test, vi } from 'vitest' -import * as versions from '../../utils/version' -import { migrateVariantOrder } from './migrate-variant-order' -vi.spyOn(versions, 'isMajor').mockReturnValue(true) +import { expect, test } from 'vitest' +import { variantOrder } from './variant-order' let css = dedent @@ -55,7 +53,7 @@ test.each([ base: __dirname, }) - expect(migrateVariantOrder(designSystem, {}, candidate)).toEqual(result) + expect(variantOrder(designSystem, {}, candidate)).toEqual(result) }) test('it works with custom variants', async () => { @@ -85,7 +83,7 @@ test('it works with custom variants', async () => { }, ) - expect(migrateVariantOrder(designSystem, {}, 'combinator:pseudo:atrule:underline')).toEqual( + expect(variantOrder(designSystem, {}, 'combinator:pseudo:atrule:underline')).toEqual( 'atrule:combinator:pseudo:underline', ) }) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.ts b/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.ts similarity index 85% rename from packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.ts rename to packages/@tailwindcss-upgrade/src/template/codemods/variant-order.ts index 3fd84af7c..07c7015ab 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.ts +++ b/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.ts @@ -1,24 +1,14 @@ -import { type AstNode } from '../../../../tailwindcss/src/ast' +import { walk, type AstNode } from '../../../../tailwindcss/src/ast' import { type Variant } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { walk } from '../../../../tailwindcss/src/walk' -import * as version from '../../utils/version' +import { printCandidate } from '../candidates' -export function migrateVariantOrder( +export function variantOrder( designSystem: DesignSystem, - _userConfig: Config | null, + _userConfig: Config, rawCandidate: string, ): string { - // This migration is only needed for Tailwind CSS v3 - // - // Changing the variant order when migrating from v3 to v4 is fine, but - // migrating v4 to v4 would make it unsafe because the variant order would - // flip-flop every time you run the migration. - if (!version.isMajor(3)) { - return rawCandidate - } - for (let candidate of designSystem.parseCandidate(rawCandidate)) { if (candidate.variants.length <= 1) { continue @@ -56,7 +46,7 @@ export function migrateVariantOrder( continue } - return designSystem.printCandidate({ ...candidate, variants: newOrder }) + return printCandidate(designSystem, { ...candidate, variants: newOrder }) } return rawCandidate } diff --git a/packages/@tailwindcss-upgrade/src/template/is-safe-migration.ts b/packages/@tailwindcss-upgrade/src/template/is-safe-migration.ts new file mode 100644 index 000000000..dc84d3022 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/is-safe-migration.ts @@ -0,0 +1,73 @@ +const QUOTES = ['"', "'", '`'] +const LOGICAL_OPERATORS = ['&&', '||', '?', '===', '==', '!=', '!==', '>', '>=', '<', '<='] +const CONDITIONAL_TEMPLATE_SYNTAX = [ + // Vue + /v-else-if=['"]$/, + /v-if=['"]$/, + /v-show=['"]$/, + + // Alpine + /x-if=['"]$/, + /x-show=['"]$/, +] +const NEXT_PLACEHOLDER_PROP = /placeholder=\{?['"]$/ + +export function isSafeMigration(location: { contents: string; start: number; end: number }) { + let currentLineBeforeCandidate = '' + for (let i = location.start - 1; i >= 0; i--) { + let char = location.contents.at(i)! + if (char === '\n') { + break + } + currentLineBeforeCandidate = char + currentLineBeforeCandidate + } + let currentLineAfterCandidate = '' + for (let i = location.end; i < location.contents.length; i++) { + let char = location.contents.at(i)! + if (char === '\n') { + break + } + currentLineAfterCandidate += char + } + + // Heuristic: Require the candidate to be inside quotes + let isQuoteBeforeCandidate = QUOTES.some((quote) => currentLineBeforeCandidate.includes(quote)) + let isQuoteAfterCandidate = QUOTES.some((quote) => currentLineAfterCandidate.includes(quote)) + if (!isQuoteBeforeCandidate || !isQuoteAfterCandidate) { + return false + } + + // Heuristic: Disallow object access immediately following the candidate + if (currentLineAfterCandidate[0] === '.') { + return false + } + + // Heuristic: Disallow function call expressions immediately following the candidate + if (currentLineAfterCandidate.trim().startsWith('(')) { + return false + } + + // Heuristic: Disallow logical operators preceding or following the candidate + for (let operator of LOGICAL_OPERATORS) { + if ( + currentLineAfterCandidate.trim().startsWith(operator) || + currentLineBeforeCandidate.trim().endsWith(operator) + ) { + return false + } + } + + // Heuristic: Disallow conditional template syntax + for (let rule of CONDITIONAL_TEMPLATE_SYNTAX) { + if (rule.test(currentLineBeforeCandidate)) { + return false + } + } + + // Heuristic: Disallow Next.js Image `placeholder` prop + if (NEXT_PLACEHOLDER_PROP.test(currentLineBeforeCandidate)) { + return false + } + + return true +} diff --git a/packages/@tailwindcss-upgrade/src/template/migrate.ts b/packages/@tailwindcss-upgrade/src/template/migrate.ts new file mode 100644 index 000000000..7fdc164f2 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/migrate.ts @@ -0,0 +1,104 @@ +import fs from 'node:fs/promises' +import path, { extname } from 'node:path' +import type { Config } from '../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../tailwindcss/src/design-system' +import { extractRawCandidates } from './candidates' +import { arbitraryValueToBareValue } from './codemods/arbitrary-value-to-bare-value' +import { automaticVarInjection } from './codemods/automatic-var-injection' +import { bgGradient } from './codemods/bg-gradient' +import { handleEmptyArbitraryValues } from './codemods/handle-empty-arbitrary-values' +import { important } from './codemods/important' +import { legacyArbitraryValues } from './codemods/legacy-arbitrary-values' +import { legacyClasses } from './codemods/legacy-classes' +import { maxWidthScreen } from './codemods/max-width-screen' +import { modernizeArbitraryValues } from './codemods/modernize-arbitrary-values' +import { prefix } from './codemods/prefix' +import { simpleLegacyClasses } from './codemods/simple-legacy-classes' +import { themeToVar } from './codemods/theme-to-var' +import { variantOrder } from './codemods/variant-order' +import { spliceChangesIntoString, type StringChange } from './splice-changes-into-string' + +export type Migration = ( + designSystem: DesignSystem, + userConfig: Config, + rawCandidate: string, + location?: { + contents: string + start: number + end: number + }, +) => string | Promise + +export const DEFAULT_MIGRATIONS: Migration[] = [ + handleEmptyArbitraryValues, + prefix, + important, + bgGradient, + simpleLegacyClasses, + legacyClasses, + maxWidthScreen, + themeToVar, + variantOrder, // Has to happen before migrations that modify variants + automaticVarInjection, + legacyArbitraryValues, + arbitraryValueToBareValue, + modernizeArbitraryValues, +] + +export async function migrateCandidate( + designSystem: DesignSystem, + userConfig: Config, + rawCandidate: string, + // Location is only set when migrating a candidate from a source file + location?: { + contents: string + start: number + end: number + }, +): Promise { + for (let migration of DEFAULT_MIGRATIONS) { + rawCandidate = await migration(designSystem, userConfig, rawCandidate, location) + } + return rawCandidate +} + +export default async function migrateContents( + designSystem: DesignSystem, + userConfig: Config, + contents: string, + extension: string, +): Promise { + let candidates = await extractRawCandidates(contents, extension) + + let changes: StringChange[] = [] + + for (let { rawCandidate, start, end } of candidates) { + let migratedCandidate = await migrateCandidate(designSystem, userConfig, rawCandidate, { + contents, + start, + end, + }) + + if (migratedCandidate === rawCandidate) { + continue + } + + changes.push({ + start, + end, + replacement: migratedCandidate, + }) + } + + return spliceChangesIntoString(contents, changes) +} + +export async function migrate(designSystem: DesignSystem, userConfig: Config, file: string) { + let fullPath = path.isAbsolute(file) ? file : path.resolve(process.cwd(), file) + let contents = await fs.readFile(fullPath, 'utf-8') + + await fs.writeFile( + fullPath, + await migrateContents(designSystem, userConfig, contents, extname(file)), + ) +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/prepare-config.ts b/packages/@tailwindcss-upgrade/src/template/prepare-config.ts similarity index 85% rename from packages/@tailwindcss-upgrade/src/codemods/template/prepare-config.ts rename to packages/@tailwindcss-upgrade/src/template/prepare-config.ts index d9fae442b..e936df6a3 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/prepare-config.ts +++ b/packages/@tailwindcss-upgrade/src/template/prepare-config.ts @@ -2,12 +2,12 @@ import { __unstable__loadDesignSystem, compile } from '@tailwindcss/node' import fs from 'node:fs/promises' import path, { dirname } from 'node:path' import { fileURLToPath } from 'node:url' -import { loadModule } from '../../../../@tailwindcss-node/src/compile' -import { resolveConfig } from '../../../../tailwindcss/src/compat/config/resolve-config' -import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' -import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { error, highlight, relative } from '../../utils/renderer' -import { migratePrefixValue } from './migrate-prefix' +import { loadModule } from '../../../@tailwindcss-node/src/compile' +import { resolveConfig } from '../../../tailwindcss/src/compat/config/resolve-config' +import type { Config } from '../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../tailwindcss/src/design-system' +import { error, highlight, relative } from '../utils/renderer' +import { migratePrefix } from './codemods/prefix' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) @@ -19,7 +19,7 @@ export async function prepareConfig( options: { base: string }, ): Promise<{ designSystem: DesignSystem - sources: { base: string; pattern: string }[] + globs: { base: string; pattern: string }[] userConfig: Config configFilePath: string @@ -46,7 +46,7 @@ export async function prepareConfig( let userConfig = await createResolvedUserConfig(configFilePath) - let newPrefix = userConfig.prefix ? migratePrefixValue(userConfig.prefix) : null + let newPrefix = userConfig.prefix ? migratePrefix(userConfig.prefix) : null let input = css` @import 'tailwindcss' ${newPrefix ? `prefix(${newPrefix})` : ''}; @config '${relative}'; @@ -59,7 +59,7 @@ export async function prepareConfig( return { designSystem, - sources: compiler.sources, + globs: compiler.globs, userConfig, newPrefix, configFilePath, @@ -82,7 +82,7 @@ async function createResolvedUserConfig(fullConfigPath: string): Promise ]) return resolveConfig(noopDesignSystem, [ - { base: dirname(fullConfigPath), config: unresolvedUserConfig, reference: false }, + { base: dirname(fullConfigPath), config: unresolvedUserConfig }, ]).resolvedConfig as any } diff --git a/packages/@tailwindcss-upgrade/src/utils/splice-changes-into-string.ts b/packages/@tailwindcss-upgrade/src/template/splice-changes-into-string.ts similarity index 100% rename from packages/@tailwindcss-upgrade/src/utils/splice-changes-into-string.ts rename to packages/@tailwindcss-upgrade/src/template/splice-changes-into-string.ts diff --git a/packages/@tailwindcss-upgrade/src/utils/git.ts b/packages/@tailwindcss-upgrade/src/utils/git.ts index 38edf21cb..d716fbb8a 100644 --- a/packages/@tailwindcss-upgrade/src/utils/git.ts +++ b/packages/@tailwindcss-upgrade/src/utils/git.ts @@ -1,20 +1,8 @@ import { execSync } from 'node:child_process' -export function gitRoot(cwd?: string): string | null { +export function isRepoDirty() { try { - return execSync('git rev-parse --show-toplevel', { - encoding: 'utf-8', - cwd, - stdio: ['ignore', 'pipe', 'ignore'], - }).trim() - } catch { - return null - } -} - -export function isRepoDirty(cwd?: string) { - try { - let stdout = execSync('git status --porcelain', { encoding: 'utf-8', cwd }) + let stdout = execSync('git status --porcelain', { encoding: 'utf-8' }) return stdout.trim() !== '' } catch (error) { // If it's not a git repository we don't know if it's dirty or not. But we diff --git a/packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.test.ts b/packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.test.ts new file mode 100644 index 000000000..1e6d8b1e8 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.test.ts @@ -0,0 +1,48 @@ +import { expect, it } from 'vitest' +import { hoistStaticGlobParts } from './hoist-static-glob-parts' + +it.each([ + // A basic glob + [ + { base: '/projects/project-a', pattern: './src/**/*.html' }, + [{ base: '/projects/project-a/src', pattern: '**/*.html' }], + ], + + // A glob pointing to a folder should result in `**/*` + [ + { base: '/projects/project-a', pattern: './src' }, + [{ base: '/projects/project-a/src', pattern: '**/*' }], + ], + + // A glob pointing to a file, should result in the file as the pattern + [ + { base: '/projects/project-a', pattern: './src/index.html' }, + [{ base: '/projects/project-a/src', pattern: 'index.html' }], + ], + + // A glob going up a directory, should result in the new directory as the base + [ + { base: '/projects/project-a', pattern: '../project-b/src/**/*.html' }, + [{ base: '/projects/project-b/src', pattern: '**/*.html' }], + ], + + // A glob with curlies, should be expanded to multiple globs + [ + { base: '/projects/project-a', pattern: '../project-{b,c}/src/**/*.html' }, + [ + { base: '/projects/project-b/src', pattern: '**/*.html' }, + { base: '/projects/project-c/src', pattern: '**/*.html' }, + ], + ], + [ + { base: '/projects/project-a', pattern: '../project-{b,c}/src/**/*.{js,html}' }, + [ + { base: '/projects/project-b/src', pattern: '**/*.js' }, + { base: '/projects/project-b/src', pattern: '**/*.html' }, + { base: '/projects/project-c/src', pattern: '**/*.js' }, + { base: '/projects/project-c/src', pattern: '**/*.html' }, + ], + ], +])('should hoist the static parts of the glob: %s', (input, output) => { + expect(hoistStaticGlobParts(input)).toEqual(output) +}) diff --git a/packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.ts b/packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.ts new file mode 100644 index 000000000..18d587998 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/utils/hoist-static-glob-parts.ts @@ -0,0 +1,82 @@ +import { normalizePath } from '@tailwindcss/node' +import braces from 'braces' +import path from 'node:path' + +interface GlobEntry { + base: string + pattern: string +} + +export function hoistStaticGlobParts(entry: GlobEntry): GlobEntry[] { + return braces(entry.pattern, { expand: true }).map((pattern) => { + let clone = { ...entry } + let [staticPart, dynamicPart] = splitPattern(pattern) + + // Move static part into the `base`. + let absolutePosixPath = normalizePath(entry.base) + + if (staticPart !== null) { + clone.base = path.posix.join(absolutePosixPath, staticPart) + } else { + clone.base = absolutePosixPath + } + + // Move dynamic part into the `pattern`. + if (dynamicPart === null) { + clone.pattern = '**/*' + } else { + clone.pattern = dynamicPart + } + + // If the pattern looks like a file, move the file name from the `base` to + // the `pattern`. + let file = path.basename(clone.base) + if (file.includes('.')) { + clone.pattern = file + clone.base = path.dirname(clone.base) + } + + return clone + }) +} + +// Split a glob pattern into a `static` and `dynamic` part. +// +// Assumption: we assume that all globs are expanded, which means that the only +// dynamic parts are using `*`. +// +// E.g.: +// Original input: `../project-b/**/*.{html,js}` +// Expanded input: `../project-b/**/*.html` & `../project-b/**/*.js` +// Split on first input: ("../project-b", "**/*.html") +// Split on second input: ("../project-b", "**/*.js") +function splitPattern(pattern: string): [staticPart: string | null, dynamicPart: string | null] { + // No dynamic parts, so we can just return the input as-is. + if (!pattern.includes('*')) { + return [pattern, null] + } + + let lastSlashPosition: number | null = null + + for (let i = 0; i < pattern.length; i++) { + let c = pattern[i] + if (c === '/') { + lastSlashPosition = i + } + + if (c === '*' || c === '!') { + break + } + } + + // Very first character is a `*`, therefore there is no static part, only a + // dynamic part. + if (lastSlashPosition === null) { + return [null, pattern] + } + + let staticPart = pattern.slice(0, lastSlashPosition).trim() + let dynamicPart = pattern.slice(lastSlashPosition + 1).trim() + + return [staticPart || null, dynamicPart || null] +} diff --git a/packages/@tailwindcss-upgrade/src/utils/package-version.ts b/packages/@tailwindcss-upgrade/src/utils/package-version.ts index 64a1e7ed1..833ccadf2 100644 --- a/packages/@tailwindcss-upgrade/src/utils/package-version.ts +++ b/packages/@tailwindcss-upgrade/src/utils/package-version.ts @@ -1,4 +1,3 @@ -import { readFileSync } from 'node:fs' import fs from 'node:fs/promises' import { resolveJsId } from './resolve' @@ -16,14 +15,3 @@ export async function getPackageVersion(pkg: string, base: string): Promise = { bun: '-d', } -const manifests = new DefaultMap((base) => { - try { - let packageJsonPath = resolve(base, 'package.json') - return readFileSync(packageJsonPath, 'utf-8') - } catch { - return '' - } -}) - export function pkg(base: string) { return { - async manager() { - return await packageManagerForBase.get(base) - }, async add(packages: string[], location: 'dependencies' | 'devDependencies' = 'dependencies') { let packageManager = await packageManagerForBase.get(base) let args = packages.slice() @@ -34,13 +21,6 @@ export function pkg(base: string) { args.push(SAVE_DEV[packageManager] || SAVE_DEV.default) } - // Allow running the `pnpm` command in the workspace root without - // erroring. Can't just use `--workspace-root` because that will force - // install dependencies in the workspace root. - if (packageManager === 'pnpm') { - args.push('--ignore-workspace-root-check') - } - let command = `${packageManager} add ${args.join(' ')}` try { return await exec(command, { cwd: base }) @@ -49,13 +29,8 @@ export function pkg(base: string) { prefix: '↳ ', }) throw e - } finally { - manifests.delete(base) } }, - has(name: string) { - return manifests.get(base).includes(`"${name}":`) - }, async remove(packages: string[]) { let packageManager = await packageManagerForBase.get(base) let command = `${packageManager} remove ${packages.join(' ')}` @@ -66,8 +41,6 @@ export function pkg(base: string) { prefix: '↳ ', }) throw e - } finally { - manifests.delete(base) } }, } diff --git a/packages/@tailwindcss-upgrade/src/utils/renderer.ts b/packages/@tailwindcss-upgrade/src/utils/renderer.ts index c9a967801..38bd9826a 100644 --- a/packages/@tailwindcss-upgrade/src/utils/renderer.ts +++ b/packages/@tailwindcss-upgrade/src/utils/renderer.ts @@ -44,7 +44,7 @@ export function wordWrap(text: string, width: number): string[] { // Handle text with newlines by maintaining the newlines, then splitting // each line separately. if (text.includes('\n')) { - return text.split('\n').flatMap((line) => (line ? wordWrap(line, width) : [''])) + return text.split('\n').flatMap((line) => wordWrap(line, width)) } let words = text.split(' ') diff --git a/packages/@tailwindcss-upgrade/src/utils/version.ts b/packages/@tailwindcss-upgrade/src/utils/version.ts deleted file mode 100644 index 27bc44f2b..000000000 --- a/packages/@tailwindcss-upgrade/src/utils/version.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { execSync } from 'node:child_process' -import semver from 'semver' -import { DefaultMap } from '../../../tailwindcss/src/utils/default-map' -import { getPackageVersionSync } from './package-version' - -/** - * Must be of major version. - * - * E.g.: `isMajor(3)` - */ -export function isMajor(version: number) { - return semver.satisfies(installedTailwindVersion(), `>=${version}.0.0 <${version + 1}.0.0`) -} - -/** - * Must be of greater than the current major version including minor and patch. - * - * E.g.: `isGreaterThan(3)` - */ -export function isGreaterThan(version: number) { - return semver.gte(installedTailwindVersion(), `${version + 1}.0.0`) -} - -let cache = new DefaultMap((base) => { - let tailwindVersion = getPackageVersionSync('tailwindcss', base) - if (!tailwindVersion) throw new Error('Tailwind CSS is not installed') - return tailwindVersion -}) - -export function installedTailwindVersion(base = process.cwd()): string { - return cache.get(base) -} - -let expectedCache = new DefaultMap((base) => { - try { - // This will report a problem if the package.json/package-lock.json - // mismatches with the installed version in node_modules. - // - // Also tested this with Bun and PNPM, both seem to work fine. - execSync('npm ls tailwindcss --json', { cwd: base, stdio: 'pipe' }) - return installedTailwindVersion(base) - } catch (_e) { - try { - let e = _e as { stdout: Buffer } - let data = JSON.parse(e.stdout.toString()) - - return ( - // Could be a sub-dependency issue, but we are only interested in - // the top-level version mismatch. - /"(.*?)" from the root project/.exec(data.dependencies.tailwindcss.invalid)?.[1] ?? - // Fallback to the installed version - installedTailwindVersion(base) - ) - } catch { - // We don't know how to verify, so let's just return the installed - // version to not block the user. - return installedTailwindVersion(base) - } - } -}) -export function expectedTailwindVersion(base = process.cwd()): string { - return expectedCache.get(base) -} diff --git a/packages/@tailwindcss-upgrade/src/utils/walk-variants.ts b/packages/@tailwindcss-upgrade/src/utils/walk-variants.ts deleted file mode 100644 index e0dd16899..000000000 --- a/packages/@tailwindcss-upgrade/src/utils/walk-variants.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Candidate, Variant } from '../../../tailwindcss/src/candidate' - -export function* walkVariants(candidate: Candidate) { - function* inner( - variant: Variant, - parent: Extract | null = null, - ): Iterable<[Variant, Extract | null]> { - yield [variant, parent] - - if (variant.kind === 'compound') { - yield* inner(variant.variant, variant) - } - } - - for (let variant of candidate.variants) { - yield* inner(variant, null) - } -} diff --git a/packages/@tailwindcss-upgrade/src/utils/write-file-safely.ts b/packages/@tailwindcss-upgrade/src/utils/write-file-safely.ts deleted file mode 100644 index 2ff99f9e7..000000000 --- a/packages/@tailwindcss-upgrade/src/utils/write-file-safely.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { randomUUID } from 'node:crypto' -import fs from 'node:fs/promises' -import path from 'node:path' - -export async function writeFileSafely(file: string, contents: string) { - // Resolve symlinks so the rename replaces the actual target file rather than - // replacing the symlink itself with a regular file. - let realFile = await fs.realpath(file).catch(() => file) - - // Start by creating a new file in the current directory that is guaranteed to - // be unique (via `uuid`). We can embed the `process.id` in case we need to - // debug things later. - // - // While we can write this to a more global `/tmp` folder, I want to be 100% - // sure that we are on the same file system (same drive) so the rename - // operation is atomic. Once the file is written, we will rename the file. If - // this fails, the old file is still intact, if it works we have an updated - // file. - // - // If this still causes problems (but it will slow things down): - // 1. We could make sure that we inherit the file permissions - // 2. Use an explicit fsync to force a flush to disk - let temporaryFile = path.join( - path.dirname(realFile), - `.${path.basename(realFile)}.tailwind-upgrade.${process.pid}.${randomUUID()}.tmp`, - ) - - // Write file uses the `w` flag by default, which is defined as: - // > Open file for writing. The file is created (if it does not exist) or truncated (if it exists). - // > https://nodejs.org/api/fs.html#file-system-flags - // - // Which means that if this function is actively running, and you cancel the - // process at the wrong time, then the truncated files are present. Since all - // these migrations happen in parallel, multiple files are open and available - // to be written to, it could mean in multiple truncated files. - // - // Writing to a temp file first means that if the process is cancelled at this - // point, that the old original file is still correct. - // - // The rename part should be atomic (especially because we guarantee it to be - // on the same file system) so this either succeeds or doesn't happen. - try { - await fs.writeFile(temporaryFile, contents, 'utf8') - await fs.rename(temporaryFile, realFile) - } catch (error) { - await fs.unlink(temporaryFile).catch(() => {}) - throw error - } -} diff --git a/packages/@tailwindcss-vite/README.md b/packages/@tailwindcss-vite/README.md index 1a5d25c73..95ec9d87d 100644 --- a/packages/@tailwindcss-vite/README.md +++ b/packages/@tailwindcss-vite/README.md @@ -13,10 +13,10 @@

- Build Status + Build Status Total Downloads - Latest Release - License + Latest Release + License

--- @@ -27,50 +27,14 @@ For full documentation, visit [tailwindcss.com](https://tailwindcss.com). ## Community -For help, discussion about best practices, or feature ideas: +For help, discussion about best practices, or any other conversation that would benefit from being searchable: -[Discuss Tailwind CSS on GitHub](https://github.com/tailwindlabs/tailwindcss/discussions) +[Discuss Tailwind CSS on GitHub](https://github.com/tailwindcss/tailwindcss/discussions) + +For chatting with others using the framework: + +[Join the Tailwind CSS Discord Server](https://discord.gg/7NF8GNe) ## Contributing -If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md) **before submitting a pull request**. - ---- - -## `@tailwindcss/vite` plugin API - -### Enabling or disabling Lightning CSS - -By default, this plugin detects whether or not the CSS is being built for production by checking the `NODE_ENV` environment variable. When building for production Lightning CSS will be enabled otherwise it is disabled. - -If you want to always enable or disable Lightning CSS the `optimize` option may be used: - -```js -import tailwindcss from '@tailwindcss/vite' -import { defineConfig } from 'vite' - -export default defineConfig({ - plugins: [ - tailwindcss({ - // Disable Lightning CSS optimization - optimize: false, - }), - ], -}) -``` - -It's also possible to keep Lightning CSS enabled but disable minification: - -```js -import tailwindcss from '@tailwindcss/vite' -import { defineConfig } from 'vite' - -export default defineConfig({ - plugins: [ - tailwindcss({ - // Enable Lightning CSS but disable minification - optimize: { minify: false }, - }), - ], -}) -``` +If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindcss/tailwindcss/blob/next/.github/CONTRIBUTING.md) **before submitting a pull request**. diff --git a/packages/@tailwindcss-vite/package.json b/packages/@tailwindcss-vite/package.json index 821704a1d..3aae1c7cb 100644 --- a/packages/@tailwindcss-vite/package.json +++ b/packages/@tailwindcss-vite/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/vite", - "version": "4.3.3", + "version": "4.0.15", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "repository": { @@ -24,12 +24,13 @@ "exports": { ".": { "types": "./dist/index.d.mts", - "default": "./dist/index.mjs" + "import": "./dist/index.mjs" } }, "dependencies": { "@tailwindcss/node": "workspace:*", "@tailwindcss/oxide": "workspace:*", + "lightningcss": "catalog:", "tailwindcss": "workspace:*" }, "devDependencies": { @@ -37,6 +38,6 @@ "vite": "catalog:" }, "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7 || ^8" + "vite": "^5.2.0 || ^6" } } diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts index 8fc660eaa..1d27ee168 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -1,178 +1,42 @@ -import { - compile, - env, - Features, - Instrumentation, - normalizePath, - optimize, - toSourceMap, -} from '@tailwindcss/node' +import { compile, env, Features, Instrumentation, normalizePath } from '@tailwindcss/node' import { clearRequireCache } from '@tailwindcss/node/require-cache' import { Scanner } from '@tailwindcss/oxide' +import { Features as LightningCssFeatures, transform } from 'lightningcss' import fs from 'node:fs/promises' import path from 'node:path' -import type { Environment, InternalResolveOptions, Plugin, ResolvedConfig } from 'vite' -import * as vite from 'vite' +import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite' const DEBUG = env.DEBUG const SPECIAL_QUERY_RE = /[?&](?:worker|sharedworker|raw|url)\b/ const COMMON_JS_PROXY_RE = /\?commonjs-proxy/ -const INLINE_STYLE_ID_RE = /[?&]index=\d+\.css$/ +const INLINE_STYLE_ID_RE = /[?&]index\=\d+\.css$/ -export type PluginOptions = { - /** - * Optimize and minify the output CSS. - */ - optimize?: boolean | { minify?: boolean } -} - -function createCustomResolver( - resolvers: ((id: string, importer: string) => Promise)[], - filter = (_path: string) => true, -) { - return async (id: string, base: string) => { - // The resolver expects an `importer` file. We don't really know where the - // current `id` was imported from, but Vite will essentially do a - // `path.dirname(importer)` so it doesn't really matter. - // - // It does matter that this is a file, otherwise we would go up a directory, - // which means that we would be resolving files from a parent folder first, - // instead of the current folder we are in. - let importer = path.resolve(base, '__placeholder__.ts') - - for (let resolver of resolvers) { - let resolved = await resolver(id, importer) - - // If we didn't resolve, we don't have to bail immediately, but we can try - // the next resolver - if (!resolved) continue - - if (resolved === id) continue - - // Looks like a relative file, let's resolve it to an absolute path - if (resolved[0] === '.') resolved = path.resolve(base, resolved) - - // Must adhere to additional filters (e.g.: must be a .css file) - if (!filter(resolved)) continue - - // If it's not an absolute path, then we don't really know how to read - // the file from disk. - if (!path.isAbsolute(resolved)) continue - - return resolved - } - } -} - -export default function tailwindcss(opts: PluginOptions = {}): Plugin[] { +export default function tailwindcss(): Plugin[] { + let servers: ViteDevServer[] = [] let config: ResolvedConfig | null = null - let rootsByEnv = new DefaultMap>((env: string) => new Map()) let isSSR = false - let shouldOptimize = true - let minify = true + let minify = false - function createRoot(env: Environment | null, id: string) { - type ResolveFn = (id: string, base: string) => Promise - - let customCssResolver: ResolveFn - let customJsResolver: ResolveFn - - if (!env) { - // Older, pre-environment Vite API - // TODO: Can we drop this?? - let cssResolver = config!.createResolver({ - ...config!.resolve, - extensions: ['.css'], - mainFields: ['style'], - conditions: ['style', 'development|production'], - tryIndex: false, - preferRelative: true, - }) - - let jsResolver = config!.createResolver(config!.resolve) - - customCssResolver = createCustomResolver( - [ - (id, importer) => cssResolver(id, importer, true, isSSR), - (id, importer) => cssResolver(id, importer, false, isSSR), - ], - (path) => path.endsWith('.css'), - ) - customJsResolver = createCustomResolver( - [ - (id, importer) => jsResolver(id, importer, true, isSSR), - (id, importer) => jsResolver(id, importer, false, isSSR), - ], - (path) => !path.endsWith('.css'), - ) - } else { - type ResolveIdFn = ( - environment: Environment, - id: string, - importer?: string, - aliasOnly?: boolean, - ) => Promise - - // There are cases where Environment API is available, - // but `createResolver` is still overridden (for example astro v5) - // - // Copied as-is from vite, because this function is not a part of public API - // - // TODO: Remove this function and pre-environment code when Vite < 7 is no longer supported - function createBackCompatIdResolver( - config: ResolvedConfig, - options?: Partial, - ): ResolveIdFn { - const compatResolve = config.createResolver(options) - let resolve: ResolveIdFn - return async (environment, id, importer, aliasOnly) => { - if (environment.name === 'client' || environment.name === 'ssr') { - return compatResolve(id, importer, aliasOnly, environment.name === 'ssr') - } - resolve ??= vite.createIdResolver(config, options) - return resolve(environment, id, importer, aliasOnly) - } - } - - // Newer Vite versions - let cssResolver = createBackCompatIdResolver(env.config, { - ...env.config.resolve, - extensions: ['.css'], - mainFields: ['style'], - conditions: ['style', 'development|production'], - tryIndex: false, - preferRelative: true, - }) - - let jsResolver = createBackCompatIdResolver(env.config, env.config.resolve) - - customCssResolver = createCustomResolver( - [ - (id, importer) => cssResolver(env, id, importer, true), - (id, importer) => cssResolver(env, id, importer, false), - ], - (path) => path.endsWith('.css'), - ) - customJsResolver = createCustomResolver( - [ - (id, importer) => jsResolver(env, id, importer, true), - (id, importer) => jsResolver(env, id, importer, false), - ], - (path) => !path.endsWith('.css'), - ) + let roots: DefaultMap = new DefaultMap((id) => { + let cssResolver = config!.createResolver({ + ...config!.resolve, + extensions: ['.css'], + mainFields: ['style'], + conditions: ['style', 'development|production'], + tryIndex: false, + preferRelative: true, + }) + function customCssResolver(id: string, base: string) { + return cssResolver(id, base, true, isSSR) } - return new Root( - id, - config!.root, - // Currently, Vite only supports CSS source maps in development and they - // are off by default. Check to see if we need them or not. - config?.css.devSourcemap ?? false, - customCssResolver, - customJsResolver, - ) - } + let jsResolver = config!.createResolver(config!.resolve) + function customJsResolver(id: string, base: string) { + return jsResolver(id, base, true, isSSR) + } + return new Root(id, config!.root, customCssResolver, customJsResolver) + }) return [ { @@ -180,24 +44,14 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] { name: '@tailwindcss/vite:scan', enforce: 'pre', + configureServer(server) { + servers.push(server) + }, + async configResolved(_config) { config = _config + minify = config.build.cssMinify !== false isSSR = config.build.ssr !== false && config.build.ssr !== undefined - - // By default we optimize CSS during the build phase but if the user - // provides explicit options we'll use those instead - if (opts.optimize !== undefined) { - shouldOptimize = opts.optimize !== false - } - - // Minification is also performed when optimizing as long as it's also - // enabled in Vite - minify = shouldOptimize && config.build.cssMinify !== false - - // But again, the user can override that choice explicitly - if (typeof opts.optimize === 'object') { - minify = opts.optimize.minify !== false - } }, }, @@ -206,35 +60,23 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] { name: '@tailwindcss/vite:generate:serve', apply: 'serve', enforce: 'pre', - transform: { - filter: { - id: { - exclude: [/\/\.vite\//, SPECIAL_QUERY_RE, COMMON_JS_PROXY_RE], - include: [/\.css(?:\?.*)?$/, /&lang\.css/, INLINE_STYLE_ID_RE], - }, - }, - async handler(src, id) { - if (!isPotentialCssRootFile(id)) return - using I = new Instrumentation() - DEBUG && I.start('[@tailwindcss/vite] Generate CSS (serve)') + async transform(src, id, options) { + if (!isPotentialCssRootFile(id)) return - let roots = rootsByEnv.get(this.environment?.name ?? 'default') - let root = roots.get(id) - if (!root) { - root ??= createRoot(this.environment ?? null, id) - roots.set(id, root) - } + using I = new Instrumentation() + DEBUG && I.start('[@tailwindcss/vite] Generate CSS (serve)') - let result = await root.generate(src, (file) => this.addWatchFile(file), I) - if (!result) { - roots.delete(id) - return - } + let root = roots.get(id) - DEBUG && I.end('[@tailwindcss/vite] Generate CSS (serve)') - return result - }, + let generated = await root.generate(src, (file) => this.addWatchFile(file), I) + if (!generated) { + roots.delete(id) + return src + } + + DEBUG && I.end('[@tailwindcss/vite] Generate CSS (serve)') + return { code: generated } }, }, @@ -243,44 +85,27 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] { name: '@tailwindcss/vite:generate:build', apply: 'build', enforce: 'pre', - transform: { - filter: { - id: { - exclude: [/\/\.vite\//, SPECIAL_QUERY_RE, COMMON_JS_PROXY_RE], - include: [/\.css(?:\?.*)?$/, /&lang\.css/, INLINE_STYLE_ID_RE], - }, - }, - async handler(src, id) { - if (!isPotentialCssRootFile(id)) return - using I = new Instrumentation() - DEBUG && I.start('[@tailwindcss/vite] Generate CSS (build)') + async transform(src, id) { + if (!isPotentialCssRootFile(id)) return - let roots = rootsByEnv.get(this.environment?.name ?? 'default') - let root = roots.get(id) - if (!root) { - root ??= createRoot(this.environment ?? null, id) - roots.set(id, root) - } + using I = new Instrumentation() + DEBUG && I.start('[@tailwindcss/vite] Generate CSS (build)') - let result = await root.generate(src, (file) => this.addWatchFile(file), I) - if (!result) { - roots.delete(id) - return - } - DEBUG && I.end('[@tailwindcss/vite] Generate CSS (build)') + let root = roots.get(id) - if (shouldOptimize) { - DEBUG && I.start('[@tailwindcss/vite] Optimize CSS') - result = optimize(result.code, { - minify, - map: result.map, - }) - DEBUG && I.end('[@tailwindcss/vite] Optimize CSS') - } + let generated = await root.generate(src, (file) => this.addWatchFile(file), I) + if (!generated) { + roots.delete(id) + return src + } + DEBUG && I.end('[@tailwindcss/vite] Generate CSS (build)') - return result - }, + DEBUG && I.start('[@tailwindcss/vite] Optimize CSS') + generated = optimizeCss(generated, { minify }) + DEBUG && I.end('[@tailwindcss/vite] Optimize CSS') + + return { code: generated } }, }, ] satisfies Plugin[] @@ -292,18 +117,52 @@ function getExtension(id: string) { } function isPotentialCssRootFile(id: string) { - if (id.includes('/.vite/')) return false - - // Don't intercept special static asset resources - if (SPECIAL_QUERY_RE.test(id)) return false - if (COMMON_JS_PROXY_RE.test(id)) return false - + if (id.includes('/.vite/')) return let extension = getExtension(id) - let isCssFile = extension === 'css' || id.includes('&lang.css') || id.match(INLINE_STYLE_ID_RE) - + let isCssFile = + (extension === 'css' || id.includes('&lang.css') || id.match(INLINE_STYLE_ID_RE)) && + // Don't intercept special static asset resources + !SPECIAL_QUERY_RE.test(id) && + !COMMON_JS_PROXY_RE.test(id) return isCssFile } +function optimizeCss( + input: string, + { file = 'input.css', minify = false }: { file?: string; minify?: boolean } = {}, +) { + function optimize(code: Buffer | Uint8Array | any) { + return transform({ + filename: file, + code, + minify, + sourceMap: false, + drafts: { + customMedia: true, + }, + nonStandard: { + deepSelectorCombinator: true, + }, + include: LightningCssFeatures.Nesting, + exclude: + LightningCssFeatures.LogicalProperties | + LightningCssFeatures.DirSelector | + LightningCssFeatures.LightDark, + targets: { + safari: (16 << 16) | (4 << 8), + ios_saf: (16 << 16) | (4 << 8), + firefox: 128 << 16, + chrome: 111 << 16, + }, + errorRecovery: true, + }).code + } + + // Running Lightning CSS twice to ensure that adjacent rules are merged after + // nesting is applied. This creates a more optimized output. + return optimize(optimize(Buffer.from(input))).toString() +} + function idToPath(id: string) { return path.resolve(id.replace(/\?.*$/, '')) } @@ -351,7 +210,6 @@ class Root { private id: string, private base: string, - private enableSourceMaps: boolean, private customCssResolver: (id: string, base: string) => Promise, private customJsResolver: (id: string, base: string) => Promise, ) {} @@ -362,33 +220,20 @@ class Root { content: string, _addWatchFile: (file: string) => void, I: Instrumentation, - ): Promise< - | { - code: string - map: string | undefined - } - | false - > { - let inputPath = idToPath(this.id) - + ): Promise { function addWatchFile(file: string) { - // Don't watch the input file since it's already a dependency and causes - // issues with some setups (e.g. Qwik). - if (file === inputPath) { - return - } - // Scanning `.svg` file containing a `#` or `?` in the path will // crash Vite. We work around this for now by ignoring updates to them. // // https://github.com/tailwindlabs/tailwindcss/issues/16877 - if (/[#?].*\.svg$/.test(file)) { + if (/[\#\?].*\.svg$/.test(file)) { return } _addWatchFile(file) } let requiresBuildPromise = this.requiresBuild() + let inputPath = idToPath(this.id) let inputBase = path.dirname(path.resolve(inputPath)) if (!this.compiler || !this.scanner || (await requiresBuildPromise)) { @@ -400,7 +245,6 @@ class Root { DEBUG && I.start('Setup compiler') let addBuildDependenciesPromises: Promise[] = [] this.compiler = await compile(content, { - from: this.enableSourceMaps ? this.id : undefined, base: inputBase, shouldRewriteUrls: true, onDependency: (path) => { @@ -424,12 +268,12 @@ class Root { // No root specified, auto-detect based on the `**/*` pattern if (this.compiler.root === null) { - return [{ base: this.base, pattern: '**/*', negated: false }] + return [{ base: this.base, pattern: '**/*' }] } // Use the specified root - return [{ ...this.compiler.root, negated: false }] - })().concat(this.compiler.sources) + return [this.compiler.root] + })().concat(this.compiler.globs) this.scanner = new Scanner({ sources }) DEBUG && I.end('Setup scanner') @@ -442,11 +286,7 @@ class Root { if ( !( this.compiler.features & - (Features.AtApply | - Features.JsPluginCompat | - Features.ThemeFunction | - Features.Utilities | - Features.Variants) + (Features.AtApply | Features.JsPluginCompat | Features.ThemeFunction | Features.Utilities) ) ) { return false @@ -464,7 +304,6 @@ class Root { } if (this.compiler.features & Features.Utilities) { - DEBUG && I.start('Register dependency messages') // Watch individual files found via custom `@source` paths for (let file of this.scanner.files) { addWatchFile(file) @@ -501,21 +340,13 @@ class Root { } } } - DEBUG && I.end('Register dependency messages') } DEBUG && I.start('Build CSS') - let code = this.compiler.build([...this.candidates]) + let result = this.compiler.build([...this.candidates]) DEBUG && I.end('Build CSS') - DEBUG && I.start('Build Source Map') - let map = this.enableSourceMaps ? toSourceMap(this.compiler.buildSourceMap()).raw : undefined - DEBUG && I.end('Build Source Map') - - return { - code, - map, - } + return result } private async addBuildDependency(path: string) { diff --git a/packages/@tailwindcss-webpack/README.md b/packages/@tailwindcss-webpack/README.md deleted file mode 100644 index 56484ed7f..000000000 --- a/packages/@tailwindcss-webpack/README.md +++ /dev/null @@ -1,102 +0,0 @@ -

- - - - - Tailwind CSS - - -

- -

- A utility-first CSS framework for rapidly building custom user interfaces. -

- -

- Build Status - Total Downloads - Latest Release - License -

- ---- - -## Documentation - -For full documentation, visit [tailwindcss.com](https://tailwindcss.com). - -## Community - -For help, discussion about best practices, or feature ideas: - -[Discuss Tailwind CSS on GitHub](https://github.com/tailwindlabs/tailwindcss/discussions) - -## Contributing - -If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md) **before submitting a pull request**. - ---- - -## @tailwindcss/webpack - -A webpack loader for Tailwind CSS v4. - -## Installation - -```sh -npm install @tailwindcss/webpack -``` - -### Usage - -```javascript -// webpack.config.js -const MiniCssExtractPlugin = require('mini-css-extract-plugin') - -module.exports = { - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /\.css$/i, - use: [MiniCssExtractPlugin.loader, 'css-loader', '@tailwindcss/webpack'], - }, - ], - }, -} -``` - -Then create a CSS file that imports Tailwind: - -```css -/* src/index.css */ -@import 'tailwindcss'; -``` - -### Options - -#### `base` - -The base directory to scan for class candidates. Defaults to the current working directory. - -```javascript -{ - loader: '@tailwindcss/webpack', - options: { - base: process.cwd(), - }, -} -``` - -#### `optimize` - -Whether to optimize and minify the output CSS. Defaults to `true` in production mode. - -```javascript -{ - loader: '@tailwindcss/webpack', - options: { - optimize: true, // or { minify: true } - }, -} -``` diff --git a/packages/@tailwindcss-webpack/src/index.cts b/packages/@tailwindcss-webpack/src/index.cts deleted file mode 100644 index dbdded748..000000000 --- a/packages/@tailwindcss-webpack/src/index.cts +++ /dev/null @@ -1,5 +0,0 @@ -import tailwindLoader from './index.ts' - -// CommonJS export for webpack loaders - must be the function directly -// @ts-ignore -export = tailwindLoader diff --git a/packages/@tailwindcss-webpack/src/index.ts b/packages/@tailwindcss-webpack/src/index.ts deleted file mode 100644 index 6d02f1f0b..000000000 --- a/packages/@tailwindcss-webpack/src/index.ts +++ /dev/null @@ -1,287 +0,0 @@ -import QuickLRU from '@alloc/quick-lru' -import { - compile, - env, - Features, - Instrumentation, - normalizePath, - optimize, - Polyfills, -} from '@tailwindcss/node' -import { clearRequireCache } from '@tailwindcss/node/require-cache' -import { Scanner } from '@tailwindcss/oxide' -import fs from 'node:fs' -import path from 'node:path' -import type { LoaderContext } from 'webpack' - -const DEBUG = env.DEBUG - -export interface LoaderOptions { - /** - * The base directory to scan for class candidates. - * - * Defaults to the current working directory. - */ - base?: string - - /** - * Optimize and minify the output CSS. - */ - optimize?: boolean | { minify?: boolean } -} - -interface CacheEntry { - mtimes: Map - compiler: null | Awaited> - scanner: null | Scanner - candidates: Set - fullRebuildPaths: string[] -} - -const cache = new QuickLRU({ maxSize: 50 }) - -function getCacheKey(resourceId: string, opts: LoaderOptions): string { - return `${resourceId}:${opts.base ?? ''}:${JSON.stringify(opts.optimize)}` -} - -function getContextFromCache(resourceId: string, opts: LoaderOptions): CacheEntry { - let key = getCacheKey(resourceId, opts) - if (cache.has(key)) return cache.get(key)! - let entry: CacheEntry = { - mtimes: new Map(), - compiler: null, - scanner: null, - candidates: new Set(), - fullRebuildPaths: [], - } - cache.set(key, entry) - return entry -} - -export default async function tailwindLoader( - this: LoaderContext, - source: string, -): Promise { - let callback = this.async() - let options = this.getOptions() ?? {} - let inputFile = this.resourcePath - let resourceId = this.resource - let base = options.base ?? process.cwd() - let shouldOptimize = options.optimize ?? process.env.NODE_ENV === 'production' - let isCSSModuleFile = inputFile.endsWith('.module.css') - - using I = new Instrumentation() - - DEBUG && I.start(`[@tailwindcss/webpack] ${path.relative(base, inputFile)}`) - - // Bail out early if this is guaranteed to be a non-Tailwind CSS file. - { - DEBUG && I.start('Quick bail check') - let canBail = !/@(import|reference|theme|variant|config|plugin|apply|tailwind)\b/.test(source) - if (canBail) { - DEBUG && I.end('Quick bail check') - DEBUG && I.end(`[@tailwindcss/webpack] ${path.relative(base, inputFile)}`) - callback(null, source) - return - } - DEBUG && I.end('Quick bail check') - } - - try { - let context = getContextFromCache(resourceId, options) - let inputBasePath = path.dirname(path.resolve(inputFile)) - - // Whether this is the first build or not - let isInitialBuild = context.compiler === null - - async function createCompiler() { - DEBUG && I.start('Setup compiler') - if (context.fullRebuildPaths.length > 0 && !isInitialBuild) { - clearRequireCache(context.fullRebuildPaths) - } - - context.fullRebuildPaths = [] - - DEBUG && I.start('Create compiler') - let compiler = await compile(source, { - from: inputFile, - base: inputBasePath, - shouldRewriteUrls: true, - onDependency: (depPath) => context.fullRebuildPaths.push(depPath), - // In CSS Module files, we have to disable the `@property` polyfill since these will - // emit global `*` rules which are considered to be non-pure and will cause builds - // to fail. - polyfills: isCSSModuleFile ? Polyfills.All ^ Polyfills.AtProperty : Polyfills.All, - }) - DEBUG && I.end('Create compiler') - - DEBUG && I.end('Setup compiler') - return compiler - } - - // Setup the compiler if it doesn't exist yet - context.compiler ??= await createCompiler() - - // Early exit if no Tailwind features are used - if (context.compiler.features === Features.None) { - DEBUG && I.end(`[@tailwindcss/webpack] ${path.relative(base, inputFile)}`) - callback(null, source) - return - } - - let rebuildStrategy: 'full' | 'incremental' = 'incremental' - - // Track file modification times to CSS files - DEBUG && I.start('Register full rebuild paths') - { - // Report dependencies for config files, plugins, etc. - for (let file of context.fullRebuildPaths) { - this.addDependency(path.resolve(file)) - } - - let files = [...context.fullRebuildPaths, inputFile] - - for (let file of files) { - let changedTime: number | null = null - try { - changedTime = fs.statSync(file)?.mtimeMs ?? null - } catch { - // File might not exist - } - - if (changedTime === null) { - if (file === inputFile) { - rebuildStrategy = 'full' - } - continue - } - - let prevTime = context.mtimes.get(file) - if (prevTime === changedTime) continue - - rebuildStrategy = 'full' - context.mtimes.set(file, changedTime) - } - } - DEBUG && I.end('Register full rebuild paths') - - if (rebuildStrategy === 'full' && !isInitialBuild) { - context.compiler = await createCompiler() - } - - let compiler = context.compiler - - // Check if we need to process this file at all - if ( - !( - compiler.features & - (Features.AtApply | Features.JsPluginCompat | Features.ThemeFunction | Features.Utilities) - ) - ) { - DEBUG && I.end(`[@tailwindcss/webpack] ${path.relative(base, inputFile)}`) - callback(null, source) - return - } - - // Setup or update scanner if needed - if (context.scanner === null || rebuildStrategy === 'full') { - DEBUG && I.start('Setup scanner') - let sources = (() => { - // Disable auto source detection - if (compiler.root === 'none') { - return [] - } - - // No root specified, use the base directory - if (compiler.root === null) { - return [{ base, pattern: '**/*', negated: false }] - } - - // Use the specified root - return [{ ...compiler.root, negated: false }] - })().concat(compiler.sources) - - context.scanner = new Scanner({ sources }) - DEBUG && I.end('Setup scanner') - } - - // Scan for candidates if utilities are used - if (compiler.features & Features.Utilities) { - DEBUG && I.start('Scan for candidates') - for (let candidate of context.scanner.scan()) { - context.candidates.add(candidate) - } - DEBUG && I.end('Scan for candidates') - - DEBUG && I.start('Register dependency messages') - // Add all found files as direct dependencies - let resolvedInputFile = path.resolve(base, inputFile) - for (let file of context.scanner.files) { - let absolutePath = path.resolve(file) - // The CSS file cannot be a dependency of itself - if (absolutePath === resolvedInputFile) { - continue - } - this.addDependency(absolutePath) - } - - // Register context dependencies for glob patterns - for (let glob of context.scanner.globs) { - // Skip negated patterns - if (glob.pattern[0] === '!') continue - - // Avoid adding a dependency on the base directory itself - if (glob.pattern === '*' && base === glob.base) { - continue - } - - this.addContextDependency(path.resolve(glob.base)) - } - - // Validate that source(...) paths are directories - let root = compiler.root - if (root !== 'none' && root !== null) { - let basePath = normalizePath(path.resolve(root.base, root.pattern)) - try { - let stats = fs.statSync(basePath) - if (!stats.isDirectory()) { - throw new Error( - `The path given to \`source(…)\` must be a directory but got \`source(${basePath})\` instead.`, - ) - } - } catch (err) { - if ((err as NodeJS.ErrnoException).code !== 'ENOENT') { - throw err - } - // Directory doesn't exist yet, which is fine - } - } - DEBUG && I.end('Register dependency messages') - } - - DEBUG && I.start('Build utilities') - let css = compiler.build([...context.candidates]) - DEBUG && I.end('Build utilities') - - // Optionally optimize the output - let result = css - if (shouldOptimize) { - DEBUG && I.start('Optimization') - let optimized = optimize(css, { - minify: typeof shouldOptimize === 'object' ? shouldOptimize.minify : true, - }) - result = optimized.code - DEBUG && I.end('Optimization') - } - - DEBUG && I.end(`[@tailwindcss/webpack] ${path.relative(base, inputFile)}`) - callback(null, result) - } catch (error) { - // Clear the cache entry on error to force a full rebuild next time - let key = getCacheKey(resourceId, options) - cache.delete(key) - - DEBUG && I.end(`[@tailwindcss/webpack] ${path.relative(base, inputFile)}`) - callback(error as Error) - } -} diff --git a/packages/@tailwindcss-webpack/tsconfig.json b/packages/@tailwindcss-webpack/tsconfig.json deleted file mode 100644 index d2131cbeb..000000000 --- a/packages/@tailwindcss-webpack/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "compilerOptions": { - "verbatimModuleSyntax": false, - }, -} diff --git a/packages/@tailwindcss-webpack/tsup.config.ts b/packages/@tailwindcss-webpack/tsup.config.ts deleted file mode 100644 index 998eebe4f..000000000 --- a/packages/@tailwindcss-webpack/tsup.config.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { defineConfig } from 'tsup' - -export default defineConfig([ - { - format: ['esm'], - clean: true, - minify: true, - cjsInterop: true, - dts: true, - entry: ['src/index.ts'], - }, - { - format: ['cjs'], - minify: true, - cjsInterop: true, - dts: true, - entry: ['src/index.cts'], - }, -]) diff --git a/packages/tailwindcss/README.md b/packages/tailwindcss/README.md index 5f532607d..95ec9d87d 100644 --- a/packages/tailwindcss/README.md +++ b/packages/tailwindcss/README.md @@ -13,10 +13,10 @@

- Build Status + Build Status Total Downloads - Latest Release - License + Latest Release + License

--- @@ -27,10 +27,14 @@ For full documentation, visit [tailwindcss.com](https://tailwindcss.com). ## Community -For help, discussion about best practices, or feature ideas: +For help, discussion about best practices, or any other conversation that would benefit from being searchable: -[Discuss Tailwind CSS on GitHub](https://github.com/tailwindlabs/tailwindcss/discussions) +[Discuss Tailwind CSS on GitHub](https://github.com/tailwindcss/tailwindcss/discussions) + +For chatting with others using the framework: + +[Join the Tailwind CSS Discord Server](https://discord.gg/7NF8GNe) ## Contributing -If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md) **before submitting a pull request**. +If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindcss/tailwindcss/blob/next/.github/CONTRIBUTING.md) **before submitting a pull request**. diff --git a/packages/tailwindcss/package.json b/packages/tailwindcss/package.json index d1288d6fa..5e1c7a73d 100644 --- a/packages/tailwindcss/package.json +++ b/packages/tailwindcss/package.json @@ -1,6 +1,6 @@ { "name": "tailwindcss", - "version": "4.3.3", + "version": "4.0.15", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "repository": { @@ -127,12 +127,9 @@ "utilities.css" ], "devDependencies": { - "@jridgewell/remapping": "^2.3.5", "@tailwindcss/oxide": "workspace:^", "@types/node": "catalog:", - "dedent": "catalog:", "lightningcss": "catalog:", - "magic-string": "^1.1.0", - "source-map-js": "^1.2.1" + "dedent": "1.5.3" } } diff --git a/packages/tailwindcss/playwright.config.ts b/packages/tailwindcss/playwright.config.ts index ba8f95d70..c0a123abf 100644 --- a/packages/tailwindcss/playwright.config.ts +++ b/packages/tailwindcss/playwright.config.ts @@ -20,7 +20,7 @@ export default defineConfig({ /* 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', { open: 'never' }]], + 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('/')`. */ @@ -36,11 +36,10 @@ export default defineConfig({ name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, - // Playwright 1.60's Windows WebKit worker hangs on shutdown after all tests - // pass, causing CI to fail with a worker force-kill error. - ...(process.platform === 'win32' - ? [] - : [{ name: 'webkit', use: { ...devices['Desktop Safari'] } }]), + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, { name: 'firefox', use: { diff --git a/packages/tailwindcss/preflight.css b/packages/tailwindcss/preflight.css index 04e87905c..d39171b95 100644 --- a/packages/tailwindcss/preflight.css +++ b/packages/tailwindcss/preflight.css @@ -32,13 +32,8 @@ html, tab-size: 4; /* 3 */ font-family: --theme( --default-font-family, - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - Roboto, - 'Helvetica Neue', - 'Noto Sans', - Arial, + ui-sans-serif, + system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', @@ -175,7 +170,7 @@ table { Use the modern Firefox focus style for all focusable elements. */ -:-moz-focusring:where(:not(iframe)) { +:-moz-focusring { outline: auto; } @@ -290,13 +285,13 @@ textarea, /* Set the default placeholder color to a semi-transparent version of the current text color in browsers that do not - crash when using `color-mix(…)` with `currentcolor`. (https://github.com/tailwindlabs/tailwindcss/issues/17194) + crash when using `color-mix(…)` with `currentColor`. (https://github.com/tailwindlabs/tailwindcss/issues/17194) */ @supports (not (-webkit-appearance: -apple-pay-button)) /* Not Safari */ or (contain-intrinsic-size: 1px) /* Safari 17+ */ { ::placeholder { - color: color-mix(in oklab, currentcolor 50%, transparent); + color: color-mix(in oklab, currentColor 50%, transparent); } } @@ -354,14 +349,6 @@ textarea { padding-block: 0; } -/* - Center dropdown marker shown on inputs with paired ``s in Chrome. (https://github.com/tailwindlabs/tailwindcss/issues/18499) -*/ - -::-webkit-calendar-picker-indicator { - line-height: 1; -} - /* Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) */ diff --git a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap index 9921709d7..049d0f33e 100644 --- a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap @@ -1,30 +1,8 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using the default theme 1`] = ` -" -@layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-shadow: 0 0 #0000; - --tw-shadow-color: initial; - --tw-shadow-alpha: 100%; - --tw-inset-shadow: 0 0 #0000; - --tw-inset-shadow-color: initial; - --tw-inset-shadow-alpha: 100%; - --tw-ring-color: initial; - --tw-ring-shadow: 0 0 #0000; - --tw-inset-ring-color: initial; - --tw-inset-ring-shadow: 0 0 #0000; - --tw-ring-inset: initial; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-offset-shadow: 0 0 #0000; - } - } -} - -:root, :host { - --color-red-500: oklch(63.7% .237 25.331); +":root, :host { + --color-red-500: oklch(.637 .237 25.331); --spacing: .25rem; } @@ -41,7 +19,7 @@ exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } -@media (min-width: 40rem) { +@media (width >= 40rem) { .sm\\:flex { display: flex; } @@ -58,12 +36,6 @@ exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using inherits: false } -@property --tw-shadow-alpha { - syntax: ""; - inherits: false; - initial-value: 100%; -} - @property --tw-inset-shadow { syntax: "*"; inherits: false; @@ -75,12 +47,6 @@ exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using inherits: false } -@property --tw-inset-shadow-alpha { - syntax: ""; - inherits: false; - initial-value: 100%; -} - @property --tw-ring-color { syntax: "*"; inherits: false @@ -124,18 +90,14 @@ exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using syntax: "*"; inherits: false; initial-value: 0 0 #0000; -} -" +}" `; exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = ` -" -@layer theme { +"@layer theme { :root, :host { - --tw-font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, - sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - --tw-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", - monospace; + --tw-font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --tw-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --tw-default-font-family: var(--tw-font-sans); --tw-default-mono-font-family: var(--tw-font-mono); } @@ -160,7 +122,7 @@ exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = ` -webkit-text-size-adjust: 100%; tab-size: 4; line-height: 1.5; - font-family: var(--tw-default-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); + font-family: var(--tw-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(--tw-default-font-feature-settings, normal); font-variation-settings: var(--tw-default-font-variation-settings, normal); -webkit-tap-highlight-color: transparent; @@ -226,7 +188,7 @@ exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = ` border-collapse: collapse; } - :-moz-focusring:where(:not(iframe)) { + :-moz-focusring { outline: auto; } @@ -292,13 +254,7 @@ exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = ` @supports (not ((-webkit-appearance: -apple-pay-button))) or (contain-intrinsic-size: 1px) { ::placeholder { - color: currentColor; - } - - @supports (color: color-mix(in lab, red, red)) { - ::placeholder { - color: color-mix(in oklab, currentcolor 50%, transparent); - } + color: color-mix(in oklab, currentColor 50%, transparent); } } @@ -359,10 +315,6 @@ exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = ` padding-block: 0; } - ::-webkit-calendar-picker-indicator { - line-height: 1; - } - :-moz-ui-invalid { box-shadow: none; } @@ -388,6 +340,5 @@ exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = ` } } -@layer components, utilities; -" +@layer components, utilities;" `; diff --git a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap index d595e8c01..7fef73d37 100644 --- a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap @@ -306,6 +306,66 @@ exports[`getClassList 1`] = ` "-col-start-11", "-col-start-12", "-col-start-13", + "-end-0", + "-end-0.5", + "-end-1", + "-end-1.5", + "-end-2", + "-end-2.5", + "-end-3", + "-end-3.5", + "-end-4", + "-end-5", + "-end-6", + "-end-7", + "-end-8", + "-end-9", + "-end-10", + "-end-11", + "-end-12", + "-end-14", + "-end-16", + "-end-20", + "-end-24", + "-end-28", + "-end-32", + "-end-36", + "-end-40", + "-end-44", + "-end-48", + "-end-52", + "-end-56", + "-end-60", + "-end-64", + "-end-72", + "-end-80", + "-end-96", + "-end-1/2", + "-end-1/3", + "-end-1/4", + "-end-1/5", + "-end-1/6", + "-end-1/12", + "-end-2/3", + "-end-2/4", + "-end-2/5", + "-end-2/6", + "-end-2/12", + "-end-3/4", + "-end-3/5", + "-end-3/6", + "-end-3/12", + "-end-4/5", + "-end-4/6", + "-end-4/12", + "-end-5/6", + "-end-5/12", + "-end-6/12", + "-end-7/12", + "-end-8/12", + "-end-9/12", + "-end-10/12", + "-end-11/12", "-end-full", "-end-px", "-indent-0", @@ -403,256 +463,8 @@ exports[`getClassList 1`] = ` "-inset-9/12", "-inset-10/12", "-inset-11/12", - "-inset-be-0", - "-inset-be-0.5", - "-inset-be-1", - "-inset-be-1.5", - "-inset-be-2", - "-inset-be-2.5", - "-inset-be-3", - "-inset-be-3.5", - "-inset-be-4", - "-inset-be-5", - "-inset-be-6", - "-inset-be-7", - "-inset-be-8", - "-inset-be-9", - "-inset-be-10", - "-inset-be-11", - "-inset-be-12", - "-inset-be-14", - "-inset-be-16", - "-inset-be-20", - "-inset-be-24", - "-inset-be-28", - "-inset-be-32", - "-inset-be-36", - "-inset-be-40", - "-inset-be-44", - "-inset-be-48", - "-inset-be-52", - "-inset-be-56", - "-inset-be-60", - "-inset-be-64", - "-inset-be-72", - "-inset-be-80", - "-inset-be-96", - "-inset-be-1/2", - "-inset-be-1/3", - "-inset-be-1/4", - "-inset-be-1/5", - "-inset-be-1/6", - "-inset-be-1/12", - "-inset-be-2/3", - "-inset-be-2/4", - "-inset-be-2/5", - "-inset-be-2/6", - "-inset-be-2/12", - "-inset-be-3/4", - "-inset-be-3/5", - "-inset-be-3/6", - "-inset-be-3/12", - "-inset-be-4/5", - "-inset-be-4/6", - "-inset-be-4/12", - "-inset-be-5/6", - "-inset-be-5/12", - "-inset-be-6/12", - "-inset-be-7/12", - "-inset-be-8/12", - "-inset-be-9/12", - "-inset-be-10/12", - "-inset-be-11/12", - "-inset-be-full", - "-inset-be-px", - "-inset-bs-0", - "-inset-bs-0.5", - "-inset-bs-1", - "-inset-bs-1.5", - "-inset-bs-2", - "-inset-bs-2.5", - "-inset-bs-3", - "-inset-bs-3.5", - "-inset-bs-4", - "-inset-bs-5", - "-inset-bs-6", - "-inset-bs-7", - "-inset-bs-8", - "-inset-bs-9", - "-inset-bs-10", - "-inset-bs-11", - "-inset-bs-12", - "-inset-bs-14", - "-inset-bs-16", - "-inset-bs-20", - "-inset-bs-24", - "-inset-bs-28", - "-inset-bs-32", - "-inset-bs-36", - "-inset-bs-40", - "-inset-bs-44", - "-inset-bs-48", - "-inset-bs-52", - "-inset-bs-56", - "-inset-bs-60", - "-inset-bs-64", - "-inset-bs-72", - "-inset-bs-80", - "-inset-bs-96", - "-inset-bs-1/2", - "-inset-bs-1/3", - "-inset-bs-1/4", - "-inset-bs-1/5", - "-inset-bs-1/6", - "-inset-bs-1/12", - "-inset-bs-2/3", - "-inset-bs-2/4", - "-inset-bs-2/5", - "-inset-bs-2/6", - "-inset-bs-2/12", - "-inset-bs-3/4", - "-inset-bs-3/5", - "-inset-bs-3/6", - "-inset-bs-3/12", - "-inset-bs-4/5", - "-inset-bs-4/6", - "-inset-bs-4/12", - "-inset-bs-5/6", - "-inset-bs-5/12", - "-inset-bs-6/12", - "-inset-bs-7/12", - "-inset-bs-8/12", - "-inset-bs-9/12", - "-inset-bs-10/12", - "-inset-bs-11/12", - "-inset-bs-full", - "-inset-bs-px", - "-inset-e-0", - "-inset-e-0.5", - "-inset-e-1", - "-inset-e-1.5", - "-inset-e-2", - "-inset-e-2.5", - "-inset-e-3", - "-inset-e-3.5", - "-inset-e-4", - "-inset-e-5", - "-inset-e-6", - "-inset-e-7", - "-inset-e-8", - "-inset-e-9", - "-inset-e-10", - "-inset-e-11", - "-inset-e-12", - "-inset-e-14", - "-inset-e-16", - "-inset-e-20", - "-inset-e-24", - "-inset-e-28", - "-inset-e-32", - "-inset-e-36", - "-inset-e-40", - "-inset-e-44", - "-inset-e-48", - "-inset-e-52", - "-inset-e-56", - "-inset-e-60", - "-inset-e-64", - "-inset-e-72", - "-inset-e-80", - "-inset-e-96", - "-inset-e-1/2", - "-inset-e-1/3", - "-inset-e-1/4", - "-inset-e-1/5", - "-inset-e-1/6", - "-inset-e-1/12", - "-inset-e-2/3", - "-inset-e-2/4", - "-inset-e-2/5", - "-inset-e-2/6", - "-inset-e-2/12", - "-inset-e-3/4", - "-inset-e-3/5", - "-inset-e-3/6", - "-inset-e-3/12", - "-inset-e-4/5", - "-inset-e-4/6", - "-inset-e-4/12", - "-inset-e-5/6", - "-inset-e-5/12", - "-inset-e-6/12", - "-inset-e-7/12", - "-inset-e-8/12", - "-inset-e-9/12", - "-inset-e-10/12", - "-inset-e-11/12", - "-inset-e-full", - "-inset-e-px", "-inset-full", "-inset-px", - "-inset-s-0", - "-inset-s-0.5", - "-inset-s-1", - "-inset-s-1.5", - "-inset-s-2", - "-inset-s-2.5", - "-inset-s-3", - "-inset-s-3.5", - "-inset-s-4", - "-inset-s-5", - "-inset-s-6", - "-inset-s-7", - "-inset-s-8", - "-inset-s-9", - "-inset-s-10", - "-inset-s-11", - "-inset-s-12", - "-inset-s-14", - "-inset-s-16", - "-inset-s-20", - "-inset-s-24", - "-inset-s-28", - "-inset-s-32", - "-inset-s-36", - "-inset-s-40", - "-inset-s-44", - "-inset-s-48", - "-inset-s-52", - "-inset-s-56", - "-inset-s-60", - "-inset-s-64", - "-inset-s-72", - "-inset-s-80", - "-inset-s-96", - "-inset-s-1/2", - "-inset-s-1/3", - "-inset-s-1/4", - "-inset-s-1/5", - "-inset-s-1/6", - "-inset-s-1/12", - "-inset-s-2/3", - "-inset-s-2/4", - "-inset-s-2/5", - "-inset-s-2/6", - "-inset-s-2/12", - "-inset-s-3/4", - "-inset-s-3/5", - "-inset-s-3/6", - "-inset-s-3/12", - "-inset-s-4/5", - "-inset-s-4/6", - "-inset-s-4/12", - "-inset-s-5/6", - "-inset-s-5/12", - "-inset-s-6/12", - "-inset-s-7/12", - "-inset-s-8/12", - "-inset-s-9/12", - "-inset-s-10/12", - "-inset-s-11/12", - "-inset-s-full", - "-inset-s-px", "-inset-x-0", "-inset-x-0.5", "-inset-x-1", @@ -874,24 +686,6 @@ exports[`getClassList 1`] = ` "-m-80", "-m-96", "-m-px", - "-mask-conic-0", - "-mask-conic-1", - "-mask-conic-2", - "-mask-conic-3", - "-mask-conic-6", - "-mask-conic-12", - "-mask-conic-45", - "-mask-conic-90", - "-mask-conic-180", - "-mask-linear-0", - "-mask-linear-1", - "-mask-linear-2", - "-mask-linear-3", - "-mask-linear-6", - "-mask-linear-12", - "-mask-linear-45", - "-mask-linear-90", - "-mask-linear-180", "-mb-0", "-mb-0.5", "-mb-1", @@ -927,76 +721,6 @@ exports[`getClassList 1`] = ` "-mb-80", "-mb-96", "-mb-px", - "-mbe-0", - "-mbe-0.5", - "-mbe-1", - "-mbe-1.5", - "-mbe-2", - "-mbe-2.5", - "-mbe-3", - "-mbe-3.5", - "-mbe-4", - "-mbe-5", - "-mbe-6", - "-mbe-7", - "-mbe-8", - "-mbe-9", - "-mbe-10", - "-mbe-11", - "-mbe-12", - "-mbe-14", - "-mbe-16", - "-mbe-20", - "-mbe-24", - "-mbe-28", - "-mbe-32", - "-mbe-36", - "-mbe-40", - "-mbe-44", - "-mbe-48", - "-mbe-52", - "-mbe-56", - "-mbe-60", - "-mbe-64", - "-mbe-72", - "-mbe-80", - "-mbe-96", - "-mbe-px", - "-mbs-0", - "-mbs-0.5", - "-mbs-1", - "-mbs-1.5", - "-mbs-2", - "-mbs-2.5", - "-mbs-3", - "-mbs-3.5", - "-mbs-4", - "-mbs-5", - "-mbs-6", - "-mbs-7", - "-mbs-8", - "-mbs-9", - "-mbs-10", - "-mbs-11", - "-mbs-12", - "-mbs-14", - "-mbs-16", - "-mbs-20", - "-mbs-24", - "-mbs-28", - "-mbs-32", - "-mbs-36", - "-mbs-40", - "-mbs-44", - "-mbs-48", - "-mbs-52", - "-mbs-56", - "-mbs-60", - "-mbs-64", - "-mbs-72", - "-mbs-80", - "-mbs-96", - "-mbs-px", "-me-0", "-me-0.5", "-me-1", @@ -1497,76 +1221,6 @@ exports[`getClassList 1`] = ` "-scroll-mb-80", "-scroll-mb-96", "-scroll-mb-px", - "-scroll-mbe-0", - "-scroll-mbe-0.5", - "-scroll-mbe-1", - "-scroll-mbe-1.5", - "-scroll-mbe-2", - "-scroll-mbe-2.5", - "-scroll-mbe-3", - "-scroll-mbe-3.5", - "-scroll-mbe-4", - "-scroll-mbe-5", - "-scroll-mbe-6", - "-scroll-mbe-7", - "-scroll-mbe-8", - "-scroll-mbe-9", - "-scroll-mbe-10", - "-scroll-mbe-11", - "-scroll-mbe-12", - "-scroll-mbe-14", - "-scroll-mbe-16", - "-scroll-mbe-20", - "-scroll-mbe-24", - "-scroll-mbe-28", - "-scroll-mbe-32", - "-scroll-mbe-36", - "-scroll-mbe-40", - "-scroll-mbe-44", - "-scroll-mbe-48", - "-scroll-mbe-52", - "-scroll-mbe-56", - "-scroll-mbe-60", - "-scroll-mbe-64", - "-scroll-mbe-72", - "-scroll-mbe-80", - "-scroll-mbe-96", - "-scroll-mbe-px", - "-scroll-mbs-0", - "-scroll-mbs-0.5", - "-scroll-mbs-1", - "-scroll-mbs-1.5", - "-scroll-mbs-2", - "-scroll-mbs-2.5", - "-scroll-mbs-3", - "-scroll-mbs-3.5", - "-scroll-mbs-4", - "-scroll-mbs-5", - "-scroll-mbs-6", - "-scroll-mbs-7", - "-scroll-mbs-8", - "-scroll-mbs-9", - "-scroll-mbs-10", - "-scroll-mbs-11", - "-scroll-mbs-12", - "-scroll-mbs-14", - "-scroll-mbs-16", - "-scroll-mbs-20", - "-scroll-mbs-24", - "-scroll-mbs-28", - "-scroll-mbs-32", - "-scroll-mbs-36", - "-scroll-mbs-40", - "-scroll-mbs-44", - "-scroll-mbs-48", - "-scroll-mbs-52", - "-scroll-mbs-56", - "-scroll-mbs-60", - "-scroll-mbs-64", - "-scroll-mbs-72", - "-scroll-mbs-80", - "-scroll-mbs-96", - "-scroll-mbs-px", "-scroll-me-0", "-scroll-me-0.5", "-scroll-me-1", @@ -1900,6 +1554,66 @@ exports[`getClassList 1`] = ` "-space-y-80", "-space-y-96", "-space-y-px", + "-start-0", + "-start-0.5", + "-start-1", + "-start-1.5", + "-start-2", + "-start-2.5", + "-start-3", + "-start-3.5", + "-start-4", + "-start-5", + "-start-6", + "-start-7", + "-start-8", + "-start-9", + "-start-10", + "-start-11", + "-start-12", + "-start-14", + "-start-16", + "-start-20", + "-start-24", + "-start-28", + "-start-32", + "-start-36", + "-start-40", + "-start-44", + "-start-48", + "-start-52", + "-start-56", + "-start-60", + "-start-64", + "-start-72", + "-start-80", + "-start-96", + "-start-1/2", + "-start-1/3", + "-start-1/4", + "-start-1/5", + "-start-1/6", + "-start-1/12", + "-start-2/3", + "-start-2/4", + "-start-2/5", + "-start-2/6", + "-start-2/12", + "-start-3/4", + "-start-3/5", + "-start-3/6", + "-start-3/12", + "-start-4/5", + "-start-4/6", + "-start-4/12", + "-start-5/6", + "-start-5/12", + "-start-6/12", + "-start-7/12", + "-start-8/12", + "-start-9/12", + "-start-10/12", + "-start-11/12", "-start-full", "-start-px", "-top-0", @@ -2222,7 +1936,6 @@ exports[`getClassList 1`] = ` "accent-current/90", "accent-current/95", "accent-current/100", - "accent-current/background", "accent-inherit", "accent-inherit/0", "accent-inherit/5", @@ -2245,7 +1958,6 @@ exports[`getClassList 1`] = ` "accent-inherit/90", "accent-inherit/95", "accent-inherit/100", - "accent-inherit/background", "accent-transparent", "accent-transparent/0", "accent-transparent/5", @@ -2268,7 +1980,6 @@ exports[`getClassList 1`] = ` "accent-transparent/90", "accent-transparent/95", "accent-transparent/100", - "accent-transparent/background", "align-baseline", "align-bottom", "align-middle", @@ -2445,8 +2156,6 @@ exports[`getClassList 1`] = ` "bg-blend-screen", "bg-blend-soft-light", "bg-bottom", - "bg-bottom-left", - "bg-bottom-right", "bg-center", "bg-clip-border", "bg-clip-content", @@ -2593,7 +2302,6 @@ exports[`getClassList 1`] = ` "bg-current/90", "bg-current/95", "bg-current/100", - "bg-current/background", "bg-fixed", "bg-inherit", "bg-inherit/0", @@ -2617,8 +2325,9 @@ exports[`getClassList 1`] = ` "bg-inherit/90", "bg-inherit/95", "bg-inherit/100", - "bg-inherit/background", "bg-left", + "bg-left-bottom", + "bg-left-top", "bg-linear-0", "bg-linear-0/oklab", "bg-linear-0/oklch", @@ -2820,10 +2529,10 @@ exports[`getClassList 1`] = ` "bg-repeat-x", "bg-repeat-y", "bg-right", + "bg-right-bottom", + "bg-right-top", "bg-scroll", "bg-top", - "bg-top-left", - "bg-top-right", "bg-transparent", "bg-transparent/0", "bg-transparent/5", @@ -2846,79 +2555,7 @@ exports[`getClassList 1`] = ` "bg-transparent/90", "bg-transparent/95", "bg-transparent/100", - "bg-transparent/background", "block", - "block-0", - "block-0.5", - "block-1", - "block-1.5", - "block-2", - "block-2.5", - "block-3", - "block-3.5", - "block-4", - "block-5", - "block-6", - "block-7", - "block-8", - "block-9", - "block-10", - "block-11", - "block-12", - "block-14", - "block-16", - "block-20", - "block-24", - "block-28", - "block-32", - "block-36", - "block-40", - "block-44", - "block-48", - "block-52", - "block-56", - "block-60", - "block-64", - "block-72", - "block-80", - "block-96", - "block-1/2", - "block-1/3", - "block-1/4", - "block-1/5", - "block-1/6", - "block-1/12", - "block-2/3", - "block-2/4", - "block-2/5", - "block-2/6", - "block-2/12", - "block-3/4", - "block-3/5", - "block-3/6", - "block-3/12", - "block-4/5", - "block-4/6", - "block-4/12", - "block-5/6", - "block-5/12", - "block-6/12", - "block-7/12", - "block-8/12", - "block-9/12", - "block-10/12", - "block-11/12", - "block-auto", - "block-dvh", - "block-fit", - "block-full", - "block-lh", - "block-lvh", - "block-max", - "block-min", - "block-px", - "block-screen", - "block-svh", "blur-none", "border", "border/0", @@ -2942,7 +2579,6 @@ exports[`getClassList 1`] = ` "border/90", "border/95", "border/100", - "border/background", "border-0", "border-2", "border-4", @@ -2969,7 +2605,6 @@ exports[`getClassList 1`] = ` "border-b/90", "border-b/95", "border-b/100", - "border-b/background", "border-b-0", "border-b-2", "border-b-4", @@ -2996,7 +2631,6 @@ exports[`getClassList 1`] = ` "border-b-current/90", "border-b-current/95", "border-b-current/100", - "border-b-current/background", "border-b-inherit", "border-b-inherit/0", "border-b-inherit/5", @@ -3019,7 +2653,6 @@ exports[`getClassList 1`] = ` "border-b-inherit/90", "border-b-inherit/95", "border-b-inherit/100", - "border-b-inherit/background", "border-b-transparent", "border-b-transparent/0", "border-b-transparent/5", @@ -3042,199 +2675,6 @@ exports[`getClassList 1`] = ` "border-b-transparent/90", "border-b-transparent/95", "border-b-transparent/100", - "border-b-transparent/background", - "border-be", - "border-be/0", - "border-be/5", - "border-be/10", - "border-be/15", - "border-be/20", - "border-be/25", - "border-be/30", - "border-be/35", - "border-be/40", - "border-be/45", - "border-be/50", - "border-be/55", - "border-be/60", - "border-be/65", - "border-be/70", - "border-be/75", - "border-be/80", - "border-be/85", - "border-be/90", - "border-be/95", - "border-be/100", - "border-be/background", - "border-be-0", - "border-be-2", - "border-be-4", - "border-be-8", - "border-be-current", - "border-be-current/0", - "border-be-current/5", - "border-be-current/10", - "border-be-current/15", - "border-be-current/20", - "border-be-current/25", - "border-be-current/30", - "border-be-current/35", - "border-be-current/40", - "border-be-current/45", - "border-be-current/50", - "border-be-current/55", - "border-be-current/60", - "border-be-current/65", - "border-be-current/70", - "border-be-current/75", - "border-be-current/80", - "border-be-current/85", - "border-be-current/90", - "border-be-current/95", - "border-be-current/100", - "border-be-current/background", - "border-be-inherit", - "border-be-inherit/0", - "border-be-inherit/5", - "border-be-inherit/10", - "border-be-inherit/15", - "border-be-inherit/20", - "border-be-inherit/25", - "border-be-inherit/30", - "border-be-inherit/35", - "border-be-inherit/40", - "border-be-inherit/45", - "border-be-inherit/50", - "border-be-inherit/55", - "border-be-inherit/60", - "border-be-inherit/65", - "border-be-inherit/70", - "border-be-inherit/75", - "border-be-inherit/80", - "border-be-inherit/85", - "border-be-inherit/90", - "border-be-inherit/95", - "border-be-inherit/100", - "border-be-inherit/background", - "border-be-transparent", - "border-be-transparent/0", - "border-be-transparent/5", - "border-be-transparent/10", - "border-be-transparent/15", - "border-be-transparent/20", - "border-be-transparent/25", - "border-be-transparent/30", - "border-be-transparent/35", - "border-be-transparent/40", - "border-be-transparent/45", - "border-be-transparent/50", - "border-be-transparent/55", - "border-be-transparent/60", - "border-be-transparent/65", - "border-be-transparent/70", - "border-be-transparent/75", - "border-be-transparent/80", - "border-be-transparent/85", - "border-be-transparent/90", - "border-be-transparent/95", - "border-be-transparent/100", - "border-be-transparent/background", - "border-bs", - "border-bs/0", - "border-bs/5", - "border-bs/10", - "border-bs/15", - "border-bs/20", - "border-bs/25", - "border-bs/30", - "border-bs/35", - "border-bs/40", - "border-bs/45", - "border-bs/50", - "border-bs/55", - "border-bs/60", - "border-bs/65", - "border-bs/70", - "border-bs/75", - "border-bs/80", - "border-bs/85", - "border-bs/90", - "border-bs/95", - "border-bs/100", - "border-bs/background", - "border-bs-0", - "border-bs-2", - "border-bs-4", - "border-bs-8", - "border-bs-current", - "border-bs-current/0", - "border-bs-current/5", - "border-bs-current/10", - "border-bs-current/15", - "border-bs-current/20", - "border-bs-current/25", - "border-bs-current/30", - "border-bs-current/35", - "border-bs-current/40", - "border-bs-current/45", - "border-bs-current/50", - "border-bs-current/55", - "border-bs-current/60", - "border-bs-current/65", - "border-bs-current/70", - "border-bs-current/75", - "border-bs-current/80", - "border-bs-current/85", - "border-bs-current/90", - "border-bs-current/95", - "border-bs-current/100", - "border-bs-current/background", - "border-bs-inherit", - "border-bs-inherit/0", - "border-bs-inherit/5", - "border-bs-inherit/10", - "border-bs-inherit/15", - "border-bs-inherit/20", - "border-bs-inherit/25", - "border-bs-inherit/30", - "border-bs-inherit/35", - "border-bs-inherit/40", - "border-bs-inherit/45", - "border-bs-inherit/50", - "border-bs-inherit/55", - "border-bs-inherit/60", - "border-bs-inherit/65", - "border-bs-inherit/70", - "border-bs-inherit/75", - "border-bs-inherit/80", - "border-bs-inherit/85", - "border-bs-inherit/90", - "border-bs-inherit/95", - "border-bs-inherit/100", - "border-bs-inherit/background", - "border-bs-transparent", - "border-bs-transparent/0", - "border-bs-transparent/5", - "border-bs-transparent/10", - "border-bs-transparent/15", - "border-bs-transparent/20", - "border-bs-transparent/25", - "border-bs-transparent/30", - "border-bs-transparent/35", - "border-bs-transparent/40", - "border-bs-transparent/45", - "border-bs-transparent/50", - "border-bs-transparent/55", - "border-bs-transparent/60", - "border-bs-transparent/65", - "border-bs-transparent/70", - "border-bs-transparent/75", - "border-bs-transparent/80", - "border-bs-transparent/85", - "border-bs-transparent/90", - "border-bs-transparent/95", - "border-bs-transparent/100", - "border-bs-transparent/background", "border-collapse", "border-current", "border-current/0", @@ -3258,7 +2698,6 @@ exports[`getClassList 1`] = ` "border-current/90", "border-current/95", "border-current/100", - "border-current/background", "border-dashed", "border-dotted", "border-double", @@ -3284,7 +2723,6 @@ exports[`getClassList 1`] = ` "border-e/90", "border-e/95", "border-e/100", - "border-e/background", "border-e-0", "border-e-2", "border-e-4", @@ -3311,7 +2749,6 @@ exports[`getClassList 1`] = ` "border-e-current/90", "border-e-current/95", "border-e-current/100", - "border-e-current/background", "border-e-inherit", "border-e-inherit/0", "border-e-inherit/5", @@ -3334,7 +2771,6 @@ exports[`getClassList 1`] = ` "border-e-inherit/90", "border-e-inherit/95", "border-e-inherit/100", - "border-e-inherit/background", "border-e-transparent", "border-e-transparent/0", "border-e-transparent/5", @@ -3357,7 +2793,6 @@ exports[`getClassList 1`] = ` "border-e-transparent/90", "border-e-transparent/95", "border-e-transparent/100", - "border-e-transparent/background", "border-hidden", "border-inherit", "border-inherit/0", @@ -3381,7 +2816,6 @@ exports[`getClassList 1`] = ` "border-inherit/90", "border-inherit/95", "border-inherit/100", - "border-inherit/background", "border-l", "border-l/0", "border-l/5", @@ -3404,7 +2838,6 @@ exports[`getClassList 1`] = ` "border-l/90", "border-l/95", "border-l/100", - "border-l/background", "border-l-0", "border-l-2", "border-l-4", @@ -3431,7 +2864,6 @@ exports[`getClassList 1`] = ` "border-l-current/90", "border-l-current/95", "border-l-current/100", - "border-l-current/background", "border-l-inherit", "border-l-inherit/0", "border-l-inherit/5", @@ -3454,7 +2886,6 @@ exports[`getClassList 1`] = ` "border-l-inherit/90", "border-l-inherit/95", "border-l-inherit/100", - "border-l-inherit/background", "border-l-transparent", "border-l-transparent/0", "border-l-transparent/5", @@ -3477,7 +2908,6 @@ exports[`getClassList 1`] = ` "border-l-transparent/90", "border-l-transparent/95", "border-l-transparent/100", - "border-l-transparent/background", "border-none", "border-r", "border-r/0", @@ -3501,7 +2931,6 @@ exports[`getClassList 1`] = ` "border-r/90", "border-r/95", "border-r/100", - "border-r/background", "border-r-0", "border-r-2", "border-r-4", @@ -3528,7 +2957,6 @@ exports[`getClassList 1`] = ` "border-r-current/90", "border-r-current/95", "border-r-current/100", - "border-r-current/background", "border-r-inherit", "border-r-inherit/0", "border-r-inherit/5", @@ -3551,7 +2979,6 @@ exports[`getClassList 1`] = ` "border-r-inherit/90", "border-r-inherit/95", "border-r-inherit/100", - "border-r-inherit/background", "border-r-transparent", "border-r-transparent/0", "border-r-transparent/5", @@ -3574,7 +3001,6 @@ exports[`getClassList 1`] = ` "border-r-transparent/90", "border-r-transparent/95", "border-r-transparent/100", - "border-r-transparent/background", "border-s", "border-s/0", "border-s/5", @@ -3597,7 +3023,6 @@ exports[`getClassList 1`] = ` "border-s/90", "border-s/95", "border-s/100", - "border-s/background", "border-s-0", "border-s-2", "border-s-4", @@ -3624,7 +3049,6 @@ exports[`getClassList 1`] = ` "border-s-current/90", "border-s-current/95", "border-s-current/100", - "border-s-current/background", "border-s-inherit", "border-s-inherit/0", "border-s-inherit/5", @@ -3647,7 +3071,6 @@ exports[`getClassList 1`] = ` "border-s-inherit/90", "border-s-inherit/95", "border-s-inherit/100", - "border-s-inherit/background", "border-s-transparent", "border-s-transparent/0", "border-s-transparent/5", @@ -3670,7 +3093,6 @@ exports[`getClassList 1`] = ` "border-s-transparent/90", "border-s-transparent/95", "border-s-transparent/100", - "border-s-transparent/background", "border-separate", "border-solid", "border-spacing-0", @@ -3800,7 +3222,6 @@ exports[`getClassList 1`] = ` "border-t/90", "border-t/95", "border-t/100", - "border-t/background", "border-t-0", "border-t-2", "border-t-4", @@ -3827,7 +3248,6 @@ exports[`getClassList 1`] = ` "border-t-current/90", "border-t-current/95", "border-t-current/100", - "border-t-current/background", "border-t-inherit", "border-t-inherit/0", "border-t-inherit/5", @@ -3850,7 +3270,6 @@ exports[`getClassList 1`] = ` "border-t-inherit/90", "border-t-inherit/95", "border-t-inherit/100", - "border-t-inherit/background", "border-t-transparent", "border-t-transparent/0", "border-t-transparent/5", @@ -3873,7 +3292,6 @@ exports[`getClassList 1`] = ` "border-t-transparent/90", "border-t-transparent/95", "border-t-transparent/100", - "border-t-transparent/background", "border-transparent", "border-transparent/0", "border-transparent/5", @@ -3896,7 +3314,6 @@ exports[`getClassList 1`] = ` "border-transparent/90", "border-transparent/95", "border-transparent/100", - "border-transparent/background", "border-x", "border-x/0", "border-x/5", @@ -3919,7 +3336,6 @@ exports[`getClassList 1`] = ` "border-x/90", "border-x/95", "border-x/100", - "border-x/background", "border-x-0", "border-x-2", "border-x-4", @@ -3946,7 +3362,6 @@ exports[`getClassList 1`] = ` "border-x-current/90", "border-x-current/95", "border-x-current/100", - "border-x-current/background", "border-x-inherit", "border-x-inherit/0", "border-x-inherit/5", @@ -3969,7 +3384,6 @@ exports[`getClassList 1`] = ` "border-x-inherit/90", "border-x-inherit/95", "border-x-inherit/100", - "border-x-inherit/background", "border-x-transparent", "border-x-transparent/0", "border-x-transparent/5", @@ -3992,7 +3406,6 @@ exports[`getClassList 1`] = ` "border-x-transparent/90", "border-x-transparent/95", "border-x-transparent/100", - "border-x-transparent/background", "border-y", "border-y/0", "border-y/5", @@ -4015,7 +3428,6 @@ exports[`getClassList 1`] = ` "border-y/90", "border-y/95", "border-y/100", - "border-y/background", "border-y-0", "border-y-2", "border-y-4", @@ -4042,7 +3454,6 @@ exports[`getClassList 1`] = ` "border-y-current/90", "border-y-current/95", "border-y-current/100", - "border-y-current/background", "border-y-inherit", "border-y-inherit/0", "border-y-inherit/5", @@ -4065,7 +3476,6 @@ exports[`getClassList 1`] = ` "border-y-inherit/90", "border-y-inherit/95", "border-y-inherit/100", - "border-y-inherit/background", "border-y-transparent", "border-y-transparent/0", "border-y-transparent/5", @@ -4088,7 +3498,6 @@ exports[`getClassList 1`] = ` "border-y-transparent/90", "border-y-transparent/95", "border-y-transparent/100", - "border-y-transparent/background", "bottom-0", "bottom-0.5", "bottom-1", @@ -4179,6 +3588,7 @@ exports[`getClassList 1`] = ` "break-inside-avoid-page", "break-keep", "break-normal", + "break-words", "brightness-0", "brightness-50", "brightness-75", @@ -4215,7 +3625,6 @@ exports[`getClassList 1`] = ` "caret-current/90", "caret-current/95", "caret-current/100", - "caret-current/background", "caret-inherit", "caret-inherit/0", "caret-inherit/5", @@ -4238,7 +3647,6 @@ exports[`getClassList 1`] = ` "caret-inherit/90", "caret-inherit/95", "caret-inherit/100", - "caret-inherit/background", "caret-transparent", "caret-transparent/0", "caret-transparent/5", @@ -4261,7 +3669,6 @@ exports[`getClassList 1`] = ` "caret-transparent/90", "caret-transparent/95", "caret-transparent/100", - "caret-transparent/background", "clear-both", "clear-end", "clear-left", @@ -4417,7 +3824,6 @@ exports[`getClassList 1`] = ` "decoration-current/90", "decoration-current/95", "decoration-current/100", - "decoration-current/background", "decoration-dashed", "decoration-dotted", "decoration-double", @@ -4444,7 +3850,6 @@ exports[`getClassList 1`] = ` "decoration-inherit/90", "decoration-inherit/95", "decoration-inherit/100", - "decoration-inherit/background", "decoration-solid", "decoration-transparent", "decoration-transparent/0", @@ -4468,7 +3873,6 @@ exports[`getClassList 1`] = ` "decoration-transparent/90", "decoration-transparent/95", "decoration-transparent/100", - "decoration-transparent/background", "decoration-wavy", "delay-75", "delay-100", @@ -4501,7 +3905,6 @@ exports[`getClassList 1`] = ` "divide-current/90", "divide-current/95", "divide-current/100", - "divide-current/background", "divide-dashed", "divide-dotted", "divide-double", @@ -4527,7 +3930,6 @@ exports[`getClassList 1`] = ` "divide-inherit/90", "divide-inherit/95", "divide-inherit/100", - "divide-inherit/background", "divide-none", "divide-solid", "divide-transparent", @@ -4552,7 +3954,6 @@ exports[`getClassList 1`] = ` "divide-transparent/90", "divide-transparent/95", "divide-transparent/100", - "divide-transparent/background", "divide-x", "divide-x-0", "divide-x-2", @@ -4565,77 +3966,8 @@ exports[`getClassList 1`] = ` "divide-y-4", "divide-y-8", "divide-y-reverse", - "drop-shadow-current", - "drop-shadow-current/0", - "drop-shadow-current/5", - "drop-shadow-current/10", - "drop-shadow-current/15", - "drop-shadow-current/20", - "drop-shadow-current/25", - "drop-shadow-current/30", - "drop-shadow-current/35", - "drop-shadow-current/40", - "drop-shadow-current/45", - "drop-shadow-current/50", - "drop-shadow-current/55", - "drop-shadow-current/60", - "drop-shadow-current/65", - "drop-shadow-current/70", - "drop-shadow-current/75", - "drop-shadow-current/80", - "drop-shadow-current/85", - "drop-shadow-current/90", - "drop-shadow-current/95", - "drop-shadow-current/100", - "drop-shadow-current/background", - "drop-shadow-inherit", - "drop-shadow-inherit/0", - "drop-shadow-inherit/5", - "drop-shadow-inherit/10", - "drop-shadow-inherit/15", - "drop-shadow-inherit/20", - "drop-shadow-inherit/25", - "drop-shadow-inherit/30", - "drop-shadow-inherit/35", - "drop-shadow-inherit/40", - "drop-shadow-inherit/45", - "drop-shadow-inherit/50", - "drop-shadow-inherit/55", - "drop-shadow-inherit/60", - "drop-shadow-inherit/65", - "drop-shadow-inherit/70", - "drop-shadow-inherit/75", - "drop-shadow-inherit/80", - "drop-shadow-inherit/85", - "drop-shadow-inherit/90", - "drop-shadow-inherit/95", - "drop-shadow-inherit/100", - "drop-shadow-inherit/background", "drop-shadow-none", "drop-shadow-sm", - "drop-shadow-transparent", - "drop-shadow-transparent/0", - "drop-shadow-transparent/5", - "drop-shadow-transparent/10", - "drop-shadow-transparent/15", - "drop-shadow-transparent/20", - "drop-shadow-transparent/25", - "drop-shadow-transparent/30", - "drop-shadow-transparent/35", - "drop-shadow-transparent/40", - "drop-shadow-transparent/45", - "drop-shadow-transparent/50", - "drop-shadow-transparent/55", - "drop-shadow-transparent/60", - "drop-shadow-transparent/65", - "drop-shadow-transparent/70", - "drop-shadow-transparent/75", - "drop-shadow-transparent/80", - "drop-shadow-transparent/85", - "drop-shadow-transparent/90", - "drop-shadow-transparent/95", - "drop-shadow-transparent/100", - "drop-shadow-transparent/background", "duration-75", "duration-100", "duration-150", @@ -4647,6 +3979,66 @@ exports[`getClassList 1`] = ` "duration-initial", "ease-initial", "ease-linear", + "end-0", + "end-0.5", + "end-1", + "end-1.5", + "end-2", + "end-2.5", + "end-3", + "end-3.5", + "end-4", + "end-5", + "end-6", + "end-7", + "end-8", + "end-9", + "end-10", + "end-11", + "end-12", + "end-14", + "end-16", + "end-20", + "end-24", + "end-28", + "end-32", + "end-36", + "end-40", + "end-44", + "end-48", + "end-52", + "end-56", + "end-60", + "end-64", + "end-72", + "end-80", + "end-96", + "end-1/2", + "end-1/3", + "end-1/4", + "end-1/5", + "end-1/6", + "end-1/12", + "end-2/3", + "end-2/4", + "end-2/5", + "end-2/6", + "end-2/12", + "end-3/4", + "end-3/5", + "end-3/6", + "end-3/12", + "end-4/5", + "end-4/6", + "end-4/12", + "end-5/6", + "end-5/12", + "end-6/12", + "end-7/12", + "end-8/12", + "end-9/12", + "end-10/12", + "end-11/12", "end-auto", "end-full", "end-px", @@ -4674,7 +4066,6 @@ exports[`getClassList 1`] = ` "fill-current/90", "fill-current/95", "fill-current/100", - "fill-current/background", "fill-inherit", "fill-inherit/0", "fill-inherit/5", @@ -4697,7 +4088,6 @@ exports[`getClassList 1`] = ` "fill-inherit/90", "fill-inherit/95", "fill-inherit/100", - "fill-inherit/background", "fill-none", "fill-transparent", "fill-transparent/0", @@ -4721,21 +4111,8 @@ exports[`getClassList 1`] = ` "fill-transparent/90", "fill-transparent/95", "fill-transparent/100", - "fill-transparent/background", "fixed", "flex", - "flex-1", - "flex-2", - "flex-3", - "flex-4", - "flex-5", - "flex-6", - "flex-7", - "flex-8", - "flex-9", - "flex-10", - "flex-11", - "flex-12", "flex-1/2", "flex-1/3", "flex-1/4", @@ -4845,7 +4222,6 @@ exports[`getClassList 1`] = ` "from-current/90", "from-current/95", "from-current/100", - "from-current/background", "from-inherit", "from-inherit/0", "from-inherit/5", @@ -4868,7 +4244,6 @@ exports[`getClassList 1`] = ` "from-inherit/90", "from-inherit/95", "from-inherit/100", - "from-inherit/background", "from-transparent", "from-transparent/0", "from-transparent/5", @@ -4891,7 +4266,6 @@ exports[`getClassList 1`] = ` "from-transparent/90", "from-transparent/95", "from-transparent/100", - "from-transparent/background", "gap-0", "gap-0.5", "gap-1", @@ -5104,7 +4478,6 @@ exports[`getClassList 1`] = ` "h-dvw", "h-fit", "h-full", - "h-lh", "h-lvh", "h-lvw", "h-max", @@ -5159,81 +4532,9 @@ exports[`getClassList 1`] = ` "indent-96", "indent-px", "inline", - "inline-0", - "inline-0.5", - "inline-1", - "inline-1.5", - "inline-2", - "inline-2.5", - "inline-3", - "inline-3.5", - "inline-4", - "inline-5", - "inline-6", - "inline-7", - "inline-8", - "inline-9", - "inline-10", - "inline-11", - "inline-12", - "inline-14", - "inline-16", - "inline-20", - "inline-24", - "inline-28", - "inline-32", - "inline-36", - "inline-40", - "inline-44", - "inline-48", - "inline-52", - "inline-56", - "inline-60", - "inline-64", - "inline-72", - "inline-80", - "inline-96", - "inline-auto", "inline-block", - "inline-dvw", - "inline-fit", "inline-flex", - "inline-full", "inline-grid", - "inline-lg", - "inline-lvw", - "inline-max", - "inline-md", - "inline-1/2", - "inline-1/3", - "inline-1/4", - "inline-1/5", - "inline-1/6", - "inline-1/12", - "inline-2/3", - "inline-2/4", - "inline-2/5", - "inline-2/6", - "inline-2/12", - "inline-3/4", - "inline-3/5", - "inline-3/6", - "inline-3/12", - "inline-4/5", - "inline-4/6", - "inline-4/12", - "inline-5/6", - "inline-5/12", - "inline-6/12", - "inline-7/12", - "inline-8/12", - "inline-9/12", - "inline-10/12", - "inline-11/12", - "inline-min", - "inline-px", - "inline-screen", - "inline-svw", "inline-table", "inset-0", "inset-0.5", @@ -5296,195 +4597,6 @@ exports[`getClassList 1`] = ` "inset-10/12", "inset-11/12", "inset-auto", - "inset-be-0", - "inset-be-0.5", - "inset-be-1", - "inset-be-1.5", - "inset-be-2", - "inset-be-2.5", - "inset-be-3", - "inset-be-3.5", - "inset-be-4", - "inset-be-5", - "inset-be-6", - "inset-be-7", - "inset-be-8", - "inset-be-9", - "inset-be-10", - "inset-be-11", - "inset-be-12", - "inset-be-14", - "inset-be-16", - "inset-be-20", - "inset-be-24", - "inset-be-28", - "inset-be-32", - "inset-be-36", - "inset-be-40", - "inset-be-44", - "inset-be-48", - "inset-be-52", - "inset-be-56", - "inset-be-60", - "inset-be-64", - "inset-be-72", - "inset-be-80", - "inset-be-96", - "inset-be-1/2", - "inset-be-1/3", - "inset-be-1/4", - "inset-be-1/5", - "inset-be-1/6", - "inset-be-1/12", - "inset-be-2/3", - "inset-be-2/4", - "inset-be-2/5", - "inset-be-2/6", - "inset-be-2/12", - "inset-be-3/4", - "inset-be-3/5", - "inset-be-3/6", - "inset-be-3/12", - "inset-be-4/5", - "inset-be-4/6", - "inset-be-4/12", - "inset-be-5/6", - "inset-be-5/12", - "inset-be-6/12", - "inset-be-7/12", - "inset-be-8/12", - "inset-be-9/12", - "inset-be-10/12", - "inset-be-11/12", - "inset-be-auto", - "inset-be-full", - "inset-be-px", - "inset-bs-0", - "inset-bs-0.5", - "inset-bs-1", - "inset-bs-1.5", - "inset-bs-2", - "inset-bs-2.5", - "inset-bs-3", - "inset-bs-3.5", - "inset-bs-4", - "inset-bs-5", - "inset-bs-6", - "inset-bs-7", - "inset-bs-8", - "inset-bs-9", - "inset-bs-10", - "inset-bs-11", - "inset-bs-12", - "inset-bs-14", - "inset-bs-16", - "inset-bs-20", - "inset-bs-24", - "inset-bs-28", - "inset-bs-32", - "inset-bs-36", - "inset-bs-40", - "inset-bs-44", - "inset-bs-48", - "inset-bs-52", - "inset-bs-56", - "inset-bs-60", - "inset-bs-64", - "inset-bs-72", - "inset-bs-80", - "inset-bs-96", - "inset-bs-1/2", - "inset-bs-1/3", - "inset-bs-1/4", - "inset-bs-1/5", - "inset-bs-1/6", - "inset-bs-1/12", - "inset-bs-2/3", - "inset-bs-2/4", - "inset-bs-2/5", - "inset-bs-2/6", - "inset-bs-2/12", - "inset-bs-3/4", - "inset-bs-3/5", - "inset-bs-3/6", - "inset-bs-3/12", - "inset-bs-4/5", - "inset-bs-4/6", - "inset-bs-4/12", - "inset-bs-5/6", - "inset-bs-5/12", - "inset-bs-6/12", - "inset-bs-7/12", - "inset-bs-8/12", - "inset-bs-9/12", - "inset-bs-10/12", - "inset-bs-11/12", - "inset-bs-auto", - "inset-bs-full", - "inset-bs-px", - "inset-e-0", - "inset-e-0.5", - "inset-e-1", - "inset-e-1.5", - "inset-e-2", - "inset-e-2.5", - "inset-e-3", - "inset-e-3.5", - "inset-e-4", - "inset-e-5", - "inset-e-6", - "inset-e-7", - "inset-e-8", - "inset-e-9", - "inset-e-10", - "inset-e-11", - "inset-e-12", - "inset-e-14", - "inset-e-16", - "inset-e-20", - "inset-e-24", - "inset-e-28", - "inset-e-32", - "inset-e-36", - "inset-e-40", - "inset-e-44", - "inset-e-48", - "inset-e-52", - "inset-e-56", - "inset-e-60", - "inset-e-64", - "inset-e-72", - "inset-e-80", - "inset-e-96", - "inset-e-1/2", - "inset-e-1/3", - "inset-e-1/4", - "inset-e-1/5", - "inset-e-1/6", - "inset-e-1/12", - "inset-e-2/3", - "inset-e-2/4", - "inset-e-2/5", - "inset-e-2/6", - "inset-e-2/12", - "inset-e-3/4", - "inset-e-3/5", - "inset-e-3/6", - "inset-e-3/12", - "inset-e-4/5", - "inset-e-4/6", - "inset-e-4/12", - "inset-e-5/6", - "inset-e-5/12", - "inset-e-6/12", - "inset-e-7/12", - "inset-e-8/12", - "inset-e-9/12", - "inset-e-10/12", - "inset-e-11/12", - "inset-e-auto", - "inset-e-full", - "inset-e-px", "inset-full", "inset-px", "inset-ring", @@ -5515,7 +4627,6 @@ exports[`getClassList 1`] = ` "inset-ring-current/90", "inset-ring-current/95", "inset-ring-current/100", - "inset-ring-current/background", "inset-ring-inherit", "inset-ring-inherit/0", "inset-ring-inherit/5", @@ -5538,7 +4649,6 @@ exports[`getClassList 1`] = ` "inset-ring-inherit/90", "inset-ring-inherit/95", "inset-ring-inherit/100", - "inset-ring-inherit/background", "inset-ring-transparent", "inset-ring-transparent/0", "inset-ring-transparent/5", @@ -5561,70 +4671,7 @@ exports[`getClassList 1`] = ` "inset-ring-transparent/90", "inset-ring-transparent/95", "inset-ring-transparent/100", - "inset-ring-transparent/background", - "inset-s-0", - "inset-s-0.5", - "inset-s-1", - "inset-s-1.5", - "inset-s-2", - "inset-s-2.5", - "inset-s-3", - "inset-s-3.5", - "inset-s-4", - "inset-s-5", - "inset-s-6", - "inset-s-7", - "inset-s-8", - "inset-s-9", - "inset-s-10", - "inset-s-11", - "inset-s-12", - "inset-s-14", - "inset-s-16", - "inset-s-20", - "inset-s-24", - "inset-s-28", - "inset-s-32", - "inset-s-36", - "inset-s-40", - "inset-s-44", - "inset-s-48", - "inset-s-52", - "inset-s-56", - "inset-s-60", - "inset-s-64", - "inset-s-72", - "inset-s-80", - "inset-s-96", - "inset-s-1/2", - "inset-s-1/3", - "inset-s-1/4", - "inset-s-1/5", - "inset-s-1/6", - "inset-s-1/12", - "inset-s-2/3", - "inset-s-2/4", - "inset-s-2/5", - "inset-s-2/6", - "inset-s-2/12", - "inset-s-3/4", - "inset-s-3/5", - "inset-s-3/6", - "inset-s-3/12", - "inset-s-4/5", - "inset-s-4/6", - "inset-s-4/12", - "inset-s-5/6", - "inset-s-5/12", - "inset-s-6/12", - "inset-s-7/12", - "inset-s-8/12", - "inset-s-9/12", - "inset-s-10/12", - "inset-s-11/12", - "inset-s-auto", - "inset-s-full", - "inset-s-px", + "inset-shadow", "inset-shadow-current", "inset-shadow-current/0", "inset-shadow-current/5", @@ -5647,7 +4694,6 @@ exports[`getClassList 1`] = ` "inset-shadow-current/90", "inset-shadow-current/95", "inset-shadow-current/100", - "inset-shadow-current/background", "inset-shadow-inherit", "inset-shadow-inherit/0", "inset-shadow-inherit/5", @@ -5670,31 +4716,8 @@ exports[`getClassList 1`] = ` "inset-shadow-inherit/90", "inset-shadow-inherit/95", "inset-shadow-inherit/100", - "inset-shadow-inherit/background", "inset-shadow-initial", - "inset-shadow-none", "inset-shadow-sm", - "inset-shadow-sm/0", - "inset-shadow-sm/5", - "inset-shadow-sm/10", - "inset-shadow-sm/15", - "inset-shadow-sm/20", - "inset-shadow-sm/25", - "inset-shadow-sm/30", - "inset-shadow-sm/35", - "inset-shadow-sm/40", - "inset-shadow-sm/45", - "inset-shadow-sm/50", - "inset-shadow-sm/55", - "inset-shadow-sm/60", - "inset-shadow-sm/65", - "inset-shadow-sm/70", - "inset-shadow-sm/75", - "inset-shadow-sm/80", - "inset-shadow-sm/85", - "inset-shadow-sm/90", - "inset-shadow-sm/95", - "inset-shadow-sm/100", "inset-shadow-transparent", "inset-shadow-transparent/0", "inset-shadow-transparent/5", @@ -5717,7 +4740,6 @@ exports[`getClassList 1`] = ` "inset-shadow-transparent/90", "inset-shadow-transparent/95", "inset-shadow-transparent/100", - "inset-shadow-transparent/background", "inset-x-0", "inset-x-0.5", "inset-x-1", @@ -6039,2383 +5061,6 @@ exports[`getClassList 1`] = ` "m-96", "m-auto", "m-px", - "mask-add", - "mask-alpha", - "mask-auto", - "mask-b-from-0", - "mask-b-from-0%", - "mask-b-from-0.5", - "mask-b-from-1", - "mask-b-from-1.5", - "mask-b-from-2", - "mask-b-from-2.5", - "mask-b-from-3", - "mask-b-from-3.5", - "mask-b-from-4", - "mask-b-from-5", - "mask-b-from-5%", - "mask-b-from-6", - "mask-b-from-7", - "mask-b-from-8", - "mask-b-from-9", - "mask-b-from-10", - "mask-b-from-10%", - "mask-b-from-11", - "mask-b-from-12", - "mask-b-from-14", - "mask-b-from-15%", - "mask-b-from-16", - "mask-b-from-20", - "mask-b-from-20%", - "mask-b-from-24", - "mask-b-from-25%", - "mask-b-from-28", - "mask-b-from-30%", - "mask-b-from-32", - "mask-b-from-35%", - "mask-b-from-36", - "mask-b-from-40", - "mask-b-from-40%", - "mask-b-from-44", - "mask-b-from-45%", - "mask-b-from-48", - "mask-b-from-50%", - "mask-b-from-52", - "mask-b-from-55%", - "mask-b-from-56", - "mask-b-from-60", - "mask-b-from-60%", - "mask-b-from-64", - "mask-b-from-65%", - "mask-b-from-70%", - "mask-b-from-72", - "mask-b-from-75%", - "mask-b-from-80", - "mask-b-from-80%", - "mask-b-from-85%", - "mask-b-from-90%", - "mask-b-from-95%", - "mask-b-from-96", - "mask-b-from-100%", - "mask-b-from-current", - "mask-b-from-current/0", - "mask-b-from-current/5", - "mask-b-from-current/10", - "mask-b-from-current/15", - "mask-b-from-current/20", - "mask-b-from-current/25", - "mask-b-from-current/30", - "mask-b-from-current/35", - "mask-b-from-current/40", - "mask-b-from-current/45", - "mask-b-from-current/50", - "mask-b-from-current/55", - "mask-b-from-current/60", - "mask-b-from-current/65", - "mask-b-from-current/70", - "mask-b-from-current/75", - "mask-b-from-current/80", - "mask-b-from-current/85", - "mask-b-from-current/90", - "mask-b-from-current/95", - "mask-b-from-current/100", - "mask-b-from-current/background", - "mask-b-from-inherit", - "mask-b-from-inherit/0", - "mask-b-from-inherit/5", - "mask-b-from-inherit/10", - "mask-b-from-inherit/15", - "mask-b-from-inherit/20", - "mask-b-from-inherit/25", - "mask-b-from-inherit/30", - "mask-b-from-inherit/35", - "mask-b-from-inherit/40", - "mask-b-from-inherit/45", - "mask-b-from-inherit/50", - "mask-b-from-inherit/55", - "mask-b-from-inherit/60", - "mask-b-from-inherit/65", - "mask-b-from-inherit/70", - "mask-b-from-inherit/75", - "mask-b-from-inherit/80", - "mask-b-from-inherit/85", - "mask-b-from-inherit/90", - "mask-b-from-inherit/95", - "mask-b-from-inherit/100", - "mask-b-from-inherit/background", - "mask-b-from-transparent", - "mask-b-from-transparent/0", - "mask-b-from-transparent/5", - "mask-b-from-transparent/10", - "mask-b-from-transparent/15", - "mask-b-from-transparent/20", - "mask-b-from-transparent/25", - "mask-b-from-transparent/30", - "mask-b-from-transparent/35", - "mask-b-from-transparent/40", - "mask-b-from-transparent/45", - "mask-b-from-transparent/50", - "mask-b-from-transparent/55", - "mask-b-from-transparent/60", - "mask-b-from-transparent/65", - "mask-b-from-transparent/70", - "mask-b-from-transparent/75", - "mask-b-from-transparent/80", - "mask-b-from-transparent/85", - "mask-b-from-transparent/90", - "mask-b-from-transparent/95", - "mask-b-from-transparent/100", - "mask-b-from-transparent/background", - "mask-b-to-0", - "mask-b-to-0%", - "mask-b-to-0.5", - "mask-b-to-1", - "mask-b-to-1.5", - "mask-b-to-2", - "mask-b-to-2.5", - "mask-b-to-3", - "mask-b-to-3.5", - "mask-b-to-4", - "mask-b-to-5", - "mask-b-to-5%", - "mask-b-to-6", - "mask-b-to-7", - "mask-b-to-8", - "mask-b-to-9", - "mask-b-to-10", - "mask-b-to-10%", - "mask-b-to-11", - "mask-b-to-12", - "mask-b-to-14", - "mask-b-to-15%", - "mask-b-to-16", - "mask-b-to-20", - "mask-b-to-20%", - "mask-b-to-24", - "mask-b-to-25%", - "mask-b-to-28", - "mask-b-to-30%", - "mask-b-to-32", - "mask-b-to-35%", - "mask-b-to-36", - "mask-b-to-40", - "mask-b-to-40%", - "mask-b-to-44", - "mask-b-to-45%", - "mask-b-to-48", - "mask-b-to-50%", - "mask-b-to-52", - "mask-b-to-55%", - "mask-b-to-56", - "mask-b-to-60", - "mask-b-to-60%", - "mask-b-to-64", - "mask-b-to-65%", - "mask-b-to-70%", - "mask-b-to-72", - "mask-b-to-75%", - "mask-b-to-80", - "mask-b-to-80%", - "mask-b-to-85%", - "mask-b-to-90%", - "mask-b-to-95%", - "mask-b-to-96", - "mask-b-to-100%", - "mask-b-to-current", - "mask-b-to-current/0", - "mask-b-to-current/5", - "mask-b-to-current/10", - "mask-b-to-current/15", - "mask-b-to-current/20", - "mask-b-to-current/25", - "mask-b-to-current/30", - "mask-b-to-current/35", - "mask-b-to-current/40", - "mask-b-to-current/45", - "mask-b-to-current/50", - "mask-b-to-current/55", - "mask-b-to-current/60", - "mask-b-to-current/65", - "mask-b-to-current/70", - "mask-b-to-current/75", - "mask-b-to-current/80", - "mask-b-to-current/85", - "mask-b-to-current/90", - "mask-b-to-current/95", - "mask-b-to-current/100", - "mask-b-to-current/background", - "mask-b-to-inherit", - "mask-b-to-inherit/0", - "mask-b-to-inherit/5", - "mask-b-to-inherit/10", - "mask-b-to-inherit/15", - "mask-b-to-inherit/20", - "mask-b-to-inherit/25", - "mask-b-to-inherit/30", - "mask-b-to-inherit/35", - "mask-b-to-inherit/40", - "mask-b-to-inherit/45", - "mask-b-to-inherit/50", - "mask-b-to-inherit/55", - "mask-b-to-inherit/60", - "mask-b-to-inherit/65", - "mask-b-to-inherit/70", - "mask-b-to-inherit/75", - "mask-b-to-inherit/80", - "mask-b-to-inherit/85", - "mask-b-to-inherit/90", - "mask-b-to-inherit/95", - "mask-b-to-inherit/100", - "mask-b-to-inherit/background", - "mask-b-to-transparent", - "mask-b-to-transparent/0", - "mask-b-to-transparent/5", - "mask-b-to-transparent/10", - "mask-b-to-transparent/15", - "mask-b-to-transparent/20", - "mask-b-to-transparent/25", - "mask-b-to-transparent/30", - "mask-b-to-transparent/35", - "mask-b-to-transparent/40", - "mask-b-to-transparent/45", - "mask-b-to-transparent/50", - "mask-b-to-transparent/55", - "mask-b-to-transparent/60", - "mask-b-to-transparent/65", - "mask-b-to-transparent/70", - "mask-b-to-transparent/75", - "mask-b-to-transparent/80", - "mask-b-to-transparent/85", - "mask-b-to-transparent/90", - "mask-b-to-transparent/95", - "mask-b-to-transparent/100", - "mask-b-to-transparent/background", - "mask-bottom", - "mask-bottom-left", - "mask-bottom-right", - "mask-center", - "mask-circle", - "mask-clip-border", - "mask-clip-content", - "mask-clip-fill", - "mask-clip-padding", - "mask-clip-stroke", - "mask-clip-view", - "mask-conic-0", - "mask-conic-1", - "mask-conic-2", - "mask-conic-3", - "mask-conic-6", - "mask-conic-12", - "mask-conic-45", - "mask-conic-90", - "mask-conic-180", - "mask-conic-from-0", - "mask-conic-from-0%", - "mask-conic-from-0.5", - "mask-conic-from-1", - "mask-conic-from-1.5", - "mask-conic-from-2", - "mask-conic-from-2.5", - "mask-conic-from-3", - "mask-conic-from-3.5", - "mask-conic-from-4", - "mask-conic-from-5", - "mask-conic-from-5%", - "mask-conic-from-6", - "mask-conic-from-7", - "mask-conic-from-8", - "mask-conic-from-9", - "mask-conic-from-10", - "mask-conic-from-10%", - "mask-conic-from-11", - "mask-conic-from-12", - "mask-conic-from-14", - "mask-conic-from-15%", - "mask-conic-from-16", - "mask-conic-from-20", - "mask-conic-from-20%", - "mask-conic-from-24", - "mask-conic-from-25%", - "mask-conic-from-28", - "mask-conic-from-30%", - "mask-conic-from-32", - "mask-conic-from-35%", - "mask-conic-from-36", - "mask-conic-from-40", - "mask-conic-from-40%", - "mask-conic-from-44", - "mask-conic-from-45%", - "mask-conic-from-48", - "mask-conic-from-50%", - "mask-conic-from-52", - "mask-conic-from-55%", - "mask-conic-from-56", - "mask-conic-from-60", - "mask-conic-from-60%", - "mask-conic-from-64", - "mask-conic-from-65%", - "mask-conic-from-70%", - "mask-conic-from-72", - "mask-conic-from-75%", - "mask-conic-from-80", - "mask-conic-from-80%", - "mask-conic-from-85%", - "mask-conic-from-90%", - "mask-conic-from-95%", - "mask-conic-from-96", - "mask-conic-from-100%", - "mask-conic-from-current", - "mask-conic-from-current/0", - "mask-conic-from-current/5", - "mask-conic-from-current/10", - "mask-conic-from-current/15", - "mask-conic-from-current/20", - "mask-conic-from-current/25", - "mask-conic-from-current/30", - "mask-conic-from-current/35", - "mask-conic-from-current/40", - "mask-conic-from-current/45", - "mask-conic-from-current/50", - "mask-conic-from-current/55", - "mask-conic-from-current/60", - "mask-conic-from-current/65", - "mask-conic-from-current/70", - "mask-conic-from-current/75", - "mask-conic-from-current/80", - "mask-conic-from-current/85", - "mask-conic-from-current/90", - "mask-conic-from-current/95", - "mask-conic-from-current/100", - "mask-conic-from-current/background", - "mask-conic-from-inherit", - "mask-conic-from-inherit/0", - "mask-conic-from-inherit/5", - "mask-conic-from-inherit/10", - "mask-conic-from-inherit/15", - "mask-conic-from-inherit/20", - "mask-conic-from-inherit/25", - "mask-conic-from-inherit/30", - "mask-conic-from-inherit/35", - "mask-conic-from-inherit/40", - "mask-conic-from-inherit/45", - "mask-conic-from-inherit/50", - "mask-conic-from-inherit/55", - "mask-conic-from-inherit/60", - "mask-conic-from-inherit/65", - "mask-conic-from-inherit/70", - "mask-conic-from-inherit/75", - "mask-conic-from-inherit/80", - "mask-conic-from-inherit/85", - "mask-conic-from-inherit/90", - "mask-conic-from-inherit/95", - "mask-conic-from-inherit/100", - "mask-conic-from-inherit/background", - "mask-conic-from-transparent", - "mask-conic-from-transparent/0", - "mask-conic-from-transparent/5", - "mask-conic-from-transparent/10", - "mask-conic-from-transparent/15", - "mask-conic-from-transparent/20", - "mask-conic-from-transparent/25", - "mask-conic-from-transparent/30", - "mask-conic-from-transparent/35", - "mask-conic-from-transparent/40", - "mask-conic-from-transparent/45", - "mask-conic-from-transparent/50", - "mask-conic-from-transparent/55", - "mask-conic-from-transparent/60", - "mask-conic-from-transparent/65", - "mask-conic-from-transparent/70", - "mask-conic-from-transparent/75", - "mask-conic-from-transparent/80", - "mask-conic-from-transparent/85", - "mask-conic-from-transparent/90", - "mask-conic-from-transparent/95", - "mask-conic-from-transparent/100", - "mask-conic-from-transparent/background", - "mask-conic-to-0", - "mask-conic-to-0%", - "mask-conic-to-0.5", - "mask-conic-to-1", - "mask-conic-to-1.5", - "mask-conic-to-2", - "mask-conic-to-2.5", - "mask-conic-to-3", - "mask-conic-to-3.5", - "mask-conic-to-4", - "mask-conic-to-5", - "mask-conic-to-5%", - "mask-conic-to-6", - "mask-conic-to-7", - "mask-conic-to-8", - "mask-conic-to-9", - "mask-conic-to-10", - "mask-conic-to-10%", - "mask-conic-to-11", - "mask-conic-to-12", - "mask-conic-to-14", - "mask-conic-to-15%", - "mask-conic-to-16", - "mask-conic-to-20", - "mask-conic-to-20%", - "mask-conic-to-24", - "mask-conic-to-25%", - "mask-conic-to-28", - "mask-conic-to-30%", - "mask-conic-to-32", - "mask-conic-to-35%", - "mask-conic-to-36", - "mask-conic-to-40", - "mask-conic-to-40%", - "mask-conic-to-44", - "mask-conic-to-45%", - "mask-conic-to-48", - "mask-conic-to-50%", - "mask-conic-to-52", - "mask-conic-to-55%", - "mask-conic-to-56", - "mask-conic-to-60", - "mask-conic-to-60%", - "mask-conic-to-64", - "mask-conic-to-65%", - "mask-conic-to-70%", - "mask-conic-to-72", - "mask-conic-to-75%", - "mask-conic-to-80", - "mask-conic-to-80%", - "mask-conic-to-85%", - "mask-conic-to-90%", - "mask-conic-to-95%", - "mask-conic-to-96", - "mask-conic-to-100%", - "mask-conic-to-current", - "mask-conic-to-current/0", - "mask-conic-to-current/5", - "mask-conic-to-current/10", - "mask-conic-to-current/15", - "mask-conic-to-current/20", - "mask-conic-to-current/25", - "mask-conic-to-current/30", - "mask-conic-to-current/35", - "mask-conic-to-current/40", - "mask-conic-to-current/45", - "mask-conic-to-current/50", - "mask-conic-to-current/55", - "mask-conic-to-current/60", - "mask-conic-to-current/65", - "mask-conic-to-current/70", - "mask-conic-to-current/75", - "mask-conic-to-current/80", - "mask-conic-to-current/85", - "mask-conic-to-current/90", - "mask-conic-to-current/95", - "mask-conic-to-current/100", - "mask-conic-to-current/background", - "mask-conic-to-inherit", - "mask-conic-to-inherit/0", - "mask-conic-to-inherit/5", - "mask-conic-to-inherit/10", - "mask-conic-to-inherit/15", - "mask-conic-to-inherit/20", - "mask-conic-to-inherit/25", - "mask-conic-to-inherit/30", - "mask-conic-to-inherit/35", - "mask-conic-to-inherit/40", - "mask-conic-to-inherit/45", - "mask-conic-to-inherit/50", - "mask-conic-to-inherit/55", - "mask-conic-to-inherit/60", - "mask-conic-to-inherit/65", - "mask-conic-to-inherit/70", - "mask-conic-to-inherit/75", - "mask-conic-to-inherit/80", - "mask-conic-to-inherit/85", - "mask-conic-to-inherit/90", - "mask-conic-to-inherit/95", - "mask-conic-to-inherit/100", - "mask-conic-to-inherit/background", - "mask-conic-to-transparent", - "mask-conic-to-transparent/0", - "mask-conic-to-transparent/5", - "mask-conic-to-transparent/10", - "mask-conic-to-transparent/15", - "mask-conic-to-transparent/20", - "mask-conic-to-transparent/25", - "mask-conic-to-transparent/30", - "mask-conic-to-transparent/35", - "mask-conic-to-transparent/40", - "mask-conic-to-transparent/45", - "mask-conic-to-transparent/50", - "mask-conic-to-transparent/55", - "mask-conic-to-transparent/60", - "mask-conic-to-transparent/65", - "mask-conic-to-transparent/70", - "mask-conic-to-transparent/75", - "mask-conic-to-transparent/80", - "mask-conic-to-transparent/85", - "mask-conic-to-transparent/90", - "mask-conic-to-transparent/95", - "mask-conic-to-transparent/100", - "mask-conic-to-transparent/background", - "mask-contain", - "mask-cover", - "mask-ellipse", - "mask-exclude", - "mask-intersect", - "mask-l-from-0", - "mask-l-from-0%", - "mask-l-from-0.5", - "mask-l-from-1", - "mask-l-from-1.5", - "mask-l-from-2", - "mask-l-from-2.5", - "mask-l-from-3", - "mask-l-from-3.5", - "mask-l-from-4", - "mask-l-from-5", - "mask-l-from-5%", - "mask-l-from-6", - "mask-l-from-7", - "mask-l-from-8", - "mask-l-from-9", - "mask-l-from-10", - "mask-l-from-10%", - "mask-l-from-11", - "mask-l-from-12", - "mask-l-from-14", - "mask-l-from-15%", - "mask-l-from-16", - "mask-l-from-20", - "mask-l-from-20%", - "mask-l-from-24", - "mask-l-from-25%", - "mask-l-from-28", - "mask-l-from-30%", - "mask-l-from-32", - "mask-l-from-35%", - "mask-l-from-36", - "mask-l-from-40", - "mask-l-from-40%", - "mask-l-from-44", - "mask-l-from-45%", - "mask-l-from-48", - "mask-l-from-50%", - "mask-l-from-52", - "mask-l-from-55%", - "mask-l-from-56", - "mask-l-from-60", - "mask-l-from-60%", - "mask-l-from-64", - "mask-l-from-65%", - "mask-l-from-70%", - "mask-l-from-72", - "mask-l-from-75%", - "mask-l-from-80", - "mask-l-from-80%", - "mask-l-from-85%", - "mask-l-from-90%", - "mask-l-from-95%", - "mask-l-from-96", - "mask-l-from-100%", - "mask-l-from-current", - "mask-l-from-current/0", - "mask-l-from-current/5", - "mask-l-from-current/10", - "mask-l-from-current/15", - "mask-l-from-current/20", - "mask-l-from-current/25", - "mask-l-from-current/30", - "mask-l-from-current/35", - "mask-l-from-current/40", - "mask-l-from-current/45", - "mask-l-from-current/50", - "mask-l-from-current/55", - "mask-l-from-current/60", - "mask-l-from-current/65", - "mask-l-from-current/70", - "mask-l-from-current/75", - "mask-l-from-current/80", - "mask-l-from-current/85", - "mask-l-from-current/90", - "mask-l-from-current/95", - "mask-l-from-current/100", - "mask-l-from-current/background", - "mask-l-from-inherit", - "mask-l-from-inherit/0", - "mask-l-from-inherit/5", - "mask-l-from-inherit/10", - "mask-l-from-inherit/15", - "mask-l-from-inherit/20", - "mask-l-from-inherit/25", - "mask-l-from-inherit/30", - "mask-l-from-inherit/35", - "mask-l-from-inherit/40", - "mask-l-from-inherit/45", - "mask-l-from-inherit/50", - "mask-l-from-inherit/55", - "mask-l-from-inherit/60", - "mask-l-from-inherit/65", - "mask-l-from-inherit/70", - "mask-l-from-inherit/75", - "mask-l-from-inherit/80", - "mask-l-from-inherit/85", - "mask-l-from-inherit/90", - "mask-l-from-inherit/95", - "mask-l-from-inherit/100", - "mask-l-from-inherit/background", - "mask-l-from-transparent", - "mask-l-from-transparent/0", - "mask-l-from-transparent/5", - "mask-l-from-transparent/10", - "mask-l-from-transparent/15", - "mask-l-from-transparent/20", - "mask-l-from-transparent/25", - "mask-l-from-transparent/30", - "mask-l-from-transparent/35", - "mask-l-from-transparent/40", - "mask-l-from-transparent/45", - "mask-l-from-transparent/50", - "mask-l-from-transparent/55", - "mask-l-from-transparent/60", - "mask-l-from-transparent/65", - "mask-l-from-transparent/70", - "mask-l-from-transparent/75", - "mask-l-from-transparent/80", - "mask-l-from-transparent/85", - "mask-l-from-transparent/90", - "mask-l-from-transparent/95", - "mask-l-from-transparent/100", - "mask-l-from-transparent/background", - "mask-l-to-0", - "mask-l-to-0%", - "mask-l-to-0.5", - "mask-l-to-1", - "mask-l-to-1.5", - "mask-l-to-2", - "mask-l-to-2.5", - "mask-l-to-3", - "mask-l-to-3.5", - "mask-l-to-4", - "mask-l-to-5", - "mask-l-to-5%", - "mask-l-to-6", - "mask-l-to-7", - "mask-l-to-8", - "mask-l-to-9", - "mask-l-to-10", - "mask-l-to-10%", - "mask-l-to-11", - "mask-l-to-12", - "mask-l-to-14", - "mask-l-to-15%", - "mask-l-to-16", - "mask-l-to-20", - "mask-l-to-20%", - "mask-l-to-24", - "mask-l-to-25%", - "mask-l-to-28", - "mask-l-to-30%", - "mask-l-to-32", - "mask-l-to-35%", - "mask-l-to-36", - "mask-l-to-40", - "mask-l-to-40%", - "mask-l-to-44", - "mask-l-to-45%", - "mask-l-to-48", - "mask-l-to-50%", - "mask-l-to-52", - "mask-l-to-55%", - "mask-l-to-56", - "mask-l-to-60", - "mask-l-to-60%", - "mask-l-to-64", - "mask-l-to-65%", - "mask-l-to-70%", - "mask-l-to-72", - "mask-l-to-75%", - "mask-l-to-80", - "mask-l-to-80%", - "mask-l-to-85%", - "mask-l-to-90%", - "mask-l-to-95%", - "mask-l-to-96", - "mask-l-to-100%", - "mask-l-to-current", - "mask-l-to-current/0", - "mask-l-to-current/5", - "mask-l-to-current/10", - "mask-l-to-current/15", - "mask-l-to-current/20", - "mask-l-to-current/25", - "mask-l-to-current/30", - "mask-l-to-current/35", - "mask-l-to-current/40", - "mask-l-to-current/45", - "mask-l-to-current/50", - "mask-l-to-current/55", - "mask-l-to-current/60", - "mask-l-to-current/65", - "mask-l-to-current/70", - "mask-l-to-current/75", - "mask-l-to-current/80", - "mask-l-to-current/85", - "mask-l-to-current/90", - "mask-l-to-current/95", - "mask-l-to-current/100", - "mask-l-to-current/background", - "mask-l-to-inherit", - "mask-l-to-inherit/0", - "mask-l-to-inherit/5", - "mask-l-to-inherit/10", - "mask-l-to-inherit/15", - "mask-l-to-inherit/20", - "mask-l-to-inherit/25", - "mask-l-to-inherit/30", - "mask-l-to-inherit/35", - "mask-l-to-inherit/40", - "mask-l-to-inherit/45", - "mask-l-to-inherit/50", - "mask-l-to-inherit/55", - "mask-l-to-inherit/60", - "mask-l-to-inherit/65", - "mask-l-to-inherit/70", - "mask-l-to-inherit/75", - "mask-l-to-inherit/80", - "mask-l-to-inherit/85", - "mask-l-to-inherit/90", - "mask-l-to-inherit/95", - "mask-l-to-inherit/100", - "mask-l-to-inherit/background", - "mask-l-to-transparent", - "mask-l-to-transparent/0", - "mask-l-to-transparent/5", - "mask-l-to-transparent/10", - "mask-l-to-transparent/15", - "mask-l-to-transparent/20", - "mask-l-to-transparent/25", - "mask-l-to-transparent/30", - "mask-l-to-transparent/35", - "mask-l-to-transparent/40", - "mask-l-to-transparent/45", - "mask-l-to-transparent/50", - "mask-l-to-transparent/55", - "mask-l-to-transparent/60", - "mask-l-to-transparent/65", - "mask-l-to-transparent/70", - "mask-l-to-transparent/75", - "mask-l-to-transparent/80", - "mask-l-to-transparent/85", - "mask-l-to-transparent/90", - "mask-l-to-transparent/95", - "mask-l-to-transparent/100", - "mask-l-to-transparent/background", - "mask-left", - "mask-linear-0", - "mask-linear-1", - "mask-linear-2", - "mask-linear-3", - "mask-linear-6", - "mask-linear-12", - "mask-linear-45", - "mask-linear-90", - "mask-linear-180", - "mask-linear-from-0", - "mask-linear-from-0%", - "mask-linear-from-0.5", - "mask-linear-from-1", - "mask-linear-from-1.5", - "mask-linear-from-2", - "mask-linear-from-2.5", - "mask-linear-from-3", - "mask-linear-from-3.5", - "mask-linear-from-4", - "mask-linear-from-5", - "mask-linear-from-5%", - "mask-linear-from-6", - "mask-linear-from-7", - "mask-linear-from-8", - "mask-linear-from-9", - "mask-linear-from-10", - "mask-linear-from-10%", - "mask-linear-from-11", - "mask-linear-from-12", - "mask-linear-from-14", - "mask-linear-from-15%", - "mask-linear-from-16", - "mask-linear-from-20", - "mask-linear-from-20%", - "mask-linear-from-24", - "mask-linear-from-25%", - "mask-linear-from-28", - "mask-linear-from-30%", - "mask-linear-from-32", - "mask-linear-from-35%", - "mask-linear-from-36", - "mask-linear-from-40", - "mask-linear-from-40%", - "mask-linear-from-44", - "mask-linear-from-45%", - "mask-linear-from-48", - "mask-linear-from-50%", - "mask-linear-from-52", - "mask-linear-from-55%", - "mask-linear-from-56", - "mask-linear-from-60", - "mask-linear-from-60%", - "mask-linear-from-64", - "mask-linear-from-65%", - "mask-linear-from-70%", - "mask-linear-from-72", - "mask-linear-from-75%", - "mask-linear-from-80", - "mask-linear-from-80%", - "mask-linear-from-85%", - "mask-linear-from-90%", - "mask-linear-from-95%", - "mask-linear-from-96", - "mask-linear-from-100%", - "mask-linear-from-current", - "mask-linear-from-current/0", - "mask-linear-from-current/5", - "mask-linear-from-current/10", - "mask-linear-from-current/15", - "mask-linear-from-current/20", - "mask-linear-from-current/25", - "mask-linear-from-current/30", - "mask-linear-from-current/35", - "mask-linear-from-current/40", - "mask-linear-from-current/45", - "mask-linear-from-current/50", - "mask-linear-from-current/55", - "mask-linear-from-current/60", - "mask-linear-from-current/65", - "mask-linear-from-current/70", - "mask-linear-from-current/75", - "mask-linear-from-current/80", - "mask-linear-from-current/85", - "mask-linear-from-current/90", - "mask-linear-from-current/95", - "mask-linear-from-current/100", - "mask-linear-from-current/background", - "mask-linear-from-inherit", - "mask-linear-from-inherit/0", - "mask-linear-from-inherit/5", - "mask-linear-from-inherit/10", - "mask-linear-from-inherit/15", - "mask-linear-from-inherit/20", - "mask-linear-from-inherit/25", - "mask-linear-from-inherit/30", - "mask-linear-from-inherit/35", - "mask-linear-from-inherit/40", - "mask-linear-from-inherit/45", - "mask-linear-from-inherit/50", - "mask-linear-from-inherit/55", - "mask-linear-from-inherit/60", - "mask-linear-from-inherit/65", - "mask-linear-from-inherit/70", - "mask-linear-from-inherit/75", - "mask-linear-from-inherit/80", - "mask-linear-from-inherit/85", - "mask-linear-from-inherit/90", - "mask-linear-from-inherit/95", - "mask-linear-from-inherit/100", - "mask-linear-from-inherit/background", - "mask-linear-from-transparent", - "mask-linear-from-transparent/0", - "mask-linear-from-transparent/5", - "mask-linear-from-transparent/10", - "mask-linear-from-transparent/15", - "mask-linear-from-transparent/20", - "mask-linear-from-transparent/25", - "mask-linear-from-transparent/30", - "mask-linear-from-transparent/35", - "mask-linear-from-transparent/40", - "mask-linear-from-transparent/45", - "mask-linear-from-transparent/50", - "mask-linear-from-transparent/55", - "mask-linear-from-transparent/60", - "mask-linear-from-transparent/65", - "mask-linear-from-transparent/70", - "mask-linear-from-transparent/75", - "mask-linear-from-transparent/80", - "mask-linear-from-transparent/85", - "mask-linear-from-transparent/90", - "mask-linear-from-transparent/95", - "mask-linear-from-transparent/100", - "mask-linear-from-transparent/background", - "mask-linear-to-0", - "mask-linear-to-0%", - "mask-linear-to-0.5", - "mask-linear-to-1", - "mask-linear-to-1.5", - "mask-linear-to-2", - "mask-linear-to-2.5", - "mask-linear-to-3", - "mask-linear-to-3.5", - "mask-linear-to-4", - "mask-linear-to-5", - "mask-linear-to-5%", - "mask-linear-to-6", - "mask-linear-to-7", - "mask-linear-to-8", - "mask-linear-to-9", - "mask-linear-to-10", - "mask-linear-to-10%", - "mask-linear-to-11", - "mask-linear-to-12", - "mask-linear-to-14", - "mask-linear-to-15%", - "mask-linear-to-16", - "mask-linear-to-20", - "mask-linear-to-20%", - "mask-linear-to-24", - "mask-linear-to-25%", - "mask-linear-to-28", - "mask-linear-to-30%", - "mask-linear-to-32", - "mask-linear-to-35%", - "mask-linear-to-36", - "mask-linear-to-40", - "mask-linear-to-40%", - "mask-linear-to-44", - "mask-linear-to-45%", - "mask-linear-to-48", - "mask-linear-to-50%", - "mask-linear-to-52", - "mask-linear-to-55%", - "mask-linear-to-56", - "mask-linear-to-60", - "mask-linear-to-60%", - "mask-linear-to-64", - "mask-linear-to-65%", - "mask-linear-to-70%", - "mask-linear-to-72", - "mask-linear-to-75%", - "mask-linear-to-80", - "mask-linear-to-80%", - "mask-linear-to-85%", - "mask-linear-to-90%", - "mask-linear-to-95%", - "mask-linear-to-96", - "mask-linear-to-100%", - "mask-linear-to-current", - "mask-linear-to-current/0", - "mask-linear-to-current/5", - "mask-linear-to-current/10", - "mask-linear-to-current/15", - "mask-linear-to-current/20", - "mask-linear-to-current/25", - "mask-linear-to-current/30", - "mask-linear-to-current/35", - "mask-linear-to-current/40", - "mask-linear-to-current/45", - "mask-linear-to-current/50", - "mask-linear-to-current/55", - "mask-linear-to-current/60", - "mask-linear-to-current/65", - "mask-linear-to-current/70", - "mask-linear-to-current/75", - "mask-linear-to-current/80", - "mask-linear-to-current/85", - "mask-linear-to-current/90", - "mask-linear-to-current/95", - "mask-linear-to-current/100", - "mask-linear-to-current/background", - "mask-linear-to-inherit", - "mask-linear-to-inherit/0", - "mask-linear-to-inherit/5", - "mask-linear-to-inherit/10", - "mask-linear-to-inherit/15", - "mask-linear-to-inherit/20", - "mask-linear-to-inherit/25", - "mask-linear-to-inherit/30", - "mask-linear-to-inherit/35", - "mask-linear-to-inherit/40", - "mask-linear-to-inherit/45", - "mask-linear-to-inherit/50", - "mask-linear-to-inherit/55", - "mask-linear-to-inherit/60", - "mask-linear-to-inherit/65", - "mask-linear-to-inherit/70", - "mask-linear-to-inherit/75", - "mask-linear-to-inherit/80", - "mask-linear-to-inherit/85", - "mask-linear-to-inherit/90", - "mask-linear-to-inherit/95", - "mask-linear-to-inherit/100", - "mask-linear-to-inherit/background", - "mask-linear-to-transparent", - "mask-linear-to-transparent/0", - "mask-linear-to-transparent/5", - "mask-linear-to-transparent/10", - "mask-linear-to-transparent/15", - "mask-linear-to-transparent/20", - "mask-linear-to-transparent/25", - "mask-linear-to-transparent/30", - "mask-linear-to-transparent/35", - "mask-linear-to-transparent/40", - "mask-linear-to-transparent/45", - "mask-linear-to-transparent/50", - "mask-linear-to-transparent/55", - "mask-linear-to-transparent/60", - "mask-linear-to-transparent/65", - "mask-linear-to-transparent/70", - "mask-linear-to-transparent/75", - "mask-linear-to-transparent/80", - "mask-linear-to-transparent/85", - "mask-linear-to-transparent/90", - "mask-linear-to-transparent/95", - "mask-linear-to-transparent/100", - "mask-linear-to-transparent/background", - "mask-luminance", - "mask-match", - "mask-no-clip", - "mask-no-repeat", - "mask-none", - "mask-origin-border", - "mask-origin-content", - "mask-origin-fill", - "mask-origin-padding", - "mask-origin-stroke", - "mask-origin-view", - "mask-r-from-0", - "mask-r-from-0%", - "mask-r-from-0.5", - "mask-r-from-1", - "mask-r-from-1.5", - "mask-r-from-2", - "mask-r-from-2.5", - "mask-r-from-3", - "mask-r-from-3.5", - "mask-r-from-4", - "mask-r-from-5", - "mask-r-from-5%", - "mask-r-from-6", - "mask-r-from-7", - "mask-r-from-8", - "mask-r-from-9", - "mask-r-from-10", - "mask-r-from-10%", - "mask-r-from-11", - "mask-r-from-12", - "mask-r-from-14", - "mask-r-from-15%", - "mask-r-from-16", - "mask-r-from-20", - "mask-r-from-20%", - "mask-r-from-24", - "mask-r-from-25%", - "mask-r-from-28", - "mask-r-from-30%", - "mask-r-from-32", - "mask-r-from-35%", - "mask-r-from-36", - "mask-r-from-40", - "mask-r-from-40%", - "mask-r-from-44", - "mask-r-from-45%", - "mask-r-from-48", - "mask-r-from-50%", - "mask-r-from-52", - "mask-r-from-55%", - "mask-r-from-56", - "mask-r-from-60", - "mask-r-from-60%", - "mask-r-from-64", - "mask-r-from-65%", - "mask-r-from-70%", - "mask-r-from-72", - "mask-r-from-75%", - "mask-r-from-80", - "mask-r-from-80%", - "mask-r-from-85%", - "mask-r-from-90%", - "mask-r-from-95%", - "mask-r-from-96", - "mask-r-from-100%", - "mask-r-from-current", - "mask-r-from-current/0", - "mask-r-from-current/5", - "mask-r-from-current/10", - "mask-r-from-current/15", - "mask-r-from-current/20", - "mask-r-from-current/25", - "mask-r-from-current/30", - "mask-r-from-current/35", - "mask-r-from-current/40", - "mask-r-from-current/45", - "mask-r-from-current/50", - "mask-r-from-current/55", - "mask-r-from-current/60", - "mask-r-from-current/65", - "mask-r-from-current/70", - "mask-r-from-current/75", - "mask-r-from-current/80", - "mask-r-from-current/85", - "mask-r-from-current/90", - "mask-r-from-current/95", - "mask-r-from-current/100", - "mask-r-from-current/background", - "mask-r-from-inherit", - "mask-r-from-inherit/0", - "mask-r-from-inherit/5", - "mask-r-from-inherit/10", - "mask-r-from-inherit/15", - "mask-r-from-inherit/20", - "mask-r-from-inherit/25", - "mask-r-from-inherit/30", - "mask-r-from-inherit/35", - "mask-r-from-inherit/40", - "mask-r-from-inherit/45", - "mask-r-from-inherit/50", - "mask-r-from-inherit/55", - "mask-r-from-inherit/60", - "mask-r-from-inherit/65", - "mask-r-from-inherit/70", - "mask-r-from-inherit/75", - "mask-r-from-inherit/80", - "mask-r-from-inherit/85", - "mask-r-from-inherit/90", - "mask-r-from-inherit/95", - "mask-r-from-inherit/100", - "mask-r-from-inherit/background", - "mask-r-from-transparent", - "mask-r-from-transparent/0", - "mask-r-from-transparent/5", - "mask-r-from-transparent/10", - "mask-r-from-transparent/15", - "mask-r-from-transparent/20", - "mask-r-from-transparent/25", - "mask-r-from-transparent/30", - "mask-r-from-transparent/35", - "mask-r-from-transparent/40", - "mask-r-from-transparent/45", - "mask-r-from-transparent/50", - "mask-r-from-transparent/55", - "mask-r-from-transparent/60", - "mask-r-from-transparent/65", - "mask-r-from-transparent/70", - "mask-r-from-transparent/75", - "mask-r-from-transparent/80", - "mask-r-from-transparent/85", - "mask-r-from-transparent/90", - "mask-r-from-transparent/95", - "mask-r-from-transparent/100", - "mask-r-from-transparent/background", - "mask-r-to-0", - "mask-r-to-0%", - "mask-r-to-0.5", - "mask-r-to-1", - "mask-r-to-1.5", - "mask-r-to-2", - "mask-r-to-2.5", - "mask-r-to-3", - "mask-r-to-3.5", - "mask-r-to-4", - "mask-r-to-5", - "mask-r-to-5%", - "mask-r-to-6", - "mask-r-to-7", - "mask-r-to-8", - "mask-r-to-9", - "mask-r-to-10", - "mask-r-to-10%", - "mask-r-to-11", - "mask-r-to-12", - "mask-r-to-14", - "mask-r-to-15%", - "mask-r-to-16", - "mask-r-to-20", - "mask-r-to-20%", - "mask-r-to-24", - "mask-r-to-25%", - "mask-r-to-28", - "mask-r-to-30%", - "mask-r-to-32", - "mask-r-to-35%", - "mask-r-to-36", - "mask-r-to-40", - "mask-r-to-40%", - "mask-r-to-44", - "mask-r-to-45%", - "mask-r-to-48", - "mask-r-to-50%", - "mask-r-to-52", - "mask-r-to-55%", - "mask-r-to-56", - "mask-r-to-60", - "mask-r-to-60%", - "mask-r-to-64", - "mask-r-to-65%", - "mask-r-to-70%", - "mask-r-to-72", - "mask-r-to-75%", - "mask-r-to-80", - "mask-r-to-80%", - "mask-r-to-85%", - "mask-r-to-90%", - "mask-r-to-95%", - "mask-r-to-96", - "mask-r-to-100%", - "mask-r-to-current", - "mask-r-to-current/0", - "mask-r-to-current/5", - "mask-r-to-current/10", - "mask-r-to-current/15", - "mask-r-to-current/20", - "mask-r-to-current/25", - "mask-r-to-current/30", - "mask-r-to-current/35", - "mask-r-to-current/40", - "mask-r-to-current/45", - "mask-r-to-current/50", - "mask-r-to-current/55", - "mask-r-to-current/60", - "mask-r-to-current/65", - "mask-r-to-current/70", - "mask-r-to-current/75", - "mask-r-to-current/80", - "mask-r-to-current/85", - "mask-r-to-current/90", - "mask-r-to-current/95", - "mask-r-to-current/100", - "mask-r-to-current/background", - "mask-r-to-inherit", - "mask-r-to-inherit/0", - "mask-r-to-inherit/5", - "mask-r-to-inherit/10", - "mask-r-to-inherit/15", - "mask-r-to-inherit/20", - "mask-r-to-inherit/25", - "mask-r-to-inherit/30", - "mask-r-to-inherit/35", - "mask-r-to-inherit/40", - "mask-r-to-inherit/45", - "mask-r-to-inherit/50", - "mask-r-to-inherit/55", - "mask-r-to-inherit/60", - "mask-r-to-inherit/65", - "mask-r-to-inherit/70", - "mask-r-to-inherit/75", - "mask-r-to-inherit/80", - "mask-r-to-inherit/85", - "mask-r-to-inherit/90", - "mask-r-to-inherit/95", - "mask-r-to-inherit/100", - "mask-r-to-inherit/background", - "mask-r-to-transparent", - "mask-r-to-transparent/0", - "mask-r-to-transparent/5", - "mask-r-to-transparent/10", - "mask-r-to-transparent/15", - "mask-r-to-transparent/20", - "mask-r-to-transparent/25", - "mask-r-to-transparent/30", - "mask-r-to-transparent/35", - "mask-r-to-transparent/40", - "mask-r-to-transparent/45", - "mask-r-to-transparent/50", - "mask-r-to-transparent/55", - "mask-r-to-transparent/60", - "mask-r-to-transparent/65", - "mask-r-to-transparent/70", - "mask-r-to-transparent/75", - "mask-r-to-transparent/80", - "mask-r-to-transparent/85", - "mask-r-to-transparent/90", - "mask-r-to-transparent/95", - "mask-r-to-transparent/100", - "mask-r-to-transparent/background", - "mask-radial-at-bottom", - "mask-radial-at-bottom-left", - "mask-radial-at-bottom-right", - "mask-radial-at-center", - "mask-radial-at-left", - "mask-radial-at-right", - "mask-radial-at-top", - "mask-radial-at-top-left", - "mask-radial-at-top-right", - "mask-radial-closest-corner", - "mask-radial-closest-side", - "mask-radial-farthest-corner", - "mask-radial-farthest-side", - "mask-radial-from-0", - "mask-radial-from-0%", - "mask-radial-from-0.5", - "mask-radial-from-1", - "mask-radial-from-1.5", - "mask-radial-from-2", - "mask-radial-from-2.5", - "mask-radial-from-3", - "mask-radial-from-3.5", - "mask-radial-from-4", - "mask-radial-from-5", - "mask-radial-from-5%", - "mask-radial-from-6", - "mask-radial-from-7", - "mask-radial-from-8", - "mask-radial-from-9", - "mask-radial-from-10", - "mask-radial-from-10%", - "mask-radial-from-11", - "mask-radial-from-12", - "mask-radial-from-14", - "mask-radial-from-15%", - "mask-radial-from-16", - "mask-radial-from-20", - "mask-radial-from-20%", - "mask-radial-from-24", - "mask-radial-from-25%", - "mask-radial-from-28", - "mask-radial-from-30%", - "mask-radial-from-32", - "mask-radial-from-35%", - "mask-radial-from-36", - "mask-radial-from-40", - "mask-radial-from-40%", - "mask-radial-from-44", - "mask-radial-from-45%", - "mask-radial-from-48", - "mask-radial-from-50%", - "mask-radial-from-52", - "mask-radial-from-55%", - "mask-radial-from-56", - "mask-radial-from-60", - "mask-radial-from-60%", - "mask-radial-from-64", - "mask-radial-from-65%", - "mask-radial-from-70%", - "mask-radial-from-72", - "mask-radial-from-75%", - "mask-radial-from-80", - "mask-radial-from-80%", - "mask-radial-from-85%", - "mask-radial-from-90%", - "mask-radial-from-95%", - "mask-radial-from-96", - "mask-radial-from-100%", - "mask-radial-from-current", - "mask-radial-from-current/0", - "mask-radial-from-current/5", - "mask-radial-from-current/10", - "mask-radial-from-current/15", - "mask-radial-from-current/20", - "mask-radial-from-current/25", - "mask-radial-from-current/30", - "mask-radial-from-current/35", - "mask-radial-from-current/40", - "mask-radial-from-current/45", - "mask-radial-from-current/50", - "mask-radial-from-current/55", - "mask-radial-from-current/60", - "mask-radial-from-current/65", - "mask-radial-from-current/70", - "mask-radial-from-current/75", - "mask-radial-from-current/80", - "mask-radial-from-current/85", - "mask-radial-from-current/90", - "mask-radial-from-current/95", - "mask-radial-from-current/100", - "mask-radial-from-current/background", - "mask-radial-from-inherit", - "mask-radial-from-inherit/0", - "mask-radial-from-inherit/5", - "mask-radial-from-inherit/10", - "mask-radial-from-inherit/15", - "mask-radial-from-inherit/20", - "mask-radial-from-inherit/25", - "mask-radial-from-inherit/30", - "mask-radial-from-inherit/35", - "mask-radial-from-inherit/40", - "mask-radial-from-inherit/45", - "mask-radial-from-inherit/50", - "mask-radial-from-inherit/55", - "mask-radial-from-inherit/60", - "mask-radial-from-inherit/65", - "mask-radial-from-inherit/70", - "mask-radial-from-inherit/75", - "mask-radial-from-inherit/80", - "mask-radial-from-inherit/85", - "mask-radial-from-inherit/90", - "mask-radial-from-inherit/95", - "mask-radial-from-inherit/100", - "mask-radial-from-inherit/background", - "mask-radial-from-transparent", - "mask-radial-from-transparent/0", - "mask-radial-from-transparent/5", - "mask-radial-from-transparent/10", - "mask-radial-from-transparent/15", - "mask-radial-from-transparent/20", - "mask-radial-from-transparent/25", - "mask-radial-from-transparent/30", - "mask-radial-from-transparent/35", - "mask-radial-from-transparent/40", - "mask-radial-from-transparent/45", - "mask-radial-from-transparent/50", - "mask-radial-from-transparent/55", - "mask-radial-from-transparent/60", - "mask-radial-from-transparent/65", - "mask-radial-from-transparent/70", - "mask-radial-from-transparent/75", - "mask-radial-from-transparent/80", - "mask-radial-from-transparent/85", - "mask-radial-from-transparent/90", - "mask-radial-from-transparent/95", - "mask-radial-from-transparent/100", - "mask-radial-from-transparent/background", - "mask-radial-to-0", - "mask-radial-to-0%", - "mask-radial-to-0.5", - "mask-radial-to-1", - "mask-radial-to-1.5", - "mask-radial-to-2", - "mask-radial-to-2.5", - "mask-radial-to-3", - "mask-radial-to-3.5", - "mask-radial-to-4", - "mask-radial-to-5", - "mask-radial-to-5%", - "mask-radial-to-6", - "mask-radial-to-7", - "mask-radial-to-8", - "mask-radial-to-9", - "mask-radial-to-10", - "mask-radial-to-10%", - "mask-radial-to-11", - "mask-radial-to-12", - "mask-radial-to-14", - "mask-radial-to-15%", - "mask-radial-to-16", - "mask-radial-to-20", - "mask-radial-to-20%", - "mask-radial-to-24", - "mask-radial-to-25%", - "mask-radial-to-28", - "mask-radial-to-30%", - "mask-radial-to-32", - "mask-radial-to-35%", - "mask-radial-to-36", - "mask-radial-to-40", - "mask-radial-to-40%", - "mask-radial-to-44", - "mask-radial-to-45%", - "mask-radial-to-48", - "mask-radial-to-50%", - "mask-radial-to-52", - "mask-radial-to-55%", - "mask-radial-to-56", - "mask-radial-to-60", - "mask-radial-to-60%", - "mask-radial-to-64", - "mask-radial-to-65%", - "mask-radial-to-70%", - "mask-radial-to-72", - "mask-radial-to-75%", - "mask-radial-to-80", - "mask-radial-to-80%", - "mask-radial-to-85%", - "mask-radial-to-90%", - "mask-radial-to-95%", - "mask-radial-to-96", - "mask-radial-to-100%", - "mask-radial-to-current", - "mask-radial-to-current/0", - "mask-radial-to-current/5", - "mask-radial-to-current/10", - "mask-radial-to-current/15", - "mask-radial-to-current/20", - "mask-radial-to-current/25", - "mask-radial-to-current/30", - "mask-radial-to-current/35", - "mask-radial-to-current/40", - "mask-radial-to-current/45", - "mask-radial-to-current/50", - "mask-radial-to-current/55", - "mask-radial-to-current/60", - "mask-radial-to-current/65", - "mask-radial-to-current/70", - "mask-radial-to-current/75", - "mask-radial-to-current/80", - "mask-radial-to-current/85", - "mask-radial-to-current/90", - "mask-radial-to-current/95", - "mask-radial-to-current/100", - "mask-radial-to-current/background", - "mask-radial-to-inherit", - "mask-radial-to-inherit/0", - "mask-radial-to-inherit/5", - "mask-radial-to-inherit/10", - "mask-radial-to-inherit/15", - "mask-radial-to-inherit/20", - "mask-radial-to-inherit/25", - "mask-radial-to-inherit/30", - "mask-radial-to-inherit/35", - "mask-radial-to-inherit/40", - "mask-radial-to-inherit/45", - "mask-radial-to-inherit/50", - "mask-radial-to-inherit/55", - "mask-radial-to-inherit/60", - "mask-radial-to-inherit/65", - "mask-radial-to-inherit/70", - "mask-radial-to-inherit/75", - "mask-radial-to-inherit/80", - "mask-radial-to-inherit/85", - "mask-radial-to-inherit/90", - "mask-radial-to-inherit/95", - "mask-radial-to-inherit/100", - "mask-radial-to-inherit/background", - "mask-radial-to-transparent", - "mask-radial-to-transparent/0", - "mask-radial-to-transparent/5", - "mask-radial-to-transparent/10", - "mask-radial-to-transparent/15", - "mask-radial-to-transparent/20", - "mask-radial-to-transparent/25", - "mask-radial-to-transparent/30", - "mask-radial-to-transparent/35", - "mask-radial-to-transparent/40", - "mask-radial-to-transparent/45", - "mask-radial-to-transparent/50", - "mask-radial-to-transparent/55", - "mask-radial-to-transparent/60", - "mask-radial-to-transparent/65", - "mask-radial-to-transparent/70", - "mask-radial-to-transparent/75", - "mask-radial-to-transparent/80", - "mask-radial-to-transparent/85", - "mask-radial-to-transparent/90", - "mask-radial-to-transparent/95", - "mask-radial-to-transparent/100", - "mask-radial-to-transparent/background", - "mask-repeat", - "mask-repeat-round", - "mask-repeat-space", - "mask-repeat-x", - "mask-repeat-y", - "mask-right", - "mask-subtract", - "mask-t-from-0", - "mask-t-from-0%", - "mask-t-from-0.5", - "mask-t-from-1", - "mask-t-from-1.5", - "mask-t-from-2", - "mask-t-from-2.5", - "mask-t-from-3", - "mask-t-from-3.5", - "mask-t-from-4", - "mask-t-from-5", - "mask-t-from-5%", - "mask-t-from-6", - "mask-t-from-7", - "mask-t-from-8", - "mask-t-from-9", - "mask-t-from-10", - "mask-t-from-10%", - "mask-t-from-11", - "mask-t-from-12", - "mask-t-from-14", - "mask-t-from-15%", - "mask-t-from-16", - "mask-t-from-20", - "mask-t-from-20%", - "mask-t-from-24", - "mask-t-from-25%", - "mask-t-from-28", - "mask-t-from-30%", - "mask-t-from-32", - "mask-t-from-35%", - "mask-t-from-36", - "mask-t-from-40", - "mask-t-from-40%", - "mask-t-from-44", - "mask-t-from-45%", - "mask-t-from-48", - "mask-t-from-50%", - "mask-t-from-52", - "mask-t-from-55%", - "mask-t-from-56", - "mask-t-from-60", - "mask-t-from-60%", - "mask-t-from-64", - "mask-t-from-65%", - "mask-t-from-70%", - "mask-t-from-72", - "mask-t-from-75%", - "mask-t-from-80", - "mask-t-from-80%", - "mask-t-from-85%", - "mask-t-from-90%", - "mask-t-from-95%", - "mask-t-from-96", - "mask-t-from-100%", - "mask-t-from-current", - "mask-t-from-current/0", - "mask-t-from-current/5", - "mask-t-from-current/10", - "mask-t-from-current/15", - "mask-t-from-current/20", - "mask-t-from-current/25", - "mask-t-from-current/30", - "mask-t-from-current/35", - "mask-t-from-current/40", - "mask-t-from-current/45", - "mask-t-from-current/50", - "mask-t-from-current/55", - "mask-t-from-current/60", - "mask-t-from-current/65", - "mask-t-from-current/70", - "mask-t-from-current/75", - "mask-t-from-current/80", - "mask-t-from-current/85", - "mask-t-from-current/90", - "mask-t-from-current/95", - "mask-t-from-current/100", - "mask-t-from-current/background", - "mask-t-from-inherit", - "mask-t-from-inherit/0", - "mask-t-from-inherit/5", - "mask-t-from-inherit/10", - "mask-t-from-inherit/15", - "mask-t-from-inherit/20", - "mask-t-from-inherit/25", - "mask-t-from-inherit/30", - "mask-t-from-inherit/35", - "mask-t-from-inherit/40", - "mask-t-from-inherit/45", - "mask-t-from-inherit/50", - "mask-t-from-inherit/55", - "mask-t-from-inherit/60", - "mask-t-from-inherit/65", - "mask-t-from-inherit/70", - "mask-t-from-inherit/75", - "mask-t-from-inherit/80", - "mask-t-from-inherit/85", - "mask-t-from-inherit/90", - "mask-t-from-inherit/95", - "mask-t-from-inherit/100", - "mask-t-from-inherit/background", - "mask-t-from-transparent", - "mask-t-from-transparent/0", - "mask-t-from-transparent/5", - "mask-t-from-transparent/10", - "mask-t-from-transparent/15", - "mask-t-from-transparent/20", - "mask-t-from-transparent/25", - "mask-t-from-transparent/30", - "mask-t-from-transparent/35", - "mask-t-from-transparent/40", - "mask-t-from-transparent/45", - "mask-t-from-transparent/50", - "mask-t-from-transparent/55", - "mask-t-from-transparent/60", - "mask-t-from-transparent/65", - "mask-t-from-transparent/70", - "mask-t-from-transparent/75", - "mask-t-from-transparent/80", - "mask-t-from-transparent/85", - "mask-t-from-transparent/90", - "mask-t-from-transparent/95", - "mask-t-from-transparent/100", - "mask-t-from-transparent/background", - "mask-t-to-0", - "mask-t-to-0%", - "mask-t-to-0.5", - "mask-t-to-1", - "mask-t-to-1.5", - "mask-t-to-2", - "mask-t-to-2.5", - "mask-t-to-3", - "mask-t-to-3.5", - "mask-t-to-4", - "mask-t-to-5", - "mask-t-to-5%", - "mask-t-to-6", - "mask-t-to-7", - "mask-t-to-8", - "mask-t-to-9", - "mask-t-to-10", - "mask-t-to-10%", - "mask-t-to-11", - "mask-t-to-12", - "mask-t-to-14", - "mask-t-to-15%", - "mask-t-to-16", - "mask-t-to-20", - "mask-t-to-20%", - "mask-t-to-24", - "mask-t-to-25%", - "mask-t-to-28", - "mask-t-to-30%", - "mask-t-to-32", - "mask-t-to-35%", - "mask-t-to-36", - "mask-t-to-40", - "mask-t-to-40%", - "mask-t-to-44", - "mask-t-to-45%", - "mask-t-to-48", - "mask-t-to-50%", - "mask-t-to-52", - "mask-t-to-55%", - "mask-t-to-56", - "mask-t-to-60", - "mask-t-to-60%", - "mask-t-to-64", - "mask-t-to-65%", - "mask-t-to-70%", - "mask-t-to-72", - "mask-t-to-75%", - "mask-t-to-80", - "mask-t-to-80%", - "mask-t-to-85%", - "mask-t-to-90%", - "mask-t-to-95%", - "mask-t-to-96", - "mask-t-to-100%", - "mask-t-to-current", - "mask-t-to-current/0", - "mask-t-to-current/5", - "mask-t-to-current/10", - "mask-t-to-current/15", - "mask-t-to-current/20", - "mask-t-to-current/25", - "mask-t-to-current/30", - "mask-t-to-current/35", - "mask-t-to-current/40", - "mask-t-to-current/45", - "mask-t-to-current/50", - "mask-t-to-current/55", - "mask-t-to-current/60", - "mask-t-to-current/65", - "mask-t-to-current/70", - "mask-t-to-current/75", - "mask-t-to-current/80", - "mask-t-to-current/85", - "mask-t-to-current/90", - "mask-t-to-current/95", - "mask-t-to-current/100", - "mask-t-to-current/background", - "mask-t-to-inherit", - "mask-t-to-inherit/0", - "mask-t-to-inherit/5", - "mask-t-to-inherit/10", - "mask-t-to-inherit/15", - "mask-t-to-inherit/20", - "mask-t-to-inherit/25", - "mask-t-to-inherit/30", - "mask-t-to-inherit/35", - "mask-t-to-inherit/40", - "mask-t-to-inherit/45", - "mask-t-to-inherit/50", - "mask-t-to-inherit/55", - "mask-t-to-inherit/60", - "mask-t-to-inherit/65", - "mask-t-to-inherit/70", - "mask-t-to-inherit/75", - "mask-t-to-inherit/80", - "mask-t-to-inherit/85", - "mask-t-to-inherit/90", - "mask-t-to-inherit/95", - "mask-t-to-inherit/100", - "mask-t-to-inherit/background", - "mask-t-to-transparent", - "mask-t-to-transparent/0", - "mask-t-to-transparent/5", - "mask-t-to-transparent/10", - "mask-t-to-transparent/15", - "mask-t-to-transparent/20", - "mask-t-to-transparent/25", - "mask-t-to-transparent/30", - "mask-t-to-transparent/35", - "mask-t-to-transparent/40", - "mask-t-to-transparent/45", - "mask-t-to-transparent/50", - "mask-t-to-transparent/55", - "mask-t-to-transparent/60", - "mask-t-to-transparent/65", - "mask-t-to-transparent/70", - "mask-t-to-transparent/75", - "mask-t-to-transparent/80", - "mask-t-to-transparent/85", - "mask-t-to-transparent/90", - "mask-t-to-transparent/95", - "mask-t-to-transparent/100", - "mask-t-to-transparent/background", - "mask-top", - "mask-top-left", - "mask-top-right", - "mask-type-alpha", - "mask-type-luminance", - "mask-x-from-0", - "mask-x-from-0%", - "mask-x-from-0.5", - "mask-x-from-1", - "mask-x-from-1.5", - "mask-x-from-2", - "mask-x-from-2.5", - "mask-x-from-3", - "mask-x-from-3.5", - "mask-x-from-4", - "mask-x-from-5", - "mask-x-from-5%", - "mask-x-from-6", - "mask-x-from-7", - "mask-x-from-8", - "mask-x-from-9", - "mask-x-from-10", - "mask-x-from-10%", - "mask-x-from-11", - "mask-x-from-12", - "mask-x-from-14", - "mask-x-from-15%", - "mask-x-from-16", - "mask-x-from-20", - "mask-x-from-20%", - "mask-x-from-24", - "mask-x-from-25%", - "mask-x-from-28", - "mask-x-from-30%", - "mask-x-from-32", - "mask-x-from-35%", - "mask-x-from-36", - "mask-x-from-40", - "mask-x-from-40%", - "mask-x-from-44", - "mask-x-from-45%", - "mask-x-from-48", - "mask-x-from-50%", - "mask-x-from-52", - "mask-x-from-55%", - "mask-x-from-56", - "mask-x-from-60", - "mask-x-from-60%", - "mask-x-from-64", - "mask-x-from-65%", - "mask-x-from-70%", - "mask-x-from-72", - "mask-x-from-75%", - "mask-x-from-80", - "mask-x-from-80%", - "mask-x-from-85%", - "mask-x-from-90%", - "mask-x-from-95%", - "mask-x-from-96", - "mask-x-from-100%", - "mask-x-from-current", - "mask-x-from-current/0", - "mask-x-from-current/5", - "mask-x-from-current/10", - "mask-x-from-current/15", - "mask-x-from-current/20", - "mask-x-from-current/25", - "mask-x-from-current/30", - "mask-x-from-current/35", - "mask-x-from-current/40", - "mask-x-from-current/45", - "mask-x-from-current/50", - "mask-x-from-current/55", - "mask-x-from-current/60", - "mask-x-from-current/65", - "mask-x-from-current/70", - "mask-x-from-current/75", - "mask-x-from-current/80", - "mask-x-from-current/85", - "mask-x-from-current/90", - "mask-x-from-current/95", - "mask-x-from-current/100", - "mask-x-from-current/background", - "mask-x-from-inherit", - "mask-x-from-inherit/0", - "mask-x-from-inherit/5", - "mask-x-from-inherit/10", - "mask-x-from-inherit/15", - "mask-x-from-inherit/20", - "mask-x-from-inherit/25", - "mask-x-from-inherit/30", - "mask-x-from-inherit/35", - "mask-x-from-inherit/40", - "mask-x-from-inherit/45", - "mask-x-from-inherit/50", - "mask-x-from-inherit/55", - "mask-x-from-inherit/60", - "mask-x-from-inherit/65", - "mask-x-from-inherit/70", - "mask-x-from-inherit/75", - "mask-x-from-inherit/80", - "mask-x-from-inherit/85", - "mask-x-from-inherit/90", - "mask-x-from-inherit/95", - "mask-x-from-inherit/100", - "mask-x-from-inherit/background", - "mask-x-from-transparent", - "mask-x-from-transparent/0", - "mask-x-from-transparent/5", - "mask-x-from-transparent/10", - "mask-x-from-transparent/15", - "mask-x-from-transparent/20", - "mask-x-from-transparent/25", - "mask-x-from-transparent/30", - "mask-x-from-transparent/35", - "mask-x-from-transparent/40", - "mask-x-from-transparent/45", - "mask-x-from-transparent/50", - "mask-x-from-transparent/55", - "mask-x-from-transparent/60", - "mask-x-from-transparent/65", - "mask-x-from-transparent/70", - "mask-x-from-transparent/75", - "mask-x-from-transparent/80", - "mask-x-from-transparent/85", - "mask-x-from-transparent/90", - "mask-x-from-transparent/95", - "mask-x-from-transparent/100", - "mask-x-from-transparent/background", - "mask-x-to-0", - "mask-x-to-0%", - "mask-x-to-0.5", - "mask-x-to-1", - "mask-x-to-1.5", - "mask-x-to-2", - "mask-x-to-2.5", - "mask-x-to-3", - "mask-x-to-3.5", - "mask-x-to-4", - "mask-x-to-5", - "mask-x-to-5%", - "mask-x-to-6", - "mask-x-to-7", - "mask-x-to-8", - "mask-x-to-9", - "mask-x-to-10", - "mask-x-to-10%", - "mask-x-to-11", - "mask-x-to-12", - "mask-x-to-14", - "mask-x-to-15%", - "mask-x-to-16", - "mask-x-to-20", - "mask-x-to-20%", - "mask-x-to-24", - "mask-x-to-25%", - "mask-x-to-28", - "mask-x-to-30%", - "mask-x-to-32", - "mask-x-to-35%", - "mask-x-to-36", - "mask-x-to-40", - "mask-x-to-40%", - "mask-x-to-44", - "mask-x-to-45%", - "mask-x-to-48", - "mask-x-to-50%", - "mask-x-to-52", - "mask-x-to-55%", - "mask-x-to-56", - "mask-x-to-60", - "mask-x-to-60%", - "mask-x-to-64", - "mask-x-to-65%", - "mask-x-to-70%", - "mask-x-to-72", - "mask-x-to-75%", - "mask-x-to-80", - "mask-x-to-80%", - "mask-x-to-85%", - "mask-x-to-90%", - "mask-x-to-95%", - "mask-x-to-96", - "mask-x-to-100%", - "mask-x-to-current", - "mask-x-to-current/0", - "mask-x-to-current/5", - "mask-x-to-current/10", - "mask-x-to-current/15", - "mask-x-to-current/20", - "mask-x-to-current/25", - "mask-x-to-current/30", - "mask-x-to-current/35", - "mask-x-to-current/40", - "mask-x-to-current/45", - "mask-x-to-current/50", - "mask-x-to-current/55", - "mask-x-to-current/60", - "mask-x-to-current/65", - "mask-x-to-current/70", - "mask-x-to-current/75", - "mask-x-to-current/80", - "mask-x-to-current/85", - "mask-x-to-current/90", - "mask-x-to-current/95", - "mask-x-to-current/100", - "mask-x-to-current/background", - "mask-x-to-inherit", - "mask-x-to-inherit/0", - "mask-x-to-inherit/5", - "mask-x-to-inherit/10", - "mask-x-to-inherit/15", - "mask-x-to-inherit/20", - "mask-x-to-inherit/25", - "mask-x-to-inherit/30", - "mask-x-to-inherit/35", - "mask-x-to-inherit/40", - "mask-x-to-inherit/45", - "mask-x-to-inherit/50", - "mask-x-to-inherit/55", - "mask-x-to-inherit/60", - "mask-x-to-inherit/65", - "mask-x-to-inherit/70", - "mask-x-to-inherit/75", - "mask-x-to-inherit/80", - "mask-x-to-inherit/85", - "mask-x-to-inherit/90", - "mask-x-to-inherit/95", - "mask-x-to-inherit/100", - "mask-x-to-inherit/background", - "mask-x-to-transparent", - "mask-x-to-transparent/0", - "mask-x-to-transparent/5", - "mask-x-to-transparent/10", - "mask-x-to-transparent/15", - "mask-x-to-transparent/20", - "mask-x-to-transparent/25", - "mask-x-to-transparent/30", - "mask-x-to-transparent/35", - "mask-x-to-transparent/40", - "mask-x-to-transparent/45", - "mask-x-to-transparent/50", - "mask-x-to-transparent/55", - "mask-x-to-transparent/60", - "mask-x-to-transparent/65", - "mask-x-to-transparent/70", - "mask-x-to-transparent/75", - "mask-x-to-transparent/80", - "mask-x-to-transparent/85", - "mask-x-to-transparent/90", - "mask-x-to-transparent/95", - "mask-x-to-transparent/100", - "mask-x-to-transparent/background", - "mask-y-from-0", - "mask-y-from-0%", - "mask-y-from-0.5", - "mask-y-from-1", - "mask-y-from-1.5", - "mask-y-from-2", - "mask-y-from-2.5", - "mask-y-from-3", - "mask-y-from-3.5", - "mask-y-from-4", - "mask-y-from-5", - "mask-y-from-5%", - "mask-y-from-6", - "mask-y-from-7", - "mask-y-from-8", - "mask-y-from-9", - "mask-y-from-10", - "mask-y-from-10%", - "mask-y-from-11", - "mask-y-from-12", - "mask-y-from-14", - "mask-y-from-15%", - "mask-y-from-16", - "mask-y-from-20", - "mask-y-from-20%", - "mask-y-from-24", - "mask-y-from-25%", - "mask-y-from-28", - "mask-y-from-30%", - "mask-y-from-32", - "mask-y-from-35%", - "mask-y-from-36", - "mask-y-from-40", - "mask-y-from-40%", - "mask-y-from-44", - "mask-y-from-45%", - "mask-y-from-48", - "mask-y-from-50%", - "mask-y-from-52", - "mask-y-from-55%", - "mask-y-from-56", - "mask-y-from-60", - "mask-y-from-60%", - "mask-y-from-64", - "mask-y-from-65%", - "mask-y-from-70%", - "mask-y-from-72", - "mask-y-from-75%", - "mask-y-from-80", - "mask-y-from-80%", - "mask-y-from-85%", - "mask-y-from-90%", - "mask-y-from-95%", - "mask-y-from-96", - "mask-y-from-100%", - "mask-y-from-current", - "mask-y-from-current/0", - "mask-y-from-current/5", - "mask-y-from-current/10", - "mask-y-from-current/15", - "mask-y-from-current/20", - "mask-y-from-current/25", - "mask-y-from-current/30", - "mask-y-from-current/35", - "mask-y-from-current/40", - "mask-y-from-current/45", - "mask-y-from-current/50", - "mask-y-from-current/55", - "mask-y-from-current/60", - "mask-y-from-current/65", - "mask-y-from-current/70", - "mask-y-from-current/75", - "mask-y-from-current/80", - "mask-y-from-current/85", - "mask-y-from-current/90", - "mask-y-from-current/95", - "mask-y-from-current/100", - "mask-y-from-current/background", - "mask-y-from-inherit", - "mask-y-from-inherit/0", - "mask-y-from-inherit/5", - "mask-y-from-inherit/10", - "mask-y-from-inherit/15", - "mask-y-from-inherit/20", - "mask-y-from-inherit/25", - "mask-y-from-inherit/30", - "mask-y-from-inherit/35", - "mask-y-from-inherit/40", - "mask-y-from-inherit/45", - "mask-y-from-inherit/50", - "mask-y-from-inherit/55", - "mask-y-from-inherit/60", - "mask-y-from-inherit/65", - "mask-y-from-inherit/70", - "mask-y-from-inherit/75", - "mask-y-from-inherit/80", - "mask-y-from-inherit/85", - "mask-y-from-inherit/90", - "mask-y-from-inherit/95", - "mask-y-from-inherit/100", - "mask-y-from-inherit/background", - "mask-y-from-transparent", - "mask-y-from-transparent/0", - "mask-y-from-transparent/5", - "mask-y-from-transparent/10", - "mask-y-from-transparent/15", - "mask-y-from-transparent/20", - "mask-y-from-transparent/25", - "mask-y-from-transparent/30", - "mask-y-from-transparent/35", - "mask-y-from-transparent/40", - "mask-y-from-transparent/45", - "mask-y-from-transparent/50", - "mask-y-from-transparent/55", - "mask-y-from-transparent/60", - "mask-y-from-transparent/65", - "mask-y-from-transparent/70", - "mask-y-from-transparent/75", - "mask-y-from-transparent/80", - "mask-y-from-transparent/85", - "mask-y-from-transparent/90", - "mask-y-from-transparent/95", - "mask-y-from-transparent/100", - "mask-y-from-transparent/background", - "mask-y-to-0", - "mask-y-to-0%", - "mask-y-to-0.5", - "mask-y-to-1", - "mask-y-to-1.5", - "mask-y-to-2", - "mask-y-to-2.5", - "mask-y-to-3", - "mask-y-to-3.5", - "mask-y-to-4", - "mask-y-to-5", - "mask-y-to-5%", - "mask-y-to-6", - "mask-y-to-7", - "mask-y-to-8", - "mask-y-to-9", - "mask-y-to-10", - "mask-y-to-10%", - "mask-y-to-11", - "mask-y-to-12", - "mask-y-to-14", - "mask-y-to-15%", - "mask-y-to-16", - "mask-y-to-20", - "mask-y-to-20%", - "mask-y-to-24", - "mask-y-to-25%", - "mask-y-to-28", - "mask-y-to-30%", - "mask-y-to-32", - "mask-y-to-35%", - "mask-y-to-36", - "mask-y-to-40", - "mask-y-to-40%", - "mask-y-to-44", - "mask-y-to-45%", - "mask-y-to-48", - "mask-y-to-50%", - "mask-y-to-52", - "mask-y-to-55%", - "mask-y-to-56", - "mask-y-to-60", - "mask-y-to-60%", - "mask-y-to-64", - "mask-y-to-65%", - "mask-y-to-70%", - "mask-y-to-72", - "mask-y-to-75%", - "mask-y-to-80", - "mask-y-to-80%", - "mask-y-to-85%", - "mask-y-to-90%", - "mask-y-to-95%", - "mask-y-to-96", - "mask-y-to-100%", - "mask-y-to-current", - "mask-y-to-current/0", - "mask-y-to-current/5", - "mask-y-to-current/10", - "mask-y-to-current/15", - "mask-y-to-current/20", - "mask-y-to-current/25", - "mask-y-to-current/30", - "mask-y-to-current/35", - "mask-y-to-current/40", - "mask-y-to-current/45", - "mask-y-to-current/50", - "mask-y-to-current/55", - "mask-y-to-current/60", - "mask-y-to-current/65", - "mask-y-to-current/70", - "mask-y-to-current/75", - "mask-y-to-current/80", - "mask-y-to-current/85", - "mask-y-to-current/90", - "mask-y-to-current/95", - "mask-y-to-current/100", - "mask-y-to-current/background", - "mask-y-to-inherit", - "mask-y-to-inherit/0", - "mask-y-to-inherit/5", - "mask-y-to-inherit/10", - "mask-y-to-inherit/15", - "mask-y-to-inherit/20", - "mask-y-to-inherit/25", - "mask-y-to-inherit/30", - "mask-y-to-inherit/35", - "mask-y-to-inherit/40", - "mask-y-to-inherit/45", - "mask-y-to-inherit/50", - "mask-y-to-inherit/55", - "mask-y-to-inherit/60", - "mask-y-to-inherit/65", - "mask-y-to-inherit/70", - "mask-y-to-inherit/75", - "mask-y-to-inherit/80", - "mask-y-to-inherit/85", - "mask-y-to-inherit/90", - "mask-y-to-inherit/95", - "mask-y-to-inherit/100", - "mask-y-to-inherit/background", - "mask-y-to-transparent", - "mask-y-to-transparent/0", - "mask-y-to-transparent/5", - "mask-y-to-transparent/10", - "mask-y-to-transparent/15", - "mask-y-to-transparent/20", - "mask-y-to-transparent/25", - "mask-y-to-transparent/30", - "mask-y-to-transparent/35", - "mask-y-to-transparent/40", - "mask-y-to-transparent/45", - "mask-y-to-transparent/50", - "mask-y-to-transparent/55", - "mask-y-to-transparent/60", - "mask-y-to-transparent/65", - "mask-y-to-transparent/70", - "mask-y-to-transparent/75", - "mask-y-to-transparent/80", - "mask-y-to-transparent/85", - "mask-y-to-transparent/90", - "mask-y-to-transparent/95", - "mask-y-to-transparent/100", - "mask-y-to-transparent/background", - "max-block-0", - "max-block-0.5", - "max-block-1", - "max-block-1.5", - "max-block-2", - "max-block-2.5", - "max-block-3", - "max-block-3.5", - "max-block-4", - "max-block-5", - "max-block-6", - "max-block-7", - "max-block-8", - "max-block-9", - "max-block-10", - "max-block-11", - "max-block-12", - "max-block-14", - "max-block-16", - "max-block-20", - "max-block-24", - "max-block-28", - "max-block-32", - "max-block-36", - "max-block-40", - "max-block-44", - "max-block-48", - "max-block-52", - "max-block-56", - "max-block-60", - "max-block-64", - "max-block-72", - "max-block-80", - "max-block-96", - "max-block-1/2", - "max-block-1/3", - "max-block-1/4", - "max-block-1/5", - "max-block-1/6", - "max-block-1/12", - "max-block-2/3", - "max-block-2/4", - "max-block-2/5", - "max-block-2/6", - "max-block-2/12", - "max-block-3/4", - "max-block-3/5", - "max-block-3/6", - "max-block-3/12", - "max-block-4/5", - "max-block-4/6", - "max-block-4/12", - "max-block-5/6", - "max-block-5/12", - "max-block-6/12", - "max-block-7/12", - "max-block-8/12", - "max-block-9/12", - "max-block-10/12", - "max-block-11/12", - "max-block-dvh", - "max-block-fit", - "max-block-full", - "max-block-lh", - "max-block-lvh", - "max-block-max", - "max-block-min", - "max-block-none", - "max-block-px", - "max-block-screen", - "max-block-svh", "max-h-0", "max-h-0.5", "max-h-1", @@ -8480,7 +5125,6 @@ exports[`getClassList 1`] = ` "max-h-dvw", "max-h-fit", "max-h-full", - "max-h-lh", "max-h-lvh", "max-h-lvw", "max-h-max", @@ -8490,78 +5134,6 @@ exports[`getClassList 1`] = ` "max-h-screen", "max-h-svh", "max-h-svw", - "max-inline-0", - "max-inline-0.5", - "max-inline-1", - "max-inline-1.5", - "max-inline-2", - "max-inline-2.5", - "max-inline-3", - "max-inline-3.5", - "max-inline-4", - "max-inline-5", - "max-inline-6", - "max-inline-7", - "max-inline-8", - "max-inline-9", - "max-inline-10", - "max-inline-11", - "max-inline-12", - "max-inline-14", - "max-inline-16", - "max-inline-20", - "max-inline-24", - "max-inline-28", - "max-inline-32", - "max-inline-36", - "max-inline-40", - "max-inline-44", - "max-inline-48", - "max-inline-52", - "max-inline-56", - "max-inline-60", - "max-inline-64", - "max-inline-72", - "max-inline-80", - "max-inline-96", - "max-inline-dvw", - "max-inline-fit", - "max-inline-full", - "max-inline-lg", - "max-inline-lvw", - "max-inline-max", - "max-inline-md", - "max-inline-1/2", - "max-inline-1/3", - "max-inline-1/4", - "max-inline-1/5", - "max-inline-1/6", - "max-inline-1/12", - "max-inline-2/3", - "max-inline-2/4", - "max-inline-2/5", - "max-inline-2/6", - "max-inline-2/12", - "max-inline-3/4", - "max-inline-3/5", - "max-inline-3/6", - "max-inline-3/12", - "max-inline-4/5", - "max-inline-4/6", - "max-inline-4/12", - "max-inline-5/6", - "max-inline-5/12", - "max-inline-6/12", - "max-inline-7/12", - "max-inline-8/12", - "max-inline-9/12", - "max-inline-10/12", - "max-inline-11/12", - "max-inline-min", - "max-inline-none", - "max-inline-px", - "max-inline-screen", - "max-inline-svw", "max-w-0", "max-w-0.5", "max-w-1", @@ -8634,6 +5206,7 @@ exports[`getClassList 1`] = ` "max-w-min", "max-w-none", "max-w-px", + "max-w-screen", "max-w-svh", "max-w-svw", "mb-0", @@ -8672,78 +5245,6 @@ exports[`getClassList 1`] = ` "mb-96", "mb-auto", "mb-px", - "mbe-0", - "mbe-0.5", - "mbe-1", - "mbe-1.5", - "mbe-2", - "mbe-2.5", - "mbe-3", - "mbe-3.5", - "mbe-4", - "mbe-5", - "mbe-6", - "mbe-7", - "mbe-8", - "mbe-9", - "mbe-10", - "mbe-11", - "mbe-12", - "mbe-14", - "mbe-16", - "mbe-20", - "mbe-24", - "mbe-28", - "mbe-32", - "mbe-36", - "mbe-40", - "mbe-44", - "mbe-48", - "mbe-52", - "mbe-56", - "mbe-60", - "mbe-64", - "mbe-72", - "mbe-80", - "mbe-96", - "mbe-auto", - "mbe-px", - "mbs-0", - "mbs-0.5", - "mbs-1", - "mbs-1.5", - "mbs-2", - "mbs-2.5", - "mbs-3", - "mbs-3.5", - "mbs-4", - "mbs-5", - "mbs-6", - "mbs-7", - "mbs-8", - "mbs-9", - "mbs-10", - "mbs-11", - "mbs-12", - "mbs-14", - "mbs-16", - "mbs-20", - "mbs-24", - "mbs-28", - "mbs-32", - "mbs-36", - "mbs-40", - "mbs-44", - "mbs-48", - "mbs-52", - "mbs-56", - "mbs-60", - "mbs-64", - "mbs-72", - "mbs-80", - "mbs-96", - "mbs-auto", - "mbs-px", "me-0", "me-0.5", "me-1", @@ -8780,77 +5281,6 @@ exports[`getClassList 1`] = ` "me-96", "me-auto", "me-px", - "min-block-0", - "min-block-0.5", - "min-block-1", - "min-block-1.5", - "min-block-2", - "min-block-2.5", - "min-block-3", - "min-block-3.5", - "min-block-4", - "min-block-5", - "min-block-6", - "min-block-7", - "min-block-8", - "min-block-9", - "min-block-10", - "min-block-11", - "min-block-12", - "min-block-14", - "min-block-16", - "min-block-20", - "min-block-24", - "min-block-28", - "min-block-32", - "min-block-36", - "min-block-40", - "min-block-44", - "min-block-48", - "min-block-52", - "min-block-56", - "min-block-60", - "min-block-64", - "min-block-72", - "min-block-80", - "min-block-96", - "min-block-1/2", - "min-block-1/3", - "min-block-1/4", - "min-block-1/5", - "min-block-1/6", - "min-block-1/12", - "min-block-2/3", - "min-block-2/4", - "min-block-2/5", - "min-block-2/6", - "min-block-2/12", - "min-block-3/4", - "min-block-3/5", - "min-block-3/6", - "min-block-3/12", - "min-block-4/5", - "min-block-4/6", - "min-block-4/12", - "min-block-5/6", - "min-block-5/12", - "min-block-6/12", - "min-block-7/12", - "min-block-8/12", - "min-block-9/12", - "min-block-10/12", - "min-block-11/12", - "min-block-auto", - "min-block-dvh", - "min-block-fit", - "min-block-full", - "min-block-lh", - "min-block-lvh", - "min-block-max", - "min-block-min", - "min-block-px", - "min-block-screen", - "min-block-svh", "min-h-0", "min-h-0.5", "min-h-1", @@ -8916,7 +5346,6 @@ exports[`getClassList 1`] = ` "min-h-dvw", "min-h-fit", "min-h-full", - "min-h-lh", "min-h-lvh", "min-h-lvw", "min-h-max", @@ -8925,78 +5354,6 @@ exports[`getClassList 1`] = ` "min-h-screen", "min-h-svh", "min-h-svw", - "min-inline-0", - "min-inline-0.5", - "min-inline-1", - "min-inline-1.5", - "min-inline-2", - "min-inline-2.5", - "min-inline-3", - "min-inline-3.5", - "min-inline-4", - "min-inline-5", - "min-inline-6", - "min-inline-7", - "min-inline-8", - "min-inline-9", - "min-inline-10", - "min-inline-11", - "min-inline-12", - "min-inline-14", - "min-inline-16", - "min-inline-20", - "min-inline-24", - "min-inline-28", - "min-inline-32", - "min-inline-36", - "min-inline-40", - "min-inline-44", - "min-inline-48", - "min-inline-52", - "min-inline-56", - "min-inline-60", - "min-inline-64", - "min-inline-72", - "min-inline-80", - "min-inline-96", - "min-inline-auto", - "min-inline-dvw", - "min-inline-fit", - "min-inline-full", - "min-inline-lg", - "min-inline-lvw", - "min-inline-max", - "min-inline-md", - "min-inline-1/2", - "min-inline-1/3", - "min-inline-1/4", - "min-inline-1/5", - "min-inline-1/6", - "min-inline-1/12", - "min-inline-2/3", - "min-inline-2/4", - "min-inline-2/5", - "min-inline-2/6", - "min-inline-2/12", - "min-inline-3/4", - "min-inline-3/5", - "min-inline-3/6", - "min-inline-3/12", - "min-inline-4/5", - "min-inline-4/6", - "min-inline-4/12", - "min-inline-5/6", - "min-inline-5/12", - "min-inline-6/12", - "min-inline-7/12", - "min-inline-8/12", - "min-inline-9/12", - "min-inline-10/12", - "min-inline-11/12", - "min-inline-min", - "min-inline-px", - "min-inline-screen", - "min-inline-svw", "min-w-0", "min-w-0.5", "min-w-1", @@ -9312,19 +5669,19 @@ exports[`getClassList 1`] = ` "not-italic", "not-sr-only", "object-bottom", - "object-bottom-left", - "object-bottom-right", "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", - "object-top-left", - "object-top-right", "oldstyle-nums", "opacity-0", "opacity-5", @@ -9362,6 +5719,7 @@ exports[`getClassList 1`] = ` "order-12", "order-first", "order-last", + "order-none", "ordinal", "origin-bottom", "origin-bottom-left", @@ -9394,7 +5752,6 @@ exports[`getClassList 1`] = ` "outline/90", "outline/95", "outline/100", - "outline/background", "outline-0", "outline-1", "outline-2", @@ -9422,7 +5779,6 @@ exports[`getClassList 1`] = ` "outline-current/90", "outline-current/95", "outline-current/100", - "outline-current/background", "outline-dashed", "outline-dotted", "outline-double", @@ -9449,7 +5805,6 @@ exports[`getClassList 1`] = ` "outline-inherit/90", "outline-inherit/95", "outline-inherit/100", - "outline-inherit/background", "outline-none", "outline-offset-0", "outline-offset-1", @@ -9479,7 +5834,6 @@ exports[`getClassList 1`] = ` "outline-transparent/90", "outline-transparent/95", "outline-transparent/100", - "outline-transparent/background", "overflow-auto", "overflow-clip", "overflow-hidden", @@ -9575,76 +5929,6 @@ exports[`getClassList 1`] = ` "pb-80", "pb-96", "pb-px", - "pbe-0", - "pbe-0.5", - "pbe-1", - "pbe-1.5", - "pbe-2", - "pbe-2.5", - "pbe-3", - "pbe-3.5", - "pbe-4", - "pbe-5", - "pbe-6", - "pbe-7", - "pbe-8", - "pbe-9", - "pbe-10", - "pbe-11", - "pbe-12", - "pbe-14", - "pbe-16", - "pbe-20", - "pbe-24", - "pbe-28", - "pbe-32", - "pbe-36", - "pbe-40", - "pbe-44", - "pbe-48", - "pbe-52", - "pbe-56", - "pbe-60", - "pbe-64", - "pbe-72", - "pbe-80", - "pbe-96", - "pbe-px", - "pbs-0", - "pbs-0.5", - "pbs-1", - "pbs-1.5", - "pbs-2", - "pbs-2.5", - "pbs-3", - "pbs-3.5", - "pbs-4", - "pbs-5", - "pbs-6", - "pbs-7", - "pbs-8", - "pbs-9", - "pbs-10", - "pbs-11", - "pbs-12", - "pbs-14", - "pbs-16", - "pbs-20", - "pbs-24", - "pbs-28", - "pbs-32", - "pbs-36", - "pbs-40", - "pbs-44", - "pbs-48", - "pbs-52", - "pbs-56", - "pbs-60", - "pbs-64", - "pbs-72", - "pbs-80", - "pbs-96", - "pbs-px", "pe-0", "pe-0.5", "pe-1", @@ -9773,7 +6057,6 @@ exports[`getClassList 1`] = ` "placeholder-current/90", "placeholder-current/95", "placeholder-current/100", - "placeholder-current/background", "placeholder-inherit", "placeholder-inherit/0", "placeholder-inherit/5", @@ -9796,7 +6079,6 @@ exports[`getClassList 1`] = ` "placeholder-inherit/90", "placeholder-inherit/95", "placeholder-inherit/100", - "placeholder-inherit/background", "placeholder-transparent", "placeholder-transparent/0", "placeholder-transparent/5", @@ -9819,7 +6101,6 @@ exports[`getClassList 1`] = ` "placeholder-transparent/90", "placeholder-transparent/95", "placeholder-transparent/100", - "placeholder-transparent/background", "pointer-events-auto", "pointer-events-none", "pr-0", @@ -10094,7 +6375,6 @@ exports[`getClassList 1`] = ` "ring-current/90", "ring-current/95", "ring-current/100", - "ring-current/background", "ring-inherit", "ring-inherit/0", "ring-inherit/5", @@ -10117,7 +6397,6 @@ exports[`getClassList 1`] = ` "ring-inherit/90", "ring-inherit/95", "ring-inherit/100", - "ring-inherit/background", "ring-inset", "ring-offset-0", "ring-offset-1", @@ -10146,7 +6425,6 @@ exports[`getClassList 1`] = ` "ring-offset-current/90", "ring-offset-current/95", "ring-offset-current/100", - "ring-offset-current/background", "ring-offset-inherit", "ring-offset-inherit/0", "ring-offset-inherit/5", @@ -10169,7 +6447,6 @@ exports[`getClassList 1`] = ` "ring-offset-inherit/90", "ring-offset-inherit/95", "ring-offset-inherit/100", - "ring-offset-inherit/background", "ring-offset-transparent", "ring-offset-transparent/0", "ring-offset-transparent/5", @@ -10192,7 +6469,6 @@ exports[`getClassList 1`] = ` "ring-offset-transparent/90", "ring-offset-transparent/95", "ring-offset-transparent/100", - "ring-offset-transparent/background", "ring-transparent", "ring-transparent/0", "ring-transparent/5", @@ -10215,7 +6491,6 @@ exports[`getClassList 1`] = ` "ring-transparent/90", "ring-transparent/95", "ring-transparent/100", - "ring-transparent/background", "rotate-0", "rotate-1", "rotate-2", @@ -10453,76 +6728,6 @@ exports[`getClassList 1`] = ` "scroll-mb-80", "scroll-mb-96", "scroll-mb-px", - "scroll-mbe-0", - "scroll-mbe-0.5", - "scroll-mbe-1", - "scroll-mbe-1.5", - "scroll-mbe-2", - "scroll-mbe-2.5", - "scroll-mbe-3", - "scroll-mbe-3.5", - "scroll-mbe-4", - "scroll-mbe-5", - "scroll-mbe-6", - "scroll-mbe-7", - "scroll-mbe-8", - "scroll-mbe-9", - "scroll-mbe-10", - "scroll-mbe-11", - "scroll-mbe-12", - "scroll-mbe-14", - "scroll-mbe-16", - "scroll-mbe-20", - "scroll-mbe-24", - "scroll-mbe-28", - "scroll-mbe-32", - "scroll-mbe-36", - "scroll-mbe-40", - "scroll-mbe-44", - "scroll-mbe-48", - "scroll-mbe-52", - "scroll-mbe-56", - "scroll-mbe-60", - "scroll-mbe-64", - "scroll-mbe-72", - "scroll-mbe-80", - "scroll-mbe-96", - "scroll-mbe-px", - "scroll-mbs-0", - "scroll-mbs-0.5", - "scroll-mbs-1", - "scroll-mbs-1.5", - "scroll-mbs-2", - "scroll-mbs-2.5", - "scroll-mbs-3", - "scroll-mbs-3.5", - "scroll-mbs-4", - "scroll-mbs-5", - "scroll-mbs-6", - "scroll-mbs-7", - "scroll-mbs-8", - "scroll-mbs-9", - "scroll-mbs-10", - "scroll-mbs-11", - "scroll-mbs-12", - "scroll-mbs-14", - "scroll-mbs-16", - "scroll-mbs-20", - "scroll-mbs-24", - "scroll-mbs-28", - "scroll-mbs-32", - "scroll-mbs-36", - "scroll-mbs-40", - "scroll-mbs-44", - "scroll-mbs-48", - "scroll-mbs-52", - "scroll-mbs-56", - "scroll-mbs-60", - "scroll-mbs-64", - "scroll-mbs-72", - "scroll-mbs-80", - "scroll-mbs-96", - "scroll-mbs-px", "scroll-me-0", "scroll-me-0.5", "scroll-me-1", @@ -10838,76 +7043,6 @@ exports[`getClassList 1`] = ` "scroll-pb-80", "scroll-pb-96", "scroll-pb-px", - "scroll-pbe-0", - "scroll-pbe-0.5", - "scroll-pbe-1", - "scroll-pbe-1.5", - "scroll-pbe-2", - "scroll-pbe-2.5", - "scroll-pbe-3", - "scroll-pbe-3.5", - "scroll-pbe-4", - "scroll-pbe-5", - "scroll-pbe-6", - "scroll-pbe-7", - "scroll-pbe-8", - "scroll-pbe-9", - "scroll-pbe-10", - "scroll-pbe-11", - "scroll-pbe-12", - "scroll-pbe-14", - "scroll-pbe-16", - "scroll-pbe-20", - "scroll-pbe-24", - "scroll-pbe-28", - "scroll-pbe-32", - "scroll-pbe-36", - "scroll-pbe-40", - "scroll-pbe-44", - "scroll-pbe-48", - "scroll-pbe-52", - "scroll-pbe-56", - "scroll-pbe-60", - "scroll-pbe-64", - "scroll-pbe-72", - "scroll-pbe-80", - "scroll-pbe-96", - "scroll-pbe-px", - "scroll-pbs-0", - "scroll-pbs-0.5", - "scroll-pbs-1", - "scroll-pbs-1.5", - "scroll-pbs-2", - "scroll-pbs-2.5", - "scroll-pbs-3", - "scroll-pbs-3.5", - "scroll-pbs-4", - "scroll-pbs-5", - "scroll-pbs-6", - "scroll-pbs-7", - "scroll-pbs-8", - "scroll-pbs-9", - "scroll-pbs-10", - "scroll-pbs-11", - "scroll-pbs-12", - "scroll-pbs-14", - "scroll-pbs-16", - "scroll-pbs-20", - "scroll-pbs-24", - "scroll-pbs-28", - "scroll-pbs-32", - "scroll-pbs-36", - "scroll-pbs-40", - "scroll-pbs-44", - "scroll-pbs-48", - "scroll-pbs-52", - "scroll-pbs-56", - "scroll-pbs-60", - "scroll-pbs-64", - "scroll-pbs-72", - "scroll-pbs-80", - "scroll-pbs-96", - "scroll-pbs-px", "scroll-pe-0", "scroll-pe-0.5", "scroll-pe-1", @@ -11154,157 +7289,12 @@ exports[`getClassList 1`] = ` "scroll-py-96", "scroll-py-px", "scroll-smooth", - "scrollbar-auto", - "scrollbar-gutter-auto", - "scrollbar-gutter-both", - "scrollbar-gutter-stable", - "scrollbar-none", - "scrollbar-thin", - "scrollbar-thumb-current", - "scrollbar-thumb-current/0", - "scrollbar-thumb-current/5", - "scrollbar-thumb-current/10", - "scrollbar-thumb-current/15", - "scrollbar-thumb-current/20", - "scrollbar-thumb-current/25", - "scrollbar-thumb-current/30", - "scrollbar-thumb-current/35", - "scrollbar-thumb-current/40", - "scrollbar-thumb-current/45", - "scrollbar-thumb-current/50", - "scrollbar-thumb-current/55", - "scrollbar-thumb-current/60", - "scrollbar-thumb-current/65", - "scrollbar-thumb-current/70", - "scrollbar-thumb-current/75", - "scrollbar-thumb-current/80", - "scrollbar-thumb-current/85", - "scrollbar-thumb-current/90", - "scrollbar-thumb-current/95", - "scrollbar-thumb-current/100", - "scrollbar-thumb-current/background", - "scrollbar-thumb-inherit", - "scrollbar-thumb-inherit/0", - "scrollbar-thumb-inherit/5", - "scrollbar-thumb-inherit/10", - "scrollbar-thumb-inherit/15", - "scrollbar-thumb-inherit/20", - "scrollbar-thumb-inherit/25", - "scrollbar-thumb-inherit/30", - "scrollbar-thumb-inherit/35", - "scrollbar-thumb-inherit/40", - "scrollbar-thumb-inherit/45", - "scrollbar-thumb-inherit/50", - "scrollbar-thumb-inherit/55", - "scrollbar-thumb-inherit/60", - "scrollbar-thumb-inherit/65", - "scrollbar-thumb-inherit/70", - "scrollbar-thumb-inherit/75", - "scrollbar-thumb-inherit/80", - "scrollbar-thumb-inherit/85", - "scrollbar-thumb-inherit/90", - "scrollbar-thumb-inherit/95", - "scrollbar-thumb-inherit/100", - "scrollbar-thumb-inherit/background", - "scrollbar-thumb-transparent", - "scrollbar-thumb-transparent/0", - "scrollbar-thumb-transparent/5", - "scrollbar-thumb-transparent/10", - "scrollbar-thumb-transparent/15", - "scrollbar-thumb-transparent/20", - "scrollbar-thumb-transparent/25", - "scrollbar-thumb-transparent/30", - "scrollbar-thumb-transparent/35", - "scrollbar-thumb-transparent/40", - "scrollbar-thumb-transparent/45", - "scrollbar-thumb-transparent/50", - "scrollbar-thumb-transparent/55", - "scrollbar-thumb-transparent/60", - "scrollbar-thumb-transparent/65", - "scrollbar-thumb-transparent/70", - "scrollbar-thumb-transparent/75", - "scrollbar-thumb-transparent/80", - "scrollbar-thumb-transparent/85", - "scrollbar-thumb-transparent/90", - "scrollbar-thumb-transparent/95", - "scrollbar-thumb-transparent/100", - "scrollbar-thumb-transparent/background", - "scrollbar-track-current", - "scrollbar-track-current/0", - "scrollbar-track-current/5", - "scrollbar-track-current/10", - "scrollbar-track-current/15", - "scrollbar-track-current/20", - "scrollbar-track-current/25", - "scrollbar-track-current/30", - "scrollbar-track-current/35", - "scrollbar-track-current/40", - "scrollbar-track-current/45", - "scrollbar-track-current/50", - "scrollbar-track-current/55", - "scrollbar-track-current/60", - "scrollbar-track-current/65", - "scrollbar-track-current/70", - "scrollbar-track-current/75", - "scrollbar-track-current/80", - "scrollbar-track-current/85", - "scrollbar-track-current/90", - "scrollbar-track-current/95", - "scrollbar-track-current/100", - "scrollbar-track-current/background", - "scrollbar-track-inherit", - "scrollbar-track-inherit/0", - "scrollbar-track-inherit/5", - "scrollbar-track-inherit/10", - "scrollbar-track-inherit/15", - "scrollbar-track-inherit/20", - "scrollbar-track-inherit/25", - "scrollbar-track-inherit/30", - "scrollbar-track-inherit/35", - "scrollbar-track-inherit/40", - "scrollbar-track-inherit/45", - "scrollbar-track-inherit/50", - "scrollbar-track-inherit/55", - "scrollbar-track-inherit/60", - "scrollbar-track-inherit/65", - "scrollbar-track-inherit/70", - "scrollbar-track-inherit/75", - "scrollbar-track-inherit/80", - "scrollbar-track-inherit/85", - "scrollbar-track-inherit/90", - "scrollbar-track-inherit/95", - "scrollbar-track-inherit/100", - "scrollbar-track-inherit/background", - "scrollbar-track-transparent", - "scrollbar-track-transparent/0", - "scrollbar-track-transparent/5", - "scrollbar-track-transparent/10", - "scrollbar-track-transparent/15", - "scrollbar-track-transparent/20", - "scrollbar-track-transparent/25", - "scrollbar-track-transparent/30", - "scrollbar-track-transparent/35", - "scrollbar-track-transparent/40", - "scrollbar-track-transparent/45", - "scrollbar-track-transparent/50", - "scrollbar-track-transparent/55", - "scrollbar-track-transparent/60", - "scrollbar-track-transparent/65", - "scrollbar-track-transparent/70", - "scrollbar-track-transparent/75", - "scrollbar-track-transparent/80", - "scrollbar-track-transparent/85", - "scrollbar-track-transparent/90", - "scrollbar-track-transparent/95", - "scrollbar-track-transparent/100", - "scrollbar-track-transparent/background", "select-all", "select-auto", "select-none", "select-text", "self-auto", "self-baseline", - "self-baseline-last", "self-center", "self-center-safe", "self-end", @@ -11315,6 +7305,7 @@ exports[`getClassList 1`] = ` "sepia-0", "sepia-50", "sepia-100", + "shadow", "shadow-current", "shadow-current/0", "shadow-current/5", @@ -11337,7 +7328,6 @@ exports[`getClassList 1`] = ` "shadow-current/90", "shadow-current/95", "shadow-current/100", - "shadow-current/background", "shadow-inherit", "shadow-inherit/0", "shadow-inherit/5", @@ -11360,7 +7350,6 @@ exports[`getClassList 1`] = ` "shadow-inherit/90", "shadow-inherit/95", "shadow-inherit/100", - "shadow-inherit/background", "shadow-initial", "shadow-none", "shadow-transparent", @@ -11385,7 +7374,6 @@ exports[`getClassList 1`] = ` "shadow-transparent/90", "shadow-transparent/95", "shadow-transparent/100", - "shadow-transparent/background", "shrink", "shrink-0", "size-0", @@ -11565,6 +7553,66 @@ exports[`getClassList 1`] = ` "space-y-reverse", "sr-only", "stacked-fractions", + "start-0", + "start-0.5", + "start-1", + "start-1.5", + "start-2", + "start-2.5", + "start-3", + "start-3.5", + "start-4", + "start-5", + "start-6", + "start-7", + "start-8", + "start-9", + "start-10", + "start-11", + "start-12", + "start-14", + "start-16", + "start-20", + "start-24", + "start-28", + "start-32", + "start-36", + "start-40", + "start-44", + "start-48", + "start-52", + "start-56", + "start-60", + "start-64", + "start-72", + "start-80", + "start-96", + "start-1/2", + "start-1/3", + "start-1/4", + "start-1/5", + "start-1/6", + "start-1/12", + "start-2/3", + "start-2/4", + "start-2/5", + "start-2/6", + "start-2/12", + "start-3/4", + "start-3/5", + "start-3/6", + "start-3/12", + "start-4/5", + "start-4/6", + "start-4/12", + "start-5/6", + "start-5/12", + "start-6/12", + "start-7/12", + "start-8/12", + "start-9/12", + "start-10/12", + "start-11/12", "start-auto", "start-full", "start-px", @@ -11596,7 +7644,6 @@ exports[`getClassList 1`] = ` "stroke-current/90", "stroke-current/95", "stroke-current/100", - "stroke-current/background", "stroke-inherit", "stroke-inherit/0", "stroke-inherit/5", @@ -11619,7 +7666,6 @@ exports[`getClassList 1`] = ` "stroke-inherit/90", "stroke-inherit/95", "stroke-inherit/100", - "stroke-inherit/background", "stroke-none", "stroke-transparent", "stroke-transparent/0", @@ -11643,11 +7689,7 @@ exports[`getClassList 1`] = ` "stroke-transparent/90", "stroke-transparent/95", "stroke-transparent/100", - "stroke-transparent/background", "subpixel-antialiased", - "tab-2", - "tab-4", - "tab-8", "table", "table-auto", "table-caption", @@ -11685,7 +7727,6 @@ exports[`getClassList 1`] = ` "text-current/90", "text-current/95", "text-current/100", - "text-current/background", "text-ellipsis", "text-end", "text-inherit", @@ -11710,83 +7751,11 @@ exports[`getClassList 1`] = ` "text-inherit/90", "text-inherit/95", "text-inherit/100", - "text-inherit/background", "text-justify", "text-left", "text-nowrap", "text-pretty", "text-right", - "text-shadow-current", - "text-shadow-current/0", - "text-shadow-current/5", - "text-shadow-current/10", - "text-shadow-current/15", - "text-shadow-current/20", - "text-shadow-current/25", - "text-shadow-current/30", - "text-shadow-current/35", - "text-shadow-current/40", - "text-shadow-current/45", - "text-shadow-current/50", - "text-shadow-current/55", - "text-shadow-current/60", - "text-shadow-current/65", - "text-shadow-current/70", - "text-shadow-current/75", - "text-shadow-current/80", - "text-shadow-current/85", - "text-shadow-current/90", - "text-shadow-current/95", - "text-shadow-current/100", - "text-shadow-current/background", - "text-shadow-inherit", - "text-shadow-inherit/0", - "text-shadow-inherit/5", - "text-shadow-inherit/10", - "text-shadow-inherit/15", - "text-shadow-inherit/20", - "text-shadow-inherit/25", - "text-shadow-inherit/30", - "text-shadow-inherit/35", - "text-shadow-inherit/40", - "text-shadow-inherit/45", - "text-shadow-inherit/50", - "text-shadow-inherit/55", - "text-shadow-inherit/60", - "text-shadow-inherit/65", - "text-shadow-inherit/70", - "text-shadow-inherit/75", - "text-shadow-inherit/80", - "text-shadow-inherit/85", - "text-shadow-inherit/90", - "text-shadow-inherit/95", - "text-shadow-inherit/100", - "text-shadow-inherit/background", - "text-shadow-initial", - "text-shadow-none", - "text-shadow-transparent", - "text-shadow-transparent/0", - "text-shadow-transparent/5", - "text-shadow-transparent/10", - "text-shadow-transparent/15", - "text-shadow-transparent/20", - "text-shadow-transparent/25", - "text-shadow-transparent/30", - "text-shadow-transparent/35", - "text-shadow-transparent/40", - "text-shadow-transparent/45", - "text-shadow-transparent/50", - "text-shadow-transparent/55", - "text-shadow-transparent/60", - "text-shadow-transparent/65", - "text-shadow-transparent/70", - "text-shadow-transparent/75", - "text-shadow-transparent/80", - "text-shadow-transparent/85", - "text-shadow-transparent/90", - "text-shadow-transparent/95", - "text-shadow-transparent/100", - "text-shadow-transparent/background", "text-start", "text-transparent", "text-transparent/0", @@ -11810,7 +7779,6 @@ exports[`getClassList 1`] = ` "text-transparent/90", "text-transparent/95", "text-transparent/100", - "text-transparent/background", "text-wrap", "text-xs", "to-0%", @@ -11856,7 +7824,6 @@ exports[`getClassList 1`] = ` "to-current/90", "to-current/95", "to-current/100", - "to-current/background", "to-inherit", "to-inherit/0", "to-inherit/5", @@ -11879,7 +7846,6 @@ exports[`getClassList 1`] = ` "to-inherit/90", "to-inherit/95", "to-inherit/100", - "to-inherit/background", "to-transparent", "to-transparent/0", "to-transparent/5", @@ -11902,7 +7868,6 @@ exports[`getClassList 1`] = ` "to-transparent/90", "to-transparent/95", "to-transparent/100", - "to-transparent/background", "top-0", "top-0.5", "top-1", @@ -12271,7 +8236,6 @@ exports[`getClassList 1`] = ` "via-current/90", "via-current/95", "via-current/100", - "via-current/background", "via-inherit", "via-inherit/0", "via-inherit/5", @@ -12294,7 +8258,6 @@ exports[`getClassList 1`] = ` "via-inherit/90", "via-inherit/95", "via-inherit/100", - "via-inherit/background", "via-none", "via-transparent", "via-transparent/0", @@ -12318,7 +8281,6 @@ exports[`getClassList 1`] = ` "via-transparent/90", "via-transparent/95", "via-transparent/100", - "via-transparent/background", "visible", "w-0", "w-0.5", @@ -12415,16 +8377,6 @@ exports[`getClassList 1`] = ` "z-40", "z-50", "z-auto", - "zoom-50", - "zoom-75", - "zoom-90", - "zoom-95", - "zoom-100", - "zoom-105", - "zoom-110", - "zoom-125", - "zoom-150", - "zoom-200", ] `; @@ -12520,7 +8472,9 @@ exports[`getVariants 1`] = ` "any-pointer-none", "any-pointer-coarse", "any-pointer-fine", - "noscript", + "scripting-initial", + "scripting-none", + "scripting", ], }, { @@ -13301,7 +9255,21 @@ exports[`getVariants 1`] = ` { "hasDash": true, "isArbitrary": false, - "name": "noscript", + "name": "scripting-initial", + "selectors": [Function], + "values": [], + }, + { + "hasDash": true, + "isArbitrary": false, + "name": "scripting-none", + "selectors": [Function], + "values": [], + }, + { + "hasDash": true, + "isArbitrary": false, + "name": "scripting", "selectors": [Function], "values": [], }, diff --git a/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap b/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap index 43e8fdf6f..5f1c2fea3 100644 --- a/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap @@ -1,18 +1,8 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`border-* 1`] = ` -" -@layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-border-style: solid; - } - } -} - -:root, :host { +":root, :host { --color-red-500: #ef4444; - --border-color-blue-500: #3b82f6; } .border { @@ -90,41 +80,31 @@ exports[`border-* 1`] = ` } .border-\\[\\#0088cc\\]\\/50 { - border-color: oklab(59.9824% -.067 -.124 / .5); + border-color: oklab(59.9824% -.06725 -.12414 / .5); } -.border-\\[color\\:var\\(--my-color\\)\\], .border-\\[color\\:var\\(--my-color\\)\\]\\/50 { +.border-\\[color\\:var\\(--my-color\\)\\] { border-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-\\[color\\:var\\(--my-color\\)\\]\\/50 { + border-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-\\[var\\(--my-color\\)\\], .border-\\[var\\(--my-color\\)\\]\\/50 { +.border-\\[var\\(--my-color\\)\\] { border-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-\\[var\\(--my-color\\)\\]\\/50 { - border-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-\\[var\\(--my-color\\)\\]\\/50 { + border-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-blue-500 { - border-color: var(--border-color-blue-500); -} - -.border-current, .border-current\\/50 { +.border-current { border-color: currentColor; } -@supports (color: color-mix(in lab, red, red)) { - .border-current\\/50 { - border-color: color-mix(in oklab, currentcolor 50%, transparent); - } +.border-current\\/50 { + border-color: color-mix(in oklab, currentColor 50%, transparent); } .border-inherit { @@ -136,43 +116,19 @@ exports[`border-* 1`] = ` } .border-red-500\\/2\\.5 { - border-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-red-500\\/2\\.5 { - border-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); - } + border-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); } .border-red-500\\/2\\.25 { - border-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-red-500\\/2\\.25 { - border-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); - } + border-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); } .border-red-500\\/2\\.75 { - border-color: #ef444407; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-red-500\\/2\\.75 { - border-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); - } + border-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); } .border-red-500\\/50 { - border-color: #ef444480; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-red-500\\/50 { - border-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); - } + border-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); } .border-transparent { @@ -183,23 +139,12 @@ exports[`border-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; exports[`border-b-* 1`] = ` -" -@layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-border-style: solid; - } - } -} - -:root, :host { +":root, :host { --color-red-500: #ef4444; - --border-color-blue-500: #3b82f6; } .border-b { @@ -277,41 +222,31 @@ exports[`border-b-* 1`] = ` } .border-b-\\[\\#0088cc\\]\\/50 { - border-bottom-color: oklab(59.9824% -.067 -.124 / .5); + border-bottom-color: oklab(59.9824% -.06725 -.12414 / .5); } -.border-b-\\[color\\:var\\(--my-color\\)\\], .border-b-\\[color\\:var\\(--my-color\\)\\]\\/50 { +.border-b-\\[color\\:var\\(--my-color\\)\\] { border-bottom-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-b-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-bottom-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-b-\\[color\\:var\\(--my-color\\)\\]\\/50 { + border-bottom-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-b-\\[var\\(--my-color\\)\\], .border-b-\\[var\\(--my-color\\)\\]\\/50 { +.border-b-\\[var\\(--my-color\\)\\] { border-bottom-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-b-\\[var\\(--my-color\\)\\]\\/50 { - border-bottom-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-b-\\[var\\(--my-color\\)\\]\\/50 { + border-bottom-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-b-blue-500 { - border-bottom-color: var(--border-color-blue-500); -} - -.border-b-current, .border-b-current\\/50 { +.border-b-current { border-bottom-color: currentColor; } -@supports (color: color-mix(in lab, red, red)) { - .border-b-current\\/50 { - border-bottom-color: color-mix(in oklab, currentcolor 50%, transparent); - } +.border-b-current\\/50 { + border-bottom-color: color-mix(in oklab, currentColor 50%, transparent); } .border-b-inherit { @@ -323,43 +258,19 @@ exports[`border-b-* 1`] = ` } .border-b-red-500\\/2\\.5 { - border-bottom-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-b-red-500\\/2\\.5 { - border-bottom-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); - } + border-bottom-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); } .border-b-red-500\\/2\\.25 { - border-bottom-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-b-red-500\\/2\\.25 { - border-bottom-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); - } + border-bottom-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); } .border-b-red-500\\/2\\.75 { - border-bottom-color: #ef444407; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-b-red-500\\/2\\.75 { - border-bottom-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); - } + border-bottom-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); } .border-b-red-500\\/50 { - border-bottom-color: #ef444480; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-b-red-500\\/50 { - border-bottom-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); - } + border-bottom-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); } .border-b-transparent { @@ -370,397 +281,12 @@ exports[`border-b-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" -`; - -exports[`border-be-* 1`] = ` -" -@layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-border-style: solid; - } - } -} - -:root, :host { - --color-red-500: #ef4444; - --border-color-blue-500: #3b82f6; -} - -.border-be { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 1px; -} - -.border-be-0 { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 0; -} - -.border-be-2 { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 2px; -} - -.border-be-4 { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 4px; -} - -.border-be-123 { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 123px; -} - -.border-be-\\[0_1\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 0 1; -} - -.border-be-\\[0_2px_0_2px\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 0 2px 0 2px; -} - -.border-be-\\[12px\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 12px; -} - -.border-be-\\[12px_8px\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 12px 8px; -} - -.border-be-\\[length\\:var\\(--my-width\\)\\], .border-be-\\[line-width\\:var\\(--my-width\\)\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: var(--my-width); -} - -.border-be-\\[medium\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: medium; -} - -.border-be-\\[thick\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: thick; -} - -.border-be-\\[thin\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: thin; -} - -.border-be-\\[thin_2px\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: thin 2px; -} - -.border-be-\\[\\#0088cc\\] { - border-block-end-color: #08c; -} - -.border-be-\\[\\#0088cc\\]\\/50 { - border-block-end-color: oklab(59.9824% -.067 -.124 / .5); -} - -.border-be-\\[color\\:var\\(--my-color\\)\\], .border-be-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-block-end-color: var(--my-color); -} - -@supports (color: color-mix(in lab, red, red)) { - .border-be-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-block-end-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } -} - -.border-be-\\[var\\(--my-color\\)\\], .border-be-\\[var\\(--my-color\\)\\]\\/50 { - border-block-end-color: var(--my-color); -} - -@supports (color: color-mix(in lab, red, red)) { - .border-be-\\[var\\(--my-color\\)\\]\\/50 { - border-block-end-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } -} - -.border-be-blue-500 { - border-block-end-color: var(--border-color-blue-500); -} - -.border-be-current, .border-be-current\\/50 { - border-block-end-color: currentColor; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-be-current\\/50 { - border-block-end-color: color-mix(in oklab, currentcolor 50%, transparent); - } -} - -.border-be-inherit { - border-block-end-color: inherit; -} - -.border-be-red-500 { - border-block-end-color: var(--color-red-500); -} - -.border-be-red-500\\/2\\.5 { - border-block-end-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-be-red-500\\/2\\.5 { - border-block-end-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); - } -} - -.border-be-red-500\\/2\\.25 { - border-block-end-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-be-red-500\\/2\\.25 { - border-block-end-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); - } -} - -.border-be-red-500\\/2\\.75 { - border-block-end-color: #ef444407; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-be-red-500\\/2\\.75 { - border-block-end-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); - } -} - -.border-be-red-500\\/50 { - border-block-end-color: #ef444480; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-be-red-500\\/50 { - border-block-end-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); - } -} - -.border-be-transparent { - border-block-end-color: #0000; -} - -@property --tw-border-style { - syntax: "*"; - inherits: false; - initial-value: solid; -} -" -`; - -exports[`border-bs-* 1`] = ` -" -@layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-border-style: solid; - } - } -} - -:root, :host { - --color-red-500: #ef4444; - --border-color-blue-500: #3b82f6; -} - -.border-bs { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 1px; -} - -.border-bs-0 { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 0; -} - -.border-bs-2 { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 2px; -} - -.border-bs-4 { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 4px; -} - -.border-bs-123 { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 123px; -} - -.border-bs-\\[0_1\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 0 1; -} - -.border-bs-\\[0_2px_0_2px\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 0 2px 0 2px; -} - -.border-bs-\\[12px\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 12px; -} - -.border-bs-\\[12px_8px\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 12px 8px; -} - -.border-bs-\\[length\\:var\\(--my-width\\)\\], .border-bs-\\[line-width\\:var\\(--my-width\\)\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: var(--my-width); -} - -.border-bs-\\[medium\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: medium; -} - -.border-bs-\\[thick\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: thick; -} - -.border-bs-\\[thin\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: thin; -} - -.border-bs-\\[thin_2px\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: thin 2px; -} - -.border-bs-\\[\\#0088cc\\] { - border-block-start-color: #08c; -} - -.border-bs-\\[\\#0088cc\\]\\/50 { - border-block-start-color: oklab(59.9824% -.067 -.124 / .5); -} - -.border-bs-\\[color\\:var\\(--my-color\\)\\], .border-bs-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-block-start-color: var(--my-color); -} - -@supports (color: color-mix(in lab, red, red)) { - .border-bs-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-block-start-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } -} - -.border-bs-\\[var\\(--my-color\\)\\], .border-bs-\\[var\\(--my-color\\)\\]\\/50 { - border-block-start-color: var(--my-color); -} - -@supports (color: color-mix(in lab, red, red)) { - .border-bs-\\[var\\(--my-color\\)\\]\\/50 { - border-block-start-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } -} - -.border-bs-blue-500 { - border-block-start-color: var(--border-color-blue-500); -} - -.border-bs-current, .border-bs-current\\/50 { - border-block-start-color: currentColor; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-bs-current\\/50 { - border-block-start-color: color-mix(in oklab, currentcolor 50%, transparent); - } -} - -.border-bs-inherit { - border-block-start-color: inherit; -} - -.border-bs-red-500 { - border-block-start-color: var(--color-red-500); -} - -.border-bs-red-500\\/2\\.5 { - border-block-start-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-bs-red-500\\/2\\.5 { - border-block-start-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); - } -} - -.border-bs-red-500\\/2\\.25 { - border-block-start-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-bs-red-500\\/2\\.25 { - border-block-start-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); - } -} - -.border-bs-red-500\\/2\\.75 { - border-block-start-color: #ef444407; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-bs-red-500\\/2\\.75 { - border-block-start-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); - } -} - -.border-bs-red-500\\/50 { - border-block-start-color: #ef444480; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-bs-red-500\\/50 { - border-block-start-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); - } -} - -.border-bs-transparent { - border-block-start-color: #0000; -} - -@property --tw-border-style { - syntax: "*"; - inherits: false; - initial-value: solid; -} -" +}" `; exports[`border-e-* 1`] = ` -" -@layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-border-style: solid; - } - } -} - -:root, :host { +":root, :host { --color-red-500: #ef4444; - --border-color-blue-500: #3b82f6; } .border-e { @@ -838,41 +364,31 @@ exports[`border-e-* 1`] = ` } .border-e-\\[\\#0088cc\\]\\/50 { - border-inline-end-color: oklab(59.9824% -.067 -.124 / .5); + border-inline-end-color: oklab(59.9824% -.06725 -.12414 / .5); } -.border-e-\\[color\\:var\\(--my-color\\)\\], .border-e-\\[color\\:var\\(--my-color\\)\\]\\/50 { +.border-e-\\[color\\:var\\(--my-color\\)\\] { border-inline-end-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-e-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-inline-end-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-e-\\[color\\:var\\(--my-color\\)\\]\\/50 { + border-inline-end-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-e-\\[var\\(--my-color\\)\\], .border-e-\\[var\\(--my-color\\)\\]\\/50 { +.border-e-\\[var\\(--my-color\\)\\] { border-inline-end-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-e-\\[var\\(--my-color\\)\\]\\/50 { - border-inline-end-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-e-\\[var\\(--my-color\\)\\]\\/50 { + border-inline-end-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-e-blue-500 { - border-inline-end-color: var(--border-color-blue-500); -} - -.border-e-current, .border-e-current\\/50 { +.border-e-current { border-inline-end-color: currentColor; } -@supports (color: color-mix(in lab, red, red)) { - .border-e-current\\/50 { - border-inline-end-color: color-mix(in oklab, currentcolor 50%, transparent); - } +.border-e-current\\/50 { + border-inline-end-color: color-mix(in oklab, currentColor 50%, transparent); } .border-e-inherit { @@ -884,43 +400,19 @@ exports[`border-e-* 1`] = ` } .border-e-red-500\\/2\\.5 { - border-inline-end-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-e-red-500\\/2\\.5 { - border-inline-end-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); - } + border-inline-end-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); } .border-e-red-500\\/2\\.25 { - border-inline-end-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-e-red-500\\/2\\.25 { - border-inline-end-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); - } + border-inline-end-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); } .border-e-red-500\\/2\\.75 { - border-inline-end-color: #ef444407; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-e-red-500\\/2\\.75 { - border-inline-end-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); - } + border-inline-end-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); } .border-e-red-500\\/50 { - border-inline-end-color: #ef444480; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-e-red-500\\/50 { - border-inline-end-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); - } + border-inline-end-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); } .border-e-transparent { @@ -931,23 +423,12 @@ exports[`border-e-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; exports[`border-l-* 1`] = ` -" -@layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-border-style: solid; - } - } -} - -:root, :host { +":root, :host { --color-red-500: #ef4444; - --border-color-blue-500: #3b82f6; } .border-l { @@ -1025,41 +506,31 @@ exports[`border-l-* 1`] = ` } .border-l-\\[\\#0088cc\\]\\/50 { - border-left-color: oklab(59.9824% -.067 -.124 / .5); + border-left-color: oklab(59.9824% -.06725 -.12414 / .5); } -.border-l-\\[color\\:var\\(--my-color\\)\\], .border-l-\\[color\\:var\\(--my-color\\)\\]\\/50 { +.border-l-\\[color\\:var\\(--my-color\\)\\] { border-left-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-l-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-left-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-l-\\[color\\:var\\(--my-color\\)\\]\\/50 { + border-left-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-l-\\[var\\(--my-color\\)\\], .border-l-\\[var\\(--my-color\\)\\]\\/50 { +.border-l-\\[var\\(--my-color\\)\\] { border-left-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-l-\\[var\\(--my-color\\)\\]\\/50 { - border-left-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-l-\\[var\\(--my-color\\)\\]\\/50 { + border-left-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-l-blue-500 { - border-left-color: var(--border-color-blue-500); -} - -.border-l-current, .border-l-current\\/50 { +.border-l-current { border-left-color: currentColor; } -@supports (color: color-mix(in lab, red, red)) { - .border-l-current\\/50 { - border-left-color: color-mix(in oklab, currentcolor 50%, transparent); - } +.border-l-current\\/50 { + border-left-color: color-mix(in oklab, currentColor 50%, transparent); } .border-l-inherit { @@ -1071,43 +542,19 @@ exports[`border-l-* 1`] = ` } .border-l-red-500\\/2\\.5 { - border-left-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-l-red-500\\/2\\.5 { - border-left-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); - } + border-left-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); } .border-l-red-500\\/2\\.25 { - border-left-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-l-red-500\\/2\\.25 { - border-left-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); - } + border-left-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); } .border-l-red-500\\/2\\.75 { - border-left-color: #ef444407; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-l-red-500\\/2\\.75 { - border-left-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); - } + border-left-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); } .border-l-red-500\\/50 { - border-left-color: #ef444480; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-l-red-500\\/50 { - border-left-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); - } + border-left-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); } .border-l-transparent { @@ -1118,23 +565,12 @@ exports[`border-l-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; exports[`border-r-* 1`] = ` -" -@layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-border-style: solid; - } - } -} - -:root, :host { +":root, :host { --color-red-500: #ef4444; - --border-color-blue-500: #3b82f6; } .border-r { @@ -1212,41 +648,31 @@ exports[`border-r-* 1`] = ` } .border-r-\\[\\#0088cc\\]\\/50 { - border-right-color: oklab(59.9824% -.067 -.124 / .5); + border-right-color: oklab(59.9824% -.06725 -.12414 / .5); } -.border-r-\\[color\\:var\\(--my-color\\)\\], .border-r-\\[color\\:var\\(--my-color\\)\\]\\/50 { +.border-r-\\[color\\:var\\(--my-color\\)\\] { border-right-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-r-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-right-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-r-\\[color\\:var\\(--my-color\\)\\]\\/50 { + border-right-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-r-\\[var\\(--my-color\\)\\], .border-r-\\[var\\(--my-color\\)\\]\\/50 { +.border-r-\\[var\\(--my-color\\)\\] { border-right-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-r-\\[var\\(--my-color\\)\\]\\/50 { - border-right-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-r-\\[var\\(--my-color\\)\\]\\/50 { + border-right-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-r-blue-500 { - border-right-color: var(--border-color-blue-500); -} - -.border-r-current, .border-r-current\\/50 { +.border-r-current { border-right-color: currentColor; } -@supports (color: color-mix(in lab, red, red)) { - .border-r-current\\/50 { - border-right-color: color-mix(in oklab, currentcolor 50%, transparent); - } +.border-r-current\\/50 { + border-right-color: color-mix(in oklab, currentColor 50%, transparent); } .border-r-inherit { @@ -1258,43 +684,19 @@ exports[`border-r-* 1`] = ` } .border-r-red-500\\/2\\.5 { - border-right-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-r-red-500\\/2\\.5 { - border-right-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); - } + border-right-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); } .border-r-red-500\\/2\\.25 { - border-right-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-r-red-500\\/2\\.25 { - border-right-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); - } + border-right-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); } .border-r-red-500\\/2\\.75 { - border-right-color: #ef444407; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-r-red-500\\/2\\.75 { - border-right-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); - } + border-right-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); } .border-r-red-500\\/50 { - border-right-color: #ef444480; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-r-red-500\\/50 { - border-right-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); - } + border-right-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); } .border-r-transparent { @@ -1305,23 +707,12 @@ exports[`border-r-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; exports[`border-s-* 1`] = ` -" -@layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-border-style: solid; - } - } -} - -:root, :host { +":root, :host { --color-red-500: #ef4444; - --border-color-blue-500: #3b82f6; } .border-s { @@ -1399,41 +790,31 @@ exports[`border-s-* 1`] = ` } .border-s-\\[\\#0088cc\\]\\/50 { - border-inline-start-color: oklab(59.9824% -.067 -.124 / .5); + border-inline-start-color: oklab(59.9824% -.06725 -.12414 / .5); } -.border-s-\\[color\\:var\\(--my-color\\)\\], .border-s-\\[color\\:var\\(--my-color\\)\\]\\/50 { +.border-s-\\[color\\:var\\(--my-color\\)\\] { border-inline-start-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-s-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-inline-start-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-s-\\[color\\:var\\(--my-color\\)\\]\\/50 { + border-inline-start-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-s-\\[var\\(--my-color\\)\\], .border-s-\\[var\\(--my-color\\)\\]\\/50 { +.border-s-\\[var\\(--my-color\\)\\] { border-inline-start-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-s-\\[var\\(--my-color\\)\\]\\/50 { - border-inline-start-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-s-\\[var\\(--my-color\\)\\]\\/50 { + border-inline-start-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-s-blue-500 { - border-inline-start-color: var(--border-color-blue-500); -} - -.border-s-current, .border-s-current\\/50 { +.border-s-current { border-inline-start-color: currentColor; } -@supports (color: color-mix(in lab, red, red)) { - .border-s-current\\/50 { - border-inline-start-color: color-mix(in oklab, currentcolor 50%, transparent); - } +.border-s-current\\/50 { + border-inline-start-color: color-mix(in oklab, currentColor 50%, transparent); } .border-s-inherit { @@ -1445,43 +826,19 @@ exports[`border-s-* 1`] = ` } .border-s-red-500\\/2\\.5 { - border-inline-start-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-s-red-500\\/2\\.5 { - border-inline-start-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); - } + border-inline-start-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); } .border-s-red-500\\/2\\.25 { - border-inline-start-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-s-red-500\\/2\\.25 { - border-inline-start-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); - } + border-inline-start-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); } .border-s-red-500\\/2\\.75 { - border-inline-start-color: #ef444407; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-s-red-500\\/2\\.75 { - border-inline-start-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); - } + border-inline-start-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); } .border-s-red-500\\/50 { - border-inline-start-color: #ef444480; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-s-red-500\\/50 { - border-inline-start-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); - } + border-inline-start-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); } .border-s-transparent { @@ -1492,23 +849,12 @@ exports[`border-s-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; exports[`border-t-* 1`] = ` -" -@layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-border-style: solid; - } - } -} - -:root, :host { +":root, :host { --color-red-500: #ef4444; - --border-color-blue-500: #3b82f6; } .border-t { @@ -1586,41 +932,31 @@ exports[`border-t-* 1`] = ` } .border-t-\\[\\#0088cc\\]\\/50 { - border-top-color: oklab(59.9824% -.067 -.124 / .5); + border-top-color: oklab(59.9824% -.06725 -.12414 / .5); } -.border-t-\\[color\\:var\\(--my-color\\)\\], .border-t-\\[color\\:var\\(--my-color\\)\\]\\/50 { +.border-t-\\[color\\:var\\(--my-color\\)\\] { border-top-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-t-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-top-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-t-\\[color\\:var\\(--my-color\\)\\]\\/50 { + border-top-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-t-\\[var\\(--my-color\\)\\], .border-t-\\[var\\(--my-color\\)\\]\\/50 { +.border-t-\\[var\\(--my-color\\)\\] { border-top-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-t-\\[var\\(--my-color\\)\\]\\/50 { - border-top-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-t-\\[var\\(--my-color\\)\\]\\/50 { + border-top-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-t-blue-500 { - border-top-color: var(--border-color-blue-500); -} - -.border-t-current, .border-t-current\\/50 { +.border-t-current { border-top-color: currentColor; } -@supports (color: color-mix(in lab, red, red)) { - .border-t-current\\/50 { - border-top-color: color-mix(in oklab, currentcolor 50%, transparent); - } +.border-t-current\\/50 { + border-top-color: color-mix(in oklab, currentColor 50%, transparent); } .border-t-inherit { @@ -1632,43 +968,19 @@ exports[`border-t-* 1`] = ` } .border-t-red-500\\/2\\.5 { - border-top-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-t-red-500\\/2\\.5 { - border-top-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); - } + border-top-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); } .border-t-red-500\\/2\\.25 { - border-top-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-t-red-500\\/2\\.25 { - border-top-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); - } + border-top-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); } .border-t-red-500\\/2\\.75 { - border-top-color: #ef444407; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-t-red-500\\/2\\.75 { - border-top-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); - } + border-top-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); } .border-t-red-500\\/50 { - border-top-color: #ef444480; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-t-red-500\\/50 { - border-top-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); - } + border-top-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); } .border-t-transparent { @@ -1679,23 +991,12 @@ exports[`border-t-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; exports[`border-x-* 1`] = ` -" -@layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-border-style: solid; - } - } -} - -:root, :host { +":root, :host { --color-red-500: #ef4444; - --border-color-blue-500: #3b82f6; } .border-x { @@ -1773,41 +1074,31 @@ exports[`border-x-* 1`] = ` } .border-x-\\[\\#0088cc\\]\\/50 { - border-inline-color: oklab(59.9824% -.067 -.124 / .5); + border-inline-color: oklab(59.9824% -.06725 -.12414 / .5); } -.border-x-\\[color\\:var\\(--my-color\\)\\], .border-x-\\[color\\:var\\(--my-color\\)\\]\\/50 { +.border-x-\\[color\\:var\\(--my-color\\)\\] { border-inline-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-x-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-inline-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-x-\\[color\\:var\\(--my-color\\)\\]\\/50 { + border-inline-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-x-\\[var\\(--my-color\\)\\], .border-x-\\[var\\(--my-color\\)\\]\\/50 { +.border-x-\\[var\\(--my-color\\)\\] { border-inline-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-x-\\[var\\(--my-color\\)\\]\\/50 { - border-inline-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-x-\\[var\\(--my-color\\)\\]\\/50 { + border-inline-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-x-blue-500 { - border-inline-color: var(--border-color-blue-500); -} - -.border-x-current, .border-x-current\\/50 { +.border-x-current { border-inline-color: currentColor; } -@supports (color: color-mix(in lab, red, red)) { - .border-x-current\\/50 { - border-inline-color: color-mix(in oklab, currentcolor 50%, transparent); - } +.border-x-current\\/50 { + border-inline-color: color-mix(in oklab, currentColor 50%, transparent); } .border-x-inherit { @@ -1819,43 +1110,19 @@ exports[`border-x-* 1`] = ` } .border-x-red-500\\/2\\.5 { - border-inline-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-x-red-500\\/2\\.5 { - border-inline-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); - } + border-inline-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); } .border-x-red-500\\/2\\.25 { - border-inline-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-x-red-500\\/2\\.25 { - border-inline-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); - } + border-inline-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); } .border-x-red-500\\/2\\.75 { - border-inline-color: #ef444407; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-x-red-500\\/2\\.75 { - border-inline-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); - } + border-inline-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); } .border-x-red-500\\/50 { - border-inline-color: #ef444480; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-x-red-500\\/50 { - border-inline-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); - } + border-inline-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); } .border-x-transparent { @@ -1866,23 +1133,12 @@ exports[`border-x-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; exports[`border-y-* 1`] = ` -" -@layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-border-style: solid; - } - } -} - -:root, :host { +":root, :host { --color-red-500: #ef4444; - --border-color-blue-500: #3b82f6; } .border-y { @@ -1960,41 +1216,31 @@ exports[`border-y-* 1`] = ` } .border-y-\\[\\#0088cc\\]\\/50 { - border-block-color: oklab(59.9824% -.067 -.124 / .5); + border-block-color: oklab(59.9824% -.06725 -.12414 / .5); } -.border-y-\\[color\\:var\\(--my-color\\)\\], .border-y-\\[color\\:var\\(--my-color\\)\\]\\/50 { +.border-y-\\[color\\:var\\(--my-color\\)\\] { border-block-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-y-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-block-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-y-\\[color\\:var\\(--my-color\\)\\]\\/50 { + border-block-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-y-\\[var\\(--my-color\\)\\], .border-y-\\[var\\(--my-color\\)\\]\\/50 { +.border-y-\\[var\\(--my-color\\)\\] { border-block-color: var(--my-color); } -@supports (color: color-mix(in lab, red, red)) { - .border-y-\\[var\\(--my-color\\)\\]\\/50 { - border-block-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } +.border-y-\\[var\\(--my-color\\)\\]\\/50 { + border-block-color: color-mix(in oklab, var(--my-color) 50%, transparent); } -.border-y-blue-500 { - border-block-color: var(--border-color-blue-500); -} - -.border-y-current, .border-y-current\\/50 { +.border-y-current { border-block-color: currentColor; } -@supports (color: color-mix(in lab, red, red)) { - .border-y-current\\/50 { - border-block-color: color-mix(in oklab, currentcolor 50%, transparent); - } +.border-y-current\\/50 { + border-block-color: color-mix(in oklab, currentColor 50%, transparent); } .border-y-inherit { @@ -2006,43 +1252,19 @@ exports[`border-y-* 1`] = ` } .border-y-red-500\\/2\\.5 { - border-block-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-y-red-500\\/2\\.5 { - border-block-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); - } + border-block-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); } .border-y-red-500\\/2\\.25 { - border-block-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-y-red-500\\/2\\.25 { - border-block-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); - } + border-block-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); } .border-y-red-500\\/2\\.75 { - border-block-color: #ef444407; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-y-red-500\\/2\\.75 { - border-block-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); - } + border-block-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); } .border-y-red-500\\/50 { - border-block-color: #ef444480; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-y-red-500\\/50 { - border-block-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); - } + border-block-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); } .border-y-transparent { @@ -2053,6 +1275,5 @@ exports[`border-y-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; diff --git a/packages/tailwindcss/src/apply.ts b/packages/tailwindcss/src/apply.ts index 2d5e5b847..676b4f2ee 100644 --- a/packages/tailwindcss/src/apply.ts +++ b/packages/tailwindcss/src/apply.ts @@ -1,11 +1,8 @@ import { Features } from '.' -import { cloneAstNode, rule, toCss, type AstNode } from './ast' +import { rule, toCss, walk, WalkAction, type AstNode } from './ast' import { compileCandidates } from './compile' import type { DesignSystem } from './design-system' -import type { SourceLocation } from './source-maps/source' import { DefaultMap } from './utils/default-map' -import { segment } from './utils/segment' -import { walk, WalkAction } from './walk' export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) { let features = Features.None @@ -26,7 +23,7 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) { let definitions = new DefaultMap(() => new Set()) // Collect all new `@utility` definitions and all `@apply` rules first - walk([root], (node, ctx) => { + walk([root], (node, { parent }) => { if (node.kind !== 'at-rule') return // Do not allow `@apply` rules inside `@keyframes` rules. @@ -62,18 +59,14 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) { if (node.name === '@apply') { // `@apply` cannot be top-level, so we need to have a parent such that we // can replace the `@apply` node with the actual utility classes later. - if (ctx.parent === null) return + if (parent === null) return features |= Features.AtApply - parents.add(ctx.parent) + parents.add(parent) for (let dependency of resolveApplyDependencies(node, designSystem)) { - // Mark every parent in the path as having a dependency to that utility. - for (let parent of ctx.path()) { - if (!parents.has(parent)) continue - dependencies.get(parent).add(dependency) - } + dependencies.get(parent).add(dependency) } } }) @@ -158,163 +151,21 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) { for (let parent of sorted) { if (!('nodes' in parent)) continue - walk(parent.nodes, (child) => { - if (child.kind !== 'at-rule' || child.name !== '@apply') return + for (let i = 0; i < parent.nodes.length; i++) { + let node = parent.nodes[i] + if (node.kind !== 'at-rule' || node.name !== '@apply') continue - let parts = child.params.split(/(\s+)/g) - let candidateOffsets: Record = {} - let normalIdents: string[] = [] - let dashedIdents: string[] = [] - - let offset = 0 - for (let [idx, part] of parts.entries()) { - if (idx % 2 === 0) { - if (part[0] === '-' && part[1] === '-') { - dashedIdents.push(part) - } else { - normalIdents.push(part) - } - - candidateOffsets[part] = offset - } - - offset += part.length - } - - if (dashedIdents.length) { - // If we have an `@apply` that only consists of dashed idents then the - // user is intending to use a CSS mixin: - // https://drafts.csswg.org/css-mixins-1/#apply-rule - // - // These are not considered utilities and need to be emitted literally. - if (normalIdents.length === 0) return WalkAction.Skip - - // If we find a dashed ident *here* it means that someone is trying - // to use mixins and our `@apply` behavior together. - // - // This is invalid and the rules must be written separately. Let the - // user know they need to move them into a separate rule. - let list = dashedIdents.join(' ') - - throw new Error( - `You cannot use \`@apply\` with both mixins and utilities. Please move \`@apply ${list}\` into a separate rule.`, - ) - } - - let hasBody = child.nodes.length > 0 - - if (hasBody && normalIdents.length) { - let list = normalIdents.join(' ') - - throw new Error(`The rule \`@apply ${list}\` must not have a body.`) - } + let candidates = node.params.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 candidates = Object.keys(candidateOffsets) - let compiled = compileCandidates(candidates, designSystem, { - respectImportant: false, + let candidateAst = compileCandidates(candidates, designSystem, { onInvalidCandidate: (candidate) => { - // When using prefix, make sure prefix is used in candidate - if (designSystem.theme.prefix && !candidate.startsWith(designSystem.theme.prefix)) { - throw new Error( - `Cannot apply unprefixed utility class \`${candidate}\`. Did you mean \`${designSystem.theme.prefix}:${candidate}\`?`, - ) - } - - // When the utility is blocklisted, let the user know - // - // Note: `@apply` is processed before handling incoming classes from - // template files. This means that the `invalidCandidates` set will - // only contain explicit classes via: - // - // - `blocklist` from a JS config - // - `@source not inline(…)` - if (designSystem.invalidCandidates.has(candidate)) { - throw new Error( - `Cannot apply utility class \`${candidate}\` because it has been explicitly disabled: https://tailwindcss.com/docs/detecting-classes-in-source-files#explicitly-excluding-classes`, - ) - } - - // Verify if variants exist - let parts = segment(candidate, ':') - if (parts.length > 1) { - let utility = parts.pop()! - - // Ensure utility on its own compiles, if not, we will fallback to - // the next error - if (designSystem.candidatesToCss([utility])[0]) { - let compiledVariants = designSystem.candidatesToCss( - parts.map((variant) => `${variant}:[--tw-variant-check:1]`), - ) - let unknownVariants = parts.filter((_, idx) => compiledVariants[idx] === null) - if (unknownVariants.length > 0) { - if (unknownVariants.length === 1) { - throw new Error( - `Cannot apply utility class \`${candidate}\` because the ${unknownVariants.map((variant) => `\`${variant}\``)} variant does not exist.`, - ) - } else { - let formatter = new Intl.ListFormat('en', { - style: 'long', - type: 'conjunction', - }) - throw new Error( - `Cannot apply utility class \`${candidate}\` because the ${formatter.format(unknownVariants.map((variant) => `\`${variant}\``))} variants do not exist.`, - ) - } - } - } - } - - // When the theme is empty, it means that no theme was loaded and - // `@import "tailwindcss"`, `@reference "app.css"` or similar is - // very likely missing. - if (designSystem.theme.size === 0) { - throw new Error( - `Cannot apply unknown utility class \`${candidate}\`. Are you using CSS modules or similar and missing \`@reference\`? https://tailwindcss.com/docs/functions-and-directives#reference-directive`, - ) - } - - // Fallback to most generic error message - throw new Error(`Cannot apply unknown utility class \`${candidate}\``) + throw new Error(`Cannot apply unknown utility class: ${candidate}`) }, - }) - - let src = child.src - - let candidateAst = compiled.astNodes.map((node) => { - let candidate = compiled.nodeSorting.get(node)?.candidate - let candidateOffset = candidate ? candidateOffsets[candidate] : undefined - - node = cloneAstNode(node) - - if (!src || !candidate || candidateOffset === undefined) { - // While the original nodes may have come from an `@utility` we still - // want to replace the source because the `@apply` is ultimately the - // reason the node was emitted into the AST. - walk([node], (node) => { - node.src = src - }) - - return node - } - - let candidateSrc: SourceLocation = [src[0], src[1], src[2]] - - candidateSrc[1] += 7 /* '@apply '.length */ + candidateOffset - candidateSrc[2] = candidateSrc[1] + candidate.length - - // While the original nodes may have come from an `@utility` we still - // want to replace the source because the `@apply` is ultimately the - // reason the node was emitted into the AST. - walk([node], (node) => { - node.src = candidateSrc - }) - - return node - }) + }).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 @@ -330,11 +181,10 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) { } } - return WalkAction.Replace(newNodes) + parent.nodes.splice(i, 1, ...newNodes) } - }) + } } - return features } diff --git a/packages/tailwindcss/src/ast.bench.ts b/packages/tailwindcss/src/ast.bench.ts deleted file mode 100644 index 034faee27..000000000 --- a/packages/tailwindcss/src/ast.bench.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { bench, describe } from 'vitest' -import { cloneAstNode, toCss, type AstNode } from './ast' -import * as CSS from './css-parser' - -const css = String.raw -const input = css` - @theme { - --color-primary: #333; - } - @tailwind utilities; - .foo { - color: red; - /* comment */ - &:hover { - color: blue; - @apply font-bold; - } - } -` -const ast = CSS.parse(input) - -describe('AST to CSS', () => { - bench('toCss', () => { - toCss(ast) - }) - - bench('toCss with source maps', () => { - toCss(ast, true) - }) -}) - -describe('Cloning AST nodes', () => { - bench('cloneAstNode()', () => { - ast.map(cloneAstNode) - }) - - bench('cloneAstNode (with spread)', () => { - ast.map(cloneAstNodeSpread) - }) - - bench('structuredClone()', () => { - structuredClone(ast) - }) -}) - -function cloneAstNodeSpread(node: T): T { - switch (node.kind) { - case 'rule': - case 'at-rule': - case 'at-root': - return { ...node, nodes: node.nodes.map(cloneAstNodeSpread) } - - case 'context': - return { ...node, context: { ...node.context }, nodes: node.nodes.map(cloneAstNodeSpread) } - - case 'declaration': - case 'comment': - return { ...node } - - default: - node satisfies never - throw new Error(`Unknown node kind: ${(node as any).kind}`) - } -} diff --git a/packages/tailwindcss/src/ast.test.ts b/packages/tailwindcss/src/ast.test.ts index 14092c9f5..8bd2004ac 100644 --- a/packages/tailwindcss/src/ast.test.ts +++ b/packages/tailwindcss/src/ast.test.ts @@ -1,28 +1,23 @@ -import { describe, expect, it, test } from 'vitest' +import { expect, it } from 'vitest' import { + atRule, context, - cssContext, decl, - handleNesting, optimizeAst, - rule, styleRule, toCss, + walk, + WalkAction, type AstNode, } from './ast' import * as CSS from './css-parser' -import { buildDesignSystem } from './design-system' -import { pretty } from './test-utils/run' -import { Theme } from './theme' -import { walk, WalkAction } from './walk' const css = String.raw -const defaultDesignSystem = buildDesignSystem(new Theme()) it('should pretty print an AST', () => { - expect(pretty(toCss(CSS.parse('.foo{color:red;&:hover{color:blue;}}')))).toMatchInlineSnapshot(` - " - .foo { + expect(toCss(optimizeAst(CSS.parse('.foo{color:red;&:hover{color:blue;}}')))) + .toMatchInlineSnapshot(` + ".foo { color: red; &:hover { color: blue; @@ -33,7 +28,7 @@ it('should pretty print an AST', () => { }) it('allows the placement of context nodes', () => { - let ast: AstNode[] = [ + const ast = [ styleRule('.foo', [decl('color', 'red')]), context({ context: 'a' }, [ styleRule('.bar', [ @@ -50,18 +45,17 @@ it('allows the placement of context nodes', () => { let blueContext let greenContext - walk(ast, (node, _ctx) => { + walk(ast, (node, { context }) => { if (node.kind !== 'declaration') return - let ctx = cssContext(_ctx) switch (node.value) { case 'red': - redContext = ctx.context + redContext = context break case 'blue': - blueContext = ctx.context + blueContext = context break case 'green': - greenContext = ctx.context + greenContext = context break } }) @@ -70,9 +64,8 @@ it('allows the placement of context nodes', () => { expect(blueContext).toEqual({ context: 'a' }) expect(greenContext).toEqual({ context: 'b' }) - expect(pretty(toCss(optimizeAst(ast, defaultDesignSystem)))).toMatchInlineSnapshot(` - " - .foo { + expect(toCss(optimizeAst(ast))).toMatchInlineSnapshot(` + ".foo { color: red; } .bar { @@ -85,6 +78,36 @@ it('allows the placement of context nodes', () => { `) }) +it('should stop walking when returning `WalkAction.Stop`', () => { + let ast = [ + styleRule('.foo', [styleRule('.nested', [styleRule('.bail', [decl('color', 'red')])])]), + styleRule('.bar'), + styleRule('.baz'), + styleRule('.qux'), + ] + + let seen = new Set() + + walk(ast, (node) => { + if (node.kind === 'rule') { + seen.add(node.selector) + } + + if (node.kind === 'rule' && node.selector === '.bail') { + return WalkAction.Stop + } + }) + + // We do not want to see `.bar`, `.baz`, or `.qux` because we bailed early + expect(seen).toMatchInlineSnapshot(` + Set { + ".foo", + ".nested", + ".bail", + } + `) +}) + it('should not emit empty rules once optimized', () => { let ast = CSS.parse(css` /* Empty rule */ @@ -127,15 +150,13 @@ it('should not emit empty rules once optimized', () => { /* Exceptions: */ @charset "UTF-8"; @layer foo, bar, baz; - @layer foo, bar, baz; /* Will be deduped */ @custom-media --modern (color), (hover); @namespace 'http://www.w3.org/1999/xhtml'; @import url('https://fonts.googleapis.com/css2?family=Cedarville+Cursive&display=swap'); `) - expect(pretty(toCss(ast))).toMatchInlineSnapshot(` - " - .foo { + expect(toCss(ast)).toMatchInlineSnapshot(` + ".foo { } .foo { .bar { @@ -162,16 +183,14 @@ it('should not emit empty rules once optimized', () => { } @charset "UTF-8"; @layer foo, bar, baz; - @layer foo, bar, baz; @custom-media --modern (color), (hover); @namespace 'http://www.w3.org/1999/xhtml'; @import url('https://fonts.googleapis.com/css2?family=Cedarville+Cursive&display=swap'); " `) - expect(pretty(toCss(optimizeAst(ast, defaultDesignSystem)))).toMatchInlineSnapshot(` - " - @charset "UTF-8"; + expect(toCss(optimizeAst(ast))).toMatchInlineSnapshot(` + "@charset "UTF-8"; @layer foo, bar, baz; @custom-media --modern (color), (hover); @namespace 'http://www.w3.org/1999/xhtml'; @@ -180,1485 +199,309 @@ it('should not emit empty rules once optimized', () => { `) }) -it('should not emit exact duplicate declarations in the same rule', () => { - let ast = CSS.parse(css` - .foo { - color: red; - .bar { - color: green; - color: blue; - color: green; - } - color: red; +it('should only visit children once when calling `replaceWith` with single element array', () => { + let visited = new Set() + + let ast = [ + atRule('@media', '', [styleRule('.foo', [decl('color', 'blue')])]), + styleRule('.bar', [decl('color', 'blue')]), + ] + + walk(ast, (node, { replaceWith }) => { + if (visited.has(node)) { + throw new Error('Visited node twice') } - .foo { - color: red; - & { - color: green; - & { - color: red; - color: green; - color: blue; - } - color: red; - } - background: blue; - .bar { - color: green; - color: blue; - color: green; - } - caret-color: orange; - } - `) + visited.add(node) - expect(pretty(toCss(ast))).toMatchInlineSnapshot(` - " - .foo { - color: red; - .bar { - color: green; - color: blue; - color: green; - } - color: red; - } - .foo { - color: red; - & { - color: green; - & { - color: red; - color: green; - color: blue; - } - color: red; - } - background: blue; - .bar { - color: green; - color: blue; - color: green; - } - caret-color: orange; - } - " - `) - - expect(pretty(toCss(optimizeAst(ast, defaultDesignSystem)))).toMatchInlineSnapshot(` - " - .foo { - .bar { - color: green; - color: blue; - color: green; - } - color: red; - & { - color: green; - & { - color: red; - color: green; - color: blue; - } - color: red; - } - background: blue; - .bar { - color: green; - color: blue; - color: green; - } - caret-color: orange; - } - " - `) -}) - -it('should not emit color-mix() fallbacks inside @keyframes', () => { - let ast = CSS.parse(css` - @keyframes my-animation { - 0% { - color: color-mix(in oklab, var(--color-emerald-600) 0%, transparent); - } - 100% { - color: color-mix(in oklab, var(--color-emerald-600) 0%, transparent); - } - } - `) - - let theme = new Theme() - theme.add('--color-emerald-600', 'oklch(59.6% 0.145 163.225)') - - let design = buildDesignSystem(theme) - - expect(pretty(toCss(optimizeAst(ast, design)))).toMatchInlineSnapshot(` - " - @keyframes my-animation { - 0% { - color: color-mix(in oklab, var(--color-emerald-600) 0%, transparent); - } - 100% { - color: color-mix(in oklab, var(--color-emerald-600) 0%, transparent); - } - } - " - `) -}) - -describe('optimization', () => { - function optimize(input: string) { - let ast = CSS.parse(input) - walk(ast, { - exit(node) { - // Depending on where we use `@scope`, it's handled slightly differently: - // - // 1. In user authored CSS, we perform the normal CSS nesting and - // hoisting of `@scope` rules - // 2. In a `@custom-variant` definition, we need to swap the `@scope` - // with the parent definition. Otherwise you'll never get the - // expected "sandwich" effect. - // - // This little `@in-variant` hack is just a different way of - // differentiating between both cases for these tests. - if (node.kind === 'at-rule' && node.name === '@in-variant') { - return WalkAction.Replace(context({ source: 'variant' }, node.nodes)) - } - }, - }) - return pretty(toCss(handleNesting(ast))) - } - - // See: https://drafts.csswg.org/css-nesting-1/ - describe('CSS Nesting Module Level 1', () => { - it('uses the descendant combinator by default', async () => { - expect( - optimize(css` - .a { - element { - --x: 1; - } - .class { - --x: 2; - } - #id { - --x: 3; - } - :pseudo-class { - --x: 4; - } - ::pseudo-element { - --x: 5; - } - [attribute] { - --x: 6; - } - * { - --x: 7; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a element { - --x: 1; - } - .a .class { - --x: 2; - } - .a #id { - --x: 3; - } - .a :pseudo-class { - --x: 4; - } - .a ::pseudo-element { - --x: 5; - } - .a [attribute] { - --x: 6; - } - .a * { - --x: 7; - } - " - `) - }) - - it('should be possible to change the combinator', async () => { - expect( - optimize(css` - .a { - + .b { - --x: 1; - } - > .c { - --x: 2; - } - ~ .d { - --x: 3; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a + .b { - --x: 1; - } - .a > .c { - --x: 2; - } - .a ~ .d { - --x: 3; - } - " - `) - }) - - it('should replace the first rule, that contains `&` with `:scope`', async () => { - expect( - optimize(css` - /* Standalone */ - & { - --x: 1; - } - - /* In an at-rule */ - @supports (--y: 1) { - & { - --x: 2; - } - } - - /* With :is(…) */ - :is(&) { - --x: 3; - } - - /* In an at-rule, with :is(…) */ - @supports (--y: 2) { - :is(&) { - --x: 4; - } - } - - /* With multiple selectors */ - &, - .a { - --x: 5; - } - - /* With multiple selectors + :is(…) */ - :is(&), - .b { - --x: 6; - } - - /* With multiple selectors in an at-rule */ - @supports (--y: 3) { - &, - .c { - --x: 7; - } - } - - /* With multiple selectors in an at-rule + :is(…) */ - @supports (--y: 4) { - :is(&), - .d { - --x: 8; - } - } - `), - ).toMatchInlineSnapshot(` - " - :scope { - --x: 1; - } - @supports (--y: 1) { - :scope { - --x: 2; - } - } - :is(:scope) { - --x: 3; - } - @supports (--y: 2) { - :is(:scope) { - --x: 4; - } - } - :scope, .a { - --x: 5; - } - :is(:scope), .b { - --x: 6; - } - @supports (--y: 3) { - :scope, .c { - --x: 7; - } - } - @supports (--y: 4) { - :is(:scope), .d { - --x: 8; - } - } - " - `) - }) - - it('should be possible to use `&` to explicitly match the parent', async () => { - expect( - optimize(css` - .a { - & + .b { - --x: 1; - } - & > .c { - --x: 2; - } - & ~ .d { - --x: 3; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a + .b { - --x: 1; - } - .a > .c { - --x: 2; - } - .a ~ .d { - --x: 3; - } - " - `) - }) - - it('should be possible to use `&` in a different location', async () => { - expect( - optimize(css` - .a { - .b & { - --x: 1; - } - .c + & { - --x: 2; - } - .d > & { - --x: 3; - } - .e ~ & { - --x: 4; - } - } - `), - ).toMatchInlineSnapshot(` - " - .b .a { - --x: 1; - } - .c + .a { - --x: 2; - } - .d > .a { - --x: 3; - } - .e ~ .a { - --x: 4; - } - " - `) - }) - - it('should be possible to use `&` on its own', async () => { - expect( - optimize(css` - .a { - & { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a { - --x: 1; - } - " - `) - }) - - it('should be possible to use `&` nested in `:is(…)`', async () => { - expect( - optimize(css` - .a { - :is(&) { - --x: 1; - } - :is(:is(:is(&))) { - --x: 2; - } - } - `), - ).toMatchInlineSnapshot(` - " - :is(.a) { - --x: 1; - } - :is(:is(:is(.a))) { - --x: 2; - } - " - `) - }) - - it('should be possible to handle nesting with a parent selector list', async () => { - expect( - optimize(css` - .a, - .b { - .c, - .d & { - --x: 1; - &:hover { - --x: 2; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - :is(.a, .b) .c, .d :is(.a, .b) { - --x: 1; - &:hover { - --x: 2; - } - } - " - `) - }) - - it('should not replace `\&`', () => { - expect( - optimize(css` - .a { - .b-\& { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a .b-\\& { - --x: 1; - } - " - `) - }) - - it('should not replace `&` as part of a string', () => { - expect( - optimize(css` - .a { - [data-b='c&d'] { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a [data-b='c&d'] { - --x: 1; - } - " - `) - }) - - it.each([ - ['element', '&element', ':is(element)element'], // Invalid CSS - ['element', 'element&', 'element:is(element)'], - ['element', '&.class', 'element.class'], - ['element', '.class&', '.class:is(element)'], // Optimization: element.class - ['element', '&#id', 'element#id'], - ['element', '#id&', '#id:is(element)'], // Optimization: element#id - ['element', '&:hover', 'element:hover'], - ['element', ':hover&', ':hover:is(element)'], - ['element', '&::before', 'element::before'], - ['element', '::before&', '::before:is(element)'], // Invalid CSS - ['element', '&:not(.x)', 'element:not(.x)'], - ['element', ':not(.x)&', ':not(.x):is(element)'], // Optimization: element:not(.x) - ['element', '&[attribute]', 'element[attribute]'], - ['element', '[attribute]&', '[attribute]:is(element)'], // Optimization: element[attribute] - ['element', '&*', ':is(element)*'], // Invalid CSS - ['element', '*&', 'element'], - - ['.class', '&element', ':is(.class)element'], // Invalid CSS - ['.class', 'element&', 'element.class'], - ['.class', '&.class', '.class.class'], - ['.class', '.class&', '.class.class'], - ['.class', '&#id', '.class#id'], - ['.class', '#id&', '#id.class'], - ['.class', '&:hover', '.class:hover'], - ['.class', ':hover&', ':hover.class'], - ['.class', '&::before', '.class::before'], - ['.class', '::before&', '::before.class'], // Invalid CSS - ['.class', '&:not(.x)', '.class:not(.x)'], - ['.class', ':not(.x)&', ':not(.x).class'], - ['.class', '&[attribute]', '.class[attribute]'], - ['.class', '[attribute]&', '[attribute].class'], - ['.class', '&*', ':is(.class)*'], // Invalid CSS - ['.class', '*&', '.class'], - - ['#id', '&element', ':is(#id)element'], // Invalid CSS - ['#id', 'element&', 'element#id'], - ['#id', '&.class', '#id.class'], - ['#id', '.class&', '.class#id'], - ['#id', '&#id', '#id#id'], - ['#id', '#id&', '#id#id'], - ['#id', '&:hover', '#id:hover'], - ['#id', ':hover&', ':hover#id'], - ['#id', '&::before', '#id::before'], - ['#id', '::before&', '::before#id'], // Invalid CSS - ['#id', '&:not(.x)', '#id:not(.x)'], - ['#id', ':not(.x)&', ':not(.x)#id'], - ['#id', '&[attribute]', '#id[attribute]'], - ['#id', '[attribute]&', '[attribute]#id'], - ['#id', '&*', ':is(#id)*'], // Invalid CSS - ['#id', '*&', '#id'], - - [':hover', '&element', ':is(:hover)element'], // Invalid CSS - [':hover', 'element&', 'element:hover'], - [':hover', '&.class', ':hover.class'], - [':hover', '.class&', '.class:hover'], - [':hover', '&#id', ':hover#id'], - [':hover', '#id&', '#id:hover'], - [':hover', '&:hover', ':hover:hover'], - [':hover', ':hover&', ':hover:hover'], - [':hover', '&::before', ':hover::before'], - [':hover', '::before&', '::before:hover'], - [':hover', '&:not(.x)', ':hover:not(.x)'], - [':hover', ':not(.x)&', ':not(.x):hover'], - [':hover', '&[attribute]', ':hover[attribute]'], - [':hover', '[attribute]&', '[attribute]:hover'], - [':hover', '&*', ':is(:hover)*'], // Invalid CSS - [':hover', '*&', ':hover'], - - ['::before', '&element', ':is(::before)element'], // Invalid CSS - ['::before', 'element&', 'element::before'], - ['::before', '&.class', '::before.class'], - ['::before', '.class&', '.class::before'], - ['::before', '&#id', '::before#id'], - ['::before', '#id&', '#id::before'], - ['::before', '&:hover', '::before:hover'], - ['::before', ':hover&', ':hover::before'], - ['::before', '&::before', '::before::before'], - ['::before', '::before&', '::before::before'], - ['::before', '&:not(.x)', '::before:not(.x)'], - ['::before', ':not(.x)&', ':not(.x)::before'], - ['::before', '&[attribute]', '::before[attribute]'], - ['::before', '[attribute]&', '[attribute]::before'], - ['::before', '&*', ':is(::before)*'], // Invalid CSS - ['::before', '*&', '::before'], - - [':not(.x)', '&element', ':is(:not(.x))element'], // Invalid CSS - [':not(.x)', 'element&', 'element:not(.x)'], - [':not(.x)', '&.class', ':not(.x).class'], - [':not(.x)', '.class&', '.class:not(.x)'], - [':not(.x)', '&#id', ':not(.x)#id'], - [':not(.x)', '#id&', '#id:not(.x)'], - [':not(.x)', '&:hover', ':not(.x):hover'], - [':not(.x)', ':hover&', ':hover:not(.x)'], - [':not(.x)', '&::before', ':not(.x)::before'], - [':not(.x)', '::before&', '::before:not(.x)'], - [':not(.x)', '&:not(.x)', ':not(.x):not(.x)'], - [':not(.x)', ':not(.x)&', ':not(.x):not(.x)'], - [':not(.x)', '&[attribute]', ':not(.x)[attribute]'], - [':not(.x)', '[attribute]&', '[attribute]:not(.x)'], - [':not(.x)', '&*', ':is(:not(.x))*'], // Invalid CSS - [':not(.x)', '*&', ':not(.x)'], - - ['[attribute]', '&element', ':is([attribute])element'], // Invalid CSS - ['[attribute]', 'element&', 'element[attribute]'], - ['[attribute]', '&.class', '[attribute].class'], - ['[attribute]', '.class&', '.class[attribute]'], - ['[attribute]', '&#id', '[attribute]#id'], - ['[attribute]', '#id&', '#id[attribute]'], - ['[attribute]', '&:hover', '[attribute]:hover'], - ['[attribute]', ':hover&', ':hover[attribute]'], - ['[attribute]', '&::before', '[attribute]::before'], - ['[attribute]', '::before&', '::before[attribute]'], - ['[attribute]', '&:not(.x)', '[attribute]:not(.x)'], - ['[attribute]', ':not(.x)&', ':not(.x)[attribute]'], - ['[attribute]', '&[attribute]', '[attribute][attribute]'], - ['[attribute]', '[attribute]&', '[attribute][attribute]'], - ['[attribute]', '&*', ':is([attribute])*'], // Invalid CSS - ['[attribute]', '*&', '[attribute]'], - - ['*', '&element', ':is(*)element'], // Invalid CSS - ['*', 'element&', 'element:is(*)'], - ['*', '&.class', '.class'], - ['*', '.class&', '.class:is(*)'], // Optimization: *.class → .class - ['*', '&#id', '#id'], - ['*', '#id&', '#id:is(*)'], // Optimization: *#id → #id - ['*', '&:hover', ':hover'], - ['*', ':hover&', ':hover:is(*)'], - ['*', '&::before', '::before'], - ['*', '::before&', '::before:is(*)'], // Invalid CSS - ['*', '&:not(.x)', ':not(.x)'], - ['*', ':not(.x)&', ':not(.x):is(*)'], // Optimization: *:not(.x) → :not(.x) - ['*', '&[attribute]', '[attribute]'], - ['*', '[attribute]&', '[attribute]:is(*)'], // Optimization: *[attribute] → [attribute] - ['*', '&*', ':is(*)*'], // Invalid CSS - ['*', '*&', '*'], - - ['&', '&element', ':is(:scope)element'], // Invalid CSS - ['&', 'element&', 'element:scope'], - ['&', '&.class', ':scope.class'], - ['&', '.class&', '.class:scope'], - ['&', '&#id', ':scope#id'], - ['&', '#id&', '#id:scope'], - ['&', '&:hover', ':scope:hover'], - ['&', ':hover&', ':hover:scope'], - ['&', '&::before', ':scope::before'], - ['&', '::before&', '::before:scope'], - ['&', '&:not(.x)', ':scope:not(.x)'], - ['&', ':not(.x)&', ':not(.x):scope'], - ['&', '&[attribute]', ':scope[attribute]'], - ['&', '[attribute]&', '[attribute]:scope'], - ['&', '&*', ':is(:scope)*'], // Invalid CSS - ['&', '*&', ':scope'], - ])(`'%s { %s }' → '%s' (%#)`, async (root, nested, expected) => { - let optimized = optimize(toCss([rule(root, [rule(nested, [decl('--x', '0')])])])) - let ast = CSS.parse(optimized) - - let count = 0 - walk(ast, () => void count++) - - // 1 rule, 1 declaration - expect(count).toBe(2) - - if (ast[0].kind !== 'rule') throw new Error('expected a rule') - expect(ast[0].selector).toEqual(expected) - }) - - it('should not remove the `*` namespace from namespaced selectors', () => { - expect( - optimize(css` - .a { - & *|div { - color: red; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a *|div { - color: red; - } - " - `) - }) - - test('multiple selectors in the list are relative to the parent', async () => { - expect( - optimize(css` - .a, - .b { - --x: 1; - + .c { - --x: 2; - } - &.d { - --x: 3; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a, .b { - --x: 1; - + .c { - --x: 2; - } - &.d { - --x: 3; - } - } - " - `) - }) - - it('should be possible to use `&` multiple times', async () => { - expect( - optimize(css` - .a { - & .b & .c & .d { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a .b .a .c .a .d { - --x: 1; - } - " - `) - }) - - it('should be possible to use `&` multiple times in a row', async () => { - expect( - optimize(css` - .a { - &&& { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a.a.a { - --x: 1; - } - " - `) - }) - - it('should be possible to use `&` inside a selector', async () => { - expect( - optimize(css` - .a { - :not(&) { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - :not(.a) { - --x: 1; - } - " - `) - }) - - it('should be possible to use deeply nested CSS', async () => { - expect( - optimize(css` - .a, - .b { - --x: 1; - - .c & { - --x: 2; - - &:hover, - &:focus { - --x: 3; - .d { - --x: 4; - } - } - } - } - `), - ).toMatchInlineSnapshot(` - " - .a, .b { - --x: 1; - .c & { - --x: 2; - &:hover, &:focus { - --x: 3; - .d { - --x: 4; - } - } - } - } - " - `) - }) - - it('should properly split rules to guarantee specificity', async () => { - expect( - optimize(css` - .a { - --before: 1; - &:hover { - --inside: 1; - } - --after: 1; - } - `), - ).toMatchInlineSnapshot(` - " - .a { - --before: 1; - &:hover { - --inside: 1; - } - --after: 1; - } - " - `) - }) - - it.each([ - ['div', '&', 'div'], - ['div', '[before]&', '[before]:is(div)'], - ['div', '&[after]', 'div[after]'], - ['div', '[before]&[after]', '[before]:is(div)[after]'], - ['div', '[before] &', '[before] div'], - ['div', '& [after]', 'div [after]'], - ['div', '[before] & [after]', '[before] div [after]'], - - ['.parent', '&', '.parent'], - ['.parent', '[before]&', '[before].parent'], - ['.parent', '&[after]', '.parent[after]'], - ['.parent', '[before]&[after]', '[before].parent[after]'], - ['.parent', '[before] &', '[before] .parent'], - ['.parent', '& [after]', '.parent [after]'], - ['.parent', '[before] & [after]', '[before] .parent [after]'], - - ['.a > .b', '&', '.a > .b'], - ['.a > .b', '[before]&', '[before]:is(.a > .b)'], - ['.a > .b', '&[after]', '.a > .b[after]'], - ['.a > .b', '[before]&[after]', '[before]:is(.a > .b)[after]'], - ['.a > .b', '[before] &', '[before] :is(.a > .b)'], - ['.a > .b', '& [after]', '.a > .b [after]'], - ['.a > .b', '[before] & [after]', '[before] :is(.a > .b) [after]'], - ])(`should optimize '%s { %s }' → '%s' (%#)`, async (root, nested, expected) => { - let optimized = optimize(toCss([rule(root, [rule(nested, [decl('--x', '0')])])])) - let ast = CSS.parse(optimized) - - let count = 0 - walk(ast, () => void count++) - - // 1 rule, 1 declaration - expect(count).toBe(2) - - if (ast[0].kind !== 'rule') throw new Error('expected a rule') - expect(ast[0].selector).toEqual(expected) - }) - - it('should hoist at-rules', async () => { - expect( - optimize(css` - @layer utilities { - .a, - .b { - @media (print) { - --x: 1; - .c { - @media (min-width: 123px) { - --x: 2; - } - } - } - } - .d { - @media (print) { - @media (min-width: 123px) { - --x: 3; - } - } - } - } - @property --foo { - syntax: '*'; - } - @layer utilities { - .e { - @media (print) { - --x: 4; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @media (print) { - .a, .b { - --x: 1; - } - @media (min-width: 123px) { - :is(.a, .b) .c { - --x: 2; - } - .d { - --x: 3; - } - } - } - } - @property --foo { - syntax: '*'; - } - @layer utilities { - @media (print) { - .e { - --x: 4; - } - } - } - " - `) - }) - - it('should hoist `@scope` at-rules', async () => { - expect( - optimize(css` - @layer utilities { - .parent { - @in-variant { - @scope (.from) to (.to) { - --x: 1; - } - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @scope (.from) to (.to) { - .parent { - --x: 1; - } - } - } - " - `) - }) - - // We have an optimization where the moment we see a declaration that we - // stop handling the nesting and emit everything as-is. This won't work when - // we're dealing with `@scope` and we still want to hoist that out. - it('should hoist `@scope` at-rules, even when sibling declarations are present', async () => { - expect( - optimize(css` - @layer utilities { - .parent { - --x: 1; - @in-variant { - @scope (.from) { - .inside { - --y: 2; - } - } - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @layer utilities { - .parent { - --x: 1; - } - @scope (.from) { - .parent .inside { - --y: 2; - } - } - } - " - `) - }) - - it('should resolve `&` in the `@scope` prelude when hoisting', async () => { - // `&` in the `` selector refers to the elements matched by - // the nearest ancestor style rule, while `&` in the `` - // selector refers to the scoping root. - // - // See: https://drafts.csswg.org/css-nesting-1/#nesting-at-scope - expect( - optimize(css` - .parent { - @scope (& > .scope) to (& .limit) { - .content { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.parent > .scope) to (:where(:scope) .limit) { - .content { - --x: 1; - } - } - " - `) - - // Selector lists need `:is(…)` to keep their meaning - expect( - optimize(css` - .parent-a, - .parent-b { - @scope (& > .from) { - .inside { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (:is(.parent-a, .parent-b) > .from) { - .inside { - --x: 1; - } - } - " - `) - - // The `` selector is optional, so the first group in the - // prelude is not necessarily the `` selector. - expect( - optimize(css` - .parent { - @scope to (& .to) { - .inside { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope to (:where(:scope) .to) { - .parent .inside { - --x: 1; - } - } - " - `) - - // The parent selector is substituted with the same logic as the nesting - // selector in regular style rules: `:is(…)` is used when substituting the - // parent as-is would change the meaning (e.g. a type selector cannot - // appear after a class selector in a compound selector), … - expect( - optimize(css` - main { - @scope (.card&) { - .inside { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.card:is(main)) { - .inside { - --x: 1; - } - } - " - `) - - // … and dropped when the substitution is provably equivalent, even for - // complex parent selectors - expect( - optimize(css` - .a .b { - @scope (& > .from) { - .inside { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.a .b > .from) { - .inside { - --x: 1; - } - } - " - `) - }) - - it('should wrap bare declarations inside `@scope` in a `&` rule', async () => { - // A bug in Lightning CSS causes an error when there are declarations - // directly in the `@scope`. To fix this we will ensure a rule with - // `:where(:scope)` is available. - expect( - optimize(css` - @scope (.from) to (.to) { - --x: 1; - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.from) to (.to) { - :where(:scope) { - --x: 1; - } - } - " - `) - - // When generated by a variant, the declarations resolve against the - // parent rule: the `@scope` rule scopes _where the utility applies_ - // (like nesting `@media` does, and like variants do). - // - // Note: this deviates from native CSS nesting, where the `` - // selector would be relative to `.parent` and the declarations would - // apply to the scoping root. User-authored `@scope` rules (without the - // `@in-variant` marker) keep those native semantics. - expect( - optimize(css` - .parent { - @in-variant { - @scope (.from) { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.from) { - .parent { - --x: 1; - } - } - " - `) - }) - - it('should replace `&` with `:where(:scope)` for rules directly inside `@scope`', async () => { - // Inside an `@scope` rule, the nesting selector behaves like - // `:where(:scope)` and matches the scoping root with zero specificity. - expect( - optimize(css` - @scope (.from) { - & > p { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.from) { - :where(:scope) > p { - --x: 1; - } - } - " - `) - }) - - it('should handle `&` deeply nested in `@scope` as normal references to a parent', async () => { - expect( - optimize(css` - @scope (.from) { - &, - .inside { - --x: 1; - - &:hover { - --x: 2; - } - - &:focus { - --x: 3; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.from) { - :where(:scope), .inside { - --x: 1; - &:hover { - --x: 2; - } - &:focus { - --x: 3; - } - } - } - " - `) - }) - - it('should compile user-authored `@scope` rules with their native CSS nesting semantics', async () => { - // The `` selector is relative to the parent rule (like - // nested style rule selectors are), the rules inside are relative to - // the scoping root, and `&` refers to the scoping root - expect( - optimize(css` - .parent { - @scope (.from) to (.to) { - .inside { - --x: 1; - } - - & > .child { - --x: 2; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.parent .from) to (.to) { - .inside { - --x: 1; - } - :where(:scope) > .child { - --x: 2; - } - } - " - `) - - // A trailing `&` makes the parent rule the scoping root: the scoping - // roots are `.parent` elements inside `.from` elements - expect( - optimize(css` - .parent { - @scope (.from &) to (.to) { - .inside { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.from .parent) to (.to) { - .inside { - --x: 1; - } - } - " - `) - - // Bare declarations apply to the scoping root. They are wrapped in a - // `:where(:scope)` rule, which is the spec's exact equivalent, because - // Lightning CSS drops bare declarations inside `@scope` entirely. - expect( - optimize(css` - .parent { - @scope (.from) { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.parent .from) { - :where(:scope) { - --x: 1; - } - } - " - `) - - // Also at the top level - expect( - optimize(css` - @scope (.from) to (.to) { - --x: 1; - - .inside { - --x: 2; - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.from) to (.to) { - :where(:scope) { - --x: 1; - } - .inside { - --x: 2; - } - } - " - `) - - // Every selector in a `` selector list is relative to the - // parent rule, whether it uses `&` or not - expect( - optimize(css` - .parent { - @scope (.a, & > .b) { - .inside { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.parent .a, .parent > .b) { - .inside { - --x: 1; - } - } - " - `) - }) - - it('should leave `@property` and `@apply` alone', async () => { - expect( - optimize(css` - .foo { - .bar { - @apply text-red-500 hover:text-red-600; - } - } - - .baz { - @property --tw-content { - syntax: '*'; - initial-value: ''; - inherits: false; - } - - @property --tw-border-spacing-x { - syntax: ''; - inherits: false; - initial-value: 0; - } - } - `), - ).toMatchInlineSnapshot(` - " - .foo .bar { - @apply text-red-500 hover:text-red-600; - } - .baz { - @property --tw-content { - syntax: '*'; - initial-value: ''; - inherits: false; - } - @property --tw-border-spacing-x { - syntax: ''; - inherits: false; - initial-value: 0; - } - } - " - `) - }) - - it('should merge a body-less @layer with an @layer with the same name that has a body', async () => { - expect( - optimize(css` - @layer a; - - @layer a { - @layer b { - .x { - color: red; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @layer a { - @layer b { - .x { - color: red; - } - } - } - " - `) - }) - - it('should not get rid of `:is(…)` when the compound selector is part of a complex selector', async () => { - expect( - optimize(css` - .foo .bar { - .system &:focus { - --x: 1; - } - } - .foo:hover { - .system &:focus { - --x: 2; - } - } - `), - ).toMatchInlineSnapshot(` - " - .system :is(.foo .bar):focus { - --x: 1; - } - .system .foo:hover:focus { - --x: 2; - } - " - `) - }) - - it('should not dedupe adjacent at-rules with the same prelude but different bodies', async () => { - expect( - optimize(css` - @font-face { - font-family: 'A'; - src: url('/fonts/a.woff2'); - } - - @font-face { - font-family: 'B'; - src: url('/fonts/b.woff2'); - } - - @keyframes spin { - to { - transform: rotate(360deg); - } - } - - @keyframes spin { - to { - transform: rotate(-360deg); - } - } - `), - ).toMatchInlineSnapshot(` - " - @font-face { - font-family: 'A'; - src: url('/fonts/a.woff2'); - } - @font-face { - font-family: 'B'; - src: url('/fonts/b.woff2'); - } - @keyframes spin { - to { - transform: rotate(360deg); - } - } - @keyframes spin { - to { - transform: rotate(-360deg); - } - } - " - `) - }) + if (node.kind === 'at-rule') replaceWith(node.nodes) }) }) + +it('should only visit children once when calling `replaceWith` with multi-element array', () => { + let visited = new Set() + + let ast = [ + atRule('@media', '', [ + context({}, [ + styleRule('.foo', [decl('color', 'red')]), + styleRule('.baz', [decl('color', 'blue')]), + ]), + ]), + styleRule('.bar', [decl('color', 'green')]), + ] + + walk(ast, (node, { replaceWith }) => { + let key = id(node) + if (visited.has(key)) { + throw new Error('Visited node twice') + } + visited.add(key) + + if (node.kind === 'at-rule') replaceWith(node.nodes) + }) + + expect(visited).toMatchInlineSnapshot(` + Set { + "@media ", + ".foo", + "color: red", + ".baz", + "color: blue", + ".bar", + "color: green", + } + `) +}) + +it('should never visit children when calling `replaceWith` with `WalkAction.Skip`', () => { + let visited = new Set() + + let inner = styleRule('.foo', [decl('color', 'blue')]) + + let ast = [atRule('@media', '', [inner]), styleRule('.bar', [decl('color', 'blue')])] + + walk(ast, (node, { replaceWith }) => { + visited.add(node) + + if (node.kind === 'at-rule') { + replaceWith(node.nodes) + return WalkAction.Skip + } + }) + + expect(visited).not.toContain(inner) + expect(visited).toMatchInlineSnapshot(` + Set { + { + "kind": "at-rule", + "name": "@media", + "nodes": [ + { + "kind": "rule", + "nodes": [ + { + "important": false, + "kind": "declaration", + "property": "color", + "value": "blue", + }, + ], + "selector": ".foo", + }, + ], + "params": "", + }, + { + "kind": "rule", + "nodes": [ + { + "important": false, + "kind": "declaration", + "property": "color", + "value": "blue", + }, + ], + "selector": ".bar", + }, + { + "important": false, + "kind": "declaration", + "property": "color", + "value": "blue", + }, + } + `) +}) + +it('should skip the correct number of children based on the the replaced children nodes', () => { + { + let ast = [ + decl('--index', '0'), + decl('--index', '1'), + decl('--index', '2'), + decl('--index', '3'), + decl('--index', '4'), + ] + let visited: string[] = [] + walk(ast, (node, { replaceWith }) => { + visited.push(id(node)) + if (node.kind === 'declaration' && node.value === '2') { + replaceWith([]) + return WalkAction.Skip + } + }) + + expect(visited).toMatchInlineSnapshot(` + [ + "--index: 0", + "--index: 1", + "--index: 2", + "--index: 3", + "--index: 4", + ] + `) + } + + { + let ast = [ + decl('--index', '0'), + decl('--index', '1'), + decl('--index', '2'), + decl('--index', '3'), + decl('--index', '4'), + ] + let visited: string[] = [] + walk(ast, (node, { replaceWith }) => { + visited.push(id(node)) + if (node.kind === 'declaration' && node.value === '2') { + replaceWith([]) + return WalkAction.Continue + } + }) + + expect(visited).toMatchInlineSnapshot(` + [ + "--index: 0", + "--index: 1", + "--index: 2", + "--index: 3", + "--index: 4", + ] + `) + } + + { + let ast = [ + decl('--index', '0'), + decl('--index', '1'), + decl('--index', '2'), + decl('--index', '3'), + decl('--index', '4'), + ] + let visited: string[] = [] + walk(ast, (node, { replaceWith }) => { + visited.push(id(node)) + if (node.kind === 'declaration' && node.value === '2') { + replaceWith([decl('--index', '2.1')]) + return WalkAction.Skip + } + }) + + expect(visited).toMatchInlineSnapshot(` + [ + "--index: 0", + "--index: 1", + "--index: 2", + "--index: 3", + "--index: 4", + ] + `) + } + + { + let ast = [ + decl('--index', '0'), + decl('--index', '1'), + decl('--index', '2'), + decl('--index', '3'), + decl('--index', '4'), + ] + let visited: string[] = [] + walk(ast, (node, { replaceWith }) => { + visited.push(id(node)) + if (node.kind === 'declaration' && node.value === '2') { + replaceWith([decl('--index', '2.1')]) + return WalkAction.Continue + } + }) + + expect(visited).toMatchInlineSnapshot(` + [ + "--index: 0", + "--index: 1", + "--index: 2", + "--index: 2.1", + "--index: 3", + "--index: 4", + ] + `) + } + + { + let ast = [ + decl('--index', '0'), + decl('--index', '1'), + decl('--index', '2'), + decl('--index', '3'), + decl('--index', '4'), + ] + let visited: string[] = [] + walk(ast, (node, { replaceWith }) => { + visited.push(id(node)) + if (node.kind === 'declaration' && node.value === '2') { + replaceWith([decl('--index', '2.1'), decl('--index', '2.2')]) + return WalkAction.Skip + } + }) + + expect(visited).toMatchInlineSnapshot(` + [ + "--index: 0", + "--index: 1", + "--index: 2", + "--index: 3", + "--index: 4", + ] + `) + } + + { + let ast = [ + decl('--index', '0'), + decl('--index', '1'), + decl('--index', '2'), + decl('--index', '3'), + decl('--index', '4'), + ] + let visited: string[] = [] + walk(ast, (node, { replaceWith }) => { + visited.push(id(node)) + if (node.kind === 'declaration' && node.value === '2') { + replaceWith([decl('--index', '2.1'), decl('--index', '2.2')]) + return WalkAction.Continue + } + }) + + expect(visited).toMatchInlineSnapshot(` + [ + "--index: 0", + "--index: 1", + "--index: 2", + "--index: 2.1", + "--index: 2.2", + "--index: 3", + "--index: 4", + ] + `) + } +}) + +function id(node: AstNode) { + switch (node.kind) { + case 'at-rule': + return `${node.name} ${node.params}` + case 'rule': + return node.selector + case 'context': + return '' + case 'at-root': + return '' + case 'declaration': + return `${node.property}: ${node.value}` + case 'comment': + return `// ${node.value}` + default: + node satisfies never + throw new Error('Unknown node kind') + } +} diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index 09c3ea051..e13e19743 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -1,25 +1,15 @@ -import { Polyfills } from '.' -import * as SelectorParser from '../src/selector-parser' import { parseAtRule } from './css-parser' import type { DesignSystem } from './design-system' -import type { Source, SourceLocation } from './source-maps/source' import { Theme, ThemeOptions } from './theme' import { DefaultMap } from './utils/default-map' -import { segment } from './utils/segment' import { extractUsedVariables } from './utils/variables' -import * as ValueParser from './value-parser' -import { walk, WalkAction, type VisitContext } from './walk' const AT_SIGN = 0x40 -const PIPE = 0x7c export type StyleRule = { kind: 'rule' selector: string nodes: AstNode[] - - src?: SourceLocation - dst?: SourceLocation } export type AtRule = { @@ -27,9 +17,6 @@ export type AtRule = { name: string params: string nodes: AstNode[] - - src?: SourceLocation - dst?: SourceLocation } export type Declaration = { @@ -37,34 +24,22 @@ export type Declaration = { property: string value: string | undefined important: boolean - - src?: SourceLocation - dst?: SourceLocation } export type Comment = { kind: 'comment' value: string - - src?: SourceLocation - dst?: SourceLocation } export type Context = { kind: 'context' context: Record nodes: AstNode[] - - src?: undefined - dst?: undefined } export type AtRoot = { kind: 'at-root' nodes: AstNode[] - - src?: undefined - dst?: undefined } export type Rule = StyleRule | AtRule @@ -126,122 +101,177 @@ export function atRoot(nodes: AstNode[]): AtRoot { } } -export function cloneAstNode(node: T): T { - switch (node.kind) { - case 'rule': - return { - kind: node.kind, - selector: node.selector, - nodes: node.nodes.map(cloneAstNode), - src: node.src, - dst: node.dst, - } satisfies StyleRule as T +export const enum WalkAction { + /** Continue walking, which is the default */ + Continue, - case 'at-rule': - return { - kind: node.kind, - name: node.name, - params: node.params, - nodes: node.nodes.map(cloneAstNode), - src: node.src, - dst: node.dst, - } satisfies AtRule as T + /** Skip visiting the children of this node */ + Skip, - case 'at-root': - return { - kind: node.kind, - nodes: node.nodes.map(cloneAstNode), - src: node.src, - dst: node.dst, - } satisfies AtRoot as T + /** Stop the walk entirely */ + Stop, +} - case 'context': - return { - kind: node.kind, - context: { ...node.context }, - nodes: node.nodes.map(cloneAstNode), - src: node.src, - dst: node.dst, - } satisfies Context as T +export function walk( + ast: AstNode[], + visit: ( + node: AstNode, + utils: { + parent: AstNode | null + replaceWith(newNode: AstNode | AstNode[]): void + context: Record + path: AstNode[] + }, + ) => void | WalkAction, + path: AstNode[] = [], + context: Record = {}, +) { + for (let i = 0; i < ast.length; i++) { + let node = ast[i] + let parent = path[path.length - 1] ?? null - case 'declaration': - return { - kind: node.kind, - property: node.property, - value: node.value, - important: node.important, - src: node.src, - dst: node.dst, - } satisfies Declaration as T + // We want context nodes to be transparent in walks. This means that + // whenever we encounter one, we immediately walk through its children and + // furthermore we also don't update the parent. + if (node.kind === 'context') { + if (walk(node.nodes, visit, path, { ...context, ...node.context }) === WalkAction.Stop) { + return WalkAction.Stop + } + continue + } - case 'comment': - return { - kind: node.kind, - value: node.value, - src: node.src, - dst: node.dst, - } satisfies Comment as T + path.push(node) + let replacedNode = false + let replacedNodeOffset = 0 + let status = + visit(node, { + parent, + context, + path, + replaceWith(newNode) { + if (replacedNode) return + replacedNode = true - default: - node satisfies never - throw new Error(`Unknown node kind: ${(node as any).kind}`) + if (Array.isArray(newNode)) { + if (newNode.length === 0) { + ast.splice(i, 1) + replacedNodeOffset = 0 + } else if (newNode.length === 1) { + ast[i] = newNode[0] + replacedNodeOffset = 1 + } else { + ast.splice(i, 1, ...newNode) + replacedNodeOffset = newNode.length + } + } else { + ast[i] = newNode + replacedNodeOffset = 1 + } + }, + }) ?? WalkAction.Continue + path.pop() + + // We want to visit or skip 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. + if (replacedNode) { + if (status === WalkAction.Continue) { + i-- + } else { + i += replacedNodeOffset - 1 + } + continue + } + + // Stop the walk entirely + if (status === WalkAction.Stop) return WalkAction.Stop + + // Skip visiting the children of this node + if (status === WalkAction.Skip) continue + + if ('nodes' in node) { + path.push(node) + let result = walk(node.nodes, visit, path, context) + path.pop() + + if (result === WalkAction.Stop) { + return WalkAction.Stop + } + } } } -export function cssContext( - ctx: VisitContext, -): VisitContext & { context: Record } { - return { - depth: ctx.depth, - index: ctx.index, - siblings: ctx.siblings, - get context() { - let context: Record = {} - for (let child of ctx.path()) { - if (child.kind === 'context') { - Object.assign(context, child.context) +// This is a depth-first traversal of the AST +export function walkDepth( + ast: AstNode[], + visit: ( + node: AstNode, + utils: { + parent: AstNode | null + path: AstNode[] + context: Record + replaceWith(newNode: AstNode[]): void + }, + ) => void, + path: AstNode[] = [], + context: Record = {}, +) { + for (let i = 0; i < ast.length; i++) { + let node = ast[i] + let parent = path[path.length - 1] ?? null + + if (node.kind === 'rule' || node.kind === 'at-rule') { + path.push(node) + walkDepth(node.nodes, visit, path, context) + path.pop() + } else if (node.kind === 'context') { + walkDepth(node.nodes, visit, path, { ...context, ...node.context }) + continue + } + + path.push(node) + visit(node, { + parent, + context, + path, + replaceWith(newNode) { + if (Array.isArray(newNode)) { + if (newNode.length === 0) { + ast.splice(i, 1) + } else if (newNode.length === 1) { + ast[i] = newNode[0] + } else { + ast.splice(i, 1, ...newNode) + } + } else { + ast[i] = newNode } - } - // Once computed, we never need to compute this again - Object.defineProperty(this, 'context', { value: context }) - return context - }, - get parent() { - let parent = (this.path().pop() as Extract) ?? null - - // Once computed, we never need to compute this again - Object.defineProperty(this, 'parent', { value: parent }) - return parent - }, - path() { - return ctx.path().filter((n) => n.kind !== 'context') - }, + // Skip over the newly inserted nodes (being depth-first it doesn't make sense to visit them) + i += newNode.length - 1 + }, + }) + path.pop() } } // Optimize the AST for printing where all the special nodes that require custom // handling are handled such that the printing is a 1-to-1 transformation. -export function optimizeAst( - ast: AstNode[], - designSystem: DesignSystem, - polyfills: Polyfills = Polyfills.All, -) { +export function optimizeAst(ast: AstNode[], designSystem: DesignSystem) { let atRoots: AstNode[] = [] let seenAtProperties = new Set() - let cssThemeVariables = new DefaultMap>(() => new Set()) - let colorMixDeclarations = new DefaultMap>(() => new Set()) + let cssThemeVariables = new DefaultMap< + Extract['nodes'], + Set + >(() => new Set()) let keyframes = new Set() let usedKeyframeNames = new Set() - let propertyFallbacksRoot: Declaration[] = [] - let propertyFallbacksUniversal: Declaration[] = [] - let variableDependencies = new DefaultMap>(() => new Set()) function transform( node: AstNode, - parent: AstNode[], + parent: Extract['nodes'], context: Record = {}, depth = 0, ) { @@ -280,20 +310,8 @@ export function optimizeAst( // Track used animation names if (node.property === 'animation') { - for (let keyframeName of extractKeyframeNames(node.value)) { + for (let keyframeName of extractKeyframeNames(node.value)) usedKeyframeNames.add(keyframeName) - } - } - - // Create fallback values for usages of the `color-mix(…)` function that reference variables - // found in the theme config. - if ( - polyfills & Polyfills.ColorMix && - !context.supportsColorMix && - !context.keyframes && - node.value.includes('color-mix(') - ) { - colorMixDeclarations.get(parent).add(node) } parent.push(node) @@ -301,14 +319,26 @@ export function optimizeAst( // Rule else if (node.kind === 'rule') { - let nodes: AstNode[] = [] - - for (let child of node.nodes) { - transform(child, nodes, context, depth + 1) + // Rules with `&` as the selector should be flattened + if (node.selector === '&') { + for (let child of node.nodes) { + let nodes: AstNode[] = [] + transform(child, nodes, context, depth + 1) + if (nodes.length > 0) { + parent.push(...nodes) + } + } } - if (nodes.length > 0) { - parent.push({ ...node, nodes }) + // + else { + let copy = { ...node, nodes: [] } + for (let child of node.nodes) { + transform(child, copy.nodes, context, depth + 1) + } + if (copy.nodes.length > 0) { + parent.push(copy) + } } } @@ -319,32 +349,6 @@ export function optimizeAst( return } - // Collect fallbacks for `@property` rules for Firefox support We turn these into rules on - // `:root` or `*` and some pseudo-elements based on the value of `inherits` - if (polyfills & Polyfills.AtProperty) { - let property = node.params - let initialValue = null - let inherits = false - - for (let prop of node.nodes) { - if (prop.kind !== 'declaration') continue - if (prop.property === 'initial-value') { - initialValue = prop.value - } else if (prop.property === 'inherits') { - inherits = prop.value === 'true' - } - } - - let fallback = decl(property, initialValue ?? 'initial') - fallback.src = node.src - - if (inherits) { - propertyFallbacksRoot.push(fallback) - } else { - propertyFallbacksUniversal.push(fallback) - } - } - seenAtProperties.add(node.params) let copy = { ...node, nodes: [] } @@ -358,8 +362,6 @@ export function optimizeAst( else if (node.kind === 'at-rule') { if (node.name === '@keyframes') { context = { ...context, keyframes: true } - } else if (node.name === '@supports' && node.params.includes('color-mix(')) { - context = { ...context, supportsColorMix: true } } let copy = { ...node, nodes: [] } @@ -367,7 +369,8 @@ export function optimizeAst( transform(child, copy.nodes, context, depth + 1) } - // Only track `@keyframes` that could be removed, when they were defined inside of a `@theme`. + // Only track `@keyframes` that could be removed, when they were defined + // inside of a `@theme`. if (node.name === '@keyframes' && context.theme) { keyframes.add(copy) } @@ -378,8 +381,7 @@ export function optimizeAst( copy.name === '@charset' || copy.name === '@custom-media' || copy.name === '@namespace' || - copy.name === '@import' || - copy.name === '@apply' + copy.name === '@import' ) { parent.push(copy) } @@ -401,21 +403,6 @@ export function optimizeAst( // Remove reference imports from printing if (node.context.reference) { return - } - - // Preserve context nodes that track the source of their subtree (variant - // bodies and the user content slotted into them), so `handleNesting` - // knows whether to compose `@scope` rules with the variant semantics or - // their native CSS nesting semantics - else if (node.context.source) { - let copy = { ...node, nodes: [] as AstNode[] } - for (let child of node.nodes) { - transform(child, copy.nodes, { ...context, ...node.context }, depth) - } - - if (copy.nodes.length > 0) { - parent.push(copy) - } } else { for (let child of node.nodes) { transform(child, parent, { ...context, ...node.context }, depth) @@ -504,1146 +491,10 @@ export function optimizeAst( } } - newAst = newAst.concat(atRoots) - - // Fallbacks - // Create fallback values for usages of the `color-mix(…)` function that reference variables - // found in the theme config. - if (polyfills & Polyfills.ColorMix) { - for (let [parent, declarations] of colorMixDeclarations) { - for (let declaration of declarations) { - let idx = parent.indexOf(declaration) - // If the declaration is no longer present, we don't need to create a polyfill anymore - if (idx === -1 || declaration.value == null) continue - - let ast = ValueParser.parse(declaration.value) - let requiresPolyfill = false - walk(ast, (node) => { - if (node.kind !== 'function' || node.value !== 'color-mix') return - - let containsUnresolvableVars = false - let containsCurrentcolor = false - walk(node.nodes, (node) => { - if (node.kind == 'word' && node.value.toLowerCase() === 'currentcolor') { - containsCurrentcolor = true - requiresPolyfill = true - return - } - - let varNode: ValueParser.ValueAstNode | null = node - let inlinedColor: string | null = null - let seenVariables = new Set() - do { - if (varNode.kind !== 'function' || varNode.value !== 'var') return - let firstChild = varNode.nodes[0] - if (!firstChild || firstChild.kind !== 'word') return - - let variableName = firstChild.value - - if (seenVariables.has(variableName)) { - containsUnresolvableVars = true - return - } - - seenVariables.add(variableName) - - requiresPolyfill = true - - inlinedColor = designSystem.theme.resolveValue(null, [firstChild.value as any]) - if (!inlinedColor) { - containsUnresolvableVars = true - return - } - if (inlinedColor.toLowerCase() === 'currentcolor') { - containsCurrentcolor = true - return - } - - if (inlinedColor.startsWith('var(')) { - let subAst = ValueParser.parse(inlinedColor) - varNode = subAst[0] - } else { - varNode = null - } - } while (varNode) - - return WalkAction.Replace({ kind: 'word', value: inlinedColor } as const) - }) - - if (containsUnresolvableVars || containsCurrentcolor) { - let separatorIndex = node.nodes.findIndex( - (node) => node.kind === 'separator' && node.value.trim().includes(','), - ) - if (separatorIndex === -1) return - let firstColorValue = - node.nodes.length > separatorIndex ? node.nodes[separatorIndex + 1] : null - if (!firstColorValue) return - return WalkAction.Replace(firstColorValue) - } else if (requiresPolyfill) { - // Change the colorspace to `srgb` since the fallback values should not be represented as - // `oklab(…)` functions again as their support in Safari <16 is very limited. - let colorspace = node.nodes[2] - if ( - colorspace.kind === 'word' && - (colorspace.value === 'oklab' || - colorspace.value === 'oklch' || - colorspace.value === 'lab' || - colorspace.value === 'lch') - ) { - colorspace.value = 'srgb' - } - } - }) - if (!requiresPolyfill) continue - - let fallback = { - ...declaration, - value: ValueParser.toCss(ast), - } - let colorMixQuery = rule('@supports (color: color-mix(in lab, red, red))', [declaration]) - colorMixQuery.src = declaration.src - parent.splice(idx, 1, fallback, colorMixQuery) - } - } - } - - if (polyfills & Polyfills.AtProperty) { - let fallbackAst = [] - - if (propertyFallbacksRoot.length > 0) { - let wrapper = rule(':root, :host', propertyFallbacksRoot) - wrapper.src = propertyFallbacksRoot[0].src - fallbackAst.push(wrapper) - } - - if (propertyFallbacksUniversal.length > 0) { - let wrapper = rule('*, ::before, ::after, ::backdrop', propertyFallbacksUniversal) - wrapper.src = propertyFallbacksUniversal[0].src - fallbackAst.push(wrapper) - } - - if (fallbackAst.length > 0) { - // Insert an empty `@layer properties;` at the beginning of the document. We need to place it - // after eventual external imports as `lightningcss` would otherwise move the content into the - // same place. - let firstValidNodeIndex = newAst.findIndex((node) => { - // License comments - if (node.kind === 'comment') return false - - if (node.kind === 'at-rule') { - // Charset - if (node.name === '@charset') return false - - // External imports - if (node.name === '@import') return false - } - - return true - }) - - let layerPropertiesStatement = atRule('@layer', 'properties', []) - layerPropertiesStatement.src = fallbackAst[0].src - - newAst.splice( - firstValidNodeIndex < 0 ? newAst.length : firstValidNodeIndex, - 0, - layerPropertiesStatement, - ) - - let block = rule('@layer properties', [ - atRule( - '@supports', - // We can't write a supports query for `@property` directly so we have to test for - // features that are added around the same time in Mozilla and Safari. - '((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b))))', - fallbackAst, - ), - ]) - - block.src = fallbackAst[0].src - block.nodes[0].src = fallbackAst[0].src - - newAst.push(block) - } - } - - return handleNesting(newAst) + return newAst.concat(atRoots) } -export function handleNesting(ast: AstNode[]): AstNode[] { - let parseSelectorCache = new DefaultMap(SelectorParser.parse) - - // Nodes with an `@scope` rule somewhere in their subtree. These cannot use - // the "emit as-is" fast path for rules containing declarations, because - // `@scope` rules change meaning when they stay nested inside a style rule and - // must go through the hoisting machinery instead. - let containsAtScope = new Set() - { - function scan(nodes: AstNode[]): boolean { - let found = false - for (let node of nodes) { - if (node.kind === 'declaration' || node.kind === 'comment') continue - - if (scan(node.nodes) || (node.kind === 'at-rule' && node.name === '@scope')) { - containsAtScope.add(node) - found = true - } - } - return found - } - scan(ast) - } - - // Track `rule` selectors as we go - let selectorStack: [ - selector: string, - src: SourceLocation | undefined, - dst: SourceLocation | undefined, - ][] = [] - - // Track `at-rule` information as we go. Tracking this separately from the - // selector stack for rules such that we can hoist this above all the rules. - let atRuleStack: [ - name: string, - params: string, - src: SourceLocation | undefined, - dst: SourceLocation | undefined, - ][] = [] - - // The current "nodes" we can push to - let nodes = null as AstNode[] | null - - // Optimization: Track the declaration properties we've seen in the current - // nodes. - let seenDeclarationProperties = new Set() - - // Track nodes lists where we want to dedupe declarations - let dedupeDeclarationsInNodes = new Set() - - // The final, new AST - let result: AstNode[] = [] - - // Track whether we should skip a node in the `exit` phase - let skipExit = new Set() - - // Track the source of the subtree we are currently in. Variant bodies are - // wrapped in a `context` node with `source: 'variant'`, and the user content - // slotted into them is wrapped in `source: 'user'`. `@scope` rules whose - // nearest source is a variant are hoisted with the variant semantics, all - // other `@scope` rules use their native CSS nesting semantics. - let sourceStack: (string | boolean)[] = [] - - // Selector stacks that are masked while inside an `@scope` rule whose - // `` selector consumed the parent selector via `&`. - // - // Once the parent selector is part of the `` selector, the rules - // inside the `@scope` are relative to the scoping root, not to the parent - // rule. Selectors in scoped style rules only match elements in scope, and the - // parent matches ancestors of the scoping root, so prefixing the rules with - // the parent selector would make them match nothing at all. - // - // E.g.: - // - // ```css - // .parent { - // @scope (& > .scope) { - // .content { - // color: red; - // } - // } - // } - // ``` - // - // becomes: - // - // ```css - // @scope (.parent > .scope) { - // .content { /* ← not `.parent .content` */ - // color: red; - // } - // } - // ``` - let maskedSelectorStacks = new Map() - - walk(ast, { - enter(node) { - switch (node.kind) { - case 'rule': { - nodes = null // Start a new level - - // First time we see a rule - if (selectorStack.length === 0) { - // A rule with a selector containing `&` should replace the `&` with - // `:scope` if there is no parent rule. - // - // Note: there could be false positives when the `&` is escaped or - // part of a string inside an attribute selector. But the - // SelectorParser will take care of that. - if (node.selector.includes('&')) { - // Inside an `@scope` rule, the nesting selector behaves like - // `:where(:scope)` and matches the scoping root with zero - // specificity. E.g.: - // - // ```css - // @scope (.checkout) { - // & > p { - // color: red; - // } - // } - // ``` - // - // becomes: - // - // ```css - // @scope (.checkout) { - // :where(:scope) > p { - // color: red; - // } - // } - // ``` - // - // See: https://drafts.csswg.org/css-nesting-1/#nesting-at-scope - let replacement = atRuleStack.some(([name]) => name === '@scope') - ? ':where(:scope)' - : ':scope' - - let ast = SelectorParser.parse(node.selector) - let changed = false - - walk(ast, (node) => { - if (node.kind === 'selector' && node.value === '&') { - changed = true - node.value = replacement - } - }) - - if (changed) { - selectorStack.push([SelectorParser.toCss(ast), node.src, node.dst]) - } else { - selectorStack.push([node.selector, node.src, node.dst]) - } - } - - // No nesting markers, track as-is - else { - selectorStack.push([node.selector, node.src, node.dst]) - } - } - - // Nested rule, ensure `&` is present in each selector. Then track the - // selector. - else { - // A rule with just `&` can be replaced by its children. Let's - // ignore this node and keep walking its children. - if (node.selector === '&') { - skipExit.add(node) - return - } - - let lastSelector = selectorStack[selectorStack.length - 1][0] - let selector = segment(node.selector, ',') - .map((selector) => substituteNestingSelector(selector, lastSelector)) - .join(', ') - selectorStack.push([selector, node.src, node.dst]) - } - - // Once we hit a rule that has at least one declaration, then we can - // stop handling the nested selectors. - // - // This ensures that browser devtools can at least show _something_ - // for a given rule. This also means that we can leverage CSS nesting - // which is better for gzip results due to increased repetition. - // - // E.g.: - // - // ```css - // .a { - // &[b] { - // color: red; - // &:hover { - // color: blue; - // } - // } - // } - // ``` - // - // ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - // - // ```css - // .a[b] { /* ← flattened */ - // color: red; /* ← saw a declaration, therefore */ - // &:hover { /* ← we keep this nested syntax */ - // color: blue; - // } - // } - // ``` - // - // The exception is when the rule contains an `@scope` rule somewhere, - // because it must go through the normal hoisting machinery instead. - // See `containsAtScope` for more details. - if ( - node.nodes.some((child) => child.kind === 'declaration') && - !containsAtScope.has(node) - ) { - // Emitting each child instead of the node itself because we do want - // to flatten the current node and its selector that is already - // pushed to the stack. - for (let child of node.nodes) emit(child) - return WalkAction.Skip - } - break - } - - case 'at-rule': { - nodes = null // Start a new level - - // `@layer` is hoistable, but when it's empty then we have to make - // sure that we still emit it because this might influence the layer - // order. We can't just get rid of it. - if (node.nodes.length === 0 && !DROPPABLE_IF_EMPTY_AT_RULES.has(node.name)) { - emit(node) - skipExit.add(node) - return WalkAction.Skip - } - - // Hoist at-rules - else if (HOISTABLE_AT_RULES.has(node.name)) { - let params = node.params - - // `@scope` is the only hoistable at-rule with selectors in its - // prelude, and the only one that changes meaning when it stays - // nested inside a style rule. Resolve the prelude and its contents - // before hoisting, using one of two composition modes: - // - // 1. Variant-generated `@scope` rules attach the parent selector - // (the utility) to the rules _inside_ the scope: - // - // ```css - // .scoped\:underline { - // @scope (.from) to (.to) { - // text-decoration-line: underline; - // } - // } - // ``` - // - // becomes: - // - // ```css - // @scope (.from) to (.to) { - // .scoped\:underline { - // text-decoration-line: underline; - // } - // } - // ``` - // This way the utility is properly sandwiched between the - // `.from` and `.to` boundaries. - // - // 2. User-authored `@scope` rules keep their native CSS nesting - // semantics: the `` selector is relative to the parent - // rule, and the rules inside are relative to the scoping root: - // - // ```css - // .parent { - // @scope (.from) { - // color: red; - // } - // } - // ``` - // - // becomes: - // - // ```css - // @scope (.parent .from) { - // :where(:scope) { - // color: red; - // } - // } - // ``` - // - // In a perfect world we leave this alone, but Lightning CSS - // seems to incorrectly transform this into a version where the - // `.parent` class is gone entirely: - // - // ```css - // @scope (.from) { - // color: red; - // } - // ``` - // - // See: https://drafts.csswg.org/css-nesting-1/#nesting-at-scope - if (node.name === '@scope') { - let isVariant = sourceStack[sourceStack.length - 1] === 'variant' - let hasParent = selectorStack.length > 0 - - // Lightning CSS bug: This snippet will error - // - // ```css - // @scope { - // --x: 1; - // } - // ``` - // - // We will wrap these bare-declarations in a `&` rule to fix the crash: - // ```css - // @scope { - // & { - // --x: 1; - // } - // } - // ``` - { - let queue: (AtRule | Context)[] = [node] - - for (let parent of queue) { - let newNodes: AstNode[] = [] - let group: AstNode[] | null = null - - for (let child of parent.nodes) { - if (child.kind === 'context') queue.push(child) - - if (child.kind === 'declaration' || child.kind === 'comment') { - if (group === null) { - group = [] - newNodes.push(styleRule('&', group)) - } - group.push(child) - } else { - group = null - newNodes.push(child) - } - } - - parent.nodes = newNodes - } - } - - if (params.includes('&') || (!isVariant && hasParent)) { - // The selector to substitute for `&` in the `` - // selector: the elements matched by the nearest ancestor style - // rule, or `:scope` when there is none. - let parentSelector = hasParent ? selectorStack[selectorStack.length - 1][0] : null - - let paramsAst = ValueParser.parse(params) - - // Whether the current `(…)` group is a `` selector. - // - // ```css - // @scope (.from) to (.to) - // ^^^^^^^ scope-start (optional) - // ^^^^^ scope-end - // - // @scope to (.to) - // ^^^^^ scope-end - // ``` - let isScopeEnd = false - - // Whether the `` selector consumed the parent - // selector - let consumedParent = false - - for (let paramNode of paramsAst) { - if (paramNode.kind === 'word' && paramNode.value === 'to') { - isScopeEnd = true - continue - } - - // A `(…)` group parses as a function call without a name - if (paramNode.kind !== 'function' || paramNode.value !== '') continue - - let selector = ValueParser.toCss(paramNode.nodes) - - // A user-authored `` selector is relative to the - // parent rule, and resolves exactly like nested style rule - // selectors do: `&` refers to the elements matched by the - // parent rule, and selectors without `&` are prefixed with - // it. - if (!isScopeEnd && !isVariant && parentSelector !== null) { - paramNode.nodes = [ - ValueParser.word( - segment(selector, ',') - .map((part) => substituteNestingSelector(part.trim(), parentSelector)) - .join(', '), - ), - ] - consumedParent = true - } - - // Otherwise, only an explicit `&` is resolved. In the - // `` selector, `&` refers to the elements - // matched by the nearest ancestor style rule (or `:scope` - // when there is none). In the `` selector, `&` - // refers to the scoping root and behaves like - // `:where(:scope)`. - else if (selector.includes('&')) { - let replacement = isScopeEnd ? ':where(:scope)' : (parentSelector ?? ':scope') - - paramNode.nodes = [ - ValueParser.word(substituteNestingSelector(selector, replacement)), - ] - if (!isScopeEnd && hasParent) consumedParent = true - } - } - - params = ValueParser.toCss(paramsAst) - - // Mask the selector stack for the `@scope` rule's subtree, so - // its rules resolve like top-level rules within the scope (and - // `&` resolves to `:where(:scope)`). The stack is restored in - // the exit phase. See `maskedSelectorStacks` for more details. - if (consumedParent) { - maskedSelectorStacks.set(node, selectorStack) - selectorStack = [] - } - } - } - - atRuleStack.push([node.name, params, node.src, node.dst]) - } - - // If we can't hoist them, emit them immediately as-is - else { - emit(node) - skipExit.add(node) - return WalkAction.Skip - } - break - } - - case 'declaration': - case 'comment': { - emit(node) - break - } - - case 'context': - if (node.context.source) sourceStack.push(node.context.source) - break - - case 'at-root': - break - - default: - node satisfies never - break - } - }, - exit(node) { - if (skipExit.delete(node)) return - - switch (node.kind) { - case 'rule': { - nodes = null - selectorStack.pop() - break - } - - case 'at-rule': { - nodes = null - atRuleStack.pop() - - // Restore the selector stack that was masked by this `@scope` rule - let maskedSelectorStack = maskedSelectorStacks.get(node) - if (maskedSelectorStack) { - selectorStack = maskedSelectorStack - maskedSelectorStacks.delete(node) - } - break - } - - case 'context': - if (node.context.source) sourceStack.pop() - break - - case 'declaration': - case 'comment': - case 'at-root': - break - - default: - node satisfies never - break - } - }, - }) - - // Dedupe declarations that we've already seen before if they match the - // `property`, `value` and `important` information. - { - for (let nodes of dedupeDeclarationsInNodes) { - let seen = new Set() - for (let i = nodes.length - 1; i >= 0; --i) { - let node = nodes[i] - if (node.kind !== 'declaration') continue - - let id = `${node.property}\0${node.value}\0${node.important}` - - if (seen.has(id)) nodes.splice(i, 1) - else seen.add(id) - } - } - } - - return result - - function emit(node: AstNode) { - // Existing nodes are available, emit into those nodes - if (nodes) { - // Optimization: track used declarations in the current node. - if (node.kind === 'declaration') { - if (seenDeclarationProperties.has(node.property)) { - dedupeDeclarationsInNodes.add(nodes) - } else { - seenDeclarationProperties.add(node.property) - } - } - - nodes.push(node) - return - } - - // Nothing available, setup a fresh node - { - // There are no parent rules or at-rules available, which means that we - // can emit the node as-is. - if (selectorStack.length === 0 && atRuleStack.length === 0) { - let target = result - let lastNode = target[target.length - 1] - - // Optimization: when the current and last node are the same, ignore the - // new node entirely otherwise we will get unnecessary duplicate - // results. - // - // We only care about at-rules with no body because some of them (such - // as `@charset` or `@layer`) need to be emitted. A normal rule that's - // empty doesn't need to be emitted. At-rules _with_ a body (such as - // `@font-face` or `@keyframes`) can share the same prelude while - // containing different bodies, so they must all be emitted. - if ( - lastNode && - lastNode.kind === 'at-rule' && - node.kind === 'at-rule' && - lastNode.nodes.length === 0 && - node.nodes.length === 0 && - lastNode.name === node.name && - lastNode.params === node.params - ) { - return - } - - result.push(node) - return - } - - // Track the new "parent" nodes - { - nodes = [node] - - // Clear out seen declarations from the previous work in progress nodes - seenDeclarationProperties.clear() - - // Track new declaration - if (node.kind === 'declaration') { - seenDeclarationProperties.add(node.property) - } - } - - // Track the new root node that we build up to store in the final AST - let root = null as AstNode | null - - let target = result - let atRuleOffset = 0 - - // Optimization: merge adjacent at-rules - // - // Figure out whether we can push our new node into a previous node that - // was already emitted. - // - // We have to make sure that the order stays the same, so therefore we - // only ever have to look at the last node that was emitted. - { - let lastNode = target[target.length - 1] - if (lastNode && lastNode.kind === 'at-rule') { - for (let i = 0; i < atRuleStack.length; i++) { - let atRule = atRuleStack[i] - if (!lastNode) break - if (lastNode.kind !== 'at-rule') break - if (lastNode.name !== atRule[0]) break - if (lastNode.params !== atRule[1]) break - - atRuleOffset++ - target = lastNode.nodes - lastNode = lastNode.nodes[lastNode.nodes.length - 1] - } - } - } - - // Build up the rule - if (selectorStack.length > 0) { - let [selector, src, dst] = selectorStack[selectorStack.length - 1] - - // Optimization: merge adjacent rules with the same selector - // - // Figure out whether we can push into an existing rule. - // - // If we have some at-rules that we have to keep into account, then we - // definitely can't. - if (atRuleStack.length - atRuleOffset <= 0) { - let lastNode = target[target.length - 1] - if (lastNode && lastNode.kind === 'rule' && lastNode.selector === selector) { - lastNode.nodes.push(...nodes) - - // Ensure that our current nodes points to the nodes of the - // `lastNode`, otherwise we will lose information. - nodes = lastNode.nodes - - // We appended a group that could contain declarations already in the - // existing rule, so let the final dedupe pass handle it once. - // - // Note: we could loop over _all_ previous nodes to figure out if we - // really want to dedupe this. But this could result in a bunch of - // duplicate work if we have `n` nodes that we want to merge - // together. - dedupeDeclarationsInNodes.add(nodes) - - // We know that we don't have to handle any more at-rules, so we can - // bail early since we just merged the nodes with the same selector. - return - } - } - - // Can't push into existing node, create a new node - root = rule(selector, nodes) - if (src || dst) Object.assign(root, { src, dst }) - } - - // Wrap in at-rules, if we can push into an existing node then we can - // ignore `offset` amount of nodes since the `root`/`nodes` will already - // point to a nested node. - for (let i = atRuleStack.length - 1; i >= atRuleOffset; --i) { - let [name, params, src, dst] = atRuleStack[i] - - root = atRule(name, params, root ? [root] : nodes) - if (src || dst) Object.assign(root, { src, dst }) - } - - // Track the root node in the AST - if (root) { - target.push(root) - } - - // We didn't build up any new root, so we can move our node directly into - // the target. This can happen when we emit a node that is not a - // declaration or a comment. - else { - target.push(...nodes) - } - } - } - - // Substitutes the `&` selector with the parent selector with `:is(…)` semantics. - // - // If the `&` is not found, we will prepend it using a descendant combinator. - // E.g.: `& ${selector}` → `${parentSelector} ${selector}` - // - // In some situations we can get rid of the `:is(…)` wrapper, but only if it's safe to do so. - function substituteNestingSelector(selector: string, parentSelector: string): string { - // Slow path: we need to replace the `&` with the parent selector. A simple - // `replaceAll(…)` won't work because a `&` could be escaped, or could be - // part of an attribute selector. - // - // Much safer to parse the selector and replace the `&` that way - if (selector.includes('&')) { - let ast = SelectorParser.parse(selector) - let changed = false - walk(ast, { - enter(node, ctx) { - if (node.kind !== 'selector' || node.value !== '&') return - - changed = true - - // Safest option: use `is(…)` semantics when substituting `&` for the - // parent selector. - node.value = `:is(${parentSelector})` - - // We should always have a parent, so this shouldn't happen - if (ctx.parent === null) return - - // Optimizations: - let parentAst = parseSelectorCache.get(parentSelector) - - // 1. If we're dealing with multiple selectors, then we know that the - // `:is(…)` needs to stay. Nothing to optimize. - if (parentAst.length === 1 && parentAst[0].kind === 'list') { - return // Keep `:is(…)` semantics - } - - // 2. We know that `&` is standalone when it's inside of a complex - // selector. E.g. `[before] & [after]` - if (ctx.parent.kind === 'complex') { - // `& [after]` - // - // `:is(…)` semantics are not required, because these are equivalent: - // - // - `:is(div) [after]` → `div [after]` - // - `:is(.x) [after]` → `.x [after]` - // - `:is([before]) [after]` → `[before] [after]` - // - `:is(.a > .b) [after]` → `.a > .b [after]` - if (ctx.index === 0) { - node.value = parentSelector - return - } - - // `[before] &` - // - // `:is(…)` semantics are required if we're dealing with a complex - // parent selector. Otherwise the meaning of the selector could - // change: - // - // - `[before] :is(div)` → `[before] div` - // - `[before] :is(.x)` → `[before] .x` - // - `[before] :is([after])` → `[before] [after]` - // - `[before] :is(.a > .b)` → `[before] :is(.a > .b)` (!) - else if (ctx.index === ctx.siblings.length - 1) { - if (parentAst[0].kind === 'complex') { - return // Keep `:is(…)` semantics - } - - node.value = parentSelector - return - } - - // `[before] & [after]` - // - // `:is(…)` semantics are required if we're dealing with a complex - // parent selector. Otherwise the meaning of the selector could - // change: - // - // - `[before] :is(div) [after]` → `[before] div [after]` - // - `[before] :is(.x) [after]` → `[before] .x [after]` - // - `[before] :is(.a > .b) [after]` → `[before] :is(.a > .b) [after]` (!) - else { - if (parentAst[0].kind === 'complex') { - return // Keep `:is(…)` semantics - } - - node.value = parentSelector - return - } - } - - // 3. We know that `&` is attached to some other selector when it's - // inside of a compound selector. - // - // E.g. `[before]&[after]` - // - // We have to be careful that our parent, when it's part of a complex - // selector, that the same rules apply - // - // E.g.: `[before] &[after]` - // ^ current node - // ^^^^^^^^ compound selector - // ^^^^^^^^^^^^^^^^^ complex selector - // - else if (ctx.parent.kind === 'compound') { - if (parentAst[0].kind === 'complex') { - let path = ctx.path() - let grandParent = path[path.length - 2] - - // When our compound parent is part of a complex selector, and it's - // not the very first node, then we can't safely get rid of the - // `:is(…)` if the last selector is a complex selector as well, - // unless the `&` maps to a single selector or compound selector. - // - // ```css - // .foo .bar { /* Complex selector */ - // .system &:focus { /* Complex selector + compound selecto*/ - // --x: 1; - // } - // } - // .foo:hover { /* Compound selector */ - // .system &:focus { /* Complex selector + compound selector */ - // --x: 2; - // } - // } - // ``` - // - // ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - // - // ```css - // .system :is(.foo .bar):focus { /* Cannot drop the `:is(…)`, otherwise `.system` and `.foo` can be swapped in the DOM */ - // --x: 1; - // } - // .system .foo:hover:focus { - // --x: 2; - // } - // ``` - if ( - grandParent && - grandParent.kind === 'complex' && - grandParent.nodes[0] !== ctx.parent - ) { - return // Keep `:is(…)` semantics - } - } - - // `&*` and `&div` are invalid CSS so these should stay invalid. They - // should be written as `*&` and `div&` instead. - if ( - ctx.siblings - .slice(ctx.index + 1) - .some( - (sibling) => - SelectorParser.isUniversalSelector(sibling) || - SelectorParser.isTypeSelector(sibling), - ) - ) { - return // Keep `:is(…)` semantics - } - - // `&[after]` - // - // `:is(…)` semantics are not required, because these are equivalent: - // - // - `:is(div)[after]` → `div[after]` - // - `:is(.x)[after]` → `.x[after]` - // - `:is([before])[after]` → `[before][after]` - // - `:is(.a > .b)[after]` → `.a > .b[after]` - if (ctx.index === 0) { - node.value = parentSelector - return - } - - // `[before]&` - // - // `:is(…)` semantics are required if we're dealing with a - // - // - complex parent selector, to prevent changing the meaning of the selector - // - a universal selector, because `*` needs to be first - // - a type selector, because `div` needs to be first - // - // - `[before]:is(div)` → `[before]:is(div)` (!) - // - `[before]:is(*)` → `[before]:is(*)` (!) - // - `[before]:is(.a > .b)` → `[before]:is(.a > .b)` (!) - // - `[before]:is(.x)` → `[before].x` - else if (ctx.index === ctx.siblings.length - 1) { - if ( - parentAst[0].kind === 'complex' || - SelectorParser.isUniversalSelector(parentAst[0]) || - SelectorParser.isTypeSelector(parentAst[0]) - ) { - return // Keep `:is(…)` semantics - } - - node.value = parentSelector - return - } - - // `[before]&[after]` - // - // `:is(…)` semantics are required if we're dealing with a - // - // - complex parent selector, to prevent changing the meaning of the selector - // - a universal selector, because `*` needs to be first - // - a type selector, because `div` needs to be first - // - // - `[before]:is(div)[after]` → `[before]:is(div)[after]` (!) - // - `[before]:is(*)[after]` → `[before]:is(*)[after]` (!) - // - `[before]:is(.a > .b)[after]` → `[before]:is(.a > .b)[after]` (!) - // - `[before]:is(.x)[after]` → `[before].x[after]` - else { - if ( - parentAst[0].kind === 'complex' || - SelectorParser.isUniversalSelector(parentAst[0]) || - SelectorParser.isTypeSelector(parentAst[0]) - ) { - return // Keep `:is(…)` semantics - } - - node.value = parentSelector - return - } - } - - // 4. When the current node is a function argument (e.g. `:not(&))`, - // then we can drop the `:is(…)` entirely. - // - // The only exception is when the parent has multiple selectors - // because then we would introduce multiple arguments. Multiple - // selectors are already handled. - else if (ctx.parent.kind === 'function') { - node.value = parentSelector - return - } - }, - exit(node, ctx) { - // Optimization: We can remove the universal selector `*` if they are - // part of a compound selector. E.g.: - // - // - `*:hover` → `:hover` - // - `*[attribute]` → `[attribute]` - // - // Except when the `*` is a namespace, e.g.: `*|div`, because `*|div` - // (any namespace) and `|div` (no namespace) have different meanings. - if ( - ctx.index === 0 && - ctx.siblings.length > 1 && - ctx.parent?.kind === 'compound' && - SelectorParser.isUniversalSelector(node) - ) { - let next = ctx.siblings[1] - if (next.kind === 'selector' && next.value.charCodeAt(0) === PIPE) { - return - } - return WalkAction.ReplaceSkip([]) - } - }, - }) - - if (changed) return SelectorParser.toCss(ast) - } - - // Fast path: we know there isn't an `&` so we can prepend the parent - // selector immediately. - let parentAst = parseSelectorCache.get(parentSelector) - return `${parentAst.length === 1 && parentAst[0].kind === 'list' ? `:is(${parentSelector})` : parentSelector} ${selector}` - } -} - -// A set of at-rules that can be hoisted to the top without any repercussions. -// Typically at-rules that rely on the environment, not parent information and -// contain other rules/declarations. -const HOISTABLE_AT_RULES = new Set([ - '@container', - '@layer', - '@media', - '@page', - '@scope', - '@starting-style', - '@supports', - '@view-transition', -]) - -// A set of at-rules that can be dropped if they don't contain any nodes. We -// don't have the distinction between an at-rule with no body, or an at-rule -// with a body that is empty right now. -const DROPPABLE_IF_EMPTY_AT_RULES = new Set([ - '@container', - '@media', - '@page', - '@scope', - '@starting-style', - '@supports', - '@view-transition', -]) - -export function toCss(ast: AstNode[], track?: boolean) { - let pos = 0 - - let source: Source = { - file: null, - code: '', - } - +export function toCss(ast: AstNode[]) { function stringify(node: AstNode, depth = 0): string { let css = '' let indent = ' '.repeat(depth) @@ -1651,70 +502,15 @@ export function toCss(ast: AstNode[], track?: boolean) { // Declaration if (node.kind === 'declaration') { css += `${indent}${node.property}: ${node.value}${node.important ? ' !important' : ''};\n` - - if (track) { - // indent - pos += indent.length - - // node.property - let start = pos - pos += node.property.length - - // `: ` - pos += 2 - - // node.value - pos += node.value?.length ?? 0 - - // !important - if (node.important) { - pos += 11 - } - - let end = pos - - // `;\n` - pos += 2 - - node.dst = [source, start, end] - } } // Rule else if (node.kind === 'rule') { css += `${indent}${node.selector} {\n` - - if (track) { - // indent - pos += indent.length - - // node.selector - let start = pos - pos += node.selector.length - - // ` ` - pos += 1 - - let end = pos - node.dst = [source, start, end] - - // `{\n` - pos += 2 - } - for (let child of node.nodes) { css += stringify(child, depth + 1) } - css += `${indent}}\n` - - if (track) { - // indent - pos += indent.length - - // `}\n` - pos += 2 - } } // AtRule @@ -1727,97 +523,22 @@ export function toCss(ast: AstNode[], track?: boolean) { // @layer base, components, utilities; // ``` if (node.nodes.length === 0) { - let css = `${indent}${node.name} ${node.params};\n` - - if (track) { - // indent - pos += indent.length - - // node.name - let start = pos - pos += node.name.length - - // ` ` - pos += 1 - - // node.params - pos += node.params.length - let end = pos - - // `;\n` - pos += 2 - - node.dst = [source, start, end] - } - - return css + return `${indent}${node.name} ${node.params};\n` } css += `${indent}${node.name}${node.params ? ` ${node.params} ` : ' '}{\n` - - if (track) { - // indent - pos += indent.length - - // node.name - let start = pos - pos += node.name.length - - if (node.params) { - // ` ` - pos += 1 - - // node.params - pos += node.params.length - } - - // ` ` - pos += 1 - - let end = pos - node.dst = [source, start, end] - - // `{\n` - pos += 2 - } - for (let child of node.nodes) { css += stringify(child, depth + 1) } - css += `${indent}}\n` - - if (track) { - // indent - pos += indent.length - - // `}\n` - pos += 2 - } } // Comment else if (node.kind === 'comment') { css += `${indent}/*${node.value}*/\n` - - if (track) { - // indent - pos += indent.length - - // The comment itself. We do this instead of just the inside because - // it seems more useful to have the entire comment span tracked. - let start = pos - pos += 2 + node.value.length + 2 - let end = pos - - node.dst = [source, start, end] - - // `\n` - pos += 1 - } } - // These should've been handled already by `optimizeAst` which + // These should've been handled already by `prepareAstForPrinting` which // means we can safely ignore them here. We return an empty string // immediately to signal that something went wrong. else if (node.kind === 'context' || node.kind === 'at-root') { @@ -1835,20 +556,20 @@ export function toCss(ast: AstNode[], track?: boolean) { let css = '' for (let node of ast) { - css += stringify(node, 0) + let result = stringify(node) + if (result !== '') { + css += result + } } - source.code = css - return css } function findNode(ast: AstNode[], fn: (node: AstNode) => boolean): AstNode[] | null { let foundPath: AstNode[] = [] - walk(ast, (node, ctx) => { + walk(ast, (node, { path }) => { if (fn(node)) { - foundPath = ctx.path() - foundPath.push(node) + foundPath = [...path] return WalkAction.Stop } }) diff --git a/packages/tailwindcss/src/at-import.test.ts b/packages/tailwindcss/src/at-import.test.ts index a19123105..32a203d51 100644 --- a/packages/tailwindcss/src/at-import.test.ts +++ b/packages/tailwindcss/src/at-import.test.ts @@ -1,13 +1,37 @@ import dedent from 'dedent' import { expect, test, vi } from 'vitest' +import type { Plugin } from './compat/plugin-api' import { compile, type Config } from './index' import plugin from './plugin' -import { compileCss, run } from './test-utils/run' +import { optimizeCss } from './test-utils/run' const css = dedent +async function run( + css: string, + { + loadStylesheet = () => Promise.reject(new Error('Unexpected stylesheet')), + loadModule = () => Promise.reject(new Error('Unexpected module')), + candidates = [], + optimize = true, + }: { + loadStylesheet?: (id: string, base: string) => Promise<{ content: string; base: string }> + loadModule?: ( + id: string, + base: string, + resourceHint: 'plugin' | 'config', + ) => Promise<{ module: Config | Plugin; base: string }> + candidates?: string[] + optimize?: boolean + }, +) { + let compiler = await compile(css, { base: '/root', loadStylesheet, loadModule }) + let result = compiler.build(candidates) + return optimize ? optimizeCss(result) : result +} + test('can resolve relative @imports', async () => { - async function loadStylesheet(id: string, base: string) { + let loadStylesheet = async (id: string, base: string) => { expect(base).toBe('/root') expect(id).toBe('./foo/bar.css') return { @@ -17,20 +41,18 @@ test('can resolve relative @imports', async () => { } `, base: '/root/foo', - path: '', } } - expect( - await compileCss( + await expect( + run( css` @import './foo/bar.css'; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - .foo { + ).resolves.toMatchInlineSnapshot(` + ".foo { color: red; } " @@ -38,14 +60,13 @@ test('can resolve relative @imports', async () => { }) test('can recursively resolve relative @imports', async () => { - async function loadStylesheet(id: string, base: string) { + let loadStylesheet = async (id: string, base: string) => { if (base === '/root' && id === './foo/bar.css') { return { content: css` @import './bar/baz.css'; `, base: '/root/foo', - path: '', } } else if (base === '/root/foo' && id === './bar/baz.css') { return { @@ -55,23 +76,21 @@ test('can recursively resolve relative @imports', async () => { } `, base: '/root/foo/bar', - path: '', } } throw new Error(`Unexpected import: ${id}`) } - expect( - await compileCss( + await expect( + run( css` @import './foo/bar.css'; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - .baz { + ).resolves.toMatchInlineSnapshot(` + ".baz { color: #00f; } " @@ -83,56 +102,52 @@ let exampleCSS = css` color: red; } ` -async function loadStylesheet(id: string) { +let loadStylesheet = async (id: string) => { if (!id.endsWith('example.css')) throw new Error('Unexpected import: ' + id) return { content: exampleCSS, base: '/root', - path: '', } } test('extracts path from @import nodes', async () => { - expect( - await compileCss( + await expect( + run( css` @import 'example.css'; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - a { + ).resolves.toMatchInlineSnapshot(` + "a { color: red; } " `) - expect( - await compileCss( + await expect( + run( css` @import './example.css'; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - a { + ).resolves.toMatchInlineSnapshot(` + "a { color: red; } " `) - expect( - await compileCss( + await expect( + run( css` @import '/example.css'; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - a { + ).resolves.toMatchInlineSnapshot(` + "a { color: red; } " @@ -140,96 +155,71 @@ test('extracts path from @import nodes', async () => { }) test('url() imports are passed-through', async () => { - expect( - await compileCss( + await expect( + run( css` @import url('example.css'); `, - { base: '/root', loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')) }, + { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false }, ), - ).toMatchInlineSnapshot(` - " - @import "example.css"; - " - `) + ).resolves.toMatchInlineSnapshot(`"@import url('example.css');"`) - expect( - await compileCss( + await expect( + run( css` @import url('./example.css'); `, - { base: '/root', loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')) }, + { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false }, ), - ).toMatchInlineSnapshot(` - " - @import "./example.css"; - " - `) + ).resolves.toMatchInlineSnapshot(`"@import url('./example.css');"`) - expect( - await compileCss( + await expect( + run( css` @import url('/example.css'); `, - { base: '/root', loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')) }, + { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false }, ), - ).toMatchInlineSnapshot(` - " - @import "/example.css"; - " - `) + ).resolves.toMatchInlineSnapshot(`"@import url('/example.css');"`) - expect( - await compileCss( + await expect( + run( css` @import url(example.css); `, - { base: '/root', loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')) }, + { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false }, ), - ).toMatchInlineSnapshot(` - " - @import "example.css"; - " - `) + ).resolves.toMatchInlineSnapshot(`"@import url(example.css);"`) - expect( - await compileCss( + await expect( + run( css` @import url(./example.css); `, - { base: '/root', loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')) }, + { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false }, ), - ).toMatchInlineSnapshot(` - " - @import "./example.css"; - " - `) + ).resolves.toMatchInlineSnapshot(`"@import url(./example.css);"`) - expect( - await compileCss( + await expect( + run( css` @import url(/example.css); `, - { base: '/root', loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')) }, + { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false }, ), - ).toMatchInlineSnapshot(` - " - @import "/example.css"; - " - `) + ).resolves.toMatchInlineSnapshot(`"@import url(/example.css);"`) }) test('handles case-insensitive @import directive', async () => { - expect( - await compileCss( + await expect( + run( css` @import 'example.css'; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - a { + ).resolves.toMatchInlineSnapshot(` + "a { color: red; } " @@ -237,16 +227,15 @@ test('handles case-insensitive @import directive', async () => { }) test('@media', async () => { - expect( - await compileCss( + await expect( + run( css` @import 'example.css' print; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @media print { + ).resolves.toMatchInlineSnapshot(` + "@media print { a { color: red; } @@ -254,16 +243,15 @@ test('@media', async () => { " `) - expect( - await compileCss( + await expect( + run( css` @import 'example.css' print, screen; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @media print, screen { + ).resolves.toMatchInlineSnapshot(` + "@media print, screen { a { color: red; } @@ -271,16 +259,15 @@ test('@media', async () => { " `) - expect( - await compileCss( + await expect( + run( css` @import 'example.css' screen and (orientation: landscape); `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @media screen and (orientation: landscape) { + ).resolves.toMatchInlineSnapshot(` + "@media screen and (orientation: landscape) { a { color: red; } @@ -288,16 +275,15 @@ test('@media', async () => { " `) - expect( - await compileCss( + await expect( + run( css` @import 'example.css' foo(bar); `, - { base: '/root', loadStylesheet }, + { loadStylesheet, optimize: false }, ), - ).toMatchInlineSnapshot(` - " - @media foo(bar) { + ).resolves.toMatchInlineSnapshot(` + "@media foo(bar) { a { color: red; } @@ -307,16 +293,15 @@ test('@media', async () => { }) test('@supports', async () => { - expect( - await compileCss( + await expect( + run( css` @import 'example.css' supports(display: grid); `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @supports (display: grid) { + ).resolves.toMatchInlineSnapshot(` + "@supports (display: grid) { a { color: red; } @@ -324,17 +309,16 @@ test('@supports', async () => { " `) - expect( - await compileCss( + await expect( + run( css` @import 'example.css' supports(display: grid) screen and (max-width: 400px); `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @supports (display: grid) { - @media screen and (max-width: 400px) { + ).resolves.toMatchInlineSnapshot(` + "@supports (display: grid) { + @media screen and (width <= 400px) { a { color: red; } @@ -343,18 +327,17 @@ test('@supports', async () => { " `) - expect( - await compileCss( + await expect( + run( css` @import 'example.css' supports((not (display: grid)) and (display: flex)) screen and (max-width: 400px); `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @supports (not (display: grid)) and (display: flex) { - @media screen and (max-width: 400px) { + ).resolves.toMatchInlineSnapshot(` + "@supports (not (display: grid)) and (display: flex) { + @media screen and (width <= 400px) { a { color: red; } @@ -363,18 +346,17 @@ test('@supports', async () => { " `) - expect( - await compileCss( + await expect( + run( // prettier-ignore css` @import 'example.css' supports((selector(h2 > p)) and (font-tech(color-COLRv1))); `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @supports selector(h2 > p) and font-tech(color-COLRv1) { + ).resolves.toMatchInlineSnapshot(` + "@supports selector(h2 > p) and font-tech(color-COLRv1) { a { color: red; } @@ -384,16 +366,15 @@ test('@supports', async () => { }) test('@layer', async () => { - expect( - await compileCss( + await expect( + run( css` @import 'example.css' layer(utilities); `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + ).resolves.toMatchInlineSnapshot(` + "@layer utilities { a { color: red; } @@ -401,16 +382,15 @@ test('@layer', async () => { " `) - expect( - await compileCss( + await expect( + run( css` @import 'example.css' layer(); `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @layer { + ).resolves.toMatchInlineSnapshot(` + "@layer { a { color: red; } @@ -421,14 +401,12 @@ test('@layer', async () => { test('supports theme(reference) imports', async () => { expect( - await run( - ['text-red-500'], + run( css` @tailwind utilities; @import 'example.css' theme(reference); `, { - base: '/root', loadStylesheet: () => Promise.resolve({ content: css` @@ -437,13 +415,12 @@ test('supports theme(reference) imports', async () => { } `, base: '', - path: '', }), + candidates: ['text-red-500'], }, ), - ).toMatchInlineSnapshot(` - " - .text-red-500 { + ).resolves.toMatchInlineSnapshot(` + ".text-red-500 { color: var(--color-red-500, red); } " @@ -451,27 +428,27 @@ test('supports theme(reference) imports', async () => { }) test('updates the base when loading modules inside nested files', async () => { - async function loadStylesheet() { - return { + let loadStylesheet = () => + Promise.resolve({ content: css` @config './nested-config.js'; @plugin './nested-plugin.js'; `, base: '/root/foo', - path: '', - } - } - using loadModule = vi.fn().mockResolvedValue({ base: '', path: '', module: () => {} }) + }) + let loadModule = vi.fn().mockResolvedValue({ base: '', module: () => {} }) expect( - await compileCss( - css` - @import './foo/bar.css'; - @config './root-config.js'; - @plugin './root-plugin.js'; - `, - { base: '/root', loadStylesheet, loadModule }, - ), + ( + await run( + css` + @import './foo/bar.css'; + @config './root-config.js'; + @plugin './root-plugin.js'; + `, + { loadStylesheet, loadModule }, + ) + ).trim(), ).toBe('') expect(loadModule).toHaveBeenNthCalledWith(1, './nested-config.js', '/root/foo', 'config') @@ -481,15 +458,13 @@ test('updates the base when loading modules inside nested files', async () => { }) test('emits the right base for @source directives inside nested files', async () => { - async function loadStylesheet() { - return { + let loadStylesheet = () => + Promise.resolve({ content: css` @source './nested/**/*.css'; `, base: '/root/foo', - path: '', - } - } + }) let compiler = await compile( css` @@ -499,25 +474,22 @@ test('emits the right base for @source directives inside nested files', async () { base: '/root', loadStylesheet }, ) - expect(compiler.sources).toEqual([ - { pattern: './nested/**/*.css', base: '/root/foo', negated: false }, - { pattern: './root/**/*.css', base: '/root', negated: false }, + expect(compiler.globs).toEqual([ + { pattern: './nested/**/*.css', base: '/root/foo' }, + { pattern: './root/**/*.css', base: '/root' }, ]) }) test('emits the right base for @source found inside JS configs and plugins from nested imports', async () => { - async function loadStylesheet() { - return { + let loadStylesheet = () => + Promise.resolve({ content: css` @config './nested-config.js'; @plugin './nested-plugin.js'; `, base: '/root/foo', - path: '', - } - } - - using loadModule = vi.fn().mockImplementation((id: string) => { + }) + let loadModule = vi.fn().mockImplementation((id: string) => { let base = id.includes('nested') ? '/root/foo' : '/root' if (id.includes('config')) { let glob = id.includes('nested') ? './nested-config/*.html' : './root-config/*.html' @@ -530,14 +502,12 @@ test('emits the right base for @source found inside JS configs and plugins from plugins: [plugin(() => {}, { content: [pluginGlob] })], } satisfies Config, base: base + '-config', - path: '', } } else { let glob = id.includes('nested') ? './nested-plugin/*.html' : './root-plugin/*.html' return { module: plugin(() => {}, { content: [glob] }), base: base + '-plugin', - path: '', } } }) @@ -551,38 +521,41 @@ test('emits the right base for @source found inside JS configs and plugins from { base: '/root', loadStylesheet, loadModule }, ) - expect(compiler.sources).toEqual([ - { pattern: './nested-plugin/*.html', base: '/root/foo-plugin', negated: false }, - { pattern: './root-plugin/*.html', base: '/root-plugin', negated: false }, + expect(compiler.globs).toEqual([ + { pattern: './nested-plugin/*.html', base: '/root/foo-plugin' }, + { pattern: './root-plugin/*.html', base: '/root-plugin' }, - { pattern: './nested-config-plugin/*.html', base: '/root/foo-config', negated: false }, - { pattern: './nested-config/*.html', base: '/root/foo-config', negated: false }, + { pattern: './nested-config-plugin/*.html', base: '/root/foo-config' }, + { pattern: './nested-config/*.html', base: '/root/foo-config' }, - { pattern: './root-config-plugin/*.html', base: '/root-config', negated: false }, - { pattern: './root-config/*.html', base: '/root-config', negated: false }, + { pattern: './root-config-plugin/*.html', base: '/root-config' }, + { pattern: './root-config/*.html', base: '/root-config' }, ]) }) test('it crashes when inside a cycle', async () => { - let input = css` - @import 'foo.css'; - ` - - async function loadStylesheet() { - return { - content: input, + let loadStylesheet = () => + Promise.resolve({ + content: css` + @import 'foo.css'; + `, base: '/root', - path: '', - } - } + }) - await expect(compileCss(input, { base: '/root', loadStylesheet })).rejects.toMatchInlineSnapshot( + expect( + run( + css` + @import 'foo.css'; + `, + { loadStylesheet }, + ), + ).rejects.toMatchInlineSnapshot( `[Error: Exceeded maximum recursion depth while resolving \`foo.css\` in \`/root\`)]`, ) }) test('resolves @reference as `@import "…" reference`', async () => { - async function loadStylesheet(id: string, base: string) { + let loadStylesheet = async (id: string, base: string) => { expect(base).toBe('/root') expect(id).toBe('./foo/bar.css') return { @@ -595,22 +568,19 @@ test('resolves @reference as `@import "…" reference`', async () => { } `, base: '/root/foo', - path: '', } } - expect( - await run( - ['text-red-500'], + await expect( + run( css` @reference './foo/bar.css'; @tailwind utilities; `, - { base: '/root', loadStylesheet }, + { loadStylesheet, candidates: ['text-red-500'] }, ), - ).toMatchInlineSnapshot(` - " - .text-red-500 { + ).resolves.toMatchInlineSnapshot(` + ".text-red-500 { color: var(--color-red-500, red); } " @@ -618,7 +588,7 @@ test('resolves @reference as `@import "…" reference`', async () => { }) test('resolves `@variant` used as `@custom-variant` inside `@reference`', async () => { - async function loadStylesheet(id: string, base: string) { + let loadStylesheet = async (id: string, base: string) => { expect(base).toBe('/root') expect(id).toBe('./foo/bar.css') return { @@ -630,22 +600,19 @@ test('resolves `@variant` used as `@custom-variant` inside `@reference`', async } `, base: '/root/foo', - path: '', } } - expect( - await run( - ['dark:flex'], + await expect( + run( css` @reference './foo/bar.css'; @tailwind utilities; `, - { base: '/root', loadStylesheet }, + { loadStylesheet, candidates: ['dark:flex'] }, ), - ).toMatchInlineSnapshot(` - " - .dark\\:flex:where([data-theme="dark"] *) { + ).resolves.toMatchInlineSnapshot(` + ".dark\\:flex:where([data-theme="dark"] *) { display: flex; } " diff --git a/packages/tailwindcss/src/at-import.ts b/packages/tailwindcss/src/at-import.ts index ba7172f8e..effd33e5b 100644 --- a/packages/tailwindcss/src/at-import.ts +++ b/packages/tailwindcss/src/at-import.ts @@ -1,29 +1,20 @@ import { Features } from '.' -import { atRule, context, type AstNode } from './ast' +import { atRule, context, walk, WalkAction, type AstNode } from './ast' import * as CSS from './css-parser' import * as ValueParser from './value-parser' -import { walk, WalkAction } from './walk' -type LoadStylesheet = ( - id: string, - basedir: string, -) => Promise<{ - path: string - base: string - content: string -}> +type LoadStylesheet = (id: string, basedir: string) => Promise<{ base: string; content: string }> export async function substituteAtImports( ast: AstNode[], base: string, loadStylesheet: LoadStylesheet, recurseCount = 0, - track = false, ) { let features = Features.None let promises: Promise[] = [] - walk(ast, (node) => { + walk(ast, (node, { replaceWith }) => { if (node.kind === 'at-rule' && (node.name === '@import' || node.name === '@reference')) { let parsed = parseImportParams(ValueParser.parse(node.params)) if (parsed === null) return @@ -54,11 +45,10 @@ export async function substituteAtImports( } let loaded = await loadStylesheet(uri, base) - let ast = CSS.parse(loaded.content, { from: track ? loaded.path : undefined }) - await substituteAtImports(ast, loaded.base, loadStylesheet, recurseCount + 1, track) + let ast = CSS.parse(loaded.content) + await substituteAtImports(ast, loaded.base, loadStylesheet, recurseCount + 1) contextNode.nodes = buildImportNodes( - node, [context({ base: loaded.base }, ast)], layer, media, @@ -67,9 +57,11 @@ export async function substituteAtImports( })(), ) + replaceWith(contextNode) + // The resolved Stylesheets already have their transitive @imports // resolved, so we can skip walking them. - return WalkAction.ReplaceSkip(contextNode) + return WalkAction.Skip } }) @@ -148,7 +140,6 @@ export function parseImportParams(params: ValueParser.ValueAstNode[]) { } function buildImportNodes( - importNode: AstNode, importedAst: AstNode[], layer: string | null, media: string | null, @@ -157,21 +148,15 @@ function buildImportNodes( let root = importedAst if (layer !== null) { - let node = atRule('@layer', layer, root) - node.src = importNode.src - root = [node] + root = [atRule('@layer', layer, root)] } if (media !== null) { - let node = atRule('@media', media, root) - node.src = importNode.src - root = [node] + root = [atRule('@media', media, root)] } if (supports !== null) { - let node = atRule('@supports', supports[0] === '(' ? supports : `(${supports})`, root) - node.src = importNode.src - root = [node] + root = [atRule('@supports', supports[0] === '(' ? supports : `(${supports})`, root)] } return root diff --git a/packages/tailwindcss/src/attribute-selector-parser.bench.ts b/packages/tailwindcss/src/attribute-selector-parser.bench.ts deleted file mode 100644 index 2d6e89590..000000000 --- a/packages/tailwindcss/src/attribute-selector-parser.bench.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { bench, describe } from 'vitest' -import * as AttributeSelectorParser from './attribute-selector-parser' - -let examples = [ - '[open]', - '[data-foo]', - '[data-state=expanded]', - '[data-state = expanded ]', - '[data-state*="expanded"]', - '[data-state*="expanded"i]', - '[data-state*=expanded i]', -] - -const ATTRIBUTE_REGEX = - /\[\s*(?[a-zA-Z_-][a-zA-Z0-9_-]*)\s*((?[*|~^$]?=)\s*(?['"])?\s*(?.*?)\4\s*(?[is])?\s*)?\]/ - -describe('parsing', () => { - bench('AttributeSelectorParser.parse', () => { - for (let example of examples) { - AttributeSelectorParser.parse(example) - } - }) - - bench('REGEX.test(…)', () => { - for (let example of examples) { - ATTRIBUTE_REGEX.exec(example) - } - }) - - bench('….match(REGEX)', () => { - for (let example of examples) { - example.match(ATTRIBUTE_REGEX) - } - }) -}) diff --git a/packages/tailwindcss/src/attribute-selector-parser.test.ts b/packages/tailwindcss/src/attribute-selector-parser.test.ts deleted file mode 100644 index 87f5f3013..000000000 --- a/packages/tailwindcss/src/attribute-selector-parser.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { parse } from './attribute-selector-parser' - -describe('parse', () => { - it.each([ - [''], - [']'], - ['[]'], - ['['], - ['="value"'], - ['data-foo]'], - ['[data-foo'], - ['[data-foo="foo]'], - ['[data-foo * = foo]'], - ['[data-foo*=]'], - ['[data-foo=value x]'], - ['[data-foo=value ix]'], - ])('should parse an invalid attribute selector (%s) as `null`', (input) => { - expect(parse(input)).toBeNull() - }) - - it.each([ - [ - '[data-foo]', - { attribute: 'data-foo', operator: null, quote: null, value: null, sensitivity: null }, - ], - [ - '[ data-foo ]', - { attribute: 'data-foo', operator: null, quote: null, value: null, sensitivity: null }, - ], - [ - // Non-ASCII code points are valid in a CSS ident - '[data-état]', - { attribute: 'data-état', operator: null, quote: null, value: null, sensitivity: null }, - ], - [ - '[data-state=expanded]', - { attribute: 'data-state', operator: '=', quote: null, value: 'expanded', sensitivity: null }, - ], - [ - '[data-state = expanded ]', - { attribute: 'data-state', operator: '=', quote: null, value: 'expanded', sensitivity: null }, - ], - [ - '[data-state*="expanded"]', - { attribute: 'data-state', operator: '*=', quote: '"', value: 'expanded', sensitivity: null }, - ], - [ - '[data-state*="expanded"i]', - { attribute: 'data-state', operator: '*=', quote: '"', value: 'expanded', sensitivity: 'i' }, - ], - [ - '[data-state*=expanded i]', - { attribute: 'data-state', operator: '*=', quote: null, value: 'expanded', sensitivity: 'i' }, - ], - ])('should parse correctly: %s', (selector, expected) => { - expect(parse(selector)).toEqual(expected) - }) - - it('should work with a real-world example', () => { - expect(parse('[data-url$=".com"i]')).toEqual({ - attribute: 'data-url', - operator: '$=', - quote: '"', - value: '.com', - sensitivity: 'i', - }) - }) -}) diff --git a/packages/tailwindcss/src/attribute-selector-parser.ts b/packages/tailwindcss/src/attribute-selector-parser.ts deleted file mode 100644 index 624043d59..000000000 --- a/packages/tailwindcss/src/attribute-selector-parser.ts +++ /dev/null @@ -1,232 +0,0 @@ -const TAB = 9 -const LINE_BREAK = 10 -const CARRIAGE_RETURN = 13 -const SPACE = 32 -const DOUBLE_QUOTE = 34 -const DOLLAR = 36 -const SINGLE_QUOTE = 39 -const ASTERISK = 42 -const EQUALS = 61 -const UPPER_I = 73 -const UPPER_S = 83 -const BACKSLASH = 92 -const CARET = 94 -const LOWER_I = 105 -const LOWER_S = 115 -const PIPE = 124 -const TILDE = 126 -const LOWER_A = 97 -const LOWER_Z = 122 -const UPPER_A = 65 -const UPPER_Z = 90 -const ZERO = 48 -const NINE = 57 -const DASH = 45 -const UNDERSCORE = 95 -const NON_ASCII = 128 - -interface AttributeSelector { - attribute: string - operator: '=' | '~=' | '|=' | '^=' | '$=' | '*=' | null - quote: '"' | "'" | null - value: string | null - sensitivity: 'i' | 's' | null -} - -export function parse(input: string): AttributeSelector | null { - // Must start with `[` and end with `]` - if (input[0] !== '[' || input[input.length - 1] !== ']') { - return null - } - - let i = 1 - let start = i - let end = input.length - 1 - - // Skip whitespace, e.g.: [ data-foo] - // ^^^ - while (isAsciiWhitespace(input.charCodeAt(i))) i++ - - // Attribute name, e.g.: [data-foo] - // ^^^^^^^^ - { - start = i - for (; i < end; i++) { - let currentChar = input.charCodeAt(i) - // Skip escaped character - if (currentChar === BACKSLASH) { - i++ - continue - } - if (currentChar >= UPPER_A && currentChar <= UPPER_Z) continue - if (currentChar >= LOWER_A && currentChar <= LOWER_Z) continue - if (currentChar >= ZERO && currentChar <= NINE) continue - if (currentChar === DASH || currentChar === UNDERSCORE) continue - // Non-ASCII code points are valid in a CSS ident, e.g.: `[data-état]` - if (currentChar >= NON_ASCII) continue - break - } - - // Must have at least one character in the attribute name - if (start === i) { - return null - } - } - let attribute = input.slice(start, i) - - // Skip whitespace, e.g.: [data-foo =value] - // ^^^ - while (isAsciiWhitespace(input.charCodeAt(i))) i++ - - // At the end, e.g.: `[data-foo]` - if (i === end) { - return { - attribute, - operator: null, - quote: null, - value: null, - sensitivity: null, - } - } - - // Operator, e.g.: [data-foo*=value] - // ^^ - let operator = null - let currentChar = input.charCodeAt(i) - if (currentChar === EQUALS) { - operator = '=' - i++ - } else if ( - (currentChar === TILDE || - currentChar === PIPE || - currentChar === CARET || - currentChar === DOLLAR || - currentChar === ASTERISK) && - input.charCodeAt(i + 1) === EQUALS - ) { - operator = input[i] + '=' - i += 2 - } else { - return null // Invalid operator - } - - // Skip whitespace, e.g.: [data-foo*= value] - // ^^^ - while (isAsciiWhitespace(input.charCodeAt(i))) i++ - - // At the end, that means that we have an operator but no valid, which is - // invalid, e.g.: `[data-foo*=]` - if (i === end) { - return null - } - - // Value, e.g.: [data-foo*=value] - // ^^^^^ - let value = '' - - // Quoted value, e.g.: [data-foo*="value"] - // ^^^^^^^ - let quote = null - currentChar = input.charCodeAt(i) - if (currentChar === SINGLE_QUOTE || currentChar === DOUBLE_QUOTE) { - quote = input[i] as '"' | "'" - i++ - - start = i - for (let j = i; j < end; j++) { - let current = input.charCodeAt(j) - // Found ending quote - if (current === currentChar) { - i = j + 1 - } - - // Skip escaped character - else if (current === BACKSLASH) { - j++ - } - } - - value = input.slice(start, i - 1) - } - - // Unquoted value, e.g.: [data-foo*=value] - // ^^^^^ - else { - start = i - // Keep going until we find whitespace or the end - while (i < end && !isAsciiWhitespace(input.charCodeAt(i))) i++ - value = input.slice(start, i) - } - - // Skip whitespace, e.g.: [data-foo*=value ] - // ^^^ - while (isAsciiWhitespace(input.charCodeAt(i))) i++ - - // At the end, e.g.: `[data-foo=value]` - if (i === end) { - return { - attribute, - operator: operator as '=' | '~=' | '|=' | '^=' | '$=' | '*=', - quote: quote as '"' | "'" | null, - value, - sensitivity: null, - } - } - - // Sensitivity, e.g.: [data-foo=value i] - // ^ - let sensitivity = null - { - switch (input.charCodeAt(i)) { - case LOWER_I: - case UPPER_I: { - sensitivity = 'i' - i++ - break - } - - case LOWER_S: - case UPPER_S: { - sensitivity = 's' - i++ - break - } - - default: - return null // Invalid sensitivity - } - } - - // Skip whitespace, e.g.: [data-foo=value i ] - // ^^^ - while (isAsciiWhitespace(input.charCodeAt(i))) i++ - - // We must be at the end now, if not, then there is an additional character - // after the sensitivity which is invalid, e.g.: [data-foo=value iX] - // ^ - if (i !== end) { - return null - } - - // Fully done - return { - attribute, - operator: operator as '=' | '~=' | '|=' | '^=' | '$=' | '*=', - quote: quote as '"' | "'" | null, - value, - sensitivity: sensitivity as 'i' | 's' | null, - } -} - -function isAsciiWhitespace(code: number): boolean { - switch (code) { - case SPACE: - case TAB: - case LINE_BREAK: - case CARRIAGE_RETURN: - return true - - default: - return false - } -} diff --git a/packages/tailwindcss/src/candidate.bench.ts b/packages/tailwindcss/src/candidate.bench.ts index 7ac0dbb0a..5b7171432 100644 --- a/packages/tailwindcss/src/candidate.bench.ts +++ b/packages/tailwindcss/src/candidate.bench.ts @@ -1,6 +1,6 @@ import { Scanner } from '@tailwindcss/oxide' -import { bench, describe } from 'vitest' -import { cloneCandidate, parseCandidate, type Candidate, type Variant } from './candidate' +import { bench } from 'vitest' +import { parseCandidate } from './candidate' import { buildDesignSystem } from './design-system' import { Theme } from './theme' @@ -8,87 +8,13 @@ import { Theme } from './theme' const root = process.env.FOLDER || process.cwd() // Auto content detection -const scanner = new Scanner({ sources: [{ base: root, pattern: '**/*', negated: false }] }) +const scanner = new Scanner({ sources: [{ base: root, pattern: '**/*' }] }) const candidates = scanner.scan() const designSystem = buildDesignSystem(new Theme()) -describe('parsing', () => { - bench('parseCandidate', () => { - for (let candidate of candidates) { - Array.from(parseCandidate(candidate, designSystem)) - } - }) -}) - -describe('Candidate cloning', async () => { - let parsedCanddiates = candidates.flatMap((candidate) => - Array.from(parseCandidate(candidate, designSystem)), - ) - - bench('structuredClone', () => { - for (let candidate of parsedCanddiates) { - structuredClone(candidate) - } - }) - - bench('cloneCandidate', () => { - for (let candidate of parsedCanddiates) { - cloneCandidate(candidate) - } - }) - - bench('cloneCandidate (spread)', () => { - for (let candidate of parsedCanddiates) { - cloneCandidateSpread(candidate) - } - }) -}) - -function cloneCandidateSpread(candidate: Candidate): Candidate { - switch (candidate.kind) { - case 'arbitrary': - return { - ...candidate, - modifier: candidate.modifier ? { ...candidate.modifier } : null, - variants: candidate.variants.map(cloneVariantSpread), - } - - case 'static': - return { ...candidate, variants: candidate.variants.map(cloneVariantSpread) } - - case 'functional': - return { - ...candidate, - value: candidate.value ? { ...candidate.value } : null, - modifier: candidate.modifier ? { ...candidate.modifier } : null, - variants: candidate.variants.map(cloneVariantSpread), - } - - default: - candidate satisfies never - throw new Error('Unknown candidate kind') +bench('parseCandidate', () => { + for (let candidate of candidates) { + Array.from(parseCandidate(candidate, designSystem)) } -} - -function cloneVariantSpread(variant: Variant): Variant { - switch (variant.kind) { - case 'arbitrary': - case 'static': - return { ...variant } - - case 'functional': - return { ...variant, modifier: variant.modifier ? { ...variant.modifier } : null } - - case 'compound': - return { - ...variant, - variant: cloneVariantSpread(variant.variant), - modifier: variant.modifier ? { ...variant.modifier } : null, - } - - default: - variant satisfies never - throw new Error('Unknown variant kind') - } -} +}) diff --git a/packages/tailwindcss/src/candidate.test.ts b/packages/tailwindcss/src/candidate.test.ts index b8f914e1b..2892f0053 100644 --- a/packages/tailwindcss/src/candidate.test.ts +++ b/packages/tailwindcss/src/candidate.test.ts @@ -1,5 +1,4 @@ -import { describe, expect, it, test } from 'vitest' -import { __unstable__loadDesignSystem } from '.' +import { expect, it } from 'vitest' import { buildDesignSystem } from './design-system' import { Theme } from './theme' import { Utilities } from './utilities' @@ -516,8 +515,6 @@ it('should not parse functional utilities with multiple modifiers', () => { utilities.functional('bg', () => []) expect(run('bg-red-1/2/3', { utilities })).toMatchInlineSnapshot(`[]`) - expect(run('bg-red-500/50/', { utilities })).toMatchInlineSnapshot(`[]`) - expect(run('bg-red-500/50//foo', { utilities })).toMatchInlineSnapshot(`[]`) }) it('should parse a utility with an arbitrary value', () => { @@ -1090,7 +1087,6 @@ it('should parse a utility with an implicit variable as the modifier using the s let utilities = new Utilities() utilities.functional('bg', () => []) - // Standard case (no underscores) expect(run('bg-red-500/(--value)', { utilities })).toMatchInlineSnapshot(` [ { @@ -1111,156 +1107,6 @@ it('should parse a utility with an implicit variable as the modifier using the s }, ] `) - - // Should preserve underscores - expect(run('bg-red-500/(--with_underscore)', { utilities })).toMatchInlineSnapshot(` - [ - { - "important": false, - "kind": "functional", - "modifier": { - "kind": "arbitrary", - "value": "var(--with_underscore)", - }, - "raw": "bg-red-500/(--with_underscore)", - "root": "bg", - "value": { - "fraction": null, - "kind": "named", - "value": "red-500", - }, - "variants": [], - }, - ] - `) - - // Should remove underscores in fallback values - expect(run('bg-red-500/(--with_underscore,fallback_value)', { utilities })) - .toMatchInlineSnapshot(` - [ - { - "important": false, - "kind": "functional", - "modifier": { - "kind": "arbitrary", - "value": "var(--with_underscore,fallback value)", - }, - "raw": "bg-red-500/(--with_underscore,fallback_value)", - "root": "bg", - "value": { - "fraction": null, - "kind": "named", - "value": "red-500", - }, - "variants": [], - }, - ] - `) - - // Should keep underscores in the CSS variable itself, but remove underscores - // in fallback values - expect(run('bg-(--a_b,c_d_var(--e_f,g_h))/(--i_j,k_l_var(--m_n,o_p))', { utilities })) - .toMatchInlineSnapshot(` - [ - { - "important": false, - "kind": "functional", - "modifier": { - "kind": "arbitrary", - "value": "var(--i_j,k l var(--m_n,o p))", - }, - "raw": "bg-(--a_b,c_d_var(--e_f,g_h))/(--i_j,k_l_var(--m_n,o_p))", - "root": "bg", - "value": { - "dataType": null, - "kind": "arbitrary", - "value": "var(--a_b,c d var(--e_f,g h))", - }, - "variants": [], - }, - ] - `) -}) - -it('should not parse an invalid arbitrary shorthand modifier', () => { - let utilities = new Utilities() - utilities.functional('bg', () => []) - - // Completely empty - expect(run('bg-red-500/()', { utilities })).toMatchInlineSnapshot(`[]`) - - // Invalid due to leading spaces - expect(run('bg-red-500/(_--)', { utilities })).toMatchInlineSnapshot(`[]`) - expect(run('bg-red-500/(_--x)', { utilities })).toMatchInlineSnapshot(`[]`) - - // Invalid due to leading spaces - expect(run('bg-red-500/(_--)', { utilities })).toMatchInlineSnapshot(`[]`) - expect(run('bg-red-500/(_--x)', { utilities })).toMatchInlineSnapshot(`[]`) - - // Invalid due to top-level `;` or `}` characters - expect(run('bg-red-500/(--x;--y)', { utilities })).toMatchInlineSnapshot(`[]`) - expect(run('bg-red-500/(--x:{foo:bar})', { utilities })).toMatchInlineSnapshot(`[]`) - - // Valid, but ensuring that we didn't make an off-by-one error - expect(run('bg-red-500/(--x)', { utilities })).toMatchInlineSnapshot(` - [ - { - "important": false, - "kind": "functional", - "modifier": { - "kind": "arbitrary", - "value": "var(--x)", - }, - "raw": "bg-red-500/(--x)", - "root": "bg", - "value": { - "fraction": null, - "kind": "named", - "value": "red-500", - }, - "variants": [], - }, - ] - `) -}) - -it('should not parse an invalid arbitrary shorthand value', () => { - let utilities = new Utilities() - utilities.functional('bg', () => []) - - // Completely empty - expect(run('bg-()', { utilities })).toMatchInlineSnapshot(`[]`) - - // Invalid due to leading spaces - expect(run('bg-(_--)', { utilities })).toMatchInlineSnapshot(`[]`) - expect(run('bg-(_--x)', { utilities })).toMatchInlineSnapshot(`[]`) - - // Invalid due to leading spaces - expect(run('bg-(_--)', { utilities })).toMatchInlineSnapshot(`[]`) - expect(run('bg-(_--x)', { utilities })).toMatchInlineSnapshot(`[]`) - - // Invalid due to top-level `;` or `}` characters - expect(run('bg-(--x;--y)', { utilities })).toMatchInlineSnapshot(`[]`) - expect(run('bg-(--x:{foo:bar})', { utilities })).toMatchInlineSnapshot(`[]`) - - // Valid, but ensuring that we didn't make an off-by-one error - expect(run('bg-(--x)', { utilities })).toMatchInlineSnapshot(` - [ - { - "important": false, - "kind": "functional", - "modifier": null, - "raw": "bg-(--x)", - "root": "bg", - "value": { - "dataType": null, - "kind": "arbitrary", - "value": "var(--x)", - }, - "variants": [], - }, - ] - `) }) it('should not parse a utility with an implicit invalid variable as the modifier using the shorthand', () => { @@ -1418,19 +1264,6 @@ it('should parse a functional variant with a modifier', () => { `) }) -it('should not parse variants with multiple modifiers', () => { - let utilities = new Utilities() - utilities.static('flex', () => []) - - let variants = new Variants() - variants.static('hover', () => {}) - variants.compound('group', Compounds.StyleRules, () => {}) - - expect(run('group-hover/foo:flex', { utilities, variants })).toHaveLength(1) - expect(run('group-hover/foo/:flex', { utilities, variants })).toEqual([]) - expect(run('group-hover/foo//bar:flex', { utilities, variants })).toEqual([]) -}) - it('should parse a functional variant starting with @', () => { let utilities = new Utilities() utilities.static('flex', () => []) @@ -1461,36 +1294,6 @@ it('should parse a functional variant starting with @', () => { `) }) -it('should parse a functional variant starting with @ that has a hyphen', () => { - let utilities = new Utilities() - utilities.static('flex', () => []) - - let variants = new Variants() - variants.functional('@', () => {}) - - expect(run('@foo-bar:flex', { utilities, variants })).toMatchInlineSnapshot(` - [ - { - "important": false, - "kind": "static", - "raw": "@foo-bar:flex", - "root": "flex", - "variants": [ - { - "kind": "functional", - "modifier": null, - "root": "@", - "value": { - "kind": "named", - "value": "foo-bar", - }, - }, - ], - }, - ] - `) -}) - it('should parse a functional variant starting with @ and a modifier', () => { let utilities = new Utilities() utilities.static('flex', () => []) @@ -1791,14 +1594,6 @@ it('should not parse compound group with a non-compoundable variant', () => { expect(run('group-*:flex', { utilities, variants })).toMatchInlineSnapshot(`[]`) }) -it('empty data types are invalid', () => { - let utilities = new Utilities() - utilities.functional('bg', () => []) - let variants = new Variants() - - expect(run('bg-[:foo]', { utilities, variants })).toMatchInlineSnapshot(`[]`) -}) - it('should parse a variant containing an arbitrary string with unbalanced parens, brackets, curlies and other quotes', () => { let utilities = new Utilities() utilities.static('flex', () => []) @@ -1995,162 +1790,3 @@ it.each([ expect(run(rawCandidate, { utilities, variants })).toEqual([]) }) - -it.each([ - // Arbitrary properties with `;` or `}` - '[color:red;color:blue]', - '[color:red}html{color:blue]', - - // Arbitrary values that end the declaration - 'bg-[red;color:blue]', - - // Arbitrary values that end the block - 'bg-[red}html{color:blue]', - - // Arbitrary variants that end the block - '[&{color:red}]:flex', - - // Arbitrary variant values that end the block - 'data-[a]{color:red}foo[a]:flex', - - // Named values contain invalid characters - 'data-foo=bar:flex', - 'bg-foo=bar', - 'bg-red-500/foo=bar', -])('should not parse invalid values: %s', (rawCandidate) => { - let utilities = new Utilities() - utilities.static('flex', () => []) - utilities.functional('bg', () => []) - - let variants = new Variants() - variants.functional('data', () => {}) - variants.compound('group', Compounds.StyleRules, () => {}) - - expect(run(rawCandidate, { utilities, variants })).toEqual([]) -}) - -const candidates = [ - // Arbitrary candidates - ['[color:red]', '[color:red]'], - ['[color:red]/50', '[color:red]/50'], - ['[color:red]/[0.5]', '[color:red]/[0.5]'], - ['[color:red]/50!', '[color:red]/50!'], - ['![color:red]/50', '[color:red]/50!'], - ['[color:red]/[0.5]!', '[color:red]/[0.5]!'], - - // Static candidates - ['box-border', 'box-border'], - ['underline!', 'underline!'], - ['!underline', 'underline!'], - ['-inset-full', '-inset-full'], - - // Functional candidates - ['bg-red-500', 'bg-red-500'], - ['bg-red-500/50', 'bg-red-500/50'], - ['bg-red-500/[0.5]', 'bg-red-500/[0.5]'], - ['bg-red-500!', 'bg-red-500!'], - ['!bg-red-500', 'bg-red-500!'], - ['bg-[#0088cc]/50', 'bg-[#0088cc]/50'], - ['bg-[#0088cc]/[0.5]', 'bg-[#0088cc]/[0.5]'], - ['bg-[#0088cc]!', 'bg-[#0088cc]!'], - ['!bg-[#0088cc]', 'bg-[#0088cc]!'], - ['bg-[var(--spacing)-1px]', 'bg-[var(--spacing)-1px]'], - ['bg-[var(--spacing)_-_1px]', 'bg-[var(--spacing)-1px]'], - ['bg-[var(--_spacing)]', 'bg-(--_spacing)'], - ['bg-(--_spacing)', 'bg-(--_spacing)'], - ['bg-[-1px_-1px]', 'bg-[-1px_-1px]'], - ['p-[round(to-zero,1px)]', 'p-[round(to-zero,1px)]'], - ['w-1/2', 'w-1/2'], - ['p-[calc((100vw-theme(maxWidth.2xl))_/_2)]', 'p-[calc((100vw-theme(maxWidth.2xl))/2)]'], - - // Keep spaces in strings - ['content-["hello_world"]', 'content-["hello_world"]'], - ['content-[____"hello_world"___]', 'content-["hello_world"]'], - - // Do not escape underscores for url() and CSS variable in var() - ['bg-[no-repeat_url(/image_13.png)]', 'bg-[no-repeat_url(/image_13.png)]'], - [ - 'bg-[var(--spacing-0_5,_var(--spacing-1_5,_3rem))]', - 'bg-(--spacing-0_5,var(--spacing-1_5,3rem))', - ], - - // Normalize whitespace in arbitrary properties - ['[display:flex]', '[display:flex]'], - ['[display:_flex]', '[display:flex]'], - ['[display:flex_]', '[display:flex]'], - ['[display:_flex_]', '[display:flex]'], - - // Normalize whitespace in `calc` expressions - ['w-[calc(100%-2rem)]', 'w-[calc(100%-2rem)]'], - ['w-[calc(100%_-_2rem)]', 'w-[calc(100%-2rem)]'], - - // Normalize the important modifier - ['!flex', 'flex!'], - ['flex!', 'flex!'], -] - -const variants = [ - ['', ''], // no variant - ['*:', '*:'], - ['focus:', 'focus:'], - ['group-focus:', 'group-focus:'], - - ['hover:focus:', 'hover:focus:'], - ['hover:group-focus:', 'hover:group-focus:'], - ['group-hover:focus:', 'group-hover:focus:'], - ['group-hover:group-focus:', 'group-hover:group-focus:'], - - ['min-[10px]:', 'min-[10px]:'], - - // Normalize spaces - ['min-[calc(1000px_+_12em)]:', 'min-[calc(1000px+12em)]:'], - ['min-[calc(1000px_+12em)]:', 'min-[calc(1000px+12em)]:'], - ['min-[calc(1000px+_12em)]:', 'min-[calc(1000px+12em)]:'], - ['min-[calc(1000px___+___12em)]:', 'min-[calc(1000px+12em)]:'], - - ['peer-[&_p]:', 'peer-[&_p]:'], - ['peer-[&_p]:hover:', 'peer-[&_p]:hover:'], - ['hover:peer-[&_p]:', 'hover:peer-[&_p]:'], - ['hover:peer-[&_p]:focus:', 'hover:peer-[&_p]:focus:'], - ['peer-[&:hover]:peer-[&_p]:', 'peer-[&:hover]:peer-[&_p]:'], - - ['[p]:', '[p]:'], - ['[_p_]:', '[p]:'], - ['has-[p]:', 'has-[p]:'], - ['has-[_p_]:', 'has-[p]:'], - - // Simplify `&:is(p)` to `p` - ['[&:is(p)]:', '[p]:'], - ['[&:is(_p_)]:', '[p]:'], - ['has-[&:is(p)]:', 'has-[p]:'], - ['has-[&:is(_p_)]:', 'has-[p]:'], - - // Handle special `@` variants. These shouldn't be printed as `@-` - ['@xl:', '@xl:'], - ['@[123px]:', '@[123px]:'], - - // Compound variants that forward modifiers - ['not-group-hover/name:', 'not-group-hover/name:'], - ['has-group-peer-hover/name:', 'has-group-peer-hover/name:'], - ['in-group-peer-hover/name:', 'in-group-peer-hover/name:'], -] - -let combinations: [string, string][] = [] - -for (let [inputVariant, outputVariant] of variants) { - for (let [inputCandidate, outputCandidate] of candidates) { - combinations.push([`${inputVariant}${inputCandidate}`, `${outputVariant}${outputCandidate}`]) - } -} - -describe('normalize candidates', () => { - test.each(combinations)('`%s` -> `%s`', async (candidate: string, result: string) => { - let designSystem = await __unstable__loadDesignSystem('@tailwind utilities', { - base: __dirname, - }) - - let [parsed] = designSystem.parseCandidate(candidate) - - expect(designSystem.printCandidate(parsed)).toEqual(result) - }) -}) diff --git a/packages/tailwindcss/src/candidate.ts b/packages/tailwindcss/src/candidate.ts index a389af365..8238dfc2f 100644 --- a/packages/tailwindcss/src/candidate.ts +++ b/packages/tailwindcss/src/candidate.ts @@ -1,19 +1,13 @@ import type { DesignSystem } from './design-system' import { decodeArbitraryValue } from './utils/decode-arbitrary-value' -import { DefaultMap } from './utils/default-map' -import { isValidArbitrary } from './utils/is-valid-arbitrary' import { segment } from './utils/segment' -import type { ValueFunctionNode } from './value-parser' -import * as ValueParser from './value-parser' -import { walk, WalkAction } from './walk' const COLON = 0x3a const DASH = 0x2d const LOWER_A = 0x61 const LOWER_Z = 0x7a -const IS_VALID_NAMED_VALUE = /^[a-zA-Z0-9_.%-]+$/ -export type ArbitraryUtilityValue = { +type ArbitraryUtilityValue = { kind: 'arbitrary' /** @@ -65,7 +59,7 @@ export type NamedUtilityValue = { fraction: string | null } -export type ArbitraryModifier = { +type ArbitraryModifier = { kind: 'arbitrary' /** @@ -77,7 +71,7 @@ export type ArbitraryModifier = { value: string } -export type NamedModifier = { +type NamedModifier = { kind: 'named' /** @@ -218,102 +212,6 @@ export type Candidate = raw: string } -export function cloneCandidate(candidate: T): T { - switch (candidate.kind) { - case 'arbitrary': - return { - kind: candidate.kind, - property: candidate.property, - value: candidate.value, - modifier: candidate.modifier - ? { kind: candidate.modifier.kind, value: candidate.modifier.value } - : null, - variants: candidate.variants.map(cloneVariant), - important: candidate.important, - raw: candidate.raw, - } satisfies Extract as T - - case 'static': - return { - kind: candidate.kind, - root: candidate.root, - variants: candidate.variants.map(cloneVariant), - important: candidate.important, - raw: candidate.raw, - } satisfies Extract as T - - case 'functional': - return { - kind: candidate.kind, - root: candidate.root, - value: candidate.value - ? candidate.value.kind === 'arbitrary' - ? { - kind: candidate.value.kind, - dataType: candidate.value.dataType, - value: candidate.value.value, - } - : { - kind: candidate.value.kind, - value: candidate.value.value, - fraction: candidate.value.fraction, - } - : null, - modifier: candidate.modifier - ? { kind: candidate.modifier.kind, value: candidate.modifier.value } - : null, - variants: candidate.variants.map(cloneVariant), - important: candidate.important, - raw: candidate.raw, - } satisfies Extract as T - - default: - candidate satisfies never - throw new Error('Unknown candidate kind') - } -} - -export function cloneVariant(variant: T): T { - switch (variant.kind) { - case 'arbitrary': - return { - kind: variant.kind, - selector: variant.selector, - relative: variant.relative, - } satisfies Extract as T - - case 'static': - return { kind: variant.kind, root: variant.root } satisfies Extract< - Variant, - { kind: 'static' } - > as T - - case 'functional': - return { - kind: variant.kind, - root: variant.root, - value: variant.value ? { kind: variant.value.kind, value: variant.value.value } : null, - modifier: variant.modifier - ? { kind: variant.modifier.kind, value: variant.modifier.value } - : null, - } satisfies Extract as T - - case 'compound': - return { - kind: variant.kind, - root: variant.root, - variant: cloneVariant(variant.variant), - modifier: variant.modifier - ? { kind: variant.modifier.kind, value: variant.modifier.value } - : null, - } satisfies Extract as T - - default: - variant satisfies never - throw new Error('Unknown variant kind') - } -} - export function* parseCandidate(input: string, designSystem: DesignSystem): Iterable { // hover:focus:underline // ^^^^^ ^^^^^^ -> Variants @@ -372,14 +270,6 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter } } - // If there's more than one modifier, the utility is invalid. - // - // E.g.: - // - // - `bg-red-500/50/50` - let parts = segment(base, '/') - if (parts.length > 2) return - // Figure out the new base and the modifier segment if present. // // E.g.: @@ -389,7 +279,14 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter // ^^^^^^^^^^ -> Base without modifier // ^^ -> Modifier segment // ``` - let [baseWithoutModifier, modifierSegment = null] = parts + let [baseWithoutModifier, modifierSegment = null, additionalModifier] = segment(base, '/') + + // If there's more than one modifier, the utility is invalid. + // + // E.g.: + // + // - `bg-red-500/50/50` + if (additionalModifier) return let parsedModifier = modifierSegment === null ? null : parseModifier(modifierSegment) @@ -429,9 +326,6 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter let property = baseWithoutModifier.slice(0, idx) let value = decodeArbitraryValue(baseWithoutModifier.slice(idx + 1)) - // Values can't contain `;` or `}` characters at the top-level. - if (!isValidArbitrary(value)) return - yield { kind: 'arbitrary', property, @@ -485,7 +379,6 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter // // E.g.: // - // ``` // bg-(--my-var) // ^^ -> Root // ^^^^^^^^^^ -> Arbitrary value @@ -512,10 +405,7 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter // An arbitrary value with `(…)` should always start with `--` since it // represents a CSS variable. - if (value[0] !== '-' || value[1] !== '-') return - - // Values can't contain `;` or `}` characters at the top-level. - if (!isValidArbitrary(value)) return + if (value[0] !== '-' && value[1] !== '-') return roots = [[root, dataType === null ? `[var(${value})]` : `[${dataType}:var(${value})]`]] } @@ -553,11 +443,8 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter let arbitraryValue = decodeArbitraryValue(value.slice(startArbitraryIdx + 1, -1)) - // Values can't contain `;` or `}` characters at the top-level. - if (!isValidArbitrary(arbitraryValue)) continue - // Extract an explicit typehint if present, e.g. `bg-[color:var(--my-var)])` - let typehint: string | null = null + let typehint = '' for (let i = 0; i < arbitraryValue.length; i++) { let code = arbitraryValue.charCodeAt(i) @@ -583,8 +470,6 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter continue } - if (typehint === '') continue - candidate.value = { kind: 'arbitrary', dataType: typehint || null, @@ -599,8 +484,6 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter ? null : `${value}/${modifierSegment}` - if (!IS_VALID_NAMED_VALUE.test(value)) continue - candidate.value = { kind: 'named', value, @@ -617,9 +500,6 @@ function parseModifier(modifier: string): CandidateModifier | null { if (modifier[0] === '[' && modifier[modifier.length - 1] === ']') { let arbitraryValue = decodeArbitraryValue(modifier.slice(1, -1)) - // Values can't contain `;` or `}` characters at the top-level. - if (!isValidArbitrary(arbitraryValue)) return null - // Empty arbitrary values are invalid. E.g.: `data-[]:` // ^^ if (arbitraryValue.length === 0 || arbitraryValue.trim().length === 0) return null @@ -631,29 +511,21 @@ function parseModifier(modifier: string): CandidateModifier | null { } if (modifier[0] === '(' && modifier[modifier.length - 1] === ')') { - // Drop the `(` and `)` characters - modifier = modifier.slice(1, -1) + let arbitraryValue = decodeArbitraryValue(modifier.slice(1, -1)) - // A modifier with `(…)` should always start with `--` since it - // represents a CSS variable. - if (modifier[0] !== '-' || modifier[1] !== '-') return null + // Empty arbitrary values are invalid. E.g.: `data-():` + // ^^ + if (arbitraryValue.length === 0 || arbitraryValue.trim().length === 0) return null - // Values can't contain `;` or `}` characters at the top-level. - if (!isValidArbitrary(modifier)) return null - - // Wrap the value in `var(…)` to ensure that it is a valid CSS variable. - modifier = `var(${modifier})` - - let arbitraryValue = decodeArbitraryValue(modifier) + // Arbitrary values must start with `--` since it represents a CSS variable. + if (arbitraryValue[0] !== '-' && arbitraryValue[1] !== '-') return null return { kind: 'arbitrary', - value: arbitraryValue, + value: `var(${arbitraryValue})`, } } - if (!IS_VALID_NAMED_VALUE.test(modifier)) return null - return { kind: 'named', value: modifier, @@ -680,9 +552,6 @@ export function parseVariant(variant: string, designSystem: DesignSystem): Varia let selector = decodeArbitraryValue(variant.slice(1, -1)) - // Values can't contain `;` or `}` characters at the top-level. - if (!isValidArbitrary(selector)) return null - // Empty arbitrary values are invalid. E.g.: `[]:` // ^^ if (selector.length === 0 || selector.trim().length === 0) return null @@ -708,18 +577,17 @@ export function parseVariant(variant: string, designSystem: DesignSystem): Varia // 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` - let parts = segment(variant, '/') - if (parts.length > 2) return null - - // group-hover/group-name - // ^^^^^^^^^^^ -> Variant without modifier - // ^^^^^^^^^^ -> Modifier - let [variantWithoutModifier, modifier = null] = parts + if (additionalModifier) return null let roots = findRoots(variantWithoutModifier, (root) => { return designSystem.variants.has(root) @@ -761,9 +629,6 @@ export function parseVariant(variant: string, designSystem: DesignSystem): Varia let arbitraryValue = decodeArbitraryValue(value.slice(1, -1)) - // Values can't contain `;` or `}` characters at the top-level. - if (!isValidArbitrary(arbitraryValue)) return null - // Empty arbitrary values are invalid. E.g.: `data-[]:` // ^^ if (arbitraryValue.length === 0 || arbitraryValue.trim().length === 0) return null @@ -785,15 +650,12 @@ export function parseVariant(variant: string, designSystem: DesignSystem): Varia let arbitraryValue = decodeArbitraryValue(value.slice(1, -1)) - // Values can't contain `;` or `}` characters at the top-level. - if (!isValidArbitrary(arbitraryValue)) return null - // Empty arbitrary values are invalid. E.g.: `data-():` // ^^ if (arbitraryValue.length === 0 || arbitraryValue.trim().length === 0) return null // Arbitrary values must start with `--` since it represents a CSS variable. - if (arbitraryValue[0] !== '-' || arbitraryValue[1] !== '-') return null + if (arbitraryValue[0] !== '-' && arbitraryValue[1] !== '-') return null return { kind: 'functional', @@ -806,8 +668,6 @@ export function parseVariant(variant: string, designSystem: DesignSystem): Varia } } - if (!IS_VALID_NAMED_VALUE.test(value)) continue - return { kind: 'functional', root, @@ -819,13 +679,6 @@ export function parseVariant(variant: string, designSystem: DesignSystem): Varia case 'compound': { if (value === null) return null - // Forward the modifier of the compound variants to its subVariant. - // This allows for `not-group-hover/name:flex` to work. - if (modifier && (root === 'not' || root === 'has' || root === 'in')) { - value = `${value}/${modifier}` - modifier = null - } - let subVariant = designSystem.parseVariant(value) if (subVariant === null) return null @@ -870,6 +723,14 @@ function* findRoots(input: string, exists: (input: string) => boolean): Iterable // 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] === '@' && exists('@')) { + yield ['@', input.slice(1)] + } + return + } // Determine the root and value by testing permutations of the incoming input. // @@ -878,7 +739,7 @@ function* findRoots(input: string, exists: (input: string) => boolean): Iterable // `bg-red-500` -> No match // `bg-red` -> No match // `bg` -> Match - while (idx > 0) { + do { let maybeRoot = input.slice(0, idx) if (exists(maybeRoot)) { @@ -889,351 +750,9 @@ function* findRoots(input: string, exists: (input: string) => boolean): Iterable // can skip any further parsing. if (root[1] === '') break - // Edge case: `@-…` is not valid as a variant or a utility so we want to - // skip if an `@` is followed by a `-`. Otherwise `@-2xl:flex` and - // `@-2xl:flex` would be considered the same. - if (root[0] === '@' && exists('@') && input[idx] === '-') break - yield root } idx = input.lastIndexOf('-', idx - 1) - } - - // Try '@' variant after permutations. This allows things like `@max` of `@max-foo-bar` - // to match before looking for `@`. - if (input[0] === '@' && exists('@')) { - yield ['@', input.slice(1)] - } -} - -export function printCandidate(designSystem: DesignSystem, candidate: Candidate) { - let parts: string[] = [] - - for (let variant of candidate.variants) { - parts.unshift(printVariant(variant)) - } - - // Handle prefix - if (designSystem.theme.prefix) { - parts.unshift(designSystem.theme.prefix) - } - - let base: string = '' - - // Handle static - if (candidate.kind === 'static') { - base += candidate.root - } - - // Handle functional - if (candidate.kind === 'functional') { - base += candidate.root - - if (candidate.value) { - if (candidate.value.kind === 'arbitrary') { - if (candidate.value !== null) { - let isVarValue = isVar(candidate.value.value) - let value = isVarValue ? candidate.value.value.slice(4, -1) : candidate.value.value - let [open, close] = isVarValue ? ['(', ')'] : ['[', ']'] - - if (candidate.value.dataType) { - base += `-${open}${candidate.value.dataType}:${printArbitraryValue(value)}${close}` - } else { - base += `-${open}${printArbitraryValue(value)}${close}` - } - } - } else if (candidate.value.kind === 'named') { - base += `-${candidate.value.value}` - } - } - } - - // Handle arbitrary - if (candidate.kind === 'arbitrary') { - base += `[${candidate.property}:${printArbitraryValue(candidate.value)}]` - } - - // Handle modifier - if (candidate.kind === 'arbitrary' || candidate.kind === 'functional') { - base += printModifier(candidate.modifier) - } - - // Handle important - if (candidate.important) { - base += '!' - } - - parts.push(base) - - return parts.join(':') -} - -export function printModifier(modifier: ArbitraryModifier | NamedModifier | null) { - if (modifier === null) return '' - - let isVarValue = isVar(modifier.value) - let value = isVarValue ? modifier.value.slice(4, -1) : modifier.value - let [open, close] = isVarValue ? ['(', ')'] : ['[', ']'] - - if (modifier.kind === 'arbitrary') { - return `/${open}${printArbitraryValue(value)}${close}` - } else if (modifier.kind === 'named') { - return `/${modifier.value}` - } else { - modifier satisfies never - return '' - } -} - -export function printVariant(variant: Variant) { - // Handle static variants - if (variant.kind === 'static') { - return variant.root - } - - // Handle arbitrary variants - if (variant.kind === 'arbitrary') { - return `[${printArbitraryValue(simplifyArbitraryVariant(variant.selector))}]` - } - - let base: string = '' - - // Handle functional variants - if (variant.kind === 'functional') { - base += variant.root - // `@` is a special case for functional variants. We want to print: `@lg` - // instead of `@-lg` - let hasDash = variant.root !== '@' - if (variant.value) { - if (variant.value.kind === 'arbitrary') { - let isVarValue = isVar(variant.value.value) - let value = isVarValue ? variant.value.value.slice(4, -1) : variant.value.value - let [open, close] = isVarValue ? ['(', ')'] : ['[', ']'] - - base += `${hasDash ? '-' : ''}${open}${printArbitraryValue(value)}${close}` - } else if (variant.value.kind === 'named') { - base += `${hasDash ? '-' : ''}${variant.value.value}` - } - } - } - - // Handle compound variants - if (variant.kind === 'compound') { - base += variant.root - base += '-' - base += printVariant(variant.variant) - } - - // Handle modifiers - if (variant.kind === 'functional' || variant.kind === 'compound') { - base += printModifier(variant.modifier) - } - - return base -} - -const printArbitraryValueCache = new DefaultMap((input) => { - let ast = ValueParser.parse(input) - - let drop = new Set() - - let symbols = new Set([ - // Selectors - '~', // Subsequent sibling combinator - '>', // Child combinator - - // Math operators - '+', // or next sibling combinator - '-', - '*', // or universal selector - '/', - ]) - walk(ast, (node, ctx) => { - // Handle operators (e.g.: inside of `calc(…)`) - if (node.kind === 'word' && symbols.has(node.value)) { - let idx = ctx.index - - // This should not be possible - if (idx === -1) return - - // a + b - // ^ node - // ^ previous (whitespace) - let previous = ctx.siblings[idx - 1] - if (previous?.kind !== 'separator' || previous.value !== ' ') return - - // a + b - // ^ node - // ^ next (whitespace) - let next = ctx.siblings[idx + 1] - if (next?.kind !== 'separator' || next.value !== ' ') return - - // a + b - // ^ node - // ^ previous (node) - let previousPrevious = ctx.siblings[idx - 2] - if (previousPrevious && symbols.has(previousPrevious.value)) return - - // a + b - // ^ node - // ^ next (node) - let nextNext = ctx.siblings[idx + 2] - if (nextNext && symbols.has(nextNext.value)) return - - drop.add(previous) - drop.add(next) - } - - // Leading and trailing whitespace - else if (node.kind === 'separator' && node.value.length > 0 && node.value.trim() === '') { - if (ctx.siblings[0] === node || ctx.siblings[ctx.siblings.length - 1] === node) { - drop.add(node) - } - } - - // Whitespace around `,` separators can be removed. - // E.g.: `min(1px , 2px)` -> `min(1px,2px)` - else if (node.kind === 'separator' && node.value.trim() === ',') { - node.value = ',' - } - - // Wrap custom functions starting with `--`, in parentheses if preceded by - // a symbol. E.g.: `calc(100%---spacing(2))` → `calc(100%-(--spacing(2)))` - else if (node.kind === 'function' && node.value.startsWith('--')) { - let idx = ctx.index - - // When it's the first argument, then we don't have to wrap it in `(…)` - // - // E.g.: `--spacing(…)`, no need to wrap - // `calc(100%---spacing(…))`, we want to wrap - // - if (idx <= 0) return - - // When it's not the first argument, we likely want to wrap it. However, - // when it's separated by a `,` then it's readable enough. - // - // E.g.: `min(100%,--spacing(2))` is readable, in fact - // `min(100%,(--spacing(2)))` would make it worse - let previous = ctx.siblings[idx - 1] - if (previous?.kind === 'separator' && previous.value === ',') return - - // When it's part of a bigger list, aka no special symbols were used, then - // we don't have to wrap it either. - // - // E.g.: `shadow-[inset_0px_1px_--theme(--color-white/15%)]`, wrapping would look unnecessary: - // `shadow-[inset_0px_1px_(--theme(--color-white/15%))]` - let previousPrevious = ctx.siblings[idx - 2] - if (previousPrevious && !symbols.has(previousPrevious.value)) return - - return WalkAction.ReplaceSkip({ - kind: 'function', - value: '', // Unnamed, so will result in `(…)` - nodes: [node], - } satisfies ValueFunctionNode) - } - }) - - if (drop.size > 0) { - walk(ast, (node) => { - if (drop.has(node)) { - drop.delete(node) - return WalkAction.ReplaceSkip([]) - } - }) - } - - recursivelyEscapeUnderscores(ast) - - return ValueParser.toCss(ast) -}) -export function printArbitraryValue(input: string) { - return printArbitraryValueCache.get(input) -} - -const simplifyArbitraryVariantCache = new DefaultMap((input) => { - let ast = ValueParser.parse(input) - - // &:is(…) - if ( - ast.length === 3 && - // & - ast[0].kind === 'word' && - ast[0].value === '&' && - // : - ast[1].kind === 'separator' && - ast[1].value === ':' && - // is(…) - ast[2].kind === 'function' && - ast[2].value === 'is' - ) { - return ValueParser.toCss(ast[2].nodes) - } - - return input -}) -function simplifyArbitraryVariant(input: string) { - return simplifyArbitraryVariantCache.get(input) -} - -function recursivelyEscapeUnderscores(ast: ValueParser.ValueAstNode[]) { - for (let node of ast) { - switch (node.kind) { - case 'function': { - if (node.value === 'url' || node.value.endsWith('_url')) { - // Don't decode underscores in url() but do decode the function name - node.value = escapeUnderscore(node.value) - break - } - - if ( - node.value === 'var' || - node.value.endsWith('_var') || - node.value === 'theme' || - node.value.endsWith('_theme') - ) { - node.value = escapeUnderscore(node.value) - for (let i = 0; i < node.nodes.length; i++) { - recursivelyEscapeUnderscores([node.nodes[i]]) - } - break - } - - node.value = escapeUnderscore(node.value) - recursivelyEscapeUnderscores(node.nodes) - break - } - case 'separator': - node.value = escapeUnderscore(node.value) - break - case 'word': { - // Dashed idents and variables `var(--my-var)` and `--my-var` should not - // have underscores escaped - if (node.value[0] !== '-' || node.value[1] !== '-') { - node.value = escapeUnderscore(node.value) - } - break - } - default: - never(node) - } - } -} - -const isVarCache = new DefaultMap((value) => { - let ast = ValueParser.parse(value) - return ast.length === 1 && ast[0].kind === 'function' && ast[0].value === 'var' -}) -function isVar(value: string) { - return isVarCache.get(value) -} - -function never(value: never): never { - throw new Error(`Unexpected value: ${value}`) -} - -function escapeUnderscore(value: string): string { - return value - .replaceAll('_', String.raw`\_`) // Escape underscores to keep them as-is - .replaceAll(' ', '_') // Replace spaces with underscores + } while (idx > 0) } diff --git a/packages/tailwindcss/src/canonicalize-calc-expressions.test.ts b/packages/tailwindcss/src/canonicalize-calc-expressions.test.ts deleted file mode 100644 index f612e5f7b..000000000 --- a/packages/tailwindcss/src/canonicalize-calc-expressions.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { expect, it } from 'vitest' -import { canonicalizeCalcExpressions } from './canonicalize-calc-expressions' - -it.each([ - ['calc(-1 * var(--foo))', 'calc(var(--foo) * -1)'], - ['calc(1rem + var(--foo))', 'calc(var(--foo) + 1rem)'], - ['calc(2rem * calc(3px * var(--foo)))', 'calc(calc(var(--foo) * 3px) * 2rem)'], - ['calc(var(--b) + var(--a))', 'calc(var(--a) + var(--b))'], - ['calc(3px * 2rem)', 'calc(2rem * 3px)'], - ['calc(5px * 3px)', 'calc(3px * 5px)'], - ['calc(1 * 1rem)', 'calc(1rem * 1)'], - ['calc(10 + 2)', 'calc(2 + 10)'], -])('`%s` → `%s` (%#)', (input, expected) => { - expect(canonicalizeCalcExpressions(input)).toBe(expected) -}) - -it.each([ - ['calc(1rem - var(--foo))'], - ['calc(1rem / 2)'], - ['calc(var(--a) + 1rem)'], - ['calc(2rem * 3px)'], -])('should keep `%s` (%#)', (input) => { - expect(canonicalizeCalcExpressions(input)).toBe(input) -}) diff --git a/packages/tailwindcss/src/canonicalize-calc-expressions.ts b/packages/tailwindcss/src/canonicalize-calc-expressions.ts deleted file mode 100644 index 7e08f8622..000000000 --- a/packages/tailwindcss/src/canonicalize-calc-expressions.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { dimensions } from './utils/dimensions' -import * as ValueParser from './value-parser' -import { walk, WalkAction } from './walk' - -// Assumption: We already assume that we receive somewhat valid `calc()` -// expressions. So we will see `calc(1 + 1)` and not `calc(1+1)` -export function canonicalizeCalcExpressions(input: string): string { - let [canonicalized, valueAst] = canonicalizeCalcExpressionsAst(ValueParser.parse(input)) - - return canonicalized ? ValueParser.toCss(valueAst) : input -} - -export function canonicalizeCalcExpressionsAst( - ast: ValueParser.ValueAstNode[], -): [canonicalized: boolean, ast: ValueParser.ValueAstNode[]] { - let canonicalized = false - - walk(ast, { - exit(valueNode) { - // We are only interested in binary expressions in `calc(…)` and `(…)`, - // and only with the `*` or `+` operators. - if (valueNode.kind !== 'function') return - if (valueNode.value !== 'calc' && valueNode.value !== '') return - if (valueNode.nodes.length !== 5) return - if (valueNode.nodes[2].kind !== 'word') return - if (valueNode.nodes[2].value !== '*' && valueNode.nodes[2].value !== '+') return - - let lhs = valueNode.nodes[0] - let rhs = valueNode.nodes[4] - - if (shouldSwap(lhs, rhs)) { - canonicalized = true - - let replacement: ValueParser.ValueFunctionNode = { - kind: 'function', - value: valueNode.value, - nodes: [ - rhs, // Now lhs - valueNode.nodes[1], // Separator - valueNode.nodes[2], // Operator - valueNode.nodes[3], // Separator - lhs, // Now rhs - ], - } - - return WalkAction.ReplaceSkip(replacement) - } - }, - }) - - return [canonicalized, ast] -} - -function shouldSwap(lhs: ValueParser.ValueAstNode, rhs: ValueParser.ValueAstNode): boolean { - let lhsDimension = lhs.kind === 'word' ? dimensions.get(lhs.value) : null - let rhsDimension = rhs.kind === 'word' ? dimensions.get(rhs.value) : null - - if (lhsDimension !== null && rhsDimension === null) return true - if (lhsDimension === null && rhsDimension !== null) return false - - if (lhsDimension !== null && rhsDimension !== null) { - let [lhsValue, lhsUnit] = lhsDimension - let [rhsValue, rhsUnit] = rhsDimension - - // Within dimensions, keep unit-bearing values ahead of unitless numbers so - // `1rem` sorts before `1`. - if (lhsUnit === null && rhsUnit !== null) return true - if (lhsUnit !== null && rhsUnit === null) return false - - // Then sort dimensions numerically, and finally by unit for ties. - if (lhsValue !== rhsValue) { - return lhsValue - rhsValue > 0 - } - - if (lhsUnit !== rhsUnit) { - return (lhsUnit ?? '').localeCompare(rhsUnit ?? '') > 0 - } - } - - // Both nodes are unknown values (not dimensions), sort them as strings - return ValueParser.toCss([lhs]).localeCompare(ValueParser.toCss([rhs])) > 0 -} diff --git a/packages/tailwindcss/src/canonicalize-candidates.test.ts b/packages/tailwindcss/src/canonicalize-candidates.test.ts deleted file mode 100644 index 567f0ad0d..000000000 --- a/packages/tailwindcss/src/canonicalize-candidates.test.ts +++ /dev/null @@ -1,1663 +0,0 @@ -import { randomUUID } from 'node:crypto' -import fs from 'node:fs' -import path from 'node:path' -import { describe, expect, test } from 'vitest' -import { __unstable__loadDesignSystem } from '.' -import { cartesian } from './cartesian' -import type { CanonicalizeOptions } from './intellisense' -import plugin from './plugin' -import { DefaultMap } from './utils/default-map' - -const css = String.raw -const timeout = 25_000 -const defaultTheme = fs.readFileSync(path.resolve(__dirname, '../theme.css'), 'utf8') - -const designSystems = new DefaultMap((base: string) => { - return new DefaultMap((input: string) => { - return __unstable__loadDesignSystem(input, { - base, - async loadStylesheet() { - return { - path: '', - base: '', - content: css` - @tailwind utilities; - - ${defaultTheme} - - /* TODO(perf): Only here to speed up the tests */ - @theme { - --*: initial; - --breakpoint-lg: 64rem; - --breakpoint-md: 48rem; - --color-blue-200: oklch(88.2% 0.059 254.128); - --color-blue-500: oklch(62.3% 0.214 259.815); - --color-red-500: oklch(63.7% 0.237 25.331); - --color-white: #fff; - --container-md: 28rem; - --font-weight-normal: 400; - --leading-relaxed: 1.625; - --spacing: 0.25rem; - --text-sm--line-height: calc(1.25 / 0.875); - --text-sm: 0.875rem; - --text-xs--line-height: calc(1 / 0.75); - --text-xs: 0.75rem; - } - `, - } - }, - }) - }) -}) - -const DEFAULT_CANONICALIZATION_OPTIONS: CanonicalizeOptions = { - rem: 16, - collapse: true, - logicalToPhysical: true, -} - -describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', (strategy) => { - let testName = '%s → %s (%#)' - if (strategy === 'with-variant') { - testName = testName.replaceAll('%s', 'focus:%s') - } else if (strategy === 'important') { - testName = testName.replaceAll('%s', '%s!') - } else if (strategy === 'prefix') { - testName = testName.replaceAll('%s', 'tw:%s') - } - - function prepare(candidate: string) { - if (strategy === 'with-variant') { - candidate = `focus:${candidate}` - } else if (strategy === 'important') { - candidate = `${candidate}!` - } else if (strategy === 'prefix') { - candidate = `tw:${candidate}` - - // Prefix all known CSS variables with `--tw-`, except when used inside of `--theme(…)`. - if (candidate.includes('--')) { - candidate = candidate - .replace( - // Replace the variable, as long as it is preceded by a `(`, e.g.: - // `bg-(--foo)` or an `:` in case of `bg-(color:--foo)`. - // - // It also has to end in a `,` or `)` to prevent replacing functions - // that look like variables, e.g.: `--spacing(…)` - /([(:])--([\w-]+)([,)])/g, - (_, start, variable, end) => `${start}--tw-${variable}${end}`, - ) - .replaceAll('--theme(--tw-', '--theme(--') - } - } - - return candidate - } - - async function expectCanonicalization( - input: string, - candidates: string, - expected: string, - options: CanonicalizeOptions = DEFAULT_CANONICALIZATION_OPTIONS, - ) { - let preparedCandidates = candidates.split(/\s+/g).map(prepare) - let preparedExpected = expected.split(/\s+/g).map(prepare) - - if (strategy === 'prefix') { - input = input.replace("@import 'tailwindcss';", "@import 'tailwindcss' prefix(tw);") - } - - let designSystem = await designSystems.get(__dirname).get(input) - let actual = designSystem.canonicalizeCandidates(preparedCandidates, options) - - try { - expect(actual).toEqual(preparedExpected) - } catch (err) { - if (err instanceof Error) Error.captureStackTrace(err, expectCanonicalization) - throw err - } - } - - /// ---------------------------------- - - describe('deprecated utilities', () => { - test.each([ - /// Legacy bg-gradient-* → bg-linear-* - ['bg-gradient-to-t', 'bg-linear-to-t'], - ['bg-gradient-to-tr', 'bg-linear-to-tr'], - ['bg-gradient-to-r', 'bg-linear-to-r'], - ['bg-gradient-to-br', 'bg-linear-to-br'], - ['bg-gradient-to-b', 'bg-linear-to-b'], - ['bg-gradient-to-bl', 'bg-linear-to-bl'], - ['bg-gradient-to-l', 'bg-linear-to-l'], - ['bg-gradient-to-tl', 'bg-linear-to-tl'], - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected) - }) - - let deprecated: [string, string][] = [ - ['order-none', 'order-0'], - ['break-words', 'wrap-break-word'], - ['overflow-ellipsis', 'text-ellipsis'], - - ['start-full', 'inset-s-full'], - ['-start-full', '-inset-s-full'], - ['start-auto', 'inset-s-auto'], - ['start-px', 'inset-s-px'], - ['-start-px', '-inset-s-px'], - ['start-8', 'inset-s-8'], // Within default spacing scale - ['-start-8', '-inset-s-8'], // Within default spacing scale - ['start-123', 'inset-s-123'], // Outside of default spacing scale - ['-start-123', '-inset-s-123'], // Outside of default spacing scale - - ['end-full', 'inset-e-full'], - ['-end-full', '-inset-e-full'], - ['end-auto', 'inset-e-auto'], - ['end-px', 'inset-e-px'], - ['-end-px', '-inset-e-px'], - ['end-8', 'inset-e-8'], // Within default spacing scale - ['-end-8', '-inset-e-8'], // Within default spacing scale - ['end-123', 'inset-e-123'], // Outside of default spacing scale - ['-end-123', '-inset-e-123'], // Outside of default spacing scale - ] - - test.each(deprecated)(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected) - }) - - describe('With custom implementation', () => { - // Creating a shared CSS file such that we can re-use the same design - // system for all of these. - let customImplementations = deprecated - .map( - ([candidate]) => css` - @utility ${candidate} { - --custom-${randomUUID()}: implementation; - } - `, - ) - .join('\n') - - // Keep the current utility because of the custom implementation - test.each(deprecated.map(([candidate]) => [candidate, candidate]))( - testName, - { timeout }, - async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - - ${customImplementations} - ` - - await expectCanonicalization(input, candidate, expected) - }, - ) - }) - }) - - describe('arbitrary properties', () => { - test.each([ - /// theme(…) to `var(…)` - // Keep candidates that don't contain `theme(…)` or `theme(…, …)` - ['[color:red]', 'text-[red]'], - - // Handle special cases around `.1` in the `theme(…)` - ['[--value:theme(spacing.1)]', '[--value:--spacing(1)]'], - ['[--value:theme(fontSize.xs.1.lineHeight)]', '[--value:var(--text-xs--line-height)]'], - ['[--value:theme(spacing[1.25])]', '[--value:--spacing(1.25)]'], - - // Should not convert invalid spacing values to calc - ['[--value:theme(spacing[1.1])]', '[--value:theme(spacing[1.1])]'], - - // Convert to `var(…)` if we can resolve the path - ['[color:theme(colors.red.500)]', 'text-red-500'], // Arbitrary property - ['[color:theme(colors.red.500)]/50', 'text-red-500/50'], // Arbitrary property + modifier - - // Multiple `theme(… / …)` calls should result in modern syntax of `theme(…)` - // - Can't convert to `var(…)` because that would lose the modifier. - // - Can't convert to a candidate modifier because there are multiple - // `theme(…)` calls. - // - // If we really want to, we can make a fancy migration that tries to move it - // to a candidate modifier _if_ all `theme(…)` calls use the same modifier. - [ - '[color:theme(colors.red.500/50,theme(colors.blue.500/50))]', - 'text-[--theme(--color-red-500/50,--theme(--color-blue-500/50))]', - ], - [ - '[color:theme(colors.red.500/50,theme(colors.blue.500/50))]/50', - 'text-[--theme(--color-red-500/50,--theme(--color-blue-500/50))]/50', - ], - - // Convert the `theme(…)`, but try to move the inline modifier (e.g. `50%`), - // to a candidate modifier. - // Arbitrary property, with simple percentage modifier - ['[color:theme(colors.red.500/75%)]', 'text-red-500/75'], - - // Arbitrary property, with numbers (0-1) without a unit - ['[color:theme(colors.red.500/.12)]', 'text-red-500/12'], - ['[color:theme(colors.red.500/0.12)]', 'text-red-500/12'], - - // Arbitrary property, with more complex modifier (we only allow whole numbers - // as bare modifiers). Convert the complex numbers to arbitrary values instead. - ['[color:theme(colors.red.500/12.34%)]', 'text-red-500/[12.34%]'], - ['[color:theme(colors.red.500/var(--opacity))]', 'text-red-500/(--opacity)'], - ['[color:theme(colors.red.500/.12345)]', 'text-red-500/1234.5'], - ['[color:theme(colors.red.500/50.25%)]', 'text-red-500/50.25'], - - // Arbitrary property that already contains a modifier - ['[color:theme(colors.red.500/50%)]/50', 'text-[--theme(--color-red-500/50%)]/50'], - - // `calc(var(--spacing)*…)` to `--spacing(…)` - ['[padding-top:min(20%,calc(var(--spacing)*8))]', 'pt-[min(20%,--spacing(8))]'], - [ - '[padding-top:min(20%,calc(var(--spacing)*var(--other)))]', - 'pt-[min(20%,--spacing(var(--other)))]', - ], - ['[padding-top:calc(var(--spacing)*8)]', 'pt-8'], - ['[padding-top:calc(var(--spacing)*var(--other))]', 'pt-[--spacing(var(--other))]'], - - // `theme(…)` calls valid in v3, but not in v4 should still be converted. - ['[--foo:theme(transitionDuration.500)]', '[--foo:theme(transitionDuration.500)]'], - - // Invalid cases - ['[--foo:theme(colors.red.500/50/50)]', '[--foo:theme(colors.red.500/50/50)]'], - ['[--foo:theme(colors.red.500/50/50)]/50', '[--foo:theme(colors.red.500/50/50)]/50'], - - // Partially invalid cases - [ - '[--foo:theme(colors.red.500/50/50)_theme(colors.blue.200)]', - '[--foo:theme(colors.red.500/50/50)_var(--color-blue-200)]', - ], - [ - '[--foo:theme(colors.red.500/50/50)_theme(colors.blue.200)]/50', - '[--foo:theme(colors.red.500/50/50)_var(--color-blue-200)]/50', - ], - - // If a utility sets `property` and `--tw-{property}` with the same value, - // we can ignore the `--tw-{property}`. This is just here for composition. - // This means that we should be able to upgrade the one _without_ to the one - // _with_ the variable - ['[font-weight:400]', 'font-normal'], - ['[line-height:0]', 'leading-0'], - ['[border-style:solid]', 'border-solid'], - - // Do not constant fold `0` to `0` when the type is unknown (which - // is often the case with CSS variables) - ['[--foo:0px]', '[--foo:0px]'], - ['[--foo:calc(0px*1)]', '[--foo:calc(0px*1)]'], - ['[--foo:calc(0*1rem)]', '[--foo:calc(0*1rem)]'], - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected) - }) - - test.each([ - // Arbitrary property to static utility - ['[text-wrap:balance]', 'text-balance'], - - // Arbitrary property to static utility with slight differences in - // whitespace. This will require some canonicalization. - ['[display:_flex_]', 'flex'], - ['[display:_flex]', 'flex'], - ['[display:flex_]', 'flex'], - - // Arbitrary property to named functional utility - ['[color:var(--color-red-500)]', 'text-red-500'], - ['[background-color:var(--color-red-500)]', 'bg-red-500'], - - // Arbitrary property to named utility (case insensitive) - ['[color:#fff]', 'text-white'], - ['[color:#FFF]', 'text-white'], - - // Arbitrary property with modifier to named functional utility with modifier - ['[color:var(--color-red-500)]/25', 'text-red-500/25'], - - // Arbitrary property with arbitrary modifier to named functional utility with - // arbitrary modifier - ['[color:var(--color-red-500)]/[25%]', 'text-red-500/25'], - ['[color:var(--color-red-500)]/[100%]', 'text-red-500'], - ['[color:var(--color-red-500)]/100', 'text-red-500'], - ['[color:var(--color-red-500)]/[10%]', 'text-red-500/10'], - ['[color:var(--color-red-500)]/[10.0%]', 'text-red-500/10'], - ['[color:var(--color-red-500)]/[.1]', 'text-red-500/10'], - ['[color:var(--color-red-500)]/[.10]', 'text-red-500/10'], - // No need for `/50` because that's already encoded in the `--color-primary` - // value - ['[color:oklch(62.3%_0.214_259.815)]/50', 'text-primary'], - - // Arbitrary property to arbitrary value - ['[max-height:20%]', 'max-h-[20%]'], - - // Arbitrary property to bare value - ['[grid-column:2]', 'col-2'], - ['[grid-column:1234]', 'col-1234'], - - // Complex arbitrary property to arbitrary value - [ - '[grid-template-columns:repeat(2,minmax(100px,1fr))]', - 'grid-cols-[repeat(2,minmax(100px,1fr))]', - ], - // Complex arbitrary property to bare value - ['[grid-template-columns:repeat(2,minmax(0,1fr))]', 'grid-cols-2'], - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - - @theme { - --*: initial; - --spacing: 0.25rem; - --color-red-500: red; - --color-white: #fff; - - /* Equivalent of blue-500/50 */ - --color-primary: color-mix(in oklab, oklch(62.3% 0.214 259.815) 50%, transparent); - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('arbitrary values', () => { - test.each([ - // Convert to `var(…)` if we can resolve the path - ['bg-[theme(colors.red.500)]', 'bg-red-500'], // Arbitrary value - ['bg-[size:theme(spacing.4)]', 'bg-size-[--spacing(4)]'], // Arbitrary value + data type hint - - // Pretty print CSS functions preceded by an operator to prevent consecutive - // operator characters. - ['w-[calc(100dvh-theme(spacing.2))]', 'w-[calc(100dvh-(--spacing(2)))]'], - ['w-[calc(100dvh+theme(spacing.2))]', 'w-[calc(100dvh+(--spacing(2)))]'], - ['w-[calc(100dvh/theme(spacing.2))]', 'w-[calc(100dvh/(--spacing(2)))]'], - ['w-[calc(100dvh*theme(spacing.2))]', 'w-[calc(100dvh*(--spacing(2)))]'], - - // Convert to `var(…)` if we can resolve the path, but keep fallback values - ['bg-[theme(colors.red.500,red)]', 'bg-(--color-red-500,red)'], - - // Keep `theme(…)` if we can't resolve the path - ['bg-[theme(colors.foo.1000)]', 'bg-[theme(colors.foo.1000)]'], - - // Keep `theme(…)` if we can't resolve the path, but still try to convert the - // fallback value. - ['bg-[theme(colors.foo.1000,theme(colors.red.500))]', 'bg-red-500'], - - // Use `theme(…)` (deeply nested) inside of a `calc(…)` function - ['text-[calc(theme(fontSize.xs)*2)]', 'text-[calc(var(--text-xs)*2)]'], - - // Arbitrary value - ['bg-[theme(colors.red.500/75%)]', 'bg-red-500/75'], - ['bg-[theme(colors.red.500/12.34%)]', 'bg-red-500/[12.34%]'], - - // Arbitrary value with different casing - ['bg-[#fff]', 'bg-white'], - ['bg-[#FFF]', 'bg-white'], - - // Values that don't contain only `theme(…)` calls should not be converted to - // use a modifier since the color is not the whole value. - [ - 'shadow-[shadow:inset_0px_1px_theme(colors.white/15%)]', - 'shadow-[inset_0px_1px_--theme(--color-white/15%)]', - ], - - // Arbitrary value, where the candidate already contains a modifier - // This should still migrate the `theme(…)` syntax to the modern syntax. - ['bg-[theme(colors.red.500/50%)]/50', 'bg-[--theme(--color-red-500/50%)]/50'], - - // Variants, we can't use `var(…)` especially inside of `@media(…)`. But - // since the value matches the `lg` breakpoint exactly, we can migrate to - // the `max-lg` variant. - ['max-[theme(screens.lg)]:flex', 'max-lg:flex'], - ['min-[theme(screens.lg)]:flex', 'lg:flex'], // We could also map to `min-lg:flex` - // There are no variables for `--spacing` multiples, so we can't convert this - ['max-[theme(spacing.4)]:flex', 'max-[theme(spacing.4)]:flex'], - - // This test in itself doesn't make much sense. But we need to make sure - // that this doesn't end up as the modifier in the candidate itself. - ['max-[theme(spacing.4/50)]:flex', 'max-[theme(spacing.4/50)]:flex'], - - // `theme(…)` calls in another CSS function is replaced correctly. - // Additionally we remove unnecessary whitespace. - ['grid-cols-[min(50%_,_theme(spacing.80))_auto]', 'grid-cols-[min(50%,--spacing(80))_auto]'], - - // `calc(var(--spacing)*…)` to `--spacing(…)` - ['pt-[min(20%,calc(var(--spacing)*8))]', 'pt-[min(20%,--spacing(8))]'], - ['pt-[min(20%,calc(var(--spacing)*var(--other)))]', 'pt-[min(20%,--spacing(var(--other)))]'], - ['pt-[calc(var(--spacing)*8)]', 'pt-8'], - ['pt-[calc(var(--spacing)*var(--other))]', 'pt-[--spacing(var(--other))]'], - - // Renamed theme keys - ['max-w-[theme(screens.md)]', 'max-w-(--breakpoint-md)'], - ['w-[theme(maxWidth.md)]', 'w-md'], - - // Arbitrary property to static utility - // Map number to keyword-like value - ['leading-[1]', 'leading-none'], - - // Arbitrary value to bare value - ['border-[2px]', 'border-2'], - ['border-[1234px]', 'border-1234'], - - // Arbitrary value with data type, to more specific arbitrary value - ['bg-[position:123px]', 'bg-position-[123px]'], - ['bg-[size:123px]', 'bg-size-[123px]'], - - // Arbitrary value with inferred data type, to more specific arbitrary value - ['bg-[123px]', 'bg-position-[123px]'], - - // Arbitrary value with spacing mul - ['w-[64rem]', 'w-256'], - - // Arbitrary value to bare value with percentage - ['from-[25%]', 'from-25%'], - - // Arbitrary percentage value must be a whole number. Should not migrate to - // a bare value. - ['from-[2.5%]', 'from-[2.5%]'], - - // Negative arbitrary values can be simplified - // 1. Try to move the sign _inside_ the arbitrary value - // 2. Try to move the sign _out_ of the arbitrary value - ['-mt-[12rem]', '-mt-48'], // Arbitrary value → bare value - ['-mt-[-12rem]', 'mt-48'], // Double negation - ['-mt-[12.34rem]', 'mt-[-12.34rem]'], // Move `-` inside - ['-mt-[-12.34rem]', 'mt-[12.34rem]'], // Move `-` inside, double negation - ['-mt-[12.34px]', 'mt-[-12.34px]'], - ['-mt-[-12.34px]', 'mt-[12.34px]'], - ['-mt-[492px]', '-mt-123'], // Moving inside, allows us to migrate to a bare value - ['-mt-[calc(-1*492px)]', 'mt-123'], // Double negation and constant folding into bare value - ['-mt-[-492px]', 'mt-123'], - ['-mt-[calc(-1*-492px)]', '-mt-123'], // Constant folding with calc expressions - ['-mt-(--my-var)', '-mt-(--my-var)'], // Keep as-is - ['-mt-[var(--my-var)]', '-mt-(--my-var)'], // Keep as-is, but convert to shorthand - ['mt-[calc(var(--my-var)*-1)]', '-mt-(--my-var)'], // Move `-` out - ['mt-[calc(-1*var(--my-var))]', '-mt-(--my-var)'], // Move `-` out - ['-mt-[calc(var(--my-var)*-1)]', 'mt-(--my-var)'], // Move `-` out - ['-mt-[calc(-1*var(--my-var))]', 'mt-(--my-var)'], // Move `-` out - ['mt-[calc(-1*calc(-1*var(--my-var)))]', 'mt-(--my-var)'], // Move `-` out - ['-mt-[calc(-1*calc(-1*var(--my-var)))]', '-mt-(--my-var)'], // Move `-` out - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - @theme { - --color-white: #fff; - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('arbitrary utilities', () => { - test('migrate with custom static utility `@utility custom {…}`', { timeout }, async () => { - let candidate = '[--key:value]' - let expected = 'custom' - - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - @utility custom { - --key: value; - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - - test( - 'migrate with custom functional utility `@utility custom-* {…}`', - { timeout }, - async () => { - let candidate = '[--key:value]' - let expected = 'custom-value' - - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - @utility custom-* { - --key: --value('value'); - } - ` - - await expectCanonicalization(input, candidate, expected) - }, - ) - - test( - 'migrate with custom functional utility `@utility custom-* {…}` that supports bare values', - { timeout }, - async () => { - let candidate = '[--resolved-value:4]' - let expected = 'example-4' - - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - @utility example-* { - --resolved-value: --value(integer); - } - ` - - await expectCanonicalization(input, candidate, expected) - }, - ) - - test.each([ - ['[--resolved-value:0]', 'example-0'], - ['[--resolved-value:4]', 'example-4'], - ['[--resolved-value:8]', 'example-a'], - ['example-[0]', 'example-0'], - ['example-[4]', 'example-4'], - ['example-[8]', 'example-a'], - ])( - 'migrate custom @utility from arbitrary values to bare values and named values (based on theme)', - async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - --example-a: 8; - } - - @utility example-* { - --resolved-value: --value(--example, integer, [integer]); - } - ` - - await expectCanonicalization(input, candidate, expected) - }, - ) - - describe.each([['@theme'], ['@theme inline']])('%s', (theme) => { - test.each([ - ['[color:CanvasText]', 'text-canvas'], - ['text-[CanvasText]', 'text-canvas'], - ])(`migrate arbitrary value to theme value ${testName}`, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ${theme} { - --*: initial; - --color-canvas: CanvasText; - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - - // Some utilities read from specific namespaces, in this case we do not want - // to migrate to a value in that namespace if we reference a variable that - // results in the same value, but comes from a different namespace. - // - // E.g.: `max-w` reads from: ['--max-width', '--spacing', '--container'] - test.each([ - // `max-w` does not read from `--breakpoint-md`, but `--breakpoint-md` and - // `--container-3xl` happen to result in the same value. The difference is - // the semantics of the value. - ['max-w-(--breakpoint-md)', 'max-w-(--breakpoint-md)'], - ['max-w-(--container-3xl)', 'max-w-3xl'], - ])( - `migrate arbitrary value to theme value ${testName}`, - { timeout }, - async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ${theme} { - --*: initial; - --breakpoint-md: 48rem; - --container-3xl: 48rem; - } - ` - - await expectCanonicalization(input, candidate, expected) - }, - ) - }) - - test( - 'migrate an arbitrary property without spaces, to a theme value with spaces (canonicalization)', - { timeout }, - async () => { - let candidate = 'font-[foo,bar,baz]' - let expected = 'font-example' - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - --font-example: foo, bar, baz; - } - ` - - await expectCanonicalization(input, candidate, expected) - }, - ) - - test.each([ - // Default spacing scale - ['w-[64rem]', 'w-256', '0.25rem'], - - // Non-suggested numbers - ['gap-[7.25rem]', 'gap-29', '0.25rem'], - ['gap-[calc(7rem+0.25rem)]', 'gap-29', '0.25rem'], - ['gap-[116px]', 'gap-29', '0.25rem'], - - // Non-suggested numbers, with the same spacing scale with different - // units - ['gap-[7.25rem]', 'gap-29', '4px'], - ['gap-[calc(7rem+0.25rem)]', 'gap-29', '4px'], - ['gap-[116px]', 'gap-29', '4px'], - - // Non-suggested numbers, with a different spacing scale - ['gap-[7.25rem]', 'gap-116', '1px'], - ['gap-[calc(7rem+0.25rem)]', 'gap-116', '1px'], - ['gap-[116px]', 'gap-116', '1px'], - - // Keep arbitrary value if units are different - ['w-[124px]', 'w-31', '0.25rem'], - - // Keep arbitrary value if bare value doesn't fit in steps of .25 - ['w-[0.123rem]', 'w-[0.123rem]', '0.25rem'], - - // Custom pixel based spacing scale - ['w-[123px]', 'w-123', '1px'], - ['w-[256px]', 'w-128', '2px'], - ])(`${testName} (spacing = \`%s\`)`, { timeout }, async (candidate, expected, spacing) => { - let input = css` - @import 'tailwindcss'; - - @theme { - --*: initial; - --spacing: ${spacing}; - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('bare values', () => { - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - --spacing: 0.25rem; - --aspect-video: 16 / 9; - --example-a: 8; - } - - @utility example-* { - --resolved-value: --value(--example, integer); - } - ` - - test.each([ - // Built-in utility with bare value fraction - ['aspect-16/9', 'aspect-video'], - - // Custom utility with bare value integer - ['example-8', 'example-a'], - ])(testName, { timeout }, async (candidate, expected) => { - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('arbitrary variants', () => { - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - --breakpoint-lg: 64rem; - } - ` - - test.each([ - // Arbitrary variant to static variant - ['[&:focus]:flex', 'focus:flex'], - - // Arbitrary variant to static variant with at-rules - ['[@media(scripting:_none)]:flex', 'noscript:flex'], - - // Arbitrary variant to static utility at-rules and with slight differences - // in whitespace. This will require some canonicalization. - ['[@media(scripting:none)]:flex', 'noscript:flex'], - ['[@media(scripting:_none)]:flex', 'noscript:flex'], - ['[@media_(scripting:_none)]:flex', 'noscript:flex'], - - // Arbitrary variants with media queries cannot use `var(…)`, but if an - // equivalent variant exists we can use that one instead. - ['[@media(width>=theme(screens.lg))]:flex', 'lg:flex'], // `min-lg:flex` would also be valid - ['[@media(width { - await expectCanonicalization(input, candidate, expected) - }) - - test('unsafe migrations keep the candidate as-is', { timeout }, async () => { - // `hover:` also includes an `@media` query in addition to the `&:hover` - // state. Migration is not safe because the functionality would be different. - let candidate = '[&:hover]:flex' - let expected = '[&:hover]:flex' - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - - test('make unsafe migration safe (1)', { timeout }, async () => { - // Overriding the `hover:` variant to only use a selector will make the - // migration safe. - let candidate = '[&:hover]:flex' - let expected = 'hover:flex' - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - @variant hover (&:hover); - ` - - await expectCanonicalization(input, candidate, expected) - }) - - test('make unsafe migration safe (2)', { timeout }, async () => { - // Overriding the `hover:` variant to only use a selector will make the - // migration safe. This time with the long-hand `@variant` syntax. - let candidate = '[&:hover]:flex' - let expected = 'hover:flex' - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - @variant hover { - &:hover { - @slot; - } - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - - test('custom selector-based variants', { timeout }, async () => { - let candidate = '[&.macos]:flex' - let expected = 'is-macos:flex' - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - @variant is-macos (&.macos); - ` - - await expectCanonicalization(input, candidate, expected) - }) - - test('custom @media-based variants', { timeout }, async () => { - let candidate = '[@media(prefers-reduced-transparency:reduce)]:flex' - let expected = 'transparency-safe:flex' - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - @variant transparency-safe { - @media (prefers-reduced-transparency: reduce) { - @slot; - } - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('drop unnecessary data types', () => { - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - --color-red-500: red; - } - ` - - test.each([ - // A color value can be inferred from the value - ['bg-[color:#008cc]', 'bg-[#008cc]'], - - // A color is the default for `bg-*` - ['bg-(color:--my-value)', 'bg-(--my-value)'], - - // A color with a known theme variable migrates to the full utility - ['bg-(color:--color-red-500)', 'bg-red-500'], - ])(testName, { timeout }, async (candidate, expected) => { - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('arbitrary value to bare value', () => { - test.each([ - ['aspect-[12/34]', 'aspect-12/34'], - ['aspect-[1.2/34]', 'aspect-[1.2/34]'], - ['col-start-[7]', 'col-start-7'], - ['flex-[2]', 'flex-2'], // `flex` is implemented as static and functional utilities - - ['grid-cols-[subgrid]', 'grid-cols-subgrid'], - ['grid-rows-[subgrid]', 'grid-rows-subgrid'], - - // Handle zeroes - ['m-[0]', 'm-0'], - ['m-[0px]', 'm-0'], - ['m-[0rem]', 'm-0'], - - ['-m-[0]', 'm-0'], - ['-m-[0px]', 'm-0'], - ['-m-[0rem]', 'm-0'], - - ['m-[-0]', 'm-0'], - ['m-[-0px]', 'm-0'], - ['m-[-0rem]', 'm-0'], - - ['-m-[-0]', 'm-0'], - ['-m-[-0px]', 'm-0'], - ['-m-[-0rem]', 'm-0'], - - ['[margin:0]', 'm-0'], - ['[margin:-0]', 'm-0'], - ['[margin:0px]', 'm-0'], - - // Limit conversions for big values using the `--spacing` multiplier - ['left-[99999px]', 'left-[99999px]'], // This would otherwise result in `left-24999.75` - ['left-[-99999px]', 'left-[-99999px]'], // This would otherwise result in `-left-24999.75` - ['left-[96rem]', 'left-384'], // Within the limit - ['left-[-96rem]', '-left-384'], // Within the limit - ['left-[calc(96rem+1px)]', 'left-[calc(96rem+1px)]'], // Out of the positive limit - ['left-[calc(-96rem-1px)]', 'left-[calc(-96rem-1px)]'], // Out of the negative limit - ['z-[9999999]', 'z-9999999'], // `--spacing` multiplier is not used - - // Not a length-unit, can't safely constant fold - ['[margin:0%]', 'm-[0%]'], - - // Only 50-200% (inclusive) are valid: - // https://developer.mozilla.org/en-US/docs/Web/CSS/font-stretch#percentage - ['font-stretch-[50%]', 'font-stretch-50%'], - ['font-stretch-[50.5%]', 'font-stretch-[50.5%]'], - ['font-stretch-[201%]', 'font-stretch-[201%]'], - ['font-stretch-[49%]', 'font-stretch-[49%]'], - // Should stay as-is - ['font-stretch-[1/2]', 'font-stretch-[1/2]'], - - // Bare value with % is valid for these utilities - ['from-[28%]', 'from-28%'], - ['via-[28%]', 'via-28%'], - ['to-[28%]', 'to-28%'], - ['from-[28.5%]', 'from-[28.5%]'], - ['via-[28.5%]', 'via-[28.5%]'], - ['to-[28.5%]', 'to-[28.5%]'], - - // This test in itself is a bit flawed because `text-[1/2]` currently - // generates something. Converting it to `text-1/2` doesn't produce anything. - ['text-[1/2]', 'text-[1/2]'], - - // Leading is special, because `leading-[123]` is the direct value of 123, but - // `leading-123` maps to `calc(--spacing(123))`. - ['leading-[123]', 'leading-[123]'], - - ['data-[selected]:flex', 'data-selected:flex'], - ['data-[foo=bar]:flex', 'data-[foo=bar]:flex'], - - ['supports-[gap]:flex', 'supports-gap:flex'], - ['supports-[display:grid]:flex', 'supports-[display:grid]:flex'], - - ['group-data-[selected]:flex', 'group-data-selected:flex'], - ['group-data-[foo=bar]:flex', 'group-data-[foo=bar]:flex'], - ['group-has-data-[selected]:flex', 'group-has-data-selected:flex'], - - ['aria-[selected]:flex', 'aria-[selected]:flex'], - ['aria-[selected="true"]:flex', 'aria-selected:flex'], - ['aria-[selected*="true"]:flex', 'aria-[selected*="true"]:flex'], - - ['group-aria-[selected]:flex', 'group-aria-[selected]:flex'], - ['group-aria-[selected="true"]:flex', 'group-aria-selected:flex'], - ['group-has-aria-[selected]:flex', 'group-has-aria-[selected]:flex'], - - ['max-lg:hover:data-[selected]:flex', 'max-lg:hover:data-selected:flex'], - [ - 'data-[selected]:aria-[selected="true"]:aspect-[12/34]', - 'data-selected:aria-selected:aspect-12/34', - ], - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - await expectCanonicalization(input, candidate, expected) - }) - }) - - // https://github.com/tailwindlabs/tailwindcss/issues/20365 - describe('media query and container query variants', () => { - test.each([ - // Breakpoints (`--breakpoint-lg: 64rem`, `--breakpoint-md: 48rem`) - ['min-[64rem]:flex', 'lg:flex'], - ['max-[64rem]:flex', 'max-lg:flex'], - ['min-[48rem]:flex', 'md:flex'], - ['max-[48rem]:flex', 'max-md:flex'], - - // With the `rem` option set, equivalent `px` values should also be - // canonicalized to the breakpoint variants. - ['min-[1024px]:flex', 'lg:flex'], - ['max-[1024px]:flex', 'max-lg:flex'], - - // Container queries (`--container-md: 28rem`) - ['@min-[28rem]:flex', '@md:flex'], - ['@max-[28rem]:flex', '@max-md:flex'], - ['@min-[448px]:flex', '@md:flex'], - ['@max-[448px]:flex', '@max-md:flex'], - - // Values that don't map to a known breakpoint or container size should - // stay as-is. - ['min-[123px]:flex', 'min-[123px]:flex'], - ['max-[123px]:flex', 'max-[123px]:flex'], - ['@min-[123px]:flex', '@min-[123px]:flex'], - ['@max-[123px]:flex', '@max-[123px]:flex'], - - // Named variants that produce the same CSS should be kept as-is - ['min-lg:flex', 'min-lg:flex'], - ['@min-md:flex', '@min-md:flex'], - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected) - }) - - test.each([ - // Without the `rem` option, `px` values cannot be safely converted to - // `rem` based breakpoints. - ['min-[1024px]:flex', 'min-[1024px]:flex'], - ['@min-[448px]:flex', '@min-[448px]:flex'], - - // Same unit still works without the `rem` option - ['min-[64rem]:flex', 'lg:flex'], - ['max-[64rem]:flex', 'max-lg:flex'], - ['@min-[28rem]:flex', '@md:flex'], - ['@max-[28rem]:flex', '@max-md:flex'], - ])(`${testName} (no rem option)`, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected, { - collapse: true, - logicalToPhysical: true, - }) - }) - }) - - describe('modernize arbitrary variants', () => { - test.each([ - // Arbitrary variants - ['[[data-visible]]:flex', 'data-visible:flex'], - ['[&[data-visible]]:flex', 'data-visible:flex'], - ['[[data-visible]&]:flex', 'data-visible:flex'], - ['[&>[data-visible]]:flex', '*:data-visible:flex'], - ['[&_>_[data-visible]]:flex', '*:data-visible:flex'], - ['[&>*]:flex', '*:flex'], - ['[&_>_*]:flex', '*:flex'], - ['[&_>_[foo]]:flex', '*:[[foo]]:flex'], - ['[&_[foo]]:flex', '**:[[foo]]:flex'], - ['[&_>_[foo=bar]]:flex', '*:[[foo=bar]]:flex'], - ['[&_[foo=bar]]:flex', '**:[[foo=bar]]:flex'], - - ['[&_[data-visible]]:flex', '**:data-visible:flex'], - ['[&_*]:flex', '**:flex'], - - ['[&:first-child]:flex', 'first:flex'], - ['[&:not(:first-child)]:flex', 'not-first:flex'], - - ['[&_:first-child]:flex', '**:first:flex'], - ['[&_>_:first-child]:flex', '*:first:flex'], - ['[&_:--custom]:flex', '**:[:--custom]:flex'], - ['[&_>_:--custom]:flex', '*:[:--custom]:flex'], - - // in-* variants - ['[p_&]:flex', 'in-[p]:flex'], - ['[.foo_&]:flex', 'in-[.foo]:flex'], - ['[[data-visible]_&]:flex', 'in-data-visible:flex'], - // Multiple selectors, should stay as-is - ['[[data-foo][data-bar]_&]:flex', '[[data-foo][data-bar]_&]:flex'], - // Using `>` instead of ` ` should not be transformed: - ['[figure>&]:my-0', '[figure>&]:my-0'], - - // nth-child - ['[&:nth-child(2)]:flex', 'nth-2:flex'], - ['[&:not(:nth-child(2))]:flex', 'not-nth-2:flex'], - - ['[&:nth-child(-n+3)]:flex', 'nth-[-n+3]:flex'], - ['[&:not(:nth-child(-n+3))]:flex', 'not-nth-[-n+3]:flex'], - ['[&:nth-child(-n_+_3)]:flex', 'nth-[-n+3]:flex'], - ['[&:not(:nth-child(-n_+_3))]:flex', 'not-nth-[-n+3]:flex'], - - // nth-last-child - ['[&:nth-last-child(2)]:flex', 'nth-last-2:flex'], - ['[&:not(:nth-last-child(2))]:flex', 'not-nth-last-2:flex'], - - ['[&:nth-last-child(-n+3)]:flex', 'nth-last-[-n+3]:flex'], - ['[&:not(:nth-last-child(-n+3))]:flex', 'not-nth-last-[-n+3]:flex'], - ['[&:nth-last-child(-n_+_3)]:flex', 'nth-last-[-n+3]:flex'], - ['[&:not(:nth-last-child(-n_+_3))]:flex', 'not-nth-last-[-n+3]:flex'], - - // nth-child odd/even - ['[&:nth-child(odd)]:flex', 'odd:flex'], - ['[&:not(:nth-child(odd))]:flex', 'even:flex'], - ['[&:nth-child(even)]:flex', 'even:flex'], - ['[&:not(:nth-child(even))]:flex', 'odd:flex'], - - // Keep multiple attribute selectors as-is - ['[[data-visible][data-dark]]:flex', '[[data-visible][data-dark]]:flex'], - - // Keep `:where(…)` as is - ['[:where([data-visible])]:flex', '[:where([data-visible])]:flex'], - - // Complex attribute selectors with operators, quotes and insensitivity flags - ['[[data-url*="example"]]:flex', 'data-[url*="example"]:flex'], - ['[[data-url$=".com"_i]]:flex', 'data-[url$=".com"_i]:flex'], - ['[[data-url$=.com_i]]:flex', 'data-[url$=.com_i]:flex'], - - // Attribute selector wrapped in `&:is(…)` - ['[&:is([data-visible])]:flex', 'data-visible:flex'], - - // Media queries - ['[@media(pointer:fine)]:flex', 'pointer-fine:flex'], - ['[@media_(pointer_:_fine)]:flex', 'pointer-fine:flex'], - ['[@media_not_(pointer_:_fine)]:flex', 'not-pointer-fine:flex'], - ['[@media_print]:flex', 'print:flex'], - ['[@media_not_print]:flex', 'not-print:flex'], - - // Hoist the `:not` part to a compound variant - ['[@media_not_(prefers-color-scheme:dark)]:flex', 'not-dark:flex'], - [ - '[@media_not_(prefers-color-scheme:unknown)]:flex', - 'not-[@media_(prefers-color-scheme:unknown)]:flex', - ], - - // Compound arbitrary variants - ['has-[[data-visible]]:flex', 'has-data-visible:flex'], - ['has-[&:is([data-visible])]:flex', 'has-data-visible:flex'], - ['has-[&>[data-visible]]:flex', 'has-[&>[data-visible]]:flex'], - - ['has-[[data-slot=description]]:flex', 'has-data-[slot=description]:flex'], - ['has-[&:is([data-slot=description])]:flex', 'has-data-[slot=description]:flex'], - - ['has-[[aria-visible="true"]]:flex', 'has-aria-visible:flex'], - ['has-[[aria-visible]]:flex', 'has-aria-[visible]:flex'], - - ['has-[&:not(:nth-child(even))]:flex', 'has-odd:flex'], - - // Arbitrary variant to compound + arbitrary variants - ['[&:has([role=checkbox])]:flex', 'has-[[role=checkbox]]:flex'], - ['[&:has([aria-visible="true"])]:flex', 'has-aria-visible:flex'], - ['[&:has([data-slot=description])]:flex', 'has-data-[slot=description]:flex'], - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('optimize modifier', () => { - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - --color-red-500: red; - } - ` - - test.each([ - // Keep the modifier as-is, nothing to optimize - ['bg-red-500/25', 'bg-red-500/25'], - - // Use a bare value modifier - ['bg-red-500/[25%]', 'bg-red-500/25'], - - // Convert 0-1 values to bare values - ['bg-[#f00]/[0.16]', 'bg-[#f00]/16'], - - // Drop unnecessary modifiers - ['bg-red-500/[100%]', 'bg-red-500'], - ['bg-red-500/100', 'bg-red-500'], - - // Keep modifiers on classes that don't _really_ exist - ['group/name', 'group/name'], - ])(testName, { timeout }, async (candidate, expected) => { - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('combine to shorthand utilities', () => { - test.each([ - // 4 to 1 - ['mt-1 mr-1 mb-1 ml-1', 'm-1'], - ['border-t-123 border-r-123 border-b-123 border-l-123', 'border-123'], - ['border-t-1 border-r-1 border-b-1 border-l-1', 'border'], // `border` is shorter than `border-1` - ['border-t-red-500 border-r-red-500 border-b-red-500 border-l-red-500', 'border-red-500'], - ['scroll-mt-1 scroll-mr-1 scroll-mb-1 scroll-ml-1', 'scroll-m-1'], - ['scroll-pt-1 scroll-pr-1 scroll-pb-1 scroll-pl-1', 'scroll-p-1'], - - // 2 to 1 - ['mt-1 mb-1', 'my-1'], - ['border-t-123 border-b-123', 'border-y-123'], - ['border-t-1 border-b-1', 'border-y'], // `border-y` is shorter than `border-y-1` - ['border-t-red-500 border-b-red-500', 'border-y-red-500'], - ['scroll-mt-1 scroll-mb-1', 'scroll-my-1'], - ['scroll-pt-1 scroll-pb-1', 'scroll-py-1'], - ['overflow-x-hidden overflow-y-hidden', 'overflow-hidden'], - ['overscroll-x-contain overscroll-y-contain', 'overscroll-contain'], - - // Different order as above - ['mb-1 mt-1', 'my-1'], - - // To completely different utility - ['w-4 h-4', 'size-4'], - - // Goes beyond the default spacing scale that's being used in intellisense - // for code completion. Since it's about bare values, we should still be - // able to combine them. - ['w-123 h-123', 'size-123'], - ['w-128 h-128', 'size-128'], // `w-128` on its own would become `w-lg` - ['mt-123 mb-123', 'my-123'], - - // Collapse duplicates into themselves - ['w-8 w-8', 'w-8'], - - // `w-*` and `h-*` would canonicalize to `size-5` - // `size-5` and `size-5` should then canonicalize to `size-5` - ['w-[calc(1rem+0.25rem)] h-[calc(1rem+0.25rem)] size-5', 'size-5'], - - // Same as above, but with an additional unrelated class - ['w-[calc(1rem+0.25rem)] h-[calc(1rem+0.25rem)] size-5 flex', 'size-5 flex'], - - // Do not touch if not operating on the same variants - ['hover:w-4 h-4', 'hover:w-4 h-4'], - - // Arbitrary properties to combined class - ['[width:_16px_] [height:16px]', 'size-4'], - - // Arbitrary properties to combined class with modifier - ['[font-size:14px] [line-height:1.625]', 'text-sm/relaxed'], - ])(testName, { timeout }, async (candidates, expected) => { - let input = css` - @import 'tailwindcss'; - ` - await expectCanonicalization(input, candidates, expected) - }) - }) - - describe('font-size/line-height to text-{x}/{y}', () => { - test.each([ - ...Array.from( - cartesian( - ['[font-size:14px]', 'text-[14px]', 'text-[14px]/6', 'text-sm', 'text-sm/6'], - ['[line-height:28px]', 'leading-[28px]', 'leading-7'], - ), - ).map((classes) => [classes.join(' ').padEnd(40, ' '), 'text-sm/7']), - ...Array.from( - cartesian( - ['[font-size:15px]', 'text-[15px]', 'text-[15px]/6'], - ['[line-height:28px]', 'leading-[28px]', 'leading-7'], - ), - ).map((classes) => [classes.join(' ').padEnd(40, ' '), 'text-[15px]/7']), - ...Array.from( - cartesian( - ['[font-size:14px]', 'text-[14px]', 'text-[14px]/6', 'text-sm', 'text-sm/6'], - ['[line-height:28.5px]', 'leading-[28.5px]'], - ), - ).map((classes) => [classes.join(' ').padEnd(40, ' '), 'text-sm/[28.5px]']), - ...Array.from( - cartesian( - ['[font-size:15px]', 'text-[15px]', 'text-[15px]/6'], - ['[line-height:28.5px]', 'leading-[28.5px]'], - ), - ).map((classes) => [classes.join(' ').padEnd(40, ' '), 'text-[15px]/[28.5px]']), - ])(testName, { timeout }, async (candidates, expected) => { - let input = css` - @import 'tailwindcss'; - ` - await expectCanonicalization(input, candidates.trim(), expected) - }) - }) - - // https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1558 - test.each([ - ['tracking-[-0.05em]', 'tracking-tighter'], - ['tracking-[-0.025em]', 'tracking-tight'], - ['tracking-[0em]', 'tracking-normal'], - ['tracking-[0.025em]', 'tracking-wide'], - ['tracking-[0.05em]', 'tracking-wider'], - ['tracking-[0.1em]', 'tracking-widest'], - - // Negative values that don't make sense - // See: https://tailwindcss.com/docs/letter-spacing#using-negative-values - ['-tracking-tighter', 'tracking-wider'], - ['-tracking-tight', 'tracking-wide'], - ['-tracking-normal', 'tracking-normal'], - ['-tracking-wide', 'tracking-tight'], - ['-tracking-wider', 'tracking-tighter'], - ])(testName, { timeout }, async (candidate, expected) => { - await expectCanonicalization( - css` - @import 'tailwindcss'; - @theme { - --tracking-tighter: -0.05em; - --tracking-tight: -0.025em; - --tracking-normal: 0em; - --tracking-wide: 0.025em; - --tracking-wider: 0.05em; - --tracking-widest: 0.1em; - } - `, - candidate, - expected, - ) - }) - - test.each([ - // Keep whitespace characters that are significant - ['[&:has(~_*_*:checked)]:flex', '[&:has(~_*_*:checked)]:flex'], - [ - 'shadow-[inset_0px_1px_--theme(--color-white/15%)]', - 'shadow-[inset_0px_1px_--theme(--color-white/15%)]', - ], - - // Improve readability when whitespace was used for readability - ['w-[calc(100%_-_calc(var(--spacing)*60))]', 'w-[calc(100%-(--spacing(60)))]'], - ['w-[calc(100%_-_--spacing(60))]', 'w-[calc(100%-(--spacing(60)))]'], - - // No need to wrap in `(…)` after a `,` - ['m-[min(100%,_--spacing(6))]', 'm-[min(100%,--spacing(6))]'], - ['m-[min(100%_,_--spacing(6))]', 'm-[min(100%,--spacing(6))]'], - ['m-[min(100%,--spacing(6))]', 'm-[min(100%,--spacing(6))]'], - ])(testName, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected) - }) - - // https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1573 - test.each([ - ['-mt-[0.04in]', 'mt-[-0.04in]'], - ['mt-[-0.04in]', 'mt-[-0.04in]'], - ['-mt-[-0.04in]', 'mt-[0.04in]'], - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected) - }) - - // https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1591 - test.each([ - ['w-[calc(100%/3.5)]', 'w-[calc(100%/3.5)]'], // Not w-[28.571428571428573%] - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected) - }) -}) - -describe('theme to var', () => { - test('extended space scale converts to var or calc', { timeout }, async () => { - let designSystem = await __unstable__loadDesignSystem( - css` - @tailwind utilities; - @theme { - --spacing: 0.25rem; - --spacing-2: 2px; - --spacing-miami: 0.875rem; - } - `, - { base: __dirname }, - ) - expect( - designSystem.canonicalizeCandidates([ - '[--value:theme(spacing.1)]', - '[--value:theme(spacing.2)]', - '[--value:theme(spacing.miami)]', - '[--value:theme(spacing.nyc)]', - ]), - ).toEqual([ - '[--value:--spacing(1)]', - '[--value:var(--spacing-2)]', - '[--value:var(--spacing-miami)]', - '[--value:theme(spacing.nyc)]', - ]) - }) - - test('custom space scale converts to var', { timeout }, async () => { - let designSystem = await __unstable__loadDesignSystem( - css` - @tailwind utilities; - @theme { - --spacing-*: initial; - --spacing-1: 0.25rem; - --spacing-2: 0.5rem; - } - `, - { base: __dirname }, - ) - expect( - designSystem.canonicalizeCandidates([ - '[--value:theme(spacing.1)]', - '[--value:theme(spacing.2)]', - '[--value:theme(spacing.3)]', - ]), - ).toEqual([ - '[--value:var(--spacing-1)]', - '[--value:var(--spacing-2)]', - '[--value:theme(spacing.3)]', - ]) - }) -}) - -describe('options', () => { - test('normalize `rem` units to `px`', { timeout }, async () => { - let designSystem = await __unstable__loadDesignSystem( - css` - @tailwind utilities; - @theme { - --spacing: 0.25rem; - } - `, - { base: __dirname }, - ) - - expect(designSystem.canonicalizeCandidates(['m-[16px]'])).toEqual(['m-[16px]']) - expect(designSystem.canonicalizeCandidates(['m-[16px]'], { rem: 16 })).toEqual(['m-4']) - expect(designSystem.canonicalizeCandidates(['m-[16px]'], { rem: 64 })).toEqual(['m-1']) - expect(designSystem.canonicalizeCandidates(['m-[16px]'])).toEqual(['m-[16px]']) // Ensure options don't influence shared state - }) -}) - -describe('regressions', () => { - // https://github.com/schoero/eslint-plugin-better-tailwindcss/issues/321 - { - test('a subset of classes should be canonicalizable', { timeout }, async () => { - let designSystem = await designSystems.get(__dirname).get(css` - @import 'tailwindcss'; - `) - - let options: CanonicalizeOptions = { - collapse: true, - logicalToPhysical: true, - rem: 16, - } - - expect( - designSystem.canonicalizeCandidates(['underline', 'h-4', 'w-4', 'text-sm'], options), - ).toEqual(['underline', 'text-sm', 'size-4']) - }) - - test('collapse canonicalization is not affected by previous calls', { timeout }, async () => { - let designSystem = await designSystems.get(__dirname).get(css` - @import 'tailwindcss'; - `) - - let options: CanonicalizeOptions = { - collapse: true, - logicalToPhysical: true, - rem: 16, - } - - let target = ['underline', 'h-4', 'w-4'] - - expect(designSystem.canonicalizeCandidates(target, options)).toEqual(['underline', 'size-4']) - - designSystem.canonicalizeCandidates(['mb-4', 'text-sm'], options) - designSystem.canonicalizeCandidates(['underline', 'mb-4'], options) - - expect(designSystem.canonicalizeCandidates(target, options)).toEqual(['underline', 'size-4']) - expect(designSystem.canonicalizeCandidates(target.concat('text-sm'), options)).toEqual([ - 'underline', - 'text-sm', - 'size-4', - ]) - }) - } - - // https://github.com/tailwindlabs/tailwindcss/pull/19727 - test( - 'collapse does not crash when utilities with no standard properties are present', - { timeout }, - async () => { - let designSystem = await designSystems.get(__dirname).get(css` - @import 'tailwindcss'; - `) - - let options: CanonicalizeOptions = { - collapse: true, - logicalToPhysical: true, - rem: 16, - } - - // Shadow utilities use CSS custom properties and @property rules but may - // produce empty property maps in the collapse algorithm. This should not - // crash with "Cannot read properties of null" or "X is not iterable". - expect(() => - designSystem.canonicalizeCandidates(['shadow-sm', 'border'], options), - ).not.toThrow() - - expect(() => designSystem.canonicalizeCandidates(['shadow-md', 'p-4'], options)).not.toThrow() - - expect(() => - designSystem.canonicalizeCandidates(['shadow-sm', 'shadow-md'], options), - ).not.toThrow() - - // Verify the candidates are returned (not collapsed, since shadows can't - // meaningfully collapse with unrelated utilities) - expect(designSystem.canonicalizeCandidates(['shadow-sm', 'border'], options)).toEqual( - expect.arrayContaining(['shadow-sm', 'border']), - ) - - expect(designSystem.canonicalizeCandidates(['shadow-sm', 'shadow-md'], options)).toEqual( - expect.arrayContaining(['shadow-sm', 'shadow-md']), - ) - }, - ) - - // https://github.com/tailwindlabs/tailwindcss/issues/19835 - test.each([ - // Arbitrary values should be collapsed to another arbitrary value - [ - ['px-[1.2rem]', 'py-[1.2rem]', 'text-left'], - ['text-left', 'p-[1.2rem]'], - ], - - // Arbitrary values could also be collapsed into a bare value - [ - ['px-[30.75rem]', 'py-[30.75rem]', 'text-left'], - ['text-left', 'p-123'], - ], - ])( - 'collapse canonicalization works for arbitrary values', - { timeout }, - async (candidates, expected) => { - let designSystem = await designSystems.get(__dirname).get(css` - @import 'tailwindcss'; - `) - - let options: CanonicalizeOptions = { - collapse: true, - logicalToPhysical: true, - rem: 16, - } - - expect(designSystem.canonicalizeCandidates(candidates, options)).toEqual(expected) - }, - ) - - // https://github.com/tailwindlabs/tailwindcss/issues/20051 - test( - 'does not crash when plugin matchComponents rejects speculative values during collapse', - { timeout }, - async () => { - let designSystem = await __unstable__loadDesignSystem( - css` - @import 'tailwindcss'; - @plugin "./plugin.js"; - `, - { - async loadStylesheet(_, base) { - return { - base, - path: '', - content: '@tailwind utilities;', - } - }, - async loadModule() { - return { - base: '', - path: '', - module: plugin(({ matchComponents }) => { - matchComponents( - { - myicon: () => { - throw new Error('Mimic as-if a custom plugin failed for some reason') - }, - }, - { values: { icon: __filename } }, - ) - }), - } - }, - }, - ) - - expect( - designSystem.canonicalizeCandidates(['border-[1.5px]', 'flex'], { - collapse: true, - logicalToPhysical: true, - rem: 16, - }), - ).toEqual(expect.arrayContaining(['border-[1.5px]', 'flex'])) - }, - ) - - // https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1579 - test('does not suggest invalid alternative when canonicalizing calc expressions', async () => { - let designSystem = await designSystems.get(__dirname).get(css` - @import 'tailwindcss'; - `) - - let options: CanonicalizeOptions = { - collapse: true, - logicalToPhysical: true, - rem: 16, - } - - expect(designSystem.canonicalizeCandidates(['px-[calc(1rem+0px)]'], options)).toEqual(['px-4']) - }) - - // https://github.com/tailwindlabs/tailwindcss/issues/20295 - test('canonicalizations work when casing of arbitrary values is different', async () => { - let designSystem = await designSystems.get(__dirname).get(css` - @import 'tailwindcss'; - @theme { - --color-brand-purple: #3f3cbb; - } - `) - - let options: CanonicalizeOptions = { - collapse: true, - logicalToPhysical: true, - rem: 16, - } - - expect(designSystem.canonicalizeCandidates(['bg-[#3F3cbb]'], options)).toEqual([ - 'bg-brand-purple', - ]) - expect(designSystem.canonicalizeCandidates(['bg-[#3F3Cbb]'], options)).toEqual([ - 'bg-brand-purple', - ]) - expect(designSystem.canonicalizeCandidates(['bg-[#3F3CBB]'], options)).toEqual([ - 'bg-brand-purple', - ]) - }) - - // https://github.com/tailwindlabs/tailwindcss/issues/20365 - test('canonicalizes min/max media query and container query variants', async () => { - let designSystem = await __unstable__loadDesignSystem( - css` - @tailwind utilities; - @theme { - --breakpoint-lg: 64rem; - --container-lg: 32rem; - --spacing: 0.25rem; - } - `, - { base: __dirname }, - ) - - expect(designSystem.canonicalizeCandidates(['min-[64rem]:flex'])).toEqual(['lg:flex']) - expect(designSystem.canonicalizeCandidates(['max-[64rem]:flex'])).toEqual(['max-lg:flex']) - expect(designSystem.canonicalizeCandidates(['@min-[32rem]:flex'])).toEqual(['@lg:flex']) - expect(designSystem.canonicalizeCandidates(['@max-[32rem]:flex'])).toEqual(['@max-lg:flex']) - expect(designSystem.canonicalizeCandidates(['min-[1024px]:flex'], { rem: 16 })).toEqual([ - 'lg:flex', - ]) - }) - - // https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1610 - test('does not merge utilities whose theme variables resolve to CSS-wide keywords', async () => { - let designSystem = await __unstable__loadDesignSystem( - css` - @tailwind utilities; - @theme { - --foreground: unset; - --default: unset; - } - @theme inline { - --color-foreground: var(--foreground); - --color-default-soft-hover: color-mix(in oklab, var(--default) 60%, transparent); - } - `, - { base: __dirname }, - ) - - expect( - designSystem.canonicalizeCandidates(['text-foreground/60', 'text-default-soft-hover']), - ).toEqual(['text-foreground/60', 'text-default-soft-hover']) - }) -}) diff --git a/packages/tailwindcss/src/canonicalize-candidates.ts b/packages/tailwindcss/src/canonicalize-candidates.ts deleted file mode 100644 index d1dfc09de..000000000 --- a/packages/tailwindcss/src/canonicalize-candidates.ts +++ /dev/null @@ -1,3062 +0,0 @@ -import { substituteAtApply } from './apply' -import { atRule, cloneAstNode, styleRule, toCss, type AstNode } from './ast' -import * as AttributeSelectorParser from './attribute-selector-parser' -import { - cloneCandidate, - cloneVariant, - printArbitraryValue, - printModifier, - type Candidate, - type CandidateModifier, - type NamedUtilityValue, - type Variant, -} from './candidate' -import { canonicalizeCalcExpressionsAst } from './canonicalize-calc-expressions' -import { keyPathToCssProperty } from './compat/apply-config-to-theme' -import { constantFoldDeclaration, constantFoldDeclarationAst } from './constant-fold-declaration' -import type { DesignSystem as BaseDesignSystem } from './design-system' -import { CompileAstFlags } from './design-system' -import { expandDeclaration } from './expand-declaration' -import * as SelectorParser from './selector-parser' -import { ThemeOptions } from './theme' -import type { Writable } from './types' -import { DefaultMap } from './utils/default-map' -import { dimensions } from './utils/dimensions' -import { isPositiveInteger, isValidSpacingMultiplier } from './utils/infer-data-type' -import { replaceObject } from './utils/replace-object' -import { segment } from './utils/segment' -import { toKeyPath } from './utils/to-key-path' -import * as ValueParser from './value-parser' -import { walk, WalkAction } from './walk' - -export interface CanonicalizeOptions { - /** - * The root font size in pixels. If provided, `rem` values will be normalized - * to `px` values. - * - * E.g.: `mt-[16px]` with `rem: 16` will become `mt-4` (assuming `--spacing: 0.25rem`). - */ - rem?: number - - /** - * Whether to collapse multiple utilities into a single utility if possible. - * - * E.g.: `mt-2 mr-2 mb-2 ml-2` → `m-2` - */ - collapse?: boolean - - /** - * Whether to convert between logical and physical properties when collapsing - * utilities. - * - * E.g.: `mr-2 ml-2` → `mx-2` - */ - logicalToPhysical?: boolean -} - -enum Features { - /** - * No features enabled (default) - */ - None = 0, - - /** - * Collapse multiple utilities into a single utility if possible. - */ - CollapseUtilities = 1 << 0, -} - -interface InternalCanonicalizeOptions { - features: Features - designSystem: DesignSystem - signatureOptions: SignatureOptions -} - -type RemValue = number | null - -interface DesignSystem extends BaseDesignSystem { - storage: { - [SIGNATURE_OPTIONS_KEY]: DefaultMap> - [COMPARE_CANDIDATES_KEY]: DefaultMap< - SignatureOptions, - (a: Candidate | string, b: Candidate | string) => boolean - > - [INTERNAL_OPTIONS_KEY]: DefaultMap< - SignatureOptions, - DefaultMap - > - [CANONICALIZE_CANDIDATE_KEY]: DefaultMap< - InternalCanonicalizeOptions, - DefaultMap - > - [CANONICALIZE_VARIANT_KEY]: DefaultMap< - InternalCanonicalizeOptions, - DefaultMap - > - [CANONICALIZE_UTILITY_KEY]: DefaultMap> - [CONVERTER_KEY]: (input: string, options?: Convert) => [string, CandidateModifier | null] - [SPACING_KEY]: DefaultMap | null - [UTILITY_SIGNATURE_KEY]: DefaultMap> - [STATIC_UTILITIES_KEY]: DefaultMap< - SignatureOptions, - DefaultMap>> - > - [UTILITY_PROPERTIES_KEY]: DefaultMap< - SignatureOptions, - DefaultMap>> - > - [PRE_COMPUTED_UTILITIES_KEY]: DefaultMap> - [VARIANT_SIGNATURE_KEY]: DefaultMap> - [PRE_COMPUTED_VARIANTS_KEY]: DefaultMap> - } -} - -export function prepareDesignSystemStorage( - baseDesignSystem: BaseDesignSystem, - options?: CanonicalizeOptions, -): DesignSystem { - let designSystem = baseDesignSystem as DesignSystem - - designSystem.storage[SIGNATURE_OPTIONS_KEY] ??= createSignatureOptionsCache() - designSystem.storage[INTERNAL_OPTIONS_KEY] ??= createInternalOptionsCache(designSystem) - designSystem.storage[CANONICALIZE_CANDIDATE_KEY] ??= createCanonicalizeCandidateCache() - designSystem.storage[CANONICALIZE_VARIANT_KEY] ??= createCanonicalizeVariantCache() - designSystem.storage[CANONICALIZE_UTILITY_KEY] ??= createCanonicalizeUtilityCache() - designSystem.storage[CONVERTER_KEY] ??= createConverterCache(designSystem) - designSystem.storage[SPACING_KEY] ??= createSpacingCache(designSystem, options) - designSystem.storage[UTILITY_SIGNATURE_KEY] ??= createUtilitySignatureCache(designSystem) - designSystem.storage[STATIC_UTILITIES_KEY] ??= createStaticUtilitiesCache() - designSystem.storage[UTILITY_PROPERTIES_KEY] ??= createUtilityPropertiesCache(designSystem) - designSystem.storage[PRE_COMPUTED_UTILITIES_KEY] ??= createPreComputedUtilitiesCache(designSystem) - designSystem.storage[VARIANT_SIGNATURE_KEY] ??= createVariantSignatureCache(designSystem) - designSystem.storage[PRE_COMPUTED_VARIANTS_KEY] ??= createPreComputedVariantsCache(designSystem) - designSystem.storage[COMPARE_CANDIDATES_KEY] ??= createSignatureComparison(designSystem) - - return designSystem -} - -const SIGNATURE_OPTIONS_KEY = Symbol() -function createSignatureOptionsCache(): DesignSystem['storage'][typeof SIGNATURE_OPTIONS_KEY] { - return new DefaultMap((rem: number | null) => { - return new DefaultMap((features: SignatureFeatures) => { - return { rem, features } satisfies SignatureOptions - }) - }) -} - -const COMPARE_CANDIDATES_KEY = Symbol() -function createSignatureComparison(designSystem: DesignSystem) { - return new DefaultMap((options: SignatureOptions) => { - let signatures = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options) - - return function hasSameSignature(a: Candidate | string, b: Candidate | string): boolean { - let aCandidateString = typeof a === 'string' ? a : designSystem.printCandidate(a) - let aSignature = signatures.get(aCandidateString) - if (typeof aSignature !== 'string') return false - - let bCandidateString = typeof b === 'string' ? b : designSystem.printCandidate(b) - let bSignature = signatures.get(bCandidateString) - if (typeof bSignature !== 'string') return false - - return aSignature === bSignature - } - }) -} - -export function createSignatureOptions( - baseDesignSystem: BaseDesignSystem, - options?: CanonicalizeOptions, -): SignatureOptions { - let features = SignatureFeatures.None - if (options?.collapse) features |= SignatureFeatures.ExpandProperties - if (options?.logicalToPhysical) features |= SignatureFeatures.LogicalToPhysical - - let designSystem = prepareDesignSystemStorage(baseDesignSystem, options) - - return designSystem.storage[SIGNATURE_OPTIONS_KEY].get(options?.rem ?? null).get(features) -} - -const INTERNAL_OPTIONS_KEY = Symbol() -function createInternalOptionsCache( - designSystem: DesignSystem, -): DesignSystem['storage'][typeof INTERNAL_OPTIONS_KEY] { - return new DefaultMap((signatureOptions: SignatureOptions) => { - return new DefaultMap((features: Features) => { - return { features, designSystem, signatureOptions } satisfies InternalCanonicalizeOptions - }) - }) -} - -function createCanonicalizeOptions( - baseDesignSystem: BaseDesignSystem, - signatureOptions: SignatureOptions, - options?: CanonicalizeOptions, -) { - let features = Features.None - if (options?.collapse) features |= Features.CollapseUtilities - - let designSystem = prepareDesignSystemStorage(baseDesignSystem) - - return designSystem.storage[INTERNAL_OPTIONS_KEY].get(signatureOptions).get(features) -} - -export function canonicalizeCandidates( - baseDesignSystem: BaseDesignSystem, - candidates: string[], - options?: CanonicalizeOptions, -): string[] { - let signatureOptions = createSignatureOptions(baseDesignSystem, options) - let canonicalizeOptions = createCanonicalizeOptions(baseDesignSystem, signatureOptions, options) - - let designSystem = prepareDesignSystemStorage(baseDesignSystem) - - let result = new Set() - let cache = designSystem.storage[CANONICALIZE_CANDIDATE_KEY].get(canonicalizeOptions) - for (let candidate of candidates) { - result.add(cache.get(candidate)) - } - - return result.size <= 1 || !(canonicalizeOptions.features & Features.CollapseUtilities) - ? Array.from(result) - : collapseCandidates(canonicalizeOptions, Array.from(result)) -} - -function collapseCandidates(options: InternalCanonicalizeOptions, candidates: string[]): string[] { - let designSystem = options.designSystem - - // To keep things simple, we group candidates such that we only collapse - // candidates with the same variants and important modifier together. - let groups = new DefaultMap((_before: string) => { - return new DefaultMap((_after: string) => { - return new Set() - }) - }) - - let prefix = options.designSystem.theme.prefix ? `${options.designSystem.theme.prefix}:` : '' - - for (let candidate of candidates) { - let variants = segment(candidate, ':') - let utility = variants.pop()! - - let important = utility.endsWith('!') - if (important) { - utility = utility.slice(0, -1) - } - - let before = variants.length > 0 ? `${variants.join(':')}:` : '' - let after = important ? '!' : '' - - // Group by variants and important flag - groups.get(before).get(after).add(`${prefix}${utility}`) - } - - let result = new Set() - for (let [before, group] of groups.entries()) { - for (let [after, candidates] of group.entries()) { - for (let candidate of collapseGroup(Array.from(candidates))) { - // Drop the prefix if we had one, because the prefix is already there as - // part of the variants. - if (prefix && candidate.startsWith(prefix)) { - candidate = candidate.slice(prefix.length) - } - - result.add(`${before}${candidate}${after}`) - } - } - } - - return Array.from(result) - - function collapseGroup(candidates: string[]) { - let signatureOptions = options.signatureOptions - let computeUtilitiesPropertiesLookup = - designSystem.storage[UTILITY_PROPERTIES_KEY].get(signatureOptions) - let staticUtilities = designSystem.storage[STATIC_UTILITIES_KEY].get(signatureOptions) - - // For each candidate, compute the used properties and values. E.g.: `mt-1` → `margin-top` → `0.25rem` - // - // NOTE: Currently assuming we are dealing with static utilities only. This - // will change the moment we have `@utility` for most built-ins. - let candidatePropertiesValues = candidates.map((candidate) => - computeUtilitiesPropertiesLookup.get(candidate), - ) - - // Hard-coded optimization: if any candidate sets `line-height` and another - // candidate sets `font-size`, we pre-compute the `text-*` utilities with - // this line-height to try and collapse to those combined values. - if (candidatePropertiesValues.some((x) => x.has('line-height'))) { - let fontSizeNames = designSystem.theme.keysInNamespaces(['--text']) - if (fontSizeNames.length > 0) { - let interestingLineHeights = new Set() - let seenLineHeights = new Set() - for (let pairs of candidatePropertiesValues) { - if (!pairs.has('line-height')) continue - - for (let lineHeight of pairs.get('line-height')) { - if (seenLineHeights.has(lineHeight)) continue - seenLineHeights.add(lineHeight) - - let bareValue = designSystem.storage[SPACING_KEY]?.get(lineHeight) ?? null - if (bareValue !== null) { - if (isValidSpacingMultiplier(bareValue)) { - interestingLineHeights.add(bareValue) - - for (let name of fontSizeNames) { - computeUtilitiesPropertiesLookup.get(`text-${name}/${bareValue}`) - } - } else { - interestingLineHeights.add(lineHeight) - - for (let name of fontSizeNames) { - computeUtilitiesPropertiesLookup.get(`text-${name}/[${lineHeight}]`) - } - } - } - } - } - - let seenFontSizes = new Set() - for (let pairs of candidatePropertiesValues) { - if (!pairs.has('font-size')) continue - - for (let fontSize of pairs.get('font-size')) { - if (seenFontSizes.has(fontSize)) continue - seenFontSizes.add(fontSize) - - for (let lineHeight of interestingLineHeights) { - if (isValidSpacingMultiplier(lineHeight)) { - computeUtilitiesPropertiesLookup.get(`text-[${fontSize}]/${lineHeight}`) - } else { - computeUtilitiesPropertiesLookup.get(`text-[${fontSize}]/[${lineHeight}]`) - } - } - } - } - } - } - - let dynamicUtilities = new DefaultMap((candidate: string) => { - let result = new DefaultMap( - (_property: string) => new DefaultMap((_value: string) => new Set()), - ) - - let relevantProperties = new Set(computeUtilitiesPropertiesLookup.get(candidate).keys()) - if (relevantProperties.size === 0) return result - - for (let parsedCandidate of parseCandidate(designSystem, candidate)) { - if (parsedCandidate.kind !== 'functional' || parsedCandidate.value === null) { - continue - } - - for (let root of designSystem.utilities.keys('functional')) { - if (root === parsedCandidate.root) continue // Skip self - - let replacement = printUnprefixedCandidate(designSystem, { - ...cloneCandidate(parsedCandidate), - root, - }) - - let propertyValues = computeUtilitiesPropertiesLookup.get(replacement) - for (let [property, values] of propertyValues) { - if (!relevantProperties.has(property)) continue // Skip properties that are not relevant for the current candidate - - for (let value of values) { - result.get(property).get(value).add(replacement) - } - } - } - - return result - } - - return result - }) - - // For each property, lookup other utilities that also set this property and - // this exact value. If multiple properties are used, use the intersection of - // each property. - // - // E.g.: `margin-top` → `mt-1`, `my-1`, `m-1` - let otherUtilities = candidatePropertiesValues.map((propertyValues, idx) => { - let result: Set | null = null - for (let property of propertyValues.keys()) { - let otherUtilities = new Set() - for (let group of staticUtilities.get(property).values()) { - for (let candidate of group) { - otherUtilities.add(candidate) - } - } - - for (let value of propertyValues.get(property)) { - for (let candidate of dynamicUtilities.get(candidates[idx]).get(property).get(value)) { - otherUtilities.add(candidate) - } - } - - if (result === null) result = otherUtilities - else result = intersection(result, otherUtilities) - - // The moment no other utilities match, we can stop searching because - // all intersections with an empty set will remain empty. - if (result!.size === 0) return result! - } - return result ?? new Set() - }) - - // Link each candidate that could be linked via another utility - // (intersection). This way we can reduce the amount of required combinations. - // - // E.g.: `mt-1` and `mb-1` can be linked via `my-1`. - // - // Candidates that cannot be linked won't be able to be collapsed. - // E.g.: `mt-1` and `text-red-500` cannot be collapsed because there is no 3rd - // utility with overlapping property/value combinations. - let linked = new DefaultMap>((key) => new Set([key])) - for (let i = 0; i < otherUtilities.length; i++) { - let current = otherUtilities[i] - for (let j = i + 1; j < otherUtilities.length; j++) { - let other = otherUtilities[j] - - for (let property of current) { - if (other.has(property)) { - linked.get(i).add(j) - linked.get(j).add(i) - - // The moment we find a link, we can stop comparing and move on to the - // next candidate. This will safe us some time - break - } - } - } - } - - // Not a single candidate can be linked to another one, nothing left to do - if (linked.size === 0) return candidates - - // Each candidate index will now have a set of other candidate indexes as - // its value. Let's make the lists unique combinations so that we can - // iterate over them. - let uniqueCombinations = new DefaultMap((key: string) => key.split(',').map(Number)) - for (let group of linked.values()) { - let sorted = Array.from(group).sort((a, b) => a - b) - uniqueCombinations.get(sorted.join(',')) - } - - // Let's try to actually collapse them now. - let result = new Set(candidates) - let drop = new Set() - - for (let idxs of uniqueCombinations.values()) { - for (let combo of combinations(idxs)) { - if (combo.some((idx) => drop.has(candidates[idx]))) continue // Skip already dropped items - - let potentialReplacements = combo.flatMap((idx) => otherUtilities[idx]).reduce(intersection) - - let collapsedSignature = designSystem.storage[UTILITY_SIGNATURE_KEY].get( - signatureOptions, - ).get( - combo - .map((idx) => candidates[idx]) - .sort((a, z) => a.localeCompare(z)) // Sort to increase cache hits - .join(' '), - ) - - for (let replacement of potentialReplacements) { - let signature = - designSystem.storage[UTILITY_SIGNATURE_KEY].get(signatureOptions).get(replacement) - if (signature !== collapsedSignature) continue // Not a safe replacement - - // Use the replacement - result.add(replacement) - - // We can replace all items in the combo with the replacement. If the - // replacement is already part of the combo, keep that one around. - for (let item of combo) { - if (candidates[item] !== replacement) { - drop.add(candidates[item]) - } - } - - break - } - } - } - - for (let item of drop) { - result.delete(item) - } - - return Array.from(result) - } -} - -const CANONICALIZE_CANDIDATE_KEY = Symbol() -function createCanonicalizeCandidateCache(): DesignSystem['storage'][typeof CANONICALIZE_CANDIDATE_KEY] { - return new DefaultMap((options: InternalCanonicalizeOptions) => { - let ds = options.designSystem - let prefix = ds.theme.prefix ? `${ds.theme.prefix}:` : '' - let variantCache = ds.storage[CANONICALIZE_VARIANT_KEY].get(options) - let utilityCache = ds.storage[CANONICALIZE_UTILITY_KEY].get(options) - - return new DefaultMap((rawCandidate: string, self) => { - for (let candidate of ds.parseCandidate(rawCandidate)) { - let variants = candidate.variants - .slice() - .reverse() - .flatMap((variant) => variantCache.get(variant)) - let important = candidate.important - - // Canonicalize the base candidate (utility), and re-attach the variants - // and important flag afterwards. This way we can maximize cache hits for - // the base candidate and each individual variant. - if (important || variants.length > 0) { - let canonicalizedUtility = self.get( - ds.printCandidate({ ...candidate, variants: [], important: false }), - ) - - // Rebuild the final candidate - let result = canonicalizedUtility - - // Remove the prefix if there are variants, because the variants exist - // between the prefix and the base candidate. - if (ds.theme.prefix !== null && variants.length > 0) { - result = result.slice(prefix.length) - } - - // Re-attach the variants - if (variants.length > 0) { - result = `${variants.map((v) => ds.printVariant(v)).join(':')}:${result}` - } - - // Re-attach the important flag - if (important) { - result += '!' - } - - // Re-attach the prefix if there were variants - if (ds.theme.prefix !== null && variants.length > 0) { - result = `${prefix}${result}` - } - - return result - } - - // We are guaranteed to have no variants and no important flag, just the - // base candidate left to canonicalize. - let result = utilityCache.get(rawCandidate) - if (result !== rawCandidate) { - return result - } - } - - return rawCandidate - }) - }) -} - -type VariantCanonicalizationFunction = ( - variant: Variant, - options: InternalCanonicalizeOptions, -) => Variant | Variant[] - -const VARIANT_CANONICALIZATIONS: VariantCanonicalizationFunction[] = [ - themeToVarVariant, - arbitraryValueToBareValueVariant, - modernizeArbitraryValuesVariant, - arbitraryVariants, -] - -const CANONICALIZE_VARIANT_KEY = Symbol() -function createCanonicalizeVariantCache(): DesignSystem['storage'][typeof CANONICALIZE_VARIANT_KEY] { - return new DefaultMap((options: InternalCanonicalizeOptions) => { - return new DefaultMap((variant: Variant): Variant[] => { - let replacement = [variant] - for (let fn of VARIANT_CANONICALIZATIONS) { - for (let current of replacement.splice(0)) { - // A single variant can result in multiple variants, e.g.: - // `[&>[data-selected]]:flex` → `*:data-selected:flex` - let result = fn(cloneVariant(current), options) - if (Array.isArray(result)) { - replacement.push(...result) - continue - } else { - replacement.push(result) - } - } - } - return replacement - }) - }) -} - -type UtilityCanonicalizationFunction = ( - candidate: Candidate, - options: InternalCanonicalizeOptions, -) => Candidate - -const UTILITY_CANONICALIZATIONS: UtilityCanonicalizationFunction[] = [ - bgGradientToLinear, - themeToVarUtility, - calcToSpacingFunction, - optimizeArbitraryValueExpressions, - arbitraryUtilities, - bareValueUtilities, - deprecatedUtilities, - dropUnnecessaryDataTypes, - arbitraryValueToBareValueUtility, - optimizeModifier, -] - -const CANONICALIZE_UTILITY_KEY = Symbol() -function createCanonicalizeUtilityCache(): DesignSystem['storage'][typeof CANONICALIZE_UTILITY_KEY] { - return new DefaultMap((options: InternalCanonicalizeOptions) => { - let designSystem = options.designSystem - return new DefaultMap((rawCandidate: string): string => { - for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) { - let replacement = cloneCandidate(readonlyCandidate) as Writable - - for (let fn of UTILITY_CANONICALIZATIONS) { - replacement = fn(replacement, options) - } - - let canonicalizedCandidate = designSystem.printCandidate(replacement) - if (rawCandidate !== canonicalizedCandidate) { - return canonicalizedCandidate - } - } - - return rawCandidate - }) - }) -} - -// ---- - -const DIRECTIONS = ['t', 'tr', 'r', 'br', 'b', 'bl', 'l', 'tl'] -function bgGradientToLinear(candidate: Candidate) { - if (candidate.kind === 'static' && candidate.root.startsWith('bg-gradient-to-')) { - let direction = candidate.root.slice(15) - - if (!DIRECTIONS.includes(direction)) { - return candidate - } - - candidate.root = `bg-linear-to-${direction}` - return candidate - } - - return candidate -} - -// ---- - -const enum Convert { - All = 0, - MigrateModifier = 1 << 0, - MigrateThemeOnly = 1 << 1, -} - -function themeToVarUtility(candidate: Candidate, options: InternalCanonicalizeOptions): Candidate { - let convert = options.designSystem.storage[CONVERTER_KEY] - - if (candidate.kind === 'arbitrary') { - let [newValue, modifier] = convert( - candidate.value, - candidate.modifier === null ? Convert.MigrateModifier : Convert.All, - ) - if (newValue !== candidate.value) { - candidate.value = newValue - - if (modifier !== null) { - candidate.modifier = modifier - } - } - } else if (candidate.kind === 'functional' && candidate.value?.kind === 'arbitrary') { - let [newValue, modifier] = convert( - candidate.value.value, - candidate.modifier === null ? Convert.MigrateModifier : Convert.All, - ) - if (newValue !== candidate.value.value) { - candidate.value.value = newValue - - if (modifier !== null) { - candidate.modifier = modifier - } - } - } - - return candidate -} - -function themeToVarVariant( - variant: Variant, - options: InternalCanonicalizeOptions, -): Variant | Variant[] { - let convert = options.designSystem.storage[CONVERTER_KEY] - - let iterator = walkVariants(variant) - for (let [variant] of iterator) { - if (variant.kind === 'arbitrary') { - let [newValue] = convert(variant.selector, Convert.MigrateThemeOnly) - if (newValue !== variant.selector) { - variant.selector = newValue - } - } else if (variant.kind === 'functional' && variant.value?.kind === 'arbitrary') { - let [newValue] = convert(variant.value.value, Convert.MigrateThemeOnly) - if (newValue !== variant.value.value) { - variant.value.value = newValue - } - } - } - - return variant -} - -function calcToSpacingFunction( - candidate: Candidate, - options: InternalCanonicalizeOptions, -): Candidate { - if (candidate.kind === 'arbitrary') { - candidate.value = spacingCalcToSpacingFunction(candidate.value, options.designSystem) - } else if (candidate.kind === 'functional' && candidate.value?.kind === 'arbitrary') { - candidate.value.value = spacingCalcToSpacingFunction( - candidate.value.value, - options.designSystem, - ) - } - - return candidate -} - -function spacingCalcToSpacingFunction(input: string, designSystem: DesignSystem) { - let spacingVariable = designSystem.theme.prefix - ? `--${designSystem.theme.prefix}-spacing` - : '--spacing' - - let ast = ValueParser.parse(input) - - walk(ast, (node) => { - // calc(…) - if (node.kind !== 'function' || node.value !== 'calc') return - - // calc(var(--spacing) * 2) - // -------------- 1. function (var) - // - 2. separator ( ) - // - 3. word (*) - // - 4. separator ( ) - // - 5. any value, word (2) - if (node.nodes.length !== 5) return - - // * - if (node.nodes[2].kind !== 'word' || node.nodes[2].value !== '*') { - return - } - - // var(--spacing) - if ( - node.nodes[0].kind !== 'function' || - node.nodes[0].value !== 'var' || - node.nodes[0].nodes.length !== 1 || - node.nodes[0].nodes[0].kind !== 'word' || - node.nodes[0].nodes[0].value !== spacingVariable - ) { - return - } - - return WalkAction.Replace( - ValueParser.parse( - `--spacing(${ - ValueParser.toCss([node.nodes[4]]) // Value node - })`, - ), - ) - }) - - return ValueParser.toCss(ast) -} - -const CONVERTER_KEY = Symbol() -function createConverterCache( - designSystem: DesignSystem, -): DesignSystem['storage'][typeof CONVERTER_KEY] { - return createConverter(designSystem) - - function createConverter(designSystem: DesignSystem) { - function convert(input: string, options = Convert.All): [string, CandidateModifier | null] { - let ast = ValueParser.parse(input) - - // In some scenarios (e.g.: variants), we can't migrate to `var(…)` if it - // ends up in the `@media (…)` part. In this case we only have to migrate to - // the new `theme(…)` notation. - if (options & Convert.MigrateThemeOnly) { - return [substituteFunctionsInValue(ast, toTheme), null] - } - - let themeUsageCount = 0 - let themeModifierCount = 0 - - // Analyze AST - walk(ast, (node) => { - if (node.kind !== 'function') return - if (node.value !== 'theme') return - - // We are only interested in the `theme` function - themeUsageCount += 1 - - // Figure out if a modifier is used - walk(node.nodes, (child) => { - // If we see a `,`, it means that we have a fallback value - if (child.kind === 'separator' && child.value.includes(',')) { - return WalkAction.Stop - } - - // If we see a `/`, we have a modifier - else if (child.kind === 'word' && child.value === '/') { - themeModifierCount += 1 - return WalkAction.Stop - } - - return WalkAction.Skip - }) - }) - - // No `theme(…)` calls, nothing to do - if (themeUsageCount === 0) { - return [input, null] - } - - // No `theme(…)` with modifiers, we can migrate to `var(…)` - if (themeModifierCount === 0) { - return [substituteFunctionsInValue(ast, toVar), null] - } - - // Multiple modifiers which means that there are multiple `theme(…/…)` - // values. In this case, we can't convert the modifier to a candidate - // modifier. - // - // We also can't migrate to `var(…)` because that would lose the modifier. - // - // Try to convert each `theme(…)` call to the modern syntax. - if (themeModifierCount > 1) { - return [substituteFunctionsInValue(ast, toTheme), null] - } - - // Only a single `theme(…)` with a modifier left, that modifier will be - // migrated to a candidate modifier. - let modifier: CandidateModifier | null = null - let result = substituteFunctionsInValue(ast, (path, fallback) => { - let parts = segment(path, '/').map((part) => part.trim()) - - // Multiple `/` separators, which makes this an invalid path - if (parts.length > 2) return null - - // The path contains a `/`, which means that there is a modifier such as - // `theme(colors.red.500/50%)`. - // - // Currently, we are assuming that this is only being used for colors, - // which means that we can typically convert them to a modifier on the - // candidate itself. - // - // If there is more than one node in the AST though, `theme(…)` must not - // be the whole value so it's not safe to use a modifier instead. - // - // E.g.: `inset 0px 1px theme(colors.red.500/50%)` is a shadow, not a color. - if (ast.length === 1 && parts.length === 2 && options & Convert.MigrateModifier) { - let [pathPart, modifierPart] = parts - - // 50% -> /50 - if (/^\d+%$/.test(modifierPart)) { - modifier = { kind: 'named', value: modifierPart.slice(0, -1) } - } - - // .12 -> /12 - // .12345 -> /[12.345] - else if (/^0?\.\d+$/.test(modifierPart)) { - let value = Number(modifierPart) * 100 - modifier = { - kind: Number.isInteger(value) ? 'named' : 'arbitrary', - value: value.toString(), - } - } - - // Anything else becomes arbitrary - else { - modifier = { kind: 'arbitrary', value: modifierPart } - } - - // Update path to be the first part - path = pathPart - } - - return toVar(path, fallback) || toTheme(path, fallback) - }) - - return [result, modifier] - } - - function pathToVariableName(path: string, shouldPrefix = true) { - let variable = `--${keyPathToCssProperty(toKeyPath(path))}` as const - if (!designSystem.theme.get([variable])) return null - - if (shouldPrefix && designSystem.theme.prefix) { - return `--${designSystem.theme.prefix}-${variable.slice(2)}` - } - - return variable - } - - function toVar(path: string, fallback?: string) { - let variable = pathToVariableName(path) - if (variable) return fallback ? `var(${variable}, ${fallback})` : `var(${variable})` - - let keyPath = toKeyPath(path) - if (keyPath[0] === 'spacing' && designSystem.theme.get(['--spacing'])) { - let multiplier = keyPath[1] - if (!isValidSpacingMultiplier(multiplier)) return null - - return `--spacing(${multiplier})` - } - - return null - } - - function toTheme(path: string, fallback?: string) { - let parts = segment(path, '/').map((part) => part.trim()) - path = parts.shift()! - - let variable = pathToVariableName(path, false) - if (!variable) return null - - let modifier = parts.length > 0 ? `/${parts.join('/')}` : '' - return fallback - ? `--theme(${variable}${modifier}, ${fallback})` - : `--theme(${variable}${modifier})` - } - - return convert - } -} - -function substituteFunctionsInValue( - ast: ValueParser.ValueAstNode[], - handle: (value: string, fallback?: string) => string | null, -) { - walk(ast, (node, ctx) => { - if (node.kind === 'function' && node.value === 'theme') { - if (node.nodes.length < 1) return - - // Ignore whitespace before the first argument - if (node.nodes[0].kind === 'separator' && node.nodes[0].value.trim() === '') { - node.nodes.shift() - } - - let pathNode = node.nodes[0] - if (pathNode.kind !== 'word') return - - let path = pathNode.value - - // For the theme function arguments, we require all separators to contain - // comma (`,`), spaces alone should be merged into the previous word to - // avoid splitting in this case: - // - // theme(--color-red-500 / 75%) theme(--color-red-500 / 75%, foo, bar) - // - // We only need to do this for the first node, as the fallback values are - // passed through as-is. - let skipUntilIndex = 1 - for (let i = skipUntilIndex; i < node.nodes.length; i++) { - if (node.nodes[i].value.includes(',')) { - break - } - path += ValueParser.toCss([node.nodes[i]]) - skipUntilIndex = i + 1 - } - - path = eventuallyUnquote(path) - let fallbackValues = node.nodes.slice(skipUntilIndex + 1) - - let replacement = - fallbackValues.length > 0 ? handle(path, ValueParser.toCss(fallbackValues)) : handle(path) - if (replacement === null) return - - { - let idx = ctx.index - 1 - while (idx !== -1) { - let previous = ctx.siblings[idx] - // Skip the space separator - if (previous.kind === 'separator' && previous.value.trim() === '') { - idx -= 1 - continue - } - - // If the previous node is a word and contains an operator, we need to - // wrap the replacement in parentheses to make the output less - // ambiguous. - // - // Input: - // - `calc(100dvh-theme(spacing.2))` - // - // Output: - // - `calc(100dvh-(--spacing(2)))` - // - // Not: - // -`calc(100dvh---spacing(2))` - // - if (/^[-+*/]$/.test(previous.value.trim())) { - replacement = `(${replacement})` - } - - break - } - } - - return WalkAction.Replace(ValueParser.parse(replacement)) - } - }) - - return ValueParser.toCss(ast) -} - -function eventuallyUnquote(value: string) { - if (value[0] !== "'" && value[0] !== '"') return value - - let unquoted = '' - let quoteChar = value[0] - for (let i = 1; i < value.length - 1; i++) { - let currentChar = value[i] - let nextChar = value[i + 1] - - if (currentChar === '\\' && (nextChar === quoteChar || nextChar === '\\')) { - unquoted += nextChar - i++ - } else { - unquoted += currentChar - } - } - - return unquoted -} - -// ---- - -function* walkVariants(variant: Variant) { - function* inner( - variant: Variant, - parent: Extract | null = null, - ): Iterable<[Variant, Extract | null]> { - yield [variant, parent] - - if (variant.kind === 'compound') { - yield* inner(variant.variant, variant) - } - } - - yield* inner(variant, null) -} - -function parseCandidate(designSystem: DesignSystem, input: string) { - return designSystem.parseCandidate( - designSystem.theme.prefix && !input.startsWith(`${designSystem.theme.prefix}:`) - ? `${designSystem.theme.prefix}:${input}` - : input, - ) -} - -function printUnprefixedCandidate(designSystem: DesignSystem, candidate: Candidate) { - let candidateString = designSystem.printCandidate(candidate) - - return designSystem.theme.prefix && candidateString.startsWith(`${designSystem.theme.prefix}:`) - ? candidateString.slice(designSystem.theme.prefix.length + 1) - : candidateString -} - -// ---- - -const SPACING_KEY = Symbol() - -// We prefer bare values over arbitrary values such as `left-[6px]` → -// `left-1.5`. -// -// There are situations where this doesn't always make sense. E.g.: -// `left-[99999px]` → `left-24999.75`. -// -// We could try and analyze the incoming value and bail out if we detect certain -// patterns. But a first step would be to limit the bare values for large values -// and keep them low. The largest rem value we have is for the 2xl breakpoint, -// which is 96rem, which is 1536px. -const MAX_BARE_VALUE_IN_PX = 1536 -const MAX_BARE_VALUE_IN_REM = MAX_BARE_VALUE_IN_PX / 16 - -function isReasonableBareValue(value: number, designSystem: DesignSystem, rem: number | null) { - let spacingMultiplier = designSystem.resolveThemeValue('--spacing') - if (spacingMultiplier === undefined) return false - - let parsed = dimensions.get(constantFoldDeclaration(spacingMultiplier, rem)) - if (parsed === null) return false - - let [spacingValue, spacingUnit] = parsed - let bareValueInPixels = value * spacingValue - - if (spacingUnit === 'px') return bareValueInPixels <= MAX_BARE_VALUE_IN_PX - if (spacingUnit === 'rem') return bareValueInPixels <= MAX_BARE_VALUE_IN_REM - return false -} - -function createSpacingCache( - designSystem: DesignSystem, - options?: CanonicalizeOptions, -): DesignSystem['storage'][typeof SPACING_KEY] { - let spacingMultiplier = designSystem.resolveThemeValue('--spacing') - if (spacingMultiplier === undefined) return null - - spacingMultiplier = constantFoldDeclaration(spacingMultiplier, options?.rem ?? null) - - let parsed = dimensions.get(spacingMultiplier) - if (!parsed) return null - - let [value, unit] = parsed - - return new DefaultMap((input) => { - // If we already know that the spacing multiplier is 0, all spacing - // multipliers will also be 0. No need to even try and parse/canonicalize - // the input value. - if (value === 0) return null - - let parsed = dimensions.get(constantFoldDeclaration(input, options?.rem ?? null)) - if (!parsed) return null - - let [myValue, myUnit] = parsed - if (myUnit !== unit) return null - - return myValue / value - }) -} - -function arbitraryUtilities(candidate: Candidate, options: InternalCanonicalizeOptions): Candidate { - // We are only interested in arbitrary properties and arbitrary values - if ( - // Arbitrary property - candidate.kind !== 'arbitrary' && - // Arbitrary value - !(candidate.kind === 'functional' && candidate.value?.kind === 'arbitrary') - ) { - return candidate - } - - let designSystem = options.designSystem - let utilities = designSystem.storage[PRE_COMPUTED_UTILITIES_KEY].get(options.signatureOptions) - let signatures = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options.signatureOptions) - let hasSameSignature = designSystem.storage[COMPARE_CANDIDATES_KEY].get(options.signatureOptions) - - let targetCandidateString = designSystem.printCandidate(candidate) - - // Compute the signature for the target candidate - let targetSignature = signatures.get(targetCandidateString) - if (typeof targetSignature !== 'string') return candidate - - // Try a few options to find a suitable replacement utility - for (let replacementCandidate of tryReplacements(targetSignature, candidate)) { - if (!hasSameSignature(candidate, replacementCandidate)) { - continue - } - - // Ensure that if CSS variables were used, that they are still used - if (!allVariablesAreUsed(designSystem, candidate, replacementCandidate)) { - continue - } - - return replacementCandidate - } - - return candidate - - function* tryReplacements( - targetSignature: string, - candidate: Extract, - ): Generator { - // Find a corresponding utility for the same signature - let replacements = utilities.get(targetSignature) - - // Multiple utilities can map to the same signature. - if (replacements.length > 1) { - // Prefer positive values over negative values - let maybeReplacement: string | undefined = undefined - for (let replacement of replacements) { - if (replacement[0] === '-') continue // Skip negative values - - // If multiple non-negative replacements exists then we are unsure - // what to do, so let's bail. - if (maybeReplacement) return - - // Consider this replacement - maybeReplacement = replacement - } - - if (maybeReplacement) { - for (let replacementCandidate of parseCandidate(designSystem, maybeReplacement)) { - yield replacementCandidate - } - } - - return - } - - // If we didn't find any replacement utilities, let's try to strip the - // modifier and find a replacement then. If we do, we can try to re-add the - // modifier later and verify if we have a valid migration. - // - // This is necessary because `text-red-500/50` will not be pre-computed, - // only `text-red-500` will. - if (replacements.length === 0 && candidate.modifier) { - let candidateWithoutModifier = { ...candidate, modifier: null } - let targetSignatureWithoutModifier = signatures.get( - designSystem.printCandidate(candidateWithoutModifier), - ) - if (typeof targetSignatureWithoutModifier === 'string') { - for (let replacementCandidate of tryReplacements( - targetSignatureWithoutModifier, - candidateWithoutModifier, - )) { - yield Object.assign({}, replacementCandidate, { modifier: candidate.modifier }) - } - } - } - - // If only a single utility maps to the signature, we can use that as the - // replacement. - if (replacements.length === 1) { - for (let replacementCandidate of parseCandidate(designSystem, replacements[0])) { - yield replacementCandidate - } - } - - // Find a corresponding functional utility for the same signature - else if (replacements.length === 0) { - // An arbitrary property will only set a single property, we can use that - // to find functional utilities that also set this property. - let value = - candidate.kind === 'arbitrary' ? candidate.value : (candidate.value?.value ?? null) - if (value === null) return - - // Try to canonicalize any incoming arbitrary value. Canonicalization of - // `rem` and `px` values will be converted to `px`, so we have to - // canonicalize the spacing multiplier as well. - if ( - options.signatureOptions.rem !== null && - candidate.kind === 'functional' && - candidate.value?.kind === 'arbitrary' - ) { - let bareValue = designSystem.storage[SPACING_KEY]?.get(value) ?? null - if (bareValue !== null) { - if ( - isValidSpacingMultiplier(bareValue) && - isReasonableBareValue(bareValue, designSystem, options.signatureOptions.rem) - ) { - yield Object.assign({}, candidate, { - value: { kind: 'named', value: bareValue, fraction: null }, - }) - } - } - } - - let spacingMultiplier = designSystem.storage[SPACING_KEY]?.get(value) ?? null - let rootPrefix = '' - if (spacingMultiplier !== null && spacingMultiplier < 0) { - rootPrefix = '-' - spacingMultiplier = Math.abs(spacingMultiplier) - } - - for (let root of Array.from(designSystem.utilities.keys('functional')).sort( - // Sort negative roots after positive roots so that we can try - // `mt-*` before `-mt-*`. This is especially useful in situations where - // `-mt-[0px]` can be translated to `mt-[0px]`. - (a, z) => Number(a[0] === '-') - Number(z[0] === '-'), - )) { - if (rootPrefix) root = `${rootPrefix}${root}` - - // Try as bare value - for (let replacementCandidate of parseCandidate(designSystem, `${root}-${value}`)) { - yield replacementCandidate - } - - // Try as bare value with modifier - if (candidate.modifier) { - for (let replacementCandidate of parseCandidate( - designSystem, - `${root}-${value}${candidate.modifier}`, - )) { - yield replacementCandidate - } - } - - // Try bare value based on the `--spacing` value. E.g.: - // - // - `w-[64rem]` → `w-256` - if ( - spacingMultiplier !== null && - isValidSpacingMultiplier(spacingMultiplier) && - isReasonableBareValue(spacingMultiplier, designSystem, options.signatureOptions.rem) - ) { - for (let replacementCandidate of parseCandidate( - designSystem, - `${root}-${spacingMultiplier}`, - )) { - yield replacementCandidate - } - - // Try bare value based on the `--spacing` value, but with a modifier - if (candidate.modifier) { - for (let replacementCandidate of parseCandidate( - designSystem, - `${root}-${spacingMultiplier}${printModifier(candidate.modifier)}`, - )) { - yield replacementCandidate - } - } - } - - // Try as arbitrary value - for (let replacementCandidate of parseCandidate(designSystem, `${root}-[${value}]`)) { - yield replacementCandidate - } - - // Try as arbitrary value with modifier - if (candidate.modifier) { - for (let replacementCandidate of parseCandidate( - designSystem, - `${root}-[${value}]${printModifier(candidate.modifier)}`, - )) { - yield replacementCandidate - } - } - } - } - } -} - -// Let's make sure that all variables used in the value are also all used in the -// found replacement. If not, then we are dealing with a different namespace or -// we could lose functionality in case the variable was changed higher up in the -// DOM tree. -function allVariablesAreUsed( - designSystem: DesignSystem, - candidate: Candidate, - replacement: Candidate, -) { - let value: string | null = null - - // Functional utility with arbitrary value and variables - if ( - candidate.kind === 'functional' && - candidate.value?.kind === 'arbitrary' && - candidate.value.value.includes('var(--') - ) { - value = candidate.value.value - } - - // Arbitrary property with variables - else if (candidate.kind === 'arbitrary' && candidate.value.includes('var(--')) { - value = candidate.value - } - - // No variables in the value, so this is a safe migration - if (value === null) { - return true - } - - let replacementAsCss = designSystem - .candidatesToCss([designSystem.printCandidate(replacement)]) - .join('\n') - - let isSafeMigration = true - walk(ValueParser.parse(value), (node) => { - if (node.kind === 'function' && node.value === 'var') { - let variable = node.nodes[0].value - let r = new RegExp(`var\\(${variable}[,)]\\s*`, 'g') - if ( - // We need to check if the variable is used in the replacement - !r.test(replacementAsCss) || - // The value cannot be set to a different value in the - // replacement because that would make it an unsafe migration - replacementAsCss.includes(`${variable}:`) - ) { - isSafeMigration = false - return WalkAction.Stop - } - } - }) - - return isSafeMigration -} - -// ---- - -function bareValueUtilities(candidate: Candidate, options: InternalCanonicalizeOptions): Candidate { - // We are only interested in bare value utilities - if (candidate.kind !== 'functional' || candidate.value?.kind !== 'named') { - return candidate - } - - let designSystem = options.designSystem - let utilities = designSystem.storage[PRE_COMPUTED_UTILITIES_KEY].get(options.signatureOptions) - let signatures = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options.signatureOptions) - let hasSameSignature = designSystem.storage[COMPARE_CANDIDATES_KEY].get(options.signatureOptions) - - let targetCandidateString = designSystem.printCandidate(candidate) - - // Compute the signature for the target candidate - let targetSignature = signatures.get(targetCandidateString) - if (typeof targetSignature !== 'string') return candidate - - // Try a few options to find a suitable replacement utility - for (let replacementCandidate of tryReplacements(targetSignature, candidate)) { - if (!hasSameSignature(candidate, replacementCandidate)) { - continue - } - - return replacementCandidate - } - - return candidate - - function* tryReplacements( - targetSignature: string, - candidate: Extract, - ): Generator { - // Find a corresponding utility for the same signature - let replacements = utilities.get(targetSignature) - - // Multiple utilities can map to the same signature. - if (replacements.length > 1) { - // Prefer positive values over negative values - let maybeReplacement: string | undefined = undefined - for (let replacement of replacements) { - if (replacement[0] === '-') continue // Skip negative values - - // If multiple non-negative replacements exists then we are unsure - // what to do, so let's bail. - if (maybeReplacement) return - - // Consider this replacement - maybeReplacement = replacement - } - - if (maybeReplacement) { - for (let replacementCandidate of parseCandidate(designSystem, maybeReplacement)) { - yield replacementCandidate - } - } - - return - } - - // If we didn't find any replacement utilities, let's try to strip the - // modifier and find a replacement then. If we do, we can try to re-add the - // modifier later and verify if we have a valid migration. - // - // This is necessary because `text-red-500/50` will not be pre-computed, - // only `text-red-500` will. - if (replacements.length === 0 && candidate.modifier) { - let candidateWithoutModifier = { ...candidate, modifier: null } - let targetSignatureWithoutModifier = signatures.get( - designSystem.printCandidate(candidateWithoutModifier), - ) - if (typeof targetSignatureWithoutModifier === 'string') { - for (let replacementCandidate of tryReplacements( - targetSignatureWithoutModifier, - candidateWithoutModifier, - )) { - yield Object.assign({}, replacementCandidate, { modifier: candidate.modifier }) - } - } - } - - // If only a single utility maps to the signature, we can use that as the - // replacement. - if (replacements.length === 1) { - for (let replacementCandidate of parseCandidate(designSystem, replacements[0])) { - yield replacementCandidate - } - } - } -} - -// ---- - -const DEPRECATION_MAP = new Map([ - ['order-none', 'order-0'], - ['break-words', 'wrap-break-word'], - ['overflow-ellipsis', 'text-ellipsis'], -]) - -const DEPRECATION_TRANSFORMATION_MAP = new Map([ - [/^(-)?start-(.*?)$/, '$1inset-s-$2'], - [/^(-)?end-(.*?)$/, '$1inset-e-$2'], -]) - -function* tryDeprecatedUtilities(candidate: string) { - // Try static replacements - let replacement = DEPRECATION_MAP.get(candidate) - if (replacement) yield replacement - - // Try dynamic replacements - for (let [searchValue, replaceValue] of DEPRECATION_TRANSFORMATION_MAP) { - let replacement = candidate.replace(searchValue, replaceValue) - if (replacement === candidate) continue - - yield replacement - } -} - -function deprecatedUtilities( - candidate: Candidate, - options: InternalCanonicalizeOptions, -): Candidate { - let designSystem = options.designSystem - let hasSameSignature = designSystem.storage[COMPARE_CANDIDATES_KEY].get(options.signatureOptions) - - let targetCandidateString = printUnprefixedCandidate(designSystem, candidate) - - for (let replacementString of tryDeprecatedUtilities(targetCandidateString)) { - if (!hasSameSignature(candidate, replacementString)) { - continue - } - - let [replacement] = parseCandidate(designSystem, replacementString) - return replacement - } - - return candidate -} - -// ---- - -function arbitraryVariants( - variant: Variant, - options: InternalCanonicalizeOptions, -): Variant | Variant[] { - let designSystem = options.designSystem - let signatures = designSystem.storage[VARIANT_SIGNATURE_KEY].get(options.signatureOptions.rem) - let variants = designSystem.storage[PRE_COMPUTED_VARIANTS_KEY].get(options.signatureOptions.rem) - - let iterator = walkVariants(variant) - for (let [variant] of iterator) { - if (variant.kind === 'compound') continue - - let targetString = designSystem.printVariant(variant) - let targetSignature = signatures.get(targetString) - if (typeof targetSignature !== 'string') continue - - let foundVariants = variants.get(targetSignature) - if (foundVariants.length === 0) continue - - // The variant is already in a canonical form, e.g.: `min-lg` and `lg` - // produce the same CSS, but both are valid so we keep them as-is. - if (foundVariants.includes(targetString)) continue - - let foundVariant = foundVariants[0] - let parsedVariant = designSystem.parseVariant(foundVariant) - if (parsedVariant === null) continue - - replaceObject(variant, parsedVariant) - } - - return variant -} - -// ---- - -function dropUnnecessaryDataTypes( - candidate: Candidate, - options: InternalCanonicalizeOptions, -): Candidate { - let designSystem = options.designSystem - let signatures = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options.signatureOptions) - - if ( - candidate.kind === 'functional' && - candidate.value?.kind === 'arbitrary' && - candidate.value.dataType !== null - ) { - let replacement = designSystem.printCandidate({ - ...candidate, - value: { ...candidate.value, dataType: null }, - }) - - if (signatures.get(designSystem.printCandidate(candidate)) === signatures.get(replacement)) { - candidate.value.dataType = null - } - } - - return candidate -} - -// ---- - -function arbitraryValueToBareValueUtility( - candidate: Candidate, - options: InternalCanonicalizeOptions, -): Candidate { - // We are only interested in functional utilities with arbitrary values - if (candidate.kind !== 'functional' || candidate.value?.kind !== 'arbitrary') { - return candidate - } - - let designSystem = options.designSystem - let signatures = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options.signatureOptions) - - let expectedSignature = signatures.get(designSystem.printCandidate(candidate)) - if (expectedSignature === null) return candidate - - for (let value of tryValueReplacements(candidate)) { - let newSignature = signatures.get(designSystem.printCandidate({ ...candidate, value })) - if (newSignature === expectedSignature) { - candidate.value = value - return candidate - } - } - - return candidate -} - -function arbitraryValueToBareValueVariant(variant: Variant): Variant | Variant[] { - let iterator = walkVariants(variant) - for (let [variant] of iterator) { - // Convert `data-[selected]` to `data-selected` - if ( - variant.kind === 'functional' && - variant.root === 'data' && - variant.value?.kind === 'arbitrary' && - !variant.value.value.includes('=') - ) { - variant.value = { - kind: 'named', - value: variant.value.value, - } - } - - // Convert `aria-[selected="true"]` to `aria-selected` - else if ( - variant.kind === 'functional' && - variant.root === 'aria' && - variant.value?.kind === 'arbitrary' && - (variant.value.value.endsWith('=true') || - variant.value.value.endsWith('="true"') || - variant.value.value.endsWith("='true'")) - ) { - let [key, _value] = segment(variant.value.value, '=') - if ( - // aria-[foo~="true"] - key[key.length - 1] === '~' || - // aria-[foo|="true"] - key[key.length - 1] === '|' || - // aria-[foo^="true"] - key[key.length - 1] === '^' || - // aria-[foo$="true"] - key[key.length - 1] === '$' || - // aria-[foo*="true"] - key[key.length - 1] === '*' - ) { - continue - } - - variant.value = { - kind: 'named', - value: variant.value.value.slice(0, variant.value.value.indexOf('=')), - } - } - - // Convert `supports-[gap]` to `supports-gap` - else if ( - variant.kind === 'functional' && - variant.root === 'supports' && - variant.value?.kind === 'arbitrary' && - /^[a-z-][a-z0-9-]*$/i.test(variant.value.value) - ) { - variant.value = { - kind: 'named', - value: variant.value.value, - } - } - } - - return variant -} - -// Convert functional utilities with arbitrary values to bare values if we can. -// We know that bare values can only be: -// -// 1. A number (with increments of .25) -// 2. A percentage (with increments of .25 followed by a `%`) -// 3. A ratio with whole numbers -// -// Not a bare value per se, but if we are dealing with a keyword, that could -// potentially also look like a bare value (aka no `[` or `]`). E.g.: -// ```diff -// grid-cols-[subgrid] -// grid-cols-subgrid -// ``` -function* tryValueReplacements( - candidate: Extract, - value: string = candidate.value?.value ?? '', - seen: Set = new Set(), -): Generator { - if (seen.has(value)) return - seen.add(value) - - // 0. Just try to drop the square brackets and see if it works - // 1. A number (with increments of .25) - yield { - kind: 'named', - value, - fraction: null, - } - - // 2. A percentage (with increments of .25 followed by a `%`) - // Try to drop the `%` and see if it works - if (value.endsWith('%') && isValidSpacingMultiplier(value.slice(0, -1))) { - yield { - kind: 'named', - value: value.slice(0, -1), - fraction: null, - } - } - - // 3. A ratio with whole numbers - if (value.includes('/')) { - let [numerator, denominator] = value.split('/') - if (isPositiveInteger(numerator) && isPositiveInteger(denominator)) { - yield { - kind: 'named', - value: numerator, - fraction: `${numerator}/${denominator}`, - } - } - } - - // It could also be that we have `20px`, we can try just `20` and see if it - // results in the same signature. - let allNumbersAndFractions = new Set() - - // Figure out all numbers and fractions in the value - for (let match of value.matchAll(/(\d+\/\d+)|(\d+\.?\d+)/g)) { - allNumbersAndFractions.add(match[0].trim()) - } - - // Sort the numbers and fractions where the smallest length comes first. This - // will result in the smallest replacement. - let options = Array.from(allNumbersAndFractions).sort((a, z) => { - return a.length - z.length - }) - - // Try all the options - for (let option of options) { - yield* tryValueReplacements(candidate, option, seen) - } -} - -// ---- - -function isSingleSelector(ast: SelectorParser.SelectorAstNode[]): boolean { - if (ast.length === 1 && ast[0].kind === 'list') return false - return true -} - -function isAttributeSelector(node: SelectorParser.SelectorNode): boolean { - return node.value[0] === '[' && node.value[node.value.length - 1] === ']' -} - -function modernizeArbitraryValuesVariant( - variant: Variant, - options: InternalCanonicalizeOptions, -): Variant | Variant[] { - let result = [variant] - let designSystem = options.designSystem - let signatures = designSystem.storage[VARIANT_SIGNATURE_KEY].get(options.signatureOptions.rem) - - let iterator = walkVariants(variant) - for (let [variant, parent] of iterator) { - // Forward modifier from the root to the compound variant - if ( - variant.kind === 'compound' && - (variant.root === 'has' || variant.root === 'not' || variant.root === 'in') - ) { - if (variant.modifier !== null) { - if ('modifier' in variant.variant) { - variant.variant.modifier = variant.modifier - variant.modifier = null - } - } - } - - // Expecting an arbitrary variant - if (variant.kind === 'arbitrary') { - // Expecting a non-relative arbitrary variant - if (variant.relative) continue - - let ast = SelectorParser.parse(variant.selector) - - // Expecting a single selector node - if (!isSingleSelector(ast)) continue - - // Keep the existing arbitrary variant modernizations working on a flat - // selector sequence while the parser exposes complex selectors as a node. - if (ast.length === 1 && ast[0].kind === 'complex') { - ast = ast[0].nodes - } - - // `[&>*]` can be replaced with `*` - if ( - // Only top-level, so `has-[&>*]` is not supported - parent === null && - // [&_>_*]:flex - // ^ ^ ^ - ast.length === 3 && - ast[0].kind === 'selector' && - ast[0].value === '&' && - ast[1].kind === 'combinator' && - ast[1].value === '>' && - ast[2].kind === 'selector' && - ast[2].value === '*' - ) { - replaceObject(variant, designSystem.parseVariant('*')) - continue - } - - // `[&_*]` can be replaced with `**` - if ( - // Only top-level, so `has-[&_*]` is not supported - parent === null && - // [&_*]:flex - // ^ ^ - ast.length === 3 && - ast[0].kind === 'selector' && - ast[0].value === '&' && - ast[1].kind === 'combinator' && - ast[1].value === ' ' && - ast[2].kind === 'selector' && - ast[2].value === '*' - ) { - replaceObject(variant, designSystem.parseVariant('**')) - continue - } - - // `[&:has(…)]` can be replaced with `has-[…]` - if ( - // Only top-level, so `group-[&:has(…)]` is not covered - - parent === null && - // [&:has(…)]:flex - // ^ ^^^^^^ - ast.length === 1 && - ast[0].kind === 'compound' && - ast[0].nodes.length === 2 && - ast[0].nodes[0].kind === 'selector' && - ast[0].nodes[0].value === '&' && - ast[0].nodes[1].kind === 'function' && - ast[0].nodes[1].value === ':has' && - ast[0].nodes[1].nodes.length === 1 && - ast[0].nodes[1].nodes[0].kind === 'selector' - ) { - replaceObject( - variant, - designSystem.parseVariant(`has-[${SelectorParser.toCss(ast[0].nodes[1].nodes, true)}]`), - ) - continue - } - - // `in-*` variant. If the selector ends with ` &`, we can convert it to an - // `in-*` variant. - // - // E.g.: `[[data-visible]_&]` => `in-data-visible` - if ( - // Only top-level, so `in-[&_[data-visible]]` is not supported - parent === null && - // [[data-visible]___&]:flex - // ^^^^^^^^^^^^^^ ^ ^ - ast.length === 3 && - ast[0].kind === 'selector' && - ast[1].kind === 'combinator' && - ast[1].value === ' ' && - ast[2].kind === 'selector' && - ast[2].value === '&' - ) { - ast.pop() // Remove the nesting node - ast.pop() // Remove the combinator - - // When handling a compound like `in-[[data-visible]]`, we will first - // handle `[[data-visible]]`, then the parent `in-*` part. This means - // that we can convert `[[data-visible]_&]` to `in-[[data-visible]]`. - // - // Later this gets converted to `in-data-visible`. - replaceObject(variant, designSystem.parseVariant(`in-[${SelectorParser.toCss(ast, true)}]`)) - continue - } - - // Hoist `not` modifier for `@media` or `@supports` variants - // - // E.g.: `[@media_not_(scripting:none)]:` -> `not-[@media_(scripting:none)]:` - if ( - // Only top-level, so something like `in-[@media(scripting:none)]` - // (which is not valid anyway) is not supported - parent === null && - // [@media_not(scripting:none)]:flex - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ast[0].kind === 'selector' && - (ast[0].value === '@media' || ast[0].value === '@supports') - ) { - let targetSignature = signatures.get(designSystem.printVariant(variant)) - - let parsed = ValueParser.parse(SelectorParser.toCss(ast, true)) - let containsNot = false - walk(parsed, (node) => { - if (node.kind === 'word' && node.value === 'not') { - containsNot = true - return WalkAction.Replace([]) - } - }) - - // Remove unnecessary whitespace - parsed = ValueParser.parse(ValueParser.toCss(parsed)) - walk(parsed, (node) => { - if (node.kind === 'separator' && node.value !== ' ' && node.value.trim() === '') { - // node.value contains at least 2 spaces. Normalize it to a single - // space. - node.value = ' ' - } - }) - - if (containsNot) { - let hoistedNot = designSystem.parseVariant(`not-[${ValueParser.toCss(parsed)}]`) - if (hoistedNot === null) continue - let hoistedNotSignature = signatures.get(designSystem.printVariant(hoistedNot)) - if (targetSignature === hoistedNotSignature) { - replaceObject(variant, hoistedNot) - continue - } - } - } - - let prefixedVariant: Variant | null = null - - // Handling a child combinator. E.g.: `[&>[data-visible]]` => `*:data-visible` - if ( - // Only top-level, so `has-[&>[data-visible]]` is not supported - parent === null && - // [&_>_[data-visible]]:flex - // ^ ^ ^^^^^^^^^^^^^^ - ast.length === 3 && - ast[0].kind === 'selector' && - ast[0].value === '&' && - ast[1].kind === 'combinator' && - ast[1].value === '>' && - ast[2].kind === 'selector' && - (ast[2].value[0] === ':' || isAttributeSelector(ast[2])) - ) { - ast = [ast[2]] - prefixedVariant = designSystem.parseVariant('*') - } - - // Handling a grand child combinator. E.g.: `[&_[data-visible]]` => `**:data-visible` - if ( - // Only top-level, so `has-[&_[data-visible]]` is not supported - parent === null && - // [&_[data-visible]]:flex - // ^ ^^^^^^^^^^^^^^ - ast.length === 3 && - ast[0].kind === 'selector' && - ast[0].value === '&' && - ast[1].kind === 'combinator' && - ast[1].value === ' ' && - ast[2].kind === 'selector' && - (ast[2].value[0] === ':' || isAttributeSelector(ast[2])) - ) { - ast = [ast[2]] - prefixedVariant = designSystem.parseVariant('**') - } - - let selectorNodes = ast - walk(selectorNodes, { - enter(node) { - // Filter out `&`. E.g.: `&[data-foo]` => `[data-foo]` - if (node.kind === 'selector' && node.value === '&') { - return WalkAction.Replace([]) - } - - if (node.kind === 'function') { - return WalkAction.Skip - } - }, - exit(node) { - if (node.kind === 'compound' && node.nodes.length === 1) { - return WalkAction.ReplaceSkip(node.nodes) - } - }, - }) - - // Expecting a single selector (normal selector or attribute selector) - if (selectorNodes.length !== 1) continue - - let target = selectorNodes[0] - if (target.kind === 'function' && target.value === ':is') { - // Expecting a single selector node - if ( - !isSingleSelector(target.nodes) || - // [foo][bar] is considered a single selector but has multiple nodes - target.nodes.length !== 1 - ) { - continue - } - - // Expecting a single attribute selector - if (target.nodes[0].kind === 'selector' && !isAttributeSelector(target.nodes[0])) continue - - // Unwrap the selector from inside `&:is(…)` - target = target.nodes[0] - } - - // Expecting a pseudo selector (or function) - if ( - (target.kind === 'function' && target.value[0] === ':') || - (target.kind === 'selector' && target.value[0] === ':') - ) { - let targetNode = target - let compoundNot = false - if (targetNode.kind === 'function' && targetNode.value === ':not') { - compoundNot = true - if (targetNode.nodes.length !== 1) continue - if (targetNode.nodes[0].kind !== 'selector' && targetNode.nodes[0].kind !== 'function') { - continue - } - if (targetNode.nodes[0].value[0] !== ':') continue - - targetNode = targetNode.nodes[0] - } - - let newVariant = ((value) => { - if ( - value === ':nth-child' && - targetNode.kind === 'function' && - targetNode.nodes.length === 1 && - targetNode.nodes[0].kind === 'value' && - targetNode.nodes[0].value === 'odd' - ) { - if (compoundNot) { - compoundNot = false - return 'even' - } - return 'odd' - } - - if ( - value === ':nth-child' && - targetNode.kind === 'function' && - targetNode.nodes.length === 1 && - targetNode.nodes[0].kind === 'value' && - targetNode.nodes[0].value === 'even' - ) { - if (compoundNot) { - compoundNot = false - return 'odd' - } - return 'even' - } - - for (let [selector, variantName] of [ - [':nth-child', 'nth'], - [':nth-last-child', 'nth-last'], - [':nth-of-type', 'nth-of-type'], - [':nth-last-of-type', 'nth-of-last-type'], - ]) { - if ( - value === selector && - targetNode.kind === 'function' && - targetNode.nodes.length === 1 - ) { - if ( - targetNode.nodes.length === 1 && - targetNode.nodes[0].kind === 'value' && - isPositiveInteger(targetNode.nodes[0].value) - ) { - return `${variantName}-${targetNode.nodes[0].value}` - } - - return `${variantName}-[${SelectorParser.toCss(targetNode.nodes, true)}]` - } - } - - // Hoist `not` modifier - if (compoundNot) { - let targetSignature = signatures.get(designSystem.printVariant(variant)) - let replacementSignature = signatures.get(`not-[${value}]`) - if (targetSignature === replacementSignature) { - return `[&${value}]` - } - } - - return null - })(targetNode.value) - - if (newVariant === null) { - if (prefixedVariant) { - replaceObject(variant, { - kind: 'arbitrary', - selector: target.value, - relative: false, - } satisfies Variant) - - return [prefixedVariant, variant] - } - - continue - } - - // Add `not-` prefix - if (compoundNot) newVariant = `not-${newVariant}` - - let parsed = designSystem.parseVariant(newVariant) - if (parsed === null) continue - - // Update original variant - replaceObject(variant, parsed) - } - - // Expecting an attribute selector - else if (target.kind === 'selector' && isAttributeSelector(target)) { - let attributeSelector = AttributeSelectorParser.parse(target.value) - if (attributeSelector === null) continue // Invalid attribute selector - - // Migrate `data-*` - if (attributeSelector.attribute.startsWith('data-')) { - let name = attributeSelector.attribute.slice(5) // Remove `data-` - - replaceObject(variant, { - kind: 'functional', - root: 'data', - modifier: null, - value: - attributeSelector.value === null - ? { kind: 'named', value: name } - : { - kind: 'arbitrary', - value: `${name}${attributeSelector.operator}${attributeSelector.quote ?? ''}${attributeSelector.value}${attributeSelector.quote ?? ''}${attributeSelector.sensitivity ? ` ${attributeSelector.sensitivity}` : ''}`, - }, - } satisfies Variant) - } - - // Migrate `aria-*` - else if (attributeSelector.attribute.startsWith('aria-')) { - let name = attributeSelector.attribute.slice(5) // Remove `aria-` - replaceObject(variant, { - kind: 'functional', - root: 'aria', - modifier: null, - value: - attributeSelector.value === null - ? { kind: 'arbitrary', value: name } // aria-[foo] - : attributeSelector.operator === '=' && - attributeSelector.value === 'true' && - attributeSelector.sensitivity === null - ? { kind: 'named', value: name } // aria-[foo="true"] or aria-[foo='true'] or aria-[foo=true] - : { - kind: 'arbitrary', - value: `${attributeSelector.attribute}${attributeSelector.operator}${attributeSelector.quote ?? ''}${attributeSelector.value}${attributeSelector.quote ?? ''}${attributeSelector.sensitivity ? ` ${attributeSelector.sensitivity}` : ''}`, - }, // aria-[foo~="true"], aria-[foo|="true"], … - } satisfies Variant) - } - - // Arbitrary attributes - else { - replaceObject(variant, { - kind: 'arbitrary', - selector: target.value, - relative: false, - } satisfies Variant) - } - } - - if (prefixedVariant) { - return [prefixedVariant, variant] - } - } - } - - return result -} - -// --- - -function optimizeArbitraryValueExpressions( - candidate: Candidate, - options: InternalCanonicalizeOptions, -): Candidate { - if (candidate.kind !== 'functional' || candidate.value?.kind !== 'arbitrary') { - return candidate - } - - let designSystem = options.designSystem - let hasSameSignature = designSystem.storage[COMPARE_CANDIDATES_KEY].get(options.signatureOptions) - - let valueAst = ValueParser.parse(candidate.value.value) - - // Start by constant folding the value expression, when dealing with `calc(…)` - if (valueAst.length === 1 && valueAst[0].kind === 'function' && valueAst[0].value === 'calc') { - let [folded, foldedValueAst] = constantFoldDeclarationAst(valueAst, null, false) - if (folded) { - let replacement = cloneCandidate(candidate) - replacement.value!.value = ValueParser.toCss(foldedValueAst) - - if (hasSameSignature(candidate, replacement)) { - candidate = replacement - valueAst = foldedValueAst - } - } - } - - // Move `-` sign into the arbitrary value itself - if (candidate.root[0] === '-') { - // We're dealing with a `var(…)`, keep as-is - if (valueAst.length === 1 && valueAst[0].kind === 'function' && valueAst[0].value === 'var') { - return candidate - } - - // Move `* -1` inside, and try to constant fold to see if it's even worth - // updating the candidate or not. - let expressionAst = ValueParser.parse(`calc(${candidate.value!.value} * -1)`) - let [folded, foldedExpressionAst] = constantFoldDeclarationAst(expressionAst, null, false) - if (folded) { - let replacement = cloneCandidate(candidate) - - replacement.root = replacement.root.slice(1) // Drop the leading `-` - replacement.value!.value = ValueParser.toCss(foldedExpressionAst) - - if (hasSameSignature(candidate, replacement)) { - candidate = replacement - valueAst = foldedExpressionAst - } - } - } - - // Move `-` sign out of the arbitrary value - if (valueAst.length === 1 && valueAst[0].kind === 'function' && valueAst[0].value === 'calc') { - let calcArgs = valueAst[0].nodes - - // `calc(arg * -1)` or `calc(-1 * arg)` - if ( - calcArgs.length === 5 && - calcArgs[1].kind === 'separator' && - calcArgs[1].value === ' ' && - calcArgs[2].kind === 'word' && - calcArgs[2].value === '*' && - calcArgs[3].kind === 'separator' && - calcArgs[3].value === ' ' - ) { - let arg = - calcArgs[4].kind === 'word' && calcArgs[4].value === '-1' - ? calcArgs[0] - : calcArgs[0].kind === 'word' && calcArgs[0].value === '-1' - ? calcArgs[4] - : null - - if (arg) { - let replacement = cloneCandidate(candidate) - replacement.root = `-${candidate.root}` - replacement.value!.value = ValueParser.toCss([arg]) - - if (hasSameSignature(candidate, replacement)) { - candidate = replacement - } - } - } - } - - return candidate -} - -// ---- - -// Optimize the modifier -// -// E.g.: -// -// - `/[25%]` → `/25` -// - `/[100%]` → `/100` → -// - `/100` → -// -function optimizeModifier(candidate: Candidate, options: InternalCanonicalizeOptions): Candidate { - // We are only interested in functional or arbitrary utilities with a modifier - if ( - (candidate.kind !== 'functional' && candidate.kind !== 'arbitrary') || - candidate.modifier === null - ) { - return candidate - } - - let designSystem = options.designSystem - let signatures = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options.signatureOptions) - - let targetSignature = signatures.get(designSystem.printCandidate(candidate)) - let modifier = candidate.modifier - - // 1. Try to drop the modifier entirely - if ( - targetSignature === - signatures.get(designSystem.printCandidate({ ...candidate, modifier: null })) - ) { - candidate.modifier = null - return candidate - } - - // 2. Try to remove the square brackets and the `%` sign - { - let newModifier: NamedUtilityValue = { - kind: 'named', - value: modifier.value.endsWith('%') - ? modifier.value.includes('.') - ? `${Number(modifier.value.slice(0, -1))}` - : modifier.value.slice(0, -1) - : modifier.value, - fraction: null, - } - - if ( - targetSignature === - signatures.get(designSystem.printCandidate({ ...candidate, modifier: newModifier })) - ) { - candidate.modifier = newModifier - return candidate - } - } - - // 3. Try to remove the square brackets, but multiply by 100. E.g.: `[0.16]` -> `16` - { - let newModifier: NamedUtilityValue = { - kind: 'named', - value: `${parseFloat(modifier.value) * 100}`, - fraction: null, - } - - if ( - targetSignature === - signatures.get(designSystem.printCandidate({ ...candidate, modifier: newModifier })) - ) { - candidate.modifier = newModifier - return candidate - } - } - - return candidate -} - -export enum SignatureFeatures { - None = 0, - ExpandProperties = 1 << 0, - LogicalToPhysical = 1 << 1, -} - -interface SignatureOptions { - /** - * The root font size in pixels. If provided, `rem` values will be normalized - * to `px` values. - * - * E.g.: `mt-[16px]` with `rem: 16` will become `mt-4` (assuming `--spacing: 0.25rem`). - */ - rem: number | null - - /** - * Features that influence how signatures are computed. - */ - features: SignatureFeatures -} - -// Given a utility, compute a signature that represents the utility. The -// signature will be a normalised form of the generated CSS for the utility, or -// a unique symbol if the utility is not valid. The class in the selector will -// be replaced with the `.x` selector. -// -// This function should only be passed the base utility so `flex`, `hover:flex` -// and `focus:flex` will all use just `flex`. Variants are handled separately. -// -// E.g.: -// -// | UTILITY | GENERATED SIGNATURE | -// | ---------------- | ----------------------- | -// | `[display:flex]` | `.x { display: flex; }` | -// | `flex` | `.x { display: flex; }` | -// -// These produce the same signature, therefore they represent the same utility. -export const UTILITY_SIGNATURE_KEY = Symbol() -function createUtilitySignatureCache( - designSystem: DesignSystem, -): DesignSystem['storage'][typeof UTILITY_SIGNATURE_KEY] { - return new DefaultMap((options: SignatureOptions) => { - return new DefaultMap((utility) => { - try { - // Ensure the prefix is added to the utility if it is not already present. - utility = - designSystem.theme.prefix && !utility.startsWith(designSystem.theme.prefix) - ? `${designSystem.theme.prefix}:${utility}` - : utility - - // Use `@apply` to normalize the selector to `.x` - let ast: AstNode[] = [styleRule('.x', [atRule('@apply', utility)])] - - temporarilyDisableThemeInline(designSystem, () => { - // There's separate utility caches for respect important vs not - // so we want to compile them both with `@theme inline` disabled - for (let candidate of designSystem.parseCandidate(utility)) { - designSystem.compileAstNodes(candidate, CompileAstFlags.RespectImportant) - } - - substituteAtApply(ast, designSystem) - }) - - // Optimize the AST. This is needed such that any internal intermediate - // nodes are gone. This will also cleanup declaration nodes with undefined - // values or `--tw-sort` declarations. - canonicalizeAst(designSystem, ast, options) - - // Compute the final signature, by generating the CSS for the utility - let signature = toCss(ast) - return signature - } catch { - // A unique symbol is returned to ensure that 2 signatures resulting in - // `null` are not considered equal. - return Symbol() - } - }) - }) -} - -// #RGB -// #RGBA -// #RRGGBB -// #RRGGBBAA -const HEX_REGEX = /#(?:[a-f0-9]{8}|[a-f0-9]{6}|[a-f0-9]{4}|[a-f0-9]{3})/gi - -// Optimize the CSS AST to make it suitable for signature comparison. We want to -// expand declarations, ignore comments, sort declarations etc... -function canonicalizeAst(designSystem: DesignSystem, ast: AstNode[], options: SignatureOptions) { - let { rem } = options - - walk(ast, { - enter(node, ctx) { - // Optimize declarations - if (node.kind === 'declaration') { - if (node.value === undefined || node.property === '--tw-sort') { - return WalkAction.Replace([]) - } - - // Ignore `--tw-{property}` if `{property}` exists with the same value - if (node.property.startsWith('--tw-')) { - if ( - ctx.siblings.some( - (sibling) => - sibling.kind === 'declaration' && - node.value === sibling.value && - node.important === sibling.important && - !sibling.property.startsWith('--tw-'), - ) - ) { - return WalkAction.Replace([]) - } - } - - if (options.features & SignatureFeatures.ExpandProperties) { - let replacement = expandDeclaration(node, options.features) - if (replacement) return WalkAction.Replace(replacement) - } - - // Resolve theme values to their inlined value. - if (node.value.includes('var(')) { - node.value = resolveVariablesInValue(node.value, designSystem) - } - - let valueAst = ValueParser.parse(node.value) - - // Very basic `calc(…)` constant folding to handle the spacing scale - // multiplier: - // - // Input: `--spacing(4)` - // → `calc(var(--spacing, 0.25rem) * 4)` - // → `calc(0.25rem * 4)` ← this is the case we will see - // after inlining the variable - // → `1rem` - let [folded, foldedValueAst] = constantFoldDeclarationAst(valueAst, rem) - - // Normalize `calc(…)` expressions such that arguments that are - // associatively equivalent are rendered in the same way. - // - // `calc(var(--foo) * -1)` === `calc(-1 * var(--foo))` - let [normalized, canonicalizedValueAst] = canonicalizeCalcExpressionsAst(foldedValueAst) - - if (folded || normalized) { - node.value = ValueParser.toCss(canonicalizedValueAst) - } - - // We will normalize the `node.value`, this is the same kind of logic - // we use when printing arbitrary values. It will remove unnecessary - // whitespace. - // - // Essentially normalizing the `node.value` to a canonical form. - node.value = printArbitraryValue(node.value) - } - - // Replace special nodes with its children - else if (node.kind === 'context' || node.kind === 'at-root') { - return WalkAction.Replace(node.nodes) - } - - // Remove comments - else if (node.kind === 'comment') { - return WalkAction.Replace([]) - } - - // Remove at-rules that are not needed for the signature - else if (node.kind === 'at-rule' && node.name === '@property') { - return WalkAction.Replace([]) - } - }, - exit(node) { - if (node.kind === 'rule' || node.kind === 'at-rule') { - // Remove declarations that are re-defined again later. - // - // This could maybe result in unwanted behavior (because similar - // properties typically exist for backwards compatibility), but for - // signature purposes we can assume that the last declaration wins. - if (node.nodes.length > 1) { - let seen = new Set() - for (let i = node.nodes.length - 1; i >= 0; i--) { - let child = node.nodes[i] - if (child.kind !== 'declaration') continue - if (child.value === undefined) continue - - if (seen.has(child.property)) { - node.nodes.splice(i, 1) - } - seen.add(child.property) - } - } - - // Sort declarations alphabetically by property name - node.nodes.sort((a, b) => { - if (a.kind !== 'declaration') return 0 - if (b.kind !== 'declaration') return 0 - return a.property.localeCompare(b.property) - }) - } - - // - else if (node.kind === 'declaration' && node.value) { - // Leave CSS variables alone - if (node.property[0] === '-' && node.property[1] === '-') return - - // Ensure hex colors are always lowercased - { - HEX_REGEX.lastIndex = 0 - node.value = node.value.replace(HEX_REGEX, (color) => color.toLowerCase()) - } - } - }, - }) - - return ast -} - -// Variables whose theme value is a CSS-wide keyword (e.g.: `unset`) are never -// inlined. These are typically registered as a placeholder to be re-assigned at -// runtime, so two variables that share such a value are not interchangeable. -// -// E.g.: -// -// ```css -// @theme { -// --foreground: unset; -// --background: unset; -// } -// ``` -// -// Inlining would make `text-(--foreground)` and `text-(--background)` produce -// the same signature `color: unset`, even though they are different at runtime. -// -// https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/Data_types#css-wide_keywords -const CSS_WIDE_KEYWORDS = ['initial', 'inherit', 'revert', 'revert-layer', 'revert-rule', 'unset'] - -// Resolve theme values to their inlined value. -// -// E.g.: -// -// `[color:var(--color-red-500)]` → `[color:oklch(63.7%_0.237_25.331)]` -// `[color:oklch(63.7%_0.237_25.331)]` → `[color:oklch(63.7%_0.237_25.331)]` -// -// Due to the `@apply` from above, this will become: -// -// ```css -// .example { -// color: oklch(63.7% 0.237 25.331); -// } -// ``` -// -// Which conveniently will be equivalent to: `text-red-500` when we inline the -// value. -// -// Without inlining: -// ```css -// .example { -// color: var(--color-red-500, oklch(63.7% 0.237 25.331)); -// } -// ``` -// -// Inlined: -// ```css -// .example { -// color: oklch(63.7% 0.237 25.331); -// } -// ``` -// -// Recently we made sure that utilities like `text-red-500` also generate the -// fallback value for usage in `@reference` mode. -// -// The second assumption is that if you use `var(--key, fallback)` that happens -// to match a known variable _and_ its inlined value. Then we can replace it -// with the inlined variable. This allows us to handle custom `@theme` and -// `@theme inline` definitions. -function resolveVariablesInValue(value: string, designSystem: DesignSystem): string { - let changed = false - let valueAst = ValueParser.parse(value) - - let seen = new Set() - walk(valueAst, (valueNode) => { - if (valueNode.kind !== 'function') return - if (valueNode.value !== 'var') return - - // Resolve the underlying value of the variable - if (valueNode.nodes.length !== 1 && valueNode.nodes.length < 3) { - return - } - - let variable = valueNode.nodes[0].value - - // Drop the prefix from the variable name if it is present. The - // internal variable doesn't have the prefix. - if (designSystem.theme.prefix && variable.startsWith(`--${designSystem.theme.prefix}-`)) { - variable = variable.slice(`--${designSystem.theme.prefix}-`.length) - } - let variableValue = designSystem.resolveThemeValue(variable) - // Prevent infinite recursion when the variable value contains the - // variable itself. - if (seen.has(variable)) return - seen.add(variable) - if (variableValue === undefined) return // Couldn't resolve the variable - - // CSS-wide keywords are never inlined - if (CSS_WIDE_KEYWORDS.includes(variableValue.toLowerCase())) return - - // Inject variable fallbacks when no fallback is present yet. - // - // A fallback could consist of multiple values. - // - // E.g.: - // - // ``` - // var(--font-sans, ui-sans-serif, system-ui, sans-serif, …) - // ``` - { - // More than 1 argument means that a fallback is already present - if (valueNode.nodes.length === 1) { - // Inject the fallback value into the variable lookup - changed = true - valueNode.nodes.push(...ValueParser.parse(`,${variableValue}`)) - } - } - - // Replace known variable + inlined fallback value with the value - // itself again - { - // We need at least 3 arguments. The variable, the separator and a fallback value. - if (valueNode.nodes.length >= 3) { - let nodeAsString = ValueParser.toCss(valueNode.nodes) // This could include more than just the variable - let constructedValue = `${valueNode.nodes[0].value},${variableValue}` - if (nodeAsString === constructedValue) { - changed = true - return WalkAction.Replace(ValueParser.parse(variableValue)) - } - } - } - }) - - // Replace the value with the new value - if (changed) return ValueParser.toCss(valueAst) - return value -} - -// Index all static utilities by property and value -const STATIC_UTILITIES_KEY = Symbol() -function createStaticUtilitiesCache(): DesignSystem['storage'][typeof STATIC_UTILITIES_KEY] { - return new DefaultMap((_optiones: SignatureOptions) => { - return new DefaultMap((_property: string) => { - return new DefaultMap((_value: string) => { - return new Set() - }) - }) - }) -} - -const UTILITY_PROPERTIES_KEY = Symbol() -function createUtilityPropertiesCache( - designSystem: DesignSystem, -): DesignSystem['storage'][typeof UTILITY_PROPERTIES_KEY] { - return new DefaultMap((options: SignatureOptions) => { - return new DefaultMap((className) => { - let localPropertyValueLookup = new DefaultMap((_property) => new Set()) - - if (designSystem.theme.prefix && !className.startsWith(designSystem.theme.prefix)) { - className = `${designSystem.theme.prefix}:${className}` - } - let parsed = designSystem.parseCandidate(className) - if (parsed.length === 0) return localPropertyValueLookup - - try { - let ast = designSystem.compileAstNodes(parsed[0]).map((x) => cloneAstNode(x.node)) - walk(canonicalizeAst(designSystem, ast, options), (node) => { - if (node.kind === 'declaration') { - localPropertyValueLookup.get(node.property).add(node.value!) - designSystem.storage[STATIC_UTILITIES_KEY].get(options) - .get(node.property) - .get(node.value!) - .add(className) - } - }) - } catch { - // Ignore errors, this could happen when we call a plugin with a value - // it didn't expect. But since plugins are functions, we can't know - // ahead of time what it expects. - } - - return localPropertyValueLookup - }) - }) -} - -// For all static utilities in the system, compute a lookup table that maps the -// utility signature to the utility name. This is used to find the utility name -// for a given utility signature. -// -// For all functional utilities, we can compute static-like utilities by -// essentially pre-computing the values and modifiers. This is a bit slow, but -// also only has to happen once per design system. -const PRE_COMPUTED_UTILITIES_KEY = Symbol() -function createPreComputedUtilitiesCache( - designSystem: DesignSystem, -): DesignSystem['storage'][typeof PRE_COMPUTED_UTILITIES_KEY] { - return new DefaultMap((options: SignatureOptions) => { - let signatures = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options) - let lookup = new DefaultMap(() => []) - - // Right now all plugins are implemented using functions so they are a black - // box. Let's use the `getClassList` and consider every known suggestion as a - // static utility for now. - for (let [className, meta] of designSystem.getClassList()) { - let signature = signatures.get(className) - if (typeof signature !== 'string') continue - - // Skip the utility if `-{utility}-0` has the same signature as - // `{utility}-0` (its positive version). This will prefer positive values - // over negative values. - if (className[0] === '-' && className.endsWith('-0')) { - let positiveSignature = signatures.get(className.slice(1)) - if (typeof positiveSignature === 'string' && signature === positiveSignature) { - continue - } - } - - lookup.get(signature).push(className) - designSystem.storage[UTILITY_PROPERTIES_KEY].get(options).get(className) - - for (let modifier of meta.modifiers) { - // Modifiers representing numbers can be computed and don't need to be - // pre-computed. Doing the math and at the time of writing this, this - // would save you 250k additionally pre-computed utilities... - if (isValidSpacingMultiplier(modifier)) { - continue - } - - let classNameWithModifier = `${className}/${modifier}` - let signature = signatures.get(classNameWithModifier) - if (typeof signature !== 'string') continue - lookup.get(signature).push(classNameWithModifier) - designSystem.storage[UTILITY_PROPERTIES_KEY].get(options).get(classNameWithModifier) - } - } - - return lookup - }) -} - -// Given a variant, compute a signature that represents the variant. The -// signature will be a normalised form of the generated CSS for the variant, or -// a unique symbol if the variant is not valid. The class in the selector will -// be replaced with `.x`. -// -// E.g.: -// -// | VARIANT | GENERATED SIGNATURE | -// | ---------------- | ----------------------------- | -// | `[&:focus]:flex` | `.x:focus { display: flex; }` | -// | `focus:flex` | `.x:focus { display: flex; }` | -// -// These produce the same signature, therefore they represent the same variant. -export const VARIANT_SIGNATURE_KEY = Symbol() -function createVariantSignatureCache( - designSystem: DesignSystem, -): DesignSystem['storage'][typeof VARIANT_SIGNATURE_KEY] { - return new DefaultMap((rem: number | null) => { - return new DefaultMap((variant) => { - try { - // Ensure the prefix is added to the utility if it is not already present. - variant = - designSystem.theme.prefix && !variant.startsWith(designSystem.theme.prefix) - ? `${designSystem.theme.prefix}:${variant}` - : variant - - // Use `@apply` to normalize the selector to `.x` - let ast: AstNode[] = [styleRule('.x', [atRule('@apply', `${variant}:flex`)])] - substituteAtApply(ast, designSystem) - - // Canonicalize selectors to their minimal form - walk(ast, (node) => { - // At-rules - if (node.kind === 'at-rule') { - // Normalize dimensions such that equivalent values in different - // units are considered the same. E.g.: with a root font size of - // `16px`, `@media (width >= 64rem)` and `@media (width >= 1024px)` - // are equivalent. - node.params = constantFoldDeclaration(node.params, rem) - - if (node.params.includes(' ')) { - node.params = node.params.replaceAll(' ', '') - } - } - - // Style rules - else if (node.kind === 'rule') { - let selectorAst = SelectorParser.parse(node.selector) - let changed = false - walk(selectorAst, (node) => { - // Assumption: when we have a list of selectors: `.foo, .bar` we - // want to mark this as changed because this will be re-printed as - // `.foo,.bar`. - // - // Similarly, when we receive a combinator like `.foo + .bar`, this - // will be printed as `.foo+.bar`. - // - // It could be that this was already optimal, but then this would be - // a no-op situation. - if (node.kind === 'list' || node.kind === 'combinator') { - changed = true - } - - // Remove unnecessary `:is(…)` selectors - else if (node.kind === 'function' && node.value === ':is') { - // A single selector inside of `:is(…)` can be replaced with the - // selector itself. - // - // E.g.: `:is(.foo)` → `.foo` - if (node.nodes.length === 1) { - changed = true - return WalkAction.Replace(node.nodes) - } - - // A selector with the universal selector `*` followed by a pseudo - // class, can be replaced with the pseudo class itself. - else if ( - node.nodes.length === 2 && - node.nodes[0].kind === 'selector' && - node.nodes[0].value === '*' && - node.nodes[1].kind === 'selector' && - node.nodes[1].value[0] === ':' - ) { - changed = true - return WalkAction.Replace(node.nodes[1]) - } - } - - // Ensure `*` exists before pseudo selectors inside of `:not(…)`, - // `:where(…)`, … - // - // E.g.: - // - // `:not(:first-child)` → `:not(*:first-child)` - // - else if ( - node.kind === 'function' && - node.value[0] === ':' && - node.nodes[0]?.kind === 'selector' && - node.nodes[0]?.value[0] === ':' - ) { - changed = true - node.nodes.unshift({ kind: 'selector', value: '*' }) - } - }) - - if (changed) { - node.selector = SelectorParser.toCss(selectorAst, true) - } - } - }) - - // Compute the final signature, by generating the CSS for the variant - let signature = toCss(ast) - return signature - } catch { - // A unique symbol is returned to ensure that 2 signatures resulting in - // `null` are not considered equal. - return Symbol() - } - }) - }) -} - -export const PRE_COMPUTED_VARIANTS_KEY = Symbol() -function createPreComputedVariantsCache( - designSystem: DesignSystem, -): DesignSystem['storage'][typeof PRE_COMPUTED_VARIANTS_KEY] { - return new DefaultMap((rem: number | null) => { - let signatures = designSystem.storage[VARIANT_SIGNATURE_KEY].get(rem) - let lookup = new DefaultMap(() => []) - - for (let [root, variant] of designSystem.variants.entries()) { - // Actual static variants - if (variant.kind === 'static') { - let signature = signatures.get(root) - if (typeof signature !== 'string') continue - lookup.get(signature).push(root) - } - - // Functional variants with known values, e.g.: `max-lg`, `@min-md`, … - else if (variant.kind === 'functional') { - for (let value of designSystem.variants.getCompletions(root)) { - let name = root === '@' ? `@${value}` : `${root}-${value}` - let signature = signatures.get(name) - if (typeof signature !== 'string') continue - lookup.get(signature).push(name) - } - } - } - - return lookup - }) -} - -function temporarilyDisableThemeInline(designSystem: DesignSystem, cb: () => T): T { - // Turn off `@theme inline` feature such that `@theme` and `@theme inline` are - // considered the same. The biggest motivation for this is referencing - // variables in another namespace that happen to contain the same value as the - // utility's own namespaces it is reading from. - // - // E.g.: - // - // The `max-w-*` utility doesn't read from the `--breakpoint-*` namespace. - // But it does read from the `--container-*` namespace. It also happens to - // be the case that `--breakpoint-md` and `--container-3xl` are the exact - // same value. - // - // If you then use the `max-w-(--breakpoint-md)` utility, inlining the - // variable would mean: - // - `max-w-(--breakpoint-md)` → `max-width: 48rem;` → `max-w-3xl` - // - `max-w-(--contianer-3xl)` → `max-width: 48rem;` → `max-w-3xl` - // - // Not inlining the variable would mean: - // - `max-w-(--breakpoint-md)` → `max-width: var(--breakpoint-md);` → `max-w-(--breakpoint-md)` - // - `max-w-(--container-3xl)` → `max-width: var(--container-3xl);` → `max-w-3xl` - - // @ts-expect-error We are monkey-patching a method that's considered private - // in TypeScript - let originalGet = designSystem.theme.values.get - - // Track all values with the inline option set, so we can restore them later. - let restorableInlineOptions = new Set<{ options: ThemeOptions }>() - - // @ts-expect-error We are monkey-patching a method that's considered private - // in TypeScript - designSystem.theme.values.get = (key: string) => { - // @ts-expect-error We are monkey-patching a method that's considered private - // in TypeScript - let value = originalGet.call(designSystem.theme.values, key) - if (value === undefined) return value - - // Remove `inline` if it was set - if (value.options & ThemeOptions.INLINE) { - restorableInlineOptions.add(value) - value.options &= ~ThemeOptions.INLINE - } - - return value - } - - try { - // Run the callback with the `@theme inline` feature disabled - return cb() - } finally { - // Restore the `@theme inline` to the original value - // @ts-expect-error We are monkey-patching a method that's private - designSystem.theme.values.get = originalGet - - // Re-add the `inline` option, in case future lookups are done - for (let value of restorableInlineOptions) { - value.options |= ThemeOptions.INLINE - } - } -} - -// Generator that generates all combinations of the given set. Using a generator -// so we can stop early when we found a suitable combination. -// -// NOTE: -// -// 1. Yield biggest combinations first -// 2. Sets of size 1 and 0 are not yielded -function* combinations(arr: T[]): Generator { - let n = arr.length - let limit = 1n << BigInt(n) - - for (let k = n; k >= 2; k--) { - let mask = (1n << BigInt(k)) - 1n - - while (mask < limit) { - let out = [] - for (let i = 0; i < n; i++) { - if ((mask >> BigInt(i)) & 1n) { - out.push(arr[i]) - } - } - yield out - - // Gosper's hack: - // - https://programmingforinsomniacs.blogspot.com/2018/03/gospers-hack-explained.html - // - https://rosettacode.org/wiki/Gosper%27s_hack - // - // We need to generate the next mask in lexicographical order. - let carry = mask & -mask - let ripple = mask + carry - mask = (((ripple ^ mask) >> 2n) / carry) | ripple - } - } -} - -function intersection(a: Set, b: Set): Set { - // @ts-expect-error Set.prototype.intersection is only available in Node.js v22+ - if (typeof a.intersection === 'function') return a.intersection(b) - - // Polyfill for environments that do not support Set.prototype.intersection yet - if (a.size === 0 || b.size === 0) return new Set() - - let result = new Set(a) - for (let item of b) { - if (!result.has(item)) { - result.delete(item) - } - } - - return result -} diff --git a/packages/tailwindcss/src/cartesian.ts b/packages/tailwindcss/src/cartesian.ts deleted file mode 100644 index f0f7bbba4..000000000 --- a/packages/tailwindcss/src/cartesian.ts +++ /dev/null @@ -1,45 +0,0 @@ -type CartesianInput = readonly unknown[][] - -type CartesianResult = T extends [ - infer Head extends unknown[], - ...infer Tail extends CartesianInput, -] - ? [Head[number], ...CartesianResult] - : [] - -export function* cartesian(...sets: T): Generator> { - let n = sets.length - if (n === 0) return - - // If any input set is empty, the Cartesian product is empty. - if (sets.some((set) => set.length === 0)) { - return - } - - // Index lookup - let idx = Array(n).fill(0) - - while (true) { - // Compute current combination - let result = [] as CartesianResult - for (let i = 0; i < n; i++) { - result[i] = sets[i][idx[i]] - } - yield result - - // Update index vector - let k = n - 1 - while (k >= 0) { - idx[k]++ - if (idx[k] < sets[k].length) { - break - } - idx[k] = 0 - k-- - } - - if (k < 0) { - return - } - } -} diff --git a/packages/tailwindcss/src/compat/apply-compat-hooks.ts b/packages/tailwindcss/src/compat/apply-compat-hooks.ts index 036b9d88e..fe30f6270 100644 --- a/packages/tailwindcss/src/compat/apply-compat-hooks.ts +++ b/packages/tailwindcss/src/compat/apply-compat-hooks.ts @@ -1,9 +1,7 @@ import { Features } from '..' -import { cssContext, styleRule, toCss, type AstNode } from '../ast' +import { styleRule, toCss, walk, WalkAction, type AstNode } from '../ast' import type { DesignSystem } from '../design-system' -import type { SourceLocation } from '../source-maps/source' import { segment } from '../utils/segment' -import { walk, WalkAction } from '../walk' import { applyConfigToTheme } from './apply-config-to-theme' import { applyKeyframesToTheme } from './apply-keyframes-to-theme' import { createCompatConfig } from './config/create-compat-config' @@ -23,7 +21,7 @@ export async function applyCompatibilityHooks({ base, ast, loadModule, - sources, + globs, }: { designSystem: DesignSystem base: string @@ -32,32 +30,20 @@ export async function applyCompatibilityHooks({ path: string, base: string, resourceHint: 'plugin' | 'config', - ) => Promise<{ - path: string - base: string - module: any - }> - sources: { base: string; pattern: string; negated: boolean }[] + ) => Promise<{ module: any; base: string }> + globs: { origin?: string; pattern: string }[] }) { let features = Features.None - let pluginPaths: [ - { id: string; base: string; reference: boolean; src: SourceLocation | undefined }, - CssPluginOptions | null, - ][] = [] - let configPaths: { - id: string - base: string - reference: boolean - src: SourceLocation | undefined - }[] = [] + let pluginPaths: [{ id: string; base: string; reference: boolean }, CssPluginOptions | null][] = + [] + let configPaths: { id: string; base: string; reference: boolean }[] = [] - walk(ast, (node, _ctx) => { + walk(ast, (node, { parent, replaceWith, context }) => { if (node.kind !== 'at-rule') return - let ctx = cssContext(_ctx) // Collect paths from `@plugin` at-rules if (node.name === '@plugin') { - if (ctx.parent !== null) { + if (parent !== null) { throw new Error('`@plugin` cannot be nested.') } @@ -110,17 +96,13 @@ export async function applyCompatibilityHooks({ } pluginPaths.push([ - { - id: pluginPath, - base: ctx.context.base as string, - reference: !!ctx.context.reference, - src: node.src, - }, + { id: pluginPath, base: context.base as string, reference: !!context.reference }, Object.keys(options).length > 0 ? options : null, ]) + replaceWith([]) features |= Features.JsPluginCompat - return WalkAction.Replace([]) + return } // Collect paths from `@config` at-rules @@ -129,18 +111,18 @@ export async function applyCompatibilityHooks({ throw new Error('`@config` cannot have a body.') } - if (ctx.parent !== null) { + if (parent !== null) { throw new Error('`@config` cannot be nested.') } configPaths.push({ id: node.params.slice(1, -1), - base: ctx.context.base as string, - reference: !!ctx.context.reference, - src: node.src, + base: context.base as string, + reference: !!context.reference, }) + replaceWith([]) features |= Features.JsPluginCompat - return WalkAction.Replace([]) + return } }) @@ -163,7 +145,7 @@ export async function applyCompatibilityHooks({ designSystem, base, ast, - sources, + globs, configs: [], pluginDetails: [], }) @@ -176,19 +158,18 @@ export async function applyCompatibilityHooks({ let [configs, pluginDetails] = await Promise.all([ Promise.all( - configPaths.map(async ({ id, base, reference, src }) => { + configPaths.map(async ({ id, base, reference }) => { let loaded = await loadModule(id, base, 'config') return { path: id, base: loaded.base, config: loaded.module as UserConfig, reference, - src, } }), ), Promise.all( - pluginPaths.map(async ([{ id, base, reference, src }, pluginOptions]) => { + pluginPaths.map(async ([{ id, base, reference }, pluginOptions]) => { let loaded = await loadModule(id, base, 'plugin') return { path: id, @@ -196,7 +177,6 @@ export async function applyCompatibilityHooks({ plugin: loaded.module as Plugin, options: pluginOptions, reference, - src, } }), ), @@ -206,7 +186,7 @@ export async function applyCompatibilityHooks({ designSystem, base, ast, - sources, + globs, configs, pluginDetails, }) @@ -218,20 +198,19 @@ function upgradeToFullPluginSupport({ designSystem, base, ast, - sources, + globs, configs, pluginDetails, }: { designSystem: DesignSystem base: string ast: AstNode[] - sources: { base: string; pattern: string; negated: boolean }[] + globs: { origin?: string; pattern: string }[] configs: { path: string base: string config: UserConfig reference: boolean - src: SourceLocation | undefined }[] pluginDetails: { path: string @@ -239,7 +218,6 @@ function upgradeToFullPluginSupport({ plugin: Plugin options: CssPluginOptions | null reference: boolean - src: SourceLocation | undefined }[] }) { let features = Features.None @@ -249,7 +227,6 @@ function upgradeToFullPluginSupport({ config: { plugins: [detail.plugin] }, base: detail.base, reference: detail.reference, - src: detail.src, } } @@ -258,7 +235,6 @@ function upgradeToFullPluginSupport({ config: { plugins: [detail.plugin(detail.options)] }, base: detail.base, reference: detail.reference, - src: detail.src, } } @@ -268,15 +244,38 @@ function upgradeToFullPluginSupport({ let userConfig = [...pluginConfigs, ...configs] let { resolvedConfig } = resolveConfig(designSystem, [ - { config: createCompatConfig(designSystem.theme), base, reference: true, src: undefined }, + { config: createCompatConfig(designSystem.theme), base, reference: true }, ...userConfig, - { config: { plugins: [darkModePlugin] }, base, reference: true, src: undefined }, + { config: { plugins: [darkModePlugin] }, base, reference: true }, ]) let { resolvedConfig: resolvedUserConfig, replacedThemeKeys } = resolveConfig( designSystem, userConfig, ) + // Replace `resolveThemeValue` with a version that is backwards compatible + // with dot-notation but also aware of any JS theme configurations registered + // by plugins or JS config files. This is significantly slower than just + // upgrading dot-notation keys so we only use this version if plugins or + // config files are actually being used. In the future we may want to optimize + // this further by only doing this if plugins or config files _actually_ + // registered JS config objects. + designSystem.resolveThemeValue = function resolveThemeValue(path: string, forceInline?: boolean) { + let resolvedValue = pluginApi.theme(path, forceInline) + + if (Array.isArray(resolvedValue) && resolvedValue.length === 2) { + // When a tuple is returned, return the first element + return resolvedValue[0] + } else if (Array.isArray(resolvedValue)) { + // Arrays get serialized into a comma-separated lists + return resolvedValue.join(', ') + } else if (typeof resolvedValue === 'string') { + // Otherwise only allow string values here, objects (and namespace maps) + // are treated as non-resolved values for the CSS `theme()` function. + return resolvedValue + } + } + let pluginApiConfig = { designSystem, ast, @@ -288,75 +287,23 @@ function upgradeToFullPluginSupport({ }, } - let sharedPluginApi = buildPluginApi({ - ...pluginApiConfig, - referenceMode: false, - src: undefined, - }) + let pluginApi = buildPluginApi({ ...pluginApiConfig, referenceMode: false }) + let referenceModePluginApi = undefined - // Replace `resolveThemeValue` with a version that is backwards compatible - // with dot-notation but also aware of any JS theme configurations registered - // by plugins or JS config files. This is significantly slower than just - // upgrading dot-notation keys so we only use this version if plugins or - // config files are actually being used. In the future we may want to optimize - // this further by only doing this if plugins or config files _actually_ - // registered JS config objects. - let defaultResolveThemeValue = designSystem.resolveThemeValue - designSystem.resolveThemeValue = function resolveThemeValue(path: string, forceInline?: boolean) { - if (path[0] === '-' && path[1] === '-') { - return defaultResolveThemeValue(path, forceInline) + for (let { handler, reference } of resolvedConfig.plugins) { + if (reference) { + referenceModePluginApi ||= buildPluginApi({ ...pluginApiConfig, referenceMode: true }) + handler(referenceModePluginApi) + } else { + handler(pluginApi) } - - let resolvedValue = sharedPluginApi.theme(path, undefined) - - // If we're dealing with an object that has the `DEFAULT` key, unwrap it to - // the default value first. This happens for namespace root lookups where - // the CSS theme defines a bare value, e.g. `--shadow` resolved via - // `theme('shadow')`. - if ( - typeof resolvedValue === 'object' && - resolvedValue !== null && - !Array.isArray(resolvedValue) && - 'DEFAULT' in resolvedValue - ) { - resolvedValue = resolvedValue.DEFAULT - } - - // When a tuple is returned, return the first element - if (Array.isArray(resolvedValue) && resolvedValue.length === 2) { - return resolvedValue[0] - } - - // Arrays get serialized into a comma-separated lists - else if (Array.isArray(resolvedValue)) { - return resolvedValue.join(', ') - } - - // Otherwise only allow string values here, objects (and namespace maps) - // are treated as non-resolved values for the CSS `theme()` function. - else if (typeof resolvedValue === 'string') { - return resolvedValue - } - } - - for (let { handler, reference, src } of resolvedConfig.plugins) { - // Each plugin gets its own instance of the plugin API because nodes added - // to the AST may need to point to the `@config` or `@plugin` that they - // originated from - let api = buildPluginApi({ - ...pluginApiConfig, - referenceMode: reference ?? false, - src, - }) - - handler(api) } // Merge the user-configured theme keys into the design system. The compat // config would otherwise expand into namespaces like `background-color` which // core utilities already read from. applyConfigToTheme(designSystem, resolvedUserConfig, replacedThemeKeys) - applyKeyframesToTheme(designSystem, resolvedUserConfig) + applyKeyframesToTheme(designSystem, resolvedUserConfig, replacedThemeKeys) registerThemeVariantOverrides(resolvedUserConfig, designSystem) registerScreensConfig(resolvedUserConfig, designSystem) @@ -389,18 +336,18 @@ function upgradeToFullPluginSupport({ if (typeof resolvedConfig.important === 'string') { let wrappingSelector = resolvedConfig.important - walk(ast, (node, _ctx) => { + walk(ast, (node, { replaceWith, parent }) => { if (node.kind !== 'at-rule') return if (node.name !== '@tailwind' || node.params !== 'utilities') return - let ctx = cssContext(_ctx) - // The AST node was already manually wrapped so there's nothing to do - if (ctx.parent?.kind === 'rule' && ctx.parent.selector === wrappingSelector) { + if (parent?.kind === 'rule' && parent.selector === wrappingSelector) { return WalkAction.Stop } - return WalkAction.ReplaceStop(styleRule(wrappingSelector, [node])) + replaceWith(styleRule(wrappingSelector, [node])) + + return WalkAction.Stop }) } @@ -415,12 +362,7 @@ function upgradeToFullPluginSupport({ ) } - let negated = false - if (file.pattern[0] == '!') { - negated = true - file.pattern = file.pattern.slice(1) - } - sources.push({ ...file, negated }) + globs.push(file) } return features } diff --git a/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts b/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts index 12f195e94..619c99182 100644 --- a/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts +++ b/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts @@ -31,16 +31,6 @@ test('config values can be merged into the theme', () => { normal: '0 1px 3px black', }, - borderWidth: { - DEFAULT: '1.5px', - }, - outlineWidth: { - DEFAULT: '2.5px', - }, - ringWidth: { - DEFAULT: '3.5px', - }, - borderRadius: { sm: '0.33rem', }, @@ -62,13 +52,6 @@ test('config values can be merged into the theme', () => { lineHeight: '1.5', }, ], - lg: ['1.125rem', '2'], - xl: ['1.5rem', '3rem', 'invalid'], - '2xl': ['2rem'], - }, - - ringColor: { - DEFAULT: '#fff', }, letterSpacing: { @@ -91,17 +74,12 @@ test('config values can be merged into the theme', () => { }, transitionTimingFunction: { - DEFAULT: 'ease-in-out', fast: 'cubic-bezier(0, 0.55, 0.45, 1)', }, - transitionDuration: { - DEFAULT: '1234ms', - }, }, }, base: '/root', reference: false, - src: undefined, }, ]) applyConfigToTheme(design, resolvedConfig, replacedThemeKeys) @@ -124,42 +102,13 @@ test('config values can be merged into the theme', () => { '1rem', { '--line-height': '1.5' }, ]) - expect(theme.resolve('lg', ['--text'])).toEqual('1.125rem') - expect(theme.resolveWith('lg', ['--text'], ['--line-height'])).toEqual([ - '1.125rem', - { '--line-height': '2' }, - ]) - expect(theme.resolve('xl', ['--text'])).toEqual('1.5rem') - expect(theme.resolveWith('xl', ['--text'], ['--line-height'])).toEqual([ - '1.5rem', - { '--line-height': '3rem' }, - ]) - expect(theme.resolve('2xl', ['--text'])).toEqual('2rem') - expect(theme.resolveWith('2xl', ['--text'], ['--line-height'])).toEqual(['2rem', {}]) - expect(theme.resolve('superWide', ['--tracking'])).toEqual('0.25em') - expect(theme.resolve('superLoose', ['--leading'])).toEqual('3') + expect(theme.resolve('super-wide', ['--tracking'])).toEqual('0.25em') + expect(theme.resolve('super-loose', ['--leading'])).toEqual('3') expect(theme.resolve('1/2', ['--width'])).toEqual('60%') expect(theme.resolve('0.5', ['--width'])).toEqual('60%') expect(theme.resolve('100%', ['--width'])).toEqual('100%') expect(theme.resolve('9xs', ['--container'])).toEqual('6rem') expect(theme.resolve('fast', ['--ease'])).toEqual('cubic-bezier(0, 0.55, 0.45, 1)') - - expect(theme.get(['--border'])).toEqual(null) - expect(theme.get(['--default-border-width'])).toEqual('1.5px') - - expect(theme.get(['--outline'])).toEqual(null) - expect(theme.get(['--default-outline-width'])).toEqual('2.5px') - - expect(theme.get(['--ring-color'])).toEqual(null) - expect(theme.get(['--default-ring-color'])).toEqual('#fff') - - expect(theme.get(['--ring-width'])).toEqual(null) - expect(theme.get(['--default-ring-width'])).toEqual('3.5px') - - expect(theme.get(['--default-transition-duration'])).toEqual('1234ms') - - expect(theme.get(['--ease'])).toEqual(null) - expect(theme.get(['--default-transition-timing-function'])).toEqual('ease-in-out') }) test('will reset default theme values with overwriting theme values', () => { @@ -196,8 +145,6 @@ test('will reset default theme values with overwriting theme values', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) applyConfigToTheme(design, resolvedConfig, replacedThemeKeys) @@ -227,8 +174,6 @@ test('invalid keys are not merged into the theme', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) @@ -267,8 +212,6 @@ test('converts opacity modifiers from decimal to percentage values', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) applyConfigToTheme(design, resolvedConfig, replacedThemeKeys) @@ -303,7 +246,6 @@ test('handles setting theme keys to null', async () => { }, base: '/root', reference: false, - src: undefined, }, ]) applyConfigToTheme(design, resolvedConfig, replacedThemeKeys) diff --git a/packages/tailwindcss/src/compat/apply-config-to-theme.ts b/packages/tailwindcss/src/compat/apply-config-to-theme.ts index 8757520ee..d1fec30bc 100644 --- a/packages/tailwindcss/src/compat/apply-config-to-theme.ts +++ b/packages/tailwindcss/src/compat/apply-config-to-theme.ts @@ -130,16 +130,7 @@ export function themeableValues(config: ResolvedConfig['theme']): [string[], unk } if (Array.isArray(value) && value.every((v) => typeof v === 'string')) { - if (path[0] === 'fontSize') { - toAdd.push([path, value[0]]) - - if (value.length >= 2) { - toAdd.push([[...path, '-line-height'], value[1]]) - } - } else { - toAdd.push([path, value.join(', ')]) - } - + toAdd.push([path, value.join(', ')]) return WalkAction.Skip } }) @@ -147,57 +138,32 @@ export function themeableValues(config: ResolvedConfig['theme']): [string[], unk return toAdd } -const SPECIAL_DEFAULT_KEYS: Record = { - borderWidth: 'border-width', - outlineWidth: 'outline-width', - ringColor: 'ring-color', - ringWidth: 'ring-width', - transitionDuration: 'transition-duration', - transitionTimingFunction: 'transition-timing-function', -} - -const OLD_TO_NEW_NAMESPACE: Record = { - animation: 'animate', - aspectRatio: 'aspect', - borderRadius: 'radius', - boxShadow: 'shadow', - colors: 'color', - containers: 'container', - fontFamily: 'font', - fontSize: 'text', - letterSpacing: 'tracking', - lineHeight: 'leading', - maxWidth: 'container', - screens: 'breakpoint', - transitionTimingFunction: 'ease', -} - -const IS_VALID_KEY = /^[a-zA-Z0-9-_%/.]+$/ +const IS_VALID_KEY = /^[a-zA-Z0-9-_%/\.]+$/ export function keyPathToCssProperty(path: string[]) { - // In some special cases the `DEFAULT` key did not map to a "default" utility - // e.g. `ringColor.DEFAULT` wasn't *just* used for `ring`. It was used for - // all ring utilities as the color when one wasn't specified. - // - // We place these specialty values under the `--default-*` namespace to signal - // that they are defaults used by (potentially) multiple utilities. - let specialDefault = SPECIAL_DEFAULT_KEYS[path[0]] - if (specialDefault && path[1] === 'DEFAULT') return `default-${specialDefault}` - // The legacy container component config should not be included in the Theme if (path[0] === 'container') return null + path = structuredClone(path) + + if (path[0] === 'animation') path[0] = 'animate' + if (path[0] === 'aspectRatio') path[0] = 'aspect' + if (path[0] === 'borderRadius') path[0] = 'radius' + if (path[0] === 'boxShadow') path[0] = 'shadow' + if (path[0] === 'colors') path[0] = 'color' + if (path[0] === 'containers') path[0] = 'container' + if (path[0] === 'fontFamily') path[0] = 'font' + if (path[0] === 'fontSize') path[0] = 'text' + if (path[0] === 'letterSpacing') path[0] = 'tracking' + if (path[0] === 'lineHeight') path[0] = 'leading' + if (path[0] === 'maxWidth') path[0] = 'container' + if (path[0] === 'screens') path[0] = 'breakpoint' + if (path[0] === 'transitionTimingFunction') path[0] = 'ease' + for (let part of path) { if (!IS_VALID_KEY.test(part)) return null } - // Map old v3 namespaces to new theme namespaces - let ns = OLD_TO_NEW_NAMESPACE[path[0]] - if (ns) { - path = path.slice() - path[0] = ns - } - return ( path // [1] should move into the nested object tuple. To create the CSS variable @@ -210,27 +176,11 @@ export function keyPathToCssProperty(path: string[]) { .map((path, idx, all) => (path === '1' && idx !== all.length - 1 ? '' : path)) // Resolve the key path to a CSS variable segment - .map((part, idx) => { - part = part.replaceAll('.', '_') - - let shouldConvert = - // The first "namespace" part should be converted to kebab-case - // This converts things like backgroundColor to `background-color` - idx === 0 || - // Any tuple nested key should be converted to kebab-case - // These are identified with a leading `-` - // e.g. `fontSize.xs.1.lineHeight` -> `font-size-xs--line-height` - part.startsWith('-') || - // `lineHeight` is a bit of a special case in which it does not - // always begin with a leading `-` even when as a nested tuple key - part === 'lineHeight' - - if (shouldConvert) { - part = part.replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}-${b.toLowerCase()}`) - } - - return part - }) + .map((part) => + part + .replaceAll('.', '_') + .replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}-${b.toLowerCase()}`), + ) // Remove the `DEFAULT` key at the end of a path // We're reading from CSS anyway so it'll be a string diff --git a/packages/tailwindcss/src/compat/apply-keyframes-to-theme.test.ts b/packages/tailwindcss/src/compat/apply-keyframes-to-theme.test.ts index aed761fce..a0b1c54d5 100644 --- a/packages/tailwindcss/src/compat/apply-keyframes-to-theme.test.ts +++ b/packages/tailwindcss/src/compat/apply-keyframes-to-theme.test.ts @@ -9,7 +9,7 @@ test('keyframes can be merged into the theme', () => { let theme = new Theme() let design = buildDesignSystem(theme) - let { resolvedConfig } = resolveConfig(design, [ + let { resolvedConfig, replacedThemeKeys } = resolveConfig(design, [ { config: { theme: { @@ -28,11 +28,9 @@ test('keyframes can be merged into the theme', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) - applyKeyframesToTheme(design, resolvedConfig) + applyKeyframesToTheme(design, resolvedConfig, replacedThemeKeys) expect(toCss(design.theme.getKeyframes())).toMatchInlineSnapshot(` "@keyframes fade-in { @@ -72,7 +70,7 @@ test('will append to the default keyframes with new keyframes', () => { ]), ) - let { resolvedConfig } = resolveConfig(design, [ + let { resolvedConfig, replacedThemeKeys } = resolveConfig(design, [ { config: { theme: { @@ -93,11 +91,9 @@ test('will append to the default keyframes with new keyframes', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) - applyKeyframesToTheme(design, resolvedConfig) + applyKeyframesToTheme(design, resolvedConfig, replacedThemeKeys) expect(toCss(design.theme.getKeyframes())).toMatchInlineSnapshot(` "@keyframes slide-in { diff --git a/packages/tailwindcss/src/compat/apply-keyframes-to-theme.ts b/packages/tailwindcss/src/compat/apply-keyframes-to-theme.ts index d5855545f..a58ca13df 100644 --- a/packages/tailwindcss/src/compat/apply-keyframes-to-theme.ts +++ b/packages/tailwindcss/src/compat/apply-keyframes-to-theme.ts @@ -6,6 +6,7 @@ import { objectToAst } from './plugin-api' export function applyKeyframesToTheme( designSystem: DesignSystem, resolvedConfig: Pick, + replacedThemeKeys: Set, ) { for (let rule of keyframesToRules(resolvedConfig)) { designSystem.theme.addKeyframes(rule) diff --git a/packages/tailwindcss/src/compat/colors.ts b/packages/tailwindcss/src/compat/colors.ts index 64e3bc9c7..0a6cba403 100644 --- a/packages/tailwindcss/src/compat/colors.ts +++ b/packages/tailwindcss/src/compat/colors.ts @@ -1,345 +1,293 @@ export default { inherit: 'inherit', - current: 'currentcolor', + current: 'currentColor', transparent: 'transparent', black: '#000', white: '#fff', slate: { - '50': 'oklch(98.4% 0.003 247.858)', - '100': 'oklch(96.8% 0.007 247.896)', - '200': 'oklch(92.9% 0.013 255.508)', - '300': 'oklch(86.9% 0.022 252.894)', - '400': 'oklch(70.4% 0.04 256.788)', - '500': 'oklch(55.4% 0.046 257.417)', - '600': 'oklch(44.6% 0.043 257.281)', - '700': 'oklch(37.2% 0.044 257.287)', - '800': 'oklch(27.9% 0.041 260.031)', - '900': 'oklch(20.8% 0.042 265.755)', - '950': 'oklch(12.9% 0.042 264.695)', + '50': 'oklch(0.984 0.003 247.858)', + '100': 'oklch(0.968 0.007 247.896)', + '200': 'oklch(0.929 0.013 255.508)', + '300': 'oklch(0.869 0.022 252.894)', + '400': 'oklch(0.704 0.04 256.788)', + '500': 'oklch(0.554 0.046 257.417)', + '600': 'oklch(0.446 0.043 257.281)', + '700': 'oklch(0.372 0.044 257.287)', + '800': 'oklch(0.279 0.041 260.031)', + '900': 'oklch(0.208 0.042 265.755)', + '950': 'oklch(0.129 0.042 264.695)', }, gray: { - '50': 'oklch(98.5% 0.002 247.839)', - '100': 'oklch(96.7% 0.003 264.542)', - '200': 'oklch(92.8% 0.006 264.531)', - '300': 'oklch(87.2% 0.01 258.338)', - '400': 'oklch(70.7% 0.022 261.325)', - '500': 'oklch(55.1% 0.027 264.364)', - '600': 'oklch(44.6% 0.03 256.802)', - '700': 'oklch(37.3% 0.034 259.733)', - '800': 'oklch(27.8% 0.033 256.848)', - '900': 'oklch(21% 0.034 264.665)', - '950': 'oklch(13% 0.028 261.692)', + '50': 'oklch(0.985 0.002 247.839)', + '100': 'oklch(0.967 0.003 264.542)', + '200': 'oklch(0.928 0.006 264.531)', + '300': 'oklch(0.872 0.01 258.338)', + '400': 'oklch(0.707 0.022 261.325)', + '500': 'oklch(0.551 0.027 264.364)', + '600': 'oklch(0.446 0.03 256.802)', + '700': 'oklch(0.373 0.034 259.733)', + '800': 'oklch(0.278 0.033 256.848)', + '900': 'oklch(0.21 0.034 264.665)', + '950': 'oklch(0.13 0.028 261.692)', }, zinc: { - '50': 'oklch(98.5% 0 none)', - '100': 'oklch(96.7% 0.001 286.375)', - '200': 'oklch(92% 0.004 286.32)', - '300': 'oklch(87.1% 0.006 286.286)', - '400': 'oklch(70.5% 0.015 286.067)', - '500': 'oklch(55.2% 0.016 285.938)', - '600': 'oklch(44.2% 0.017 285.786)', - '700': 'oklch(37% 0.013 285.805)', - '800': 'oklch(27.4% 0.006 286.033)', - '900': 'oklch(21% 0.006 285.885)', - '950': 'oklch(14.1% 0.005 285.823)', + '50': 'oklch(0.985 0 0)', + '100': 'oklch(0.967 0.001 286.375)', + '200': 'oklch(0.92 0.004 286.32)', + '300': 'oklch(0.871 0.006 286.286)', + '400': 'oklch(0.705 0.015 286.067)', + '500': 'oklch(0.552 0.016 285.938)', + '600': 'oklch(0.442 0.017 285.786)', + '700': 'oklch(0.37 0.013 285.805)', + '800': 'oklch(0.274 0.006 286.033)', + '900': 'oklch(0.21 0.006 285.885)', + '950': 'oklch(0.141 0.005 285.823)', }, neutral: { - '50': 'oklch(98.5% 0 none)', - '100': 'oklch(97% 0 none)', - '200': 'oklch(92.2% 0 none)', - '300': 'oklch(87% 0 none)', - '400': 'oklch(70.8% 0 none)', - '500': 'oklch(55.6% 0 none)', - '600': 'oklch(43.9% 0 none)', - '700': 'oklch(37.1% 0 none)', - '800': 'oklch(26.9% 0 none)', - '900': 'oklch(20.5% 0 none)', - '950': 'oklch(14.5% 0 none)', + '50': 'oklch(0.985 0 0)', + '100': 'oklch(0.97 0 0)', + '200': 'oklch(0.922 0 0)', + '300': 'oklch(0.87 0 0)', + '400': 'oklch(0.708 0 0)', + '500': 'oklch(0.556 0 0)', + '600': 'oklch(0.439 0 0)', + '700': 'oklch(0.371 0 0)', + '800': 'oklch(0.269 0 0)', + '900': 'oklch(0.205 0 0)', + '950': 'oklch(0.145 0 0)', }, stone: { - '50': 'oklch(98.5% 0.001 106.423)', - '100': 'oklch(97% 0.001 106.424)', - '200': 'oklch(92.3% 0.003 48.717)', - '300': 'oklch(86.9% 0.005 56.366)', - '400': 'oklch(70.9% 0.01 56.259)', - '500': 'oklch(55.3% 0.013 58.071)', - '600': 'oklch(44.4% 0.011 73.639)', - '700': 'oklch(37.4% 0.01 67.558)', - '800': 'oklch(26.8% 0.007 34.298)', - '900': 'oklch(21.6% 0.006 56.043)', - '950': 'oklch(14.7% 0.004 49.25)', - }, - mauve: { - '50': 'oklch(98.5% 0 none)', - '100': 'oklch(96% 0.003 325.6)', - '200': 'oklch(92.2% 0.005 325.62)', - '300': 'oklch(86.5% 0.012 325.68)', - '400': 'oklch(71.1% 0.019 323.02)', - '500': 'oklch(54.2% 0.034 322.5)', - '600': 'oklch(43.5% 0.029 321.78)', - '700': 'oklch(36.4% 0.029 323.89)', - '800': 'oklch(26.3% 0.024 320.12)', - '900': 'oklch(21.2% 0.019 322.12)', - '950': 'oklch(14.5% 0.008 326)', - }, - olive: { - '50': 'oklch(98.8% 0.003 106.5)', - '100': 'oklch(96.6% 0.005 106.5)', - '200': 'oklch(93% 0.007 106.5)', - '300': 'oklch(88% 0.011 106.6)', - '400': 'oklch(73.7% 0.021 106.9)', - '500': 'oklch(58% 0.031 107.3)', - '600': 'oklch(46.6% 0.025 107.3)', - '700': 'oklch(39.4% 0.023 107.4)', - '800': 'oklch(28.6% 0.016 107.4)', - '900': 'oklch(22.8% 0.013 107.4)', - '950': 'oklch(15.3% 0.006 107.1)', - }, - mist: { - '50': 'oklch(98.7% 0.002 197.1)', - '100': 'oklch(96.3% 0.002 197.1)', - '200': 'oklch(92.5% 0.005 214.3)', - '300': 'oklch(87.2% 0.007 219.6)', - '400': 'oklch(72.3% 0.014 214.4)', - '500': 'oklch(56% 0.021 213.5)', - '600': 'oklch(45% 0.017 213.2)', - '700': 'oklch(37.8% 0.015 216)', - '800': 'oklch(27.5% 0.011 216.9)', - '900': 'oklch(21.8% 0.008 223.9)', - '950': 'oklch(14.8% 0.004 228.8)', - }, - taupe: { - '50': 'oklch(98.6% 0.002 67.8)', - '100': 'oklch(96% 0.002 17.2)', - '200': 'oklch(92.2% 0.005 34.3)', - '300': 'oklch(86.8% 0.007 39.5)', - '400': 'oklch(71.4% 0.014 41.2)', - '500': 'oklch(54.7% 0.021 43.1)', - '600': 'oklch(43.8% 0.017 39.3)', - '700': 'oklch(36.7% 0.016 35.7)', - '800': 'oklch(26.8% 0.011 36.5)', - '900': 'oklch(21.4% 0.009 43.1)', - '950': 'oklch(14.7% 0.004 49.3)', + '50': 'oklch(0.985 0.001 106.423)', + '100': 'oklch(0.97 0.001 106.424)', + '200': 'oklch(0.923 0.003 48.717)', + '300': 'oklch(0.869 0.005 56.366)', + '400': 'oklch(0.709 0.01 56.259)', + '500': 'oklch(0.553 0.013 58.071)', + '600': 'oklch(0.444 0.011 73.639)', + '700': 'oklch(0.374 0.01 67.558)', + '800': 'oklch(0.268 0.007 34.298)', + '900': 'oklch(0.216 0.006 56.043)', + '950': 'oklch(0.147 0.004 49.25)', }, red: { - '50': 'oklch(97.1% 0.013 17.38)', - '100': 'oklch(93.6% 0.032 17.717)', - '200': 'oklch(88.5% 0.062 18.334)', - '300': 'oklch(80.8% 0.114 19.571)', - '400': 'oklch(70.4% 0.191 22.216)', - '500': 'oklch(63.7% 0.237 25.331)', - '600': 'oklch(57.7% 0.245 27.325)', - '700': 'oklch(50.5% 0.213 27.518)', - '800': 'oklch(44.4% 0.177 26.899)', - '900': 'oklch(39.6% 0.141 25.723)', - '950': 'oklch(25.8% 0.092 26.042)', + '50': 'oklch(0.971 0.013 17.38)', + '100': 'oklch(0.936 0.032 17.717)', + '200': 'oklch(0.885 0.062 18.334)', + '300': 'oklch(0.808 0.114 19.571)', + '400': 'oklch(0.704 0.191 22.216)', + '500': 'oklch(0.637 0.237 25.331)', + '600': 'oklch(0.577 0.245 27.325)', + '700': 'oklch(0.505 0.213 27.518)', + '800': 'oklch(0.444 0.177 26.899)', + '900': 'oklch(0.396 0.141 25.723)', + '950': 'oklch(0.258 0.092 26.042)', }, orange: { - '50': 'oklch(98% 0.016 73.684)', - '100': 'oklch(95.4% 0.038 75.164)', - '200': 'oklch(90.1% 0.076 70.697)', - '300': 'oklch(83.7% 0.128 66.29)', - '400': 'oklch(75% 0.183 55.934)', - '500': 'oklch(70.5% 0.213 47.604)', - '600': 'oklch(64.6% 0.222 41.116)', - '700': 'oklch(55.3% 0.195 38.402)', - '800': 'oklch(47% 0.157 37.304)', - '900': 'oklch(40.8% 0.123 38.172)', - '950': 'oklch(26.6% 0.079 36.259)', + '50': 'oklch(0.98 0.016 73.684)', + '100': 'oklch(0.954 0.038 75.164)', + '200': 'oklch(0.901 0.076 70.697)', + '300': 'oklch(0.837 0.128 66.29)', + '400': 'oklch(0.75 0.183 55.934)', + '500': 'oklch(0.705 0.213 47.604)', + '600': 'oklch(0.646 0.222 41.116)', + '700': 'oklch(0.553 0.195 38.402)', + '800': 'oklch(0.47 0.157 37.304)', + '900': 'oklch(0.408 0.123 38.172)', + '950': 'oklch(0.266 0.079 36.259)', }, amber: { - '50': 'oklch(98.7% 0.022 95.277)', - '100': 'oklch(96.2% 0.059 95.617)', - '200': 'oklch(92.4% 0.12 95.746)', - '300': 'oklch(87.9% 0.169 91.605)', - '400': 'oklch(82.8% 0.189 84.429)', - '500': 'oklch(76.9% 0.188 70.08)', - '600': 'oklch(66.6% 0.179 58.318)', - '700': 'oklch(55.5% 0.163 48.998)', - '800': 'oklch(47.3% 0.137 46.201)', - '900': 'oklch(41.4% 0.112 45.904)', - '950': 'oklch(27.9% 0.077 45.635)', + '50': 'oklch(0.987 0.022 95.277)', + '100': 'oklch(0.962 0.059 95.617)', + '200': 'oklch(0.924 0.12 95.746)', + '300': 'oklch(0.879 0.169 91.605)', + '400': 'oklch(0.828 0.189 84.429)', + '500': 'oklch(0.769 0.188 70.08)', + '600': 'oklch(0.666 0.179 58.318)', + '700': 'oklch(0.555 0.163 48.998)', + '800': 'oklch(0.473 0.137 46.201)', + '900': 'oklch(0.414 0.112 45.904)', + '950': 'oklch(0.279 0.077 45.635)', }, yellow: { - '50': 'oklch(98.7% 0.026 102.212)', - '100': 'oklch(97.3% 0.071 103.193)', - '200': 'oklch(94.5% 0.129 101.54)', - '300': 'oklch(90.5% 0.182 98.111)', - '400': 'oklch(85.2% 0.199 91.936)', - '500': 'oklch(79.5% 0.184 86.047)', - '600': 'oklch(68.1% 0.162 75.834)', - '700': 'oklch(55.4% 0.135 66.442)', - '800': 'oklch(47.6% 0.114 61.907)', - '900': 'oklch(42.1% 0.095 57.708)', - '950': 'oklch(28.6% 0.066 53.813)', + '50': 'oklch(0.987 0.026 102.212)', + '100': 'oklch(0.973 0.071 103.193)', + '200': 'oklch(0.945 0.129 101.54)', + '300': 'oklch(0.905 0.182 98.111)', + '400': 'oklch(0.852 0.199 91.936)', + '500': 'oklch(0.795 0.184 86.047)', + '600': 'oklch(0.681 0.162 75.834)', + '700': 'oklch(0.554 0.135 66.442)', + '800': 'oklch(0.476 0.114 61.907)', + '900': 'oklch(0.421 0.095 57.708)', + '950': 'oklch(0.286 0.066 53.813)', }, lime: { - '50': 'oklch(98.6% 0.031 120.757)', - '100': 'oklch(96.7% 0.067 122.328)', - '200': 'oklch(93.8% 0.127 124.321)', - '300': 'oklch(89.7% 0.196 126.665)', - '400': 'oklch(84.1% 0.238 128.85)', - '500': 'oklch(76.8% 0.233 130.85)', - '600': 'oklch(64.8% 0.2 131.684)', - '700': 'oklch(53.2% 0.157 131.589)', - '800': 'oklch(45.3% 0.124 130.933)', - '900': 'oklch(40.5% 0.101 131.063)', - '950': 'oklch(27.4% 0.072 132.109)', + '50': 'oklch(0.986 0.031 120.757)', + '100': 'oklch(0.967 0.067 122.328)', + '200': 'oklch(0.938 0.127 124.321)', + '300': 'oklch(0.897 0.196 126.665)', + '400': 'oklch(0.841 0.238 128.85)', + '500': 'oklch(0.768 0.233 130.85)', + '600': 'oklch(0.648 0.2 131.684)', + '700': 'oklch(0.532 0.157 131.589)', + '800': 'oklch(0.453 0.124 130.933)', + '900': 'oklch(0.405 0.101 131.063)', + '950': 'oklch(0.274 0.072 132.109)', }, green: { - '50': 'oklch(98.2% 0.018 155.826)', - '100': 'oklch(96.2% 0.044 156.743)', - '200': 'oklch(92.5% 0.084 155.995)', - '300': 'oklch(87.1% 0.15 154.449)', - '400': 'oklch(79.2% 0.209 151.711)', - '500': 'oklch(72.3% 0.219 149.579)', - '600': 'oklch(62.7% 0.194 149.214)', - '700': 'oklch(52.7% 0.154 150.069)', - '800': 'oklch(44.8% 0.119 151.328)', - '900': 'oklch(39.3% 0.095 152.535)', - '950': 'oklch(26.6% 0.065 152.934)', + '50': 'oklch(0.982 0.018 155.826)', + '100': 'oklch(0.962 0.044 156.743)', + '200': 'oklch(0.925 0.084 155.995)', + '300': 'oklch(0.871 0.15 154.449)', + '400': 'oklch(0.792 0.209 151.711)', + '500': 'oklch(0.723 0.219 149.579)', + '600': 'oklch(0.627 0.194 149.214)', + '700': 'oklch(0.527 0.154 150.069)', + '800': 'oklch(0.448 0.119 151.328)', + '900': 'oklch(0.393 0.095 152.535)', + '950': 'oklch(0.266 0.065 152.934)', }, emerald: { - '50': 'oklch(97.9% 0.021 166.113)', - '100': 'oklch(95% 0.052 163.051)', - '200': 'oklch(90.5% 0.093 164.15)', - '300': 'oklch(84.5% 0.143 164.978)', - '400': 'oklch(76.5% 0.177 163.223)', - '500': 'oklch(69.6% 0.17 162.48)', - '600': 'oklch(59.6% 0.145 163.225)', - '700': 'oklch(50.8% 0.118 165.612)', - '800': 'oklch(43.2% 0.095 166.913)', - '900': 'oklch(37.8% 0.077 168.94)', - '950': 'oklch(26.2% 0.051 172.552)', + '50': 'oklch(0.979 0.021 166.113)', + '100': 'oklch(0.95 0.052 163.051)', + '200': 'oklch(0.905 0.093 164.15)', + '300': 'oklch(0.845 0.143 164.978)', + '400': 'oklch(0.765 0.177 163.223)', + '500': 'oklch(0.696 0.17 162.48)', + '600': 'oklch(0.596 0.145 163.225)', + '700': 'oklch(0.508 0.118 165.612)', + '800': 'oklch(0.432 0.095 166.913)', + '900': 'oklch(0.378 0.077 168.94)', + '950': 'oklch(0.262 0.051 172.552)', }, teal: { - '50': 'oklch(98.4% 0.014 180.72)', - '100': 'oklch(95.3% 0.051 180.801)', - '200': 'oklch(91% 0.096 180.426)', - '300': 'oklch(85.5% 0.138 181.071)', - '400': 'oklch(77.7% 0.152 181.912)', - '500': 'oklch(70.4% 0.14 182.503)', - '600': 'oklch(60% 0.118 184.704)', - '700': 'oklch(51.1% 0.096 186.391)', - '800': 'oklch(43.7% 0.078 188.216)', - '900': 'oklch(38.6% 0.063 188.416)', - '950': 'oklch(27.7% 0.046 192.524)', + '50': 'oklch(0.984 0.014 180.72)', + '100': 'oklch(0.953 0.051 180.801)', + '200': 'oklch(0.91 0.096 180.426)', + '300': 'oklch(0.855 0.138 181.071)', + '400': 'oklch(0.777 0.152 181.912)', + '500': 'oklch(0.704 0.14 182.503)', + '600': 'oklch(0.6 0.118 184.704)', + '700': 'oklch(0.511 0.096 186.391)', + '800': 'oklch(0.437 0.078 188.216)', + '900': 'oklch(0.386 0.063 188.416)', + '950': 'oklch(0.277 0.046 192.524)', }, cyan: { - '50': 'oklch(98.4% 0.019 200.873)', - '100': 'oklch(95.6% 0.045 203.388)', - '200': 'oklch(91.7% 0.08 205.041)', - '300': 'oklch(86.5% 0.127 207.078)', - '400': 'oklch(78.9% 0.154 211.53)', - '500': 'oklch(71.5% 0.143 215.221)', - '600': 'oklch(60.9% 0.126 221.723)', - '700': 'oklch(52% 0.105 223.128)', - '800': 'oklch(45% 0.085 224.283)', - '900': 'oklch(39.8% 0.07 227.392)', - '950': 'oklch(30.2% 0.056 229.695)', + '50': 'oklch(0.984 0.019 200.873)', + '100': 'oklch(0.956 0.045 203.388)', + '200': 'oklch(0.917 0.08 205.041)', + '300': 'oklch(0.865 0.127 207.078)', + '400': 'oklch(0.789 0.154 211.53)', + '500': 'oklch(0.715 0.143 215.221)', + '600': 'oklch(0.609 0.126 221.723)', + '700': 'oklch(0.52 0.105 223.128)', + '800': 'oklch(0.45 0.085 224.283)', + '900': 'oklch(0.398 0.07 227.392)', + '950': 'oklch(0.302 0.056 229.695)', }, sky: { - '50': 'oklch(97.7% 0.013 236.62)', - '100': 'oklch(95.1% 0.026 236.824)', - '200': 'oklch(90.1% 0.058 230.902)', - '300': 'oklch(82.8% 0.111 230.318)', - '400': 'oklch(74.6% 0.16 232.661)', - '500': 'oklch(68.5% 0.169 237.323)', - '600': 'oklch(58.8% 0.158 241.966)', - '700': 'oklch(50% 0.134 242.749)', - '800': 'oklch(44.3% 0.11 240.79)', - '900': 'oklch(39.1% 0.09 240.876)', - '950': 'oklch(29.3% 0.066 243.157)', + '50': 'oklch(0.977 0.013 236.62)', + '100': 'oklch(0.951 0.026 236.824)', + '200': 'oklch(0.901 0.058 230.902)', + '300': 'oklch(0.828 0.111 230.318)', + '400': 'oklch(0.746 0.16 232.661)', + '500': 'oklch(0.685 0.169 237.323)', + '600': 'oklch(0.588 0.158 241.966)', + '700': 'oklch(0.5 0.134 242.749)', + '800': 'oklch(0.443 0.11 240.79)', + '900': 'oklch(0.391 0.09 240.876)', + '950': 'oklch(0.293 0.066 243.157)', }, blue: { - '50': 'oklch(97% 0.014 254.604)', - '100': 'oklch(93.2% 0.032 255.585)', - '200': 'oklch(88.2% 0.059 254.128)', - '300': 'oklch(80.9% 0.105 251.813)', - '400': 'oklch(70.7% 0.165 254.624)', - '500': 'oklch(62.3% 0.214 259.815)', - '600': 'oklch(54.6% 0.245 262.881)', - '700': 'oklch(48.8% 0.243 264.376)', - '800': 'oklch(42.4% 0.199 265.638)', - '900': 'oklch(37.9% 0.146 265.522)', - '950': 'oklch(28.2% 0.091 267.935)', + '50': 'oklch(0.97 0.014 254.604)', + '100': 'oklch(0.932 0.032 255.585)', + '200': 'oklch(0.882 0.059 254.128)', + '300': 'oklch(0.809 0.105 251.813)', + '400': 'oklch(0.707 0.165 254.624)', + '500': 'oklch(0.623 0.214 259.815)', + '600': 'oklch(0.546 0.245 262.881)', + '700': 'oklch(0.488 0.243 264.376)', + '800': 'oklch(0.424 0.199 265.638)', + '900': 'oklch(0.379 0.146 265.522)', + '950': 'oklch(0.282 0.091 267.935)', }, indigo: { - '50': 'oklch(96.2% 0.018 272.314)', - '100': 'oklch(93% 0.034 272.788)', - '200': 'oklch(87% 0.065 274.039)', - '300': 'oklch(78.5% 0.115 274.713)', - '400': 'oklch(67.3% 0.182 276.935)', - '500': 'oklch(58.5% 0.233 277.117)', - '600': 'oklch(51.1% 0.262 276.966)', - '700': 'oklch(45.7% 0.24 277.023)', - '800': 'oklch(39.8% 0.195 277.366)', - '900': 'oklch(35.9% 0.144 278.697)', - '950': 'oklch(25.7% 0.09 281.288)', + '50': 'oklch(0.962 0.018 272.314)', + '100': 'oklch(0.93 0.034 272.788)', + '200': 'oklch(0.87 0.065 274.039)', + '300': 'oklch(0.785 0.115 274.713)', + '400': 'oklch(0.673 0.182 276.935)', + '500': 'oklch(0.585 0.233 277.117)', + '600': 'oklch(0.511 0.262 276.966)', + '700': 'oklch(0.457 0.24 277.023)', + '800': 'oklch(0.398 0.195 277.366)', + '900': 'oklch(0.359 0.144 278.697)', + '950': 'oklch(0.257 0.09 281.288)', }, violet: { - '50': 'oklch(96.9% 0.016 293.756)', - '100': 'oklch(94.3% 0.029 294.588)', - '200': 'oklch(89.4% 0.057 293.283)', - '300': 'oklch(81.1% 0.111 293.571)', - '400': 'oklch(70.2% 0.183 293.541)', - '500': 'oklch(60.6% 0.25 292.717)', - '600': 'oklch(54.1% 0.281 293.009)', - '700': 'oklch(49.1% 0.27 292.581)', - '800': 'oklch(43.2% 0.232 292.759)', - '900': 'oklch(38% 0.189 293.745)', - '950': 'oklch(28.3% 0.141 291.089)', + '50': 'oklch(0.969 0.016 293.756)', + '100': 'oklch(0.943 0.029 294.588)', + '200': 'oklch(0.894 0.057 293.283)', + '300': 'oklch(0.811 0.111 293.571)', + '400': 'oklch(0.702 0.183 293.541)', + '500': 'oklch(0.606 0.25 292.717)', + '600': 'oklch(0.541 0.281 293.009)', + '700': 'oklch(0.491 0.27 292.581)', + '800': 'oklch(0.432 0.232 292.759)', + '900': 'oklch(0.38 0.189 293.745)', + '950': 'oklch(0.283 0.141 291.089)', }, purple: { - '50': 'oklch(97.7% 0.014 308.299)', - '100': 'oklch(94.6% 0.033 307.174)', - '200': 'oklch(90.2% 0.063 306.703)', - '300': 'oklch(82.7% 0.119 306.383)', - '400': 'oklch(71.4% 0.203 305.504)', - '500': 'oklch(62.7% 0.265 303.9)', - '600': 'oklch(55.8% 0.288 302.321)', - '700': 'oklch(49.6% 0.265 301.924)', - '800': 'oklch(43.8% 0.218 303.724)', - '900': 'oklch(38.1% 0.176 304.987)', - '950': 'oklch(29.1% 0.149 302.717)', + '50': 'oklch(0.977 0.014 308.299)', + '100': 'oklch(0.946 0.033 307.174)', + '200': 'oklch(0.902 0.063 306.703)', + '300': 'oklch(0.827 0.119 306.383)', + '400': 'oklch(0.714 0.203 305.504)', + '500': 'oklch(0.627 0.265 303.9)', + '600': 'oklch(0.558 0.288 302.321)', + '700': 'oklch(0.496 0.265 301.924)', + '800': 'oklch(0.438 0.218 303.724)', + '900': 'oklch(0.381 0.176 304.987)', + '950': 'oklch(0.291 0.149 302.717)', }, fuchsia: { - '50': 'oklch(97.7% 0.017 320.058)', - '100': 'oklch(95.2% 0.037 318.852)', - '200': 'oklch(90.3% 0.076 319.62)', - '300': 'oklch(83.3% 0.145 321.434)', - '400': 'oklch(74% 0.238 322.16)', - '500': 'oklch(66.7% 0.295 322.15)', - '600': 'oklch(59.1% 0.293 322.896)', - '700': 'oklch(51.8% 0.253 323.949)', - '800': 'oklch(45.2% 0.211 324.591)', - '900': 'oklch(40.1% 0.17 325.612)', - '950': 'oklch(29.3% 0.136 325.661)', + '50': 'oklch(0.977 0.017 320.058)', + '100': 'oklch(0.952 0.037 318.852)', + '200': 'oklch(0.903 0.076 319.62)', + '300': 'oklch(0.833 0.145 321.434)', + '400': 'oklch(0.74 0.238 322.16)', + '500': 'oklch(0.667 0.295 322.15)', + '600': 'oklch(0.591 0.293 322.896)', + '700': 'oklch(0.518 0.253 323.949)', + '800': 'oklch(0.452 0.211 324.591)', + '900': 'oklch(0.401 0.17 325.612)', + '950': 'oklch(0.293 0.136 325.661)', }, pink: { - '50': 'oklch(97.1% 0.014 343.198)', - '100': 'oklch(94.8% 0.028 342.258)', - '200': 'oklch(89.9% 0.061 343.231)', - '300': 'oklch(82.3% 0.12 346.018)', - '400': 'oklch(71.8% 0.202 349.761)', - '500': 'oklch(65.6% 0.241 354.308)', - '600': 'oklch(59.2% 0.249 0.584)', - '700': 'oklch(52.5% 0.223 3.958)', - '800': 'oklch(45.9% 0.187 3.815)', - '900': 'oklch(40.8% 0.153 2.432)', - '950': 'oklch(28.4% 0.109 3.907)', + '50': 'oklch(0.971 0.014 343.198)', + '100': 'oklch(0.948 0.028 342.258)', + '200': 'oklch(0.899 0.061 343.231)', + '300': 'oklch(0.823 0.12 346.018)', + '400': 'oklch(0.718 0.202 349.761)', + '500': 'oklch(0.656 0.241 354.308)', + '600': 'oklch(0.592 0.249 0.584)', + '700': 'oklch(0.525 0.223 3.958)', + '800': 'oklch(0.459 0.187 3.815)', + '900': 'oklch(0.408 0.153 2.432)', + '950': 'oklch(0.284 0.109 3.907)', }, rose: { - '50': 'oklch(96.9% 0.015 12.422)', - '100': 'oklch(94.1% 0.03 12.58)', - '200': 'oklch(89.2% 0.058 10.001)', - '300': 'oklch(81% 0.117 11.638)', - '400': 'oklch(71.2% 0.194 13.428)', - '500': 'oklch(64.5% 0.246 16.439)', - '600': 'oklch(58.6% 0.253 17.585)', - '700': 'oklch(51.4% 0.222 16.935)', - '800': 'oklch(45.5% 0.188 13.697)', - '900': 'oklch(41% 0.159 10.272)', - '950': 'oklch(27.1% 0.105 12.094)', + '50': 'oklch(0.969 0.015 12.422)', + '100': 'oklch(0.941 0.03 12.58)', + '200': 'oklch(0.892 0.058 10.001)', + '300': 'oklch(0.81 0.117 11.638)', + '400': 'oklch(0.712 0.194 13.428)', + '500': 'oklch(0.645 0.246 16.439)', + '600': 'oklch(0.586 0.253 17.585)', + '700': 'oklch(0.514 0.222 16.935)', + '800': 'oklch(0.455 0.188 13.697)', + '900': 'oklch(0.41 0.159 10.272)', + '950': 'oklch(0.271 0.105 12.094)', }, } diff --git a/packages/tailwindcss/src/compat/config.test.ts b/packages/tailwindcss/src/compat/config.test.ts index 1f69ae446..f856a557d 100644 --- a/packages/tailwindcss/src/compat/config.test.ts +++ b/packages/tailwindcss/src/compat/config.test.ts @@ -1,7 +1,6 @@ import { describe, expect, test, vi } from 'vitest' import { compile, type Config } from '..' import { default as plugin } from '../plugin' -import { compileCss, run } from '../test-utils/run' import flattenColorPalette from './flatten-color-palette' const css = String.raw @@ -13,28 +12,25 @@ test('Config files can add content', async () => { ` let compiler = await compile(input, { - loadModule: async () => ({ module: { content: ['./file.txt'] }, base: '/root', path: '' }), + loadModule: async () => ({ module: { content: ['./file.txt'] }, base: '/root' }), }) - expect(compiler.sources).toEqual([{ base: '/root', pattern: './file.txt', negated: false }]) + expect(compiler.globs).toEqual([{ base: '/root', pattern: './file.txt' }]) }) test('Config files can change dark mode (media)', async () => { - expect( - await run( - ['dark:underline'], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ module: { darkMode: 'media' }, base: '/root', path: '' }), - }, - ), - ).toMatchInlineSnapshot(` - " - @media (prefers-color-scheme: dark) { - .dark\\:underline { + let input = css` + @tailwind utilities; + @config "./config.js"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ module: { darkMode: 'media' }, base: '/root' }), + }) + + expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(` + ".dark\\:underline { + @media (prefers-color-scheme: dark) { text-decoration-line: underline; } } @@ -43,80 +39,73 @@ test('Config files can change dark mode (media)', async () => { }) test('Config files can change dark mode (selector)', async () => { - expect( - await run( - ['dark:underline'], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ module: { darkMode: 'selector' }, base: '/root', path: '' }), - }, - ), - ).toMatchInlineSnapshot(` - " - .dark\\:underline:where(.dark, .dark *) { - text-decoration-line: underline; + let input = css` + @tailwind utilities; + @config "./config.js"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ module: { darkMode: 'selector' }, base: '/root' }), + }) + + expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(` + ".dark\\:underline { + &:where(.dark, .dark *) { + text-decoration-line: underline; + } } " `) }) test('Config files can change dark mode (variant)', async () => { - expect( - await run( - ['dark:underline'], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { darkMode: ['variant', '&:where(:not(.light))'] }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - .dark\\:underline:where(:not(.light)) { - text-decoration-line: underline; + let input = css` + @tailwind utilities; + @config "./config.js"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { darkMode: ['variant', '&:where(:not(.light))'] }, + base: '/root', + }), + }) + + expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(` + ".dark\\:underline { + &:where(:not(.light)) { + text-decoration-line: underline; + } } " `) }) test('Config files can add plugins', async () => { - expect( - await run( - ['no-scrollbar'], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { - plugins: [ - plugin(function ({ addUtilities }) { - addUtilities({ - '.no-scrollbar': { - 'scrollbar-width': 'none', - }, - }) - }), - ], - }, - base: '/root', - path: '', - }), + let input = css` + @tailwind utilities; + @config "./config.js"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + plugins: [ + plugin(function ({ addUtilities }) { + addUtilities({ + '.no-scrollbar': { + 'scrollbar-width': 'none', + }, + }) + }), + ], }, - ), - ).toMatchInlineSnapshot(` - " - .no-scrollbar { + base: '/root', + }), + }) + + expect(compiler.build(['no-scrollbar'])).toMatchInlineSnapshot(` + ".no-scrollbar { scrollbar-width: none; } " @@ -124,181 +113,137 @@ test('Config files can add plugins', async () => { }) test('Plugins loaded from config files can contribute to the config', async () => { - expect( - await run( - ['dark:underline'], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { - plugins: [ - plugin(() => {}, { - darkMode: ['variant', '&:where(:not(.light))'], - }), - ], - }, - base: '/root', - path: '', - }), + let input = css` + @tailwind utilities; + @config "./config.js"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + plugins: [ + plugin(() => {}, { + darkMode: ['variant', '&:where(:not(.light))'], + }), + ], }, - ), - ).toMatchInlineSnapshot(` - " - .dark\\:underline:where(:not(.light)) { - text-decoration-line: underline; + base: '/root', + }), + }) + + expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(` + ".dark\\:underline { + &:where(:not(.light)) { + text-decoration-line: underline; + } } " `) }) test('Config file presets can contribute to the config', async () => { - expect( - await run( - ['dark:underline'], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { presets: [{ darkMode: ['variant', '&:where(:not(.light))'] }] }, - base: '/root', - path: '', - }), + let input = css` + @tailwind utilities; + @config "./config.js"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + presets: [ + { + darkMode: ['variant', '&:where(:not(.light))'], + }, + ], }, - ), - ).toMatchInlineSnapshot(` - " - .dark\\:underline:where(:not(.light)) { - text-decoration-line: underline; + base: '/root', + }), + }) + + expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(` + ".dark\\:underline { + &:where(:not(.light)) { + text-decoration-line: underline; + } } " `) }) test('Config files can affect the theme', async () => { - expect( - await run( - ['bg-primary', 'scrollbar-primary'], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - colors: { - primary: '#c0ffee', - }, - }, + let input = css` + @tailwind utilities; + @config "./config.js"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + colors: { + primary: '#c0ffee', }, - - plugins: [ - plugin(function ({ addUtilities, theme }) { - addUtilities({ - '.scrollbar-primary': { - scrollbarColor: theme('colors.primary'), - }, - }) - }), - ], }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - .scrollbar-primary { - scrollbar-color: #c0ffee; - } + }, - .bg-primary { + plugins: [ + plugin(function ({ addUtilities, theme }) { + addUtilities({ + '.scrollbar-primary': { + scrollbarColor: theme('colors.primary'), + }, + }) + }), + ], + }, + base: '/root', + }), + }) + + expect(compiler.build(['bg-primary', 'scrollbar-primary'])).toMatchInlineSnapshot(` + ".bg-primary { background-color: #c0ffee; } - " - `) -}) - -// https://github.com/tailwindlabs/tailwindcss/issues/19091 -test('Accessing a default color if a sub-color exists via CSS should work as expected', async () => { - expect( - await compileCss( - css` - @tailwind utilities; - @config "./config.js"; - - .example { - color: theme('colors.foo-bar'); - border-color: theme('colors.foo'); - } - `, - { - loadModule: async () => ({ - module: { - theme: { - // Internally this object gets converted to something like: - // ``` - // { - // foo: { DEFAULT: 'var(--foo-foo)', bar: 'var(--foo-foo-bar)' }, - // } - // ``` - colors: { - foo: 'var(--foo-foo)', - 'foo-bar': 'var(--foo-foo-bar)', - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - .example { - color: var(--foo-foo-bar); - border-color: var(--foo-foo); + .scrollbar-primary { + scrollbar-color: #c0ffee; } " `) }) test('Variants in CSS overwrite variants from plugins', async () => { - expect( - await run( - ['dark:underline', 'light:underline'], - css` - @tailwind utilities; - @config "./config.js"; - @custom-variant dark (&:is(.my-dark)); - @custom-variant light (&:is(.my-light)); - `, - { - loadModule: async () => ({ - module: { - darkMode: ['variant', '&:is(.dark)'], - plugins: [ - plugin(function ({ addVariant }) { - addVariant('light', '&:is(.light)') - }), - ], - }, - base: '/root', - path: '', - }), + let input = css` + @tailwind utilities; + @config "./config.js"; + @custom-variant dark (&:is(.my-dark)); + @custom-variant light (&:is(.my-light)); + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + darkMode: ['variant', '&:is(.dark)'], + plugins: [ + plugin(function ({ addVariant }) { + addVariant('light', '&:is(.light)') + }), + ], }, - ), - ).toMatchInlineSnapshot(` - " - .dark\\:underline.my-dark, .light\\:underline.my-light { - text-decoration-line: underline; + base: '/root', + }), + }) + + expect(compiler.build(['dark:underline', 'light:underline'])).toMatchInlineSnapshot(` + ".dark\\:underline { + &:is(.my-dark) { + text-decoration-line: underline; + } + } + .light\\:underline { + &:is(.my-light) { + text-decoration-line: underline; + } } " `) @@ -308,167 +253,146 @@ describe('theme callbacks', () => { test('tuple values from the config overwrite `@theme default` tuple-ish values from the CSS theme', async ({ expect, }) => { - expect( - await run( - ['leading-base', 'leading-md', 'leading-xl', 'prose'], - css` - @theme default { - --text-base: 0rem; - --text-base--line-height: 1rem; - --text-md: 0rem; - --text-md--line-height: 1rem; - --text-xl: 0rem; - --text-xl--line-height: 1rem; - } - @theme { - --text-base: 100rem; - --text-md--line-height: 101rem; - } - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - fontSize: { - base: ['200rem', { lineHeight: '201rem' }], - md: ['200rem', { lineHeight: '201rem' }], - xl: ['200rem', { lineHeight: '201rem' }], - }, + let input = css` + @theme default { + --text-base: 0rem; + --text-base--line-height: 1rem; + --text-md: 0rem; + --text-md--line-height: 1rem; + --text-xl: 0rem; + --text-xl--line-height: 1rem; + } + @theme { + --text-base: 100rem; + --text-md--line-height: 101rem; + } + @tailwind utilities; + @config "./config.js"; + ` - // Direct access - lineHeight: ({ theme }) => ({ - base: theme('fontSize.base[1].lineHeight'), - md: theme('fontSize.md[1].lineHeight'), - xl: theme('fontSize.xl[1].lineHeight'), - }), - - // Tuple access - typography: ({ theme }) => ({ - '[class~=lead-base]': { - fontSize: theme('fontSize.base')[0], - ...theme('fontSize.base')[1], - }, - '[class~=lead-md]': { - fontSize: theme('fontSize.md')[0], - ...theme('fontSize.md')[1], - }, - '[class~=lead-xl]': { - fontSize: theme('fontSize.xl')[0], - ...theme('fontSize.xl')[1], - }, - }), - }, + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + fontSize: { + base: ['200rem', { lineHeight: '201rem' }], + md: ['200rem', { lineHeight: '201rem' }], + xl: ['200rem', { lineHeight: '201rem' }], }, - plugins: [ - plugin(function ({ addUtilities, theme }) { - addUtilities({ - '.prose': { - ...theme('typography'), - }, - }) - }), - ], - } satisfies Config, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - @layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-leading: initial; + // Direct access + lineHeight: ({ theme }) => ({ + base: theme('fontSize.base[1].lineHeight'), + md: theme('fontSize.md[1].lineHeight'), + xl: theme('fontSize.xl[1].lineHeight'), + }), + + // Tuple access + typography: ({ theme }) => ({ + '[class~=lead-base]': { + fontSize: theme('fontSize.base')[0], + ...theme('fontSize.base')[1], + }, + '[class~=lead-md]': { + fontSize: theme('fontSize.md')[0], + ...theme('fontSize.md')[1], + }, + '[class~=lead-xl]': { + fontSize: theme('fontSize.xl')[0], + ...theme('fontSize.xl')[1], + }, + }), + }, + }, + + plugins: [ + plugin(function ({ addUtilities, theme }) { + addUtilities({ + '.prose': { + ...theme('typography'), + }, + }) + }), + ], + } satisfies Config, + base: '/root', + }), + }) + + expect(compiler.build(['leading-base', 'leading-md', 'leading-xl', 'prose'])) + .toMatchInlineSnapshot(` + ".prose { + [class~=lead-base] { + font-size: 100rem; + line-height: 201rem; + } + [class~=lead-md] { + font-size: 200rem; + line-height: 101rem; + } + [class~=lead-xl] { + font-size: 200rem; + line-height: 201rem; } } - } - - .prose [class~="lead-base"] { - font-size: 100rem; - line-height: 201rem; - } - - .prose [class~="lead-md"] { - font-size: 200rem; - line-height: 101rem; - } - - .prose [class~="lead-xl"] { - font-size: 200rem; - line-height: 201rem; - } - - .leading-base { - --tw-leading: 201rem; - line-height: 201rem; - } - - .leading-md { - --tw-leading: 101rem; - line-height: 101rem; - } - - .leading-xl { - --tw-leading: 201rem; - line-height: 201rem; - } - - @property --tw-leading { - syntax: "*"; - inherits: false - } - " - `) + .leading-base { + --tw-leading: 201rem; + line-height: 201rem; + } + .leading-md { + --tw-leading: 101rem; + line-height: 101rem; + } + .leading-xl { + --tw-leading: 201rem; + line-height: 201rem; + } + @property --tw-leading { + syntax: "*"; + inherits: false; + } + " + `) }) }) describe('theme overrides order', () => { test('user theme > js config > default theme', async () => { - expect( - await run( - ['bg-red', 'bg-blue'], - css` - @theme default { - --color-red: red; - } - @theme { - --color-blue: blue; - } - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - colors: { - red: 'very-red', - blue: 'very-blue', - }, - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - :root, :host { + let input = css` + @theme default { + --color-red: red; + } + @theme { --color-blue: blue; } + @tailwind utilities; + @config "./config.js"; + ` + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + colors: { + red: 'very-red', + blue: 'very-blue', + }, + }, + }, + }, + base: '/root', + }), + }) + + expect(compiler.build(['bg-red', 'bg-blue'])).toMatchInlineSnapshot(` + ":root, :host { + --color-blue: blue; + } .bg-blue { background-color: var(--color-blue); } - .bg-red { background-color: very-red; } @@ -477,134 +401,131 @@ describe('theme overrides order', () => { }) test('user theme > js config > default theme (with nested object)', async () => { - expect( - await run( - [ - 'bg-slate-100', - 'bg-slate-200', - 'bg-slate-300', - 'bg-slate-400', - 'bg-slate-500', - 'bg-slate-600', - 'hover-bg-slate-100', - 'hover-bg-slate-200', - 'hover-bg-slate-300', - 'hover-bg-slate-400', - 'hover-bg-slate-500', - 'hover-bg-slate-600', - ], - css` - @theme default { - --color-slate-100: #000100; - --color-slate-200: #000200; - --color-slate-300: #000300; - } - @theme { - --color-slate-400: #100400; - --color-slate-500: #100500; - } - @tailwind utilities; - @config "./config.js"; - @plugin "./plugin.js"; - `, - { - loadModule: async (id) => { - if (id.includes('config.js')) { - return { - module: { - theme: { - extend: { - colors: { - slate: { - 200: '#200200', - 400: '#200400', - 600: '#200600', - }, - }, + let input = css` + @theme default { + --color-slate-100: #000100; + --color-slate-200: #000200; + --color-slate-300: #000300; + } + @theme { + --color-slate-400: #100400; + --color-slate-500: #100500; + } + @tailwind utilities; + @config "./config.js"; + @plugin "./plugin.js"; + ` + + let compiler = await compile(input, { + loadModule: async (id) => { + if (id.includes('config.js')) { + return { + module: { + theme: { + extend: { + colors: { + slate: { + 200: '#200200', + 400: '#200400', + 600: '#200600', }, }, - } satisfies Config, - base: '/root', - path: '', - } - } else { - return { - module: plugin(({ matchUtilities, theme }) => { - matchUtilities( - { - 'hover-bg': (value) => { - return { - '&:hover': { - backgroundColor: value, - }, - } + }, + }, + } satisfies Config, + base: '/root', + } + } else { + return { + module: plugin(({ matchUtilities, theme }) => { + matchUtilities( + { + 'hover-bg': (value) => { + return { + '&:hover': { + backgroundColor: value, }, - }, - { values: flattenColorPalette(theme('colors')) }, - ) - }), - base: '/root', - path: '', - } - } - }, - }, - ), + } + }, + }, + { values: flattenColorPalette(theme('colors')) }, + ) + }), + base: '/root', + } + } + }, + }) + + expect( + compiler.build([ + 'bg-slate-100', + 'bg-slate-200', + 'bg-slate-300', + 'bg-slate-400', + 'bg-slate-500', + 'bg-slate-600', + 'hover-bg-slate-100', + 'hover-bg-slate-200', + 'hover-bg-slate-300', + 'hover-bg-slate-400', + 'hover-bg-slate-500', + 'hover-bg-slate-600', + ]), ).toMatchInlineSnapshot(` - " - :root, :host { + ":root, :host { --color-slate-100: #000100; --color-slate-300: #000300; --color-slate-400: #100400; --color-slate-500: #100500; } - .bg-slate-100 { background-color: var(--color-slate-100); } - .bg-slate-200 { background-color: #200200; } - .bg-slate-300 { background-color: var(--color-slate-300); } - .bg-slate-400 { background-color: var(--color-slate-400); } - .bg-slate-500 { background-color: var(--color-slate-500); } - .bg-slate-600 { background-color: #200600; } - - .hover-bg-slate-100:hover { - background-color: #000100; + .hover-bg-slate-100 { + &:hover { + background-color: #000100; + } } - - .hover-bg-slate-200:hover { - background-color: #200200; + .hover-bg-slate-200 { + &:hover { + background-color: #200200; + } } - - .hover-bg-slate-300:hover { - background-color: #000300; + .hover-bg-slate-300 { + &:hover { + background-color: #000300; + } } - - .hover-bg-slate-400:hover { - background-color: #100400; + .hover-bg-slate-400 { + &:hover { + background-color: #100400; + } } - - .hover-bg-slate-500:hover { - background-color: #100500; + .hover-bg-slate-500 { + &:hover { + background-color: #100500; + } } - - .hover-bg-slate-600:hover { - background-color: #200600; + .hover-bg-slate-600 { + &:hover { + background-color: #200600; + } } " `) @@ -613,35 +534,31 @@ describe('theme overrides order', () => { describe('default font family compatibility', () => { test('overriding `fontFamily.sans` sets `--default-font-family`', async () => { - expect( - await run( - ['font-sans'], - css` - @theme default { - --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); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - sans: 'Potato Sans', - }, - }, + let input = css` + @theme default { + --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); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + sans: 'Potato Sans', }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-sans { + base: '/root', + }), + }) + + expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` + ".font-sans { font-family: Potato Sans; } " @@ -651,37 +568,33 @@ describe('default font family compatibility', () => { test('overriding `fontFamily.sans[1].fontFeatureSettings` sets `--default-font-feature-settings`', async ({ expect, }) => { - expect( - await run( - ['font-sans'], - css` - @theme default { - --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); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - sans: ['Potato Sans', { fontFeatureSettings: '"cv06"' }], - }, - }, + let input = css` + @theme default { + --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); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + sans: ['Potato Sans', { fontFeatureSettings: '"cv06"' }], }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-sans { - font-feature-settings: "cv06"; + base: '/root', + }), + }) + + expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` + ".font-sans { font-family: Potato Sans; + font-feature-settings: "cv06"; } " `) @@ -690,37 +603,33 @@ describe('default font family compatibility', () => { test('overriding `fontFamily.sans[1].fontVariationSettings` sets `--default-font-variation-settings`', async ({ expect, }) => { - expect( - await run( - ['font-sans'], - css` - @theme default { - --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); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - sans: ['Potato Sans', { fontVariationSettings: '"XHGT" 0.7' }], - }, - }, + let input = css` + @theme default { + --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); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + sans: ['Potato Sans', { fontVariationSettings: '"XHGT" 0.7' }], }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-sans { - font-variation-settings: "XHGT" .7; + base: '/root', + }), + }) + + expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` + ".font-sans { font-family: Potato Sans; + font-variation-settings: "XHGT" 0.7; } " `) @@ -729,41 +638,37 @@ describe('default font family compatibility', () => { test('overriding `fontFeatureSettings` and `fontVariationSettings` for `fontFamily.sans` sets `--default-font-feature-settings` and `--default-font-variation-settings`', async ({ expect, }) => { - expect( - await run( - ['font-sans'], - css` - @theme default { - --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); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - sans: [ - 'Potato Sans', - { fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' }, - ], - }, - }, + let input = css` + @theme default { + --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); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + sans: [ + 'Potato Sans', + { fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' }, + ], }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-sans { - font-feature-settings: "cv06"; - font-variation-settings: "XHGT" .7; + base: '/root', + }), + }) + + expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` + ".font-sans { font-family: Potato Sans; + font-feature-settings: "cv06"; + font-variation-settings: "XHGT" 0.7; } " `) @@ -772,41 +677,36 @@ describe('default font family compatibility', () => { test('overriding `--font-family-sans` in `@theme` without `default` preserves the original `--default-font-*` values', async ({ expect, }) => { - expect( - await run( - ['font-sans'], - css` - @theme default { - --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); - } - @config "./config.js"; - @theme { - --font-sans: Sandwich Sans; - } - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - sans: 'Potato Sans', - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - :root, :host { + let input = css` + @theme default { + --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); + } + @config "./config.js"; + @theme { --font-sans: Sandwich Sans; } + @tailwind utilities; + ` + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + sans: 'Potato Sans', + }, + }, + }, + base: '/root', + }), + }) + + expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` + ":root, :host { + --font-sans: Sandwich Sans; + } .font-sans { font-family: var(--font-sans); } @@ -817,35 +717,31 @@ describe('default font family compatibility', () => { test('overriding `fontFamily.sans` in a config file with an array sets `--default-font-family`', async ({ expect, }) => { - expect( - await run( - ['font-sans'], - css` - @theme default { - --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); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - sans: ['Inter', 'system-ui', 'sans-serif'], - }, - }, + let input = css` + @theme default { + --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); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + sans: ['Inter', 'system-ui', 'sans-serif'], }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-sans { + base: '/root', + }), + }) + + expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` + ".font-sans { font-family: Inter, system-ui, sans-serif; } " @@ -855,67 +751,58 @@ describe('default font family compatibility', () => { test('overriding `fontFamily.sans` in a config file with an unexpected type is ignored', async ({ expect, }) => { - expect( - await run( - ['font-sans'], - css` - @theme default { - --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); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - sans: { foo: 'bar', banana: 'sandwich' }, - }, - }, + let input = css` + @theme default { + --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); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + sans: { foo: 'bar', banana: 'sandwich' }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toEqual('') + base: '/root', + }), + }) + + expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`""`) }) test('overriding `fontFamily.mono` sets `--default-mono-font-family`', async () => { - expect( - await run( - ['font-mono'], - css` - @theme default { - --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 - ); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - mono: 'Potato Mono', - }, - }, + let input = css` + @theme default { + --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); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + mono: 'Potato Mono', }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-mono { + base: '/root', + }), + }) + + expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` + ".font-mono { font-family: Potato Mono; } " @@ -925,39 +812,33 @@ describe('default font family compatibility', () => { test('overriding `fontFamily.mono[1].fontFeatureSettings` sets `--default-mono-font-feature-settings`', async ({ expect, }) => { - expect( - await run( - ['font-mono'], - css` - @theme default { - --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 - ); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - mono: ['Potato Mono', { fontFeatureSettings: '"cv06"' }], - }, - }, + let input = css` + @theme default { + --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); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + mono: ['Potato Mono', { fontFeatureSettings: '"cv06"' }], }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-mono { - font-feature-settings: "cv06"; + base: '/root', + }), + }) + + expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` + ".font-mono { font-family: Potato Mono; + font-feature-settings: "cv06"; } " `) @@ -966,39 +847,33 @@ describe('default font family compatibility', () => { test('overriding `fontFamily.mono[1].fontVariationSettings` sets `--default-mono-font-variation-settings`', async ({ expect, }) => { - expect( - await run( - ['font-mono'], - css` - @theme default { - --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 - ); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - mono: ['Potato Mono', { fontVariationSettings: '"XHGT" 0.7' }], - }, - }, + let input = css` + @theme default { + --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); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + mono: ['Potato Mono', { fontVariationSettings: '"XHGT" 0.7' }], }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-mono { - font-variation-settings: "XHGT" .7; + base: '/root', + }), + }) + + expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` + ".font-mono { font-family: Potato Mono; + font-variation-settings: "XHGT" 0.7; } " `) @@ -1007,41 +882,37 @@ describe('default font family compatibility', () => { test('overriding `fontFeatureSettings` and `fontVariationSettings` for `fontFamily.mono` sets `--default-mono-font-feature-settings` and `--default-mono-font-variation-settings`', async ({ expect, }) => { - expect( - await run( - ['font-mono'], - css` - @theme default { - --default-mono-font-family: var(--font-mono); - --default-mono-font-feature-settings: var(--font-mono--font-feature-settings); - --default-mono-font-variation-settings: var(--font-mono--font-variation-settings); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - mono: [ - 'Potato Mono', - { fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' }, - ], - }, - }, + let input = css` + @theme default { + --default-mono-font-family: var(--font-mono); + --default-mono-font-feature-settings: var(--font-mono--font-feature-settings); + --default-mono-font-variation-settings: var(--font-mono--font-variation-settings); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + mono: [ + 'Potato Mono', + { fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' }, + ], }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-mono { - font-feature-settings: "cv06"; - font-variation-settings: "XHGT" .7; + base: '/root', + }), + }) + + expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` + ".font-mono { font-family: Potato Mono; + font-feature-settings: "cv06"; + font-variation-settings: "XHGT" 0.7; } " `) @@ -1050,41 +921,36 @@ describe('default font family compatibility', () => { test('overriding `--font-family-mono` in `@theme` without `default` preserves the original `--default-mono-font-*` values', async ({ expect, }) => { - expect( - await run( - ['font-mono'], - css` - @theme default { - --default-mono-font-family: var(--font-mono); - --default-mono-font-feature-settings: var(--font-mono--font-feature-settings); - --default-mono-font-variation-settings: var(--font-mono--font-variation-settings); - } - @config "./config.js"; - @theme { - --font-mono: Sandwich Mono; - } - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - mono: 'Potato Mono', - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - :root, :host { + let input = css` + @theme default { + --default-mono-font-family: var(--font-mono); + --default-mono-font-feature-settings: var(--font-mono--font-feature-settings); + --default-mono-font-variation-settings: var(--font-mono--font-variation-settings); + } + @config "./config.js"; + @theme { --font-mono: Sandwich Mono; } + @tailwind utilities; + ` + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + mono: 'Potato Mono', + }, + }, + }, + base: '/root', + }), + }) + + expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` + ":root, :host { + --font-mono: Sandwich Mono; + } .font-mono { font-family: var(--font-mono); } @@ -1095,117 +961,115 @@ describe('default font family compatibility', () => { test('overriding `fontFamily.mono` in a config file with an unexpected type is ignored', async ({ expect, }) => { - expect( - await run( - ['font-mono'], - css` - @theme default { - --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 - ); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - mono: { foo: 'bar', banana: 'sandwich' }, - }, - }, + let input = css` + @theme default { + --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); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + mono: { foo: 'bar', banana: 'sandwich' }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toEqual('') + base: '/root', + }), + }) + + expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`""`) }) }) test('creates variants for `data`, `supports`, and `aria` theme options at the same level as the core utility ', async () => { - expect( - await run( - [ - 'aria-polite:underline', - 'supports-child-combinator:underline', - 'supports-foo:underline', - 'data-checked:underline', + let input = css` + @tailwind utilities; + @config "./config.js"; + ` - // Ensure core variants still work - 'aria-hidden:flex', - 'supports-grid:flex', - 'data-foo:flex', - - // The `print` variant should still be sorted last, even after registering - // the other custom variants. - 'print:flex', - ], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - aria: { - polite: 'live="polite"', - }, - supports: { - 'child-combinator': 'selector(h2 > p)', - foo: 'bar', - }, - data: { - checked: 'ui~="checked"', - }, - }, + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + aria: { + polite: 'live="polite"', + }, + supports: { + 'child-combinator': 'selector(h2 > p)', + foo: 'bar', + }, + data: { + checked: 'ui~="checked"', }, }, - base: '/root', - path: '', - }), + }, }, - ), + base: '/root', + }), + }) + + expect( + compiler.build([ + 'aria-polite:underline', + 'supports-child-combinator:underline', + 'supports-foo:underline', + 'data-checked:underline', + + // Ensure core variants still work + 'aria-hidden:flex', + 'supports-grid:flex', + 'data-foo:flex', + + // The `print` variant should still be sorted last, even after registering + // the other custom variants. + 'print:flex', + ]), ).toMatchInlineSnapshot(` - " - .aria-hidden\\:flex[aria-hidden="true"] { - display: flex; - } - - .aria-polite\\:underline[aria-live="polite"], .data-checked\\:underline[data-ui~="checked"] { - text-decoration-line: underline; - } - - .data-foo\\:flex[data-foo] { - display: flex; - } - - @supports selector(h2 > p) { - .supports-child-combinator\\:underline { - text-decoration-line: underline; - } - } - - @supports (bar: var(--tw)) { - .supports-foo\\:underline { - text-decoration-line: underline; - } - } - - @supports (grid: var(--tw)) { - .supports-grid\\:flex { + ".aria-hidden\\:flex { + &[aria-hidden="true"] { display: flex; } } - - @media print { - .print\\:flex { + .aria-polite\\:underline { + &[aria-live="polite"] { + text-decoration-line: underline; + } + } + .data-checked\\:underline { + &[data-ui~="checked"] { + text-decoration-line: underline; + } + } + .data-foo\\:flex { + &[data-foo] { + display: flex; + } + } + .supports-child-combinator\\:underline { + @supports selector(h2 > p) { + text-decoration-line: underline; + } + } + .supports-foo\\:underline { + @supports (bar: var(--tw)) { + text-decoration-line: underline; + } + } + .supports-grid\\:flex { + @supports (grid: var(--tw)) { + display: flex; + } + } + .print\\:flex { + @media print { display: flex; } } @@ -1214,66 +1078,62 @@ test('creates variants for `data`, `supports`, and `aria` theme options at the s }) test('merges css breakpoints with js config screens', async () => { - expect( - await run( - ['sm:flex', 'md:flex', 'lg:flex', 'min-sm:max-md:underline'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @theme { - --breakpoint-md: 50rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - sm: '44rem', - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @theme { + --breakpoint-md: 50rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + screens: { + sm: '44rem', }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - @media (min-width: 44rem) { - .sm\\:flex { - display: flex; - } + base: '/root', + }), + }) - @media not all and (min-width: 50rem) { - .min-sm\\:max-md\\:underline { - text-decoration-line: underline; + expect(compiler.build(['sm:flex', 'md:flex', 'lg:flex', 'min-sm:max-md:underline'])) + .toMatchInlineSnapshot(` + ".sm\\:flex { + @media (width >= 44rem) { + display: flex; + } + } + .min-sm\\:max-md\\:underline { + @media (width >= 44rem) { + @media (width < 50rem) { + text-decoration-line: underline; + } } } - } - - @media (min-width: 50rem) { .md\\:flex { - display: flex; + @media (width >= 50rem) { + display: flex; + } } - } - - @media (min-width: 64rem) { .lg\\:flex { - display: flex; + @media (width >= 64rem) { + display: flex; + } } - } - " - `) + " + `) }) test('utilities must be prefixed', async () => { @@ -1286,70 +1146,72 @@ test('utilities must be prefixed', async () => { } ` - let options: Partial[1]> = { - loadModule: async (_id, base) => ({ - path: '', + let compiler = await compile(input, { + loadModule: async (id, base) => ({ base, module: { prefix: 'tw' }, }), - } + }) // Prefixed utilities are generated - expect(await run(['tw:underline', 'tw:hover:line-through', 'tw:custom'], input, options)) + expect(compiler.build(['tw:underline', 'tw:hover:line-through', 'tw:custom'])) .toMatchInlineSnapshot(` - " - .tw\\:custom { - color: red; - } - - .tw\\:underline { - text-decoration-line: underline; - } - - @media (hover: hover) { - .tw\\:hover\\:line-through:hover { + ".tw\\:custom { + color: red; + } + .tw\\:underline { + text-decoration-line: underline; + } + .tw\\:hover\\:line-through { + &:hover { + @media (hover: hover) { text-decoration-line: line-through; } } - " - `) + } + " + `) // Non-prefixed utilities are ignored - expect(await run(['underline', 'hover:line-through', 'custom'], input, options)).toEqual('') + compiler = await compile(input, { + loadModule: async (id, base) => ({ + base, + module: { prefix: 'tw' }, + }), + }) + + expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toEqual('') }) test('utilities used in @apply must be prefixed', async () => { - // Prefixed utilities are generated - expect( - await compileCss( - css` - @config "./config.js"; + let compiler = await compile( + css` + @config "./config.js"; - .my-underline { - @apply tw:underline; - } - `, - { - loadModule: async (_id, base) => ({ - path: '', - base, - module: { prefix: 'tw' }, - }), - }, - ), - ).toMatchInlineSnapshot(` - " - .my-underline { + .my-underline { + @apply tw:underline; + } + `, + { + loadModule: async (id, base) => ({ + base, + module: { prefix: 'tw' }, + }), + }, + ) + + // Prefixed utilities are generated + expect(compiler.build([])).toMatchInlineSnapshot(` + ".my-underline { text-decoration-line: underline; } " `) // Non-prefixed utilities cause an error - await expect( + expect(() => compile( css` - @tailwind utilities; @config "./config.js"; .my-underline { @@ -1357,50 +1219,46 @@ test('utilities used in @apply must be prefixed', async () => { } `, { - loadModule: async (_id, base) => ({ - path: '', + loadModule: async (id, base) => ({ base, module: { prefix: 'tw' }, }), }, ), ).rejects.toThrowErrorMatchingInlineSnapshot( - `[Error: Cannot apply unprefixed utility class \`underline\`. Did you mean \`tw:underline\`?]`, + `[Error: Cannot apply unknown utility class: underline]`, ) }) test('Prefixes configured in CSS take precedence over those defined in JS configs', async () => { - expect( - await run( - ['wat:custom'], - css` - @theme prefix(wat) { - --color-red: #f00; - --color-green: #0f0; - --breakpoint-sm: 640px; + let compiler = await compile( + css` + @theme prefix(wat) { + --color-red: #f00; + --color-green: #0f0; + --breakpoint-sm: 640px; + } + + @config "./plugin.js"; + + @tailwind utilities; + + @utility custom { + color: red; + } + `, + { + async loadModule(id, base) { + return { + base, + module: { prefix: 'tw' }, } - - @config "./plugin.js"; - - @tailwind utilities; - - @utility custom { - color: red; - } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: { prefix: 'tw' }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - .wat\\:custom { + }, + ) + + expect(compiler.build(['wat:custom'])).toMatchInlineSnapshot(` + ".wat\\:custom { color: red; } " @@ -1414,9 +1272,8 @@ test('a prefix must be letters only', async () => { @config "./plugin.js"; `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: { prefix: '__' }, } @@ -1429,38 +1286,36 @@ test('a prefix must be letters only', async () => { }) test('important: `#app`', async () => { - expect( - await run( - ['underline', 'hover:line-through', 'custom'], - css` - @tailwind utilities; - @config "./config.js"; + let input = css` + @tailwind utilities; + @config "./config.js"; - @utility custom { - color: red; - } - `, - { - loadModule: async (_id, base) => ({ - path: '', - base, - module: { important: '#app' }, - }), - }, - ), - ).toMatchInlineSnapshot(` - " - #app .custom { + @utility custom { color: red; } + ` - #app .underline { - text-decoration-line: underline; - } + let compiler = await compile(input, { + loadModule: async (_, base) => ({ + base, + module: { important: '#app' }, + }), + }) - @media (hover: hover) { - #app .hover\\:line-through:hover { - text-decoration-line: line-through; + expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toMatchInlineSnapshot(` + "#app { + .custom { + color: red; + } + .underline { + text-decoration-line: underline; + } + .hover\\:line-through { + &:hover { + @media (hover: hover) { + text-decoration-line: line-through; + } + } } } " @@ -1468,38 +1323,34 @@ test('important: `#app`', async () => { }) test('important: true', async () => { - expect( - await run( - ['underline', 'hover:line-through', 'custom'], - css` - @tailwind utilities; - @config "./config.js"; + let input = css` + @tailwind utilities; + @config "./config.js"; - @utility custom { - color: red; - } - `, - { - loadModule: async (_id, base) => ({ - path: '', - base, - module: { important: true }, - }), - }, - ), - ).toMatchInlineSnapshot(` - " - .custom { + @utility custom { + color: red; + } + ` + + let compiler = await compile(input, { + loadModule: async (_, base) => ({ + base, + module: { important: true }, + }), + }) + + expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toMatchInlineSnapshot(` + ".custom { color: red !important; } - .underline { text-decoration-line: underline !important; } - - @media (hover: hover) { - .hover\\:line-through:hover { - text-decoration-line: line-through !important; + .hover\\:line-through { + &:hover { + @media (hover: hover) { + text-decoration-line: line-through !important; + } } } " @@ -1507,49 +1358,46 @@ test('important: true', async () => { }) test('blocklisted candidates are not generated', async () => { - let input = css` - @theme reference { - --color-white: #fff; - --breakpoint-md: 48rem; - } - @tailwind utilities; - @config "./config.js"; - ` - - let options: Partial[1]> = { - async loadModule(_id, base) { - return { - path: '', - base, - module: { - blocklist: ['bg-white'], - }, + let compiler = await compile( + css` + @theme reference { + --color-white: #fff; + --breakpoint-md: 48rem; } + @tailwind utilities; + @config "./config.js"; + `, + { + async loadModule(id, base) { + return { + base, + module: { + blocklist: ['bg-white'], + }, + } + }, }, - } + ) // bg-white will not get generated - expect(await run(['bg-white'], input, options)).toEqual('') + expect(compiler.build(['bg-white'])).toEqual('') // underline will as will md:bg-white - expect(await run(['underline', 'bg-white', 'md:bg-white'], input, options)) - .toMatchInlineSnapshot(` - " - .underline { - text-decoration-line: underline; + expect(compiler.build(['underline', 'bg-white', 'md:bg-white'])).toMatchInlineSnapshot(` + ".underline { + text-decoration-line: underline; + } + .md\\:bg-white { + @media (width >= 48rem) { + background-color: var(--color-white, #fff); } - - @media (min-width: 48rem) { - .md\\:bg-white { - background-color: var(--color-white, #fff); - } - } - " - `) + } + " + `) }) test('blocklisted candidates cannot be used with `@apply`', async () => { - await expect( + await expect(() => compile( css` @theme reference { @@ -1563,9 +1411,8 @@ test('blocklisted candidates cannot be used with `@apply`', async () => { } `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: { blocklist: ['bg-white'], @@ -1575,12 +1422,12 @@ test('blocklisted candidates cannot be used with `@apply`', async () => { }, ), ).rejects.toThrowErrorMatchingInlineSnapshot( - `[Error: Cannot apply utility class \`bg-white\` because it has been explicitly disabled: https://tailwindcss.com/docs/detecting-classes-in-source-files#explicitly-excluding-classes]`, + `[Error: Cannot apply unknown utility class: bg-white]`, ) }) test('old theme values are merged with their renamed counterparts in the CSS theme', async () => { - using didCallPluginFn = vi.fn() + let didCallPluginFn = vi.fn() await compile( css` @@ -1616,9 +1463,8 @@ test('old theme values are merged with their renamed counterparts in the CSS the @plugin "./plugin.js"; `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: plugin(function ({ theme }) { didCallPluginFn() @@ -1704,146 +1550,52 @@ test('old theme values are merged with their renamed counterparts in the CSS the }) test('handles setting theme keys to null', async () => { - expect( - await run( - ['bg-red-50', 'bg-red-100', 'bg-red-200'], - css` - @theme default { - --color-red-50: oklch(0.971 0.013 17.38); - --color-red-100: oklch(0.936 0.032 17.717); - } - @config "./my-config.js"; - @tailwind utilities; - @theme { - --color-red-100: oklch(0.936 0.032 17.717); - --color-red-200: oklch(0.885 0.062 18.334); - } - `, - { - loadModule: async () => { - return { - module: { - theme: { - extend: { - colors: { - red: null, - }, + let compiler = await compile( + css` + @theme default { + --color-red-50: oklch(0.971 0.013 17.38); + --color-red-100: oklch(0.936 0.032 17.717); + } + @config "./my-config.js"; + @tailwind utilities; + @theme { + --color-red-100: oklch(0.936 0.032 17.717); + --color-red-200: oklch(0.885 0.062 18.334); + } + `, + { + loadModule: async () => { + return { + module: { + theme: { + extend: { + colors: { + red: null, }, }, }, - base: '/root', - path: '', - } - }, + }, + base: '/root', + } }, - ), - ).toMatchInlineSnapshot(` - " - :root, :host { - --color-red-50: oklch(97.1% .013 17.38); - --color-red-100: oklch(93.6% .032 17.717); - --color-red-200: oklch(88.5% .062 18.334); - } + }, + ) + expect(compiler.build(['bg-red-50', 'bg-red-100', 'bg-red-200'])).toMatchInlineSnapshot(` + ":root, :host { + --color-red-50: oklch(0.971 0.013 17.38); + --color-red-100: oklch(0.936 0.032 17.717); + --color-red-200: oklch(0.885 0.062 18.334); + } .bg-red-50 { background-color: var(--color-red-50); } - .bg-red-100 { background-color: var(--color-red-100); } - .bg-red-200 { background-color: var(--color-red-200); } " `) }) - -test('The theme() function does not try indexing into strings', async () => { - expect( - await compileCss(css` - @theme default { - --color-what-50: #f00; - --color-what-950: #f00; - } - - @theme { - /* - * The value of this theme variable is > 50 chars because colors.what.50 was previously - * indexing the string: "light-dark(theme(colors.what.950), theme(colors.what.50))" - * because the resolved config contained an object with a "what" key that was that string - */ - --color-what: light-dark(theme(colors.what.950), theme(colors.what.50)); - } - - @source inline("text-what"); - - @tailwind utilities; - `), - ).toMatchInlineSnapshot(` - " - :root, :host { - --color-what: red; - } - - .text-what { - color: var(--color-what); - } - " - `) -}) - -test('camel case keys are preserved', async () => { - expect( - await run( - [ - // From CSS - 'bg-blue-green', // should be output - 'bg-blueGreen', // should not - - // From JS config - 'bg-light-green', // should not be output - 'bg-lightGreen', // should be - ], - css` - @tailwind utilities; - @theme { - --color-blue-green: slate; - } - @config "./plugin.js"; - `, - { - loadModule: async () => { - return { - base: '/', - path: '', - module: { - theme: { - extend: { - backgroundColor: { - lightGreen: '#c0ffee', - }, - }, - }, - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .bg-blue-green { - background-color: var(--color-blue-green); - } - - .bg-lightGreen { - background-color: #c0ffee; - } - - :root, :host { - --color-blue-green: slate; - } - " - `) -}) diff --git a/packages/tailwindcss/src/compat/config/create-compat-config.test.ts b/packages/tailwindcss/src/compat/config/create-compat-config.test.ts deleted file mode 100644 index aa1b21cb7..000000000 --- a/packages/tailwindcss/src/compat/config/create-compat-config.test.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { describe, expect, test } from 'vitest' -import { buildDesignSystem } from '../../design-system' -import { Theme } from '../../theme' -import { createCompatConfig } from './create-compat-config' -import { resolveConfig } from './resolve-config' - -function buildCompatConfig(cssValues: Record) { - let theme = new Theme() - for (let [key, value] of Object.entries(cssValues)) { - theme.add(key, value) - } - let design = buildDesignSystem(theme) - - let { resolvedConfig } = resolveConfig(design, [ - { config: createCompatConfig(design.theme), base: '/root', reference: true, src: undefined }, - ]) - - return resolvedConfig -} - -describe('theme namespace lookups', () => { - test('a namespace that only defines a bare value resolves to `{ DEFAULT: … }`, not a string', () => { - // When the CSS theme only defines `--text` (a single bare value), - // `theme('text', {})` used to return the string `'1rem'`. - // - // Spreading that string, like the compat config does, produced char-indexed - // garbage (`{ '0': '1', '1': 'r', … }`) instead of `{ DEFAULT: '1rem' }`. - let config = buildCompatConfig({ '--text': '1rem' }) - - expect(config.theme?.fontSize?.DEFAULT).toBe('1rem') - expect(config.theme?.fontSize).not.toHaveProperty('0') - }) - - test('a bare value is kept alongside suffixed values in the same namespace', () => { - let config = buildCompatConfig({ - '--text': '1rem', - '--text-sm': '0.875rem', - }) - - expect(config.theme?.fontSize?.DEFAULT).toBe('1rem') - expect(config.theme?.fontSize?.sm).toBe('0.875rem') - }) - - test('a naive spread of a theme namespace in a user config is safe', () => { - let theme = new Theme() - theme.add('--text', '1rem') - let design = buildDesignSystem(theme) - - let { resolvedConfig } = resolveConfig(design, [ - { - config: { - theme: { - extend: { - fontSize: ({ theme }: any) => ({ ...theme('text', {}) }), - }, - }, - }, - base: '/root', - reference: true, - src: undefined, - }, - ]) - - expect(resolvedConfig.theme?.fontSize?.DEFAULT).toBe('1rem') - expect(resolvedConfig.theme?.fontSize).not.toHaveProperty('0') - }) - - test('namespace root lookups resolve to an object, key lookups resolve to the value', () => { - let theme = new Theme() - theme.add('--text', '1rem') - theme.add('--animate-spin', 'spin 1s linear infinite') - let design = buildDesignSystem(theme) - - let root: unknown - let rootWithDefault: unknown - let leaf: unknown - - resolveConfig(design, [ - { - config: { - theme: { - extend: { - fontSize: ({ theme }: any) => { - root = theme('text') - rootWithDefault = theme('text', {}) - leaf = theme('animate.spin') - return {} - }, - }, - }, - }, - base: '/root', - reference: true, - src: undefined, - }, - ]) - - // A namespace root lookup always resolves to an object, like in v3, even - // when the namespace only contains a bare value - expect(root).toMatchObject({ DEFAULT: '1rem' }) - expect(rootWithDefault).toMatchObject({ DEFAULT: '1rem' }) - - // Lookups of a specific key resolve to the value itself - expect(leaf).toBe('spin 1s linear infinite') - }) - - test('lookups of a specific key still resolve to the value itself', () => { - let theme = new Theme() - theme.add('--animate-spin', 'spin 1s linear infinite') - let design = buildDesignSystem(theme) - - let { resolvedConfig } = resolveConfig(design, [ - { - config: { - theme: { - extend: { - animation: ({ theme }: any) => ({ spin: theme('animate.spin') }), - }, - }, - }, - base: '/root', - reference: true, - src: undefined, - }, - ]) - - expect(resolvedConfig.theme?.animation?.spin).toBe('spin 1s linear infinite') - }) -}) - -describe('createCompatConfig namespace mappings', () => { - test('fontSize maps from CSS text namespace', () => { - let config = buildCompatConfig({ '--text-base': '1rem' }) - expect(config.theme?.fontSize?.base).toBe('1rem') - }) - - test('boxShadow maps from CSS shadow namespace', () => { - let config = buildCompatConfig({ '--shadow-md': '0 4px 6px #000' }) - expect(config.theme?.boxShadow?.md).toBe('0 4px 6px #000') - }) - - test('animation maps from CSS animate namespace', () => { - let config = buildCompatConfig({ '--animate-spin': 'spin 1s linear infinite' }) - expect(config.theme?.animation?.spin).toBe('spin 1s linear infinite') - }) - - test('aspectRatio maps from CSS aspect namespace', () => { - let config = buildCompatConfig({ '--aspect-square': '1 / 1' }) - expect(config.theme?.aspectRatio?.square).toBe('1 / 1') - }) - - test('borderRadius maps from CSS radius namespace', () => { - let config = buildCompatConfig({ '--radius-lg': '0.5rem' }) - expect(config.theme?.borderRadius?.lg).toBe('0.5rem') - }) - - test('screens maps from CSS breakpoint namespace', () => { - let config = buildCompatConfig({ '--breakpoint-sm': '640px' }) - expect(config.theme?.screens?.sm).toBe('640px') - }) - - test('letterSpacing maps from CSS tracking namespace', () => { - let config = buildCompatConfig({ '--tracking-wide': '0.025em' }) - expect(config.theme?.letterSpacing?.wide).toBe('0.025em') - }) - - test('lineHeight maps from CSS leading namespace', () => { - let config = buildCompatConfig({ '--leading-relaxed': '1.75' }) - expect(config.theme?.lineHeight?.relaxed).toBe('1.75') - }) - - test('maxWidth maps from the container namespace in the default theme', () => { - let config = buildCompatConfig({}) - // The default theme has container.sm = '24rem' - expect(config.theme?.maxWidth?.sm).toBe('24rem') - }) - - test('transitionDuration gets DEFAULT from --default-transition-duration', () => { - let config = buildCompatConfig({ '--default-transition-duration': '150ms' }) - expect(config.theme?.transitionDuration?.DEFAULT).toBe('150ms') - }) - - test('transitionTimingFunction gets DEFAULT from --default-transition-timing-function', () => { - let config = buildCompatConfig({ - '--default-transition-timing-function': 'cubic-bezier(0.4, 0, 0.2, 1)', - }) - expect(config.theme?.transitionTimingFunction?.DEFAULT).toBe('cubic-bezier(0.4, 0, 0.2, 1)') - }) -}) diff --git a/packages/tailwindcss/src/compat/config/resolve-config.test.ts b/packages/tailwindcss/src/compat/config/resolve-config.test.ts index d8cc21a10..d96bdc700 100644 --- a/packages/tailwindcss/src/compat/config/resolve-config.test.ts +++ b/packages/tailwindcss/src/compat/config/resolve-config.test.ts @@ -20,8 +20,6 @@ test('top level theme keys are replaced', () => { }, }, base: '/root', - reference: false, - src: undefined, }, { config: { @@ -32,8 +30,6 @@ test('top level theme keys are replaced', () => { }, }, base: '/root', - reference: false, - src: undefined, }, { config: { @@ -44,8 +40,6 @@ test('top level theme keys are replaced', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) @@ -79,8 +73,6 @@ test('theme can be extended', () => { }, }, base: '/root', - reference: false, - src: undefined, }, { config: { @@ -93,8 +85,6 @@ test('theme can be extended', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) @@ -130,8 +120,6 @@ test('theme keys can reference other theme keys using the theme function regardl }, }, base: '/root', - reference: false, - src: undefined, }, { config: { @@ -145,8 +133,6 @@ test('theme keys can reference other theme keys using the theme function regardl }, }, base: '/root', - reference: false, - src: undefined, }, { config: { @@ -159,8 +145,6 @@ test('theme keys can reference other theme keys using the theme function regardl }, }, base: '/root', - reference: false, - src: undefined, }, ]) @@ -228,8 +212,6 @@ test('theme keys can read from the CSS theme', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) @@ -249,17 +231,17 @@ test('theme keys can read from the CSS theme', () => { }, caretColor: { primary: { - '100': 'oklch(96.2% 0.044 156.743)', - '200': 'oklch(92.5% 0.084 155.995)', - '300': 'oklch(87.1% 0.15 154.449)', - '400': 'oklch(79.2% 0.209 151.711)', - '50': 'oklch(98.2% 0.018 155.826)', - '500': 'oklch(72.3% 0.219 149.579)', - '600': 'oklch(62.7% 0.194 149.214)', - '700': 'oklch(52.7% 0.154 150.069)', - '800': 'oklch(44.8% 0.119 151.328)', - '900': 'oklch(39.3% 0.095 152.535)', - '950': 'oklch(26.6% 0.065 152.934)', + '50': 'oklch(0.982 0.018 155.826)', + '100': 'oklch(0.962 0.044 156.743)', + '200': 'oklch(0.925 0.084 155.995)', + '300': 'oklch(0.871 0.15 154.449)', + '400': 'oklch(0.792 0.209 151.711)', + '500': 'oklch(0.723 0.219 149.579)', + '600': 'oklch(0.627 0.194 149.214)', + '700': 'oklch(0.527 0.154 150.069)', + '800': 'oklch(0.448 0.119 151.328)', + '900': 'oklch(0.393 0.095 152.535)', + '950': 'oklch(0.266 0.065 152.934)', }, }, transitionColor: { @@ -292,7 +274,6 @@ test('handles null as theme values', () => { }, base: '/root', reference: false, - src: undefined, }, { config: { @@ -306,7 +287,6 @@ test('handles null as theme values', () => { }, base: '/root', reference: false, - src: undefined, }, ]) diff --git a/packages/tailwindcss/src/compat/config/resolve-config.ts b/packages/tailwindcss/src/compat/config/resolve-config.ts index 858f4bbbe..6801c7b60 100644 --- a/packages/tailwindcss/src/compat/config/resolve-config.ts +++ b/packages/tailwindcss/src/compat/config/resolve-config.ts @@ -1,5 +1,4 @@ import type { DesignSystem } from '../../design-system' -import type { SourceLocation } from '../../source-maps/source' import colors from '../colors' import type { PluginWithConfig } from '../plugin-api' import { createThemeFn } from '../plugin-functions' @@ -17,7 +16,6 @@ export interface ConfigFile { base: string config: UserConfig reference: boolean - src: SourceLocation | undefined } interface ResolutionContext { @@ -33,7 +31,6 @@ interface ResolutionContext { let minimal: ResolvedConfig = { blocklist: [], future: {}, - experimental: {}, prefix: '', important: false, darkMode: null, @@ -134,7 +131,7 @@ export type PluginUtils = { function extractConfigs( ctx: ResolutionContext, - { config, base, path, reference, src }: ConfigFile, + { config, base, path, reference }: ConfigFile, ): void { let plugins: PluginWithConfig[] = [] @@ -143,17 +140,17 @@ function extractConfigs( if ('__isOptionsFunction' in plugin) { // Happens with `plugin.withOptions()` when no options were passed: // e.g. `require("my-plugin")` instead of `require("my-plugin")(options)` - plugins.push({ ...plugin(), reference, src }) + plugins.push({ ...plugin(), reference }) } else if ('handler' in plugin) { // Happens with `plugin(…)`: // e.g. `require("my-plugin")` // // or with `plugin.withOptions()` when the user passed options: // e.g. `require("my-plugin")(options)` - plugins.push({ ...plugin, reference, src }) + plugins.push({ ...plugin, reference }) } else { // Just a plain function without using the plugin(…) API - plugins.push({ handler: plugin, reference, src }) + plugins.push({ handler: plugin, reference }) } } @@ -165,7 +162,7 @@ function extractConfigs( } for (let preset of config.presets ?? []) { - extractConfigs(ctx, { path, base, config: preset, reference, src }) + extractConfigs(ctx, { path, base, config: preset, reference }) } // Apply configs from plugins @@ -173,13 +170,7 @@ function extractConfigs( ctx.plugins.push(plugin) if (plugin.config) { - extractConfigs(ctx, { - path, - base, - config: plugin.config, - reference: !!plugin.reference, - src: plugin.src ?? src, - }) + extractConfigs(ctx, { path, base, config: plugin.config, reference: !!plugin.reference }) } } diff --git a/packages/tailwindcss/src/compat/config/types.ts b/packages/tailwindcss/src/compat/config/types.ts index 76851a397..dbcb30de3 100644 --- a/packages/tailwindcss/src/compat/config/types.ts +++ b/packages/tailwindcss/src/compat/config/types.ts @@ -52,7 +52,7 @@ type DarkModeStrategy = // Use the `class` strategy with a custom class instead of `.dark`. | ['class', string] - // Use the `selector` strategy — same as `class` but uses `:where()` for more predictable behavior + // Use the `selector` strategy — same as `class` but uses `:where()` for more predicable behavior | 'selector' // Use the `selector` strategy with a custom selector instead of `.dark`. @@ -105,12 +105,3 @@ export interface UserConfig { export interface ResolvedConfig { future: Record } - -// `experimental` key support -export interface UserConfig { - experimental?: 'all' | Record -} - -export interface ResolvedConfig { - experimental: Record -} diff --git a/packages/tailwindcss/src/compat/container-config.test.ts b/packages/tailwindcss/src/compat/container-config.test.ts index 39a12557c..b5618de84 100644 --- a/packages/tailwindcss/src/compat/container-config.test.ts +++ b/packages/tailwindcss/src/compat/container-config.test.ts @@ -1,74 +1,54 @@ import { expect, test } from 'vitest' -import { run } from '../test-utils/run' +import { compile } from '..' const css = String.raw test('creates a custom utility to extend the built-in container', async () => { - expect( - await run( - ['container'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - center: true, - padding: '2rem', - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - .container { - width: 100%; + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; } + @config "./config.js"; + @tailwind utilities; + ` - @media (min-width: 40rem) { - .container { + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + center: true, + padding: '2rem', + }, + }, + }, + base: '/root', + }), + }) + + expect(compiler.build(['container'])).toMatchInlineSnapshot(` + ".container { + width: 100%; + @media (width >= 40rem) { max-width: 40rem; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { max-width: 64rem; } - } - - @media (min-width: 80rem) { - .container { + @media (width >= 80rem) { max-width: 80rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { max-width: 96rem; } } - .container { margin-inline: auto; padding-inline: 2rem; @@ -78,87 +58,61 @@ test('creates a custom utility to extend the built-in container', async () => { }) test('allows padding to be defined at custom breakpoints', async () => { - expect( - await run( - ['container'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - padding: { - // The order here is messed up on purpose - '2xl': '3rem', - DEFAULT: '1rem', - lg: '2rem', - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + padding: { + // The order here is messed up on purpose + '2xl': '3rem', + DEFAULT: '1rem', + lg: '2rem', }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .container { - width: 100%; - } + base: '/root', + }), + }) - @media (min-width: 40rem) { - .container { + expect(compiler.build(['container'])).toMatchInlineSnapshot(` + ".container { + width: 100%; + @media (width >= 40rem) { max-width: 40rem; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { max-width: 64rem; } - } - - @media (min-width: 80rem) { - .container { + @media (width >= 80rem) { max-width: 80rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { max-width: 96rem; } } - .container { padding-inline: 1rem; - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { padding-inline: 2rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { padding-inline: 3rem; } } @@ -167,87 +121,61 @@ test('allows padding to be defined at custom breakpoints', async () => { }) test('allows breakpoints to be overwritten', async () => { - expect( - await run( - ['container'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - screens: { - xl: '1280px', - '2xl': '1536px', - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + screens: { + xl: '1280px', + '2xl': '1536px', }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .container { - width: 100%; - } + base: '/root', + }), + }) - @media (min-width: 40rem) { - .container { + expect(compiler.build(['container'])).toMatchInlineSnapshot(` + ".container { + width: 100%; + @media (width >= 40rem) { max-width: 40rem; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { max-width: 64rem; } - } - - @media (min-width: 80rem) { - .container { + @media (width >= 80rem) { max-width: 80rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { max-width: 96rem; } } - - @media (min-width: 40rem) { - .container { + .container { + @media (width >= 40rem) { max-width: none; } - } - - @media (min-width: 1280px) { - .container { + @media (width >= 1280px) { max-width: 1280px; } - } - - @media (min-width: 1536px) { - .container { + @media (width >= 1536px) { max-width: 1536px; } } @@ -256,84 +184,61 @@ test('allows breakpoints to be overwritten', async () => { }) test('padding applies to custom `container` screens', async () => { - expect( - await run( - ['container'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - padding: { - sm: '2rem', - md: '3rem', - }, - screens: { - md: '48rem', - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + padding: { + sm: '2rem', + md: '3rem', + }, + screens: { + md: '48rem', }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .container { - width: 100%; - } + base: '/root', + }), + }) - @media (min-width: 40rem) { - .container { + expect(compiler.build(['container'])).toMatchInlineSnapshot(` + ".container { + width: 100%; + @media (width >= 40rem) { max-width: 40rem; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { max-width: 64rem; } - } - - @media (min-width: 80rem) { - .container { + @media (width >= 80rem) { max-width: 80rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { max-width: 96rem; } } - - @media (min-width: 40rem) { - .container { + .container { + @media (width >= 40rem) { max-width: none; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; padding-inline: 3rem; } @@ -343,81 +248,58 @@ test('padding applies to custom `container` screens', async () => { }) test("an empty `screen` config will undo all custom media screens and won't apply any breakpoint-specific padding", async () => { - expect( - await run( - ['container'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - padding: { - DEFAULT: '1rem', - sm: '2rem', - md: '3rem', - }, - screens: {}, - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - .container { - width: 100%; + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; } + @config "./config.js"; + @tailwind utilities; + ` - @media (min-width: 40rem) { - .container { + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + padding: { + DEFAULT: '1rem', + sm: '2rem', + md: '3rem', + }, + screens: {}, + }, + }, + }, + base: '/root', + }), + }) + + expect(compiler.build(['container'])).toMatchInlineSnapshot(` + ".container { + width: 100%; + @media (width >= 40rem) { max-width: 40rem; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { max-width: 64rem; } - } - - @media (min-width: 80rem) { - .container { + @media (width >= 80rem) { max-width: 80rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { max-width: 96rem; } } - .container { padding-inline: 1rem; - } - - @media (min-width: 40rem) { - .container { + @media (width >= 40rem) { max-width: none; } } @@ -426,50 +308,45 @@ test("an empty `screen` config will undo all custom media screens and won't appl }) test('legacy container component does not interfere with new --container variables', async () => { - expect( - await run( - ['max-w-sm'], - css` - @theme default { - --container-3xs: 16rem; - --container-2xs: 18rem; - --container-xs: 20rem; - --container-sm: 24rem; - --container-md: 28rem; - --container-lg: 32rem; - --container-xl: 36rem; - --container-2xl: 42rem; - --container-3xl: 48rem; - --container-4xl: 56rem; - --container-5xl: 64rem; - --container-6xl: 72rem; - --container-7xl: 80rem; - --container-prose: 65ch; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - center: true, - padding: '2rem', - }, - }, + let input = css` + @theme default { + --container-3xs: 16rem; + --container-2xs: 18rem; + --container-xs: 20rem; + --container-sm: 24rem; + --container-md: 28rem; + --container-lg: 32rem; + --container-xl: 36rem; + --container-2xl: 42rem; + --container-3xl: 48rem; + --container-4xl: 56rem; + --container-5xl: 64rem; + --container-6xl: 72rem; + --container-7xl: 80rem; + --container-prose: 65ch; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + center: true, + padding: '2rem', }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - :root, :host { + base: '/root', + }), + }) + + expect(compiler.build(['max-w-sm'])).toMatchInlineSnapshot(` + ":root, :host { --container-sm: 24rem; } - .max-w-sm { max-width: var(--container-sm); } @@ -478,168 +355,107 @@ test('legacy container component does not interfere with new --container variabl }) test('combines custom padding and screen overwrites', async () => { - expect( - await run( - ['container', '!container'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - center: true, - padding: { - DEFAULT: '2rem', - '2xl': '4rem', - }, - screens: { - md: '48rem', // Matches a default --breakpoint - xl: '1280px', - '2xl': '1536px', - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + center: true, + padding: { + DEFAULT: '2rem', + '2xl': '4rem', + }, + screens: { + md: '48rem', // Matches a default --breakpoint + xl: '1280px', + '2xl': '1536px', }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .\\!container { - width: 100% !important; - } + base: '/root', + }), + }) - @media (min-width: 40rem) { - .\\!container { + expect(compiler.build(['container', '!container'])).toMatchInlineSnapshot(` + ".\\!container { + width: 100% !important; + @media (width >= 40rem) { max-width: 40rem !important; } - } - - @media (min-width: 48rem) { - .\\!container { + @media (width >= 48rem) { max-width: 48rem !important; } - } - - @media (min-width: 64rem) { - .\\!container { + @media (width >= 64rem) { max-width: 64rem !important; } - } - - @media (min-width: 80rem) { - .\\!container { + @media (width >= 80rem) { max-width: 80rem !important; } - } - - @media (min-width: 96rem) { - .\\!container { + @media (width >= 96rem) { max-width: 96rem !important; } } - .container { width: 100%; - } - - @media (min-width: 40rem) { - .container { + @media (width >= 40rem) { max-width: 40rem; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { max-width: 64rem; } - } - - @media (min-width: 80rem) { - .container { + @media (width >= 80rem) { max-width: 80rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { max-width: 96rem; } } - .\\!container { margin-inline: auto !important; padding-inline: 2rem !important; - } - - @media (min-width: 40rem) { - .\\!container { + @media (width >= 40rem) { max-width: none !important; } - } - - @media (min-width: 48rem) { - .\\!container { + @media (width >= 48rem) { max-width: 48rem !important; } - } - - @media (min-width: 1280px) { - .\\!container { + @media (width >= 1280px) { max-width: 1280px !important; } - } - - @media (min-width: 1536px) { - .\\!container { + @media (width >= 1536px) { max-width: 1536px !important; padding-inline: 4rem !important; } } - .container { margin-inline: auto; padding-inline: 2rem; - } - - @media (min-width: 40rem) { - .container { + @media (width >= 40rem) { max-width: none; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 1280px) { - .container { + @media (width >= 1280px) { max-width: 1280px; } - } - - @media (min-width: 1536px) { - .container { + @media (width >= 1536px) { max-width: 1536px; padding-inline: 4rem; } @@ -649,106 +465,74 @@ test('combines custom padding and screen overwrites', async () => { }) test('filters out complex breakpoints', async () => { - expect( - await run( - ['container'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - center: true, - padding: { - DEFAULT: '2rem', - '2xl': '4rem', - }, - screens: { - sm: '20px', - md: { min: '100px' }, - lg: { max: '200px' }, - xl: { min: '300px', max: '400px' }, - '2xl': { raw: 'print' }, - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + center: true, + padding: { + DEFAULT: '2rem', + '2xl': '4rem', + }, + screens: { + sm: '20px', + md: { min: '100px' }, + lg: { max: '200px' }, + xl: { min: '300px', max: '400px' }, + '2xl': { raw: 'print' }, }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .container { - width: 100%; - } + base: '/root', + }), + }) - @media (min-width: 40rem) { - .container { + expect(compiler.build(['container'])).toMatchInlineSnapshot(` + ".container { + width: 100%; + @media (width >= 40rem) { max-width: 40rem; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { max-width: 64rem; } - } - - @media (min-width: 80rem) { - .container { + @media (width >= 80rem) { max-width: 80rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { max-width: 96rem; } } - .container { margin-inline: auto; padding-inline: 2rem; - } - - @media (min-width: 40rem) { - .container { + @media (width >= 40rem) { max-width: none; } - } - - @media (min-width: 20px) { - .container { + @media (width >= 20px) { max-width: 20px; } - } - - @media (min-width: 100px) { - .container { + @media (width >= 100px) { max-width: 100px; } - } - - @media (min-width: 300px) { - .container { + @media (width >= 300px) { max-width: 300px; } } diff --git a/packages/tailwindcss/src/compat/container.ts b/packages/tailwindcss/src/compat/container.ts index 98f23ce7a..3cf72c4db 100644 --- a/packages/tailwindcss/src/compat/container.ts +++ b/packages/tailwindcss/src/compat/container.ts @@ -1,4 +1,4 @@ -import { atRule, cloneAstNode, decl, type AstNode, type AtRule } from '../ast' +import { atRule, decl, type AstNode, type AtRule } from '../ast' import type { DesignSystem } from '../design-system' import { compareBreakpoints } from '../utils/compare-breakpoints' import type { ResolvedConfig } from './config/types' @@ -16,7 +16,7 @@ export function registerContainerCompat(userConfig: ResolvedConfig, designSystem return } - designSystem.utilities.static('container', () => rules.map(cloneAstNode)) + designSystem.utilities.static('container', () => structuredClone(rules)) } export function buildCustomContainerUtilityRules( @@ -50,7 +50,7 @@ export function buildCustomContainerUtilityRules( if (typeof screens === 'object' && screens !== null) { breakpointOverwrites = new Map() - // When setting the `screens` in v3, you were overwriting the default + // When setting a the `screens` in v3, you were overwriting the default // screens config. To do this in v4, you have to manually unset all core // screens. diff --git a/packages/tailwindcss/src/compat/default-theme.ts b/packages/tailwindcss/src/compat/default-theme.ts index 90e699c1c..90f6e017c 100644 --- a/packages/tailwindcss/src/compat/default-theme.ts +++ b/packages/tailwindcss/src/compat/default-theme.ts @@ -156,7 +156,7 @@ export default { '3xl': '64px', }, borderColor: ({ theme }) => ({ - DEFAULT: 'currentcolor', + DEFAULT: 'currentColor', ...theme('colors'), }), borderOpacity: ({ theme }) => theme('opacity'), @@ -353,13 +353,8 @@ export default { }, fontFamily: { sans: [ - '-apple-system', - 'BlinkMacSystemFont', - '"Segoe UI"', - 'Roboto', - '"Helvetica Neue"', - '"Noto Sans"', - 'Arial', + 'ui-sans-serif', + 'system-ui', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', @@ -830,7 +825,7 @@ export default { placeholderColor: ({ theme }) => theme('colors'), placeholderOpacity: ({ theme }) => theme('opacity'), ringColor: ({ theme }) => ({ - DEFAULT: 'currentcolor', + DEFAULT: 'currentColor', ...theme('colors'), }), ringOffsetColor: ({ theme }) => theme('colors'), diff --git a/packages/tailwindcss/src/compat/legacy-utilities.test.ts b/packages/tailwindcss/src/compat/legacy-utilities.test.ts index c90eae845..9a828d673 100644 --- a/packages/tailwindcss/src/compat/legacy-utilities.test.ts +++ b/packages/tailwindcss/src/compat/legacy-utilities.test.ts @@ -1,23 +1,27 @@ import { expect, test } from 'vitest' -import { run } from '../test-utils/run' +import { compileCss, run } from '../test-utils/run' const css = String.raw test('bg-gradient-*', async () => { expect( - await run([ - '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', - ]), + await compileCss( + css` + @tailwind utilities; + `, + [ + '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', + ], + ), ).toMatchInlineSnapshot(` - " - .bg-gradient-to-b { + ".bg-gradient-to-b { --tw-gradient-position: to bottom in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } @@ -55,21 +59,13 @@ test('bg-gradient-*', async () => { .bg-gradient-to-tr { --tw-gradient-position: to top right in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); - } - " + }" `) }) test('max-w-screen', async () => { expect( - await run( - [ - 'max-w-screen-sm', - 'max-w-screen-md', - 'max-w-screen-lg', - 'max-w-screen-xl', - 'max-w-screen-2xl', - ], + await compileCss( css` @theme { --breakpoint-sm: 40rem; @@ -80,10 +76,16 @@ test('max-w-screen', async () => { } @tailwind utilities; `, + [ + 'max-w-screen-sm', + 'max-w-screen-md', + 'max-w-screen-lg', + 'max-w-screen-xl', + 'max-w-screen-2xl', + ], ), ).toMatchInlineSnapshot(` - " - :root, :host { + ":root, :host { --breakpoint-sm: 40rem; --breakpoint-md: 48rem; --breakpoint-lg: 64rem; @@ -109,8 +111,7 @@ test('max-w-screen', async () => { .max-w-screen-xl { max-width: var(--breakpoint-xl); - } - " + }" `) expect( await run([ @@ -133,8 +134,7 @@ test('max-w-screen', async () => { test('box-decoration', async () => { expect(await run(['decoration-slice', 'decoration-clone'])).toMatchInlineSnapshot(` - " - .decoration-clone { + ".decoration-clone { -webkit-box-decoration-break: clone; box-decoration-break: clone; } @@ -142,25 +142,21 @@ test('box-decoration', async () => { .decoration-slice { -webkit-box-decoration-break: slice; box-decoration-break: slice; - } - " + }" `) }) test('overflow-ellipsis', async () => { expect(await run(['overflow-ellipsis'])).toMatchInlineSnapshot(` - " - .overflow-ellipsis { + ".overflow-ellipsis { text-overflow: ellipsis; - } - " + }" `) }) test('flex-grow', async () => { expect(await run(['flex-grow', 'flex-grow-0', 'flex-grow-[123]'])).toMatchInlineSnapshot(` - " - .flex-grow { + ".flex-grow { flex-grow: 1; } @@ -170,8 +166,7 @@ test('flex-grow', async () => { .flex-grow-\\[123\\] { flex-grow: 123; - } - " + }" `) expect( await run([ @@ -190,8 +185,7 @@ test('flex-grow', async () => { test('flex-shrink', async () => { expect(await run(['flex-shrink', 'flex-shrink-0', 'flex-shrink-[123]'])).toMatchInlineSnapshot(` - " - .flex-shrink { + ".flex-shrink { flex-shrink: 1; } @@ -201,8 +195,7 @@ test('flex-shrink', async () => { .flex-shrink-\\[123\\] { flex-shrink: 123; - } - " + }" `) expect( await run([ @@ -218,181 +211,3 @@ test('flex-shrink', async () => { ]), ).toEqual('') }) - -test('start', async () => { - expect( - await run( - [ - 'start-shadowned', - 'start-auto', - '-start-full', - 'start-full', - 'start-3/4', - 'start-4', - '-start-4', - 'start-[4px]', - ], - css` - @theme { - --spacing: 0.25rem; - --inset-shadowned: 1940px; - } - @tailwind utilities; - `, - ), - ).toMatchInlineSnapshot(` - " - :root, :host { - --spacing: .25rem; - --inset-shadowned: 1940px; - } - - .-start-4 { - inset-inline-start: calc(var(--spacing) * -4); - } - - .-start-full { - inset-inline-start: -100%; - } - - .start-3\\/4 { - inset-inline-start: 75%; - } - - .start-4 { - inset-inline-start: calc(var(--spacing) * 4); - } - - .start-\\[4px\\] { - inset-inline-start: 4px; - } - - .start-auto { - inset-inline-start: auto; - } - - .start-full { - inset-inline-start: 100%; - } - - .start-shadowned { - inset-inline-start: var(--inset-shadowned); - } - " - `) - expect( - await run( - [ - 'start-shadow-sm', - 'start', - 'start--1', - 'start--1/2', - 'start--1/-2', - 'start-1/-2', - 'start-auto/foo', - '-start-full/foo', - 'start-full/foo', - 'start-3/4/foo', - 'start-4/foo', - '-start-4/foo', - 'start-[4px]/foo', - ], - css` - @theme reference { - --spacing: 0.25rem; - --inset-shadow-sm: inset 0 1px 1px rgb(0 0 0 / 0.05); - } - @tailwind utilities; - `, - ), - ).toEqual('') -}) - -test('end', async () => { - expect( - await run( - [ - 'end-shadowned', - 'end-auto', - '-end-full', - 'end-full', - 'end-3/4', - 'end-4', - '-end-4', - 'end-[4px]', - ], - css` - @theme { - --spacing: 0.25rem; - --inset-shadowned: 1940px; - } - @tailwind utilities; - `, - ), - ).toMatchInlineSnapshot(` - " - :root, :host { - --spacing: .25rem; - --inset-shadowned: 1940px; - } - - .-end-4 { - inset-inline-end: calc(var(--spacing) * -4); - } - - .-end-full { - inset-inline-end: -100%; - } - - .end-3\\/4 { - inset-inline-end: 75%; - } - - .end-4 { - inset-inline-end: calc(var(--spacing) * 4); - } - - .end-\\[4px\\] { - inset-inline-end: 4px; - } - - .end-auto { - inset-inline-end: auto; - } - - .end-full { - inset-inline-end: 100%; - } - - .end-shadowned { - inset-inline-end: var(--inset-shadowned); - } - " - `) - expect( - await run( - [ - 'end-shadow-sm', - 'end', - 'end--1', - 'end--1/2', - 'end--1/-2', - 'end-1/-2', - 'end-auto/foo', - '-end-full/foo', - 'end-full/foo', - 'end-3/4/foo', - 'end-4/foo', - '-end-4/foo', - 'end-[4px]/foo', - ], - css` - @theme reference { - --spacing: 0.25rem; - --inset-shadow-sm: inset 0 1px 1px rgb(0 0 0 / 0.05); - } - @tailwind utilities; - `, - ), - ).toEqual('') -}) diff --git a/packages/tailwindcss/src/compat/legacy-utilities.ts b/packages/tailwindcss/src/compat/legacy-utilities.ts index 8e7829035..5711892d7 100644 --- a/packages/tailwindcss/src/compat/legacy-utilities.ts +++ b/packages/tailwindcss/src/compat/legacy-utilities.ts @@ -1,7 +1,6 @@ import { decl } from '../ast' import type { DesignSystem } from '../design-system' -import { isPositiveInteger, isValidSpacingMultiplier } from '../utils/infer-data-type' -import { segment } from '../utils/segment' +import { isPositiveInteger } from '../utils/infer-data-type' export function registerLegacyUtilities(designSystem: DesignSystem) { for (let [value, direction] of [ @@ -14,42 +13,12 @@ export function registerLegacyUtilities(designSystem: DesignSystem) { ['l', 'left'], ['tl', 'top left'], ]) { - designSystem.utilities.suggest(`bg-gradient-to-${value}`, () => []) designSystem.utilities.static(`bg-gradient-to-${value}`, () => [ decl('--tw-gradient-position', `to ${direction} in oklab`), decl('background-image', `linear-gradient(var(--tw-gradient-stops))`), ]) } - // Legacy `background-position` utilities for compatibility with v4.0 and earlier - designSystem.utilities.suggest('bg-left-top', () => []) - designSystem.utilities.static('bg-left-top', () => [decl('background-position', 'left top')]) - designSystem.utilities.suggest('bg-right-top', () => []) - designSystem.utilities.static('bg-right-top', () => [decl('background-position', 'right top')]) - designSystem.utilities.suggest('bg-left-bottom', () => []) - designSystem.utilities.static('bg-left-bottom', () => [ - decl('background-position', 'left bottom'), - ]) - designSystem.utilities.suggest('bg-right-bottom', () => []) - designSystem.utilities.static('bg-right-bottom', () => [ - decl('background-position', 'right bottom'), - ]) - - // Legacy `object-position` utilities for compatibility with v4.0 and earlier - designSystem.utilities.suggest('object-left-top', () => []) - designSystem.utilities.static('object-left-top', () => [decl('object-position', 'left top')]) - designSystem.utilities.suggest('object-right-top', () => []) - designSystem.utilities.static('object-right-top', () => [decl('object-position', 'right top')]) - designSystem.utilities.suggest('object-left-bottom', () => []) - designSystem.utilities.static('object-left-bottom', () => [ - decl('object-position', 'left bottom'), - ]) - designSystem.utilities.suggest('object-right-bottom', () => []) - designSystem.utilities.static('object-right-bottom', () => [ - decl('object-position', 'right bottom'), - ]) - - designSystem.utilities.suggest('max-w-screen', () => []) designSystem.utilities.functional('max-w-screen', (candidate) => { if (!candidate.value) return if (candidate.value.kind === 'arbitrary') return @@ -58,22 +27,18 @@ export function registerLegacyUtilities(designSystem: DesignSystem) { return [decl('max-width', value)] }) - designSystem.utilities.suggest('overflow-ellipsis', () => []) designSystem.utilities.static(`overflow-ellipsis`, () => [decl('text-overflow', `ellipsis`)]) - designSystem.utilities.suggest('decoration-slice', () => []) designSystem.utilities.static(`decoration-slice`, () => [ decl('-webkit-box-decoration-break', `slice`), decl('box-decoration-break', `slice`), ]) - designSystem.utilities.suggest('decoration-clone', () => []) designSystem.utilities.static(`decoration-clone`, () => [ decl('-webkit-box-decoration-break', `clone`), decl('box-decoration-break', `clone`), ]) - designSystem.utilities.suggest('flex-shrink', () => []) designSystem.utilities.functional('flex-shrink', (candidate) => { if (candidate.modifier) return @@ -90,7 +55,6 @@ export function registerLegacyUtilities(designSystem: DesignSystem) { } }) - designSystem.utilities.suggest('flex-grow', () => []) designSystem.utilities.functional('flex-grow', (candidate) => { if (candidate.modifier) return @@ -106,69 +70,4 @@ export function registerLegacyUtilities(designSystem: DesignSystem) { return [decl('flex-grow', candidate.value.value)] } }) - - designSystem.utilities.suggest('order-none', () => []) - designSystem.utilities.static('order-none', () => [decl('order', '0')]) - - designSystem.utilities.suggest('break-words', () => []) - designSystem.utilities.static('break-words', () => [decl('overflow-wrap', 'break-word')]) - - // Legacy `start` and `end` inset utilities, replaced by `inset-s` and `inset-e` - for (let [name, property] of [ - ['start', 'inset-inline-start'], - ['end', 'inset-inline-end'], - ] as const) { - designSystem.utilities.static(`${name}-auto`, () => [decl(property, 'auto')]) - designSystem.utilities.static(`${name}-full`, () => [decl(property, '100%')]) - designSystem.utilities.static(`-${name}-full`, () => [decl(property, '-100%')]) - designSystem.utilities.static(`${name}-px`, () => [decl(property, '1px')]) - designSystem.utilities.static(`-${name}-px`, () => [decl(property, '-1px')]) - - function handleInset({ negative }: { negative: boolean }) { - return (candidate: Extract) => { - if (candidate.value === null) return - - if (candidate.value.kind === 'arbitrary') { - if (candidate.modifier) return - let value = candidate.value.value - return [decl(property, negative ? `calc(${value} * -1)` : value)] - } - - let value = designSystem.theme.resolve(candidate.value.fraction ?? candidate.value.value, [ - '--inset', - '--spacing', - ]) - - // Handle fractions like `start-1/2` - if (value === null && candidate.value.fraction) { - let [lhs, rhs] = segment(candidate.value.fraction, '/') - if (!isPositiveInteger(lhs) || !isPositiveInteger(rhs)) return - value = `calc(${candidate.value.fraction} * 100%)` - } - - // Handle bare spacing multiplier values like `start-4` - if (value === null && negative) { - let multiplier = designSystem.theme.resolve(null, ['--spacing']) - if (multiplier && isValidSpacingMultiplier(candidate.value.value)) { - value = `calc(${multiplier} * -${candidate.value.value})` - if (value !== null) return [decl(property, value)] - } - } - - if (value === null) { - let multiplier = designSystem.theme.resolve(null, ['--spacing']) - if (multiplier && isValidSpacingMultiplier(candidate.value.value)) { - value = `calc(${multiplier} * ${candidate.value.value})` - } - } - - if (value === null) return - - return [decl(property, negative ? `calc(${value} * -1)` : value)] - } - } - - designSystem.utilities.functional(`-${name}`, handleInset({ negative: true })) - designSystem.utilities.functional(name, handleInset({ negative: false })) - } } diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts index 5fcacb5cd..b786f2311 100644 --- a/packages/tailwindcss/src/compat/plugin-api.test.ts +++ b/packages/tailwindcss/src/compat/plugin-api.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test, vi } from 'vitest' import { compile } from '..' import plugin from '../plugin' -import { compileCss, run } from '../test-utils/run' +import { optimizeCss } from '../test-utils/run' import defaultTheme from './default-theme' import type { CssInJs, PluginAPI } from './plugin-api' @@ -9,61 +9,57 @@ const css = String.raw describe('theme', async () => { test('plugin theme can contain objects', async () => { - expect( - await compileCss( - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ addBase, theme }) { - addBase({ - '@keyframes enter': theme('keyframes.enter'), - '@keyframes exit': theme('keyframes.exit'), - }) - }, - { - theme: { - extend: { - keyframes: { - enter: { - from: { - opacity: 'var(--tw-enter-opacity, 1)', - transform: - 'translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))', - }, - }, - exit: { - to: { - opacity: 'var(--tw-exit-opacity, 1)', - transform: - 'translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))', - }, - }, + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin( + function ({ addBase, theme }) { + addBase({ + '@keyframes enter': theme('keyframes.enter'), + '@keyframes exit': theme('keyframes.exit'), + }) + }, + { + theme: { + extend: { + keyframes: { + enter: { + from: { + opacity: 'var(--tw-enter-opacity, 1)', + transform: + 'translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))', + }, + }, + exit: { + to: { + opacity: 'var(--tw-exit-opacity, 1)', + transform: + 'translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))', }, }, }, }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer base { + }, + }, + ), + } + }, + }) + + expect(compiler.build([])).toMatchInlineSnapshot(` + "@layer base { @keyframes enter { from { opacity: var(--tw-enter-opacity, 1); transform: translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0)); } } - @keyframes exit { to { opacity: var(--tw-exit-opacity, 1); @@ -76,29 +72,30 @@ describe('theme', async () => { }) test('keyframes added via addUtilities are appended to the AST', async () => { - expect( - await compileCss( - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ addUtilities }) { - addUtilities({ - '@keyframes enter': { from: { opacity: 'var(--tw-enter-opacity, 1)' } }, - }) - }), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @keyframes enter { + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin(function ({ addUtilities, theme }) { + addUtilities({ + '@keyframes enter': { + from: { + opacity: 'var(--tw-enter-opacity, 1)', + }, + }, + }) + }), + } + }, + }) + + expect(compiler.build([])).toMatchInlineSnapshot(` + "@keyframes enter { from { opacity: var(--tw-enter-opacity, 1); } @@ -108,40 +105,47 @@ describe('theme', async () => { }) test('plugin theme can extend colors', async () => { - expect( - await run( - ['scrollbar-red-500', 'scrollbar-russet-700'], - css` - @theme reference { - --color-red-500: #ef4444; - } - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-color': value }) }, - { values: theme('colors') }, - ) + let input = css` + @theme reference { + --color-red-500: #ef4444; + } + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { + scrollbar: (value) => ({ 'scrollbar-color': value }), }, - { theme: { extend: { colors: { 'russet-700': '#7a4724' } } } }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .scrollbar-red-500 { + { + values: theme('colors'), + }, + ) + }, + { + theme: { + extend: { + colors: { + 'russet-700': '#7a4724', + }, + }, + }, + }, + ), + } + }, + }) + + expect(compiler.build(['scrollbar-red-500', 'scrollbar-russet-700'])).toMatchInlineSnapshot(` + ".scrollbar-red-500 { scrollbar-color: #ef4444; } - .scrollbar-russet-700 { scrollbar-color: #7a4724; } @@ -152,43 +156,45 @@ describe('theme', async () => { test('plugin theme values can reference legacy theme keys that have been replaced with bare value support', async ({ expect, }) => { - expect( - await run( - ['animate-duration-316'], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { 'animate-duration': (value) => ({ 'animation-duration': value }) }, - { values: theme('animationDuration') }, - ) + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { + 'animate-duration': (value) => ({ 'animation-duration': value }), }, { - theme: { - extend: { - animationDuration: ({ theme }: { theme: (path: string) => any }) => { - return { ...theme('transitionDuration') } - }, - }, + values: theme('animationDuration'), + }, + ) + }, + { + theme: { + extend: { + animationDuration: ({ theme }: { theme: (path: string) => any }) => { + return { + ...theme('transitionDuration'), + } }, }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .animate-duration-316 { - animation-duration: .316s; + }, + }, + ), + } + }, + }) + + expect(compiler.build(['animate-duration-316'])).toMatchInlineSnapshot(` + ".animate-duration-316 { + animation-duration: 316ms; } " `) @@ -197,402 +203,433 @@ describe('theme', async () => { test('plugin theme values that support bare values are merged with other values for that theme key', async ({ expect, }) => { - expect( - await run( - ['animate-duration-316', 'animate-duration-slow'], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { 'animate-duration': (value) => ({ 'animation-duration': value }) }, - { values: theme('animationDuration') }, - ) + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { + 'animate-duration': (value) => ({ 'animation-duration': value }), }, { - theme: { - extend: { - transitionDuration: { slow: '800ms' }, - animationDuration: ({ theme }: { theme: (path: string) => any }) => ({ - ...theme('transitionDuration'), - }), - }, - }, + values: theme('animationDuration'), }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .animate-duration-316 { - animation-duration: .316s; - } + ) + }, + { + theme: { + extend: { + transitionDuration: { + slow: '800ms', + }, + animationDuration: ({ theme }: { theme: (path: string) => any }) => ({ + ...theme('transitionDuration'), + }), + }, + }, + }, + ), + } + }, + }) + + expect(compiler.build(['animate-duration-316', 'animate-duration-slow'])) + .toMatchInlineSnapshot(` + ".animate-duration-316 { + animation-duration: 316ms; + } .animate-duration-slow { - animation-duration: .8s; + animation-duration: 800ms; } " `) }) test('plugin theme can have opacity modifiers', async () => { - expect( - await run( - ['percentage', 'fraction', 'variable'], - css` - @tailwind utilities; - @theme { - --color-red-500: #ef4444; - } - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ addUtilities, theme }) { - addUtilities({ - '.percentage': { color: theme('colors.red.500 / 50%') }, - '.fraction': { color: theme('colors.red.500 / 0.5') }, - '.variable': { color: theme('colors.red.500 / var(--opacity)') }, - }) - }), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .fraction, .percentage { - color: oklab(63.6834% .187 .088 / .5); + let input = css` + @tailwind utilities; + @theme { + --color-red-500: #ef4444; } + @plugin "my-plugin"; + ` - .variable { - color: #ef4444; - } - - @supports (color: color-mix(in lab, red, red)) { - .variable { - color: color-mix(in oklab, #ef4444 var(--opacity), transparent); + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin(function ({ addUtilities, theme }) { + addUtilities({ + '.percentage': { + color: theme('colors.red.500 / 50%'), + }, + '.fraction': { + color: theme('colors.red.500 / 0.5'), + }, + '.variable': { + color: theme('colors.red.500 / var(--opacity)'), + }, + }) + }), } + }, + }) + + expect(compiler.build(['percentage', 'fraction', 'variable'])).toMatchInlineSnapshot(` + ".fraction { + color: color-mix(in oklab, #ef4444 50%, transparent); + } + .percentage { + color: color-mix(in oklab, #ef4444 50%, transparent); + } + .variable { + color: color-mix(in oklab, #ef4444 var(--opacity), transparent); } " `) }) test('plugin theme colors can use ', async () => { + let input = css` + @tailwind utilities; + @theme { + /* This should not work */ + --color-custom-css: rgba(255 0 0 / ); + } + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin( + function ({ addUtilities, theme }) { + addUtilities({ + '.css-percentage': { + color: theme('colors.custom-css / 50%'), + }, + '.css-fraction': { + color: theme('colors.custom-css / 0.5'), + }, + '.css-variable': { + color: theme('colors.custom-css / var(--opacity)'), + }, + '.js-percentage': { + color: theme('colors.custom-js / 50%'), + }, + '.js-fraction': { + color: theme('colors.custom-js / 0.5'), + }, + '.js-variable': { + color: theme('colors.custom-js / var(--opacity)'), + }, + }) + }, + { + theme: { + colors: { + /* This should work */ + 'custom-js': 'rgb(255 0 0 / )', + }, + }, + }, + ), + } + }, + }) + expect( - await run( - [ - 'bg-custom', - 'css-percentage', - 'css-fraction', - 'css-variable', - 'js-percentage', - 'js-fraction', - 'js-variable', - ], - css` - @tailwind utilities; - @theme { - /* This should not work */ - --color-custom-css: rgba(255 0 0 / ); - } - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ addUtilities, theme }) { - addUtilities({ - '.css-percentage': { color: theme('colors.custom-css / 50%') }, - '.css-fraction': { color: theme('colors.custom-css / 0.5') }, - '.css-variable': { color: theme('colors.custom-css / var(--opacity)') }, - '.js-percentage': { color: theme('colors.custom-js / 50%') }, - '.js-fraction': { color: theme('colors.custom-js / 0.5') }, - '.js-variable': { color: theme('colors.custom-js / var(--opacity)') }, - }) - }, - { - theme: { - colors: { - /* This should work */ - 'custom-js': 'rgb(255 0 0 / )', - }, - }, - }, - ), - } - }, - }, - ), + compiler.build([ + 'bg-custom', + 'css-percentage', + 'css-fraction', + 'css-variable', + 'js-percentage', + 'js-fraction', + 'js-variable', + ]), ).toMatchInlineSnapshot(` - " - .css-fraction, .css-percentage { + ".css-fraction { + color: color-mix(in oklab, rgba(255 0 0 / ) 50%, transparent); + } + .css-percentage { color: color-mix(in oklab, rgba(255 0 0 / ) 50%, transparent); } - .css-variable { - color: rgba(255 0 0 / ); + color: color-mix(in oklab, rgba(255 0 0 / ) var(--opacity), transparent); } - - @supports (color: color-mix(in lab, red, red)) { - .css-variable { - color: color-mix(in oklab, rgba(255 0 0 / ) var(--opacity), transparent); - } + .js-fraction { + color: color-mix(in oklab, rgb(255 0 0 / 1) 50%, transparent); } - - .js-fraction, .js-percentage { - color: oklab(62.7955% .224 .125 / .5); + .js-percentage { + color: color-mix(in oklab, rgb(255 0 0 / 1) 50%, transparent); } - .js-variable { - color: red; - } - - @supports (color: color-mix(in lab, red, red)) { - .js-variable { - color: color-mix(in oklab, red var(--opacity), transparent); - } + color: color-mix(in oklab, rgb(255 0 0 / 1) var(--opacity), transparent); } " `) }) test('theme value functions are resolved correctly regardless of order', async () => { - expect( - await run( - ['animate-delay-316', 'animate-delay-slow'], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { 'animate-delay': (value) => ({ 'animation-delay': value }) }, - { values: theme('animationDelay') }, - ) + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { + 'animate-delay': (value) => ({ 'animation-delay': value }), }, { - theme: { - extend: { - animationDuration: ({ theme }: { theme: (path: string) => any }) => ({ - ...theme('transitionDuration'), - }), - animationDelay: ({ theme }: { theme: (path: string) => any }) => ({ - ...theme('animationDuration'), - }), - transitionDuration: { slow: '800ms' }, - }, + values: theme('animationDelay'), + }, + ) + }, + { + theme: { + extend: { + animationDuration: ({ theme }: { theme: (path: string) => any }) => ({ + ...theme('transitionDuration'), + }), + + animationDelay: ({ theme }: { theme: (path: string) => any }) => ({ + ...theme('animationDuration'), + }), + + transitionDuration: { + slow: '800ms', }, }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .animate-delay-316 { - animation-delay: .316s; - } + }, + }, + ), + } + }, + }) + expect(compiler.build(['animate-delay-316', 'animate-delay-slow'])).toMatchInlineSnapshot(` + ".animate-delay-316 { + animation-delay: 316ms; + } .animate-delay-slow { - animation-delay: .8s; + animation-delay: 800ms; } " `) }) test('plugins can override the default key', async () => { - expect( - await run( - ['animate-duration'], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { 'animate-duration': (value) => ({ 'animation-delay': value }) }, - { values: theme('transitionDuration') }, - ) + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { + 'animate-duration': (value) => ({ 'animation-delay': value }), }, - { theme: { extend: { transitionDuration: { DEFAULT: '1500ms' } } } }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .animate-duration { - animation-delay: 1.5s; + { + values: theme('transitionDuration'), + }, + ) + }, + { + theme: { + extend: { + transitionDuration: { + DEFAULT: '1500ms', + }, + }, + }, + }, + ), + } + }, + }) + + expect(compiler.build(['animate-duration'])).toMatchInlineSnapshot(` + ".animate-duration { + animation-delay: 1500ms; } " `) }) test('plugins can read CSS theme keys using the old theme key notation', async () => { - expect( - await run( - ['animation-spin', 'animation', 'animation2', 'animation2-twist'], - css` - @tailwind utilities; - @plugin "my-plugin"; - @theme reference { - --animate: pulse 1s linear infinite; - --animate-spin: spin 1s linear infinite; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ matchUtilities, theme }) { - matchUtilities( - { animation: (value) => ({ animation: value }) }, - { values: theme('animation') }, - ) - - matchUtilities( - { animation2: (value) => ({ animation: value }) }, - { - values: { DEFAULT: theme('animation.DEFAULT'), twist: theme('animation.spin') }, - }, - ) - }), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .animation { - animation: 1s linear infinite pulse; + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + @theme reference { + --animate: pulse 1s linear infinite; + --animate-spin: spin 1s linear infinite; } + ` - .animation-spin { - animation: 1s linear infinite spin; - } + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin(function ({ matchUtilities, theme }) { + matchUtilities( + { + animation: (value) => ({ animation: value }), + }, + { + values: theme('animation'), + }, + ) - .animation2 { - animation: 1s linear infinite pulse; - } + matchUtilities( + { + animation2: (value) => ({ animation: value }), + }, + { + values: { + DEFAULT: theme('animation.DEFAULT'), + twist: theme('animation.spin'), + }, + }, + ) + }), + } + }, + }) - .animation2-twist { - animation: 1s linear infinite spin; - } - " - `) + expect(compiler.build(['animation-spin', 'animation', 'animation2', 'animation2-twist'])) + .toMatchInlineSnapshot(` + ".animation { + animation: pulse 1s linear infinite; + } + .animation-spin { + animation: spin 1s linear infinite; + } + .animation2 { + animation: pulse 1s linear infinite; + } + .animation2-twist { + animation: spin 1s linear infinite; + } + " + `) }) test('CSS theme values are merged with JS theme values', async () => { - expect( - await run( - ['animation', 'animation-spin', 'animation-bounce'], - css` - @tailwind utilities; - @plugin "my-plugin"; - @theme reference { - --animate: pulse 1s linear infinite; - --animate-spin: spin 1s linear infinite; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { animation: (value) => ({ '--animation': value }) }, - { values: theme('animation') }, - ) + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + @theme reference { + --animate: pulse 1s linear infinite; + --animate-spin: spin 1s linear infinite; + } + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { + animation: (value) => ({ '--animation': value }), }, - { theme: { extend: { animation: { bounce: 'bounce 1s linear infinite' } } } }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .animation { - --animation: pulse 1s linear infinite; - } + { + values: theme('animation'), + }, + ) + }, + { + theme: { + extend: { + animation: { + bounce: 'bounce 1s linear infinite', + }, + }, + }, + }, + ), + } + }, + }) - .animation-bounce { - --animation: bounce 1s linear infinite; - } - - .animation-spin { - --animation: spin 1s linear infinite; - } - " - `) + expect(compiler.build(['animation', 'animation-spin', 'animation-bounce'])) + .toMatchInlineSnapshot(` + ".animation { + --animation: pulse 1s linear infinite; + } + .animation-bounce { + --animation: bounce 1s linear infinite; + } + .animation-spin { + --animation: spin 1s linear infinite; + } + " + `) }) test('CSS theme defaults take precedence over JS theme defaults', async () => { - expect( - await run( - ['animation'], - css` - @tailwind utilities; - @plugin "my-plugin"; - @theme reference { - --animate: pulse 1s linear infinite; - --animate-spin: spin 1s linear infinite; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { animation: (value) => ({ '--animation': value }) }, - { values: theme('animation') }, - ) + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + @theme reference { + --animate: pulse 1s linear infinite; + --animate-spin: spin 1s linear infinite; + } + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { + animation: (value) => ({ '--animation': value }), }, - { theme: { extend: { animation: { DEFAULT: 'twist 1s linear infinite' } } } }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .animation { + { + values: theme('animation'), + }, + ) + }, + { + theme: { + extend: { + animation: { + DEFAULT: 'twist 1s linear infinite', + }, + }, + }, + }, + ), + } + }, + }) + + expect(compiler.build(['animation'])).toMatchInlineSnapshot(` + ".animation { --animation: pulse 1s linear infinite; } " @@ -609,19 +646,24 @@ describe('theme', async () => { } ` - using fn = vi.fn() + let fn = vi.fn() await compile(input, { - loadModule: async (_id, base) => { + loadModule: async (id, base) => { return { - path: '', base, module: plugin( function ({ theme }) { fn(theme('animation.simple')) }, { - theme: { extend: { animation: { simple: 'simple 1s linear' } } }, + theme: { + extend: { + animation: { + simple: 'simple 1s linear', + }, + }, + }, }, ), } @@ -635,259 +677,240 @@ describe('theme', async () => { }) }) - test('theme() resolves the DEFAULT value when a bare CSS theme key shares a prefix with a sibling key', async () => { - expect( - await run( - ['example-foo', 'example-foo-bar'], - css` - @tailwind utilities; - @theme { - --color-foo: red; - --color-foo-bar: blue; - } - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ addUtilities, theme }) { - addUtilities({ - '.example-foo': { color: theme('colors.foo') }, - '.example-foo-bar': { color: theme('colors.foo-bar') }, - }) - }), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .example-foo { - color: red; - } - - .example-foo-bar { - color: #00f; - } - " - `) - }) - test('all necessary theme keys support bare values', async () => { - expect( - await run( - [ - 'my-aspect-2/5', - 'my-backdrop-brightness-1', - 'my-backdrop-contrast-1', - 'my-backdrop-grayscale-1', - 'my-backdrop-hue-rotate-1', - 'my-backdrop-invert-1', - 'my-backdrop-opacity-1', - 'my-backdrop-saturate-1', - 'my-backdrop-sepia-1', - 'my-border-width-1', - 'my-brightness-1', - 'my-columns-1', - 'my-contrast-1', - 'my-divide-width-1', - 'my-flex-grow-1', - 'my-flex-shrink-1', - 'my-gradient-color-stop-positions-1', - 'my-grayscale-1', - 'my-grid-row-end-1', - 'my-grid-row-start-1', - 'my-grid-template-columns-1', - 'my-grid-template-rows-1', - 'my-hue-rotate-1', - 'my-invert-1', - 'my-line-clamp-1', - 'my-opacity-1', - 'my-order-1', - 'my-outline-offset-1', - 'my-outline-width-1', - 'my-ring-offset-width-1', - 'my-ring-width-1', - 'my-rotate-1', - 'my-saturate-1', - 'my-scale-1', - 'my-sepia-1', - 'my-skew-1', - 'my-stroke-width-1', - 'my-text-decoration-thickness-1', - 'my-text-underline-offset-1', - 'my-transition-delay-1', - 'my-transition-duration-1', - 'my-z-index-1', - ], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ matchUtilities, theme }) { - function utility(name: string, themeKey: string) { - matchUtilities( - { [name]: (value) => ({ '--value': value }) }, - { values: theme(themeKey) }, - ) - } + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` - utility('my-aspect', 'aspectRatio') - utility('my-backdrop-brightness', 'backdropBrightness') - utility('my-backdrop-contrast', 'backdropContrast') - utility('my-backdrop-grayscale', 'backdropGrayscale') - utility('my-backdrop-hue-rotate', 'backdropHueRotate') - utility('my-backdrop-invert', 'backdropInvert') - utility('my-backdrop-opacity', 'backdropOpacity') - utility('my-backdrop-saturate', 'backdropSaturate') - utility('my-backdrop-sepia', 'backdropSepia') - utility('my-border-width', 'borderWidth') - utility('my-brightness', 'brightness') - utility('my-columns', 'columns') - utility('my-contrast', 'contrast') - utility('my-divide-width', 'divideWidth') - utility('my-flex-grow', 'flexGrow') - utility('my-flex-shrink', 'flexShrink') - utility('my-gradient-color-stop-positions', 'gradientColorStopPositions') - utility('my-grayscale', 'grayscale') - utility('my-grid-row-end', 'gridRowEnd') - utility('my-grid-row-start', 'gridRowStart') - utility('my-grid-template-columns', 'gridTemplateColumns') - utility('my-grid-template-rows', 'gridTemplateRows') - utility('my-hue-rotate', 'hueRotate') - utility('my-invert', 'invert') - utility('my-line-clamp', 'lineClamp') - utility('my-opacity', 'opacity') - utility('my-order', 'order') - utility('my-outline-offset', 'outlineOffset') - utility('my-outline-width', 'outlineWidth') - utility('my-ring-offset-width', 'ringOffsetWidth') - utility('my-ring-width', 'ringWidth') - utility('my-rotate', 'rotate') - utility('my-saturate', 'saturate') - utility('my-scale', 'scale') - utility('my-sepia', 'sepia') - utility('my-skew', 'skew') - utility('my-stroke-width', 'strokeWidth') - utility('my-text-decoration-thickness', 'textDecorationThickness') - utility('my-text-underline-offset', 'textUnderlineOffset') - utility('my-transition-delay', 'transitionDelay') - utility('my-transition-duration', 'transitionDuration') - utility('my-z-index', 'zIndex') - }), + let { build } = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin(function ({ matchUtilities, theme }) { + function utility(name: string, themeKey: string) { + matchUtilities( + { [name]: (value) => ({ '--value': value }) }, + { values: theme(themeKey) }, + ) } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .my-aspect-2\\/5 { + + utility('my-aspect', 'aspectRatio') + utility('my-backdrop-brightness', 'backdropBrightness') + utility('my-backdrop-contrast', 'backdropContrast') + utility('my-backdrop-grayscale', 'backdropGrayscale') + utility('my-backdrop-hue-rotate', 'backdropHueRotate') + utility('my-backdrop-invert', 'backdropInvert') + utility('my-backdrop-opacity', 'backdropOpacity') + utility('my-backdrop-saturate', 'backdropSaturate') + utility('my-backdrop-sepia', 'backdropSepia') + utility('my-border-width', 'borderWidth') + utility('my-brightness', 'brightness') + utility('my-columns', 'columns') + utility('my-contrast', 'contrast') + utility('my-divide-width', 'divideWidth') + utility('my-flex-grow', 'flexGrow') + utility('my-flex-shrink', 'flexShrink') + utility('my-gradient-color-stop-positions', 'gradientColorStopPositions') + utility('my-grayscale', 'grayscale') + utility('my-grid-row-end', 'gridRowEnd') + utility('my-grid-row-start', 'gridRowStart') + utility('my-grid-template-columns', 'gridTemplateColumns') + utility('my-grid-template-rows', 'gridTemplateRows') + utility('my-hue-rotate', 'hueRotate') + utility('my-invert', 'invert') + utility('my-line-clamp', 'lineClamp') + utility('my-opacity', 'opacity') + utility('my-order', 'order') + utility('my-outline-offset', 'outlineOffset') + utility('my-outline-width', 'outlineWidth') + utility('my-ring-offset-width', 'ringOffsetWidth') + utility('my-ring-width', 'ringWidth') + utility('my-rotate', 'rotate') + utility('my-saturate', 'saturate') + utility('my-scale', 'scale') + utility('my-sepia', 'sepia') + utility('my-skew', 'skew') + utility('my-stroke-width', 'strokeWidth') + utility('my-text-decoration-thickness', 'textDecorationThickness') + utility('my-text-underline-offset', 'textUnderlineOffset') + utility('my-transition-delay', 'transitionDelay') + utility('my-transition-duration', 'transitionDuration') + utility('my-z-index', 'zIndex') + }), + } + }, + }) + + let output = build([ + 'my-aspect-2/5', + 'my-backdrop-brightness-1', + 'my-backdrop-contrast-1', + 'my-backdrop-grayscale-1', + 'my-backdrop-hue-rotate-1', + 'my-backdrop-invert-1', + 'my-backdrop-opacity-1', + 'my-backdrop-saturate-1', + 'my-backdrop-sepia-1', + 'my-border-width-1', + 'my-brightness-1', + 'my-columns-1', + 'my-contrast-1', + 'my-divide-width-1', + 'my-flex-grow-1', + 'my-flex-shrink-1', + 'my-gradient-color-stop-positions-1', + 'my-grayscale-1', + 'my-grid-row-end-1', + 'my-grid-row-start-1', + 'my-grid-template-columns-1', + 'my-grid-template-rows-1', + 'my-hue-rotate-1', + 'my-invert-1', + 'my-line-clamp-1', + 'my-opacity-1', + 'my-order-1', + 'my-outline-offset-1', + 'my-outline-width-1', + 'my-ring-offset-width-1', + 'my-ring-width-1', + 'my-rotate-1', + 'my-saturate-1', + 'my-scale-1', + 'my-sepia-1', + 'my-skew-1', + 'my-stroke-width-1', + 'my-text-decoration-thickness-1', + 'my-text-underline-offset-1', + 'my-transition-delay-1', + 'my-transition-duration-1', + 'my-z-index-1', + ]) + + expect(output).toMatchInlineSnapshot(` + ".my-aspect-2\\/5 { --value: 2/5; } - - .my-backdrop-brightness-1, .my-backdrop-contrast-1, .my-backdrop-grayscale-1 { + .my-backdrop-brightness-1 { + --value: 1%; + } + .my-backdrop-contrast-1 { + --value: 1%; + } + .my-backdrop-grayscale-1 { --value: 1%; } - .my-backdrop-hue-rotate-1 { --value: 1deg; } - - .my-backdrop-invert-1, .my-backdrop-opacity-1, .my-backdrop-saturate-1, .my-backdrop-sepia-1 { + .my-backdrop-invert-1 { + --value: 1%; + } + .my-backdrop-opacity-1 { + --value: 1%; + } + .my-backdrop-saturate-1 { + --value: 1%; + } + .my-backdrop-sepia-1 { --value: 1%; } - .my-border-width-1 { --value: 1px; } - .my-brightness-1 { --value: 1%; } - .my-columns-1 { --value: 1; } - .my-contrast-1 { --value: 1%; } - .my-divide-width-1 { --value: 1px; } - - .my-flex-grow-1, .my-flex-shrink-1 { + .my-flex-grow-1 { --value: 1; } - - .my-gradient-color-stop-positions-1, .my-grayscale-1 { + .my-flex-shrink-1 { + --value: 1; + } + .my-gradient-color-stop-positions-1 { --value: 1%; } - - .my-grid-row-end-1, .my-grid-row-start-1 { + .my-grayscale-1 { + --value: 1%; + } + .my-grid-row-end-1 { --value: 1; } - - .my-grid-template-columns-1, .my-grid-template-rows-1 { + .my-grid-row-start-1 { + --value: 1; + } + .my-grid-template-columns-1 { + --value: repeat(1, minmax(0, 1fr)); + } + .my-grid-template-rows-1 { --value: repeat(1, minmax(0, 1fr)); } - .my-hue-rotate-1 { --value: 1deg; } - .my-invert-1 { --value: 1%; } - .my-line-clamp-1 { --value: 1; } - .my-opacity-1 { --value: 1%; } - .my-order-1 { --value: 1; } - - .my-outline-offset-1, .my-outline-width-1, .my-ring-offset-width-1, .my-ring-width-1 { + .my-outline-offset-1 { + --value: 1px; + } + .my-outline-width-1 { + --value: 1px; + } + .my-ring-offset-width-1 { + --value: 1px; + } + .my-ring-width-1 { --value: 1px; } - .my-rotate-1 { --value: 1deg; } - - .my-saturate-1, .my-scale-1, .my-sepia-1 { + .my-saturate-1 { + --value: 1%; + } + .my-scale-1 { + --value: 1%; + } + .my-sepia-1 { --value: 1%; } - .my-skew-1 { --value: 1deg; } - .my-stroke-width-1 { --value: 1; } - - .my-text-decoration-thickness-1, .my-text-underline-offset-1 { + .my-text-decoration-thickness-1 { --value: 1px; } - - .my-transition-delay-1, .my-transition-duration-1 { + .my-text-underline-offset-1 { + --value: 1px; + } + .my-transition-delay-1 { + --value: 1ms; + } + .my-transition-duration-1 { --value: 1ms; } - .my-z-index-1 { --value: 1; } @@ -905,12 +928,11 @@ describe('theme', async () => { } ` - using fn = vi.fn() + let fn = vi.fn() await compile(input, { - loadModule: async (_id, base) => { + loadModule: async (id, base) => { return { - path: '', base, module: plugin( ({ theme }) => { @@ -948,12 +970,11 @@ describe('theme', async () => { } ` - using fn = vi.fn() + let fn = vi.fn() await compile(input, { - loadModule: async (_id, base) => { + loadModule: async (id, base) => { return { - path: '', base, module: plugin(({ theme }) => { fn(theme('transitionTimingFunction.DEFAULT')) @@ -980,12 +1001,11 @@ describe('theme', async () => { } ` - using fn = vi.fn() + let fn = vi.fn() await compile(input, { - loadModule: async (_id, base) => { + loadModule: async (id, base) => { return { - path: '', base, module: plugin(({ theme }) => { fn(theme('color.red.100')) @@ -1009,12 +1029,11 @@ describe('theme', async () => { @plugin "my-plugin"; ` - using fn = vi.fn() + let fn = vi.fn() await compile(input, { - loadModule: async (_id, base) => { + loadModule: async (id, base) => { return { - path: '', base, module: plugin(({ theme }) => { fn(theme('i.do.not.exist')) @@ -1033,242 +1052,271 @@ describe('theme', async () => { }) test('Candidates can match multiple utility definitions', async () => { - expect( - await run( - ['foo-bar'], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(({ addUtilities, matchUtilities }) => { - addUtilities({ '.foo-bar': { color: 'red' } }) - matchUtilities( - { foo: (value) => ({ '--my-prop': value }) }, - { values: { bar: 'bar-valuer', baz: 'bar-valuer' } }, - ) - addUtilities({ '.foo-bar': { backgroundColor: 'red' } }) - }), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let { build } = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin(({ addUtilities, matchUtilities }) => { + addUtilities({ + '.foo-bar': { + color: 'red', + }, + }) + + matchUtilities( + { + foo: (value) => ({ + '--my-prop': value, + }), + }, + { + values: { + bar: 'bar-valuer', + baz: 'bar-valuer', + }, + }, + ) + + addUtilities({ + '.foo-bar': { + backgroundColor: 'red', + }, + }) + }), + } + }, + }) + + expect(build(['foo-bar'])).toMatchInlineSnapshot(` + ".foo-bar { + background-color: red; + } .foo-bar { color: red; + } + .foo-bar { --my-prop: bar-valuer; - background-color: red; } " `) }) test('spreading `tailwindcss/defaultTheme` exports keeps bare values', async () => { - expect( - await run( - [ - 'my-aspect-2/5', - // 'my-backdrop-brightness-1', - // 'my-backdrop-contrast-1', - // 'my-backdrop-grayscale-1', - // 'my-backdrop-hue-rotate-1', - // 'my-backdrop-invert-1', - // 'my-backdrop-opacity-1', - // 'my-backdrop-saturate-1', - // 'my-backdrop-sepia-1', - // 'my-divide-width-1', - 'my-border-width-1', - 'my-brightness-1', - 'my-columns-1', - 'my-contrast-1', - 'my-flex-grow-1', - 'my-flex-shrink-1', - 'my-gradient-color-stop-positions-1', - 'my-grayscale-1', - 'my-grid-row-end-1', - 'my-grid-row-start-1', - 'my-grid-template-columns-1', - 'my-grid-template-rows-1', - 'my-hue-rotate-1', - 'my-invert-1', - 'my-line-clamp-1', - 'my-opacity-1', - 'my-order-1', - 'my-outline-offset-1', - 'my-outline-width-1', - 'my-ring-offset-width-1', - 'my-ring-width-1', - 'my-rotate-1', - 'my-saturate-1', - 'my-scale-1', - 'my-sepia-1', - 'my-skew-1', - 'my-stroke-width-1', - 'my-text-decoration-thickness-1', - 'my-text-underline-offset-1', - 'my-transition-delay-1', - 'my-transition-duration-1', - 'my-z-index-1', - ], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ matchUtilities }) { - function utility(name: string, themeKey: string) { - matchUtilities( - { [name]: (value) => ({ '--value': value }) }, - // @ts-ignore - { values: defaultTheme[themeKey] }, - ) - } + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` - utility('my-aspect', 'aspectRatio') - // The following keys deliberately doesn't work as these are exported - // as functions from the compat config. - // - // utility('my-backdrop-brightness', 'backdropBrightness') - // utility('my-backdrop-contrast', 'backdropContrast') - // utility('my-backdrop-grayscale', 'backdropGrayscale') - // utility('my-backdrop-hue-rotate', 'backdropHueRotate') - // utility('my-backdrop-invert', 'backdropInvert') - // utility('my-backdrop-opacity', 'backdropOpacity') - // utility('my-backdrop-saturate', 'backdropSaturate') - // utility('my-backdrop-sepia', 'backdropSepia') - // utility('my-divide-width', 'divideWidth') - utility('my-border-width', 'borderWidth') - utility('my-brightness', 'brightness') - utility('my-columns', 'columns') - utility('my-contrast', 'contrast') - utility('my-flex-grow', 'flexGrow') - utility('my-flex-shrink', 'flexShrink') - utility('my-gradient-color-stop-positions', 'gradientColorStopPositions') - utility('my-grayscale', 'grayscale') - utility('my-grid-row-end', 'gridRowEnd') - utility('my-grid-row-start', 'gridRowStart') - utility('my-grid-template-columns', 'gridTemplateColumns') - utility('my-grid-template-rows', 'gridTemplateRows') - utility('my-hue-rotate', 'hueRotate') - utility('my-invert', 'invert') - utility('my-line-clamp', 'lineClamp') - utility('my-opacity', 'opacity') - utility('my-order', 'order') - utility('my-outline-offset', 'outlineOffset') - utility('my-outline-width', 'outlineWidth') - utility('my-ring-offset-width', 'ringOffsetWidth') - utility('my-ring-width', 'ringWidth') - utility('my-rotate', 'rotate') - utility('my-saturate', 'saturate') - utility('my-scale', 'scale') - utility('my-sepia', 'sepia') - utility('my-skew', 'skew') - utility('my-stroke-width', 'strokeWidth') - utility('my-text-decoration-thickness', 'textDecorationThickness') - utility('my-text-underline-offset', 'textUnderlineOffset') - utility('my-transition-delay', 'transitionDelay') - utility('my-transition-duration', 'transitionDuration') - utility('my-z-index', 'zIndex') - }), + let { build } = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin(function ({ matchUtilities }) { + function utility(name: string, themeKey: string) { + matchUtilities( + { [name]: (value) => ({ '--value': value }) }, + // @ts-ignore + { values: defaultTheme[themeKey] }, + ) } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .my-aspect-2\\/5 { + + utility('my-aspect', 'aspectRatio') + // The following keys deliberately doesn't work as these are exported + // as functions from the compat config. + // + // utility('my-backdrop-brightness', 'backdropBrightness') + // utility('my-backdrop-contrast', 'backdropContrast') + // utility('my-backdrop-grayscale', 'backdropGrayscale') + // utility('my-backdrop-hue-rotate', 'backdropHueRotate') + // utility('my-backdrop-invert', 'backdropInvert') + // utility('my-backdrop-opacity', 'backdropOpacity') + // utility('my-backdrop-saturate', 'backdropSaturate') + // utility('my-backdrop-sepia', 'backdropSepia') + // utility('my-divide-width', 'divideWidth') + utility('my-border-width', 'borderWidth') + utility('my-brightness', 'brightness') + utility('my-columns', 'columns') + utility('my-contrast', 'contrast') + utility('my-flex-grow', 'flexGrow') + utility('my-flex-shrink', 'flexShrink') + utility('my-gradient-color-stop-positions', 'gradientColorStopPositions') + utility('my-grayscale', 'grayscale') + utility('my-grid-row-end', 'gridRowEnd') + utility('my-grid-row-start', 'gridRowStart') + utility('my-grid-template-columns', 'gridTemplateColumns') + utility('my-grid-template-rows', 'gridTemplateRows') + utility('my-hue-rotate', 'hueRotate') + utility('my-invert', 'invert') + utility('my-line-clamp', 'lineClamp') + utility('my-opacity', 'opacity') + utility('my-order', 'order') + utility('my-outline-offset', 'outlineOffset') + utility('my-outline-width', 'outlineWidth') + utility('my-ring-offset-width', 'ringOffsetWidth') + utility('my-ring-width', 'ringWidth') + utility('my-rotate', 'rotate') + utility('my-saturate', 'saturate') + utility('my-scale', 'scale') + utility('my-sepia', 'sepia') + utility('my-skew', 'skew') + utility('my-stroke-width', 'strokeWidth') + utility('my-text-decoration-thickness', 'textDecorationThickness') + utility('my-text-underline-offset', 'textUnderlineOffset') + utility('my-transition-delay', 'transitionDelay') + utility('my-transition-duration', 'transitionDuration') + utility('my-z-index', 'zIndex') + }), + } + }, + }) + + let output = build([ + 'my-aspect-2/5', + // 'my-backdrop-brightness-1', + // 'my-backdrop-contrast-1', + // 'my-backdrop-grayscale-1', + // 'my-backdrop-hue-rotate-1', + // 'my-backdrop-invert-1', + // 'my-backdrop-opacity-1', + // 'my-backdrop-saturate-1', + // 'my-backdrop-sepia-1', + // 'my-divide-width-1', + 'my-border-width-1', + 'my-brightness-1', + 'my-columns-1', + 'my-contrast-1', + 'my-flex-grow-1', + 'my-flex-shrink-1', + 'my-gradient-color-stop-positions-1', + 'my-grayscale-1', + 'my-grid-row-end-1', + 'my-grid-row-start-1', + 'my-grid-template-columns-1', + 'my-grid-template-rows-1', + 'my-hue-rotate-1', + 'my-invert-1', + 'my-line-clamp-1', + 'my-opacity-1', + 'my-order-1', + 'my-outline-offset-1', + 'my-outline-width-1', + 'my-ring-offset-width-1', + 'my-ring-width-1', + 'my-rotate-1', + 'my-saturate-1', + 'my-scale-1', + 'my-sepia-1', + 'my-skew-1', + 'my-stroke-width-1', + 'my-text-decoration-thickness-1', + 'my-text-underline-offset-1', + 'my-transition-delay-1', + 'my-transition-duration-1', + 'my-z-index-1', + ]) + + expect(output).toMatchInlineSnapshot(` + ".my-aspect-2\\/5 { --value: 2/5; } - .my-border-width-1 { --value: 1px; } - .my-brightness-1 { --value: 1%; } - .my-columns-1 { --value: 1; } - .my-contrast-1 { --value: 1%; } - - .my-flex-grow-1, .my-flex-shrink-1 { + .my-flex-grow-1 { --value: 1; } - - .my-gradient-color-stop-positions-1, .my-grayscale-1 { + .my-flex-shrink-1 { + --value: 1; + } + .my-gradient-color-stop-positions-1 { --value: 1%; } - - .my-grid-row-end-1, .my-grid-row-start-1 { + .my-grayscale-1 { + --value: 1%; + } + .my-grid-row-end-1 { --value: 1; } - - .my-grid-template-columns-1, .my-grid-template-rows-1 { + .my-grid-row-start-1 { + --value: 1; + } + .my-grid-template-columns-1 { + --value: repeat(1, minmax(0, 1fr)); + } + .my-grid-template-rows-1 { --value: repeat(1, minmax(0, 1fr)); } - .my-hue-rotate-1 { --value: 1deg; } - .my-invert-1 { --value: 1%; } - .my-line-clamp-1 { --value: 1; } - .my-opacity-1 { --value: 1%; } - .my-order-1 { --value: 1; } - - .my-outline-offset-1, .my-outline-width-1, .my-ring-offset-width-1, .my-ring-width-1 { + .my-outline-offset-1 { + --value: 1px; + } + .my-outline-width-1 { + --value: 1px; + } + .my-ring-offset-width-1 { + --value: 1px; + } + .my-ring-width-1 { --value: 1px; } - .my-rotate-1 { --value: 1deg; } - - .my-saturate-1, .my-scale-1, .my-sepia-1 { + .my-saturate-1 { + --value: 1%; + } + .my-scale-1 { + --value: 1%; + } + .my-sepia-1 { --value: 1%; } - .my-skew-1 { --value: 1deg; } - .my-stroke-width-1 { --value: 1; } - - .my-text-decoration-thickness-1, .my-text-underline-offset-1 { + .my-text-decoration-thickness-1 { --value: 1px; } - - .my-transition-delay-1, .my-transition-duration-1 { + .my-text-underline-offset-1 { + --value: 1px; + } + .my-transition-delay-1 { + --value: 1ms; + } + .my-transition-duration-1 { --value: 1ms; } - .my-z-index-1 { --value: 1; } @@ -1277,211 +1325,190 @@ describe('theme', async () => { }) test('can use escaped JS variables in theme values', async () => { - expect( - await run( - ['my-width-1', 'my-width-1/2', 'my-width-1.5'], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { 'my-width': (value) => ({ width: value }) }, - { values: theme('width') }, - ) - }, - { - theme: { - extend: { - width: { - '1': '0.25rem', - // Purposely setting to something different from the v3 default - '1/2': '60%', - '1.5': '0.375rem', - }, - }, + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { 'my-width': (value) => ({ width: value }) }, + { values: theme('width') }, + ) + }, + { + theme: { + extend: { + width: { + '1': '0.25rem', + // Purposely setting to something different from the v3 default + '1/2': '60%', + '1.5': '0.375rem', }, }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .my-width-1 { - width: .25rem; - } + }, + }, + ), + } + }, + }) + expect(compiler.build(['my-width-1', 'my-width-1/2', 'my-width-1.5'])).toMatchInlineSnapshot( + ` + ".my-width-1 { + width: 0.25rem; + } .my-width-1\\.5 { - width: .375rem; + width: 0.375rem; } - .my-width-1\\/2 { width: 60%; } " - `) + `, + ) }) test('can use escaped CSS variables in theme values', async () => { - expect( - await run( - ['my-width-1', 'my-width-1.5', 'my-width-1/2', 'my-width-2.5'], - css` - @tailwind utilities; - @plugin "my-plugin"; + let input = css` + @tailwind utilities; + @plugin "my-plugin"; - @theme { - --width-1: 0.25rem; - /* Purposely setting to something different from the v3 default */ - --width-1\/2: 60%; - --width-1\.5: 0.375rem; - --width-2_5: 0.625rem; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ matchUtilities, theme }) { - matchUtilities( - { 'my-width': (value) => ({ width: value }) }, - { values: theme('width') }, - ) - }), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .my-width-1 { - width: .25rem; + @theme { + --width-1: 0.25rem; + /* Purposely setting to something different from the v3 default */ + --width-1\/2: 60%; + --width-1\.5: 0.375rem; + --width-2_5: 0.625rem; } + ` - .my-width-1\\.5 { - width: .375rem; - } + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin(function ({ matchUtilities, theme }) { + matchUtilities( + { 'my-width': (value) => ({ width: value }) }, + { values: theme('width') }, + ) + }), + } + }, + }) - .my-width-1\\/2 { - width: 60%; - } - - .my-width-2\\.5 { - width: .625rem; - } - " - `) + expect(compiler.build(['my-width-1', 'my-width-1.5', 'my-width-1/2', 'my-width-2.5'])) + .toMatchInlineSnapshot(` + ".my-width-1 { + width: 0.25rem; + } + .my-width-1\\.5 { + width: 0.375rem; + } + .my-width-1\\/2 { + width: 60%; + } + .my-width-2\\.5 { + width: 0.625rem; + } + " + `) }) test('can use escaped CSS variables in referenced theme namespace', async () => { - expect( - await run( - ['my-width-1', 'my-width-1.5', 'my-width-1/2', 'my-width-2.5'], - css` - @tailwind utilities; - @plugin "my-plugin"; + let input = css` + @tailwind utilities; + @plugin "my-plugin"; - @theme { - --width-1: 0.25rem; - /* Purposely setting to something different from the v3 default */ - --width-1\/2: 60%; - --width-1\.5: 0.375rem; - --width-2_5: 0.625rem; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { 'my-width': (value) => ({ width: value }) }, - { values: theme('myWidth') }, - ) - }, - { - theme: { myWidth: ({ theme }) => theme('width') }, - }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .my-width-1 { - width: .25rem; + @theme { + --width-1: 0.25rem; + /* Purposely setting to something different from the v3 default */ + --width-1\/2: 60%; + --width-1\.5: 0.375rem; + --width-2_5: 0.625rem; } + ` - .my-width-1\\.5 { - width: .375rem; - } + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { 'my-width': (value) => ({ width: value }) }, + { values: theme('myWidth') }, + ) + }, + { + theme: { myWidth: ({ theme }) => theme('width') }, + }, + ), + } + }, + }) - .my-width-1\\/2 { - width: 60%; - } - - .my-width-2\\.5 { - width: .625rem; - } - " - `) + expect(compiler.build(['my-width-1', 'my-width-1.5', 'my-width-1/2', 'my-width-2.5'])) + .toMatchInlineSnapshot(` + ".my-width-1 { + width: 0.25rem; + } + .my-width-1\\.5 { + width: 0.375rem; + } + .my-width-1\\/2 { + width: 60%; + } + .my-width-2\\.5 { + width: 0.625rem; + } + " + `) }) }) describe('addBase', () => { test('does not create rules when imported via `@import "…" reference`', async () => { - expect( - await compileCss( - css` - @tailwind utilities; - @plugin "outside"; - @import './inside.css' reference; - `, - { - loadModule: async (_id, base) => { - if (_id === 'inside') { - return { - path: '', - base, - module: plugin(function ({ addBase }) { - addBase({ inside: { color: 'red' } }) - }), - } - } - return { - path: '', - base, - module: plugin(function ({ addBase }) { - addBase({ outside: { color: 'red' } }) - }), - } - }, - async loadStylesheet() { - return { - path: '', - base: '', - content: css` - @plugin "inside"; - `, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer base { + let input = css` + @tailwind utilities; + @plugin "outside"; + @import './inside.css' reference; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + if (id === 'inside') { + return { + base, + module: plugin(function ({ addBase }) { + addBase({ inside: { color: 'red' } }) + }), + } + } + return { + base, + module: plugin(function ({ addBase }) { + addBase({ outside: { color: 'red' } }) + }), + } + }, + async loadStylesheet() { + return { + content: css` + @plugin "inside"; + `, + base: '', + } + }, + }) + + expect(compiler.build([])).toMatchInlineSnapshot(` + "@layer base { outside { color: red; } @@ -1491,24 +1518,27 @@ describe('addBase', () => { }) test('does not modify CSS variables', async () => { - expect( - await compileCss( - css` - @plugin "my-plugin"; - `, - { - loadModule: async () => ({ - path: '', - base: '/root', - module: plugin(function ({ addBase }) { - addBase({ ':root': { '--PascalCase': '1', '--camelCase': '1', '--UPPERCASE': '1' } }) - }), - }), - }, - ), - ).toMatchInlineSnapshot(` - " - @layer base { + let input = css` + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: plugin(function ({ addBase }) { + addBase({ + ':root': { + '--PascalCase': '1', + '--camelCase': '1', + '--UPPERCASE': '1', + }, + }) + }), + base: '/root', + }), + }) + + expect(compiler.build([])).toMatchInlineSnapshot(` + "@layer base { :root { --PascalCase: 1; --camelCase: 1; @@ -1518,321 +1548,150 @@ describe('addBase', () => { " `) }) - - test('@variant is replaced inside rules using addBase', async () => { - expect( - await compileCss( - css` - @plugin "my-plugin"; - @theme { - --breakpoint-supertiny: 128px; - } - `, - { - loadModule: async () => ({ - path: '', - base: '/root', - module: plugin(function ({ addBase }) { - addBase({ - ':root': { - '@variant supertiny': { - '--PascalCase': '1', - '--camelCase': '1', - '--UPPERCASE': '1', - }, - '@variant data-enabled:focus, disabled': { - '--x': '1', - }, - }, - }) - }), - }), - }, - ), - ).toMatchInlineSnapshot(` - " - @layer base { - @media (min-width: 128px) { - :root { - --PascalCase: 1; - --camelCase: 1; - --UPPERCASE: 1; - } - } - - :root[data-enabled]:focus, :root:disabled { - --x: 1; - } - } - " - `) - }) - - test('@variant inside addBase works with custom variants', async () => { - expect( - await compileCss( - css` - @plugin "my-plugin"; - - @custom-variant custom (&.custom); - `, - { - loadModule: async () => ({ - path: '', - base: '/root', - module: plugin(function ({ addBase }) { - addBase({ - ':root': { - '@variant custom': { - '--x': '1', - }, - }, - }) - }), - }), - }, - ), - ).toMatchInlineSnapshot(` - " - @layer base { - :root.custom { - --x: 1; - } - } - " - `) - }) }) describe('addVariant', () => { test('addVariant with string selector', async () => { - expect( - await run( - ['hocus:underline', 'group-hocus:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ addVariant }: PluginAPI) => { + addVariant('hocus', '&:hover, &:focus') + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant('hocus', '&:hover, &:focus') - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - :is(:is(:where(.group):hover, :where(.group):focus) .group-hocus\\:flex) { + }, + ) + let compiled = build(['hocus:underline', 'group-hocus:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + .group-hocus\\:flex:is(:is(:where(.group):hover, :where(.group):focus) *) { display: flex; } .hocus\\:underline:hover, .hocus\\:underline:focus { text-decoration-line: underline; } - } - " - `) - }) - - test('addVariant with `@scope`', async () => { - expect( - await run( - ['blue:underline', 'green:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant('blue', '@scope ([data-theme=blue]) to ([data-theme])') - addVariant('green', '@scope ([data-theme=green]) to ([data-theme]) { & }') - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @scope ([data-theme="blue"]) to ([data-theme]) { - .blue\\:underline { - text-decoration-line: underline; - } - } - - @scope ([data-theme="green"]) to ([data-theme]) { - .green\\:underline { - text-decoration-line: underline; - } - } - } - " - `) - }) - - test('matchVariant with `@scope`', async () => { - expect( - await run( - ['theme-[blue]:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('theme', (value) => `@scope ([data-theme=${value}]) to ([data-theme])`) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @scope ([data-theme="blue"]) to ([data-theme]) { - .theme-\\[blue\\]\\:underline { - text-decoration-line: underline; - } - } - } - " + }" `) }) test('addVariant with array of selectors', async () => { - expect( - await run( - ['hocus:underline', 'group-hocus:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ addVariant }: PluginAPI) => { + addVariant('hocus', ['&:hover', '&:focus']) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant('hocus', ['&:hover', '&:focus']) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - :is(:where(.group):hover .group-hocus\\:flex), :is(:where(.group):focus .group-hocus\\:flex) { + }, + ) + + let compiled = build(['hocus:underline', 'group-hocus:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + .group-hocus\\:flex:is(:where(.group):hover *), .group-hocus\\:flex:is(:where(.group):focus *) { display: flex; } .hocus\\:underline:hover, .hocus\\:underline:focus { text-decoration-line: underline; } - } - " + }" `) }) test('addVariant with object syntax and @slot', async () => { - expect( - await run( - ['hocus:underline', 'group-hocus:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ addVariant }: PluginAPI) => { + addVariant('hocus', { + '&:hover': '@slot', + '&:focus': '@slot', + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant('hocus', { - '&:hover': '@slot', - '&:focus': '@slot', - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - :is(:where(.group):hover .group-hocus\\:flex), :is(:where(.group):focus .group-hocus\\:flex) { + }, + ) + let compiled = build(['hocus:underline', 'group-hocus:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + .group-hocus\\:flex:is(:where(.group):hover *), .group-hocus\\:flex:is(:where(.group):focus *) { display: flex; } .hocus\\:underline:hover, .hocus\\:underline:focus { text-decoration-line: underline; } - } - " + }" `) }) test('addVariant with object syntax, media, nesting and multiple @slot', async () => { - expect( - await run( - ['hocus:underline', 'group-hocus:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ addVariant }: PluginAPI) => { + addVariant('hocus', { + '@media (hover: hover)': { + '&:hover': '@slot', + }, + '&:focus': '@slot', + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant('hocus', { - '@media (hover: hover)': { - '&:hover': '@slot', - }, - '&:focus': '@slot', - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['hocus:underline', 'group-hocus:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { @media (hover: hover) { - :is(:where(.group):hover .group-hocus\\:flex) { + .group-hocus\\:flex:is(:where(.group):hover *) { display: flex; } } - :is(:where(.group):focus .group-hocus\\:flex) { + .group-hocus\\:flex:is(:where(.group):focus *) { display: flex; } @@ -1845,40 +1704,37 @@ describe('addVariant', () => { .hocus\\:underline:focus { text-decoration-line: underline; } - } - " + }" `) }) test('addVariant with at-rules and placeholder', async () => { - expect( - await run( - ['potato:underline', 'potato:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ addVariant }: PluginAPI) => { + addVariant( + 'potato', + '@media (max-width: 400px) { @supports (font:bold) { &:large-potato } }', + ) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant( - 'potato', - '@media (max-width: 400px) { @supports (font:bold) { &:large-potato } }', - ) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @media (max-width: 400px) { + }, + ) + let compiled = build(['potato:underline', 'potato:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + @media (width <= 400px) { @supports (font: bold) { .potato\\:flex:large-potato { display: flex; @@ -1889,42 +1745,39 @@ describe('addVariant', () => { } } } - } - " + }" `) }) test('@slot is preserved when used as a custom property value', async () => { - expect( - await run( - ['hocus:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ addVariant }: PluginAPI) => { + addVariant('hocus', { + '&': { + '--custom-property': '@slot', + '&:hover': '@slot', + '&:focus': '@slot', + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant('hocus', { - '&': { - '--custom-property': '@slot', - '&:hover': '@slot', - '&:focus': '@slot', - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['hocus:underline']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .hocus\\:underline { --custom-property: @slot; } @@ -1932,73 +1785,35 @@ describe('addVariant', () => { .hocus\\:underline:hover, .hocus\\:underline:focus { text-decoration-line: underline; } - } - " - `) - }) - - test('ignores variants that use :merge(…) and ensures `peer-*` and `group-*` rules work out of the box', async () => { - expect( - await run( - ['optional:flex', 'group-optional:flex', 'peer-optional:flex', 'group-optional/foo:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant('optional', '&:optional') - addVariant('group-optional', { ':merge(.group):optional &': '@slot' }) - addVariant('peer-optional', { '&': { ':merge(.peer):optional ~ &': '@slot' } }) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - :is(:where(.group):optional .group-optional\\:flex), :is(:where(.group\\/foo):optional .group-optional\\/foo\\:flex), :is(:where(.peer):optional ~ .peer-optional\\:flex), .optional\\:flex:optional { - display: flex; - } - } - " + }" `) }) }) describe('matchVariant', () => { test('partial arbitrary variants', async () => { - expect( - await run( - ['potato-[yellow]:underline', 'potato-[baked]:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('potato', (flavor) => `.potato-${flavor} &`) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('potato', (flavor) => `.potato-${flavor} &`) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['potato-[yellow]:underline', 'potato-[baked]:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .potato-baked .potato-\\[baked\\]\\:flex { display: flex; } @@ -2006,36 +1821,33 @@ describe('matchVariant', () => { .potato-yellow .potato-\\[yellow\\]\\:underline { text-decoration-line: underline; } - } - " + }" `) }) test('partial arbitrary variants with at-rules', async () => { - expect( - await run( - ['potato-[yellow]:underline', 'potato-[baked]:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('potato', (flavor) => `@media (potato: ${flavor})`) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('potato', (flavor) => `@media (potato: ${flavor})`) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['potato-[yellow]:underline', 'potato-[baked]:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { @media (potato: baked) { .potato-\\[baked\\]\\:flex { display: flex; @@ -2047,38 +1859,37 @@ describe('matchVariant', () => { text-decoration-line: underline; } } - } - " + }" `) }) test('partial arbitrary variants with at-rules and placeholder', async () => { - expect( - await run( - ['potato-[yellow]:underline', 'potato-[baked]:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant( + 'potato', + (flavor) => + `@media (potato: ${flavor}) { @supports (font:bold) { &:large-potato } }`, + ) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('potato', (flavor) => { - return `@media (potato: ${flavor}) { @supports (font:bold) { &:large-potato } }` - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['potato-[yellow]:underline', 'potato-[baked]:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { @media (potato: baked) { @supports (font: bold) { .potato-\\[baked\\]\\:flex:large-potato { @@ -2094,41 +1905,38 @@ describe('matchVariant', () => { } } } - } - " + }" `) }) test('partial arbitrary variants with default values', async () => { - expect( - await run( - ['tooltip-bottom:underline', 'tooltip-top:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('tooltip', (side) => `&${side}`, { + values: { + bottom: '[data-location="bottom"]', + top: '[data-location="top"]', + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('tooltip', (side) => `&${side}`, { - values: { - bottom: '[data-location="bottom"]', - top: '[data-location="top"]', - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['tooltip-bottom:underline', 'tooltip-top:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .tooltip-bottom\\:underline[data-location="bottom"] { text-decoration-line: underline; } @@ -2136,309 +1944,302 @@ describe('matchVariant', () => { .tooltip-top\\:flex[data-location="top"] { display: flex; } - } - " + }" `) }) test('matched variant values maintain the sort order they are registered in', async () => { - expect( - await run( - [ - 'alphabet-c:underline', - 'alphabet-a:underline', - 'alphabet-d:underline', - 'alphabet-b:underline', - ], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('alphabet', (side) => `&${side}`, { + values: { + d: '[data-order="1"]', + a: '[data-order="2"]', + c: '[data-order="3"]', + b: '[data-order="4"]', + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('alphabet', (side) => `&${side}`, { - values: { - d: '[data-order="1"]', - a: '[data-order="2"]', - c: '[data-order="3"]', - b: '[data-order="4"]', - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build([ + 'alphabet-c:underline', + 'alphabet-a:underline', + 'alphabet-d:underline', + 'alphabet-b:underline', + ]) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .alphabet-d\\:underline[data-order="1"], .alphabet-a\\:underline[data-order="2"], .alphabet-c\\:underline[data-order="3"], .alphabet-b\\:underline[data-order="4"] { text-decoration-line: underline; } - } - " + }" `) }) test('matchVariant can return an array of format strings from the function', async () => { - expect( - await run( - ['test-[a,b,c]:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('test', (selector) => + selector.split(',').map((selector) => `&.${selector} > *`), + ) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('test', (selector) => - selector.split(',').map((selector) => `&.${selector} > *`), - ) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['test-[a,b,c]:underline']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .test-\\[a\\,b\\,c\\]\\:underline.a > *, .test-\\[a\\,b\\,c\\]\\:underline.b > *, .test-\\[a\\,b\\,c\\]\\:underline.c > * { text-decoration-line: underline; } - } - " + }" `) }) test('should be possible to sort variants', async () => { - expect( - await run( - ['testmin-[600px]:flex', 'testmin-[500px]:underline', 'testmin-[700px]:italic'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @media (min-width: 500px) { + }, + ) + let compiled = build([ + 'testmin-[600px]:flex', + 'testmin-[500px]:underline', + 'testmin-[700px]:italic', + ]) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + @media (width >= 500px) { .testmin-\\[500px\\]\\:underline { text-decoration-line: underline; } } - @media (min-width: 600px) { + @media (width >= 600px) { .testmin-\\[600px\\]\\:flex { display: flex; } } - @media (min-width: 700px) { + @media (width >= 700px) { .testmin-\\[700px\\]\\:italic { font-style: italic; } } - } - " + }" `) }) test('should be possible to compare arbitrary variants and hardcoded variants', async () => { - expect( - await run( - ['testmin-[700px]:italic', 'testmin-example:italic', 'testmin-[500px]:italic'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + values: { + example: '600px', + }, + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - values: { example: '600px' }, - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @media (min-width: 500px) { + }, + ) + let compiled = build([ + 'testmin-[700px]:italic', + 'testmin-example:italic', + 'testmin-[500px]:italic', + ]) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + @media (width >= 500px) { .testmin-\\[500px\\]\\:italic { font-style: italic; } } - @media (min-width: 600px) { + @media (width >= 600px) { .testmin-example\\:italic { font-style: italic; } } - @media (min-width: 700px) { + @media (width >= 700px) { .testmin-\\[700px\\]\\:italic { font-style: italic; } } - } - " + }" `) }) test('should be possible to sort stacked arbitrary variants correctly', async () => { - expect( - await run( - [ - 'testmin-[150px]:testmax-[400px]:order-2', - 'testmin-[100px]:testmax-[350px]:order-3', - 'testmin-[100px]:testmax-[300px]:order-4', - 'testmin-[100px]:testmax-[400px]:order-1', - ], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) - matchVariant('testmax', (value) => `@media (max-width: ${value})`, { - sort(a, z) { - return parseInt(z.value) - parseInt(a.value) - }, - }) - }, - } - }, + matchVariant('testmax', (value) => `@media (max-width: ${value})`, { + sort(a, z) { + return parseInt(z.value) - parseInt(a.value) + }, + }) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @media (min-width: 100px) { - @media (max-width: 400px) { + }, + ) + + let compiled = build([ + 'testmin-[150px]:testmax-[400px]:order-2', + 'testmin-[100px]:testmax-[350px]:order-3', + 'testmin-[100px]:testmax-[300px]:order-4', + 'testmin-[100px]:testmax-[400px]:order-1', + ]) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + @media (width >= 100px) { + @media (width <= 400px) { .testmin-\\[100px\\]\\:testmax-\\[400px\\]\\:order-1 { order: 1; } } } - @media (min-width: 150px) { - @media (max-width: 400px) { + @media (width >= 150px) { + @media (width <= 400px) { .testmin-\\[150px\\]\\:testmax-\\[400px\\]\\:order-2 { order: 2; } } } - @media (min-width: 100px) { - @media (max-width: 350px) { + @media (width >= 100px) { + @media (width <= 350px) { .testmin-\\[100px\\]\\:testmax-\\[350px\\]\\:order-3 { order: 3; } } - @media (max-width: 300px) { + @media (width <= 300px) { .testmin-\\[100px\\]\\:testmax-\\[300px\\]\\:order-4 { order: 4; } } } - } - " + }" `) }) test('should maintain sort from other variants, if sort functions of arbitrary variants return 0', async () => { - // Expect :focus to come after :hover - expect( - await run( - [ - 'testmin-[100px]:testmax-[200px]:focus:underline', - 'testmin-[100px]:testmax-[200px]:hover:underline', - ], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) - matchVariant('testmax', (value) => `@media (max-width: ${value})`, { - sort(a, z) { - return parseInt(z.value) - parseInt(a.value) - }, - }) - }, - } - }, + matchVariant('testmax', (value) => `@media (max-width: ${value})`, { + sort(a, z) { + return parseInt(z.value) - parseInt(a.value) + }, + }) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @media (min-width: 100px) { - @media (max-width: 200px) { + }, + ) + let compiled = build([ + 'testmin-[100px]:testmax-[200px]:focus:underline', + 'testmin-[100px]:testmax-[200px]:hover:underline', + ]) + + // Expect :focus to come after :hover + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + @media (width >= 100px) { + @media (width <= 200px) { @media (hover: hover) { .testmin-\\[100px\\]\\:testmax-\\[200px\\]\\:hover\\:underline:hover { text-decoration-line: underline; @@ -2450,782 +2251,618 @@ describe('matchVariant', () => { } } } - } - " + }" `) }) test('should sort arbitrary variants left to right (1)', async () => { - expect( - await run( - [ - 'testmin-[200px]:testmax-[400px]:order-2', - 'testmin-[200px]:testmax-[300px]:order-4', - 'testmin-[100px]:testmax-[400px]:order-1', - 'testmin-[100px]:testmax-[300px]:order-3', - ], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) + matchVariant('testmax', (value) => `@media (max-width: ${value})`, { + sort(a, z) { + return parseInt(z.value) - parseInt(a.value) + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) - matchVariant('testmax', (value) => `@media (max-width: ${value})`, { - sort(a, z) { - return parseInt(z.value) - parseInt(a.value) - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @media (min-width: 100px) { - @media (max-width: 400px) { + }, + ) + let compiled = build([ + 'testmin-[200px]:testmax-[400px]:order-2', + 'testmin-[200px]:testmax-[300px]:order-4', + 'testmin-[100px]:testmax-[400px]:order-1', + 'testmin-[100px]:testmax-[300px]:order-3', + ]) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + @media (width >= 100px) { + @media (width <= 400px) { .testmin-\\[100px\\]\\:testmax-\\[400px\\]\\:order-1 { order: 1; } } } - @media (min-width: 200px) { - @media (max-width: 400px) { + @media (width >= 200px) { + @media (width <= 400px) { .testmin-\\[200px\\]\\:testmax-\\[400px\\]\\:order-2 { order: 2; } } } - @media (min-width: 100px) { - @media (max-width: 300px) { + @media (width >= 100px) { + @media (width <= 300px) { .testmin-\\[100px\\]\\:testmax-\\[300px\\]\\:order-3 { order: 3; } } } - @media (min-width: 200px) { - @media (max-width: 300px) { + @media (width >= 200px) { + @media (width <= 300px) { .testmin-\\[200px\\]\\:testmax-\\[300px\\]\\:order-4 { order: 4; } } } - } - " + }" `) }) test('should sort arbitrary variants left to right (2)', async () => { - expect( - await run( - [ - 'testmax-[400px]:testmin-[200px]:underline', - 'testmax-[300px]:testmin-[200px]:underline', - 'testmax-[400px]:testmin-[100px]:underline', - 'testmax-[300px]:testmin-[100px]:underline', - ], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) + matchVariant('testmax', (value) => `@media (max-width: ${value})`, { + sort(a, z) { + return parseInt(z.value) - parseInt(a.value) + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) - matchVariant('testmax', (value) => `@media (max-width: ${value})`, { - sort(a, z) { - return parseInt(z.value) - parseInt(a.value) - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @media (max-width: 400px) { - @media (min-width: 100px) { + }, + ) + let compiled = build([ + 'testmax-[400px]:testmin-[200px]:underline', + 'testmax-[300px]:testmin-[200px]:underline', + 'testmax-[400px]:testmin-[100px]:underline', + 'testmax-[300px]:testmin-[100px]:underline', + ]) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + @media (width <= 400px) { + @media (width >= 100px) { .testmax-\\[400px\\]\\:testmin-\\[100px\\]\\:underline { text-decoration-line: underline; } } - @media (min-width: 200px) { + @media (width >= 200px) { .testmax-\\[400px\\]\\:testmin-\\[200px\\]\\:underline { text-decoration-line: underline; } } } - @media (max-width: 300px) { - @media (min-width: 100px) { + @media (width <= 300px) { + @media (width >= 100px) { .testmax-\\[300px\\]\\:testmin-\\[100px\\]\\:underline { text-decoration-line: underline; } } - @media (min-width: 200px) { + @media (width >= 200px) { .testmax-\\[300px\\]\\:testmin-\\[200px\\]\\:underline { text-decoration-line: underline; } } } - } - " + }" `) }) test('should guarantee that we are not passing values from other variants to the wrong function', async () => { - expect( - await run( - [ - 'testmin-[200px]:testmax-[400px]:order-2', - 'testmin-[200px]:testmax-[300px]:order-4', - 'testmin-[100px]:testmax-[400px]:order-1', - 'testmin-[100px]:testmax-[300px]:order-3', - ], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + let lookup = ['100px', '200px'] + if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) { + throw new Error('We are seeing values that should not be there!') + } + return lookup.indexOf(a.value) - lookup.indexOf(z.value) + }, + }) + matchVariant('testmax', (value) => `@media (max-width: ${value})`, { + sort(a, z) { + let lookup = ['300px', '400px'] + if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) { + throw new Error('We are seeing values that should not be there!') + } + return lookup.indexOf(z.value) - lookup.indexOf(a.value) + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - let lookup = ['100px', '200px'] - if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) { - throw new Error('We are seeing values that should not be there!') - } - return lookup.indexOf(a.value) - lookup.indexOf(z.value) - }, - }) - matchVariant('testmax', (value) => `@media (max-width: ${value})`, { - sort(a, z) { - let lookup = ['300px', '400px'] - if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) { - throw new Error('We are seeing values that should not be there!') - } - return lookup.indexOf(z.value) - lookup.indexOf(a.value) - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @media (min-width: 100px) { - @media (max-width: 400px) { + }, + ) + let compiled = build([ + 'testmin-[200px]:testmax-[400px]:order-2', + 'testmin-[200px]:testmax-[300px]:order-4', + 'testmin-[100px]:testmax-[400px]:order-1', + 'testmin-[100px]:testmax-[300px]:order-3', + ]) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + @media (width >= 100px) { + @media (width <= 400px) { .testmin-\\[100px\\]\\:testmax-\\[400px\\]\\:order-1 { order: 1; } } } - @media (min-width: 200px) { - @media (max-width: 400px) { + @media (width >= 200px) { + @media (width <= 400px) { .testmin-\\[200px\\]\\:testmax-\\[400px\\]\\:order-2 { order: 2; } } } - @media (min-width: 100px) { - @media (max-width: 300px) { + @media (width >= 100px) { + @media (width <= 300px) { .testmin-\\[100px\\]\\:testmax-\\[300px\\]\\:order-3 { order: 3; } } } - @media (min-width: 200px) { - @media (max-width: 300px) { + @media (width >= 200px) { + @media (width <= 300px) { .testmin-\\[200px\\]\\:testmax-\\[300px\\]\\:order-4 { order: 4; } } } - } - " + }" `) }) test('should default to the DEFAULT value for variants', async () => { - expect( - await run( - ['foo:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('foo', (value) => `.foo${value} &`, { + values: { + DEFAULT: '.bar', + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('foo', (value) => `.foo${value} &`, { - values: { DEFAULT: '.bar' }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['foo:underline']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .foo.bar .foo\\:underline { text-decoration-line: underline; } - } - " + }" `) }) test('should not generate anything if the matchVariant does not have a DEFAULT value configured', async () => { - expect( - await run( - ['foo:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('foo', (value) => `.foo${value} &`) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('foo', (value) => `.foo${value} &`) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities; - " - `) + }, + ) + let compiled = build(['foo:underline']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(`"@layer utilities;"`) }) test('should be possible to use `null` as a DEFAULT value', async () => { - expect( - await run( - ['foo:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('foo', (value) => `.foo${value === null ? '-good' : '-bad'} &`, { + values: { DEFAULT: null }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('foo', (value) => `.foo${value === null ? '-good' : '-bad'} &`, { - values: { DEFAULT: null }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['foo:underline']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .foo-good .foo\\:underline { text-decoration-line: underline; } - } - " + }" `) }) test('should be possible to use `undefined` as a DEFAULT value', async () => { - expect( - await run( - ['foo:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, { + values: { DEFAULT: undefined }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, { - values: { DEFAULT: undefined }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['foo:underline']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .foo-good .foo\\:underline { text-decoration-line: underline; } - } - " + }" `) }) test('should be called with eventual modifiers', async () => { - expect( - await run( - ['my-container-[250px]:underline', 'my-container-[250px]/placement:underline'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('my-container', (value, { modifier }) => { - function parseValue(value: string) { - const numericValue = value.match(/^(\d+\.\d+|\d+|\.\d+)\D+/)?.[1] ?? null - if (numericValue === null) return null + let { build } = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { + loadModule: async (id, base) => { + return { + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('my-container', (value, { modifier }) => { + function parseValue(value: string) { + const numericValue = value.match(/^(\d+\.\d+|\d+|\.\d+)\D+/)?.[1] ?? null + if (numericValue === null) return null - return parseFloat(value) - } + return parseFloat(value) + } - const parsed = parseValue(value) - return parsed !== null ? `@container ${modifier ?? ''} (min-width: ${value})` : [] - }) - }, - } - }, + const parsed = parseValue(value) + return parsed !== null ? `@container ${modifier ?? ''} (min-width: ${value})` : [] + }) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - @container (min-width: 250px) { + }, + ) + let compiled = build([ + 'my-container-[250px]:underline', + 'my-container-[250px]/placement:underline', + ]) + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@container (width >= 250px) { .my-container-\\[250px\\]\\:underline { text-decoration-line: underline; } } - @container placement (min-width: 250px) { + @container placement (width >= 250px) { .my-container-\\[250px\\]\\/placement\\:underline { text-decoration-line: underline; } - } - " - `) - }) - - test('ignores variants that use :merge(…)', async () => { - expect( - await run( - [ - 'optional-[test]:flex', - 'group-optional-[test]:flex', - 'peer-optional-[test]:flex', - 'group-optional-[test]/foo:flex', - ], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('optional', (flavor) => `&:optional:has(${flavor}) &`) - matchVariant( - 'group-optional', - (flavor) => `:merge(.group):optional:has(${flavor}) &`, - ) - matchVariant( - 'peer-optional', - (flavor) => `:merge(.peer):optional:has(${flavor}) ~ &`, - ) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - :is(:where(.group):optional:has(test) :where(.group) .group-optional-\\[test\\]\\:flex), :is(:where(.group\\/foo):optional:has(test) :where(.group\\/foo) .group-optional-\\[test\\]\\/foo\\:flex), :is(:where(.peer):optional:has(test) :where(.peer) ~ .peer-optional-\\[test\\]\\:flex), .optional-\\[test\\]\\:flex:optional:has(test) .optional-\\[test\\]\\:flex { - display: flex; - } - } - " - `) - }) - - test('ignores variants that use unknown values', async () => { - expect( - await run( - ['foo-[test]:flex', 'foo-known:flex', 'foo-unknown:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('foo', (flavor) => `&:is(${flavor})`, { - values: { known: 'known' }, - }) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - .foo-known\\:flex:is(known), .foo-\\[test\\]\\:flex:is(test) { - display: flex; - } - } - " - `) - }) - - test('ignores variants that produce non-string values', async () => { - expect( - await run( - ['foo-[test]:flex', 'foo-string:flex', 'foo-object:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('foo', (flavor) => `&:is(${flavor})`, { - values: { - string: 'some string', - object: { some: 'object' }, - }, - }) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - .foo-string\\:flex:is(some string), .foo-\\[test\\]\\:flex:is(test) { - display: flex; - } - } - " + }" `) }) }) describe('addUtilities()', () => { test('custom static utility', async () => { - expect( - await run( - ['text-trim', 'lg:text-trim'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } + let compiled = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } - @theme reference { - --breakpoint-lg: 1024px; + @theme reference { + --breakpoint-lg: 1024px; + } + `, + { + async loadModule(id, base) { + return { + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.text-trim': { + 'text-box-trim': 'both', + 'text-box-edge': 'cap alphabetic', + }, + }) + }, } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.text-trim': { - 'text-box-trim': 'both', - 'text-box-edge': 'cap alphabetic', - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + + expect(optimizeCss(compiled.build(['text-trim', 'lg:text-trim'])).trim()) + .toMatchInlineSnapshot(` + "@layer utilities { .text-trim { text-box-trim: both; text-box-edge: cap alphabetic; } - @media (min-width: 1024px) { + @media (width >= 1024px) { .lg\\:text-trim { text-box-trim: both; text-box-edge: cap alphabetic; } } - } - " + }" `) }) test('return multiple rule objects from a custom utility', async () => { - expect( - await run( - ['text-trim'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities([ - { - '.text-trim': [ - { 'text-box-trim': 'both' }, - { 'text-box-edge': 'cap alphabetic' }, - ], - }, - ]) - }, - } - }, + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { + async loadModule(id, base) { + return { + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities([ + { + '.text-trim': [ + { 'text-box-trim': 'both' }, + { 'text-box-edge': 'cap alphabetic' }, + ], + }, + ]) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - .text-trim { + }, + ) + + expect(optimizeCss(compiled.build(['text-trim'])).trim()).toMatchInlineSnapshot(` + ".text-trim { text-box-trim: both; text-box-edge: cap alphabetic; - } - " + }" `) }) test('define multiple utilities with array syntax', async () => { - expect( - await run( - ['text-trim', 'text-trim-2'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities([ - { - '.text-trim': { - 'text-box-trim': 'both', - 'text-box-edge': 'cap alphabetic', - }, + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { + async loadModule(id, base) { + return { + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities([ + { + '.text-trim': { + 'text-box-trim': 'both', + 'text-box-edge': 'cap alphabetic', }, - { - '.text-trim-2': { - 'text-box-trim': 'both', - 'text-box-edge': 'cap alphabetic', - }, + }, + { + '.text-trim-2': { + 'text-box-trim': 'both', + 'text-box-edge': 'cap alphabetic', }, - ]) - }, - } - }, + }, + ]) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - .text-trim, .text-trim-2 { + }, + ) + + expect(optimizeCss(compiled.build(['text-trim', 'text-trim-2'])).trim()).toMatchInlineSnapshot(` + ".text-trim, .text-trim-2 { text-box-trim: both; text-box-edge: cap alphabetic; - } - " + }" `) }) test('utilities can use arrays for fallback declaration values', async () => { - expect( - await run( - ['outlined'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities([ - { - '.outlined': { - outline: ['1px solid ButtonText', '1px auto -webkit-focus-ring-color'], - }, + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { + async loadModule(id, base) { + return { + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities([ + { + '.outlined': { + outline: ['1px solid ButtonText', '1px auto -webkit-focus-ring-color'], }, - ]) - }, - } - }, + }, + ]) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - .outlined { + }, + ) + + expect(optimizeCss(compiled.build(['outlined'])).trim()).toMatchInlineSnapshot(` + ".outlined { outline: 1px solid buttontext; outline: 1px auto -webkit-focus-ring-color; - } - " + }" `) }) test('camel case properties are converted to kebab-case', async () => { - expect( - await run( - ['text-trim'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let compiled = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + async loadModule(id, base) { + return { + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.text-trim': { + WebkitAppearance: 'none', + textBoxTrim: 'both', + textBoxEdge: 'cap alphabetic', + }, + }) + }, } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.text-trim': { - WebkitAppearance: 'none', - textBoxTrim: 'both', - textBoxEdge: 'cap alphabetic', - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + + expect(optimizeCss(compiled.build(['text-trim'])).trim()).toMatchInlineSnapshot(` + "@layer utilities { .text-trim { -webkit-appearance: none; text-box-trim: both; text-box-edge: cap alphabetic; } - } - " + }" `) }) test('custom static utilities support `@apply`', async () => { - expect( - await run( - ['foo', 'lg:foo'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } + let compiled = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } - @theme reference { - --breakpoint-lg: 1024px; + @theme reference { + --breakpoint-lg: 1024px; + } + `, + { + async loadModule(id, base) { + return { + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.foo': { + '@apply flex dark:underline': {}, + }, + }) + }, } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.foo': { - '@apply flex dark:underline': {}, - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + + expect(optimizeCss(compiled.build(['foo', 'lg:foo'])).trim()).toMatchInlineSnapshot(` + "@layer utilities { .foo { display: flex; } @@ -3236,7 +2873,7 @@ describe('addUtilities()', () => { } } - @media (min-width: 1024px) { + @media (width >= 1024px) { .lg\\:foo { display: flex; } @@ -3247,8 +2884,7 @@ describe('addUtilities()', () => { } } } - } - " + }" `) }) @@ -3266,9 +2902,8 @@ describe('addUtilities()', () => { } `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: ({ addUtilities }: PluginAPI) => { addUtilities({ @@ -3282,336 +2917,354 @@ describe('addUtilities()', () => { }, }, ) - }).rejects.toThrow(/invalid utility selector/) + }).rejects.toThrowError(/invalid utility selector/) }) test('supports multiple selector names', async () => { - expect( - await run( - ['form-input', 'lg:form-textarea'], - css` - @plugin "my-plugin"; - @tailwind utilities; + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; - @theme reference { - --breakpoint-lg: 1024px; + @theme reference { + --breakpoint-lg: 1024px; + } + `, + { + async loadModule(id, base) { + return { + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.form-input, .form-textarea': { + appearance: 'none', + 'background-color': '#fff', + }, + }) + }, } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.form-input, .form-textarea': { - appearance: 'none', - 'background-color': '#fff', - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - .form-input { - appearance: none; - background-color: #fff; - } + }, + ) - @media (min-width: 1024px) { - .lg\\:form-textarea { + expect(optimizeCss(compiled.build(['form-input', 'lg:form-textarea'])).trim()) + .toMatchInlineSnapshot(` + ".form-input { appearance: none; background-color: #fff; } - } - " - `) + + @media (width >= 1024px) { + .lg\\:form-textarea { + appearance: none; + background-color: #fff; + } + }" + `) }) test('supports pseudo classes and pseudo elements', async () => { - expect( - await run( - ['form-input', 'lg:form-textarea'], - css` - @plugin "my-plugin"; - @tailwind utilities; + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; - @theme reference { - --breakpoint-lg: 1024px; + @theme reference { + --breakpoint-lg: 1024px; + } + `, + { + async loadModule(id, base) { + return { + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.form-input, .form-input::placeholder, .form-textarea:hover:focus': { + 'background-color': 'red', + }, + }) + }, } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.form-input, .form-input::placeholder, .form-textarea:hover:focus': { - 'background-color': 'red', - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - .form-input, .form-input::placeholder { - background-color: red; - } + }, + ) - @media (min-width: 1024px) { - .lg\\:form-textarea:hover:focus { + expect(compiled.build(['form-input', 'lg:form-textarea']).trim()).toMatchInlineSnapshot(` + ".form-input { + background-color: red; + &::placeholder { background-color: red; } } - " + .lg\\:form-textarea { + @media (width >= 1024px) { + &:hover:focus { + background-color: red; + } + } + }" `) }) test('nests complex utility names', async () => { - expect( - await run( - ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.a .b:hover .c': { color: 'red' }, - '.d > *': { color: 'red' }, - '.e .bar:not(.f):has(.g)': { color: 'red' }, - '.h~.i': { color: 'red' }, - '.j.j': { color: 'red' }, - }) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - .j.j, .a .b:hover .c, .d > *, .e .bar:not(.f):has(.g), .h ~ .i { - color: red; + let compiled = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; } - } - " - `) - }) + `, + { + async loadModule(id, base) { + return { + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.a .b:hover .c': { + color: 'red', + }, + '.d > *': { + color: 'red', + }, + '.e .bar:not(.f):has(.g)': { + color: 'red', + }, + '.h~.i': { + color: 'red', + }, + '.j.j': { + color: 'red', + }, + }) + }, + } + }, + }, + ) - test('does not register class names inside `:nth-child(… of …)` as utilities', async () => { expect( - await run( - ['foo', 'mark'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + compiled.build(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']).trim(), + ).toMatchInlineSnapshot( + ` + "@layer utilities { + .j { + &.j { + color: red; + } + .j& { + color: red; } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.foo:nth-child(2 of .mark)': { color: 'red' }, - }) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - .foo:nth-child(2 of .mark) { - color: red; } - } - " - `) + .a { + & .b:hover .c { + color: red; + } + } + .b { + .a &:hover .c { + color: red; + } + } + .c { + .a .b:hover & { + color: red; + } + } + .d { + & > * { + color: red; + } + } + .e { + & .bar:not(.f):has(.g) { + color: red; + } + } + .g { + .e .bar:not(.f):has(&) { + color: red; + } + } + .h { + &~.i { + color: red; + } + } + .i { + .h~& { + color: red; + } + } + }" + `, + ) }) test('prefixes nested class names with the configured theme prefix', async () => { - expect( - await run( - ['tw:a', 'tw:b', 'tw:c', 'tw:d'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - @theme prefix(tw) { - } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ '.a .b:hover .c.d': { color: 'red' } }) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - .tw\\:a .tw\\:b:hover .tw\\:c.tw\\:d { - color: red; + let compiled = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; } - } - " - `) + @theme prefix(tw) { + } + `, + { + async loadModule(id, base) { + return { + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.a .b:hover .c.d': { + color: 'red', + }, + }) + }, + } + }, + }, + ) + + expect(compiled.build(['tw:a', 'tw:b', 'tw:c', 'tw:d']).trim()).toMatchInlineSnapshot( + ` + "@layer utilities { + .tw\\:a { + & .tw\\:b:hover .tw\\:c.tw\\:d { + color: red; + } + } + .tw\\:b { + .tw\\:a &:hover .tw\\:c.tw\\:d { + color: red; + } + } + .tw\\:c { + .tw\\:a .tw\\:b:hover &.tw\\:d { + color: red; + } + } + .tw\\:d { + .tw\\:a .tw\\:b:hover .tw\\:c& { + color: red; + } + } + }" + `, + ) }) test('replaces the class name with variants in nested selectors', async () => { - expect( - await run( - ['foo', 'md:foo', 'not-hover:md:foo'], - css` - @plugin "my-plugin"; - @theme { - --breakpoint-md: 768px; + let compiled = await compile( + css` + @plugin "my-plugin"; + @theme { + --breakpoint-md: 768px; + } + @tailwind utilities; + `, + { + async loadModule(id, base) { + return { + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.foo': { + ':where(.foo > :first-child)': { + color: 'red', + }, + }, + }) + }, } - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ '.foo': { ':where(.foo > :first-child)': { color: 'red' } } }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - .foo :where(.foo > :first-child) { - color: red; - } + }, + ) - @media (min-width: 768px) { - .md\\:foo :where(.md\\:foo > :first-child), .not-hover\\:md\\:foo:not(:hover) :where(.not-hover\\:md\\:foo > :first-child) { + expect(compiled.build(['foo', 'md:foo', 'not-hover:md:foo']).trim()).toMatchInlineSnapshot(` + ".foo { + :where(.foo > :first-child) { color: red; } } - - @media not all and (hover: hover) { - @media (min-width: 768px) { - .not-hover\\:md\\:foo :where(.not-hover\\:md\\:foo > :first-child) { + .md\\:foo { + @media (width >= 768px) { + :where(.md\\:foo > :first-child) { color: red; } } } - " - `) - }) - - test('values that are `false`, `null`, or `undefined` are discarded from CSS object ASTs', async () => { - expect( - await run( - ['foo'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.foo': { - a: 'red', - // @ts-ignore: While this isn't valid per the types this did work in v3 - 'z-index': 0, - // @ts-ignore - '.bar': false, - // @ts-ignore - '.baz': null, - // @ts-ignore - '.qux': undefined, - }, - }) - }, + .not-hover\\:md\\:foo { + &:not(*:hover) { + @media (width >= 768px) { + :where(.not-hover\\:md\\:foo > :first-child) { + color: red; } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .foo { - a: red; - z-index: 0; - } - " + } + } + @media not (hover: hover) { + @media (width >= 768px) { + :where(.not-hover\\:md\\:foo > :first-child) { + color: red; + } + } + } + }" `) }) }) describe('matchUtilities()', () => { test('custom functional utility', async () => { - let input = css` - @plugin "my-plugin"; + async function run(candidates: string[]) { + let compiled = await compile( + css` + @plugin "my-plugin"; - @tailwind utilities; + @tailwind utilities; - @theme reference { - --breakpoint-lg: 1024px; - } - ` + @theme reference { + --breakpoint-lg: 1024px; + } + `, - expect( - await run( - [ - 'border-block', - 'border-block-2', - 'border-block-[35px]', - 'border-block-[var(--foo)]', - 'lg:border-block-2', - ], - input, { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: ({ matchUtilities }: PluginAPI) => { matchUtilities( - { 'border-block': (value) => ({ 'border-block-width': value }) }, - { values: { DEFAULT: '1px', '2': '2px' } }, + { + 'border-block': (value) => ({ 'border-block-width': value }), + }, + { + values: { + DEFAULT: '1px', + '2': '2px', + }, + }, ) }, } }, }, - ), + ) + + return compiled.build(candidates) + } + + expect( + optimizeCss( + await run([ + 'border-block', + 'border-block-2', + 'border-block-[35px]', + 'border-block-[var(--foo)]', + 'lg:border-block-2', + ]), + ).trim(), ).toMatchInlineSnapshot(` - " - .border-block { + ".border-block { border-block-width: 1px; } @@ -3627,122 +3280,114 @@ describe('matchUtilities()', () => { border-block-width: var(--foo); } - @media (min-width: 1024px) { + @media (width >= 1024px) { .lg\\:border-block-2 { border-block-width: 2px; } - } - " + }" `) expect( - await run( - [ + optimizeCss( + await run([ '-border-block', '-border-block-2', 'lg:-border-block-2', 'border-block-unknown', 'border-block/1', - ], - input, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities( - { 'border-block': (value) => ({ 'border-block-width': value }) }, - { values: { DEFAULT: '1px', '2': '2px' } }, - ) - }, - } - }, - }, - ), + ]), + ).trim(), ).toEqual('') }) test('custom functional utilities can start with @', async () => { - expect( - await run( - ['@w-1', 'hover:@w-1'], + async function run(candidates: string[]) { + let compiled = await compile( css` @plugin "my-plugin"; @tailwind utilities; `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities({ '@w': (value) => ({ width: value }) }, { values: { 1: '1px' } }) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .\\@w-1 { - width: 1px; - } - @media (hover: hover) { - .hover\\:\\@w-1:hover { - width: 1px; - } - } - " - `) - }) - - test('custom functional utilities can return an array of rules', async () => { - expect( - await run( - ['all-but-order-bottom-left-radius'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: ({ matchUtilities }: PluginAPI) => { matchUtilities( + { '@w': (value) => ({ width: value }) }, { - 'all-but-order-bottom-left-radius': (value) => - [ - { 'border-top-left-radius': value }, - { 'border-top-right-radius': value }, - { 'border-bottom-right-radius': value }, - ] as CssInJs[], + values: { + 1: '1px', + }, }, - { values: { DEFAULT: '1px' } }, ) }, } }, }, - ), - ).toMatchInlineSnapshot(` - " - .all-but-order-bottom-left-radius { - border-top-left-radius: 1px; - border-top-right-radius: 1px; - border-bottom-right-radius: 1px; - } - " - `) + ) + + return compiled.build(candidates) + } + + expect(optimizeCss(await run(['@w-1', 'hover:@w-1'])).trim()).toMatchInlineSnapshot(` + ".\\@w-1 { + width: 1px; + } + + @media (hover: hover) { + .hover\\:\\@w-1:hover { + width: 1px; + } + }" + `) + }) + + test('custom functional utilities can return an array of rules', async () => { + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { + async loadModule(id, base) { + return { + base, + module: ({ matchUtilities }: PluginAPI) => { + matchUtilities( + { + 'all-but-order-bottom-left-radius': (value) => + [ + { 'border-top-left-radius': value }, + { 'border-top-right-radius': value }, + { 'border-bottom-right-radius': value }, + ] as CssInJs[], + }, + { + values: { + DEFAULT: '1px', + }, + }, + ) + }, + } + }, + }, + ) + + expect(optimizeCss(compiled.build(['all-but-order-bottom-left-radius'])).trim()) + .toMatchInlineSnapshot(` + ".all-but-order-bottom-left-radius { + border-top-left-radius: 1px; + border-top-right-radius: 1px; + border-bottom-right-radius: 1px; + }" + `) }) test('custom functional utility with any modifier', async () => { - expect( - await run( - ['border-block', 'border-block-2', 'border-block/foo', 'border-block-2/foo'], + async function run(candidates: string[]) { + let compiled = await compile( css` @plugin "my-plugin"; @@ -3752,10 +3397,10 @@ describe('matchUtilities()', () => { --breakpoint-lg: 1024px; } `, + { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: ({ matchUtilities }: PluginAPI) => { matchUtilities( @@ -3765,16 +3410,30 @@ describe('matchUtilities()', () => { 'border-block-width': value, }), }, - { values: { DEFAULT: '1px', '2': '2px' }, modifiers: 'any' }, + { + values: { + DEFAULT: '1px', + '2': '2px', + }, + + modifiers: 'any', + }, ) }, } }, }, - ), + ) + + return compiled.build(candidates) + } + + expect( + optimizeCss( + await run(['border-block', 'border-block-2', 'border-block/foo', 'border-block-2/foo']), + ).trim(), ).toMatchInlineSnapshot(` - " - .border-block { + ".border-block { --my-modifier: none; border-block-width: 1px; } @@ -3792,30 +3451,26 @@ describe('matchUtilities()', () => { .border-block\\/foo { --my-modifier: foo; border-block-width: 1px; - } - " + }" `) }) test('custom functional utility with known modifier', async () => { - let input = css` - @plugin "my-plugin"; + async function run(candidates: string[]) { + let compiled = await compile( + css` + @plugin "my-plugin"; - @tailwind utilities; + @tailwind utilities; - @theme reference { - --breakpoint-lg: 1024px; - } - ` + @theme reference { + --breakpoint-lg: 1024px; + } + `, - expect( - await run( - ['border-block', 'border-block-2', 'border-block/foo', 'border-block-2/foo'], - input, { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: ({ matchUtilities }: PluginAPI) => { matchUtilities( @@ -3826,18 +3481,31 @@ describe('matchUtilities()', () => { }), }, { - values: { DEFAULT: '1px', '2': '2px' }, - modifiers: { foo: 'foo' }, + values: { + DEFAULT: '1px', + '2': '2px', + }, + + modifiers: { + foo: 'foo', + }, }, ) }, } }, }, - ), + ) + + return compiled.build(candidates) + } + + expect( + optimizeCss( + await run(['border-block', 'border-block-2', 'border-block/foo', 'border-block-2/foo']), + ).trim(), ).toMatchInlineSnapshot(` - " - .border-block { + ".border-block { --my-modifier: none; border-block-width: 1px; } @@ -3855,33 +3523,11 @@ describe('matchUtilities()', () => { .border-block\\/foo { --my-modifier: foo; border-block-width: 1px; - } - " + }" `) expect( - await run(['border-block/unknown', 'border-block-2/unknown'], input, { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities( - { - 'border-block': (value, { modifier }) => ({ - '--my-modifier': modifier ?? 'none', - 'border-block-width': value, - }), - }, - { - values: { DEFAULT: '1px', '2': '2px' }, - modifiers: { foo: 'foo' }, - }, - ) - }, - } - }, - }), + optimizeCss(await run(['border-block/unknown', 'border-block-2/unknown'])).trim(), ).toEqual('') }) @@ -3889,127 +3535,190 @@ describe('matchUtilities()', () => { // future don't be afraid to change what should happen in this scenario. describe('plugins that handle a specific arbitrary value type prevent falling through to other plugins if the result is invalid for that plugin', () => { test('implicit color modifier', async () => { - let input = css` - @tailwind utilities; - @plugin "my-plugin"; - ` - - let options: Partial[1]> = { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-color': value }) }, - { type: ['color', 'any'] }, - ) - matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-width': value }) }, - { type: ['length'] }, - ) - }, - } - }, - } - - expect( - await run(['scrollbar-[2px]', 'scrollbar-[#08c]', 'scrollbar-[#08c]/50'], input, options), - ).toMatchInlineSnapshot(` - " - .scrollbar-\\[2px\\] { - scrollbar-width: 2px; - } - - .scrollbar-\\[\\#08c\\] { - scrollbar-color: #08c; - } - - .scrollbar-\\[\\#08c\\]\\/50 { - scrollbar-color: oklab(59.9824% -.067 -.124 / .5); - } - " - `) - expect(await run(['scrollbar-[2px]/50'], input, options)).toEqual('') - }) - - test('no modifiers are supported by the plugins', async () => { - expect( - await run( - ['scrollbar-[2px]/50'], + async function run(candidates: string[]) { + let compiled = await compile( css` @tailwind utilities; @plugin "my-plugin"; `, + { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: ({ matchUtilities }: PluginAPI) => { matchUtilities( - { scrollbar: (value) => ({ '--scrollbar-angle': value }) }, - { type: ['angle', 'any'] }, + { + scrollbar: (value) => ({ 'scrollbar-color': value }), + }, + { type: ['color', 'any'] }, ) matchUtilities( - { scrollbar: (value) => ({ '--scrollbar-width': value }) }, + { + scrollbar: (value) => ({ 'scrollbar-width': value }), + }, { type: ['length'] }, ) }, } }, }, - ), - ).toEqual('') + ) + + return compiled.build(candidates) + } + + expect( + optimizeCss( + await run(['scrollbar-[2px]', 'scrollbar-[#08c]', 'scrollbar-[#08c]/50']), + ).trim(), + ).toMatchInlineSnapshot(` + ".scrollbar-\\[\\#08c\\] { + scrollbar-color: #08c; + } + + .scrollbar-\\[\\#08c\\]\\/50 { + scrollbar-color: oklab(59.9824% -.06725 -.12414 / .5); + } + + .scrollbar-\\[2px\\] { + scrollbar-width: 2px; + }" + `) + expect(optimizeCss(await run(['scrollbar-[2px]/50'])).trim()).toEqual('') }) - test('invalid named modifier', async () => { - expect( - await run( - ['scrollbar-[2px]/foo'], + test('no modifiers are supported by the plugins', async () => { + async function run(candidates: string[]) { + let compiled = await compile( css` @tailwind utilities; @plugin "my-plugin"; `, + { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: ({ matchUtilities }: PluginAPI) => { matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-color': value }) }, + { + scrollbar: (value) => ({ '--scrollbar-angle': value }), + }, + { type: ['angle', 'any'] }, + ) + + matchUtilities( + { + scrollbar: (value) => ({ '--scrollbar-width': value }), + }, + { type: ['length'] }, + ) + }, + } + }, + }, + ) + + return compiled.build(candidates) + } + + expect(optimizeCss(await run(['scrollbar-[2px]/50'])).trim()).toEqual('') + }) + + test('invalid named modifier', async () => { + async function run(candidates: string[]) { + let compiled = await compile( + css` + @tailwind utilities; + @plugin "my-plugin"; + `, + + { + async loadModule(id, base) { + return { + base, + module: ({ matchUtilities }: PluginAPI) => { + matchUtilities( + { + scrollbar: (value) => ({ 'scrollbar-color': value }), + }, { type: ['color', 'any'], modifiers: { foo: 'foo' } }, ) matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-width': value }) }, + { + scrollbar: (value) => ({ 'scrollbar-width': value }), + }, { type: ['length'], modifiers: { bar: 'bar' } }, ) }, } }, }, - ), - ).toEqual('') + ) + + return compiled.build(candidates) + } + + expect(optimizeCss(await run(['scrollbar-[2px]/foo'])).trim()).toEqual('') }) }) test('custom functional utilities with different types', async () => { - let input = css` - @plugin "my-plugin"; + async function run(candidates: string[]) { + let compiled = await compile( + css` + @plugin "my-plugin"; - @tailwind utilities; + @tailwind utilities; - @theme reference { - --breakpoint-lg: 1024px; - } - ` + @theme reference { + --breakpoint-lg: 1024px; + } + `, + + { + async loadModule(id, base) { + return { + base, + module: ({ matchUtilities }: PluginAPI) => { + matchUtilities( + { + scrollbar: (value) => ({ 'scrollbar-color': value }), + }, + { + type: ['color', 'any'], + values: { + black: 'black', + }, + }, + ) + + matchUtilities( + { + scrollbar: (value) => ({ 'scrollbar-width': value }), + }, + { + type: ['length'], + values: { + 2: '2px', + }, + }, + ) + }, + } + }, + }, + ) + + return compiled.build(candidates) + } expect( - await run( - [ + optimizeCss( + await run([ 'scrollbar-black', 'scrollbar-black/50', 'scrollbar-2', @@ -4021,38 +3730,13 @@ describe('matchUtilities()', () => { 'scrollbar-[color:var(--my-color)]', 'scrollbar-[color:var(--my-color)]/50', 'scrollbar-[length:var(--my-width)]', - ], - input, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-color': value }) }, - { type: ['color', 'any'], values: { black: 'black' } }, - ) - - matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-width': value }) }, - { type: ['length'], values: { 2: '2px' } }, - ) - }, - } - }, - }, - ), + ]), + ).trim(), ).toMatchInlineSnapshot(` - " - .scrollbar-2, .scrollbar-\\[2px\\] { + ".scrollbar-2 { scrollbar-width: 2px; } - .scrollbar-\\[length\\:var\\(--my-width\\)\\] { - scrollbar-width: var(--my-width); - } - .scrollbar-\\[\\#fff\\] { scrollbar-color: #fff; } @@ -4061,24 +3745,28 @@ describe('matchUtilities()', () => { scrollbar-color: oklab(100% 0 5.96046e-8 / .5); } - .scrollbar-\\[color\\:var\\(--my-color\\)\\], .scrollbar-\\[color\\:var\\(--my-color\\)\\]\\/50 { + .scrollbar-\\[2px\\] { + scrollbar-width: 2px; + } + + .scrollbar-\\[color\\:var\\(--my-color\\)\\] { scrollbar-color: var(--my-color); } - @supports (color: color-mix(in lab, red, red)) { - .scrollbar-\\[color\\:var\\(--my-color\\)\\]\\/50 { - scrollbar-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } + .scrollbar-\\[color\\:var\\(--my-color\\)\\]\\/50 { + scrollbar-color: color-mix(in oklab, var(--my-color) 50%, transparent); } - .scrollbar-\\[var\\(--my-color\\)\\], .scrollbar-\\[var\\(--my-color\\)\\]\\/50 { + .scrollbar-\\[length\\:var\\(--my-width\\)\\] { + scrollbar-width: var(--my-width); + } + + .scrollbar-\\[var\\(--my-color\\)\\] { scrollbar-color: var(--my-color); } - @supports (color: color-mix(in lab, red, red)) { - .scrollbar-\\[var\\(--my-color\\)\\]\\/50 { - scrollbar-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } + .scrollbar-\\[var\\(--my-color\\)\\]\\/50 { + scrollbar-color: color-mix(in oklab, var(--my-color) 50%, transparent); } .scrollbar-black { @@ -4087,48 +3775,23 @@ describe('matchUtilities()', () => { .scrollbar-black\\/50 { scrollbar-color: oklab(0% none none / .5); - } - " + }" `) expect( - await run( - ['scrollbar-2/50', 'scrollbar-[2px]/50', 'scrollbar-[length:var(--my-width)]/50'], - input, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-color': value }) }, - { type: ['color', 'any'], values: { black: 'black' } }, - ) - - matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-width': value }) }, - { type: ['length'], values: { 2: '2px' } }, - ) - }, - } - }, - }, - ), + optimizeCss( + await run([ + 'scrollbar-2/50', + 'scrollbar-[2px]/50', + 'scrollbar-[length:var(--my-width)]/50', + ]), + ).trim(), ).toEqual('') }) test('functional utilities with `type: color` automatically support opacity', async () => { - expect( - await run( - [ - 'scrollbar-current', - 'scrollbar-current/45', - 'scrollbar-black', - 'scrollbar-black/33', - 'scrollbar-black/[50%]', - 'scrollbar-[var(--my-color)]/[25%]', - ], + async function run(candidates: string[]) { + let compiled = await compile( css` @plugin "my-plugin"; @@ -4138,31 +3801,46 @@ describe('matchUtilities()', () => { --breakpoint-lg: 1024px; } `, + { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: ({ matchUtilities }: PluginAPI) => { matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-color': value }) }, - { type: ['color', 'any'], values: { black: 'black' } }, + { + scrollbar: (value) => ({ 'scrollbar-color': value }), + }, + { + type: ['color', 'any'], + values: { + black: 'black', + }, + }, ) }, } }, }, - ), - ).toMatchInlineSnapshot(` - " - .scrollbar-\\[var\\(--my-color\\)\\]\\/\\[25\\%\\] { - scrollbar-color: var(--my-color); - } + ) - @supports (color: color-mix(in lab, red, red)) { - .scrollbar-\\[var\\(--my-color\\)\\]\\/\\[25\\%\\] { - scrollbar-color: color-mix(in oklab, var(--my-color) 25%, transparent); - } + return compiled.build(candidates) + } + + expect( + optimizeCss( + await run([ + 'scrollbar-current', + 'scrollbar-current/45', + 'scrollbar-black', + 'scrollbar-black/33', + 'scrollbar-black/[50%]', + 'scrollbar-[var(--my-color)]/[25%]', + ]), + ).trim(), + ).toMatchInlineSnapshot(` + ".scrollbar-\\[var\\(--my-color\\)\\]\\/\\[25\\%\\] { + scrollbar-color: color-mix(in oklab, var(--my-color) 25%, transparent); } .scrollbar-black { @@ -4177,23 +3855,19 @@ describe('matchUtilities()', () => { scrollbar-color: oklab(0% none none / .5); } - .scrollbar-current, .scrollbar-current\\/45 { - scrollbar-color: currentcolor; + .scrollbar-current { + scrollbar-color: currentColor; } - @supports (color: color-mix(in lab, red, red)) { - .scrollbar-current\\/45 { - scrollbar-color: color-mix(in oklab, currentcolor 45%, transparent); - } - } - " + .scrollbar-current\\/45 { + scrollbar-color: color-mix(in oklab, currentColor 45%, transparent); + }" `) }) test('functional utilities with explicit modifiers', async () => { - expect( - await run( - ['scrollbar-[12px]', 'scrollbar-[12px]/foo', 'scrollbar-[12px]/bar'], + async function run(candidates: string[]) { + let compiled = await compile( css` @plugin "my-plugin"; @@ -4204,10 +3878,10 @@ describe('matchUtilities()', () => { --opacity-my-opacity: 0.5; } `, + { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: ({ matchUtilities }: PluginAPI) => { matchUtilities( @@ -4217,16 +3891,29 @@ describe('matchUtilities()', () => { 'scrollbar-width': value, }), }, - { type: ['any'], values: {}, modifiers: { foo: 'foo' } }, + { + type: ['any'], + values: {}, + modifiers: { + foo: 'foo', + }, + }, ) }, } }, }, - ), + ) + + return compiled.build(candidates) + } + + expect( + optimizeCss( + await run(['scrollbar-[12px]', 'scrollbar-[12px]/foo', 'scrollbar-[12px]/bar']), + ).trim(), ).toMatchInlineSnapshot(` - " - .scrollbar-\\[12px\\] { + ".scrollbar-\\[12px\\] { --modifier: none; scrollbar-width: 12px; } @@ -4234,43 +3921,50 @@ describe('matchUtilities()', () => { .scrollbar-\\[12px\\]\\/foo { --modifier: foo; scrollbar-width: 12px; - } - " + }" `) }) test('functional utilities support `@apply`', async () => { - expect( - await run( - ['foo-bar', 'lg:foo-bar', 'foo-[12px]', 'lg:foo-[12px]'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } + let compiled = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } - @theme reference { - --breakpoint-lg: 1024px; + @theme reference { + --breakpoint-lg: 1024px; + } + `, + { + async loadModule(id, base) { + return { + base, + module: ({ matchUtilities }: PluginAPI) => { + matchUtilities( + { + foo: (value) => ({ + '--foo': value, + [`@apply flex`]: {}, + }), + }, + { + values: { + bar: 'bar', + }, + }, + ) + }, } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities( - { foo: (value) => ({ '--foo': value, [`@apply flex`]: {} }) }, - { values: { bar: 'bar' } }, - ) - }, - } - }, }, - ), + }, + ) + + expect( + optimizeCss(compiled.build(['foo-bar', 'lg:foo-bar', 'foo-[12px]', 'lg:foo-[12px]'])).trim(), ).toMatchInlineSnapshot(` - " - @layer utilities { + "@layer utilities { .foo-\\[12px\\] { --foo: 12px; display: flex; @@ -4281,7 +3975,7 @@ describe('matchUtilities()', () => { display: flex; } - @media (min-width: 1024px) { + @media (width >= 1024px) { .lg\\:foo-\\[12px\\] { --foo: 12px; display: flex; @@ -4292,8 +3986,7 @@ describe('matchUtilities()', () => { display: flex; } } - } - " + }" `) }) @@ -4312,9 +4005,8 @@ describe('matchUtilities()', () => { `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: ({ matchUtilities }: PluginAPI) => { matchUtilities({ @@ -4328,103 +4020,119 @@ describe('matchUtilities()', () => { }, }, ) - }).rejects.toThrow(/invalid utility name/) + }).rejects.toThrowError(/invalid utility name/) }) test('replaces the class name with variants in nested selectors', async () => { - expect( - await run( - ['foo-red', 'md:foo-red', 'not-hover:md:foo-red'], - css` - @plugin "my-plugin"; - @theme { - --breakpoint-md: 768px; - } - @tailwind utilities; - `, - { - async loadModule(base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities( - { foo: (value) => ({ ':where(.foo > :first-child)': { color: value } }) }, - { values: { red: 'red' } }, - ) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .foo-red :where(.foo-red > :first-child) { - color: red; - } - - @media (min-width: 768px) { - .md\\:foo-red :where(.md\\:foo-red > :first-child), .not-hover\\:md\\:foo-red:not(:hover) :where(.not-hover\\:md\\:foo-red > :first-child) { - color: red; + let compiled = await compile( + css` + @plugin "my-plugin"; + @theme { + --breakpoint-md: 768px; } - } + @tailwind utilities; + `, + { + async loadModule(base) { + return { + base, + module: ({ matchUtilities }: PluginAPI) => { + matchUtilities( + { + foo: (value) => ({ + ':where(.foo > :first-child)': { + color: value, + }, + }), + }, + { + values: { + red: 'red', + }, + }, + ) + }, + } + }, + }, + ) - @media not all and (hover: hover) { - @media (min-width: 768px) { - .not-hover\\:md\\:foo-red :where(.not-hover\\:md\\:foo-red > :first-child) { + expect(compiled.build(['foo-red', 'md:foo-red', 'not-hover:md:foo-red']).trim()) + .toMatchInlineSnapshot(` + ".foo-red { + :where(.foo-red > :first-child) { color: red; } } - } - " - `) + .md\\:foo-red { + @media (width >= 768px) { + :where(.md\\:foo-red > :first-child) { + color: red; + } + } + } + .not-hover\\:md\\:foo-red { + &:not(*:hover) { + @media (width >= 768px) { + :where(.not-hover\\:md\\:foo-red > :first-child) { + color: red; + } + } + } + @media not (hover: hover) { + @media (width >= 768px) { + :where(.not-hover\\:md\\:foo-red > :first-child) { + color: red; + } + } + } + }" + `) }) }) describe('addComponents()', () => { test('is an alias for addUtilities', async () => { - expect( - await run( - ['btn', 'btn-blue', 'btn-red'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addComponents }: PluginAPI) => { - addComponents({ - '.btn': { - padding: '.5rem 1rem', - borderRadius: '.25rem', - fontWeight: '600', + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { + async loadModule(id, base) { + return { + base, + module: ({ addComponents }: PluginAPI) => { + addComponents({ + '.btn': { + padding: '.5rem 1rem', + borderRadius: '.25rem', + fontWeight: '600', + }, + '.btn-blue': { + backgroundColor: '#3490dc', + color: '#fff', + '&:hover': { + backgroundColor: '#2779bd', }, - '.btn-blue': { - backgroundColor: '#3490dc', - color: '#fff', - '&:hover': { - backgroundColor: '#2779bd', - }, + }, + '.btn-red': { + backgroundColor: '#e3342f', + color: '#fff', + '&:hover': { + backgroundColor: '#cc1f1a', }, - '.btn-red': { - backgroundColor: '#e3342f', - color: '#fff', - '&:hover': { - backgroundColor: '#cc1f1a', - }, - }, - }) - }, - } - }, + }, + }) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - .btn { + }, + ) + + expect(optimizeCss(compiled.build(['btn', 'btn-blue', 'btn-red'])).trim()) + .toMatchInlineSnapshot(` + ".btn { border-radius: .25rem; padding: .5rem 1rem; font-weight: 600; @@ -4446,39 +4154,44 @@ describe('addComponents()', () => { .btn-red:hover { background-color: #cc1f1a; - } - " + }" `) }) }) describe('matchComponents()', () => { test('is an alias for matchUtilities', async () => { - expect( - await run( - ['prose', 'sm:prose-sm', 'hover:prose-lg'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchComponents }: PluginAPI) => { - matchComponents( - { prose: (value) => ({ '--container-size': value }) }, - { values: { DEFAULT: 'normal', sm: 'sm', lg: 'lg' } }, - ) - }, - } - }, + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { + async loadModule(id, base) { + return { + base, + module: ({ matchComponents }: PluginAPI) => { + matchComponents( + { + prose: (value) => ({ '--container-size': value }), + }, + { + values: { + DEFAULT: 'normal', + sm: 'sm', + lg: 'lg', + }, + }, + ) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - .prose { + }, + ) + + expect(optimizeCss(compiled.build(['prose', 'sm:prose-sm', 'hover:prose-lg'])).trim()) + .toMatchInlineSnapshot(` + ".prose { --container-size: normal; } @@ -4486,23 +4199,21 @@ describe('matchComponents()', () => { .hover\\:prose-lg:hover { --container-size: lg; } - } - " + }" `) }) }) describe('prefix()', () => { test('is an identity function', async () => { - using fn = vi.fn() + let fn = vi.fn() await compile( css` @plugin "my-plugin"; `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: ({ prefix }: PluginAPI) => { fn(prefix('btn')) @@ -4518,15 +4229,14 @@ describe('prefix()', () => { describe('config()', () => { test('can return the resolved config when passed no arguments', async () => { - using fn = vi.fn() + let fn = vi.fn() await compile( css` @plugin "my-plugin"; `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: ({ config }: PluginAPI) => { fn(config()) @@ -4546,15 +4256,14 @@ describe('config()', () => { }) test('can return part of the config', async () => { - using fn = vi.fn() + let fn = vi.fn() await compile( css` @plugin "my-plugin"; `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: ({ config }: PluginAPI) => { fn(config('theme')) @@ -4572,15 +4281,14 @@ describe('config()', () => { }) test('falls back to default value if requested path does not exist', async () => { - using fn = vi.fn() + let fn = vi.fn() await compile( css` @plugin "my-plugin"; `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { - path: '', base, module: ({ config }: PluginAPI) => { fn(config('somekey', 'defaultvalue')) @@ -4592,40 +4300,4 @@ describe('config()', () => { expect(fn).toHaveBeenCalledWith('defaultvalue') }) - - // https://github.com/tailwindlabs/tailwindcss/issues/19721 - test('matchUtilities does not match Object.prototype properties as values', async ({ - expect, - }) => { - // These should not crash or produce output - expect( - await run( - [ - 'test-constructor', - 'test-hasOwnProperty', - 'test-toString', - 'test-valueOf', - 'test-__proto__', - ], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ matchUtilities }) { - matchUtilities( - { test: (value) => ({ '--test': value }) }, - { values: { foo: 'bar' } }, - ) - }), - } - }, - }, - ), - ).toEqual('') - }) }) diff --git a/packages/tailwindcss/src/compat/plugin-api.ts b/packages/tailwindcss/src/compat/plugin-api.ts index 11878fb30..9a2f5d117 100644 --- a/packages/tailwindcss/src/compat/plugin-api.ts +++ b/packages/tailwindcss/src/compat/plugin-api.ts @@ -1,12 +1,10 @@ import type { Features } from '..' import { substituteAtApply } from '../apply' -import { atRule, cloneAstNode, context, decl, rule, type AstNode } from '../ast' +import { atRule, decl, rule, walk, type AstNode } from '../ast' import type { Candidate, CandidateModifier, NamedUtilityValue } from '../candidate' import { substituteFunctions } from '../css-functions' import * as CSS from '../css-parser' import type { DesignSystem } from '../design-system' -import * as SelectorParser from '../selector-parser' -import type { SourceLocation } from '../source-maps/source' import { withAlpha } from '../utilities' import { DefaultMap } from '../utils/default-map' import { escape } from '../utils/escape' @@ -14,9 +12,9 @@ import { inferDataType } from '../utils/infer-data-type' import { segment } from '../utils/segment' import { toKeyPath } from '../utils/to-key-path' import { compoundsForSelectors, IS_VALID_VARIANT_NAME, substituteAtSlot } from '../variants' -import { walk, WalkAction } from '../walk' import type { ResolvedConfig, UserConfig } from './config/types' import { createThemeFn } from './plugin-functions' +import * as SelectorParser from './selector-parser' export type Config = UserConfig export type PluginFn = (api: PluginAPI) => void @@ -26,7 +24,6 @@ export type PluginWithConfig = { /** @internal */ reference?: boolean - src?: SourceLocation | undefined } export type PluginWithOptions = { (options?: T): PluginWithConfig @@ -96,50 +93,28 @@ export function buildPluginApi({ resolvedConfig, featuresRef, referenceMode, - src, }: { designSystem: DesignSystem ast: AstNode[] resolvedConfig: ResolvedConfig featuresRef: { current: Features } referenceMode: boolean - src: SourceLocation | undefined }): PluginAPI { let api: PluginAPI = { addBase(css) { if (referenceMode) return let baseNodes = objectToAst(css) featuresRef.current |= substituteFunctions(baseNodes, designSystem) - let rule = atRule('@layer', 'base', baseNodes) - walk([rule], (node) => { - node.src = src - }) - ast.push(rule) + ast.push(atRule('@layer', 'base', baseNodes)) }, addVariant(name, variant) { if (!IS_VALID_VARIANT_NAME.test(name)) { throw new Error( - `\`addVariant('${name}')\` defines an invalid variant name. Variants should only contain alphanumeric, dashes, or underscore characters and start with a lowercase letter or number.`, + `\`addVariant('${name}')\` defines an invalid variant name. Variants should only contain alphanumeric, dashes or underscore characters.`, ) } - // Ignore variants emitting v3 `:merge(…)` rules. In v4, the `group-*` and `peer-*` variants - // compound automatically. - if (typeof variant === 'string') { - if (variant.includes(':merge(')) return - } else if (Array.isArray(variant)) { - if (variant.some((v) => v.includes(':merge('))) return - } else if (typeof variant === 'object') { - function keyIncludes(object: Record, search: string): boolean { - return Object.entries(object).some( - ([key, value]) => - key.includes(search) || (typeof value === 'object' && keyIncludes(value, search)), - ) - } - if (keyIncludes(variant, ':merge(')) return - } - // Single selector or multiple parallel selectors if (typeof variant === 'string' || Array.isArray(variant)) { designSystem.variants.static( @@ -155,7 +130,7 @@ export function buildPluginApi({ // CSS-in-JS object else if (typeof variant === 'object') { - designSystem.variants.fromAst(name, objectToAst(variant), designSystem) + designSystem.variants.fromAst(name, objectToAst(variant)) } }, matchVariant(name, fn, options) { @@ -168,17 +143,6 @@ export function buildPluginApi({ return parseVariantValue(resolved, nodes) } - try { - // Sample variant value and ignore variants emitting v3 `:merge` rules. In - // v4, the `group-*` and `peer-*` variants compound automatically. - let sample = fn('a', { modifier: null }) - if (typeof sample === 'string' && sample.includes(':merge(')) { - return - } else if (Array.isArray(sample) && sample.some((r) => r.includes(':merge('))) { - return - } - } catch {} - let defaultOptionKeys = Object.keys(options?.values ?? {}) designSystem.variants.group( () => { @@ -202,17 +166,12 @@ export function buildPluginApi({ ruleNodes.nodes, ) } else if (variant.value.kind === 'named' && options?.values) { - if (!Object.hasOwn(options.values, variant.value.value)) { - return null - } let defaultValue = options.values[variant.value.value] if (typeof defaultValue !== 'string') { - return null + return } ruleNodes.nodes = resolveVariantValue(defaultValue, variant.modifier, ruleNodes.nodes) - } else { - return null } }) }, @@ -226,14 +185,8 @@ export function buildPluginApi({ let aValueKey = a.value ? a.value.value : 'DEFAULT' let zValueKey = z.value ? z.value.value : 'DEFAULT' - let aValue = - (options?.values && Object.hasOwn(options.values, aValueKey) - ? options.values[aValueKey] - : undefined) ?? aValueKey - let zValue = - (options?.values && Object.hasOwn(options.values, zValueKey) - ? options.values[zValueKey] - : undefined) ?? zValueKey + let aValue = options?.values?.[aValueKey] ?? aValueKey + let zValue = options?.values?.[zValueKey] ?? zValueKey if (options && typeof options.sort === 'function') { return options.sort( @@ -257,10 +210,6 @@ export function buildPluginApi({ return aValue < zValue ? -1 : 1 }, ) - - designSystem.variants.suggest(name, () => - Object.keys(options?.values ?? {}).filter((v) => v !== 'DEFAULT'), - ) }, addUtilities(utilities) { @@ -279,11 +228,7 @@ export function buildPluginApi({ for (let [name, css] of entries) { if (name.startsWith('@keyframes ')) { if (!referenceMode) { - let keyframes = rule(name, objectToAst(css)) - walk([keyframes], (node) => { - node.src = src - }) - ast.push(keyframes) + ast.push(rule(name, objectToAst(css))) } continue } @@ -291,7 +236,7 @@ export function buildPluginApi({ let selectorAst = SelectorParser.parse(name) let foundValidUtility = false - walk(selectorAst, (node) => { + SelectorParser.walk(selectorAst, (node) => { if ( node.kind === 'selector' && node.value[0] === '.' && @@ -310,15 +255,8 @@ export function buildPluginApi({ return } - if ( - node.kind === 'function' && - (node.value === ':not' || - // A class inside `:nth-child(… of )` is part of the - // condition, not a utility being defined. - node.value === ':nth-child' || - node.value === ':nth-last-child') - ) { - return WalkAction.Skip + if (node.kind === 'function' && node.value === ':not') { + return SelectorParser.SelectorWalkAction.Skip } }) @@ -335,7 +273,7 @@ export function buildPluginApi({ walk(ast, (node) => { if (node.kind === 'rule') { let selectorAst = SelectorParser.parse(node.selector) - walk(selectorAst, (node) => { + SelectorParser.walk(selectorAst, (node) => { if (node.kind === 'selector' && node.value[0] === '.') { node.value = `.${designSystem.theme.prefix}\\:${node.value.slice(1)}` } @@ -346,7 +284,7 @@ export function buildPluginApi({ } designSystem.utilities.static(className, (candidate) => { - let clonedAst = ast.map(cloneAstNode) + let clonedAst = structuredClone(ast) replaceNestedClassNameReferences(clonedAst, className, candidate.raw) featuresRef.current |= substituteAtApply(clonedAst, designSystem) return clonedAst @@ -406,13 +344,13 @@ export function buildPluginApi({ let values = options?.values ?? {} if (isColor) { - // Color utilities implicitly support `inherit`, `transparent`, and `currentcolor` + // Color utilities implicitly support `inherit`, `transparent`, and `currentColor` // for backwards compatibility but still allow them to be overridden values = Object.assign( { inherit: 'inherit', transparent: 'transparent', - current: 'currentcolor', + current: 'currentColor', }, values, ) @@ -422,13 +360,10 @@ export function buildPluginApi({ value = values.DEFAULT ?? null } else if (candidate.value.kind === 'arbitrary') { value = candidate.value.value - } else if ( - candidate.value.fraction && - Object.hasOwn(values, candidate.value.fraction) - ) { + } else if (candidate.value.fraction && values[candidate.value.fraction]) { value = values[candidate.value.fraction] ignoreModifier = true - } else if (Object.hasOwn(values, candidate.value.value)) { + } else if (values[candidate.value.value]) { value = values[candidate.value.value] } else if (values.__BARE_VALUE__) { value = values.__BARE_VALUE__(candidate.value) ?? null @@ -449,7 +384,7 @@ export function buildPluginApi({ modifier = null } else if (modifiers === 'any' || candidate.modifier.kind === 'arbitrary') { modifier = candidate.modifier.value - } else if (modifiers && Object.hasOwn(modifiers, candidate.modifier.value)) { + } else if (modifiers?.[candidate.modifier.value]) { modifier = modifiers[candidate.modifier.value] } else if (isColor && !Number.isNaN(Number(candidate.modifier.value))) { modifier = `${candidate.modifier.value}%` @@ -492,10 +427,6 @@ export function buildPluginApi({ // work even with legacy configs and plugins valueKeys.delete('__BARE_VALUE__') - // The `__CSS_VALUES__` key is a special key used by the theme function - // to transport `@theme` metadata about values from CSS - valueKeys.delete('__CSS_VALUES__') - // The `DEFAULT` key is represented as `null` in the utility API if (valueKeys.has('DEFAULT')) { valueKeys.delete('DEFAULT') @@ -570,13 +501,6 @@ export function objectToAst(rules: CssInJs | CssInJs[]): AstNode[] { let entries = rules.flatMap((rule) => Object.entries(rule)) for (let [name, value] of entries) { - if (value === null || value === undefined) continue - - // @ts-expect-error - // We do not want `false` present in the types but still need to discard these nodes for - // compatibility purposes - if (value === false) continue - if (typeof value !== 'object') { if (!name.startsWith('--')) { if (value === '@slot') { @@ -598,7 +522,7 @@ export function objectToAst(rules: CssInJs | CssInJs[]): AstNode[] { ast.push(rule(name, objectToAst(item))) } } - } else { + } else if (value !== null) { ast.push(rule(name, objectToAst(value))) } } @@ -608,28 +532,14 @@ export function objectToAst(rules: CssInJs | CssInJs[]): AstNode[] { function parseVariantValue(resolved: string | string[], nodes: AstNode[]): AstNode[] { let resolvedArray = typeof resolved === 'string' ? [resolved] : resolved - return resolvedArray.flatMap((r): AstNode | AstNode[] => { + return resolvedArray.flatMap((r) => { if (r.trim().endsWith('}')) { - let usesAtScope = r.includes('@scope') let updatedCSS = r.replace('}', '{@slot}}') let ast = CSS.parse(updatedCSS) - - if (usesAtScope) { - substituteAtSlot(ast, [context({ source: 'user' }, nodes)]) - return context({ source: 'variant' }, ast) - } - substituteAtSlot(ast, nodes) return ast } else { - let node = rule(r, nodes) - - if (node.kind === 'at-rule' && node.name === '@scope') { - node.nodes = [context({ source: 'user' }, nodes)] - return context({ source: 'variant' }, [node]) - } - - return node + return rule(r, nodes) } }) } @@ -646,7 +556,7 @@ function replaceNestedClassNameReferences( walk(ast, (node) => { if (node.kind === 'rule') { let selectorAst = SelectorParser.parse(node.selector) - walk(selectorAst, (node) => { + SelectorParser.walk(selectorAst, (node) => { if (node.kind === 'selector' && node.value === `.${utilityName}`) { node.value = `.${escape(rawCandidate)}` } diff --git a/packages/tailwindcss/src/compat/plugin-functions.ts b/packages/tailwindcss/src/compat/plugin-functions.ts index bbd5fac46..339bd11c6 100644 --- a/packages/tailwindcss/src/compat/plugin-functions.ts +++ b/packages/tailwindcss/src/compat/plugin-functions.ts @@ -100,28 +100,6 @@ export function createThemeFn( return [base, extra] } - // If `--color-foo` and `--color-foo-bar` are both defined, `colors.foo` - // produces a synthetic object: - // - // ```ts - // { DEFAULT: 'red', bar: 'blue', __CSS_VALUES__: { DEFAULT: 0, bar: 0 } } - // ``` - // - // Prefer `DEFAULT` instead of exposing the object to plugin code. - // - // This only applies to lookups of a specific key (e.g. `colors.foo`). A - // namespace root lookup (e.g. `theme('shadow')`) resolves to the whole - // namespace as an object, like in v3. - if ( - keypath.length > 1 && - cssValue !== null && - typeof cssValue === 'object' && - !Array.isArray(cssValue) && - 'DEFAULT' in cssValue - ) { - return cssValue.DEFAULT - } - // Values from CSS take precedence over values from the config return cssValue ?? configValue })() @@ -230,12 +208,7 @@ function readFromCss( // The request looked like `theme('animation.spin')` and was turned into a // lookup for `--animation-spin-*` which had only one entry which means it // should be returned directly. - // - // This only applies to lookups of a specific key (`path.length > 1`). A - // namespace root lookup like `theme('shadow')` resolves to the whole - // namespace as an object, like in v3, so it must keep the object shape (`{ - // DEFAULT: … }`). - if (path.length > 1 && 'DEFAULT' in obj && Object.keys(obj).length === 1) { + if ('DEFAULT' in obj && Object.keys(obj).length === 1) { return [obj.DEFAULT as any, optionsObj.DEFAULT ?? ThemeOptions.NONE] as const } @@ -250,10 +223,8 @@ function get(obj: any, path: string[]) { for (let i = 0; i < path.length; ++i) { let key = path[i] - // The key does not exist so concatenate it with the next key. - // We use Object.hasOwn to avoid matching inherited prototype properties - // (e.g. "constructor", "toString") when traversing config objects. - if (obj === null || obj === undefined || typeof obj !== 'object' || !Object.hasOwn(obj, key)) { + // The key does not exist so concatenate it with the next key + if (obj?.[key] === undefined) { if (path[i + 1] === undefined) { return undefined } diff --git a/packages/tailwindcss/src/compat/screens-config.test.ts b/packages/tailwindcss/src/compat/screens-config.test.ts index d81cd8de6..ac73d8216 100644 --- a/packages/tailwindcss/src/compat/screens-config.test.ts +++ b/packages/tailwindcss/src/compat/screens-config.test.ts @@ -1,89 +1,85 @@ import { describe, expect, test } from 'vitest' -import { run } from '../test-utils/run' +import { compile } from '..' const css = String.raw test('CSS `--breakpoint-*` merge with JS config `screens`', async () => { - expect( - await run( - [ - 'sm:flex', - 'md:flex', - 'lg:flex', - 'min-sm:max-md:underline', - 'min-md:max-lg:underline', - 'max-w-screen-sm', - // Ensure other core variants appear at the end - 'print:items-end', - ], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @theme { - --breakpoint-md: 50rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - sm: '44rem', - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @theme { + --breakpoint-md: 50rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + screens: { + sm: '44rem', }, }, - base: '/root', - path: '', - }), + }, }, - ), + base: '/root', + }), + }) + + expect( + compiler.build([ + 'sm:flex', + 'md:flex', + 'lg:flex', + 'min-sm:max-md:underline', + 'min-md:max-lg:underline', + 'max-w-screen-sm', + // Ensure other core variants appear at the end + 'print:items-end', + ]), ).toMatchInlineSnapshot(` - " - .max-w-screen-sm { + ".max-w-screen-sm { max-width: 44rem; } - - @media (min-width: 44rem) { - .sm\\:flex { + .sm\\:flex { + @media (width >= 44rem) { display: flex; } - - @media not all and (min-width: 50rem) { - .min-sm\\:max-md\\:underline { + } + .min-sm\\:max-md\\:underline { + @media (width >= 44rem) { + @media (width < 50rem) { text-decoration-line: underline; } } } - - @media (min-width: 50rem) { - .md\\:flex { + .md\\:flex { + @media (width >= 50rem) { display: flex; } - - @media not all and (min-width: 64rem) { - .min-md\\:max-lg\\:underline { + } + .min-md\\:max-lg\\:underline { + @media (width >= 50rem) { + @media (width < 64rem) { text-decoration-line: underline; } } } - - @media (min-width: 64rem) { - .lg\\:flex { + .lg\\:flex { + @media (width >= 64rem) { display: flex; } } - - @media print { - .print\\:items-end { + .print\\:items-end { + @media print { align-items: flex-end; } } @@ -92,98 +88,100 @@ test('CSS `--breakpoint-*` merge with JS config `screens`', async () => { }) test('JS config `screens` extend CSS `--breakpoint-*`', async () => { - expect( - await run( - [ - // Order is messed up on purpose - 'md:flex', - 'sm:flex', - 'lg:flex', - 'xs:flex', - 'min-md:max-lg:underline', - 'min-sm:max-md:underline', - 'min-xs:flex', - 'min-xs:max-md:underline', + let input = css` + @theme default { + --breakpoint-xs: 39rem; + --breakpoint-md: 49rem; + } + @theme { + --breakpoint-md: 50rem; + } + @config "./config.js"; + @tailwind utilities; + ` - // Ensure other core variants appear at the end - 'print:items-end', - ], - css` - @theme default { - --breakpoint-xs: 39rem; - --breakpoint-md: 49rem; - } - @theme { - --breakpoint-md: 50rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - xs: '30rem', - sm: '40rem', - md: '48rem', - lg: '60rem', - }, - }, + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + screens: { + xs: '30rem', + sm: '40rem', + md: '48rem', + lg: '60rem', }, }, - base: '/root', - path: '', - }), + }, }, - ), + base: '/root', + }), + }) + + expect( + compiler.build([ + // Order is messed up on purpose + 'md:flex', + 'sm:flex', + 'lg:flex', + 'xs:flex', + 'min-md:max-lg:underline', + 'min-sm:max-md:underline', + 'min-xs:flex', + 'min-xs:max-md:underline', + + // Ensure other core variants appear at the end + 'print:items-end', + ]), ).toMatchInlineSnapshot(` - " - @media (min-width: 30rem) { - .min-xs\\:flex, .xs\\:flex { + ".min-xs\\:flex { + @media (width >= 30rem) { display: flex; } - - @media not all and (min-width: 50rem) { - .min-xs\\:max-md\\:underline { + } + .xs\\:flex { + @media (width >= 30rem) { + display: flex; + } + } + .min-xs\\:max-md\\:underline { + @media (width >= 30rem) { + @media (width < 50rem) { text-decoration-line: underline; } } } - - @media (min-width: 40rem) { - .sm\\:flex { + .sm\\:flex { + @media (width >= 40rem) { display: flex; } - - @media not all and (min-width: 50rem) { - .min-sm\\:max-md\\:underline { + } + .min-sm\\:max-md\\:underline { + @media (width >= 40rem) { + @media (width < 50rem) { text-decoration-line: underline; } } } - - @media (min-width: 50rem) { - .md\\:flex { + .md\\:flex { + @media (width >= 50rem) { display: flex; } - - @media not all and (min-width: 60rem) { - .min-md\\:max-lg\\:underline { + } + .min-md\\:max-lg\\:underline { + @media (width >= 50rem) { + @media (width < 60rem) { text-decoration-line: underline; } } } - - @media (min-width: 60rem) { - .lg\\:flex { + .lg\\:flex { + @media (width >= 60rem) { display: flex; } } - - @media print { - .print\\:items-end { + .print\\:items-end { + @media print { align-items: flex-end; } } @@ -192,73 +190,70 @@ test('JS config `screens` extend CSS `--breakpoint-*`', async () => { }) test('JS config `screens` only setup, even if those match the default-theme export', async () => { - expect( - await run( - [ - // Order is messed up on purpose - 'md:flex', - 'sm:flex', - 'lg:flex', - 'min-md:max-lg:underline', - 'min-sm:max-md:underline', + let input = css` + @config "./config.js"; + @tailwind utilities; + ` - // Ensure other core variants appear at the end - 'print:items-end', - ], - css` - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - screens: { - sm: '40rem', - md: '48rem', - lg: '64rem', - }, - }, + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + screens: { + sm: '40rem', + md: '48rem', + lg: '64rem', }, - base: '/root', - path: '', - }), + }, }, - ), + base: '/root', + }), + }) + + expect( + compiler.build([ + // Order is messed up on purpose + 'md:flex', + 'sm:flex', + 'lg:flex', + 'min-md:max-lg:underline', + 'min-sm:max-md:underline', + + // Ensure other core variants appear at the end + 'print:items-end', + ]), ).toMatchInlineSnapshot(` - " - @media (min-width: 40rem) { - .sm\\:flex { + ".sm\\:flex { + @media (width >= 40rem) { display: flex; } - - @media not all and (min-width: 48rem) { - .min-sm\\:max-md\\:underline { + } + .min-sm\\:max-md\\:underline { + @media (width >= 40rem) { + @media (width < 48rem) { text-decoration-line: underline; } } } - - @media (min-width: 48rem) { - .md\\:flex { + .md\\:flex { + @media (width >= 48rem) { display: flex; } - - @media not all and (min-width: 64rem) { - .min-md\\:max-lg\\:underline { + } + .min-md\\:max-lg\\:underline { + @media (width >= 48rem) { + @media (width < 64rem) { text-decoration-line: underline; } } } - - @media (min-width: 64rem) { - .lg\\:flex { + .lg\\:flex { + @media (width >= 64rem) { display: flex; } } - - @media print { - .print\\:items-end { + .print\\:items-end { + @media print { align-items: flex-end; } } @@ -267,85 +262,106 @@ test('JS config `screens` only setup, even if those match the default-theme expo }) test('JS config `screens` overwrite CSS `--breakpoint-*`', async () => { - expect( - await run( - [ - 'sm:flex', - 'md:flex', - 'mini:flex', - 'midi:flex', - 'maxi:flex', - 'min-md:max-lg:underline', - 'min-sm:max-md:underline', - 'min-midi:max-maxi:underline', - 'min-mini:max-midi:underline', + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @config "./config.js"; + @tailwind utilities; + ` - // Ensure other core variants appear at the end - 'print:items-end', - ], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - mini: '40rem', - midi: '48rem', - maxi: '64rem', - }, - }, + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + screens: { + mini: '40rem', + midi: '48rem', + maxi: '64rem', }, }, - base: '/root', - path: '', - }), + }, }, - ), + base: '/root', + }), + }) + + expect( + compiler.build([ + 'sm:flex', + 'md:flex', + 'mini:flex', + 'midi:flex', + 'maxi:flex', + 'min-md:max-lg:underline', + 'min-sm:max-md:underline', + 'min-midi:max-maxi:underline', + 'min-mini:max-midi:underline', + + // Ensure other core variants appear at the end + 'print:items-end', + ]), ).toMatchInlineSnapshot(` - " - @media (min-width: 40rem) { - .mini\\:flex, .sm\\:flex { + ".mini\\:flex { + @media (width >= 40rem) { display: flex; } - - @media not all and (min-width: 48rem) { - .min-mini\\:max-midi\\:underline, .min-sm\\:max-md\\:underline { + } + .sm\\:flex { + @media (width >= 40rem) { + display: flex; + } + } + .min-mini\\:max-midi\\:underline { + @media (width >= 40rem) { + @media (width < 48rem) { text-decoration-line: underline; } } } - - @media (min-width: 48rem) { - .md\\:flex, .midi\\:flex { - display: flex; - } - - @media not all and (min-width: 64rem) { - .min-md\\:max-lg\\:underline, .min-midi\\:max-maxi\\:underline { + .min-sm\\:max-md\\:underline { + @media (width >= 40rem) { + @media (width < 48rem) { text-decoration-line: underline; } } } - - @media (min-width: 64rem) { - .maxi\\:flex { + .md\\:flex { + @media (width >= 48rem) { display: flex; } } - - @media print { - .print\\:items-end { + .midi\\:flex { + @media (width >= 48rem) { + display: flex; + } + } + .min-md\\:max-lg\\:underline { + @media (width >= 48rem) { + @media (width < 64rem) { + text-decoration-line: underline; + } + } + } + .min-midi\\:max-maxi\\:underline { + @media (width >= 48rem) { + @media (width < 64rem) { + text-decoration-line: underline; + } + } + } + .maxi\\:flex { + @media (width >= 64rem) { + display: flex; + } + } + .print\\:items-end { + @media print { align-items: flex-end; } } @@ -359,91 +375,70 @@ test('JS config with `theme: { extends }` should not include the `default-config @tailwind utilities; ` - expect( - await run(['sm:flex', 'md:flex', 'min-md:max-lg:underline', 'min-sm:max-md:underline'], input, { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - mini: '40rem', - midi: '48rem', - maxi: '64rem', - }, + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + screens: { + mini: '40rem', + midi: '48rem', + maxi: '64rem', }, }, }, - base: '/root', - path: '', - }), + }, + base: '/root', }), + }) + + expect( + compiler.build(['sm:flex', 'md:flex', 'min-md:max-lg:underline', 'min-sm:max-md:underline']), ).toBe('') expect( - await run( - [ - 'mini:flex', - 'midi:flex', - 'maxi:flex', - 'min-midi:max-maxi:underline', - 'min-mini:max-midi:underline', + compiler.build([ + 'mini:flex', + 'midi:flex', + 'maxi:flex', + 'min-midi:max-maxi:underline', + 'min-mini:max-midi:underline', - // Ensure other core variants appear at the end - 'print:items-end', - ], - input, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - mini: '40rem', - midi: '48rem', - maxi: '64rem', - }, - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), + // Ensure other core variants appear at the end + 'print:items-end', + ]), ).toMatchInlineSnapshot(` - " - @media (min-width: 40rem) { - .mini\\:flex { + ".mini\\:flex { + @media (width >= 40rem) { display: flex; } - - @media not all and (min-width: 48rem) { - .min-mini\\:max-midi\\:underline { + } + .min-mini\\:max-midi\\:underline { + @media (width >= 40rem) { + @media (width < 48rem) { text-decoration-line: underline; } } } - - @media (min-width: 48rem) { - .midi\\:flex { + .midi\\:flex { + @media (width >= 48rem) { display: flex; } - - @media not all and (min-width: 64rem) { - .min-midi\\:max-maxi\\:underline { + } + .min-midi\\:max-maxi\\:underline { + @media (width >= 48rem) { + @media (width < 64rem) { text-decoration-line: underline; } } } - - @media (min-width: 64rem) { - .maxi\\:flex { + .maxi\\:flex { + @media (width >= 64rem) { display: flex; } } - - @media print { - .print\\:items-end { + .print\\:items-end { + @media print { align-items: flex-end; } } @@ -458,104 +453,76 @@ describe('complex screen configs', () => { @tailwind utilities; ` - expect( - await run(['min-sm:flex', 'min-md:flex', 'min-xl:flex', 'min-tall:flex'], input, { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - sm: { max: '639px' }, - md: [ - // - { min: '668px', max: '767px' }, - '868px', - ], - lg: { min: '868px' }, - xl: { min: '1024px', max: '1279px' }, - tall: { raw: '(min-height: 800px)' }, - }, + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + screens: { + sm: { max: '639px' }, + md: [ + // + { min: '668px', max: '767px' }, + '868px', + ], + lg: { min: '868px' }, + xl: { min: '1024px', max: '1279px' }, + tall: { raw: '(min-height: 800px)' }, }, }, }, - base: '/root', - path: '', - }), + }, + base: '/root', }), - ).toBe('') + }) + + expect(compiler.build(['min-sm:flex', 'min-md:flex', 'min-xl:flex', 'min-tall:flex'])).toBe('') expect( - await run( - [ - 'sm:flex', - 'md:flex', - 'lg:flex', - 'min-lg:flex', - 'xl:flex', - 'tall:flex', + compiler.build([ + 'sm:flex', + 'md:flex', + 'lg:flex', + 'min-lg:flex', + 'xl:flex', + 'tall:flex', - // Ensure other core variants appear at the end - 'print:items-end', - ], - input, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - sm: { max: '639px' }, - md: [ - // - { min: '668px', max: '767px' }, - '868px', - ], - lg: { min: '868px' }, - xl: { min: '1024px', max: '1279px' }, - tall: { raw: '(min-height: 800px)' }, - }, - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), + // Ensure other core variants appear at the end + 'print:items-end', + ]), ).toMatchInlineSnapshot(` - " - @media (min-width: 868px) { - .lg\\:flex, .min-lg\\:flex { + ".lg\\:flex { + @media (width >= 868px) { display: flex; } } - - @media (max-width: 639px) { - .sm\\:flex { + .min-lg\\:flex { + @media (width >= 868px) { display: flex; } } - - @media (max-width: 767px) and (min-width: 668px), (min-width: 868px) { - .md\\:flex { + .sm\\:flex { + @media (639px >= width) { display: flex; } } - - @media (max-width: 1279px) and (min-width: 1024px) { - .xl\\:flex { + .md\\:flex { + @media (767px >= width >= 668px), (width >= 868px) { display: flex; } } - - @media (min-height: 800px) { - .tall\\:flex { + .xl\\:flex { + @media (1279px >= width >= 1024px) { display: flex; } } - - @media print { - .print\\:items-end { + .tall\\:flex { + @media (min-height: 800px) { + display: flex; + } + } + .print\\:items-end { + @media print { align-items: flex-end; } } @@ -564,65 +531,70 @@ describe('complex screen configs', () => { }) test("don't interfere with `min-*` and `max-*` variants of non-complex screen configs", async () => { - expect( - await run( - [ - 'sm:flex', - 'md:flex', - 'portrait:flex', - 'min-sm:flex', - 'min-md:flex', - 'min-portrait:flex', - // Ensure other core variants appear at the end - 'print:items-end', - ], - css` - @theme default { - --breakpoint-sm: 39rem; - --breakpoint-md: 48rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - sm: '40rem', - portrait: { raw: 'screen and (orientation: portrait)' }, - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 39rem; + --breakpoint-md: 48rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + screens: { + sm: '40rem', + portrait: { raw: 'screen and (orientation: portrait)' }, }, }, - base: '/root', - path: '', - }), + }, }, - ), + base: '/root', + }), + }) + + expect( + compiler.build([ + 'sm:flex', + 'md:flex', + 'portrait:flex', + 'min-sm:flex', + 'min-md:flex', + 'min-portrait:flex', + // Ensure other core variants appear at the end + 'print:items-end', + ]), ).toMatchInlineSnapshot(` - " - @media (min-width: 40rem) { - .min-sm\\:flex, .sm\\:flex { + ".min-sm\\:flex { + @media (width >= 40rem) { display: flex; } } - - @media (min-width: 48rem) { - .md\\:flex, .min-md\\:flex { + .sm\\:flex { + @media (width >= 40rem) { display: flex; } } - - @media screen and (orientation: portrait) { - .portrait\\:flex { + .md\\:flex { + @media (width >= 48rem) { display: flex; } } - - @media print { - .print\\:items-end { + .min-md\\:flex { + @media (width >= 48rem) { + display: flex; + } + } + .portrait\\:flex { + @media screen and (orientation: portrait) { + display: flex; + } + } + .print\\:items-end { + @media print { align-items: flex-end; } } @@ -632,38 +604,37 @@ describe('complex screen configs', () => { }) test('JS config `screens` can overwrite default CSS `--breakpoint-*`', async () => { + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + screens: { + mini: '40rem', + midi: '48rem', + maxi: '64rem', + }, + }, + }, + base: '/root', + }), + }) + // Note: The `sm`, `md`, and other variants are still there because they are // created before the compat layer can intercept. We do not remove them // currently. expect( - await run( - ['min-sm:flex', 'min-md:flex', 'min-lg:flex', 'min-xl:flex', 'min-2xl:flex'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - screens: { - mini: '40rem', - midi: '48rem', - maxi: '64rem', - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), - ).toEqual('') + compiler.build(['min-sm:flex', 'min-md:flex', 'min-lg:flex', 'min-xl:flex', 'min-2xl:flex']), + ).toMatchInlineSnapshot(`""`) }) diff --git a/packages/tailwindcss/src/compat/selector-parser.test.ts b/packages/tailwindcss/src/compat/selector-parser.test.ts new file mode 100644 index 000000000..f995c9de7 --- /dev/null +++ b/packages/tailwindcss/src/compat/selector-parser.test.ts @@ -0,0 +1,176 @@ +import { describe, expect, it } from 'vitest' +import { parse, toCss, walk } from './selector-parser' + +describe('parse', () => { + it('should parse a simple selector', () => { + expect(parse('.foo')).toEqual([{ kind: 'selector', value: '.foo' }]) + }) + + it('should parse a compound selector', () => { + expect(parse('.foo.bar:hover#id')).toEqual([ + { kind: 'selector', value: '.foo' }, + { kind: 'selector', value: '.bar' }, + { kind: 'selector', value: ':hover' }, + { kind: 'selector', value: '#id' }, + ]) + }) + + it('should parse a selector list', () => { + expect(parse('.foo,.bar')).toEqual([ + { kind: 'selector', value: '.foo' }, + { kind: 'separator', value: ',' }, + { kind: 'selector', value: '.bar' }, + ]) + }) + + it('should combine everything within attribute selectors', () => { + expect(parse('.foo[bar="baz"]')).toEqual([ + { kind: 'selector', value: '.foo' }, + { kind: 'selector', value: '[bar="baz"]' }, + ]) + }) + + it('should parse functions', () => { + expect(parse('.foo:hover:not(.bar:focus)')).toEqual([ + { kind: 'selector', value: '.foo' }, + { kind: 'selector', value: ':hover' }, + { + kind: 'function', + nodes: [ + { + kind: 'selector', + value: '.bar', + }, + { + kind: 'selector', + value: ':focus', + }, + ], + value: ':not', + }, + ]) + }) + + it('should handle next-children combinator', () => { + expect(parse('.foo + p')).toEqual([ + { kind: 'selector', value: '.foo' }, + { kind: 'combinator', value: ' + ' }, + { kind: 'selector', value: 'p' }, + ]) + }) + + it('should handle escaped characters', () => { + expect(parse('foo\\.bar')).toEqual([{ kind: 'selector', value: 'foo\\.bar' }]) + }) + + it('parses :nth-child()', () => { + expect(parse(':nth-child(n+1)')).toEqual([ + { + kind: 'function', + value: ':nth-child', + nodes: [ + { + kind: 'value', + value: 'n+1', + }, + ], + }, + ]) + }) + + it('parses &:has(.child:nth-child(2))', () => { + expect(parse('&:has(.child:nth-child(2))')).toEqual([ + { + kind: 'selector', + value: '&', + }, + { + kind: 'function', + value: ':has', + nodes: [ + { + kind: 'selector', + value: '.child', + }, + { + kind: 'function', + value: ':nth-child', + nodes: [ + { + kind: 'value', + value: '2', + }, + ], + }, + ], + }, + ]) + }) + + it('parses &:has(:nth-child(2))', () => { + expect(parse('&:has(:nth-child(2))')).toEqual([ + { + kind: 'selector', + value: '&', + }, + { + kind: 'function', + value: ':has', + nodes: [ + { + kind: 'function', + value: ':nth-child', + nodes: [ + { + kind: 'value', + value: '2', + }, + ], + }, + ], + }, + ]) + }) +}) + +describe('toCss', () => { + it('should print a simple selector', () => { + expect(toCss(parse('.foo'))).toBe('.foo') + }) + + it('should print a compound selector', () => { + expect(toCss(parse('.foo.bar:hover#id'))).toBe('.foo.bar:hover#id') + }) + + it('should print a selector list', () => { + expect(toCss(parse('.foo,.bar'))).toBe('.foo,.bar') + }) + + it('should print an attribute selectors', () => { + expect(toCss(parse('.foo[bar="baz"]'))).toBe('.foo[bar="baz"]') + }) + + it('should print a function', () => { + expect(toCss(parse('.foo:hover:not(.bar:focus)'))).toBe('.foo:hover:not(.bar:focus)') + }) + + it('should print escaped characters', () => { + expect(toCss(parse('foo\\.bar'))).toBe('foo\\.bar') + }) + + it('should print :nth-child()', () => { + expect(toCss(parse(':nth-child(n+1)'))).toBe(':nth-child(n+1)') + }) +}) + +describe('walk', () => { + it('can be used to replace a function call', () => { + const ast = parse('.foo:hover:not(.bar:focus)') + walk(ast, (node, { replaceWith }) => { + if (node.kind === 'function' && node.value === ':not') { + replaceWith({ kind: 'selector', value: '.inverted-bar' }) + } + }) + expect(toCss(ast)).toBe('.foo:hover.inverted-bar') + }) +}) diff --git a/packages/tailwindcss/src/compat/selector-parser.ts b/packages/tailwindcss/src/compat/selector-parser.ts new file mode 100644 index 000000000..77a1ce4d4 --- /dev/null +++ b/packages/tailwindcss/src/compat/selector-parser.ts @@ -0,0 +1,467 @@ +export type SelectorCombinatorNode = { + kind: 'combinator' + value: string +} + +export type SelectorFunctionNode = { + kind: 'function' + value: string + nodes: SelectorAstNode[] +} + +export type SelectorNode = { + kind: 'selector' + value: string +} + +export type SelectorValueNode = { + kind: 'value' + value: string +} + +export type SelectorSeparatorNode = { + kind: 'separator' + value: string +} + +export type SelectorAstNode = + | SelectorCombinatorNode + | SelectorFunctionNode + | SelectorNode + | SelectorSeparatorNode + | SelectorValueNode +type SelectorParentNode = SelectorFunctionNode | null + +function combinator(value: string): SelectorCombinatorNode { + return { + kind: 'combinator', + value, + } +} + +function fun(value: string, nodes: SelectorAstNode[]): SelectorFunctionNode { + return { + kind: 'function', + value: value, + nodes, + } +} + +function selector(value: string): SelectorNode { + return { + kind: 'selector', + value, + } +} + +function separator(value: string): SelectorSeparatorNode { + return { + kind: 'separator', + value, + } +} + +function value(value: string): SelectorValueNode { + return { + kind: 'value', + value, + } +} + +export const enum SelectorWalkAction { + /** Continue walking, which is the default */ + Continue, + + /** Skip visiting the children of this node */ + Skip, + + /** Stop the walk entirely */ + Stop, +} + +export function walk( + ast: SelectorAstNode[], + visit: ( + node: SelectorAstNode, + utils: { + parent: SelectorParentNode + replaceWith(newNode: SelectorAstNode | SelectorAstNode[]): void + }, + ) => void | SelectorWalkAction, + parent: SelectorParentNode = null, +) { + for (let i = 0; i < ast.length; i++) { + let node = ast[i] + let replacedNode = false + let replacedNodeOffset = 0 + let status = + visit(node, { + parent, + replaceWith(newNode) { + if (replacedNode) return + replacedNode = true + + if (Array.isArray(newNode)) { + if (newNode.length === 0) { + ast.splice(i, 1) + replacedNodeOffset = 0 + } else if (newNode.length === 1) { + ast[i] = newNode[0] + replacedNodeOffset = 1 + } else { + ast.splice(i, 1, ...newNode) + replacedNodeOffset = newNode.length + } + } else { + ast[i] = newNode + replacedNodeOffset = 1 + } + }, + }) ?? SelectorWalkAction.Continue + + // We want to visit or skip 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. + if (replacedNode) { + if (status === SelectorWalkAction.Continue) { + i-- + } else { + i += replacedNodeOffset - 1 + } + continue + } + + // Stop the walk entirely + if (status === SelectorWalkAction.Stop) return SelectorWalkAction.Stop + + // Skip visiting the children of this node + if (status === SelectorWalkAction.Skip) continue + + if (node.kind === 'function') { + if (walk(node.nodes, visit, node) === SelectorWalkAction.Stop) { + return SelectorWalkAction.Stop + } + } + } +} + +export function toCss(ast: SelectorAstNode[]) { + let css = '' + for (const node of ast) { + switch (node.kind) { + case 'combinator': + case 'selector': + case 'separator': + case 'value': { + css += node.value + break + } + case 'function': { + css += node.value + '(' + toCss(node.nodes) + ')' + } + } + } + return css +} + +const BACKSLASH = 0x5c +const CLOSE_BRACKET = 0x5d +const CLOSE_PAREN = 0x29 +const COLON = 0x3a +const COMMA = 0x2c +const DOUBLE_QUOTE = 0x22 +const FULL_STOP = 0x2e +const GREATER_THAN = 0x3e +const NEWLINE = 0x0a +const NUMBER_SIGN = 0x23 +const OPEN_BRACKET = 0x5b +const OPEN_PAREN = 0x28 +const PLUS = 0x2b +const SINGLE_QUOTE = 0x27 +const SPACE = 0x20 +const TAB = 0x09 +const TILDE = 0x7e + +export function parse(input: string) { + input = input.replaceAll('\r\n', '\n') + + let ast: SelectorAstNode[] = [] + + let stack: (SelectorFunctionNode | null)[] = [] + + let parent = null as SelectorFunctionNode | null + + let buffer = '' + + let peekChar + + for (let i = 0; i < input.length; i++) { + let currentChar = input.charCodeAt(i) + + switch (currentChar) { + // E.g.: + // + // ```css + // .foo .bar + // ^ + // + // .foo > .bar + // ^^^ + // ``` + case COMMA: + case GREATER_THAN: + case NEWLINE: + case SPACE: + case PLUS: + case TAB: + case TILDE: { + // 1. Handle everything before the combinator as a selector + if (buffer.length > 0) { + let node = selector(buffer) + if (parent) { + parent.nodes.push(node) + } else { + ast.push(node) + } + buffer = '' + } + + // 2. Look ahead and find the end of the combinator + let start = i + let end = i + 1 + for (; end < input.length; end++) { + peekChar = input.charCodeAt(end) + if ( + peekChar !== COMMA && + peekChar !== GREATER_THAN && + peekChar !== NEWLINE && + peekChar !== SPACE && + peekChar !== PLUS && + peekChar !== TAB && + peekChar !== TILDE + ) { + break + } + } + i = end - 1 + + let contents = input.slice(start, end) + let node = contents.trim() === ',' ? separator(contents) : combinator(contents) + if (parent) { + parent.nodes.push(node) + } else { + ast.push(node) + } + + break + } + + // Start of a function call. + // + // E.g.: + // + // ```css + // .foo:not(.bar) + // ^ + // ``` + case OPEN_PAREN: { + let node = fun(buffer, []) + buffer = '' + + // If the function is not one of the following, we combine all it's + // contents into a single value node + if ( + node.value !== ':not' && + node.value !== ':where' && + node.value !== ':has' && + node.value !== ':is' + ) { + // Find the end of the function call + let start = i + 1 + let nesting = 0 + + // Find the closing bracket. + for (let j = i + 1; j < input.length; j++) { + peekChar = input.charCodeAt(j) + if (peekChar === OPEN_PAREN) { + nesting++ + continue + } + if (peekChar === CLOSE_PAREN) { + if (nesting === 0) { + i = j + break + } + nesting-- + } + } + let end = i + + node.nodes.push(value(input.slice(start, end))) + buffer = '' + i = end + + if (parent) { + parent.nodes.push(node) + } else { + ast.push(node) + } + + break + } + + if (parent) { + parent.nodes.push(node) + } else { + ast.push(node) + } + stack.push(node) + parent = node + + break + } + + // End of a function call. + // + // E.g.: + // + // ```css + // foo(bar, baz) + // ^ + // ``` + case CLOSE_PAREN: { + let tail = stack.pop() + + // Handle everything before the closing paren a selector + if (buffer.length > 0) { + let node = selector(buffer) + tail!.nodes.push(node) + buffer = '' + } + + if (stack.length > 0) { + parent = stack[stack.length - 1] + } else { + parent = null + } + + break + } + + // Split compound selectors. + // + // E.g.: + // + // ```css + // .foo.bar + // ^ + // ``` + case FULL_STOP: + case COLON: + case NUMBER_SIGN: { + // Handle everything before the combinator as a selector and + // start a new selector + if (buffer.length > 0) { + let node = selector(buffer) + if (parent) { + parent.nodes.push(node) + } else { + ast.push(node) + } + } + buffer = String.fromCharCode(currentChar) + break + } + + // Start of an attribute selector. + case OPEN_BRACKET: { + // Handle everything before the combinator as a selector + if (buffer.length > 0) { + let node = selector(buffer) + if (parent) { + parent.nodes.push(node) + } else { + ast.push(node) + } + } + buffer = '' + + let start = i + let nesting = 0 + + // Find the closing bracket. + for (let j = i + 1; j < input.length; j++) { + peekChar = input.charCodeAt(j) + if (peekChar === OPEN_BRACKET) { + nesting++ + continue + } + if (peekChar === CLOSE_BRACKET) { + if (nesting === 0) { + i = j + break + } + nesting-- + } + } + + // Adjust `buffer` to include the string. + buffer += input.slice(start, i + 1) + break + } + + // Start of a string. + case SINGLE_QUOTE: + case DOUBLE_QUOTE: { + let start = i + + // We need to ensure that the closing quote is the same as the opening + // quote. + // + // E.g.: + // + // ```css + // "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++) { + peekChar = input.charCodeAt(j) + // Current character is a `\` therefore the next character is escaped. + if (peekChar === BACKSLASH) { + j += 1 + } + + // End of the string. + else if (peekChar === currentChar) { + i = j + break + } + } + + // Adjust `buffer` to include the string. + buffer += input.slice(start, i + 1) + break + } + + // Escaped characters. + case BACKSLASH: { + let nextChar = input.charCodeAt(i + 1) + buffer += String.fromCharCode(currentChar) + String.fromCharCode(nextChar) + i += 1 + break + } + + // Everything else will be collected in the buffer + default: { + buffer += String.fromCharCode(currentChar) + } + } + } + + // Collect the remainder as a word + if (buffer.length > 0) { + ast.push(selector(buffer)) + } + + return ast +} diff --git a/packages/tailwindcss/src/compile.ts b/packages/tailwindcss/src/compile.ts index 46f562918..6530541a0 100644 --- a/packages/tailwindcss/src/compile.ts +++ b/packages/tailwindcss/src/compile.ts @@ -1,20 +1,25 @@ -import { atRule, context, decl, rule, type AstNode, type Rule, type StyleRule } from './ast' +import { + atRule, + decl, + rule, + walk, + WalkAction, + type AstNode, + type Rule, + type StyleRule, +} from './ast' import { type Candidate, type Variant } from './candidate' -import { CompileAstFlags, type DesignSystem } from './design-system' +import { type DesignSystem } from './design-system' import GLOBAL_PROPERTY_ORDER from './property-order' import { asColor, type Utility } from './utilities' import { compare } from './utils/compare' import { escape } from './utils/escape' import type { Variants } from './variants' -import { walk, WalkAction } from './walk' export function compileCandidates( rawCandidates: Iterable, designSystem: DesignSystem, - { - onInvalidCandidate, - respectImportant, - }: { onInvalidCandidate?: (candidate: string) => void; respectImportant?: boolean } = {}, + { onInvalidCandidate }: { onInvalidCandidate?: (candidate: string) => void } = {}, ) { let nodeSorting = new Map< AstNode, @@ -39,12 +44,6 @@ export function compileCandidates( matches.set(rawCandidate, candidates) } - let flags = CompileAstFlags.None - - if (respectImportant ?? true) { - flags |= CompileAstFlags.RespectImportant - } - let variantOrderMap = designSystem.getVariantOrder() // Create the AST @@ -52,7 +51,7 @@ export function compileCandidates( let found = false for (let candidate of candidates) { - let rules = designSystem.compileAstNodes(candidate, flags) + let rules = designSystem.compileAstNodes(candidate) if (rules.length === 0) continue found = true @@ -120,16 +119,10 @@ export function compileCandidates( } } -export function compileAstNodes( - candidate: Candidate, - designSystem: DesignSystem, - flags: CompileAstFlags, -) { +export function compileAstNodes(candidate: Candidate, designSystem: DesignSystem) { let asts = compileBaseUtility(candidate, designSystem) if (asts.length === 0) return [] - let respectImportant = designSystem.important && Boolean(flags & CompileAstFlags.RespectImportant) - let rules: { node: AstNode propertySort: { @@ -143,10 +136,7 @@ export function compileAstNodes( for (let nodes of asts) { let propertySort = getPropertySort(nodes) - // If the candidate itself is important then we want to always mark - // the utility as important. However, at a design system level we want - // to be able to opt-out when using things like `@apply` - if (candidate.important || respectImportant) { + if (candidate.important || designSystem.important) { applyImportant(nodes) } @@ -187,18 +177,7 @@ export function applyVariant( // E.g. `[>img]:flex` is not valid, but `has-[>img]:flex` is if (variant.relative && depth === 0) return null - let child: AstNode = rule(variant.selector, node.nodes) - - // Wrap variant-generated `@scope` rules in a `context` marker, so they are - // hoisted with the variant semantics instead of their native CSS nesting - // semantics. The slotted content is marked as `user` content, so `@scope` - // rules defined there keep their native CSS nesting semantics. - if (child.kind === 'at-rule' && child.name === '@scope') { - child.nodes = [context({ source: 'user' }, node.nodes)] - child = context({ source: 'variant' }, [child]) - } - - node.nodes = [child] + node.nodes = [rule(variant.selector, node.nodes)] return } @@ -298,19 +277,7 @@ function compileBaseUtility(candidate: Candidate, designSystem: DesignSystem) { let compiledNodes = utility.compileFn(candidate) if (compiledNodes === undefined) continue - if (compiledNodes === null) { - // `null` means that the result is invalid and that this plugin should not - // result in any CSS, but that doesn't mean that subsequent plugins are - // invalid as well. - // - // However, for backwards compatibility with `matchUtilities` this means - // that we do need to bail entirely: plugins that handle a specific - // arbitrary value type prevent falling through to other plugins if the - // result is invalid for that plugin - if (utility.options?.types?.length) return asts - - continue - } + if (compiledNodes === null) return asts asts.push(compiledNodes) } @@ -322,19 +289,7 @@ function compileBaseUtility(candidate: Candidate, designSystem: DesignSystem) { let compiledNodes = utility.compileFn(candidate) if (compiledNodes === undefined) continue - if (compiledNodes === null) { - // `null` means that the result is invalid and that this plugin should not - // result in any CSS, but that doesn't mean that subsequent plugins are - // invalid as well. - // - // However, for backwards compatibility with `matchUtilities` this means - // that we do need to bail entirely: plugins that handle a specific - // arbitrary value type prevent falling through to other plugins if the - // result is invalid for that plugin - if (utility.options?.types?.length) return asts - - continue - } + if (compiledNodes === null) return asts asts.push(compiledNodes) } diff --git a/packages/tailwindcss/src/constant-fold-declaration.test.ts b/packages/tailwindcss/src/constant-fold-declaration.test.ts deleted file mode 100644 index 5a0c867b6..000000000 --- a/packages/tailwindcss/src/constant-fold-declaration.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { expect, it } from 'vitest' -import { constantFoldDeclaration } from './constant-fold-declaration' - -it.each([ - // Simple expression - ['calc(1 + 1)', '2'], - ['calc(3 - 2)', '1'], - ['calc(2 * 3)', '6'], - ['calc(8 / 2)', '4'], - - // Nested - ['calc(1 + calc(1 + 1))', '3'], - ['calc(3 - calc(1 + 2))', '0'], - ['calc(2 * calc(1 + 3))', '8'], - ['calc(8 / calc(2 + 2))', '2'], - ['calc(1 + (1 + 1))', '3'], - ['calc(3 - (1 + 2))', '0'], - ['calc(2 * (1 + 3))', '8'], - ['calc(8 / (2 + 2))', '2'], - - // With units - ['calc(1rem * 2)', '2rem'], - ['calc(2rem - 0.5rem)', '1.5rem'], - ['calc(3rem * 6)', '18rem'], - ['calc(5rem / 2)', '2.5rem'], - - // Negating with units - ['calc(2rem * -1)', '-2rem'], - ['calc(-1 * 2rem)', '-2rem'], - - // Nested partial evaluation - ['calc(calc(1 + 2) + 2rem)', 'calc(3 + 2rem)'], - - // Nested multiplication with unknown values - ['calc(2 * calc(3 * var(--foo)))', 'calc(6 * var(--foo))'], - ['calc(calc(3 * var(--foo)) * 2)', 'calc(6 * var(--foo))'], - ['calc(2rem * calc(3 * var(--foo)))', 'calc(6rem * var(--foo))'], - - // Nested addition with unknown values - ['calc(1rem + calc(2rem + var(--foo)))', 'calc(3rem + var(--foo))'], - ['calc(calc(2rem + var(--foo)) + 1rem)', 'calc(3rem + var(--foo))'], - - // Nested multiplication can collapse away entirely - ['calc(-1 * calc(-1 * var(--foo)))', 'var(--foo)'], - ['calc(calc(-1 * var(--foo)) * -1)', 'var(--foo)'], - ['calc(-1 * calc(var(--foo) * -1))', 'var(--foo)'], - ['calc(-1 * (-1 * var(--foo)))', 'var(--foo)'], - ['calc(1 * var(--foo))', 'var(--foo)'], - ['calc(var(--foo) * 1)', 'var(--foo)'], - - // Evaluation only handles two operands right now, this can change in the future - ['calc(1 + 2 + 3)', 'calc(1 + 2 + 3)'], -])('should constant fold `%s` into `%s`', (input, expected) => { - expect(constantFoldDeclaration(input)).toBe(expected) -}) - -it.each([ - ['calc(1rem * 2%)'], - ['calc(1rem * 2px)'], - ['calc(2rem - 6)'], - ['calc(3rem * 3dvw)'], - ['calc(3rem * 2dvh)'], - ['calc(5rem / 17px)'], - ['calc(2rem * calc(3px * var(--foo)))'], - ['calc(1rem + 0px + var(--foo))'], -])('should not constant fold different units `%s`', (input) => { - expect(constantFoldDeclaration(input)).toBe(input) -}) - -it.each([ - ['calc(0 * 100vw)'], - ['calc(0 * calc(1 * 2))'], - ['calc(0 * var(--foo))'], - ['calc(0 * calc(var(--spacing) * 32))'], - - ['calc(100vw * 0)'], - ['calc(calc(1 * 2) * 0)'], - ['calc(var(--foo) * 0)'], - ['calc(calc(var(--spacing, 0.25rem) * 32) * 0)'], - ['calc(var(--spacing, 0.25rem) * -0)'], - - // Zeroes - ['0px'], - ['0rem'], - ['0em'], - ['0dvh'], - ['-0'], - ['+0'], - ['-0.0rem'], - ['+0.00rem'], - - // Expressions - ['calc(-0px * -1)'], - ['calc(-1 * -0px)'], -])('should constant fold `%s` to `0` (%#)', (input) => { - expect(constantFoldDeclaration(input)).toBe('0') -}) - -it.each([ - // Expressions, keep unit when they are nested - // - // TODO: We might be able to fold this further to just `1rem`, but we can't do - // that for any `0`. E.g.: `calc(0s + 1rem)` which is invalid, would - // become valid if we just use `1rem`. - ['calc(calc(0px * -1) + 1rem)', 'calc(0px + 1rem)'], - ['calc(calc(-1 * 0px) + 1rem)', 'calc(0px + 1rem)'], - - // Non-foldable units - ['0deg', '0deg'], - ['0rad', '0deg'], - ['0%', '0%'], - ['0turn', '0deg'], - ['0fr', '0fr'], - ['0ms', '0s'], - ['0s', '0s'], - ['-0.0deg', '0deg'], - ['-0.0rad', '0deg'], - ['-0.0%', '0%'], - ['-0.0turn', '0deg'], - ['-0.0fr', '0fr'], - ['-0.0ms', '0s'], - ['-0.0s', '0s'], -])('should not fold non-foldable units to `0`. Constant fold `%s` into `%s`', (input, expected) => { - expect(constantFoldDeclaration(input)).toBe(expected) -}) - -it('should not constant fold when dividing by `0`', () => { - expect(constantFoldDeclaration('calc(123rem / 0)')).toBe('calc(123rem / 0)') -}) - -it('should not constant fold when a computation has a high-precision result', () => { - expect(constantFoldDeclaration('calc(100% / 3.5)')).toBe('calc(100% / 3.5)') -}) - -it('should constant fold division results with floating-point error after scaling', () => { - expect(constantFoldDeclaration('calc(29% / 100)')).toBe('0.29%') -}) diff --git a/packages/tailwindcss/src/constant-fold-declaration.ts b/packages/tailwindcss/src/constant-fold-declaration.ts deleted file mode 100644 index c15420150..000000000 --- a/packages/tailwindcss/src/constant-fold-declaration.ts +++ /dev/null @@ -1,357 +0,0 @@ -import { dimensions } from './utils/dimensions' -import { isLength } from './utils/infer-data-type' -import * as ValueParser from './value-parser' -import { walk, WalkAction } from './walk' - -// Assumption: We already assume that we receive somewhat valid `calc()` -// expressions. So we will see `calc(1 + 1)` and not `calc(1+1)` -export function constantFoldDeclaration( - input: string, - rem: number | null = null, - normalizeUnit = true, -): string { - let [folded, valueAst] = constantFoldDeclarationAst(ValueParser.parse(input), rem, normalizeUnit) - - return folded ? ValueParser.toCss(valueAst) : input -} - -export function constantFoldDeclarationAst( - ast: ValueParser.ValueAstNode[], - rem: number | null = null, - normalizeUnit = true, -): [folded: boolean, ast: ValueParser.ValueAstNode[]] { - let folded = false - - walk(ast, { - exit(valueNode, ctx) { - // Canonicalize dimensions to their simplest form. This includes: - // - Convert `-0`, `+0`, `0.0`, … to `0` - // - Convert `-0px`, `+0em`, `0.0rem`, … to `0` - // - Convert units to an equivalent unit - if ( - valueNode.kind === 'word' && - valueNode.value !== '0' // Already `0`, nothing to do - ) { - let canonical = canonicalizeDimension(valueNode.value, rem, normalizeUnit) - if (canonical === null) return // Couldn't be canonicalized, nothing to do - if (canonical === valueNode.value) return // Already in canonical form, nothing to do - - // We need to be careful with `0` because `0` can only be - // converted to `0` if we're dealing with a `` type. - if (canonical === '0') { - // When used inside of a function such as `calc(…)`, then this isn't - // always safe to convert to `0`. - // - // E.g.: - // - `calc(0px + 1rem)` → `calc(0 + 1rem)` this goes from valid to invalid - // - `calc(0px * 1rem)` → `calc(0 * 1rem)` this goes from invalid to valid - if (ctx.parent?.kind === 'function') { - let withUnit = canonicalizeDimension(valueNode.value, rem, false) - if (withUnit === null) return - - folded = true - return WalkAction.ReplaceSkip(ValueParser.word(withUnit)) - } - } - - folded = true - return WalkAction.ReplaceSkip(ValueParser.word(canonical)) - } - - // Constant fold `calc()` expressions with two operands and one operator - else if ( - valueNode.kind === 'function' && - (valueNode.value === 'calc' || valueNode.value === '') - ) { - // [ - // { kind: 'word', value: '0.25rem' }, 0 - // { kind: 'separator', value: ' ' }, 1 - // { kind: 'word', value: '*' }, 2 - // { kind: 'separator', value: ' ' }, 3 - // { kind: 'word', value: '256' } 4 - // ] - if (valueNode.nodes.length !== 5) return - if (valueNode.nodes[2].kind !== 'word') return - - let lhsNode = valueNode.nodes[0] - let operator = valueNode.nodes[2].value - let rhsNode = valueNode.nodes[4] - - let lhs = lhsNode.kind === 'word' ? dimensions.get(lhsNode.value) : null - let rhs = rhsNode.kind === 'word' ? dimensions.get(rhsNode.value) : null - - // Nullify entire expression when multiplying by `0`, e.g.: `calc(0 * 100vw)` -> `0` - // - // TODO: Ensure it's safe to do so based on the data types? - if ( - operator === '*' && - ((lhs?.[0] === 0 && lhs?.[1] === null) || // 0 * something - (rhs?.[0] === 0 && rhs?.[1] === null)) // something * 0 - ) { - folded = true - return WalkAction.ReplaceSkip(ValueParser.word('0')) - } - - // Fold `0 * something-without-unit` to just `0`, inside of a function such as `calc(…)` - if (operator === '*' && lhs?.[0] === 0 && lhs?.[1] !== null && rhs?.[1] === null) { - folded = true - if (ctx.parent?.kind === 'function') { - return WalkAction.ReplaceSkip(ValueParser.word(`0${lhs[1]}`)) - } else { - return WalkAction.ReplaceSkip(ValueParser.word('0')) - } - } - - // Fold `something-without-unit * 0` to just `0`, inside of a function such as `calc(…)` - if (operator === '*' && rhs?.[0] === 0 && rhs?.[1] !== null && lhs?.[1] === null) { - folded = true - if (ctx.parent?.kind === 'function') { - return WalkAction.ReplaceSkip(ValueParser.word(`0${rhs[1]}`)) - } else { - return WalkAction.ReplaceSkip(ValueParser.word('0')) - } - } - - if (operator === '*') { - // Multiplying by `1` can always unwrap the other side, even when that - // side is an expression like `var(--foo)` that we can't fully fold. - if (lhs?.[0] === 1 && lhs?.[1] === null) { - folded = true - return WalkAction.ReplaceSkip(rhsNode) - } - - if (rhs?.[0] === 1 && rhs?.[1] === null) { - folded = true - return WalkAction.ReplaceSkip(lhsNode) - } - } - - if (operator === '*' || operator === '+') { - // If only one side is known, and the unknown side is itself another - // binary expression with the same operator, try to combine the two - // known parts and keep the unknown part in place. - // - // E.g.: - // - // - `calc(2rem * calc(3 * var(--foo)))` → `calc(6rem * var(--foo))` - // - `calc(2rem + calc(3rem + var(--foo)))` → `calc(5rem + var(--foo))` - // - // At this point `lhs` and `rhs` are dimensions ([value, unit] | - // null). The `null` one is the unknown node. - let constant = lhs ?? rhs - let nestedNode = lhs === null ? lhsNode : rhs === null ? rhsNode : null - - if ( - constant !== null && - nestedNode !== null && - nestedNode.kind === 'function' && - (nestedNode.value === 'calc' || nestedNode.value === '') && - nestedNode.nodes.length === 5 && - nestedNode.nodes[2].kind === 'word' && - nestedNode.nodes[2].value === operator - ) { - let nestedLhsNode = nestedNode.nodes[0] - let nestedRhsNode = nestedNode.nodes[4] - let nestedLhs = - nestedLhsNode.kind === 'word' ? dimensions.get(nestedLhsNode.value) : null - let nestedRhs = - nestedRhsNode.kind === 'word' ? dimensions.get(nestedRhsNode.value) : null - - let known = nestedLhs ?? nestedRhs - let unknown = - nestedLhs === null ? nestedLhsNode : nestedRhs === null ? nestedRhsNode : null - - if (known !== null && unknown !== null) { - // `*` requires both values being unitless, or one of them. Both - // values having a unit is invalid. - // - // - `2 * 3` → valid - // - `4rem * 5` → valid - // - `6 * 7rem` → valid - // - `8rem * 9rem` → invalid - if ( - operator === '*' && - !( - (constant[1] === null && known[1] === null) || // Both can be unitless - (constant[1] === null && known[1] !== null) || // One of them can be unitless, but the other can't - (constant[1] !== null && known[1] === null) // One of them can be unitless, but the other can't - ) - ) { - return - } - - // `+` requires that both values being unitless, or both values - // have a unit. Mixed units are valid, but we won't fold these. - // - // - `2 + 3` → valid - // - `4rem + 5` → invalid - // - `6 + 7rem` → invalid - // - `8rem + 9rem` → valid - if ( - operator === '+' && - !( - constant[1] === known[1] // Only same unit is allowed - ) - ) { - return - } - - // Re-associate nested expressions so we can still fold the known - // part of `x op (y op z)` when `z` is unknown. - // - // Examples: - // - `calc(2 * calc(3 * var(--foo)))` -> `calc(6 * var(--foo))` - // - `calc(1rem + calc(2rem + var(--foo)))` -> `calc(3rem + var(--foo))` - let combined: string - switch (operator) { - case '*': { - combined = `${constant[0] * known[0]}${constant[1] ?? known[1] ?? ''}` - break - } - case '+': { - combined = `${constant[0] + known[0]}${constant[1] ?? known[1] ?? ''}` - break - } - - default: - return - } - - folded = true - - if (operator === '*' && combined === '1') { - return WalkAction.ReplaceSkip(unknown) - } - - let replacement: ValueParser.ValueFunctionNode = { - kind: 'function', - value: valueNode.value, - nodes: [ - ValueParser.word(combined), - valueNode.nodes[1], - valueNode.nodes[2], - valueNode.nodes[3], - unknown, - ], - } - - return WalkAction.ReplaceSkip(replacement) - } - } - } - - // We're not dealing with dimensions, so we can't fold this - if (lhs === null || rhs === null) { - return - } - - switch (operator) { - case '*': { - if ( - lhs[1] === rhs[1] || // Same Units, e.g.: `1rem * 2rem`, `8 * 6` - (lhs[1] === null && rhs[1] !== null) || // Unitless * Unit, e.g.: `2 * 1rem` - (lhs[1] !== null && rhs[1] === null) // Unit * Unitless, e.g.: `1rem * 2` - ) { - folded = true - return WalkAction.ReplaceSkip( - ValueParser.word(`${lhs[0] * rhs[0]}${lhs[1] ?? rhs[1] ?? ''}`), - ) - } - break - } - - case '+': { - if ( - lhs[1] === rhs[1] // Same unit or unitless, e.g.: `1rem + 2rem`, `8 + 6` - ) { - folded = true - return WalkAction.ReplaceSkip(ValueParser.word(`${lhs[0] + rhs[0]}${lhs[1] ?? ''}`)) - } - break - } - - case '-': { - if ( - lhs[1] === rhs[1] // Same unit or unitless, e.g.: `2rem - 1rem`, `8 - 6` - ) { - folded = true - return WalkAction.ReplaceSkip(ValueParser.word(`${lhs[0] - rhs[0]}${lhs[1] ?? ''}`)) - } - break - } - - case '/': { - if ( - rhs[0] !== 0 && // Don't divide by zero - ((lhs[1] === null && rhs[1] === null) || // Unitless / Unitless, e.g.: `8 / 2` - (lhs[1] !== null && rhs[1] === null)) // Unit / Unitless, e.g.: `1rem / 2` - ) { - let computed = lhs[0] / rhs[0] - - // Only fold with .xx precision, anything beyond that might be a bit too much. - // - // E.g. 100% / 3.5 = 28.571428571428573%, which is correct but not - // as user friendly. Especially when going from - // `w-[calc(100%/3.5)]` → `w-[28.571428571428573%]` - if (Math.round(computed * 100) / 100 !== computed) { - break - } - - folded = true - return WalkAction.ReplaceSkip(ValueParser.word(`${computed}${lhs[1] ?? ''}`)) - } - break - } - } - } - }, - }) - - return [folded, ast] -} - -function canonicalizeDimension( - input: string, - rem: number | null = null, - normalizeUnit = true, -): string | null { - let dimension = dimensions.get(input) - if (dimension === null) return null // This shouldn't happen - - let [value, unit] = dimension - if (unit === null) return `${value}` // Already unitless, nothing to do - - // Replace `0` units with just `0` - if (value === 0 && isLength(input)) { - if (normalizeUnit) return '0' - else return `0${unit}` // Keep unit - } - - // Only normalize into base units when necessary - if (!normalizeUnit) return `${input}` - - // prettier-ignore - switch (unit.toLowerCase()) { - // to px, https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Values_and_units#lengths - case 'in': return `${value * 96}px` // 1in = 96.000px - case 'cm': return `${value * 96 / 2.54}px` // 1cm = 37.795px - case 'mm': return `${value * 96 / 2.54 / 10}px` // 1mm = 3.779px - case 'q': return `${value * 96 / 2.54 / 10 / 4}px` // 1q = 0.945px - case 'pc': return `${value * 96 / 6}px` // 1pc = 16.000px - case 'pt': return `${value * 96 / 72}px` // 1pt = 1.333px - case 'rem': return rem !== null ? `${value * rem}px` : null // 1rem = 16.000px (Assuming root font-size is 16px) - - // to deg, https://developer.mozilla.org/en-US/docs/Web/CSS/angle - case 'grad': return `${value * 0.9}deg` // 1grad = 0.900deg - case 'rad': return `${value * 180 / Math.PI}deg` // 1rad = 57.296deg - case 'turn': return `${value * 360}deg` // 1turn = 360.000deg - - //
- -
Hello world
-
Hello world
`, ) - expect(await getPropertyList('#a', 'box-shadow')).toEqual([ - expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 1px 0px inset/), - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - ]) - expect(await getPropertyList('#b', 'box-shadow')).toEqual([ - expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 2px 4px 0px inset/), - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - ]) - expect(await getPropertyList('#c', 'box-shadow')).toEqual([ - expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 3px 6px 0px inset/), - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - ]) - - expect(await getPropertyList('#d', 'box-shadow')).toEqual([ - expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 1px 0px inset/), - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - ]) - - expect(await getPropertyList('#f', 'box-shadow')).toEqual([ - 'oklab(0 0 0 / 0.75) 0px 1px 1px 0px inset', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - ]) - - expect(await getPropertyList('#g', 'box-shadow')).toEqual([ - expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+ \/ 0.56\d+\) 0px 1px 1px 0px inset/), - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - ]) - - await page.locator('#d').hover() - - expect(await getPropertyList('#d', 'box-shadow')).toEqual([ - expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 2px 4px 0px inset/), - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - ]) - - expect(await getPropertyList('#e', 'box-shadow')).toEqual([ - expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 1px 0px inset/), - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - ]) - - await page.locator('#e').hover() - - expect(await getPropertyList('#e', 'box-shadow')).toEqual([ - 'rgba(0, 0, 0, 0.05) 0px 2px 4px 0px inset', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', - ]) -}) - -test('text shadow colors', async ({ page }) => { - let { getPropertyValue } = await render( - page, - html` -
-
-
-
Hello world
-
- Hello world -
- -
Hello world
-
Hello world
- `, + expect(await getPropertyValue('#a', 'box-shadow')).toEqual( + [ + 'rgb(255, 0, 0) 0px 1px 1px 0px inset', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + ].join(', '), + ) + expect(await getPropertyValue('#b', 'box-shadow')).toEqual( + [ + 'rgb(255, 0, 0) 0px 2px 4px 0px inset', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + ].join(', '), + ) + expect(await getPropertyValue('#c', 'box-shadow')).toEqual( + [ + 'rgb(255, 0, 0) 0px 3px 6px 0px inset', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + ].join(', '), ) - expect(await getPropertyValue('#a', 'text-shadow')).toMatch( - /oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 1px/, - ) - expect(await getPropertyValue('#b', 'text-shadow')).toMatch( - /oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 2px, oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 3px 2px, oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 4px 8px/, - ) - expect(await getPropertyValue('#c', 'text-shadow')).toMatch( - /oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 2px 4px/, - ) - expect(await getPropertyValue('#d', 'text-shadow')).toMatch( - /oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 1px/, - ) - - expect(await getPropertyValue('#f', 'text-shadow')).toEqual('oklab(0 0 0 / 0.75) 0px 1px 1px') - expect(await getPropertyValue('#g', 'text-shadow')).toMatch( - /oklab\(0.627\d+ 0.224\d+ 0.125\d+ \/ 0.56\d+\) 0px 1px 1px/, + expect(await getPropertyValue('#d', 'box-shadow')).toEqual( + [ + 'rgb(255, 0, 0) 0px 1px 1px 0px inset', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + ].join(', '), ) await page.locator('#d').hover() - expect(await getPropertyValue('#d', 'text-shadow')).toMatch( - /oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 2px, oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 3px 2px, oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 4px 8px/, + expect(await getPropertyValue('#d', 'box-shadow')).toEqual( + [ + 'rgb(255, 0, 0) 0px 2px 4px 0px inset', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + ].join(', '), ) - expect(await getPropertyValue('#e', 'text-shadow')).toMatch( - /oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 1px/, + expect(await getPropertyValue('#e', 'box-shadow')).toEqual( + [ + 'rgb(255, 0, 0) 0px 1px 1px 0px inset', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + ].join(', '), ) await page.locator('#e').hover() - expect(await getPropertyValue('#e', 'text-shadow')).toEqual( - 'rgba(0, 0, 0, 0.1) 0px 1px 2px, rgba(0, 0, 0, 0.1) 0px 3px 2px, rgba(0, 0, 0, 0.1) 0px 4px 8px', + expect(await getPropertyValue('#e', 'box-shadow')).toEqual( + [ + 'rgba(0, 0, 0, 0.05) 0px 2px 4px 0px inset', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + 'rgba(0, 0, 0, 0) 0px 0px 0px 0px', + ].join(', '), ) }) @@ -1826,92 +439,13 @@ test('filter', async ({ page }) => { expect(await getPropertyValue('#b', 'filter')).toEqual('contrast(1)') }) -test('drop shadow colors', async ({ page }) => { - let { getPropertyList } = await render( - page, - html` -
-
-
-
-
- `, - ) - - expect(await getPropertyList('#a', 'filter')).toEqual([ - 'drop-shadow(rgba(0, 0, 0, 0.12) 0px 3px 3px)', - ]) - - expect(await getPropertyList('#b', 'filter')).toEqual([ - expect.stringMatching(/drop-shadow\(oklab\(0\.627\d+ 0\.224\d+ 0\.125\d+\) 0px 3px 3px\)/), - ]) - - expect(await getPropertyList('#c', 'filter')).toEqual([ - 'drop-shadow(oklab(0 0 0 / 0.5) 0px 3px 3px)', - ]) - - expect(await getPropertyList('#d', 'filter')).toEqual([ - expect.stringMatching( - /drop-shadow\(oklab\(0\.627\d+ 0\.224\d+ 0\.125\d+ \/ 0\.5\) 0px 3px 3px\)/, - ), - ]) - - expect(await getPropertyList('#e', 'filter')).toEqual([ - expect.stringMatching( - /drop-shadow\(oklab\(0\.627\d+ 0\.224\d+ 0\.125\d+ \/ 0\.25\) 0px 3px 3px\)/, - ), - ]) -}) - -test('multiple drop shadow filters with `@theme inline`', async ({ page }) => { - let { getPropertyList } = await render( - page, - html` -
-
-
-
-
-
-
-
- `, - css` - @theme { - --drop-shadow-var: 0 1px 1px rgb(0 0 0 / 0.5), 0 10px 10px rgb(0 0 0 / 0.25); - } - @theme inline { - --drop-shadow-inlined: 0 1px 1px rgb(0 0 0 / 0.5), 0 10px 10px rgb(0 0 0 / 0.25); - } - `, - ) - - expect(await getPropertyList('#a', 'filter')).toEqual([ - 'drop-shadow(rgba(0, 0, 0, 0.5) 0px 1px 1px) drop-shadow(rgba(0, 0, 0, 0.25) 0px 10px 10px)', - ]) - - expect(await getPropertyList('#b', 'filter')).toEqual([ - expect.stringMatching( - /drop-shadow\(oklab\(0\.627\d+ 0\.224\d+ 0\.125\d+\) 0px 1px 1px\) drop-shadow\(oklab\(0\.627\d+ 0\.224\d+ 0\.125\d+\) 0px 10px 10px\)/, - ), - ]) - - expect(await getPropertyList('#c', 'filter')).toEqual([ - 'drop-shadow(rgba(0, 0, 0, 0.75) 0px 20px 20px)', - ]) - - // Multiple values are only supported with `@theme inline` because otherwise we use var(…) - // inside of drop-shadow(…) which can only ever be a single shadow - expect(await getPropertyList('#d', 'filter')).toEqual(['none']) -}) - test('outline style is optional', async ({ page }) => { let { getPropertyValue } = await render( page, html`
`, ) - expect(await getPropertyValue('#x', 'outline')).toEqual('2px solid rgb(255, 255, 255)') + expect(await getPropertyValue('#x', 'outline')).toEqual('rgb(255, 255, 255) solid 2px') }) test('outline style is preserved when changing outline width', async ({ page }) => { @@ -1922,11 +456,11 @@ test('outline style is preserved when changing outline width', async ({ page })
`, ) - expect(await getPropertyValue('#x', 'outline')).toEqual('2px dotted rgb(255, 255, 255)') + expect(await getPropertyValue('#x', 'outline')).toEqual('rgb(255, 255, 255) dotted 2px') await page.locator('#x').hover() - expect(await getPropertyValue('#x', 'outline')).toEqual('4px dotted rgb(255, 255, 255)') + expect(await getPropertyValue('#x', 'outline')).toEqual('rgb(255, 255, 255) dotted 4px') }) test('borders can be added without a border-style utility', async ({ page }) => { @@ -2177,330 +711,6 @@ test('shadow DOM has access to variables', async ({ page }) => { expect(gap).toBe('8px') }) -// @scope related tests -{ - test('custom variant using `@scope`', async ({ page }) => { - let { getPropertyValue } = await render( - page, - html` -
-
In scope
-
-
Beyond the limit
-
-
-
Not in scope
- `, - css` - @custom-variant in-checkout { - @scope (.checkout) to (.payment) { - @slot; - } - } - `, - ) - - test.skip(!(await supportsAtScope(page)), '`@scope` is not supported in this browser') - - expect(await getPropertyValue('#in', 'text-decoration-line')).toEqual('underline') - expect(await getPropertyValue('#beyond', 'text-decoration-line')).toEqual('none') - expect(await getPropertyValue('#out', 'text-decoration-line')).toEqual('none') - }) - - test('custom variant using `@scope` behaves like the equivalent authored CSS', async ({ - page, - }) => { - // Render the same structure twice: - // - Once styled by authored CSS using `@scope`, like the "normal CSS" - // reference in https://github.com/tailwindlabs/tailwindcss/issues/18961 - // - Once styled by utilities using an equivalent custom variant - function structure(prefix: string, probe: string) { - return html` -
-
In scope
-
-
Beyond the limit
-
-
-
Not in scope
- ` - } - - let { getPropertyValue } = await render( - page, - structure('a', 'a-probe') + structure('c', 'in-checkout:underline'), - css` - @custom-variant in-checkout { - @scope (.checkout) to (.payment) { - @slot; - } - } - `, - ) - - // The `a-*` structure is styled by the authored CSS as-is - await page.addStyleTag({ - content: css` - @scope (.checkout) to (.payment) { - .a-probe { - text-decoration-line: underline; - } - } - `, - }) - - test.skip(!(await supportsAtScope(page)), '`@scope` is not supported in this browser') - - for (let prefix of ['a', 'c']) { - expect(await getPropertyValue(`#${prefix}-in`, 'text-decoration-line')).toEqual('underline') - expect(await getPropertyValue(`#${prefix}-beyond`, 'text-decoration-line')).toEqual('none') - expect(await getPropertyValue(`#${prefix}-out`, 'text-decoration-line')).toEqual('none') - } - }) - - test('user-authored nested `@scope` behaves like the authored CSS', async ({ page }) => { - // User-authored `@scope` rules are compiled to their native CSS nesting - // semantics. Rendering the same structure twice: - // - Once styled by the authored (nested) CSS as-is - // - Once styled by the compiled output - function structure(prefix: string) { - return html` -
-
- in scope -
- beyond the limit -
-
-
-
- scope not inside parent -
- ` - } - - function scopeCss(prefix: string) { - return css` - .${prefix}-parent { - @scope (& > .${prefix}-scope) to (& .${prefix}-limit) { - .${prefix}-content { - text-decoration-line: underline; - } - } - } - ` - } - - // The `c-*` structure is styled by the compiled CSS - let { getPropertyValue } = await render(page, structure('a') + structure('c'), scopeCss('c')) - - // The `a-*` structure is styled by the authored CSS as-is - await page.addStyleTag({ content: scopeCss('a') }) - - test.skip(!(await supportsAtScope(page)), '`@scope` is not supported in this browser') - - // The compiled output only applies within the scope - expect(await getPropertyValue('#c-in', 'text-decoration-line')).toEqual('underline') - expect(await getPropertyValue('#c-beyond', 'text-decoration-line')).toEqual('none') - expect(await getPropertyValue('#c-out', 'text-decoration-line')).toEqual('none') - - // The authored CSS behaves the same - expect(await getPropertyValue('#a-in', 'text-decoration-line')).toEqual('underline') - expect(await getPropertyValue('#a-out', 'text-decoration-line')).toEqual('none') - - // WebKit currently drops the `` limit entirely when it contains - // `&`. Since we resolve the `&` at build time, the compiled output works - // even though the authored CSS doesn't. - if (test.info().project.name !== 'webkit') { - expect(await getPropertyValue('#a-beyond', 'text-decoration-line')).toEqual('none') - } - }) - - test('user-authored `@scope` nested inside another `@scope`', async ({ page }) => { - function structure(prefix: string) { - return html` - -
-
-
ctrl
-
-
- - -
-
-
ancestor
-
-
- - -
-
-
-
beyond
-
-
-
- ` - } - - function scopeCss(prefix: string) { - return css` - @scope (.${prefix}-outer) to (.${prefix}-outer-limit) { - .${prefix}-middle { - @scope (.${prefix}-inner) to (.${prefix}-inner-limit) { - text-decoration-line: underline; - } - } - } - ` - } - - // The `c-*` structure is styled by the compiled CSS - let { getPropertyValue } = await render(page, structure('a') + structure('c'), scopeCss('c')) - - // The `a-*` structure is styled by the authored CSS as-is - await page.addStyleTag({ content: scopeCss('a') }) - - test.skip(!(await supportsAtScope(page)), '`@scope` is not supported in this browser') - - // The compiled output behaves the same in every browser - expect(await getPropertyValue('#c-ctrl', 'text-decoration-line')).toEqual('underline') - expect(await getPropertyValue('#c-ancestor', 'text-decoration-line')).toEqual('none') - expect(await getPropertyValue('#c-beyond', 'text-decoration-line')).toEqual('none') - - // The authored CSS behaves the same … - expect(await getPropertyValue('#a-ctrl', 'text-decoration-line')).toEqual('underline') - expect(await getPropertyValue('#a-beyond', 'text-decoration-line')).toEqual('none') - - // … except in Chromium, which resolves the nesting context of a - // rule-nested `@scope` without the enclosing scope's constraint (Safari - // and Firefox both constrain it). The compiled output normalizes this - // browser difference. - if (test.info().project.name !== 'chromium') { - expect(await getPropertyValue('#a-ancestor', 'text-decoration-line')).toEqual('none') - } - }) - - function supportsAtScope(page: Page) { - return page.evaluate(() => typeof CSSScopeRule !== 'undefined') - } -} - -for (let kind of ['group', 'peer']) { - test(`${kind}-* variants`, async ({ page }) => { - let targets = html` -
-
- ` - - let { getPropertyValue } = await render( - page, - kind === 'group' - ? html`
${targets}
` - : html`
-
- ${targets} -
`, - css` - .owner > .applied { - @apply ${kind}-focus:z-20; - } - `, - ) - - expect(await getPropertyValue('#target', 'z-index')).toEqual('auto') - expect(await getPropertyValue('#applied', 'z-index')).toEqual('auto') - - await page.locator('#trigger').focus() - - expect(await getPropertyValue('#target', 'z-index')).toEqual('10') - expect(await getPropertyValue('#applied', 'z-index')).toEqual('20') - - await page.locator('#trigger').blur() - - expect(await getPropertyValue('#target', 'z-index')).toEqual('auto') - expect(await getPropertyValue('#applied', 'z-index')).toEqual('auto') - }) -} - -test('group-* variants do not change specificity', async ({ page }) => { - let { getPropertyValue } = await render( - page, - html` -
-
-
- `, - css` - @layer utilities { - .target { - z-index: 20; - } - .target.equal { - z-index: 30; - } - } - `, - ) - - await page.locator('#trigger').focus() - - expect(await getPropertyValue('#target', 'z-index')).toEqual('10') - - await page.locator('#target').evaluate((element) => element.classList.add('equal')) - - expect(await getPropertyValue('#target', 'z-index')).toEqual('30') -}) - -test('stacked group-* variants match regardless of the order of the groups', async ({ page }) => { - for (let [outer, inner] of [ - ['a', 'b'], - ['b', 'a'], - ]) { - let { getPropertyValue } = await render( - page, - html` -
-
-
-
-
- `, - ) - - expect(await getPropertyValue('#target', 'z-index')).toEqual('10') - - await page.locator('#inner').evaluate((element) => element.removeAttribute('data-active')) - - expect(await getPropertyValue('#target', 'z-index')).toEqual('auto') - } -}) - -test('compound group-* and peer-* variants', async ({ page }) => { - let { getPropertyValue } = await render( - page, - html` -
-
-
-
-
-
- `, - ) - - await page.locator('#peer').focus() - - expect(await getPropertyValue('#target', 'z-index')).toEqual('10') - expect(await getPropertyValue('#middle', 'z-index')).toEqual('auto') - - await page.locator('#outer').focus() - - expect(await getPropertyValue('#target', 'z-index')).toEqual('auto') - expect(await getPropertyValue('#middle', 'z-index')).toEqual('20') -}) - // --- const preflight = fs.readFileSync(path.resolve(__dirname, '..', 'preflight.css'), 'utf-8') @@ -2516,8 +726,6 @@ async function render(page: Page, content: string, extraCss: string = '') { --color-red: rgb(255, 0, 0); --color-green: rgb(0, 255, 0); --color-blue: rgb(0, 0, 255); - --color-black: black; - --color-transparent: transparent; } } @layer base { @@ -2540,7 +748,7 @@ async function render(page: Page, content: string, extraCss: string = '') { let scanner = new Scanner({}) let candidates = scanner.scanFiles([{ content, extension: 'html' }]) - let { code: styles } = optimize(build(candidates)) + let styles = optimizeCss(build(candidates)) content = `${content}` await page.setContent(content) @@ -2555,16 +763,6 @@ async function render(page: Page, content: string, extraCss: string = '') { property, ) }, - - async getPropertyList(selector: string | [string, string], property: string) { - let value = await getPropertyValue( - page, - Array.isArray(selector) ? selector : [selector, undefined], - property, - ) - - return segment(value, ',').map((item) => item.trim()) - }, } } @@ -2573,7 +771,7 @@ async function getPropertyValue( selector: [string, string | undefined], property: string, ) { - let value = await page.evaluate( + return page.evaluate( ([[selector, pseudo], property]) => { return window .getComputedStyle(document.querySelector(selector)!, pseudo) @@ -2581,16 +779,4 @@ async function getPropertyValue( }, [selector, property] as const, ) - - switch (property) { - case 'outline': - // Order in webkit vs chromium is different. Sort to normalize. - return segment(value, ' ') - .map((part) => part.trim()) - .sort((a, z) => a.length - z.length) - .join(' ') - - default: - return value - } } diff --git a/packages/tailwindcss/theme.css b/packages/tailwindcss/theme.css index 05364f5c2..6f40223e4 100644 --- a/packages/tailwindcss/theme.css +++ b/packages/tailwindcss/theme.css @@ -1,323 +1,275 @@ @theme default { --font-sans: - -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', 'Noto Sans', Arial, - sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; --font-serif: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; - --color-red-50: oklch(97.1% 0.013 17.38); - --color-red-100: oklch(93.6% 0.032 17.717); - --color-red-200: oklch(88.5% 0.062 18.334); - --color-red-300: oklch(80.8% 0.114 19.571); - --color-red-400: oklch(70.4% 0.191 22.216); - --color-red-500: oklch(63.7% 0.237 25.331); - --color-red-600: oklch(57.7% 0.245 27.325); - --color-red-700: oklch(50.5% 0.213 27.518); - --color-red-800: oklch(44.4% 0.177 26.899); - --color-red-900: oklch(39.6% 0.141 25.723); - --color-red-950: oklch(25.8% 0.092 26.042); + --color-red-50: oklch(0.971 0.013 17.38); + --color-red-100: oklch(0.936 0.032 17.717); + --color-red-200: oklch(0.885 0.062 18.334); + --color-red-300: oklch(0.808 0.114 19.571); + --color-red-400: oklch(0.704 0.191 22.216); + --color-red-500: oklch(0.637 0.237 25.331); + --color-red-600: oklch(0.577 0.245 27.325); + --color-red-700: oklch(0.505 0.213 27.518); + --color-red-800: oklch(0.444 0.177 26.899); + --color-red-900: oklch(0.396 0.141 25.723); + --color-red-950: oklch(0.258 0.092 26.042); - --color-orange-50: oklch(98% 0.016 73.684); - --color-orange-100: oklch(95.4% 0.038 75.164); - --color-orange-200: oklch(90.1% 0.076 70.697); - --color-orange-300: oklch(83.7% 0.128 66.29); - --color-orange-400: oklch(75% 0.183 55.934); - --color-orange-500: oklch(70.5% 0.213 47.604); - --color-orange-600: oklch(64.6% 0.222 41.116); - --color-orange-700: oklch(55.3% 0.195 38.402); - --color-orange-800: oklch(47% 0.157 37.304); - --color-orange-900: oklch(40.8% 0.123 38.172); - --color-orange-950: oklch(26.6% 0.079 36.259); + --color-orange-50: oklch(0.98 0.016 73.684); + --color-orange-100: oklch(0.954 0.038 75.164); + --color-orange-200: oklch(0.901 0.076 70.697); + --color-orange-300: oklch(0.837 0.128 66.29); + --color-orange-400: oklch(0.75 0.183 55.934); + --color-orange-500: oklch(0.705 0.213 47.604); + --color-orange-600: oklch(0.646 0.222 41.116); + --color-orange-700: oklch(0.553 0.195 38.402); + --color-orange-800: oklch(0.47 0.157 37.304); + --color-orange-900: oklch(0.408 0.123 38.172); + --color-orange-950: oklch(0.266 0.079 36.259); - --color-amber-50: oklch(98.7% 0.022 95.277); - --color-amber-100: oklch(96.2% 0.059 95.617); - --color-amber-200: oklch(92.4% 0.12 95.746); - --color-amber-300: oklch(87.9% 0.169 91.605); - --color-amber-400: oklch(82.8% 0.189 84.429); - --color-amber-500: oklch(76.9% 0.188 70.08); - --color-amber-600: oklch(66.6% 0.179 58.318); - --color-amber-700: oklch(55.5% 0.163 48.998); - --color-amber-800: oklch(47.3% 0.137 46.201); - --color-amber-900: oklch(41.4% 0.112 45.904); - --color-amber-950: oklch(27.9% 0.077 45.635); + --color-amber-50: oklch(0.987 0.022 95.277); + --color-amber-100: oklch(0.962 0.059 95.617); + --color-amber-200: oklch(0.924 0.12 95.746); + --color-amber-300: oklch(0.879 0.169 91.605); + --color-amber-400: oklch(0.828 0.189 84.429); + --color-amber-500: oklch(0.769 0.188 70.08); + --color-amber-600: oklch(0.666 0.179 58.318); + --color-amber-700: oklch(0.555 0.163 48.998); + --color-amber-800: oklch(0.473 0.137 46.201); + --color-amber-900: oklch(0.414 0.112 45.904); + --color-amber-950: oklch(0.279 0.077 45.635); - --color-yellow-50: oklch(98.7% 0.026 102.212); - --color-yellow-100: oklch(97.3% 0.071 103.193); - --color-yellow-200: oklch(94.5% 0.129 101.54); - --color-yellow-300: oklch(90.5% 0.182 98.111); - --color-yellow-400: oklch(85.2% 0.199 91.936); - --color-yellow-500: oklch(79.5% 0.184 86.047); - --color-yellow-600: oklch(68.1% 0.162 75.834); - --color-yellow-700: oklch(55.4% 0.135 66.442); - --color-yellow-800: oklch(47.6% 0.114 61.907); - --color-yellow-900: oklch(42.1% 0.095 57.708); - --color-yellow-950: oklch(28.6% 0.066 53.813); + --color-yellow-50: oklch(0.987 0.026 102.212); + --color-yellow-100: oklch(0.973 0.071 103.193); + --color-yellow-200: oklch(0.945 0.129 101.54); + --color-yellow-300: oklch(0.905 0.182 98.111); + --color-yellow-400: oklch(0.852 0.199 91.936); + --color-yellow-500: oklch(0.795 0.184 86.047); + --color-yellow-600: oklch(0.681 0.162 75.834); + --color-yellow-700: oklch(0.554 0.135 66.442); + --color-yellow-800: oklch(0.476 0.114 61.907); + --color-yellow-900: oklch(0.421 0.095 57.708); + --color-yellow-950: oklch(0.286 0.066 53.813); - --color-lime-50: oklch(98.6% 0.031 120.757); - --color-lime-100: oklch(96.7% 0.067 122.328); - --color-lime-200: oklch(93.8% 0.127 124.321); - --color-lime-300: oklch(89.7% 0.196 126.665); - --color-lime-400: oklch(84.1% 0.238 128.85); - --color-lime-500: oklch(76.8% 0.233 130.85); - --color-lime-600: oklch(64.8% 0.2 131.684); - --color-lime-700: oklch(53.2% 0.157 131.589); - --color-lime-800: oklch(45.3% 0.124 130.933); - --color-lime-900: oklch(40.5% 0.101 131.063); - --color-lime-950: oklch(27.4% 0.072 132.109); + --color-lime-50: oklch(0.986 0.031 120.757); + --color-lime-100: oklch(0.967 0.067 122.328); + --color-lime-200: oklch(0.938 0.127 124.321); + --color-lime-300: oklch(0.897 0.196 126.665); + --color-lime-400: oklch(0.841 0.238 128.85); + --color-lime-500: oklch(0.768 0.233 130.85); + --color-lime-600: oklch(0.648 0.2 131.684); + --color-lime-700: oklch(0.532 0.157 131.589); + --color-lime-800: oklch(0.453 0.124 130.933); + --color-lime-900: oklch(0.405 0.101 131.063); + --color-lime-950: oklch(0.274 0.072 132.109); - --color-green-50: oklch(98.2% 0.018 155.826); - --color-green-100: oklch(96.2% 0.044 156.743); - --color-green-200: oklch(92.5% 0.084 155.995); - --color-green-300: oklch(87.1% 0.15 154.449); - --color-green-400: oklch(79.2% 0.209 151.711); - --color-green-500: oklch(72.3% 0.219 149.579); - --color-green-600: oklch(62.7% 0.194 149.214); - --color-green-700: oklch(52.7% 0.154 150.069); - --color-green-800: oklch(44.8% 0.119 151.328); - --color-green-900: oklch(39.3% 0.095 152.535); - --color-green-950: oklch(26.6% 0.065 152.934); + --color-green-50: oklch(0.982 0.018 155.826); + --color-green-100: oklch(0.962 0.044 156.743); + --color-green-200: oklch(0.925 0.084 155.995); + --color-green-300: oklch(0.871 0.15 154.449); + --color-green-400: oklch(0.792 0.209 151.711); + --color-green-500: oklch(0.723 0.219 149.579); + --color-green-600: oklch(0.627 0.194 149.214); + --color-green-700: oklch(0.527 0.154 150.069); + --color-green-800: oklch(0.448 0.119 151.328); + --color-green-900: oklch(0.393 0.095 152.535); + --color-green-950: oklch(0.266 0.065 152.934); - --color-emerald-50: oklch(97.9% 0.021 166.113); - --color-emerald-100: oklch(95% 0.052 163.051); - --color-emerald-200: oklch(90.5% 0.093 164.15); - --color-emerald-300: oklch(84.5% 0.143 164.978); - --color-emerald-400: oklch(76.5% 0.177 163.223); - --color-emerald-500: oklch(69.6% 0.17 162.48); - --color-emerald-600: oklch(59.6% 0.145 163.225); - --color-emerald-700: oklch(50.8% 0.118 165.612); - --color-emerald-800: oklch(43.2% 0.095 166.913); - --color-emerald-900: oklch(37.8% 0.077 168.94); - --color-emerald-950: oklch(26.2% 0.051 172.552); + --color-emerald-50: oklch(0.979 0.021 166.113); + --color-emerald-100: oklch(0.95 0.052 163.051); + --color-emerald-200: oklch(0.905 0.093 164.15); + --color-emerald-300: oklch(0.845 0.143 164.978); + --color-emerald-400: oklch(0.765 0.177 163.223); + --color-emerald-500: oklch(0.696 0.17 162.48); + --color-emerald-600: oklch(0.596 0.145 163.225); + --color-emerald-700: oklch(0.508 0.118 165.612); + --color-emerald-800: oklch(0.432 0.095 166.913); + --color-emerald-900: oklch(0.378 0.077 168.94); + --color-emerald-950: oklch(0.262 0.051 172.552); - --color-teal-50: oklch(98.4% 0.014 180.72); - --color-teal-100: oklch(95.3% 0.051 180.801); - --color-teal-200: oklch(91% 0.096 180.426); - --color-teal-300: oklch(85.5% 0.138 181.071); - --color-teal-400: oklch(77.7% 0.152 181.912); - --color-teal-500: oklch(70.4% 0.14 182.503); - --color-teal-600: oklch(60% 0.118 184.704); - --color-teal-700: oklch(51.1% 0.096 186.391); - --color-teal-800: oklch(43.7% 0.078 188.216); - --color-teal-900: oklch(38.6% 0.063 188.416); - --color-teal-950: oklch(27.7% 0.046 192.524); + --color-teal-50: oklch(0.984 0.014 180.72); + --color-teal-100: oklch(0.953 0.051 180.801); + --color-teal-200: oklch(0.91 0.096 180.426); + --color-teal-300: oklch(0.855 0.138 181.071); + --color-teal-400: oklch(0.777 0.152 181.912); + --color-teal-500: oklch(0.704 0.14 182.503); + --color-teal-600: oklch(0.6 0.118 184.704); + --color-teal-700: oklch(0.511 0.096 186.391); + --color-teal-800: oklch(0.437 0.078 188.216); + --color-teal-900: oklch(0.386 0.063 188.416); + --color-teal-950: oklch(0.277 0.046 192.524); - --color-cyan-50: oklch(98.4% 0.019 200.873); - --color-cyan-100: oklch(95.6% 0.045 203.388); - --color-cyan-200: oklch(91.7% 0.08 205.041); - --color-cyan-300: oklch(86.5% 0.127 207.078); - --color-cyan-400: oklch(78.9% 0.154 211.53); - --color-cyan-500: oklch(71.5% 0.143 215.221); - --color-cyan-600: oklch(60.9% 0.126 221.723); - --color-cyan-700: oklch(52% 0.105 223.128); - --color-cyan-800: oklch(45% 0.085 224.283); - --color-cyan-900: oklch(39.8% 0.07 227.392); - --color-cyan-950: oklch(30.2% 0.056 229.695); + --color-cyan-50: oklch(0.984 0.019 200.873); + --color-cyan-100: oklch(0.956 0.045 203.388); + --color-cyan-200: oklch(0.917 0.08 205.041); + --color-cyan-300: oklch(0.865 0.127 207.078); + --color-cyan-400: oklch(0.789 0.154 211.53); + --color-cyan-500: oklch(0.715 0.143 215.221); + --color-cyan-600: oklch(0.609 0.126 221.723); + --color-cyan-700: oklch(0.52 0.105 223.128); + --color-cyan-800: oklch(0.45 0.085 224.283); + --color-cyan-900: oklch(0.398 0.07 227.392); + --color-cyan-950: oklch(0.302 0.056 229.695); - --color-sky-50: oklch(97.7% 0.013 236.62); - --color-sky-100: oklch(95.1% 0.026 236.824); - --color-sky-200: oklch(90.1% 0.058 230.902); - --color-sky-300: oklch(82.8% 0.111 230.318); - --color-sky-400: oklch(74.6% 0.16 232.661); - --color-sky-500: oklch(68.5% 0.169 237.323); - --color-sky-600: oklch(58.8% 0.158 241.966); - --color-sky-700: oklch(50% 0.134 242.749); - --color-sky-800: oklch(44.3% 0.11 240.79); - --color-sky-900: oklch(39.1% 0.09 240.876); - --color-sky-950: oklch(29.3% 0.066 243.157); + --color-sky-50: oklch(0.977 0.013 236.62); + --color-sky-100: oklch(0.951 0.026 236.824); + --color-sky-200: oklch(0.901 0.058 230.902); + --color-sky-300: oklch(0.828 0.111 230.318); + --color-sky-400: oklch(0.746 0.16 232.661); + --color-sky-500: oklch(0.685 0.169 237.323); + --color-sky-600: oklch(0.588 0.158 241.966); + --color-sky-700: oklch(0.5 0.134 242.749); + --color-sky-800: oklch(0.443 0.11 240.79); + --color-sky-900: oklch(0.391 0.09 240.876); + --color-sky-950: oklch(0.293 0.066 243.157); - --color-blue-50: oklch(97% 0.014 254.604); - --color-blue-100: oklch(93.2% 0.032 255.585); - --color-blue-200: oklch(88.2% 0.059 254.128); - --color-blue-300: oklch(80.9% 0.105 251.813); - --color-blue-400: oklch(70.7% 0.165 254.624); - --color-blue-500: oklch(62.3% 0.214 259.815); - --color-blue-600: oklch(54.6% 0.245 262.881); - --color-blue-700: oklch(48.8% 0.243 264.376); - --color-blue-800: oklch(42.4% 0.199 265.638); - --color-blue-900: oklch(37.9% 0.146 265.522); - --color-blue-950: oklch(28.2% 0.091 267.935); + --color-blue-50: oklch(0.97 0.014 254.604); + --color-blue-100: oklch(0.932 0.032 255.585); + --color-blue-200: oklch(0.882 0.059 254.128); + --color-blue-300: oklch(0.809 0.105 251.813); + --color-blue-400: oklch(0.707 0.165 254.624); + --color-blue-500: oklch(0.623 0.214 259.815); + --color-blue-600: oklch(0.546 0.245 262.881); + --color-blue-700: oklch(0.488 0.243 264.376); + --color-blue-800: oklch(0.424 0.199 265.638); + --color-blue-900: oklch(0.379 0.146 265.522); + --color-blue-950: oklch(0.282 0.091 267.935); - --color-indigo-50: oklch(96.2% 0.018 272.314); - --color-indigo-100: oklch(93% 0.034 272.788); - --color-indigo-200: oklch(87% 0.065 274.039); - --color-indigo-300: oklch(78.5% 0.115 274.713); - --color-indigo-400: oklch(67.3% 0.182 276.935); - --color-indigo-500: oklch(58.5% 0.233 277.117); - --color-indigo-600: oklch(51.1% 0.262 276.966); - --color-indigo-700: oklch(45.7% 0.24 277.023); - --color-indigo-800: oklch(39.8% 0.195 277.366); - --color-indigo-900: oklch(35.9% 0.144 278.697); - --color-indigo-950: oklch(25.7% 0.09 281.288); + --color-indigo-50: oklch(0.962 0.018 272.314); + --color-indigo-100: oklch(0.93 0.034 272.788); + --color-indigo-200: oklch(0.87 0.065 274.039); + --color-indigo-300: oklch(0.785 0.115 274.713); + --color-indigo-400: oklch(0.673 0.182 276.935); + --color-indigo-500: oklch(0.585 0.233 277.117); + --color-indigo-600: oklch(0.511 0.262 276.966); + --color-indigo-700: oklch(0.457 0.24 277.023); + --color-indigo-800: oklch(0.398 0.195 277.366); + --color-indigo-900: oklch(0.359 0.144 278.697); + --color-indigo-950: oklch(0.257 0.09 281.288); - --color-violet-50: oklch(96.9% 0.016 293.756); - --color-violet-100: oklch(94.3% 0.029 294.588); - --color-violet-200: oklch(89.4% 0.057 293.283); - --color-violet-300: oklch(81.1% 0.111 293.571); - --color-violet-400: oklch(70.2% 0.183 293.541); - --color-violet-500: oklch(60.6% 0.25 292.717); - --color-violet-600: oklch(54.1% 0.281 293.009); - --color-violet-700: oklch(49.1% 0.27 292.581); - --color-violet-800: oklch(43.2% 0.232 292.759); - --color-violet-900: oklch(38% 0.189 293.745); - --color-violet-950: oklch(28.3% 0.141 291.089); + --color-violet-50: oklch(0.969 0.016 293.756); + --color-violet-100: oklch(0.943 0.029 294.588); + --color-violet-200: oklch(0.894 0.057 293.283); + --color-violet-300: oklch(0.811 0.111 293.571); + --color-violet-400: oklch(0.702 0.183 293.541); + --color-violet-500: oklch(0.606 0.25 292.717); + --color-violet-600: oklch(0.541 0.281 293.009); + --color-violet-700: oklch(0.491 0.27 292.581); + --color-violet-800: oklch(0.432 0.232 292.759); + --color-violet-900: oklch(0.38 0.189 293.745); + --color-violet-950: oklch(0.283 0.141 291.089); - --color-purple-50: oklch(97.7% 0.014 308.299); - --color-purple-100: oklch(94.6% 0.033 307.174); - --color-purple-200: oklch(90.2% 0.063 306.703); - --color-purple-300: oklch(82.7% 0.119 306.383); - --color-purple-400: oklch(71.4% 0.203 305.504); - --color-purple-500: oklch(62.7% 0.265 303.9); - --color-purple-600: oklch(55.8% 0.288 302.321); - --color-purple-700: oklch(49.6% 0.265 301.924); - --color-purple-800: oklch(43.8% 0.218 303.724); - --color-purple-900: oklch(38.1% 0.176 304.987); - --color-purple-950: oklch(29.1% 0.149 302.717); + --color-purple-50: oklch(0.977 0.014 308.299); + --color-purple-100: oklch(0.946 0.033 307.174); + --color-purple-200: oklch(0.902 0.063 306.703); + --color-purple-300: oklch(0.827 0.119 306.383); + --color-purple-400: oklch(0.714 0.203 305.504); + --color-purple-500: oklch(0.627 0.265 303.9); + --color-purple-600: oklch(0.558 0.288 302.321); + --color-purple-700: oklch(0.496 0.265 301.924); + --color-purple-800: oklch(0.438 0.218 303.724); + --color-purple-900: oklch(0.381 0.176 304.987); + --color-purple-950: oklch(0.291 0.149 302.717); - --color-fuchsia-50: oklch(97.7% 0.017 320.058); - --color-fuchsia-100: oklch(95.2% 0.037 318.852); - --color-fuchsia-200: oklch(90.3% 0.076 319.62); - --color-fuchsia-300: oklch(83.3% 0.145 321.434); - --color-fuchsia-400: oklch(74% 0.238 322.16); - --color-fuchsia-500: oklch(66.7% 0.295 322.15); - --color-fuchsia-600: oklch(59.1% 0.293 322.896); - --color-fuchsia-700: oklch(51.8% 0.253 323.949); - --color-fuchsia-800: oklch(45.2% 0.211 324.591); - --color-fuchsia-900: oklch(40.1% 0.17 325.612); - --color-fuchsia-950: oklch(29.3% 0.136 325.661); + --color-fuchsia-50: oklch(0.977 0.017 320.058); + --color-fuchsia-100: oklch(0.952 0.037 318.852); + --color-fuchsia-200: oklch(0.903 0.076 319.62); + --color-fuchsia-300: oklch(0.833 0.145 321.434); + --color-fuchsia-400: oklch(0.74 0.238 322.16); + --color-fuchsia-500: oklch(0.667 0.295 322.15); + --color-fuchsia-600: oklch(0.591 0.293 322.896); + --color-fuchsia-700: oklch(0.518 0.253 323.949); + --color-fuchsia-800: oklch(0.452 0.211 324.591); + --color-fuchsia-900: oklch(0.401 0.17 325.612); + --color-fuchsia-950: oklch(0.293 0.136 325.661); - --color-pink-50: oklch(97.1% 0.014 343.198); - --color-pink-100: oklch(94.8% 0.028 342.258); - --color-pink-200: oklch(89.9% 0.061 343.231); - --color-pink-300: oklch(82.3% 0.12 346.018); - --color-pink-400: oklch(71.8% 0.202 349.761); - --color-pink-500: oklch(65.6% 0.241 354.308); - --color-pink-600: oklch(59.2% 0.249 0.584); - --color-pink-700: oklch(52.5% 0.223 3.958); - --color-pink-800: oklch(45.9% 0.187 3.815); - --color-pink-900: oklch(40.8% 0.153 2.432); - --color-pink-950: oklch(28.4% 0.109 3.907); + --color-pink-50: oklch(0.971 0.014 343.198); + --color-pink-100: oklch(0.948 0.028 342.258); + --color-pink-200: oklch(0.899 0.061 343.231); + --color-pink-300: oklch(0.823 0.12 346.018); + --color-pink-400: oklch(0.718 0.202 349.761); + --color-pink-500: oklch(0.656 0.241 354.308); + --color-pink-600: oklch(0.592 0.249 0.584); + --color-pink-700: oklch(0.525 0.223 3.958); + --color-pink-800: oklch(0.459 0.187 3.815); + --color-pink-900: oklch(0.408 0.153 2.432); + --color-pink-950: oklch(0.284 0.109 3.907); - --color-rose-50: oklch(96.9% 0.015 12.422); - --color-rose-100: oklch(94.1% 0.03 12.58); - --color-rose-200: oklch(89.2% 0.058 10.001); - --color-rose-300: oklch(81% 0.117 11.638); - --color-rose-400: oklch(71.2% 0.194 13.428); - --color-rose-500: oklch(64.5% 0.246 16.439); - --color-rose-600: oklch(58.6% 0.253 17.585); - --color-rose-700: oklch(51.4% 0.222 16.935); - --color-rose-800: oklch(45.5% 0.188 13.697); - --color-rose-900: oklch(41% 0.159 10.272); - --color-rose-950: oklch(27.1% 0.105 12.094); + --color-rose-50: oklch(0.969 0.015 12.422); + --color-rose-100: oklch(0.941 0.03 12.58); + --color-rose-200: oklch(0.892 0.058 10.001); + --color-rose-300: oklch(0.81 0.117 11.638); + --color-rose-400: oklch(0.712 0.194 13.428); + --color-rose-500: oklch(0.645 0.246 16.439); + --color-rose-600: oklch(0.586 0.253 17.585); + --color-rose-700: oklch(0.514 0.222 16.935); + --color-rose-800: oklch(0.455 0.188 13.697); + --color-rose-900: oklch(0.41 0.159 10.272); + --color-rose-950: oklch(0.271 0.105 12.094); - --color-slate-50: oklch(98.4% 0.003 247.858); - --color-slate-100: oklch(96.8% 0.007 247.896); - --color-slate-200: oklch(92.9% 0.013 255.508); - --color-slate-300: oklch(86.9% 0.022 252.894); - --color-slate-400: oklch(70.4% 0.04 256.788); - --color-slate-500: oklch(55.4% 0.046 257.417); - --color-slate-600: oklch(44.6% 0.043 257.281); - --color-slate-700: oklch(37.2% 0.044 257.287); - --color-slate-800: oklch(27.9% 0.041 260.031); - --color-slate-900: oklch(20.8% 0.042 265.755); - --color-slate-950: oklch(12.9% 0.042 264.695); + --color-slate-50: oklch(0.984 0.003 247.858); + --color-slate-100: oklch(0.968 0.007 247.896); + --color-slate-200: oklch(0.929 0.013 255.508); + --color-slate-300: oklch(0.869 0.022 252.894); + --color-slate-400: oklch(0.704 0.04 256.788); + --color-slate-500: oklch(0.554 0.046 257.417); + --color-slate-600: oklch(0.446 0.043 257.281); + --color-slate-700: oklch(0.372 0.044 257.287); + --color-slate-800: oklch(0.279 0.041 260.031); + --color-slate-900: oklch(0.208 0.042 265.755); + --color-slate-950: oklch(0.129 0.042 264.695); - --color-gray-50: oklch(98.5% 0.002 247.839); - --color-gray-100: oklch(96.7% 0.003 264.542); - --color-gray-200: oklch(92.8% 0.006 264.531); - --color-gray-300: oklch(87.2% 0.01 258.338); - --color-gray-400: oklch(70.7% 0.022 261.325); - --color-gray-500: oklch(55.1% 0.027 264.364); - --color-gray-600: oklch(44.6% 0.03 256.802); - --color-gray-700: oklch(37.3% 0.034 259.733); - --color-gray-800: oklch(27.8% 0.033 256.848); - --color-gray-900: oklch(21% 0.034 264.665); - --color-gray-950: oklch(13% 0.028 261.692); + --color-gray-50: oklch(0.985 0.002 247.839); + --color-gray-100: oklch(0.967 0.003 264.542); + --color-gray-200: oklch(0.928 0.006 264.531); + --color-gray-300: oklch(0.872 0.01 258.338); + --color-gray-400: oklch(0.707 0.022 261.325); + --color-gray-500: oklch(0.551 0.027 264.364); + --color-gray-600: oklch(0.446 0.03 256.802); + --color-gray-700: oklch(0.373 0.034 259.733); + --color-gray-800: oklch(0.278 0.033 256.848); + --color-gray-900: oklch(0.21 0.034 264.665); + --color-gray-950: oklch(0.13 0.028 261.692); - --color-zinc-50: oklch(98.5% 0 none); - --color-zinc-100: oklch(96.7% 0.001 286.375); - --color-zinc-200: oklch(92% 0.004 286.32); - --color-zinc-300: oklch(87.1% 0.006 286.286); - --color-zinc-400: oklch(70.5% 0.015 286.067); - --color-zinc-500: oklch(55.2% 0.016 285.938); - --color-zinc-600: oklch(44.2% 0.017 285.786); - --color-zinc-700: oklch(37% 0.013 285.805); - --color-zinc-800: oklch(27.4% 0.006 286.033); - --color-zinc-900: oklch(21% 0.006 285.885); - --color-zinc-950: oklch(14.1% 0.005 285.823); + --color-zinc-50: oklch(0.985 0 0); + --color-zinc-100: oklch(0.967 0.001 286.375); + --color-zinc-200: oklch(0.92 0.004 286.32); + --color-zinc-300: oklch(0.871 0.006 286.286); + --color-zinc-400: oklch(0.705 0.015 286.067); + --color-zinc-500: oklch(0.552 0.016 285.938); + --color-zinc-600: oklch(0.442 0.017 285.786); + --color-zinc-700: oklch(0.37 0.013 285.805); + --color-zinc-800: oklch(0.274 0.006 286.033); + --color-zinc-900: oklch(0.21 0.006 285.885); + --color-zinc-950: oklch(0.141 0.005 285.823); - --color-neutral-50: oklch(98.5% 0 none); - --color-neutral-100: oklch(97% 0 none); - --color-neutral-200: oklch(92.2% 0 none); - --color-neutral-300: oklch(87% 0 none); - --color-neutral-400: oklch(70.8% 0 none); - --color-neutral-500: oklch(55.6% 0 none); - --color-neutral-600: oklch(43.9% 0 none); - --color-neutral-700: oklch(37.1% 0 none); - --color-neutral-800: oklch(26.9% 0 none); - --color-neutral-900: oklch(20.5% 0 none); - --color-neutral-950: oklch(14.5% 0 none); + --color-neutral-50: oklch(0.985 0 0); + --color-neutral-100: oklch(0.97 0 0); + --color-neutral-200: oklch(0.922 0 0); + --color-neutral-300: oklch(0.87 0 0); + --color-neutral-400: oklch(0.708 0 0); + --color-neutral-500: oklch(0.556 0 0); + --color-neutral-600: oklch(0.439 0 0); + --color-neutral-700: oklch(0.371 0 0); + --color-neutral-800: oklch(0.269 0 0); + --color-neutral-900: oklch(0.205 0 0); + --color-neutral-950: oklch(0.145 0 0); - --color-stone-50: oklch(98.5% 0.001 106.423); - --color-stone-100: oklch(97% 0.001 106.424); - --color-stone-200: oklch(92.3% 0.003 48.717); - --color-stone-300: oklch(86.9% 0.005 56.366); - --color-stone-400: oklch(70.9% 0.01 56.259); - --color-stone-500: oklch(55.3% 0.013 58.071); - --color-stone-600: oklch(44.4% 0.011 73.639); - --color-stone-700: oklch(37.4% 0.01 67.558); - --color-stone-800: oklch(26.8% 0.007 34.298); - --color-stone-900: oklch(21.6% 0.006 56.043); - --color-stone-950: oklch(14.7% 0.004 49.25); - - --color-mauve-50: oklch(98.5% 0 none); - --color-mauve-100: oklch(96% 0.003 325.6); - --color-mauve-200: oklch(92.2% 0.005 325.62); - --color-mauve-300: oklch(86.5% 0.012 325.68); - --color-mauve-400: oklch(71.1% 0.019 323.02); - --color-mauve-500: oklch(54.2% 0.034 322.5); - --color-mauve-600: oklch(43.5% 0.029 321.78); - --color-mauve-700: oklch(36.4% 0.029 323.89); - --color-mauve-800: oklch(26.3% 0.024 320.12); - --color-mauve-900: oklch(21.2% 0.019 322.12); - --color-mauve-950: oklch(14.5% 0.008 326); - - --color-olive-50: oklch(98.8% 0.003 106.5); - --color-olive-100: oklch(96.6% 0.005 106.5); - --color-olive-200: oklch(93% 0.007 106.5); - --color-olive-300: oklch(88% 0.011 106.6); - --color-olive-400: oklch(73.7% 0.021 106.9); - --color-olive-500: oklch(58% 0.031 107.3); - --color-olive-600: oklch(46.6% 0.025 107.3); - --color-olive-700: oklch(39.4% 0.023 107.4); - --color-olive-800: oklch(28.6% 0.016 107.4); - --color-olive-900: oklch(22.8% 0.013 107.4); - --color-olive-950: oklch(15.3% 0.006 107.1); - - --color-mist-50: oklch(98.7% 0.002 197.1); - --color-mist-100: oklch(96.3% 0.002 197.1); - --color-mist-200: oklch(92.5% 0.005 214.3); - --color-mist-300: oklch(87.2% 0.007 219.6); - --color-mist-400: oklch(72.3% 0.014 214.4); - --color-mist-500: oklch(56% 0.021 213.5); - --color-mist-600: oklch(45% 0.017 213.2); - --color-mist-700: oklch(37.8% 0.015 216); - --color-mist-800: oklch(27.5% 0.011 216.9); - --color-mist-900: oklch(21.8% 0.008 223.9); - --color-mist-950: oklch(14.8% 0.004 228.8); - - --color-taupe-50: oklch(98.6% 0.002 67.8); - --color-taupe-100: oklch(96% 0.002 17.2); - --color-taupe-200: oklch(92.2% 0.005 34.3); - --color-taupe-300: oklch(86.8% 0.007 39.5); - --color-taupe-400: oklch(71.4% 0.014 41.2); - --color-taupe-500: oklch(54.7% 0.021 43.1); - --color-taupe-600: oklch(43.8% 0.017 39.3); - --color-taupe-700: oklch(36.7% 0.016 35.7); - --color-taupe-800: oklch(26.8% 0.011 36.5); - --color-taupe-900: oklch(21.4% 0.009 43.1); - --color-taupe-950: oklch(14.7% 0.004 49.3); + --color-stone-50: oklch(0.985 0.001 106.423); + --color-stone-100: oklch(0.97 0.001 106.424); + --color-stone-200: oklch(0.923 0.003 48.717); + --color-stone-300: oklch(0.869 0.005 56.366); + --color-stone-400: oklch(0.709 0.01 56.259); + --color-stone-500: oklch(0.553 0.013 58.071); + --color-stone-600: oklch(0.444 0.011 73.639); + --color-stone-700: oklch(0.374 0.01 67.558); + --color-stone-800: oklch(0.268 0.007 34.298); + --color-stone-900: oklch(0.216 0.006 56.043); + --color-stone-950: oklch(0.147 0.004 49.25); --color-black: #000; --color-white: #fff; @@ -422,15 +374,6 @@ --drop-shadow-xl: 0 9px 7px rgb(0 0 0 / 0.1); --drop-shadow-2xl: 0 25px 25px rgb(0 0 0 / 0.15); - --text-shadow-2xs: 0px 1px 0px rgb(0 0 0 / 0.15); - --text-shadow-xs: 0px 1px 1px rgb(0 0 0 / 0.2); - --text-shadow-sm: - 0px 1px 0px rgb(0 0 0 / 0.075), 0px 1px 1px rgb(0 0 0 / 0.075), 0px 2px 2px rgb(0 0 0 / 0.075); - --text-shadow-md: - 0px 1px 1px rgb(0 0 0 / 0.1), 0px 1px 2px rgb(0 0 0 / 0.1), 0px 2px 4px rgb(0 0 0 / 0.1); - --text-shadow-lg: - 0px 1px 2px rgb(0 0 0 / 0.1), 0px 3px 2px rgb(0 0 0 / 0.1), 0px 4px 8px rgb(0 0 0 / 0.1); - --ease-in: cubic-bezier(0.4, 0, 1, 1); --ease-out: cubic-bezier(0, 0, 0.2, 1); --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); diff --git a/packages/tailwindcss/tsconfig.json b/packages/tailwindcss/tsconfig.json index d2131cbeb..6ae022f65 100644 --- a/packages/tailwindcss/tsconfig.json +++ b/packages/tailwindcss/tsconfig.json @@ -1,6 +1,3 @@ { "extends": "../tsconfig.base.json", - "compilerOptions": { - "verbatimModuleSyntax": false, - }, } diff --git a/packages/tailwindcss/vitest.config.mts b/packages/tailwindcss/vitest.config.ts similarity index 100% rename from packages/tailwindcss/vitest.config.mts rename to packages/tailwindcss/vitest.config.ts diff --git a/patches/@napi-rs__cli@3.7.4.patch b/patches/@napi-rs__cli@3.7.4.patch deleted file mode 100644 index e72212499..000000000 --- a/patches/@napi-rs__cli@3.7.4.patch +++ /dev/null @@ -1,80 +0,0 @@ -diff --git a/dist/cli.js b/dist/cli.js -index c4c2d568bc070c07dbf3a1f9f4a0c946466668cf..353bbca86e3261a2ba9a8fc56dc612c7c84dab6d 100755 ---- a/dist/cli.js -+++ b/dist/cli.js -@@ -1,4 +1,13 @@ - #!/usr/bin/env node -+// PATCHED (see patches/@napi-rs__cli@3.7.4.patch): the embedded wasi loader -+// and worker templates below retry `new WASI(...)` with narrower preopens, -+// because preopening `/` throws `UVWASI_EACCES` on sandboxed platforms (e.g. -+// OpenHarmony), which would make the generated wasm binding fail to load. -+// -+// The same templates also exist in `dist/index.js` and `dist/index.cjs` (the -+// programmatic API). Those are intentionally NOT patched because we only -+// build through the `napi` bin, which runs this file. If we ever start using -+// the programmatic API, update the patch to cover those bundles too. - import { createRequire } from "node:module"; - import { Cli, Command, Option } from "clipanion"; - import path, { basename, dirname, isAbsolute, join, parse, resolve } from "node:path"; -@@ -1021,13 +1030,25 @@ const { - - const __rootDir = __nodePath.parse(process.cwd()).root - --const __wasi = new __nodeWASI({ -- version: 'preview1', -- env: process.env, -- preopens: { -- [__rootDir]: __rootDir, -+const __wasi = (() => { -+ // Preopening '/' fails with UVWASI_EACCES in sandboxed environments (e.g. -+ // OpenHarmony, Android), which would prevent the wasm binding from loading -+ // at all. Retry with narrower preopens instead. Without any preopens the -+ // binding still loads; only file system access is unavailable. -+ let lastError = null -+ for (const dir of [__rootDir, process.cwd(), null]) { -+ try { -+ return new __nodeWASI({ -+ version: 'preview1', -+ env: process.env, -+ preopens: dir === null ? {} : { [dir]: dir }, -+ }) -+ } catch (error) { -+ lastError = error -+ } - } --}) -+ throw lastError -+})() - - const __emnapiContext = __emnapiGetDefaultContext() - -@@ -1151,13 +1172,22 @@ const __rootDir = parse(process.cwd()).root; - - const handler = new MessageHandler({ - onLoad({ wasmModule, wasmMemory }) { -- const wasi = new WASI({ -- version: 'preview1', -- env: process.env, -- preopens: { -- [__rootDir]: __rootDir, -- }, -- }); -+ // Keep in sync with the preopen fallback in the main-thread loader. -+ const wasi = (() => { -+ let lastError = null; -+ for (const dir of [__rootDir, process.cwd(), null]) { -+ try { -+ return new WASI({ -+ version: 'preview1', -+ env: process.env, -+ preopens: dir === null ? {} : { [dir]: dir }, -+ }); -+ } catch (error) { -+ lastError = error; -+ } -+ } -+ throw lastError; -+ })(); - - return instantiateNapiModuleSync(wasmModule, { - childThread: true, diff --git a/patches/@parcel__watcher@2.6.0.patch b/patches/@parcel__watcher@2.5.1.patch similarity index 52% rename from patches/@parcel__watcher@2.6.0.patch rename to patches/@parcel__watcher@2.5.1.patch index 37ff83f04..ed9fa9040 100644 --- a/patches/@parcel__watcher@2.6.0.patch +++ b/patches/@parcel__watcher@2.5.1.patch @@ -1,22 +1,20 @@ diff --git a/index.js b/index.js -index 78ee744d74900c39a839cafa46b295258fc86307..f28fd092644ccfb8aff2618b966cb5219a181a02 100644 +index 8afb2b1126dcc687b7ff9b631589da252c1f9c22..398af238a439912d150b3573367873d2a9a311e3 100644 --- a/index.js +++ b/index.js -@@ -1,43 +1,32 @@ - const {createWrapper} = require('./wrapper'); +@@ -1,41 +1,34 @@ +-const {createWrapper} = require('./wrapper'); ++const { createWrapper } = require('./wrapper') -let name = `@parcel/watcher-${process.platform}-${process.arch}`; -if (process.platform === 'linux') { -- const {MUSL, familySync} = require('detect-libc'); -- const family = familySync(); +- const { MUSL, family } = require('detect-libc'); - if (family === MUSL) { - name += '-musl'; - } else { - name += '-glibc'; - } -} -+// We need to take the `process.env.PLATFORM_LIBC` into account, and we also -+// need static analysis, so we can't make use of the `parts.push(…)` approach. +function loadPackage() { + if (process.platform === 'linux') { + if (process.env.PLATFORM_LIBC === "musl") { @@ -24,8 +22,7 @@ index 78ee744d74900c39a839cafa46b295258fc86307..f28fd092644ccfb8aff2618b966cb521 + } else if (process.env.PLATFORM_LIBC === "glibc") { + return require(`@parcel/watcher-${process.platform}-${process.arch}-glibc`) + } else { -+ let { MUSL, GLIBC, familySync } = require('detect-libc') -+ let family = familySync() ++ let { MUSL, GLIBC, family, familySync } = require('detect-libc') -let binding; -try { @@ -40,12 +37,14 @@ index 78ee744d74900c39a839cafa46b295258fc86307..f28fd092644ccfb8aff2618b966cb521 - binding = require('./build/Debug/watcher.node'); - } catch (err) { - handleError(err); -- throw new Error( -- `No prebuild or local build of @parcel/watcher found. Tried ${name}. Please ensure it is installed (don't use --no-optional when installing with npm). Otherwise it is possible we don't support your platform yet. If this is the case, please report an issue to https://github.com/parcel-bundler/watcher.`, -- ); +- throw new Error(`No prebuild or local build of @parcel/watcher found. Tried ${name}. Please ensure it is installed (don't use --no-optional when installing with npm). Otherwise it is possible we don't support your platform yet. If this is the case, please report an issue to https://github.com/parcel-bundler/watcher.`); - } - } -} ++ // Bun polyfills `detect-libc` in compiled binaries. We rely on ++ // detect-libc@1.0.3 but the polyfilled version is 2.x. In detect-libc@2x ++ // there is a `familySync` function that we can use instead. ++ if (typeof familySync === 'function') family = familySync() -function handleError(err) { - if (err?.code !== 'MODULE_NOT_FOUND') { @@ -64,7 +63,12 @@ index 78ee744d74900c39a839cafa46b295258fc86307..f28fd092644ccfb8aff2618b966cb521 } -const wrapper = createWrapper(binding); -+const wrapper = createWrapper(loadPackage()); - exports.writeSnapshot = wrapper.writeSnapshot; - exports.getEventsSince = wrapper.getEventsSince; - exports.subscribe = wrapper.subscribe; +-exports.writeSnapshot = wrapper.writeSnapshot; +-exports.getEventsSince = wrapper.getEventsSince; +-exports.subscribe = wrapper.subscribe; +-exports.unsubscribe = wrapper.unsubscribe; ++const wrapper = createWrapper(loadPackage()) ++exports.writeSnapshot = wrapper.writeSnapshot ++exports.getEventsSince = wrapper.getEventsSince ++exports.subscribe = wrapper.subscribe ++exports.unsubscribe = wrapper.unsubscribe diff --git a/patches/lightningcss@1.33.0.patch b/patches/lightningcss@1.29.2.patch similarity index 58% rename from patches/lightningcss@1.33.0.patch rename to patches/lightningcss@1.29.2.patch index e70be2315..204f06ab3 100644 --- a/patches/lightningcss@1.33.0.patch +++ b/patches/lightningcss@1.29.2.patch @@ -1,8 +1,8 @@ diff --git a/node/index.js b/node/index.js -index 6fe25aef457aa5448f9ee6204da1e253f5534800..742be0229b94450b631b6e069ffc372d2b35df47 100644 +index 011d04b450687083e7a0ec9023c41ee79104831c..ef26db49d98c60b5b31f3601f6ad045f0b0e8f4f 100644 --- a/node/index.js +++ b/node/index.js -@@ -1,25 +1,40 @@ +@@ -1,28 +1,41 @@ -let parts = [process.platform, process.arch]; -if (process.platform === 'linux') { - const { MUSL, familySync } = require('detect-libc'); @@ -11,8 +11,12 @@ index 6fe25aef457aa5448f9ee6204da1e253f5534800..742be0229b94450b631b6e069ffc372d - parts.push('musl'); - } else if (process.arch === 'arm') { - parts.push('gnueabihf'); -+// We need to take the `process.env.PLATFORM_LIBC` into account, and we also -+// need static analysis, so we can't make use of the `parts.push(…)` approach. +- } else { +- parts.push('gnu'); +- } +-} else if (process.platform === 'win32') { +- parts.push('msvc'); +-} +function loadPackage() { + if (process.platform === 'linux') { + if (process.env.PLATFORM_LIBC === 'musl') { @@ -24,8 +28,19 @@ index 6fe25aef457aa5448f9ee6204da1e253f5534800..742be0229b94450b631b6e069ffc372d + return require(`lightningcss-${process.platform}-${process.arch}-gnu`) + } + } else { -+ let { MUSL, GLIBC, familySync } = require('detect-libc') -+ let family = familySync() ++ let { MUSL, GLIBC, family, familySync } = require('detect-libc') + +-if (process.env.CSS_TRANSFORMER_WASM) { +- module.exports = require(`../pkg`); +-} else { +- try { +- module.exports = require(`lightningcss-${parts.join('-')}`); +- } catch (err) { +- module.exports = require(`../lightningcss.${parts.join('-')}.node`); ++ // Bun polyfills `detect-libc` in compiled binaries. We rely on ++ // detect-libc@1.0.3 but the polyfilled version is 2.x. In detect-libc@2x ++ // there is a `familySync` function that we can use instead. ++ if (typeof familySync === 'function') family = familySync() + + if (family === MUSL) { + return require(`lightningcss-${process.platform}-${process.arch}-musl`) @@ -41,22 +56,15 @@ index 6fe25aef457aa5448f9ee6204da1e253f5534800..742be0229b94450b631b6e069ffc372d + } + } else if (process.platform === 'win32') { + return require(`lightningcss-${process.platform}-${process.arch}-msvc`) - } else { -- parts.push('gnu'); ++ } else { + return require(`lightningcss-${process.platform}-${process.arch}`) } --} else if (process.platform === 'win32') { -- parts.push('msvc'); } - --let native; --try { -- native = require(`lightningcss-${parts.join('-')}`); --} catch (err) { -- native = require(`../lightningcss.${parts.join('-')}.node`); --} - -+let native = loadPackage(); - module.exports.transform = wrap(native.transform); - module.exports.transformStyleAttribute = wrap(native.transformStyleAttribute); - module.exports.bundle = wrap(native.bundle); + +-module.exports.browserslistToTargets = require('./browserslistToTargets'); +-module.exports.composeVisitors = require('./composeVisitors'); +-module.exports.Features = require('./flags').Features; ++module.exports = loadPackage() ++module.exports.browserslistToTargets = require('./browserslistToTargets') ++module.exports.composeVisitors = require('./composeVisitors') ++module.exports.Features = require('./flags').Features diff --git a/playgrounds/nextjs/app/layout.tsx b/playgrounds/nextjs/app/layout.tsx index 41ee7f492..cf832e94e 100644 --- a/playgrounds/nextjs/app/layout.tsx +++ b/playgrounds/nextjs/app/layout.tsx @@ -15,7 +15,7 @@ export default function RootLayout({ children: React.ReactNode }>) { return ( - + {/* */} {children} diff --git a/playgrounds/nextjs/app/page.module.css b/playgrounds/nextjs/app/page.module.css index b0117f44c..e0c1bf51e 100644 --- a/playgrounds/nextjs/app/page.module.css +++ b/playgrounds/nextjs/app/page.module.css @@ -1,5 +1,228 @@ -@reference "./globals.css"; - -.heading { - @apply text-red-500; +.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 index 70ea89898..3c5390442 100644 --- a/playgrounds/nextjs/app/page.tsx +++ b/playgrounds/nextjs/app/page.tsx @@ -1,5 +1,3 @@ -import styles from './page.module.css' - export default function Home() { - return

Hello world!

+ return

Hello world!

} diff --git a/playgrounds/nextjs/package.json b/playgrounds/nextjs/package.json index 5487af5a5..a80871011 100644 --- a/playgrounds/nextjs/package.json +++ b/playgrounds/nextjs/package.json @@ -5,20 +5,23 @@ "scripts": { "dev": "next dev", "build": "next build", - "start": "next start" + "start": "next start", + "lint": "next lint" }, "dependencies": { "@tailwindcss/postcss": "workspace:^", "fast-glob": "^3.3.3", - "next": "16.2.7", - "react": "19.2.7", - "react-dom": "19.2.7", + "next": "15.2.3", + "react": "^19.0.0", + "react-dom": "^19.0.0", "tailwindcss": "workspace:^" }, "devDependencies": { "@types/node": "catalog:", - "@types/react": "19.2.15", - "@types/react-dom": "19.2.3", - "typescript": "^6.0.3" + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "eslint": "^9.22.0", + "eslint-config-next": "^15.2.3", + "typescript": "^5.5.4" } } diff --git a/playgrounds/nextjs/tsconfig.json b/playgrounds/nextjs/tsconfig.json index 27414aae7..862143548 100644 --- a/playgrounds/nextjs/tsconfig.json +++ b/playgrounds/nextjs/tsconfig.json @@ -10,7 +10,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "react-jsx", + "jsx": "preserve", "incremental": true, "plugins": [ { @@ -20,14 +20,7 @@ "paths": { "@/*": ["./*"], }, - "target": "ES2017", }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts", - ".next/dev/types/**/*.ts", - ], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"], } diff --git a/playgrounds/v3/package.json b/playgrounds/v3/package.json index 8c08310db..5fc2f046b 100644 --- a/playgrounds/v3/package.json +++ b/playgrounds/v3/package.json @@ -9,16 +9,18 @@ "upgrade": "node scripts/upgrade.mjs" }, "dependencies": { - "next": "16.2.7", - "react": "19.2.7", - "react-dom": "19.2.7", + "next": "15.2.3", + "react": "^19.0.0", + "react-dom": "^19.0.0", "tailwindcss": "^3" }, "devDependencies": { - "@types/node": "catalog:", - "@types/react": "19.2.15", - "@types/react-dom": "19.2.3", - "autoprefixer": "^10.5.0", - "typescript": "^6.0.3" + "@types/node": "^20.14.8", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "autoprefixer": "^10.4.21", + "eslint": "^9.22.0", + "eslint-config-next": "^15.2.3", + "typescript": "^5.5.4" } } diff --git a/playgrounds/v3/tsconfig.json b/playgrounds/v3/tsconfig.json index 27414aae7..862143548 100644 --- a/playgrounds/v3/tsconfig.json +++ b/playgrounds/v3/tsconfig.json @@ -10,7 +10,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "react-jsx", + "jsx": "preserve", "incremental": true, "plugins": [ { @@ -20,14 +20,7 @@ "paths": { "@/*": ["./*"], }, - "target": "ES2017", }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts", - ".next/dev/types/**/*.ts", - ], + "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 index 25ab1a33b..749a6bcbf 100644 --- a/playgrounds/vite/package.json +++ b/playgrounds/vite/package.json @@ -10,15 +10,15 @@ }, "dependencies": { "@tailwindcss/vite": "workspace:^", - "@vitejs/plugin-react": "^6.0.2", - "react": "^19.2.6", - "react-dom": "^19.2.6", + "@vitejs/plugin-react": "^4.3.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", "tailwindcss": "workspace:^" }, "devDependencies": { - "@types/react": "^19.2.15", - "@types/react-dom": "^19.2.3", - "bun": "^1.3.14", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "bun": "^1.2.4", "vite": "catalog:" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8382b2ba5..efeac8bfd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,136 +6,87 @@ settings: catalogs: default: - '@parcel/watcher': - specifier: 2.6.0 - version: 2.6.0 - '@parcel/watcher-darwin-arm64': - specifier: 2.6.0 - version: 2.6.0 - '@parcel/watcher-darwin-x64': - specifier: 2.6.0 - version: 2.6.0 - '@parcel/watcher-linux-arm64-glibc': - specifier: 2.6.0 - version: 2.6.0 - '@parcel/watcher-linux-arm64-musl': - specifier: 2.6.0 - version: 2.6.0 - '@parcel/watcher-linux-x64-glibc': - specifier: 2.6.0 - version: 2.6.0 - '@parcel/watcher-linux-x64-musl': - specifier: 2.6.0 - version: 2.6.0 - '@parcel/watcher-win32-x64': - specifier: 2.6.0 - version: 2.6.0 '@types/node': - specifier: 22.20.1 - version: 22.20.1 - dedent: - specifier: 1.7.2 - version: 1.7.2 - enhanced-resolve: - specifier: ^5.24.5 - version: 5.24.5 + specifier: ^20.14.8 + version: 20.14.13 lightningcss: - specifier: 1.33.0 - version: 1.33.0 + specifier: 1.29.2 + version: 1.29.2 lightningcss-darwin-arm64: - specifier: 1.33.0 - version: 1.33.0 + specifier: 1.29.2 + version: 1.29.2 lightningcss-darwin-x64: - specifier: 1.33.0 - version: 1.33.0 + specifier: 1.29.2 + version: 1.29.2 lightningcss-linux-arm64-gnu: - specifier: 1.33.0 - version: 1.33.0 + specifier: 1.29.2 + version: 1.29.2 lightningcss-linux-arm64-musl: - specifier: 1.33.0 - version: 1.33.0 + specifier: 1.29.2 + version: 1.29.2 lightningcss-linux-x64-gnu: - specifier: 1.33.0 - version: 1.33.0 + specifier: 1.29.2 + version: 1.29.2 lightningcss-linux-x64-musl: - specifier: 1.33.0 - version: 1.33.0 + specifier: 1.29.2 + version: 1.29.2 lightningcss-win32-x64-msvc: - specifier: 1.33.0 - version: 1.33.0 - postcss: - specifier: ^8.5.25 - version: 8.5.25 + specifier: 1.29.2 + version: 1.29.2 prettier: - specifier: 3.9.6 - version: 3.9.6 + specifier: 3.5.0 + version: 3.5.0 vite: - specifier: ^8.2.0 - version: 8.2.0 - webpack: - specifier: ^5.109.2 - version: 5.109.2 + specifier: ^6.0.0 + version: 6.0.0 patchedDependencies: - '@napi-rs/cli@3.7.4': 9912bf0a9c2cef8329d11c41fbce4d33ce2bdcf660a630b258c962c0a3c44bed - '@parcel/watcher@2.6.0': 705ce75ccea54337110c4d7fe0a8b421658ea0c23e1a77c1fa890a86041179da - lightningcss@1.33.0: 1d4a8800d60d13d42887b88b3a86576df4b451670308145fb432ec8abbf40930 + '@parcel/watcher@2.5.1': + hash: zs2vvlrje3h42xp5ed2v44fep4 + path: patches/@parcel__watcher@2.5.1.patch + lightningcss@1.29.2: + hash: tzyxy3asfxcqc7ihrooumyi5fm + path: patches/lightningcss@1.29.2.patch importers: .: devDependencies: '@playwright/test': - specifier: ^1.62.1 - version: 1.62.1 + specifier: ^1.51.0 + version: 1.51.0 '@types/node': specifier: 'catalog:' - version: 22.20.1 + version: 20.14.13 postcss: - specifier: 'catalog:' - version: 8.5.25 + specifier: 8.5.3 + version: 8.5.3 postcss-import: - specifier: ^16.1.1 - version: 16.1.1(postcss@8.5.25) + specifier: ^16.1.0 + version: 16.1.0(postcss@8.5.3) prettier: specifier: 'catalog:' - version: 3.9.6 + version: 3.5.0 prettier-plugin-embed: - specifier: ^0.5.1 - version: 0.5.1 + specifier: ^0.5.0 + version: 0.5.0 prettier-plugin-organize-imports: - specifier: ^4.3.0 - version: 4.3.0(prettier@3.9.6)(typescript@5.9.3) + specifier: ^4.0.0 + version: 4.0.0(prettier@3.5.0)(typescript@5.5.4) tsup: - specifier: ^8.5.1 - version: 8.5.1(jiti@2.7.0)(postcss@8.5.25)(typescript@5.9.3)(yaml@2.9.0) + specifier: ^8.4.0 + version: 8.4.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.1)(typescript@5.5.4)(yaml@2.6.0) turbo: - specifier: ^2.10.8 - version: 2.10.8 + specifier: ^2.4.4 + version: 2.4.4 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^5.5.4 + version: 5.5.4 vitest: - specifier: ^4.1.10 - version: 4.1.10(@types/node@22.20.1)(vite@8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + specifier: ^2.0.5 + version: 2.0.5(@types/node@20.14.13)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6) crates/node: - devDependencies: - '@emnapi/core': - specifier: 1.11.3 - version: 1.11.3 - '@emnapi/runtime': - specifier: 1.11.3 - version: 1.11.3 - '@napi-rs/cli': - specifier: 3.7.4 - version: 3.7.4(patch_hash=9912bf0a9c2cef8329d11c41fbce4d33ce2bdcf660a630b258c962c0a3c44bed)(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@25.9.1)(node-addon-api@8.7.0) - '@napi-rs/wasm-runtime': - specifier: ^1.2.2 - version: 1.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - emnapi: - specifier: 1.11.3 - version: 1.11.3(node-addon-api@8.7.0) optionalDependencies: '@tailwindcss/oxide-android-arm64': specifier: workspace:* @@ -164,15 +115,16 @@ importers: '@tailwindcss/oxide-linux-x64-musl': specifier: workspace:* version: link:npm/linux-x64-musl - '@tailwindcss/oxide-wasm32-wasi': - specifier: workspace:* - version: link:npm/wasm32-wasi '@tailwindcss/oxide-win32-arm64-msvc': specifier: workspace:* version: link:npm/win32-arm64-msvc '@tailwindcss/oxide-win32-x64-msvc': specifier: workspace:* version: link:npm/win32-x64-msvc + devDependencies: + '@napi-rs/cli': + specifier: ^2.18.4 + version: 2.18.4 crates/node/npm/android-arm-eabi: {} @@ -194,27 +146,6 @@ importers: crates/node/npm/linux-x64-musl: {} - crates/node/npm/wasm32-wasi: - dependencies: - '@emnapi/core': - specifier: ^1.11.3 - version: 1.11.3 - '@emnapi/runtime': - specifier: ^1.11.3 - version: 1.11.3 - '@emnapi/wasi-threads': - specifier: ^1.2.3 - version: 1.2.3 - '@napi-rs/wasm-runtime': - specifier: ^1.2.2 - version: 1.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - '@tybys/wasm-util': - specifier: ^0.10.3 - version: 0.10.3 - tslib: - specifier: ^2.8.1 - version: 2.8.1 - crates/node/npm/win32-arm64-msvc: {} crates/node/npm/win32-x64-msvc: {} @@ -222,26 +153,20 @@ importers: integrations: devDependencies: dedent: - specifier: 'catalog:' - version: 1.7.2 + specifier: 1.5.3 + version: 1.5.3 fast-glob: specifier: ^3.3.3 version: 3.3.3 - source-map-js: - specifier: ^1.2.1 - version: 1.2.1 - yaml: - specifier: ^2.9.0 - version: 2.9.0 packages/@tailwindcss-browser: devDependencies: h3: - specifier: ^1.15.11 - version: 1.15.11 + specifier: ^1.15.1 + version: 1.15.1 listhen: - specifier: ^1.10.1 - version: 1.10.1(@parcel/watcher@2.6.0(patch_hash=705ce75ccea54337110c4d7fe0a8b421658ea0c23e1a77c1fa890a86041179da)) + specifier: ^1.9.0 + version: 1.9.0 tailwindcss: specifier: workspace:* version: link:../tailwindcss @@ -249,8 +174,8 @@ importers: packages/@tailwindcss-cli: dependencies: '@parcel/watcher': - specifier: 'catalog:' - version: 2.6.0(patch_hash=705ce75ccea54337110c4d7fe0a8b421658ea0c23e1a77c1fa890a86041179da) + specifier: ^2.5.1 + version: 2.5.1(patch_hash=zs2vvlrje3h42xp5ed2v44fep4) '@tailwindcss/node': specifier: workspace:* version: link:../@tailwindcss-node @@ -258,8 +183,11 @@ importers: specifier: workspace:* version: link:../../crates/node enhanced-resolve: + specifier: ^5.18.1 + version: 5.18.1 + lightningcss: specifier: 'catalog:' - version: 5.24.5 + version: 1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm) mri: specifier: ^1.2.0 version: 1.2.0 @@ -270,26 +198,60 @@ importers: specifier: workspace:* version: link:../tailwindcss - packages/@tailwindcss-node: + packages/@tailwindcss-compatibility: dependencies: - '@jridgewell/remapping': - specifier: ^2.3.5 - version: 2.3.5 - enhanced-resolve: - specifier: 'catalog:' - version: 5.24.5 - jiti: - specifier: ^2.7.0 - version: 2.7.0 + '@csstools/postcss-cascade-layers': + specifier: ^5.0.1 + version: 5.0.1(postcss@8.5.3) + '@csstools/postcss-color-mix-function': + specifier: ^3.0.8 + version: 3.0.8(postcss@8.5.3) + '@csstools/postcss-oklab-function': + specifier: ^4.0.8 + version: 4.0.8(postcss@8.5.3) + '@tailwindcss/node': + specifier: workspace:* + version: link:../@tailwindcss-node + '@tailwindcss/oxide': + specifier: workspace:* + version: link:../../crates/node + autoprefixer: + specifier: ^10.4.21 + version: 10.4.21(postcss@8.5.3) lightningcss: specifier: 'catalog:' - version: 1.33.0(patch_hash=1d4a8800d60d13d42887b88b3a86576df4b451670308145fb432ec8abbf40930) - magic-string: - specifier: ^1.1.0 - version: 1.1.0 - source-map-js: - specifier: ^1.2.1 - version: 1.2.1 + version: 1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm) + postcss: + specifier: ^8.4.41 + version: 8.5.3 + postcss-media-minmax: + specifier: ^5.0.0 + version: 5.0.0(postcss@8.5.3) + postcss-nesting: + specifier: ^13.0.1 + version: 13.0.1(postcss@8.5.3) + devDependencies: + '@tailwindcss/postcss': + specifier: workspace:* + version: link:../@tailwindcss-postcss + '@types/node': + specifier: 'catalog:' + version: 20.14.13 + dedent: + specifier: 1.5.3 + version: 1.5.3 + tailwindcss: + specifier: workspace:* + version: link:../tailwindcss + + packages/@tailwindcss-node: + dependencies: + enhanced-resolve: + specifier: ^5.18.1 + version: 5.18.1 + jiti: + specifier: ^2.4.2 + version: 2.4.2 tailwindcss: specifier: workspace:* version: link:../tailwindcss @@ -305,28 +267,31 @@ importers: '@tailwindcss/oxide': specifier: workspace:* version: link:../../crates/node - postcss: + lightningcss: specifier: 'catalog:' - version: 8.5.25 + version: 1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm) + postcss: + specifier: ^8.4.41 + version: 8.4.41 tailwindcss: specifier: workspace:* version: link:../tailwindcss devDependencies: '@types/node': specifier: 'catalog:' - version: 22.20.1 + version: 20.14.13 '@types/postcss-import': specifier: 14.0.3 version: 14.0.3 dedent: - specifier: 'catalog:' - version: 1.7.2 + specifier: 1.5.3 + version: 1.5.3 internal-example-plugin: specifier: workspace:* version: link:../internal-example-plugin postcss-import: - specifier: ^16.1.1 - version: 16.1.1(postcss@8.5.25) + specifier: ^16.1.0 + version: 16.1.0(postcss@8.4.41) packages/@tailwindcss-standalone: dependencies: @@ -337,91 +302,69 @@ importers: specifier: workspace:* version: link:../@tailwindcss-cli '@tailwindcss/forms': - specifier: ^0.5.11 - version: 0.5.11(tailwindcss@packages+tailwindcss) + specifier: ^0.5.10 + version: 0.5.10(tailwindcss@packages+tailwindcss) '@tailwindcss/typography': - specifier: ^0.5.20 - version: 0.5.20(tailwindcss@packages+tailwindcss) + specifier: ^0.5.16 + version: 0.5.16(tailwindcss@packages+tailwindcss) detect-libc: specifier: 1.0.3 version: 1.0.3 enhanced-resolve: - specifier: 'catalog:' - version: 5.24.5 + specifier: ^5.18.1 + version: 5.18.1 tailwindcss: specifier: workspace:* version: link:../tailwindcss devDependencies: '@parcel/watcher-darwin-arm64': - specifier: 'catalog:' - version: 2.6.0 + specifier: ^2.5.1 + version: 2.5.1 '@parcel/watcher-darwin-x64': - specifier: 'catalog:' - version: 2.6.0 + specifier: ^2.5.1 + version: 2.5.1 '@parcel/watcher-linux-arm64-glibc': - specifier: 'catalog:' - version: 2.6.0 + specifier: ^2.5.1 + version: 2.5.1 '@parcel/watcher-linux-arm64-musl': - specifier: 'catalog:' - version: 2.6.0 + specifier: ^2.5.1 + version: 2.5.1 '@parcel/watcher-linux-x64-glibc': - specifier: 'catalog:' - version: 2.6.0 + specifier: ^2.5.1 + version: 2.5.1 '@parcel/watcher-linux-x64-musl': - specifier: 'catalog:' - version: 2.6.0 + specifier: ^2.5.1 + version: 2.5.1 '@parcel/watcher-win32-x64': - specifier: 'catalog:' - version: 2.6.0 + specifier: ^2.5.1 + version: 2.5.1 '@types/bun': - specifier: ^1.3.14 - version: 1.3.14 + specifier: ^1.2.4 + version: 1.2.4 bun: - specifier: ^1.3.14 - version: 1.3.14 + specifier: ^1.2.4 + version: 1.2.4 lightningcss-darwin-arm64: specifier: 'catalog:' - version: 1.33.0 + version: 1.29.2 lightningcss-darwin-x64: specifier: 'catalog:' - version: 1.33.0 + version: 1.29.2 lightningcss-linux-arm64-gnu: specifier: 'catalog:' - version: 1.33.0 + version: 1.29.2 lightningcss-linux-arm64-musl: specifier: 'catalog:' - version: 1.33.0 + version: 1.29.2 lightningcss-linux-x64-gnu: specifier: 'catalog:' - version: 1.33.0 + version: 1.29.2 lightningcss-linux-x64-musl: specifier: 'catalog:' - version: 1.33.0 + version: 1.29.2 lightningcss-win32-x64-msvc: specifier: 'catalog:' - version: 1.33.0 - - packages/@tailwindcss-turbopack: - dependencies: - '@alloc/quick-lru': - specifier: ^5.2.0 - version: 5.2.0 - '@tailwindcss/node': - specifier: workspace:* - version: link:../@tailwindcss-node - '@tailwindcss/oxide': - specifier: workspace:* - version: link:../../crates/node - tailwindcss: - specifier: workspace:* - version: link:../tailwindcss - devDependencies: - '@types/node': - specifier: 'catalog:' - version: 22.20.1 - webpack: - specifier: 'catalog:' - version: 5.109.2(esbuild@0.27.7)(postcss@8.5.25) + version: 1.29.2 packages/@tailwindcss-upgrade: dependencies: @@ -431,18 +374,21 @@ importers: '@tailwindcss/oxide': specifier: workspace:* version: link:../../crates/node + braces: + specifier: ^3.0.3 + version: 3.0.3 dedent: - specifier: 'catalog:' - version: 1.7.2 + specifier: 1.5.3 + version: 1.5.3 enhanced-resolve: - specifier: 'catalog:' - version: 5.24.5 + specifier: ^5.18.1 + version: 5.18.1 globby: - specifier: ^16.2.2 - version: 16.2.2 + specifier: ^14.1.0 + version: 14.1.0 jiti: - specifier: ^2.7.0 - version: 2.7.0 + specifier: ^2.0.0-beta.3 + version: 2.4.2 mri: specifier: ^1.2.0 version: 1.2.0 @@ -450,39 +396,36 @@ importers: specifier: ^1.1.1 version: 1.1.1 postcss: - specifier: 'catalog:' - version: 8.5.25 + specifier: ^8.4.41 + version: 8.4.41 postcss-import: - specifier: ^16.1.1 - version: 16.1.1(postcss@8.5.25) + specifier: ^16.1.0 + version: 16.1.0(postcss@8.4.41) postcss-selector-parser: - specifier: ^7.1.4 - version: 7.1.4 + specifier: ^7.1.0 + version: 7.1.0 prettier: specifier: 'catalog:' - version: 3.9.6 - semver: - specifier: ^7.8.5 - version: 7.8.5 + version: 3.5.0 tailwindcss: specifier: workspace:* version: link:../tailwindcss tree-sitter: - specifier: ^0.25.1 - version: 0.25.1 + specifier: ^0.22.4 + version: 0.22.4 tree-sitter-typescript: specifier: ^0.23.2 - version: 0.23.2(tree-sitter@0.25.1) + version: 0.23.2(tree-sitter@0.22.4) devDependencies: + '@types/braces': + specifier: ^3.0.5 + version: 3.0.5 '@types/node': specifier: 'catalog:' - version: 22.20.1 + version: 20.14.13 '@types/postcss-import': specifier: ^14.0.3 version: 14.0.3 - '@types/semver': - specifier: ^7.8.0 - version: 7.8.0 packages/@tailwindcss-vite: dependencies: @@ -492,67 +435,36 @@ importers: '@tailwindcss/oxide': specifier: workspace:* version: link:../../crates/node + lightningcss: + specifier: 'catalog:' + version: 1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm) tailwindcss: specifier: workspace:* version: link:../tailwindcss devDependencies: '@types/node': specifier: 'catalog:' - version: 22.20.1 + version: 20.14.13 vite: specifier: 'catalog:' - version: 8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) - - packages/@tailwindcss-webpack: - dependencies: - '@alloc/quick-lru': - specifier: ^5.2.0 - version: 5.2.0 - '@rspack/core': - specifier: ^1.0.0 || ^2.0.0 - version: 2.0.2(@swc/helpers@0.5.15) - '@tailwindcss/node': - specifier: workspace:* - version: link:../@tailwindcss-node - '@tailwindcss/oxide': - specifier: workspace:* - version: link:../../crates/node - tailwindcss: - specifier: workspace:* - version: link:../tailwindcss - devDependencies: - '@types/node': - specifier: 'catalog:' - version: 22.20.1 - webpack: - specifier: 'catalog:' - version: 5.109.2(esbuild@0.27.7)(postcss@8.5.25) + version: 6.0.0(@types/node@20.14.13)(jiti@2.4.2)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6)(tsx@4.19.1)(yaml@2.6.0) packages/internal-example-plugin: {} packages/tailwindcss: devDependencies: - '@jridgewell/remapping': - specifier: ^2.3.5 - version: 2.3.5 '@tailwindcss/oxide': specifier: workspace:^ version: link:../../crates/node '@types/node': specifier: 'catalog:' - version: 22.20.1 + version: 20.14.13 dedent: - specifier: 'catalog:' - version: 1.7.2 + specifier: 1.5.3 + version: 1.5.3 lightningcss: specifier: 'catalog:' - version: 1.33.0(patch_hash=1d4a8800d60d13d42887b88b3a86576df4b451670308145fb432ec8abbf40930) - magic-string: - specifier: ^1.1.0 - version: 1.1.0 - source-map-js: - specifier: ^1.2.1 - version: 1.2.1 + version: 1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm) playgrounds/nextjs: dependencies: @@ -563,61 +475,73 @@ importers: specifier: ^3.3.3 version: 3.3.3 next: - specifier: 16.2.7 - version: 16.2.7(@playwright/test@1.62.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 15.2.3 + version: 15.2.3(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: - specifier: 19.2.7 - version: 19.2.7 + specifier: ^19.0.0 + version: 19.0.0 react-dom: - specifier: 19.2.7 - version: 19.2.7(react@19.2.7) + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) tailwindcss: specifier: workspace:^ version: link:../../packages/tailwindcss devDependencies: '@types/node': specifier: 'catalog:' - version: 22.20.1 + version: 20.14.13 '@types/react': - specifier: 19.2.15 - version: 19.2.15 + specifier: ^19.0.10 + version: 19.0.10 '@types/react-dom': - specifier: 19.2.3 - version: 19.2.3(@types/react@19.2.15) + specifier: ^19.0.4 + version: 19.0.4(@types/react@19.0.10) + eslint: + specifier: ^9.22.0 + version: 9.22.0(jiti@2.4.2) + eslint-config-next: + specifier: ^15.2.3 + version: 15.2.3(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4) typescript: - specifier: ^6.0.3 - version: 6.0.3 + specifier: ^5.5.4 + version: 5.5.4 playgrounds/v3: dependencies: next: - specifier: 16.2.7 - version: 16.2.7(@playwright/test@1.62.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 15.2.3 + version: 15.2.3(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: - specifier: 19.2.7 - version: 19.2.7 + specifier: ^19.0.0 + version: 19.0.0 react-dom: - specifier: 19.2.7 - version: 19.2.7(react@19.2.7) + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) tailwindcss: specifier: ^3 - version: 3.4.19(yaml@2.9.0) + version: 3.4.14 devDependencies: '@types/node': - specifier: 'catalog:' - version: 22.20.1 + specifier: ^20.14.8 + version: 20.14.13 '@types/react': - specifier: 19.2.15 - version: 19.2.15 + specifier: ^19.0.10 + version: 19.0.10 '@types/react-dom': - specifier: 19.2.3 - version: 19.2.3(@types/react@19.2.15) + specifier: ^19.0.4 + version: 19.0.4(@types/react@19.0.10) autoprefixer: - specifier: ^10.5.0 - version: 10.5.0(postcss@8.5.25) + specifier: ^10.4.21 + version: 10.4.21(postcss@8.5.3) + eslint: + specifier: ^9.22.0 + version: 9.22.0(jiti@2.4.2) + eslint-config-next: + specifier: ^15.2.3 + version: 15.2.3(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3) typescript: - specifier: ^6.0.3 - version: 6.0.3 + specifier: ^5.5.4 + version: 5.6.3 playgrounds/vite: dependencies: @@ -625,30 +549,30 @@ importers: specifier: workspace:^ version: link:../../packages/@tailwindcss-vite '@vitejs/plugin-react': - specifier: ^6.0.2 - version: 6.0.2(vite@8.2.0(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + specifier: ^4.3.4 + version: 4.3.4(vite@6.0.0(@types/node@20.14.13)(jiti@2.4.2)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6)(tsx@4.19.1)(yaml@2.6.0)) react: - specifier: ^19.2.6 - version: 19.2.6 + specifier: ^19.0.0 + version: 19.0.0 react-dom: - specifier: ^19.2.6 - version: 19.2.6(react@19.2.6) + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) tailwindcss: specifier: workspace:^ version: link:../../packages/tailwindcss devDependencies: '@types/react': - specifier: ^19.2.15 - version: 19.2.15 + specifier: ^19.0.10 + version: 19.0.10 '@types/react-dom': - specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.15) + specifier: ^19.0.4 + version: 19.0.4(@types/react@19.0.10) bun: - specifier: ^1.3.14 - version: 1.3.14 + specifier: ^1.2.4 + version: 1.2.4 vite: specifier: 'catalog:' - version: 8.2.0(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) + version: 6.0.0(@types/node@20.14.13)(jiti@2.4.2)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6)(tsx@4.19.1)(yaml@2.6.0) packages: @@ -656,906 +580,981 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@emnapi/core@1.10.0': - resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} - '@emnapi/core@1.11.3': - resolution: {integrity: sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} - '@emnapi/core@2.0.0-alpha.3': - resolution: {integrity: sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==} + '@babel/compat-data@7.26.3': + resolution: {integrity: sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==} + engines: {node: '>=6.9.0'} - '@emnapi/runtime@1.10.0': - resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@babel/core@7.26.0': + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + engines: {node: '>=6.9.0'} - '@emnapi/runtime@1.11.3': - resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + '@babel/generator@7.26.3': + resolution: {integrity: sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==} + engines: {node: '>=6.9.0'} - '@emnapi/runtime@2.0.0-alpha.3': - resolution: {integrity: sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==} + '@babel/helper-compilation-targets@7.25.9': + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} + engines: {node: '>=6.9.0'} - '@emnapi/wasi-threads@1.2.1': - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} - '@emnapi/wasi-threads@1.2.3': - resolution: {integrity: sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==} + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 - '@emnapi/wasi-threads@2.0.1': - resolution: {integrity: sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==} + '@babel/helper-plugin-utils@7.25.9': + resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} + engines: {node: '>=6.9.0'} - '@esbuild/aix-ppc64@0.27.7': - resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.26.0': + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.3': + resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.25.9': + resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.25.9': + resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.26.4': + resolution: {integrity: sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.3': + resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} + engines: {node: '>=6.9.0'} + + '@csstools/color-helpers@5.0.2': + resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.2': + resolution: {integrity: sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-color-parser@3.0.8': + resolution: {integrity: sha512-pdwotQjCCnRPuNi06jFuP68cykU1f3ZWExLe/8MQ1LOs8Xq+fTkYgd+2V8mWUWMrOn9iS2HftPVaMZDaXzGbhQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-parser-algorithms@3.0.4': + resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-tokenizer@3.0.3': + resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} + engines: {node: '>=18'} + + '@csstools/postcss-cascade-layers@5.0.1': + resolution: {integrity: sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-color-mix-function@3.0.8': + resolution: {integrity: sha512-yuZpgWUzqZWQhEqfvtJufhl28DgO9sBwSbXbf/59gejNuvZcoUTRGQZhzhwF4ccqb53YAGB+u92z9+eSKoB4YA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-oklab-function@4.0.8': + resolution: {integrity: sha512-+5aPsNWgxohXoYNS1f+Ys0x3Qnfehgygv3qrPyv+Y25G0yX54/WlVB+IXprqBLOXHM1gsVF+QQSjlArhygna0Q==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-progressive-custom-properties@4.0.0': + resolution: {integrity: sha512-XQPtROaQjomnvLUSy/bALTR5VCtTVUFwYs1SblvYgLSeTo2a/bMNwUwo2piXw5rTv/FEYiy5yPSXBqg9OKUx7Q==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/selector-resolve-nested@3.0.0': + resolution: {integrity: sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==} + engines: {node: '>=18'} + peerDependencies: + postcss-selector-parser: ^7.0.0 + + '@csstools/selector-specificity@5.0.0': + resolution: {integrity: sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==} + engines: {node: '>=18'} + peerDependencies: + postcss-selector-parser: ^7.0.0 + + '@csstools/utilities@2.0.0': + resolution: {integrity: sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@emnapi/runtime@1.3.1': + resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.7': - resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + '@esbuild/aix-ppc64@0.24.0': + resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.25.0': + resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.7': - resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + '@esbuild/android-arm64@0.24.0': + resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.25.0': + resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.7': - resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + '@esbuild/android-arm@0.24.0': + resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.25.0': + resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.7': - resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + '@esbuild/android-x64@0.24.0': + resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.25.0': + resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.7': - resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + '@esbuild/darwin-arm64@0.24.0': + resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.25.0': + resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.7': - resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + '@esbuild/darwin-x64@0.24.0': + resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.0': + resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.7': - resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + '@esbuild/freebsd-arm64@0.24.0': + resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.25.0': + resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.7': - resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + '@esbuild/freebsd-x64@0.24.0': + resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.0': + resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.7': - resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + '@esbuild/linux-arm64@0.24.0': + resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.25.0': + resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.7': - resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + '@esbuild/linux-arm@0.24.0': + resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.25.0': + resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.7': - resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + '@esbuild/linux-ia32@0.24.0': + resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.25.0': + resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.7': - resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + '@esbuild/linux-loong64@0.24.0': + resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.25.0': + resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.7': - resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + '@esbuild/linux-mips64el@0.24.0': + resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.25.0': + resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.7': - resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + '@esbuild/linux-ppc64@0.24.0': + resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.25.0': + resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.7': - resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + '@esbuild/linux-riscv64@0.24.0': + resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.0': + resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.7': - resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + '@esbuild/linux-s390x@0.24.0': + resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.25.0': + resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.7': - resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + '@esbuild/linux-x64@0.24.0': + resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.25.0': + resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.0': + resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.7': - resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.7': - resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + '@esbuild/netbsd-x64@0.24.0': + resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.0': + resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.7': - resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + '@esbuild/openbsd-arm64@0.24.0': + resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.25.0': + resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.7': - resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + '@esbuild/openbsd-x64@0.24.0': + resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] + cpu: [x64] + os: [openbsd] - '@esbuild/sunos-x64@0.27.7': - resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + '@esbuild/openbsd-x64@0.25.0': + resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.7': - resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + '@esbuild/sunos-x64@0.24.0': + resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.25.0': + resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.7': - resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + '@esbuild/win32-arm64@0.24.0': + resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.25.0': + resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.7': - resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + '@esbuild/win32-ia32@0.24.0': + resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.25.0': + resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@img/colour@1.1.0': - resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + '@esbuild/win32-x64@0.24.0': + resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} engines: {node: '>=18'} + cpu: [x64] + os: [win32] - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + '@esbuild/win32-x64@0.25.0': + resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.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 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.19.2': + resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.1.0': + resolution: {integrity: sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.12.0': + resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.0': + resolution: {integrity: sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.22.0': + resolution: {integrity: sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.7': + resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.2': + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} + engines: {node: '>=18.18'} + + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] os: [linux] - libc: [glibc] - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] os: [linux] - libc: [glibc] - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] os: [linux] - libc: [glibc] - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] os: [linux] - libc: [glibc] - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] os: [linux] - libc: [musl] - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] os: [linux] - libc: [musl] - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [glibc] - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - libc: [glibc] - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - libc: [glibc] - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [glibc] - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [musl] - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [musl] - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] - '@inquirer/ansi@2.0.7': - resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} - '@inquirer/checkbox@5.2.1': - resolution: {integrity: sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/confirm@6.1.1': - resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/core@11.2.1': - resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/editor@5.2.2': - resolution: {integrity: sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/expand@5.1.1': - resolution: {integrity: sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/external-editor@3.0.3': - resolution: {integrity: sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/figures@2.0.7': - resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - - '@inquirer/input@5.1.2': - resolution: {integrity: sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/number@4.1.1': - resolution: {integrity: sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/password@5.1.1': - resolution: {integrity: sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/prompts@8.5.2': - resolution: {integrity: sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/rawlist@5.3.1': - resolution: {integrity: sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/search@4.2.1': - resolution: {integrity: sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/select@5.2.1': - resolution: {integrity: sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/type@4.0.7': - resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/source-map@0.3.11': - resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/source-map@0.3.6': + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - '@napi-rs/cli@3.7.4': - resolution: {integrity: sha512-idELIUceJ9zPgx/shcMt1fSSsteFG68v56RIXi4qUm7OYuJOt7CoMj2KnHVmbOqXgUHWQU1lCULrtQdZDG4F5A==} - engines: {node: '>= 16'} + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@napi-rs/cli@2.18.4': + resolution: {integrity: sha512-SgJeA4df9DE2iAEpr3M2H0OKl/yjtg1BnRI5/JyowS71tUWhrfSu2LT0V3vlHET+g1hBVlrO60PmEXwUEKp8Mg==} + engines: {node: '>= 10'} hasBin: true - peerDependencies: - '@emnapi/runtime': ^1.7.1 - peerDependenciesMeta: - '@emnapi/runtime': - optional: true - '@napi-rs/cross-toolchain@1.0.3': - resolution: {integrity: sha512-ENPfLe4937bsKVTDA6zdABx4pq9w0tHqRrJHyaGxgaPq03a2Bd1unD5XSKjXJjebsABJ+MjAv1A2OvCgK9yehg==} - peerDependencies: - '@napi-rs/cross-toolchain-arm64-target-aarch64': ^1.0.3 - '@napi-rs/cross-toolchain-arm64-target-armv7': ^1.0.3 - '@napi-rs/cross-toolchain-arm64-target-ppc64le': ^1.0.3 - '@napi-rs/cross-toolchain-arm64-target-s390x': ^1.0.3 - '@napi-rs/cross-toolchain-arm64-target-x86_64': ^1.0.3 - '@napi-rs/cross-toolchain-x64-target-aarch64': ^1.0.3 - '@napi-rs/cross-toolchain-x64-target-armv7': ^1.0.3 - '@napi-rs/cross-toolchain-x64-target-ppc64le': ^1.0.3 - '@napi-rs/cross-toolchain-x64-target-s390x': ^1.0.3 - '@napi-rs/cross-toolchain-x64-target-x86_64': ^1.0.3 - peerDependenciesMeta: - '@napi-rs/cross-toolchain-arm64-target-aarch64': - optional: true - '@napi-rs/cross-toolchain-arm64-target-armv7': - optional: true - '@napi-rs/cross-toolchain-arm64-target-ppc64le': - optional: true - '@napi-rs/cross-toolchain-arm64-target-s390x': - optional: true - '@napi-rs/cross-toolchain-arm64-target-x86_64': - optional: true - '@napi-rs/cross-toolchain-x64-target-aarch64': - optional: true - '@napi-rs/cross-toolchain-x64-target-armv7': - optional: true - '@napi-rs/cross-toolchain-x64-target-ppc64le': - optional: true - '@napi-rs/cross-toolchain-x64-target-s390x': - optional: true - '@napi-rs/cross-toolchain-x64-target-x86_64': - optional: true + '@next/env@15.2.3': + resolution: {integrity: sha512-a26KnbW9DFEUsSxAxKBORR/uD9THoYoKbkpFywMN/AFvboTt94b8+g/07T8J6ACsdLag8/PDU60ov4rPxRAixw==} - '@napi-rs/lzma-android-arm-eabi@1.4.5': - resolution: {integrity: sha512-Up4gpyw2SacmyKWWEib06GhiDdF+H+CCU0LAV8pnM4aJIDqKKd5LHSlBht83Jut6frkB0vwEPmAkv4NjQ5u//Q==} - engines: {node: '>= 10'} - cpu: [arm] - os: [android] + '@next/eslint-plugin-next@15.2.3': + resolution: {integrity: sha512-eNSOIMJtjs+dp4Ms1tB1PPPJUQHP3uZK+OQ7iFY9qXpGO6ojT6imCL+KcUOqE/GXGidWbBZJzYdgAdPHqeCEPA==} - '@napi-rs/lzma-android-arm64@1.4.5': - resolution: {integrity: sha512-uwa8sLlWEzkAM0MWyoZJg0JTD3BkPknvejAFG2acUA1raXM8jLrqujWCdOStisXhqQjZ2nDMp3FV6cs//zjfuQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - - '@napi-rs/lzma-darwin-arm64@1.4.5': - resolution: {integrity: sha512-0Y0TQLQ2xAjVabrMDem1NhIssOZzF/y/dqetc6OT8mD3xMTDtF8u5BqZoX3MyPc9FzpsZw4ksol+w7DsxHrpMA==} + '@next/swc-darwin-arm64@15.2.3': + resolution: {integrity: sha512-uaBhA8aLbXLqwjnsHSkxs353WrRgQgiFjduDpc7YXEU0B54IKx3vU+cxQlYwPCyC8uYEEX7THhtQQsfHnvv8dw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@napi-rs/lzma-darwin-x64@1.4.5': - resolution: {integrity: sha512-vR2IUyJY3En+V1wJkwmbGWcYiT8pHloTAWdW4pG24+51GIq+intst6Uf6D/r46citObGZrlX0QvMarOkQeHWpw==} + '@next/swc-darwin-x64@15.2.3': + resolution: {integrity: sha512-pVwKvJ4Zk7h+4hwhqOUuMx7Ib02u3gDX3HXPKIShBi9JlYllI0nU6TWLbPT94dt7FSi6mSBhfc2JrHViwqbOdw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@napi-rs/lzma-freebsd-x64@1.4.5': - resolution: {integrity: sha512-XpnYQC5SVovO35tF0xGkbHYjsS6kqyNCjuaLQ2dbEblFRr5cAZVvsJ/9h7zj/5FluJPJRDojVNxGyRhTp4z2lw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [freebsd] - - '@napi-rs/lzma-linux-arm-gnueabihf@1.4.5': - resolution: {integrity: sha512-ic1ZZMoRfRMwtSwxkyw4zIlbDZGC6davC9r+2oX6x9QiF247BRqqT94qGeL5ZP4Vtz0Hyy7TEViWhx5j6Bpzvw==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - - '@napi-rs/lzma-linux-arm64-gnu@1.4.5': - resolution: {integrity: sha512-asEp7FPd7C1Yi6DQb45a3KPHKOFBSfGuJWXcAd4/bL2Fjetb2n/KK2z14yfW8YC/Fv6x3rBM0VAZKmJuz4tysg==} + '@next/swc-linux-arm64-gnu@15.2.3': + resolution: {integrity: sha512-50ibWdn2RuFFkOEUmo9NCcQbbV9ViQOrUfG48zHBCONciHjaUKtHcYFiCwBVuzD08fzvzkWuuZkd4AqbvKO7UQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] - '@napi-rs/lzma-linux-arm64-musl@1.4.5': - resolution: {integrity: sha512-yWjcPDgJ2nIL3KNvi4536dlT/CcCWO0DUyEOlBs/SacG7BeD6IjGh6yYzd3/X1Y3JItCbZoDoLUH8iB1lTXo3w==} + '@next/swc-linux-arm64-musl@15.2.3': + resolution: {integrity: sha512-2gAPA7P652D3HzR4cLyAuVYwYqjG0mt/3pHSWTCyKZq/N/dJcUAEoNQMyUmwTZWCJRKofB+JPuDVP2aD8w2J6Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] - '@napi-rs/lzma-linux-ppc64-gnu@1.4.5': - resolution: {integrity: sha512-0XRhKuIU/9ZjT4WDIG/qnX7Xz7mSQHYZo9Gb3MP2gcvBgr6BA4zywQ9k3gmQaPn9ECE+CZg2V7DV7kT+x2pUMQ==} - engines: {node: '>= 10'} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@napi-rs/lzma-linux-riscv64-gnu@1.4.5': - resolution: {integrity: sha512-QrqDIPEUUB23GCpyQj/QFyMlr8SGxxyExeZz9OWFnHfb70kXdTLWrHS/hEI1Ru+lSbQ/6xRqeoGyQ4Aqdg+/RA==} - engines: {node: '>= 10'} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@napi-rs/lzma-linux-s390x-gnu@1.4.5': - resolution: {integrity: sha512-k8RVM5aMhW86E9H0QXdquwojew4H3SwPxbRVbl49/COJQWCUjGi79X6mYruMnMPEznZinUiT1jgKbFo2A00NdA==} - engines: {node: '>= 10'} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@napi-rs/lzma-linux-x64-gnu@1.4.5': - resolution: {integrity: sha512-6rMtBgnIq2Wcl1rQdZsnM+rtCcVCbws1nF8S2NzaUsVaZv8bjrPiAa0lwg4Eqnn1d9lgwqT+cZgm5m+//K08Kw==} + '@next/swc-linux-x64-gnu@15.2.3': + resolution: {integrity: sha512-ODSKvrdMgAJOVU4qElflYy1KSZRM3M45JVbeZu42TINCMG3anp7YCBn80RkISV6bhzKwcUqLBAmOiWkaGtBA9w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] - '@napi-rs/lzma-linux-x64-musl@1.4.5': - resolution: {integrity: sha512-eiadGBKi7Vd0bCArBUOO/qqRYPHt/VQVvGyYvDFt6C2ZSIjlD+HuOl+2oS1sjf4CFjK4eDIog6EdXnL0NE6iyQ==} + '@next/swc-linux-x64-musl@15.2.3': + resolution: {integrity: sha512-ZR9kLwCWrlYxwEoytqPi1jhPd1TlsSJWAc+H/CJHmHkf2nD92MQpSRIURR1iNgA/kuFSdxB8xIPt4p/T78kwsg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] - '@napi-rs/lzma-wasm32-wasi@1.4.5': - resolution: {integrity: sha512-+VyHHlr68dvey6fXc2hehw9gHVFIW3TtGF1XkcbAu65qVXsA9D/T+uuoRVqhE+JCyFHFrO0ixRbZDRK1XJt1sA==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@napi-rs/lzma-win32-arm64-msvc@1.4.5': - resolution: {integrity: sha512-eewnqvIyyhHi3KaZtBOJXohLvwwN27gfS2G/YDWdfHlbz1jrmfeHAmzMsP5qv8vGB+T80TMHNkro4kYjeh6Deg==} + '@next/swc-win32-arm64-msvc@15.2.3': + resolution: {integrity: sha512-+G2FrDcfm2YDbhDiObDU/qPriWeiz/9cRR0yMWJeTLGGX6/x8oryO3tt7HhodA1vZ8r2ddJPCjtLcpaVl7TE2Q==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@napi-rs/lzma-win32-ia32-msvc@1.4.5': - resolution: {integrity: sha512-OeacFVRCJOKNU/a0ephUfYZ2Yt+NvaHze/4TgOwJ0J0P4P7X1mHzN+ig9Iyd74aQDXYqc7kaCXA2dpAOcH87Cg==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@napi-rs/lzma-win32-x64-msvc@1.4.5': - resolution: {integrity: sha512-T4I1SamdSmtyZgDXGAGP+y5LEK5vxHUFwe8mz6D4R7Sa5/WCxTcCIgPJ9BD7RkpO17lzhlaM2vmVvMy96Lvk9Q==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@napi-rs/lzma@1.4.5': - resolution: {integrity: sha512-zS5LuN1OBPAyZpda2ZZgYOEDC+xecUdAGnrvbYzjnLXkrq/OBC3B9qcRvlxbDR3k5H/gVfvef1/jyUqPknqjbg==} - engines: {node: '>= 10'} - - '@napi-rs/tar-android-arm-eabi@1.1.0': - resolution: {integrity: sha512-h2Ryndraj/YiKgMV/r5by1cDusluYIRT0CaE0/PekQ4u+Wpy2iUVqvzVU98ZPnhXaNeYxEvVJHNGafpOfaD0TA==} - engines: {node: '>= 10'} - cpu: [arm] - os: [android] - - '@napi-rs/tar-android-arm64@1.1.0': - resolution: {integrity: sha512-DJFyQHr1ZxNZorm/gzc1qBNLF/FcKzcH0V0Vwan5P+o0aE2keQIGEjJ09FudkF9v6uOuJjHCVDdK6S6uHtShAw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - - '@napi-rs/tar-darwin-arm64@1.1.0': - resolution: {integrity: sha512-Zz2sXRzjIX4e532zD6xm2SjXEym6MkvfCvL2RMpG2+UwNVDVscHNcz3d47Pf3sysP2e2af7fBB3TIoK2f6trPw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@napi-rs/tar-darwin-x64@1.1.0': - resolution: {integrity: sha512-EI+CptIMNweT0ms9S3mkP/q+J6FNZ1Q6pvpJOEcWglRfyfQpLqjlC0O+dptruTPE8VamKYuqdjxfqD8hifZDOA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@napi-rs/tar-freebsd-x64@1.1.0': - resolution: {integrity: sha512-J0PIqX+pl6lBIAckL/c87gpodLbjZB1OtIK+RDscKC9NLdpVv6VGOxzUV/fYev/hctcE8EfkLbgFOfpmVQPg2g==} - engines: {node: '>= 10'} - cpu: [x64] - os: [freebsd] - - '@napi-rs/tar-linux-arm-gnueabihf@1.1.0': - resolution: {integrity: sha512-SLgIQo3f3EjkZ82ZwvrEgFvMdDAhsxCYjyoSuWfHCz0U16qx3SuGCp8+FYOPYCECHN3ZlGjXnoAIt9ERd0dEUg==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - - '@napi-rs/tar-linux-arm64-gnu@1.1.0': - resolution: {integrity: sha512-d014cdle52EGaH6GpYTQOP9Py7glMO1zz/+ynJPjjzYFSxvdYx0byrjumZk2UQdIyGZiJO2MEFpCkEEKFSgPYA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@napi-rs/tar-linux-arm64-musl@1.1.0': - resolution: {integrity: sha512-L/y1/26q9L/uBqiW/JdOb/Dc94egFvNALUZV2WCGKQXc6UByPBMgdiEyW2dtoYxYYYYc+AKD+jr+wQPcvX2vrQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@napi-rs/tar-linux-ppc64-gnu@1.1.0': - resolution: {integrity: sha512-EPE1K/80RQvPbLRJDJs1QmCIcH+7WRi0F73+oTe1582y9RtfGRuzAkzeBuAGRXAQEjRQw/RjtNqr6UTJ+8UuWQ==} - engines: {node: '>= 10'} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@napi-rs/tar-linux-s390x-gnu@1.1.0': - resolution: {integrity: sha512-B2jhWiB1ffw1nQBqLUP1h4+J1ovAxBOoe5N2IqDMOc63fsPZKNqF1PvO/dIem8z7LL4U4bsfmhy3gBfu547oNQ==} - engines: {node: '>= 10'} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@napi-rs/tar-linux-x64-gnu@1.1.0': - resolution: {integrity: sha512-tbZDHnb9617lTnsDMGo/eAMZxnsQFnaRe+MszRqHguKfMwkisc9CCJnks/r1o84u5fECI+J/HOrKXgczq/3Oww==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@napi-rs/tar-linux-x64-musl@1.1.0': - resolution: {integrity: sha512-dV6cODlzbO8u6Anmv2N/ilQHq/AWz0xyltuXoLU3yUyXbZcnWYZuB2rL8OBGPmqNcD+x9NdScBNXh7vWN0naSQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@napi-rs/tar-wasm32-wasi@1.1.0': - resolution: {integrity: sha512-jIa9nb2HzOrfH0F8QQ9g3WE4aMH5vSI5/1NYVNm9ysCmNjCCtMXCAhlI3WKCdm/DwHf0zLqdrrtDFXODcNaqMw==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@napi-rs/tar-win32-arm64-msvc@1.1.0': - resolution: {integrity: sha512-vfpG71OB0ijtjemp3WTdmBKJm9R70KM8vsSExMsIQtV0lVzP07oM1CW6JbNRPXNLhRoue9ofYLiUDk8bE0Hckg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@napi-rs/tar-win32-ia32-msvc@1.1.0': - resolution: {integrity: sha512-hGPyPW60YSpOSgzfy68DLBHgi6HxkAM+L59ZZZPMQ0TOXjQg+p2EW87+TjZfJOkSpbYiEkULwa/f4a2hcVjsqQ==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@napi-rs/tar-win32-x64-msvc@1.1.0': - resolution: {integrity: sha512-L6Ed1DxXK9YSCMyvpR8MiNAyKNkQLjsHsHK9E0qnHa8NzLFqzDKhvs5LfnWxM2kJ+F7m/e5n9zPm24kHb3LsVw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@napi-rs/tar@1.1.0': - resolution: {integrity: sha512-7cmzIu+Vbupriudo7UudoMRH2OA3cTw67vva8MxeoAe5S7vPFI7z0vp0pMXiA25S8IUJefImQ90FeJjl8fjEaQ==} - engines: {node: '>= 10'} - - '@napi-rs/wasm-runtime@1.1.4': - resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 - - '@napi-rs/wasm-runtime@1.2.2': - resolution: {integrity: sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} - peerDependencies: - '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.3 - '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.3 - - '@napi-rs/wasm-tools-android-arm-eabi@1.0.1': - resolution: {integrity: sha512-lr07E/l571Gft5v4aA1dI8koJEmF1F0UigBbsqg9OWNzg80H3lDPO+auv85y3T/NHE3GirDk7x/D3sLO57vayw==} - engines: {node: '>= 10'} - cpu: [arm] - os: [android] - - '@napi-rs/wasm-tools-android-arm64@1.0.1': - resolution: {integrity: sha512-WDR7S+aRLV6LtBJAg5fmjKkTZIdrEnnQxgdsb7Cf8pYiMWBHLU+LC49OUVppQ2YSPY0+GeYm9yuZWW3kLjJ7Bg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - - '@napi-rs/wasm-tools-darwin-arm64@1.0.1': - resolution: {integrity: sha512-qWTI+EEkiN0oIn/N2gQo7+TVYil+AJ20jjuzD2vATS6uIjVz+Updeqmszi7zq7rdFTLp6Ea3/z4kDKIfZwmR9g==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@napi-rs/wasm-tools-darwin-x64@1.0.1': - resolution: {integrity: sha512-bA6hubqtHROR5UI3tToAF/c6TDmaAgF0SWgo4rADHtQ4wdn0JeogvOk50gs2TYVhKPE2ZD2+qqt7oBKB+sxW3A==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@napi-rs/wasm-tools-freebsd-x64@1.0.1': - resolution: {integrity: sha512-90+KLBkD9hZEjPQW1MDfwSt5J1L46EUKacpCZWyRuL6iIEO5CgWU0V/JnEgFsDOGyyYtiTvHc5bUdUTWd4I9Vg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [freebsd] - - '@napi-rs/wasm-tools-linux-arm64-gnu@1.0.1': - resolution: {integrity: sha512-rG0QlS65x9K/u3HrKafDf8cFKj5wV2JHGfl8abWgKew0GVPyp6vfsDweOwHbWAjcHtp2LHi6JHoW80/MTHm52Q==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@napi-rs/wasm-tools-linux-arm64-musl@1.0.1': - resolution: {integrity: sha512-jAasbIvjZXCgX0TCuEFQr+4D6Lla/3AAVx2LmDuMjgG4xoIXzjKWl7c4chuaD+TI+prWT0X6LJcdzFT+ROKGHQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@napi-rs/wasm-tools-linux-x64-gnu@1.0.1': - resolution: {integrity: sha512-Plgk5rPqqK2nocBGajkMVbGm010Z7dnUgq0wtnYRZbzWWxwWcXfZMPa8EYxrK4eE8SzpI7VlZP1tdVsdjgGwMw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@napi-rs/wasm-tools-linux-x64-musl@1.0.1': - resolution: {integrity: sha512-GW7AzGuWxtQkyHknHWYFdR0CHmW6is8rG2Rf4V6GNmMpmwtXt/ItWYWtBe4zqJWycMNazpfZKSw/BpT7/MVCXQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@napi-rs/wasm-tools-wasm32-wasi@1.0.1': - resolution: {integrity: sha512-/nQVSTrqSsn7YdAc2R7Ips/tnw5SPUcl3D7QrXCNGPqjbatIspnaexvaOYNyKMU6xPu+pc0BTnKVmqhlJJCPLA==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@napi-rs/wasm-tools-win32-arm64-msvc@1.0.1': - resolution: {integrity: sha512-PFi7oJIBu5w7Qzh3dwFea3sHRO3pojMsaEnUIy22QvsW+UJfNQwJCryVrpoUt8m4QyZXI+saEq/0r4GwdoHYFQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@napi-rs/wasm-tools-win32-ia32-msvc@1.0.1': - resolution: {integrity: sha512-gXkuYzxQsgkj05Zaq+KQTkHIN83dFAwMcTKa2aQcpYPRImFm2AQzEyLtpXmyCWzJ0F9ZYAOmbSyrNew8/us6bw==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@napi-rs/wasm-tools-win32-x64-msvc@1.0.1': - resolution: {integrity: sha512-rEAf05nol3e3eei2sRButmgXP+6ATgm0/38MKhz9Isne82T4rPIMYsCIFj0kOisaGeVwoi2fnm7O9oWp5YVnYQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@napi-rs/wasm-tools@1.0.1': - resolution: {integrity: sha512-enkZYyuCdo+9jneCPE/0fjIta4wWnvVN9hBo2HuiMpRF0q3lzv1J6b/cl7i0mxZUKhBrV3aCKDBQnCOhwKbPmQ==} - engines: {node: '>= 10'} - - '@next/env@16.2.7': - resolution: {integrity: sha512-tMJizPlj6ZYpBMMdK8S0LJufrP4QTdR6pcv9KQ/bVETPAmg0j1mlHE9G2c38UyGHxoBapgwuj7XjbGJ2RcDFOg==} - - '@next/swc-darwin-arm64@16.2.7': - resolution: {integrity: sha512-vm1EDI/pVaBNNiychmxk3fft+OhQPVD9cIM/tReLZIQ3TfQ4kqI9DwKk00dzuS1ulC7icbrzCFrmRRlk9PfNdw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@next/swc-darwin-x64@16.2.7': - resolution: {integrity: sha512-O3IRSv1ZBL1zs0WrIgefTEcTKFVn+ryxBNe54erJ6KsD+2f/Mmt7g2jOYh8PSBdUwPtKQJuCsTMlZ7tIu2AcsQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@next/swc-linux-arm64-gnu@16.2.7': - resolution: {integrity: sha512-Re6PZtjBDd0aMU+VcZcC/PrIvj4WhrjDYtMhhCVQamWN4L90EVP0pcEOBQD25prSlw7OzNw5QpHLWMilRLsRNw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-arm64-musl@16.2.7': - resolution: {integrity: sha512-qyogG9QtBzWxgJfeGBvOEHI3851gTfCF3wLZ5RDLTBJGAmE9p1qDwKCOdrBrvBzRvYDT+gUDp72pzlSEfAXgNA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@next/swc-linux-x64-gnu@16.2.7': - resolution: {integrity: sha512-Vhe4ZDuBpmMogrGi5D4R2Kq4JAQlj6+wvgaFYy31zfES0zPmt6TLA+cuYpM/OLrPZjo2MYQTHVqNUSCR6+fDZQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-x64-musl@16.2.7': - resolution: {integrity: sha512-srvian89JahFLw1YLBEuhvPJ0DO5lpUeJQMXy4xYo7g628ZlNgXdNkqoxSAv9OYrBfByh6vxISMwW/mRbzCY+g==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@next/swc-win32-arm64-msvc@16.2.7': - resolution: {integrity: sha512-GX3wvLpULFuRFJzwHaKfm7QZJ18F4ZSuxlPJ96BoBglCzBmdSjyeBKF+ZhWhvL/ckxNfLnNa7bsObO2ipYpszw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@next/swc-win32-x64-msvc@16.2.7': - resolution: {integrity: sha512-J4WlM72NMk076Qsg0jTdK3SNXatlSdnjW7L7oNGLst1tAGjHrJh/FYi+pw9wyIjEtGRKDNzD0zuiY16oWYWVaw==} + '@next/swc-win32-x64-msvc@15.2.3': + resolution: {integrity: sha512-gHYS9tc+G2W0ZC8rBL+H6RdtXIyk40uLiaos0yj5US85FNhbFEndMA2nW3z47nzOWiSvXTZ5kBClc3rD0zJg0w==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1572,539 +1571,351 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@octokit/auth-token@6.0.0': - resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} - engines: {node: '>= 20'} + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} - '@octokit/core@7.0.6': - resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} - engines: {node: '>= 20'} - - '@octokit/endpoint@11.0.3': - resolution: {integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==} - engines: {node: '>= 20'} - - '@octokit/graphql@9.0.3': - resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} - engines: {node: '>= 20'} - - '@octokit/openapi-types@27.0.0': - resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} - - '@octokit/plugin-paginate-rest@14.0.0': - resolution: {integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==} - engines: {node: '>= 20'} - peerDependencies: - '@octokit/core': '>=6' - - '@octokit/plugin-request-log@6.0.0': - resolution: {integrity: sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==} - engines: {node: '>= 20'} - peerDependencies: - '@octokit/core': '>=6' - - '@octokit/plugin-rest-endpoint-methods@17.0.0': - resolution: {integrity: sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==} - engines: {node: '>= 20'} - peerDependencies: - '@octokit/core': '>=6' - - '@octokit/request-error@7.1.0': - resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} - engines: {node: '>= 20'} - - '@octokit/request@10.0.9': - resolution: {integrity: sha512-o8Bi3f608eyM+7BmBiUWxFsdjLb3/ym1cQek5LZOv9KkZcxRrHCPhhRzm6xjO6HVZ85ItD6+sTsjxo821SVa/A==} - engines: {node: '>= 20'} - - '@octokit/rest@22.0.1': - resolution: {integrity: sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==} - engines: {node: '>= 20'} - - '@octokit/types@16.0.0': - resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} - - '@oven/bun-darwin-aarch64@1.3.14': - resolution: {integrity: sha512-Omj20SuiHBOUjUBIyqtkNjSUIjOtEOJwmbix/ZyFH4BaQ6OZTaaRWIR4TjHVz0yadHgli6lLTiAh1uarnvD49A==} + '@oven/bun-darwin-aarch64@1.2.4': + resolution: {integrity: sha512-xBz/Q7X6AFwMg7MXtBemjjt5uB+tvEYBmi9Zbm1r8qnI2V8m/Smuhma0EARhiVfLuIAYj2EM5qjzxeAFV4TBJA==} cpu: [arm64] os: [darwin] - '@oven/bun-darwin-x64-baseline@1.3.14': - resolution: {integrity: sha512-OSfsTZstc898HHElhU4NccaBGOSSDn5VfahiVTnidZ9B/+wb7WTyfZJaBeJcfjwJ9H2W9uTh2TGtl3UfcXgV9g==} + '@oven/bun-darwin-x64-baseline@1.2.4': + resolution: {integrity: sha512-stsq8vBiYgfGunBGlf2M7ST7Ymyw3WnwrxEeJ04vkKmMEEE2LpX8Rkol6UPRvZawab9s9/scFIRgFi6hu9H4SQ==} cpu: [x64] os: [darwin] - '@oven/bun-darwin-x64@1.3.14': - resolution: {integrity: sha512-FFj3QdU/OhlDyZOJ8CWfN5eWLpRlT4qjZg7lMQi7jA6GuoY5ajlO1zWLP/MuHYRSbXQUvV52RejNi8DVnAp13w==} + '@oven/bun-darwin-x64@1.2.4': + resolution: {integrity: sha512-ufyty+2754QCFDhq447H39JiqabMlFRItLn1YFp+2hdpKak7KCYLGOUuHnlr1pmImKJzDHURjnvTTq1QRlUWAA==} cpu: [x64] os: [darwin] - '@oven/bun-freebsd-aarch64@1.3.14': - resolution: {integrity: sha512-LIKrXaFxAHybVO5Pf+9XP2FHUj/5APvXTUKk9dqHm5iFz4oH+W24cmhjkJirNujh9hKeTyrpWSe3no9JZKowIw==} - cpu: [arm64] - os: [freebsd] + '@oven/bun-linux-aarch64-musl@1.2.4': + resolution: {integrity: sha512-+lxWF7up9MuB1ZdGxXCH3AH3XmYtdBC6soQ38+yg3+y3iOPrAlSG+wytHEkypN/UU2mGvCuaEED3cMvejrGdDw==} + cpu: [aarch64] + os: [linux] - '@oven/bun-freebsd-x64@1.3.14': - resolution: {integrity: sha512-uwD+fGUH1ADpIF3B1U2jWzzb20QwRLZfj5QZ28GUCGrAJ/nTmWrD6YYGsblCY1wuhldRez3lU40AyuvSCyLYmw==} - cpu: [x64] - os: [freebsd] - - '@oven/bun-linux-aarch64-android@1.3.14': - resolution: {integrity: sha512-y4kq5b85lsrmFb9Xvi4w9mA5IEFJkLMrSmYn06q24KjL9rUWDWO3VFZEtteZxUN5+ec3Zm5S8OnJw1umaCbVjA==} - cpu: [arm64] - os: [android] - - '@oven/bun-linux-aarch64-musl@1.3.14': - resolution: {integrity: sha512-jmqOA92Cd1NL/1XBd4bFkJLxQ86K0RW7ohxS2qzzAvuitO4JiIxjjTeCspoU44zCozH72HpfZfUE2On31OjnWA==} + '@oven/bun-linux-aarch64@1.2.4': + resolution: {integrity: sha512-OhVpzme2vvLA7w8GYeJg2SQ2h2CwJQN9oYDiGeoML4EwE+DEoYHdxgaBZsQySOwZtFIr8ufpc/8iD4hssJ50qg==} cpu: [arm64] os: [linux] - '@oven/bun-linux-aarch64@1.3.14': - resolution: {integrity: sha512-X5SsPZHs+iYO8R/efIcRtc7gT2Q2DgPfliCxEkx4cXBumwkw0c/EsHMNwH3EgGpCDaZ7IYVPhpCG/xBOQHEwZw==} - cpu: [arm64] - os: [linux] - - '@oven/bun-linux-x64-android@1.3.14': - resolution: {integrity: sha512-qe9e1d+3VAEU7nAA2ol9Jvmy/o99PVMSgZhHn7Q/9O3YcDrfEqyQ8zm4zoe5qTEo8HZH0dN03Le0Ys2eQPs7eg==} - cpu: [x64] - os: [android] - - '@oven/bun-linux-x64-baseline@1.3.14': - resolution: {integrity: sha512-q/8EdOC0yUE8FPeoOVq8/Pw5I9/tJaYmUfO/uDUAREx8IUnOJH1RJ5A3BjFqre8pvJoiZA9AovPJq5FnNNjSxA==} + '@oven/bun-linux-x64-baseline@1.2.4': + resolution: {integrity: sha512-3nmDDZJH73MzhBg2sRYioj4CE8wgaz0w24OieMqj4/c44BbNr3X5RewrldsMD2cU6DtVbi52FuD5WpTw3N8nmw==} cpu: [x64] os: [linux] - '@oven/bun-linux-x64-musl-baseline@1.3.14': - resolution: {integrity: sha512-n6iE71G4lQE4XkrZhQQcL5YUlxDbnq6nqV7zeQi33PMsLT/0kYE+RvHOtBWZ3w0wMdXZfINmp63hIb9ijUBGtw==} + '@oven/bun-linux-x64-musl-baseline@1.2.4': + resolution: {integrity: sha512-qDsUvKCW0WUlVOt6Yx5eEzxgCbvzuSJBsu0sXtr6uGt8TnMKghmliRO5FmiMLQ0k/PUDA8vPJCtuv5k14bDi6g==} cpu: [x64] os: [linux] - '@oven/bun-linux-x64-musl@1.3.14': - resolution: {integrity: sha512-GBCB/k/sIqcr06eTNgg7g46qiUv35Jasx4XiccJ/n7RGqrE4RWUD/XJBbWFprVPjvqd59+QtSnS99XGqvftHfg==} + '@oven/bun-linux-x64-musl@1.2.4': + resolution: {integrity: sha512-cLdMbK7srNoUbYSG3Tp4GYdPAO0+5mgUhdbU053GZs0DLQmQ8h1JQhALp+ZjrUWstmQe7ddcNu7l7EAu6E76XA==} cpu: [x64] os: [linux] - '@oven/bun-linux-x64@1.3.14': - resolution: {integrity: sha512-7OVTAKvwfPmSbIV1HpdOoVVx5VRc427GuPPne93N6vk4eQBPId9nXmZDh9/zGaKPdbVjVtQSZafWQoUjx38Utw==} + '@oven/bun-linux-x64@1.2.4': + resolution: {integrity: sha512-oof3ii92Cz2yIOZRbVFHMHmffCutRPFITIdXLZ2/rkqVuKUe0ZdqWjHPhxJFm31AL9MlJ/dSqDbPb51SaLI7tw==} cpu: [x64] os: [linux] - '@oven/bun-windows-aarch64@1.3.14': - resolution: {integrity: sha512-T7s3x/BsVKQObGU6QDkZeI6wKynzqGbBH1yI77jrrj5siElclxr3DQrDIk8CV4G5/SJq2HHq4kpLyYY2DKCSmA==} - cpu: [arm64] - os: [win32] - - '@oven/bun-windows-x64-baseline@1.3.14': - resolution: {integrity: sha512-uIjLUC1S9DWgICzuoMba7vurBJnBruE4S5CxnvmZkdqWVXRzx1Rgu636HoH+k0qeaQCFh3jeG3JQ1y6fRHv0sw==} + '@oven/bun-windows-x64-baseline@1.2.4': + resolution: {integrity: sha512-j/G4bnfsRAiCvpTADda40m29iSGvueIaF9Kc9hBu4jN8dTS9fEXdNNXuf8c30/z7/npxw2dhzsAn8jbc5QvD1A==} cpu: [x64] os: [win32] - '@oven/bun-windows-x64@1.3.14': - resolution: {integrity: sha512-mUFWL3BoYkNpjd8e9PqROiFF/1Xeotq20mABJsiQH62jM1g5zqWh4khw1RZ6bX8Q8fWvlPaxG1PjofkmjUi3vg==} + '@oven/bun-windows-x64@1.2.4': + resolution: {integrity: sha512-4YRJd4pdaTWEM+uawYmchOeNv874RGAFpIZQubWnN4SBf6HfcDm0OMMZcm0f0I70Wd5gbPg1+rvCRtDZWVmZog==} cpu: [x64] os: [win32] - '@oxc-project/types@0.142.0': - resolution: {integrity: sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==} - - '@parcel/watcher-android-arm64@2.6.0': - resolution: {integrity: sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==} + '@parcel/watcher-android-arm64@2.5.0': + resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [android] - '@parcel/watcher-darwin-arm64@2.6.0': - resolution: {integrity: sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==} + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.0': + resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [darwin] - '@parcel/watcher-darwin-x64@2.6.0': - resolution: {integrity: sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==} + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.0': + resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [darwin] - '@parcel/watcher-freebsd-x64@2.6.0': - resolution: {integrity: sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==} + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.0': + resolution: {integrity: sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [freebsd] - '@parcel/watcher-linux-arm-glibc@2.6.0': - resolution: {integrity: sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==} + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.0': + resolution: {integrity: sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] - '@parcel/watcher-linux-arm-musl@2.6.0': - resolution: {integrity: sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==} + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] - '@parcel/watcher-linux-arm64-glibc@2.6.0': - resolution: {integrity: sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==} + '@parcel/watcher-linux-arm-musl@2.5.0': + resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.5.0': + resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - '@parcel/watcher-linux-arm64-musl@2.6.0': - resolution: {integrity: sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==} + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - '@parcel/watcher-linux-x64-glibc@2.6.0': - resolution: {integrity: sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==} + '@parcel/watcher-linux-arm64-musl@2.5.0': + resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.0': + resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - '@parcel/watcher-linux-x64-musl@2.6.0': - resolution: {integrity: sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==} + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - '@parcel/watcher-wasm@2.5.6': - resolution: {integrity: sha512-byAiBZ1t3tXQvc8dMD/eoyE7lTXYorhn+6uVW5AC+JGI1KtJC/LvDche5cfUE+qiefH+Ybq0bUCJU0aB1cSHUA==} + '@parcel/watcher-linux-x64-musl@2.5.0': + resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-wasm@2.5.0': + resolution: {integrity: sha512-Z4ouuR8Pfggk1EYYbTaIoxc+Yv4o7cGQnH0Xy8+pQ+HbiW+ZnwhcD2LPf/prfq1nIWpAxjOkQ8uSMFWMtBLiVQ==} engines: {node: '>= 10.0.0'} bundledDependencies: - napi-wasm - '@parcel/watcher-win32-arm64@2.6.0': - resolution: {integrity: sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==} + '@parcel/watcher-win32-arm64@2.5.0': + resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [win32] - '@parcel/watcher-win32-x64@2.6.0': - resolution: {integrity: sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==} + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.0': + resolution: {integrity: sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.0': + resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [win32] - '@parcel/watcher@2.6.0': - resolution: {integrity: sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==} + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.0': + resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==} engines: {node: '>= 10.0.0'} - '@playwright/test@1.62.1': - resolution: {integrity: sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==} - engines: {node: '>=20'} + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@playwright/test@1.51.0': + resolution: {integrity: sha512-dJ0dMbZeHhI+wb77+ljx/FeC8VBP6j/rj9OAojO08JI80wTZy6vRk9KvHKiDCUh4iMpEiseMgqRBIeW+eKX6RA==} + engines: {node: '>=18'} hasBin: true - '@rolldown/binding-android-arm64@1.2.1': - resolution: {integrity: sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.2.1': - resolution: {integrity: sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.2.1': - resolution: {integrity: sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.2.1': - resolution: {integrity: sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.2.1': - resolution: {integrity: sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.2.1': - resolution: {integrity: sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-arm64-musl@1.2.1': - resolution: {integrity: sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rolldown/binding-linux-ppc64-gnu@1.2.1': - resolution: {integrity: sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-s390x-gnu@1.2.1': - resolution: {integrity: sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-gnu@1.2.1': - resolution: {integrity: sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-musl@1.2.1': - resolution: {integrity: sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rolldown/binding-openharmony-arm64@1.2.1': - resolution: {integrity: sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.2.1': - resolution: {integrity: sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==} - engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} - - '@rolldown/binding-win32-arm64-msvc@1.2.1': - resolution: {integrity: sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.2.1': - resolution: {integrity: sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - - '@rollup/rollup-android-arm-eabi@4.60.4': - resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} + '@rollup/rollup-android-arm-eabi@4.34.9': + resolution: {integrity: sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.60.4': - resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} + '@rollup/rollup-android-arm64@4.34.9': + resolution: {integrity: sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.4': - resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} + '@rollup/rollup-darwin-arm64@4.34.9': + resolution: {integrity: sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.4': - resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} + '@rollup/rollup-darwin-x64@4.34.9': + resolution: {integrity: sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.4': - resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} + '@rollup/rollup-freebsd-arm64@4.34.9': + resolution: {integrity: sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.60.4': - resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} + '@rollup/rollup-freebsd-x64@4.34.9': + resolution: {integrity: sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.60.4': - resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} + '@rollup/rollup-linux-arm-gnueabihf@4.34.9': + resolution: {integrity: sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==} cpu: [arm] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.60.4': - resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} + '@rollup/rollup-linux-arm-musleabihf@4.34.9': + resolution: {integrity: sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==} cpu: [arm] os: [linux] - libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.60.4': - resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} + '@rollup/rollup-linux-arm64-gnu@4.34.9': + resolution: {integrity: sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==} cpu: [arm64] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.60.4': - resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} + '@rollup/rollup-linux-arm64-musl@4.34.9': + resolution: {integrity: sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==} cpu: [arm64] os: [linux] - libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.60.4': - resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} + '@rollup/rollup-linux-loongarch64-gnu@4.34.9': + resolution: {integrity: sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==} cpu: [loong64] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.60.4': - resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.60.4': - resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} + '@rollup/rollup-linux-powerpc64le-gnu@4.34.9': + resolution: {integrity: sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==} cpu: [ppc64] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.60.4': - resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.60.4': - resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} + '@rollup/rollup-linux-riscv64-gnu@4.34.9': + resolution: {integrity: sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==} cpu: [riscv64] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.60.4': - resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-s390x-gnu@4.60.4': - resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} + '@rollup/rollup-linux-s390x-gnu@4.34.9': + resolution: {integrity: sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==} cpu: [s390x] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.60.4': - resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} + '@rollup/rollup-linux-x64-gnu@4.34.9': + resolution: {integrity: sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==} cpu: [x64] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.60.4': - resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} + '@rollup/rollup-linux-x64-musl@4.34.9': + resolution: {integrity: sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==} cpu: [x64] os: [linux] - libc: [musl] - '@rollup/rollup-openbsd-x64@4.60.4': - resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.60.4': - resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.60.4': - resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} + '@rollup/rollup-win32-arm64-msvc@4.34.9': + resolution: {integrity: sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.4': - resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} + '@rollup/rollup-win32-ia32-msvc@4.34.9': + resolution: {integrity: sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.60.4': - resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} + '@rollup/rollup-win32-x64-msvc@4.34.9': + resolution: {integrity: sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.60.4': - resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} - cpu: [x64] - os: [win32] + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@rspack/binding-darwin-arm64@2.0.2': - resolution: {integrity: sha512-0o7lbgBBsDlICWdjIH0q3e0BsSco4GRiImHWVfZSVEG+q2+ykZJvSvYCVhPM1Co375Z0S3VMPa/8SjcY1FHwlw==} - cpu: [arm64] - os: [darwin] + '@rushstack/eslint-patch@1.10.4': + resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==} - '@rspack/binding-darwin-x64@2.0.2': - resolution: {integrity: sha512-tOwxZpoPlTlRs/w6UyUinXJ4TYRVHMlR7+eQxO1R3muKpixvhXQjtvoaY16HuFyTVky5F0IfOoWr3x9FEsgdLg==} - cpu: [x64] - os: [darwin] - - '@rspack/binding-linux-arm64-gnu@2.0.2': - resolution: {integrity: sha512-1ZD4YFhG1rmgqj+W8hfwHyKV8xDxGsc/3KgU0FwmiVEX7JfzhCkgBO/xlCG79kRKSrzuVzt4icO/G3cCKn0pag==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rspack/binding-linux-arm64-musl@2.0.2': - resolution: {integrity: sha512-/PtTkM/DsDLjeuXTmeJeRfbjCDbcL9jvoVgZrgxYFZ28y2cdLvbChbW9uigOzs5dQEs1CIBQXMTTj7KhdBTuQg==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rspack/binding-linux-x64-gnu@2.0.2': - resolution: {integrity: sha512-bBjsZxMHRaPo6X9SokApm6ucs+UhXtAJFyJJyuk2BH4XJsLeCU9Dz1vMwioeohFbJUUeTASVPm6/BL+RhSaunw==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rspack/binding-linux-x64-musl@2.0.2': - resolution: {integrity: sha512-HjlpInqzabDNkhVsUJpsHPqa9QYVWBViJoyWNjzXCAW0vKMDvwaphyUvokSinX8FGTlZi/sr5UEaHJo6XtQ35g==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rspack/binding-wasm32-wasi@2.0.2': - resolution: {integrity: sha512-YaRYNFLJRpkGfYjSWR7n9f+nQKtrlmrrffpAn/blc2geHcRvXoBc5SCs1idPtsLhj7H9qWWhs7ucjyHy4csWFg==} - cpu: [wasm32] - - '@rspack/binding-win32-arm64-msvc@2.0.2': - resolution: {integrity: sha512-d/3kTEKq+asLjRFPO96t+wfWiM7DLN76VQEPDD9bc1kdsZXlVJBuvyXfsgK8bbEvKplWXYcSsokhmEnuXrLOpg==} - cpu: [arm64] - os: [win32] - - '@rspack/binding-win32-ia32-msvc@2.0.2': - resolution: {integrity: sha512-161cWineq3RW+Jdm1FAfSpXeUtYWvhB3kAbm46vNT9h/YYz+spwsFMvveAZ1nsVSVL0IC5lDBGUte7yUAY8K2g==} - cpu: [ia32] - os: [win32] - - '@rspack/binding-win32-x64-msvc@2.0.2': - resolution: {integrity: sha512-y7Q0S1FE+OlkL5GMqLG0PwxrPw6E1r892KhGrGKE1Vdufe5YTEx6xTPxzZ+b7N2KPD7s9G1/iJmWHQxb1+Bjkg==} - cpu: [x64] - os: [win32] - - '@rspack/binding@2.0.2': - resolution: {integrity: sha512-0kZPplW9GWx8mfC6DfsaRY3QBIYPuUs42JfmSM6aSb8tMHZAXQeLeMB8M+h8i4SeI+aFtCgO6UuYGtyWf7+L+A==} - - '@rspack/core@2.0.2': - resolution: {integrity: sha512-VM3UHOo26uC+4QSqY5tU1ybI7KuXY5rTof8nhFOaBY9SYau0Smvr+hMSAPmrmHwknB6dXT8yaNVxrj7I+qxE1Q==} - engines: {node: ^20.19.0 || >=22.12.0} - peerDependencies: - '@module-federation/runtime-tools': ^0.24.1 || ^2.0.0 - '@swc/helpers': '>=0.5.1' - peerDependenciesMeta: - '@module-federation/runtime-tools': - optional: true - '@swc/helpers': - optional: true - - '@sindresorhus/merge-streams@4.0.0': - resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + '@sindresorhus/merge-streams@2.3.0': + resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -2114,200 +1925,169 @@ packages: peerDependencies: tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1' - '@tailwindcss/forms@0.5.11': - resolution: {integrity: sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==} + '@tailwindcss/forms@0.5.10': + resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==} peerDependencies: tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1' - '@tailwindcss/typography@0.5.20': - resolution: {integrity: sha512-hwbzQuNUfcPvbegQFatVPl/MY/tcM9KLl963hQ5laJKPh81TEZ1+dNG9PirGvcaDBkp+BCshExAyKVPW91dozw==} + '@tailwindcss/typography@0.5.16': + resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==} peerDependencies: - tailwindcss: '>=3.0.0 || >=4.0.0 || insiders' + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' - '@turbo/darwin-64@2.10.8': - resolution: {integrity: sha512-po+7rfJfUnFXjWlcoN2RwhErgzCdRtBc1T26vYPcywHlggmCQiQe1uWaE4j+BibI2uY9/2pDoFzMN0rmSaPFOw==} - cpu: [x64] - os: [darwin] + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - '@turbo/darwin-arm64@2.10.8': - resolution: {integrity: sha512-+zB2btDJ00lnPRuqOvpVvgl4x34k/djZQGZTTCfjn7JgNCl8QFY5Njo5+dqkY1g/+9gbbsnAvWm9CmJg9ebcXA==} - cpu: [arm64] - os: [darwin] + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} - '@turbo/linux-64@2.10.8': - resolution: {integrity: sha512-K1dxqiVisyN7cViVsfQLs6xscQbYuI8aO2nbUhFURDACgEDfZRdP/b4CCxeosBJpcMfhYyiibWqJorCnvz9kKg==} - cpu: [x64] - os: [android, linux] + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - '@turbo/linux-arm64@2.10.8': - resolution: {integrity: sha512-Gi77ibVnrE1fEmvr+/wBD/yvRqhwp/RQuCp2+//lv1U1wNFFyVg0V7Wj8FG9FXPFAw5QHReo8rxc9+wBSDZjzA==} - cpu: [arm64] - os: [android, linux] + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - '@turbo/windows-64@2.10.8': - resolution: {integrity: sha512-znnLO1haJPYTHoKMKwlAvlkjRiYbbhBzME6wIGaMd+fwir23U6jVd1ecaTWWi1fbnRVqxMfgDBKseQ/hLKb83g==} - cpu: [x64] - os: [win32] + '@types/braces@3.0.5': + resolution: {integrity: sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==} - '@turbo/windows-arm64@2.10.8': - resolution: {integrity: sha512-VN30vh3b3Czh2WzYHNTfF1FE0YMZ5aHsLO8dBMGHJewA6792wX6iJR8ZxlzFW6WdOu0gEAKIvlYhfyT81Wkm4Q==} - cpu: [arm64] - os: [win32] + '@types/bun@1.2.4': + resolution: {integrity: sha512-QtuV5OMR8/rdKJs213iwXDpfVvnskPXY/S0ZiFbsTjQZycuqPbMW8Gf/XhLfwE5njW8sxI2WjISURXPlHypMFA==} - '@tybys/wasm-util@0.10.3': - resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} - - '@types/bun@1.3.14': - resolution: {integrity: sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw==} - - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@22.20.1': - resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/node@25.9.1': - resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} + '@types/node@20.14.13': + resolution: {integrity: sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==} '@types/postcss-import@14.0.3': resolution: {integrity: sha512-raZhRVTf6Vw5+QbmQ7LOHSDML71A5rj4+EqDzAbrZPfxfoGzFxMHRCq16VlddGIZpHELw0BG4G0YE2ANkdZiIQ==} - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + '@types/react-dom@19.0.4': + resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==} peerDependencies: - '@types/react': ^19.2.0 + '@types/react': ^19.0.0 - '@types/react@19.2.15': - resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==} + '@types/react@19.0.10': + resolution: {integrity: sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==} - '@types/semver@7.8.0': - resolution: {integrity: sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ==} + '@types/ws@8.5.12': + resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} - '@vitejs/plugin-react@6.0.2': - resolution: {integrity: sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==} - engines: {node: ^20.19.0 || >=22.12.0} + '@typescript-eslint/eslint-plugin@8.11.0': + resolution: {integrity: sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 - babel-plugin-react-compiler: ^1.0.0 - vite: ^8.0.0 + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' peerDependenciesMeta: - '@rolldown/plugin-babel': - optional: true - babel-plugin-react-compiler: + typescript: optional: true - '@vitest/expect@4.1.10': - resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} - - '@vitest/mocker@4.1.10': - resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + '@typescript-eslint/parser@8.11.0': + resolution: {integrity: sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' peerDependenciesMeta: - msw: - optional: true - vite: + typescript: optional: true - '@vitest/pretty-format@4.1.10': - resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + '@typescript-eslint/scope-manager@8.11.0': + resolution: {integrity: sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/runner@4.1.10': - resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} + '@typescript-eslint/type-utils@8.11.0': + resolution: {integrity: sha512-ItiMfJS6pQU0NIKAaybBKkuVzo6IdnAhPFZA/2Mba/uBjuPQPet/8+zh5GtLHwmuFRShZx+8lhIs7/QeDHflOg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - '@vitest/snapshot@4.1.10': - resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} + '@typescript-eslint/types@8.11.0': + resolution: {integrity: sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/spy@4.1.10': - resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + '@typescript-eslint/typescript-estree@8.11.0': + resolution: {integrity: sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - '@vitest/utils@4.1.10': - resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + '@typescript-eslint/utils@8.11.0': + resolution: {integrity: sha512-CYiX6WZcbXNJV7UNB4PLDIBtSdRmRI/nb0FMyqHPTQD1rMjA0foPLaPUV39C/MxkTd/QKSeX+Gb34PPsDVC35g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 - '@webassemblyjs/ast@1.14.1': - resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + '@typescript-eslint/visitor-keys@8.11.0': + resolution: {integrity: sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@webassemblyjs/floating-point-hex-parser@1.13.2': - resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + '@vitejs/plugin-react@4.3.4': + resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 - '@webassemblyjs/helper-api-error@1.13.2': - resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + '@vitest/expect@2.0.5': + resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} - '@webassemblyjs/helper-buffer@1.14.1': - resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + '@vitest/pretty-format@2.0.5': + resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} - '@webassemblyjs/helper-numbers@1.13.2': - resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + '@vitest/runner@2.0.5': + resolution: {integrity: sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==} - '@webassemblyjs/helper-wasm-bytecode@1.13.2': - resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + '@vitest/snapshot@2.0.5': + resolution: {integrity: sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==} - '@webassemblyjs/helper-wasm-section@1.14.1': - resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + '@vitest/spy@2.0.5': + resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} - '@webassemblyjs/ieee754@1.13.2': - resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + '@vitest/utils@2.0.5': + resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} - '@webassemblyjs/leb128@1.13.2': - resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - '@webassemblyjs/utf8@1.13.2': - resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} - - '@webassemblyjs/wasm-edit@1.14.1': - resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} - - '@webassemblyjs/wasm-gen@1.14.1': - resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} - - '@webassemblyjs/wasm-opt@1.14.1': - resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} - - '@webassemblyjs/wasm-parser@1.14.1': - resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} - - '@webassemblyjs/wast-printer@1.14.1': - resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} - - '@xtuc/ieee754@1.2.0': - resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} - - '@xtuc/long@4.2.2': - resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true - ajv-formats@2.1.1: - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv-keywords@5.1.0: - resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} - peerDependencies: - ajv: ^8.8.2 + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} - ajv@8.20.0: - resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -2322,48 +2102,105 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} + + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.5: + resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + engines: {node: '>= 0.4'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - autoprefixer@10.5.0: - resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + + autoprefixer@10.4.21: + resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: postcss: ^8.1.0 - baseline-browser-mapping@2.10.31: - resolution: {integrity: sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==} - engines: {node: '>=6.0.0'} - hasBin: true + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} - before-after-hook@4.0.0: - resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + axe-core@4.10.2: + resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==} + engines: {node: '>=4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.2: - resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + browserslist@4.24.2: + resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - bun-types@1.3.14: - resolution: {integrity: sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ==} + bun-types@1.2.4: + resolution: {integrity: sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q==} - bun@1.3.14: - resolution: {integrity: sha512-aB6GVd42x1Y5ie1K16SF+oLGtgSkwX9hgoDdIW88pjvfTccU8F1vfpoOt34QLv0dZ1v3XimtaxPlZUG81Gx9Zg==} - cpu: [arm64, x64] - os: [darwin, linux, android, freebsd, win32] + bun@1.2.4: + resolution: {integrity: sha512-ZY0EZ/UKqheaLeAtMsfJA6jWoWvV9HAtfFaOJSmS3LrNpFKs1Sg5fZLSsczN1h3a+Dtheo4O3p3ZYWrf40kRGw==} + cpu: [arm64, x64, aarch64] + os: [darwin, linux, win32] hasBin: true bundle-require@5.1.0: @@ -2372,23 +2209,40 @@ packages: peerDependencies: esbuild: '>=0.18' + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001793: - resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + caniuse-lite@1.0.30001705: + resolution: {integrity: sha512-S0uyMMiYvA7CxNgomYBwwwPUnWzFD83f3B1ce5jHUfHTH//QL6hHsreI8RVC5606R4ssqravelYO5TU6t8sEyg==} - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} + chai@5.1.1: + resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + engines: {node: '>=12'} - chardet@2.1.1: - resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} @@ -2398,27 +2252,29 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} - chrome-trace-event@1.0.4: - resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} - engines: {node: '>=6.0'} - - citty@0.2.2: - resolution: {integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==} - - cli-width@4.1.0: - resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} - engines: {node: '>= 12'} + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - clipanion@4.0.0-rc.4: - resolution: {integrity: sha512-CXkMQxU6s9GklO/1f714dkKBMu1lopS1WFF0B8o4AxPykR1hpozxSiUZ5ZUeBjfPgCWqbcNOtZVFhB8Lkfp1+Q==} - peerDependencies: - typanion: '*' + clipboardy@4.0.0: + resolution: {integrity: sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==} + engines: {node: '>=18'} - colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -2427,44 +2283,69 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} engines: {node: ^14.18.0 || >=16.10.0} - content-type@2.0.0: - resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} - engines: {node: '>=18'} + consola@3.4.0: + resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} + engines: {node: ^14.18.0 || >=16.10.0} convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-es@1.2.3: - resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==} + cookie-es@1.2.2: + resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} - crossws@0.3.5: - resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} - crossws@0.4.10: - resolution: {integrity: sha512-pz3oubH/dt12KjqsUB0IuXW4nwRDQ583iDsP4555Cpdqx0NoU7pGlWBcayyFI8f/l/idRpgjMEfwuOxSWJYlIA==} - peerDependencies: - srvx: '>=0.11.5' - peerDependenciesMeta: - srvx: - optional: true + crossws@0.3.1: + resolution: {integrity: sha512-HsZgeVYaG+b5zA+9PbIPGq4+J/CJynJuearykPsXx4V/eMhyQ5EDVg3Ak2FBZtVXCiOLu/U7IiwDHTr9MA+IKw==} + + crossws@0.3.4: + resolution: {integrity: sha512-uj0O1ETYX1Bh6uSgktfPvwDiPYGQ3aI4qVsaC/LWpkIzGj1nUYm5FK3K+t11oOlpN01lGbprFCH4wBlKdJjVgw==} cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + + data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -2472,27 +2353,51 @@ packages: supports-color: optional: true - dedent@1.7.2: - resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: babel-plugin-macros: optional: true - defu@6.1.7: - resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} - destr@2.0.5: - resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + destr@2.0.3: + resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} detect-libc@1.0.3: resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} engines: {node: '>=0.10'} hasBin: true - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} didyoumean@1.2.2: @@ -2501,33 +2406,77 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - electron-to-chromium@1.5.361: - resolution: {integrity: sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==} + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} - emnapi@1.11.3: - resolution: {integrity: sha512-+/ZS90YK/rYfVOHtGLHkGffVsnmD/MAKaBHio+Y4XAtg75RLr4cveV/w0jTkUdLM1CcAlaRgG76mpIemWAlk0A==} - peerDependencies: - node-addon-api: '>= 6.1.0' - peerDependenciesMeta: - node-addon-api: - optional: true + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - enhanced-resolve@5.24.5: - resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} + electron-to-chromium@1.5.119: + resolution: {integrity: sha512-Ku4NMzUjz3e3Vweh7PhApPrZSS4fyiCIbcIrG9eKrriYVLmbMepETR/v6SU7xPm98QTqMSYiCwfO89QNjXLkbQ==} + + electron-to-chromium@1.5.45: + resolution: {integrity: sha512-vOzZS6uZwhhbkZbcRyiy99Wg+pYFV5hk+5YaECvx0+Z31NR3Tt5zS6dze2OepT6PCTzVzT0dIJItti+uAW5zmw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + enhanced-resolve@5.18.1: + resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} + es-abstract@1.23.3: + resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-module-lexer@2.1.0: - resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-iterator-helpers@1.1.0: + resolution: {integrity: sha512-/SurEfycdyssORP/E+bj4sEu1CWw4EmLDsHynHwSXQ7utgbrMRWW195pTrCjFgFCddf/UkYm3oqKPRq5i8bJbw==} + engines: {node: '>= 0.4'} - es-toolkit@1.50.0: - resolution: {integrity: sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==} + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} - esbuild@0.27.7: - resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + + es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.24.0: + resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.25.0: + resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} engines: {node: '>=18'} hasBin: true @@ -2535,18 +2484,118 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-next@15.2.3: + resolution: {integrity: sha512-VDQwbajhNMFmrhLWVyUXCqsGPN+zz5G8Ys/QwFubfsxTIrkqdx3N3x3QPW+pERz8bzGPP0IgEm8cNbZcd8PFRQ==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-import-resolver-typescript@3.6.3: + resolution: {integrity: sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-module-utils@2.12.0: + resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} + 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 + + eslint-plugin-import@2.31.0: + resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jsx-a11y@6.10.1: + resolution: {integrity: sha512-zHByM9WTUMnfsDTafGXRiqxp6lFtNoSOWBY6FonVRn3A+BUwN1L/tdBXT40BcBJi0cZjOGTXZ0eD/rTG9fEJ0g==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + + eslint-plugin-react-hooks@5.0.0: + resolution: {integrity: sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react@7.37.2: + resolution: {integrity: sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@8.3.0: + resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.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-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.22.0: + resolution: {integrity: sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} - estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} @@ -2554,68 +2603,74 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} - engines: {node: '>=12.0.0'} - - fast-content-type-parse@3.0.0: - resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-equals@5.4.0: - resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} - engines: {node: '>=6.0.0'} + fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} - fast-string-truncated-width@3.0.3: - resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - fast-string-width@3.0.2: - resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-stringify@4.0.0: - resolution: {integrity: sha512-lE2DIivBaLysf6hK5WH/VfMgqRbvBVHcpGVVTmA5Zi8oWIjq9YxIt6lYGdUgP1HNSXxTIat7HEIDnrSvXSeKQw==} + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - fast-uri@3.1.2: - resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} - - fast-wrap-ansi@0.2.2: - resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} - - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} + fdir@6.4.3: + resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: picomatch: optional: true + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - find-up-simple@1.0.1: - resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} + find-up-simple@1.0.0: + resolution: {integrity: sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==} engines: {node: '>=18'} - fix-dts-default-cjs-exports@1.0.1: - resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} - fraction.js@5.3.4: - resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} @@ -2630,8 +2685,37 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - get-port-please@3.2.0: - resolution: {integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==} + function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + + 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'} + + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + + get-port-please@3.1.2: + resolution: {integrity: sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.8.1: + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -2641,184 +2725,419 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - globby@16.2.2: - resolution: {integrity: sha512-NLvV9ubZ6NDsJaOpKPy3cQeJpKi9DcWiyCiFUpJPA0YihRqiE6RWaLUmgNNPr8MgPpLZjnBjSmou7uZBRJv9wA==} - engines: {node: '>=20'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globby@14.1.0: + resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==} + engines: {node: '>=18'} + + gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - h3@1.15.11: - resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + h3@1.15.1: + resolution: {integrity: sha512-+ORaOBttdUm1E2Uu/obAyCguiI7MbBvsLTndc3gyK3zU+SYLoZXlyCP9Xgy0gikkGufFLTZXCXD6+4BsufnmHA==} + + has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - hasown@2.0.3: - resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + 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'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} http-shutdown@1.2.2: resolution: {integrity: sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - iconv-lite@0.7.2: - resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} - engines: {node: '>=0.10.0'} + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.3: + resolution: {integrity: sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==} + engines: {node: '>= 4'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + engines: {node: '>= 0.4'} + iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-async-function@2.0.0: + resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + engines: {node: '>= 0.4'} + + is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-core-module@2.16.2: - resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} + is-bun-module@1.2.1: + resolution: {integrity: sha512-AmidtEM6D6NmUiLOvvU7+IePxjEjOzra2h0pSrsfSAcXwl/83zLLXDByafUJy9k/rKK0pvXMLdwKwGHlX2Ke6Q==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.15.0: + resolution: {integrity: sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} + + is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + 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==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-path-inside@4.0.0: - resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} - engines: {node: '>=12'} + is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} - jest-worker@27.5.1: - resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} - engines: {node: '>= 10.13.0'} + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} - jiti@1.21.7: - resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + + is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + + is-weakset@2.0.3: + resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} + engines: {node: '>= 0.4'} + + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + + is64bit@2.0.0: + resolution: {integrity: sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==} + engines: {node: '>=18'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterator.prototype@1.1.3: + resolution: {integrity: sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==} + engines: {node: '>= 0.4'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.6: + resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true - jiti@2.7.0: - resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} - js-yaml@4.3.1: - resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true - json-with-bigint@3.5.8: - resolution: {integrity: sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==} + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - lightningcss-android-arm64@1.33.0: - resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - lightningcss-darwin-arm64@1.33.0: - resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + 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 + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-darwin-arm64@1.29.2: + resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.33.0: - resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + lightningcss-darwin-x64@1.29.2: + resolution: {integrity: sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.33.0: - resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + lightningcss-freebsd-x64@1.29.2: + resolution: {integrity: sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.33.0: - resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + lightningcss-linux-arm-gnueabihf@1.29.2: + resolution: {integrity: sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.33.0: - resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + lightningcss-linux-arm64-gnu@1.29.2: + resolution: {integrity: sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-arm64-musl@1.33.0: - resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + lightningcss-linux-arm64-musl@1.29.2: + resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-x64-gnu@1.33.0: - resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + lightningcss-linux-x64-gnu@1.29.2: + resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-linux-x64-musl@1.33.0: - resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + lightningcss-linux-x64-musl@1.29.2: + resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-win32-arm64-msvc@1.33.0: - resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + lightningcss-win32-arm64-msvc@1.29.2: + resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.33.0: - resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + lightningcss-win32-x64-msvc@1.29.2: + resolution: {integrity: sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.33.0: - resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + lightningcss@1.29.2: + resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==} engines: {node: '>= 12.0.0'} - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} engines: {node: '>=14'} lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - listhen@1.10.1: - resolution: {integrity: sha512-6nt/86SkqUQSLW1ofz8MxC6RhRMqOl3ONISe6qqvJ3xj09aJWQx6DhgSZpugs3PX4PXdOas/WD6A9jx6J2N19A==} + listhen@1.9.0: + resolution: {integrity: sha512-I8oW2+QL5KJo8zXNWX046M134WchxsXC7SawLPvRQpogCbkyQIaFxPE89A2HiwR7vAK2Dm2ERBAmyjTYGYEpBg==} hasBin: true - peerDependencies: - '@parcel/watcher': ^2.5.6 - peerDependenciesMeta: - '@parcel/watcher': - optional: true load-tsconfig@0.2.5: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} - magic-string@1.1.0: - resolution: {integrity: sha512-kS3VHe0nEPST2saQV4Rbkchcd3UBRkVTQHo1D3h/ZTwFDhai/mfKkmtPAtD129EOI7K3HlHIsFOt0WrI2/oU9g==} + lodash.castarray@4.4.0: + resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + loupe@3.1.1: + resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -2827,101 +3146,75 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - micro-memoize@5.1.1: - resolution: {integrity: sha512-QDwluos8YeMijiKxZGwaV4f4tzj0soS6+xcsJhJ3+4wdEIHMyKbIKVUziebOgWX3e6yiijdoaHo+9tyhbnaWXA==} + micro-memoize@4.1.3: + resolution: {integrity: sha512-DzRMi8smUZXT7rCGikRwldEh6eO6qzKiPPopcr1+2EY3AYKpy5fu159PKWwIS9A6IWnrvPKDMcuFtyrroZa8Bw==} + + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} mini-svg-data-uri@1.4.4: resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} hasBin: true - minimizer-webpack-plugin@5.6.1: - resolution: {integrity: sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw==} - engines: {node: '>= 10.13.0'} - peerDependencies: - '@minify-html/node': '*' - '@swc/core': '*' - '@swc/css': '*' - '@swc/html': '*' - clean-css: '*' - cssnano: '*' - csso: '*' - esbuild: '*' - html-minifier-terser: '*' - lightningcss: '*' - postcss: '*' - uglify-js: '*' - webpack: ^5.1.0 - peerDependenciesMeta: - '@minify-html/node': - optional: true - '@swc/core': - optional: true - '@swc/css': - optional: true - '@swc/html': - optional: true - clean-css: - optional: true - cssnano: - optional: true - csso: - optional: true - esbuild: - optional: true - html-minifier-terser: - optional: true - lightningcss: - optional: true - postcss: - optional: true - uglify-js: - optional: true + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - mlly@1.8.2: - resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mlly@1.7.3: + resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==} mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - mute-stream@3.0.0: - resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} - engines: {node: ^20.17.0 || >=22.9.0} - mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@3.3.16: - resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - next@16.2.7: - resolution: {integrity: sha512-eMJxgjRzBaj3olkP4cBamHDXL79A8FC6u1GcsO1D1Tsx8bw/LLXUJCaoajVxtnhD3A1IJqIT8IcRJjgBIPJq4w==} - engines: {node: '>=20.9.0'} + next@15.2.3: + resolution: {integrity: sha512-x6eDkZxk2rPpu46E1ZVUWIBhYCLszmUY6fvHBFcbzJ9dD+qRX6vcHusaqqDlnY+VngKzKbAiG2iRCkPbmi8f7w==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.51.1 + '@playwright/test': ^1.41.2 babel-plugin-react-compiler: '*' react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 @@ -2939,29 +3232,47 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-addon-api@8.7.0: - resolution: {integrity: sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==} + node-addon-api@8.2.2: + resolution: {integrity: sha512-9emqXAKhVoNrQ792nLI/wpzPpJ/bj/YXxW0CvAau1+RdGBcCRF1Dmz7719zgVsQNrzHl9Tzn3ImZ4qWFarWL0A==} engines: {node: ^18 || ^20 || >= 21} - node-forge@1.4.0: - resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} + node-addon-api@8.3.0: + resolution: {integrity: sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==} + engines: {node: ^18 || ^20 || >= 21} + + node-forge@1.3.1: + resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} + node-gyp-build@4.8.2: + resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} + hasBin: true + node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-mock-http@1.0.4: - resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} + node-mock-http@1.0.0: + resolution: {integrity: sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ==} - node-releases@2.0.46: - resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} - engines: {node: '>=18'} + node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2970,77 +3281,157 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} - obug@2.1.4: - resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} - engines: {node: '>=12.20.0'} + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} + + object.entries@1.1.8: + resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.0: + resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} + engines: {node: '>= 0.4'} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} package-up@5.0.0: resolution: {integrity: sha512-MQEgDUvXCa3sGvqHg3pzHO8e9gqTCMPVrWUko3vPQGntwegmFo52mZb2abIVTjFnUcW0BcPz0D93jV5Cas1DWA==} engines: {node: '>=18'} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + 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'} + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@6.0.0: + resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} + engines: {node: '>=18'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.2: - resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} - - picomatch@4.0.5: - resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + pkg-types@1.3.0: + resolution: {integrity: sha512-kS7yWjVFCkIw9hqdJBoMxDdzEngmkr5FXeWZZfQ6GoYacjVnsW6l2CcYW/0ThD0vF4LPJgVYnrg4d0uuhwYQbg==} - playwright-core@1.62.1: - resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==} - engines: {node: '>=20'} + playwright-core@1.51.0: + resolution: {integrity: sha512-x47yPE3Zwhlil7wlNU/iktF7t2r/URR3VLbH6EknJd/04Qc/PSJ0EY3CMXipmglLG+zyRxW6HNo2EGbKLHPWMg==} + engines: {node: '>=18'} hasBin: true - playwright@1.62.1: - resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==} - engines: {node: '>=20'} + playwright@1.51.0: + resolution: {integrity: sha512-442pTfGM0xxfCYxuBa/Pu6B2OqxqqaYq39JS8QDMGThUvIOCd6s0ANDog3uwA0cHavVlnTQzGCN7Id2YekDSXA==} + engines: {node: '>=18'} hasBin: true + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} peerDependencies: postcss: ^8.0.0 - postcss-import@16.1.1: - resolution: {integrity: sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ==} + postcss-import@16.1.0: + resolution: {integrity: sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg==} engines: {node: '>=18.0.0'} peerDependencies: postcss: ^8.0.0 - postcss-js@4.1.0: - resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + postcss-js@4.0.1: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} engines: {node: ^12 || ^14 || >= 16} peerDependencies: postcss: ^8.4.21 + 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 + postcss-load-config@6.0.1: resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} engines: {node: '>= 18'} @@ -3059,12 +3450,24 @@ packages: yaml: optional: true + postcss-media-minmax@5.0.0: + resolution: {integrity: sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + postcss: ^8.1.0 + postcss-nested@6.2.0: resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 + postcss-nesting@13.0.1: + resolution: {integrity: sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + postcss-selector-parser@6.0.10: resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} engines: {node: '>=4'} @@ -3073,8 +3476,8 @@ packages: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} - postcss-selector-parser@7.1.4: - resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==} + postcss-selector-parser@7.1.0: + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} engines: {node: '>=4'} postcss-value-parser@4.2.0: @@ -3084,54 +3487,66 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.16: - resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} + postcss@8.4.41: + resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.25: - resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==} + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} - prettier-plugin-embed@0.5.1: - resolution: {integrity: sha512-2Ege8gIlLNTvHElUeU5XcFsD7/dbDXkQA6H9TczHSJAGxB58nNjjifk/dlRMS5E29eqQx/z+ToA4ZVMWzjME/A==} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} - prettier-plugin-organize-imports@4.3.0: - resolution: {integrity: sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw==} + prettier-plugin-embed@0.5.0: + resolution: {integrity: sha512-A5nzX8U9x+FJdpOKrDrH9eq86xHZNiGguWpphS6chTME0OK1bDgH1X+WLtZq7qV3kUEMkL/dHkr6C1NLdUA7RQ==} + + prettier-plugin-organize-imports@4.0.0: + resolution: {integrity: sha512-vnKSdgv9aOlqKeEFGhf9SCBsTyzDSyScy1k7E0R1Uo4L0cTcOV7c1XQaT7jfXIOc/p08WLBfN2QUQA9zDSZMxA==} peerDependencies: + '@vue/language-plugin-pug': ^2.0.24 prettier: '>=2.0' typescript: '>=2.9' - vue-tsc: ^2.1.0 || 3 + vue-tsc: ^2.0.24 peerDependenciesMeta: + '@vue/language-plugin-pug': + optional: true vue-tsc: optional: true - prettier@3.9.6: - resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} + prettier@3.5.0: + resolution: {integrity: sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==} engines: {node: '>=14'} hasBin: true + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} - react-dom@19.2.6: - resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} peerDependencies: - react: ^19.2.6 + react: ^19.0.0 - react-dom@19.2.7: - resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} - peerDependencies: - react: ^19.2.7 + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react@19.2.6: - resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} - react@19.2.7: - resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -3141,59 +3556,93 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + readdirp@4.1.1: + resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==} engines: {node: '>= 14.18.0'} - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} + reflect.getprototypeof@1.0.6: + resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} + engines: {node: '>= 0.4'} + + 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'} - resolve@1.22.12: - resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} - engines: {node: '>= 0.4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rolldown@1.2.1: - resolution: {integrity: sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - rollup@4.60.4: - resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} + rollup@4.34.9: + resolution: {integrity: sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + engines: {node: '>=0.4'} - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} - schema-utils@4.3.3: - resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} - engines: {node: '>= 10.13.0'} + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} - semver@7.8.5: - resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} hasBin: true - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -3201,10 +3650,17 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + slash@5.1.0: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} + source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -3216,18 +3672,69 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - source-map@0.7.6: - resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} - engines: {node: '>= 12'} + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@4.1.0: - resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} - std-env@4.2.0: - resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + + string.prototype.matchall@4.0.11: + resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} @@ -3242,34 +3749,34 @@ packages: babel-plugin-macros: optional: true - sucrase@3.35.1: - resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tagged-tag@1.0.0: - resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} - engines: {node: '>=20'} + system-architecture@0.1.0: + resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==} + engines: {node: '>=18'} - tailwindcss@3.4.19: - resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + tailwindcss@3.4.14: + resolution: {integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==} engines: {node: '>=14.0.0'} hasBin: true - tapable@2.3.3: - resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - terser@5.48.0: - resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==} + terser@5.31.6: + resolution: {integrity: sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==} engines: {node: '>=10'} hasBin: true @@ -3286,33 +3793,32 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyclip@0.1.15: - resolution: {integrity: sha512-uo33abH+Ays0xYaDysoBt494Hb3hsEczMpcC0MwFl773pazORx4fmvKhclhR1wonUbB6vvpRsvVMwnhfqeMc+A==} - engines: {node: ^16.14.0 || >= 17.3.0} - tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyexec@1.1.2: - resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} - engines: {node: '>=18'} - - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + tinyglobby@0.2.12: + resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} engines: {node: '>=12.0.0'} - tinyglobby@0.2.17: - resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} - engines: {node: '>=12.0.0'} + tinypool@1.0.0: + resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==} + engines: {node: ^18.0.0 || >=20.0.0} - tinyrainbow@3.1.0: - resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.0: + resolution: {integrity: sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==} engines: {node: '>=14.0.0'} to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -3333,17 +3839,26 @@ packages: tree-sitter: optional: true - tree-sitter@0.25.1: - resolution: {integrity: sha512-mrcEdkYtHfrK1A6fs3O6FxkBo0Qig5XUXqHhxUOQu0bmPo00QF4XaSx4edpazdHwxnSCjlGKGgIqWdaN4dvTLA==} + tree-sitter@0.22.4: + resolution: {integrity: sha512-usbHZP9/oxNsUY65MQUsduGRqDHQOou1cagUSwjhoSYAmSahjQDAVsh9s+SlZkn8X8+O1FULRGwHu7AFP3kjzg==} + + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - tsup@8.5.1: - resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + tslib@2.8.0: + resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==} + + tsup@8.4.0: + resolution: {integrity: sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -3361,90 +3876,175 @@ packages: typescript: optional: true - turbo@2.10.8: - resolution: {integrity: sha512-9+8YX5QOkGXzZxcIykTHgaooRHGMWO+jfdyRK0o+rN0U7hBIig2MrJ8r/aNzIPDPhdA73SGb0O+tIztaModTMg==} + tsx@4.19.1: + resolution: {integrity: sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==} + engines: {node: '>=18.0.0'} hasBin: true - typanion@3.14.0: - resolution: {integrity: sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==} + turbo-darwin-64@2.4.4: + resolution: {integrity: sha512-5kPvRkLAfmWI0MH96D+/THnDMGXlFNmjeqNRj5grLKiry+M9pKj3pRuScddAXPdlxjO5Ptz06UNaOQrrYGTx1g==} + cpu: [x64] + os: [darwin] - type-fest@5.6.0: - resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==} - engines: {node: '>=20'} + turbo-darwin-arm64@2.4.4: + resolution: {integrity: sha512-/gtHPqbGQXDFhrmy+Q/MFW2HUTUlThJ97WLLSe4bxkDrKHecDYhAjbZ4rN3MM93RV9STQb3Tqy4pZBtsd4DfCw==} + cpu: [arm64] + os: [darwin] - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + turbo-linux-64@2.4.4: + resolution: {integrity: sha512-SR0gri4k0bda56hw5u9VgDXLKb1Q+jrw4lM7WAhnNdXvVoep4d6LmnzgMHQQR12Wxl3KyWPbkz9d1whL6NTm2Q==} + cpu: [x64] + os: [linux] + + turbo-linux-arm64@2.4.4: + resolution: {integrity: sha512-COXXwzRd3vslQIfJhXUklgEqlwq35uFUZ7hnN+AUyXx7hUOLIiD5NblL+ETrHnhY4TzWszrbwUMfe2BYWtaPQg==} + cpu: [arm64] + os: [linux] + + turbo-windows-64@2.4.4: + resolution: {integrity: sha512-PV9rYNouGz4Ff3fd6sIfQy5L7HT9a4fcZoEv8PKRavU9O75G7PoDtm8scpHU10QnK0QQNLbE9qNxOAeRvF0fJg==} + cpu: [x64] + os: [win32] + + turbo-windows-arm64@2.4.4: + resolution: {integrity: sha512-403sqp9t5sx6YGEC32IfZTVWkRAixOQomGYB8kEc6ZD+//LirSxzeCHCnM8EmSXw7l57U1G+Fb0kxgTcKPU/Lg==} + cpu: [arm64] + os: [win32] + + turbo@2.4.4: + resolution: {integrity: sha512-N9FDOVaY3yz0YCOhYIgOGYad7+m2ptvinXygw27WPLQvcZDl3+0Sa77KGVlLSiuPDChOUEnTKE9VJwLSi9BPGQ==} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@4.36.0: + resolution: {integrity: sha512-3T/PUdKTCnkUmhQU6FFJEHsLwadsRegktX3TNHk+2JJB9HlA8gp1/VXblXVDI93kSnXF2rdPx0GMbHtJIV2LPg==} + engines: {node: '>=16'} + + typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.6: + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + engines: {node: '>= 0.4'} + + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} engines: {node: '>=14.17'} hasBin: true - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true - ufo@1.6.4: - resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + ufo@1.5.4: + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + + unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} - unicorn-magic@0.4.0: - resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==} - engines: {node: '>=20'} - - universal-user-agent@7.0.3: - resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} - - untun@0.2.2: - resolution: {integrity: sha512-+NnOJcSiEtYsVgJmXUzQbJeRAFXJC4yPJYuh6kF9B0Rm6zunXcs/3GZOTllyocSbUDIxD6Bj7e/4ATw7sph1Sw==} + untun@0.1.3: + resolution: {integrity: sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ==} hasBin: true - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' - uqr@0.1.3: - resolution: {integrity: sha512-0rjE8iEJe4YmT9TOhwsZtqCMRLc5DXZUI2UEYUUg63ikBkqqE5EYWaI0etFe/5KUcmcYwLih2RND1kq+hrUJXA==} + uqr@0.1.2: + resolution: {integrity: sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - vite@8.2.0: - resolution: {integrity: sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==} - engines: {node: ^20.19.0 || >=22.12.0} + vite-node@2.0.5: + resolution: {integrity: sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.0: + resolution: {integrity: sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.4.0 - esbuild: ^0.27.0 || ^0.28.0 + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vite@6.0.0: + resolution: {integrity: sha512-Q2+5yQV79EdnpbNxjD3/QHVMCBaQ3Kpd4/uL51UGuh38bIIM+s4o3FqyCzRvTRwFb+cWIUeZvaWwS9y2LD2qeQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 jiti: '>=1.21.0' - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 peerDependenciesMeta: '@types/node': optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true jiti: optional: true less: optional: true + lightningcss: + optional: true sass: optional: true sass-embedded: @@ -3460,39 +4060,23 @@ packages: yaml: optional: true - vitest@4.1.10: - resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + vitest@2.0.5: + resolution: {integrity: sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.10 - '@vitest/browser-preview': 4.1.10 - '@vitest/browser-webdriverio': 4.1.10 - '@vitest/coverage-istanbul': 4.1.10 - '@vitest/coverage-v8': 4.1.10 - '@vitest/ui': 4.1.10 + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.0.5 + '@vitest/ui': 2.0.5 happy-dom: '*' jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true - '@opentelemetry/api': - optional: true '@types/node': optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/coverage-istanbul': - optional: true - '@vitest/coverage-v8': + '@vitest/browser': optional: true '@vitest/ui': optional: true @@ -3501,704 +4085,728 @@ packages: jsdom: optional: true - watchpack@2.5.2: - resolution: {integrity: sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==} - engines: {node: '>=10.13.0'} + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - webpack-sources@3.5.1: - resolution: {integrity: sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==} - engines: {node: '>=10.13.0'} + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - webpack@5.109.2: - resolution: {integrity: sha512-U9/cvLzxObKNEZ9+TtdqrHM5/9z3lgl2c+c4BzbqGxFQvQvBAq87yql5A8pQ+rrMbS496MZJeF5enVBndIy2hw==} - engines: {node: '>=10.13.0'} + which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + + which-builtin-type@1.1.4: + resolution: {integrity: sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true - yaml@2.9.0: - resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} - engines: {node: '>= 14.6'} + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.6.0: + resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} + engines: {node: '>= 14'} hasBin: true + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + snapshots: '@alloc/quick-lru@5.2.0': {} - '@emnapi/core@1.10.0': + '@ampproject/remapping@2.3.0': dependencies: - '@emnapi/wasi-threads': 1.2.1 - tslib: 2.8.1 - optional: true + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 - '@emnapi/core@1.11.3': + '@babel/code-frame@7.26.2': dependencies: - '@emnapi/wasi-threads': 1.2.3 - tslib: 2.8.1 + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 - '@emnapi/core@2.0.0-alpha.3': + '@babel/compat-data@7.26.3': {} + + '@babel/core@7.26.0': dependencies: - '@emnapi/wasi-threads': 2.0.1 - tslib: 2.8.1 - optional: true + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.3 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.3 + '@babel/template': 7.25.9 + '@babel/traverse': 7.26.4 + '@babel/types': 7.26.3 + convert-source-map: 2.0.0 + debug: 4.4.0 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color - '@emnapi/runtime@1.10.0': + '@babel/generator@7.26.3': dependencies: - tslib: 2.8.1 - optional: true + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 - '@emnapi/runtime@1.11.3': + '@babel/helper-compilation-targets@7.25.9': dependencies: - tslib: 2.8.1 + '@babel/compat-data': 7.26.3 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.2 + lru-cache: 5.1.1 + semver: 6.3.1 - '@emnapi/runtime@2.0.0-alpha.3': + '@babel/helper-module-imports@7.25.9': dependencies: - tslib: 2.8.1 - optional: true + '@babel/traverse': 7.26.4 + '@babel/types': 7.26.3 + transitivePeerDependencies: + - supports-color - '@emnapi/wasi-threads@1.2.1': + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': dependencies: - tslib: 2.8.1 - optional: true + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.26.4 + transitivePeerDependencies: + - supports-color - '@emnapi/wasi-threads@1.2.3': + '@babel/helper-plugin-utils@7.25.9': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helpers@7.26.0': dependencies: - tslib: 2.8.1 + '@babel/template': 7.25.9 + '@babel/types': 7.26.3 - '@emnapi/wasi-threads@2.0.1': + '@babel/parser@7.26.3': dependencies: - tslib: 2.8.1 + '@babel/types': 7.26.3 + + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/template@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 + + '@babel/traverse@7.26.4': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.3 + '@babel/parser': 7.26.3 + '@babel/template': 7.25.9 + '@babel/types': 7.26.3 + debug: 4.4.0 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.26.3': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@csstools/color-helpers@5.0.2': {} + + '@csstools/css-calc@2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-color-parser@3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/color-helpers': 5.0.2 + '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-tokenizer@3.0.3': {} + + '@csstools/postcss-cascade-layers@5.0.1(postcss@8.5.3)': + dependencies: + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 + + '@csstools/postcss-color-mix-function@3.0.8(postcss@8.5.3)': + dependencies: + '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3) + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 + + '@csstools/postcss-oklab-function@4.0.8(postcss@8.5.3)': + dependencies: + '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.5.3) + '@csstools/utilities': 2.0.0(postcss@8.5.3) + postcss: 8.5.3 + + '@csstools/postcss-progressive-custom-properties@4.0.0(postcss@8.5.3)': + dependencies: + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + + '@csstools/selector-resolve-nested@3.0.0(postcss-selector-parser@7.1.0)': + dependencies: + postcss-selector-parser: 7.1.0 + + '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.0)': + dependencies: + postcss-selector-parser: 7.1.0 + + '@csstools/utilities@2.0.0(postcss@8.5.3)': + dependencies: + postcss: 8.5.3 + + '@emnapi/runtime@1.3.1': + dependencies: + tslib: 2.8.0 optional: true - '@esbuild/aix-ppc64@0.27.7': + '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/android-arm64@0.27.7': + '@esbuild/aix-ppc64@0.23.1': optional: true - '@esbuild/android-arm@0.27.7': + '@esbuild/aix-ppc64@0.24.0': optional: true - '@esbuild/android-x64@0.27.7': + '@esbuild/aix-ppc64@0.25.0': optional: true - '@esbuild/darwin-arm64@0.27.7': + '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/darwin-x64@0.27.7': + '@esbuild/android-arm64@0.23.1': optional: true - '@esbuild/freebsd-arm64@0.27.7': + '@esbuild/android-arm64@0.24.0': optional: true - '@esbuild/freebsd-x64@0.27.7': + '@esbuild/android-arm64@0.25.0': optional: true - '@esbuild/linux-arm64@0.27.7': + '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/linux-arm@0.27.7': + '@esbuild/android-arm@0.23.1': optional: true - '@esbuild/linux-ia32@0.27.7': + '@esbuild/android-arm@0.24.0': optional: true - '@esbuild/linux-loong64@0.27.7': + '@esbuild/android-arm@0.25.0': optional: true - '@esbuild/linux-mips64el@0.27.7': + '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/linux-ppc64@0.27.7': + '@esbuild/android-x64@0.23.1': optional: true - '@esbuild/linux-riscv64@0.27.7': + '@esbuild/android-x64@0.24.0': optional: true - '@esbuild/linux-s390x@0.27.7': + '@esbuild/android-x64@0.25.0': optional: true - '@esbuild/linux-x64@0.27.7': + '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/netbsd-arm64@0.27.7': + '@esbuild/darwin-arm64@0.23.1': optional: true - '@esbuild/netbsd-x64@0.27.7': + '@esbuild/darwin-arm64@0.24.0': optional: true - '@esbuild/openbsd-arm64@0.27.7': + '@esbuild/darwin-arm64@0.25.0': optional: true - '@esbuild/openbsd-x64@0.27.7': + '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/openharmony-arm64@0.27.7': + '@esbuild/darwin-x64@0.23.1': optional: true - '@esbuild/sunos-x64@0.27.7': + '@esbuild/darwin-x64@0.24.0': optional: true - '@esbuild/win32-arm64@0.27.7': + '@esbuild/darwin-x64@0.25.0': optional: true - '@esbuild/win32-ia32@0.27.7': + '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/win32-x64@0.27.7': + '@esbuild/freebsd-arm64@0.23.1': optional: true - '@img/colour@1.1.0': + '@esbuild/freebsd-arm64@0.24.0': optional: true - '@img/sharp-darwin-arm64@0.34.5': + '@esbuild/freebsd-arm64@0.25.0': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.23.1': + optional: true + + '@esbuild/freebsd-x64@0.24.0': + optional: true + + '@esbuild/freebsd-x64@0.25.0': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.23.1': + optional: true + + '@esbuild/linux-arm64@0.24.0': + optional: true + + '@esbuild/linux-arm64@0.25.0': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.23.1': + optional: true + + '@esbuild/linux-arm@0.24.0': + optional: true + + '@esbuild/linux-arm@0.25.0': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.23.1': + optional: true + + '@esbuild/linux-ia32@0.24.0': + optional: true + + '@esbuild/linux-ia32@0.25.0': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.23.1': + optional: true + + '@esbuild/linux-loong64@0.24.0': + optional: true + + '@esbuild/linux-loong64@0.25.0': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.23.1': + optional: true + + '@esbuild/linux-mips64el@0.24.0': + optional: true + + '@esbuild/linux-mips64el@0.25.0': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.23.1': + optional: true + + '@esbuild/linux-ppc64@0.24.0': + optional: true + + '@esbuild/linux-ppc64@0.25.0': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.23.1': + optional: true + + '@esbuild/linux-riscv64@0.24.0': + optional: true + + '@esbuild/linux-riscv64@0.25.0': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.23.1': + optional: true + + '@esbuild/linux-s390x@0.24.0': + optional: true + + '@esbuild/linux-s390x@0.25.0': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.23.1': + optional: true + + '@esbuild/linux-x64@0.24.0': + optional: true + + '@esbuild/linux-x64@0.25.0': + optional: true + + '@esbuild/netbsd-arm64@0.25.0': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/netbsd-x64@0.24.0': + optional: true + + '@esbuild/netbsd-x64@0.25.0': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.24.0': + optional: true + + '@esbuild/openbsd-arm64@0.25.0': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-x64@0.24.0': + optional: true + + '@esbuild/openbsd-x64@0.25.0': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.23.1': + optional: true + + '@esbuild/sunos-x64@0.24.0': + optional: true + + '@esbuild/sunos-x64@0.25.0': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.23.1': + optional: true + + '@esbuild/win32-arm64@0.24.0': + optional: true + + '@esbuild/win32-arm64@0.25.0': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.23.1': + optional: true + + '@esbuild/win32-ia32@0.24.0': + optional: true + + '@esbuild/win32-ia32@0.25.0': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.23.1': + optional: true + + '@esbuild/win32-x64@0.24.0': + optional: true + + '@esbuild/win32-x64@0.25.0': + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@9.22.0(jiti@2.4.2))': + dependencies: + eslint: 9.22.0(jiti@2.4.2) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.19.2': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.1.0': {} + + '@eslint/core@0.12.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.0': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + 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@9.22.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.2.7': + dependencies: + '@eslint/core': 0.12.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.2': {} + + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-arm64': 1.0.4 optional: true - '@img/sharp-darwin-x64@0.34.5': + '@img/sharp-darwin-x64@0.33.5': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 optional: true - '@img/sharp-libvips-darwin-arm64@1.2.4': + '@img/sharp-libvips-darwin-arm64@1.0.4': optional: true - '@img/sharp-libvips-darwin-x64@1.2.4': + '@img/sharp-libvips-darwin-x64@1.0.4': optional: true - '@img/sharp-libvips-linux-arm64@1.2.4': + '@img/sharp-libvips-linux-arm64@1.0.4': optional: true - '@img/sharp-libvips-linux-arm@1.2.4': + '@img/sharp-libvips-linux-arm@1.0.5': optional: true - '@img/sharp-libvips-linux-ppc64@1.2.4': + '@img/sharp-libvips-linux-s390x@1.0.4': optional: true - '@img/sharp-libvips-linux-riscv64@1.2.4': + '@img/sharp-libvips-linux-x64@1.0.4': optional: true - '@img/sharp-libvips-linux-s390x@1.2.4': + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': optional: true - '@img/sharp-libvips-linux-x64@1.2.4': + '@img/sharp-libvips-linuxmusl-x64@1.0.4': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - optional: true - - '@img/sharp-linux-arm64@0.34.5': + '@img/sharp-linux-arm64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.0.4 optional: true - '@img/sharp-linux-arm@0.34.5': + '@img/sharp-linux-arm@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.0.5 optional: true - '@img/sharp-linux-ppc64@0.34.5': + '@img/sharp-linux-s390x@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 optional: true - '@img/sharp-linux-riscv64@0.34.5': + '@img/sharp-linux-x64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.0.4 optional: true - '@img/sharp-linux-s390x@0.34.5': + '@img/sharp-linuxmusl-arm64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 optional: true - '@img/sharp-linux-x64@0.34.5': + '@img/sharp-linuxmusl-x64@0.33.5': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 optional: true - '@img/sharp-linuxmusl-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - optional: true - - '@img/sharp-linuxmusl-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - optional: true - - '@img/sharp-wasm32@0.34.5': + '@img/sharp-wasm32@0.33.5': dependencies: - '@emnapi/runtime': 1.11.3 + '@emnapi/runtime': 1.3.1 optional: true - '@img/sharp-win32-arm64@0.34.5': + '@img/sharp-win32-ia32@0.33.5': optional: true - '@img/sharp-win32-ia32@0.34.5': + '@img/sharp-win32-x64@0.33.5': optional: true - '@img/sharp-win32-x64@0.34.5': - optional: true - - '@inquirer/ansi@2.0.7': {} - - '@inquirer/checkbox@5.2.1(@types/node@25.9.1)': + '@isaacs/cliui@8.0.2': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@25.9.1) - optionalDependencies: - '@types/node': 25.9.1 + 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 - '@inquirer/confirm@6.1.1(@types/node@25.9.1)': + '@jridgewell/gen-mapping@0.3.5': dependencies: - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/type': 4.0.7(@types/node@25.9.1) - optionalDependencies: - '@types/node': 25.9.1 - - '@inquirer/core@11.2.1(@types/node@25.9.1)': - dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@25.9.1) - cli-width: 4.1.0 - fast-wrap-ansi: 0.2.2 - mute-stream: 3.0.0 - signal-exit: 4.1.0 - optionalDependencies: - '@types/node': 25.9.1 - - '@inquirer/editor@5.2.2(@types/node@25.9.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/external-editor': 3.0.3(@types/node@25.9.1) - '@inquirer/type': 4.0.7(@types/node@25.9.1) - optionalDependencies: - '@types/node': 25.9.1 - - '@inquirer/expand@5.1.1(@types/node@25.9.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/type': 4.0.7(@types/node@25.9.1) - optionalDependencies: - '@types/node': 25.9.1 - - '@inquirer/external-editor@3.0.3(@types/node@25.9.1)': - dependencies: - chardet: 2.1.1 - iconv-lite: 0.7.2 - optionalDependencies: - '@types/node': 25.9.1 - - '@inquirer/figures@2.0.7': {} - - '@inquirer/input@5.1.2(@types/node@25.9.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/type': 4.0.7(@types/node@25.9.1) - optionalDependencies: - '@types/node': 25.9.1 - - '@inquirer/number@4.1.1(@types/node@25.9.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/type': 4.0.7(@types/node@25.9.1) - optionalDependencies: - '@types/node': 25.9.1 - - '@inquirer/password@5.1.1(@types/node@25.9.1)': - dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/type': 4.0.7(@types/node@25.9.1) - optionalDependencies: - '@types/node': 25.9.1 - - '@inquirer/prompts@8.5.2(@types/node@25.9.1)': - dependencies: - '@inquirer/checkbox': 5.2.1(@types/node@25.9.1) - '@inquirer/confirm': 6.1.1(@types/node@25.9.1) - '@inquirer/editor': 5.2.2(@types/node@25.9.1) - '@inquirer/expand': 5.1.1(@types/node@25.9.1) - '@inquirer/input': 5.1.2(@types/node@25.9.1) - '@inquirer/number': 4.1.1(@types/node@25.9.1) - '@inquirer/password': 5.1.1(@types/node@25.9.1) - '@inquirer/rawlist': 5.3.1(@types/node@25.9.1) - '@inquirer/search': 4.2.1(@types/node@25.9.1) - '@inquirer/select': 5.2.1(@types/node@25.9.1) - optionalDependencies: - '@types/node': 25.9.1 - - '@inquirer/rawlist@5.3.1(@types/node@25.9.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/type': 4.0.7(@types/node@25.9.1) - optionalDependencies: - '@types/node': 25.9.1 - - '@inquirer/search@4.2.1(@types/node@25.9.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@25.9.1) - optionalDependencies: - '@types/node': 25.9.1 - - '@inquirer/select@5.2.1(@types/node@25.9.1)': - dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@25.9.1) - optionalDependencies: - '@types/node': 25.9.1 - - '@inquirer/type@4.0.7(@types/node@25.9.1)': - optionalDependencies: - '@types/node': 25.9.1 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/source-map@0.3.11': + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/source-map@0.3.6': dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + optional: true - '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.5.0': {} - '@jridgewell/trace-mapping@0.3.31': + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.5.0 - '@napi-rs/cli@3.7.4(patch_hash=9912bf0a9c2cef8329d11c41fbce4d33ce2bdcf660a630b258c962c0a3c44bed)(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@25.9.1)(node-addon-api@8.7.0)': + '@napi-rs/cli@2.18.4': {} + + '@next/env@15.2.3': {} + + '@next/eslint-plugin-next@15.2.3': dependencies: - '@inquirer/prompts': 8.5.2(@types/node@25.9.1) - '@napi-rs/cross-toolchain': 1.0.3(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - '@napi-rs/wasm-tools': 1.0.1(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - '@octokit/rest': 22.0.1 - clipanion: 4.0.0-rc.4(typanion@3.14.0) - colorette: 2.0.20 - emnapi: 1.11.3(node-addon-api@8.7.0) - es-toolkit: 1.50.0 - js-yaml: 4.3.1 - obug: 2.1.4 - semver: 7.8.5 - typanion: 3.14.0 - optionalDependencies: - '@emnapi/runtime': 1.11.3 - transitivePeerDependencies: - - '@emnapi/core' - - '@napi-rs/cross-toolchain-arm64-target-aarch64' - - '@napi-rs/cross-toolchain-arm64-target-armv7' - - '@napi-rs/cross-toolchain-arm64-target-ppc64le' - - '@napi-rs/cross-toolchain-arm64-target-s390x' - - '@napi-rs/cross-toolchain-arm64-target-x86_64' - - '@napi-rs/cross-toolchain-x64-target-aarch64' - - '@napi-rs/cross-toolchain-x64-target-armv7' - - '@napi-rs/cross-toolchain-x64-target-ppc64le' - - '@napi-rs/cross-toolchain-x64-target-s390x' - - '@napi-rs/cross-toolchain-x64-target-x86_64' - - '@types/node' - - node-addon-api - - supports-color + fast-glob: 3.3.1 - '@napi-rs/cross-toolchain@1.0.3(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': - dependencies: - '@napi-rs/lzma': 1.4.5(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - '@napi-rs/tar': 1.1.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - debug: 4.4.3 - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - - supports-color - - '@napi-rs/lzma-android-arm-eabi@1.4.5': + '@next/swc-darwin-arm64@15.2.3': optional: true - '@napi-rs/lzma-android-arm64@1.4.5': + '@next/swc-darwin-x64@15.2.3': optional: true - '@napi-rs/lzma-darwin-arm64@1.4.5': + '@next/swc-linux-arm64-gnu@15.2.3': optional: true - '@napi-rs/lzma-darwin-x64@1.4.5': + '@next/swc-linux-arm64-musl@15.2.3': optional: true - '@napi-rs/lzma-freebsd-x64@1.4.5': + '@next/swc-linux-x64-gnu@15.2.3': optional: true - '@napi-rs/lzma-linux-arm-gnueabihf@1.4.5': + '@next/swc-linux-x64-musl@15.2.3': optional: true - '@napi-rs/lzma-linux-arm64-gnu@1.4.5': + '@next/swc-win32-arm64-msvc@15.2.3': optional: true - '@napi-rs/lzma-linux-arm64-musl@1.4.5': - optional: true - - '@napi-rs/lzma-linux-ppc64-gnu@1.4.5': - optional: true - - '@napi-rs/lzma-linux-riscv64-gnu@1.4.5': - optional: true - - '@napi-rs/lzma-linux-s390x-gnu@1.4.5': - optional: true - - '@napi-rs/lzma-linux-x64-gnu@1.4.5': - optional: true - - '@napi-rs/lzma-linux-x64-musl@1.4.5': - optional: true - - '@napi-rs/lzma-wasm32-wasi@1.4.5(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': - dependencies: - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - optional: true - - '@napi-rs/lzma-win32-arm64-msvc@1.4.5': - optional: true - - '@napi-rs/lzma-win32-ia32-msvc@1.4.5': - optional: true - - '@napi-rs/lzma-win32-x64-msvc@1.4.5': - optional: true - - '@napi-rs/lzma@1.4.5(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': - optionalDependencies: - '@napi-rs/lzma-android-arm-eabi': 1.4.5 - '@napi-rs/lzma-android-arm64': 1.4.5 - '@napi-rs/lzma-darwin-arm64': 1.4.5 - '@napi-rs/lzma-darwin-x64': 1.4.5 - '@napi-rs/lzma-freebsd-x64': 1.4.5 - '@napi-rs/lzma-linux-arm-gnueabihf': 1.4.5 - '@napi-rs/lzma-linux-arm64-gnu': 1.4.5 - '@napi-rs/lzma-linux-arm64-musl': 1.4.5 - '@napi-rs/lzma-linux-ppc64-gnu': 1.4.5 - '@napi-rs/lzma-linux-riscv64-gnu': 1.4.5 - '@napi-rs/lzma-linux-s390x-gnu': 1.4.5 - '@napi-rs/lzma-linux-x64-gnu': 1.4.5 - '@napi-rs/lzma-linux-x64-musl': 1.4.5 - '@napi-rs/lzma-wasm32-wasi': 1.4.5(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - '@napi-rs/lzma-win32-arm64-msvc': 1.4.5 - '@napi-rs/lzma-win32-ia32-msvc': 1.4.5 - '@napi-rs/lzma-win32-x64-msvc': 1.4.5 - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - - '@napi-rs/tar-android-arm-eabi@1.1.0': - optional: true - - '@napi-rs/tar-android-arm64@1.1.0': - optional: true - - '@napi-rs/tar-darwin-arm64@1.1.0': - optional: true - - '@napi-rs/tar-darwin-x64@1.1.0': - optional: true - - '@napi-rs/tar-freebsd-x64@1.1.0': - optional: true - - '@napi-rs/tar-linux-arm-gnueabihf@1.1.0': - optional: true - - '@napi-rs/tar-linux-arm64-gnu@1.1.0': - optional: true - - '@napi-rs/tar-linux-arm64-musl@1.1.0': - optional: true - - '@napi-rs/tar-linux-ppc64-gnu@1.1.0': - optional: true - - '@napi-rs/tar-linux-s390x-gnu@1.1.0': - optional: true - - '@napi-rs/tar-linux-x64-gnu@1.1.0': - optional: true - - '@napi-rs/tar-linux-x64-musl@1.1.0': - optional: true - - '@napi-rs/tar-wasm32-wasi@1.1.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': - dependencies: - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - optional: true - - '@napi-rs/tar-win32-arm64-msvc@1.1.0': - optional: true - - '@napi-rs/tar-win32-ia32-msvc@1.1.0': - optional: true - - '@napi-rs/tar-win32-x64-msvc@1.1.0': - optional: true - - '@napi-rs/tar@1.1.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': - optionalDependencies: - '@napi-rs/tar-android-arm-eabi': 1.1.0 - '@napi-rs/tar-android-arm64': 1.1.0 - '@napi-rs/tar-darwin-arm64': 1.1.0 - '@napi-rs/tar-darwin-x64': 1.1.0 - '@napi-rs/tar-freebsd-x64': 1.1.0 - '@napi-rs/tar-linux-arm-gnueabihf': 1.1.0 - '@napi-rs/tar-linux-arm64-gnu': 1.1.0 - '@napi-rs/tar-linux-arm64-musl': 1.1.0 - '@napi-rs/tar-linux-ppc64-gnu': 1.1.0 - '@napi-rs/tar-linux-s390x-gnu': 1.1.0 - '@napi-rs/tar-linux-x64-gnu': 1.1.0 - '@napi-rs/tar-linux-x64-musl': 1.1.0 - '@napi-rs/tar-wasm32-wasi': 1.1.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - '@napi-rs/tar-win32-arm64-msvc': 1.1.0 - '@napi-rs/tar-win32-ia32-msvc': 1.1.0 - '@napi-rs/tar-win32-x64-msvc': 1.1.0 - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.3 - optional: true - - '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': - dependencies: - '@emnapi/core': 1.11.3 - '@emnapi/runtime': 1.11.3 - '@tybys/wasm-util': 0.10.3 - - '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)': - dependencies: - '@emnapi/core': 2.0.0-alpha.3 - '@emnapi/runtime': 2.0.0-alpha.3 - '@tybys/wasm-util': 0.10.3 - optional: true - - '@napi-rs/wasm-tools-android-arm-eabi@1.0.1': - optional: true - - '@napi-rs/wasm-tools-android-arm64@1.0.1': - optional: true - - '@napi-rs/wasm-tools-darwin-arm64@1.0.1': - optional: true - - '@napi-rs/wasm-tools-darwin-x64@1.0.1': - optional: true - - '@napi-rs/wasm-tools-freebsd-x64@1.0.1': - optional: true - - '@napi-rs/wasm-tools-linux-arm64-gnu@1.0.1': - optional: true - - '@napi-rs/wasm-tools-linux-arm64-musl@1.0.1': - optional: true - - '@napi-rs/wasm-tools-linux-x64-gnu@1.0.1': - optional: true - - '@napi-rs/wasm-tools-linux-x64-musl@1.0.1': - optional: true - - '@napi-rs/wasm-tools-wasm32-wasi@1.0.1(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': - dependencies: - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - optional: true - - '@napi-rs/wasm-tools-win32-arm64-msvc@1.0.1': - optional: true - - '@napi-rs/wasm-tools-win32-ia32-msvc@1.0.1': - optional: true - - '@napi-rs/wasm-tools-win32-x64-msvc@1.0.1': - optional: true - - '@napi-rs/wasm-tools@1.0.1(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': - optionalDependencies: - '@napi-rs/wasm-tools-android-arm-eabi': 1.0.1 - '@napi-rs/wasm-tools-android-arm64': 1.0.1 - '@napi-rs/wasm-tools-darwin-arm64': 1.0.1 - '@napi-rs/wasm-tools-darwin-x64': 1.0.1 - '@napi-rs/wasm-tools-freebsd-x64': 1.0.1 - '@napi-rs/wasm-tools-linux-arm64-gnu': 1.0.1 - '@napi-rs/wasm-tools-linux-arm64-musl': 1.0.1 - '@napi-rs/wasm-tools-linux-x64-gnu': 1.0.1 - '@napi-rs/wasm-tools-linux-x64-musl': 1.0.1 - '@napi-rs/wasm-tools-wasm32-wasi': 1.0.1(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - '@napi-rs/wasm-tools-win32-arm64-msvc': 1.0.1 - '@napi-rs/wasm-tools-win32-ia32-msvc': 1.0.1 - '@napi-rs/wasm-tools-win32-x64-msvc': 1.0.1 - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - - '@next/env@16.2.7': {} - - '@next/swc-darwin-arm64@16.2.7': - optional: true - - '@next/swc-darwin-x64@16.2.7': - optional: true - - '@next/swc-linux-arm64-gnu@16.2.7': - optional: true - - '@next/swc-linux-arm64-musl@16.2.7': - optional: true - - '@next/swc-linux-x64-gnu@16.2.7': - optional: true - - '@next/swc-linux-x64-musl@16.2.7': - optional: true - - '@next/swc-win32-arm64-msvc@16.2.7': - optional: true - - '@next/swc-win32-x64-msvc@16.2.7': + '@next/swc-win32-x64-msvc@15.2.3': optional: true '@nodelib/fs.scandir@2.1.5': @@ -4211,665 +4819,716 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 + fastq: 1.17.1 - '@octokit/auth-token@6.0.0': {} + '@nolyfill/is-core-module@1.0.39': {} - '@octokit/core@7.0.6': - dependencies: - '@octokit/auth-token': 6.0.0 - '@octokit/graphql': 9.0.3 - '@octokit/request': 10.0.9 - '@octokit/request-error': 7.1.0 - '@octokit/types': 16.0.0 - before-after-hook: 4.0.0 - universal-user-agent: 7.0.3 - - '@octokit/endpoint@11.0.3': - dependencies: - '@octokit/types': 16.0.0 - universal-user-agent: 7.0.3 - - '@octokit/graphql@9.0.3': - dependencies: - '@octokit/request': 10.0.9 - '@octokit/types': 16.0.0 - universal-user-agent: 7.0.3 - - '@octokit/openapi-types@27.0.0': {} - - '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)': - dependencies: - '@octokit/core': 7.0.6 - '@octokit/types': 16.0.0 - - '@octokit/plugin-request-log@6.0.0(@octokit/core@7.0.6)': - dependencies: - '@octokit/core': 7.0.6 - - '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.6)': - dependencies: - '@octokit/core': 7.0.6 - '@octokit/types': 16.0.0 - - '@octokit/request-error@7.1.0': - dependencies: - '@octokit/types': 16.0.0 - - '@octokit/request@10.0.9': - dependencies: - '@octokit/endpoint': 11.0.3 - '@octokit/request-error': 7.1.0 - '@octokit/types': 16.0.0 - content-type: 2.0.0 - fast-content-type-parse: 3.0.0 - json-with-bigint: 3.5.8 - universal-user-agent: 7.0.3 - - '@octokit/rest@22.0.1': - dependencies: - '@octokit/core': 7.0.6 - '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) - '@octokit/plugin-request-log': 6.0.0(@octokit/core@7.0.6) - '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6) - - '@octokit/types@16.0.0': - dependencies: - '@octokit/openapi-types': 27.0.0 - - '@oven/bun-darwin-aarch64@1.3.14': + '@oven/bun-darwin-aarch64@1.2.4': optional: true - '@oven/bun-darwin-x64-baseline@1.3.14': + '@oven/bun-darwin-x64-baseline@1.2.4': optional: true - '@oven/bun-darwin-x64@1.3.14': + '@oven/bun-darwin-x64@1.2.4': optional: true - '@oven/bun-freebsd-aarch64@1.3.14': + '@oven/bun-linux-aarch64-musl@1.2.4': optional: true - '@oven/bun-freebsd-x64@1.3.14': + '@oven/bun-linux-aarch64@1.2.4': optional: true - '@oven/bun-linux-aarch64-android@1.3.14': + '@oven/bun-linux-x64-baseline@1.2.4': optional: true - '@oven/bun-linux-aarch64-musl@1.3.14': + '@oven/bun-linux-x64-musl-baseline@1.2.4': optional: true - '@oven/bun-linux-aarch64@1.3.14': + '@oven/bun-linux-x64-musl@1.2.4': optional: true - '@oven/bun-linux-x64-android@1.3.14': + '@oven/bun-linux-x64@1.2.4': optional: true - '@oven/bun-linux-x64-baseline@1.3.14': + '@oven/bun-windows-x64-baseline@1.2.4': optional: true - '@oven/bun-linux-x64-musl-baseline@1.3.14': + '@oven/bun-windows-x64@1.2.4': optional: true - '@oven/bun-linux-x64-musl@1.3.14': + '@parcel/watcher-android-arm64@2.5.0': optional: true - '@oven/bun-linux-x64@1.3.14': + '@parcel/watcher-android-arm64@2.5.1': optional: true - '@oven/bun-windows-aarch64@1.3.14': + '@parcel/watcher-darwin-arm64@2.5.0': optional: true - '@oven/bun-windows-x64-baseline@1.3.14': + '@parcel/watcher-darwin-arm64@2.5.1': {} + + '@parcel/watcher-darwin-x64@2.5.0': optional: true - '@oven/bun-windows-x64@1.3.14': + '@parcel/watcher-darwin-x64@2.5.1': {} + + '@parcel/watcher-freebsd-x64@2.5.0': optional: true - '@oxc-project/types@0.142.0': {} - - '@parcel/watcher-android-arm64@2.6.0': + '@parcel/watcher-freebsd-x64@2.5.1': optional: true - '@parcel/watcher-darwin-arm64@2.6.0': {} - - '@parcel/watcher-darwin-x64@2.6.0': {} - - '@parcel/watcher-freebsd-x64@2.6.0': + '@parcel/watcher-linux-arm-glibc@2.5.0': optional: true - '@parcel/watcher-linux-arm-glibc@2.6.0': + '@parcel/watcher-linux-arm-glibc@2.5.1': optional: true - '@parcel/watcher-linux-arm-musl@2.6.0': + '@parcel/watcher-linux-arm-musl@2.5.0': optional: true - '@parcel/watcher-linux-arm64-glibc@2.6.0': {} + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true - '@parcel/watcher-linux-arm64-musl@2.6.0': {} + '@parcel/watcher-linux-arm64-glibc@2.5.0': + optional: true - '@parcel/watcher-linux-x64-glibc@2.6.0': {} + '@parcel/watcher-linux-arm64-glibc@2.5.1': {} - '@parcel/watcher-linux-x64-musl@2.6.0': {} + '@parcel/watcher-linux-arm64-musl@2.5.0': + optional: true - '@parcel/watcher-wasm@2.5.6': + '@parcel/watcher-linux-arm64-musl@2.5.1': {} + + '@parcel/watcher-linux-x64-glibc@2.5.0': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': {} + + '@parcel/watcher-linux-x64-musl@2.5.0': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': {} + + '@parcel/watcher-wasm@2.5.0': dependencies: is-glob: 4.0.3 - picomatch: 4.0.5 + micromatch: 4.0.7 - '@parcel/watcher-win32-arm64@2.6.0': + '@parcel/watcher-win32-arm64@2.5.0': optional: true - '@parcel/watcher-win32-x64@2.6.0': {} + '@parcel/watcher-win32-arm64@2.5.1': + optional: true - '@parcel/watcher@2.6.0(patch_hash=705ce75ccea54337110c4d7fe0a8b421658ea0c23e1a77c1fa890a86041179da)': + '@parcel/watcher-win32-ia32@2.5.0': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.0': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': {} + + '@parcel/watcher@2.5.0': dependencies: - detect-libc: 2.1.2 + detect-libc: 1.0.3 is-glob: 4.0.3 + micromatch: 4.0.7 node-addon-api: 7.1.1 - picomatch: 4.0.5 optionalDependencies: - '@parcel/watcher-android-arm64': 2.6.0 - '@parcel/watcher-darwin-arm64': 2.6.0 - '@parcel/watcher-darwin-x64': 2.6.0 - '@parcel/watcher-freebsd-x64': 2.6.0 - '@parcel/watcher-linux-arm-glibc': 2.6.0 - '@parcel/watcher-linux-arm-musl': 2.6.0 - '@parcel/watcher-linux-arm64-glibc': 2.6.0 - '@parcel/watcher-linux-arm64-musl': 2.6.0 - '@parcel/watcher-linux-x64-glibc': 2.6.0 - '@parcel/watcher-linux-x64-musl': 2.6.0 - '@parcel/watcher-win32-arm64': 2.6.0 - '@parcel/watcher-win32-x64': 2.6.0 + '@parcel/watcher-android-arm64': 2.5.0 + '@parcel/watcher-darwin-arm64': 2.5.0 + '@parcel/watcher-darwin-x64': 2.5.0 + '@parcel/watcher-freebsd-x64': 2.5.0 + '@parcel/watcher-linux-arm-glibc': 2.5.0 + '@parcel/watcher-linux-arm-musl': 2.5.0 + '@parcel/watcher-linux-arm64-glibc': 2.5.0 + '@parcel/watcher-linux-arm64-musl': 2.5.0 + '@parcel/watcher-linux-x64-glibc': 2.5.0 + '@parcel/watcher-linux-x64-musl': 2.5.0 + '@parcel/watcher-win32-arm64': 2.5.0 + '@parcel/watcher-win32-ia32': 2.5.0 + '@parcel/watcher-win32-x64': 2.5.0 - '@playwright/test@1.62.1': + '@parcel/watcher@2.5.1(patch_hash=zs2vvlrje3h42xp5ed2v44fep4)': dependencies: - playwright: 1.62.1 - - '@rolldown/binding-android-arm64@1.2.1': - optional: true - - '@rolldown/binding-darwin-arm64@1.2.1': - optional: true - - '@rolldown/binding-darwin-x64@1.2.1': - optional: true - - '@rolldown/binding-freebsd-x64@1.2.1': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.2.1': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.2.1': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.2.1': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.2.1': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.2.1': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.2.1': - optional: true - - '@rolldown/binding-linux-x64-musl@1.2.1': - optional: true - - '@rolldown/binding-openharmony-arm64@1.2.1': - optional: true - - '@rolldown/binding-wasm32-wasi@1.2.1': - dependencies: - '@emnapi/core': 2.0.0-alpha.3 - '@emnapi/runtime': 2.0.0-alpha.3 - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3) - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.2.1': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.2.1': - optional: true - - '@rolldown/pluginutils@1.0.1': {} - - '@rollup/rollup-android-arm-eabi@4.60.4': - optional: true - - '@rollup/rollup-android-arm64@4.60.4': - optional: true - - '@rollup/rollup-darwin-arm64@4.60.4': - optional: true - - '@rollup/rollup-darwin-x64@4.60.4': - optional: true - - '@rollup/rollup-freebsd-arm64@4.60.4': - optional: true - - '@rollup/rollup-freebsd-x64@4.60.4': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.60.4': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.60.4': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.60.4': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.60.4': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.60.4': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.60.4': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-x64-musl@4.60.4': - optional: true - - '@rollup/rollup-openbsd-x64@4.60.4': - optional: true - - '@rollup/rollup-openharmony-arm64@4.60.4': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.60.4': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.60.4': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.60.4': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.60.4': - optional: true - - '@rspack/binding-darwin-arm64@2.0.2': - optional: true - - '@rspack/binding-darwin-x64@2.0.2': - optional: true - - '@rspack/binding-linux-arm64-gnu@2.0.2': - optional: true - - '@rspack/binding-linux-arm64-musl@2.0.2': - optional: true - - '@rspack/binding-linux-x64-gnu@2.0.2': - optional: true - - '@rspack/binding-linux-x64-musl@2.0.2': - optional: true - - '@rspack/binding-wasm32-wasi@2.0.2': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) - optional: true - - '@rspack/binding-win32-arm64-msvc@2.0.2': - optional: true - - '@rspack/binding-win32-ia32-msvc@2.0.2': - optional: true - - '@rspack/binding-win32-x64-msvc@2.0.2': - optional: true - - '@rspack/binding@2.0.2': + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 optionalDependencies: - '@rspack/binding-darwin-arm64': 2.0.2 - '@rspack/binding-darwin-x64': 2.0.2 - '@rspack/binding-linux-arm64-gnu': 2.0.2 - '@rspack/binding-linux-arm64-musl': 2.0.2 - '@rspack/binding-linux-x64-gnu': 2.0.2 - '@rspack/binding-linux-x64-musl': 2.0.2 - '@rspack/binding-wasm32-wasi': 2.0.2 - '@rspack/binding-win32-arm64-msvc': 2.0.2 - '@rspack/binding-win32-ia32-msvc': 2.0.2 - '@rspack/binding-win32-x64-msvc': 2.0.2 + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 - '@rspack/core@2.0.2(@swc/helpers@0.5.15)': + '@pkgjs/parseargs@0.11.0': + optional: true + + '@playwright/test@1.51.0': dependencies: - '@rspack/binding': 2.0.2 - optionalDependencies: - '@swc/helpers': 0.5.15 + playwright: 1.51.0 - '@sindresorhus/merge-streams@4.0.0': {} + '@rollup/rollup-android-arm-eabi@4.34.9': + optional: true - '@standard-schema/spec@1.1.0': {} + '@rollup/rollup-android-arm64@4.34.9': + optional: true + + '@rollup/rollup-darwin-arm64@4.34.9': + optional: true + + '@rollup/rollup-darwin-x64@4.34.9': + optional: true + + '@rollup/rollup-freebsd-arm64@4.34.9': + optional: true + + '@rollup/rollup-freebsd-x64@4.34.9': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.34.9': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.34.9': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.34.9': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-x64-musl@4.34.9': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.34.9': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.34.9': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.34.9': + optional: true + + '@rtsao/scc@1.1.0': {} + + '@rushstack/eslint-patch@1.10.4': {} + + '@sindresorhus/merge-streams@2.3.0': {} + + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.15': dependencies: - tslib: 2.8.1 + tslib: 2.8.0 '@tailwindcss/aspect-ratio@0.4.2(tailwindcss@packages+tailwindcss)': dependencies: tailwindcss: link:packages/tailwindcss - '@tailwindcss/forms@0.5.11(tailwindcss@packages+tailwindcss)': + '@tailwindcss/forms@0.5.10(tailwindcss@packages+tailwindcss)': dependencies: mini-svg-data-uri: 1.4.4 tailwindcss: link:packages/tailwindcss - '@tailwindcss/typography@0.5.20(tailwindcss@packages+tailwindcss)': + '@tailwindcss/typography@0.5.16(tailwindcss@packages+tailwindcss)': dependencies: + lodash.castarray: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 postcss-selector-parser: 6.0.10 tailwindcss: link:packages/tailwindcss - '@turbo/darwin-64@2.10.8': - optional: true - - '@turbo/darwin-arm64@2.10.8': - optional: true - - '@turbo/linux-64@2.10.8': - optional: true - - '@turbo/linux-arm64@2.10.8': - optional: true - - '@turbo/windows-64@2.10.8': - optional: true - - '@turbo/windows-arm64@2.10.8': - optional: true - - '@tybys/wasm-util@0.10.3': + '@types/babel__core@7.20.5': dependencies: - tslib: 2.8.1 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 - '@types/bun@1.3.14': + '@types/babel__generator@7.6.8': dependencies: - bun-types: 1.3.14 + '@babel/types': 7.26.3 - '@types/chai@5.2.3': + '@types/babel__template@7.4.4': dependencies: - '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 - '@types/deep-eql@4.0.2': {} + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.26.3 - '@types/estree@1.0.8': {} + '@types/braces@3.0.5': {} - '@types/estree@1.0.9': {} + '@types/bun@1.2.4': + dependencies: + bun-types: 1.2.4 + + '@types/estree@1.0.6': {} '@types/json-schema@7.0.15': {} - '@types/node@22.20.1': - dependencies: - undici-types: 6.21.0 + '@types/json5@0.0.29': {} - '@types/node@25.9.1': + '@types/node@20.14.13': dependencies: - undici-types: 7.24.6 + undici-types: 5.26.5 '@types/postcss-import@14.0.3': dependencies: - postcss: 8.5.25 + postcss: 8.5.3 - '@types/react-dom@19.2.3(@types/react@19.2.15)': + '@types/react-dom@19.0.4(@types/react@19.0.10)': dependencies: - '@types/react': 19.2.15 + '@types/react': 19.0.10 - '@types/react@19.2.15': + '@types/react@19.0.10': dependencies: - csstype: 3.2.3 + csstype: 3.1.3 - '@types/semver@7.8.0': {} - - '@vitejs/plugin-react@6.0.2(vite@8.2.0(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))': + '@types/ws@8.5.12': dependencies: - '@rolldown/pluginutils': 1.0.1 - vite: 8.2.0(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) + '@types/node': 20.14.13 - '@vitest/expect@4.1.10': + '@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4)': dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 - chai: 6.2.2 - tinyrainbow: 3.1.0 + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.11.0 + '@typescript-eslint/type-utils': 8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/utils': 8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.11.0 + eslint: 9.22.0(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color - '@vitest/mocker@4.1.10(vite@8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))': + '@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3)': dependencies: - '@vitest/spy': 4.1.10 + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.11.0 + '@typescript-eslint/type-utils': 8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3) + '@typescript-eslint/utils': 8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.11.0 + eslint: 9.22.0(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4)': + dependencies: + '@typescript-eslint/scope-manager': 8.11.0 + '@typescript-eslint/types': 8.11.0 + '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.11.0 + debug: 4.4.0 + eslint: 9.22.0(jiti@2.4.2) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.11.0 + '@typescript-eslint/types': 8.11.0 + '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.11.0 + debug: 4.4.0 + eslint: 9.22.0(jiti@2.4.2) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.11.0': + dependencies: + '@typescript-eslint/types': 8.11.0 + '@typescript-eslint/visitor-keys': 8.11.0 + + '@typescript-eslint/type-utils@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4)': + dependencies: + '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.5.4) + '@typescript-eslint/utils': 8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4) + debug: 4.4.0 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - eslint + - supports-color + + '@typescript-eslint/type-utils@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3) + '@typescript-eslint/utils': 8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3) + debug: 4.4.0 + ts-api-utils: 1.3.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - eslint + - supports-color + + '@typescript-eslint/types@8.11.0': {} + + '@typescript-eslint/typescript-estree@8.11.0(typescript@5.5.4)': + dependencies: + '@typescript-eslint/types': 8.11.0 + '@typescript-eslint/visitor-keys': 8.11.0 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.11.0(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 8.11.0 + '@typescript-eslint/visitor-keys': 8.11.0 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.3.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.11.0 + '@typescript-eslint/types': 8.11.0 + '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.5.4) + eslint: 9.22.0(jiti@2.4.2) + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/utils@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.11.0 + '@typescript-eslint/types': 8.11.0 + '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3) + eslint: 9.22.0(jiti@2.4.2) + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@8.11.0': + dependencies: + '@typescript-eslint/types': 8.11.0 + eslint-visitor-keys: 3.4.3 + + '@vitejs/plugin-react@4.3.4(vite@6.0.0(@types/node@20.14.13)(jiti@2.4.2)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6)(tsx@4.19.1)(yaml@2.6.0))': + dependencies: + '@babel/core': 7.26.0 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 6.0.0(@types/node@20.14.13)(jiti@2.4.2)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6)(tsx@4.19.1)(yaml@2.6.0) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@2.0.5': + dependencies: + '@vitest/spy': 2.0.5 + '@vitest/utils': 2.0.5 + chai: 5.1.1 + tinyrainbow: 1.2.0 + + '@vitest/pretty-format@2.0.5': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.0.5': + dependencies: + '@vitest/utils': 2.0.5 + pathe: 1.1.2 + + '@vitest/snapshot@2.0.5': + dependencies: + '@vitest/pretty-format': 2.0.5 + magic-string: 0.30.11 + pathe: 1.1.2 + + '@vitest/spy@2.0.5': + dependencies: + tinyspy: 3.0.0 + + '@vitest/utils@2.0.5': + dependencies: + '@vitest/pretty-format': 2.0.5 estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) + loupe: 3.1.1 + tinyrainbow: 1.2.0 - '@vitest/pretty-format@4.1.10': + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: - tinyrainbow: 3.1.0 + acorn: 8.14.0 - '@vitest/runner@4.1.10': - dependencies: - '@vitest/utils': 4.1.10 - pathe: 2.0.3 + acorn@8.14.0: {} - '@vitest/snapshot@4.1.10': - dependencies: - '@vitest/pretty-format': 4.1.10 - '@vitest/utils': 4.1.10 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.1.10': {} - - '@vitest/utils@4.1.10': - dependencies: - '@vitest/pretty-format': 4.1.10 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 - - '@webassemblyjs/ast@1.14.1': - dependencies: - '@webassemblyjs/helper-numbers': 1.13.2 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - - '@webassemblyjs/floating-point-hex-parser@1.13.2': {} - - '@webassemblyjs/helper-api-error@1.13.2': {} - - '@webassemblyjs/helper-buffer@1.14.1': {} - - '@webassemblyjs/helper-numbers@1.13.2': - dependencies: - '@webassemblyjs/floating-point-hex-parser': 1.13.2 - '@webassemblyjs/helper-api-error': 1.13.2 - '@xtuc/long': 4.2.2 - - '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} - - '@webassemblyjs/helper-wasm-section@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-buffer': 1.14.1 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/wasm-gen': 1.14.1 - - '@webassemblyjs/ieee754@1.13.2': - dependencies: - '@xtuc/ieee754': 1.2.0 - - '@webassemblyjs/leb128@1.13.2': - dependencies: - '@xtuc/long': 4.2.2 - - '@webassemblyjs/utf8@1.13.2': {} - - '@webassemblyjs/wasm-edit@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-buffer': 1.14.1 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/helper-wasm-section': 1.14.1 - '@webassemblyjs/wasm-gen': 1.14.1 - '@webassemblyjs/wasm-opt': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - '@webassemblyjs/wast-printer': 1.14.1 - - '@webassemblyjs/wasm-gen@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/ieee754': 1.13.2 - '@webassemblyjs/leb128': 1.13.2 - '@webassemblyjs/utf8': 1.13.2 - - '@webassemblyjs/wasm-opt@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-buffer': 1.14.1 - '@webassemblyjs/wasm-gen': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - - '@webassemblyjs/wasm-parser@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-api-error': 1.13.2 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/ieee754': 1.13.2 - '@webassemblyjs/leb128': 1.13.2 - '@webassemblyjs/utf8': 1.13.2 - - '@webassemblyjs/wast-printer@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@xtuc/long': 4.2.2 - - '@xtuc/ieee754@1.2.0': {} - - '@xtuc/long@4.2.2': {} - - acorn@8.16.0: {} - - ajv-formats@2.1.1(ajv@8.20.0): - optionalDependencies: - ajv: 8.20.0 - - ajv-keywords@5.1.0(ajv@8.20.0): - dependencies: - ajv: 8.20.0 - fast-deep-equal: 3.1.3 - - ajv@8.20.0: + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.2 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} any-promise@1.3.0: {} anymatch@3.1.3: dependencies: normalize-path: 3.0.0 - picomatch: 2.3.2 + picomatch: 2.3.1 arg@5.0.2: {} argparse@2.0.1: {} + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + is-array-buffer: 3.0.4 + + array-includes@3.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 + is-string: 1.0.7 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 + + array.prototype.findlastindex@1.2.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 + + array.prototype.flat@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.flatmap@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-shim-unscopables: 1.0.2 + + arraybuffer.prototype.slice@1.0.3: + dependencies: + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + is-array-buffer: 3.0.4 + is-shared-array-buffer: 1.0.3 + assertion-error@2.0.1: {} - autoprefixer@10.5.0(postcss@8.5.25): + ast-types-flow@0.0.8: {} + + autoprefixer@10.4.21(postcss@8.5.3): dependencies: - browserslist: 4.28.2 - caniuse-lite: 1.0.30001793 - fraction.js: 5.3.4 + browserslist: 4.24.4 + caniuse-lite: 1.0.30001705 + fraction.js: 4.3.7 + normalize-range: 0.1.2 picocolors: 1.1.1 - postcss: 8.5.25 + postcss: 8.5.3 postcss-value-parser: 4.2.0 - baseline-browser-mapping@2.10.31: {} + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.0.0 - before-after-hook@4.0.0: {} + axe-core@4.10.2: {} + + axobject-query@4.1.0: {} + + balanced-match@1.0.2: {} binary-extensions@2.3.0: {} + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + braces@3.0.3: dependencies: fill-range: 7.1.1 - browserslist@4.28.2: + browserslist@4.24.2: dependencies: - baseline-browser-mapping: 2.10.31 - caniuse-lite: 1.0.30001793 - electron-to-chromium: 1.5.361 - node-releases: 2.0.46 - update-browserslist-db: 1.2.3(browserslist@4.28.2) + caniuse-lite: 1.0.30001705 + electron-to-chromium: 1.5.45 + node-releases: 2.0.18 + update-browserslist-db: 1.1.1(browserslist@4.24.2) - buffer-from@1.1.2: {} - - bun-types@1.3.14: + browserslist@4.24.4: dependencies: - '@types/node': 25.9.1 + caniuse-lite: 1.0.30001705 + electron-to-chromium: 1.5.119 + node-releases: 2.0.19 + update-browserslist-db: 1.1.1(browserslist@4.24.4) - bun@1.3.14: + buffer-from@1.1.2: + optional: true + + bun-types@1.2.4: + dependencies: + '@types/node': 20.14.13 + '@types/ws': 8.5.12 + + bun@1.2.4: optionalDependencies: - '@oven/bun-darwin-aarch64': 1.3.14 - '@oven/bun-darwin-x64': 1.3.14 - '@oven/bun-darwin-x64-baseline': 1.3.14 - '@oven/bun-freebsd-aarch64': 1.3.14 - '@oven/bun-freebsd-x64': 1.3.14 - '@oven/bun-linux-aarch64': 1.3.14 - '@oven/bun-linux-aarch64-android': 1.3.14 - '@oven/bun-linux-aarch64-musl': 1.3.14 - '@oven/bun-linux-x64': 1.3.14 - '@oven/bun-linux-x64-android': 1.3.14 - '@oven/bun-linux-x64-baseline': 1.3.14 - '@oven/bun-linux-x64-musl': 1.3.14 - '@oven/bun-linux-x64-musl-baseline': 1.3.14 - '@oven/bun-windows-aarch64': 1.3.14 - '@oven/bun-windows-x64': 1.3.14 - '@oven/bun-windows-x64-baseline': 1.3.14 + '@oven/bun-darwin-aarch64': 1.2.4 + '@oven/bun-darwin-x64': 1.2.4 + '@oven/bun-darwin-x64-baseline': 1.2.4 + '@oven/bun-linux-aarch64': 1.2.4 + '@oven/bun-linux-aarch64-musl': 1.2.4 + '@oven/bun-linux-x64': 1.2.4 + '@oven/bun-linux-x64-baseline': 1.2.4 + '@oven/bun-linux-x64-musl': 1.2.4 + '@oven/bun-linux-x64-musl-baseline': 1.2.4 + '@oven/bun-windows-x64': 1.2.4 + '@oven/bun-windows-x64-baseline': 1.2.4 - bundle-require@5.1.0(esbuild@0.27.7): + bundle-require@5.1.0(esbuild@0.25.0): dependencies: - esbuild: 0.27.7 + esbuild: 0.25.0 load-tsconfig: 0.2.5 + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + cac@6.7.14: {} + call-bind@1.0.7: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + + callsites@3.1.0: {} + camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001793: {} + caniuse-lite@1.0.30001705: {} - chai@6.2.2: {} + chai@5.1.1: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.1 + pathval: 2.0.0 - chardet@2.1.1: {} + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + check-error@2.1.1: {} chokidar@3.6.0: dependencies: @@ -4885,138 +5544,665 @@ snapshots: chokidar@4.0.3: dependencies: - readdirp: 4.1.2 + readdirp: 4.1.1 - chrome-trace-event@1.0.4: {} - - citty@0.2.2: {} - - cli-width@4.1.0: {} + citty@0.1.6: + dependencies: + consola: 3.2.3 client-only@0.0.1: {} - clipanion@4.0.0-rc.4(typanion@3.14.0): + clipboardy@4.0.0: dependencies: - typanion: 3.14.0 + execa: 8.0.1 + is-wsl: 3.1.0 + is64bit: 2.0.0 - colorette@2.0.20: {} + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 - commander@2.20.3: {} + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + optional: true + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + optional: true + + commander@2.20.3: + optional: true commander@4.1.1: {} + concat-map@0.0.1: {} + confbox@0.1.8: {} - consola@3.4.2: {} + consola@3.2.3: {} - content-type@2.0.0: {} + consola@3.4.0: {} convert-source-map@2.0.0: {} - cookie-es@1.2.3: {} + cookie-es@1.2.2: {} - crossws@0.3.5: + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crossws@0.3.1: dependencies: uncrypto: 0.1.3 - crossws@0.4.10: {} + crossws@0.3.4: + dependencies: + uncrypto: 0.1.3 cssesc@3.0.0: {} - csstype@3.2.3: {} + csstype@3.1.3: {} - debug@4.4.3: + damerau-levenshtein@1.0.8: {} + + data-view-buffer@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-offset@1.0.0: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + debug@3.2.7: dependencies: ms: 2.1.3 - dedent@1.7.2: {} + debug@4.3.6: + dependencies: + ms: 2.1.2 - defu@6.1.7: {} + debug@4.4.0: + dependencies: + ms: 2.1.3 - destr@2.0.5: {} + dedent@1.5.3: {} + + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + defu@6.1.4: {} + + destr@2.0.3: {} detect-libc@1.0.3: {} - detect-libc@2.1.2: {} + detect-libc@2.0.3: {} didyoumean@1.2.2: {} dlv@1.1.3: {} - electron-to-chromium@1.5.361: {} + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 - emnapi@1.11.3(node-addon-api@8.7.0): - optionalDependencies: - node-addon-api: 8.7.0 + eastasianwidth@0.2.0: {} - enhanced-resolve@5.24.5: + electron-to-chromium@1.5.119: {} + + electron-to-chromium@1.5.45: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + enhanced-resolve@5.18.1: dependencies: graceful-fs: 4.2.11 - tapable: 2.3.3 + tapable: 2.2.1 + + es-abstract@1.23.3: + dependencies: + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 + data-view-byte-offset: 1.0.0 + es-define-property: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-set-tostringtag: 2.0.3 + 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.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 + is-callable: 1.2.7 + is-data-view: 1.0.1 + is-negative-zero: 2.0.3 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.3 + is-string: 1.0.7 + is-typed-array: 1.1.13 + is-weakref: 1.0.2 + object-inspect: 1.13.2 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.2 + safe-array-concat: 1.1.2 + safe-regex-test: 1.0.3 + string.prototype.trim: 1.2.9 + string.prototype.trimend: 1.0.8 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.2 + typed-array-byte-length: 1.0.1 + typed-array-byte-offset: 1.0.2 + typed-array-length: 1.0.6 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.15 + + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 es-errors@1.3.0: {} - es-module-lexer@2.1.0: {} + es-iterator-helpers@1.1.0: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-set-tostringtag: 2.0.3 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + globalthis: 1.0.4 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + internal-slot: 1.0.7 + iterator.prototype: 1.1.3 + safe-array-concat: 1.1.2 - es-toolkit@1.50.0: {} + es-object-atoms@1.0.0: + dependencies: + es-errors: 1.3.0 - esbuild@0.27.7: + es-set-tostringtag@2.0.3: + dependencies: + get-intrinsic: 1.2.4 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.0.2: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.2.1: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + + esbuild@0.21.5: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.7 - '@esbuild/android-arm': 0.27.7 - '@esbuild/android-arm64': 0.27.7 - '@esbuild/android-x64': 0.27.7 - '@esbuild/darwin-arm64': 0.27.7 - '@esbuild/darwin-x64': 0.27.7 - '@esbuild/freebsd-arm64': 0.27.7 - '@esbuild/freebsd-x64': 0.27.7 - '@esbuild/linux-arm': 0.27.7 - '@esbuild/linux-arm64': 0.27.7 - '@esbuild/linux-ia32': 0.27.7 - '@esbuild/linux-loong64': 0.27.7 - '@esbuild/linux-mips64el': 0.27.7 - '@esbuild/linux-ppc64': 0.27.7 - '@esbuild/linux-riscv64': 0.27.7 - '@esbuild/linux-s390x': 0.27.7 - '@esbuild/linux-x64': 0.27.7 - '@esbuild/netbsd-arm64': 0.27.7 - '@esbuild/netbsd-x64': 0.27.7 - '@esbuild/openbsd-arm64': 0.27.7 - '@esbuild/openbsd-x64': 0.27.7 - '@esbuild/openharmony-arm64': 0.27.7 - '@esbuild/sunos-x64': 0.27.7 - '@esbuild/win32-arm64': 0.27.7 - '@esbuild/win32-ia32': 0.27.7 - '@esbuild/win32-x64': 0.27.7 + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + optional: true + + esbuild@0.24.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.0 + '@esbuild/android-arm': 0.24.0 + '@esbuild/android-arm64': 0.24.0 + '@esbuild/android-x64': 0.24.0 + '@esbuild/darwin-arm64': 0.24.0 + '@esbuild/darwin-x64': 0.24.0 + '@esbuild/freebsd-arm64': 0.24.0 + '@esbuild/freebsd-x64': 0.24.0 + '@esbuild/linux-arm': 0.24.0 + '@esbuild/linux-arm64': 0.24.0 + '@esbuild/linux-ia32': 0.24.0 + '@esbuild/linux-loong64': 0.24.0 + '@esbuild/linux-mips64el': 0.24.0 + '@esbuild/linux-ppc64': 0.24.0 + '@esbuild/linux-riscv64': 0.24.0 + '@esbuild/linux-s390x': 0.24.0 + '@esbuild/linux-x64': 0.24.0 + '@esbuild/netbsd-x64': 0.24.0 + '@esbuild/openbsd-arm64': 0.24.0 + '@esbuild/openbsd-x64': 0.24.0 + '@esbuild/sunos-x64': 0.24.0 + '@esbuild/win32-arm64': 0.24.0 + '@esbuild/win32-ia32': 0.24.0 + '@esbuild/win32-x64': 0.24.0 + + esbuild@0.25.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.0 + '@esbuild/android-arm': 0.25.0 + '@esbuild/android-arm64': 0.25.0 + '@esbuild/android-x64': 0.25.0 + '@esbuild/darwin-arm64': 0.25.0 + '@esbuild/darwin-x64': 0.25.0 + '@esbuild/freebsd-arm64': 0.25.0 + '@esbuild/freebsd-x64': 0.25.0 + '@esbuild/linux-arm': 0.25.0 + '@esbuild/linux-arm64': 0.25.0 + '@esbuild/linux-ia32': 0.25.0 + '@esbuild/linux-loong64': 0.25.0 + '@esbuild/linux-mips64el': 0.25.0 + '@esbuild/linux-ppc64': 0.25.0 + '@esbuild/linux-riscv64': 0.25.0 + '@esbuild/linux-s390x': 0.25.0 + '@esbuild/linux-x64': 0.25.0 + '@esbuild/netbsd-arm64': 0.25.0 + '@esbuild/netbsd-x64': 0.25.0 + '@esbuild/openbsd-arm64': 0.25.0 + '@esbuild/openbsd-x64': 0.25.0 + '@esbuild/sunos-x64': 0.25.0 + '@esbuild/win32-arm64': 0.25.0 + '@esbuild/win32-ia32': 0.25.0 + '@esbuild/win32-x64': 0.25.0 escalade@3.2.0: {} - eslint-scope@5.1.1: + escape-string-regexp@4.0.0: {} + + eslint-config-next@15.2.3(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4): + dependencies: + '@next/eslint-plugin-next': 15.2.3 + '@rushstack/eslint-patch': 1.10.4 + '@typescript-eslint/eslint-plugin': 8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/parser': 8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4) + eslint: 9.22.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.22.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.22.0(jiti@2.4.2)) + eslint-plugin-jsx-a11y: 6.10.1(eslint@9.22.0(jiti@2.4.2)) + eslint-plugin-react: 7.37.2(eslint@9.22.0(jiti@2.4.2)) + eslint-plugin-react-hooks: 5.0.0(eslint@9.22.0(jiti@2.4.2)) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + + eslint-config-next@15.2.3(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3): + dependencies: + '@next/eslint-plugin-next': 15.2.3 + '@rushstack/eslint-patch': 1.10.4 + '@typescript-eslint/eslint-plugin': 8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3) + '@typescript-eslint/parser': 8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3) + eslint: 9.22.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.22.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.22.0(jiti@2.4.2)) + eslint-plugin-jsx-a11y: 6.10.1(eslint@9.22.0(jiti@2.4.2)) + eslint-plugin-react: 7.37.2(eslint@9.22.0(jiti@2.4.2)) + eslint-plugin-react-hooks: 5.0.0(eslint@9.22.0(jiti@2.4.2)) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.15.1 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.22.0(jiti@2.4.2)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.0 + enhanced-resolve: 5.18.1 + eslint: 9.22.0(jiti@2.4.2) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.22.0(jiti@2.4.2)) + fast-glob: 3.3.3 + get-tsconfig: 4.8.1 + is-bun-module: 1.2.1 + is-glob: 4.0.3 + optionalDependencies: + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.22.0(jiti@2.4.2)) + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.22.0(jiti@2.4.2)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.0 + enhanced-resolve: 5.18.1 + eslint: 9.22.0(jiti@2.4.2) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.22.0(jiti@2.4.2)) + fast-glob: 3.3.3 + get-tsconfig: 4.8.1 + is-bun-module: 1.2.1 + is-glob: 4.0.3 + optionalDependencies: + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.22.0(jiti@2.4.2)) + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.22.0(jiti@2.4.2)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4) + eslint: 9.22.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.22.0(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.22.0(jiti@2.4.2)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3) + eslint: 9.22.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.22.0(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.22.0(jiti@2.4.2)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.22.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.22.0(jiti@2.4.2)) + hasown: 2.0.2 + is-core-module: 2.15.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + string.prototype.trimend: 1.0.8 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.5.4) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.22.0(jiti@2.4.2)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.22.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.22.0(jiti@2.4.2)) + hasown: 2.0.2 + is-core-module: 2.15.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + string.prototype.trimend: 1.0.8 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.11.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.6.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jsx-a11y@6.10.1(eslint@9.22.0(jiti@2.4.2)): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.8 + array.prototype.flatmap: 1.3.2 + ast-types-flow: 0.0.8 + axe-core: 4.10.2 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + es-iterator-helpers: 1.1.0 + eslint: 9.22.0(jiti@2.4.2) + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + safe-regex-test: 1.0.3 + string.prototype.includes: 2.0.1 + + eslint-plugin-react-hooks@5.0.0(eslint@9.22.0(jiti@2.4.2)): + dependencies: + eslint: 9.22.0(jiti@2.4.2) + + eslint-plugin-react@7.37.2(eslint@9.22.0(jiti@2.4.2)): + dependencies: + array-includes: 3.1.8 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.2 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.1.0 + eslint: 9.22.0(jiti@2.4.2) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.8 + object.fromentries: 2.0.8 + object.values: 1.2.0 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.11 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.3.0: dependencies: esrecurse: 4.3.0 - estraverse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.22.0(jiti@2.4.2): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.19.2 + '@eslint/config-helpers': 0.1.0 + '@eslint/core': 0.12.0 + '@eslint/eslintrc': 3.3.0 + '@eslint/js': 9.22.0 + '@eslint/plugin-kit': 0.2.7 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.2 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.3.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.4.2 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 esrecurse@4.3.0: dependencies: estraverse: 5.3.0 - estraverse@4.3.0: {} - estraverse@5.3.0: {} estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.9 + '@types/estree': 1.0.6 - events@3.3.0: {} + esutils@2.0.3: {} - expect-type@1.3.0: {} - - fast-content-type-parse@3.0.0: {} + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 fast-deep-equal@3.1.3: {} - fast-equals@5.4.0: {} + fast-glob@3.3.1: + 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.8 fast-glob@3.3.3: dependencies: @@ -5026,45 +6212,50 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-string-truncated-width@3.0.3: {} + fast-json-stable-stringify@2.1.0: {} - fast-string-width@3.0.2: + fast-levenshtein@2.0.6: {} + + fastq@1.17.1: dependencies: - fast-string-truncated-width: 3.0.3 + reusify: 1.0.4 - fast-stringify@4.0.0: {} - - fast-uri@3.1.2: {} - - fast-wrap-ansi@0.2.2: - dependencies: - fast-string-width: 3.0.2 - - fastq@1.20.1: - dependencies: - reusify: 1.1.0 - - fdir@6.5.0(picomatch@4.0.4): + fdir@6.4.3(picomatch@4.0.2): optionalDependencies: - picomatch: 4.0.4 + picomatch: 4.0.2 - fdir@6.5.0(picomatch@4.0.5): - optionalDependencies: - picomatch: 4.0.5 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 - find-up-simple@1.0.1: {} + find-up-simple@1.0.0: {} - fix-dts-default-cjs-exports@1.0.1: + find-up@5.0.0: dependencies: - magic-string: 0.30.21 - mlly: 1.8.2 - rollup: 4.60.4 + locate-path: 6.0.0 + path-exists: 4.0.0 - fraction.js@5.3.4: {} + flat-cache@4.0.1: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + + flatted@3.3.1: {} + + for-each@0.3.3: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fraction.js@4.3.7: {} fsevents@2.3.2: optional: true @@ -5074,7 +6265,40 @@ snapshots: function-bind@1.1.2: {} - get-port-please@3.2.0: {} + function.prototype.name@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + functions-have-names: 1.2.3 + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-func-name@2.0.2: {} + + get-intrinsic@1.2.4: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + + get-port-please@3.1.2: {} + + get-stream@8.0.1: {} + + get-symbol-description@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + + get-tsconfig@4.8.1: + dependencies: + resolve-pkg-maps: 1.0.0 glob-parent@5.1.2: dependencies: @@ -5084,203 +6308,426 @@ snapshots: dependencies: is-glob: 4.0.3 - globby@16.2.2: + glob@10.4.5: dependencies: - '@sindresorhus/merge-streams': 4.0.0 + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + globals@11.12.0: {} + + globals@14.0.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.0.1 + + globby@14.1.0: + dependencies: + '@sindresorhus/merge-streams': 2.3.0 fast-glob: 3.3.3 - ignore: 7.0.5 - is-path-inside: 4.0.0 + ignore: 7.0.3 + path-type: 6.0.0 slash: 5.1.0 - unicorn-magic: 0.4.0 + unicorn-magic: 0.3.0 + + gopd@1.0.1: + dependencies: + get-intrinsic: 1.2.4 graceful-fs@4.2.11: {} - h3@1.15.11: + graphemer@1.4.0: {} + + h3@1.15.1: dependencies: - cookie-es: 1.2.3 - crossws: 0.3.5 - defu: 6.1.7 - destr: 2.0.5 + cookie-es: 1.2.2 + crossws: 0.3.4 + defu: 6.1.4 + destr: 2.0.3 iron-webcrypto: 1.2.1 - node-mock-http: 1.0.4 + node-mock-http: 1.0.0 radix3: 1.1.2 - ufo: 1.6.4 + ufo: 1.5.4 uncrypto: 0.1.3 + has-bigints@1.0.2: {} + has-flag@4.0.0: {} - hasown@2.0.3: + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.0 + + has-proto@1.0.3: {} + + has-symbols@1.0.3: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.0.3 + + hasown@2.0.2: dependencies: function-bind: 1.1.2 http-shutdown@1.2.2: {} - iconv-lite@0.7.2: - dependencies: - safer-buffer: 2.1.2 + human-signals@5.0.0: {} - ignore@7.0.5: {} + ignore@5.3.2: {} + + ignore@7.0.3: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + internal-slot@1.0.7: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.0.6 iron-webcrypto@1.2.1: {} + is-array-buffer@3.0.4: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + is-arrayish@0.3.2: + optional: true + + is-async-function@2.0.0: + dependencies: + has-tostringtag: 1.0.2 + + is-bigint@1.0.4: + dependencies: + has-bigints: 1.0.2 + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 - is-core-module@2.16.2: + is-boolean-object@1.1.2: dependencies: - hasown: 2.0.3 + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-bun-module@1.2.1: + dependencies: + semver: 7.6.3 + + is-callable@1.2.7: {} + + is-core-module@2.15.0: + dependencies: + hasown: 2.0.2 + + is-core-module@2.15.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.1: + dependencies: + is-typed-array: 1.1.13 + + is-date-object@1.0.5: + dependencies: + has-tostringtag: 1.0.2 + + is-docker@3.0.0: {} is-extglob@2.1.1: {} + is-finalizationregistry@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.0.10: + dependencies: + has-tostringtag: 1.0.2 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + is-number@7.0.0: {} - is-path-inside@4.0.0: {} - - jest-worker@27.5.1: + is-regex@1.1.4: dependencies: - '@types/node': 25.9.1 - merge-stream: 2.0.0 - supports-color: 8.1.1 + call-bind: 1.0.7 + has-tostringtag: 1.0.2 - jiti@1.21.7: {} + is-set@2.0.3: {} - jiti@2.7.0: {} + is-shared-array-buffer@1.0.3: + dependencies: + call-bind: 1.0.7 + + is-stream@3.0.0: {} + + is-string@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-symbol@1.0.4: + dependencies: + has-symbols: 1.0.3 + + is-typed-array@1.1.13: + dependencies: + which-typed-array: 1.1.15 + + is-weakmap@2.0.2: {} + + is-weakref@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-weakset@2.0.3: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + + is64bit@2.0.0: + dependencies: + system-architecture: 0.1.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + iterator.prototype@1.1.3: + dependencies: + define-properties: 1.2.1 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + reflect.getprototypeof: 1.0.6 + set-function-name: 2.0.2 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.6: {} + + jiti@2.4.2: {} joycon@3.1.1: {} - js-yaml@4.3.1: + js-tokens@4.0.0: {} + + js-yaml@4.1.0: dependencies: argparse: 2.0.1 - json-schema-traverse@1.0.0: {} + jsesc@3.1.0: {} - json-with-bigint@3.5.8: {} + json-buffer@3.0.1: {} - lightningcss-android-arm64@1.33.0: - optional: true + json-schema-traverse@0.4.1: {} - lightningcss-darwin-arm64@1.33.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} - lightningcss-darwin-x64@1.33.0: {} - - lightningcss-freebsd-x64@1.33.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.33.0: - optional: true - - lightningcss-linux-arm64-gnu@1.33.0: {} - - lightningcss-linux-arm64-musl@1.33.0: {} - - lightningcss-linux-x64-gnu@1.33.0: {} - - lightningcss-linux-x64-musl@1.33.0: {} - - lightningcss-win32-arm64-msvc@1.33.0: - optional: true - - lightningcss-win32-x64-msvc@1.33.0: {} - - lightningcss@1.33.0(patch_hash=1d4a8800d60d13d42887b88b3a86576df4b451670308145fb432ec8abbf40930): + json5@1.0.2: dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.33.0 - lightningcss-darwin-arm64: 1.33.0 - lightningcss-darwin-x64: 1.33.0 - lightningcss-freebsd-x64: 1.33.0 - lightningcss-linux-arm-gnueabihf: 1.33.0 - lightningcss-linux-arm64-gnu: 1.33.0 - lightningcss-linux-arm64-musl: 1.33.0 - lightningcss-linux-x64-gnu: 1.33.0 - lightningcss-linux-x64-musl: 1.33.0 - lightningcss-win32-arm64-msvc: 1.33.0 - lightningcss-win32-x64-msvc: 1.33.0 + minimist: 1.2.8 - lilconfig@3.1.3: {} + json5@2.2.3: {} + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.8 + array.prototype.flat: 1.3.2 + object.assign: 4.1.5 + object.values: 1.2.0 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-darwin-arm64@1.29.2: {} + + lightningcss-darwin-x64@1.29.2: {} + + lightningcss-freebsd-x64@1.29.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.29.2: + optional: true + + lightningcss-linux-arm64-gnu@1.29.2: {} + + lightningcss-linux-arm64-musl@1.29.2: {} + + lightningcss-linux-x64-gnu@1.29.2: {} + + lightningcss-linux-x64-musl@1.29.2: {} + + lightningcss-win32-arm64-msvc@1.29.2: + optional: true + + lightningcss-win32-x64-msvc@1.29.2: {} + + lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm): + dependencies: + detect-libc: 2.0.3 + optionalDependencies: + lightningcss-darwin-arm64: 1.29.2 + lightningcss-darwin-x64: 1.29.2 + lightningcss-freebsd-x64: 1.29.2 + lightningcss-linux-arm-gnueabihf: 1.29.2 + lightningcss-linux-arm64-gnu: 1.29.2 + lightningcss-linux-arm64-musl: 1.29.2 + lightningcss-linux-x64-gnu: 1.29.2 + lightningcss-linux-x64-musl: 1.29.2 + lightningcss-win32-arm64-msvc: 1.29.2 + lightningcss-win32-x64-msvc: 1.29.2 + + lilconfig@2.1.0: {} + + lilconfig@3.1.2: {} lines-and-columns@1.2.4: {} - listhen@1.10.1(@parcel/watcher@2.6.0(patch_hash=705ce75ccea54337110c4d7fe0a8b421658ea0c23e1a77c1fa890a86041179da)): + listhen@1.9.0: dependencies: - '@parcel/watcher-wasm': 2.5.6 - citty: 0.2.2 - consola: 3.4.2 - crossws: 0.4.10 - defu: 6.1.7 - get-port-please: 3.2.0 - h3: 1.15.11 + '@parcel/watcher': 2.5.0 + '@parcel/watcher-wasm': 2.5.0 + citty: 0.1.6 + clipboardy: 4.0.0 + consola: 3.2.3 + crossws: 0.3.1 + defu: 6.1.4 + get-port-please: 3.1.2 + h3: 1.15.1 http-shutdown: 1.2.2 - jiti: 2.7.0 - node-forge: 1.4.0 - pathe: 2.0.3 - std-env: 4.2.0 - tinyclip: 0.1.15 - ufo: 1.6.4 - untun: 0.2.2 - uqr: 0.1.3 - optionalDependencies: - '@parcel/watcher': 2.6.0(patch_hash=705ce75ccea54337110c4d7fe0a8b421658ea0c23e1a77c1fa890a86041179da) - transitivePeerDependencies: - - srvx + jiti: 2.4.2 + mlly: 1.7.3 + node-forge: 1.3.1 + pathe: 1.1.2 + std-env: 3.7.0 + ufo: 1.5.4 + untun: 0.1.3 + uqr: 0.1.2 load-tsconfig@0.2.5: {} - magic-string@0.30.21: + locate-path@6.0.0: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + p-locate: 5.0.0 - magic-string@1.1.0: + lodash.castarray@4.4.0: {} + + lodash.isplainobject@4.0.6: {} + + lodash.merge@4.6.2: {} + + lodash.sortby@4.7.0: {} + + loose-envify@1.4.0: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + js-tokens: 4.0.0 + + loupe@3.1.1: + dependencies: + get-func-name: 2.0.2 + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.11: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 merge-stream@2.0.0: {} merge2@1.4.1: {} - micro-memoize@5.1.1: + micro-memoize@4.1.3: {} + + micromatch@4.0.7: dependencies: - fast-equals: 5.4.0 - fast-stringify: 4.0.0 + braces: 3.0.3 + picomatch: 2.3.1 micromatch@4.0.8: dependencies: braces: 3.0.3 - picomatch: 2.3.2 + picomatch: 2.3.1 - mime-db@1.54.0: {} + mimic-fn@4.0.0: {} mini-svg-data-uri@1.4.4: {} - minimizer-webpack-plugin@5.6.1(esbuild@0.27.7)(postcss@8.5.25)(webpack@5.109.2(esbuild@0.27.7)(postcss@8.5.25)): + minimatch@3.1.2: dependencies: - '@jridgewell/trace-mapping': 0.3.31 - jest-worker: 27.5.1 - schema-utils: 4.3.3 - terser: 5.48.0 - webpack: 5.109.2(esbuild@0.27.7)(postcss@8.5.25) - optionalDependencies: - esbuild: 0.27.7 - postcss: 8.5.25 + brace-expansion: 1.1.11 - mlly@1.8.2: + minimatch@9.0.5: dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.4 + brace-expansion: 2.0.1 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + mlly@1.7.3: + dependencies: + acorn: 8.14.0 + pathe: 1.1.2 + pkg-types: 1.3.0 + ufo: 1.5.4 mri@1.2.0: {} - ms@2.1.3: {} + ms@2.1.2: {} - mute-stream@3.0.0: {} + ms@2.1.3: {} mz@2.7.0: dependencies: @@ -5288,133 +6735,238 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.12: {} + nanoid@3.3.7: {} - nanoid@3.3.16: {} + nanoid@3.3.8: {} - neo-async@2.6.2: {} + natural-compare@1.4.0: {} - next@16.2.7(@playwright/test@1.62.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + next@15.2.3(@playwright/test@1.51.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - '@next/env': 16.2.7 + '@next/env': 15.2.3 + '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.10.31 - caniuse-lite: 1.0.30001793 + busboy: 1.6.0 + caniuse-lite: 1.0.30001705 postcss: 8.4.31 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - styled-jsx: 5.1.6(react@19.2.7) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + styled-jsx: 5.1.6(react@19.0.0) optionalDependencies: - '@next/swc-darwin-arm64': 16.2.7 - '@next/swc-darwin-x64': 16.2.7 - '@next/swc-linux-arm64-gnu': 16.2.7 - '@next/swc-linux-arm64-musl': 16.2.7 - '@next/swc-linux-x64-gnu': 16.2.7 - '@next/swc-linux-x64-musl': 16.2.7 - '@next/swc-win32-arm64-msvc': 16.2.7 - '@next/swc-win32-x64-msvc': 16.2.7 - '@playwright/test': 1.62.1 - sharp: 0.34.5 + '@next/swc-darwin-arm64': 15.2.3 + '@next/swc-darwin-x64': 15.2.3 + '@next/swc-linux-arm64-gnu': 15.2.3 + '@next/swc-linux-arm64-musl': 15.2.3 + '@next/swc-linux-x64-gnu': 15.2.3 + '@next/swc-linux-x64-musl': 15.2.3 + '@next/swc-win32-arm64-msvc': 15.2.3 + '@next/swc-win32-x64-msvc': 15.2.3 + '@playwright/test': 1.51.0 + sharp: 0.33.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros node-addon-api@7.1.1: {} - node-addon-api@8.7.0: {} + node-addon-api@8.2.2: {} - node-forge@1.4.0: {} + node-addon-api@8.3.0: {} + + node-forge@1.3.1: {} + + node-gyp-build@4.8.2: {} node-gyp-build@4.8.4: {} - node-mock-http@1.0.4: {} + node-mock-http@1.0.0: {} - node-releases@2.0.46: {} + node-releases@2.0.18: {} + + node-releases@2.0.19: {} normalize-path@3.0.0: {} + normalize-range@0.1.2: {} + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + object-assign@4.1.1: {} object-hash@3.0.0: {} - obug@2.1.1: {} + object-inspect@1.13.2: {} - obug@2.1.4: {} + object-keys@1.1.1: {} + + object.assign@4.1.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + + object.entries@1.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + + object.values@1.2.0: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} package-up@5.0.0: dependencies: - find-up-simple: 1.0.1 + find-up-simple: 1.0.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} path-parse@1.0.7: {} - pathe@2.0.3: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-type@6.0.0: {} + + pathe@1.1.2: {} + + pathval@2.0.0: {} picocolors@1.1.1: {} - picomatch@2.3.2: {} + picomatch@2.3.1: {} - picomatch@4.0.4: {} - - picomatch@4.0.5: {} + picomatch@4.0.2: {} pify@2.3.0: {} - pirates@4.0.7: {} + pirates@4.0.6: {} - pkg-types@1.3.1: + pkg-types@1.3.0: dependencies: confbox: 0.1.8 - mlly: 1.8.2 - pathe: 2.0.3 + mlly: 1.7.3 + pathe: 1.1.2 - playwright-core@1.62.1: {} + playwright-core@1.51.0: {} - playwright@1.62.1: + playwright@1.51.0: dependencies: - playwright-core: 1.62.1 + playwright-core: 1.51.0 optionalDependencies: fsevents: 2.3.2 - postcss-import@15.1.0(postcss@8.5.16): + possible-typed-array-names@1.0.0: {} + + postcss-import@15.1.0(postcss@8.5.3): dependencies: - postcss: 8.5.16 + postcss: 8.5.3 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.12 + resolve: 1.22.8 - postcss-import@16.1.1(postcss@8.5.25): + postcss-import@16.1.0(postcss@8.4.41): dependencies: - postcss: 8.5.25 + postcss: 8.4.41 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.12 + resolve: 1.22.8 - postcss-js@4.1.0(postcss@8.5.16): + postcss-import@16.1.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.8 + + postcss-js@4.0.1(postcss@8.5.3): dependencies: camelcase-css: 2.0.1 - postcss: 8.5.16 + postcss: 8.5.3 - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.16)(yaml@2.9.0): + postcss-load-config@4.0.2(postcss@8.5.3): dependencies: - lilconfig: 3.1.3 + lilconfig: 3.1.2 + yaml: 2.6.0 optionalDependencies: - jiti: 1.21.7 - postcss: 8.5.16 - yaml: 2.9.0 + postcss: 8.5.3 - postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.25)(yaml@2.9.0): + postcss-load-config@6.0.1(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.1)(yaml@2.6.0): dependencies: - lilconfig: 3.1.3 + lilconfig: 3.1.2 optionalDependencies: - jiti: 2.7.0 - postcss: 8.5.25 - yaml: 2.9.0 + jiti: 2.4.2 + postcss: 8.5.3 + tsx: 4.19.1 + yaml: 2.6.0 - postcss-nested@6.2.0(postcss@8.5.16): + postcss-media-minmax@5.0.0(postcss@8.5.3): dependencies: - postcss: 8.5.16 + postcss: 8.5.3 + + postcss-nested@6.2.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 postcss-selector-parser: 6.1.2 + postcss-nesting@13.0.1(postcss@8.5.3): + dependencies: + '@csstools/selector-resolve-nested': 3.0.0(postcss-selector-parser@7.1.0) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) + postcss: 8.5.3 + postcss-selector-parser: 7.1.0 + postcss-selector-parser@6.0.10: dependencies: cssesc: 3.0.0 @@ -5425,7 +6977,7 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-selector-parser@7.1.4: + postcss-selector-parser@7.1.0: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 @@ -5434,57 +6986,64 @@ snapshots: postcss@8.4.31: dependencies: - nanoid: 3.3.12 + nanoid: 3.3.8 picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.16: + postcss@8.4.41: dependencies: - nanoid: 3.3.12 + nanoid: 3.3.7 + picocolors: 1.1.1 + source-map-js: 1.2.0 + + postcss@8.5.3: + dependencies: + nanoid: 3.3.8 picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.25: - dependencies: - nanoid: 3.3.16 - picocolors: 1.1.1 - source-map-js: 1.2.1 + prelude-ls@1.2.1: {} - prettier-plugin-embed@0.5.1: + prettier-plugin-embed@0.5.0: dependencies: - '@types/estree': 1.0.9 - dedent: 1.7.2 - micro-memoize: 5.1.1 + '@types/estree': 1.0.6 + dedent: 1.5.3 + micro-memoize: 4.1.3 package-up: 5.0.0 tiny-jsonc: 1.0.2 - type-fest: 5.6.0 + type-fest: 4.36.0 transitivePeerDependencies: - babel-plugin-macros - prettier-plugin-organize-imports@4.3.0(prettier@3.9.6)(typescript@5.9.3): + prettier-plugin-organize-imports@4.0.0(prettier@3.5.0)(typescript@5.5.4): dependencies: - prettier: 3.9.6 - typescript: 5.9.3 + prettier: 3.5.0 + typescript: 5.5.4 - prettier@3.9.6: {} + prettier@3.5.0: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + punycode@2.3.1: {} queue-microtask@1.2.3: {} radix3@1.1.2: {} - react-dom@19.2.6(react@19.2.6): + react-dom@19.0.0(react@19.0.0): dependencies: - react: 19.2.6 - scheduler: 0.27.0 + react: 19.0.0 + scheduler: 0.25.0 - react-dom@19.2.7(react@19.2.7): - dependencies: - react: 19.2.7 - scheduler: 0.27.0 + react-is@16.13.1: {} - react@19.2.6: {} + react-refresh@0.14.2: {} - react@19.2.7: {} + react@19.0.0: {} read-cache@1.0.0: dependencies: @@ -5492,171 +7051,280 @@ snapshots: readdirp@3.6.0: dependencies: - picomatch: 2.3.2 + picomatch: 2.3.1 - readdirp@4.1.2: {} + readdirp@4.1.1: {} - require-from-string@2.0.2: {} + reflect.getprototypeof@1.0.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + globalthis: 1.0.4 + which-builtin-type: 1.1.4 + + regexp.prototype.flags@1.5.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-errors: 1.3.0 + set-function-name: 2.0.2 + + resolve-from@4.0.0: {} resolve-from@5.0.0: {} - resolve@1.22.12: + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.8: dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.2 + is-core-module: 2.15.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - reusify@1.1.0: {} - - rolldown@1.2.1: + resolve@2.0.0-next.5: dependencies: - '@oxc-project/types': 0.142.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.2.1 - '@rolldown/binding-darwin-arm64': 1.2.1 - '@rolldown/binding-darwin-x64': 1.2.1 - '@rolldown/binding-freebsd-x64': 1.2.1 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.1 - '@rolldown/binding-linux-arm64-gnu': 1.2.1 - '@rolldown/binding-linux-arm64-musl': 1.2.1 - '@rolldown/binding-linux-ppc64-gnu': 1.2.1 - '@rolldown/binding-linux-s390x-gnu': 1.2.1 - '@rolldown/binding-linux-x64-gnu': 1.2.1 - '@rolldown/binding-linux-x64-musl': 1.2.1 - '@rolldown/binding-openharmony-arm64': 1.2.1 - '@rolldown/binding-wasm32-wasi': 1.2.1 - '@rolldown/binding-win32-arm64-msvc': 1.2.1 - '@rolldown/binding-win32-x64-msvc': 1.2.1 + is-core-module: 2.15.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 - rollup@4.60.4: + reusify@1.0.4: {} + + rollup@4.34.9: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.4 - '@rollup/rollup-android-arm64': 4.60.4 - '@rollup/rollup-darwin-arm64': 4.60.4 - '@rollup/rollup-darwin-x64': 4.60.4 - '@rollup/rollup-freebsd-arm64': 4.60.4 - '@rollup/rollup-freebsd-x64': 4.60.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 - '@rollup/rollup-linux-arm-musleabihf': 4.60.4 - '@rollup/rollup-linux-arm64-gnu': 4.60.4 - '@rollup/rollup-linux-arm64-musl': 4.60.4 - '@rollup/rollup-linux-loong64-gnu': 4.60.4 - '@rollup/rollup-linux-loong64-musl': 4.60.4 - '@rollup/rollup-linux-ppc64-gnu': 4.60.4 - '@rollup/rollup-linux-ppc64-musl': 4.60.4 - '@rollup/rollup-linux-riscv64-gnu': 4.60.4 - '@rollup/rollup-linux-riscv64-musl': 4.60.4 - '@rollup/rollup-linux-s390x-gnu': 4.60.4 - '@rollup/rollup-linux-x64-gnu': 4.60.4 - '@rollup/rollup-linux-x64-musl': 4.60.4 - '@rollup/rollup-openbsd-x64': 4.60.4 - '@rollup/rollup-openharmony-arm64': 4.60.4 - '@rollup/rollup-win32-arm64-msvc': 4.60.4 - '@rollup/rollup-win32-ia32-msvc': 4.60.4 - '@rollup/rollup-win32-x64-gnu': 4.60.4 - '@rollup/rollup-win32-x64-msvc': 4.60.4 + '@rollup/rollup-android-arm-eabi': 4.34.9 + '@rollup/rollup-android-arm64': 4.34.9 + '@rollup/rollup-darwin-arm64': 4.34.9 + '@rollup/rollup-darwin-x64': 4.34.9 + '@rollup/rollup-freebsd-arm64': 4.34.9 + '@rollup/rollup-freebsd-x64': 4.34.9 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.9 + '@rollup/rollup-linux-arm-musleabihf': 4.34.9 + '@rollup/rollup-linux-arm64-gnu': 4.34.9 + '@rollup/rollup-linux-arm64-musl': 4.34.9 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.9 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.9 + '@rollup/rollup-linux-riscv64-gnu': 4.34.9 + '@rollup/rollup-linux-s390x-gnu': 4.34.9 + '@rollup/rollup-linux-x64-gnu': 4.34.9 + '@rollup/rollup-linux-x64-musl': 4.34.9 + '@rollup/rollup-win32-arm64-msvc': 4.34.9 + '@rollup/rollup-win32-ia32-msvc': 4.34.9 + '@rollup/rollup-win32-x64-msvc': 4.34.9 fsevents: 2.3.3 run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - safer-buffer@2.1.2: {} - - scheduler@0.27.0: {} - - schema-utils@4.3.3: + safe-array-concat@1.1.2: dependencies: - '@types/json-schema': 7.0.15 - ajv: 8.20.0 - ajv-formats: 2.1.1(ajv@8.20.0) - ajv-keywords: 5.1.0(ajv@8.20.0) + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + isarray: 2.0.5 - semver@7.8.5: {} - - sharp@0.34.5: + safe-regex-test@1.0.3: dependencies: - '@img/colour': 1.1.0 - detect-libc: 2.1.2 - semver: 7.8.5 + call-bind: 1.0.7 + es-errors: 1.3.0 + is-regex: 1.1.4 + + scheduler@0.25.0: {} + + semver@6.3.1: {} + + semver@7.6.3: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.0.3 + semver: 7.6.3 optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 optional: true + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel@1.0.6: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.2 + siginfo@2.0.0: {} signal-exit@4.1.0: {} + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + optional: true + slash@5.1.0: {} + source-map-js@1.2.0: {} + source-map-js@1.2.1: {} source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 + optional: true - source-map@0.6.1: {} + source-map@0.6.1: + optional: true - source-map@0.7.6: {} + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 stackback@0.0.2: {} - std-env@4.1.0: {} + std-env@3.7.0: {} - std-env@4.2.0: {} + streamsearch@1.1.0: {} - styled-jsx@5.1.6(react@19.2.7): + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + + string.prototype.matchall@4.0.11: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.7 + regexp.prototype.flags: 1.5.2 + set-function-name: 2.0.2 + side-channel: 1.0.6 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.23.3 + + string.prototype.trim@1.2.9: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + string.prototype.trimend@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.0.1 + + strip-bom@3.0.0: {} + + strip-final-newline@3.0.0: {} + + strip-json-comments@3.1.1: {} + + styled-jsx@5.1.6(react@19.0.0): dependencies: client-only: 0.0.1 - react: 19.2.7 + react: 19.0.0 - sucrase@3.35.1: + sucrase@3.35.0: dependencies: - '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/gen-mapping': 0.3.5 commander: 4.1.1 + glob: 10.4.5 lines-and-columns: 1.2.4 mz: 2.7.0 - pirates: 4.0.7 - tinyglobby: 0.2.16 + pirates: 4.0.6 ts-interface-checker: 0.1.13 - supports-color@8.1.1: + supports-color@7.2.0: dependencies: has-flag: 4.0.0 supports-preserve-symlinks-flag@1.0.0: {} - tagged-tag@1.0.0: {} + system-architecture@0.1.0: {} - tailwindcss@3.4.19(yaml@2.9.0): + tailwindcss@3.4.14: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -5666,32 +7334,32 @@ snapshots: fast-glob: 3.3.3 glob-parent: 6.0.2 is-glob: 4.0.3 - jiti: 1.21.7 - lilconfig: 3.1.3 - micromatch: 4.0.8 + jiti: 1.21.6 + lilconfig: 2.1.0 + micromatch: 4.0.7 normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.16 - postcss-import: 15.1.0(postcss@8.5.16) - postcss-js: 4.1.0(postcss@8.5.16) - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.16)(yaml@2.9.0) - postcss-nested: 6.2.0(postcss@8.5.16) + postcss: 8.5.3 + postcss-import: 15.1.0(postcss@8.5.3) + postcss-js: 4.0.1(postcss@8.5.3) + postcss-load-config: 4.0.2(postcss@8.5.3) + postcss-nested: 6.2.0(postcss@8.5.3) postcss-selector-parser: 6.1.2 - resolve: 1.22.12 - sucrase: 3.35.1 + resolve: 1.22.8 + sucrase: 3.35.0 transitivePeerDependencies: - - tsx - - yaml + - ts-node - tapable@2.3.3: {} + tapable@2.2.1: {} - terser@5.48.0: + terser@5.31.6: dependencies: - '@jridgewell/source-map': 0.3.11 - acorn: 8.16.0 + '@jridgewell/source-map': 0.3.6 + acorn: 8.14.0 commander: 2.20.3 source-map-support: 0.5.21 + optional: true thenify-all@1.6.0: dependencies: @@ -5705,227 +7373,360 @@ snapshots: tinybench@2.9.0: {} - tinyclip@0.1.15: {} - tinyexec@0.3.2: {} - tinyexec@1.1.2: {} - - tinyglobby@0.2.16: + tinyglobby@0.2.12: dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + fdir: 6.4.3(picomatch@4.0.2) + picomatch: 4.0.2 - tinyglobby@0.2.17: - dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 + tinypool@1.0.0: {} - tinyrainbow@3.1.0: {} + tinyrainbow@1.2.0: {} + + tinyspy@3.0.0: {} to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} - tree-sitter-javascript@0.23.1(tree-sitter@0.25.1): + tree-sitter-javascript@0.23.1(tree-sitter@0.22.4): dependencies: - node-addon-api: 8.7.0 - node-gyp-build: 4.8.4 + node-addon-api: 8.2.2 + node-gyp-build: 4.8.2 optionalDependencies: - tree-sitter: 0.25.1 + tree-sitter: 0.22.4 - tree-sitter-typescript@0.23.2(tree-sitter@0.25.1): + tree-sitter-typescript@0.23.2(tree-sitter@0.22.4): dependencies: - node-addon-api: 8.7.0 - node-gyp-build: 4.8.4 - tree-sitter-javascript: 0.23.1(tree-sitter@0.25.1) + node-addon-api: 8.2.2 + node-gyp-build: 4.8.2 + tree-sitter-javascript: 0.23.1(tree-sitter@0.22.4) optionalDependencies: - tree-sitter: 0.25.1 + tree-sitter: 0.22.4 - tree-sitter@0.25.1: + tree-sitter@0.22.4: dependencies: - node-addon-api: 8.7.0 + node-addon-api: 8.3.0 node-gyp-build: 4.8.4 + ts-api-utils@1.3.0(typescript@5.5.4): + dependencies: + typescript: 5.5.4 + + ts-api-utils@1.3.0(typescript@5.6.3): + dependencies: + typescript: 5.6.3 + ts-interface-checker@0.1.13: {} - tslib@2.8.1: {} - - tsup@8.5.1(jiti@2.7.0)(postcss@8.5.25)(typescript@5.9.3)(yaml@2.9.0): + tsconfig-paths@3.15.0: dependencies: - bundle-require: 5.1.0(esbuild@0.27.7) + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.0: {} + + tsup@8.4.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.1)(typescript@5.5.4)(yaml@2.6.0): + dependencies: + bundle-require: 5.1.0(esbuild@0.25.0) cac: 6.7.14 chokidar: 4.0.3 - consola: 3.4.2 - debug: 4.4.3 - esbuild: 0.27.7 - fix-dts-default-cjs-exports: 1.0.1 + consola: 3.4.0 + debug: 4.4.0 + esbuild: 0.25.0 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.25)(yaml@2.9.0) + postcss-load-config: 6.0.1(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.1)(yaml@2.6.0) resolve-from: 5.0.0 - rollup: 4.60.4 - source-map: 0.7.6 - sucrase: 3.35.1 + rollup: 4.34.9 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 tinyexec: 0.3.2 - tinyglobby: 0.2.16 + tinyglobby: 0.2.12 tree-kill: 1.2.2 optionalDependencies: - postcss: 8.5.25 - typescript: 5.9.3 + postcss: 8.5.3 + typescript: 5.5.4 transitivePeerDependencies: - jiti - supports-color - tsx - yaml - turbo@2.10.8: - optionalDependencies: - '@turbo/darwin-64': 2.10.8 - '@turbo/darwin-arm64': 2.10.8 - '@turbo/linux-64': 2.10.8 - '@turbo/linux-arm64': 2.10.8 - '@turbo/windows-64': 2.10.8 - '@turbo/windows-arm64': 2.10.8 - - typanion@3.14.0: {} - - type-fest@5.6.0: + tsx@4.19.1: dependencies: - tagged-tag: 1.0.0 + esbuild: 0.23.1 + get-tsconfig: 4.8.1 + optionalDependencies: + fsevents: 2.3.3 + optional: true - typescript@5.9.3: {} + turbo-darwin-64@2.4.4: + optional: true - typescript@6.0.3: {} + turbo-darwin-arm64@2.4.4: + optional: true - ufo@1.6.4: {} + turbo-linux-64@2.4.4: + optional: true + + turbo-linux-arm64@2.4.4: + optional: true + + turbo-windows-64@2.4.4: + optional: true + + turbo-windows-arm64@2.4.4: + optional: true + + turbo@2.4.4: + optionalDependencies: + turbo-darwin-64: 2.4.4 + turbo-darwin-arm64: 2.4.4 + turbo-linux-64: 2.4.4 + turbo-linux-arm64: 2.4.4 + turbo-windows-64: 2.4.4 + turbo-windows-arm64: 2.4.4 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@4.36.0: {} + + typed-array-buffer@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-typed-array: 1.1.13 + + typed-array-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-byte-offset@1.0.2: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-length@1.0.6: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + possible-typed-array-names: 1.0.0 + + typescript@5.5.4: {} + + typescript@5.6.3: {} + + ufo@1.5.4: {} + + unbox-primitive@1.0.2: + dependencies: + call-bind: 1.0.7 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 uncrypto@0.1.3: {} - undici-types@6.21.0: {} + undici-types@5.26.5: {} - undici-types@7.24.6: {} + unicorn-magic@0.3.0: {} - unicorn-magic@0.4.0: {} - - universal-user-agent@7.0.3: {} - - untun@0.2.2: {} - - update-browserslist-db@1.2.3(browserslist@4.28.2): + untun@0.1.3: dependencies: - browserslist: 4.28.2 + citty: 0.1.6 + consola: 3.2.3 + pathe: 1.1.2 + + update-browserslist-db@1.1.1(browserslist@4.24.2): + dependencies: + browserslist: 4.24.2 escalade: 3.2.0 picocolors: 1.1.1 - uqr@0.1.3: {} + update-browserslist-db@1.1.1(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + uqr@0.1.2: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 util-deprecate@1.0.2: {} - vite@8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0): + vite-node@2.0.5(@types/node@20.14.13)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6): dependencies: - lightningcss: 1.33.0(patch_hash=1d4a8800d60d13d42887b88b3a86576df4b451670308145fb432ec8abbf40930) - picomatch: 4.0.5 - postcss: 8.5.25 - rolldown: 1.2.1 - tinyglobby: 0.2.17 - optionalDependencies: - '@types/node': 22.20.1 - esbuild: 0.27.7 - fsevents: 2.3.3 - jiti: 2.7.0 - terser: 5.48.0 - yaml: 2.9.0 + cac: 6.7.14 + debug: 4.4.0 + pathe: 1.1.2 + tinyrainbow: 1.2.0 + vite: 5.4.0(@types/node@20.14.13)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser - vite@8.2.0(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0): + vite@5.4.0(@types/node@20.14.13)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6): dependencies: - lightningcss: 1.33.0(patch_hash=1d4a8800d60d13d42887b88b3a86576df4b451670308145fb432ec8abbf40930) - picomatch: 4.0.5 - postcss: 8.5.25 - rolldown: 1.2.1 - tinyglobby: 0.2.17 + esbuild: 0.21.5 + postcss: 8.5.3 + rollup: 4.34.9 optionalDependencies: - '@types/node': 25.9.1 - esbuild: 0.27.7 + '@types/node': 20.14.13 fsevents: 2.3.3 - jiti: 2.7.0 - terser: 5.48.0 - yaml: 2.9.0 + lightningcss: 1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm) + terser: 5.31.6 - vitest@4.1.10(@types/node@22.20.1)(vite@8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)): + vite@6.0.0(@types/node@20.14.13)(jiti@2.4.2)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6)(tsx@4.19.1)(yaml@2.6.0): dependencies: - '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(vite@8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.10 - '@vitest/runner': 4.1.10 - '@vitest/snapshot': 4.1.10 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 + esbuild: 0.24.0 + postcss: 8.5.3 + rollup: 4.34.9 + optionalDependencies: + '@types/node': 20.14.13 + fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm) + terser: 5.31.6 + tsx: 4.19.1 + yaml: 2.6.0 + + vitest@2.0.5(@types/node@20.14.13)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6): + dependencies: + '@ampproject/remapping': 2.3.0 + '@vitest/expect': 2.0.5 + '@vitest/pretty-format': 2.0.5 + '@vitest/runner': 2.0.5 + '@vitest/snapshot': 2.0.5 + '@vitest/spy': 2.0.5 + '@vitest/utils': 2.0.5 + chai: 5.1.1 + debug: 4.3.6 + execa: 8.0.1 + magic-string: 0.30.11 + pathe: 1.1.2 + std-env: 3.7.0 tinybench: 2.9.0 - tinyexec: 1.1.2 - tinyglobby: 0.2.17 - tinyrainbow: 3.1.0 - vite: 8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) + tinypool: 1.0.0 + tinyrainbow: 1.2.0 + vite: 5.4.0(@types/node@20.14.13)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6) + vite-node: 2.0.5(@types/node@20.14.13)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 20.14.13 transitivePeerDependencies: - - msw - - watchpack@2.5.2: - dependencies: - graceful-fs: 4.2.11 - - webpack-sources@3.5.1: {} - - webpack@5.109.2(esbuild@0.27.7)(postcss@8.5.25): - dependencies: - '@types/estree': 1.0.9 - '@types/json-schema': 7.0.15 - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/wasm-edit': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.16.0 - browserslist: 4.28.2 - chrome-trace-event: 1.0.4 - enhanced-resolve: 5.24.5 - es-module-lexer: 2.1.0 - eslint-scope: 5.1.1 - events: 3.3.0 - graceful-fs: 4.2.11 - mime-db: 1.54.0 - minimizer-webpack-plugin: 5.6.1(esbuild@0.27.7)(postcss@8.5.25)(webpack@5.109.2(esbuild@0.27.7)(postcss@8.5.25)) - neo-async: 2.6.2 - schema-utils: 4.3.3 - tapable: 2.3.3 - watchpack: 2.5.2 - webpack-sources: 3.5.1 - transitivePeerDependencies: - - '@minify-html/node' - - '@swc/core' - - '@swc/css' - - '@swc/html' - - clean-css - - cssnano - - csso - - esbuild - - html-minifier-terser + - less - lightningcss - - postcss - - uglify-js + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + webidl-conversions@4.0.2: {} + + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + + which-boxed-primitive@1.0.2: + 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.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.2 + which-typed-array: 1.1.15 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.3 + + which-typed-array@1.1.15: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - yaml@2.9.0: {} + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + yallist@3.1.1: {} + + yaml@2.6.0: {} + + yocto-queue@0.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index bcddef126..98329b69e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,50 +5,15 @@ packages: - 'playgrounds/*' - 'integrations' -patchedDependencies: - '@napi-rs/cli@3.7.4': patches/@napi-rs__cli@3.7.4.patch - '@parcel/watcher@2.6.0': patches/@parcel__watcher@2.6.0.patch - lightningcss@1.33.0: patches/lightningcss@1.33.0.patch - catalog: - '@types/node': 22.20.1 - dedent: 1.7.2 - enhanced-resolve: ^5.24.5 - lightningcss-darwin-arm64: 1.33.0 - lightningcss-darwin-x64: 1.33.0 - lightningcss-linux-arm64-gnu: 1.33.0 - lightningcss-linux-arm64-musl: 1.33.0 - lightningcss-linux-x64-gnu: 1.33.0 - lightningcss-linux-x64-musl: 1.33.0 - lightningcss-win32-x64-msvc: 1.33.0 - lightningcss: 1.33.0 - '@parcel/watcher-darwin-arm64': 2.6.0 - '@parcel/watcher-darwin-x64': 2.6.0 - '@parcel/watcher-linux-arm64-glibc': 2.6.0 - '@parcel/watcher-linux-arm64-musl': 2.6.0 - '@parcel/watcher-linux-x64-glibc': 2.6.0 - '@parcel/watcher-linux-x64-musl': 2.6.0 - '@parcel/watcher-win32-x64': 2.6.0 - '@parcel/watcher': 2.6.0 - postcss: ^8.5.25 - prettier: 3.9.6 - vite: ^8.2.0 - webpack: ^5.109.2 - -onlyBuiltDependencies: - - '@parcel/watcher' - - bun - - esbuild - - sharp - - tree-sitter - - tree-sitter-javascript - - tree-sitter-typescript - -allowBuilds: - '@parcel/watcher': true - bun: true - esbuild: true - sharp: true - tree-sitter: true - tree-sitter-javascript: true - tree-sitter-typescript: true + '@types/node': ^20.14.8 + prettier: 3.5.0 + vite: ^6.0.0 + lightningcss: 1.29.2 + lightningcss-darwin-arm64: 1.29.2 + lightningcss-darwin-x64: 1.29.2 + lightningcss-linux-arm64-gnu: 1.29.2 + lightningcss-linux-arm64-musl: 1.29.2 + lightningcss-linux-x64-gnu: 1.29.2 + lightningcss-linux-x64-musl: 1.29.2 + lightningcss-win32-x64-msvc: 1.29.2 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index f92020c65..aaba1588e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.95.0" +channel = "1.85.0" profile = "default" diff --git a/scripts/pack-packages.mjs b/scripts/pack-packages.mjs index 2b0b9f15d..3bcf23bec 100644 --- a/scripts/pack-packages.mjs +++ b/scripts/pack-packages.mjs @@ -21,25 +21,30 @@ let workspaces = new Map() for (let path of paths) { let pkg = await fs.readFile(path, 'utf8').then(JSON.parse) if (pkg.private) continue - workspaces.set(pkg.name, { - version: pkg.version ?? '', - dir: dirname(path), - hasBundledDependencies: pkg.bundledDependencies?.length > 0 ?? false, - }) + workspaces.set(pkg.name, { version: pkg.version ?? '', dir: dirname(path) }) +} + +// Move napi artifacts into sub packages +const tailwindcssOxideRoot = path.join(root, 'crates', 'node') +for (let file of await fs.readdir(tailwindcssOxideRoot)) { + if (file.startsWith('tailwindcss-oxide.') && file.endsWith('.node')) { + let target = file.split('.')[1] + await fs.cp( + path.join(tailwindcssOxideRoot, file), + path.join(tailwindcssOxideRoot, 'npm', target, file), + ) + } } // Clean dist folder await fs.rm(path.join(root, 'dist'), { recursive: true, force: true }) Promise.all( - [...workspaces.entries()].map(async ([name, { dir, hasBundledDependencies }]) => { + [...workspaces.entries()].map(async ([name, { version, dir }]) => { function pack() { return new Promise((resolve) => { - // `bundledDependencies` can only be packed with the hoisted node linker - // since pnpm v10. We opt into it for those packages only, instead of - // changing the node linker for the entire workspace. exec( - `pnpm pack --pack-gzip-level=0 --pack-destination="${path.join(root, 'dist').replace(/\\/g, '\\\\')}"${hasBundledDependencies ? ' --config.node-linker=hoisted' : ''}`, + `pnpm pack --pack-gzip-level=0 --pack-destination="${path.join(root, 'dist').replace(/\\/g, '\\\\')}"`, { cwd: dir }, (err, stdout, stderr) => { if (err) { diff --git a/scripts/release-notes.mjs b/scripts/release-notes.mjs index bb703c07b..289cb1d96 100644 --- a/scripts/release-notes.mjs +++ b/scripts/release-notes.mjs @@ -1,5 +1,5 @@ // Given a version, figure out what the release notes are so that we can use this to pre-fill the -// release notes on a GitHub release for the current version. +// relase notes on a GitHub release for the current version. import * as fs from 'node:fs/promises' import * as path from 'node:path' diff --git a/scripts/version-packages.mjs b/scripts/version-packages.mjs index 1641d5404..36f412cc3 100644 --- a/scripts/version-packages.mjs +++ b/scripts/version-packages.mjs @@ -28,9 +28,8 @@ const syncedWorkspaces = new Map([ 'crates/node/npm/linux-arm64-musl', 'crates/node/npm/linux-x64-gnu', 'crates/node/npm/linux-x64-musl', - 'crates/node/npm/wasm32-wasi', - 'crates/node/npm/win32-arm64-msvc', 'crates/node/npm/win32-x64-msvc', + 'crates/node/npm/win32-arm64-msvc', ], ], ['@tailwindcss/cli', ['packages/@tailwindcss-standalone']], diff --git a/turbo.json b/turbo.json index c48410b29..87eba2732 100644 --- a/turbo.json +++ b/turbo.json @@ -1,5 +1,5 @@ { - "$schema": "https://v2-10-8.turborepo.dev/schema.json", + "$schema": "https://turbo.build/schema.json", "ui": "tui", "tasks": { "@tailwindcss/oxide#build": { diff --git a/vitest.config.mts b/vitest.config.mts deleted file mode 100644 index 3ef999be2..000000000 --- a/vitest.config.mts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - projects: ['./packages/*', '!./packages/tsconfig.base.json'], - exclude: ['**/*.spec.?(c|m)[jt]s?(x)', 'integrations/**/*'], - }, -}) 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/*']