@ssgoi/react 3.2.1 → 3.3.0

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
@@ -106,14 +106,14 @@ Animate specific elements during mount/unmount:
106
106
 
107
107
  ```tsx
108
108
  import { transition } from '@ssgoi/react';
109
- import { fadeIn, slideUp } from '@ssgoi/react/transitions';
109
+ import { fade, slide } from '@ssgoi/react/transitions';
110
110
 
111
111
  function Card() {
112
112
  return (
113
113
  <div ref={transition({
114
114
  key: 'card',
115
- in: fadeIn(),
116
- out: slideUp()
115
+ in: fade(),
116
+ out: slide({ direction: 'up' })
117
117
  })}>
118
118
  <h2>Animated Card</h2>
119
119
  </div>
@@ -121,6 +121,111 @@ function Card() {
121
121
  }
122
122
  ```
123
123
 
124
+ ### Auto Key Plugin
125
+
126
+ The Auto Key Plugin automatically generates unique keys for your transitions based on the file location (`file:line:column`), eliminating the need to manually provide keys.
127
+
128
+ **Benefits:**
129
+ - **Automatic Key Generation**: No need to manually specify `key` in `transition()` calls
130
+ - **Collision-Free**: Keys are based on exact code location
131
+ - **Cleaner Code**: Less boilerplate in your components
132
+
133
+ **⚠️ Important**: For list items rendered with `.map()`, always provide an explicit `key` for stability. The plugin cannot guarantee stable keys for dynamically generated elements.
134
+
135
+ #### Setup with Next.js
136
+
137
+ ```tsx
138
+ // next.config.ts
139
+ import type { NextConfig } from "next";
140
+ import SsgoiAutoKey from "@ssgoi/react/unplugin/webpack";
141
+
142
+ const nextConfig: NextConfig = {
143
+ webpack: (config) => {
144
+ config.plugins.push(SsgoiAutoKey());
145
+ return config;
146
+ },
147
+ };
148
+
149
+ export default nextConfig;
150
+ ```
151
+
152
+ #### Setup with Vite
153
+
154
+ ```ts
155
+ // vite.config.ts
156
+ import { defineConfig } from 'vite';
157
+ import react from '@vitejs/plugin-react';
158
+ import SsgoiAutoKey from "@ssgoi/react/unplugin/vite";
159
+
160
+ export default defineConfig({
161
+ plugins: [react(), SsgoiAutoKey()],
162
+ });
163
+ ```
164
+
165
+ #### Usage Examples
166
+
167
+ **WITH Auto Key Plugin (Recommended):**
168
+ ```tsx
169
+ import { transition } from '@ssgoi/react';
170
+ import { fade, slide } from '@ssgoi/react/transitions';
171
+
172
+ function Card() {
173
+ return (
174
+ <div ref={transition({ in: fade(), out: slide({ direction: 'up' }) })}>
175
+ <h2>Animated Card</h2>
176
+ </div>
177
+ );
178
+ }
179
+
180
+ // Even simpler with single transition
181
+ function SimpleCard() {
182
+ return (
183
+ <div ref={transition(fade())}>
184
+ <h2>Fades in on mount</h2>
185
+ </div>
186
+ );
187
+ }
188
+ ```
189
+
190
+ **WITHOUT Auto Key Plugin:**
191
+ ```tsx
192
+ // Explicit key required for transition state tracking
193
+ function Card() {
194
+ return (
195
+ <div ref={transition({
196
+ key: 'my-card',
197
+ in: fade(),
198
+ out: slide({ direction: 'up' })
199
+ })}>
200
+ <h2>Animated Card</h2>
201
+ </div>
202
+ );
203
+ }
204
+ ```
205
+
206
+ **List Items (Always Use Explicit Keys):**
207
+ ```tsx
208
+ // For list items, always provide explicit keys for stability
209
+ function List() {
210
+ return (
211
+ <ul>
212
+ {items.map((item) => (
213
+ <li
214
+ key={item.id}
215
+ ref={transition({
216
+ key: `list-item-${item.id}`,
217
+ in: fade(),
218
+ out: slide({ direction: 'up' })
219
+ })}
220
+ >
221
+ {item.name}
222
+ </li>
223
+ ))}
224
+ </ul>
225
+ );
226
+ }
227
+ ```
228
+
124
229
  ## Next.js App Router Example
125
230
 
126
231
  ```tsx
@@ -195,8 +300,8 @@ Apply transitions to individual elements.
195
300
  ```tsx
196
301
  <div ref={transition({
197
302
  key: 'unique-key',
198
- in: fadeIn(),
199
- out: fadeOut()
303
+ in: fade(),
304
+ out: fade()
200
305
  })}>
201
306
  Content
202
307
  </div>
@@ -212,12 +317,13 @@ Apply transitions to individual elements.
212
317
  - `pinterest()` - Pinterest-style expand effect
213
318
 
214
319
  ### Element Transitions (`@ssgoi/react/transitions`)
215
- - `fadeIn()` / `fadeOut()`
216
- - `slideUp()` / `slideDown()` / `slideLeft()` / `slideRight()`
217
- - `scaleIn()` / `scaleOut()`
218
- - `bounce()`
219
- - `blur()`
220
- - `rotate()`
320
+ - `fade()` - Fade in/out
321
+ - `scale()` - Scale in/out
322
+ - `slide()` - Slide (direction: up/down/left/right)
323
+ - `rotate()` - Rotate
324
+ - `bounce()` - Bounce
325
+ - `blur()` - Blur
326
+ - `fly()` - Fly (custom x, y position)
221
327
 
222
328
  ## Spring Physics Configuration
223
329
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,2 @@
1
- export { transition } from './transition';
2
- export * from './types';
3
- export { Ssgoi } from './ssgoi';
4
- export { SsgoiTransition } from './ssgoi-transition';
5
- export { TransitionScope } from './transition-scope';
1
+ export * from './lib/index'
2
+ export {}
@@ -0,0 +1,5 @@
1
+ export { transition } from './transition';
2
+ export * from './types';
3
+ export { Ssgoi } from './ssgoi';
4
+ export { SsgoiTransition } from './ssgoi-transition';
5
+ export { TransitionScope } from './transition-scope';
@@ -0,0 +1 @@
1
+ export * from '@ssgoi/core/presets';
@@ -2,7 +2,6 @@ import { Transition, TransitionKey, TransitionScope } from '@ssgoi/core';
2
2
  type TransitionOptions = Transition<undefined> & {
3
3
  key?: TransitionKey;
4
4
  scope?: TransitionScope;
5
- ref?: object;
6
5
  };
7
6
  /**
8
7
  * React ref callback for element transitions
@@ -10,6 +9,12 @@ type TransitionOptions = Transition<undefined> & {
10
9
  * OUT transition is automatically triggered by MutationObserver
11
10
  * when the element is removed from the DOM - works with React 18+
12
11
  *
12
+ * The returned ref callback has a stable reference (cached by key),
13
+ * so it won't cause unnecessary re-renders when used with React refs.
14
+ *
15
+ * Note: key is optional in the type signature because it can be auto-injected
16
+ * by babel-plugin-ssgoi at build time. However, it is required at runtime.
17
+ *
13
18
  * @example
14
19
  * ```tsx
15
20
  * <div ref={transition({
@@ -19,5 +24,5 @@ type TransitionOptions = Transition<undefined> & {
19
24
  * })} />
20
25
  * ```
21
26
  */
22
- export declare const transition: (options: TransitionOptions) => (element: HTMLElement | null) => void;
27
+ export declare const transition: (options: TransitionOptions) => import('@ssgoi/core').RefCallback;
23
28
  export {};
@@ -0,0 +1 @@
1
+ export * from '@ssgoi/core/transitions';
@@ -0,0 +1 @@
1
+ export * from '@ssgoi/core/types';
@@ -0,0 +1 @@
1
+ export * from '@ssgoi/core/view-transitions';
@@ -1 +1,2 @@
1
- export * from '@ssgoi/core/presets';
1
+ export * from '../lib/presets/index'
2
+ export {}
@@ -1,2 +1,2 @@
1
1
  "use client";
2
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("@ssgoi/core"),c=i=>{const r=o.transition(i);return t=>{if(t){const n=r(t);n&&o.watchUnmount(t,n)}}};exports.transition=c;
2
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("@ssgoi/core"),i=r=>{if(!r.key)throw new Error("[ssgoi] transition() requires a 'key' property. Either provide it manually or use plugin for auto-injection. import plugin from @ssgoi/react/unplugin/webpack; ");return t.transition(r,"auto")};exports.transition=i;
@@ -1,14 +1,15 @@
1
1
  "use client";
2
- import { transition as a, watchUnmount as r } from "@ssgoi/core";
3
- const s = (o) => {
4
- const i = a(o);
5
- return (t) => {
6
- if (t) {
7
- const n = i(t);
8
- n && r(t, n);
9
- }
10
- };
2
+ import { transition as i } from "@ssgoi/core";
3
+ const o = (r) => {
4
+ if (!r.key)
5
+ throw new Error(
6
+ "[ssgoi] transition() requires a 'key' property. Either provide it manually or use plugin for auto-injection. import plugin from @ssgoi/react/unplugin/webpack; "
7
+ );
8
+ return i(
9
+ r,
10
+ "auto"
11
+ );
11
12
  };
12
13
  export {
13
- s as transition
14
+ o as transition
14
15
  };
@@ -1 +1,2 @@
1
- export * from '@ssgoi/core/transitions';
1
+ export * from '../lib/transitions/index'
2
+ export {}
package/dist/types.d.ts CHANGED
@@ -1 +1,2 @@
1
- export * from '@ssgoi/core/types';
1
+ export * from './lib/types'
2
+ export {}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./index.cjs"),u=e.unplugin.esbuild;exports.default=u;
@@ -0,0 +1,2 @@
1
+ declare const _default: (options?: import('./index').SsgoiAutoKeyOptions | undefined) => import('esbuild').Plugin;
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import { unplugin as u } from "./index.js";
2
+ const i = u.esbuild;
3
+ export {
4
+ i as default
5
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const S=require("unplugin"),j=require("@babel/core"),P={include:[".tsx",".jsx"],exclude:[/node_modules/]};function h(){return function({types:e}){return{name:"babel-plugin-ssgoi-auto-key",visitor:{CallExpression(r,l){const n=r.node.callee;if(!e.isIdentifier(n)||n.name!=="transition"||r.node._ssgoiProcessed)return;r.node._ssgoiProcessed=!0;const a=r.node.arguments;if(a.length===0)return;const s=a[0];if(e.isObjectExpression(s)&&s.properties.length>=2&&e.isSpreadElement(s.properties[0])&&e.isObjectProperty(s.properties[s.properties.length-1])&&e.isIdentifier(s.properties[s.properties.length-1].key)&&s.properties[s.properties.length-1].key.name==="key")return;const d=l.filename||"unknown",x=d.split("/").pop()||d,m=r.node.loc,b=m?.start?.line??0,k=m?.start?.column??0,c=`${x}:${b}:${k}`;let u=null;const p=r.findParent(i=>i.isJSXElement()||i.isJSXFragment());if(p&&e.isJSXElement(p.node)){const i=p.node.openingElement.attributes.find(t=>e.isJSXAttribute(t)&&e.isJSXIdentifier(t.name)&&t.name.name==="key");i&&(e.isJSXExpressionContainer(i.value)?e.isJSXEmptyExpression(i.value.expression)||(u=i.value.expression):e.isStringLiteral(i.value)&&(u=i.value))}let f;u?f=e.templateLiteral([e.templateElement({raw:`${c}:`,cooked:`${c}:`}),e.templateElement({raw:"",cooked:""},!0)],[u]):f=e.stringLiteral(c);const g=e.objectProperty(e.identifier("key"),f);if(e.isObjectExpression(s)){if(s.properties.some(t=>e.isObjectProperty(t)&&e.isIdentifier(t.key)&&t.key.name==="key"))return;s.properties.push(g)}else e.isExpression(s)&&!e.isSpreadElement(s)&&(a[0]=e.objectExpression([e.spreadElement(s),g]))}}}}}function y(o,e){return e.include.some(n=>o.endsWith(n))?!e.exclude.some(n=>typeof n=="string"?o.includes(n):n.test(o)):!1}const v=o=>{const e={...P,...o};return{name:"unplugin-ssgoi-auto-key",enforce:"pre",transformInclude(r){return y(r,e)},transform(r,l){if(!y(l,e))return null;try{const n=j.transformSync(r,{filename:l,plugins:[h()],parserOpts:{plugins:["jsx","typescript"]},generatorOpts:{retainLines:!0},sourceMaps:!0,configFile:!1,babelrc:!1});if(n&&n.code)return{code:n.code,map:n.map}}catch(n){console.error(`[unplugin-ssgoi-auto-key] Error transforming ${l}:`,n)}return null}}},E=S.createUnplugin(v);exports.default=E;exports.unplugin=E;
@@ -0,0 +1,14 @@
1
+ export interface SsgoiAutoKeyOptions {
2
+ /**
3
+ * File extensions to process
4
+ * @default ['.tsx', '.jsx']
5
+ */
6
+ include?: string[];
7
+ /**
8
+ * File patterns to exclude
9
+ * @default [/node_modules/]
10
+ */
11
+ exclude?: (string | RegExp)[];
12
+ }
13
+ export declare const unplugin: import('unplugin').UnpluginInstance<SsgoiAutoKeyOptions | undefined, boolean>;
14
+ export default unplugin;
@@ -0,0 +1,116 @@
1
+ import { createUnplugin as b } from "unplugin";
2
+ import { transformSync as S } from "@babel/core";
3
+ const j = {
4
+ include: [".tsx", ".jsx"],
5
+ exclude: [/node_modules/]
6
+ };
7
+ function P() {
8
+ return function({
9
+ types: e
10
+ }) {
11
+ return {
12
+ name: "babel-plugin-ssgoi-auto-key",
13
+ visitor: {
14
+ CallExpression(r, l) {
15
+ const n = r.node.callee;
16
+ if (!e.isIdentifier(n) || n.name !== "transition" || r.node._ssgoiProcessed)
17
+ return;
18
+ r.node._ssgoiProcessed = !0;
19
+ const a = r.node.arguments;
20
+ if (a.length === 0)
21
+ return;
22
+ const s = a[0];
23
+ if (e.isObjectExpression(s) && s.properties.length >= 2 && e.isSpreadElement(s.properties[0]) && e.isObjectProperty(
24
+ s.properties[s.properties.length - 1]
25
+ ) && e.isIdentifier(
26
+ s.properties[s.properties.length - 1].key
27
+ ) && s.properties[s.properties.length - 1].key.name === "key")
28
+ return;
29
+ const m = l.filename || "unknown", x = m.split("/").pop() || m, d = r.node.loc, E = d?.start?.line ?? 0, k = d?.start?.column ?? 0, c = `${x}:${E}:${k}`;
30
+ let u = null;
31
+ const p = r.findParent(
32
+ (i) => i.isJSXElement() || i.isJSXFragment()
33
+ );
34
+ if (p && e.isJSXElement(p.node)) {
35
+ const i = p.node.openingElement.attributes.find(
36
+ (t) => e.isJSXAttribute(t) && e.isJSXIdentifier(t.name) && t.name.name === "key"
37
+ );
38
+ i && (e.isJSXExpressionContainer(i.value) ? e.isJSXEmptyExpression(i.value.expression) || (u = i.value.expression) : e.isStringLiteral(i.value) && (u = i.value));
39
+ }
40
+ let f;
41
+ u ? f = e.templateLiteral(
42
+ [
43
+ e.templateElement({
44
+ raw: `${c}:`,
45
+ cooked: `${c}:`
46
+ }),
47
+ e.templateElement({ raw: "", cooked: "" }, !0)
48
+ ],
49
+ [u]
50
+ ) : f = e.stringLiteral(c);
51
+ const g = e.objectProperty(e.identifier("key"), f);
52
+ if (e.isObjectExpression(s)) {
53
+ if (s.properties.some(
54
+ (t) => e.isObjectProperty(t) && e.isIdentifier(t.key) && t.key.name === "key"
55
+ ))
56
+ return;
57
+ s.properties.push(g);
58
+ } else e.isExpression(s) && !e.isSpreadElement(s) && (a[0] = e.objectExpression([
59
+ e.spreadElement(s),
60
+ g
61
+ ]));
62
+ }
63
+ }
64
+ };
65
+ };
66
+ }
67
+ function y(o, e) {
68
+ return e.include.some((n) => o.endsWith(n)) ? !e.exclude.some((n) => typeof n == "string" ? o.includes(n) : n.test(o)) : !1;
69
+ }
70
+ const h = (o) => {
71
+ const e = {
72
+ ...j,
73
+ ...o
74
+ };
75
+ return {
76
+ name: "unplugin-ssgoi-auto-key",
77
+ enforce: "pre",
78
+ transformInclude(r) {
79
+ return y(r, e);
80
+ },
81
+ transform(r, l) {
82
+ if (!y(l, e))
83
+ return null;
84
+ try {
85
+ const n = S(r, {
86
+ filename: l,
87
+ plugins: [P()],
88
+ parserOpts: {
89
+ plugins: ["jsx", "typescript"]
90
+ },
91
+ generatorOpts: {
92
+ retainLines: !0
93
+ },
94
+ sourceMaps: !0,
95
+ configFile: !1,
96
+ babelrc: !1
97
+ });
98
+ if (n && n.code)
99
+ return {
100
+ code: n.code,
101
+ map: n.map
102
+ };
103
+ } catch (n) {
104
+ console.error(
105
+ `[unplugin-ssgoi-auto-key] Error transforming ${l}:`,
106
+ n
107
+ );
108
+ }
109
+ return null;
110
+ }
111
+ };
112
+ }, X = /* @__PURE__ */ b(h);
113
+ export {
114
+ X as default,
115
+ X as unplugin
116
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./index.cjs"),u=e.unplugin.rollup;exports.default=u;
@@ -0,0 +1,2 @@
1
+ declare const _default: (options?: import('./index').SsgoiAutoKeyOptions | undefined) => import('rollup').Plugin<any> | import('rollup').Plugin<any>[];
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import { unplugin as l } from "./index.js";
2
+ const p = l.rollup;
3
+ export {
4
+ p as default
5
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./index.cjs"),t=e.unplugin.vite;exports.default=t;
@@ -0,0 +1,2 @@
1
+ declare const _default: (options?: import('./index').SsgoiAutoKeyOptions | undefined) => import('vite').Plugin<any> | import('vite').Plugin<any>[];
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import { unplugin as t } from "./index.js";
2
+ const i = t.vite;
3
+ export {
4
+ i as default
5
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./index.cjs"),u=e.unplugin.webpack;exports.default=u;
@@ -0,0 +1,2 @@
1
+ declare const _default: (options?: import('./index').SsgoiAutoKeyOptions | undefined) => import('webpack').WebpackPluginInstance;
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ import { unplugin as p } from "./index.js";
2
+ const e = p.webpack;
3
+ export {
4
+ e as default
5
+ };
@@ -1 +1,2 @@
1
- export * from '@ssgoi/core/view-transitions';
1
+ export * from '../lib/view-transitions/index'
2
+ export {}
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@ssgoi/react",
3
- "version": "3.2.1",
3
+ "version": "3.3.0",
4
4
  "description": "React bindings for SSGOI - Native app-like page transitions for React applications",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "files": [
8
8
  "dist",
9
+ "plugin",
9
10
  "!dist/**/*.test.*",
10
11
  "!dist/**/*.spec.*"
11
12
  ],
@@ -42,12 +43,45 @@
42
43
  "import": "./dist/presets/index.js",
43
44
  "require": "./dist/presets/index.cjs",
44
45
  "default": "./dist/presets/index.js"
46
+ },
47
+ "./unplugin": {
48
+ "types": "./dist/unplugin/index.d.ts",
49
+ "import": "./dist/unplugin/index.js",
50
+ "require": "./dist/unplugin/index.cjs",
51
+ "default": "./dist/unplugin/index.js"
52
+ },
53
+ "./unplugin/webpack": {
54
+ "types": "./dist/unplugin/webpack.d.ts",
55
+ "import": "./dist/unplugin/webpack.js",
56
+ "require": "./dist/unplugin/webpack.cjs",
57
+ "default": "./dist/unplugin/webpack.js"
58
+ },
59
+ "./unplugin/vite": {
60
+ "types": "./dist/unplugin/vite.d.ts",
61
+ "import": "./dist/unplugin/vite.js",
62
+ "require": "./dist/unplugin/vite.cjs",
63
+ "default": "./dist/unplugin/vite.js"
64
+ },
65
+ "./unplugin/rollup": {
66
+ "types": "./dist/unplugin/rollup.d.ts",
67
+ "import": "./dist/unplugin/rollup.js",
68
+ "require": "./dist/unplugin/rollup.cjs",
69
+ "default": "./dist/unplugin/rollup.js"
70
+ },
71
+ "./unplugin/esbuild": {
72
+ "types": "./dist/unplugin/esbuild.d.ts",
73
+ "import": "./dist/unplugin/esbuild.js",
74
+ "require": "./dist/unplugin/esbuild.cjs",
75
+ "default": "./dist/unplugin/esbuild.js"
45
76
  }
46
77
  },
47
78
  "dependencies": {
48
- "@ssgoi/core": "^3.2.0"
79
+ "unplugin": "^2.3.2",
80
+ "@babel/core": "^7.26.10",
81
+ "@ssgoi/core": "^3.3.0"
49
82
  },
50
83
  "devDependencies": {
84
+ "@types/babel__core": "^7.20.5",
51
85
  "@eslint/js": "^9.30.1",
52
86
  "@types/react": "^19.1.8",
53
87
  "@types/react-dom": "^19.1.6",
File without changes
File without changes