diff --git a/.changeset/onrest-cancelled-before-first-frame.md b/.changeset/onrest-cancelled-before-first-frame.md deleted file mode 100644 index f9406207aa..0000000000 --- a/.changeset/onrest-cancelled-before-first-frame.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@react-spring/core': patch ---- - -Fire `onRest` when an animation is cancelled before its first frame. `SpringValue._stop` gated `onRest` on `anim.changed`, which only becomes `true` once a frame has rendered. When two `start()` calls happened within a single tick and the second returned the spring to its current value, the engine stopped the animation before any frame fired, so `onRest` was silently dropped. The same gap affected `set()` and `stop()` called synchronously after `start()`. Closes #1802. diff --git a/.changeset/preserve-string-precision.md b/.changeset/preserve-string-precision.md deleted file mode 100644 index 615b21ff96..0000000000 --- a/.changeset/preserve-string-precision.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@react-spring/shared': patch ---- - -Preserve decimal precision when interpolating between stringified numbers. Previously, `useSpring({ from: '0.00', to: '1.50' })` would render `'0'` at rest and lose precision mid-tween. The string interpolator now returns each keyframe verbatim when the input lands exactly on a range value, and pads mid-animation results to the shared decimal count of the keyframes (when every keyframe has the same non-zero fractional length). Closes #1461. diff --git a/.changeset/reverse-transition-trail.md b/.changeset/reverse-transition-trail.md deleted file mode 100644 index 3463128888..0000000000 --- a/.changeset/reverse-transition-trail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@react-spring/core': minor ---- - -Add a `reverse` prop to `useTransition` that flips the order in which `trail` delays are assigned to transitioning items. Toggle it with caller state (e.g. `reverse: !open`) to make items animate in forward on `enter` and backward on `leave`. Render order is untouched — use `sort` for that. Closes #1794. diff --git a/.changeset/stop-superseded-loop-chains.md b/.changeset/stop-superseded-loop-chains.md deleted file mode 100644 index fede65663d..0000000000 --- a/.changeset/stop-superseded-loop-chains.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@react-spring/core': patch ---- - -Stop looping when a re-render flips `loop` to a falsy value. Previously a declarative update like `useSpring({ to, loop })` would keep looping even after `loop` toggled to `false`, because the in-flight loop chain captured its own `loop` value and kept scheduling iterations in parallel with the new update. The Controller now tags each loop chain with a generation token and exits when a newer external update has superseded it. Closes #1193. diff --git a/.changeset/transition-phase-leave-render.md b/.changeset/transition-phase-leave-render.md deleted file mode 100644 index b161924c0c..0000000000 --- a/.changeset/transition-phase-leave-render.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@react-spring/core': patch ---- - -Expose `phase === 'leave'` to the `useTransition` render function during the render that runs the leave animation. Previously `t.phase` was only updated to the new phase in a layout effect after render, so the render fn always saw the previous phase (`'enter'`) while a leaving item animated out — and by the time a follow-up render could surface `'leave'`, the transition had expired and been pruned. The render fn now receives a state whose `phase` matches the upcoming animation, so consumers can reliably branch on `state.phase === 'leave'`. Closes #1654. diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..4ea4543afc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,23 @@ +.git +.github +.husky +.claude +.vercel +.vscode +.idea +.DS_Store + +# build outputs and caches (rebuilt inside container) +**/node_modules +**/.turbo +**/build +**/dist +**/.cache +**/.react-router +**/__screenshots__ +**/coverage +**/*.log + +# editor / OS noise +*.tgz +Thumbs.db diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml new file mode 100644 index 0000000000..689c72a743 --- /dev/null +++ b/.github/workflows/docs-deploy.yml @@ -0,0 +1,109 @@ +name: Docs deploy + +on: + push: + branches: [next] + paths: + - 'docs/**' + - 'Dockerfile' + - '.dockerignore' + - '.github/workflows/docs-deploy.yml' + pull_request: + types: [opened, synchronize, reopened, closed] + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + id-token: write + +env: + GCP_PROJECT_NUMBER: 204644970562 + CLOUD_RUN_SERVICE: react-spring + CLOUD_RUN_REGION: europe-west1 + +jobs: + pr-changes: + if: github.event_name == 'pull_request' && github.event.action != 'closed' + runs-on: ubuntu-latest + outputs: + docs: ${{ steps.filter.outputs.docs }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + docs: + - 'docs/**' + - 'Dockerfile' + - '.dockerignore' + - '.github/workflows/docs-deploy.yml' + + deploy-prod: + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.WIF_PROVIDER }} + service_account: ${{ secrets.GCP_SA_EMAIL }} + - uses: google-github-actions/setup-gcloud@v2 + - name: Deploy to Cloud Run (prod) + run: | + gcloud run deploy "$CLOUD_RUN_SERVICE" \ + --source . \ + --region "$CLOUD_RUN_REGION" \ + --quiet + + deploy-preview: + needs: pr-changes + if: | + github.event_name == 'pull_request' && + github.event.action != 'closed' && + needs.pr-changes.outputs.docs == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.WIF_PROVIDER }} + service_account: ${{ secrets.GCP_SA_EMAIL }} + - uses: google-github-actions/setup-gcloud@v2 + - name: Deploy preview revision + id: deploy + run: | + TAG="pr-${{ github.event.number }}" + gcloud run deploy "$CLOUD_RUN_SERVICE" \ + --source . \ + --region "$CLOUD_RUN_REGION" \ + --tag "$TAG" \ + --no-traffic \ + --quiet + URL="https://${TAG}---${CLOUD_RUN_SERVICE}-${GCP_PROJECT_NUMBER}.${CLOUD_RUN_REGION}.run.app" + echo "url=$URL" >> "$GITHUB_OUTPUT" + - uses: marocchino/sticky-pull-request-comment@v2 + with: + header: docs-preview + message: | + **Docs preview:** ${{ steps.deploy.outputs.url }} + + Deployed at `${{ github.sha }}`. Updates on every push. + + cleanup-preview: + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: ubuntu-latest + steps: + - uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.WIF_PROVIDER }} + service_account: ${{ secrets.GCP_SA_EMAIL }} + - uses: google-github-actions/setup-gcloud@v2 + # tolerate failure: the PR may not have triggered a preview deploy + - name: Remove preview tag + run: | + gcloud run services update-traffic "$CLOUD_RUN_SERVICE" \ + --region "$CLOUD_RUN_REGION" \ + --remove-tags "pr-${{ github.event.number }}" \ + --quiet || echo "No preview tag to remove" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..db0a08b6fd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# syntax=docker/dockerfile:1.7 + +# ----- Build stage: install + build inside the full monorepo ----- +FROM node:24-alpine AS builder + +RUN corepack enable +WORKDIR /repo + +COPY . . + +RUN pnpm install --frozen-lockfile + +# Build the docs and any workspace packages it depends on +RUN pnpm --filter @react-spring/docs... build + +# Produce a self-contained dir with prod-only deps (symlinks dereferenced) +RUN pnpm --filter @react-spring/docs deploy --prod /out + +# pnpm deploy doesn't carry over the build output; copy it in explicitly +RUN cp -r /repo/docs/build /out/build + +# ----- Runtime stage: slim image with only what's needed to serve ----- +FROM node:24-alpine + +WORKDIR /app +ENV NODE_ENV=production +ENV PORT=8080 + +COPY --from=builder /out ./ + +EXPOSE 8080 +CMD ["node_modules/.bin/react-router-serve", "./build/server/index.js"] diff --git a/docs/app/root.tsx b/docs/app/root.tsx index befd9174b6..1156c3e718 100644 --- a/docs/app/root.tsx +++ b/docs/app/root.tsx @@ -16,7 +16,7 @@ import { WidgetGoogleTagManagerBody, } from './components/Widgets/WidgetGoogleTagManager' import { lightThemeClass } from './styles/light-theme.css' -import global from './styles/global.css?url' +import './styles/global.css' import docusearch from '@docsearch/css/dist/style.css?url' import { getTheme, setTheme } from './helpers/theme.server' import { darkThemeClass } from './styles/dark-theme.css' @@ -62,7 +62,6 @@ export const meta: MetaFunction = () => { export const links: LinksFunction = () => [ { rel: 'stylesheet', href: docusearch }, - { rel: 'stylesheet', href: global }, { rel: 'stylesheet', href: 'https://rsms.me/inter/inter.css' }, { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, { rel: 'preconnect', href: 'https://fonts.gstatic.com' }, diff --git a/docs/package.json b/docs/package.json index d178659ddc..c431bae002 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,7 +5,10 @@ "version": "2.0.0", "private": true, "type": "module", - "sideEffects": false, + "sideEffects": [ + "**/*.css", + "**/*.css.ts" + ], "scripts": { "build": "react-router build", "dev": "concurrently \"pnpm dev:rr\" \"pnpm scripts:watch\"", diff --git a/docs/react-router.config.ts b/docs/react-router.config.ts index 690950523d..0d2bd26824 100644 --- a/docs/react-router.config.ts +++ b/docs/react-router.config.ts @@ -1,6 +1,3 @@ -import { vercelPreset } from '@vercel/react-router/vite' import type { Config } from '@react-router/dev/config' -export default { - presets: [vercelPreset()], -} satisfies Config +export default {} satisfies Config diff --git a/packages/animated/package.json b/packages/animated/package.json index 2b927acc6a..f90d56bcf0 100644 --- a/packages/animated/package.json +++ b/packages/animated/package.json @@ -1,6 +1,6 @@ { "name": "@react-spring/animated", - "version": "10.0.4", + "version": "10.1.0", "description": "Animated component props for React", "module": "./dist/react-spring_animated.legacy-esm.js", "main": "./dist/cjs/index.js", diff --git a/packages/core/package.json b/packages/core/package.json index 001353f18d..16f2d6a468 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@react-spring/core", - "version": "10.0.4", + "version": "10.1.0", "module": "./dist/react-spring_core.legacy-esm.js", "main": "./dist/cjs/index.js", "types": "./dist/react-spring_core.modern.d.mts", diff --git a/packages/mock-raf/package.json b/packages/mock-raf/package.json index ed9dde976e..64d8619eb1 100644 --- a/packages/mock-raf/package.json +++ b/packages/mock-raf/package.json @@ -1,6 +1,6 @@ { "name": "@react-spring/mock-raf", - "version": "10.0.4", + "version": "10.1.0", "private": true, "description": "Vendored copy of mock-raf for react-spring tests", "license": "MIT", diff --git a/packages/parallax/package.json b/packages/parallax/package.json index f2955d8f25..9d4d72e003 100644 --- a/packages/parallax/package.json +++ b/packages/parallax/package.json @@ -1,6 +1,6 @@ { "name": "@react-spring/parallax", - "version": "10.0.4", + "version": "10.1.0", "module": "./dist/react-spring_parallax.legacy-esm.js", "main": "./dist/cjs/index.js", "types": "./dist/react-spring_parallax.modern.d.mts", diff --git a/packages/rafz/package.json b/packages/rafz/package.json index efe281c930..6a6b60cfa3 100644 --- a/packages/rafz/package.json +++ b/packages/rafz/package.json @@ -1,6 +1,6 @@ { "name": "@react-spring/rafz", - "version": "10.0.4", + "version": "10.1.0", "description": "react-spring's fork of rafz one frameloop to rule them all", "module": "./dist/react-spring_rafz.legacy-esm.js", "main": "./dist/cjs/index.js", diff --git a/packages/react-spring/package.json b/packages/react-spring/package.json index e7631d9f26..31cb0904f6 100644 --- a/packages/react-spring/package.json +++ b/packages/react-spring/package.json @@ -1,6 +1,6 @@ { "name": "react-spring", - "version": "10.0.3", + "version": "10.0.4", "module": "./dist/react-spring.legacy-esm.js", "main": "./dist/cjs/index.js", "types": "./dist/react-spring.modern.d.mts", diff --git a/packages/shared/package.json b/packages/shared/package.json index c3f970cded..f65f60b5c4 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@react-spring/shared", - "version": "10.0.4", + "version": "10.1.0", "description": "Globals and shared modules", "module": "./dist/react-spring_shared.legacy-esm.js", "main": "./dist/cjs/index.js", diff --git a/packages/types/package.json b/packages/types/package.json index f2dfc06fd9..7db5928ec1 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@react-spring/types", - "version": "10.0.4", + "version": "10.1.0", "description": "Internal package with TypeScript stuff", "module": "./dist/react-spring_types.legacy-esm.js", "main": "./dist/cjs/index.js", diff --git a/targets/konva/package.json b/targets/konva/package.json index ec9d007e4b..df1028e7df 100644 --- a/targets/konva/package.json +++ b/targets/konva/package.json @@ -1,6 +1,6 @@ { "name": "@react-spring/konva", - "version": "10.0.4", + "version": "10.1.0", "module": "./dist/react-spring_konva.legacy-esm.js", "main": "./dist/cjs/index.js", "types": "./dist/react-spring_konva.modern.d.mts", diff --git a/targets/native/package.json b/targets/native/package.json index 5ffceef535..fe9fb60744 100644 --- a/targets/native/package.json +++ b/targets/native/package.json @@ -1,6 +1,6 @@ { "name": "@react-spring/native", - "version": "10.0.4", + "version": "10.1.0", "module": "./dist/react-spring_native.legacy-esm.js", "main": "./dist/cjs/index.js", "types": "./dist/react-spring_native.modern.d.mts", diff --git a/targets/three/package.json b/targets/three/package.json index 2249e57436..f14037a51d 100644 --- a/targets/three/package.json +++ b/targets/three/package.json @@ -1,6 +1,6 @@ { "name": "@react-spring/three", - "version": "10.0.4", + "version": "10.1.0", "module": "./dist/react-spring_three.legacy-esm.js", "main": "./dist/cjs/index.js", "types": "./dist/react-spring_three.modern.d.mts", diff --git a/targets/web/package.json b/targets/web/package.json index d56eae9e15..f6ccaebfbc 100644 --- a/targets/web/package.json +++ b/targets/web/package.json @@ -1,6 +1,6 @@ { "name": "@react-spring/web", - "version": "10.0.4", + "version": "10.1.0", "module": "./dist/react-spring_web.legacy-esm.js", "main": "./dist/cjs/index.js", "types": "./dist/react-spring_web.modern.d.mts", diff --git a/targets/zdog/package.json b/targets/zdog/package.json index e5757ae4ad..89939d1938 100644 --- a/targets/zdog/package.json +++ b/targets/zdog/package.json @@ -1,6 +1,6 @@ { "name": "@react-spring/zdog", - "version": "10.0.4", + "version": "10.1.0", "module": "./dist/react-spring_zdog.legacy-esm.js", "main": "./dist/cjs/index.js", "types": "./dist/react-spring_zdog.modern.d.mts",