gsap-nuxt-module 1.1.8 → 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
@@ -120,6 +120,31 @@ onMounted(() => {
120
120
  </script>
121
121
  ```
122
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
+
123
148
  ## Available composables
124
149
 
125
150
  | Composable | Plugin | `nuxt.config.ts` key |
@@ -154,8 +179,7 @@ export default defineNuxtConfig({
154
179
 
155
180
  ## Cleanup
156
181
 
157
- GSAP animations are not automatically cleaned up when a component unmounts.
158
- 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.
159
183
 
160
184
  Plugin registration is handled once by the module at app startup — never call `gsap.registerPlugin()` manually.
161
185
  In components, clean up only the instances your component created (tweens, timelines, ScrollTrigger instances, Draggable instances, etc.).
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0"
6
6
  },
7
- "version": "1.1.8",
7
+ "version": "1.1.9",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -1,10 +1,43 @@
1
1
  import { gsap } from 'gsap';
2
2
  import type { ComputedRef, Ref, WatchSource } from 'vue';
3
- type ContextSafeFn = <F extends (...args: unknown[]) => unknown>(fn: F) => F;
3
+ /**
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;
4
11
  export interface UseGsapOptions {
5
12
  scope?: Ref<HTMLElement | null> | ComputedRef<HTMLElement | null>;
6
13
  dependencies?: WatchSource | WatchSource[];
7
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';
8
41
  }
9
42
  /**
10
43
  * Zero-argument overload — returns the raw GSAP instance.
@@ -92,7 +125,7 @@ export declare const useScrollTrigger: () => {
92
125
  snapDirectional(incrementOrArray: number | number[]): ScrollTrigger.SnapDirectionalFunc;
93
126
  sort(func?: Function): ScrollTrigger[];
94
127
  update(): void;
95
- } | null;
128
+ };
96
129
  export declare const useScrollSmoother: () => {
97
130
  new (vars: ScrollSmoother.Vars): {
98
131
  readonly scrollTrigger: ScrollTrigger;
@@ -120,7 +153,7 @@ export declare const useScrollSmoother: () => {
120
153
  create(vars: ScrollSmoother.Vars): ScrollSmoother;
121
154
  get(): ScrollSmoother | undefined;
122
155
  refresh(safe?: boolean): void;
123
- } | null;
156
+ };
124
157
  export declare const useSplitText: () => {
125
158
  new (target: gsap.DOMTarget, vars?: SplitText.Vars): {
126
159
  readonly chars: Element[];
@@ -134,14 +167,14 @@ export declare const useSplitText: () => {
134
167
  split(vars: SplitText.Vars): SplitText;
135
168
  };
136
169
  create(target: gsap.DOMTarget, vars?: SplitText.Vars): SplitText;
137
- } | null;
170
+ };
138
171
  export declare const useMotionPathHelper: () => {
139
172
  new (target: gsap.DOMTarget, vars?: MotionPathHelper.Vars): {
140
173
  kill(): void;
141
174
  };
142
175
  create(target: gsap.DOMTarget, vars?: MotionPathHelper.Vars): MotionPathHelper;
143
176
  editPath(target: gsap.DOMTarget, vars?: MotionPathHelper.EditPathVars): MotionPathHelper;
144
- } | null;
177
+ };
145
178
  export declare const useDraggable: () => {
146
179
  new (target: gsap.DOMTarget, vars?: Draggable.Vars): {
147
180
  readonly autoScroll: number;
@@ -194,7 +227,7 @@ export declare const useDraggable: () => {
194
227
  get(target: gsap.DOMTarget): Draggable;
195
228
  hitTest(testObject1: Draggable.TestObject, testObject2: Draggable.TestObject, threshold?: number | string): boolean;
196
229
  timeSinceDrag(): number;
197
- } | null;
230
+ };
198
231
  export declare const useFlip: () => {
199
232
  new (): {};
200
233
  readonly version: string;
@@ -212,7 +245,7 @@ export declare const useFlip: () => {
212
245
  register(core: typeof globalThis.gsap): void;
213
246
  ElementState: typeof Flip.ElementState;
214
247
  FlipState: typeof Flip.FlipState;
215
- } | null;
248
+ };
216
249
  export declare const useObserver: () => {
217
250
  new (): {
218
251
  readonly deltaX: number;
@@ -244,14 +277,14 @@ export declare const useObserver: () => {
244
277
  create(vars: Observer.ObserverVars): Observer;
245
278
  getAll(): Observer[];
246
279
  getById(id: string): Observer | undefined;
247
- } | null;
280
+ };
248
281
  export declare const useGSDevTools: () => {
249
282
  new (target: gsap.DOMTarget, vars?: GSDevTools.Vars): {
250
283
  kill(): void;
251
284
  };
252
285
  create(vars?: GSDevTools.Vars): GSDevTools;
253
286
  getById(id: string): GSDevTools | null;
254
- } | null;
287
+ };
255
288
  export declare const useCustomEase: () => {
256
289
  new (id: string, data?: string | number[], config?: CustomEaseConfig): {
257
290
  id: string;
@@ -265,19 +298,19 @@ export declare const useCustomEase: () => {
265
298
  register(core: object): void;
266
299
  get(id: string): EaseFunction;
267
300
  getSVGData(ease: CustomEase | EaseFunction | string, config?: CustomEaseConfig): string;
268
- } | null;
301
+ };
269
302
  export declare const useCustomWiggle: () => {
270
303
  new (id: string, vars?: CustomWiggleVars): {
271
304
  ease: EaseFunction;
272
305
  };
273
306
  create(id: string, vars?: CustomWiggleVars): EaseFunction;
274
307
  register(core: object): void;
275
- } | null;
308
+ };
276
309
  export declare const useCustomBounce: () => {
277
310
  new (id: string, vars?: CustomBounceVars): {
278
311
  ease: EaseFunction;
279
312
  };
280
313
  create(id: string, vars?: CustomBounceVars): EaseFunction;
281
314
  register(core: object): void;
282
- } | null;
315
+ };
283
316
  export {};
@@ -1,5 +1,7 @@
1
+ import { useNuxtApp } from "#app";
1
2
  import { gsap } from "gsap";
2
3
  import { onMounted, onScopeDispose, watch } from "vue";
4
+ import { onBeforeRouteLeave } from "vue-router";
3
5
  import { createGsapComposable } from "../create-gsap-composable.js";
4
6
  export function useGsap(setup, options) {
5
7
  if (!setup) return gsap;
@@ -7,31 +9,47 @@ export function useGsap(setup, options) {
7
9
  return { contextSafe: (fn) => fn };
8
10
  }
9
11
  let ctx = null;
12
+ let isLeavingViaRoute = false;
10
13
  const runSetup = () => {
11
14
  const scope = options?.scope?.value ?? void 0;
12
15
  ctx = gsap.context(setup, scope);
13
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
+ });
14
25
  onMounted(() => {
15
26
  runSetup();
16
27
  });
17
28
  if (options?.dependencies !== void 0) {
18
29
  const deps = Array.isArray(options.dependencies) ? options.dependencies : [options.dependencies];
19
- watch(deps, () => {
20
- if (options.revertOnUpdate === false) return;
21
- ctx?.revert();
22
- runSetup();
23
- }, { flush: "post" });
30
+ watch(
31
+ deps,
32
+ () => {
33
+ if (options.revertOnUpdate === false) return;
34
+ ctx?.revert();
35
+ runSetup();
36
+ },
37
+ { flush: "post" }
38
+ );
24
39
  }
25
40
  onScopeDispose(() => {
26
- ctx?.revert();
27
- ctx = null;
41
+ if (!isLeavingViaRoute) {
42
+ ctx?.revert();
43
+ ctx = null;
44
+ }
28
45
  });
29
46
  const contextSafe = (fn) => {
30
47
  return ((...args) => {
31
48
  if (ctx) {
32
- return ctx.add(() => fn(...args));
49
+ ctx.add(() => fn(...args));
50
+ } else {
51
+ fn(...args);
33
52
  }
34
- return fn(...args);
35
53
  });
36
54
  };
37
55
  return { contextSafe };
@@ -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.8",
3
+ "version": "1.1.9",
4
4
  "description": "GSAP integration for Nuxt.",
5
5
  "repository": "LucaArgentieri/gsap-nuxt-module",
6
6
  "author": "Luca Argentieri",
@@ -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')