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.');