fetta 1.3.5 → 1.4.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 +11 -4
- package/dist/{chunk-HQLYE4Q5.js → chunk-NTRJ6XDH.js} +60 -3
- package/dist/index.d.ts +21 -1
- package/dist/index.js +1 -1
- package/dist/react.d.ts +27 -0
- package/dist/react.js +52 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,8 @@ const result = splitText(element, options);
|
|
|
86
86
|
| `revertOnComplete` | `boolean` | `false` | Auto-revert when animation completes |
|
|
87
87
|
| `propIndex` | `boolean` | `false` | Add CSS custom properties: `--char-index`, `--word-index`, `--line-index` |
|
|
88
88
|
| `disableKerning` | `boolean` | `false` | Skip kerning compensation (no margin adjustments) |
|
|
89
|
+
| `initialStyles` | `object` | — | Apply initial inline styles to chars/words/lines after split |
|
|
90
|
+
| `initialClasses` | `object` | — | Apply initial CSS classes to chars/words/lines after split |
|
|
89
91
|
|
|
90
92
|
#### Return Value
|
|
91
93
|
|
|
@@ -117,6 +119,9 @@ import { SplitText } from 'fetta/react';
|
|
|
117
119
|
| `inView` | `boolean \| InViewOptions` | `false` | Enable viewport detection |
|
|
118
120
|
| `onInView` | `function` | — | Called when element enters viewport |
|
|
119
121
|
| `onLeaveView` | `function` | — | Called when element leaves viewport |
|
|
122
|
+
| `initialStyles` | `object` | — | Apply initial inline styles to chars/words/lines |
|
|
123
|
+
| `initialClasses` | `object` | — | Apply initial CSS classes to chars/words/lines |
|
|
124
|
+
| `resetOnLeave` | `boolean` | `false` | Re-apply initialStyles/initialClasses when leaving viewport |
|
|
120
125
|
|
|
121
126
|
#### InView Options
|
|
122
127
|
|
|
@@ -147,13 +152,15 @@ import { SplitText } from 'fetta/react';
|
|
|
147
152
|
|
|
148
153
|
```tsx
|
|
149
154
|
<SplitText
|
|
150
|
-
|
|
151
|
-
|
|
155
|
+
options={{ type: 'words' }}
|
|
156
|
+
initialStyles={{
|
|
157
|
+
words: { opacity: '0', transform: 'translateY(20px)' }
|
|
152
158
|
}}
|
|
153
|
-
inView={{ amount: 0.5
|
|
159
|
+
inView={{ amount: 0.5 }}
|
|
154
160
|
onInView={({ words }) => {
|
|
155
|
-
animate(words, { opacity: 1, y:
|
|
161
|
+
animate(words, { opacity: 1, y: 0 }, { delay: stagger(0.03) });
|
|
156
162
|
}}
|
|
163
|
+
resetOnLeave
|
|
157
164
|
>
|
|
158
165
|
<p>Animates when scrolled into view</p>
|
|
159
166
|
</SplitText>
|
|
@@ -411,6 +411,32 @@ function createMaskWrapper(display = "inline-block") {
|
|
|
411
411
|
wrapper.style.overflow = "clip";
|
|
412
412
|
return wrapper;
|
|
413
413
|
}
|
|
414
|
+
var PROTECTED_STYLES = /* @__PURE__ */ new Set([
|
|
415
|
+
"display",
|
|
416
|
+
"position",
|
|
417
|
+
"textDecoration",
|
|
418
|
+
"fontVariantLigatures"
|
|
419
|
+
]);
|
|
420
|
+
function applyInitialStyles(elements, style) {
|
|
421
|
+
if (!style || elements.length === 0) return;
|
|
422
|
+
const isFn = typeof style === "function";
|
|
423
|
+
for (let i = 0; i < elements.length; i++) {
|
|
424
|
+
const el = elements[i];
|
|
425
|
+
const styles = isFn ? style(el, i) : style;
|
|
426
|
+
for (const [key, value] of Object.entries(styles)) {
|
|
427
|
+
if (!PROTECTED_STYLES.has(key) && value !== void 0) {
|
|
428
|
+
el.style[key] = value;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function applyInitialClasses(elements, className) {
|
|
434
|
+
if (!className || elements.length === 0) return;
|
|
435
|
+
const classes = className.split(/\s+/).filter(Boolean);
|
|
436
|
+
for (const el of elements) {
|
|
437
|
+
el.classList.add(...classes);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
414
440
|
function groupIntoLines(elements, element) {
|
|
415
441
|
const fontSize = parseFloat(getComputedStyle(element).fontSize);
|
|
416
442
|
const tolerance = Math.max(5, fontSize * 0.3);
|
|
@@ -437,6 +463,7 @@ function groupIntoLines(elements, element) {
|
|
|
437
463
|
return lineGroups;
|
|
438
464
|
}
|
|
439
465
|
function performSplit(element, measuredWords, charClass, wordClass, lineClass, splitChars, splitWords, splitLines, options) {
|
|
466
|
+
var _a, _b;
|
|
440
467
|
element.textContent = "";
|
|
441
468
|
const allChars = [];
|
|
442
469
|
const allWords = [];
|
|
@@ -738,12 +765,34 @@ function performSplit(element, measuredWords, charClass, wordClass, lineClass, s
|
|
|
738
765
|
element.appendChild(lineSpan);
|
|
739
766
|
}
|
|
740
767
|
});
|
|
768
|
+
if (options == null ? void 0 : options.initialStyles) {
|
|
769
|
+
const { chars, words, lines } = options.initialStyles;
|
|
770
|
+
if (chars) applyInitialStyles(allChars, chars);
|
|
771
|
+
if (words) applyInitialStyles(allWords, words);
|
|
772
|
+
if (lines) applyInitialStyles(allLines, lines);
|
|
773
|
+
}
|
|
774
|
+
if (options == null ? void 0 : options.initialClasses) {
|
|
775
|
+
const { chars, words, lines } = options.initialClasses;
|
|
776
|
+
if (chars) applyInitialClasses(allChars, chars);
|
|
777
|
+
if (words) applyInitialClasses(allWords, words);
|
|
778
|
+
if (lines) applyInitialClasses(allLines, lines);
|
|
779
|
+
}
|
|
741
780
|
return {
|
|
742
781
|
chars: allChars,
|
|
743
782
|
words: splitWords ? allWords : [],
|
|
744
783
|
lines: allLines
|
|
745
784
|
};
|
|
746
785
|
}
|
|
786
|
+
if (options == null ? void 0 : options.initialStyles) {
|
|
787
|
+
const { chars, words } = options.initialStyles;
|
|
788
|
+
if (chars) applyInitialStyles(allChars, chars);
|
|
789
|
+
if (words) applyInitialStyles(allWords, words);
|
|
790
|
+
}
|
|
791
|
+
if (options == null ? void 0 : options.initialClasses) {
|
|
792
|
+
const { chars, words } = options.initialClasses;
|
|
793
|
+
if (chars) applyInitialClasses(allChars, chars);
|
|
794
|
+
if (words) applyInitialClasses(allWords, words);
|
|
795
|
+
}
|
|
747
796
|
return {
|
|
748
797
|
chars: allChars,
|
|
749
798
|
words: splitWords ? allWords : [],
|
|
@@ -795,6 +844,12 @@ function performSplit(element, measuredWords, charClass, wordClass, lineClass, s
|
|
|
795
844
|
element.appendChild(lineSpan);
|
|
796
845
|
}
|
|
797
846
|
});
|
|
847
|
+
if ((_a = options == null ? void 0 : options.initialStyles) == null ? void 0 : _a.lines) {
|
|
848
|
+
applyInitialStyles(allLines, options.initialStyles.lines);
|
|
849
|
+
}
|
|
850
|
+
if ((_b = options == null ? void 0 : options.initialClasses) == null ? void 0 : _b.lines) {
|
|
851
|
+
applyInitialClasses(allLines, options.initialClasses.lines);
|
|
852
|
+
}
|
|
798
853
|
return { chars: [], words: [], lines: allLines };
|
|
799
854
|
} else {
|
|
800
855
|
const fullText = measuredWords.map((w) => w.chars.map((c) => c.char).join("")).join(" ");
|
|
@@ -814,7 +869,9 @@ function splitText(element, {
|
|
|
814
869
|
onSplit,
|
|
815
870
|
revertOnComplete = false,
|
|
816
871
|
propIndex = false,
|
|
817
|
-
disableKerning = false
|
|
872
|
+
disableKerning = false,
|
|
873
|
+
initialStyles,
|
|
874
|
+
initialClasses
|
|
818
875
|
} = {}) {
|
|
819
876
|
var _a;
|
|
820
877
|
if (!(element instanceof HTMLElement)) {
|
|
@@ -865,7 +922,7 @@ function splitText(element, {
|
|
|
865
922
|
splitChars,
|
|
866
923
|
splitWords,
|
|
867
924
|
splitLines,
|
|
868
|
-
{ propIndex, mask, ariaHidden: !trackAncestors, disableKerning }
|
|
925
|
+
{ propIndex, mask, ariaHidden: !trackAncestors, disableKerning, initialStyles, initialClasses }
|
|
869
926
|
);
|
|
870
927
|
currentChars = chars;
|
|
871
928
|
currentWords = words;
|
|
@@ -940,7 +997,7 @@ function splitText(element, {
|
|
|
940
997
|
splitChars,
|
|
941
998
|
splitWords,
|
|
942
999
|
splitLines,
|
|
943
|
-
{ propIndex, mask, ariaHidden: !trackAncestors, disableKerning }
|
|
1000
|
+
{ propIndex, mask, ariaHidden: !trackAncestors, disableKerning, initialStyles, initialClasses }
|
|
944
1001
|
);
|
|
945
1002
|
currentChars = result.chars;
|
|
946
1003
|
currentWords = result.words;
|
package/dist/index.d.ts
CHANGED
|
@@ -46,7 +46,27 @@ interface SplitTextOptions {
|
|
|
46
46
|
* Kerning is naturally lost when splitting into inline-block spans.
|
|
47
47
|
* Use this if you prefer no compensation over imperfect Safari compensation. */
|
|
48
48
|
disableKerning?: boolean;
|
|
49
|
+
/** Apply initial inline styles to elements after split (and after kerning compensation).
|
|
50
|
+
* Can be a static style object or a function that receives (element, index). */
|
|
51
|
+
initialStyles?: {
|
|
52
|
+
chars?: InitialStyle;
|
|
53
|
+
words?: InitialStyle;
|
|
54
|
+
lines?: InitialStyle;
|
|
55
|
+
};
|
|
56
|
+
/** Apply initial classes to elements after split (and after kerning compensation).
|
|
57
|
+
* Classes are added via classList.add() and support space-separated class names. */
|
|
58
|
+
initialClasses?: {
|
|
59
|
+
chars?: string;
|
|
60
|
+
words?: string;
|
|
61
|
+
lines?: string;
|
|
62
|
+
};
|
|
49
63
|
}
|
|
64
|
+
/** Style value for initialStyles - a partial CSSStyleDeclaration object */
|
|
65
|
+
type InitialStyleValue = Partial<CSSStyleDeclaration>;
|
|
66
|
+
/** Function that returns styles based on element and index */
|
|
67
|
+
type InitialStyleFn = (element: HTMLElement, index: number) => InitialStyleValue;
|
|
68
|
+
/** Initial style can be a static object or a function */
|
|
69
|
+
type InitialStyle = InitialStyleValue | InitialStyleFn;
|
|
50
70
|
/**
|
|
51
71
|
* Result returned by splitText containing arrays of split elements and a revert function.
|
|
52
72
|
*
|
|
@@ -112,6 +132,6 @@ interface SplitTextResult {
|
|
|
112
132
|
* });
|
|
113
133
|
* ```
|
|
114
134
|
*/
|
|
115
|
-
declare function splitText(element: HTMLElement, { type, charClass, wordClass, lineClass, mask, autoSplit, onResize, onSplit, revertOnComplete, propIndex, disableKerning, }?: SplitTextOptions): SplitTextResult;
|
|
135
|
+
declare function splitText(element: HTMLElement, { type, charClass, wordClass, lineClass, mask, autoSplit, onResize, onSplit, revertOnComplete, propIndex, disableKerning, initialStyles, initialClasses, }?: SplitTextOptions): SplitTextResult;
|
|
116
136
|
|
|
117
137
|
export { type SplitTextOptions, type SplitTextResult, splitText };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { splitText } from './chunk-
|
|
1
|
+
export { splitText } from './chunk-NTRJ6XDH.js';
|
package/dist/react.d.ts
CHANGED
|
@@ -2,6 +2,24 @@ import * as react from 'react';
|
|
|
2
2
|
import { ReactElement } from 'react';
|
|
3
3
|
export { SplitTextOptions, SplitTextResult } from './index.js';
|
|
4
4
|
|
|
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
|
+
}
|
|
5
23
|
interface SplitTextOptions {
|
|
6
24
|
type?: "chars" | "words" | "lines" | "chars,words" | "words,lines" | "chars,lines" | "chars,words,lines";
|
|
7
25
|
charClass?: string;
|
|
@@ -71,6 +89,15 @@ interface SplitTextProps {
|
|
|
71
89
|
onInView?: (result: SplitTextElements) => CallbackReturn;
|
|
72
90
|
/** Called when element leaves viewport */
|
|
73
91
|
onLeaveView?: (result: SplitTextElements) => CallbackReturn;
|
|
92
|
+
/** Apply initial inline styles to elements after split (and after kerning compensation).
|
|
93
|
+
* Can be a static style object or a function that receives (element, index). */
|
|
94
|
+
initialStyles?: InitialStyles;
|
|
95
|
+
/** Apply initial classes to elements after split (and after kerning compensation).
|
|
96
|
+
* Classes are added via classList.add() and support space-separated class names. */
|
|
97
|
+
initialClasses?: InitialClasses;
|
|
98
|
+
/** Re-apply initialStyles and initialClasses when element leaves viewport.
|
|
99
|
+
* Useful for scroll-triggered animations that should reset when scrolling away. */
|
|
100
|
+
resetOnLeave?: boolean;
|
|
74
101
|
}
|
|
75
102
|
/**
|
|
76
103
|
* React component wrapper for text splitting with kerning compensation.
|
package/dist/react.js
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
|
-
import { splitText, __spreadProps, __spreadValues, normalizeToPromise } from './chunk-
|
|
1
|
+
import { splitText, __spreadProps, __spreadValues, normalizeToPromise } from './chunk-NTRJ6XDH.js';
|
|
2
2
|
import { forwardRef, useRef, useCallback, useState, useLayoutEffect, useEffect, isValidElement, cloneElement } from 'react';
|
|
3
3
|
import { jsx } from 'react/jsx-runtime';
|
|
4
4
|
|
|
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
|
+
}
|
|
5
25
|
var SplitText = forwardRef(
|
|
6
26
|
function SplitText2({
|
|
7
27
|
children,
|
|
@@ -15,7 +35,10 @@ var SplitText = forwardRef(
|
|
|
15
35
|
revertOnComplete = false,
|
|
16
36
|
inView,
|
|
17
37
|
onInView,
|
|
18
|
-
onLeaveView
|
|
38
|
+
onLeaveView,
|
|
39
|
+
initialStyles,
|
|
40
|
+
initialClasses,
|
|
41
|
+
resetOnLeave = false
|
|
19
42
|
}, forwardedRef) {
|
|
20
43
|
const containerRef = useRef(null);
|
|
21
44
|
const mergedRef = useCallback(
|
|
@@ -38,6 +61,9 @@ var SplitText = forwardRef(
|
|
|
38
61
|
const inViewRef = useRef(inView);
|
|
39
62
|
const onInViewRef = useRef(onInView);
|
|
40
63
|
const onLeaveViewRef = useRef(onLeaveView);
|
|
64
|
+
const initialStylesRef = useRef(initialStyles);
|
|
65
|
+
const initialClassesRef = useRef(initialClasses);
|
|
66
|
+
const resetOnLeaveRef = useRef(resetOnLeave);
|
|
41
67
|
useLayoutEffect(() => {
|
|
42
68
|
onSplitRef.current = onSplit;
|
|
43
69
|
onResizeRef.current = onResize;
|
|
@@ -46,6 +72,9 @@ var SplitText = forwardRef(
|
|
|
46
72
|
inViewRef.current = inView;
|
|
47
73
|
onInViewRef.current = onInView;
|
|
48
74
|
onLeaveViewRef.current = onLeaveView;
|
|
75
|
+
initialStylesRef.current = initialStyles;
|
|
76
|
+
initialClassesRef.current = initialClasses;
|
|
77
|
+
resetOnLeaveRef.current = resetOnLeave;
|
|
49
78
|
});
|
|
50
79
|
const hasSplitRef = useRef(false);
|
|
51
80
|
const hasRevertedRef = useRef(false);
|
|
@@ -67,6 +96,8 @@ var SplitText = forwardRef(
|
|
|
67
96
|
const result = splitText(childElement, __spreadProps(__spreadValues({}, optionsRef.current), {
|
|
68
97
|
autoSplit,
|
|
69
98
|
revertOnComplete: revertOnCompleteRef.current,
|
|
99
|
+
initialStyles: initialStylesRef.current,
|
|
100
|
+
initialClasses: initialClassesRef.current,
|
|
70
101
|
onResize: (resizeResult) => {
|
|
71
102
|
var _a2;
|
|
72
103
|
const newSplitTextElements = {
|
|
@@ -159,8 +190,25 @@ var SplitText = forwardRef(
|
|
|
159
190
|
console.warn("[fetta] Animation rejected, text not reverted");
|
|
160
191
|
});
|
|
161
192
|
}
|
|
162
|
-
} else if (!isInView &&
|
|
163
|
-
|
|
193
|
+
} else if (!isInView && splitResultRef.current) {
|
|
194
|
+
if (resetOnLeaveRef.current) {
|
|
195
|
+
const { chars, words, lines } = splitResultRef.current;
|
|
196
|
+
const styles = initialStylesRef.current;
|
|
197
|
+
const classes = initialClassesRef.current;
|
|
198
|
+
if (styles) {
|
|
199
|
+
reapplyInitialStyles(chars, styles.chars);
|
|
200
|
+
reapplyInitialStyles(words, styles.words);
|
|
201
|
+
reapplyInitialStyles(lines, styles.lines);
|
|
202
|
+
}
|
|
203
|
+
if (classes) {
|
|
204
|
+
reapplyInitialClasses(chars, classes.chars);
|
|
205
|
+
reapplyInitialClasses(words, classes.words);
|
|
206
|
+
reapplyInitialClasses(lines, classes.lines);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (onLeaveViewRef.current) {
|
|
210
|
+
onLeaveViewRef.current(splitResultRef.current);
|
|
211
|
+
}
|
|
164
212
|
}
|
|
165
213
|
}, [isInView]);
|
|
166
214
|
if (!isValidElement(children)) {
|