fetta 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,267 @@
1
+ # fetta
2
+
3
+ A text-splitting library with kerning compensation for smooth, natural text animations.
4
+
5
+ Split text into characters, words, and lines while preserving the original typography. Works with any animation library.
6
+
7
+ ## Features
8
+
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
12
+ - **Dash Handling** — Allows text to wrap naturally after em-dashes, en-dashes, and hyphens
13
+ - **Auto Re-split** — Automatically re-splits on container resize with debouncing
14
+ - **Viewport Detection** — Built-in IntersectionObserver support for scroll-triggered animations
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
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install fetta
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ### Vanilla JavaScript
28
+
29
+ ```js
30
+ import { splitText } from 'fetta';
31
+ import { animate, stagger } from 'motion';
32
+
33
+ const { chars, words, lines, revert } = splitText(
34
+ document.querySelector('h1'),
35
+ { type: 'chars,words,lines' }
36
+ );
37
+
38
+ animate(chars, { opacity: [0, 1], y: [20, 0] }, { delay: stagger(0.02) });
39
+ ```
40
+
41
+ ### React
42
+
43
+ ```tsx
44
+ import { SplitText } from 'fetta/react';
45
+ import { animate, stagger } from 'motion';
46
+
47
+ function Hero() {
48
+ return (
49
+ <SplitText
50
+ onSplit={({ words }) => {
51
+ animate(words, { opacity: [0, 1], y: [20, 0] }, { delay: stagger(0.05) });
52
+ }}
53
+ >
54
+ <h1>Hello World</h1>
55
+ </SplitText>
56
+ );
57
+ }
58
+ ```
59
+
60
+ ## API
61
+
62
+ ### `splitText(element, options?)`
63
+
64
+ Splits text content into characters, words, and/or lines.
65
+
66
+ ```ts
67
+ const result = splitText(element, options);
68
+ ```
69
+
70
+ #### Options
71
+
72
+ | Option | Type | Default | Description |
73
+ |--------|------|---------|-------------|
74
+ | `type` | `string` | `"chars,words,lines"` | What to split: `"chars"`, `"words"`, `"lines"`, or combinations |
75
+ | `charClass` | `string` | `"split-char"` | CSS class for character elements |
76
+ | `wordClass` | `string` | `"split-word"` | CSS class for word elements |
77
+ | `lineClass` | `string` | `"split-line"` | CSS class for line elements |
78
+ | `mask` | `"lines" \| "words" \| "chars"` | — | Wraps elements in `overflow: clip` container for reveal animations |
79
+ | `autoSplit` | `boolean` | `false` | Re-split on container resize |
80
+ | `onResize` | `function` | — | Callback after resize re-split |
81
+ | `onSplit` | `function` | — | Callback after initial split |
82
+ | `revertOnComplete` | `boolean` | `false` | Auto-revert when animation completes |
83
+ | `propIndex` | `boolean` | `false` | Add CSS custom properties: `--char-index`, `--word-index`, `--line-index` |
84
+ | `willChange` | `boolean` | `false` | Add `will-change: transform, opacity` for performance |
85
+
86
+ #### Return Value
87
+
88
+ ```ts
89
+ {
90
+ chars: HTMLSpanElement[]; // Character elements
91
+ words: HTMLSpanElement[]; // Word elements
92
+ lines: HTMLSpanElement[]; // Line elements
93
+ revert: () => void; // Restore original HTML and cleanup
94
+ }
95
+ ```
96
+
97
+ ### `<SplitText>` (React)
98
+
99
+ ```tsx
100
+ import { SplitText } from 'fetta/react';
101
+ ```
102
+
103
+ #### Props
104
+
105
+ | Prop | Type | Default | Description |
106
+ |------|------|---------|-------------|
107
+ | `children` | `ReactElement` | — | Single element to split |
108
+ | `onSplit` | `function` | — | Called after text is split |
109
+ | `onResize` | `function` | — | Called on autoSplit re-split |
110
+ | `options` | `object` | — | Split options (type, classes, mask, propIndex, willChange) |
111
+ | `autoSplit` | `boolean` | `false` | Re-split on container resize |
112
+ | `revertOnComplete` | `boolean` | `false` | Revert after animation completes |
113
+ | `inView` | `boolean \| InViewOptions` | `false` | Enable viewport detection |
114
+ | `onInView` | `function` | — | Called when element enters viewport |
115
+ | `onLeaveView` | `function` | — | Called when element leaves viewport |
116
+
117
+ #### InView Options
118
+
119
+ ```ts
120
+ {
121
+ amount?: number; // How much must be visible (0-1), default: 0
122
+ margin?: string; // Root margin, default: "0px"
123
+ once?: boolean; // Only trigger once, default: false
124
+ }
125
+ ```
126
+
127
+ ## Examples
128
+
129
+ ### Masked Line Reveal
130
+
131
+ ```tsx
132
+ <SplitText
133
+ options={{ type: 'lines', mask: 'lines' }}
134
+ onSplit={({ lines }) => {
135
+ animate(lines, { y: ['100%', 0] }, { delay: stagger(0.1) });
136
+ }}
137
+ >
138
+ <p>Each line reveals from below</p>
139
+ </SplitText>
140
+ ```
141
+
142
+ ### Scroll-Triggered Animation
143
+
144
+ ```tsx
145
+ <SplitText
146
+ onSplit={({ words }) => {
147
+ words.forEach(w => (w.style.opacity = '0'));
148
+ }}
149
+ inView={{ amount: 0.5, once: true }}
150
+ onInView={({ words }) => {
151
+ animate(words, { opacity: 1, y: [20, 0] }, { delay: stagger(0.03) });
152
+ }}
153
+ >
154
+ <p>Animates when scrolled into view</p>
155
+ </SplitText>
156
+ ```
157
+
158
+ ### Auto-Revert After Animation
159
+
160
+ ```tsx
161
+ <SplitText
162
+ revertOnComplete
163
+ onSplit={({ chars }) => {
164
+ return animate(chars, { opacity: [0, 1] }, { delay: stagger(0.02) });
165
+ }}
166
+ >
167
+ <h1>Reverts to original HTML after animation</h1>
168
+ </SplitText>
169
+ ```
170
+
171
+ ### Responsive Re-split
172
+
173
+ ```tsx
174
+ <SplitText
175
+ autoSplit
176
+ onSplit={({ lines }) => animateLines(lines)}
177
+ onResize={({ lines }) => animateLines(lines)}
178
+ >
179
+ <p>Re-animates when container resizes</p>
180
+ </SplitText>
181
+ ```
182
+
183
+ ### CSS-Only Animation with Index Props
184
+
185
+ ```tsx
186
+ <SplitText options={{ type: 'chars', propIndex: true }}>
187
+ <h1 className="stagger-fade">Hello</h1>
188
+ </SplitText>
189
+ ```
190
+
191
+ ```css
192
+ .stagger-fade .split-char {
193
+ opacity: 0;
194
+ animation: fade-in 0.5s forwards;
195
+ animation-delay: calc(var(--char-index) * 0.03s);
196
+ }
197
+
198
+ @keyframes fade-in {
199
+ to { opacity: 1; }
200
+ }
201
+ ```
202
+
203
+ ### With GSAP
204
+
205
+ ```tsx
206
+ import { SplitText } from 'fetta/react';
207
+ import gsap from 'gsap';
208
+
209
+ <SplitText
210
+ revertOnComplete
211
+ onSplit={({ words }) => {
212
+ return gsap.from(words, {
213
+ opacity: 0,
214
+ y: 20,
215
+ stagger: 0.05,
216
+ duration: 0.6,
217
+ });
218
+ }}
219
+ >
220
+ <h1>Works with GSAP</h1>
221
+ </SplitText>
222
+ ```
223
+
224
+ ### Nested HTML Elements
225
+
226
+ Fetta preserves inline elements like links, emphasis, and other formatting. Attributes (href, class, id, data-*, etc.) are maintained.
227
+
228
+ ```tsx
229
+ <SplitText
230
+ onSplit={({ chars }) => {
231
+ animate(chars, { opacity: [0, 1] }, { delay: stagger(0.02) });
232
+ }}
233
+ >
234
+ <p>Click <a href="/signup">here</a> to <em>get started</em></p>
235
+ </SplitText>
236
+ ```
237
+
238
+ ## CSS Classes
239
+
240
+ Default classes applied to split elements:
241
+
242
+ | Class | Element | Notes |
243
+ |-------|---------|-------|
244
+ | `.split-char` | Characters | Inline positioning |
245
+ | `.split-word` | Words | Inline positioning |
246
+ | `.split-line` | Lines | Block display |
247
+
248
+ Each element also receives a `data-index` attribute with its position.
249
+
250
+ ## Notes
251
+
252
+ - **Fonts must be loaded** before splitting. The React component waits for `document.fonts.ready` automatically.
253
+ - **Ligatures are disabled** (`font-variant-ligatures: none`) because ligatures cannot span multiple elements.
254
+ - **Accessibility**: Split elements receive an `aria-label` with the original text for screen readers.
255
+
256
+ ## Browser Support
257
+
258
+ All modern browsers: Chrome, Firefox, Safari, Edge.
259
+
260
+ Requires:
261
+ - `ResizeObserver`
262
+ - `IntersectionObserver`
263
+ - `Intl.Segmenter`
264
+
265
+ ## License
266
+
267
+ MIT