fetta 1.4.3 → 1.5.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 +169 -25
- package/dist/chunk-FP4I6OR2.js +43 -0
- package/dist/chunk-ORMEWXMH.js +33 -0
- package/dist/chunk-UPF3IYHC.js +1762 -0
- package/dist/helpers.d.ts +46 -0
- package/dist/helpers.js +121 -0
- package/dist/index-c1UKfWWK.d.ts +144 -0
- package/dist/index.d.ts +1 -137
- package/dist/index.js +2 -1
- package/dist/initialStyles-BGuPp5CS.d.ts +23 -0
- package/dist/motion.d.ts +185 -0
- package/dist/motion.js +2116 -0
- package/dist/react.d.ts +37 -76
- package/dist/react.js +136 -101
- package/package.json +36 -3
- package/dist/chunk-24TCJGUM.js +0 -1112
package/dist/react.d.ts
CHANGED
|
@@ -1,25 +1,9 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactElement } from 'react';
|
|
3
|
-
|
|
3
|
+
import { A as AnimationCallbackReturn } from './index-c1UKfWWK.js';
|
|
4
|
+
export { a as CoreSplitTextOptions, S as SplitTextResult } from './index-c1UKfWWK.js';
|
|
5
|
+
import { I as InitialStyles, a as InitialClasses } from './initialStyles-BGuPp5CS.js';
|
|
4
6
|
|
|
5
|
-
/** Style value for initialStyles - a partial CSSStyleDeclaration object */
|
|
6
|
-
type InitialStyleValue = Partial<CSSStyleDeclaration>;
|
|
7
|
-
/** Function that returns styles based on element and index */
|
|
8
|
-
type InitialStyleFn = (element: HTMLElement, index: number) => InitialStyleValue;
|
|
9
|
-
/** Initial style can be a static object or a function */
|
|
10
|
-
type InitialStyle = InitialStyleValue | InitialStyleFn;
|
|
11
|
-
/** Initial styles configuration for chars, words, and/or lines */
|
|
12
|
-
interface InitialStyles {
|
|
13
|
-
chars?: InitialStyle;
|
|
14
|
-
words?: InitialStyle;
|
|
15
|
-
lines?: InitialStyle;
|
|
16
|
-
}
|
|
17
|
-
/** Initial classes configuration for chars, words, and/or lines */
|
|
18
|
-
interface InitialClasses {
|
|
19
|
-
chars?: string;
|
|
20
|
-
words?: string;
|
|
21
|
-
lines?: string;
|
|
22
|
-
}
|
|
23
7
|
interface SplitTextOptions {
|
|
24
8
|
type?: "chars" | "words" | "lines" | "chars,words" | "words,lines" | "chars,lines" | "chars,words,lines";
|
|
25
9
|
charClass?: string;
|
|
@@ -33,16 +17,21 @@ interface SplitTextOptions {
|
|
|
33
17
|
* Use this if you prefer no compensation over imperfect Safari compensation. */
|
|
34
18
|
disableKerning?: boolean;
|
|
35
19
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
amount?: number;
|
|
39
|
-
/** Root margin for IntersectionObserver. Default: "0px" */
|
|
40
|
-
margin?: string;
|
|
20
|
+
/** Matches Motion's viewport prop */
|
|
21
|
+
interface ViewportOptions {
|
|
41
22
|
/** Only trigger once. Default: false */
|
|
42
23
|
once?: boolean;
|
|
24
|
+
/** How much of the element must be visible. Motion supports "some" | "all" | number. Default: 0 */
|
|
25
|
+
amount?: number | "some" | "all";
|
|
26
|
+
/** How much visibility is required to consider the element out of view. Default: 0 (fully out) */
|
|
27
|
+
leave?: number | "some" | "all";
|
|
28
|
+
/** Root margin for IntersectionObserver. Default: "0px" */
|
|
29
|
+
margin?: string;
|
|
30
|
+
/** Root element for IntersectionObserver */
|
|
31
|
+
root?: React.RefObject<Element>;
|
|
43
32
|
}
|
|
44
33
|
/**
|
|
45
|
-
* Result passed to SplitText callbacks (onSplit,
|
|
34
|
+
* Result passed to SplitText callbacks (onSplit, onViewportEnter, onViewportLeave, onResplit).
|
|
46
35
|
*
|
|
47
36
|
* Contains arrays of split elements and a revert function for manual control.
|
|
48
37
|
* Empty arrays are returned for split types not requested in options.
|
|
@@ -57,16 +46,12 @@ interface SplitTextElements {
|
|
|
57
46
|
/** Revert to original HTML and cleanup observers */
|
|
58
47
|
revert: () => void;
|
|
59
48
|
}
|
|
60
|
-
|
|
61
|
-
type
|
|
62
|
-
|
|
63
|
-
} | Array<{
|
|
64
|
-
finished: Promise<unknown>;
|
|
65
|
-
}> | Promise<unknown>;
|
|
66
|
-
interface SplitTextProps {
|
|
49
|
+
type ControlledWrapperHTMLKeys = "children" | "className" | "style" | "ref" | "as" | "onSplit" | "onResplit" | "options" | "autoSplit" | "revertOnComplete" | "viewport" | "onViewportEnter" | "onViewportLeave" | "onRevert" | "initialStyles" | "initialClasses" | "resetOnViewportLeave" | "waitForFonts";
|
|
50
|
+
type WrapperHTMLProps = Omit<React.HTMLAttributes<HTMLElement>, ControlledWrapperHTMLKeys>;
|
|
51
|
+
interface SplitTextProps extends WrapperHTMLProps {
|
|
67
52
|
children: ReactElement;
|
|
68
53
|
/** The wrapper element type. Default: "div" */
|
|
69
|
-
as?: keyof
|
|
54
|
+
as?: keyof HTMLElementTagNameMap;
|
|
70
55
|
/** Class name for the wrapper element */
|
|
71
56
|
className?: string;
|
|
72
57
|
/** Additional styles for the wrapper element (merged with internal styles) */
|
|
@@ -74,30 +59,33 @@ interface SplitTextProps {
|
|
|
74
59
|
/**
|
|
75
60
|
* Called after text is split.
|
|
76
61
|
* Return an animation or promise to enable revert (requires revertOnComplete).
|
|
77
|
-
* If inView is enabled, this is called immediately but animation typically runs in onInView.
|
|
78
62
|
*/
|
|
79
|
-
onSplit?: (result: SplitTextElements) =>
|
|
80
|
-
/** Called when autoSplit
|
|
81
|
-
|
|
63
|
+
onSplit?: (result: SplitTextElements) => AnimationCallbackReturn;
|
|
64
|
+
/** Called when autoSplit/full-resplit replaces split output elements */
|
|
65
|
+
onResplit?: (result: SplitTextElements) => void;
|
|
82
66
|
options?: SplitTextOptions;
|
|
83
67
|
autoSplit?: boolean;
|
|
84
68
|
/** When true, reverts to original HTML after animation promise resolves */
|
|
85
69
|
revertOnComplete?: boolean;
|
|
86
|
-
/**
|
|
87
|
-
|
|
88
|
-
/** Called when element enters viewport. Return animation for revertOnComplete support */
|
|
89
|
-
|
|
90
|
-
/** Called when element leaves viewport */
|
|
91
|
-
|
|
70
|
+
/** Viewport observer options (replaces `inView`). Configures IntersectionObserver. */
|
|
71
|
+
viewport?: ViewportOptions;
|
|
72
|
+
/** Called when element enters viewport (replaces `onInView`). Return animation for revertOnComplete support */
|
|
73
|
+
onViewportEnter?: (result: SplitTextElements) => AnimationCallbackReturn;
|
|
74
|
+
/** Called when element leaves viewport (replaces `onLeaveView`) */
|
|
75
|
+
onViewportLeave?: (result: SplitTextElements) => AnimationCallbackReturn;
|
|
76
|
+
/** Called when split text is reverted (manual or automatic) */
|
|
77
|
+
onRevert?: () => void;
|
|
92
78
|
/** Apply initial inline styles to elements after split (and after kerning compensation).
|
|
93
79
|
* Can be a static style object or a function that receives (element, index). */
|
|
94
80
|
initialStyles?: InitialStyles;
|
|
95
81
|
/** Apply initial classes to elements after split (and after kerning compensation).
|
|
96
82
|
* Classes are added via classList.add() and support space-separated class names. */
|
|
97
83
|
initialClasses?: InitialClasses;
|
|
98
|
-
/** Re-apply initialStyles
|
|
84
|
+
/** Re-apply initialStyles/initialClasses when element leaves viewport.
|
|
99
85
|
* Useful for scroll-triggered animations that should reset when scrolling away. */
|
|
100
|
-
|
|
86
|
+
resetOnViewportLeave?: boolean;
|
|
87
|
+
/** Wait for `document.fonts.ready` before splitting. Disable for immediate split. */
|
|
88
|
+
waitForFonts?: boolean;
|
|
101
89
|
}
|
|
102
90
|
/**
|
|
103
91
|
* React component wrapper for text splitting with kerning compensation.
|
|
@@ -105,6 +93,8 @@ interface SplitTextProps {
|
|
|
105
93
|
* Wraps a single child element and splits its text content into characters,
|
|
106
94
|
* words, and/or lines. Handles lifecycle cleanup automatically on unmount.
|
|
107
95
|
*
|
|
96
|
+
* Supports callback mode via `onSplit`, `onViewportEnter`, `onViewportLeave`.
|
|
97
|
+
*
|
|
108
98
|
* @param props - Component props including callbacks and options
|
|
109
99
|
* @returns The child element wrapped in a container div
|
|
110
100
|
*
|
|
@@ -113,7 +103,7 @@ interface SplitTextProps {
|
|
|
113
103
|
* import { SplitText } from "fetta/react";
|
|
114
104
|
* import { animate, stagger } from "motion";
|
|
115
105
|
*
|
|
116
|
-
* //
|
|
106
|
+
* // Imperative animation
|
|
117
107
|
* <SplitText
|
|
118
108
|
* onSplit={({ words }) => {
|
|
119
109
|
* animate(words, { opacity: [0, 1], y: [20, 0] }, { delay: stagger(0.05) });
|
|
@@ -122,36 +112,7 @@ interface SplitTextProps {
|
|
|
122
112
|
* <h1>Animated Text</h1>
|
|
123
113
|
* </SplitText>
|
|
124
114
|
* ```
|
|
125
|
-
*
|
|
126
|
-
* @example
|
|
127
|
-
* ```tsx
|
|
128
|
-
* // Scroll-triggered with auto-revert
|
|
129
|
-
* <SplitText
|
|
130
|
-
* onSplit={({ chars }) => {
|
|
131
|
-
* chars.forEach(c => c.style.opacity = "0");
|
|
132
|
-
* }}
|
|
133
|
-
* inView={{ amount: 0.5, once: true }}
|
|
134
|
-
* onInView={({ chars }) =>
|
|
135
|
-
* animate(chars, { opacity: 1 }, { delay: stagger(0.02) })
|
|
136
|
-
* }
|
|
137
|
-
* revertOnComplete
|
|
138
|
-
* >
|
|
139
|
-
* <p>Reveals on scroll, reverts after animation</p>
|
|
140
|
-
* </SplitText>
|
|
141
|
-
* ```
|
|
142
|
-
*
|
|
143
|
-
* @example
|
|
144
|
-
* ```tsx
|
|
145
|
-
* // Responsive re-splitting
|
|
146
|
-
* <SplitText
|
|
147
|
-
* autoSplit
|
|
148
|
-
* onSplit={({ lines }) => animate(lines, { opacity: [0, 1] })}
|
|
149
|
-
* onResize={({ lines }) => animate(lines, { opacity: [0, 1] })}
|
|
150
|
-
* >
|
|
151
|
-
* <p>Re-animates when container resizes</p>
|
|
152
|
-
* </SplitText>
|
|
153
|
-
* ```
|
|
154
115
|
*/
|
|
155
116
|
declare const SplitText: react.ForwardRefExoticComponent<SplitTextProps & react.RefAttributes<HTMLElement>>;
|
|
156
117
|
|
|
157
|
-
export { SplitText, type SplitTextElements };
|
|
118
|
+
export { SplitText, type SplitTextElements, type SplitTextOptions };
|
package/dist/react.js
CHANGED
|
@@ -1,45 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { waitForFontsReady, reapplyInitialStyles, reapplyInitialClasses } from './chunk-FP4I6OR2.js';
|
|
2
|
+
import { splitText, normalizeToPromise } from './chunk-UPF3IYHC.js';
|
|
3
|
+
import { __objRest, __spreadProps, __spreadValues } from './chunk-ORMEWXMH.js';
|
|
4
|
+
import { forwardRef, useRef, useCallback, useState, useLayoutEffect, useEffect, isValidElement, createElement } from 'react';
|
|
4
5
|
|
|
5
|
-
function reapplyInitialStyles(elements, style) {
|
|
6
|
-
if (!style || elements.length === 0) return;
|
|
7
|
-
const isFn = typeof style === "function";
|
|
8
|
-
for (let i = 0; i < elements.length; i++) {
|
|
9
|
-
const el = elements[i];
|
|
10
|
-
const styles = isFn ? style(el, i) : style;
|
|
11
|
-
for (const [key, value] of Object.entries(styles)) {
|
|
12
|
-
if (value !== void 0) {
|
|
13
|
-
el.style[key] = value;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
function reapplyInitialClasses(elements, className) {
|
|
19
|
-
if (!className || elements.length === 0) return;
|
|
20
|
-
const classes = className.split(/\s+/).filter(Boolean);
|
|
21
|
-
for (const el of elements) {
|
|
22
|
-
el.classList.add(...classes);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
6
|
var SplitText = forwardRef(
|
|
26
|
-
function SplitText2({
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
7
|
+
function SplitText2(_a, forwardedRef) {
|
|
8
|
+
var _b = _a, {
|
|
9
|
+
children,
|
|
10
|
+
as: Component = "div",
|
|
11
|
+
className,
|
|
12
|
+
style: userStyle,
|
|
13
|
+
onSplit,
|
|
14
|
+
onResplit,
|
|
15
|
+
options,
|
|
16
|
+
autoSplit = false,
|
|
17
|
+
revertOnComplete = false,
|
|
18
|
+
viewport,
|
|
19
|
+
onViewportEnter,
|
|
20
|
+
onViewportLeave,
|
|
21
|
+
onRevert,
|
|
22
|
+
initialStyles,
|
|
23
|
+
initialClasses,
|
|
24
|
+
resetOnViewportLeave = false,
|
|
25
|
+
waitForFonts = true
|
|
26
|
+
} = _b, wrapperProps = __objRest(_b, [
|
|
27
|
+
"children",
|
|
28
|
+
"as",
|
|
29
|
+
"className",
|
|
30
|
+
"style",
|
|
31
|
+
"onSplit",
|
|
32
|
+
"onResplit",
|
|
33
|
+
"options",
|
|
34
|
+
"autoSplit",
|
|
35
|
+
"revertOnComplete",
|
|
36
|
+
"viewport",
|
|
37
|
+
"onViewportEnter",
|
|
38
|
+
"onViewportLeave",
|
|
39
|
+
"onRevert",
|
|
40
|
+
"initialStyles",
|
|
41
|
+
"initialClasses",
|
|
42
|
+
"resetOnViewportLeave",
|
|
43
|
+
"waitForFonts"
|
|
44
|
+
]);
|
|
43
45
|
const containerRef = useRef(null);
|
|
44
46
|
const mergedRef = useCallback(
|
|
45
47
|
(node) => {
|
|
@@ -54,27 +56,30 @@ var SplitText = forwardRef(
|
|
|
54
56
|
);
|
|
55
57
|
const [childElement, setChildElement] = useState(null);
|
|
56
58
|
const [isInView, setIsInView] = useState(false);
|
|
59
|
+
const needsViewport = !!(onViewportEnter || onViewportLeave || resetOnViewportLeave || viewport);
|
|
57
60
|
const onSplitRef = useRef(onSplit);
|
|
58
|
-
const
|
|
61
|
+
const onResplitRef = useRef(onResplit);
|
|
59
62
|
const optionsRef = useRef(options);
|
|
60
63
|
const revertOnCompleteRef = useRef(revertOnComplete);
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
+
const viewportRef = useRef(viewport);
|
|
65
|
+
const onViewportEnterRef = useRef(onViewportEnter);
|
|
66
|
+
const onViewportLeaveRef = useRef(onViewportLeave);
|
|
67
|
+
const onRevertRef = useRef(onRevert);
|
|
64
68
|
const initialStylesRef = useRef(initialStyles);
|
|
65
69
|
const initialClassesRef = useRef(initialClasses);
|
|
66
|
-
const
|
|
70
|
+
const resetOnViewportLeaveRef = useRef(resetOnViewportLeave);
|
|
67
71
|
useLayoutEffect(() => {
|
|
68
72
|
onSplitRef.current = onSplit;
|
|
69
|
-
|
|
73
|
+
onResplitRef.current = onResplit;
|
|
70
74
|
optionsRef.current = options;
|
|
71
75
|
revertOnCompleteRef.current = revertOnComplete;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
viewportRef.current = viewport;
|
|
77
|
+
onViewportEnterRef.current = onViewportEnter;
|
|
78
|
+
onViewportLeaveRef.current = onViewportLeave;
|
|
79
|
+
onRevertRef.current = onRevert;
|
|
75
80
|
initialStylesRef.current = initialStyles;
|
|
76
81
|
initialClassesRef.current = initialClasses;
|
|
77
|
-
|
|
82
|
+
resetOnViewportLeaveRef.current = resetOnViewportLeave;
|
|
78
83
|
});
|
|
79
84
|
const hasSplitRef = useRef(false);
|
|
80
85
|
const hasRevertedRef = useRef(false);
|
|
@@ -82,53 +87,65 @@ var SplitText = forwardRef(
|
|
|
82
87
|
const splitResultRef = useRef(null);
|
|
83
88
|
const observerRef = useRef(null);
|
|
84
89
|
const hasTriggeredOnceRef = useRef(false);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
useLayoutEffect(() => {
|
|
91
|
+
var _a2;
|
|
92
|
+
const element = (_a2 = containerRef.current) == null ? void 0 : _a2.firstElementChild;
|
|
93
|
+
setChildElement(element instanceof HTMLElement ? element : null);
|
|
94
|
+
}, [children]);
|
|
88
95
|
useEffect(() => {
|
|
89
96
|
if (!childElement) return;
|
|
90
97
|
if (hasSplitRef.current) return;
|
|
91
98
|
let isMounted = true;
|
|
92
|
-
|
|
93
|
-
var _a, _b;
|
|
99
|
+
waitForFontsReady(waitForFonts).then(() => {
|
|
94
100
|
if (!isMounted || hasSplitRef.current) return;
|
|
95
101
|
if (!containerRef.current) return;
|
|
102
|
+
let coreRevert = null;
|
|
103
|
+
const revert = () => {
|
|
104
|
+
var _a2;
|
|
105
|
+
if (hasRevertedRef.current) return;
|
|
106
|
+
hasRevertedRef.current = true;
|
|
107
|
+
try {
|
|
108
|
+
(_a2 = onRevertRef.current) == null ? void 0 : _a2.call(onRevertRef);
|
|
109
|
+
} finally {
|
|
110
|
+
coreRevert == null ? void 0 : coreRevert();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
96
113
|
const result = splitText(childElement, __spreadProps(__spreadValues({}, optionsRef.current), {
|
|
97
114
|
autoSplit,
|
|
98
115
|
revertOnComplete: revertOnCompleteRef.current,
|
|
99
116
|
initialStyles: initialStylesRef.current,
|
|
100
117
|
initialClasses: initialClassesRef.current,
|
|
101
|
-
|
|
118
|
+
onResplit: (resizeResult) => {
|
|
102
119
|
var _a2;
|
|
103
120
|
const newSplitTextElements = {
|
|
104
121
|
chars: resizeResult.chars,
|
|
105
122
|
words: resizeResult.words,
|
|
106
123
|
lines: resizeResult.lines,
|
|
107
|
-
revert
|
|
124
|
+
revert
|
|
108
125
|
};
|
|
109
126
|
splitResultRef.current = newSplitTextElements;
|
|
110
|
-
(_a2 =
|
|
127
|
+
(_a2 = onResplitRef.current) == null ? void 0 : _a2.call(onResplitRef, newSplitTextElements);
|
|
111
128
|
}
|
|
112
129
|
}));
|
|
113
|
-
|
|
130
|
+
coreRevert = result.revert;
|
|
131
|
+
revertFnRef.current = revert;
|
|
114
132
|
hasSplitRef.current = true;
|
|
115
133
|
const splitElements = {
|
|
116
134
|
chars: result.chars,
|
|
117
135
|
words: result.words,
|
|
118
136
|
lines: result.lines,
|
|
119
|
-
revert
|
|
137
|
+
revert
|
|
120
138
|
};
|
|
121
139
|
splitResultRef.current = splitElements;
|
|
122
140
|
containerRef.current.style.visibility = "visible";
|
|
123
141
|
if (onSplitRef.current) {
|
|
124
142
|
const callbackResult = onSplitRef.current(splitElements);
|
|
125
|
-
if (!
|
|
143
|
+
if (!needsViewport && revertOnCompleteRef.current) {
|
|
126
144
|
const promise = normalizeToPromise(callbackResult);
|
|
127
145
|
if (promise) {
|
|
128
146
|
promise.then(() => {
|
|
129
147
|
if (!isMounted || hasRevertedRef.current) return;
|
|
130
|
-
|
|
131
|
-
hasRevertedRef.current = true;
|
|
148
|
+
splitElements.revert();
|
|
132
149
|
}).catch(() => {
|
|
133
150
|
console.warn("[fetta] Animation rejected, text not reverted");
|
|
134
151
|
});
|
|
@@ -139,29 +156,44 @@ var SplitText = forwardRef(
|
|
|
139
156
|
}
|
|
140
157
|
}
|
|
141
158
|
}
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
const threshold = (_a = inViewOptions.amount) != null ? _a : 0;
|
|
145
|
-
const rootMargin = (_b = inViewOptions.margin) != null ? _b : "0px";
|
|
146
|
-
const thresholds = threshold > 0 ? [0, threshold] : 0;
|
|
147
|
-
observerRef.current = new IntersectionObserver(
|
|
148
|
-
(entries) => {
|
|
149
|
-
const entry = entries[0];
|
|
150
|
-
if (!entry) return;
|
|
151
|
-
const isOnce = typeof inViewRef.current === "object" && inViewRef.current.once;
|
|
152
|
-
if (entry.isIntersecting && entry.intersectionRatio >= threshold) {
|
|
153
|
-
if (isOnce && hasTriggeredOnceRef.current) return;
|
|
154
|
-
hasTriggeredOnceRef.current = true;
|
|
155
|
-
setIsInView(true);
|
|
156
|
-
} else if (!entry.isIntersecting && !isOnce) {
|
|
157
|
-
setIsInView(false);
|
|
158
|
-
}
|
|
159
|
-
},
|
|
160
|
-
{ threshold: thresholds, rootMargin }
|
|
161
|
-
);
|
|
162
|
-
observerRef.current.observe(containerRef.current);
|
|
159
|
+
if (needsViewport && containerRef.current) {
|
|
160
|
+
setupViewportObserver(containerRef.current);
|
|
163
161
|
}
|
|
164
162
|
});
|
|
163
|
+
function setupViewportObserver(container) {
|
|
164
|
+
var _a2, _b2, _c, _d, _e;
|
|
165
|
+
const vpOptions = viewportRef.current || {};
|
|
166
|
+
const amount = (_a2 = vpOptions.amount) != null ? _a2 : 0;
|
|
167
|
+
const leave = (_b2 = vpOptions.leave) != null ? _b2 : 0;
|
|
168
|
+
const threshold = amount === "some" ? 0 : amount === "all" ? 1 : amount;
|
|
169
|
+
const leaveThreshold = leave === "some" ? 0 : leave === "all" ? 1 : leave;
|
|
170
|
+
const rootMargin = (_c = vpOptions.margin) != null ? _c : "0px";
|
|
171
|
+
const root = (_e = (_d = vpOptions.root) == null ? void 0 : _d.current) != null ? _e : void 0;
|
|
172
|
+
const thresholdValues = Array.from(
|
|
173
|
+
/* @__PURE__ */ new Set([0, threshold, leaveThreshold])
|
|
174
|
+
).sort((a, b) => a - b);
|
|
175
|
+
const thresholds = thresholdValues.length === 1 ? thresholdValues[0] : thresholdValues;
|
|
176
|
+
observerRef.current = new IntersectionObserver(
|
|
177
|
+
(entries) => {
|
|
178
|
+
const entry = entries[0];
|
|
179
|
+
if (!entry) return;
|
|
180
|
+
const isOnce = vpOptions.once;
|
|
181
|
+
if (entry.isIntersecting && entry.intersectionRatio >= threshold) {
|
|
182
|
+
if (isOnce && hasTriggeredOnceRef.current) return;
|
|
183
|
+
hasTriggeredOnceRef.current = true;
|
|
184
|
+
setIsInView(true);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (isOnce) return;
|
|
188
|
+
const shouldLeave = leaveThreshold === 0 ? !entry.isIntersecting : entry.intersectionRatio <= leaveThreshold;
|
|
189
|
+
if (shouldLeave) {
|
|
190
|
+
setIsInView(false);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
{ threshold: thresholds, rootMargin, root }
|
|
194
|
+
);
|
|
195
|
+
observerRef.current.observe(container);
|
|
196
|
+
}
|
|
165
197
|
return () => {
|
|
166
198
|
isMounted = false;
|
|
167
199
|
if (observerRef.current) {
|
|
@@ -173,25 +205,26 @@ var SplitText = forwardRef(
|
|
|
173
205
|
}
|
|
174
206
|
hasSplitRef.current = false;
|
|
175
207
|
};
|
|
176
|
-
}, [childElement, autoSplit]);
|
|
208
|
+
}, [childElement, autoSplit, needsViewport, waitForFonts]);
|
|
177
209
|
useEffect(() => {
|
|
178
210
|
if (!splitResultRef.current) return;
|
|
179
211
|
if (hasRevertedRef.current) return;
|
|
180
|
-
if (isInView &&
|
|
181
|
-
const callbackResult =
|
|
212
|
+
if (isInView && onViewportEnterRef.current) {
|
|
213
|
+
const callbackResult = onViewportEnterRef.current(
|
|
214
|
+
splitResultRef.current
|
|
215
|
+
);
|
|
182
216
|
const promise = normalizeToPromise(callbackResult);
|
|
183
217
|
if (revertOnCompleteRef.current && promise) {
|
|
184
218
|
promise.then(() => {
|
|
185
|
-
var
|
|
219
|
+
var _a2;
|
|
186
220
|
if (hasRevertedRef.current) return;
|
|
187
|
-
(
|
|
188
|
-
hasRevertedRef.current = true;
|
|
221
|
+
(_a2 = splitResultRef.current) == null ? void 0 : _a2.revert();
|
|
189
222
|
}).catch(() => {
|
|
190
223
|
console.warn("[fetta] Animation rejected, text not reverted");
|
|
191
224
|
});
|
|
192
225
|
}
|
|
193
226
|
} else if (!isInView && splitResultRef.current) {
|
|
194
|
-
if (
|
|
227
|
+
if (resetOnViewportLeaveRef.current) {
|
|
195
228
|
const { chars, words, lines } = splitResultRef.current;
|
|
196
229
|
const styles = initialStylesRef.current;
|
|
197
230
|
const classes = initialClassesRef.current;
|
|
@@ -206,8 +239,8 @@ var SplitText = forwardRef(
|
|
|
206
239
|
reapplyInitialClasses(lines, classes.lines);
|
|
207
240
|
}
|
|
208
241
|
}
|
|
209
|
-
if (
|
|
210
|
-
|
|
242
|
+
if (onViewportLeaveRef.current) {
|
|
243
|
+
onViewportLeaveRef.current(splitResultRef.current);
|
|
211
244
|
}
|
|
212
245
|
}
|
|
213
246
|
}, [isInView]);
|
|
@@ -215,18 +248,20 @@ var SplitText = forwardRef(
|
|
|
215
248
|
console.error("SplitText: children must be a single valid React element");
|
|
216
249
|
return null;
|
|
217
250
|
}
|
|
218
|
-
const clonedChild = cloneElement(children, {
|
|
219
|
-
ref: childRefCallback
|
|
220
|
-
});
|
|
221
251
|
const Wrapper = Component;
|
|
222
|
-
return
|
|
252
|
+
return createElement(
|
|
223
253
|
Wrapper,
|
|
224
|
-
{
|
|
254
|
+
__spreadProps(__spreadValues({
|
|
225
255
|
ref: mergedRef,
|
|
256
|
+
"data-fetta-auto-split-wrapper": "true"
|
|
257
|
+
}, wrapperProps), {
|
|
226
258
|
className,
|
|
227
|
-
style: __spreadValues({
|
|
228
|
-
|
|
229
|
-
|
|
259
|
+
style: __spreadValues({
|
|
260
|
+
visibility: waitForFonts ? "hidden" : "visible",
|
|
261
|
+
position: "relative"
|
|
262
|
+
}, userStyle)
|
|
263
|
+
}),
|
|
264
|
+
children
|
|
230
265
|
);
|
|
231
266
|
}
|
|
232
267
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fetta",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Text splitting library with kerning compensation for animations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -13,9 +13,17 @@
|
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"import": "./dist/index.js"
|
|
15
15
|
},
|
|
16
|
+
"./helpers": {
|
|
17
|
+
"types": "./dist/helpers.d.ts",
|
|
18
|
+
"import": "./dist/helpers.js"
|
|
19
|
+
},
|
|
16
20
|
"./react": {
|
|
17
21
|
"types": "./dist/react.d.ts",
|
|
18
22
|
"import": "./dist/react.js"
|
|
23
|
+
},
|
|
24
|
+
"./motion": {
|
|
25
|
+
"types": "./dist/motion.d.ts",
|
|
26
|
+
"import": "./dist/motion.js"
|
|
19
27
|
}
|
|
20
28
|
},
|
|
21
29
|
"main": "./dist/index.js",
|
|
@@ -33,6 +41,7 @@
|
|
|
33
41
|
"test": "vitest",
|
|
34
42
|
"test:ui": "vitest --ui",
|
|
35
43
|
"test:coverage": "vitest run --coverage",
|
|
44
|
+
"pretest:e2e": "pnpm run build",
|
|
36
45
|
"test:e2e": "playwright test",
|
|
37
46
|
"test:all": "vitest run --coverage && playwright test",
|
|
38
47
|
"size": "size-limit",
|
|
@@ -49,14 +58,29 @@
|
|
|
49
58
|
"path": "dist/react.js",
|
|
50
59
|
"import": "*",
|
|
51
60
|
"ignore": ["react"]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "Motion",
|
|
64
|
+
"path": "dist/motion.js",
|
|
65
|
+
"import": "*",
|
|
66
|
+
"ignore": ["react", "motion", "motion/react"]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "Helpers",
|
|
70
|
+
"path": "dist/helpers.js",
|
|
71
|
+
"import": "*"
|
|
52
72
|
}
|
|
53
73
|
],
|
|
54
74
|
"peerDependencies": {
|
|
55
|
-
"react": ">=18.0.0"
|
|
75
|
+
"react": ">=18.0.0",
|
|
76
|
+
"motion": ">=11.0.0"
|
|
56
77
|
},
|
|
57
78
|
"peerDependenciesMeta": {
|
|
58
79
|
"react": {
|
|
59
80
|
"optional": true
|
|
81
|
+
},
|
|
82
|
+
"motion": {
|
|
83
|
+
"optional": true
|
|
60
84
|
}
|
|
61
85
|
},
|
|
62
86
|
"devDependencies": {
|
|
@@ -80,11 +104,20 @@
|
|
|
80
104
|
"keywords": [
|
|
81
105
|
"text",
|
|
82
106
|
"split",
|
|
107
|
+
"split-text",
|
|
108
|
+
"splittext",
|
|
109
|
+
"text-animation",
|
|
110
|
+
"text-effect",
|
|
83
111
|
"animation",
|
|
84
112
|
"motion",
|
|
85
113
|
"kerning",
|
|
86
114
|
"typography",
|
|
87
|
-
"gsap"
|
|
115
|
+
"gsap",
|
|
116
|
+
"javascript",
|
|
117
|
+
"typescript",
|
|
118
|
+
"react",
|
|
119
|
+
"web-animation",
|
|
120
|
+
"accessible"
|
|
88
121
|
],
|
|
89
122
|
"license": "MIT",
|
|
90
123
|
"author": "dimi",
|