├── .ci
├── .env.example
└── Taskfile.yaml
├── .dev
├── .env.example
├── Taskfile.yaml
├── UnitTestUi.Dockerfile
└── docker-compose.yaml
├── .github
├── README.md
├── images
│ └── performance.png
└── workflows
│ ├── deploy.yml
│ └── test-and-build.yml
├── .gitignore
├── .vscode
├── extensions.json
└── settings.json
├── LICENSE
├── README.md
├── docs
├── .gitignore
├── bartholomeow.html
├── e2e.html
├── e2e
│ ├── ScreenShot.test.ts
│ └── ScreenShot.test.ts-snapshots
│ │ ├── 0-1-chromium-linux.png
│ │ ├── 0-1-firefox-linux.png
│ │ ├── 0-1-webkit-linux.png
│ │ ├── 1000-1-chromium-linux.png
│ │ ├── 1000-1-firefox-linux.png
│ │ ├── 1000-1-webkit-linux.png
│ │ ├── 3000-1-chromium-linux.png
│ │ ├── 3000-1-firefox-linux.png
│ │ ├── 3000-1-webkit-linux.png
│ │ ├── 500-1-chromium-linux.png
│ │ ├── 500-1-firefox-linux.png
│ │ └── 500-1-webkit-linux.png
├── env.d.ts
├── gsap.html
├── index.html
├── package.json
├── playwright.config.ts
├── postcss.config.cjs
├── public
│ ├── bartholomeow.png
│ ├── example.glb
│ ├── vite.svg
│ ├── worm-ball.png
│ └── worm-bg.png
├── src
│ ├── bartholomeow.css
│ ├── bartholomeow.ts
│ ├── e2e.ts
│ ├── gsap.css
│ └── gsap.ts
├── tests.html
├── tsconfig.json
├── types.html
├── vite.config.ts
└── yarn.lock
├── eslint.config.js
├── lib
├── .gitignore
├── dist
│ ├── Animation.d.ts
│ ├── Animation.types.d.ts
│ ├── Driver.d.ts
│ ├── Driver.types.d.ts
│ ├── Observer.d.ts
│ ├── Observer.types.d.ts
│ ├── TheSupersonicPlugin.d.ts
│ ├── TheSupersonicPlugin.types.d.ts
│ ├── index.d.ts
│ ├── the-supersonic-plugin-for-scroll-based-animation.iife.js
│ └── the-supersonic-plugin-for-scroll-based-animation.js
├── env.d.ts
├── package.json
├── src
│ ├── Animation.test.ts
│ ├── Animation.ts
│ ├── Animation.types.ts
│ ├── Driver.test.ts
│ ├── Driver.ts
│ ├── Driver.types.ts
│ ├── Observer.ts
│ ├── Observer.types.ts
│ ├── TheSupersonicPlugin.test.ts
│ ├── TheSupersonicPlugin.ts
│ ├── TheSupersonicPlugin.types.ts
│ ├── index.ts
│ └── utils.ts
├── tsconfig.json
├── vite.config.ts
└── yarn.lock
├── package.json
└── yarn.lock
/.ci/.env.example:
--------------------------------------------------------------------------------
1 | NODE_IMAGE="node:20"
2 | PLAYWRIGHT_IMAGE="mcr.microsoft.com/playwright:v1.43.0-jammy"
3 | CLOUDFLARE_ACCOUNT_ID="example"
4 | CLOUDFLARE_API_TOKEN="example"
--------------------------------------------------------------------------------
/.ci/Taskfile.yaml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | dotenv: [.env]
4 |
5 | tasks:
6 | env:
7 | desc: makes .env from .env.example
8 | cmds:
9 | - cp .env.example .env
10 |
11 | root:yarn:
12 | desc: runs 'yarn' in root directory
13 | cmds:
14 | - |
15 | docker run --rm \
16 | -w /app \
17 | -v $(pwd)/..:/app \
18 | $NODE_IMAGE yarn
19 |
20 | docs:yarn:
21 | desc: runs 'yarn' in 'docs' directory
22 | cmds:
23 | - |
24 | docker run --rm \
25 | -w /app \
26 | -v $(pwd)/../docs:/app \
27 | $NODE_IMAGE yarn
28 |
29 | lib:yarn:
30 | desc: runs 'yarn' in 'lib' directory
31 | cmds:
32 | - |
33 | docker run --rm \
34 | -w /app \
35 | -v $(pwd)/../lib:/app \
36 | $NODE_IMAGE yarn
37 |
38 | yarn:
39 | cmds:
40 | - task root:yarn
41 | - task docs:yarn
42 | - task lib:yarn
43 |
44 | lint:
45 | cmds:
46 | - |
47 | docker run --rm \
48 | -w /app \
49 | -v $(pwd)/..:/app \
50 | $NODE_IMAGE /bin/bash -c "yarn lint"
51 |
52 | build:docs:
53 | desc: creates production build of docs
54 | cmds:
55 | - task lint
56 | - |
57 | docker run --rm \
58 | -w /app/docs \
59 | -v $(pwd)/../docs:/app/docs \
60 | -v $(pwd)/../lib:/app/lib \
61 | $NODE_IMAGE /bin/bash -c "yarn build"
62 |
63 | build:lib:
64 | desc: creates production build of lib
65 | cmds:
66 | - task lint
67 | - |
68 | docker run --rm \
69 | -w /app \
70 | -v $(pwd)/../lib:/app \
71 | $NODE_IMAGE /bin/bash -c "yarn build"
72 |
73 | build:
74 | cmds:
75 | - task build:docs
76 | - task build:lib
77 |
78 | up:
79 | cmds:
80 | - |
81 | docker run \
82 | --rm --network="host" \
83 | -v $(pwd)/../docs:/app/docs \
84 | -v $(pwd)/../lib:/app/lib \
85 | -w /app/docs \
86 | --name ci-preview \
87 | -d $NODE_IMAGE \
88 | yarn preview
89 |
90 | down:
91 | cmds:
92 | - docker container stop ci-preview
93 |
94 | test:unit:
95 | desc: runs unit tests in 'lib' directory
96 | cmds:
97 | - |
98 | docker run --rm \
99 | -v $(pwd)/../lib:/app \
100 | -w /app \
101 | $NODE_IMAGE \
102 | yarn test:unit
103 |
104 | test:e2e:
105 | desc: runs Playwright
106 | cmds:
107 | - |
108 | docker run --rm \
109 | --network="host" \
110 | -v $(pwd)/../docs:/app \
111 | -w /app \
112 | $PLAYWRIGHT_IMAGE \
113 | yarn test:e2e
114 |
115 | test:
116 | cmds:
117 | - task test:unit
118 | - task test:e2e
119 |
120 | deploy:pages:
121 | desc: deploy to CloudFlare pages
122 | cmds:
123 | - |
124 | docker run \
125 | --rm \
126 | -w /app \
127 | -v $(pwd)/../docs:/app \
128 | -e CLOUDFLARE_ACCOUNT_ID \
129 | -e CLOUDFLARE_API_TOKEN \
130 | $NODE_IMAGE yarn deploy
131 |
132 | deploy:
133 | desc: Lints, tests, builds and deploys to CloudFlare pages
134 | cmds:
135 | - task build
136 | - task up
137 | - task test
138 | - task deploy:pages
139 |
--------------------------------------------------------------------------------
/.dev/.env.example:
--------------------------------------------------------------------------------
1 | NODE_IMAGE="node:20"
2 | PLAYWRIGHT_IMAGE="mcr.microsoft.com/playwright:v1.43.0-jammy"
--------------------------------------------------------------------------------
/.dev/Taskfile.yaml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | dotenv: [.env]
4 |
5 | vars:
6 | MY_UID: $(id -u) # TODO: Change this line if you are not on Linux
7 | MY_GID: $(id -g) # TODO: Change this line if you are not on Linux
8 |
9 | tasks:
10 | env:
11 | desc: makes .env from .env.example
12 | cmds:
13 | - cp .env.example .env
14 |
15 | init:
16 | desc: prepare everything for development
17 | cmds:
18 | - task root:yarn
19 | - task lib:yarn
20 | - task docs:yarn
21 | - docker pull $PLAYWRIGHT_IMAGE
22 | - docker build -t unit-tests-ui - < ./UnitTestUi.Dockerfile --build-arg NODE_IMAGE
23 |
24 | up:
25 | desc: start services
26 | cmds:
27 | - MY_UID="{{.MY_UID}}" MY_GID="{{.MY_GID}}" docker compose up -d
28 |
29 | down:
30 | desc: stops services
31 | cmds:
32 | - MY_UID="{{.MY_UID}}" MY_GID="{{.MY_GID}}" docker compose down
33 |
34 | restart:
35 | desc: restart services
36 | cmds:
37 | - task down
38 | - task up
39 |
40 | logs:
41 | desc: logs the development compose
42 | cmds:
43 | - docker compose logs -f
44 |
45 | # ROOT
46 | root:
47 | desc: launch node with mounted root directory
48 | cmds:
49 | - |
50 | docker run --user {{.MY_UID}}:{{.MY_GID}} \
51 | -it --rm \
52 | -w /app \
53 | -v $(pwd)/..:/app \
54 | $NODE_IMAGE /bin/bash
55 |
56 | root:yarn:
57 | desc: runs 'yarn' in root directory
58 | cmds:
59 | - |
60 | docker run --user {{.MY_UID}}:{{.MY_GID}} \
61 | --rm \
62 | -w /app \
63 | -v $(pwd)/..:/app \
64 | $NODE_IMAGE /bin/bash -c "yarn {{.CLI_ARGS }}"
65 |
66 | root:lint:
67 | desc: runs 'lint' in root directory
68 | cmds:
69 | - |
70 | docker run --user {{.MY_UID}}:{{.MY_GID}} \
71 | --rm \
72 | -w /app \
73 | -v $(pwd)/..:/app \
74 | $NODE_IMAGE /bin/bash -c "yarn lint"
75 |
76 | root:lint:fix:
77 | desc: runs 'lint:fix' in root directory
78 | cmds:
79 | - |
80 | docker run --user {{.MY_UID}}:{{.MY_GID}} \
81 | --rm \
82 | -w /app \
83 | -v $(pwd)/..:/app \
84 | $NODE_IMAGE /bin/bash -c "yarn lint:fix"
85 |
86 | # LIB
87 | lib:
88 | desc: launch node with mounted 'lib' directory
89 | cmds:
90 | - |
91 | docker run --user {{.MY_UID}}:{{.MY_GID}} \
92 | -it --rm \
93 | -w /app \
94 | -v $(pwd)/../lib:/app \
95 | $NODE_IMAGE /bin/bash
96 |
97 | lib:yarn:
98 | desc: runs 'yarn' in 'lib' directory
99 | cmds:
100 | - |
101 | docker run --user {{.MY_UID}}:{{.MY_GID}} \
102 | --rm \
103 | -w /app \
104 | -v $(pwd)/../lib:/app \
105 | $NODE_IMAGE /bin/bash -c "yarn {{.CLI_ARGS }}"
106 |
107 | lib:build:
108 | desc: runs 'yarn build' in 'lib' directory
109 | cmds:
110 | - |
111 | docker run --user {{.MY_UID}}:{{.MY_GID}} \
112 | --rm \
113 | -w /app \
114 | -v $(pwd)/../lib:/app \
115 | $NODE_IMAGE /bin/bash -c "yarn build && yarn minify"
116 | - rm ../lib/dist/the-supersonic-plugin-for-scroll-based-animation.js
117 | - mv ../lib/dist/the-supersonic-plugin-for-scroll-based-animation.min.js ../lib/dist/the-supersonic-plugin-for-scroll-based-animation.js
118 |
119 | # DOCS
120 | docs:
121 | desc: launch node with mounted 'docs' directory
122 | cmds:
123 | - |
124 | docker run --user {{.MY_UID}}:{{.MY_GID}} \
125 | -it --rm \
126 | -w /app \
127 | -v $(pwd)/../docs:/app \
128 | $NODE_IMAGE /bin/bash
129 |
130 | docs:yarn:
131 | desc: runs 'yarn' in 'docs' directory
132 | cmds:
133 | - |
134 | docker run --user {{.MY_UID}}:{{.MY_GID}} \
135 | --rm \
136 | -w /app \
137 | -v $(pwd)/../docs:/app \
138 | $NODE_IMAGE /bin/bash -c "yarn {{.CLI_ARGS }}"
139 |
140 | docs:build:
141 | desc: runs 'yarn build' in 'docs' directory
142 | cmds:
143 | - |
144 | docker run --user {{.MY_UID}}:{{.MY_GID}} \
145 | --rm \
146 | -w /app/docs \
147 | -v $(pwd)/../docs:/app/docs \
148 | -v $(pwd)/../lib:/app/lib \
149 | $NODE_IMAGE /bin/bash -c "yarn build"
150 |
151 | # TESTS
152 | test:unit:
153 | desc: runs unit tests in 'lib' directory
154 | cmds:
155 | - |
156 | docker run --user {{.MY_UID}}:{{.MY_GID}} \
157 | -v $(pwd)/../lib:/app \
158 | -w /app \
159 | $NODE_IMAGE \
160 | yarn test:unit
161 |
162 | test:e2e:
163 | desc: runs Playwright
164 | cmds:
165 | - |
166 | docker run --user {{.MY_UID}}:{{.MY_GID}} \
167 | --rm --network="host" --ipc=host \
168 | -v $(pwd)/../docs:/app \
169 | -w /app \
170 | $PLAYWRIGHT_IMAGE \
171 | yarn test:e2e
172 |
173 | test:e2e:ui:
174 | desc: runs Playwright in UI mode (works only in Linux)
175 | # TODO: Please refer to https://www.oddbird.net/2022/11/30/headed-playwright-in-docker/ if you want to make it work on Windows or macOS
176 | cmds:
177 | - |
178 | docker run --user {{.MY_UID}}:{{.MY_GID}} \
179 | --rm --network="host" --ipc=host \
180 | -e DISPLAY=${DISPLAY} \
181 | -v /tmp/.X11-unix:/tmp/.X11-unix \
182 | -v $(pwd)/../docs:/app \
183 | -w /app \
184 | $PLAYWRIGHT_IMAGE \
185 | yarn test:e2e:ui
186 |
187 | test:
188 | cmds:
189 | - task test:unit
190 | - task test:e2e
191 |
--------------------------------------------------------------------------------
/.dev/UnitTestUi.Dockerfile:
--------------------------------------------------------------------------------
1 | ARG NODE_IMAGE
2 |
3 | FROM ${NODE_IMAGE}
4 |
5 | RUN apt update && apt install -y xdg-utils
6 |
7 | # @vitest/ui needs xdg-utils to be able to spawn UI server
--------------------------------------------------------------------------------
/.dev/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | docs:
3 | image: ${NODE_IMAGE}
4 | working_dir: /app/docs
5 | command: yarn dev
6 | user: ${MY_UID}:${MY_GID}
7 | ports:
8 | - 80:80
9 | volumes:
10 | - ../docs:/app/docs
11 | - ../lib:/app/lib
12 |
13 | docs-preview:
14 | image: ${NODE_IMAGE}
15 | working_dir: /app/docs
16 | command: yarn preview
17 | user: ${MY_UID}:${MY_GID}
18 | ports:
19 | - 9000:80
20 | volumes:
21 | - ../docs:/app/docs
22 | - ../lib:/app/lib
23 |
24 | lib-tests-unit:
25 | image: unit-tests-ui
26 | user: ${MY_UID}:${MY_GID}
27 | working_dir: /app
28 | command: yarn test:unit:ui
29 | ports:
30 | - 9010:8000
31 | volumes:
32 | - ../lib:/app
33 |
--------------------------------------------------------------------------------
/.github/README.md:
--------------------------------------------------------------------------------
1 | # The Supersonic Plugin For Scroll Based Animation
2 |
3 | 
4 | 
5 |
6 | - [Main features](#main-features)
7 | - [Installation](#installation)
8 | - [Examples](#examples)
9 | - [Configuration](#configuration)
10 | - [Possible issues](#possible-issues)
11 |
12 | ---
13 |
14 | Once upon a time, I suddenly realized that I need to animate 1000 pictures of my cat Bartholomew with scrolling. Moreover, it had to work on mobiles as is, without any additional rules. And work fast without killing the browser. And be highly customizable to fit any needs.
15 |
16 | Other solutions did not go well, so I decided to write my own (of course, I did).
17 |
18 | [Go check the final result and see how 1000 Bartholomews are animated!](https://the-illarionov.com/the-supersonic-plugin-for-scroll-based-animation/bartholomeow)
19 |
20 | # Main features
21 |
22 | 1. ## 2.5kb gzipped
23 | - 0 dependencies
24 | - 100% TypeScript
25 | - ESM and IIFE formats
26 |
27 | 2. ## Use native CSS animations
28 | ```javascript
29 | const plugin = new TheSupersonicPlugin([
30 | {
31 | start: '.start', // Animation starts when this element appears on the screen
32 | end: '.end', // Animation ends when this element appears on the screen
33 | elements: ['.animatable'] // List of elements with CSS animations
34 | },
35 | ]);
36 | ```
37 | ```css
38 | .animatable {
39 | animation-name: hooray;
40 | animation-duration: 10s; /* It has to be 10s, more details in examples */
41 | animation-play-state: paused; /* It will be controlled by script */
42 | }
43 | ```
44 | No more struggling with calculating animations, dealing with orchestrating them etc.
45 |
46 | Write your CSS animations as usual and let the plugin to control them. You can even have CSS animations that are being played as usual together with plugin controlled ones! [Check examples](https://the-illarionov.com/the-supersonic-plugin-for-scroll-based-animation/examples).
47 |
48 | As a bonus, you can have different animations for different screens with 0 lines of javascript code!
49 |
50 | Sure, we have `scroll-timeline` upcoming, but it has some disavantages:
51 | - Still experimental at the time of April 2024.
52 | - You can't bind start of an animation to one element and end of it to completely another.
53 |
54 | 3. ## Use DOM elements as drivers of your animation
55 |
56 | Instead of manually setting the start and end values, you use HTML elements (**_drivers_**). Their appearance and position on the screen will control the animation. The Plugin uses the driver's top offset as a value, [check examples](https://the-illarionov.com/the-supersonic-plugin-for-scroll-based-animation/examples).
57 |
58 | And again, as a bonus of using DOM elements as drivers you don't have to think about responsivity, it comes out of the box (you can use `media queries` to reposition your drivers).
59 |
60 | 4. ## Customization and crazy flexibility
61 | Every internal stuff has a hook. Actually, everything that plugin does it does through it's API so you create as complex stuff as you want.
62 |
63 | Look at the [type declarations](https://the-illarionov.com/the-supersonic-plugin-for-scroll-based-animation/types) to discover all of the customization possibilities.
64 |
65 | You can even use plugin to animate whatever you like, not only CSS animations. Check [last example here](https://the-illarionov.com/the-supersonic-plugin-for-scroll-based-animation/examples), you will be surprised ;)
66 |
67 | 5. ## Speed
68 |
69 | The Plugin uses IntersectionObserver, so only visible elements will be processed. It means you can have lots of elements!
70 |
71 | Also, there are small tricks for tiny optimizations.
72 |
73 | For example, float number rounding after the decimal point is used heavily, and instead of ```parseFloat(float.toFixed(2))``` The Plugin uses bitwise rounding:
74 | ```javascript
75 | function supersonicToFixed(number: number, precision: number) {
76 | precision = 10 ** precision
77 | return ~~(number * precision) / precision
78 | }
79 | ```
80 | It is 90% more performant.
81 |
82 | As a result, [1000 Bartholomews](https://the-illarionov.com/the-supersonic-plugin-for-scroll-based-animation/bartholomeow) perform like this on 10 years old PC (i7-4770, 8Gb RAM):
83 |
84 | 
85 |
86 | Yeah, it lags a little, but check the Heap size and total Scripting time!
87 |
88 | The Plugin itself still works extremely fast, it takes about ***0.5ms*** per frame to make all of the calculations, the rest is rendering.
89 |
90 | It is one goddamn thousand HTML elements animating over here!
91 |
92 | 6. ## SPA-ready
93 |
94 | Plugin has `uninit()` method, which clears all of the stuff. Don't forget to call it when you unmount your component.
95 |
96 | ```javascript
97 | onMounted() {
98 | const plugin = new TheSupersonicPlugin([
99 | // config...
100 | ])
101 | }
102 |
103 | onBeforeUnmount() {
104 | plugin.uninit()
105 | }
106 | ```
107 |
108 | And all of this at a price of 2.5Kb!
109 |
110 | So what are you waiting for?
111 |
112 | # Installation
113 |
114 | ## NPM
115 |
116 | ```
117 | npm install the-supersonic-plugin-for-scroll-based-animation --save-dev
118 | ```
119 |
120 | ```javascript
121 | import { TheSupersonicPlugin } from "the-supersonic-plugin-for-scroll-based-animation"
122 |
123 | new TheSupersonicPlugin([
124 | // ...configuration
125 | ])
126 | ```
127 |
128 | ## CDN
129 | ### IIFE
130 | ```html
131 |
132 |
133 |
138 | ```
139 | ### ESM
140 | ```javascript
141 | import { TheSupersonicPlugin } from "https://esm.sh/the-supersonic-plugin-for-scroll-based-animation"
142 |
143 | new TheSupersonicPlugin([
144 | // ...configuration
145 | ])
146 | ```
147 |
148 | # Examples
149 |
150 | You can [find them here](https://the-illarionov.com/the-supersonic-plugin-for-scroll-based-animation/examples).
151 |
152 | # Configuration
153 | Check [types declarations](https://the-illarionov.com/the-supersonic-plugin-for-scroll-based-animation/types) to see all of the configuration options.
154 |
155 | The Plugin consists of 3 classes:
156 | 1. `TheSupersonicPlugin`, which acts like main entry point.
157 | 2. `Driver`, which calculates it's progress from 0 to 1 according to current scroll and position of `start` and `end` elements.
158 | 3. `Animation`, which is responsible for storing and setting CSS Animation `currentTime` property.
159 |
160 | All of them are provided too, so you can manually create your own instances:
161 | ```javascript
162 | import {
163 | TheSupersonicPlugin,
164 | Driver,
165 | Animation
166 | } from "https://esm.sh/the-supersonic-plugin-for-scroll-based-animation"
167 |
168 | const plugin = new TheSupersonicPlugin([
169 | // ...configuration
170 | ])
171 |
172 | const animation = new Animation({
173 | driver: plugin.driverInstances.get('some-driver'),
174 | // ...another configuration
175 | })
176 | ```
177 |
178 | ## Possible issues
179 | 1. When you have **lots** of animations (more 500), plugin can start to lag a little on start.
180 |
181 | It's because `domElement.getAnimations()` doesn't scale too well.
182 |
183 | If you really need to have lots of animations, implement lazy initialization of animation at `onActivation` driver hook. Or you can manually updating properties like [i did with 1000 Bartholomeows](https://the-illarionov.com/the-supersonic-plugin-for-scroll-based-animation/bartholomeow).
184 |
185 | 2. If you have **lots** of drivers (more 1000), plugin can start to lag a little on start.
186 |
187 | It's because plugin fires `updateLimits()` on start to set proper top distances to all elements, which causes `reflow`.
188 |
189 | If it's really a problem, consider cancelling default rendering via `onBeforeRender: () => false` and batching all of `updateLimits` call at one call.
190 |
191 | ---
192 |
193 | Cheers from Bartholomew :3
194 |
--------------------------------------------------------------------------------
/.github/images/performance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/.github/images/performance.png
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: deploy
2 |
3 | run-name: Deploying
4 |
5 | concurrency:
6 | group: '${{ github.workflow }}'
7 | cancel-in-progress: true
8 |
9 | on:
10 | workflow_dispatch:
11 | push:
12 | branches:
13 | master
14 |
15 | jobs:
16 | deploy:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v4
20 |
21 | - name: Download artifacts
22 | uses: actions/github-script@v7
23 | with:
24 | script: |
25 | const { data } = await github.rest.actions.listWorkflowRuns({
26 | owner: context.repo.owner,
27 | repo: context.repo.repo,
28 | workflow_id: 'test-and-build.yml',
29 | status: 'completed',
30 | });
31 |
32 | const successfulRunId = data.workflow_runs.find(run => run.conclusion === 'success').id;
33 |
34 | const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
35 | owner: context.repo.owner,
36 | repo: context.repo.repo,
37 | run_id: successfulRunId,
38 | });
39 |
40 | const artifactId = allArtifacts.data.artifacts.find((artifact) => artifact.name == "docs-dist").id;
41 |
42 | const downloadArtifact = await github.rest.actions.downloadArtifact({
43 | owner: context.repo.owner,
44 | repo: context.repo.repo,
45 | artifact_id: artifactId,
46 | archive_format: 'zip',
47 | });
48 |
49 | let fs = require('fs');
50 | fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/docs.zip`, Buffer.from(downloadArtifact.data));
51 |
52 | - name: Unzip artifacts
53 | run: |
54 | unzip docs.zip -d dist
55 |
56 | - name: Deploy to Cloudflare Pages
57 | uses: cloudflare/pages-action@v1
58 | with:
59 | apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
60 | accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
61 | projectName: the-supersonic-plugin-for-scroll-based-animation
62 | directory: dist
63 | branch: main
64 |
--------------------------------------------------------------------------------
/.github/workflows/test-and-build.yml:
--------------------------------------------------------------------------------
1 | name: test-and-build
2 |
3 | run-name: Tests and prepares build for deploying
4 |
5 | concurrency:
6 | group: '${{ github.workflow }}'
7 | cancel-in-progress: true
8 |
9 | on:
10 | workflow_dispatch:
11 | pull_request:
12 | branches:
13 | master
14 |
15 | jobs:
16 | test:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v4
20 |
21 | - uses: arduino/setup-task@v2
22 |
23 | - name: Prepare env
24 | run: task -d .ci env
25 |
26 | - name: 'Cache node_modules: check'
27 | uses: actions/cache/restore@v4
28 | id: cache-node-modules
29 | with:
30 | path: |
31 | node_modules
32 | docs/node_modules
33 | lib/node_modules
34 | key: node-modules-${{ hashFiles('yarn.lock', 'docs/yarn.lock', 'lib/yarn.lock') }}
35 |
36 | - if: steps.cache-node-modules.outputs.cache-hit != 'true'
37 | name: 'Cache node_modules doesn''t exist: install'
38 | run: task -d .ci yarn
39 |
40 | - if: steps.cache-node-modules.outputs.cache-hit != 'true'
41 | name: 'Cache node_modules doesn''t exist: save node_modules to cache'
42 | uses: actions/cache/save@v4
43 | with:
44 | path: |
45 | node_modules
46 | docs/node_modules
47 | lib/node_modules
48 | key: node-modules-${{ hashFiles('yarn.lock', 'docs/yarn.lock', 'lib/yarn.lock') }}
49 |
50 | - name: Build
51 | run: task -d .ci build
52 |
53 | - name: Runs server
54 | run: task -d .ci up
55 |
56 | - name: Tests
57 | run: task -d .ci test
58 |
59 | - name: 'Uploads ''docs/dist'' artifact'
60 | uses: actions/upload-artifact@v4
61 | with:
62 | path: docs/dist
63 | name: docs-dist
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | node_modules
3 | _old
4 | notes
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-playwright.playwright",
4 | "dbaeumer.vscode-eslint",
5 | "stylelint.vscode-stylelint"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | // Enable the ESlint flat config support
3 | "eslint.experimental.useFlatConfig": true,
4 |
5 | "editor.formatOnSave": false,
6 |
7 | // Auto fix
8 | "editor.codeActionsOnSave": {
9 | "source.fixAll": "explicit",
10 | "source.organizeImports": "never"
11 | },
12 |
13 | // Silent the stylistic rules in you IDE, but still auto fix them
14 | "eslint.rules.customizations": [
15 | { "rule": "style/*", "severity": "off" },
16 | { "rule": "format/*", "severity": "off" },
17 | { "rule": "*-indent", "severity": "off" },
18 | { "rule": "*-spacing", "severity": "off" },
19 | { "rule": "*-spaces", "severity": "off" },
20 | { "rule": "*-order", "severity": "off" },
21 | { "rule": "*-dangle", "severity": "off" },
22 | { "rule": "*-newline", "severity": "off" },
23 | { "rule": "*quotes", "severity": "off" },
24 | { "rule": "*semi", "severity": "off" }
25 | ],
26 |
27 | // Enable eslint for all supported languages
28 | "eslint.validate": [
29 | "javascript",
30 | "javascriptreact",
31 | "typescript",
32 | "typescriptreact",
33 | "html",
34 | "markdown",
35 | "json",
36 | "jsonc",
37 | "yaml",
38 | "toml"
39 | ],
40 |
41 | // My customs
42 | "css.validate": false,
43 | "less.validate": false,
44 | "scss.validate": false,
45 | "stylelint.validate": ["css"]
46 | }
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Alex Illarionov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # The Supersonic Plugin For Scroll Based Animation
2 |
3 | Once upon a time, I suddenly realized that I need to animate 1000 pictures of my cat Bartholomew with scrolling. Moreover, it had to work on mobiles as is, without any additional rules. And work fast without killing the browser. And be highly customizable to fit any needs.
4 |
5 | Other solutions did not go well, so I decided to write my own (of course, I did).
6 |
7 | [Go check the final result and see how 1000 Bartholomews are animated!](https://the-illarionov.com/the-supersonic-plugin-for-scroll-based-animation/bartholomeow)
8 |
9 | [Read the full documentation on GitHub](https://github.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation#readme).
10 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | test-results/
27 | playwright-report/
28 | .wrangler
--------------------------------------------------------------------------------
/docs/bartholomeow.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
13 |
14 | The Supersonic Plugin For Scroll Based Animation: 1000 Bartholomeows!
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/docs/e2e.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
102 |
103 |
107 |
108 |
109 |
110 |
114 |
115 |
116 |
117 |
118 |
119 |
123 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/docs/e2e/ScreenShot.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 |
3 | test('0', async ({ page }) => {
4 | await page.goto('/e2e.html')
5 | await sleep()
6 | await expect(page).toHaveScreenshot({
7 | animations: 'allow',
8 | })
9 | })
10 |
11 | test('500', async ({ page }) => {
12 | await page.goto('/e2e.html')
13 | await sleep()
14 | await page.evaluate(() => window.scrollTo(0, 500))
15 | await sleep()
16 | await expect(page).toHaveScreenshot({
17 | animations: 'allow',
18 | })
19 | })
20 |
21 | test('1000', async ({ page }) => {
22 | await page.goto('/e2e.html')
23 | await sleep()
24 | await page.evaluate(() => window.scrollTo(0, 1000))
25 | await sleep()
26 | await expect(page).toHaveScreenshot({
27 | animations: 'allow',
28 | })
29 | })
30 |
31 | test('3000', async ({ page }) => {
32 | await page.goto('/e2e.html')
33 | await sleep()
34 | await page.evaluate(() => window.scrollTo(0, 3000))
35 | await sleep()
36 | await expect(page).toHaveScreenshot({
37 | animations: 'allow',
38 | })
39 | })
40 |
41 | function sleep(time = 500) {
42 | return new Promise((res) => {
43 | setTimeout(res, time)
44 | })
45 | }
46 |
--------------------------------------------------------------------------------
/docs/e2e/ScreenShot.test.ts-snapshots/0-1-chromium-linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/e2e/ScreenShot.test.ts-snapshots/0-1-chromium-linux.png
--------------------------------------------------------------------------------
/docs/e2e/ScreenShot.test.ts-snapshots/0-1-firefox-linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/e2e/ScreenShot.test.ts-snapshots/0-1-firefox-linux.png
--------------------------------------------------------------------------------
/docs/e2e/ScreenShot.test.ts-snapshots/0-1-webkit-linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/e2e/ScreenShot.test.ts-snapshots/0-1-webkit-linux.png
--------------------------------------------------------------------------------
/docs/e2e/ScreenShot.test.ts-snapshots/1000-1-chromium-linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/e2e/ScreenShot.test.ts-snapshots/1000-1-chromium-linux.png
--------------------------------------------------------------------------------
/docs/e2e/ScreenShot.test.ts-snapshots/1000-1-firefox-linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/e2e/ScreenShot.test.ts-snapshots/1000-1-firefox-linux.png
--------------------------------------------------------------------------------
/docs/e2e/ScreenShot.test.ts-snapshots/1000-1-webkit-linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/e2e/ScreenShot.test.ts-snapshots/1000-1-webkit-linux.png
--------------------------------------------------------------------------------
/docs/e2e/ScreenShot.test.ts-snapshots/3000-1-chromium-linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/e2e/ScreenShot.test.ts-snapshots/3000-1-chromium-linux.png
--------------------------------------------------------------------------------
/docs/e2e/ScreenShot.test.ts-snapshots/3000-1-firefox-linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/e2e/ScreenShot.test.ts-snapshots/3000-1-firefox-linux.png
--------------------------------------------------------------------------------
/docs/e2e/ScreenShot.test.ts-snapshots/3000-1-webkit-linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/e2e/ScreenShot.test.ts-snapshots/3000-1-webkit-linux.png
--------------------------------------------------------------------------------
/docs/e2e/ScreenShot.test.ts-snapshots/500-1-chromium-linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/e2e/ScreenShot.test.ts-snapshots/500-1-chromium-linux.png
--------------------------------------------------------------------------------
/docs/e2e/ScreenShot.test.ts-snapshots/500-1-firefox-linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/e2e/ScreenShot.test.ts-snapshots/500-1-firefox-linux.png
--------------------------------------------------------------------------------
/docs/e2e/ScreenShot.test.ts-snapshots/500-1-webkit-linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/e2e/ScreenShot.test.ts-snapshots/500-1-webkit-linux.png
--------------------------------------------------------------------------------
/docs/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/docs/gsap.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |

16 |
17 |
18 |
19 |

20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
16 | The Supersonic Plugin For Scroll Based Animation
17 |
30 |
31 |
32 | 1. Basic example
33 |
52 |
53 | 2. Customizing CSS animation properties
54 |
75 |
76 | 3. Using driver hooks to implement custom transition
77 |
97 |
98 | 4. Specifying animations
99 |
120 |
121 | 5. Using plugin hooks to stop rendering at all
122 |
143 |
144 | 6. Using plugin 'uninit' to stop rendering at all
145 |
168 |
169 | 7. Using driver hooks to stop rendering
170 |
191 |
192 | 8. Using animation hooks to stop rendering
193 |
214 |
215 | 9. Using animation hooks to manually set currentTime
216 |
237 |
238 | 10. Using driver hooks to animate custom element
239 |
261 |
262 | 11. Replicating GSAP examples: Airpods
263 |
284 |
285 |
286 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docs",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "scripts": {
7 | "dev": "vite --port=80 --host",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview --port=80 --host",
10 | "test:e2e": "playwright test",
11 | "test:e2e:ui": "playwright test --ui",
12 | "deploy": "wrangler pages deploy dist --project-name=the-supersonic-plugin-for-scroll-based-animation --branch=main"
13 | },
14 | "devDependencies": {
15 | "@playwright/test": "^1.43.0",
16 | "@types/node": "^20.12.7",
17 | "postcss": "^8.4.38",
18 | "typescript": "^5.2.2",
19 | "vite": "^5.2.0",
20 | "wrangler": "^3.50.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/docs/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import process from 'node:process'
2 | import { defineConfig, devices } from '@playwright/test'
3 |
4 | /**
5 | * Read environment variables from file.
6 | * https://github.com/motdotla/dotenv
7 | */
8 | // require('dotenv').config();
9 |
10 | /**
11 | * See https://playwright.dev/docs/test-configuration.
12 | */
13 | export default defineConfig({
14 | testDir: './e2e',
15 | /* Maximum time one test can run for. */
16 | timeout: 30 * 1000,
17 | expect: {
18 | /**
19 | * Maximum time expect() should wait for the condition to be met.
20 | * For example in `await expect(locator).toHaveText();`
21 | */
22 | timeout: 5000,
23 | },
24 | /* Fail the build on CI if you accidentally left test.only in the source code. */
25 | forbidOnly: !!process.env.CI,
26 | /* Retry on CI only */
27 | retries: process.env.CI ? 2 : 0,
28 | /* Opt out of parallel tests on CI. */
29 | workers: process.env.CI ? 1 : undefined,
30 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */
31 | reporter: [['html', { open: 'never' }]],
32 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
33 | use: {
34 | /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
35 | actionTimeout: 0,
36 | /* Base URL to use in actions like `await page.goto('/')`. */
37 | baseURL: 'http://localhost',
38 |
39 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
40 | trace: 'on-first-retry',
41 |
42 | headless: true,
43 |
44 | testIdAttribute: 'data-test',
45 | },
46 |
47 | /* Configure projects for major browsers */
48 | projects: [
49 | {
50 | name: 'chromium',
51 | use: {
52 | ...devices['Desktop Chrome'],
53 | viewport: { width: 1000, height: 1000 },
54 | },
55 | },
56 | {
57 | name: 'firefox',
58 | use: {
59 | ...devices['Desktop Firefox'],
60 | viewport: { width: 1000, height: 1000 },
61 | },
62 | },
63 | {
64 | name: 'webkit',
65 | use: {
66 | ...devices['Desktop Safari'],
67 | viewport: { width: 1000, height: 1000 },
68 | },
69 | },
70 |
71 | /* Test against mobile viewports. */
72 | /* {
73 | name: 'Mobile Chrome',
74 | use: {
75 | ...devices['Pixel 5'],
76 | },
77 | }, */
78 | /* {
79 | name: 'Mobile Safari',
80 | use: {
81 | ...devices['iPhone 12'],
82 | },
83 | }, */
84 |
85 | /* Test against branded browsers. */
86 | // {
87 | // name: 'Microsoft Edge',
88 | // use: {
89 | // channel: 'msedge',
90 | // },
91 | // },
92 | // {
93 | // name: 'Google Chrome',
94 | // use: {
95 | // channel: 'chrome',
96 | // },
97 | // },
98 | ],
99 |
100 | /* Folder for test artifacts such as screenshots, videos, traces, etc. */
101 | // outputDir: 'test-results/',
102 | })
103 |
--------------------------------------------------------------------------------
/docs/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | function bartholomeow() {
2 | return {
3 | postcssPlugin: 'bartholomeow',
4 | AtRule: {
5 | barth: (atRule) => {
6 | const size = Number.parseInt(atRule.params)
7 | let resultCss = ''
8 | let row = 0
9 | for (let i = 0; i < 49; i++) {
10 | if (i % 7 === 0 && i > 0)
11 | row++
12 |
13 | resultCss += `
14 | .barth-${i + 1} {
15 | background-position: ${(i % 7) * size}px ${row * size}px
16 | }
17 | `
18 | }
19 | atRule.after(resultCss)
20 | atRule.remove()
21 | },
22 | },
23 | }
24 | }
25 |
26 | bartholomeow.postcss = true
27 |
28 | module.exports = {
29 | plugins: [
30 | bartholomeow,
31 | ],
32 | }
33 |
--------------------------------------------------------------------------------
/docs/public/bartholomeow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/public/bartholomeow.png
--------------------------------------------------------------------------------
/docs/public/example.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/public/example.glb
--------------------------------------------------------------------------------
/docs/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/public/worm-ball.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/public/worm-ball.png
--------------------------------------------------------------------------------
/docs/public/worm-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/public/worm-bg.png
--------------------------------------------------------------------------------
/docs/src/bartholomeow.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | * {
8 | box-sizing: border-box;
9 | }
10 |
11 | body {
12 | position: relative;
13 | }
14 |
15 | body {
16 | overflow-x: hidden;
17 | background: #fff;
18 | }
19 |
20 | .driver {
21 | position: absolute;
22 | }
23 |
24 | .barth {
25 | position: absolute;
26 |
27 | width: 170px;
28 | height: 170px;
29 |
30 | background-image: url("/bartholomeow.png");
31 | border: 15px solid rgb(32 32 32);
32 | border-radius: 100%;
33 | }
34 |
35 | @barth 150;
36 |
37 | @media (width <= 1024px) {
38 | .barth {
39 | width: 100px;
40 | height: 100px;
41 | background-size: 700px 700px;
42 | border-width: 10px;
43 | }
44 |
45 | @barth 100;
46 | }
--------------------------------------------------------------------------------
/docs/src/bartholomeow.ts:
--------------------------------------------------------------------------------
1 | import { TheSupersonicPlugin } from '../../lib/src'
2 | import type { DriverConfiguration } from '../../lib/src/Driver.types'
3 | import { toFixed } from '../../lib/src/utils'
4 |
5 | const isMobile = matchMedia('(max-width: 1024px)').matches
6 | const elementSize: number = isMobile ? 150 : 190
7 | const elementsAmount = 1000
8 | const maxHorizontalElements = Math.ceil(window.innerWidth / elementSize)
9 | const maxRows = Math.ceil(window.innerHeight / elementSize)
10 | const elementsOnScreen = maxRows * maxHorizontalElements
11 | const drivers: DriverConfiguration[] = []
12 |
13 | const props = [
14 | {
15 | name: 'translateX',
16 | minStart: 0,
17 | maxStart: 0,
18 | minEnd: -(window.innerWidth / 5),
19 | maxEnd: window.innerWidth / 5,
20 | },
21 |
22 | {
23 | name: 'translateY',
24 | minStart: 0,
25 | maxStart: 0,
26 | minEnd: -(window.innerHeight / 5),
27 | maxEnd: window.innerHeight / 5,
28 | },
29 |
30 | {
31 | name: 'rotate',
32 | minStart: 0,
33 | maxStart: 0,
34 | minEnd: -90,
35 | maxEnd: 90,
36 | },
37 | {
38 | name: 'scale',
39 | minStart: 1,
40 | maxStart: 1,
41 | minEnd: 3,
42 | maxEnd: 3.5,
43 | },
44 | ]
45 |
46 | let row = 0
47 | const last10sprites: any = []
48 | function gen() {
49 | return Math.floor(Math.random() * 49) + 1
50 | }
51 |
52 | function randomSprite() {
53 | const index = gen()
54 | if (last10sprites.includes(index))
55 | return randomSprite()
56 | if (last10sprites.length === 10)
57 | last10sprites.shift()
58 | last10sprites.push(index)
59 | return index
60 | }
61 | for (let i = 0; i < elementsAmount; i++) {
62 | const sprite = randomSprite()
63 | const image = document.createElement('span')
64 | image.classList.add('barth')
65 | image.classList.add(`barth-${sprite}`)
66 |
67 | if (i % maxHorizontalElements === 0 && i > 0)
68 | row++
69 |
70 | const elementTop = row * elementSize
71 |
72 | image.style.top = `${elementTop}px`
73 | image.style.left = `${(i % maxHorizontalElements) * elementSize}px`
74 | // @ts-expect-error foo
75 | image.style.zIndex = i + 100
76 | image.id = `barth-${i}`
77 |
78 | document.body.appendChild(image)
79 | }
80 |
81 | const height = Math.ceil(row * elementSize)
82 |
83 | const driverHeight = elementSize * 2
84 | const driverAmount = Math.ceil((height - window.innerHeight) / driverHeight)
85 | const driversOnScreen = Math.ceil(window.innerHeight / driverHeight)
86 |
87 | for (let i = 0; i < driverAmount; i++) {
88 | const driverId = `driver-${i}`
89 | const start = document.createElement('i')
90 | start.classList.add('driver')
91 | const driverStart = i * driverHeight + window.innerHeight
92 | start.style.top = `${driverStart}px`
93 |
94 | document.body.appendChild(start)
95 |
96 | const end = document.createElement('i')
97 | end.classList.add('driver')
98 | end.style.top = `${driverStart + driverHeight + window.innerHeight}px`
99 | document.body.appendChild(end)
100 |
101 | const elementsPerDriver = elementsOnScreen / driversOnScreen
102 |
103 | const driverRow = Math.round(driverStart / elementSize) - driversOnScreen
104 | const driverStartFromElement = driverRow * maxHorizontalElements
105 |
106 | drivers.push({
107 | id: driverId,
108 | start,
109 | end,
110 | hooks: {
111 | onAfterInit(driver) {
112 | driver.data.elements = []
113 |
114 | for (let index = 0; index < elementsPerDriver; index++) {
115 | const element: any = {}
116 | element.domElement = document.querySelector(`#barth-${index + driverStartFromElement}`)!
117 |
118 | element.properties = {}
119 |
120 | for (let i = 0; i < props.length; i++) {
121 | const sourceProp = props[i]
122 |
123 | element.properties[sourceProp.name] = {}
124 |
125 | const targetProp = element.properties[sourceProp.name]
126 |
127 | targetProp.start = Math.random() * (sourceProp.maxStart - sourceProp.minStart) + sourceProp.minStart
128 | if (sourceProp.name === 'opacity' || sourceProp.name === 'scale')
129 | targetProp.start = Number.parseFloat(targetProp.start.toFixed(2))
130 | else targetProp.start = Math.ceil(targetProp.start)
131 |
132 | targetProp.end = Math.random() * (sourceProp.maxEnd - sourceProp.minEnd) + sourceProp.minEnd
133 | if (sourceProp.name === 'opacity' || sourceProp.name === 'scale')
134 | targetProp.end = Number.parseFloat(targetProp.end.toFixed(2))
135 | else targetProp.end = Math.ceil(targetProp.end)
136 |
137 | targetProp.distance = toFixed(targetProp.end - targetProp.start, 2)
138 | }
139 |
140 | driver.data.elements.push(element)
141 | }
142 | },
143 | onAfterRender(driver) {
144 | driver.data.elements.forEach((element: any) => {
145 | const translateX = element.properties.translateX.distance * driver.progress + element.properties.translateX.start
146 | const translateY = element.properties.translateY.distance * driver.progress + element.properties.translateY.start
147 | const scale = element.properties.scale.distance * driver.progress + element.properties.scale.start
148 | const rotate = element.properties.rotate.distance * driver.progress + element.properties.rotate.start
149 |
150 | element.domElement.style.setProperty('transform',
151 | // eslint-disable-next-line prefer-template
152 | 'translate3d(' + translateX + 'px, ' + translateY + 'px, 0) '
153 | + 'scale(' + scale + ') '
154 | + 'rotate(' + rotate + 'deg')
155 | })
156 | },
157 | },
158 | })
159 | }
160 |
161 | const plugin = new TheSupersonicPlugin(
162 | drivers,
163 | )
164 |
165 | // @ts-expect-error foo
166 | window.plugin = plugin
167 |
--------------------------------------------------------------------------------
/docs/src/e2e.ts:
--------------------------------------------------------------------------------
1 | import { TheSupersonicPlugin } from '../../lib/src/index'
2 |
3 | const scrollLog = document.querySelector('#scroll-log')
4 |
5 | const plugin = new TheSupersonicPlugin([
6 | {
7 | start: '.top-100',
8 | end: '.top-200',
9 | elements: [
10 | '.basic',
11 | {
12 | selector: '.cancel-render-animation',
13 | animations: [
14 | {
15 | name: 'basic',
16 | hooks: {
17 | onBeforeRender() {
18 | return false
19 | },
20 | },
21 | },
22 | ],
23 | },
24 | {
25 | selector: '.changes-bg-on-render',
26 | animations: [
27 | {
28 | name: 'basic',
29 | hooks: {
30 | onBeforeRender(animation) {
31 | animation.domElement.style.background = `#${animation.driver.plugin.scroll.toString(16)}`
32 | },
33 | },
34 | },
35 | ],
36 | },
37 | ],
38 | },
39 | {
40 | start: '.top-100',
41 | end: '.top-200',
42 | elements: ['.cancel-render-driver'],
43 | hooks: {
44 | onBeforeRender() {
45 | return false
46 | },
47 | },
48 | },
49 | {
50 | start: '.top-250',
51 | end: '.top-400',
52 | id: 'bottom',
53 | elements: ['.bottom-driver'],
54 | },
55 | ], {
56 | debug: true,
57 | hooks: {
58 | onBeforeRender(plugin) {
59 | scrollLog!.innerHTML = plugin.scroll.toString()
60 | },
61 | },
62 | })
63 |
64 | console.log(plugin)
65 |
66 | // @ts-expect-error window
67 | window.plugin = plugin
68 |
--------------------------------------------------------------------------------
/docs/src/gsap.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | background: #0e100f;
5 | }
6 |
7 | .worm-wrapper {
8 | width: 584px;
9 | padding: 80px 0;
10 | position: relative;
11 | }
12 |
13 | .worm-container {
14 |
15 | height: 500px;
16 | }
17 |
18 | .worm-container__path {
19 | width: 100%;
20 | }
21 |
22 | .worm-container__ball {
23 | position: absolute;
24 | left: 322px;
25 | width: 93px;
26 | top: -93px;
27 | }
28 |
29 | .worm-container__ball-y {
30 | /* animation-name: ball-y; */
31 | animation-duration: 1s;
32 | animation-timing-function: linear;
33 |
34 | animation-iteration-count: infinite;
35 | }
36 |
37 | @keyframes ball-y {
38 | 0% {
39 | translate: 0 0 0;
40 | }
41 | 10% {
42 | translate: 0 247px 0;
43 | }
44 | 20% {
45 | translate: 0 247px 0;
46 | }
47 | 30% {
48 | translate: 0 350px 0;
49 | }
50 | 40% {
51 | translate: 0 350px 0;
52 | }
53 | 50% {
54 | translate: 0 464px 0;
55 | }
56 | 60% {
57 | translate: 0 464px 0;
58 | }
59 | 100% {
60 | translate: 0 740px 0;
61 | }
62 | }
63 |
64 | .worm-container__ball-x {
65 | /* animation-name: ball-x; */
66 | animation-duration: 1s;
67 | animation-timing-function: linear;
68 |
69 | animation-iteration-count: infinite;
70 | }
71 |
72 | @keyframes ball-x {
73 | 0% {
74 | translate: 0 0 0;
75 | }
76 | 10% {
77 | translate: -322px 0 0;
78 | }
79 | 20% {
80 | translate: -322px 0 0;
81 | }
82 | 30% {
83 | translate: 166px 0 0;
84 | }
85 | 40% {
86 | translate: 166px 0 0;
87 | }
88 | 100% {
89 | translate: -174px 0 0;
90 | }
91 | }
--------------------------------------------------------------------------------
/docs/src/gsap.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/35223418e8ef91cc97dbbb206f96d8143c40a7a0/docs/src/gsap.ts
--------------------------------------------------------------------------------
/docs/tests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | TESTS
8 |
9 |
60 |
61 |
62 |
63 |
Lib Unit
64 |
68 |
69 |
70 |
71 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/docs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
5 | "useDefineForClassFields": true,
6 | "module": "ESNext",
7 |
8 | /* Bundler mode */
9 | "moduleResolution": "bundler",
10 |
11 | "paths": {
12 | "@lib/*": ["../lib/src/*"]
13 | },
14 | "resolveJsonModule": true,
15 | "allowImportingTsExtensions": true,
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noFallthroughCasesInSwitch": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "noEmit": true,
23 | "isolatedModules": true,
24 | "skipLibCheck": true
25 | },
26 | "include": ["src", "vite.config.ts", "playwright.config.ts", "env.d.ts"]
27 | }
28 |
--------------------------------------------------------------------------------
/docs/types.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
13 | The Supersonic Plugin For Scroll Based Animation: Types
14 |
23 |
24 |
25 |
26 |
TheSupersonicPlugin.d.ts
27 |
28 |
29 |
30 |
31 |
Driver.d.ts
32 |
33 |
34 |
35 |
36 |
Animation.d.ts
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/docs/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path'
2 |
3 | import { defineConfig } from 'vite'
4 |
5 | export default defineConfig(() => {
6 | return {
7 | build: {
8 | rollupOptions: {
9 | input: {
10 | main: resolve(__dirname, 'index.html'),
11 | barth: resolve(__dirname, 'bartholomeow.html'),
12 | types: resolve(__dirname, 'types.html'),
13 | e2e: resolve(__dirname, 'e2e.html'),
14 | },
15 | },
16 | },
17 | }
18 | })
19 |
--------------------------------------------------------------------------------
/docs/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@cloudflare/kv-asset-handler@0.3.1":
6 | version "0.3.1"
7 | resolved "https://registry.yarnpkg.com/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.1.tgz#9b86167e58dbc419943c8d3ddcd8e2823f5db300"
8 | integrity sha512-lKN2XCfKCmpKb86a1tl4GIwsJYDy9TGuwjhDELLmpKygQhw8X2xR4dusgpC5Tg7q1pB96Eb0rBo81kxSILQMwA==
9 | dependencies:
10 | mime "^3.0.0"
11 |
12 | "@cloudflare/workerd-darwin-64@1.20240405.0":
13 | version "1.20240405.0"
14 | resolved "https://registry.yarnpkg.com/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240405.0.tgz#07637ec31e2b6272c8924c5d6b499ec022378fa5"
15 | integrity sha512-ut8kwpHmlz9dNSjoov6v1b6jS50J46Mj9QcMA0t1Hne36InaQk/qqPSd12fN5p2GesZ9OOBJvBdDsTblVdyJ1w==
16 |
17 | "@cloudflare/workerd-darwin-arm64@1.20240405.0":
18 | version "1.20240405.0"
19 | resolved "https://registry.yarnpkg.com/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240405.0.tgz#5fba9fbfdb6906f5c216ab1e9451a3360daa90fd"
20 | integrity sha512-x3A3Ym+J2DH1uYnw0aedeKOTnUebEo312+Aladv7bFri97pjRJcqVbYhMtOHLkHjwYn7bpKSY2eL5iM+0XT29A==
21 |
22 | "@cloudflare/workerd-linux-64@1.20240405.0":
23 | version "1.20240405.0"
24 | resolved "https://registry.yarnpkg.com/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240405.0.tgz#9f455b9484921b4ef0b30f2f13747abfc73bf86c"
25 | integrity sha512-3tYpfjtxEQ0R30Pna7OF3Bz0CTx30hc0QNtH61KnkvXtaeYMkWutSKQKXIuVlPa/7v1MHp+8ViBXMflmS7HquA==
26 |
27 | "@cloudflare/workerd-linux-arm64@1.20240405.0":
28 | version "1.20240405.0"
29 | resolved "https://registry.yarnpkg.com/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240405.0.tgz#50220db83d4e9f423bb59623a37b67fd4251680f"
30 | integrity sha512-NpKZlvmdgcX/m4tP5zM91AfJpZrue2/GRA+Sl3szxAivu2uE5jDVf5SS9dzqzCVfPrdhylqH7yeL4U/cafFNOg==
31 |
32 | "@cloudflare/workerd-windows-64@1.20240405.0":
33 | version "1.20240405.0"
34 | resolved "https://registry.yarnpkg.com/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240405.0.tgz#4cb542cf8003fcd230bcf3b767f0d697a6cd799d"
35 | integrity sha512-REBeJMxvUCjwuEVzSSIBtzAyM69QjToab8qBst0S9vdih+9DObym4dw8CevdBQhDbFrHiyL9E6pAZpLPNHVgCw==
36 |
37 | "@cspotcode/source-map-support@0.8.1":
38 | version "0.8.1"
39 | resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
40 | integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
41 | dependencies:
42 | "@jridgewell/trace-mapping" "0.3.9"
43 |
44 | "@esbuild-plugins/node-globals-polyfill@^0.2.3":
45 | version "0.2.3"
46 | resolved "https://registry.yarnpkg.com/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz#0e4497a2b53c9e9485e149bc92ddb228438d6bcf"
47 | integrity sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==
48 |
49 | "@esbuild-plugins/node-modules-polyfill@^0.2.2":
50 | version "0.2.2"
51 | resolved "https://registry.yarnpkg.com/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz#cefa3dc0bd1c16277a8338b52833420c94987327"
52 | integrity sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==
53 | dependencies:
54 | escape-string-regexp "^4.0.0"
55 | rollup-plugin-node-polyfills "^0.2.1"
56 |
57 | "@esbuild/aix-ppc64@0.20.2":
58 | version "0.20.2"
59 | resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
60 | integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==
61 |
62 | "@esbuild/android-arm64@0.17.19":
63 | version "0.17.19"
64 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd"
65 | integrity sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==
66 |
67 | "@esbuild/android-arm64@0.20.2":
68 | version "0.20.2"
69 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9"
70 | integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==
71 |
72 | "@esbuild/android-arm@0.17.19":
73 | version "0.17.19"
74 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz#5898f7832c2298bc7d0ab53701c57beb74d78b4d"
75 | integrity sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==
76 |
77 | "@esbuild/android-arm@0.20.2":
78 | version "0.20.2"
79 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995"
80 | integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==
81 |
82 | "@esbuild/android-x64@0.17.19":
83 | version "0.17.19"
84 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz#658368ef92067866d95fb268719f98f363d13ae1"
85 | integrity sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==
86 |
87 | "@esbuild/android-x64@0.20.2":
88 | version "0.20.2"
89 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98"
90 | integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==
91 |
92 | "@esbuild/darwin-arm64@0.17.19":
93 | version "0.17.19"
94 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz#584c34c5991b95d4d48d333300b1a4e2ff7be276"
95 | integrity sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==
96 |
97 | "@esbuild/darwin-arm64@0.20.2":
98 | version "0.20.2"
99 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb"
100 | integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==
101 |
102 | "@esbuild/darwin-x64@0.17.19":
103 | version "0.17.19"
104 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz#7751d236dfe6ce136cce343dce69f52d76b7f6cb"
105 | integrity sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==
106 |
107 | "@esbuild/darwin-x64@0.20.2":
108 | version "0.20.2"
109 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0"
110 | integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==
111 |
112 | "@esbuild/freebsd-arm64@0.17.19":
113 | version "0.17.19"
114 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz#cacd171665dd1d500f45c167d50c6b7e539d5fd2"
115 | integrity sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==
116 |
117 | "@esbuild/freebsd-arm64@0.20.2":
118 | version "0.20.2"
119 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911"
120 | integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==
121 |
122 | "@esbuild/freebsd-x64@0.17.19":
123 | version "0.17.19"
124 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz#0769456eee2a08b8d925d7c00b79e861cb3162e4"
125 | integrity sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==
126 |
127 | "@esbuild/freebsd-x64@0.20.2":
128 | version "0.20.2"
129 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c"
130 | integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==
131 |
132 | "@esbuild/linux-arm64@0.17.19":
133 | version "0.17.19"
134 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz#38e162ecb723862c6be1c27d6389f48960b68edb"
135 | integrity sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==
136 |
137 | "@esbuild/linux-arm64@0.20.2":
138 | version "0.20.2"
139 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5"
140 | integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==
141 |
142 | "@esbuild/linux-arm@0.17.19":
143 | version "0.17.19"
144 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz#1a2cd399c50040184a805174a6d89097d9d1559a"
145 | integrity sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==
146 |
147 | "@esbuild/linux-arm@0.20.2":
148 | version "0.20.2"
149 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c"
150 | integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==
151 |
152 | "@esbuild/linux-ia32@0.17.19":
153 | version "0.17.19"
154 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz#e28c25266b036ce1cabca3c30155222841dc035a"
155 | integrity sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==
156 |
157 | "@esbuild/linux-ia32@0.20.2":
158 | version "0.20.2"
159 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa"
160 | integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==
161 |
162 | "@esbuild/linux-loong64@0.17.19":
163 | version "0.17.19"
164 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz#0f887b8bb3f90658d1a0117283e55dbd4c9dcf72"
165 | integrity sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==
166 |
167 | "@esbuild/linux-loong64@0.20.2":
168 | version "0.20.2"
169 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5"
170 | integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==
171 |
172 | "@esbuild/linux-mips64el@0.17.19":
173 | version "0.17.19"
174 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz#f5d2a0b8047ea9a5d9f592a178ea054053a70289"
175 | integrity sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==
176 |
177 | "@esbuild/linux-mips64el@0.20.2":
178 | version "0.20.2"
179 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa"
180 | integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==
181 |
182 | "@esbuild/linux-ppc64@0.17.19":
183 | version "0.17.19"
184 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz#876590e3acbd9fa7f57a2c7d86f83717dbbac8c7"
185 | integrity sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==
186 |
187 | "@esbuild/linux-ppc64@0.20.2":
188 | version "0.20.2"
189 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20"
190 | integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==
191 |
192 | "@esbuild/linux-riscv64@0.17.19":
193 | version "0.17.19"
194 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz#7f49373df463cd9f41dc34f9b2262d771688bf09"
195 | integrity sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==
196 |
197 | "@esbuild/linux-riscv64@0.20.2":
198 | version "0.20.2"
199 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300"
200 | integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==
201 |
202 | "@esbuild/linux-s390x@0.17.19":
203 | version "0.17.19"
204 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz#e2afd1afcaf63afe2c7d9ceacd28ec57c77f8829"
205 | integrity sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==
206 |
207 | "@esbuild/linux-s390x@0.20.2":
208 | version "0.20.2"
209 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685"
210 | integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==
211 |
212 | "@esbuild/linux-x64@0.17.19":
213 | version "0.17.19"
214 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz#8a0e9738b1635f0c53389e515ae83826dec22aa4"
215 | integrity sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==
216 |
217 | "@esbuild/linux-x64@0.20.2":
218 | version "0.20.2"
219 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff"
220 | integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==
221 |
222 | "@esbuild/netbsd-x64@0.17.19":
223 | version "0.17.19"
224 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz#c29fb2453c6b7ddef9a35e2c18b37bda1ae5c462"
225 | integrity sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==
226 |
227 | "@esbuild/netbsd-x64@0.20.2":
228 | version "0.20.2"
229 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6"
230 | integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==
231 |
232 | "@esbuild/openbsd-x64@0.17.19":
233 | version "0.17.19"
234 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz#95e75a391403cb10297280d524d66ce04c920691"
235 | integrity sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==
236 |
237 | "@esbuild/openbsd-x64@0.20.2":
238 | version "0.20.2"
239 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf"
240 | integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==
241 |
242 | "@esbuild/sunos-x64@0.17.19":
243 | version "0.17.19"
244 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz#722eaf057b83c2575937d3ffe5aeb16540da7273"
245 | integrity sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==
246 |
247 | "@esbuild/sunos-x64@0.20.2":
248 | version "0.20.2"
249 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f"
250 | integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==
251 |
252 | "@esbuild/win32-arm64@0.17.19":
253 | version "0.17.19"
254 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz#9aa9dc074399288bdcdd283443e9aeb6b9552b6f"
255 | integrity sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==
256 |
257 | "@esbuild/win32-arm64@0.20.2":
258 | version "0.20.2"
259 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90"
260 | integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==
261 |
262 | "@esbuild/win32-ia32@0.17.19":
263 | version "0.17.19"
264 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz#95ad43c62ad62485e210f6299c7b2571e48d2b03"
265 | integrity sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==
266 |
267 | "@esbuild/win32-ia32@0.20.2":
268 | version "0.20.2"
269 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23"
270 | integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==
271 |
272 | "@esbuild/win32-x64@0.17.19":
273 | version "0.17.19"
274 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061"
275 | integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==
276 |
277 | "@esbuild/win32-x64@0.20.2":
278 | version "0.20.2"
279 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc"
280 | integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==
281 |
282 | "@fastify/busboy@^2.0.0":
283 | version "2.1.1"
284 | resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d"
285 | integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==
286 |
287 | "@jridgewell/resolve-uri@^3.0.3":
288 | version "3.1.2"
289 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
290 | integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
291 |
292 | "@jridgewell/sourcemap-codec@^1.4.10":
293 | version "1.4.15"
294 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
295 | integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
296 |
297 | "@jridgewell/trace-mapping@0.3.9":
298 | version "0.3.9"
299 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
300 | integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
301 | dependencies:
302 | "@jridgewell/resolve-uri" "^3.0.3"
303 | "@jridgewell/sourcemap-codec" "^1.4.10"
304 |
305 | "@playwright/test@^1.43.1":
306 | version "1.43.1"
307 | resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.43.1.tgz#16728a59eb8ce0f60472f98d8886d6cab0fa3e42"
308 | integrity sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==
309 | dependencies:
310 | playwright "1.43.1"
311 |
312 | "@rollup/rollup-android-arm-eabi@4.14.1":
313 | version "4.14.1"
314 | resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.1.tgz#ca0501dd836894216cb9572848c5dde4bfca3bec"
315 | integrity sha512-fH8/o8nSUek8ceQnT7K4EQbSiV7jgkHq81m9lWZFIXjJ7lJzpWXbQFpT/Zh6OZYnpFykvzC3fbEvEAFZu03dPA==
316 |
317 | "@rollup/rollup-android-arm64@4.14.1":
318 | version "4.14.1"
319 | resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.1.tgz#154ca7e4f815d2e442ffc62ee7f64aee8b2547b0"
320 | integrity sha512-Y/9OHLjzkunF+KGEoJr3heiD5X9OLa8sbT1lm0NYeKyaM3oMhhQFvPB0bNZYJwlq93j8Z6wSxh9+cyKQaxS7PQ==
321 |
322 | "@rollup/rollup-darwin-arm64@4.14.1":
323 | version "4.14.1"
324 | resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.1.tgz#02b522ab6ccc2c504634651985ff8e657b42c055"
325 | integrity sha512-+kecg3FY84WadgcuSVm6llrABOdQAEbNdnpi5X3UwWiFVhZIZvKgGrF7kmLguvxHNQy+UuRV66cLVl3S+Rkt+Q==
326 |
327 | "@rollup/rollup-darwin-x64@4.14.1":
328 | version "4.14.1"
329 | resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.1.tgz#217737f9f73de729fdfd7d529afebb6c8283f554"
330 | integrity sha512-2pYRzEjVqq2TB/UNv47BV/8vQiXkFGVmPFwJb+1E0IFFZbIX8/jo1olxqqMbo6xCXf8kabANhp5bzCij2tFLUA==
331 |
332 | "@rollup/rollup-linux-arm-gnueabihf@4.14.1":
333 | version "4.14.1"
334 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.1.tgz#a87e478ab3f697c7f4e74c8b1cac1e0667f8f4be"
335 | integrity sha512-mS6wQ6Do6/wmrF9aTFVpIJ3/IDXhg1EZcQFYHZLHqw6AzMBjTHWnCG35HxSqUNphh0EHqSM6wRTT8HsL1C0x5g==
336 |
337 | "@rollup/rollup-linux-arm64-gnu@4.14.1":
338 | version "4.14.1"
339 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.1.tgz#4da6830eca27e5f4ca15f9197e5660952ca185c6"
340 | integrity sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==
341 |
342 | "@rollup/rollup-linux-arm64-musl@4.14.1":
343 | version "4.14.1"
344 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.1.tgz#0b0ed35720aebc8f5e501d370a9ea0f686ead1e0"
345 | integrity sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==
346 |
347 | "@rollup/rollup-linux-powerpc64le-gnu@4.14.1":
348 | version "4.14.1"
349 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.1.tgz#024ad04d162726f25e62915851f7df69a9677c17"
350 | integrity sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==
351 |
352 | "@rollup/rollup-linux-riscv64-gnu@4.14.1":
353 | version "4.14.1"
354 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.1.tgz#180694d1cd069ddbe22022bb5b1bead3b7de581c"
355 | integrity sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==
356 |
357 | "@rollup/rollup-linux-s390x-gnu@4.14.1":
358 | version "4.14.1"
359 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.1.tgz#f7b4e2b0ca49be4e34f9ef0b548c926d94edee87"
360 | integrity sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==
361 |
362 | "@rollup/rollup-linux-x64-gnu@4.14.1":
363 | version "4.14.1"
364 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.1.tgz#0aaf79e5b9ccf7db3084fe6c3f2d2873a27d5af4"
365 | integrity sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==
366 |
367 | "@rollup/rollup-linux-x64-musl@4.14.1":
368 | version "4.14.1"
369 | resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.1.tgz#38f0a37ca5015eb07dff86a1b6f94279c179f4ed"
370 | integrity sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==
371 |
372 | "@rollup/rollup-win32-arm64-msvc@4.14.1":
373 | version "4.14.1"
374 | resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.1.tgz#84d48c55740ede42c77373f76e85f368633a0cc3"
375 | integrity sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==
376 |
377 | "@rollup/rollup-win32-ia32-msvc@4.14.1":
378 | version "4.14.1"
379 | resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.1.tgz#c1e0bc39e20e760f0a526ddf14ae0543af796605"
380 | integrity sha512-TdloItiGk+T0mTxKx7Hp279xy30LspMso+GzQvV2maYePMAWdmrzqSNZhUpPj3CGw12aGj57I026PgLCTu8CGg==
381 |
382 | "@rollup/rollup-win32-x64-msvc@4.14.1":
383 | version "4.14.1"
384 | resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.1.tgz#299eee74b7d87e116083ac5b1ce8dd9434668294"
385 | integrity sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew==
386 |
387 | "@types/estree@1.0.5":
388 | version "1.0.5"
389 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
390 | integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
391 |
392 | "@types/json-schema@^7.0.15":
393 | version "7.0.15"
394 | resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
395 | integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
396 |
397 | "@types/node-forge@^1.3.0":
398 | version "1.3.11"
399 | resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da"
400 | integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==
401 | dependencies:
402 | "@types/node" "*"
403 |
404 | "@types/node@*", "@types/node@^20.12.7":
405 | version "20.12.7"
406 | resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.7.tgz#04080362fa3dd6c5822061aa3124f5c152cff384"
407 | integrity sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==
408 | dependencies:
409 | undici-types "~5.26.4"
410 |
411 | acorn-walk@^8.2.0:
412 | version "8.3.2"
413 | resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa"
414 | integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==
415 |
416 | acorn@^8.8.0:
417 | version "8.11.3"
418 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
419 | integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
420 |
421 | anymatch@~3.1.2:
422 | version "3.1.3"
423 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
424 | integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
425 | dependencies:
426 | normalize-path "^3.0.0"
427 | picomatch "^2.0.4"
428 |
429 | as-table@^1.0.36:
430 | version "1.0.55"
431 | resolved "https://registry.yarnpkg.com/as-table/-/as-table-1.0.55.tgz#dc984da3937745de902cea1d45843c01bdbbec4f"
432 | integrity sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==
433 | dependencies:
434 | printable-characters "^1.0.42"
435 |
436 | balanced-match@^1.0.0:
437 | version "1.0.2"
438 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
439 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
440 |
441 | binary-extensions@^2.0.0:
442 | version "2.3.0"
443 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
444 | integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
445 |
446 | blake3-wasm@^2.1.5:
447 | version "2.1.5"
448 | resolved "https://registry.yarnpkg.com/blake3-wasm/-/blake3-wasm-2.1.5.tgz#b22dbb84bc9419ed0159caa76af4b1b132e6ba52"
449 | integrity sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==
450 |
451 | brace-expansion@^2.0.1:
452 | version "2.0.1"
453 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
454 | integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
455 | dependencies:
456 | balanced-match "^1.0.0"
457 |
458 | braces@~3.0.2:
459 | version "3.0.2"
460 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
461 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
462 | dependencies:
463 | fill-range "^7.0.1"
464 |
465 | capnp-ts@^0.7.0:
466 | version "0.7.0"
467 | resolved "https://registry.yarnpkg.com/capnp-ts/-/capnp-ts-0.7.0.tgz#16fd8e76b667d002af8fcf4bf92bf15d1a7b54a9"
468 | integrity sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==
469 | dependencies:
470 | debug "^4.3.1"
471 | tslib "^2.2.0"
472 |
473 | chokidar@^3.5.3:
474 | version "3.6.0"
475 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
476 | integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
477 | dependencies:
478 | anymatch "~3.1.2"
479 | braces "~3.0.2"
480 | glob-parent "~5.1.2"
481 | is-binary-path "~2.1.0"
482 | is-glob "~4.0.1"
483 | normalize-path "~3.0.0"
484 | readdirp "~3.6.0"
485 | optionalDependencies:
486 | fsevents "~2.3.2"
487 |
488 | commander@^12.0.0:
489 | version "12.0.0"
490 | resolved "https://registry.yarnpkg.com/commander/-/commander-12.0.0.tgz#b929db6df8546080adfd004ab215ed48cf6f2592"
491 | integrity sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==
492 |
493 | cookie@^0.5.0:
494 | version "0.5.0"
495 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
496 | integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
497 |
498 | data-uri-to-buffer@^2.0.0:
499 | version "2.0.2"
500 | resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz#d296973d5a4897a5dbe31716d118211921f04770"
501 | integrity sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==
502 |
503 | debug@^4.3.1:
504 | version "4.3.4"
505 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
506 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
507 | dependencies:
508 | ms "2.1.2"
509 |
510 | esbuild@0.17.19:
511 | version "0.17.19"
512 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.19.tgz#087a727e98299f0462a3d0bcdd9cd7ff100bd955"
513 | integrity sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==
514 | optionalDependencies:
515 | "@esbuild/android-arm" "0.17.19"
516 | "@esbuild/android-arm64" "0.17.19"
517 | "@esbuild/android-x64" "0.17.19"
518 | "@esbuild/darwin-arm64" "0.17.19"
519 | "@esbuild/darwin-x64" "0.17.19"
520 | "@esbuild/freebsd-arm64" "0.17.19"
521 | "@esbuild/freebsd-x64" "0.17.19"
522 | "@esbuild/linux-arm" "0.17.19"
523 | "@esbuild/linux-arm64" "0.17.19"
524 | "@esbuild/linux-ia32" "0.17.19"
525 | "@esbuild/linux-loong64" "0.17.19"
526 | "@esbuild/linux-mips64el" "0.17.19"
527 | "@esbuild/linux-ppc64" "0.17.19"
528 | "@esbuild/linux-riscv64" "0.17.19"
529 | "@esbuild/linux-s390x" "0.17.19"
530 | "@esbuild/linux-x64" "0.17.19"
531 | "@esbuild/netbsd-x64" "0.17.19"
532 | "@esbuild/openbsd-x64" "0.17.19"
533 | "@esbuild/sunos-x64" "0.17.19"
534 | "@esbuild/win32-arm64" "0.17.19"
535 | "@esbuild/win32-ia32" "0.17.19"
536 | "@esbuild/win32-x64" "0.17.19"
537 |
538 | esbuild@^0.20.1:
539 | version "0.20.2"
540 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1"
541 | integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==
542 | optionalDependencies:
543 | "@esbuild/aix-ppc64" "0.20.2"
544 | "@esbuild/android-arm" "0.20.2"
545 | "@esbuild/android-arm64" "0.20.2"
546 | "@esbuild/android-x64" "0.20.2"
547 | "@esbuild/darwin-arm64" "0.20.2"
548 | "@esbuild/darwin-x64" "0.20.2"
549 | "@esbuild/freebsd-arm64" "0.20.2"
550 | "@esbuild/freebsd-x64" "0.20.2"
551 | "@esbuild/linux-arm" "0.20.2"
552 | "@esbuild/linux-arm64" "0.20.2"
553 | "@esbuild/linux-ia32" "0.20.2"
554 | "@esbuild/linux-loong64" "0.20.2"
555 | "@esbuild/linux-mips64el" "0.20.2"
556 | "@esbuild/linux-ppc64" "0.20.2"
557 | "@esbuild/linux-riscv64" "0.20.2"
558 | "@esbuild/linux-s390x" "0.20.2"
559 | "@esbuild/linux-x64" "0.20.2"
560 | "@esbuild/netbsd-x64" "0.20.2"
561 | "@esbuild/openbsd-x64" "0.20.2"
562 | "@esbuild/sunos-x64" "0.20.2"
563 | "@esbuild/win32-arm64" "0.20.2"
564 | "@esbuild/win32-ia32" "0.20.2"
565 | "@esbuild/win32-x64" "0.20.2"
566 |
567 | escape-string-regexp@^4.0.0:
568 | version "4.0.0"
569 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
570 | integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
571 |
572 | estree-walker@^0.6.1:
573 | version "0.6.1"
574 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
575 | integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==
576 |
577 | exit-hook@^2.2.1:
578 | version "2.2.1"
579 | resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-2.2.1.tgz#007b2d92c6428eda2b76e7016a34351586934593"
580 | integrity sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==
581 |
582 | fill-range@^7.0.1:
583 | version "7.0.1"
584 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
585 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
586 | dependencies:
587 | to-regex-range "^5.0.1"
588 |
589 | fs.realpath@^1.0.0:
590 | version "1.0.0"
591 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
592 | integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
593 |
594 | fsevents@2.3.2:
595 | version "2.3.2"
596 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
597 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
598 |
599 | fsevents@~2.3.2, fsevents@~2.3.3:
600 | version "2.3.3"
601 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
602 | integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
603 |
604 | function-bind@^1.1.2:
605 | version "1.1.2"
606 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
607 | integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
608 |
609 | get-source@^2.0.12:
610 | version "2.0.12"
611 | resolved "https://registry.yarnpkg.com/get-source/-/get-source-2.0.12.tgz#0b47d57ea1e53ce0d3a69f4f3d277eb8047da944"
612 | integrity sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==
613 | dependencies:
614 | data-uri-to-buffer "^2.0.0"
615 | source-map "^0.6.1"
616 |
617 | glob-parent@~5.1.2:
618 | version "5.1.2"
619 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
620 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
621 | dependencies:
622 | is-glob "^4.0.1"
623 |
624 | glob-to-regexp@^0.4.1:
625 | version "0.4.1"
626 | resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
627 | integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
628 |
629 | glob@^8.0.3:
630 | version "8.1.0"
631 | resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
632 | integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
633 | dependencies:
634 | fs.realpath "^1.0.0"
635 | inflight "^1.0.4"
636 | inherits "2"
637 | minimatch "^5.0.1"
638 | once "^1.3.0"
639 |
640 | hasown@^2.0.0:
641 | version "2.0.2"
642 | resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
643 | integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
644 | dependencies:
645 | function-bind "^1.1.2"
646 |
647 | inflight@^1.0.4:
648 | version "1.0.6"
649 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
650 | integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
651 | dependencies:
652 | once "^1.3.0"
653 | wrappy "1"
654 |
655 | inherits@2:
656 | version "2.0.4"
657 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
658 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
659 |
660 | is-binary-path@~2.1.0:
661 | version "2.1.0"
662 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
663 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
664 | dependencies:
665 | binary-extensions "^2.0.0"
666 |
667 | is-core-module@^2.13.0:
668 | version "2.13.1"
669 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384"
670 | integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==
671 | dependencies:
672 | hasown "^2.0.0"
673 |
674 | is-extglob@^2.1.1:
675 | version "2.1.1"
676 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
677 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
678 |
679 | is-glob@^4.0.1, is-glob@~4.0.1:
680 | version "4.0.3"
681 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
682 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
683 | dependencies:
684 | is-extglob "^2.1.1"
685 |
686 | is-number@^7.0.0:
687 | version "7.0.0"
688 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
689 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
690 |
691 | json5@^2.2.3:
692 | version "2.2.3"
693 | resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
694 | integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
695 |
696 | magic-string@^0.25.3:
697 | version "0.25.9"
698 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
699 | integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
700 | dependencies:
701 | sourcemap-codec "^1.4.8"
702 |
703 | mime@^3.0.0:
704 | version "3.0.0"
705 | resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7"
706 | integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==
707 |
708 | miniflare@3.20240405.1:
709 | version "3.20240405.1"
710 | resolved "https://registry.yarnpkg.com/miniflare/-/miniflare-3.20240405.1.tgz#4a6e335c0f2fbccb4cd94a9abf40e8af4a767254"
711 | integrity sha512-oShOR/ckr9JTO1bkPQH0nXvuSgJjoE+E5+M1tvP01Q8Z+Q0GJnzU2+FDYUH8yIK/atHv7snU8yy0X6KWVn1YdQ==
712 | dependencies:
713 | "@cspotcode/source-map-support" "0.8.1"
714 | acorn "^8.8.0"
715 | acorn-walk "^8.2.0"
716 | capnp-ts "^0.7.0"
717 | exit-hook "^2.2.1"
718 | glob-to-regexp "^0.4.1"
719 | stoppable "^1.1.0"
720 | undici "^5.28.2"
721 | workerd "1.20240405.0"
722 | ws "^8.11.0"
723 | youch "^3.2.2"
724 | zod "^3.20.6"
725 |
726 | minimatch@^5.0.1:
727 | version "5.1.6"
728 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
729 | integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
730 | dependencies:
731 | brace-expansion "^2.0.1"
732 |
733 | ms@2.1.2:
734 | version "2.1.2"
735 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
736 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
737 |
738 | mustache@^4.2.0:
739 | version "4.2.0"
740 | resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64"
741 | integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
742 |
743 | nanoid@^3.3.3, nanoid@^3.3.7:
744 | version "3.3.7"
745 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
746 | integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
747 |
748 | node-forge@^1:
749 | version "1.3.1"
750 | resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
751 | integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
752 |
753 | normalize-path@^3.0.0, normalize-path@~3.0.0:
754 | version "3.0.0"
755 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
756 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
757 |
758 | once@^1.3.0:
759 | version "1.4.0"
760 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
761 | integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
762 | dependencies:
763 | wrappy "1"
764 |
765 | path-parse@^1.0.7:
766 | version "1.0.7"
767 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
768 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
769 |
770 | path-to-regexp@^6.2.0:
771 | version "6.2.2"
772 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz#324377a83e5049cbecadc5554d6a63a9a4866b36"
773 | integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==
774 |
775 | picocolors@^1.0.0:
776 | version "1.0.0"
777 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
778 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
779 |
780 | picomatch@^2.0.4, picomatch@^2.2.1:
781 | version "2.3.1"
782 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
783 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
784 |
785 | playwright-core@1.43.1:
786 | version "1.43.1"
787 | resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.43.1.tgz#0eafef9994c69c02a1a3825a4343e56c99c03b02"
788 | integrity sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==
789 |
790 | playwright@1.43.1:
791 | version "1.43.1"
792 | resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.43.1.tgz#8ad08984ac66c9ef3d0db035be54dd7ec9f1c7d9"
793 | integrity sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==
794 | dependencies:
795 | playwright-core "1.43.1"
796 | optionalDependencies:
797 | fsevents "2.3.2"
798 |
799 | postcss@^8.4.38:
800 | version "8.4.38"
801 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
802 | integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
803 | dependencies:
804 | nanoid "^3.3.7"
805 | picocolors "^1.0.0"
806 | source-map-js "^1.2.0"
807 |
808 | printable-characters@^1.0.42:
809 | version "1.0.42"
810 | resolved "https://registry.yarnpkg.com/printable-characters/-/printable-characters-1.0.42.tgz#3f18e977a9bd8eb37fcc4ff5659d7be90868b3d8"
811 | integrity sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==
812 |
813 | readdirp@~3.6.0:
814 | version "3.6.0"
815 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
816 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
817 | dependencies:
818 | picomatch "^2.2.1"
819 |
820 | resolve.exports@^2.0.2:
821 | version "2.0.2"
822 | resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800"
823 | integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==
824 |
825 | resolve@^1.22.8:
826 | version "1.22.8"
827 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
828 | integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
829 | dependencies:
830 | is-core-module "^2.13.0"
831 | path-parse "^1.0.7"
832 | supports-preserve-symlinks-flag "^1.0.0"
833 |
834 | rollup-plugin-inject@^3.0.0:
835 | version "3.0.2"
836 | resolved "https://registry.yarnpkg.com/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz#e4233855bfba6c0c12a312fd6649dff9a13ee9f4"
837 | integrity sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==
838 | dependencies:
839 | estree-walker "^0.6.1"
840 | magic-string "^0.25.3"
841 | rollup-pluginutils "^2.8.1"
842 |
843 | rollup-plugin-node-polyfills@^0.2.1:
844 | version "0.2.1"
845 | resolved "https://registry.yarnpkg.com/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz#53092a2744837164d5b8a28812ba5f3ff61109fd"
846 | integrity sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==
847 | dependencies:
848 | rollup-plugin-inject "^3.0.0"
849 |
850 | rollup-pluginutils@^2.8.1:
851 | version "2.8.2"
852 | resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e"
853 | integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==
854 | dependencies:
855 | estree-walker "^0.6.1"
856 |
857 | rollup@^4.13.0:
858 | version "4.14.1"
859 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.14.1.tgz#228d5159c3f4d8745bd24819d734bc6c6ca87c09"
860 | integrity sha512-4LnHSdd3QK2pa1J6dFbfm1HN0D7vSK/ZuZTsdyUAlA6Rr1yTouUTL13HaDOGJVgby461AhrNGBS7sCGXXtT+SA==
861 | dependencies:
862 | "@types/estree" "1.0.5"
863 | optionalDependencies:
864 | "@rollup/rollup-android-arm-eabi" "4.14.1"
865 | "@rollup/rollup-android-arm64" "4.14.1"
866 | "@rollup/rollup-darwin-arm64" "4.14.1"
867 | "@rollup/rollup-darwin-x64" "4.14.1"
868 | "@rollup/rollup-linux-arm-gnueabihf" "4.14.1"
869 | "@rollup/rollup-linux-arm64-gnu" "4.14.1"
870 | "@rollup/rollup-linux-arm64-musl" "4.14.1"
871 | "@rollup/rollup-linux-powerpc64le-gnu" "4.14.1"
872 | "@rollup/rollup-linux-riscv64-gnu" "4.14.1"
873 | "@rollup/rollup-linux-s390x-gnu" "4.14.1"
874 | "@rollup/rollup-linux-x64-gnu" "4.14.1"
875 | "@rollup/rollup-linux-x64-musl" "4.14.1"
876 | "@rollup/rollup-win32-arm64-msvc" "4.14.1"
877 | "@rollup/rollup-win32-ia32-msvc" "4.14.1"
878 | "@rollup/rollup-win32-x64-msvc" "4.14.1"
879 | fsevents "~2.3.2"
880 |
881 | safe-stable-stringify@^2.4.3:
882 | version "2.4.3"
883 | resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886"
884 | integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==
885 |
886 | selfsigned@^2.0.1:
887 | version "2.4.1"
888 | resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0"
889 | integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==
890 | dependencies:
891 | "@types/node-forge" "^1.3.0"
892 | node-forge "^1"
893 |
894 | source-map-js@^1.2.0:
895 | version "1.2.0"
896 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
897 | integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
898 |
899 | source-map@0.6.1, source-map@^0.6.1:
900 | version "0.6.1"
901 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
902 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
903 |
904 | sourcemap-codec@^1.4.8:
905 | version "1.4.8"
906 | resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
907 | integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
908 |
909 | stacktracey@^2.1.8:
910 | version "2.1.8"
911 | resolved "https://registry.yarnpkg.com/stacktracey/-/stacktracey-2.1.8.tgz#bf9916020738ce3700d1323b32bd2c91ea71199d"
912 | integrity sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==
913 | dependencies:
914 | as-table "^1.0.36"
915 | get-source "^2.0.12"
916 |
917 | stoppable@^1.1.0:
918 | version "1.1.0"
919 | resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b"
920 | integrity sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==
921 |
922 | supports-preserve-symlinks-flag@^1.0.0:
923 | version "1.0.0"
924 | resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
925 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
926 |
927 | to-regex-range@^5.0.1:
928 | version "5.0.1"
929 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
930 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
931 | dependencies:
932 | is-number "^7.0.0"
933 |
934 | ts-json-schema-generator@^1.5.0:
935 | version "1.5.1"
936 | resolved "https://registry.yarnpkg.com/ts-json-schema-generator/-/ts-json-schema-generator-1.5.1.tgz#7759c421240be86d393a884ad186f926b22332db"
937 | integrity sha512-apX5qG2+NA66j7b4AJm8q/DpdTeOsjfh7A3LpKsUiil0FepkNwtN28zYgjrsiiya2/OPhsr/PSjX5FUYg79rCg==
938 | dependencies:
939 | "@types/json-schema" "^7.0.15"
940 | commander "^12.0.0"
941 | glob "^8.0.3"
942 | json5 "^2.2.3"
943 | normalize-path "^3.0.0"
944 | safe-stable-stringify "^2.4.3"
945 | typescript "~5.4.2"
946 |
947 | tslib@^2.2.0:
948 | version "2.6.2"
949 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
950 | integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
951 |
952 | typescript@^5.2.2, typescript@~5.4.2:
953 | version "5.4.5"
954 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611"
955 | integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==
956 |
957 | undici-types@~5.26.4:
958 | version "5.26.5"
959 | resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
960 | integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
961 |
962 | undici@^5.28.2:
963 | version "5.28.4"
964 | resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068"
965 | integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==
966 | dependencies:
967 | "@fastify/busboy" "^2.0.0"
968 |
969 | vite@^5.2.0:
970 | version "5.2.8"
971 | resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.8.tgz#a99e09939f1a502992381395ce93efa40a2844aa"
972 | integrity sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==
973 | dependencies:
974 | esbuild "^0.20.1"
975 | postcss "^8.4.38"
976 | rollup "^4.13.0"
977 | optionalDependencies:
978 | fsevents "~2.3.3"
979 |
980 | workerd@1.20240405.0:
981 | version "1.20240405.0"
982 | resolved "https://registry.yarnpkg.com/workerd/-/workerd-1.20240405.0.tgz#c5cb6563b0a9b16dc981f5138f4b71fa2f976683"
983 | integrity sha512-AWrOSBh4Ll7sBWHuh0aywm8hDkKqsZmcwnDB0PVGszWZM5mndNBI5iJ/8haXVpdoyqkJQEVdhET9JDi4yU8tRg==
984 | optionalDependencies:
985 | "@cloudflare/workerd-darwin-64" "1.20240405.0"
986 | "@cloudflare/workerd-darwin-arm64" "1.20240405.0"
987 | "@cloudflare/workerd-linux-64" "1.20240405.0"
988 | "@cloudflare/workerd-linux-arm64" "1.20240405.0"
989 | "@cloudflare/workerd-windows-64" "1.20240405.0"
990 |
991 | wrangler@^3.50.0:
992 | version "3.50.0"
993 | resolved "https://registry.yarnpkg.com/wrangler/-/wrangler-3.50.0.tgz#012e5dd9162e59b717a5a239a7e2b99b7270990a"
994 | integrity sha512-JlLuch+6DtaC5HGp8YD9Au++XvMv34g3ySdlB5SyPbaObELi8P9ZID5vgyf9AA75djzxL7cuNOk1YdKCJEuq0w==
995 | dependencies:
996 | "@cloudflare/kv-asset-handler" "0.3.1"
997 | "@esbuild-plugins/node-globals-polyfill" "^0.2.3"
998 | "@esbuild-plugins/node-modules-polyfill" "^0.2.2"
999 | blake3-wasm "^2.1.5"
1000 | chokidar "^3.5.3"
1001 | esbuild "0.17.19"
1002 | miniflare "3.20240405.1"
1003 | nanoid "^3.3.3"
1004 | path-to-regexp "^6.2.0"
1005 | resolve "^1.22.8"
1006 | resolve.exports "^2.0.2"
1007 | selfsigned "^2.0.1"
1008 | source-map "0.6.1"
1009 | ts-json-schema-generator "^1.5.0"
1010 | xxhash-wasm "^1.0.1"
1011 | optionalDependencies:
1012 | fsevents "~2.3.2"
1013 |
1014 | wrappy@1:
1015 | version "1.0.2"
1016 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
1017 | integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
1018 |
1019 | ws@^8.11.0:
1020 | version "8.16.0"
1021 | resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4"
1022 | integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==
1023 |
1024 | xxhash-wasm@^1.0.1:
1025 | version "1.0.2"
1026 | resolved "https://registry.yarnpkg.com/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz#ecc0f813219b727af4d5f3958ca6becee2f2f1ff"
1027 | integrity sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==
1028 |
1029 | youch@^3.2.2:
1030 | version "3.3.3"
1031 | resolved "https://registry.yarnpkg.com/youch/-/youch-3.3.3.tgz#50cfdf5bc395ce664a5073e31b712ff4a859d928"
1032 | integrity sha512-qSFXUk3UZBLfggAW3dJKg0BMblG5biqSF8M34E06o5CSsZtH92u9Hqmj2RzGiHDi64fhe83+4tENFP2DB6t6ZA==
1033 | dependencies:
1034 | cookie "^0.5.0"
1035 | mustache "^4.2.0"
1036 | stacktracey "^2.1.8"
1037 |
1038 | zod@^3.20.6:
1039 | version "3.22.4"
1040 | resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
1041 | integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==
1042 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import antfu from '@antfu/eslint-config'
2 |
3 | export default antfu({
4 | formatters: {
5 | html: true,
6 | },
7 | ignores: [
8 | '**/playwright-report/',
9 | '**/test-results/',
10 | '**/dist/',
11 | ],
12 | }, {
13 | rules: {
14 | 'array-bracket-newline': [
15 | 'error',
16 | 'consistent',
17 | ],
18 | 'array-element-newline': [
19 | 'error',
20 | 'consistent',
21 | ],
22 | 'object-curly-newline': [
23 | 'error',
24 | { consistent: true },
25 | ],
26 | 'object-property-newline': [
27 | 'error',
28 | {
29 | allowAllPropertiesOnSameLine: true,
30 | },
31 | ],
32 |
33 | 'no-console': 'off',
34 | 'node/prefer-global/process': 'off',
35 | 'ts/consistent-type-definitions': 'off',
36 | },
37 | })
38 |
--------------------------------------------------------------------------------
/lib/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | # dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/lib/dist/Animation.d.ts:
--------------------------------------------------------------------------------
1 | import { Driver } from './Driver';
2 | import { AnimationConstructor, AnimationHooks, AnimationRender } from './Animation.types';
3 |
4 | export declare class Animation {
5 | id: string;
6 | cssAnimation: CSSAnimation;
7 | /** You can store your custom data here to use between hooks */
8 | data: any;
9 | /** Reference to linked `Driver` instance */
10 | driver: Driver;
11 | /** You can access domElement this animation is belongs to */
12 | domElement: HTMLElement;
13 | hooks: AnimationHooks;
14 | constructor({ id, cssAnimation, hooks, driver, domElement }: AnimationConstructor);
15 | render({ driverProgress }: AnimationRender): false | undefined;
16 | }
17 |
--------------------------------------------------------------------------------
/lib/dist/Animation.types.d.ts:
--------------------------------------------------------------------------------
1 | import { Driver } from './Driver';
2 | import { Animation } from './Animation';
3 |
4 | export type AnimationConstructor = {
5 | id: string;
6 | hooks: AnimationHooks;
7 | cssAnimation: CSSAnimation;
8 | driver: Driver;
9 | domElement: HTMLElement;
10 | };
11 | export type AnimationHooks = {
12 | onInit?: (animation: Animation) => void;
13 | /** You can `return false` inside your hook, it will cancel rendering. Or you can return a number, it will be an animation currentTime */
14 | onBeforeRender?: (animation: Animation) => void | undefined | boolean | number;
15 | onAfterRender?: (animation: Animation) => void;
16 | };
17 | type AnimationConfiguration = string | {
18 | name: string;
19 | hooks: AnimationHooks;
20 | };
21 | export type ElementSelector = string | {
22 | selector: string;
23 | animations: AnimationConfiguration[];
24 | };
25 | export type AnimationRender = {
26 | driverProgress: number;
27 | };
28 | export {};
29 |
--------------------------------------------------------------------------------
/lib/dist/Driver.d.ts:
--------------------------------------------------------------------------------
1 | import { DriverBorderConstructor, DriverBorderUpdateLimits, DriverCalculateProgress, DriverConstructor, DriverHelperConstructor, DriverHelperUpdateLimits, DriverHooks, DriverRender, DriverUpdateLimits } from './Driver.types';
2 | import { TheSupersonicPlugin } from './TheSupersonicPlugin';
3 | import { Animation } from './Animation';
4 |
5 | /**
6 | * The main purpose of Driver is to calculate current progress from 0 to 1 depending on current scroll and position of 'start' and 'end' elements
7 | */
8 | export declare class Driver {
9 | id: string;
10 | /** Progress is generated by script and means how much of the scroll covered right now. Minimum value: 0, maximum value: 1, float number with 4 numbers after decimal point precision */
11 | progress: number;
12 | /** You can store your custom data here to use between hooks */
13 | data: any;
14 | /** Start is HTML element. When it appears on the screen, driver will start an animation */
15 | start: DriverBorder;
16 | /** End is HTML element. When it appears on the screen, driver will stop an animation */
17 | end: DriverBorder;
18 | /** Link to plugin instance to be able to access global variables like 'scroll', 'screenHeight' */
19 | plugin: TheSupersonicPlugin;
20 | animations: Map;
21 | /** Helper is an element which need for IntersectionObserver to activate or deactive driver */
22 | helper: DriverHelper;
23 | hooks: DriverHooks;
24 | constructor({ id, start, end, plugin, elements, hooks }: DriverConstructor);
25 | /** Driver calculates its progress and then renders all of it's properties with progress value */
26 | render({ scroll, renderedInitially, consoleColor }: DriverRender): false | undefined;
27 | /** Calculates current driver progress, depending on current scroll and top offset of DOM elements */
28 | calculateProgress({ scroll, start, end }: DriverCalculateProgress): number;
29 | /** Recalculates DOM elements top offset */
30 | updateLimits({ scroll, screenHeight }: DriverUpdateLimits): void;
31 | /** Activates driver when it becomes visible on the screen */
32 | activate(): void;
33 | /** Deactivates driver when it's progress becomes 0 or 1' */
34 | deactivate(): void;
35 | }
36 | /** An HTML Element. It's top offset serves as indicator of where driver starts and where it ends */
37 | declare class DriverBorder {
38 | /** Associated DOM element */
39 | domElement: HTMLElement;
40 | /** Top means amount of scroll needed to border activate or deactivate driver */
41 | top: number;
42 | constructor({ domElement, type, driver }: DriverBorderConstructor);
43 | /** Recalculates top offset */
44 | updateLimits({ scroll, screenHeight }: DriverBorderUpdateLimits): void;
45 | }
46 | /** A helper HTML element, which connects to Border instances and starts being tracked by IntersectionObserver */
47 | declare class DriverHelper {
48 | /** DOM element which is dynamically generated by plugin */
49 | domElement: HTMLElement;
50 | pluginId: string;
51 | debug: boolean;
52 | constructor({ id, pluginId, debug }: DriverHelperConstructor);
53 | /** Sets position of helper */
54 | updateLimits({ top, height }: DriverHelperUpdateLimits): void;
55 | /** Deletes helper DOM element */
56 | uninit(): void;
57 | }
58 | export {};
59 |
--------------------------------------------------------------------------------
/lib/dist/Driver.types.d.ts:
--------------------------------------------------------------------------------
1 | import { TheSupersonicPlugin } from './TheSupersonicPlugin';
2 | import { ElementSelector } from './Animation.types';
3 | import { Driver } from './Driver';
4 |
5 | export type DriverConfiguration = {
6 | id?: string;
7 | start: DriverBorderConfiguration;
8 | end: DriverBorderConfiguration;
9 | elements?: ElementSelector[];
10 | hooks?: DriverHooks;
11 | };
12 | export type DriverConstructor = Omit & {
13 | id: string;
14 | plugin: TheSupersonicPlugin;
15 | };
16 | export type DriverRender = {
17 | scroll: number;
18 | renderedInitially: boolean;
19 | consoleColor?: string;
20 | };
21 | export type DriverCalculateProgress = {
22 | scroll: number;
23 | start: number;
24 | end: number;
25 | };
26 | export type DriverHooks = {
27 | onBeforeInit?: (driver: Driver) => void;
28 | onAfterInit?: (driver: Driver) => void;
29 | /** You can `return false` inside your hook, it will cancel rendering */
30 | onBeforeRender?: (driver: Driver) => void | undefined | boolean;
31 | onAfterRender?: (driver: Driver) => void;
32 | onActivation?: (driver: Driver) => void;
33 | onDeactivation?: (driver: Driver) => void;
34 | onUpdateLimits?: (driver: Driver) => void;
35 | };
36 | type DriverBorderConfiguration = HTMLElement | string | null;
37 | export type DriverBorderConstructor = {
38 | domElement: DriverBorderConfiguration;
39 | driver: Driver;
40 | type: 'start' | 'end';
41 | };
42 | export type DriverHelperConstructor = {
43 | id: string;
44 | pluginId: string;
45 | debug?: boolean;
46 | };
47 | export type DriverUpdateLimits = {
48 | scroll: number;
49 | screenHeight: number;
50 | };
51 | export type DriverBorderUpdateLimits = {
52 | scroll: number;
53 | screenHeight: number;
54 | };
55 | export type DriverHelperUpdateLimits = {
56 | top: number;
57 | height: number;
58 | };
59 | export {};
60 |
--------------------------------------------------------------------------------
/lib/dist/Observer.d.ts:
--------------------------------------------------------------------------------
1 | import { Constructor } from './Observer.types';
2 |
3 | export declare class Observer {
4 | instance: IntersectionObserver;
5 | constructor({ observables, driverInstances }: Constructor);
6 | uninit(): void;
7 | }
8 |
--------------------------------------------------------------------------------
/lib/dist/Observer.types.d.ts:
--------------------------------------------------------------------------------
1 | import { Driver } from './Driver';
2 |
3 | export type Constructor = {
4 | observables: HTMLElement[];
5 | driverInstances: Map;
6 | };
7 |
--------------------------------------------------------------------------------
/lib/dist/TheSupersonicPlugin.d.ts:
--------------------------------------------------------------------------------
1 | import { DriverConfiguration } from './Driver.types';
2 | import { PluginConfiguration, PluginHooks, PluginRender } from './TheSupersonicPlugin.types';
3 | import { Observer } from './Observer';
4 | import { Driver } from './Driver';
5 |
6 | /**
7 | *
8 | * Main class handling all of the logic. To initialize the plugin, you create a new instance of this class
9 | *
10 | * @example
11 | * const plugin = new TheSupersonicPlugin([
12 | * {
13 | * start: '.start',
14 | * end: '.end',
15 | * elements: ['.animatable-element']
16 | * }
17 | * ]);
18 | *
19 | */
20 | export declare class TheSupersonicPlugin {
21 | /** Unique id of this running instance. You explicitly define it or let plugin auto generate it */
22 | id: string;
23 | /** Current window scrollY */
24 | scroll: number;
25 | /** Current screen height */
26 | screenHeight: number;
27 | /** Required to get all of the drivers render at once to stand on their first frame */
28 | renderedInitially: boolean;
29 | /** Used to cancelAnimationFrame on 'uninit()' */
30 | rafId: number;
31 | /** Color of console messages in dev mode. It changes each frame to make it more convenient to visually separate frames */
32 | consoleColor: string;
33 | /** IntersectionObserver instance */
34 | observer: Observer | null;
35 | /** Debounced resize listener */
36 | onResize: EventListener | null;
37 | /** You can store your custom data here to use between hooks */
38 | data: any;
39 | /** Make helper visible */
40 | debug: boolean;
41 | hooks: PluginHooks;
42 | driverInstances: Map;
43 | driverActiveInstances: Set;
44 | constructor(drivers: DriverConfiguration[], configuration?: PluginConfiguration);
45 | /** Removes all of the plugin stuff (useful for SPA) */
46 | uninit(): void;
47 | /** Main rendering cycle. Active drivers are visible ones */
48 | render({ useActiveDrivers }: PluginRender): false | undefined;
49 | /** Updates global scroll and driver DOM elements top offset. Called once on page load and each time after window.resize */
50 | updateLimits(): void;
51 | updateScroll(): void;
52 | /** Dirty hack for calculating screen height. We can't just use "window.innerHeight" because it "jumps" on mobile phones when you scroll and toolbar collapses */
53 | updateScreenHeight(): void;
54 | }
55 |
--------------------------------------------------------------------------------
/lib/dist/TheSupersonicPlugin.types.d.ts:
--------------------------------------------------------------------------------
1 | import { TheSupersonicPlugin } from './TheSupersonicPlugin';
2 |
3 | export type PluginConfiguration = {
4 | id?: string;
5 | hooks?: PluginHooks;
6 | debug?: boolean;
7 | };
8 | export type PluginHooks = {
9 | onBeforeInit?: (plugin: TheSupersonicPlugin) => void;
10 | onAfterInit?: (plugin: TheSupersonicPlugin) => void;
11 | /** You can `return false` inside your hook, it will cancel rendering */
12 | onBeforeRender?: (plugin: TheSupersonicPlugin) => void | undefined | boolean;
13 | onAfterRender?: (plugin: TheSupersonicPlugin) => void;
14 | onBeforeResize?: (plugin: TheSupersonicPlugin) => void;
15 | onAfterResize?: (plugin: TheSupersonicPlugin) => void;
16 | };
17 | export type PluginRender = {
18 | useActiveDrivers: boolean;
19 | };
20 |
--------------------------------------------------------------------------------
/lib/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | import { Animation } from './Animation';
2 | import { Driver } from './Driver';
3 | import { TheSupersonicPlugin } from './TheSupersonicPlugin';
4 |
5 | export { TheSupersonicPlugin, Driver, Animation, };
6 |
--------------------------------------------------------------------------------
/lib/dist/the-supersonic-plugin-for-scroll-based-animation.iife.js:
--------------------------------------------------------------------------------
1 | var TheSupersonicPluginWrapper=function(h){"use strict";var $=Object.defineProperty;var P=(h,l,c)=>l in h?$(h,l,{enumerable:!0,configurable:!0,writable:!0,value:c}):h[l]=c;var s=(h,l,c)=>(P(h,typeof l!="symbol"?l+"":l,c),c);function l(a,t){return t=10**t,~~(a*t)/t}function c(a,t){let e;return function(){clearTimeout(e),e=setTimeout(()=>a.apply(this),t)}}function A(){return Math.random().toString(16).substring(2)}class E{constructor({id:t,cssAnimation:e,hooks:i,driver:o,domElement:r}){s(this,"id");s(this,"cssAnimation");s(this,"data",{});s(this,"driver");s(this,"domElement");s(this,"hooks");this.id=t,this.driver=o,this.cssAnimation=e,this.hooks=i,this.domElement=r,this.hooks.onInit&&this.hooks.onInit(this)}render({driverProgress:t}){let e=t*1e4;if(this.hooks.onBeforeRender){const i=this.hooks.onBeforeRender(this);if(typeof i=="number")e=i;else if(typeof i=="boolean"&&!i)return!1}this.cssAnimation.currentTime=e,this.hooks.onAfterRender&&this.hooks.onAfterRender(this)}}const u={colors:["red","blue","orange","yellow"],colorIndex:0};class g{constructor({id:t,start:e,end:i,plugin:o,elements:r=[],hooks:m={}}){s(this,"id");s(this,"progress",0);s(this,"data",{});s(this,"start");s(this,"end");s(this,"plugin");s(this,"animations",new Map);s(this,"helper");s(this,"hooks");this.id=t,this.plugin=o,this.hooks=m,this.hooks.onBeforeInit&&this.hooks.onBeforeInit(this),this.start=new b({domElement:e,type:"start",driver:this}),this.end=new b({domElement:i,type:"end",driver:this}),this.helper=new S({id:t,pluginId:this.plugin.id,debug:o.debug}),r.forEach(n=>{const f=typeof n=="string"?n:n.selector,v=Array.from(document.querySelectorAll(f));if(v.length===0)throw new Error(`Can't find element "${f}"`);v.forEach((R,x)=>{const k=R.getAnimations(),I=[];typeof n=="string"?(k.length,k.forEach(d=>{I.push({cssAnimation:d,hooks:{}})})):typeof n=="object"&&n.animations.forEach(d=>{let p="",y={};typeof d=="string"?p=d:typeof d=="object"&&(p=d.name,y=d.hooks);const w=k.find(z=>z.animationName===p);w&&I.push({cssAnimation:w,hooks:y})}),I.forEach(d=>{const p=`${this.id}---${f}---${x}---${d.cssAnimation.animationName}`,y=new E({id:p,driver:this,cssAnimation:d.cssAnimation,hooks:d.hooks,domElement:R});this.animations.set(p,y)})})}),this.hooks.onAfterInit&&this.hooks.onAfterInit(this)}render({scroll:t,renderedInitially:e,consoleColor:i="#000000"}){const o=this.progress;if(this.progress=this.calculateProgress({scroll:t,start:this.start.top,end:this.end.top}),this.hooks.onBeforeRender){const r=this.hooks.onBeforeRender(this);if(typeof r=="boolean"&&!r)return!1}if(o!==this.progress||!e){for(const r of this.animations.values())r.render({driverProgress:this.progress});this.hooks.onAfterRender&&this.hooks.onAfterRender(this)}}calculateProgress({scroll:t,start:e,end:i}){let o=(t-e)/(i-e);return o<0?o=0:o>1?o=1:o=l(o,4),o}updateLimits({scroll:t,screenHeight:e}){this.start.updateLimits({scroll:t,screenHeight:e}),this.end.updateLimits({scroll:t,screenHeight:e});const i=this.start.top+e;this.helper.updateLimits({top:i,height:this.end.top-i}),this.hooks.onUpdateLimits&&this.hooks.onUpdateLimits(this)}activate(){this.plugin.driverActiveInstances.add(this),this.hooks.onActivation&&this.hooks.onActivation(this)}deactivate(){this.plugin.driverActiveInstances.delete(this),this.hooks.onDeactivation&&this.hooks.onDeactivation(this)}}class b{constructor({domElement:t,type:e,driver:i}){s(this,"domElement");s(this,"top",0);if(typeof t=="string"&&(t=document.querySelector(t)),!t)throw new Error(`Can't find "${e}" HTMLElement for driver "${i.id}"`);this.domElement=t}updateLimits({scroll:t,screenHeight:e}){this.top=~~this.domElement.getBoundingClientRect().top+t-e}}class S{constructor({id:t,pluginId:e,debug:i=!1}){s(this,"domElement");s(this,"pluginId");s(this,"debug");if(this.pluginId=e,this.debug=i,this.domElement=document.createElement("i"),this.domElement.style.position="absolute",this.domElement.style.left="0",this.domElement.style.width="1px",this.domElement.setAttribute("data-supersonic-driver",t),this.domElement.setAttribute("data-supersonic-type","helper"),this.domElement.setAttribute("data-supersonic-plugin-id",this.pluginId),this.domElement.classList.add("supersonic-helper"),document.body.appendChild(this.domElement),i){const r=Array.from(document.querySelectorAll("[data-supersonic-type='helper']")).indexOf(this.domElement);this.domElement.style.left=`${r*10}px`,this.domElement.style.width="10px",this.domElement.style.minHeight="50px",this.domElement.style.background=u.colors[u.colorIndex],this.domElement.style.zIndex="100000",u.colorIndex===3?u.colorIndex=0:u.colorIndex++,this.domElement.style.opacity="0.75"}}updateLimits({top:t,height:e}){e<=0&&(e=1),this.domElement.style.setProperty("top",`${t}px`),this.domElement.style.setProperty("height",`${e}px`)}uninit(){this.domElement.remove()}}class B{constructor({observables:t,driverInstances:e}){s(this,"instance");this.instance=new IntersectionObserver(i=>{i.forEach(o=>{const m=o.target.dataset.supersonicDriver,n=e.get(m);if(!n)throw new Error(`Observer can't find driver "${m}"`);o.isIntersecting?n.activate():n.deactivate()})}),t.forEach(i=>{this.instance.observe(i)})}uninit(){this.instance.disconnect()}}class L{constructor(t,e){s(this,"id");s(this,"scroll",0);s(this,"screenHeight",0);s(this,"renderedInitially",!1);s(this,"rafId",0);s(this,"consoleColor","#ffffff");s(this,"observer",null);s(this,"onResize",null);s(this,"data",{});s(this,"debug");s(this,"hooks",{});s(this,"driverInstances",new Map);s(this,"driverActiveInstances",new Set);var r,m;this.id=(e==null?void 0:e.id)??A(),this.hooks=(e==null?void 0:e.hooks)??{},this.debug=(e==null?void 0:e.debug)??!1,(r=this.hooks)!=null&&r.onBeforeInit&&this.hooks.onBeforeInit(this),t.forEach(n=>{const f=n.id??A(),v=new g({id:f,hooks:n.hooks,start:n.start,end:n.end,elements:n.elements,plugin:this});this.driverInstances.set(f,v)}),this.updateLimits();const i=Array.from(document.querySelectorAll(`[data-supersonic-type="helper"][data-supersonic-plugin-id="${this.id}"]`));this.observer=new B({observables:i,driverInstances:this.driverInstances});const o=()=>{var n;(n=this.hooks)!=null&&n.onBeforeResize&&this.hooks.onBeforeResize(this),this.updateLimits(),this.render({useActiveDrivers:!1}),this.hooks.onAfterResize&&this.hooks.onAfterResize(this)};this.onResize=c(o,250),window.addEventListener("resize",this.onResize),this.render({useActiveDrivers:!1}),this.renderedInitially=!0,(m=this.hooks)!=null&&m.onAfterInit&&this.hooks.onAfterInit(this)}uninit(){for(const t of this.driverInstances.values())t.helper.uninit();this.driverInstances.clear(),this.driverActiveInstances.clear(),this.observer.uninit(),cancelAnimationFrame(this.rafId),window.removeEventListener("resize",this.onResize)}render({useActiveDrivers:t}){if(this.updateScroll(),this.hooks.onBeforeRender){const i=this.hooks.onBeforeRender(this);if(typeof i=="boolean"&&!i)return!1}const e=t?this.driverActiveInstances.values():this.driverInstances.values();for(const i of e)i.render({scroll:this.scroll,renderedInitially:this.renderedInitially,consoleColor:this.consoleColor});this.rafId=requestAnimationFrame(()=>{this.render({useActiveDrivers:!0})}),this.hooks.onAfterRender&&this.hooks.onAfterRender(this)}updateLimits(){this.updateScreenHeight(),this.updateScroll();for(const t of this.driverInstances.values())t.updateLimits({scroll:this.scroll,screenHeight:this.screenHeight})}updateScroll(){this.scroll=window.scrollY||document.documentElement.scrollTop}updateScreenHeight(){const t={position:"absolute",left:"0",top:"0",height:"100vh",width:"1px",zIndex:"-1",visibility:"hidden"},e=document.createElement("div");for(const i in t)e.style.setProperty(i,t[i]);document.body.appendChild(e),this.screenHeight=e.clientHeight,e.remove()}}return h.Animation=E,h.Driver=g,h.TheSupersonicPlugin=L,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"}),h}({});
2 |
--------------------------------------------------------------------------------
/lib/dist/the-supersonic-plugin-for-scroll-based-animation.js:
--------------------------------------------------------------------------------
1 | var g = Object.defineProperty;
2 | var R = (r, e, t) => e in r ? g(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t;
3 | var s = (r, e, t) => (R(r, typeof e != "symbol" ? e + "" : e, t), t);
4 | function w(r, e) {
5 | return e = 10 ** e, ~~(r * e) / e;
6 | }
7 | function x(r, e) {
8 | let t;
9 | return function() {
10 | clearTimeout(t), t = setTimeout(() => r.apply(this), e);
11 | };
12 | }
13 | function I() {
14 | return Math.random().toString(16).substring(2);
15 | }
16 | class B {
17 | constructor({ id: e, cssAnimation: t, hooks: i, driver: o, domElement: h }) {
18 | s(this, "id");
19 | s(this, "cssAnimation");
20 | /** You can store your custom data here to use between hooks */
21 | s(this, "data", {});
22 | /** Reference to linked `Driver` instance */
23 | s(this, "driver");
24 | /** You can access domElement this animation is belongs to */
25 | s(this, "domElement");
26 | s(this, "hooks");
27 | this.id = e, this.driver = o, this.cssAnimation = t, this.hooks = i, this.domElement = h, this.hooks.onInit && this.hooks.onInit(this);
28 | }
29 | render({ driverProgress: e }) {
30 | let t = e * 1e4;
31 | if (this.hooks.onBeforeRender) {
32 | const i = this.hooks.onBeforeRender(this);
33 | if (typeof i == "number")
34 | t = i;
35 | else if (typeof i == "boolean" && !i)
36 | return !1;
37 | }
38 | this.cssAnimation.currentTime = t, this.hooks.onAfterRender && this.hooks.onAfterRender(this);
39 | }
40 | }
41 | const m = {
42 | colors: ["red", "blue", "orange", "yellow"],
43 | colorIndex: 0
44 | };
45 | class L {
46 | constructor({ id: e, start: t, end: i, plugin: o, elements: h = [], hooks: l = {} }) {
47 | s(this, "id");
48 | /** Progress is generated by script and means how much of the scroll covered right now. Minimum value: 0, maximum value: 1, float number with 4 numbers after decimal point precision */
49 | s(this, "progress", 0);
50 | /** You can store your custom data here to use between hooks */
51 | s(this, "data", {});
52 | /** Start is HTML element. When it appears on the screen, driver will start an animation */
53 | s(this, "start");
54 | /** End is HTML element. When it appears on the screen, driver will stop an animation */
55 | s(this, "end");
56 | /** Link to plugin instance to be able to access global variables like 'scroll', 'screenHeight' */
57 | s(this, "plugin");
58 | s(this, "animations", /* @__PURE__ */ new Map());
59 | /** Helper is an element which need for IntersectionObserver to activate or deactive driver */
60 | s(this, "helper");
61 | s(this, "hooks");
62 | this.id = e, this.plugin = o, this.hooks = l, this.hooks.onBeforeInit && this.hooks.onBeforeInit(this), this.start = new A({
63 | domElement: t,
64 | type: "start",
65 | driver: this
66 | }), this.end = new A({
67 | domElement: i,
68 | type: "end",
69 | driver: this
70 | }), this.helper = new S({
71 | id: e,
72 | pluginId: this.plugin.id,
73 | debug: o.debug
74 | }), h.forEach((n) => {
75 | const a = typeof n == "string" ? n : n.selector, p = Array.from(document.querySelectorAll(a));
76 | if (p.length === 0)
77 | throw new Error(`Can't find element "${a}"`);
78 | p.forEach((k, E) => {
79 | const u = k.getAnimations(), v = [];
80 | typeof n == "string" ? (u.length, u.forEach((d) => {
81 | v.push({
82 | cssAnimation: d,
83 | hooks: {}
84 | });
85 | })) : typeof n == "object" && n.animations.forEach((d) => {
86 | let c = "", f = {};
87 | typeof d == "string" ? c = d : typeof d == "object" && (c = d.name, f = d.hooks);
88 | const y = u.find((b) => b.animationName === c);
89 | y && v.push({
90 | cssAnimation: y,
91 | hooks: f
92 | });
93 | }), v.forEach((d) => {
94 | const c = `${this.id}---${a}---${E}---${d.cssAnimation.animationName}`, f = new B({
95 | id: c,
96 | driver: this,
97 | cssAnimation: d.cssAnimation,
98 | hooks: d.hooks,
99 | domElement: k
100 | });
101 | this.animations.set(c, f);
102 | });
103 | });
104 | }), this.hooks.onAfterInit && this.hooks.onAfterInit(this);
105 | }
106 | /** Driver calculates its progress and then renders all of it's properties with progress value */
107 | render({ scroll: e, renderedInitially: t, consoleColor: i = "#000000" }) {
108 | const o = this.progress;
109 | if (this.progress = this.calculateProgress({
110 | scroll: e,
111 | start: this.start.top,
112 | end: this.end.top
113 | }), this.hooks.onBeforeRender) {
114 | const h = this.hooks.onBeforeRender(this);
115 | if (typeof h == "boolean" && !h)
116 | return !1;
117 | }
118 | if (o !== this.progress || !t) {
119 | for (const h of this.animations.values())
120 | h.render({ driverProgress: this.progress });
121 | this.hooks.onAfterRender && this.hooks.onAfterRender(this);
122 | }
123 | }
124 | /** Calculates current driver progress, depending on current scroll and top offset of DOM elements */
125 | calculateProgress({ scroll: e, start: t, end: i }) {
126 | let o = (e - t) / (i - t);
127 | return o < 0 ? o = 0 : o > 1 ? o = 1 : o = w(o, 4), o;
128 | }
129 | /** Recalculates DOM elements top offset */
130 | updateLimits({ scroll: e, screenHeight: t }) {
131 | this.start.updateLimits({ scroll: e, screenHeight: t }), this.end.updateLimits({ scroll: e, screenHeight: t });
132 | const i = this.start.top + t;
133 | this.helper.updateLimits({
134 | top: i,
135 | height: this.end.top - i
136 | }), this.hooks.onUpdateLimits && this.hooks.onUpdateLimits(this);
137 | }
138 | /** Activates driver when it becomes visible on the screen */
139 | activate() {
140 | this.plugin.driverActiveInstances.add(this), this.hooks.onActivation && this.hooks.onActivation(this);
141 | }
142 | /** Deactivates driver when it's progress becomes 0 or 1' */
143 | deactivate() {
144 | this.plugin.driverActiveInstances.delete(this), this.hooks.onDeactivation && this.hooks.onDeactivation(this);
145 | }
146 | }
147 | class A {
148 | constructor({ domElement: e, type: t, driver: i }) {
149 | /** Associated DOM element */
150 | s(this, "domElement");
151 | /** Top means amount of scroll needed to border activate or deactivate driver */
152 | s(this, "top", 0);
153 | if (typeof e == "string" && (e = document.querySelector(e)), !e)
154 | throw new Error(`Can't find "${t}" HTMLElement for driver "${i.id}"`);
155 | this.domElement = e;
156 | }
157 | /** Recalculates top offset */
158 | updateLimits({ scroll: e, screenHeight: t }) {
159 | this.top = ~~this.domElement.getBoundingClientRect().top + e - t;
160 | }
161 | }
162 | class S {
163 | constructor({ id: e, pluginId: t, debug: i = !1 }) {
164 | /** DOM element which is dynamically generated by plugin */
165 | s(this, "domElement");
166 | s(this, "pluginId");
167 | s(this, "debug");
168 | if (this.pluginId = t, this.debug = i, this.domElement = document.createElement("i"), this.domElement.style.position = "absolute", this.domElement.style.left = "0", this.domElement.style.width = "1px", this.domElement.setAttribute("data-supersonic-driver", e), this.domElement.setAttribute("data-supersonic-type", "helper"), this.domElement.setAttribute("data-supersonic-plugin-id", this.pluginId), this.domElement.classList.add("supersonic-helper"), document.body.appendChild(this.domElement), i) {
169 | const h = Array.from(document.querySelectorAll("[data-supersonic-type='helper']")).indexOf(this.domElement);
170 | this.domElement.style.left = `${h * 10}px`, this.domElement.style.width = "10px", this.domElement.style.minHeight = "50px", this.domElement.style.background = m.colors[m.colorIndex], this.domElement.style.zIndex = "100000", m.colorIndex === 3 ? m.colorIndex = 0 : m.colorIndex++, this.domElement.style.opacity = "0.75";
171 | }
172 | }
173 | /** Sets position of helper */
174 | updateLimits({ top: e, height: t }) {
175 | t <= 0 && (t = 1), this.domElement.style.setProperty("top", `${e}px`), this.domElement.style.setProperty("height", `${t}px`);
176 | }
177 | /** Deletes helper DOM element */
178 | uninit() {
179 | this.domElement.remove();
180 | }
181 | }
182 | class z {
183 | constructor({ observables: e, driverInstances: t }) {
184 | s(this, "instance");
185 | this.instance = new IntersectionObserver(
186 | (i) => {
187 | i.forEach((o) => {
188 | const l = o.target.dataset.supersonicDriver, n = t.get(l);
189 | if (!n)
190 | throw new Error(`Observer can't find driver "${l}"`);
191 | o.isIntersecting ? n.activate() : n.deactivate();
192 | });
193 | }
194 | ), e.forEach((i) => {
195 | this.instance.observe(i);
196 | });
197 | }
198 | uninit() {
199 | this.instance.disconnect();
200 | }
201 | }
202 | class D {
203 | constructor(e, t) {
204 | /** Unique id of this running instance. You explicitly define it or let plugin auto generate it */
205 | s(this, "id");
206 | /** Current window scrollY */
207 | s(this, "scroll", 0);
208 | /** Current screen height */
209 | s(this, "screenHeight", 0);
210 | /** Required to get all of the drivers render at once to stand on their first frame */
211 | s(this, "renderedInitially", !1);
212 | /** Used to cancelAnimationFrame on 'uninit()' */
213 | s(this, "rafId", 0);
214 | /** Color of console messages in dev mode. It changes each frame to make it more convenient to visually separate frames */
215 | s(this, "consoleColor", "#ffffff");
216 | /** IntersectionObserver instance */
217 | s(this, "observer", null);
218 | /** Debounced resize listener */
219 | s(this, "onResize", null);
220 | /** You can store your custom data here to use between hooks */
221 | s(this, "data", {});
222 | /** Make helper visible */
223 | s(this, "debug");
224 | s(this, "hooks", {});
225 | s(this, "driverInstances", /* @__PURE__ */ new Map());
226 | s(this, "driverActiveInstances", /* @__PURE__ */ new Set());
227 | var h, l;
228 | this.id = (t == null ? void 0 : t.id) ?? I(), this.hooks = (t == null ? void 0 : t.hooks) ?? {}, this.debug = (t == null ? void 0 : t.debug) ?? !1, (h = this.hooks) != null && h.onBeforeInit && this.hooks.onBeforeInit(this), e.forEach((n) => {
229 | const a = n.id ?? I(), p = new L({
230 | id: a,
231 | hooks: n.hooks,
232 | start: n.start,
233 | end: n.end,
234 | elements: n.elements,
235 | plugin: this
236 | });
237 | this.driverInstances.set(a, p);
238 | }), this.updateLimits();
239 | const i = Array.from(document.querySelectorAll(`[data-supersonic-type="helper"][data-supersonic-plugin-id="${this.id}"]`));
240 | this.observer = new z({
241 | observables: i,
242 | driverInstances: this.driverInstances
243 | });
244 | const o = () => {
245 | var n;
246 | (n = this.hooks) != null && n.onBeforeResize && this.hooks.onBeforeResize(this), this.updateLimits(), this.render({ useActiveDrivers: !1 }), this.hooks.onAfterResize && this.hooks.onAfterResize(this);
247 | };
248 | this.onResize = x(o, 250), window.addEventListener("resize", this.onResize), this.render({ useActiveDrivers: !1 }), this.renderedInitially = !0, (l = this.hooks) != null && l.onAfterInit && this.hooks.onAfterInit(this);
249 | }
250 | /** Removes all of the plugin stuff (useful for SPA) */
251 | uninit() {
252 | for (const e of this.driverInstances.values())
253 | e.helper.uninit();
254 | this.driverInstances.clear(), this.driverActiveInstances.clear(), this.observer.uninit(), cancelAnimationFrame(this.rafId), window.removeEventListener("resize", this.onResize);
255 | }
256 | /** Main rendering cycle. Active drivers are visible ones */
257 | render({ useActiveDrivers: e }) {
258 | if (this.updateScroll(), this.hooks.onBeforeRender) {
259 | const i = this.hooks.onBeforeRender(this);
260 | if (typeof i == "boolean" && !i)
261 | return !1;
262 | }
263 | const t = e ? this.driverActiveInstances.values() : this.driverInstances.values();
264 | for (const i of t)
265 | i.render({
266 | scroll: this.scroll,
267 | renderedInitially: this.renderedInitially,
268 | consoleColor: this.consoleColor
269 | });
270 | this.rafId = requestAnimationFrame(() => {
271 | this.render({ useActiveDrivers: !0 });
272 | }), this.hooks.onAfterRender && this.hooks.onAfterRender(this);
273 | }
274 | /** Updates global scroll and driver DOM elements top offset. Called once on page load and each time after window.resize */
275 | updateLimits() {
276 | this.updateScreenHeight(), this.updateScroll();
277 | for (const e of this.driverInstances.values())
278 | e.updateLimits({
279 | scroll: this.scroll,
280 | screenHeight: this.screenHeight
281 | });
282 | }
283 | updateScroll() {
284 | this.scroll = window.scrollY || document.documentElement.scrollTop;
285 | }
286 | /** Dirty hack for calculating screen height. We can't just use "window.innerHeight" because it "jumps" on mobile phones when you scroll and toolbar collapses */
287 | updateScreenHeight() {
288 | const e = {
289 | position: "absolute",
290 | left: "0",
291 | top: "0",
292 | height: "100vh",
293 | width: "1px",
294 | zIndex: "-1",
295 | visibility: "hidden"
296 | }, t = document.createElement("div");
297 | for (const i in e)
298 | t.style.setProperty(i, e[i]);
299 | document.body.appendChild(t), this.screenHeight = t.clientHeight, t.remove();
300 | }
301 | }
302 | export {
303 | B as Animation,
304 | L as Driver,
305 | D as TheSupersonicPlugin
306 | };
307 |
--------------------------------------------------------------------------------
/lib/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/lib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lib",
3 | "type": "module",
4 | "version": "0.0.0",
5 | "private": true,
6 | "scripts": {
7 | "build": "tsc && vite build",
8 | "minify": "terser -c -m --module dist/the-supersonic-plugin-for-scroll-based-animation.js > dist/the-supersonic-plugin-for-scroll-based-animation.min.js",
9 | "test:unit": "vitest run",
10 | "test:unit:ui": "vitest --api.host 0.0.0.0 --api.port 8000 --ui"
11 | },
12 | "devDependencies": {
13 | "@types/node": "^20.12.7",
14 | "@vitest/ui": "^1.5.0",
15 | "jsdom": "^24.0.0",
16 | "terser": "^5.30.3",
17 | "typescript": "^5.2.2",
18 | "vite": "^5.2.0",
19 | "vite-plugin-dts": "^3.8.3",
20 | "vitest": "^1.5.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/src/Animation.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, it } from 'vitest'
2 |
3 | import { Animation } from './Animation'
4 | import type { Driver } from './Driver'
5 |
6 | it('adds onInit hook', () => {
7 | const animation = new Animation({
8 | id: 'animation-id',
9 | driver: {} as Driver,
10 | hooks: {
11 | onInit(animation) {
12 | animation.data.foo = 'bar'
13 | },
14 | },
15 | cssAnimation: {} as CSSAnimation,
16 | domElement: {} as HTMLElement,
17 | })
18 |
19 | expect(animation.data.foo).toBe('bar')
20 | })
21 |
22 | it('renders', () => {
23 | const animation = new Animation({
24 | id: 'animation-id',
25 | driver: {} as Driver,
26 | hooks: {},
27 | cssAnimation: { currentTime: 0 } as CSSAnimation,
28 | domElement: {} as HTMLElement,
29 | })
30 |
31 | animation.render({ driverProgress: 0.1234 })
32 |
33 | expect(animation.cssAnimation.currentTime).toBe(1234)
34 | })
35 |
36 | it('adds onBeforeRender hook', () => {
37 | const animation = new Animation({
38 | id: 'animation-id',
39 | driver: {} as Driver,
40 | hooks: {
41 | onBeforeRender(animation) {
42 | animation.data.foo = 'bar'
43 | return false
44 | },
45 | },
46 | cssAnimation: { currentTime: 0 } as CSSAnimation,
47 | domElement: {} as HTMLElement,
48 | })
49 |
50 | animation.render({ driverProgress: 0.1234 })
51 |
52 | expect(animation.data.foo).toBe('bar')
53 | expect(animation.cssAnimation.currentTime).toBe(0)
54 | })
55 |
56 | it('manually sets animation.currentTime', () => {
57 | const animation = new Animation({
58 | id: 'animation-id',
59 | driver: {} as Driver,
60 | hooks: {
61 | onBeforeRender() {
62 | return 100
63 | },
64 | },
65 | cssAnimation: { currentTime: 0 } as CSSAnimation,
66 | domElement: {} as HTMLElement,
67 | })
68 |
69 | animation.render({ driverProgress: 0.1234 })
70 |
71 | expect(animation.cssAnimation.currentTime).toBe(100)
72 | })
73 |
74 | it('adds onAfterRender hook', () => {
75 | const animation = new Animation({
76 | id: 'animation-id',
77 | driver: {} as Driver,
78 | hooks: {
79 | onAfterRender(animation) {
80 | animation.data.foo = 'bar'
81 | },
82 | },
83 | cssAnimation: {} as CSSAnimation,
84 | domElement: {} as HTMLElement,
85 | })
86 |
87 | animation.render({ driverProgress: 0.1234 })
88 |
89 | expect(animation.data.foo).toBe('bar')
90 | })
91 |
--------------------------------------------------------------------------------
/lib/src/Animation.ts:
--------------------------------------------------------------------------------
1 | import type { AnimationConstructor, AnimationHooks, AnimationRender } from './Animation.types'
2 | import type { Driver } from './Driver'
3 |
4 | export class Animation {
5 | id: string
6 |
7 | cssAnimation: CSSAnimation
8 |
9 | /** You can store your custom data here to use between hooks */
10 | data: any = {}
11 |
12 | /** Reference to linked `Driver` instance */
13 | driver: Driver
14 |
15 | /** You can access domElement this animation is belongs to */
16 | domElement: HTMLElement
17 |
18 | hooks: AnimationHooks
19 |
20 | constructor({ id, cssAnimation, hooks, driver, domElement }: AnimationConstructor) {
21 | this.id = id
22 | this.driver = driver
23 | this.cssAnimation = cssAnimation
24 | this.hooks = hooks
25 | this.domElement = domElement
26 |
27 | if (this.hooks.onInit)
28 | this.hooks.onInit(this)
29 | }
30 |
31 | render({ driverProgress }: AnimationRender) {
32 | let currentTime = driverProgress * 10000
33 |
34 | if (this.hooks.onBeforeRender) {
35 | const onBeforeRenderReturn = this.hooks.onBeforeRender(this)
36 |
37 | if (typeof onBeforeRenderReturn === 'number')
38 | currentTime = onBeforeRenderReturn
39 |
40 | else if (typeof onBeforeRenderReturn === 'boolean' && !onBeforeRenderReturn)
41 | return false
42 | }
43 |
44 | this.cssAnimation.currentTime = currentTime
45 |
46 | if (this.hooks.onAfterRender)
47 | this.hooks.onAfterRender(this)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/src/Animation.types.ts:
--------------------------------------------------------------------------------
1 | import type { Animation } from './Animation'
2 | import type { Driver } from './Driver'
3 |
4 | export type AnimationConstructor = {
5 | id: string
6 | hooks: AnimationHooks
7 | cssAnimation: CSSAnimation
8 | driver: Driver
9 | domElement: HTMLElement
10 | }
11 |
12 | export type AnimationHooks = {
13 | onInit?: (animation: Animation) => void
14 | /** You can `return false` inside your hook, it will cancel rendering. Or you can return a number, it will be an animation currentTime */
15 | onBeforeRender?: (animation: Animation) => void | undefined | boolean | number
16 | onAfterRender?: (animation: Animation) => void
17 | }
18 |
19 | type AnimationConfiguration = |
20 | string |
21 | {
22 | name: string
23 | hooks: AnimationHooks
24 | }
25 |
26 | export type ElementSelector = |
27 | string |
28 | {
29 | selector: string
30 | animations: AnimationConfiguration[]
31 | }
32 |
33 | export type AnimationRender = {
34 | driverProgress: number
35 | }
36 |
--------------------------------------------------------------------------------
/lib/src/Driver.test.ts:
--------------------------------------------------------------------------------
1 | import { afterEach, expect, it, vi } from 'vitest'
2 | import { Driver } from './Driver'
3 | import type { TheSupersonicPlugin } from './TheSupersonicPlugin'
4 |
5 | it('adds onBeforeInit hook', () => {
6 | const driver = new Driver({
7 | ...driverSetup(),
8 | hooks: {
9 | onBeforeInit(driver) {
10 | driver.data.foo = 'bar'
11 | },
12 | },
13 | })
14 |
15 | expect(driver.data.foo).toBe('bar')
16 | })
17 |
18 | it('adds onAfterInit hook', () => {
19 | const driver = new Driver({
20 | ...driverSetup(),
21 | hooks: {
22 | onAfterInit(driver) {
23 | driver.data.foo = 'bar'
24 | },
25 | },
26 | })
27 |
28 | expect(driver.data.foo).toBe('bar')
29 | })
30 |
31 | it('adds onBeforeRender hook', () => {
32 | const driver = new Driver({
33 | ...driverSetup(),
34 | hooks: {
35 | onBeforeRender(driver) {
36 | driver.data.foo = 'bar'
37 | return false
38 | },
39 | onAfterRender(driver) {
40 | driver.data.foo = 'foo'
41 | },
42 | },
43 | })
44 |
45 | driver.render({ scroll: 0, renderedInitially: false })
46 |
47 | expect(driver.data.foo).toBe('bar')
48 | })
49 |
50 | it('adds onAfterRender hook', () => {
51 | const driver = new Driver({
52 | ...driverSetup(),
53 | hooks: {
54 | onAfterRender(driver) {
55 | driver.data.foo = 'foo'
56 | },
57 | },
58 | })
59 |
60 | driver.render({ scroll: 0, renderedInitially: false })
61 |
62 | expect(driver.data.foo).toBe('foo')
63 | })
64 |
65 | it('adds onUpdateLimits hook', () => {
66 | const driver = new Driver({
67 | ...driverSetup(),
68 | hooks: {
69 | onUpdateLimits(driver) {
70 | driver.data.foo = 'foo'
71 | },
72 | },
73 | })
74 |
75 | driver.updateLimits({ scroll: 0, screenHeight: 0 })
76 |
77 | expect(driver.data.foo).toBe('foo')
78 | })
79 |
80 | it('creates borders and tests "updateLimits"', () => {
81 | const driver = new Driver({
82 | ...driverSetup(),
83 | })
84 |
85 | driver.updateLimits({ scroll: 100, screenHeight: 1000 })
86 |
87 | expect(driver.start.top).toBe(100)
88 | expect(driver.end.top).toBe(2100)
89 | })
90 |
91 | it('creates helper and tests "updateLimits"', () => {
92 | const driver = new Driver({
93 | ...driverSetup(),
94 | })
95 |
96 | driver.helper.updateLimits({ top: 500, height: 800 })
97 |
98 | expect(driver.helper.domElement.style.top).toBe('500px')
99 | expect(driver.helper.domElement.style.height).toBe('800px')
100 | })
101 |
102 | it('renders and calculates progress', () => {
103 | const driver = new Driver({
104 | ...driverSetup(),
105 | })
106 |
107 | driver.updateLimits({ scroll: 0, screenHeight: 1000 })
108 |
109 | driver.render({
110 | scroll: 1000,
111 | renderedInitially: false,
112 | })
113 |
114 | expect(driver.progress).toBe(0.5)
115 | })
116 |
117 | it('reports an error if element is not found', () => {
118 | expect(() => {
119 | // eslint-disable-next-line no-new
120 | new Driver({
121 | ...driverSetup(),
122 | elements: ['.foo'],
123 | })
124 | }).toThrowError('Can\'t find element ".foo"')
125 | })
126 |
127 | it('warns in console if element hasn\'t animations', () => {
128 | const { animatableElement } = createAnimatableElement()
129 | animatableElement.getAnimations = vi.fn(() => ([]))
130 |
131 | const consoleSpy = vi.spyOn(console, 'warn')
132 |
133 | // eslint-disable-next-line no-new
134 | new Driver({
135 | ...driverSetup(),
136 | elements: ['.animatable-element'],
137 | })
138 |
139 | expect(consoleSpy).toHaveBeenCalledWith('Element \".animatable-element\" hasn\'t animations')
140 | })
141 |
142 | it('creates animations from simple config', () => {
143 | const { firstAnimation, secondAnimation } = createAnimatableElement(true)
144 |
145 | const driver = new Driver({
146 | ...driverSetup(),
147 | elements: ['.animatable-element'],
148 | })
149 |
150 | expect(driver.animations.get(firstAnimation)).toBeTruthy()
151 | expect(driver.animations.get(secondAnimation)).toBeTruthy()
152 | })
153 |
154 | it('creates only specified animations', () => {
155 | const { firstAnimation, secondAnimation } = createAnimatableElement(true)
156 |
157 | const driver = new Driver({
158 | ...driverSetup(),
159 | elements: [{
160 | selector: '.animatable-element',
161 | animations: ['first-animation'],
162 | }],
163 | })
164 |
165 | expect(driver.animations.get(firstAnimation)).toBeTruthy()
166 | expect(driver.animations.get(secondAnimation)).toBeFalsy()
167 | })
168 |
169 | it('adds onInit hook to animation', () => {
170 | const { firstAnimation } = createAnimatableElement(true)
171 |
172 | const driver = new Driver({
173 | ...driverSetup(),
174 | elements: [{
175 | selector: '.animatable-element',
176 | animations: [{
177 | name: 'first-animation',
178 | hooks: {
179 | onInit(animation) {
180 | animation.data.foo = 'bar'
181 | },
182 | },
183 | }],
184 | }],
185 | })
186 |
187 | expect(driver.animations.get(firstAnimation)?.data.foo).toBe('bar')
188 | })
189 |
190 | it('renders animation', () => {
191 | const { firstAnimation } = createAnimatableElement(true)
192 |
193 | const driver = new Driver({
194 | ...driverSetup(),
195 | elements: ['.animatable-element'],
196 | })
197 |
198 | driver.updateLimits({ scroll: 0, screenHeight: 1000 })
199 |
200 | driver.render({ scroll: 1000, renderedInitially: false })
201 |
202 | expect(driver.animations.get(firstAnimation)?.cssAnimation.currentTime).toBe(5000)
203 | })
204 |
205 | it('adds onBeforeRender hook to animation', () => {
206 | const { firstAnimation } = createAnimatableElement(true)
207 |
208 | const driver = new Driver({
209 | ...driverSetup(),
210 | elements: [{
211 | selector: '.animatable-element',
212 | animations: [{
213 | name: 'first-animation',
214 | hooks: {
215 | onBeforeRender(animation) {
216 | animation.data.foo = 'bar'
217 |
218 | return false
219 | },
220 | onAfterRender(animation) {
221 | animation.data.foo = 'foo'
222 | },
223 | },
224 | }],
225 | }],
226 | })
227 |
228 | driver.updateLimits({ scroll: 0, screenHeight: 1000 })
229 |
230 | driver.render({ scroll: 550, renderedInitially: false })
231 |
232 | expect(driver.animations.get(firstAnimation)?.data.foo).toBe('bar')
233 | })
234 |
235 | afterEach(() => {
236 | document.body.innerHTML = ''
237 | })
238 |
239 | function driverSetup() {
240 | const start = document.createElement('div')
241 | const end = document.createElement('div')
242 |
243 | // @ts-expect-error not full implementation of DOMRect
244 | start.getBoundingClientRect = vi.fn(() => ({
245 | top: 1000,
246 | }))
247 |
248 | // @ts-expect-error not full implementation of DOMRect
249 | end.getBoundingClientRect = vi.fn(() => ({
250 | top: 3000,
251 | }))
252 |
253 | return {
254 | id: 'driver-id',
255 | plugin: {} as TheSupersonicPlugin,
256 | start,
257 | end,
258 | }
259 | }
260 |
261 | function createAnimatableElement(withAnimations = false) {
262 | const animatableElement = document.createElement('div')
263 | animatableElement.classList.add('animatable-element')
264 | document.body.appendChild(animatableElement)
265 |
266 | if (withAnimations) {
267 | // @ts-expect-error not full implementation of Animation
268 | animatableElement.getAnimations = vi.fn(() => ([
269 | {
270 | animationName: 'first-animation',
271 | currentTime: 0,
272 | },
273 | {
274 | animationName: 'second-animation',
275 | currentTime: 0,
276 | },
277 | ]))
278 | }
279 |
280 | const firstAnimation = 'driver-id---.animatable-element---0---first-animation'
281 | const secondAnimation = 'driver-id---.animatable-element---0---second-animation'
282 |
283 | return {
284 | animatableElement,
285 | firstAnimation,
286 | secondAnimation,
287 | }
288 | }
289 |
--------------------------------------------------------------------------------
/lib/src/Driver.ts:
--------------------------------------------------------------------------------
1 | import { toFixed } from './utils'
2 |
3 | import { Animation } from './Animation'
4 | import type { AnimationHooks } from './Animation.types'
5 |
6 | import type { TheSupersonicPlugin } from './TheSupersonicPlugin'
7 |
8 | import type { DriverBorderConstructor, DriverBorderUpdateLimits, DriverCalculateProgress, DriverConstructor, DriverHelperConstructor, DriverHelperUpdateLimits, DriverHooks, DriverRender, DriverUpdateLimits } from './Driver.types'
9 |
10 | const debugColors = {
11 | colors: ['red', 'blue', 'orange', 'yellow'],
12 | colorIndex: 0,
13 | }
14 |
15 | /**
16 | * The main purpose of Driver is to calculate current progress from 0 to 1 depending on current scroll and position of 'start' and 'end' elements
17 | */
18 | export class Driver {
19 | id: string
20 | /** Progress is generated by script and means how much of the scroll covered right now. Minimum value: 0, maximum value: 1, float number with 4 numbers after decimal point precision */
21 | progress = 0
22 | /** You can store your custom data here to use between hooks */
23 | data: any = {}
24 | /** Start is HTML element. When it appears on the screen, driver will start an animation */
25 | start: DriverBorder
26 | /** End is HTML element. When it appears on the screen, driver will stop an animation */
27 | end: DriverBorder
28 | /** Link to plugin instance to be able to access global variables like 'scroll', 'screenHeight' */
29 | plugin: TheSupersonicPlugin
30 |
31 | animations: Map = new Map()
32 |
33 | /** Helper is an element which need for IntersectionObserver to activate or deactive driver */
34 | helper: DriverHelper
35 |
36 | hooks: DriverHooks
37 |
38 | constructor({ id, start, end, plugin, elements = [], hooks = {} }: DriverConstructor) {
39 | this.id = id
40 | this.plugin = plugin
41 | this.hooks = hooks
42 |
43 | if (this.hooks.onBeforeInit)
44 | this.hooks.onBeforeInit(this)
45 |
46 | // Initializing borders
47 | this.start = new DriverBorder({
48 | domElement: start,
49 | type: 'start',
50 | driver: this,
51 | })
52 | this.end = new DriverBorder({
53 | domElement: end,
54 | type: 'end',
55 | driver: this,
56 | })
57 |
58 | this.helper = new DriverHelper({
59 | id,
60 | pluginId: this.plugin.id,
61 | debug: plugin.debug,
62 | })
63 |
64 | // Initializing animations
65 | elements.forEach((selector) => {
66 | const actualSelector = typeof selector === 'string' ? selector : selector.selector
67 | const domElements = Array.from(document.querySelectorAll(actualSelector))
68 |
69 | if (domElements.length === 0)
70 | throw new Error(`Can't find element "${actualSelector}"`)
71 |
72 | domElements.forEach((domElement, domElementIndex) => {
73 | const elementCssAnimations = domElement.getAnimations() as unknown as CSSAnimation[]
74 |
75 | const animationConfigurations: {
76 | cssAnimation: CSSAnimation
77 | hooks: AnimationHooks
78 | }[] = []
79 |
80 | if (typeof selector === 'string') {
81 | if (elementCssAnimations.length === 0)
82 | console.warn(`Element "${actualSelector}" hasn't animations`)
83 |
84 | elementCssAnimations.forEach((cssAnimation) => {
85 | animationConfigurations.push({
86 | cssAnimation,
87 | hooks: {},
88 | })
89 | })
90 | }
91 | else if (typeof selector === 'object') {
92 | selector.animations.forEach((animationConfiguration) => {
93 | let animationName = ''
94 | let hooks: AnimationHooks = {}
95 |
96 | if (typeof animationConfiguration === 'string') {
97 | animationName = animationConfiguration
98 | }
99 | else if (typeof animationConfiguration === 'object') {
100 | animationName = animationConfiguration.name
101 | hooks = animationConfiguration.hooks
102 | }
103 |
104 | const cssAnimation = elementCssAnimations.find(animation => animation.animationName === animationName)
105 |
106 | if (cssAnimation) {
107 | animationConfigurations.push({
108 | cssAnimation,
109 | hooks,
110 | })
111 | }
112 | else {
113 | console.warn(`Element "${actualSelector}" hasn't animation: "${animationName}"`)
114 | }
115 | })
116 | }
117 |
118 | animationConfigurations.forEach((animationConfiguration) => {
119 | const id = `${this.id}---${actualSelector}---${domElementIndex}---${animationConfiguration.cssAnimation.animationName}`
120 | const animation = new Animation({
121 | id,
122 | driver: this,
123 | cssAnimation: animationConfiguration.cssAnimation,
124 | hooks: animationConfiguration.hooks,
125 | domElement,
126 | })
127 |
128 | this.animations.set(id, animation)
129 | })
130 | })
131 | })
132 |
133 | if (this.hooks.onAfterInit)
134 | this.hooks.onAfterInit(this)
135 | }
136 |
137 | /** Driver calculates its progress and then renders all of it's properties with progress value */
138 | render({ scroll, renderedInitially, consoleColor = '#000000' }: DriverRender) {
139 | const oldProgress = this.progress
140 | this.progress = this.calculateProgress({
141 | scroll,
142 | start: this.start.top,
143 | end: this.end.top,
144 | })
145 |
146 | if (this.hooks.onBeforeRender) {
147 | const onBeforeRenderReturn = this.hooks.onBeforeRender(this)
148 |
149 | if (typeof onBeforeRenderReturn === 'boolean' && !onBeforeRenderReturn)
150 | return false
151 | }
152 |
153 | if (oldProgress !== this.progress || !renderedInitially) {
154 | console.groupCollapsed(
155 | `%cDriver "${this.id}" starts rendering, progress is ${this.progress}, scroll is ${scroll}`,
156 | `color: ${consoleColor}`,
157 | )
158 |
159 | for (const animation of this.animations.values()) {
160 | console.log(`Animation "${animation.id}" starts rendering`)
161 | animation.render({ driverProgress: this.progress })
162 | console.log(`Animation "${animation.id}" finished rendering, currentTime: ${animation.cssAnimation.currentTime}`)
163 | }
164 |
165 | if (this.hooks.onAfterRender)
166 | this.hooks.onAfterRender(this)
167 |
168 | console.log(`Driver "${this.id}" finished rendering`)
169 | console.groupEnd()
170 | }
171 | }
172 |
173 | /** Calculates current driver progress, depending on current scroll and top offset of DOM elements */
174 | calculateProgress({ scroll, start, end }: DriverCalculateProgress): number {
175 | let progress = (scroll - start) / (end - start)
176 | if (progress < 0)
177 | progress = 0
178 | else if (progress > 1)
179 | progress = 1
180 | else progress = toFixed(progress, 4)
181 |
182 | return progress
183 | }
184 |
185 | /** Recalculates DOM elements top offset */
186 | updateLimits({ scroll, screenHeight }: DriverUpdateLimits) {
187 | this.start.updateLimits({ scroll, screenHeight })
188 | this.end.updateLimits({ scroll, screenHeight })
189 |
190 | const top = this.start.top + screenHeight
191 | this.helper.updateLimits({
192 | top,
193 | height: this.end.top - top,
194 | })
195 |
196 | if (this.hooks.onUpdateLimits)
197 | this.hooks.onUpdateLimits(this)
198 | }
199 |
200 | /** Activates driver when it becomes visible on the screen */
201 | activate() {
202 | this.plugin.driverActiveInstances.add(this)
203 |
204 | if (this.hooks.onActivation)
205 | this.hooks.onActivation(this)
206 |
207 | console.log(`Driver "${this.id}" activated`)
208 | }
209 |
210 | /** Deactivates driver when it's progress becomes 0 or 1' */
211 | deactivate() {
212 | this.plugin.driverActiveInstances.delete(this)
213 |
214 | if (this.hooks.onDeactivation)
215 | this.hooks.onDeactivation(this)
216 |
217 | console.log(`Driver "${this.id}" deactivated`)
218 | }
219 | }
220 |
221 | /** An HTML Element. It's top offset serves as indicator of where driver starts and where it ends */
222 | class DriverBorder {
223 | /** Associated DOM element */
224 | domElement: HTMLElement
225 | /** Top means amount of scroll needed to border activate or deactivate driver */
226 | top: number = 0
227 |
228 | constructor({ domElement, type, driver }: DriverBorderConstructor) {
229 | if (typeof domElement === 'string')
230 | domElement = document.querySelector(domElement)
231 |
232 | if (!domElement)
233 | throw new Error(`Can't find "${type}" HTMLElement for driver "${driver.id}"`)
234 |
235 | this.domElement = domElement
236 | }
237 |
238 | /** Recalculates top offset */
239 | updateLimits({ scroll, screenHeight }: DriverBorderUpdateLimits) {
240 | this.top = ~~this.domElement.getBoundingClientRect().top + scroll - screenHeight
241 | }
242 | }
243 |
244 | /** A helper HTML element, which connects to Border instances and starts being tracked by IntersectionObserver */
245 | class DriverHelper {
246 | /** DOM element which is dynamically generated by plugin */
247 | domElement: HTMLElement
248 |
249 | pluginId: string
250 |
251 | debug: boolean
252 |
253 | constructor({ id, pluginId, debug = false }: DriverHelperConstructor) {
254 | this.pluginId = pluginId
255 | this.debug = debug
256 | this.domElement = document.createElement('i')
257 | this.domElement.style.position = 'absolute'
258 | this.domElement.style.left = '0'
259 | this.domElement.style.width = '1px'
260 |
261 | this.domElement.setAttribute('data-supersonic-driver', id)
262 | this.domElement.setAttribute('data-supersonic-type', 'helper')
263 | this.domElement.setAttribute('data-supersonic-plugin-id', this.pluginId)
264 | this.domElement.classList.add('supersonic-helper')
265 |
266 | document.body.appendChild(this.domElement)
267 |
268 | if (debug) {
269 | const elements = Array.from(document.querySelectorAll('[data-supersonic-type=\'helper\']'))
270 | const index = elements.indexOf(this.domElement)
271 | this.domElement.style.left
272 | = `${index * 10}px`
273 | this.domElement.style.width = '10px'
274 | this.domElement.style.minHeight = '50px'
275 | this.domElement.style.background = debugColors.colors[debugColors.colorIndex]
276 | this.domElement.style.zIndex = (100000).toString()
277 | if (debugColors.colorIndex === 3)
278 | debugColors.colorIndex = 0
279 | else debugColors.colorIndex++
280 | this.domElement.style.opacity = '0.75'
281 | }
282 | }
283 |
284 | /** Sets position of helper */
285 | updateLimits({ top, height }: DriverHelperUpdateLimits) {
286 | if (height <= 0)
287 | height = 1
288 | this.domElement.style.setProperty('top', `${top}px`)
289 | this.domElement.style.setProperty('height', `${height}px`)
290 | }
291 |
292 | /** Deletes helper DOM element */
293 | uninit() {
294 | this.domElement.remove()
295 | }
296 | }
297 |
--------------------------------------------------------------------------------
/lib/src/Driver.types.ts:
--------------------------------------------------------------------------------
1 | import type { Driver } from './Driver'
2 | import type { ElementSelector } from './Animation.types'
3 | import type { TheSupersonicPlugin } from './TheSupersonicPlugin'
4 |
5 | export type DriverConfiguration = {
6 | id?: string
7 | start: DriverBorderConfiguration
8 | end: DriverBorderConfiguration
9 | elements?: ElementSelector[]
10 | hooks?: DriverHooks
11 | }
12 |
13 | export type DriverConstructor = Omit & {
14 | id: string
15 | plugin: TheSupersonicPlugin
16 | }
17 |
18 | export type DriverRender = {
19 | scroll: number
20 | renderedInitially: boolean
21 | consoleColor?: string
22 | }
23 |
24 | export type DriverCalculateProgress = {
25 | scroll: number
26 | start: number
27 | end: number
28 | }
29 |
30 | export type DriverHooks = {
31 | onBeforeInit?: (driver: Driver) => void
32 | onAfterInit?: (driver: Driver) => void
33 | /** You can `return false` inside your hook, it will cancel rendering */
34 | onBeforeRender?: (driver: Driver) => void | undefined | boolean
35 | onAfterRender?: (driver: Driver) => void
36 | onActivation?: (driver: Driver) => void
37 | onDeactivation?: (driver: Driver) => void
38 | onUpdateLimits?: (driver: Driver) => void
39 | }
40 |
41 | type DriverBorderConfiguration = HTMLElement | string | null
42 |
43 | export type DriverBorderConstructor = {
44 | domElement: DriverBorderConfiguration
45 | driver: Driver
46 | type: 'start' | 'end'
47 | }
48 |
49 | export type DriverHelperConstructor = {
50 | id: string
51 | pluginId: string
52 | debug?: boolean
53 | }
54 |
55 | export type DriverUpdateLimits = {
56 | scroll: number
57 | screenHeight: number
58 | }
59 |
60 | export type DriverBorderUpdateLimits = {
61 | scroll: number
62 | screenHeight: number
63 | }
64 |
65 | export type DriverHelperUpdateLimits = {
66 | top: number
67 | height: number
68 | }
69 |
--------------------------------------------------------------------------------
/lib/src/Observer.ts:
--------------------------------------------------------------------------------
1 | import type { Constructor } from './Observer.types'
2 |
3 | export class Observer {
4 | instance: IntersectionObserver
5 |
6 | constructor({ observables, driverInstances }: Constructor) {
7 | this.instance = new IntersectionObserver(
8 | (entries) => {
9 | entries.forEach((entry) => {
10 | const target = entry.target as HTMLElement
11 | const driverId = target.dataset.supersonicDriver!
12 | const driver = driverInstances.get(driverId)
13 |
14 | if (!driver)
15 | throw new Error(`Observer can't find driver "${driverId}"`)
16 |
17 | if (entry.isIntersecting)
18 | driver.activate()
19 |
20 | else
21 | driver.deactivate()
22 | })
23 | },
24 | )
25 |
26 | observables.forEach((observable) => {
27 | this.instance.observe(observable)
28 | })
29 | }
30 |
31 | uninit() {
32 | this.instance.disconnect()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/src/Observer.types.ts:
--------------------------------------------------------------------------------
1 | import type { Driver } from './Driver'
2 |
3 | export type Constructor = {
4 | observables: HTMLElement[]
5 | driverInstances: Map
6 | }
7 |
--------------------------------------------------------------------------------
/lib/src/TheSupersonicPlugin.test.ts:
--------------------------------------------------------------------------------
1 | import { afterEach, expect, it, vi } from 'vitest'
2 | import { TheSupersonicPlugin } from './TheSupersonicPlugin'
3 |
4 | const IOObserver = vi.fn()
5 | const IODisconnect = vi.fn()
6 |
7 | it('adds onBeforeInit hook', () => {
8 | const plugin = new TheSupersonicPlugin(
9 | [{ ...driverSetup() }],
10 | {
11 | hooks: {
12 | onBeforeInit(plugin) {
13 | plugin.data.foo = 'bar'
14 | },
15 | },
16 | },
17 | )
18 |
19 | expect(plugin.data.foo).toBe('bar')
20 | })
21 |
22 | it('creates driver instance onInit', () => {
23 | const plugin = new TheSupersonicPlugin([{ ...driverSetup(), id: 'foo' }])
24 |
25 | expect(plugin.driverInstances.has('foo')).toBeTruthy()
26 | })
27 |
28 | it('uninits', () => {
29 | const plugin = new TheSupersonicPlugin([{ ...driverSetup() }])
30 |
31 | plugin.uninit()
32 |
33 | expect(document.body.innerHTML).toBe('')
34 | expect(plugin.driverInstances.size).toBe(0)
35 | expect(plugin.driverActiveInstances.size).toBe(0)
36 | expect(IODisconnect).toHaveBeenCalledOnce()
37 | })
38 |
39 | it('adds onBeforeRender hook', () => {
40 | const plugin = new TheSupersonicPlugin([{ ...driverSetup() }], {
41 | hooks: {
42 | onBeforeRender(plugin) {
43 | plugin.data.foo = 'bar'
44 | return false
45 | },
46 | onAfterRender(plugin) {
47 | plugin.data.foo = 'foo'
48 | },
49 | },
50 | })
51 |
52 | expect(plugin.data.foo).toBe('bar')
53 | })
54 |
55 | it('renders all drivers', () => {
56 | const plugin = new TheSupersonicPlugin([
57 | {
58 | ...driverSetup(),
59 | id: 'foo',
60 | },
61 | {
62 | ...driverSetup(),
63 | id: 'bar',
64 | },
65 | ])
66 |
67 | plugin.screenHeight = 1000
68 |
69 | plugin.updateLimits()
70 |
71 | vi.stubGlobal('scrollY', 2000)
72 |
73 | plugin.render({ useActiveDrivers: false })
74 |
75 | expect(plugin.driverInstances.get('foo')?.progress).toBe(0.5)
76 | expect(plugin.driverInstances.get('bar')?.progress).toBe(0.5)
77 | })
78 |
79 | it('renders only active drivers', () => {
80 | const plugin = new TheSupersonicPlugin([
81 | {
82 | ...driverSetup(),
83 | id: 'foo',
84 | },
85 | {
86 | ...driverSetup(),
87 | id: 'bar',
88 | },
89 | ])
90 |
91 | plugin.screenHeight = 1000
92 |
93 | plugin.updateLimits()
94 |
95 | vi.stubGlobal('scrollY', 2000)
96 |
97 | plugin.driverInstances.get('foo')?.activate()
98 |
99 | plugin.render({ useActiveDrivers: true })
100 |
101 | expect(plugin.driverInstances.get('foo')?.progress).toBe(0.5)
102 | expect(plugin.driverInstances.get('bar')?.progress).toBe(0)
103 | })
104 |
105 | afterEach(() => {
106 | document.body.innerHTML = ''
107 | vi.stubGlobal('scrollY', 0)
108 | })
109 |
110 | vi.stubGlobal('IntersectionObserver', vi.fn(() => ({
111 | observe: IOObserver,
112 | disconnect: IODisconnect,
113 | })))
114 |
115 | function driverSetup() {
116 | const start = document.createElement('div')
117 | const end = document.createElement('div')
118 |
119 | // @ts-expect-error not full implementation of DOMRect
120 | start.getBoundingClientRect = vi.fn(() => ({
121 | top: 1000,
122 | }))
123 |
124 | // @ts-expect-error not full implementation of DOMRect
125 | end.getBoundingClientRect = vi.fn(() => ({
126 | top: 3000,
127 | }))
128 |
129 | return {
130 | start,
131 | end,
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/lib/src/TheSupersonicPlugin.ts:
--------------------------------------------------------------------------------
1 | import { Driver } from './Driver'
2 | import { Observer } from './Observer'
3 | import { debounce, generateId } from './utils'
4 |
5 | import type { PluginConfiguration, PluginHooks, PluginRender } from './TheSupersonicPlugin.types'
6 | import type { DriverConfiguration } from './Driver.types'
7 |
8 | /**
9 | *
10 | * Main class handling all of the logic. To initialize the plugin, you create a new instance of this class
11 | *
12 | * @example
13 | * const plugin = new TheSupersonicPlugin([
14 | * {
15 | * start: '.start',
16 | * end: '.end',
17 | * elements: ['.animatable-element']
18 | * }
19 | * ]);
20 | *
21 | */
22 | export class TheSupersonicPlugin {
23 | /** Unique id of this running instance. You explicitly define it or let plugin auto generate it */
24 | id: string
25 | /** Current window scrollY */
26 | scroll = 0
27 |
28 | /** Current screen height */
29 | screenHeight = 0
30 |
31 | /** Required to get all of the drivers render at once to stand on their first frame */
32 | renderedInitially: boolean = false
33 |
34 | /** Used to cancelAnimationFrame on 'uninit()' */
35 | rafId = 0
36 |
37 | /** Color of console messages in dev mode. It changes each frame to make it more convenient to visually separate frames */
38 | consoleColor = '#ffffff'
39 |
40 | /** IntersectionObserver instance */
41 | observer: Observer | null = null
42 |
43 | /** Debounced resize listener */
44 | onResize: EventListener | null = null
45 | /** You can store your custom data here to use between hooks */
46 | data: any = {}
47 |
48 | /** Make helper visible */
49 | debug: boolean
50 |
51 | hooks: PluginHooks = {}
52 |
53 | driverInstances: Map = new Map()
54 | driverActiveInstances: Set = new Set()
55 |
56 | constructor(drivers: DriverConfiguration[], configuration?: PluginConfiguration) {
57 | this.id = configuration?.id ?? generateId()
58 | this.hooks = configuration?.hooks ?? {}
59 | this.debug = configuration?.debug ?? false
60 |
61 | if (this.hooks?.onBeforeInit)
62 | this.hooks.onBeforeInit(this)
63 |
64 | // Initializing driver instances
65 | drivers.forEach((driverConfiguration) => {
66 | const id = driverConfiguration.id ?? generateId()
67 |
68 | const driver = new Driver({
69 | id,
70 | hooks: driverConfiguration.hooks,
71 | start: driverConfiguration.start,
72 | end: driverConfiguration.end,
73 | elements: driverConfiguration.elements,
74 | plugin: this,
75 | })
76 | this.driverInstances.set(id, driver)
77 | })
78 |
79 | this.updateLimits()
80 |
81 | // Creating IntersectionObserver, which handles "active" state on Driver instances
82 | const observables = Array.from(document.querySelectorAll(`[data-supersonic-type="helper"][data-supersonic-plugin-id="${this.id}"]`))
83 | this.observer = new Observer({
84 | observables,
85 | driverInstances: this.driverInstances,
86 | })
87 |
88 | // Adding event listener for resize
89 | const resize = () => {
90 | if (this.hooks?.onBeforeResize)
91 | this.hooks.onBeforeResize(this)
92 |
93 | this.updateLimits()
94 | this.render({ useActiveDrivers: false })
95 |
96 | if (this.hooks.onAfterResize)
97 | this.hooks.onAfterResize(this)
98 | }
99 | this.onResize = debounce(resize, 250)
100 | window.addEventListener('resize', this.onResize)
101 |
102 | this.render({ useActiveDrivers: false })
103 | this.renderedInitially = true
104 |
105 | if (this.hooks?.onAfterInit)
106 | this.hooks.onAfterInit(this)
107 |
108 | console.log('Driver instances:', this.driverInstances)
109 | }
110 |
111 | /** Removes all of the plugin stuff (useful for SPA) */
112 | uninit() {
113 | for (const driver of this.driverInstances.values())
114 | driver.helper.uninit()
115 |
116 | this.driverInstances.clear()
117 | this.driverActiveInstances.clear()
118 |
119 | this.observer!.uninit()
120 | cancelAnimationFrame(this.rafId)
121 |
122 | window.removeEventListener('resize', this.onResize!)
123 | }
124 |
125 | /** Main rendering cycle. Active drivers are visible ones */
126 | render({ useActiveDrivers }: PluginRender) {
127 | this.updateScroll()
128 |
129 | if (this.hooks.onBeforeRender) {
130 | const onBeforeRenderReturn = this.hooks.onBeforeRender(this)
131 |
132 | if (typeof onBeforeRenderReturn === 'boolean' && !onBeforeRenderReturn)
133 | return false
134 | }
135 |
136 | const drivers = useActiveDrivers ? this.driverActiveInstances.values() : this.driverInstances.values()
137 |
138 | for (const driver of drivers) {
139 | driver.render({
140 | scroll: this.scroll,
141 | renderedInitially: this.renderedInitially,
142 | consoleColor: this.consoleColor,
143 | })
144 | }
145 |
146 | this.rafId = requestAnimationFrame(() => {
147 | this.render({ useActiveDrivers: true })
148 | })
149 |
150 | if (this.hooks.onAfterRender)
151 | this.hooks.onAfterRender(this)
152 |
153 | if (import.meta.env.DEV) {
154 | const randomInt = ~~(Math.random() * 100000)
155 | this.consoleColor = `#${randomInt.toString(16).padStart(6, 'f')}`
156 | }
157 | }
158 |
159 | /** Updates global scroll and driver DOM elements top offset. Called once on page load and each time after window.resize */
160 | updateLimits() {
161 | this.updateScreenHeight()
162 | this.updateScroll()
163 |
164 | for (const driver of this.driverInstances.values()) {
165 | driver.updateLimits({
166 | scroll: this.scroll,
167 | screenHeight: this.screenHeight,
168 | })
169 | }
170 | }
171 |
172 | updateScroll() {
173 | this.scroll = window.scrollY || document.documentElement.scrollTop
174 | }
175 |
176 | /** Dirty hack for calculating screen height. We can't just use "window.innerHeight" because it "jumps" on mobile phones when you scroll and toolbar collapses */
177 | updateScreenHeight() {
178 | const styles: { [key: string]: string } = {
179 | position: 'absolute',
180 | left: '0',
181 | top: '0',
182 | height: '100vh',
183 | width: '1px',
184 | zIndex: '-1',
185 | visibility: 'hidden',
186 | }
187 | const helper = document.createElement('div')
188 | for (const property in styles)
189 | helper.style.setProperty(property, styles[property])
190 |
191 | document.body.appendChild(helper)
192 | this.screenHeight = helper.clientHeight
193 | helper.remove()
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/lib/src/TheSupersonicPlugin.types.ts:
--------------------------------------------------------------------------------
1 | import type { TheSupersonicPlugin } from './TheSupersonicPlugin'
2 |
3 | export type PluginConfiguration = {
4 | id?: string
5 | hooks?: PluginHooks
6 | debug?: boolean
7 | }
8 |
9 | export type PluginHooks = {
10 | onBeforeInit?: (plugin: TheSupersonicPlugin) => void
11 | onAfterInit?: (plugin: TheSupersonicPlugin) => void
12 |
13 | /** You can `return false` inside your hook, it will cancel rendering */
14 | onBeforeRender?: (plugin: TheSupersonicPlugin) => void | undefined | boolean
15 | onAfterRender?: (plugin: TheSupersonicPlugin) => void
16 |
17 | onBeforeResize?: (plugin: TheSupersonicPlugin) => void
18 | onAfterResize?: (plugin: TheSupersonicPlugin) => void
19 | }
20 |
21 | export type PluginRender = {
22 | useActiveDrivers: boolean
23 | }
24 |
--------------------------------------------------------------------------------
/lib/src/index.ts:
--------------------------------------------------------------------------------
1 | import { TheSupersonicPlugin } from './TheSupersonicPlugin'
2 | import { Driver } from './Driver'
3 | import { Animation } from './Animation'
4 |
5 | export {
6 | TheSupersonicPlugin,
7 | Driver,
8 | Animation,
9 | }
10 |
--------------------------------------------------------------------------------
/lib/src/utils.ts:
--------------------------------------------------------------------------------
1 | function toFixed(number: number, precision: number) {
2 | precision = 10 ** precision
3 | return ~~(number * precision) / precision
4 | }
5 |
6 | function debounce(func: Function, time: number) {
7 | let timeout: any
8 | return function () {
9 | clearTimeout(timeout)
10 | // @ts-expect-error this
11 | timeout = setTimeout(() => func.apply(this), time)
12 | }
13 | }
14 |
15 | function generateId(): string {
16 | return Math.random().toString(16).substring(2)
17 | }
18 |
19 | export {
20 | toFixed,
21 | debounce,
22 | generateId,
23 | }
24 |
--------------------------------------------------------------------------------
/lib/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
5 | "useDefineForClassFields": true,
6 | "module": "ESNext",
7 |
8 | /* Bundler mode */
9 | "moduleResolution": "bundler",
10 | "resolveJsonModule": true,
11 | "allowImportingTsExtensions": true,
12 |
13 | /* Linting */
14 | "strict": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "noUnusedLocals": true,
17 | "noUnusedParameters": true,
18 | "noEmit": true,
19 | "isolatedModules": true,
20 | "skipLibCheck": true
21 | },
22 | "include": ["src", "env.d.ts"]
23 | }
24 |
--------------------------------------------------------------------------------
/lib/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { resolve } from 'node:path'
4 | import { defineConfig } from 'vite'
5 | import dts from 'vite-plugin-dts'
6 |
7 | export default defineConfig(({ mode }) => {
8 | return {
9 | build: {
10 | lib: {
11 | entry: resolve(__dirname, 'src/index.ts'),
12 | fileName: 'the-supersonic-plugin-for-scroll-based-animation',
13 | name: 'TheSupersonicPluginWrapper',
14 | formats: ['es', 'iife'],
15 | },
16 | },
17 | esbuild: {
18 | drop: mode === 'production'
19 | ? ['console']
20 | : [],
21 | },
22 | test: {
23 | environment: 'jsdom',
24 | },
25 | plugins: [dts({
26 | exclude: ['**/*.test.ts', '**/utils.ts'],
27 | })],
28 | }
29 | })
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "the-supersonic-plugin-for-scroll-based-animation",
3 | "type": "module",
4 | "version": "2.1.0",
5 | "description": "The Supersonic Plugin For Scroll Based Animation",
6 | "author": {
7 | "name": "Alex Illarionov",
8 | "email": "the.illarionov@gmail.com",
9 | "url": "https://github.com/the-illarionov"
10 | },
11 | "license": "MIT",
12 | "homepage": "https://the-illarionov.com/the-supersonic-plugin-for-scroll-based-animation/examples",
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation.git"
16 | },
17 | "bugs": {
18 | "url": "https://github.com/the-illarionov/the-supersonic-plugin-for-scroll-based-animation/issues",
19 | "email": "the.illarionov@gmail.com"
20 | },
21 | "keywords": [
22 | "javascript",
23 | "scroll",
24 | "animation",
25 | "fast",
26 | "custom",
27 | "parallax",
28 | "supersonic",
29 | "plugin",
30 | "css"
31 | ],
32 | "browser": "./lib/dist/the-supersonic-plugin-for-scroll-based-animation.js",
33 | "unpkg": "./lib/dist/the-supersonic-plugin-for-scroll-based-animation.iife.js",
34 | "types": "./lib/dist/index.d.ts",
35 | "files": [
36 | "./lib/dist"
37 | ],
38 | "scripts": {
39 | "lint": "eslint .",
40 | "lint:fix": "eslint . --fix"
41 | },
42 | "devDependencies": {
43 | "@antfu/eslint-config": "^2.12.2",
44 | "eslint": "^8.56.0",
45 | "eslint-plugin-format": "^0.1.0",
46 | "typescript": "~5.4.0"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------