gsap-nuxt-module 1.1.7 → 1.1.9

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/README.md CHANGED
@@ -18,7 +18,8 @@ Find and replace all on all files (CMD+SHIFT+F):
18
18
 
19
19
  **Enhance your Nuxt application with powerful animations and transitions using GSAP!**
20
20
 
21
- - [🏀 Online playground](https://stackblitz.com/github/LucaArgentieri/gsap-nuxt-module?file=playground%2Fapp.vue)
21
+ - [📖 Documentation](https://lucaargentieri.github.io/gsap-nuxt-module/)
22
+ - [🏀 Online playground](https://stackblitz.com/edit/gsap-nuxt-module?file=README.md)
22
23
  - [📖 GSAP](https://gsap.com/)
23
24
 
24
25
  ## Features
@@ -119,6 +120,31 @@ onMounted(() => {
119
120
  </script>
120
121
  ```
121
122
 
123
+ ### Context form (recommended)
124
+
125
+ Pass a `setup` function to wrap animations in a [`gsap.context()`](https://gsap.com/docs/v3/GSAP/gsap.context/).
126
+ The context reverts automatically when the component unmounts — no `onUnmounted` boilerplate needed.
127
+ During page navigation, cleanup is deferred until after the leave transition finishes.
128
+
129
+ ```ts
130
+ <script setup lang="ts">
131
+ const containerRef = ref<HTMLElement | null>(null)
132
+
133
+ useGsap(() => {
134
+ gsap.from('.box', { opacity: 0, y: 30, duration: 0.6 })
135
+ }, { scope: containerRef })
136
+ </script>
137
+
138
+ <template>
139
+ <div ref="containerRef">
140
+ <div class="box">I animate in safely</div>
141
+ </div>
142
+ </template>
143
+ ```
144
+
145
+ Returns `{ contextSafe }` for wrapping event handlers that add animations after mount.
146
+ See the [full docs](https://lucaargentieri.github.io/gsap-nuxt-module/composables/use-gsap) for all options.
147
+
122
148
  ## Available composables
123
149
 
124
150
  | Composable | Plugin | `nuxt.config.ts` key |
@@ -153,11 +179,10 @@ export default defineNuxtConfig({
153
179
 
154
180
  ## Cleanup
155
181
 
156
- GSAP animations are not automatically cleaned up when a component unmounts.
157
- Use `onUnmounted` to prevent memory leaks.
182
+ When using `useGsap()` with a setup function, cleanup is automatic. When using the zero-arg form, use `onUnmounted` to prevent memory leaks.
158
183
 
159
- Do not call `gsap.unregisterPlugin(...)` inside page/components: plugin registration is app-wide.
160
- In components, clean up only the instances created by that component.
184
+ Plugin registration is handled once by the module at app startup — never call `gsap.registerPlugin()` manually.
185
+ In components, clean up only the instances your component created (tweens, timelines, ScrollTrigger instances, Draggable instances, etc.).
161
186
 
162
187
  **Simple plugin instance cleanup (`Draggable`):**
163
188
 
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0"
6
6
  },
7
- "version": "1.1.7",
7
+ "version": "1.1.9",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -1,15 +1,70 @@
1
+ import { gsap } from 'gsap';
2
+ import type { ComputedRef, Ref, WatchSource } from 'vue';
1
3
  /**
2
- * Returns the GSAP instance for direct use.
4
+ * Wraps an event handler so it runs inside the active GSAP context.
5
+ *
6
+ * **Note:** return values are discarded when a context exists — `ctx.add()`
7
+ * returns `void`. Use `contextSafe` only for void side-effect handlers
8
+ * (DOM events, pointer callbacks, etc.).
9
+ */
10
+ type ContextSafeFn = <F extends (...args: unknown[]) => void>(fn: F) => F;
11
+ export interface UseGsapOptions {
12
+ scope?: Ref<HTMLElement | null> | ComputedRef<HTMLElement | null>;
13
+ dependencies?: WatchSource | WatchSource[];
14
+ revertOnUpdate?: boolean;
15
+ /**
16
+ * When to revert the GSAP context during page navigation.
17
+ *
18
+ * @deprecated This option has no behavioral effect. Both `'unmount'` and
19
+ * `'route-leave'` produce identical behavior: the GSAP context is reverted
20
+ * after the leave transition finishes, when the component unmounts via
21
+ * `onScopeDispose`. Kept for backward compatibility only.
22
+ *
23
+ * - `'unmount'` (default) — reverts after the leave transition finishes,
24
+ * when the component is unmounted via `onScopeDispose`.
25
+ * - `'route-leave'` — semantically identical to `'unmount'`.
26
+ *
27
+ * In both cases, animations continue to play during the leave transition,
28
+ * then are reverted once the component unmounts.
29
+ *
30
+ * **Note:** if a `router.beforeEach` guard rejects navigation *after*
31
+ * `onBeforeRouteLeave` is registered, the `onBeforeRouteLeave` hook will still
32
+ * have fired and set `isLeavingViaRoute` to `true`, but the component will not unmount.
33
+ * The context remains active and animations continue until the next successful
34
+ * navigation.
35
+ *
36
+ * @example
37
+ * // Continuous animation that should play through the page-leave transition
38
+ * useGsap(() => { ... }, { cleanupOn: 'route-leave' })
39
+ */
40
+ cleanupOn?: 'unmount' | 'route-leave';
41
+ }
42
+ /**
43
+ * Zero-argument overload — returns the raw GSAP instance.
3
44
  *
4
45
  * Use it to call `gsap.timeline()`, `gsap.to()`, `gsap.set()`,
5
46
  * utility methods (`gsap.utils`, `gsap.ticker`), and more.
6
47
  *
7
48
  * **Cleanup is the caller's responsibility.**
8
- * Use `gsap.context()` + `ctx.revert()` in `onUnmounted` to avoid memory leaks.
9
49
  *
10
50
  * @see https://gsap.com/docs/v3/GSAP/
11
51
  */
12
- export declare function useGsap(): typeof globalThis.gsap;
52
+ export declare function useGsap(): typeof gsap;
53
+ /**
54
+ * Setup-function overload — wraps `gsap.context()` with automatic revert.
55
+ *
56
+ * Animations declared inside `setup` are scoped to the optional `scope` element
57
+ * and are reverted automatically when the component unmounts (or the effect scope
58
+ * is disposed). Pass `dependencies` to re-run `setup` reactively.
59
+ *
60
+ * Returns `{ contextSafe }` — a wrapper for event handlers that need to add
61
+ * animations to the existing context after mount.
62
+ *
63
+ * @see https://gsap.com/docs/v3/GSAP/gsap.context()
64
+ */
65
+ export declare function useGsap(setup: (ctx: gsap.Context) => void, options?: UseGsapOptions): {
66
+ contextSafe: ContextSafeFn;
67
+ };
13
68
  export declare const useScrollTrigger: () => {
14
69
  new (vars: ScrollTrigger.StaticVars, animation?: gsap.core.Animation): {
15
70
  readonly animation?: gsap.core.Animation | undefined;
@@ -70,7 +125,7 @@ export declare const useScrollTrigger: () => {
70
125
  snapDirectional(incrementOrArray: number | number[]): ScrollTrigger.SnapDirectionalFunc;
71
126
  sort(func?: Function): ScrollTrigger[];
72
127
  update(): void;
73
- } | null;
128
+ };
74
129
  export declare const useScrollSmoother: () => {
75
130
  new (vars: ScrollSmoother.Vars): {
76
131
  readonly scrollTrigger: ScrollTrigger;
@@ -98,7 +153,7 @@ export declare const useScrollSmoother: () => {
98
153
  create(vars: ScrollSmoother.Vars): ScrollSmoother;
99
154
  get(): ScrollSmoother | undefined;
100
155
  refresh(safe?: boolean): void;
101
- } | null;
156
+ };
102
157
  export declare const useSplitText: () => {
103
158
  new (target: gsap.DOMTarget, vars?: SplitText.Vars): {
104
159
  readonly chars: Element[];
@@ -112,14 +167,14 @@ export declare const useSplitText: () => {
112
167
  split(vars: SplitText.Vars): SplitText;
113
168
  };
114
169
  create(target: gsap.DOMTarget, vars?: SplitText.Vars): SplitText;
115
- } | null;
170
+ };
116
171
  export declare const useMotionPathHelper: () => {
117
172
  new (target: gsap.DOMTarget, vars?: MotionPathHelper.Vars): {
118
173
  kill(): void;
119
174
  };
120
175
  create(target: gsap.DOMTarget, vars?: MotionPathHelper.Vars): MotionPathHelper;
121
176
  editPath(target: gsap.DOMTarget, vars?: MotionPathHelper.EditPathVars): MotionPathHelper;
122
- } | null;
177
+ };
123
178
  export declare const useDraggable: () => {
124
179
  new (target: gsap.DOMTarget, vars?: Draggable.Vars): {
125
180
  readonly autoScroll: number;
@@ -172,7 +227,7 @@ export declare const useDraggable: () => {
172
227
  get(target: gsap.DOMTarget): Draggable;
173
228
  hitTest(testObject1: Draggable.TestObject, testObject2: Draggable.TestObject, threshold?: number | string): boolean;
174
229
  timeSinceDrag(): number;
175
- } | null;
230
+ };
176
231
  export declare const useFlip: () => {
177
232
  new (): {};
178
233
  readonly version: string;
@@ -190,7 +245,7 @@ export declare const useFlip: () => {
190
245
  register(core: typeof globalThis.gsap): void;
191
246
  ElementState: typeof Flip.ElementState;
192
247
  FlipState: typeof Flip.FlipState;
193
- } | null;
248
+ };
194
249
  export declare const useObserver: () => {
195
250
  new (): {
196
251
  readonly deltaX: number;
@@ -222,14 +277,14 @@ export declare const useObserver: () => {
222
277
  create(vars: Observer.ObserverVars): Observer;
223
278
  getAll(): Observer[];
224
279
  getById(id: string): Observer | undefined;
225
- } | null;
280
+ };
226
281
  export declare const useGSDevTools: () => {
227
282
  new (target: gsap.DOMTarget, vars?: GSDevTools.Vars): {
228
283
  kill(): void;
229
284
  };
230
285
  create(vars?: GSDevTools.Vars): GSDevTools;
231
286
  getById(id: string): GSDevTools | null;
232
- } | null;
287
+ };
233
288
  export declare const useCustomEase: () => {
234
289
  new (id: string, data?: string | number[], config?: CustomEaseConfig): {
235
290
  id: string;
@@ -243,18 +298,19 @@ export declare const useCustomEase: () => {
243
298
  register(core: object): void;
244
299
  get(id: string): EaseFunction;
245
300
  getSVGData(ease: CustomEase | EaseFunction | string, config?: CustomEaseConfig): string;
246
- } | null;
301
+ };
247
302
  export declare const useCustomWiggle: () => {
248
303
  new (id: string, vars?: CustomWiggleVars): {
249
304
  ease: EaseFunction;
250
305
  };
251
306
  create(id: string, vars?: CustomWiggleVars): EaseFunction;
252
307
  register(core: object): void;
253
- } | null;
308
+ };
254
309
  export declare const useCustomBounce: () => {
255
310
  new (id: string, vars?: CustomBounceVars): {
256
311
  ease: EaseFunction;
257
312
  };
258
313
  create(id: string, vars?: CustomBounceVars): EaseFunction;
259
314
  register(core: object): void;
260
- } | null;
315
+ };
316
+ export {};
@@ -1,7 +1,58 @@
1
+ import { useNuxtApp } from "#app";
1
2
  import { gsap } from "gsap";
3
+ import { onMounted, onScopeDispose, watch } from "vue";
4
+ import { onBeforeRouteLeave } from "vue-router";
2
5
  import { createGsapComposable } from "../create-gsap-composable.js";
3
- export function useGsap() {
4
- return gsap;
6
+ export function useGsap(setup, options) {
7
+ if (!setup) return gsap;
8
+ if (import.meta.server) {
9
+ return { contextSafe: (fn) => fn };
10
+ }
11
+ let ctx = null;
12
+ let isLeavingViaRoute = false;
13
+ const runSetup = () => {
14
+ const scope = options?.scope?.value ?? void 0;
15
+ ctx = gsap.context(setup, scope);
16
+ };
17
+ onBeforeRouteLeave(() => {
18
+ isLeavingViaRoute = true;
19
+ const nuxtApp = useNuxtApp();
20
+ nuxtApp.hooks.hookOnce("page:transition:finish", () => {
21
+ ctx?.revert();
22
+ ctx = null;
23
+ });
24
+ });
25
+ onMounted(() => {
26
+ runSetup();
27
+ });
28
+ if (options?.dependencies !== void 0) {
29
+ const deps = Array.isArray(options.dependencies) ? options.dependencies : [options.dependencies];
30
+ watch(
31
+ deps,
32
+ () => {
33
+ if (options.revertOnUpdate === false) return;
34
+ ctx?.revert();
35
+ runSetup();
36
+ },
37
+ { flush: "post" }
38
+ );
39
+ }
40
+ onScopeDispose(() => {
41
+ if (!isLeavingViaRoute) {
42
+ ctx?.revert();
43
+ ctx = null;
44
+ }
45
+ });
46
+ const contextSafe = (fn) => {
47
+ return ((...args) => {
48
+ if (ctx) {
49
+ ctx.add(() => fn(...args));
50
+ } else {
51
+ fn(...args);
52
+ }
53
+ });
54
+ };
55
+ return { contextSafe };
5
56
  }
6
57
  export const useScrollTrigger = createGsapComposable("ScrollTrigger");
7
58
  export const useScrollSmoother = createGsapComposable("ScrollSmoother");
@@ -2,7 +2,8 @@
2
2
  * Factory that creates a composable returning a registered GSAP plugin.
3
3
  *
4
4
  * The returned composable reads the plugin from `nuxtApp` at call time.
5
- * Returns `null` during SSR (plugin is client-only).
5
+ * This is a client-only composable — it is safe to call on the server but
6
+ * will return an inert value (the plugin is never registered server-side).
6
7
  * Throws on the client if the plugin was not enabled in `nuxt.config.ts`.
7
8
  *
8
9
  * **Cleanup is the caller's responsibility.**
@@ -12,4 +13,4 @@
12
13
  * @param pluginName - Name of the GSAP plugin as registered in nuxtApp.
13
14
  * @param fallbackMessage - Optional custom error message for missing plugin.
14
15
  */
15
- export declare function createGsapComposable<T>(pluginName: string, fallbackMessage?: string): () => T | null;
16
+ export declare function createGsapComposable<T>(pluginName: string, fallbackMessage?: string): () => NonNullable<T>;
@@ -1,3 +1,7 @@
1
+ /**
2
+ * This object contains lazy-loaded GSAP plugins for use in the application.
3
+ *
4
+ */
1
5
  export declare const gsapPlugins: {
2
6
  ScrollTrigger: () => Promise<{
3
7
  new (vars: ScrollTrigger.StaticVars, animation?: gsap.core.Animation): {
@@ -1,4 +1,3 @@
1
- import { loadDraggable, loadFlip, loadObserver } from "./gsap-loaders.js";
2
1
  export const gsapPlugins = {
3
2
  // Scroll Plugins
4
3
  ScrollTrigger: () => import("gsap/ScrollTrigger").then((mod) => mod.ScrollTrigger),
@@ -14,10 +13,10 @@ export const gsapPlugins = {
14
13
  MotionPathPlugin: () => import("gsap/MotionPathPlugin").then((mod) => mod.MotionPathPlugin),
15
14
  MotionPathHelper: () => import("gsap/MotionPathHelper").then((mod) => mod.MotionPathHelper),
16
15
  // UI Plugins
17
- Flip: () => loadFlip().then((mod) => mod.Flip),
18
- Draggable: () => loadDraggable().then((mod) => mod.Draggable),
16
+ Flip: () => import("gsap/Flip").then((mod) => mod.Flip),
17
+ Draggable: () => import("gsap/Draggable").then((mod) => mod.Draggable),
19
18
  InertiaPlugin: () => import("gsap/InertiaPlugin").then((mod) => mod.InertiaPlugin),
20
- Observer: () => loadObserver().then((mod) => mod.Observer),
19
+ Observer: () => import("gsap/Observer").then((mod) => mod.Observer),
21
20
  // Other Plugins
22
21
  PixiPlugin: () => import("gsap/PixiPlugin").then((mod) => mod.PixiPlugin),
23
22
  EaselPlugin: () => import("gsap/EaselPlugin").then((mod) => mod.EaselPlugin),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsap-nuxt-module",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "description": "GSAP integration for Nuxt.",
5
5
  "repository": "LucaArgentieri/gsap-nuxt-module",
6
6
  "author": "Luca Argentieri",
@@ -29,6 +29,7 @@
29
29
  "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
30
30
  "release": "npm run lint && npm run prepack && changelogen --release --patch && npm publish && git push --follow-tags",
31
31
  "lint": "eslint .",
32
+ "lint:fix": "eslint . --fix",
32
33
  "test": "vitest run",
33
34
  "test:watch": "vitest watch",
34
35
  "test:types": "vue-tsc --noEmit && nuxi prepare playground && cd playground && vue-tsc --noEmit"
@@ -1,3 +0,0 @@
1
- export function loadFlip(): Promise<typeof import("gsap/Flip")>;
2
- export function loadDraggable(): Promise<typeof import("gsap/Draggable")>;
3
- export function loadObserver(): Promise<typeof import("gsap/Observer")>;
@@ -1,7 +0,0 @@
1
- // @ts-nocheck
2
-
3
- export const loadFlip = () => import('gsap/Flip')
4
-
5
- export const loadDraggable = () => import('gsap/Draggable')
6
-
7
- export const loadObserver = () => import('gsap/Observer')