fetta 1.5.4 → 1.5.5
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 +3 -1
- package/dist/{chunk-OUXSJF3P.js → chunk-44MU2I5B.js} +18 -1
- package/dist/helpers.d.ts +1 -1
- package/dist/{index-c1UKfWWK.d.ts → index-Box92Sue.d.ts} +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/motion.d.ts +4 -2
- package/dist/motion.js +35 -2
- package/dist/react.d.ts +4 -2
- package/dist/react.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,6 +109,7 @@ const result = splitText(element, options);
|
|
|
109
109
|
| `lineClass` | `string` | `"split-line"` | CSS class for line elements |
|
|
110
110
|
| `mask` | `"chars" \| "words" \| "lines"` | — | Wrap elements in `overflow: clip` container |
|
|
111
111
|
| `autoSplit` | `boolean` | `false` | Re-split on container resize |
|
|
112
|
+
| `resplitDebounceMs` | `number` | `100` | Debounce delay for autoSplit/full-resplit width updates (`0` disables debounce) |
|
|
112
113
|
| `onResplit` | `(result) => void` | — | Callback after autoSplit/full-resplit replaces split output elements |
|
|
113
114
|
| `onSplit` | `(result) => CallbackReturn` | — | Callback after initial split. Return animation/promise for `revertOnComplete` |
|
|
114
115
|
| `revertOnComplete` | `boolean` | `false` | Auto-revert when returned animation completes |
|
|
@@ -158,7 +159,7 @@ import { SplitText } from "fetta/react";
|
|
|
158
159
|
| `ref` | `Ref<HTMLElement>` | — | Ref to wrapper element |
|
|
159
160
|
| `onSplit` | `(result) => CallbackReturn` | — | Called after initial split |
|
|
160
161
|
| `onResplit` | `(result) => void` | — | Called when autoSplit/full-resplit replaces split output elements |
|
|
161
|
-
| `options` | `SplitTextOptions` | — | Split options (`type`, classes, mask, etc.) |
|
|
162
|
+
| `options` | `SplitTextOptions` | — | Split options (`type`, classes, mask, debounce, etc.) |
|
|
162
163
|
| `autoSplit` | `boolean` | `false` | Re-split on container resize |
|
|
163
164
|
| `waitForFonts` | `boolean` | `true` | Wait for `document.fonts.ready` before splitting |
|
|
164
165
|
| `revertOnComplete` | `boolean` | `false` | Revert after animation completes |
|
|
@@ -324,6 +325,7 @@ Fetta keeps split text readable by screen readers:
|
|
|
324
325
|
|
|
325
326
|
- Ligatures are disabled (`font-variant-ligatures: none`) because ligatures cannot span multiple elements.
|
|
326
327
|
- Authored hard breaks are preserved (`<br>` and block boundaries are kept as hard split boundaries).
|
|
328
|
+
- `autoSplit` width resplits are debounced by default (`100ms`), configurable via `resplitDebounceMs`.
|
|
327
329
|
|
|
328
330
|
## Browser Support
|
|
329
331
|
|
|
@@ -470,6 +470,13 @@ var ARIA_LABEL_ALLOWED_TAGS = /* @__PURE__ */ new Set([
|
|
|
470
470
|
"footer",
|
|
471
471
|
"main"
|
|
472
472
|
]);
|
|
473
|
+
var DEFAULT_RESPLIT_DEBOUNCE_MS = 100;
|
|
474
|
+
function resolveResplitDebounceMs(value) {
|
|
475
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
476
|
+
return DEFAULT_RESPLIT_DEBOUNCE_MS;
|
|
477
|
+
}
|
|
478
|
+
return value;
|
|
479
|
+
}
|
|
473
480
|
function splitTextData(element, rawOptions = {}) {
|
|
474
481
|
const options = rawOptions;
|
|
475
482
|
const {
|
|
@@ -1378,6 +1385,7 @@ function splitText(element, rawOptions = {}) {
|
|
|
1378
1385
|
lineClass = "split-line",
|
|
1379
1386
|
mask,
|
|
1380
1387
|
autoSplit = false,
|
|
1388
|
+
resplitDebounceMs,
|
|
1381
1389
|
onResplit,
|
|
1382
1390
|
onSplit,
|
|
1383
1391
|
revertOnComplete = false,
|
|
@@ -1387,6 +1395,7 @@ function splitText(element, rawOptions = {}) {
|
|
|
1387
1395
|
initialClasses
|
|
1388
1396
|
} = options;
|
|
1389
1397
|
const isolateKerningMeasurement = options.isolateKerningMeasurement !== false;
|
|
1398
|
+
const resolvedResplitDebounceMs = resolveResplitDebounceMs(resplitDebounceMs);
|
|
1390
1399
|
if (!(element instanceof HTMLElement)) {
|
|
1391
1400
|
throw new Error("splitText: element must be an HTMLElement");
|
|
1392
1401
|
}
|
|
@@ -1717,8 +1726,16 @@ function splitText(element, rawOptions = {}) {
|
|
|
1717
1726
|
lastChangedTarget = changedTarget;
|
|
1718
1727
|
if (autoSplitDebounceTimer) {
|
|
1719
1728
|
clearTimeout(autoSplitDebounceTimer);
|
|
1729
|
+
autoSplitDebounceTimer = null;
|
|
1720
1730
|
}
|
|
1721
|
-
|
|
1731
|
+
if (resolvedResplitDebounceMs <= 0) {
|
|
1732
|
+
handleResize();
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
autoSplitDebounceTimer = setTimeout(
|
|
1736
|
+
handleResize,
|
|
1737
|
+
resolvedResplitDebounceMs
|
|
1738
|
+
);
|
|
1722
1739
|
});
|
|
1723
1740
|
targets.forEach((target) => {
|
|
1724
1741
|
autoSplitObserver.observe(target);
|
package/dist/helpers.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ interface SplitTextOptions {
|
|
|
32
32
|
mask?: "lines" | "words" | "chars";
|
|
33
33
|
/** Auto-split on resize (observes parent element) */
|
|
34
34
|
autoSplit?: boolean;
|
|
35
|
+
/** Debounce delay for autoSplit/full-resplit width updates in milliseconds (`0` disables debounce). */
|
|
36
|
+
resplitDebounceMs?: number;
|
|
35
37
|
/** Callback when autoSplit/full-resplit replaces split output elements */
|
|
36
38
|
onResplit?: (result: Omit<SplitTextResult, "revert" | "dispose">) => void;
|
|
37
39
|
/** Callback fired after text is split, receives split elements. Return animation for revertOnComplete. */
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { a as SplitTextOptions, S as SplitTextResult, s as splitText } from './index-
|
|
1
|
+
export { a as SplitTextOptions, S as SplitTextResult, s as splitText } from './index-Box92Sue.js';
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { splitText } from './chunk-
|
|
1
|
+
export { splitText } from './chunk-44MU2I5B.js';
|
package/dist/motion.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { I as InitialStyles, a as InitialClasses } from './initialStyles-BGuPp5C
|
|
|
2
2
|
import { DOMKeyframesDefinition, AnimationOptions, scroll } from 'motion';
|
|
3
3
|
import { HTMLMotionProps } from 'motion/react';
|
|
4
4
|
import { ReactElement, RefAttributes } from 'react';
|
|
5
|
-
import { A as AnimationCallbackReturn } from './index-
|
|
6
|
-
export { a as CoreSplitTextOptions, S as SplitTextResult } from './index-
|
|
5
|
+
import { A as AnimationCallbackReturn } from './index-Box92Sue.js';
|
|
6
|
+
export { a as CoreSplitTextOptions, S as SplitTextResult } from './index-Box92Sue.js';
|
|
7
7
|
|
|
8
8
|
interface SplitTextOptions {
|
|
9
9
|
type?: "chars" | "words" | "lines" | "chars,words" | "words,lines" | "chars,lines" | "chars,words,lines";
|
|
@@ -12,6 +12,8 @@ interface SplitTextOptions {
|
|
|
12
12
|
lineClass?: string;
|
|
13
13
|
/** Apply overflow mask wrapper to elements for reveal animations */
|
|
14
14
|
mask?: "lines" | "words" | "chars";
|
|
15
|
+
/** Debounce delay for autoSplit/full-resplit width updates in milliseconds (`0` disables debounce). */
|
|
16
|
+
resplitDebounceMs?: number;
|
|
15
17
|
propIndex?: boolean;
|
|
16
18
|
/** Skip kerning compensation (no margin adjustments applied).
|
|
17
19
|
* Kerning is naturally lost when splitting into inline-block spans.
|
package/dist/motion.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { waitForFontsReady, createViewportObserver, reapplyInitialStyles, reapplyInitialClasses } from './chunk-PA22PLRB.js';
|
|
2
|
-
import { splitTextData, buildLineFingerprintFromData, normalizeToPromise, buildKerningStyleKey, resolveAutoSplitTargets, getObservedWidth, recordWidthChange, resolveAutoSplitWidth, querySplitWords, clearKerningCompensation, applyKerningCompensation } from './chunk-
|
|
2
|
+
import { splitTextData, buildLineFingerprintFromData, normalizeToPromise, buildKerningStyleKey, resolveAutoSplitTargets, getObservedWidth, recordWidthChange, resolveAutoSplitWidth, querySplitWords, clearKerningCompensation, applyKerningCompensation } from './chunk-44MU2I5B.js';
|
|
3
3
|
import { scroll, animate } from 'motion';
|
|
4
4
|
import { usePresence, useReducedMotion, MotionConfig, motion } from 'motion/react';
|
|
5
5
|
import { forwardRef, useRef, useState, useCallback, useMemo, useLayoutEffect, isValidElement, useEffect, createElement, cloneElement } from 'react';
|
|
6
6
|
|
|
7
|
+
var DEFAULT_RESPLIT_DEBOUNCE_MS = 100;
|
|
8
|
+
function resolveResplitDebounceMs(value) {
|
|
9
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
10
|
+
return DEFAULT_RESPLIT_DEBOUNCE_MS;
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
7
14
|
var ELEMENT_TYPE_KEYS = ["chars", "words", "lines"];
|
|
8
15
|
var VOID_HTML_TAGS = /* @__PURE__ */ new Set(["br", "hr", "img", "input", "meta", "link"]);
|
|
9
16
|
function isPerTypeVariant(v) {
|
|
@@ -1097,6 +1104,15 @@ var SplitText = forwardRef(function SplitText2({
|
|
|
1097
1104
|
}
|
|
1098
1105
|
return safeFallbackWidth;
|
|
1099
1106
|
}, []);
|
|
1107
|
+
const lockCurrentRenderedLines = useCallback((root) => {
|
|
1108
|
+
const lineClass = optionsRef.current?.lineClass ?? "split-line";
|
|
1109
|
+
const classTokens = lineClass.split(/\s+/).filter(Boolean);
|
|
1110
|
+
if (classTokens.length === 0) return;
|
|
1111
|
+
const selector = `.${classTokens.join(".")}`;
|
|
1112
|
+
root.querySelectorAll(selector).forEach((line) => {
|
|
1113
|
+
line.style.whiteSpace = "nowrap";
|
|
1114
|
+
});
|
|
1115
|
+
}, []);
|
|
1100
1116
|
useEffect(() => {
|
|
1101
1117
|
if (!childElement) return;
|
|
1102
1118
|
if (hasSplitRef.current) return;
|
|
@@ -1526,6 +1542,7 @@ var SplitText = forwardRef(function SplitText2({
|
|
|
1526
1542
|
clearTimeout(resizeTimerRef.current);
|
|
1527
1543
|
resizeTimerRef.current = null;
|
|
1528
1544
|
}
|
|
1545
|
+
lockCurrentRenderedLines(currentElement);
|
|
1529
1546
|
pendingFullResplitRef.current = true;
|
|
1530
1547
|
let resplitWidth;
|
|
1531
1548
|
const targets = resolveAutoSplitTargets(currentElement);
|
|
@@ -1607,6 +1624,7 @@ var SplitText = forwardRef(function SplitText2({
|
|
|
1607
1624
|
autoSplit,
|
|
1608
1625
|
childTreeVersion,
|
|
1609
1626
|
data,
|
|
1627
|
+
lockCurrentRenderedLines,
|
|
1610
1628
|
measureAndSetData,
|
|
1611
1629
|
resolveLineMeasureWidth
|
|
1612
1630
|
]);
|
|
@@ -1707,14 +1725,28 @@ var SplitText = forwardRef(function SplitText2({
|
|
|
1707
1725
|
}
|
|
1708
1726
|
if (resizeTimerRef.current) {
|
|
1709
1727
|
clearTimeout(resizeTimerRef.current);
|
|
1728
|
+
resizeTimerRef.current = null;
|
|
1729
|
+
}
|
|
1730
|
+
const debounceMs = resolveResplitDebounceMs(
|
|
1731
|
+
optionsRef.current?.resplitDebounceMs
|
|
1732
|
+
);
|
|
1733
|
+
if (debounceMs <= 0) {
|
|
1734
|
+
lockCurrentRenderedLines(child2);
|
|
1735
|
+
pendingFullResplitRef.current = true;
|
|
1736
|
+
measureAndSetData(
|
|
1737
|
+
true,
|
|
1738
|
+
splitLines ? lineMeasureWidth : currentWidth
|
|
1739
|
+
);
|
|
1740
|
+
return;
|
|
1710
1741
|
}
|
|
1711
1742
|
resizeTimerRef.current = setTimeout(() => {
|
|
1743
|
+
lockCurrentRenderedLines(child2);
|
|
1712
1744
|
pendingFullResplitRef.current = true;
|
|
1713
1745
|
measureAndSetData(
|
|
1714
1746
|
true,
|
|
1715
1747
|
splitLines ? lineMeasureWidth : currentWidth
|
|
1716
1748
|
);
|
|
1717
|
-
},
|
|
1749
|
+
}, debounceMs);
|
|
1718
1750
|
};
|
|
1719
1751
|
resizeObserverRef.current = new ResizeObserver((entries) => {
|
|
1720
1752
|
let changed = false;
|
|
@@ -1755,6 +1787,7 @@ var SplitText = forwardRef(function SplitText2({
|
|
|
1755
1787
|
}, [
|
|
1756
1788
|
autoSplit,
|
|
1757
1789
|
data,
|
|
1790
|
+
lockCurrentRenderedLines,
|
|
1758
1791
|
measureAndSetData,
|
|
1759
1792
|
measureLineFingerprintForWidth,
|
|
1760
1793
|
resolveLineMeasureWidth
|
package/dist/react.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactElement } from 'react';
|
|
3
|
-
import { A as AnimationCallbackReturn } from './index-
|
|
4
|
-
export { a as CoreSplitTextOptions, S as SplitTextResult } from './index-
|
|
3
|
+
import { A as AnimationCallbackReturn } from './index-Box92Sue.js';
|
|
4
|
+
export { a as CoreSplitTextOptions, S as SplitTextResult } from './index-Box92Sue.js';
|
|
5
5
|
import { I as InitialStyles, a as InitialClasses } from './initialStyles-BGuPp5CS.js';
|
|
6
6
|
|
|
7
7
|
interface SplitTextOptions {
|
|
@@ -11,6 +11,8 @@ interface SplitTextOptions {
|
|
|
11
11
|
lineClass?: string;
|
|
12
12
|
/** Apply overflow mask wrapper to elements for reveal animations */
|
|
13
13
|
mask?: "lines" | "words" | "chars";
|
|
14
|
+
/** Debounce delay for autoSplit/full-resplit width updates in milliseconds (`0` disables debounce). */
|
|
15
|
+
resplitDebounceMs?: number;
|
|
14
16
|
propIndex?: boolean;
|
|
15
17
|
/** Skip kerning compensation (no margin adjustments applied).
|
|
16
18
|
* Kerning is naturally lost when splitting into inline-block spans.
|
package/dist/react.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { waitForFontsReady, createViewportObserver, reapplyInitialStyles, reapplyInitialClasses } from './chunk-PA22PLRB.js';
|
|
2
|
-
import { splitText, normalizeToPromise } from './chunk-
|
|
2
|
+
import { splitText, normalizeToPromise } from './chunk-44MU2I5B.js';
|
|
3
3
|
import { forwardRef, useRef, useCallback, useState, useLayoutEffect, useEffect, isValidElement, createElement } from 'react';
|
|
4
4
|
|
|
5
5
|
var SplitText = forwardRef(
|