diff --git a/LICENSE b/LICENSE index 7cc760f..acd7040 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ ISC License -Copyright (c) 2021–2022 Alexey Raspopov +Copyright (c) 2021–2024 Oleksii Raspopov Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/packages/oscillation/package.json b/packages/oscillation/package.json index 80f6604..94ca780 100644 --- a/packages/oscillation/package.json +++ b/packages/oscillation/package.json @@ -3,7 +3,7 @@ "version": "0.2.0", "description": "An animation library", "license": "ISC", - "author": "Alexey Raspopov", + "author": "Oleksii Raspopov", "repository": "UnknownPrinciple/oscillation", "main": "./oscillation.js", "module": "./oscillation.js", diff --git a/packages/react-oscillation/README.md b/packages/react-oscillation/README.md deleted file mode 100644 index bde3675..0000000 --- a/packages/react-oscillation/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# React Oscillation - -Complementary React hooks and components for Oscillation library - - npm install react-oscillation - -## Usage - -```jsx -import { useMotion, spring } from "react-oscillation"; - -function Toggle({ checked, onChange }) { - let { x } = useMotion(() => ({ x: spring(checked ? 100 : 0) }), [checked]); - let transform = `translateX(${x}px)`; - return ( -
-
onChange(!checked)}> -
-
-
- ); -} -``` - -## API - -### `useMotion(factory, deps)` - -Hook to translate arbitrary input into motion-based value. A `factory` function suppose to return an -object that enumerates what values need to be advancing. Similar to `useMemo()`, `deps` array tracks -dependencies to update motion target. - -```jsx -import { useMotion, spring } from "react-oscillation"; - -function Scene({ targetX, targetY }) { - let { x, y } = useMotion(() => { - return { x: spring(targetX), y: spring(targetY) }; - }, [targetX, targetY]); - let transform = `translateX(${x}px, ${y}px)`; - return ( -
-
-
- ); -} -``` - -### `useMotionState(initialValues)` - -Hook to translate arbitrary input into motion-based value just like `useMotion()` but additionally -allows manually updating the target, similar to how `useState()` works. - -```jsx -import { useMotionState, spring } from "react-oscillation"; - -function Scene() { - let [{ x, y }, setState] = useMotion({ x: 0, y: 0 }); - let transform = `translateX(${x}px, ${y}px)`; - let randomize = () => { - let randomX = Math.random() * 500; - let randomY = Math.random() * 300; - setState({ x: spring(randomX), y: spring(randomY) }); - }; - return ( -
-
- -
- ); -} -``` - -### `` - -Component that provides motion-based values via render-prop. Suitable for cases where a small part -of component's render should be updated but re-rendering the whole component is bad for performance. - -```jsx -import { useMotionState, spring } from "react-oscillation"; - -function Scene() { - let [{ x, y }, setState] = useState({ x: 0, y: 0 }); - let randomize = () => { - setState({ x: Math.random() * 500, y: Math.random() * 300 }); - }; - return ( -
- - {({ x, y }) => { - let transform = `translateX(${x}px, ${y}px)`; - return
; - }} - - -
- ); -} -``` diff --git a/packages/react-oscillation/jest.config.js b/packages/react-oscillation/jest.config.js deleted file mode 100644 index 7107606..0000000 --- a/packages/react-oscillation/jest.config.js +++ /dev/null @@ -1,11 +0,0 @@ -export default { - resetMocks: true, - collectCoverage: true, - transform: { - "\\.js$": "./jest.transform.js", - }, - moduleNameMapper: { - // expects build artifacts - oscillation$: "/../oscillation/build", - }, -}; diff --git a/packages/react-oscillation/jest.transform.js b/packages/react-oscillation/jest.transform.js deleted file mode 100644 index 9afd9b9..0000000 --- a/packages/react-oscillation/jest.transform.js +++ /dev/null @@ -1,5 +0,0 @@ -import bj from "babel-jest"; - -export default bj.createTransformer({ - presets: [["@babel/preset-env", { loose: true }], "@babel/preset-react"], -}); diff --git a/packages/react-oscillation/modules/Motion.js b/packages/react-oscillation/modules/Motion.js deleted file mode 100644 index 7789a03..0000000 --- a/packages/react-oscillation/modules/Motion.js +++ /dev/null @@ -1,15 +0,0 @@ -import { useMotion } from "./useMotion.js"; - -export function Motion(props) { - let values = useMotion(() => collect(props), [props]); - return props.children(values); -} - -function collect(source) { - let result = {}; - for (let key in source) { - if (typeof source[key] === "function") continue; - result[key] = source[key]; - } - return result; -} diff --git a/packages/react-oscillation/modules/__mocks__/host.js b/packages/react-oscillation/modules/__mocks__/host.js deleted file mode 100644 index 25348e7..0000000 --- a/packages/react-oscillation/modules/__mocks__/host.js +++ /dev/null @@ -1,36 +0,0 @@ -import { jest } from "@jest/globals"; - -export default function instantiateMock() { - let timestamp = 0; - let performance = { now: jest.fn(() => timestamp) }; - let callbacks = {}; - let id = 0; - let rAF = (cb) => { - callbacks[id] = cb; - return id++; - }; - - let cAF = (id) => { - delete callbacks[id]; - }; - - let step = (ms) => { - let cbs = callbacks; - callbacks = {}; - timestamp += ms; - for (let k in cbs) { - cbs[k](timestamp); - } - }; - - let aAf = (steps, ms) => { - for (var i = 0; i < steps; i++) { - step(ms); - } - }; - - global.performance = performance; - global.requestAnimationFrame = jest.fn(rAF); - global.cancelAnimationFrame = jest.fn(cAF); - global.advanceAnimationFrame = aAf; -} diff --git a/packages/react-oscillation/modules/__tests__/Motion-test.js b/packages/react-oscillation/modules/__tests__/Motion-test.js deleted file mode 100644 index 642ab55..0000000 --- a/packages/react-oscillation/modules/__tests__/Motion-test.js +++ /dev/null @@ -1,43 +0,0 @@ -import * as React from "react"; - -import { create, act } from "react-test-renderer"; -import { Motion } from "../Motion.js"; - -import { spring } from "../spring.js"; -import instantiateMock from "../__mocks__/host.js"; - -beforeEach(() => { - instantiateMock(); -}); - -test("proper react hooks data passing to the core library", () => { - let snapshots = []; - function Component({ value }) { - return ( - - {({ number, constant }) => { - snapshots.push(number); - return ( - - {number}, {constant} - - ); - }} - - ); - } - let renderer = create(); - expect(renderer.toJSON().children).toEqual(["0", ", ", "1"]); - act(() => { - renderer.update(); - }); - act(() => { - advanceAnimationFrame(1, 0); - advanceAnimationFrame(5, 1000 / 60); - }); - expect(snapshots).toEqual([0, 0, 3.6720468997166984]); - act(() => { - advanceAnimationFrame(60, 1000 / 60); - }); - expect(renderer.toJSON().children).toEqual(["10", ", ", "1"]); -}); diff --git a/packages/react-oscillation/modules/__tests__/useMotion-test.js b/packages/react-oscillation/modules/__tests__/useMotion-test.js deleted file mode 100644 index 9da4b1d..0000000 --- a/packages/react-oscillation/modules/__tests__/useMotion-test.js +++ /dev/null @@ -1,63 +0,0 @@ -import * as React from "react"; -import { jest } from "@jest/globals"; -import { create, act } from "react-test-renderer"; -import { useMotion } from "../useMotion.js"; -import { useMotionState } from "../useMotionState.js"; -import { spring } from "../spring.js"; -import instantiateMock from "../__mocks__/host.js"; - -beforeEach(() => { - instantiateMock(); -}); - -test("proper react hooks data passing to the core library", () => { - let snapshots = []; - function Component({ value }) { - let { number, constant } = useMotion(() => ({ number: spring(value), constant: 1 }), [value]); - snapshots.push(number); - return ( - - {number}, {constant} - - ); - } - let renderer = create(); - expect(renderer.toJSON().children).toEqual(["0", ", ", "1"]); - act(() => { - renderer.update(); - }); - act(() => { - advanceAnimationFrame(1, 0); - advanceAnimationFrame(5, 1000 / 60); - }); - expect(snapshots).toEqual([0, 0, 3.6720468997166984]); - act(() => { - advanceAnimationFrame(60, 1000 / 60); - }); - expect(renderer.toJSON().children).toEqual(["10", ", ", "1"]); -}); - -test("proper react hooks setter scheduling", () => { - let snapshots = []; - let setStateFn; - function Component() { - let [state, setState] = useMotionState(() => ({ x: 0 })); - snapshots.push(state.x); - setStateFn = setState; - return {state.x}; - } - let renderer = create(); - expect(renderer.toJSON().children).toEqual(["0"]); - act(() => { - setStateFn({ x: spring(10) }); - advanceAnimationFrame(1, 0); - advanceAnimationFrame(5, 1000 / 60); - }); - expect(snapshots).toEqual([0, 3.6720468997166984]); - let fn = jest.fn(() => ({ x: 0 })); - act(() => { - setStateFn(fn); - }); - expect(fn).toHaveBeenCalledWith({ x: 3.6720468997166984 }); - expect(renderer.toJSON().children).toEqual(["0"]); -}); diff --git a/packages/react-oscillation/modules/motionState.js b/packages/react-oscillation/modules/motionState.js deleted file mode 100644 index fceb0b5..0000000 --- a/packages/react-oscillation/modules/motionState.js +++ /dev/null @@ -1,12 +0,0 @@ -export function serializeMotionState(state) { - let serialized = Object.create(state); - for (let key in state) { - let target = state[key]; - serialized[key] = isMotionConfig(target) ? target.value : target; - } - return serialized; -} - -export function isMotionConfig(target) { - return target != null && typeof target.update === "function"; -} diff --git a/packages/react-oscillation/modules/spring.js b/packages/react-oscillation/modules/spring.js deleted file mode 100644 index dc30ad7..0000000 --- a/packages/react-oscillation/modules/spring.js +++ /dev/null @@ -1,5 +0,0 @@ -import { spring, springs } from "oscillation"; - -// Re-exporting spring configs from the core library so the users -// won't need to bother remembering different endpoints to import from -export { spring, springs }; diff --git a/packages/react-oscillation/modules/useMotion.js b/packages/react-oscillation/modules/useMotion.js deleted file mode 100644 index 785f660..0000000 --- a/packages/react-oscillation/modules/useMotion.js +++ /dev/null @@ -1,21 +0,0 @@ -import { useState, useLayoutEffect } from "react"; -import { requestMotion, cancelMotion } from "oscillation"; -import { serializeMotionState } from "./motionState.js"; - -export function useMotion(factory, deps) { - // initial state should be a snapshot of any values that factory() returns - // this hook must synchronously return initial state - let v = useState(() => serializeMotionState(factory())); - - // layout effect is used to schedule necessary animations immediately when - // DOM elements are available - // this effect directly uses `deps` as the source of dependencies and - // intentionnaly omits tracking `state` since it will be up to date - // every time the effect needs to be restarted - useLayoutEffect(() => { - let motionId = requestMotion(v[0], factory(), v[1]); - return () => cancelMotion(motionId); - }, deps); - - return v[0]; -} diff --git a/packages/react-oscillation/modules/useMotionState.js b/packages/react-oscillation/modules/useMotionState.js deleted file mode 100644 index 934e8f8..0000000 --- a/packages/react-oscillation/modules/useMotionState.js +++ /dev/null @@ -1,34 +0,0 @@ -import { useState, useRef, useCallback } from "react"; -import { requestMotion, cancelMotion } from "oscillation"; -import { isMotionConfig } from "./motionState.js"; - -export function useMotionState(initialValue) { - let v = useState(initialValue); - - // ref for saving motion id so the setter can cancel current animaiton - // before starting a new one - let idRef = useRef(null); - - let setMotionState = useCallback((values) => { - cancelMotion(idRef.current); - - v[1]((state) => { - // in the same way as setState(), this setter can receive a callback to determine - // the next value based on the current one - let nextValues = typeof values === "function" ? values(state) : values; - - // if any of new values require motion, trigger animaitons and save the id - for (let key in nextValues) { - if (isMotionConfig(nextValues[key])) { - idRef.current = requestMotion(state, nextValues, v[1]); - return state; - } - } - - // otherwise, assume immediate state change - return nextValues; - }); - }, []); - - return [v[0], setMotionState]; -} diff --git a/packages/react-oscillation/package.json b/packages/react-oscillation/package.json deleted file mode 100644 index 84e2ea2..0000000 --- a/packages/react-oscillation/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "react-oscillation", - "version": "0.1.0", - "description": "An animation library", - "license": "ISC", - "author": "Alexey Raspopov", - "repository": "UnknownPrinciple/oscillation", - "main": "./react-oscillation.js", - "module": "./react-oscillation.js", - "exports": "./react-oscillation.js", - "type": "module", - "sideEffects": false, - "scripts": { - "test": "jest --config jest.config.js", - "build": "rollup --config rollup.config.js" - }, - "dependencies": { - "oscillation": "*" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0" - }, - "devDependencies": { - "react": "^18.2.0", - "react-test-renderer": "^18.2.0" - } -} diff --git a/packages/react-oscillation/rollup.config.js b/packages/react-oscillation/rollup.config.js deleted file mode 100644 index de4a1f5..0000000 --- a/packages/react-oscillation/rollup.config.js +++ /dev/null @@ -1,47 +0,0 @@ -import copy from "rollup-plugin-copy"; -import size from "rollup-plugin-bundle-size"; -import multi from "@rollup/plugin-multi-entry"; -import externals from "rollup-plugin-auto-external"; - -export default { - input: ["modules/*.js", "!modules/motionState.js"], - output: { file: "build/react-oscillation.js", format: "esm" }, - plugins: [ - externals(), - multi(), - size(), - copy({ - targets: [ - { src: ["../../LICENSE", "README.md"], dest: "build" }, - { src: ["package.json"], dest: "build", transform: generatePkg }, - ], - }), - ], -}; - -function generatePkg(contents) { - let pkg = JSON.parse(contents.toString()); - return JSON.stringify( - { - name: pkg.name, - version: pkg.version, - description: pkg.description, - license: pkg.license, - author: pkg.author, - homepage: pkg.homepage, - repository: pkg.repository, - main: pkg.main, - module: pkg.module, - exports: pkg.exports, - type: pkg.type, - types: pkg.types, - sideEffects: pkg.sideEffects, - files: pkg.files, - keywords: pkg.keywords, - dependencies: pkg.dependencies, - peerDependencies: pkg.peerDependencies, - }, - null, - 2, - ); -}