@octanejs/gsap 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dominic Gannaway and Octane contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # @octanejs/gsap
2
+
3
+ GSAP lifecycle integration for Octane. It mirrors the official `@gsap/react`
4
+ `useGSAP` contract while using compiler-selected Octane hook slots.
5
+
6
+ ```bash
7
+ pnpm add @octanejs/gsap gsap
8
+ ```
9
+
10
+ ```tsrx
11
+ import gsap from 'gsap';
12
+ import { useGSAP } from '@octanejs/gsap';
13
+ import { useRef } from 'octane';
14
+
15
+ function AnimatedCard() @{
16
+ const scope = useRef<HTMLDivElement>(null);
17
+ const { contextSafe } = useGSAP(
18
+ () => {
19
+ gsap.from('.card', { y: 24, opacity: 0, stagger: 0.08 });
20
+ },
21
+ { scope },
22
+ );
23
+
24
+ const pulse = contextSafe(() => {
25
+ gsap.to('.card', { scale: 1.05, yoyo: true, repeat: 1 });
26
+ });
27
+
28
+ <div ref={scope}>
29
+ <button onClick={pulse}>Pulse</button>
30
+ <div class="card">Scoped animation</div>
31
+ </div>
32
+ }
33
+ ```
34
+
35
+ ## Contract
36
+
37
+ `useGSAP` accepts the same callback, dependency-array, and config forms as
38
+ `@gsap/react` 2.1.2. Config supports `scope`, `dependencies`, and
39
+ `revertOnUpdate`. The returned `context` and `contextSafe` identities remain
40
+ stable for the component lifetime, and GSAP context cleanup runs on dependency
41
+ replacement and unmount.
42
+
43
+ `useGSAP.register(gsapInstance)` selects a GSAP instance explicitly.
44
+ `useGSAP.headless` is `true`, matching the upstream package contract.
45
+
46
+ ## SSR
47
+
48
+ Server rendering returns stable context helpers but does not execute animation
49
+ effects. Hydration activates the normal client lifecycle.
50
+
51
+ ## Licensing
52
+
53
+ This adapter is independently authored and MIT licensed. GSAP is a peer
54
+ dependency and is not copied or redistributed here. GSAP uses its own
55
+ no-charge license; review the [GSAP licensing terms](https://gsap.com/licensing/)
56
+ for your use case.
package/UPSTREAM.md ADDED
@@ -0,0 +1,64 @@
1
+ # Upstream provenance
2
+
3
+ `@octanejs/gsap` ports the React-facing adapter published as `@gsap/react@2.1.2`.
4
+
5
+ | Field | Pin |
6
+ | --- | --- |
7
+ | Package | `@gsap/react` |
8
+ | Version and supported range | `2.1.2` exactly |
9
+ | Canonical tag | `greensock/react@2.1.2` |
10
+ | Tag commit | `b7f44ca767eb93390f965d6f19679e2b77be7966` |
11
+ | Framework-neutral peer/oracle | `gsap@3.15.0` (compatible with upstream's `^3.12.5` peer range) |
12
+
13
+ ## Source boundary and licensing
14
+
15
+ The canonical tag contains one React-facing source module, `src/index.js`, its
16
+ declaration file at `types/index.d.ts`, built artifacts, the README, and package
17
+ metadata. The tag contains no tests, test configuration, fixtures, snapshots,
18
+ or test scripts.
19
+
20
+ The upstream package declares `SEE LICENSE AT https://gsap.com/standard-license`,
21
+ and its source header says it is subject to the GSAP Standard License. It is
22
+ therefore inspected at the immutable commit above but is not vendored or
23
+ redistributed here. The Octane adapter is independently authored and MIT
24
+ licensed; GSAP remains an external peer dependency.
25
+
26
+ The Octane replacement mirrors the sole upstream source module at `src/index.ts`.
27
+ `src/internal.ts` contains only Octane's compiler hook-slot adapter and has no
28
+ upstream counterpart.
29
+
30
+ ## Export crosswalk
31
+
32
+ | Upstream surface | Octane status | Evidence |
33
+ | --- | --- | --- |
34
+ | `useGSAP(callback?, dependenciesOrConfig?)` | Ported. Callback, positional dependency, and config signatures are supported. | `tests/use-gsap.test.ts`; `tests/differential/parity.test.ts`; `typetests/adapted/types.test-d.ts` |
35
+ | Returned `context` | Ported with stable component-lifetime identity. | `tests/use-gsap.test.ts`; `tests/differential/parity.test.ts` |
36
+ | Returned `contextSafe` | Ported with stable identity and GSAP context registration. | `tests/use-gsap.test.ts`; `tests/differential/parity.test.ts` |
37
+ | Config `scope` | Ported for selector, element, and ref-like values. | `tests/_fixtures/app.tsrx`; `typetests/adapted/types.test-d.ts` |
38
+ | Config `dependencies` | Ported, including upstream's distinction between an absent property and explicit `undefined` or `null`. | `tests/use-gsap.test.ts` |
39
+ | Config `revertOnUpdate` | Ported. | `tests/use-gsap.test.ts`; `tests/differential/parity.test.ts` |
40
+ | `useGSAP.register(core)` | Ported. | `tests/use-gsap.test.ts`; `typetests/adapted/types.test-d.ts` |
41
+ | `useGSAP.headless === true` | Ported. | `tests/use-gsap.test.ts`; `typetests/adapted/types.test-d.ts` |
42
+
43
+ There are no silent or open export gaps at this pin.
44
+
45
+ ## Test and type disposition
46
+
47
+ Upstream `2.1.2` ships no runtime or type test suite in either the canonical tag
48
+ or the published package metadata, so there is no pristine suite to run or adapt.
49
+ Parity evidence is therefore repo-authored and registered in
50
+ `audit/react-parity.json`:
51
+
52
+ - `gsap-differential` — the same lifecycle fixture through Octane and published
53
+ `@gsap/react` 2.1.2 via the shared differential rig (required runtime oracle)
54
+ - pristine/adapted type lanes under `typetests/{pristine,adapted}/`
55
+
56
+ The ordinary `tests/use-gsap.test.ts` and SSR cases remain unpaired
57
+ framework-contract tests in the general shards; they are not parity-owned.
58
+
59
+ The upstream declaration contains a single `useGSAP` overload. Its accepted
60
+ callback/config forms are covered one-for-one in the type lanes, with negative
61
+ controls for an invalid callback and invalid scope. Static `register` and
62
+ `headless` are present in runtime source but omitted from the upstream
63
+ declaration; the Octane package intentionally types them because they are part
64
+ of the published runtime contract.
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@octanejs/gsap",
3
+ "version": "0.0.1",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=22.22.2"
8
+ },
9
+ "octane": {
10
+ "hookSlots": {
11
+ "manual": [
12
+ "src"
13
+ ]
14
+ }
15
+ },
16
+ "description": "GSAP lifecycle bindings for Octane — the useGSAP hook over the framework-agnostic GSAP core.",
17
+ "author": {
18
+ "name": "Dominic Gannaway",
19
+ "email": "dg@domgan.com"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/octanejs/octane.git",
27
+ "directory": "packages/gsap"
28
+ },
29
+ "main": "src/index.ts",
30
+ "module": "src/index.ts",
31
+ "types": "src/index.ts",
32
+ "files": [
33
+ "src",
34
+ "README.md",
35
+ "LICENSE",
36
+ "UPSTREAM.md",
37
+ "status.json"
38
+ ],
39
+ "exports": {
40
+ ".": "./src/index.ts"
41
+ },
42
+ "peerDependencies": {
43
+ "gsap": "^3.12.5",
44
+ "octane": "0.1.32"
45
+ },
46
+ "devDependencies": {
47
+ "@gsap/react": "2.1.2",
48
+ "@tsrx/react": "^0.2.56",
49
+ "@types/react": "^19.2.17",
50
+ "@types/react-dom": "^19.2.3",
51
+ "esbuild": "^0.28.1",
52
+ "gsap": "3.15.0",
53
+ "react": "^19.2.7",
54
+ "react-dom": "^19.2.7",
55
+ "vitest": "^4.1.10",
56
+ "octane": "0.1.32"
57
+ },
58
+ "scripts": {
59
+ "test": "vitest run --root ../.. --project gsap --project gsap-ssr --project gsap-differential"
60
+ }
61
+ }
package/src/index.ts ADDED
@@ -0,0 +1,114 @@
1
+ import gsap from 'gsap';
2
+ import { useEffect, useLayoutEffect, useRef } from 'octane';
3
+ import { splitSlot, subSlot } from './internal.ts';
4
+
5
+ type AnyFunction = (...args: any[]) => any;
6
+ export type ContextSafeFunction = <T extends AnyFunction>(func: T) => T;
7
+ export type ContextFunction = (
8
+ context: gsap.Context,
9
+ contextSafe: ContextSafeFunction,
10
+ ) => AnyFunction | unknown | void;
11
+
12
+ interface RefLike<T> {
13
+ current: T | null;
14
+ }
15
+
16
+ export interface UseGSAPConfig {
17
+ scope?: RefLike<Element> | Element | string;
18
+ dependencies?: unknown[];
19
+ revertOnUpdate?: boolean;
20
+ }
21
+
22
+ export interface UseGSAPReturn {
23
+ context: gsap.Context;
24
+ contextSafe: ContextSafeFunction;
25
+ }
26
+
27
+ interface UseGSAP {
28
+ (callback?: ContextFunction, dependencies?: unknown[]): UseGSAPReturn;
29
+ (callback?: ContextFunction, config?: UseGSAPConfig): UseGSAPReturn;
30
+ (config?: UseGSAPConfig): UseGSAPReturn;
31
+ register(core: typeof gsap): void;
32
+ headless: true;
33
+ }
34
+
35
+ const useIsomorphicLayoutEffect = typeof document !== 'undefined' ? useLayoutEffect : useEffect;
36
+ const emptyDependencies: unknown[] = [];
37
+ const defaultConfig: UseGSAPConfig = {};
38
+ let activeGSAP = gsap;
39
+
40
+ function isConfig(value: unknown): value is UseGSAPConfig {
41
+ return value !== null && !Array.isArray(value) && typeof value === 'object';
42
+ }
43
+
44
+ const useGSAPImpl = (...rawArgs: unknown[]): UseGSAPReturn => {
45
+ const [args, slot] = splitSlot(rawArgs);
46
+ let callback = args[0] as ContextFunction | UseGSAPConfig | undefined;
47
+ let dependencies: unknown[] | UseGSAPConfig | null | undefined = (
48
+ args[1] === undefined ? emptyDependencies : args[1]
49
+ ) as unknown[] | UseGSAPConfig | null;
50
+ let config = defaultConfig;
51
+
52
+ if (isConfig(callback)) {
53
+ config = callback;
54
+ callback = undefined;
55
+ dependencies = 'dependencies' in config ? config.dependencies : emptyDependencies;
56
+ } else if (isConfig(dependencies)) {
57
+ config = dependencies;
58
+ dependencies = 'dependencies' in config ? config.dependencies : emptyDependencies;
59
+ }
60
+
61
+ if (callback !== undefined && typeof callback !== 'function') {
62
+ console.warn('First parameter must be a function or config object');
63
+ }
64
+
65
+ const { scope, revertOnUpdate } = config;
66
+ const mounted = useRef(false, subSlot(slot, 'mounted'));
67
+ const context = useRef(
68
+ activeGSAP.context(() => {}, scope),
69
+ subSlot(slot, 'context'),
70
+ );
71
+ const contextSafe = useRef<ContextSafeFunction>(
72
+ (func) =>
73
+ (context.current.add as unknown as (name: null, callback: AnyFunction) => AnyFunction)(
74
+ null,
75
+ func,
76
+ ) as typeof func,
77
+ subSlot(slot, 'context-safe'),
78
+ );
79
+ const resolvedDependencies = dependencies as unknown[] | null | undefined;
80
+ const deferCleanup =
81
+ resolvedDependencies != null && resolvedDependencies.length > 0 && revertOnUpdate !== true;
82
+
83
+ useIsomorphicLayoutEffect(
84
+ () => {
85
+ mounted.current = true;
86
+ return () => context.current.revert();
87
+ },
88
+ emptyDependencies,
89
+ subSlot(slot, 'mount-cleanup'),
90
+ );
91
+
92
+ useIsomorphicLayoutEffect(
93
+ () => {
94
+ if (typeof callback === 'function') context.current.add(callback, scope);
95
+ if (!deferCleanup || !mounted.current) {
96
+ return () => context.current.revert();
97
+ }
98
+ },
99
+ resolvedDependencies,
100
+ subSlot(slot, 'callback'),
101
+ );
102
+
103
+ return {
104
+ context: context.current,
105
+ contextSafe: contextSafe.current,
106
+ };
107
+ };
108
+
109
+ export const useGSAP = useGSAPImpl as UseGSAP;
110
+
111
+ useGSAP.register = (core) => {
112
+ activeGSAP = core;
113
+ };
114
+ useGSAP.headless = true;
@@ -0,0 +1,24 @@
1
+ const subSlotCache = new Map<symbol, Map<string, symbol>>();
2
+ const bareSlotCache = new Map<string, symbol>();
3
+
4
+ export function subSlot(slot: symbol | undefined, tag: string): symbol {
5
+ if (slot === undefined) {
6
+ let bare = bareSlotCache.get(tag);
7
+ if (bare === undefined) bareSlotCache.set(tag, (bare = Symbol.for('@octanejs/gsap:' + tag)));
8
+ return bare;
9
+ }
10
+
11
+ let byTag = subSlotCache.get(slot);
12
+ if (byTag === undefined) subSlotCache.set(slot, (byTag = new Map()));
13
+ let derived = byTag.get(tag);
14
+ if (derived === undefined) {
15
+ byTag.set(tag, (derived = Symbol.for((slot.description ?? '@octanejs/gsap') + ':' + tag)));
16
+ }
17
+ return derived;
18
+ }
19
+
20
+ export function splitSlot(args: unknown[]): [unknown[], symbol | undefined] {
21
+ const tail = args[args.length - 1];
22
+ const slot = typeof tail === 'symbol' ? tail : undefined;
23
+ return [slot === undefined ? args : args.slice(0, -1), slot];
24
+ }
package/status.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "upstream": {
3
+ "package": "@gsap/react",
4
+ "version": "2.1.2",
5
+ "source": "https://github.com/greensock/react/tree/2.1.2",
6
+ "tagCommit": "b7f44ca767eb93390f965d6f19679e2b77be7966",
7
+ "license": "GSAP Standard License"
8
+ },
9
+ "peer": {
10
+ "package": "gsap",
11
+ "version": "3.15.0",
12
+ "license": "GSAP Standard No Charge License"
13
+ },
14
+ "surface": "Full useGSAP hook contract: callback, dependency-array and config signatures; scoped contexts; contextSafe; revertOnUpdate; register; and headless.",
15
+ "divergences": [
16
+ "The adapter imports Octane hooks and uses compiler-selected manual hook slots instead of React hooks.",
17
+ "GSAP remains an external peer dependency and is not redistributed by this MIT-licensed adapter."
18
+ ],
19
+ "ssr": "Server rendering creates stable context helpers without running GSAP effects. Client hydration activates the standard lifecycle.",
20
+ "verified": "2026-08-02",
21
+ "supportedExports": ["useGSAP"],
22
+ "unsupported": []
23
+ }