diff --git a/packages/docz/package.json b/packages/docz/package.json
index 4ab8b9a..d210aab 100644
--- a/packages/docz/package.json
+++ b/packages/docz/package.json
@@ -23,7 +23,7 @@
"gsap": "^3.2.6",
"react": "^16.8.6",
"react-dom": "^16.8.6",
- "react-gsap": "3.0.0"
+ "react-gsap": "3.1.0"
},
"devDependencies": {
"@types/react": "^16.8.23",
diff --git a/packages/docz/src/components/Timeline.mdx b/packages/docz/src/components/Timeline.mdx
index 90c571d..4b2d4de 100644
--- a/packages/docz/src/components/Timeline.mdx
+++ b/packages/docz/src/components/Timeline.mdx
@@ -87,7 +87,7 @@ In this way these component can be better reused and the refs not only work in a
You can also pass an array ref like seen with div2. In this way you can use the `stagger` prop.
```javascript
-const TargetWithNames = forwardRef((props, ref: any) => {
+const TargetWithNames = forwardRef((props, ref) => {
const div1 = useRef(null);
const div2 = useRef([]);
const div3 = useRef(null);
@@ -112,6 +112,26 @@ const TargetWithNames = forwardRef((props, ref: any) => {
```
+If you want to combine multiple of those named components, you can do it like this:
+
+```javascript
+const TargetWithNamesCombined = forwardRef((props, ref) => {
+ const target1 = useRef({});
+ const target2 = useRef({});
+ useImperativeHandle(ref, () => ({
+ ...target1.current,
+ ...target2.current,
+ }));
+ return (
+ <>
+
+
+ >
+ );
+});
+
+```
+
For version < 3:
If you need to target individual elements you can use a special forwardRef function.
diff --git a/packages/next/package.json b/packages/next/package.json
index 355a7d2..31d8729 100644
--- a/packages/next/package.json
+++ b/packages/next/package.json
@@ -12,7 +12,7 @@
"next": "^9.5.1",
"react": "^16.12.0",
"react-dom": "^16.12.0",
- "react-gsap": "3.0.0"
+ "react-gsap": "3.1.0"
},
"devDependencies": {
"@types/node": "^14.14.22",
diff --git a/packages/playground/package.json b/packages/playground/package.json
index 91e90e0..e8de21d 100644
--- a/packages/playground/package.json
+++ b/packages/playground/package.json
@@ -17,7 +17,7 @@
"gsap": "^3.2.6",
"react": "^16.13.1",
"react-dom": "^16.13.1",
- "react-gsap": "3.0.0",
+ "react-gsap": "3.1.0",
"react-router-dom": "^5.1.2",
"react-scripts": "4.0.1",
"react-transition-group": "^4.3.0",
diff --git a/packages/playground/src/examples/Timeline.tsx b/packages/playground/src/examples/Timeline.tsx
index ba3f6e5..d5446f2 100644
--- a/packages/playground/src/examples/Timeline.tsx
+++ b/packages/playground/src/examples/Timeline.tsx
@@ -14,6 +14,22 @@ import { Tween, Timeline, SplitWords, SplitChars, Controls, PlayState } from 're
const TimelineStyled = styled.div``;
+const StyledTarget1 = styled.div`
+ height: 200px;
+ background-color: #accef7;
+`;
+
+const StyledTarget2 = styled.div`
+ height: 50px;
+ background-color: #ff4757;
+ padding: 50px;
+`;
+
+const Inline = styled.div`
+ display: inline-block;
+ font-size: 40px;
+`;
+
const TimelinePlayState = () => {
const [playing, setPlaying] = React.useState(false);
const [progress, setProgress] = React.useState(0);
@@ -147,6 +163,44 @@ const TargetWithNames = forwardRef((props, ref: any) => {
);
});
+const TargetWithNames2 = forwardRef((props, ref: any) => {
+ const div4 = useRef(null);
+ const div5 = useRef[]>([]);
+ const div6 = useRef(null);
+ useImperativeHandle(ref, () => ({
+ div4,
+ div5,
+ div6,
+ }));
+ return (
+
+
first
+
) => div5.current.push(charRef)}
+ wrapper={}
+ >
+ second
+
+
third
+
+ );
+});
+
+const TargetWithNamesCombined = forwardRef((props, ref: any) => {
+ const target1 = useRef({});
+ const target2 = useRef({});
+ useImperativeHandle(ref, () => ({
+ ...target1.current,
+ ...target2.current,
+ }));
+ return (
+ <>
+
+
+ >
+ );
+});
+
const TimelineTargets = () => {
return (
}>
@@ -159,6 +213,16 @@ const TimelineTargets = () => {
//export default TimelineTargets;
+const ForwardRefComponent = forwardRef(({ children }, ref: any) => {
+ return (
+
+
+ {children}
+
+
+ );
+});
+
const Component = forwardRef(({ children }, ref?) => {
const div1 = useRef(null);
const div2 = useRef(null);
@@ -213,7 +277,7 @@ const Out = () => {
const divRef1 = useCallback(ref => {
if (ref !== null) {
// Ref never updates
- console.log(ref);
+ // console.log(ref);
}
}, []);
@@ -221,26 +285,90 @@ const Out = () => {
useEffect(() => {
// Ref never updates
- console.log(divRef2.current);
+ // console.log(divRef2.current);
}, []);
return (
-
}>
-
-
-
+
+ //
+ //
+ //
+ //
+ //
+ //
+ // >
+ // }
+ // target={}
+ // target={}
+ target={
+ <>
+
+
+ >
+ }
+ // target={}
+ // target={
+ // <>
+ //
+ // ForwardRefComponent 1
+ // ForwardRefComponent 2
+ // >
+ // }
+ >
+ {/**/}
+ {/* */}
+ {/**/}
-
-
-
+ {/**/}
+ {/* */}
+ {/**/}
+
+
+
+
+
+ {/**/}
+ {/**/}
);
};
-export default Out;
+//export default Out;
+
+const Test = () => {
+ // the array gets filled up with every new render!
+ // can SplitWords outputs it's refs as array, so that we don't need to push into?
+ const ref = useRef[]>([]);
+
+ useEffect(() => {
+ console.log(ref);
+ }, []);
+
+ return (
+
+
+ ref.current.push(charRef)} wrapper={}>
+ This text gets splitted by words.
+
+
+ }
+ >
+
+
+
+
+
+ );
+};
+
+export default Test;
diff --git a/packages/react-gsap/package.json b/packages/react-gsap/package.json
index 6027df1..31eb0ca 100644
--- a/packages/react-gsap/package.json
+++ b/packages/react-gsap/package.json
@@ -1,6 +1,6 @@
{
"name": "react-gsap",
- "version": "3.0.0",
+ "version": "3.1.0",
"description": "React components for GSAP",
"author": "bitworking",
"license": "MIT",
diff --git a/packages/react-gsap/src/Timeline.tsx b/packages/react-gsap/src/Timeline.tsx
index d080eb8..8ca741f 100644
--- a/packages/react-gsap/src/Timeline.tsx
+++ b/packages/react-gsap/src/Timeline.tsx
@@ -2,13 +2,7 @@ import React, { Fragment, ReactNode, ReactElement } from 'react';
import { gsap } from 'gsap';
import { isForwardRef, isFragment } from 'react-is';
import { PlayState } from './types';
-import {
- getTweenFunction,
- setPlayState,
- nullishCoalescing,
- getRefProp,
- getTargetRefProp,
-} from './helper';
+import { getTweenFunction, setPlayState, nullishCoalescing, getTargetRefProp } from './helper';
import Provider, { Context } from './Provider';
import { TweenProps } from './Tween';
@@ -205,7 +199,7 @@ class Timeline extends Provider {
cloneElement(child: any) {
// @ts-ignore
- return React.cloneElement(child, getRefProp(child, this.addTarget));
+ return React.cloneElement(child, getTargetRefProp(child, this.setTarget, this.addTarget));
}
renderTarget(target?: Target): ReactNode {
@@ -215,8 +209,7 @@ class Timeline extends Provider {
// if is forwardRef clone and pass targets as ref
if (isForwardRef(target)) {
- // @ts-ignore
- return React.cloneElement(target, getTargetRefProp(target, this.setTarget));
+ return this.cloneElement(target);
}
// else iterate the first level of children and set targets
diff --git a/packages/react-gsap/src/helper.ts b/packages/react-gsap/src/helper.ts
index 1fc4d68..c400254 100644
--- a/packages/react-gsap/src/helper.ts
+++ b/packages/react-gsap/src/helper.ts
@@ -1,6 +1,14 @@
import { gsap } from 'gsap';
+import React from 'react';
import { PlayState } from './types';
+if (!String.prototype.startsWith) {
+ String.prototype.startsWith = function(searchString, position) {
+ position = position || 0;
+ return this.indexOf(searchString, position) === position;
+ };
+}
+
const setPlayState = (
playState?: PlayState,
prevPlayState?: PlayState | null,
@@ -136,7 +144,52 @@ const refOrInnerRef = (child: any) => {
return 'ref';
};
+function isElement(element: any) {
+ return React.isValidElement(element);
+}
+
+function isDOMTypeElement(element: any) {
+ return isElement(element) && typeof element.type === 'string';
+}
+
+// https://stackoverflow.com/a/39165137
+function getReactNode(dom: any, traverseUp = 0) {
+ const key = Object.keys(dom ?? {}).find(key => key.startsWith('__reactInternalInstance$'));
+
+ const domFiber = key && dom[key];
+ if (!domFiber) return null;
+
+ // react <16
+ if (domFiber._currentElement) {
+ let compFiber = domFiber._currentElement._owner;
+ for (let i = 0; i < traverseUp; i++) {
+ compFiber = compFiber._currentElement._owner;
+ }
+ return compFiber._instance;
+ }
+
+ // react 16+
+ if (domFiber.stateNode) {
+ return domFiber.stateNode;
+ }
+
+ const getCompFiber = (fiber: any) => {
+ //return fiber._debugOwner; // this also works, but is __DEV__ only
+ let parentFiber = fiber.return;
+ while (typeof parentFiber.type == 'string') {
+ parentFiber = parentFiber.return;
+ }
+ return parentFiber;
+ };
+ let compFiber = getCompFiber(domFiber);
+ for (let i = 0; i < traverseUp; i++) {
+ compFiber = getCompFiber(compFiber);
+ }
+ return compFiber.stateNode;
+}
+
const getRefProp = (child: any, addTarget: (target: any) => void) => {
+ // has to be tested if it works, which lib does still use innerRef?
if (child.props.innerRef) {
return {
innerRef: (target: any) => {
@@ -158,26 +211,54 @@ const getRefProp = (child: any, addTarget: (target: any) => void) => {
};
};
-const getTargetRefProp = (child: any, setTarget: (key: string, target: any) => void) => {
- return {
- ref: (target: any) => {
- const { ref } = child;
+const setOrAddTarget = (
+ target: any,
+ setTarget: (key: string, target: any) => void,
+ addTarget: (target: any) => void
+) => {
+ const reactNode = getReactNode(target);
- if (target) {
- Object.keys(target).forEach(key => {
- const elementRef = target[key];
- if (typeof elementRef === 'object' && elementRef.current) {
- if (Array.isArray(elementRef.current)) {
- elementRef.current.forEach((singleRef: React.RefObject) => {
- setTarget(key, singleRef);
- });
- } else {
- setTarget(key, elementRef.current);
- }
- }
- });
+ if (reactNode) {
+ addTarget(reactNode);
+ } else if (target) {
+ Object.keys(target).forEach(key => {
+ const elementRef = target[key];
+ if (typeof elementRef === 'object' && elementRef.current) {
+ if (Array.isArray(elementRef.current)) {
+ elementRef.current.forEach((singleRef: React.RefObject) => {
+ setTarget(key, singleRef);
+ });
+ } else {
+ setTarget(key, elementRef.current);
+ }
}
+ });
+ }
+};
+
+const getTargetRefProp = (
+ child: any,
+ setTarget: (key: string, target: any) => void,
+ addTarget: (target: any) => void
+) => {
+ // has to be tested if it works, which lib does still use innerRef?
+ if (child.props.innerRef) {
+ return {
+ innerRef: (target: any) => {
+ setOrAddTarget(target, setTarget, addTarget);
+ // merge refs
+ const { innerRef } = child.props;
+ if (typeof innerRef === 'function') innerRef(target);
+ else if (innerRef) innerRef.current = target;
+ },
+ };
+ }
+ return {
+ ref: (target: any) => {
+ setOrAddTarget(target, setTarget, addTarget);
+ // merge refs
+ const { ref } = child;
if (typeof ref === 'function') ref(target);
else if (ref) ref.current = target;
},
diff --git a/packages/react-gsap/src/tools/SplitText.tsx b/packages/react-gsap/src/tools/SplitText.tsx
index dc3af35..b290d14 100644
--- a/packages/react-gsap/src/tools/SplitText.tsx
+++ b/packages/react-gsap/src/tools/SplitText.tsx
@@ -17,6 +17,7 @@ const escapeRegExp = (regExp: string) => {
return regExp.replace(regex, '\\$1');
};
+// TODO: possible or better to output all the refs as one array?
export const SplitWords = React.forwardRef(
({ children, wrapper, delimiter = ' ' }, ref) => {
if (typeof children !== 'string') {
@@ -36,6 +37,7 @@ export const SplitWords = React.forwardRef(
}
);
+// TODO: possible or better to output all the refs as one array?
export const SplitChars = React.forwardRef(({ children, wrapper }, ref) => {
if (typeof children !== 'string') {
throw new Error('SplitChars only accepts a string as child.');