fetta 1.0.2 → 1.1.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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # fetta
1
+ # Fetta
2
2
 
3
3
  A text-splitting library with kerning compensation for smooth, natural text animations.
4
4
 
@@ -14,7 +14,7 @@ Split text into characters, words, and lines while preserving the original typog
14
14
  - **Auto-Revert** — Restore original HTML after animations
15
15
  - **Masking** — Wrap elements in clip containers for reveal animations
16
16
  - **Emoji Support** — Properly handles compound emojis and complex Unicode characters
17
- - **Accessible** — Adds `aria-label` with original text for screen readers
17
+ - **Accessible** — Automatic screen reader support, even when splitting text with nested links or emphasis
18
18
  - **TypeScript** — Full type definitions included
19
19
  - **React Component** — Declarative wrapper for React projects
20
20
  - **Built-in InView** — Viewport detection for scroll-triggered animations in React
@@ -255,7 +255,7 @@ Each element also receives a `data-index` attribute with its position.
255
255
 
256
256
  - **Fonts must be loaded** before splitting. The React component waits for `document.fonts.ready` automatically.
257
257
  - **Ligatures are disabled** (`font-variant-ligatures: none`) because ligatures cannot span multiple elements.
258
- - **Accessibility**: Split elements receive an `aria-label` with the original text for screen readers.
258
+ - **Accessibility**: Automatic screen reader support for both simple text and text with nested elements like links.
259
259
 
260
260
  ## Browser Support
261
261
 
@@ -64,6 +64,30 @@ var INLINE_ELEMENTS = /* @__PURE__ */ new Set([
64
64
  "var"
65
65
  ]);
66
66
  var isSafari = typeof navigator !== "undefined" && /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
67
+ var srOnlyStylesInjected = false;
68
+ function injectSrOnlyStyles() {
69
+ if (srOnlyStylesInjected || typeof document === "undefined") return;
70
+ const style = document.createElement("style");
71
+ style.textContent = `
72
+ .fetta-sr-only:not(:focus):not(:active) {
73
+ clip: rect(0 0 0 0);
74
+ clip-path: inset(50%);
75
+ height: 1px;
76
+ overflow: hidden;
77
+ position: absolute;
78
+ white-space: nowrap;
79
+ width: 1px;
80
+ }`;
81
+ document.head.appendChild(style);
82
+ srOnlyStylesInjected = true;
83
+ }
84
+ function createScreenReaderCopy(originalHTML) {
85
+ const srCopy = document.createElement("span");
86
+ srCopy.className = "fetta-sr-only";
87
+ srCopy.innerHTML = originalHTML;
88
+ srCopy.dataset.fettaSrCopy = "true";
89
+ return srCopy;
90
+ }
67
91
  function hasInlineDescendants(element) {
68
92
  const walker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT);
69
93
  let node;
@@ -631,7 +655,6 @@ function splitText(element, {
631
655
  let currentChars = [];
632
656
  let currentWords = [];
633
657
  let currentLines = [];
634
- element.setAttribute("aria-label", text);
635
658
  if (splitChars) {
636
659
  element.style.fontVariantLigatures = "none";
637
660
  }
@@ -655,6 +678,19 @@ function splitText(element, {
655
678
  currentChars = chars;
656
679
  currentWords = words;
657
680
  currentLines = lines;
681
+ if (trackAncestors) {
682
+ injectSrOnlyStyles();
683
+ const visualWrapper = document.createElement("span");
684
+ visualWrapper.setAttribute("aria-hidden", "true");
685
+ visualWrapper.dataset.fettaVisual = "true";
686
+ while (element.firstChild) {
687
+ visualWrapper.appendChild(element.firstChild);
688
+ }
689
+ element.appendChild(visualWrapper);
690
+ element.appendChild(createScreenReaderCopy(originalHTML));
691
+ } else {
692
+ element.setAttribute("aria-label", text);
693
+ }
658
694
  const dispose = () => {
659
695
  if (!isActive) return;
660
696
  if (resizeObserver) {
@@ -670,7 +706,9 @@ function splitText(element, {
670
706
  const revert = () => {
671
707
  if (!isActive) return;
672
708
  element.innerHTML = originalHTML;
673
- element.removeAttribute("aria-label");
709
+ if (!trackAncestors) {
710
+ element.removeAttribute("aria-label");
711
+ }
674
712
  if (splitChars) {
675
713
  element.style.fontVariantLigatures = "none";
676
714
  }
@@ -718,6 +756,16 @@ function splitText(element, {
718
756
  currentChars = result.chars;
719
757
  currentWords = result.words;
720
758
  currentLines = result.lines;
759
+ if (trackAncestors) {
760
+ const visualWrapper = document.createElement("span");
761
+ visualWrapper.setAttribute("aria-hidden", "true");
762
+ visualWrapper.dataset.fettaVisual = "true";
763
+ while (element.firstChild) {
764
+ visualWrapper.appendChild(element.firstChild);
765
+ }
766
+ element.appendChild(visualWrapper);
767
+ element.appendChild(createScreenReaderCopy(originalHTML));
768
+ }
721
769
  const newFingerprint = getLineFingerprint(result.lines);
722
770
  if (onResize && newFingerprint !== previousFingerprint) {
723
771
  onResize({
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { splitText } from './chunk-G5P33GJE.js';
1
+ export { splitText } from './chunk-7L4UQPGS.js';
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { splitText, __spreadProps, __spreadValues, normalizeToPromise } from './chunk-G5P33GJE.js';
1
+ import { splitText, __spreadProps, __spreadValues, normalizeToPromise } from './chunk-7L4UQPGS.js';
2
2
  import { forwardRef, useRef, useCallback, useState, useLayoutEffect, useEffect, isValidElement, cloneElement } from 'react';
3
3
  import { jsx } from 'react/jsx-runtime';
4
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fetta",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Text splitting library with kerning compensation for animations",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -34,8 +34,23 @@
34
34
  "test:ui": "vitest --ui",
35
35
  "test:coverage": "vitest run --coverage",
36
36
  "test:e2e": "playwright test",
37
- "test:all": "vitest run --coverage && playwright test"
37
+ "test:all": "vitest run --coverage && playwright test",
38
+ "size": "size-limit",
39
+ "size:why": "size-limit --why"
38
40
  },
41
+ "size-limit": [
42
+ {
43
+ "name": "Core",
44
+ "path": "dist/index.js",
45
+ "import": "*"
46
+ },
47
+ {
48
+ "name": "React",
49
+ "path": "dist/react.js",
50
+ "import": "*",
51
+ "ignore": ["react"]
52
+ }
53
+ ],
39
54
  "peerDependencies": {
40
55
  "react": ">=18.0.0"
41
56
  },
@@ -46,6 +61,7 @@
46
61
  },
47
62
  "devDependencies": {
48
63
  "@playwright/test": "^1.49.0",
64
+ "@size-limit/preset-small-lib": "^12.0.0",
49
65
  "@testing-library/dom": "^10.4.0",
50
66
  "@testing-library/jest-dom": "^6.6.3",
51
67
  "@testing-library/react": "^16.1.0",
@@ -56,6 +72,7 @@
56
72
  "react": "^19.2.3",
57
73
  "react-dom": "^19.0.0",
58
74
  "serve": "^14.2.4",
75
+ "size-limit": "^12.0.0",
59
76
  "tsup": "^8.0.0",
60
77
  "typescript": "^5",
61
78
  "vitest": "^2.1.8"