diff --git a/packages/react-animation/src/SplitTextWrapper/SplitTextWrapper.mdx b/packages/react-animation/src/SplitTextWrapper/SplitTextWrapper.mdx index 6463e0c..df0a7a5 100644 --- a/packages/react-animation/src/SplitTextWrapper/SplitTextWrapper.mdx +++ b/packages/react-animation/src/SplitTextWrapper/SplitTextWrapper.mdx @@ -41,6 +41,39 @@ function Component(): ReactElement { +## Rendering children wraped in a parent element + +The `SplitTextWrapper` renders to HTML inside the component to make sure that compnents from the +vDOM are not changed on render making them untargetable in the created SplitText instance. **_This +time the children being wrap in another parent element_** + +> Warning: state inside the rendered children is lost when the children change. + +```tsx +function Component(): ReactElement { + const splitTextRef = useRef(null); + + useEffect(() => { + // Do something with `splitTextRef.current` + }); + + return ( + +

+ Lorem ipsum dolor sit amet consectetur +
adipisicing elit. Tenetur perspiciatis eius ea, ratione, +
illo molestias, quia sapiente modi quo +
molestiae temporibus. +

+
+ ); +} +``` + +### Demo + + + ## Using dangerouslySetInnerHTML The children are rendered to a string, this is not necessary when the `dangerouslySetInnerHTML` diff --git a/packages/react-animation/src/SplitTextWrapper/SplitTextWrapper.stories.tsx b/packages/react-animation/src/SplitTextWrapper/SplitTextWrapper.stories.tsx index f1110d3..c65aeb4 100644 --- a/packages/react-animation/src/SplitTextWrapper/SplitTextWrapper.stories.tsx +++ b/packages/react-animation/src/SplitTextWrapper/SplitTextWrapper.stories.tsx @@ -88,6 +88,80 @@ export const Children: Story = { }, }; +export const ChildrenWrap: Story = { + render(): ReactElement { + const splitTextRef = useRef(null); + + const animation = useAnimation(() => { + if (!splitTextRef.current) { + return; + } + + return gsap.from(splitTextRef.current.lines, { + paused: true, + y: 20, + opacity: 0, + duration: 0.2, + stagger: 0.05, + }); + }, []); + + const onPlay = useCallback(() => { + animation.current?.play(0); + }, [animation]); + + return ( + <> + +

+ Lorem ipsum dolor sit amet consectetur +
adipisicing elit. Tenetur perspiciatis eius ea, ratione, +
illo molestias, quia sapiente modi quo +
molestiae temporibus. +

+
+ + + ); + }, + async play({ canvasElement }) { + const canvas = within(canvasElement); + const wrapper = canvas.getByTestId('wrapper'); + + expect(wrapper).toBeInTheDocument(); + expect(wrapper.childElementCount).toEqual(4); + + // Wait 2 ticks for styles to be initialized + await new Promise((resolve) => { + setTimeout(resolve, 0); + }); + await new Promise((resolve) => { + setTimeout(resolve, 0); + }); + + expect(wrapper.children[0]).toHaveStyle({ opacity: '0' }); + expect(wrapper.children[3]).toHaveStyle({ opacity: '0' }); + + await userEvent.click(canvas.getByText('Play')); + await new Promise((resolve) => { + setTimeout(resolve, 200 + wrapper.childElementCount * 50); + }); + + expect(wrapper.children[0]).toHaveStyle({ opacity: '1' }); + expect(wrapper.children[3]).toHaveStyle({ opacity: '1' }); + }, +}; + export const DangerouslySetInnerHtml: Story = { render(): ReactElement { const splitTextRef = useRef(null); diff --git a/packages/react-animation/src/SplitTextWrapper/SplitTextWrapper.tsx b/packages/react-animation/src/SplitTextWrapper/SplitTextWrapper.tsx index 4d5fc2a..654ffd1 100644 --- a/packages/react-animation/src/SplitTextWrapper/SplitTextWrapper.tsx +++ b/packages/react-animation/src/SplitTextWrapper/SplitTextWrapper.tsx @@ -53,7 +53,15 @@ export const SplitTextWrapper: SplitTextWrapperComponent = ensuredForwardRef( return; } - ref.current = new SplitText(element, variables); + /** + * Detecting the if firstChild is an element only if there is only one child + */ + const content = + element.children.length === 1 && element.firstChild?.nodeType === Node.ELEMENT_NODE + ? (element.firstChild as HTMLElement) + : element; + + ref.current = new SplitText(content, variables); }; const Component = (as ?? 'div') as unknown as ComponentType;