Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .cursor/plans/text_splitter_package_1eeee927.plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,47 @@ overview: Add a new `@wix/splittext` package to the interact monorepo that provi
todos:
- id: pkg-setup
content: Create package directory structure, package.json, tsconfig, vite.config
status: pending
status: completed
- id: types
content: Define TypeScript interfaces for options and result types
status: pending
status: completed
dependencies:
- pkg-setup
- id: line-detection
content: Implement Range API-based line detection (lineDetection.ts)
status: pending
status: completed
dependencies:
- types
- id: core-split
content: Implement core splitText function with chars/words/lines splitting
status: pending
status: completed
dependencies:
- types
- line-detection
- wrapper-spans
- id: accessibility
content: Add ARIA attribute handling for screen reader support
status: pending
status: completed
dependencies:
- core-split
- id: wrapper-spans
content: Implement customizable span wrapper creation with class/style/attrs options
status: pending
status: completed
dependencies:
- types
- id: autosplit
content: Add responsive autoSplit with resize/font-load observers
status: pending
status: completed
dependencies:
- core-split
- id: react-hook
content: Create useSplitText React hook with proper cleanup
status: pending
status: completed
dependencies:
- core-split
- id: tests
content: Write comprehensive test suite for all features
status: pending
status: completed
dependencies:
- core-split
- accessibility
Expand Down
19 changes: 14 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ A monorepo for Wix's web animation and interaction libraries, built on the nativ

### Project Map

| Project | Package | Directory |
| -------- | --------------------- | -------------------------- |
| Motion | `@wix/motion` | `packages/motion/` |
| Interact | `@wix/interact` | `packages/interact/` |
| Presets | `@wix/motion-presets` | `packages/motion-presets/` |
| Project | Package | Directory |
| --------- | --------------------- | -------------------------- |
| Motion | `@wix/motion` | `packages/motion/` |
| Interact | `@wix/interact` | `packages/interact/` |
| Presets | `@wix/motion-presets` | `packages/motion-presets/` |
| SplitText | `@wix/splittext` | `packages/splittext/` |

### Dependency Graph

Expand All @@ -26,6 +27,10 @@ A monorepo for Wix's web animation and interaction libraries, built on the nativ
@wix/motion-presets ← ready-made presets
```

```
@wix/splittext ← standalone text splitting utility (no @wix/motion dependency)
```

### Motion (`@wix/motion`)

Core animation toolkit. Provides low-level APIs for running animations via the Web Animations API and CSS, including scroll-driven (ViewTimeline) and pointer-based animations. Uses `fastdom` to batch DOM reads/writes and reduce layout thrashing.
Expand All @@ -38,6 +43,10 @@ Declarative, configuration-driven interaction library built on top of `@wix/moti

Ready-made animation presets for `@wix/motion`, organized in five categories: entrance, ongoing, scroll, mouse, and background-scroll. Each preset is a separate module under `library/`. Consumed via `registerEffects()`.

### SplitText (`@wix/splittext`)

Lightweight, accessible text splitting utility. Splits element text into animatable `<span>` wrappers at the character, word, line, or sentence level. Uses `Intl.Segmenter` for locale-aware segmentation and the Range API for accurate line detection. Ships two entry points: vanilla JS (`@wix/splittext`) and React (`@wix/splittext/react`). Pairs naturally with `@wix/motion` for staggered entrance animations.

## CLI Commands

Always run `nvm use` before executing any CLI commands to ensure the correct Node.js version is active.
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@ Web-native animation and interaction libraries — declarative, AI-ready, framew

## Packages

| Package | Description | Links |
| -------------------------------------------------------------------------------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| [`@wix/interact`](https://github.com/wix/interact/blob/master/packages/interact/) | Declarative interaction layer (main package) | [README](https://github.com/wix/interact/blob/master/packages/interact/README.md) · [npm](https://www.npmjs.com/package/@wix/interact) |
| [`@wix/motion`](https://github.com/wix/interact/blob/master/packages/motion/) | Low-level animation engine | [README](https://github.com/wix/interact/blob/master/packages/motion/README.md) · [npm](https://www.npmjs.com/package/@wix/motion) |
| [`@wix/motion-presets`](https://github.com/wix/interact/tree/master/packages/motion-presets) | Ready-made animation presets | [npm](https://www.npmjs.com/package/@wix/motion-presets) |
| Package | Description | Links |
| -------------------------------------------------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| [`@wix/interact`](https://github.com/wix/interact/blob/master/packages/interact/) | Declarative interaction layer (main package) | [README](https://github.com/wix/interact/blob/master/packages/interact/README.md) · [npm](https://www.npmjs.com/package/@wix/interact) |
| [`@wix/motion`](https://github.com/wix/interact/blob/master/packages/motion/) | Low-level animation engine | [README](https://github.com/wix/interact/blob/master/packages/motion/README.md) · [npm](https://www.npmjs.com/package/@wix/motion) |
| [`@wix/motion-presets`](https://github.com/wix/interact/tree/master/packages/motion-presets) | Ready-made animation presets | [npm](https://www.npmjs.com/package/@wix/motion-presets) |
| [`@wix/splittext`](https://github.com/wix/interact/blob/master/packages/splittext/) | Accessible text splitting for animations | [README](https://github.com/wix/interact/blob/master/packages/splittext/README.md) · [npm](https://www.npmjs.com/package/@wix/splittext) |

```
@wix/motion ← @wix/interact (declarative layer)
@wix/motion ← @wix/motion-presets (ready-made effects)
@wix/splittext (standalone — pairs with @wix/motion for staggered animations)
```

## Quick Start
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,8 @@
"eslint-plugin-react": "^7.37.5",
"prettier": "3.8.3",
"typescript": "^5.9.3"
},
"resolutions": {
"semver": "7.8.0"
}
}
145 changes: 145 additions & 0 deletions packages/splittext/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# @wix/splittext

Lightweight, accessible text splitting utility for creating staggered animations on characters, words, lines, and sentences.

[![npm version](https://img.shields.io/npm/v/@wix/splittext)](https://www.npmjs.com/package/@wix/splittext)
[![license](https://img.shields.io/npm/l/@wix/splittext)](https://github.com/wix/interact/blob/master/LICENSE)

## Features

- **Split by chars, words, lines, or sentences** — each piece wrapped in an animatable `<span>`
- **Locale-aware segmentation** — uses `Intl.Segmenter` (handles emoji, unicode, grapheme clusters)
- **Range API line detection** — accurate line breaks from browser rendering, no pre-wrapping required
- **Lazy evaluation** — DOM is only mutated when a result getter is first accessed
- **Accessible by default** — original text preserved for screen readers and SEO
- **Customizable wrappers** — add classes, inline styles, and data attributes per split type
- **CSS stagger hooks** — `--char-index`, `--word-index`, etc. set automatically on every span
- **React hook** — `useSplitText` with automatic cleanup on unmount
- **Responsive** — optional `autoSplit` re-splits on resize and font load
- **Revertible** — restore the original DOM at any time with `result.revert()`

## Installation

```bash
npm install @wix/splittext
```

**Browser requirement:** `Intl.Segmenter` (Chrome 87+, Safari 14.1+, Firefox 125+). For older environments supply a polyfill via the `segmenter` option.

## Quick Start

```typescript
import { splitText } from '@wix/splittext';

// Lazy — DOM unchanged until getter accessed
const result = splitText('.headline');
const chars = result.chars; // DOM split into chars here, cached

// Eager — split immediately on call
const { chars } = splitText('.headline', { type: 'chars' });

// Animate with any library
animate(chars, { opacity: [0, 1], transform: ['translateY(8px)', 'translateY(0)'], stagger: 0.03 });
```

### Staggered CSS animation

```typescript
splitText('.headline', {
type: 'chars',
wrapperClass: 'char',
});
```

```css
.char {
opacity: 0;
animation: fadeUp 0.4s ease forwards;
animation-delay: calc(var(--char-index) * 0.04s);
}
@keyframes fadeUp {
to {
opacity: 1;
transform: translateY(0);
}
}
```

### React

```tsx
import { useRef, useEffect } from 'react';
import { useSplitText } from '@wix/splittext/react';

function Headline() {
const ref = useRef<HTMLHeadingElement>(null);
const result = useSplitText(ref, { type: 'chars' });

useEffect(() => {
if (!result) return;
// animate result.chars …
}, [result]);

return <h1 ref={ref}>Hello World</h1>;
}
```

## API

### `splitText(target, options?)`

| Parameter | Type | Description |
| --------- | ----------------------- | ------------------------- |
| `target` | `string \| HTMLElement` | CSS selector or element |
| `options` | `SplitTextOptions` | Configuration (see below) |

Returns a `SplitTextResult` with lazy getters: `.chars`, `.words`, `.lines`, `.sentences`.

### Options

| Option | Type | Default | Description |
| ------------------ | ---------------------------------------------------- | ------------ | ------------------------------------------------- |
| `type` | `SplitType \| SplitType[]` | — | Split eagerly on call instead of lazily |
| `wrapperClass` | `string \| WrapperClassConfig` | — | Extra CSS class(es) on wrapper spans |
| `wrapperStyle` | `Partial<CSSStyleDeclaration> \| WrapperStyleConfig` | — | Inline styles on wrapper spans |
| `wrapperAttrs` | `Record<string, string> \| WrapperAttrsConfig` | — | Custom attributes on wrapper spans |
| `contentAttribute` | `'none' \| 'both' \| 'attribute-only'` | `'both'` | Controls `data-content` on char/word wrappers |
| `aria` | `'auto' \| 'none'` | `'auto'` | ARIA handling mode |
| `preserveText` | `boolean` | `true` | Insert visually-hidden original text for a11y/SEO |
| `partIndexing` | `boolean` | `true` | Set `--char-index` / `--word-index` etc. on spans |
| `nested` | `'flatten' \| 'preserve' \| number` | `'preserve'` | How inner DOM structure is handled |
| `autoSplit` | `boolean` | — | Re-split on resize / font load |
| `onSplit` | `(result) => void` | — | Callback after each split |
| `segmenter` | `Intl.Segmenter \| constructor` | — | Polyfill for `Intl.Segmenter` |
| `ignore` | `string[] \| (node) => boolean` | — | Selectors / predicate to skip nodes |

### Default CSS classes

| Split type | Class |
| ----------- | ---------- |
| `chars` | `.split-c` |
| `words` | `.split-w` |
| `lines` | `.split-l` |
| `sentences` | `.split-s` |

Base styles (`display: inline-block`, etc.) are injected once via `adoptedStyleSheets`.

## Accessibility

With defaults (`aria: 'auto'`, `preserveText: true`), the DOM looks like:

```html
<h1>
<span class="sr-only">Original text</span>
<div aria-hidden="true" data-splittext-wrapper>
<span class="split-c" data-content="H">H</span>
<!-- … -->
</div>
</h1>
```

Screen readers and crawlers see the original text; the split spans are hidden from the accessibility tree.

## License

[MIT](https://github.com/wix/interact/blob/master/LICENSE)
82 changes: 82 additions & 0 deletions packages/splittext/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{
"name": "@wix/splittext",
"version": "0.1.0",
"description": "Lightweight, accessible text splitting utility for creating staggered animations on characters, words, and lines.",
"license": "MIT",
"main": "dist/cjs/index.js",
"module": "dist/es/index.js",
"types": "dist/types/index.d.ts",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/es/index.js",
"require": "./dist/cjs/index.js"
},
"./react": {
"types": "./dist/types/react/index.d.ts",
"import": "./dist/es/react.js",
"require": "./dist/cjs/react.js"
}
},
"files": [
"dist",
"docs"
],
"sideEffects": false,
"scripts": {
"build": "rimraf dist && vite build && npm run build:types",
"build:types": "tsc -p tsconfig.build.json",
"lint": "tsc --noEmit",
"test": "vitest run",
"coverage": "vitest run --coverage"
},
"keywords": [
"animation",
"text-split",
"split-text",
"waapi",
"web-animations-api",
"javascript",
"stagger",
"motion",
"wix"
],
"author": {
"name": "wow!Team",
"email": "wow-dev@wix.com"
},
"repository": {
"type": "git",
"url": "git+https://github.com/wix/interact.git"
},
"bugs": {
"url": "https://github.com/wix/interact/issues"
},
"peerDependencies": {
"react": "^18.3.1 || ^19.0.0",
"react-dom": "^18.3.1 || ^19.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
},
"devDependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.1.0",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^4.0.14",
"jsdom": "^24.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"rimraf": "^6.0.1",
"typescript": "^5.9.3",
"vite": "^7.2.2",
"vitest": "^4.0.14"
}
}
Loading
Loading