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 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 Element Support** — Preserves inline HTML elements (`<a>`, `<em>`, `<strong>`, etc.) with all attributes intact
11
- - **Natural Line Wrapping** — Detects lines based on Y-position clustering, works with any container width
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
- - **Viewport Detection** — Built-in IntersectionObserver support for scroll-triggered animations
14
+ - **Auto-Revert** — Restore original HTML after animations
15
15
  - **Masking** — Wrap elements in clip containers for reveal animations
16
- - **Animation Agnostic** — Works with Motion, GSAP, or any animation library
17
- - **React Component** — First-class React support with hooks and cleanup
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 = false
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 measuredWords = measureOriginalText(element, splitChars, trackAncestors);
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, splitChars, trackAncestors);
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-KGMU2B53.js';
1
+ export { splitText } from './chunk-G5P33GJE.js';
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { splitText, __spreadProps, __spreadValues, normalizeToPromise } from './chunk-KGMU2B53.js';
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
- if (!isOnce) {
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fetta",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Text splitting library with kerning compensation for animations",
5
5
  "type": "module",
6
6
  "sideEffects": false,