mirror of
https://github.com/webstudio-is/webstudio.git
synced 2025-03-14 09:57:02 +00:00
experimental: Proof of concept of the Animation component using ScrollTimeline (#4797)
## Description ref #2645 Proof of Concept for the Animation Component Using ScrollTimeline For FF and Safari `scroll-timeline-polyfill` is used. SSR Support https://animate.development.webstudio.is/playground Storybook Part 1. https://6382151c8b47d4399fb9fc69-limlyrmprp.chromatic.com/?path=/story/sdk-components-animation-scroll-animations--in-out Next steps: - Add stories with additional examples i.e. closest usage, images etc. https://scroll-driven-animations.style/ - Add support to view animations - Add view animations examples - Add enter/leave actions and animations support - Builder UI - Add lottie, rive etc compatibility (should work through context.getAnimations()[0].effect.getComputedTiming().progress) ## Steps for reproduction 1. click button 2. expect xyz ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 0000) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file
This commit is contained in:
46
.github/actions/submodules-checkout/action.yml
vendored
Normal file
46
.github/actions/submodules-checkout/action.yml
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
name: CI setup
|
||||
|
||||
description: |
|
||||
Sets up the CI environment for the project.
|
||||
|
||||
inputs:
|
||||
submodules-ssh-key:
|
||||
description: "The SSH key to private submodules to use for the checkout"
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
|
||||
steps:
|
||||
- name: Set up SSH for Git
|
||||
if: ${{ inputs.submodules-ssh-key }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ inputs.submodules-ssh-key }}" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keyscan github.com >> ~/.ssh/known_hosts
|
||||
shell: bash
|
||||
|
||||
- name: Verify SSH Connection (Optional)
|
||||
if: ${{ inputs.submodules-ssh-key }}
|
||||
run: |
|
||||
ssh -T git@github.com || true
|
||||
shell: bash
|
||||
|
||||
- name: Verify SSH Connection (Optional)
|
||||
if: ${{ inputs.submodules-ssh-key }}
|
||||
run: |
|
||||
echo Branch is ${{ github.event.pull_request.head.ref || github.ref_name }}
|
||||
shell: bash
|
||||
|
||||
- name: Try checkout submodules to the same branch as main repo
|
||||
if: ${{ inputs.submodules-ssh-key }}
|
||||
run: |
|
||||
./submodules.sh ${{ github.event.pull_request.head.ref || github.ref_name }}
|
||||
shell: bash
|
||||
|
||||
- name: Show main readme
|
||||
if: ${{ inputs.submodules-ssh-key }}
|
||||
run: |
|
||||
cat ./packages/sdk-components-animation/private-src/README.md || echo "No README found"
|
||||
shell: bash
|
67
.github/workflows/check-submodules.yml
vendored
Normal file
67
.github/workflows/check-submodules.yml
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
name: Check submodules
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
# cancel in-progress runs on new commits to same PR (gitub.event.number)
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
checks:
|
||||
timeout-minutes: 20
|
||||
|
||||
environment:
|
||||
name: development
|
||||
|
||||
env:
|
||||
DATABASE_URL: postgres://
|
||||
AUTH_SECRET: test
|
||||
|
||||
runs-on: ubuntu-24.04-arm
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- uses: ./.github/actions/submodules-checkout
|
||||
with:
|
||||
submodules-ssh-key: ${{ secrets.PRIVATE_GITHUB_DEPLOY_TOKEN }}
|
||||
|
||||
- name: Check if any submodule branch matches github.ref_name
|
||||
run: |
|
||||
echo "C ${{ github.workflow }}-${{ github.event.number || github.sha }}"
|
||||
# Get the current branch or tag name
|
||||
REF_NAME="${{ github.event.pull_request.head.ref || github.ref_name }}"
|
||||
|
||||
echo "Branch is:" $REF_NAME
|
||||
|
||||
# List all submodule paths
|
||||
SUBMODULES=$(git submodule status | awk '{print $2}')
|
||||
|
||||
# Check each submodule's branch
|
||||
for SUBMODULE in $SUBMODULES; do
|
||||
echo "Checking submodule: $SUBMODULE"
|
||||
(
|
||||
cd "$SUBMODULE"
|
||||
# Get the current branch of the submodule
|
||||
SUBMODULE_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
echo "Submodule branch: $SUBMODULE_BRANCH"
|
||||
|
||||
# Compare the submodule branch to the ref_name
|
||||
if [ "$SUBMODULE_BRANCH" = "$REF_NAME" ]; then
|
||||
echo "::error::Submodule '$SUBMODULE' is on branch '$SUBMODULE_BRANCH', which matches the current ref '$REF_NAME'."
|
||||
exit 1
|
||||
fi
|
||||
)
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1 # Fail the workflow if any submodule branch matches
|
||||
fi
|
||||
done
|
||||
|
||||
echo "No submodule is on the same branch as the current ref '$REF_NAME'."
|
8
.github/workflows/chromatic.yml
vendored
8
.github/workflows/chromatic.yml
vendored
@ -20,12 +20,20 @@ jobs:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
environment:
|
||||
name: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2 # we need to fetch at least parent commit to satisfy Chromatic
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }} # HEAD commit instead of merge commit
|
||||
|
||||
# Storybook with submodules
|
||||
- uses: ./.github/actions/submodules-checkout
|
||||
with:
|
||||
submodules-ssh-key: ${{ secrets.PRIVATE_GITHUB_DEPLOY_TOKEN }}
|
||||
|
||||
- uses: ./.github/actions/ci-setup
|
||||
|
||||
- name: Chromatic
|
||||
|
2
.github/workflows/cli-r2-static.yaml
vendored
2
.github/workflows/cli-r2-static.yaml
vendored
@ -30,6 +30,8 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.sha }} # HEAD commit instead of merge commit
|
||||
|
||||
# Do not checkout submodules, they are not needed for this workflow
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
|
5
.github/workflows/cli-r2.yaml
vendored
5
.github/workflows/cli-r2.yaml
vendored
@ -31,6 +31,11 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.sha }} # HEAD commit instead of merge commit
|
||||
|
||||
# We need submodules here as this is used for the cloudflare build
|
||||
- uses: ./.github/actions/submodules-checkout
|
||||
with:
|
||||
submodules-ssh-key: ${{ secrets.PRIVATE_GITHUB_DEPLOY_TOKEN }}
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
|
15
.github/workflows/fixtures-test.yml
vendored
15
.github/workflows/fixtures-test.yml
vendored
@ -9,6 +9,12 @@ on:
|
||||
builder-host:
|
||||
required: true
|
||||
type: string
|
||||
environment:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
PRIVATE_GITHUB_DEPLOY_TOKEN:
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
@ -23,6 +29,9 @@ jobs:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
environment:
|
||||
name: ${{ inputs.environment }}
|
||||
|
||||
env:
|
||||
DATABASE_URL: postgres://
|
||||
AUTH_SECRET: test
|
||||
@ -32,7 +41,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }} # HEAD commit instead of merge commit
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
# Test that everything is working with submodules
|
||||
- uses: ./.github/actions/submodules-checkout
|
||||
with:
|
||||
submodules-ssh-key: ${{ secrets.PRIVATE_GITHUB_DEPLOY_TOKEN }}
|
||||
|
||||
- uses: ./.github/actions/ci-setup
|
||||
|
||||
|
44
.github/workflows/main.yml
vendored
44
.github/workflows/main.yml
vendored
@ -18,6 +18,15 @@ jobs:
|
||||
checks:
|
||||
timeout-minutes: 20
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
environment:
|
||||
- empty
|
||||
- development
|
||||
|
||||
environment:
|
||||
name: ${{ matrix.environment }}
|
||||
|
||||
env:
|
||||
DATABASE_URL: postgres://
|
||||
AUTH_SECRET: test
|
||||
@ -27,7 +36,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }} # HEAD commit instead of merge commit
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
# Will not checkout submodules on empty environment, and will on development
|
||||
- uses: ./.github/actions/submodules-checkout
|
||||
with:
|
||||
submodules-ssh-key: ${{ secrets.PRIVATE_GITHUB_DEPLOY_TOKEN }}
|
||||
|
||||
- uses: ./.github/actions/ci-setup
|
||||
|
||||
@ -41,21 +55,35 @@ jobs:
|
||||
|
||||
- run: echo ===SHA USED=== ${{ github.event.pull_request.head.sha || github.sha }} # todo: remove after check whats happening on main
|
||||
|
||||
- run: pnpm prettier --cache --check "**/*.{js,md,ts,tsx}"
|
||||
- run: |
|
||||
pnpm prettier --cache --check "**/*.{js,md,ts,tsx}"
|
||||
|
||||
- run: pnpm lint --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslint-cache
|
||||
- name: Lint
|
||||
run: |
|
||||
pnpm lint --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslint-cache
|
||||
|
||||
- run: pnpm -r test
|
||||
- run: pnpm --filter=@webstudio-is/prisma-client build
|
||||
- run: pnpm -r typecheck
|
||||
- name: Test
|
||||
run: |
|
||||
pnpm -r test
|
||||
|
||||
- name: Typecheck
|
||||
run: |
|
||||
pnpm -r typecheck
|
||||
|
||||
check-size:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04-arm
|
||||
|
||||
environment:
|
||||
name: development
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }} # HEAD commit instead of merge commit
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- uses: ./.github/actions/submodules-checkout
|
||||
with:
|
||||
submodules-ssh-key: ${{ secrets.PRIVATE_GITHUB_DEPLOY_TOKEN }}
|
||||
|
||||
- uses: ./.github/actions/ci-setup
|
||||
|
||||
|
14
.github/workflows/vercel-deploy-staging.yml
vendored
14
.github/workflows/vercel-deploy-staging.yml
vendored
@ -5,7 +5,7 @@ on:
|
||||
|
||||
# cancel in-progress runs on new commits to same PR (gitub.event.number)
|
||||
concurrency:
|
||||
group: vercel-deploy-${{ github.workflow }}-${{ github.event.number || github.sha }}
|
||||
group: vercel-deploy-${{ github.workflow }}-${{ github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
@ -38,9 +38,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }} # HEAD commit instead of merge commit
|
||||
ref: ${{ github.sha }}
|
||||
|
||||
- uses: ./.github/actions/submodules-checkout
|
||||
with:
|
||||
submodules-ssh-key: ${{ secrets.PRIVATE_GITHUB_DEPLOY_TOKEN }}
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
@ -85,6 +90,11 @@ jobs:
|
||||
with:
|
||||
builder-url: ${{ needs.deployment.outputs.builder-url }}
|
||||
builder-host: ${{ needs.deployment.outputs.builder-host }}
|
||||
environment: development
|
||||
secrets:
|
||||
# We are not passing the secret here (as it does not exist in the current environment).
|
||||
# Instead, this serves as a signal to the calling workflow that it has permission to extract it from the environment.
|
||||
PRIVATE_GITHUB_DEPLOY_TOKEN: ${{ secrets.PRIVATE_GITHUB_DEPLOY_TOKEN }}
|
||||
|
||||
delete-github-deployments:
|
||||
needs: fixtures-test
|
||||
|
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
[submodule "packages/sdk-components-animation/private-src"]
|
||||
path = packages/sdk-components-animation/private-src
|
||||
url = git@github.com:webstudio-is/sdk-components-animation.git
|
||||
branch = main
|
@ -1,5 +1,19 @@
|
||||
import * as path from "node:path";
|
||||
import type { StorybookConfig } from "@storybook/react-vite";
|
||||
import { existsSync, readdirSync } from "node:fs";
|
||||
|
||||
const isFolderEmpty = (folderPath: string) => {
|
||||
if (!existsSync(folderPath)) {
|
||||
return true; // Folder does not exist
|
||||
}
|
||||
const contents = readdirSync(folderPath);
|
||||
|
||||
return contents.length === 0;
|
||||
};
|
||||
|
||||
const hasPrivateFolders = !isFolderEmpty(
|
||||
path.join(__dirname, "../../packages/sdk-components-animation/private-src")
|
||||
);
|
||||
|
||||
const visualTestingStories: StorybookConfig["stories"] = [
|
||||
{
|
||||
@ -37,6 +51,10 @@ export default {
|
||||
directory: "../packages/sdk-components-react-radix",
|
||||
titlePrefix: "SDK Components React Radix",
|
||||
},
|
||||
{
|
||||
directory: "../packages/sdk-components-animation",
|
||||
titlePrefix: "SDK Components Animation",
|
||||
},
|
||||
],
|
||||
framework: {
|
||||
name: "@storybook/react-vite",
|
||||
@ -50,6 +68,10 @@ export default {
|
||||
async viteFinal(config) {
|
||||
return {
|
||||
...config,
|
||||
optimizeDeps: {
|
||||
exclude: ["scroll-timeline-polyfill"],
|
||||
},
|
||||
|
||||
define: {
|
||||
...config.define,
|
||||
// storybook use "util" package internally which is bundled with stories
|
||||
@ -59,7 +81,17 @@ export default {
|
||||
},
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
conditions: ["webstudio", "import", "module", "browser", "default"],
|
||||
conditions: hasPrivateFolders
|
||||
? [
|
||||
"webstudio-private",
|
||||
"webstudio",
|
||||
"import",
|
||||
"module",
|
||||
"browser",
|
||||
"default",
|
||||
]
|
||||
: ["webstudio", "import", "module", "browser", "default"],
|
||||
|
||||
alias: [
|
||||
{
|
||||
find: "~",
|
||||
|
14
@types/scroll-timeline.d.ts
vendored
Normal file
14
@types/scroll-timeline.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
type ScrollAxis = "block" | "inline" | "x" | "y";
|
||||
|
||||
interface ScrollTimelineOptions {
|
||||
source?: Element | Document | null;
|
||||
axis?: ScrollAxis;
|
||||
}
|
||||
|
||||
declare class ScrollTimeline extends AnimationTimeline {
|
||||
constructor(options?: ScrollTimelineOptions);
|
||||
}
|
||||
|
||||
declare class ViewTimeline extends ScrollTimeline {
|
||||
constructor(options?: ScrollTimelineOptions);
|
||||
}
|
92
apps/builder/app/routes/_ui.playground.tsx
Normal file
92
apps/builder/app/routes/_ui.playground.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import { Scroll } from "@webstudio-is/sdk-components-animation";
|
||||
import { parseCssValue } from "@webstudio-is/css-data";
|
||||
import { Box, styled } from "@webstudio-is/design-system";
|
||||
|
||||
const H1 = styled("h1");
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
const Playground = () => {
|
||||
return (
|
||||
<Box>
|
||||
<Scroll
|
||||
debug={DEBUG}
|
||||
action={{
|
||||
type: "scroll",
|
||||
animations: [
|
||||
{
|
||||
timing: {
|
||||
fill: "backwards",
|
||||
rangeStart: ["start", { type: "unit", value: 0, unit: "%" }],
|
||||
rangeEnd: ["start", { type: "unit", value: 200, unit: "px" }],
|
||||
},
|
||||
keyframes: [
|
||||
{
|
||||
offset: 0,
|
||||
styles: {
|
||||
transform: parseCssValue(
|
||||
"transform",
|
||||
"translate(0, -120px)"
|
||||
),
|
||||
opacity: parseCssValue("opacity", "0.2"),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
timing: {
|
||||
fill: "forwards",
|
||||
rangeStart: ["end", { type: "unit", value: 200, unit: "px" }],
|
||||
rangeEnd: ["end", { type: "unit", value: 0, unit: "%" }],
|
||||
},
|
||||
keyframes: [
|
||||
{
|
||||
offset: 1,
|
||||
styles: {
|
||||
transform: parseCssValue(
|
||||
"transform",
|
||||
"translate(0, 120px)"
|
||||
),
|
||||
opacity: parseCssValue("opacity", "0.0"),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
<H1
|
||||
css={{
|
||||
position: "fixed",
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
top: "80px",
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
"&:hover": {
|
||||
color: "red",
|
||||
},
|
||||
}}
|
||||
>
|
||||
HELLO WORLD
|
||||
</H1>
|
||||
</Scroll>
|
||||
<Box css={{ height: "200px", backgroundColor: "#eee", p: 4 }}>
|
||||
Start scrolling, and when the current box scrolls out, the “HELLO WORLD”
|
||||
header will fly in and become hoverable. (During the animation, it won’t
|
||||
be hoverable.)
|
||||
</Box>
|
||||
<Box css={{ height: "200vh" }}></Box>
|
||||
<Box css={{ height: "200px", backgroundColor: "#eee", p: 4 }}>
|
||||
When you see this box, the “HELLO WORLD” header will fly out.
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Playground;
|
||||
|
||||
// Reduces Vercel function size from 29MB to 9MB for unknown reasons; effective when used in limited files.
|
||||
export const config = {
|
||||
maxDuration: 30,
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { resolve } from "node:path";
|
||||
import path, { resolve } from "node:path";
|
||||
import { defineConfig, type CorsOptions } from "vite";
|
||||
import { vitePlugin as remix } from "@remix-run/dev";
|
||||
import { vercelPreset } from "@vercel/remix/vite";
|
||||
@ -9,13 +9,28 @@ import {
|
||||
getAuthorizationServerOrigin,
|
||||
isBuilderUrl,
|
||||
} from "./app/shared/router-utils/origins";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
||||
|
||||
const isFolderEmpty = (folderPath: string) => {
|
||||
if (!existsSync(folderPath)) {
|
||||
return true; // Folder does not exist
|
||||
}
|
||||
const contents = readdirSync(folderPath);
|
||||
|
||||
return contents.length === 0;
|
||||
};
|
||||
|
||||
const hasPrivateFolders = !isFolderEmpty(
|
||||
path.join(__dirname, "../../packages/sdk-components-animation/private-src")
|
||||
);
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
if (mode === "test") {
|
||||
return {
|
||||
resolve: {
|
||||
conditions: ["webstudio"],
|
||||
conditions: hasPrivateFolders
|
||||
? ["webstudio-private", "webstudio"]
|
||||
: ["webstudio"],
|
||||
alias: [
|
||||
{
|
||||
find: "~",
|
||||
@ -69,7 +84,10 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
],
|
||||
resolve: {
|
||||
conditions: ["webstudio"],
|
||||
conditions: hasPrivateFolders
|
||||
? ["webstudio-private", "webstudio"]
|
||||
: ["webstudio"],
|
||||
|
||||
alias: [
|
||||
{
|
||||
find: "~",
|
||||
|
@ -88,7 +88,8 @@
|
||||
"css-tree@2.3.1": "patches/css-tree@2.3.1.patch",
|
||||
"@types/css-tree@2.3.1": "patches/@types__css-tree@2.3.1.patch",
|
||||
"@radix-ui/react-scroll-area@1.0.5": "patches/@radix-ui__react-scroll-area@1.0.5.patch",
|
||||
"@remix-run/dev": "patches/@remix-run__dev.patch"
|
||||
"@remix-run/dev": "patches/@remix-run__dev.patch",
|
||||
"scroll-timeline-polyfill@1.1.0": "patches/scroll-timeline-polyfill@1.1.0.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"webstudio-private": "./private-src/components.ts",
|
||||
"webstudio": "./src/components.ts",
|
||||
"types": "./lib/types/components.d.ts",
|
||||
"import": "./lib/components.js"
|
||||
@ -51,9 +52,12 @@
|
||||
"react-dom": "18.3.0-canary-14898b6a9-20240318"
|
||||
},
|
||||
"dependencies": {
|
||||
"@webstudio-is/css-engine": "workspace:*",
|
||||
"@webstudio-is/icons": "workspace:*",
|
||||
"@webstudio-is/react-sdk": "workspace:*",
|
||||
"@webstudio-is/sdk": "workspace:*"
|
||||
"@webstudio-is/sdk": "workspace:*",
|
||||
"react-error-boundary": "^5.0.0",
|
||||
"scroll-timeline-polyfill": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.70",
|
||||
@ -63,7 +67,10 @@
|
||||
"@webstudio-is/sdk-components-react": "workspace:*",
|
||||
"@webstudio-is/template": "workspace:*",
|
||||
"@webstudio-is/tsconfig": "workspace:*",
|
||||
"@webstudio-is/css-data": "workspace:*",
|
||||
"@webstudio-is/design-system": "workspace:*",
|
||||
"react": "18.3.0-canary-14898b6a9-20240318",
|
||||
"react-dom": "18.3.0-canary-14898b6a9-20240318"
|
||||
"react-dom": "18.3.0-canary-14898b6a9-20240318",
|
||||
"type-fest": "^4.32.0"
|
||||
}
|
||||
}
|
||||
|
1
packages/sdk-components-animation/private-src
Submodule
1
packages/sdk-components-animation/private-src
Submodule
Submodule packages/sdk-components-animation/private-src added at 80edfcb8e4
@ -1 +1 @@
|
||||
export {};
|
||||
export { Scroll } from "./scroll";
|
||||
|
14
packages/sdk-components-animation/src/scroll.tsx
Normal file
14
packages/sdk-components-animation/src/scroll.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { forwardRef, type ElementRef } from "react";
|
||||
import type { AnimationAction } from "./shared/animation-types";
|
||||
|
||||
type ScrollProps = {
|
||||
debug?: boolean;
|
||||
children?: React.ReactNode;
|
||||
action: AnimationAction;
|
||||
};
|
||||
|
||||
export const Scroll = forwardRef<ElementRef<"div">, ScrollProps>(
|
||||
({ debug = false, action, ...props }, ref) => {
|
||||
return <div ref={ref} style={{ display: "contents" }} {...props} />;
|
||||
}
|
||||
);
|
113
packages/sdk-components-animation/src/shared/animation-types.tsx
Normal file
113
packages/sdk-components-animation/src/shared/animation-types.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
import type { StyleValue, UnitValue } from "@webstudio-is/css-engine";
|
||||
|
||||
export type KeyframeStyles = { [property: string]: StyleValue | undefined };
|
||||
|
||||
export type AnimationKeyframe = {
|
||||
offset: number | undefined;
|
||||
// We are using composite: auto as the default value for now
|
||||
// composite?: CompositeOperationOrAuto;
|
||||
styles: KeyframeStyles;
|
||||
};
|
||||
|
||||
const RANGE_UNITS = [
|
||||
"%",
|
||||
"px",
|
||||
// Does not supported by polyfill and we are converting it to px ourselfs
|
||||
"cm",
|
||||
"mm",
|
||||
"q",
|
||||
"in",
|
||||
"pt",
|
||||
"pc",
|
||||
"em",
|
||||
"rem",
|
||||
"ex",
|
||||
"rex",
|
||||
"cap",
|
||||
"rcap",
|
||||
"ch",
|
||||
"rch",
|
||||
"lh",
|
||||
"rlh",
|
||||
"vw",
|
||||
"svw",
|
||||
"lvw",
|
||||
"dvw",
|
||||
"vh",
|
||||
"svh",
|
||||
"lvh",
|
||||
"dvh",
|
||||
"vi",
|
||||
"svi",
|
||||
"lvi",
|
||||
"dvi",
|
||||
"vb",
|
||||
"svb",
|
||||
"lvb",
|
||||
"dvb",
|
||||
"vmin",
|
||||
"svmin",
|
||||
"lvmin",
|
||||
"dvmin",
|
||||
"vmax",
|
||||
"svmax",
|
||||
"lvmax",
|
||||
"dvmax",
|
||||
] as const;
|
||||
|
||||
export type RangeUnit = (typeof RANGE_UNITS)[number];
|
||||
|
||||
export const isRangeUnit = (value: unknown): value is RangeUnit =>
|
||||
RANGE_UNITS.includes(value as RangeUnit);
|
||||
|
||||
export type RangeUnitValue = { type: "unit"; value: number; unit: RangeUnit };
|
||||
|
||||
({}) as RangeUnitValue satisfies UnitValue;
|
||||
|
||||
type KeyframeEffectOptions = {
|
||||
easing?: string;
|
||||
fill?: FillMode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Scroll does not support https://drafts.csswg.org/scroll-animations/#named-ranges
|
||||
* However, for simplicity and type unification with the view, we will use the names "start" and "end,"
|
||||
* which will be transformed as follows:
|
||||
* - "start" → `calc(0% + range)`
|
||||
* - "end" → `calc(100% - range)`
|
||||
*/
|
||||
type ScrollNamedRange = "start" | "end";
|
||||
|
||||
/**
|
||||
* Scroll does not support https://drafts.csswg.org/scroll-animations/#named-ranges
|
||||
* However, for simplicity and type unification with the view, we will use the names "start" and "end,"
|
||||
* See ScrollNamedRange type for more information.
|
||||
*/
|
||||
export type ScrollRangeValue = [name: ScrollNamedRange, value: RangeUnitValue];
|
||||
|
||||
type ScrollRangeOptions = {
|
||||
rangeStart?: ScrollRangeValue | undefined;
|
||||
rangeEnd?: ScrollRangeValue | undefined;
|
||||
};
|
||||
|
||||
/*
|
||||
type AnimationTiming = {
|
||||
delay?: number;
|
||||
duration?: number;
|
||||
easing?: string;
|
||||
fill?: FillMode;
|
||||
};
|
||||
*/
|
||||
|
||||
type ScrollAction = {
|
||||
type: "scroll";
|
||||
source?: "closest" | "nearest" | "root";
|
||||
axis?: "block" | "inline" | "x" | "y";
|
||||
animations: {
|
||||
timing: KeyframeEffectOptions & ScrollRangeOptions;
|
||||
keyframes: AnimationKeyframe[];
|
||||
}[];
|
||||
};
|
||||
|
||||
// | ViewAction | ...
|
||||
export type AnimationAction = ScrollAction;
|
@ -1,3 +0,0 @@
|
||||
import { createProxy } from "@webstudio-is/template";
|
||||
|
||||
export const animation = createProxy("@webstudio-is/sdk-components-animation:");
|
@ -1,3 +1,12 @@
|
||||
{
|
||||
"extends": "@webstudio-is/tsconfig/base.json"
|
||||
"extends": "@webstudio-is/tsconfig/base.json",
|
||||
"include": [
|
||||
"src",
|
||||
"../../@types/**/scroll-timeline.d.ts",
|
||||
"private-src/scroll.stories.tsx",
|
||||
"private-src/scroll.tsx"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"types": ["react/experimental", "react-dom/experimental", "@types/node"]
|
||||
}
|
||||
}
|
||||
|
25
patches/scroll-timeline-polyfill@1.1.0.patch
Normal file
25
patches/scroll-timeline-polyfill@1.1.0.patch
Normal file
@ -0,0 +1,25 @@
|
||||
diff --git a/src/scroll-timeline-base.js b/src/scroll-timeline-base.js
|
||||
index 2c854928701c5dae23c985eb04f73a6ca2b661c3..abf1a7629568f411dd83dd509640850706d59528 100644
|
||||
--- a/src/scroll-timeline-base.js
|
||||
+++ b/src/scroll-timeline-base.js
|
||||
@@ -162,13 +162,19 @@ function isValidAxis(axis) {
|
||||
*/
|
||||
export function measureSource (source) {
|
||||
const style = getComputedStyle(source);
|
||||
+ const clientHeight = source.clientHeight;
|
||||
return {
|
||||
scrollLeft: source.scrollLeft,
|
||||
scrollTop: source.scrollTop,
|
||||
scrollWidth: source.scrollWidth,
|
||||
scrollHeight: source.scrollHeight,
|
||||
clientWidth: source.clientWidth,
|
||||
- clientHeight: source.clientHeight,
|
||||
+ get clientHeight() {
|
||||
+ if (document.scrollingElement === source) {
|
||||
+ return window.innerHeight;
|
||||
+ }
|
||||
+ return clientHeight;
|
||||
+ },
|
||||
writingMode: style.writingMode,
|
||||
direction: style.direction,
|
||||
scrollPaddingTop: style.scrollPaddingTop,
|
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@ -26,6 +26,9 @@ patchedDependencies:
|
||||
css-tree@2.3.1:
|
||||
hash: epgcmebti7rfrc2ej4odb3t4jy
|
||||
path: patches/css-tree@2.3.1.patch
|
||||
scroll-timeline-polyfill@1.1.0:
|
||||
hash: i4g3vdpump4efgy2hri5l5rsfm
|
||||
path: patches/scroll-timeline-polyfill@1.1.0.patch
|
||||
|
||||
importers:
|
||||
|
||||
@ -1914,6 +1917,9 @@ importers:
|
||||
|
||||
packages/sdk-components-animation:
|
||||
dependencies:
|
||||
'@webstudio-is/css-engine':
|
||||
specifier: workspace:*
|
||||
version: link:../css-engine
|
||||
'@webstudio-is/icons':
|
||||
specifier: workspace:*
|
||||
version: link:../icons
|
||||
@ -1923,6 +1929,12 @@ importers:
|
||||
'@webstudio-is/sdk':
|
||||
specifier: workspace:*
|
||||
version: link:../sdk
|
||||
react-error-boundary:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0(react@18.3.0-canary-14898b6a9-20240318)
|
||||
scroll-timeline-polyfill:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(patch_hash=i4g3vdpump4efgy2hri5l5rsfm)
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^18.2.70
|
||||
@ -1930,6 +1942,12 @@ importers:
|
||||
'@types/react-dom':
|
||||
specifier: ^18.2.25
|
||||
version: 18.2.25
|
||||
'@webstudio-is/css-data':
|
||||
specifier: workspace:*
|
||||
version: link:../css-data
|
||||
'@webstudio-is/design-system':
|
||||
specifier: workspace:*
|
||||
version: link:../design-system
|
||||
'@webstudio-is/generate-arg-types':
|
||||
specifier: workspace:*
|
||||
version: link:../generate-arg-types
|
||||
@ -1951,6 +1969,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: 18.3.0-canary-14898b6a9-20240318
|
||||
version: 18.3.0-canary-14898b6a9-20240318(react@18.3.0-canary-14898b6a9-20240318)
|
||||
type-fest:
|
||||
specifier: ^4.32.0
|
||||
version: 4.32.0
|
||||
|
||||
packages/sdk-components-react:
|
||||
dependencies:
|
||||
@ -8398,6 +8419,9 @@ packages:
|
||||
scheduler@0.24.0-canary-14898b6a9-20240318:
|
||||
resolution: {integrity: sha512-ifDO3bUdooS4OlxvGxMyoDEC/aq14MvJLDd0thjrUSZGeLJA7WBc+sr9NZxIxrXfVqMl1GTGGPwXqRJZDNW76w==}
|
||||
|
||||
scroll-timeline-polyfill@1.1.0:
|
||||
resolution: {integrity: sha512-BpL3gk3Ynt/5VYaDFUNUP/FTkDldwKQnWcA07g/mDHkMVS9pQUyUXBpsy4RZYAgsfFeI1tWcnPNrEFtCpQoO9Q==}
|
||||
|
||||
selfsigned@2.1.1:
|
||||
resolution: {integrity: sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==}
|
||||
engines: {node: '>=10'}
|
||||
@ -16336,6 +16360,8 @@ snapshots:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
||||
scroll-timeline-polyfill@1.1.0(patch_hash=i4g3vdpump4efgy2hri5l5rsfm): {}
|
||||
|
||||
selfsigned@2.1.1:
|
||||
dependencies:
|
||||
node-forge: 1.3.1
|
||||
|
25
submodules.sh
Executable file
25
submodules.sh
Executable file
@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
BRANCH="$1"
|
||||
|
||||
|
||||
git submodule update --init --recursive
|
||||
|
||||
git submodule foreach '
|
||||
# If a branch parameter is provided, use it; otherwise, determine the branch dynamically
|
||||
if [ -n "'"$BRANCH"'" ]; then
|
||||
SUBMODULE_BRANCH="'"$BRANCH"'"
|
||||
else
|
||||
SUBMODULE_BRANCH=$(git -C $toplevel rev-parse --abbrev-ref HEAD)
|
||||
fi
|
||||
|
||||
echo "Checking out \"$SUBMODULE_BRANCH\" branch in \"$name\" submodule"
|
||||
|
||||
# Check if the branch exists in the remote
|
||||
if git ls-remote --exit-code --heads origin "$SUBMODULE_BRANCH" > /dev/null; then
|
||||
git checkout "$SUBMODULE_BRANCH" && git pull origin "$SUBMODULE_BRANCH"
|
||||
else
|
||||
# Fallback to "main" if the branch does not exist
|
||||
git checkout "main" && git pull origin "main"
|
||||
fi
|
||||
'
|
Reference in New Issue
Block a user