diff --git a/.cursor/plans/text_splitter_package_1eeee927.plan.md b/.cursor/plans/text_splitter_package_1eeee927.plan.md index e7f3566..b5294bf 100644 --- a/.cursor/plans/text_splitter_package_1eeee927.plan.md +++ b/.cursor/plans/text_splitter_package_1eeee927.plan.md @@ -71,6 +71,8 @@ isProject: false Create a new `@wix/splittext` package within the `packages/` directory that exports a functional API for splitting text into animatable parts (characters, words, lines, sentences). The package will be framework-agnostic with optional React bindings. +**Browser requirement:** This package uses `Intl.Segmenter` for locale-aware text segmentation (95%+ global support: Chrome 87+, Safari 14.1+, Firefox 125+). For environments without native support, a polyfill can be provided via the `segmenter` option (see **Segmenter Polyfill API** section). + ## Key Design Decisions The API will have: @@ -80,10 +82,10 @@ The API will have: - **Customizable `` wrappers**: All split items wrapped in `` tags with configurable classes, styles, and attributes for styling and animation - **Lazy evaluation with caching**: Split types are computed on-demand when accessed, not eagerly on invocation - **Eager split when `type` provided**: If `type` option is specified, only those types are split immediately -- **Accessibility by default**: Add ARIA attributes automatically +- **Accessibility by default**: Split content wrapped in an inner `aria-hidden` div; original text preserved via visually-hidden span (see Accessibility and SEO sections) - **Revertible**: Include a `revert()` method to restore original content - **Responsive support**: Optional `autoSplit` mode that re-splits on resize/font-load -- `Intl.Segmenter` **API** for locale-sensitive text segmentation to split on meaningful items (graphemes, words or sentences) in a string +- `Intl.Segmenter` API for locale-sensitive text segmentation to split on meaningful items (graphemes, words or sentences) in a string - **Range API for line detection**: Use `Range.getClientRects()` to detect line breaks from text nodes _before_ DOM manipulation, avoiding unnecessary wrapper creation during measurement ## Package Structure @@ -146,16 +148,29 @@ interface SplitTextOptions { wrapperAttrs?: Record | WrapperAttrsConfig; // Custom attributes (data-*, etc.) // Accessibility - aria?: 'auto' | 'hidden' | 'none'; // default: 'auto' + aria?: 'auto' | 'none'; // default: 'auto' + + // SEO and a11y + preserveText?: boolean; // default: true - visually-hidden duplicate for SEO and screen readers + + // Base CSS (inline-block, white-space, etc.) + injectStyles?: boolean; // default: true - auto-inject minimal base stylesheet (deduplicated via data-splittext) + + // DOM structure + nested?: 'flatten' | 'preserve' | number; // default: 'flatten' + + // Text segmentation polyfill (optional — see "Segmenter Polyfill API" section) + segmenter?: Intl.Segmenter | { new(locale: string, options: { granularity: string }): Intl.Segmenter }; + + // BiDi (optional external plugin — see "BiDi Plugin API" section) + bidiResolver?: (text: string) => Array<{ text: string; direction: 'ltr' | 'rtl' }>; // Responsive re-splitting autoSplit?: boolean; onSplit?: (result: SplitTextResult) => Animation | void; // Advanced - splitBy?: string; // default: ' ' (space for words) - ignore?: string[]; // selectors to skip (e.g., ['sup', 'sub']) - preserveWhitespace?: boolean; + ignore?: string[] | ((node: Node) => boolean); // selectors to skip or predicate (e.g., ['sup', 'sub']) } // Per-type wrapper configuration @@ -185,7 +200,7 @@ interface SplitTextResult { // Each element is a wrapper that can be styled/animated get chars: HTMLSpanElement[]; // Splits into characters on first access get words: HTMLSpanElement[]; // Splits into words on first access - get lines: HTMLSpanElement[]; // Splits into lines on first access + get lines: HTMLSpanElement[]; // Splits into lines on first access (note: triggers layout queries) get sentences: HTMLSpanElement[]; // Splits into sentences on first access // Methods @@ -230,7 +245,7 @@ Key files to implement: - Parse target (CSS selector or element) - **Use Range API for line detection** (see Key Implementation Details) - Extract text content preserving structure -- **Use** `Intl.Segmenter` **API for locale-sensitive text splitting on meaningful items** (chars, words, sentences) +- Use `Intl.Segmenter` API for locale-sensitive text splitting on meaningful items (chars, words, sentences) - Create wrapper spans with appropriate classes after detection 1. `src/lineDetection.ts` - Range-based line detection: @@ -242,9 +257,9 @@ Key files to implement: 1. `src/accessibility.ts`: -- Add `aria-label` with original text to container -- Add `aria-hidden="true"` to split elements -- Handle nested elements appropriately +- When `aria: 'auto'` and `preserveText` is true: insert a visually-hidden `` with the original text as a direct child of the container (exposed to AT and crawlers). Wrap all split content in an inner `