diff --git a/flow-typed/environments/web-animations.js b/flow-typed/environments/web-animations.js index ac05963..7182496 100644 --- a/flow-typed/environments/web-animations.js +++ b/flow-typed/environments/web-animations.js @@ -67,7 +67,9 @@ type DocumentTimelineOptions = {| |}; type EffectTiming = {| + delay: number, direction: PlaybackDirection, + duration: number | string, easing: string, fill: FillMode, iterations: number, @@ -81,14 +83,14 @@ type GetAnimationsOptions = {| type KeyframeAnimationOptions = {| ...KeyframeEffectOptions, - id: string, - timeline: AnimationTimeline | null, + id?: string, + timeline?: AnimationTimeline | null, |}; type KeyframeEffectOptions = {| - ...EffectTiming, - composite: CompositeOperation, - pseudoElement: string | null, + ...Partial, + composite?: CompositeOperation, + pseudoElement?: string | null, |}; type Keyframe = { diff --git a/package-lock.json b/package-lock.json index e06ebb8..1591040 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "eslint-plugin-promise": "^6.0.0", "eslint-plugin-react": "^7.33.1", "eslint-plugin-react-hooks": "6.1.0-canary-12bc60f5-20250613", - "flow-api-translator": "^0.25.0", + "flow-api-translator": "^0.32.1", "flow-bin": "^0.270.0", "glob": "^11.0.3", "hermes-eslint": "^0.32.0", @@ -9485,31 +9485,28 @@ "license": "MIT" }, "node_modules/@typescript-eslint/parser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", - "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", - "license": "BSD-2-Clause", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", + "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "7.2.0", - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/typescript-estree": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/project-service": { @@ -9549,16 +9546,17 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", - "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", + "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", + "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0" + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -9720,93 +9718,103 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "node_modules/@typescript-eslint/types": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", + "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", "dev": true, "license": "MIT", "engines": { - "node": ">=18.12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "peerDependencies": { - "typescript": ">=4.8.4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", - "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", + "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", + "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.38.0", + "@typescript-eslint/tsconfig-utils": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", - "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", - "license": "BSD-2-Clause", + "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", + "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", + "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "@typescript-eslint/tsconfig-utils": "^8.38.0", + "@typescript-eslint/types": "^8.38.0", + "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", + "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -9822,6 +9830,7 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -9830,15 +9839,6 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@typescript-eslint/utils": { "version": "8.44.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.0.tgz", @@ -9994,30 +9994,18 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/utils/node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", - "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", + "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", + "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.2.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "8.38.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -10025,12 +10013,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -15195,19 +15184,6 @@ "node": ">=10" } }, - "node_modules/eslint-config-next/node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, "node_modules/eslint-config-prettier": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", @@ -16890,97 +16866,41 @@ "license": "ISC" }, "node_modules/flow-api-translator": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/flow-api-translator/-/flow-api-translator-0.25.1.tgz", - "integrity": "sha512-PI2IXcDUdnEZ5yErIhai4Ipufw0GpnIm/mrNNlbz07l8Lkzr72l4ycbo9mZx98+SV+wl84QLn8Gpt4uKxrM/kQ==", + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/flow-api-translator/-/flow-api-translator-0.32.1.tgz", + "integrity": "sha512-TiPXLD2HLO2DKZt4HXQagK96UUJsq8/YJwoy3QEI+nJG1NgCkvg7AfdSx+Y0sUTOH8VVkkLgURQT2bahfervzw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.16.0", - "@typescript-eslint/parser": "7.2.0", - "@typescript-eslint/visitor-keys": "7.2.0", + "@typescript-eslint/parser": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "flow-enums-runtime": "^0.0.6", - "hermes-eslint": "0.25.1", - "hermes-estree": "0.25.1", - "hermes-parser": "0.25.1", - "hermes-transform": "0.25.1", + "hermes-eslint": "0.32.1", + "hermes-estree": "0.32.1", + "hermes-parser": "0.32.1", + "hermes-transform": "0.32.1", "typescript": "5.3.2" }, "peerDependencies": { "prettier": "^3.0.0 || ^2.7.1" } }, - "node_modules/flow-api-translator/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "node_modules/flow-api-translator/node_modules/hermes-estree": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.1.tgz", + "integrity": "sha512-ne5hkuDxheNBAikDjqvCZCwihnz0vVu9YsBzAEO1puiyFR4F1+PAz/SiPHSsNTuOveCYGRMX8Xbx4LOubeC0Qg==", + "dev": true, "license": "MIT" }, - "node_modules/flow-api-translator/node_modules/hermes-eslint": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-eslint/-/hermes-eslint-0.25.1.tgz", - "integrity": "sha512-nPz9+oyejT1zsIwoJ2pWdUvLcN1i+tbaWCOD8PpNBYQtnHXaPXImZp/6zZHnm3bo/DoFcAgh8+SNcxLFxh7m/A==", - "license": "MIT", - "dependencies": { - "esrecurse": "^4.3.0", - "hermes-estree": "0.25.1", - "hermes-parser": "0.25.1" - } - }, - "node_modules/flow-api-translator/node_modules/hermes-transform": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-transform/-/hermes-transform-0.25.1.tgz", - "integrity": "sha512-KSFRTAygJPclP7DMdQrmNrJaUn/h/tA7WSvP8USNK77L5ZSiyFv0019XcrVtlnYUoiEAp4591yD9L8s1d8/qqQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.16.0", - "esquery": "^1.4.0", - "flow-enums-runtime": "^0.0.6", - "hermes-eslint": "0.25.1", - "hermes-estree": "0.25.1", - "hermes-parser": "0.25.1", - "string-width": "4.2.3" - }, - "peerDependencies": { - "prettier": "^3.0.0 || ^2.7.1", - "prettier-plugin-hermes-parser": "0.25.1" - } - }, - "node_modules/flow-api-translator/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/flow-api-translator/node_modules/prettier-plugin-hermes-parser": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-hermes-parser/-/prettier-plugin-hermes-parser-0.25.1.tgz", - "integrity": "sha512-qVsgSt1ZLz7sxQyMmLM3b8JYIcUt4pkE+OCMEoUTe5G87ghNe9lluYMy7ptu1h0f3fAZ+zkifUV3JojMmQcKkg==", - "license": "MIT", - "peer": true, - "dependencies": { - "hermes-estree": "0.25.1", - "hermes-parser": "0.25.1", - "prettier-plugin-hermes-parser": "0.25.1" - }, - "peerDependencies": { - "prettier": "^3.0.0 || ^2.7.1" - } - }, - "node_modules/flow-api-translator/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/flow-api-translator/node_modules/hermes-parser": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.1.tgz", + "integrity": "sha512-175dz634X/W5AiwrpLdoMl/MOb17poLHyIqgyExlE8D9zQ1OPnoORnGMB5ltRKnpvQzBjMYvT2rN/sHeIfZW5Q==", + "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" + "hermes-estree": "0.32.1" } }, "node_modules/flow-bin": { @@ -18177,32 +18097,32 @@ } }, "node_modules/hermes-eslint": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-eslint/-/hermes-eslint-0.32.0.tgz", - "integrity": "sha512-f/gnFD3Nl7QNrclG6otkHnHsUbwYrJGO76AMtoDeIYs2+i7fFgqJgSg7DKwejTtAKBoXQg51hAQuo9cgcp1R1w==", + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/hermes-eslint/-/hermes-eslint-0.32.1.tgz", + "integrity": "sha512-3ljktN2ek+bRRsPAcMeqMEJou6s2MRe6VuLkLsXDXuVrJfRZ7V2VUw41T9uAt9lcA2xaJP4yykYAnMg15nsRPw==", "dev": true, "license": "MIT", "dependencies": { "esrecurse": "^4.3.0", - "hermes-estree": "0.32.0", - "hermes-parser": "0.32.0" + "hermes-estree": "0.32.1", + "hermes-parser": "0.32.1" } }, "node_modules/hermes-eslint/node_modules/hermes-estree": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", - "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.1.tgz", + "integrity": "sha512-ne5hkuDxheNBAikDjqvCZCwihnz0vVu9YsBzAEO1puiyFR4F1+PAz/SiPHSsNTuOveCYGRMX8Xbx4LOubeC0Qg==", "dev": true, "license": "MIT" }, "node_modules/hermes-eslint/node_modules/hermes-parser": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", - "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.1.tgz", + "integrity": "sha512-175dz634X/W5AiwrpLdoMl/MOb17poLHyIqgyExlE8D9zQ1OPnoORnGMB5ltRKnpvQzBjMYvT2rN/sHeIfZW5Q==", "dev": true, "license": "MIT", "dependencies": { - "hermes-estree": "0.32.0" + "hermes-estree": "0.32.1" } }, "node_modules/hermes-estree": { @@ -18220,6 +18140,80 @@ "hermes-estree": "0.25.1" } }, + "node_modules/hermes-transform": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/hermes-transform/-/hermes-transform-0.32.1.tgz", + "integrity": "sha512-SLywdP4yDrdj2CEGDlp2NeQZ1Xkk1zy3gNJcSGjSocppfkmB7dlhxqEmTV9fcOBDMdGj+80SqVTCwaIGLiUXBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "esquery": "^1.4.0", + "flow-enums-runtime": "^0.0.6", + "hermes-eslint": "0.32.1", + "hermes-estree": "0.32.1", + "hermes-parser": "0.32.1", + "string-width": "4.2.3" + }, + "peerDependencies": { + "prettier": "^3.0.0", + "prettier-plugin-hermes-parser": "*" + }, + "peerDependenciesMeta": { + "prettier-plugin-hermes-parser": { + "optional": true + } + } + }, + "node_modules/hermes-transform/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-transform/node_modules/hermes-estree": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.1.tgz", + "integrity": "sha512-ne5hkuDxheNBAikDjqvCZCwihnz0vVu9YsBzAEO1puiyFR4F1+PAz/SiPHSsNTuOveCYGRMX8Xbx4LOubeC0Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-transform/node_modules/hermes-parser": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.1.tgz", + "integrity": "sha512-175dz634X/W5AiwrpLdoMl/MOb17poLHyIqgyExlE8D9zQ1OPnoORnGMB5ltRKnpvQzBjMYvT2rN/sHeIfZW5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.32.1" + } + }, + "node_modules/hermes-transform/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hermes-transform/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/history": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", @@ -28757,6 +28751,10 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-strict-animated": { + "resolved": "packages/react-strict-animated", + "link": true + }, "node_modules/react-strict-dom": { "resolved": "packages/react-strict-dom", "link": true @@ -32308,15 +32306,16 @@ } }, "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-interface-checker": { @@ -34184,6 +34183,27 @@ "url": "https://github.com/sponsors/wooorm" } }, + "packages/animated": { + "name": "react-strict-animated", + "version": "0.0.1", + "extraneous": true, + "dependencies": { + "react-strict-dom": "*" + }, + "devDependencies": { + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^26.0.1", + "@rollup/plugin-node-resolve": "^15.2.3", + "react": "~19.0.0", + "react-dom": "~19.0.0", + "rollup": "^4.22.4" + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-native": ">=0.79.5" + } + }, "packages/benchmarks": { "version": "0.0.54", "license": "MIT", @@ -34269,6 +34289,25 @@ "node": ">=10.13.0" } }, + "packages/react-strict-animated": { + "version": "0.0.1", + "dependencies": { + "react-strict-dom": "*" + }, + "devDependencies": { + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^26.0.1", + "@rollup/plugin-node-resolve": "^15.2.3", + "react": "~19.0.0", + "react-dom": "~19.0.0", + "rollup": "^4.22.4" + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-native": ">=0.79.5" + } + }, "packages/react-strict-dom": { "version": "0.0.54", "license": "MIT", @@ -34339,6 +34378,292 @@ "generate-types": "generate-types.js" } }, + "packages/scripts/node_modules/@typescript-eslint/parser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", + "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "packages/scripts/node_modules/@typescript-eslint/scope-manager": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", + "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "packages/scripts/node_modules/@typescript-eslint/types": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "packages/scripts/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "packages/scripts/node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "packages/scripts/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "packages/scripts/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "packages/scripts/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "packages/scripts/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/scripts/node_modules/flow-api-translator": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/flow-api-translator/-/flow-api-translator-0.25.1.tgz", + "integrity": "sha512-PI2IXcDUdnEZ5yErIhai4Ipufw0GpnIm/mrNNlbz07l8Lkzr72l4ycbo9mZx98+SV+wl84QLn8Gpt4uKxrM/kQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "@typescript-eslint/parser": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "flow-enums-runtime": "^0.0.6", + "hermes-eslint": "0.25.1", + "hermes-estree": "0.25.1", + "hermes-parser": "0.25.1", + "hermes-transform": "0.25.1", + "typescript": "5.3.2" + }, + "peerDependencies": { + "prettier": "^3.0.0 || ^2.7.1" + } + }, + "packages/scripts/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/scripts/node_modules/hermes-eslint": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-eslint/-/hermes-eslint-0.25.1.tgz", + "integrity": "sha512-nPz9+oyejT1zsIwoJ2pWdUvLcN1i+tbaWCOD8PpNBYQtnHXaPXImZp/6zZHnm3bo/DoFcAgh8+SNcxLFxh7m/A==", + "license": "MIT", + "dependencies": { + "esrecurse": "^4.3.0", + "hermes-estree": "0.25.1", + "hermes-parser": "0.25.1" + } + }, + "packages/scripts/node_modules/hermes-transform": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-transform/-/hermes-transform-0.25.1.tgz", + "integrity": "sha512-KSFRTAygJPclP7DMdQrmNrJaUn/h/tA7WSvP8USNK77L5ZSiyFv0019XcrVtlnYUoiEAp4591yD9L8s1d8/qqQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "esquery": "^1.4.0", + "flow-enums-runtime": "^0.0.6", + "hermes-eslint": "0.25.1", + "hermes-estree": "0.25.1", + "hermes-parser": "0.25.1", + "string-width": "4.2.3" + }, + "peerDependencies": { + "prettier": "^3.0.0 || ^2.7.1", + "prettier-plugin-hermes-parser": "0.25.1" + } + }, + "packages/scripts/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/scripts/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/scripts/node_modules/prettier-plugin-hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-hermes-parser/-/prettier-plugin-hermes-parser-0.25.1.tgz", + "integrity": "sha512-qVsgSt1ZLz7sxQyMmLM3b8JYIcUt4pkE+OCMEoUTe5G87ghNe9lluYMy7ptu1h0f3fAZ+zkifUV3JojMmQcKkg==", + "license": "MIT", + "peer": true, + "dependencies": { + "hermes-estree": "0.25.1", + "hermes-parser": "0.25.1", + "prettier-plugin-hermes-parser": "0.25.1" + }, + "peerDependencies": { + "prettier": "^3.0.0 || ^2.7.1" + } + }, + "packages/scripts/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "packages/scripts/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "packages/scripts/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "packages/website": { "version": "0.0.54", "dependencies": { diff --git a/package.json b/package.json index 3efc1c2..2681e5c 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "eslint-plugin-promise": "^6.0.0", "eslint-plugin-react": "^7.33.1", "eslint-plugin-react-hooks": "6.1.0-canary-12bc60f5-20250613", - "flow-api-translator": "^0.25.0", + "flow-api-translator": "^0.32.1", "flow-bin": "^0.270.0", "glob": "^11.0.3", "hermes-eslint": "^0.32.0", diff --git a/packages/benchmarks/size/run.js b/packages/benchmarks/size/run.js index b288105..03bf456 100644 --- a/packages/benchmarks/size/run.js +++ b/packages/benchmarks/size/run.js @@ -26,7 +26,9 @@ const outfile = argv.outfile; const files = [ path.join(__dirname, '../../react-strict-dom/dist/web/index.js'), path.join(__dirname, '../../react-strict-dom/dist/web/runtime.js'), - path.join(__dirname, '../../react-strict-dom/dist/native/index.js') + path.join(__dirname, '../../react-strict-dom/dist/native/index.js'), + path.join(__dirname, '../../react-strict-animated/dist/web/index.js'), + path.join(__dirname, '../../react-strict-animated/dist/native/index.js') ]; console.log('Running benchmark-size, please wait...'); diff --git a/packages/react-strict-animated/LICENSE b/packages/react-strict-animated/LICENSE new file mode 100644 index 0000000..b93be90 --- /dev/null +++ b/packages/react-strict-animated/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/react-strict-animated/package.json b/packages/react-strict-animated/package.json new file mode 100644 index 0000000..46cc0ac --- /dev/null +++ b/packages/react-strict-animated/package.json @@ -0,0 +1,45 @@ +{ + "name": "react-strict-animated", + "version": "0.0.1", + "description": "A subset of React Native's Animated API supported on web and native.", + "exports": { + ".": { + "react-native": { + "types": "./dist/native/index.d.ts", + "default": "./dist/native/index.js" + }, + "default": { + "types": "./dist/web/index.d.ts", + "default": "./dist/web/index.js" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "dist/*", + "LICENSE", + "package.json" + ], + "sideEffects": false, + "scripts": { + "build": "rollup --config ./tools/rollup.config.mjs", + "clean": "del-cli \"./dist/*\"", + "prebuild": "npm run clean && generate-types -i src/ -o dist" + }, + "dependencies": { + "react-strict-dom": "*" + }, + "devDependencies": { + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^26.0.1", + "@rollup/plugin-node-resolve": "^15.2.3", + "react": "~19.0.0", + "react-dom": "~19.0.0", + "rollup": "^4.22.4" + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-native": ">=0.79.5" + } +} \ No newline at end of file diff --git a/packages/react-strict-animated/src/native/Animation.js b/packages/react-strict-animated/src/native/Animation.js new file mode 100644 index 0000000..afce02e --- /dev/null +++ b/packages/react-strict-animated/src/native/Animation.js @@ -0,0 +1,59 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import type { + InterpolationConfig, + ParallelConfig, + SpringAnimationConfig, + TimingAnimationConfig +} from '../shared/SharedAnimatedTypes'; +import type { CompositeAnimation } from 'react-native/Libraries/Animated/Animated'; +import { Animated, useAnimatedValue } from 'react-native'; + +export function useValue(initialValue: number): Animated.Value { + return useAnimatedValue(initialValue, { useNativeDriver: true }); +} + +export function parallel( + animations: Array, + config?: ParallelConfig +): CompositeAnimation { + return Animated.parallel(animations, config); +} + +export function sequence( + animations: Array +): CompositeAnimation { + return Animated.sequence(animations); +} + +export function delay(time: number): CompositeAnimation { + return Animated.delay(time); +} + +export function timing( + value: Animated.Value, + config: TimingAnimationConfig +): CompositeAnimation { + return Animated.timing(value, { ...config, useNativeDriver: true }); +} + +export function spring( + value: Animated.Value, + config: SpringAnimationConfig +): CompositeAnimation { + return Animated.spring(value, { ...config, useNativeDriver: true }); +} + +export function interpolate( + value: Animated.Node, + config: InterpolationConfig +): Animated.Node { + return new Animated.Interpolation(value, config); +} diff --git a/packages/react-strict-animated/src/native/animated.js b/packages/react-strict-animated/src/native/animated.js new file mode 100644 index 0000000..762968b --- /dev/null +++ b/packages/react-strict-animated/src/native/animated.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import { AnimatedDiv, AnimatedImg, AnimatedSpan } from './components'; + +export const div = AnimatedDiv; +export const span = AnimatedSpan; +export const img = AnimatedImg; diff --git a/packages/react-strict-animated/src/native/components.js b/packages/react-strict-animated/src/native/components.js new file mode 100644 index 0000000..87fb611 --- /dev/null +++ b/packages/react-strict-animated/src/native/components.js @@ -0,0 +1,108 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +/* eslint-disable no-unreachable */ + +import type { AnimatedStyleValue } from '../shared/SharedAnimatedTypes'; +import type { Text, View } from 'react-native'; +import type { ImageProps } from 'react-native/Libraries/Image/ImageProps'; +import type { html } from 'react-strict-dom'; + +import * as React from 'react'; +import { Animated } from 'react-native'; + +import { compat, css } from 'react-strict-dom'; + +const styles = css.create({ + defaults: { + boxSizing: 'content-box', + position: 'static' + } +}); + +export component AnimatedDiv( + animatedStyle?: AnimatedStyleValue, + children?: React.Node, + ref?: React.RefSetter>, + ...htmlProps: Omit, 'children'> +) { + return ( + // $FlowFixMe[prop-missing] - RSD missing ref type on compat.native API + + {(nativeProps: React.PropsOf) => { + return ( + + {nativeProps.children} + + ); + }} + + ); +} + +export component AnimatedSpan( + animatedStyle: AnimatedStyleValue, + children?: React.Node, + ref?: React.RefSetter>, + ...htmlProps: Omit, 'children'> +) { + return ( + // $FlowFixMe[prop-missing] - RSD missing ref type on compat.native API + + {(nativeProps: React.PropsOf) => { + return ( + + {children} + + ); + }} + + ); +} + +export component AnimatedImg( + animatedStyle: AnimatedStyleValue, + ref?: React.RefSetter>, + ...htmlProps: React.PropsOf +) { + return ( + // $FlowFixMe[prop-missing] - RSD missing ref type on compat.native API + + {(nativeProps: ImageProps) => { + return ( + + ); + }} + + ); +} diff --git a/packages/react-strict-animated/src/native/index.js b/packages/react-strict-animated/src/native/index.js new file mode 100644 index 0000000..6f65c25 --- /dev/null +++ b/packages/react-strict-animated/src/native/index.js @@ -0,0 +1,30 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +/* eslint-disable no-unreachable */ + +import type { + InterpolationConfig, + SpringAnimationConfig, + TimingAnimationConfig +} from '../shared/SharedAnimatedTypes'; +import type { CompositeAnimation } from 'react-native/Libraries/Animated/Animated'; + +import { Animated } from 'react-native'; + +export type AnimatedValue = Animated.Value; +export type { + CompositeAnimation, + SpringAnimationConfig, + TimingAnimationConfig, + InterpolationConfig +}; + +export * as animated from './animated'; +export * as Animation from './Animation'; diff --git a/packages/react-strict-animated/src/shared/SharedAnimatedTypes.js b/packages/react-strict-animated/src/shared/SharedAnimatedTypes.js new file mode 100644 index 0000000..93b6dbe --- /dev/null +++ b/packages/react-strict-animated/src/shared/SharedAnimatedTypes.js @@ -0,0 +1,65 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +export type TimingAnimationConfig = { + delay?: number, + duration?: number, + easing?: (value: number) => number, + toValue: number +}; + +export type AnimatedTransformValue = + | $ReadOnly<{ perspective: number | TAnimatedNode }> + | $ReadOnly<{ rotate: string | TAnimatedNode }> + | $ReadOnly<{ rotateX: string | TAnimatedNode }> + | $ReadOnly<{ rotateY: string | TAnimatedNode }> + | $ReadOnly<{ rotateZ: string | TAnimatedNode }> + | $ReadOnly<{ scale: number | TAnimatedNode }> + | $ReadOnly<{ scaleX: number | TAnimatedNode }> + | $ReadOnly<{ scaleY: number | TAnimatedNode }> + | $ReadOnly<{ translateX: number | string | TAnimatedNode }> + | $ReadOnly<{ translateY: number | string | TAnimatedNode }> + | $ReadOnly<{ skewX: string | TAnimatedNode }> + | $ReadOnly<{ skewY: string | TAnimatedNode }>; + +export type AnimatedStyleValue = $ReadOnly<{ + opacity?: number | TAnimatedNode, + transform?: $ReadOnlyArray> +}>; + +export type ParallelConfig = { + stopTogether?: boolean +}; + +export type SpringAnimationConfig = { + bounciness?: number, + damping?: number, + delay?: number, + friction?: number, + mass?: number, + overshootClamping?: boolean, + restDisplacementThreshold?: number, + restSpeedThreshold?: number, + speed?: number, + stiffness?: number, + tension?: number, + toValue: number, + velocity?: number +}; + +type ExtrapolateType = 'extend' | 'identity' | 'clamp'; + +export type InterpolationConfig = { + easing?: (input: number) => number, + extrapolate?: ExtrapolateType, + extrapolateLeft?: ExtrapolateType, + extrapolateRight?: ExtrapolateType, + inputRange: Array, + outputRange: Array +}; diff --git a/packages/react-strict-animated/src/web/Animation.js b/packages/react-strict-animated/src/web/Animation.js new file mode 100644 index 0000000..bfb589f --- /dev/null +++ b/packages/react-strict-animated/src/web/Animation.js @@ -0,0 +1,80 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import type { + InterpolationConfig, + SpringAnimationConfig, + TimingAnimationConfig +} from '../shared/SharedAnimatedTypes'; +import type AnimatedValue from './nodes/AnimatedValue'; +import type AnimatedWithChildren from './nodes/AnimatedWithChildren'; +import type { + AnimatedNodeType, + CompositeAnimation, + EndCallback +} from './types/AnimatedTypes'; + +import Delay from './animations/Delay'; +import ParallelAnimation from './animations/ParallelAnimation'; +import SequenceAnimation from './animations/SequenceAnimation'; +import SpringAnimation from './animations/SpringAnimation'; +import TimingAnimation from './animations/TimingAnimation'; +import useAnimatedValue from './hooks/useAnimatedValue'; +import { Interpolate } from './nodes/AnimatedInterpolation'; + +export const useValue = useAnimatedValue; + +export const parallel = ParallelAnimation; +export const sequence = SequenceAnimation; +export const delay = Delay; + +export function timing( + value: AnimatedValue, + config: TimingAnimationConfig +): CompositeAnimation { + return { + reset() { + value.resetAnimation(); + }, + start(callback?: EndCallback) { + value.animate(new TimingAnimation(config), callback); + }, + stop() { + value.stopAnimation(); + } + }; +} + +export function spring( + value: AnimatedValue, + config: SpringAnimationConfig +): CompositeAnimation { + return { + reset() { + value.resetAnimation(); + }, + start(callback?: EndCallback) { + value.animate(new SpringAnimation(config), callback); + }, + stop() { + value.stopAnimation(); + } + }; +} + +export function interpolate( + value: AnimatedNodeType, + config: InterpolationConfig +): AnimatedNodeType { + return Interpolate( + // $FlowFixMe[incompatible-cast] - need a sketchy cast to be compatible with RN version of Animated + value as AnimatedWithChildren, + config + ); +} diff --git a/packages/react-strict-animated/src/web/animated.js b/packages/react-strict-animated/src/web/animated.js new file mode 100644 index 0000000..762968b --- /dev/null +++ b/packages/react-strict-animated/src/web/animated.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import { AnimatedDiv, AnimatedImg, AnimatedSpan } from './components'; + +export const div = AnimatedDiv; +export const span = AnimatedSpan; +export const img = AnimatedImg; diff --git a/packages/react-strict-animated/src/web/animations/Animation.js b/packages/react-strict-animated/src/web/animations/Animation.js new file mode 100644 index 0000000..f929a0f --- /dev/null +++ b/packages/react-strict-animated/src/web/animations/Animation.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +export type KeyframeObject = Keyframe; +export type KeyframeMap = Map>; +export type GenerateResult = { + config: Partial, + keyframeMap: KeyframeMap +}; + +export interface AnimatedAnimation { + generate( + fromValue: number, + onUpdate: (value: number, keyframeMap: KeyframeMap) => void + ): GenerateResult; + getDuration(): number; + getTimeline(): Array; +} diff --git a/packages/react-strict-animated/src/web/animations/Delay.js b/packages/react-strict-animated/src/web/animations/Delay.js new file mode 100644 index 0000000..87e5248 --- /dev/null +++ b/packages/react-strict-animated/src/web/animations/Delay.js @@ -0,0 +1,42 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import type { CompositeAnimation, EndCallback } from '../types/AnimatedTypes'; + +export default function delay(time: number): CompositeAnimation { + let timeoutID: TimeoutID | null = null; + let cb: EndCallback | null = null; + + const stopDelay = () => { + if (timeoutID !== null) { + window.clearTimeout(timeoutID); + timeoutID = null; + } + cb?.({ finished: false }); + cb = null; + }; + + return { + reset() { + stopDelay(); + }, + start(callback?: EndCallback) { + if (callback != null) { + cb = callback; + } + timeoutID = window.setTimeout(() => { + timeoutID = null; + cb?.({ finished: true }); + }, time); + }, + stop() { + stopDelay(); + } + }; +} diff --git a/packages/react-strict-animated/src/web/animations/ParallelAnimation.js b/packages/react-strict-animated/src/web/animations/ParallelAnimation.js new file mode 100644 index 0000000..19da6ee --- /dev/null +++ b/packages/react-strict-animated/src/web/animations/ParallelAnimation.js @@ -0,0 +1,64 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import type { ParallelConfig } from '../../shared/SharedAnimatedTypes'; +import type { CompositeAnimation, EndCallback } from '../types/AnimatedTypes'; + +export default function ParallelAnimation( + animations: Array, + config?: ParallelConfig +): CompositeAnimation { + let doneCount = 0; + // Make sure we only call stop() at most once for each animation + const hasEnded: { [number]: boolean } = {}; + const stopTogether = config?.stopTogether ?? false; + + const result = { + reset() { + animations.forEach((animation, idx) => { + animation.reset(); + hasEnded[idx] = false; + }); + doneCount = 0; + }, + start(callback?: EndCallback) { + if (doneCount === animations.length) { + callback?.({ finished: true }); + return; + } + animations.forEach((animation, idx) => { + const cb = (endResult: { finished: boolean, ... }) => { + hasEnded[idx] = true; + doneCount++; + if (doneCount === animations.length) { + doneCount = 0; + callback?.(endResult); + return; + } + if (!endResult.finished && stopTogether) { + result.stop(); + } + }; + if (!animation) { + cb({ finished: true }); + } else { + animation.start(cb); + } + }); + }, + stop() { + animations.forEach((animation, idx) => { + !hasEnded[idx] && animation.stop(); + hasEnded[idx] = true; + }); + } + }; + + return result; +} diff --git a/packages/react-strict-animated/src/web/animations/SequenceAnimation.js b/packages/react-strict-animated/src/web/animations/SequenceAnimation.js new file mode 100644 index 0000000..fe86751 --- /dev/null +++ b/packages/react-strict-animated/src/web/animations/SequenceAnimation.js @@ -0,0 +1,57 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import type { + CompositeAnimation, + EndCallback, + EndResult +} from '../types/AnimatedTypes'; + +export default function SequenceAnimation( + animations: Array +): CompositeAnimation { + let current = 0; + return { + reset() { + animations.forEach((animation, idx) => { + if (idx <= current) { + animation.reset(); + } + }); + current = 0; + }, + start(callback?: EndCallback) { + const onComplete = (result: EndResult) => { + if (!result.finished) { + callback?.(result); + return; + } + + current++; + if (current === animations.length) { + callback?.(result); + return; + } + + animations[current].start(onComplete); + }; + + if (animations.length === 0) { + callback?.({ finished: true }); + } else { + animations[current].start(onComplete); + } + }, + stop() { + if (current < animations.length) { + animations[current].stop(); + } + } + }; +} diff --git a/packages/react-strict-animated/src/web/animations/SpringAnimation.js b/packages/react-strict-animated/src/web/animations/SpringAnimation.js new file mode 100644 index 0000000..37ecf8b --- /dev/null +++ b/packages/react-strict-animated/src/web/animations/SpringAnimation.js @@ -0,0 +1,230 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import type { SpringAnimationConfig } from '../../shared/SharedAnimatedTypes'; +import type { + AnimatedAnimation, + GenerateResult, + KeyframeMap +} from './Animation'; + +import { TIMESTEP_COEFFICIENT } from '../utils/constants'; +import * as SpringConfig from '../utils/SpringConfig'; + +export default class SpringAnimation implements AnimatedAnimation { + #overshootClamping: boolean; + #restDisplacementThreshold: number; + #restSpeedThreshold: number; + + #toValue: number; + #delay: number; + #fromValue: number; + + #damping: number; + #mass: number; + #stiffness: number; + #initialVelocity: number; + + #timeline: Array; + #duration: number; + + constructor(config: SpringAnimationConfig) { + this.#overshootClamping = config.overshootClamping ?? false; + this.#restDisplacementThreshold = config.restDisplacementThreshold ?? 0.001; + this.#restSpeedThreshold = config.restSpeedThreshold ?? 0.001; + this.#initialVelocity = config.velocity ?? 0; + + this.#fromValue = 0; + this.#toValue = config.toValue; + this.#delay = config.delay ?? 0; + + if ( + config.stiffness !== undefined || + config.damping !== undefined || + config.mass !== undefined + ) { + if ( + !( + config.bounciness === undefined && + config.speed === undefined && + config.tension === undefined && + config.friction === undefined + ) + ) { + throw new Error( + 'You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one' + ); + } + this.#stiffness = config.stiffness ?? 100; + this.#damping = config.damping ?? 10; + this.#mass = config.mass ?? 1; + } else if (config.bounciness !== undefined || config.speed !== undefined) { + // Convert the origami bounciness/speed values to stiffness/damping + // We assume mass is 1. + if ( + !( + config.tension === undefined && + config.friction === undefined && + config.stiffness === undefined && + config.damping === undefined && + config.mass === undefined + ) + ) { + throw new Error( + 'You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one' + ); + } + const springConfig = SpringConfig.fromBouncinessAndSpeed( + config.bounciness ?? 8, + config.speed ?? 12 + ); + this.#stiffness = springConfig.stiffness; + this.#damping = springConfig.damping; + this.#mass = 1; + } else { + // Convert the origami tension/friction values to stiffness/damping + // We assume mass is 1. + const springConfig = SpringConfig.fromOrigamiTensionAndFriction( + config.tension ?? 40, + config.friction ?? 7 + ); + this.#stiffness = springConfig.stiffness; + this.#damping = springConfig.damping; + this.#mass = 1; + } + + this.#timeline = []; + this.#duration = 0; + + if (this.#stiffness <= 0) { + throw new Error('Stiffness value must be greater than 0'); + } + if (this.#damping <= 0) { + throw new Error('Damping value must be greater than 0'); + } + if (this.#mass <= 0) { + throw new Error('Damping value must be greater than 0'); + } + } + + getDuration(): number { + return this.#duration; + } + + getTimeline(): Array { + return this.#timeline; + } + + generate( + fromValue: number, + onUpdate: (value: number, keyframeMap: KeyframeMap) => void + ): GenerateResult { + const keyframeMap: KeyframeMap = new Map(); + + this.#fromValue = fromValue; + + let elapsedTime = 0; + let finished = false; + while (!finished) { + const [nextValue, isFinished] = this.#sampleSpring(elapsedTime); + this.#timeline.push(nextValue); + onUpdate(nextValue, keyframeMap); + if (!isFinished) { + elapsedTime += TIMESTEP_COEFFICIENT; + } + finished = isFinished; + } + + this.#duration = elapsedTime; + + const config: Partial = { + delay: this.#delay, + direction: 'normal', + duration: this.#duration, + easing: 'linear', + fill: 'backwards', + iterations: 1, + iterationStart: 0 + }; + + return { config, keyframeMap }; + } + + #sampleSpring(elapsedTime: number): [number, boolean] { + // spring operates on a "seconds" scale while the elapasedTime is in + // "milliseconds" + const t = elapsedTime / 1000; + + const c = this.#damping; + const m = this.#mass; + const k = this.#stiffness; + const v0 = this.#initialVelocity; + + const zeta = c / (2 * Math.sqrt(k * m)); // damping ratio + const omega0 = Math.sqrt(k / m); // undamped angular frequency of the oscillator (rad/ms) + const omega1 = omega0 * Math.sqrt(1.0 - zeta * zeta); // exponential decay + const x0 = this.#toValue - this.#fromValue; // calculate the oscillation from x0 = 1 to x = 0 + + let position = 0; + let velocity = 0; + if (zeta < 1) { + // Under damped + const envelope = Math.exp(-zeta * omega0 * t); + position = + this.#toValue - + envelope * + (((v0 + zeta * omega0 * x0) / omega1) * Math.sin(omega1 * t) + + x0 * Math.cos(omega1 * t)); + // This looks crazy -- it's actually just the derivative of the + // oscillation function + velocity = + zeta * + omega0 * + envelope * + ((Math.sin(omega1 * t) * (v0 + zeta * omega0 * x0)) / omega1 + + x0 * Math.cos(omega1 * t)) - + envelope * + (Math.cos(omega1 * t) * (v0 + zeta * omega0 * x0) - + omega1 * x0 * Math.sin(omega1 * t)); + } else { + // Critically damped + const envelope = Math.exp(-omega0 * t); + position = this.#toValue - envelope * (x0 + (v0 + omega0 * x0) * t); + velocity = + envelope * (v0 * (t * omega0 - 1) + t * x0 * (omega0 * omega0)); + } + + // Conditions for stopping the spring animation + let finished = false; + let isOvershooting = false; + if (this.#overshootClamping && this.#stiffness !== 0) { + if (this.#fromValue < this.#toValue) { + isOvershooting = position > this.#toValue; + } else { + isOvershooting = position < this.#toValue; + } + } + const isVelocity = Math.abs(velocity) <= this.#restSpeedThreshold; + let isDisplacement = true; + if (this.#stiffness !== 0) { + isDisplacement = + Math.abs(this.#toValue - position) <= this.#restDisplacementThreshold; + } + + if (isOvershooting || (isVelocity && isDisplacement)) { + if (this.#stiffness !== 0) { + // Ensure that we end up with a round value + position = this.#toValue; + } + finished = true; + } + + return [position, finished]; + } +} diff --git a/packages/react-strict-animated/src/web/animations/TimingAnimation.js b/packages/react-strict-animated/src/web/animations/TimingAnimation.js new file mode 100644 index 0000000..a76ad55 --- /dev/null +++ b/packages/react-strict-animated/src/web/animations/TimingAnimation.js @@ -0,0 +1,76 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import type { TimingAnimationConfig } from '../../shared/SharedAnimatedTypes'; +import type { + AnimatedAnimation, + GenerateResult, + KeyframeMap +} from './Animation'; + +import { TIMESTEP_COEFFICIENT } from '../utils/constants'; + +export default class TimingAnimation implements AnimatedAnimation { + #toValue: number; + #duration: number; + #delay: number; + #easing: (t: number) => number; + #timeline: Array; + + constructor(config: TimingAnimationConfig) { + this.#toValue = config.toValue; + this.#easing = config.easing ?? ((t) => t); + this.#duration = config.duration ?? 500; + this.#delay = config.delay ?? 0; + + this.#timeline = []; + } + + getTimeline(): Array { + return this.#timeline; + } + + getDuration(): number { + return this.#duration; + } + + generate( + fromValue: number, + onUpdate: (value: number, keyframeMap: KeyframeMap) => void + ): GenerateResult { + const keyframeMap: KeyframeMap = new Map(); + + const numSteps = this.#duration / TIMESTEP_COEFFICIENT; + const timestep = 1 / numSteps; + + let currentTime = 0; + for (let i = 0; i < numSteps; i++) { + const nextValue = + fromValue + this.#easing(currentTime) * (this.#toValue - fromValue); + this.#timeline.push(nextValue); + onUpdate(nextValue, keyframeMap); + currentTime += timestep; + } + + // call update callback on "last" value + onUpdate(this.#toValue, keyframeMap); + + const config: Partial = { + delay: this.#delay, + direction: 'normal', + duration: this.#duration, + easing: 'linear', + fill: 'backwards', + iterations: 1, + iterationStart: 0 + }; + + return { config, keyframeMap }; + } +} diff --git a/packages/react-strict-animated/src/web/components.js b/packages/react-strict-animated/src/web/components.js new file mode 100644 index 0000000..c99106e --- /dev/null +++ b/packages/react-strict-animated/src/web/components.js @@ -0,0 +1,85 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +/* eslint-disable no-unreachable */ + +import type { AnimatedStyleValue } from '../shared/SharedAnimatedTypes'; + +import * as React from 'react'; +import { html, css } from 'react-strict-dom'; +import useAnimatedStyle from './useAnimatedStyle'; +import type { AnimatedNodeType } from './types/AnimatedTypes'; + +const styles = css.create({ + animatedOpacity: (opacity: string) => ({ opacity }), + animatedTransform: (transform: string) => ({ transform }) +}); + +export component AnimatedDiv( + animatedStyle?: AnimatedStyleValue, + ref?: React.RefSetter>, + style as incomingStyle?: React.PropsOf['style'], + ...restProps: Omit, 'style'> +) { + const [{ opacity, transform }, animatedRefSetter] = + useAnimatedStyle(animatedStyle, ref); + return ( + + ); +} + +export component AnimatedSpan( + animatedStyle?: AnimatedStyleValue, + ref?: React.RefSetter>, + style as incomingStyle?: React.PropsOf['style'], + ...restProps: Omit, 'style'> +) { + const [{ opacity, transform }, animatedRefSetter] = + useAnimatedStyle(animatedStyle, ref); + return ( + + ); +} + +export component AnimatedImg( + animatedStyle?: AnimatedStyleValue, + ref?: React.RefSetter>, + style as incomingStyle?: React.PropsOf['style'], + ...restProps: Omit, 'style'> +) { + const [{ opacity, transform }, animatedRefSetter] = + useAnimatedStyle(animatedStyle, ref); + return ( + + ); +} diff --git a/packages/react-strict-animated/src/web/hooks/useAnimatedValue.js b/packages/react-strict-animated/src/web/hooks/useAnimatedValue.js new file mode 100644 index 0000000..d75cc81 --- /dev/null +++ b/packages/react-strict-animated/src/web/hooks/useAnimatedValue.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import AnimatedValue from '../nodes/AnimatedValue'; + +import { useState } from 'react'; + +export default function useAnimatedValue(initialValue: number): AnimatedValue { + return useState(() => new AnimatedValue(initialValue))[0]; +} diff --git a/packages/react-strict-animated/src/web/index.js b/packages/react-strict-animated/src/web/index.js new file mode 100644 index 0000000..b996eb5 --- /dev/null +++ b/packages/react-strict-animated/src/web/index.js @@ -0,0 +1,27 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import type AnimatedValue from './nodes/AnimatedValue'; +import type { CompositeAnimation } from './types/AnimatedTypes'; +import type { + InterpolationConfig, + SpringAnimationConfig, + TimingAnimationConfig +} from '../shared/SharedAnimatedTypes'; + +export type { + AnimatedValue, + CompositeAnimation, + SpringAnimationConfig, + TimingAnimationConfig, + InterpolationConfig +}; + +export * as animated from './animated'; +export * as Animation from './Animation'; diff --git a/packages/react-strict-animated/src/web/nodes/AnimatedInterpolation.js b/packages/react-strict-animated/src/web/nodes/AnimatedInterpolation.js new file mode 100644 index 0000000..8c0b805 --- /dev/null +++ b/packages/react-strict-animated/src/web/nodes/AnimatedInterpolation.js @@ -0,0 +1,298 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import type { InterpolationConfig } from '../../shared/SharedAnimatedTypes'; + +import AnimatedWithChildren from './AnimatedWithChildren'; + +import normalizeColor from '../utils/normalizeColor'; + +type ExtrapolateType = 'extend' | 'identity' | 'clamp'; + +function findRange(input: number, inputRange: $ReadOnlyArray) { + let i; + for (i = 1; i < inputRange.length - 1; ++i) { + if (inputRange[i] >= input) { + break; + } + } + return i - 1; +} + +function performInterpolation( + input: number, + inputMin: number, + inputMax: number, + outputMin: number, + outputMax: number, + easing: (input: number) => number, + extrapolateLeft: ExtrapolateType, + extrapolateRight: ExtrapolateType +) { + let result = input; + + // Extrapolate + if (result < inputMin) { + if (extrapolateLeft === 'identity') { + return result; + } else if (extrapolateLeft === 'clamp') { + result = inputMin; + } else if (extrapolateLeft === 'extend') { + // noop + } + } + + if (result > inputMax) { + if (extrapolateRight === 'identity') { + return result; + } else if (extrapolateRight === 'clamp') { + result = inputMax; + } else if (extrapolateRight === 'extend') { + // noop + } + } + + if (outputMin === outputMax) { + return outputMin; + } + + if (inputMin === inputMax) { + if (input <= inputMin) { + return outputMin; + } + return outputMax; + } + + // Input Range + if (inputMin === -Infinity) { + result = -result; + } else if (inputMax === Infinity) { + result -= inputMin; + } else { + result = (result - inputMin) / (inputMax - inputMin); + } + + // Easing + result = easing(result); + + // Output Range + if (outputMin === -Infinity) { + result = -result; + } else if (outputMax === Infinity) { + result += outputMin; + } else { + result = result * (outputMax - outputMin) + outputMin; + } + + return result; +} + +/** + * Very handy helper to map input ranges to output ranges with an easing + * function and custom behavior outside of the ranges. + */ +function createNumericInterpolation( + config: InterpolationConfig +): (input: number) => number { + const outputRange: $ReadOnlyArray = config.outputRange; + const inputRange = config.inputRange; + + const easing = config.easing || ((t) => t); + + let extrapolateLeft: ExtrapolateType = 'extend'; + if (config.extrapolateLeft !== undefined) { + extrapolateLeft = config.extrapolateLeft; + } else if (config.extrapolate !== undefined) { + extrapolateLeft = config.extrapolate; + } + + let extrapolateRight: ExtrapolateType = 'extend'; + if (config.extrapolateRight !== undefined) { + extrapolateRight = config.extrapolateRight; + } else if (config.extrapolate !== undefined) { + extrapolateRight = config.extrapolate; + } + + return (input) => { + if (typeof input !== 'number') { + throw new Error('Cannot interpolate an input which is not a number'); + } + + const range = findRange(input, inputRange); + return performInterpolation( + input, + inputRange[range], + inputRange[range + 1], + outputRange[range], + outputRange[range + 1], + easing, + extrapolateLeft, + extrapolateRight + ); + }; +} + +const numericComponentRegex = /[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/g; + +// Maps string inputs an RGBA color or an array of numeric components +function mapStringToNumericComponents( + input: string +): + | { components: [number, number, number, number], isColor: true } + | { components: $ReadOnlyArray, isColor: false } { + let normalizedColor = normalizeColor(input); + + if (normalizedColor != null && typeof normalizedColor === 'object') { + throw new Error('PlatformColors are not supported'); + } + + if (typeof normalizedColor === 'number') { + normalizedColor = normalizedColor || 0; + const r = (normalizedColor & 0xff000000) >>> 24; + const g = (normalizedColor & 0x00ff0000) >>> 16; + const b = (normalizedColor & 0x0000ff00) >>> 8; + const a = (normalizedColor & 0x000000ff) / 255; + return { components: [r, g, b, a], isColor: true }; + } else { + const components: Array = []; + let lastMatchEnd = 0; + let match: RegExp$matchResult | null = null; + while ((match = numericComponentRegex.exec(input)) != null) { + if (match != null) { + if (match.index > lastMatchEnd) { + components.push(input.substring(lastMatchEnd, match.index)); + } + components.push(parseFloat(match[0])); + lastMatchEnd = match.index + match[0].length; + } + } + if (components.length === 0) { + throw new Error( + 'outputRange must contain color or value with numeric component' + ); + } + if (lastMatchEnd < input.length) { + components.push(input.substring(lastMatchEnd, input.length)); + } + return { components, isColor: false }; + } +} + +/** + * Supports string shapes by extracting numbers so new values can be computed, + * and recombines those values into new strings of the same shape. Supports + * things like: + * + * rgba(123, 42, 99, 0.36) // colors + * -45deg // values with units + */ +function createStringInterpolation( + config: InterpolationConfig +): (input: number) => string { + if (config.outputRange.length < 2) { + throw new Error('Bad output range'); + } + const outputRange = config.outputRange.map(mapStringToNumericComponents); + + const isColor = outputRange[0].isColor; + + const numericComponents: $ReadOnlyArray<$ReadOnlyArray> = + outputRange.map((output) => { + if (output.isColor) { + return output.components; + } else { + // $FlowFixMe[incompatible-call] + return output.components.filter((c) => typeof c === 'number'); + } + }); + const interpolations = numericComponents[0].map((_, i) => + createNumericInterpolation({ + ...config, + outputRange: numericComponents.map((components) => components[i]) + }) + ); + if (!isColor) { + return (input) => { + const values = interpolations.map((interpolation) => + interpolation(input) + ); + let i = 0; + return outputRange[0].components + .map((c) => (typeof c === 'number' ? values[i++] : c)) + .join(''); + }; + } else { + return (input) => { + const result = interpolations.map((interpolation, i) => { + const value = interpolation(input); + // rgba requires that the r,g,b are integers.... so we want to round them, but we *dont* want to + // round the opacity (4th column). + return i < 3 ? Math.round(value) : Math.round(value * 1000) / 1000; + }); + return `rgba(${result[0]}, ${result[1]}, ${result[2]}, ${result[3]})`; + }; + } +} + +class AnimatedInterpolation< + TOutput: number | string +> extends AnimatedWithChildren { + #parent: AnimatedWithChildren; + #interpolation: (input: number) => TOutput; + + constructor( + parent: AnimatedWithChildren, + interpolation: (input: number) => TOutput + ) { + super(); + this.#parent = parent; + this.#interpolation = interpolation; + } + + __getValue(): TOutput { + const parentValue = this.#parent.__getValue(); + return this.#interpolation(parentValue); + } + + __attach(): void { + this.#parent.__addChild(this); + } + + __detach(): void { + this.#parent.__removeChild(this); + } +} + +function createInterpolator( + config: InterpolationConfig +): ((number) => TOutput) | null { + switch (typeof config.outputRange[0]) { + case 'number': { + return createNumericInterpolation(config as $FlowFixMe) as $FlowFixMe; + } + case 'string': { + return createStringInterpolation(config as $FlowFixMe) as $FlowFixMe; + } + default: { + return null; + } + } +} + +export function Interpolate( + value: AnimatedWithChildren, + config: InterpolationConfig +): AnimatedInterpolation { + const interpolator = createInterpolator(config); + if (interpolator == null) { + throw new Error('Invalid output range'); + } + // $FlowFixMe[incompatible-type] - Flow currently doesn't have a mechanism to refine the polymorphic config type + return new AnimatedInterpolation(value, interpolator); +} diff --git a/packages/react-strict-animated/src/web/nodes/AnimatedNode.js b/packages/react-strict-animated/src/web/nodes/AnimatedNode.js new file mode 100644 index 0000000..c946b2e --- /dev/null +++ b/packages/react-strict-animated/src/web/nodes/AnimatedNode.js @@ -0,0 +1,19 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +export default class AnimatedNode<+TOutput> { + __attach(): void {} + __detach(): void {} + __getValue(): TOutput { + throw new Error('Method not implemented'); + } + __getAnimatedValue(): TOutput { + return this.__getValue(); + } +} diff --git a/packages/react-strict-animated/src/web/nodes/AnimatedStyle.js b/packages/react-strict-animated/src/web/nodes/AnimatedStyle.js new file mode 100644 index 0000000..0488030 --- /dev/null +++ b/packages/react-strict-animated/src/web/nodes/AnimatedStyle.js @@ -0,0 +1,114 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import type { AnimatedStyleValue } from '../../shared/SharedAnimatedTypes'; +import type { KeyframeMap } from '../animations/Animation'; +import type { AnimatedNodeType } from '../types/AnimatedTypes'; + +import AnimatedNode from './AnimatedNode'; +import AnimatedTransform from './AnimatedTransform'; +import AnimatedWithChildren from './AnimatedWithChildren'; + +type ElementGetter = () => ?HTMLElement; +type Flusher = () => void; + +type OutputAnimatedStyle = { + opacity?: string, + transform?: string +}; + +export type ReadOnlyOutputAnimatedStyle = $ReadOnly; + +export default class AnimatedStyleNode extends AnimatedNode { + #style: $ReadOnly<{ + ...Omit, 'transform'>, + transform?: AnimatedTransform + }>; + #elementGetter: ElementGetter; + #flush: Flusher; + + constructor( + style: AnimatedStyleValue, + elementGetter: ElementGetter, + flusher: Flusher + ) { + super(); + this.#elementGetter = elementGetter; + this.#flush = flusher; + this.#style = { + opacity: style.opacity, + transform: + style.transform != null + ? new AnimatedTransform(style.transform) + : undefined + }; + } + + __getValue(): ReadOnlyOutputAnimatedStyle { + const outputStyle: OutputAnimatedStyle = {}; + for (const key of Object.keys(this.#style)) { + const value = this.#style[key]; + if (value instanceof AnimatedNode) { + outputStyle[key] = value.__getValue(); + } else if (typeof value === 'number') { + // TODO: currently works because we're only taking opacity into account + // but will have to set units if we expand usage + outputStyle[key] = `${value}`; + } else if (typeof value === 'string') { + outputStyle[key] = value; + } + } + return outputStyle; + } + + __getAnimatedValue(): ReadOnlyOutputAnimatedStyle { + const outputStyle: OutputAnimatedStyle = {}; + for (const key of Object.keys(this.#style)) { + const value = this.#style[key]; + if (value instanceof AnimatedNode) { + outputStyle[key] = value.__getAnimatedValue(); + } + } + return outputStyle; + } + + __attach(): void { + for (const key of Object.keys(this.#style)) { + const value = this.#style[key]; + if (value instanceof AnimatedWithChildren) { + value.__addChild(this); + } + } + } + + __detach(): void { + for (const key of Object.keys(this.#style)) { + const value = this.#style[key]; + if (value instanceof AnimatedWithChildren) { + value.__removeChild(this); + } + } + } + + flush() { + this.#flush(); + } + + update(keyframeMap: KeyframeMap) { + const domElement = this.#elementGetter(); + if (domElement != null) { + const keyframes = keyframeMap.get(domElement); + if (keyframes != null) { + keyframes.push({ ...this.__getAnimatedValue() }); + } else { + keyframeMap.set(domElement, [{ ...this.__getAnimatedValue() }]); + } + } + } +} diff --git a/packages/react-strict-animated/src/web/nodes/AnimatedTransform.js b/packages/react-strict-animated/src/web/nodes/AnimatedTransform.js new file mode 100644 index 0000000..e1f85f0 --- /dev/null +++ b/packages/react-strict-animated/src/web/nodes/AnimatedTransform.js @@ -0,0 +1,102 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +'use strict'; + +import type { AnimatedTransformValue } from '../../shared/SharedAnimatedTypes'; +import type { AnimatedNodeType } from '../types/AnimatedTypes'; + +import AnimatedNode from './AnimatedNode'; +import AnimatedWithChildren from './AnimatedWithChildren'; + +const UNIT: $ReadOnly<{ + rotate: string, + rotateX: string, + rotateY: string, + rotateZ: string, + scale: string, + scaleX: string, + scaleY: string, + skewX: string, + skewY: string, + translate: string, + translateX: string, + translateY: string, + perspective: string +}> = { + perspective: 'px', + rotate: 'deg', + rotateX: 'deg', + rotateY: 'deg', + rotateZ: 'deg', + scale: '', + scaleX: '', + scaleY: '', + skewX: 'deg', + skewY: 'deg', + translate: 'px', + translateX: 'px', + translateY: 'px' +}; + +function mapTransform(t: AnimatedTransformValue): string { + const key = Object.keys(t)[0]; + // $FlowFixMe[prop-missing] - this is type safe as the key using to access object was literally just extracted from the object + let value = t[key]; + if (value instanceof AnimatedNode) { + value = value.__getAnimatedValue(); + } + if (typeof value === 'number') { + return `${key}(${value}${UNIT[key]})`; + } else if (typeof value === 'string') { + return `${key}(${value})`; + } + throw new Error('value is neither a number or string'); +} + +type TransformsArray = $ReadOnlyArray>; + +export default class AnimatedTransform extends AnimatedWithChildren { + #transforms: TransformsArray; + + constructor(transforms: TransformsArray) { + super(); + this.#transforms = transforms; + } + + __getValue(): string { + return this.#transforms.reduce((tformString, tformObj) => { + return tformString + mapTransform(tformObj); + }, ''); + } + + __attach(): void { + for (const transform of this.#transforms) { + for (const key of Object.keys(transform)) { + // $FlowFixMe[prop-missing] - this is type safe as the key using to access object was literally just extracted from the object + const value = transform[key]; + if (value instanceof AnimatedWithChildren) { + value.__addChild(this); + } + } + } + } + + __detach(): void { + for (const transform of this.#transforms) { + for (const key of Object.keys(transform)) { + // $FlowFixMe[prop-missing] - this is type safe as the key using to access object was literally just extracted from the object + const value = transform[key]; + if (value instanceof AnimatedWithChildren) { + value.__removeChild(this); + } + } + } + } +} diff --git a/packages/react-strict-animated/src/web/nodes/AnimatedValue.js b/packages/react-strict-animated/src/web/nodes/AnimatedValue.js new file mode 100644 index 0000000..ed50db0 --- /dev/null +++ b/packages/react-strict-animated/src/web/nodes/AnimatedValue.js @@ -0,0 +1,165 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import type AnimatedNode from './AnimatedNode'; +import type { EndCallback } from '../types/AnimatedTypes'; +import type { AnimatedAnimation, KeyframeMap } from '../animations/Animation'; + +import AnimatedStyle from './AnimatedStyle'; +import AnimatedWithChildren from './AnimatedWithChildren'; + +type AnimationData = $ReadOnly<{ + animatedAnimation: AnimatedAnimation, + webAnimations: $ReadOnlyArray +}>; + +function findAnimatedStyles( + node: AnimatedNode, + animatedStyles: Set +) { + if (node instanceof AnimatedStyle) { + animatedStyles.add(node); + } else if (node instanceof AnimatedWithChildren) { + node + .__getChildren() + .forEach((child) => findAnimatedStyles(child, animatedStyles)); + } +} + +function flush(rootNode: AnimatedValue) { + const animatedStyles: Set = new Set(); + findAnimatedStyles(rootNode, animatedStyles); + animatedStyles.forEach((aStyle) => aStyle.flush()); + return animatedStyles; +} + +function flushToKeyframeMap(rootNode: AnimatedValue, keyframeMap: KeyframeMap) { + const animatedStyles: Set = new Set(); + findAnimatedStyles(rootNode, animatedStyles); + animatedStyles.forEach((aStyle) => aStyle.update(keyframeMap)); +} + +export default class AnimatedValue extends AnimatedWithChildren { + #value: number; + #startingValue: number; + #currentAnimation: AnimationData | null; + + constructor(startingValue: number) { + super(); + this.#value = startingValue; + this.#startingValue = startingValue; + this.#currentAnimation = null; + } + + __getValue(): number { + return this.#value; + } + + /** + * Directly set the value. This will stop any animations running on the value + * and update all the bound properties. + */ + setValue(value: number): void { + if (this.#currentAnimation != null) { + this.#currentAnimation.webAnimations.forEach((anim) => { + anim.cancel(); + }); + this.#currentAnimation = null; + } + this.#value = value; + flush(this); + } + + stopAnimation() { + const currentAnimation = this.#currentAnimation; + if (currentAnimation != null) { + const duration = currentAnimation.animatedAnimation.getDuration(); + const elapsedTime = currentAnimation.webAnimations[0]?.currentTime; + if (elapsedTime != null) { + // TODO: actually get the interpolated resulting value instead of one + // clamped to a keyframe + const timeline = currentAnimation.animatedAnimation.getTimeline(); + const timelineIndex = Math.max( + Math.ceil((elapsedTime / duration) * timeline.length) - 1, + 0 + ); + const newValue = timeline[timelineIndex]; + this.setValue(newValue != null ? newValue : this.#value); + } else { + currentAnimation.webAnimations.forEach((anim) => { + anim.cancel(); + }); + this.#currentAnimation = null; + } + } + } + + resetAnimation() { + if (this.#currentAnimation != null) { + this.setValue(this.#startingValue); + } + } + + animate(animation: AnimatedAnimation, callback?: EndCallback): void { + this.stopAnimation(); + + const { config, keyframeMap } = animation.generate( + this.#value, + this.__updateValue + ); + + const currentAnimations = Array.from(keyframeMap.entries()) + .map(([domElement, keyframes]) => { + if (keyframes.length < 2) { + return null; + } + return domElement.animate(keyframes, { ...config }); + }) + .filter(Boolean); + + this.#currentAnimation = { + animatedAnimation: animation, + webAnimations: currentAnimations + }; + + const animPromise = Promise.all( + currentAnimations.map((anim) => { + if (anim.finished != null) { + return anim.finished; + } + return new Promise((resolve, reject) => { + anim.onfinish = resolve; + anim.oncancel = reject; + }); + }) + ); + + animPromise + .then(() => { + // animation finished + this.#currentAnimation = null; + if (callback != null) { + callback({ finished: true }); + } + }) + .catch(() => { + // animation cancelled + if (callback != null) { + callback({ finished: false }); + } + }); + + flush(this); + } + + __updateValue = (value: number, keyframeMap: KeyframeMap) => { + this.#value = value; + flushToKeyframeMap(this, keyframeMap); + }; +} diff --git a/packages/react-strict-animated/src/web/nodes/AnimatedWithChildren.js b/packages/react-strict-animated/src/web/nodes/AnimatedWithChildren.js new file mode 100644 index 0000000..6abe8f8 --- /dev/null +++ b/packages/react-strict-animated/src/web/nodes/AnimatedWithChildren.js @@ -0,0 +1,44 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import AnimatedNode from './AnimatedNode'; + +export default class AnimatedWithChildren< + TOutput +> extends AnimatedNode { + #children: Array>; + + constructor() { + super(); + this.#children = []; + } + + __addChild(child: AnimatedNode): void { + if (this.#children.length === 0) { + this.__attach(); + } + this.#children.push(child); + } + + __removeChild(child: AnimatedNode): void { + const index = this.#children.indexOf(child); + if (index === -1) { + console.warn("Trying to remove a child that doesn't exist"); + return; + } + this.#children.splice(index, 1); + if (this.#children.length === 0) { + this.__detach(); + } + } + + __getChildren(): Array> { + return this.#children; + } +} diff --git a/packages/react-strict-animated/src/web/types/AnimatedTypes.js b/packages/react-strict-animated/src/web/types/AnimatedTypes.js new file mode 100644 index 0000000..0192d4c --- /dev/null +++ b/packages/react-strict-animated/src/web/types/AnimatedTypes.js @@ -0,0 +1,28 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +import type InternalAnimatedNode from '../nodes/AnimatedNode'; + +import AnimatedValue from '../nodes/AnimatedValue'; + +// The React Native version of AnimatedNode isn't polymorphic (and not +// type-safe) to to keep type parity I'm intentionally reducing the type +// safety on the web version +export type AnimatedNodeType = InternalAnimatedNode<$FlowFixMe>; + +export type EndResult = { finished: boolean, ... }; +export type EndCallback = (result: EndResult) => void; + +export type CompositeAnimation = $ReadOnly<{ + reset: () => void, + start: (callback?: EndCallback) => void, + stop: () => void +}>; + +export { AnimatedValue }; diff --git a/packages/react-strict-animated/src/web/useAnimatedStyle.js b/packages/react-strict-animated/src/web/useAnimatedStyle.js new file mode 100644 index 0000000..fec9a6c --- /dev/null +++ b/packages/react-strict-animated/src/web/useAnimatedStyle.js @@ -0,0 +1,229 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +/* eslint-disable no-unreachable */ + +import type { AnimatedStyleValue } from '../shared/SharedAnimatedTypes'; +import type { ReadOnlyOutputAnimatedStyle } from './nodes/AnimatedStyle'; + +import AnimatedNode from './nodes/AnimatedNode'; +import AnimatedStyle from './nodes/AnimatedStyle'; + +import nullthrows from 'nullthrows'; +import { + isValidElement, + useCallback, + useInsertionEffect, + useMemo, + useReducer, + useRef, + useState +} from 'react'; + +type CallbackRef = (T) => void; + +type CompositeKeyComponent = + | AnimatedNode + | $ReadOnlyArray + | $ReadOnly<{ [string]: CompositeKeyComponent }>; + +type $ReadOnlyCompositeKeyComponent = + | AnimatedNode + | $ReadOnlyArray<$ReadOnlyCompositeKeyComponent | null> + | $ReadOnly<{ [string]: $ReadOnlyCompositeKeyComponent }>; + +function isPlainObject(value: mixed): value is $ReadOnly<{ [string]: mixed }> { + return ( + /* $FlowFixMe[incompatible-type-guard] - Flow does not know that the prototype + and ReactElement checks preserve the type refinement of `value`. */ + value !== null && + typeof value === 'object' && + Object.getPrototypeOf(value).isPrototypeOf(Object) && + !isValidElement(value) + ); +} + +function createCompositeKeyForArray( + array: $ReadOnlyArray +): $ReadOnlyArray<$ReadOnlyCompositeKeyComponent | null> | null { + let compositeKey: Array<$ReadOnlyCompositeKeyComponent | null> | null = null; + + for (let ii = 0, length = array.length; ii < length; ii++) { + const value = array[ii]; + + let compositeKeyComponent; + if (value instanceof AnimatedNode) { + compositeKeyComponent = value; + } else if (Array.isArray(value)) { + compositeKeyComponent = createCompositeKeyForArray(value); + } else if (isPlainObject(value)) { + compositeKeyComponent = createCompositeKeyForObject(value); + } + if (compositeKeyComponent != null) { + if (compositeKey == null) { + compositeKey = new Array<$ReadOnlyCompositeKeyComponent | null>( + array.length + ).fill(null); + } + compositeKey[ii] = compositeKeyComponent; + } + } + + return compositeKey; +} + +function createCompositeKeyForObject( + object: $ReadOnly<{ [string]: mixed }> +): $ReadOnly<{ [string]: $ReadOnlyCompositeKeyComponent }> | null { + let compositeKey: { [string]: $ReadOnlyCompositeKeyComponent } | null = null; + + const keys = Object.keys(object); + for (let ii = 0, length = keys.length; ii < length; ii++) { + const key = keys[ii]; + const value = object[key]; + + let compositeKeyComponent; + if (value instanceof AnimatedNode) { + compositeKeyComponent = value; + } else if (Array.isArray(value)) { + compositeKeyComponent = createCompositeKeyForArray(value); + } else if (isPlainObject(value)) { + compositeKeyComponent = createCompositeKeyForObject(value); + } + if (compositeKeyComponent != null) { + if (compositeKey == null) { + compositeKey = {} as { [string]: $ReadOnlyCompositeKeyComponent }; + } + compositeKey[key] = compositeKeyComponent; + } + } + + return compositeKey; +} + +function areCompositeKeyComponentsEqual( + prev: $ReadOnlyCompositeKeyComponent | null, + next: $ReadOnlyCompositeKeyComponent | null +): boolean { + if (prev === next) { + return true; + } + if (prev instanceof AnimatedNode) { + return prev === next; + } + if (Array.isArray(prev)) { + if (!Array.isArray(next)) { + return false; + } + const length = prev.length; + if (length !== next.length) { + return false; + } + for (let ii = 0; ii < length; ii++) { + if (!areCompositeKeyComponentsEqual(prev[ii], next[ii])) { + return false; + } + } + return true; + } + if (isPlainObject(prev)) { + if (!isPlainObject(next)) { + return false; + } + const keys = Object.keys(prev); + const length = keys.length; + if (length !== Object.keys(next).length) { + return false; + } + for (let ii = 0; ii < length; ii++) { + const key = keys[ii]; + if ( + !nullthrows(next).hasOwnProperty(key) || + !areCompositeKeyComponentsEqual(prev[key], next[key]) + ) { + return false; + } + } + return true; + } + return false; +} + +function useMemoizedAnimatedStyle( + create: () => ?AnimatedStyle, + style: ?AnimatedStyleValue> +): ?AnimatedStyle { + const compositeKey = useMemo( + () => (style != null ? createCompositeKeyForObject(style) : null), + [style] + ); + + const [currentData, updateData] = useState< + $ReadOnly<{ + compositeKey: typeof compositeKey, + node: ?AnimatedStyle + }> + >(() => ({ + compositeKey, + node: create() + })); + + if (!areCompositeKeyComponentsEqual(currentData.compositeKey, compositeKey)) { + updateData({ + compositeKey, + node: create() + }); + } + + return currentData.node; +} + +export default function useAnimatedStyle( + style: ?AnimatedStyleValue>, + parentRef?: React.RefSetter +): [ReadOnlyOutputAnimatedStyle, CallbackRef] { + const domElemRef = useRef(null); + const [, scheduleUpdate] = useReducer((count) => count + 1, 0); + const node = useMemoizedAnimatedStyle( + () => + style != null + ? new AnimatedStyle( + style, + () => domElemRef.current, + () => scheduleUpdate() + ) + : null, + style + ); + + useInsertionEffect(() => { + if (node != null) { + node.__attach(); + return () => { + node.__detach(); + }; + } + }, [node]); + + const refHandler = useCallback( + (instance: TInstance) => { + domElemRef.current = instance; + if (parentRef != null) { + if (typeof parentRef === 'function') { + parentRef(instance); + } else { + parentRef.current = instance; + } + } + }, + [parentRef] + ); + + return [node?.__getValue() ?? {}, refHandler]; +} diff --git a/packages/react-strict-animated/src/web/utils/SpringConfig.js b/packages/react-strict-animated/src/web/utils/SpringConfig.js new file mode 100644 index 0000000..1fdd890 --- /dev/null +++ b/packages/react-strict-animated/src/web/utils/SpringConfig.js @@ -0,0 +1,95 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + */ + +type SpringConfigType = { + damping: number, + stiffness: number, + ... +}; + +function stiffnessFromOrigamiValue(oValue: number) { + return (oValue - 30) * 3.62 + 194; +} + +function dampingFromOrigamiValue(oValue: number) { + return (oValue - 8) * 3 + 25; +} + +export function fromOrigamiTensionAndFriction( + tension: number, + friction: number +): SpringConfigType { + return { + damping: dampingFromOrigamiValue(friction), + stiffness: stiffnessFromOrigamiValue(tension) + }; +} + +export function fromBouncinessAndSpeed( + bounciness: number, + speed: number +): SpringConfigType { + function normalize(value: number, startValue: number, endValue: number) { + return (value - startValue) / (endValue - startValue); + } + + function projectNormal(n: number, start: number, end: number) { + return start + n * (end - start); + } + + function linearInterpolation(t: number, start: number, end: number) { + return t * end + (1 - t) * start; + } + + function quadraticOutInterpolation(t: number, start: number, end: number) { + return linearInterpolation(2 * t - t * t, start, end); + } + + function b3Friction1(x: number) { + return 0.0007 * Math.pow(x, 3) - 0.031 * Math.pow(x, 2) + 0.64 * x + 1.28; + } + + function b3Friction2(x: number) { + return 0.000044 * Math.pow(x, 3) - 0.006 * Math.pow(x, 2) + 0.36 * x + 2; + } + + function b3Friction3(x: number) { + return ( + 0.00000045 * Math.pow(x, 3) - + 0.000332 * Math.pow(x, 2) + + 0.1078 * x + + 5.84 + ); + } + + function b3Nobounce(tension: number) { + if (tension <= 18) { + return b3Friction1(tension); + } else if (tension > 18 && tension <= 44) { + return b3Friction2(tension); + } else { + return b3Friction3(tension); + } + } + + let b = normalize(bounciness / 1.7, 0, 20); + b = projectNormal(b, 0, 0.8); + const s = normalize(speed / 1.7, 0, 20); + const bouncyTension = projectNormal(s, 0.5, 200); + const bouncyFriction = quadraticOutInterpolation( + b, + b3Nobounce(bouncyTension), + 0.01 + ); + + return { + damping: dampingFromOrigamiValue(bouncyFriction), + stiffness: stiffnessFromOrigamiValue(bouncyTension) + }; +} diff --git a/packages/react-strict-animated/src/web/utils/constants.js b/packages/react-strict-animated/src/web/utils/constants.js new file mode 100644 index 0000000..d1a236a --- /dev/null +++ b/packages/react-strict-animated/src/web/utils/constants.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + */ + +export const TIMESTEP_COEFFICIENT = 16.667; // 60fps diff --git a/packages/react-strict-animated/src/web/utils/normalizeColor.js b/packages/react-strict-animated/src/web/utils/normalizeColor.js new file mode 100644 index 0000000..d5cf1ae --- /dev/null +++ b/packages/react-strict-animated/src/web/utils/normalizeColor.js @@ -0,0 +1,478 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + */ + +export default function normalizeColor(color: string | number): number | null { + if (typeof color === 'number') { + if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) { + return color; + } + return null; + } + + if (typeof color !== 'string') { + return null; + } + + const matchers = getMatchers(); + let match; + + // Ordered based on occurrences on Facebook codebase + if ((match = matchers.hex6.exec(color))) { + return parseInt(match[1] + 'ff', 16) >>> 0; + } + + const colorFromKeyword = normalizeKeyword(color); + if (colorFromKeyword != null) { + return colorFromKeyword; + } + + if ((match = matchers.rgba.exec(color) || matchers.rgb.exec(color))) { + // rgb(R G B / A) / rgba(R G B / A) notation + if (match[9] !== undefined) { + return ( + ((parse255(match[9]) << 24) | // r + (parse255(match[10]) << 16) | // g + (parse255(match[11]) << 8) | // b + parse1(match[12])) >>> // a + 0 + ); + } + // rgb(R, G, B, A) / rgba(R, G, B, A) notation + else if (match[5] !== undefined) { + return ( + ((parse255(match[5]) << 24) | // r + (parse255(match[6]) << 16) | // g + (parse255(match[7]) << 8) | // b + parse1(match[8])) >>> // a + 0 + ); + } + // rgb(R, G, B) / rgba(R, G, B) notation + return ( + ((parse255(match[2]) << 24) | // r + (parse255(match[3]) << 16) | // g + (parse255(match[4]) << 8) | // b + 0x000000ff) >>> // a + 0 + ); + } + + if ((match = matchers.hex3.exec(color))) { + return ( + parseInt( + match[1] + + match[1] + // r + match[2] + + match[2] + // g + match[3] + + match[3] + // b + 'ff', // a + 16 + ) >>> 0 + ); + } + + // https://drafts.csswg.org/css-color-4/#hex-notation + if ((match = matchers.hex8.exec(color))) { + return parseInt(match[1], 16) >>> 0; + } + + if ((match = matchers.hex4.exec(color))) { + return ( + parseInt( + match[1] + + match[1] + // r + match[2] + + match[2] + // g + match[3] + + match[3] + // b + match[4] + + match[4], // a + 16 + ) >>> 0 + ); + } + + if ((match = matchers.hsl.exec(color))) { + return ( + (hslToRgb( + parse360(match[1]), // h + parsePercentage(match[2]), // s + parsePercentage(match[3]) // l + ) | + 0x000000ff) >>> // a + 0 + ); + } + + if ((match = matchers.hsla.exec(color))) { + // hsla(H S L / A) notation + if (match[6] !== undefined) { + return ( + (hslToRgb( + parse360(match[6]), // h + parsePercentage(match[7]), // s + parsePercentage(match[8]) // l + ) | + parse1(match[9])) >>> // a + 0 + ); + } + + // hsla(H, S, L, A) notation + return ( + (hslToRgb( + parse360(match[2]), // h + parsePercentage(match[3]), // s + parsePercentage(match[4]) // l + ) | + parse1(match[5])) >>> // a + 0 + ); + } + + if ((match = matchers.hwb.exec(color))) { + if (match[5] !== undefined) { + // hwb(H W B / A) notation + return ( + (hwbToRgb( + parse360(match[5]), // h + parsePercentage(match[6]), // w + parsePercentage(match[7]) // b + ) | + parse1(match[8])) >>> // a + 0 + ); + } + // hwb(H W B) notation + return ( + (hwbToRgb( + parse360(match[2]), // h + parsePercentage(match[3]), // w + parsePercentage(match[4]) // b + ) | + 0x000000ff) >>> // a + 0 + ); + } + + return null; +} + +function hue2rgb(p: number, q: number, t: number) { + let tt = t; + if (tt < 0) { + tt += 1; + } + if (tt > 1) { + tt -= 1; + } + if (tt < 1 / 6) { + return p + (q - p) * 6 * tt; + } + if (tt < 1 / 2) { + return q; + } + if (tt < 2 / 3) { + return p + (q - p) * (2 / 3 - tt) * 6; + } + return p; +} + +function hslToRgb(h: number, s: number, l: number) { + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + const r = hue2rgb(p, q, h + 1 / 3); + const g = hue2rgb(p, q, h); + const b = hue2rgb(p, q, h - 1 / 3); + + return ( + (Math.round(r * 255) << 24) | + (Math.round(g * 255) << 16) | + (Math.round(b * 255) << 8) + ); +} + +function hwbToRgb(h: number, w: number, b: number) { + if (w + b >= 1) { + const gray = Math.round((w * 255) / (w + b)); + + return (gray << 24) | (gray << 16) | (gray << 8); + } + + const red = hue2rgb(0, 1, h + 1 / 3) * (1 - w - b) + w; + const green = hue2rgb(0, 1, h) * (1 - w - b) + w; + const blue = hue2rgb(0, 1, h - 1 / 3) * (1 - w - b) + w; + + return ( + (Math.round(red * 255) << 24) | + (Math.round(green * 255) << 16) | + (Math.round(blue * 255) << 8) + ); +} + +const NUMBER = '[-+]?\\d*\\.?\\d+'; +const PERCENTAGE = NUMBER + '%'; + +function call(...args: Array) { + return '\\(\\s*(' + args.join(')\\s*,?\\s*(') + ')\\s*\\)'; +} + +function callModern(...args: Array) { + return '\\(\\s*(' + args.join(')\\s*(') + ')\\s*\\)'; +} + +function callWithSlashSeparator(...args: Array) { + return ( + '\\(\\s*(' + + args.slice(0, args.length - 1).join(')\\s*,?\\s*(') + + ')\\s*/\\s*(' + + args[args.length - 1] + + ')\\s*\\)' + ); +} + +function commaSeparatedCall(...args: Array) { + return '\\(\\s*(' + args.join(')\\s*,\\s*(') + ')\\s*\\)'; +} + +let cachedMatchers; + +function getMatchers() { + if (cachedMatchers === undefined) { + const rgbRegexPattern = + call(NUMBER, NUMBER, NUMBER) + + '|' + + commaSeparatedCall(NUMBER, NUMBER, NUMBER, NUMBER) + + '|' + + callWithSlashSeparator(NUMBER, NUMBER, NUMBER, NUMBER); + + cachedMatchers = { + hex3: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, + hex4: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, + hex6: /^#([0-9a-fA-F]{6})$/, + hex8: /^#([0-9a-fA-F]{8})$/, + hsl: new RegExp('hsl' + call(NUMBER, PERCENTAGE, PERCENTAGE)), + hsla: new RegExp( + 'hsla(' + + commaSeparatedCall(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER) + + '|' + + callWithSlashSeparator(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER) + + ')' + ), + hwb: new RegExp( + 'hwb(' + + callModern(NUMBER, PERCENTAGE, PERCENTAGE) + + '|' + + callWithSlashSeparator(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER) + + ')' + ), + rgb: new RegExp('rgb(' + rgbRegexPattern + ')'), + rgba: new RegExp('rgba(' + rgbRegexPattern + ')') + }; + } + return cachedMatchers; +} + +function parse255(str: string) { + const int = parseInt(str, 10); + if (int < 0) { + return 0; + } + if (int > 255) { + return 255; + } + return int; +} + +function parse360(str: string) { + const int = parseFloat(str); + return (((int % 360) + 360) % 360) / 360; +} + +function parse1(str: string) { + const num = parseFloat(str); + if (num < 0) { + return 0; + } + if (num > 1) { + return 255; + } + return Math.round(num * 255); +} + +function parsePercentage(str: string) { + // parseFloat conveniently ignores the final % + const int = parseFloat(str); + if (int < 0) { + return 0; + } + if (int > 100) { + return 1; + } + return int / 100; +} + +function normalizeKeyword(name: string) { + // prettier-ignore + switch (name) { + case 'transparent': return 0x00000000; + // http://www.w3.org/TR/css3-color/#svg-color + case 'aliceblue': return 0xf0f8ffff; + case 'antiquewhite': return 0xfaebd7ff; + case 'aqua': return 0x00ffffff; + case 'aquamarine': return 0x7fffd4ff; + case 'azure': return 0xf0ffffff; + case 'beige': return 0xf5f5dcff; + case 'bisque': return 0xffe4c4ff; + case 'black': return 0x000000ff; + case 'blanchedalmond': return 0xffebcdff; + case 'blue': return 0x0000ffff; + case 'blueviolet': return 0x8a2be2ff; + case 'brown': return 0xa52a2aff; + case 'burlywood': return 0xdeb887ff; + case 'burntsienna': return 0xea7e5dff; + case 'cadetblue': return 0x5f9ea0ff; + case 'chartreuse': return 0x7fff00ff; + case 'chocolate': return 0xd2691eff; + case 'coral': return 0xff7f50ff; + case 'cornflowerblue': return 0x6495edff; + case 'cornsilk': return 0xfff8dcff; + case 'crimson': return 0xdc143cff; + case 'cyan': return 0x00ffffff; + case 'darkblue': return 0x00008bff; + case 'darkcyan': return 0x008b8bff; + case 'darkgoldenrod': return 0xb8860bff; + case 'darkgray': return 0xa9a9a9ff; + case 'darkgreen': return 0x006400ff; + case 'darkgrey': return 0xa9a9a9ff; + case 'darkkhaki': return 0xbdb76bff; + case 'darkmagenta': return 0x8b008bff; + case 'darkolivegreen': return 0x556b2fff; + case 'darkorange': return 0xff8c00ff; + case 'darkorchid': return 0x9932ccff; + case 'darkred': return 0x8b0000ff; + case 'darksalmon': return 0xe9967aff; + case 'darkseagreen': return 0x8fbc8fff; + case 'darkslateblue': return 0x483d8bff; + case 'darkslategray': return 0x2f4f4fff; + case 'darkslategrey': return 0x2f4f4fff; + case 'darkturquoise': return 0x00ced1ff; + case 'darkviolet': return 0x9400d3ff; + case 'deeppink': return 0xff1493ff; + case 'deepskyblue': return 0x00bfffff; + case 'dimgray': return 0x696969ff; + case 'dimgrey': return 0x696969ff; + case 'dodgerblue': return 0x1e90ffff; + case 'firebrick': return 0xb22222ff; + case 'floralwhite': return 0xfffaf0ff; + case 'forestgreen': return 0x228b22ff; + case 'fuchsia': return 0xff00ffff; + case 'gainsboro': return 0xdcdcdcff; + case 'ghostwhite': return 0xf8f8ffff; + case 'gold': return 0xffd700ff; + case 'goldenrod': return 0xdaa520ff; + case 'gray': return 0x808080ff; + case 'green': return 0x008000ff; + case 'greenyellow': return 0xadff2fff; + case 'grey': return 0x808080ff; + case 'honeydew': return 0xf0fff0ff; + case 'hotpink': return 0xff69b4ff; + case 'indianred': return 0xcd5c5cff; + case 'indigo': return 0x4b0082ff; + case 'ivory': return 0xfffff0ff; + case 'khaki': return 0xf0e68cff; + case 'lavender': return 0xe6e6faff; + case 'lavenderblush': return 0xfff0f5ff; + case 'lawngreen': return 0x7cfc00ff; + case 'lemonchiffon': return 0xfffacdff; + case 'lightblue': return 0xadd8e6ff; + case 'lightcoral': return 0xf08080ff; + case 'lightcyan': return 0xe0ffffff; + case 'lightgoldenrodyellow': return 0xfafad2ff; + case 'lightgray': return 0xd3d3d3ff; + case 'lightgreen': return 0x90ee90ff; + case 'lightgrey': return 0xd3d3d3ff; + case 'lightpink': return 0xffb6c1ff; + case 'lightsalmon': return 0xffa07aff; + case 'lightseagreen': return 0x20b2aaff; + case 'lightskyblue': return 0x87cefaff; + case 'lightslategray': return 0x778899ff; + case 'lightslategrey': return 0x778899ff; + case 'lightsteelblue': return 0xb0c4deff; + case 'lightyellow': return 0xffffe0ff; + case 'lime': return 0x00ff00ff; + case 'limegreen': return 0x32cd32ff; + case 'linen': return 0xfaf0e6ff; + case 'magenta': return 0xff00ffff; + case 'maroon': return 0x800000ff; + case 'mediumaquamarine': return 0x66cdaaff; + case 'mediumblue': return 0x0000cdff; + case 'mediumorchid': return 0xba55d3ff; + case 'mediumpurple': return 0x9370dbff; + case 'mediumseagreen': return 0x3cb371ff; + case 'mediumslateblue': return 0x7b68eeff; + case 'mediumspringgreen': return 0x00fa9aff; + case 'mediumturquoise': return 0x48d1ccff; + case 'mediumvioletred': return 0xc71585ff; + case 'midnightblue': return 0x191970ff; + case 'mintcream': return 0xf5fffaff; + case 'mistyrose': return 0xffe4e1ff; + case 'moccasin': return 0xffe4b5ff; + case 'navajowhite': return 0xffdeadff; + case 'navy': return 0x000080ff; + case 'oldlace': return 0xfdf5e6ff; + case 'olive': return 0x808000ff; + case 'olivedrab': return 0x6b8e23ff; + case 'orange': return 0xffa500ff; + case 'orangered': return 0xff4500ff; + case 'orchid': return 0xda70d6ff; + case 'palegoldenrod': return 0xeee8aaff; + case 'palegreen': return 0x98fb98ff; + case 'paleturquoise': return 0xafeeeeff; + case 'palevioletred': return 0xdb7093ff; + case 'papayawhip': return 0xffefd5ff; + case 'peachpuff': return 0xffdab9ff; + case 'peru': return 0xcd853fff; + case 'pink': return 0xffc0cbff; + case 'plum': return 0xdda0ddff; + case 'powderblue': return 0xb0e0e6ff; + case 'purple': return 0x800080ff; + case 'rebeccapurple': return 0x663399ff; + case 'red': return 0xff0000ff; + case 'rosybrown': return 0xbc8f8fff; + case 'royalblue': return 0x4169e1ff; + case 'saddlebrown': return 0x8b4513ff; + case 'salmon': return 0xfa8072ff; + case 'sandybrown': return 0xf4a460ff; + case 'seagreen': return 0x2e8b57ff; + case 'seashell': return 0xfff5eeff; + case 'sienna': return 0xa0522dff; + case 'silver': return 0xc0c0c0ff; + case 'skyblue': return 0x87ceebff; + case 'slateblue': return 0x6a5acdff; + case 'slategray': return 0x708090ff; + case 'slategrey': return 0x708090ff; + case 'snow': return 0xfffafaff; + case 'springgreen': return 0x00ff7fff; + case 'steelblue': return 0x4682b4ff; + case 'tan': return 0xd2b48cff; + case 'teal': return 0x008080ff; + case 'thistle': return 0xd8bfd8ff; + case 'tomato': return 0xff6347ff; + case 'turquoise': return 0x40e0d0ff; + case 'violet': return 0xee82eeff; + case 'wheat': return 0xf5deb3ff; + case 'white': return 0xffffffff; + case 'whitesmoke': return 0xf5f5f5ff; + case 'yellow': return 0xffff00ff; + case 'yellowgreen': return 0x9acd32ff; + default: return null; + } +} diff --git a/packages/react-strict-animated/tools/rollup.config.mjs b/packages/react-strict-animated/tools/rollup.config.mjs new file mode 100644 index 0000000..9c29658 --- /dev/null +++ b/packages/react-strict-animated/tools/rollup.config.mjs @@ -0,0 +1,90 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { babel } from '@rollup/plugin-babel'; +import commonjs from '@rollup/plugin-commonjs'; +import resolve from '@rollup/plugin-node-resolve'; + +import path, { dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const babelPlugin = babel({ + babelHelpers: 'bundled', + configFile: path.resolve(__dirname, 'rollup/babelConfig.mjs') +}); + +function ossLicensePlugin() { + const header = `/** + * @license react-strict-dom + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict';`; + + return { + renderChunk(source) { + return `${header}\n${source}`; + } + }; +} + +const sharedPlugins = [ + babelPlugin, + ossLicensePlugin(), + resolve(), + commonjs() // commonjs packages: postcss-value-parser, styleq +]; + +/** + * Web bundles + */ +const webConfigs = [ + // OSS build + { + external: ['react', 'react/jsx-runtime', 'react-strict-dom'], + input: path.join(__dirname, '../src/web/index.js'), + output: { + file: path.join(__dirname, '../dist/web/index.js'), + format: 'es' + }, + plugins: [...sharedPlugins], + treeshake: { + moduleSideEffects: false + } + } +]; + +/** + * Native bundles + */ +const nativeConfigs = [ + // OSS build + { + external: [ + 'react', + 'react/jsx-runtime', + 'react-strict-dom', + /^react-native.*/ + ], + input: path.join(__dirname, '../src/native/index.js'), + output: { + file: path.join(__dirname, '../dist/native/index.js'), + format: 'es' + }, + plugins: [...sharedPlugins], + treeshake: { + moduleSideEffects: false + } + } +]; + +export default [...webConfigs, ...nativeConfigs]; diff --git a/packages/react-strict-animated/tools/rollup/babelConfig.mjs b/packages/react-strict-animated/tools/rollup/babelConfig.mjs new file mode 100644 index 0000000..24dbc6e --- /dev/null +++ b/packages/react-strict-animated/tools/rollup/babelConfig.mjs @@ -0,0 +1,29 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const config = { + assumptions: { + iterableIsArray: true + }, + comments: false, + parserOpts: { + enableExperimentalComponentSyntax: true, + reactRuntimeTarget: '19' + }, + plugins: ['babel-plugin-syntax-hermes-parser'], + presets: [ + [ + '@babel/preset-react', + { + runtime: 'automatic' + } + ], + '@babel/preset-flow' + ] +}; + +export default config;