fetta 1.0.0 → 1.0.2
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 +15 -5
- package/dist/{chunk-KGMU2B53.js → chunk-G5P33GJE.js} +12 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/react.js +7 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,14 +7,18 @@ Split text into characters, words, and lines while preserving the original typog
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- **Kerning Compensation** — Measures character positions before splitting, then applies margin adjustments to maintain original spacing
|
|
10
|
-
- **Nested
|
|
11
|
-
- **
|
|
10
|
+
- **Nested Elements** — Preserves inline HTML elements (`<a>`, `<em>`, `<strong>`, etc.) with all attributes intact
|
|
11
|
+
- **Line Detection** — Detects lines based on Y-position clustering, works with any container width
|
|
12
12
|
- **Dash Handling** — Allows text to wrap naturally after em-dashes, en-dashes, and hyphens
|
|
13
13
|
- **Auto Re-split** — Automatically re-splits on container resize with debouncing
|
|
14
|
-
- **
|
|
14
|
+
- **Auto-Revert** — Restore original HTML after animations
|
|
15
15
|
- **Masking** — Wrap elements in clip containers for reveal animations
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
16
|
+
- **Emoji Support** — Properly handles compound emojis and complex Unicode characters
|
|
17
|
+
- **Accessible** — Adds `aria-label` with original text for screen readers
|
|
18
|
+
- **TypeScript** — Full type definitions included
|
|
19
|
+
- **React Component** — Declarative wrapper for React projects
|
|
20
|
+
- **Built-in InView** — Viewport detection for scroll-triggered animations in React
|
|
21
|
+
- **Library Agnostic** — Works with Motion, GSAP, or any animation library
|
|
18
22
|
|
|
19
23
|
## Installation
|
|
20
24
|
|
|
@@ -262,6 +266,12 @@ Requires:
|
|
|
262
266
|
- `IntersectionObserver`
|
|
263
267
|
- `Intl.Segmenter`
|
|
264
268
|
|
|
269
|
+
### Safari
|
|
270
|
+
|
|
271
|
+
Kerning compensation is not available in Safari due to its Range API returning integer values instead of sub-pixel precision. Text splitting works normally, just without the margin adjustments.
|
|
272
|
+
|
|
273
|
+
When using `revertOnComplete` with character splitting in Safari, font kerning is automatically disabled to prevent visual shift on revert.
|
|
274
|
+
|
|
265
275
|
## License
|
|
266
276
|
|
|
267
277
|
MIT
|
|
@@ -63,6 +63,7 @@ var INLINE_ELEMENTS = /* @__PURE__ */ new Set([
|
|
|
63
63
|
"u",
|
|
64
64
|
"var"
|
|
65
65
|
]);
|
|
66
|
+
var isSafari = typeof navigator !== "undefined" && /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
|
|
66
67
|
function hasInlineDescendants(element) {
|
|
67
68
|
const walker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT);
|
|
68
69
|
let node;
|
|
@@ -429,7 +430,7 @@ function performSplit(element, measuredWords, charClass, wordClass, lineClass, s
|
|
|
429
430
|
i++;
|
|
430
431
|
}
|
|
431
432
|
}
|
|
432
|
-
if (splitChars && allChars.length > 1) {
|
|
433
|
+
if (splitChars && allChars.length > 1 && !isSafari) {
|
|
433
434
|
const positions = allChars.map((c) => c.getBoundingClientRect().left);
|
|
434
435
|
for (let i2 = 1; i2 < allChars.length; i2++) {
|
|
435
436
|
const charSpan = allChars[i2];
|
|
@@ -593,7 +594,7 @@ function splitText(element, {
|
|
|
593
594
|
onSplit,
|
|
594
595
|
revertOnComplete = false,
|
|
595
596
|
propIndex = false,
|
|
596
|
-
willChange =
|
|
597
|
+
willChange = true
|
|
597
598
|
} = {}) {
|
|
598
599
|
var _a;
|
|
599
600
|
if (!(element instanceof HTMLElement)) {
|
|
@@ -634,8 +635,12 @@ function splitText(element, {
|
|
|
634
635
|
if (splitChars) {
|
|
635
636
|
element.style.fontVariantLigatures = "none";
|
|
636
637
|
}
|
|
638
|
+
if (isSafari && splitChars && revertOnComplete) {
|
|
639
|
+
element.style.fontKerning = "none";
|
|
640
|
+
}
|
|
637
641
|
const trackAncestors = hasInlineDescendants(element);
|
|
638
|
-
const
|
|
642
|
+
const measureChars = splitChars && !isSafari;
|
|
643
|
+
const measuredWords = measureOriginalText(element, measureChars, trackAncestors);
|
|
639
644
|
const { chars, words, lines } = performSplit(
|
|
640
645
|
element,
|
|
641
646
|
measuredWords,
|
|
@@ -669,6 +674,9 @@ function splitText(element, {
|
|
|
669
674
|
if (splitChars) {
|
|
670
675
|
element.style.fontVariantLigatures = "none";
|
|
671
676
|
}
|
|
677
|
+
if (isSafari && splitChars && revertOnComplete) {
|
|
678
|
+
element.style.fontKerning = "none";
|
|
679
|
+
}
|
|
672
680
|
dispose();
|
|
673
681
|
};
|
|
674
682
|
if (autoSplit) {
|
|
@@ -695,7 +703,7 @@ function splitText(element, {
|
|
|
695
703
|
element.innerHTML = originalHTML;
|
|
696
704
|
requestAnimationFrame(() => {
|
|
697
705
|
if (!isActive) return;
|
|
698
|
-
const newMeasuredWords = measureOriginalText(element,
|
|
706
|
+
const newMeasuredWords = measureOriginalText(element, measureChars, trackAncestors);
|
|
699
707
|
const result = performSplit(
|
|
700
708
|
element,
|
|
701
709
|
newMeasuredWords,
|
package/dist/index.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ interface SplitTextOptions {
|
|
|
42
42
|
revertOnComplete?: boolean;
|
|
43
43
|
/** Add CSS custom properties (--char-index, --word-index, --line-index) */
|
|
44
44
|
propIndex?: boolean;
|
|
45
|
-
/** Add will-change: transform, opacity to split elements for better animation performance */
|
|
45
|
+
/** Add will-change: transform, opacity to split elements for better animation performance (default: true) */
|
|
46
46
|
willChange?: boolean;
|
|
47
47
|
}
|
|
48
48
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { splitText } from './chunk-
|
|
1
|
+
export { splitText } from './chunk-G5P33GJE.js';
|
package/dist/react.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { splitText, __spreadProps, __spreadValues, normalizeToPromise } from './chunk-
|
|
1
|
+
import { splitText, __spreadProps, __spreadValues, normalizeToPromise } from './chunk-G5P33GJE.js';
|
|
2
2
|
import { forwardRef, useRef, useCallback, useState, useLayoutEffect, useEffect, isValidElement, cloneElement } from 'react';
|
|
3
3
|
import { jsx } from 'react/jsx-runtime';
|
|
4
4
|
|
|
@@ -63,6 +63,7 @@ var SplitText = forwardRef(
|
|
|
63
63
|
if (!containerRef.current) return;
|
|
64
64
|
const result = splitText(childElement, __spreadProps(__spreadValues({}, optionsRef.current), {
|
|
65
65
|
autoSplit,
|
|
66
|
+
revertOnComplete: revertOnCompleteRef.current,
|
|
66
67
|
onResize: (resizeResult) => {
|
|
67
68
|
var _a2;
|
|
68
69
|
const newSplitTextElements = {
|
|
@@ -108,22 +109,21 @@ var SplitText = forwardRef(
|
|
|
108
109
|
const inViewOptions = typeof inViewRef.current === "object" ? inViewRef.current : {};
|
|
109
110
|
const threshold = (_a = inViewOptions.amount) != null ? _a : 0;
|
|
110
111
|
const rootMargin = (_b = inViewOptions.margin) != null ? _b : "0px";
|
|
112
|
+
const thresholds = threshold > 0 ? [0, threshold] : 0;
|
|
111
113
|
observerRef.current = new IntersectionObserver(
|
|
112
114
|
(entries) => {
|
|
113
115
|
const entry = entries[0];
|
|
114
116
|
if (!entry) return;
|
|
115
117
|
const isOnce = typeof inViewRef.current === "object" && inViewRef.current.once;
|
|
116
|
-
if (entry.isIntersecting) {
|
|
118
|
+
if (entry.isIntersecting && entry.intersectionRatio >= threshold) {
|
|
117
119
|
if (isOnce && hasTriggeredOnceRef.current) return;
|
|
118
120
|
hasTriggeredOnceRef.current = true;
|
|
119
121
|
setIsInView(true);
|
|
120
|
-
} else {
|
|
121
|
-
|
|
122
|
-
setIsInView(false);
|
|
123
|
-
}
|
|
122
|
+
} else if (!entry.isIntersecting && !isOnce) {
|
|
123
|
+
setIsInView(false);
|
|
124
124
|
}
|
|
125
125
|
},
|
|
126
|
-
{ threshold, rootMargin }
|
|
126
|
+
{ threshold: thresholds, rootMargin }
|
|
127
127
|
);
|
|
128
128
|
observerRef.current.observe(containerRef.current);
|
|
129
129
|
}
|