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 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
- autoSplitDebounceTimer = setTimeout(handleResize, 100);
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
@@ -1,4 +1,4 @@
1
- import { S as SplitTextResult } from './index-c1UKfWWK.js';
1
+ import { S as SplitTextResult } from './index-Box92Sue.js';
2
2
 
3
3
  type SplitUnit = "chars" | "words" | "lines";
4
4
  type StyleInput = Partial<CSSStyleDeclaration> | ((ctx: {
@@ -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-c1UKfWWK.js';
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-OUXSJF3P.js';
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-c1UKfWWK.js';
6
- export { a as CoreSplitTextOptions, S as SplitTextResult } from './index-c1UKfWWK.js';
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-OUXSJF3P.js';
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
- }, 100);
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-c1UKfWWK.js';
4
- export { a as CoreSplitTextOptions, S as SplitTextResult } from './index-c1UKfWWK.js';
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-OUXSJF3P.js';
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(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fetta",
3
- "version": "1.5.4",
3
+ "version": "1.5.5",
4
4
  "description": "Text splitting library with kerning compensation for animations",
5
5
  "type": "module",
6
6
  "sideEffects": false,