@edwinvakayil/calligraphy 1.4.0 → 1.5.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 +351 -130
- package/dist/animation.d.ts +19 -0
- package/dist/index.d.ts +104 -3
- package/dist/index.esm.js +201 -42
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +201 -41
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +98 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ interface TypographyProviderProps {
|
|
|
17
17
|
children: React.ReactNode;
|
|
18
18
|
}
|
|
19
19
|
declare const TypographyProvider: React.FC<TypographyProviderProps>;
|
|
20
|
+
/**
|
|
21
|
+
* Returns the resolved theme from the nearest TypographyProvider.
|
|
22
|
+
* Falls back to DEFAULT_THEME if used outside a provider.
|
|
23
|
+
*/
|
|
24
|
+
declare function useTypographyTheme(): Required<TypographyTheme>;
|
|
20
25
|
|
|
21
26
|
type TypographyVariant = "Display" | "H1" | "H2" | "H3" | "H4" | "H5" | "H6" | "Subheading" | "Overline" | "Body" | "Label" | "Caption";
|
|
22
27
|
type TextAlign = "left" | "center" | "right" | "justify";
|
|
@@ -76,8 +81,83 @@ type TextAlign = "left" | "center" | "right" | "justify";
|
|
|
76
81
|
* wordFade per-word cross-dissolve with warmth scale, no Y movement
|
|
77
82
|
* rotateIn full Y-axis card-flip per word (face-up reveal)
|
|
78
83
|
* pressIn presses to 0.92 then springs past 1 (physical button feel)
|
|
84
|
+
* maskSweep mask-image sweep reveals text left to right
|
|
85
|
+
* gradSweep gradient sweep across text then resolves to solid
|
|
79
86
|
*/
|
|
80
|
-
type HeroAnimation = "rise" | "stagger" | "clip" | "pop" | "letters" | "blur" | "flip" | "swipe" | "typewriter" | "bounce" | "velvet" | "curtain" | "morph" | "ground" | "cascade" | "spotlight" | "ink" | "hinge" | "stretch" | "peel" | "fold" | "shear" | "ripple" | "cinch" | "tiltrise" | "cardFlip" | "converge" | "splitRise" | "tectonic" | "stratify" | "unfurl" | "gravityWell" | "orbit" | "liquid" | "noiseFade" | "slab" | "thread" | "billboard" | "glassReveal" | "wordPop" | "charDrop" | "scanline" | "chromaShift" | "wordFade" | "rotateIn" | "pressIn";
|
|
87
|
+
type HeroAnimation = "rise" | "stagger" | "clip" | "pop" | "letters" | "blur" | "flip" | "swipe" | "typewriter" | "bounce" | "velvet" | "curtain" | "morph" | "ground" | "cascade" | "spotlight" | "ink" | "hinge" | "stretch" | "peel" | "fold" | "shear" | "ripple" | "cinch" | "tiltrise" | "cardFlip" | "converge" | "splitRise" | "tectonic" | "stratify" | "unfurl" | "gravityWell" | "orbit" | "liquid" | "noiseFade" | "slab" | "thread" | "billboard" | "glassReveal" | "wordPop" | "charDrop" | "scanline" | "chromaShift" | "wordFade" | "rotateIn" | "pressIn" | "maskSweep" | "gradSweep";
|
|
88
|
+
/**
|
|
89
|
+
* Custom motion configuration for a Typography element.
|
|
90
|
+
* Use this when the built-in `animation` presets don't fit your needs —
|
|
91
|
+
* for example when you have brand-specific easing tokens, or when you want
|
|
92
|
+
* to animate a non-hero variant like H2 or Body.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* // Whole-element fade up
|
|
96
|
+
* motionConfig={{
|
|
97
|
+
* keyframes: `from { opacity: 0; transform: translateY(24px); }
|
|
98
|
+
* to { opacity: 1; transform: none; }`,
|
|
99
|
+
* duration: "0.8s",
|
|
100
|
+
* easing: "cubic-bezier(0.16, 1, 0.3, 1)",
|
|
101
|
+
* delay: "0.2s",
|
|
102
|
+
* split: "none",
|
|
103
|
+
* }}
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* // Per-word stagger with custom keyframe
|
|
107
|
+
* motionConfig={{
|
|
108
|
+
* keyframes: `from { opacity: 0; transform: skewX(8deg) translateX(-12px); }
|
|
109
|
+
* to { opacity: 1; transform: none; }`,
|
|
110
|
+
* duration: "0.6s",
|
|
111
|
+
* easing: "cubic-bezier(0.16, 1, 0.3, 1)",
|
|
112
|
+
* staggerDelay: 0.08,
|
|
113
|
+
* split: "words",
|
|
114
|
+
* }}
|
|
115
|
+
*/
|
|
116
|
+
interface MotionConfig {
|
|
117
|
+
/**
|
|
118
|
+
* CSS keyframes body — the content between @keyframes name { … }.
|
|
119
|
+
* Do not include the @keyframes rule or a name; the component generates both.
|
|
120
|
+
*
|
|
121
|
+
* @example "from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: none; }"
|
|
122
|
+
*/
|
|
123
|
+
keyframes: string;
|
|
124
|
+
/**
|
|
125
|
+
* Animation duration. Accepts any CSS time value.
|
|
126
|
+
* @default "0.8s"
|
|
127
|
+
*/
|
|
128
|
+
duration?: string;
|
|
129
|
+
/**
|
|
130
|
+
* CSS easing function.
|
|
131
|
+
* @default "cubic-bezier(0.16, 1, 0.3, 1)"
|
|
132
|
+
*/
|
|
133
|
+
easing?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Initial delay before the animation begins.
|
|
136
|
+
* @default "0s"
|
|
137
|
+
*/
|
|
138
|
+
delay?: string;
|
|
139
|
+
/**
|
|
140
|
+
* animation-fill-mode value.
|
|
141
|
+
* @default "both"
|
|
142
|
+
*/
|
|
143
|
+
fillMode?: "none" | "forwards" | "backwards" | "both";
|
|
144
|
+
/**
|
|
145
|
+
* How to split the text before animating.
|
|
146
|
+
*
|
|
147
|
+
* - "none" — animate the whole element (default)
|
|
148
|
+
* - "words" — wrap each word in a <span> and stagger them
|
|
149
|
+
* - "chars" — wrap each character in a <span> and stagger them
|
|
150
|
+
*
|
|
151
|
+
* @default "none"
|
|
152
|
+
*/
|
|
153
|
+
split?: "none" | "words" | "chars";
|
|
154
|
+
/**
|
|
155
|
+
* Delay increment between each word or character span (in seconds).
|
|
156
|
+
* Only used when split is "words" or "chars".
|
|
157
|
+
* @default 0.07 (words) | 0.04 (chars)
|
|
158
|
+
*/
|
|
159
|
+
staggerDelay?: number;
|
|
160
|
+
}
|
|
81
161
|
interface TypographyProps extends HTMLAttributes<HTMLElement> {
|
|
82
162
|
/** Typography scale variant */
|
|
83
163
|
variant?: TypographyVariant;
|
|
@@ -94,10 +174,31 @@ interface TypographyProps extends HTMLAttributes<HTMLElement> {
|
|
|
94
174
|
/** Clamp to N lines */
|
|
95
175
|
maxLines?: number;
|
|
96
176
|
/**
|
|
97
|
-
*
|
|
177
|
+
* Built-in hero entrance animation.
|
|
98
178
|
* Only applied on variant="Display" or variant="H1".
|
|
99
179
|
*/
|
|
100
180
|
animation?: HeroAnimation;
|
|
181
|
+
/**
|
|
182
|
+
* Custom motion config — use your own keyframes, easing, and split strategy.
|
|
183
|
+
* Works on ALL variants (not just heroes).
|
|
184
|
+
* Takes precedence over `animation` when both are provided.
|
|
185
|
+
*
|
|
186
|
+
* @see MotionConfig
|
|
187
|
+
*/
|
|
188
|
+
motionConfig?: MotionConfig;
|
|
189
|
+
/**
|
|
190
|
+
* Ref callback giving direct access to the rendered DOM element.
|
|
191
|
+
* Use this to drive animations with GSAP, Framer Motion, or the Web
|
|
192
|
+
* Animations API. Called after mount and on every re-render.
|
|
193
|
+
* Takes precedence over both `animation` and `motionConfig`.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* motionRef={(el) => {
|
|
197
|
+
* if (!el) return;
|
|
198
|
+
* gsap.from(el, { opacity: 0, y: 40, duration: 0.9 });
|
|
199
|
+
* }}
|
|
200
|
+
*/
|
|
201
|
+
motionRef?: (el: HTMLElement | null) => void;
|
|
101
202
|
/**
|
|
102
203
|
* Italic accent for Display / H1 heroes.
|
|
103
204
|
* When true, <em> children render in Instrument Serif italic + accentColor.
|
|
@@ -138,4 +239,4 @@ declare function injectFont(url: string): void;
|
|
|
138
239
|
*/
|
|
139
240
|
declare function preloadFonts(families: string[]): void;
|
|
140
241
|
|
|
141
|
-
export { GOOGLE_FONTS, HeroAnimation, TextAlign, Typography, TypographyProps, TypographyProvider, TypographyProviderProps, TypographyTheme, TypographyVariant, buildFontUrl, Typography as default, injectFont, preloadFonts };
|
|
242
|
+
export { GOOGLE_FONTS, HeroAnimation, MotionConfig, TextAlign, Typography, TypographyProps, TypographyProvider, TypographyProviderProps, TypographyTheme, TypographyVariant, buildFontUrl, Typography as default, injectFont, preloadFonts, useTypographyTheme };
|
package/dist/index.esm.js
CHANGED
|
@@ -526,6 +526,17 @@ function wrapSplitRise(html) {
|
|
|
526
526
|
function wrapThread(html) {
|
|
527
527
|
return wrapChars(html, "rts-thread-ch", 0.04);
|
|
528
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* Called by Typography after dangerouslySetInnerHTML for "thread" animation.
|
|
531
|
+
* Stamps the sine-wave Y offset as --ty on each character span.
|
|
532
|
+
*/
|
|
533
|
+
function applyThreadOffsets(container) {
|
|
534
|
+
const spans = container.querySelectorAll(".rts-thread-ch");
|
|
535
|
+
spans.forEach((el, i) => {
|
|
536
|
+
const offset = Math.sin(i * 0.85) * 22;
|
|
537
|
+
el.style.setProperty("--ty", `${offset.toFixed(1)}px`);
|
|
538
|
+
});
|
|
539
|
+
}
|
|
529
540
|
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
530
541
|
function buildSplitHTML(animation, html) {
|
|
531
542
|
switch (animation) {
|
|
@@ -566,6 +577,88 @@ function buildSplitHTML(animation, html) {
|
|
|
566
577
|
default: return html;
|
|
567
578
|
}
|
|
568
579
|
}
|
|
580
|
+
// ─── Custom motion builder ────────────────────────────────────────────────────
|
|
581
|
+
/**
|
|
582
|
+
* Injects a one-off @keyframes rule with a generated name and returns that name.
|
|
583
|
+
* Deduped by a hash of the keyframe body so the same keyframe is only injected once.
|
|
584
|
+
*/
|
|
585
|
+
function injectCustomKeyframes(keyframeBody) {
|
|
586
|
+
const hash = keyframeBody
|
|
587
|
+
.split("")
|
|
588
|
+
.reduce((acc, ch) => (Math.imul(31, acc) + ch.charCodeAt(0)) | 0, 0)
|
|
589
|
+
.toString(36)
|
|
590
|
+
.replace("-", "n");
|
|
591
|
+
const name = `rts-custom-${hash}`;
|
|
592
|
+
if (typeof document === "undefined")
|
|
593
|
+
return name;
|
|
594
|
+
if (document.getElementById(name))
|
|
595
|
+
return name;
|
|
596
|
+
const style = document.createElement("style");
|
|
597
|
+
style.id = name;
|
|
598
|
+
style.textContent = `@keyframes ${name} { ${keyframeBody} }`;
|
|
599
|
+
document.head.appendChild(style);
|
|
600
|
+
return name;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Builds the innerHTML for a custom motionConfig.
|
|
604
|
+
* - split "none" → returns the raw html unchanged; caller applies class to element
|
|
605
|
+
* - split "words" → wraps each word in a span with the animation + stagger delay
|
|
606
|
+
* - split "chars" → wraps each character in a span with the animation + stagger delay
|
|
607
|
+
*
|
|
608
|
+
* Returns { html, animationValue } where animationValue is the full CSS animation
|
|
609
|
+
* shorthand to set on each span (or on the element itself when split is "none").
|
|
610
|
+
*/
|
|
611
|
+
function buildCustomHTML(html, keyframeName, duration, easing, delay, fillMode, split, staggerDelay) {
|
|
612
|
+
var _a;
|
|
613
|
+
const baseAnimation = `${keyframeName} ${duration} ${easing} ${delay} ${fillMode}`;
|
|
614
|
+
if (split === "none") {
|
|
615
|
+
return { html, baseAnimation };
|
|
616
|
+
}
|
|
617
|
+
if (split === "words") {
|
|
618
|
+
const tokens = (_a = html.match(/(<em>[\s\S]*?<\/em>|[^\s]+)/g)) !== null && _a !== void 0 ? _a : [];
|
|
619
|
+
const result = tokens.map((tok, i) => {
|
|
620
|
+
const totalDelay = i === 0
|
|
621
|
+
? delay
|
|
622
|
+
: `${(parseFloat(delay) + i * staggerDelay).toFixed(3)}s`;
|
|
623
|
+
const anim = `${keyframeName} ${duration} ${easing} ${totalDelay} ${fillMode}`;
|
|
624
|
+
if (tok.startsWith("<em>")) {
|
|
625
|
+
return `<em><span style="display:inline-block;animation:${anim}">${tok.slice(4, -5)}</span></em>`;
|
|
626
|
+
}
|
|
627
|
+
return `<span style="display:inline-block;animation:${anim}">${tok}</span>`;
|
|
628
|
+
});
|
|
629
|
+
return { html: result.join(" "), baseAnimation };
|
|
630
|
+
}
|
|
631
|
+
// chars
|
|
632
|
+
const result = [];
|
|
633
|
+
let inEm = false, charIndex = 0, i = 0;
|
|
634
|
+
while (i < html.length) {
|
|
635
|
+
if (html.startsWith("<em>", i)) {
|
|
636
|
+
inEm = true;
|
|
637
|
+
i += 4;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
if (html.startsWith("</em>", i)) {
|
|
641
|
+
inEm = false;
|
|
642
|
+
i += 5;
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
const ch = html[i];
|
|
646
|
+
if (ch === " ") {
|
|
647
|
+
result.push(" ");
|
|
648
|
+
i++;
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
const totalDelay = charIndex === 0
|
|
652
|
+
? delay
|
|
653
|
+
: `${(parseFloat(delay) + charIndex * staggerDelay).toFixed(3)}s`;
|
|
654
|
+
const anim = `${keyframeName} ${duration} ${easing} ${totalDelay} ${fillMode}`;
|
|
655
|
+
const span = `<span style="display:inline-block;animation:${anim}">${ch}</span>`;
|
|
656
|
+
result.push(inEm ? `<em>${span}</em>` : span);
|
|
657
|
+
charIndex++;
|
|
658
|
+
i++;
|
|
659
|
+
}
|
|
660
|
+
return { html: result.join(""), baseAnimation };
|
|
661
|
+
}
|
|
569
662
|
|
|
570
663
|
// ─── Defaults ─────────────────────────────────────────────────────────────────
|
|
571
664
|
const DEFAULT_THEME = {
|
|
@@ -603,7 +696,7 @@ function useTypographyTheme() {
|
|
|
603
696
|
return useContext(TypographyContext);
|
|
604
697
|
}
|
|
605
698
|
|
|
606
|
-
// ───
|
|
699
|
+
// ─── Variant → HTML tag map ───────────────────────────────────────────────────
|
|
607
700
|
const variantTagMap = {
|
|
608
701
|
Display: "h1",
|
|
609
702
|
H1: "h1",
|
|
@@ -618,6 +711,7 @@ const variantTagMap = {
|
|
|
618
711
|
Label: "label",
|
|
619
712
|
Caption: "span",
|
|
620
713
|
};
|
|
714
|
+
// ─── Variant → base CSS styles ────────────────────────────────────────────────
|
|
621
715
|
const variantStyleMap = {
|
|
622
716
|
Display: {
|
|
623
717
|
fontSize: "clamp(2.5rem, 6vw, 5rem)",
|
|
@@ -693,9 +787,15 @@ const variantStyleMap = {
|
|
|
693
787
|
letterSpacing: "0.03em",
|
|
694
788
|
},
|
|
695
789
|
};
|
|
696
|
-
// ─── Constants
|
|
790
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
791
|
+
// Always pre-loaded for hero variants so toggling italic is instant with no FOUC
|
|
697
792
|
const INSTRUMENT_SERIF_URL = "https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&display=swap";
|
|
698
|
-
// ─── Helpers
|
|
793
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
794
|
+
/**
|
|
795
|
+
* Serialise React children to a raw HTML string.
|
|
796
|
+
* Handles plain strings and <em>text</em> elements.
|
|
797
|
+
* Used to feed text into the animation split-builders.
|
|
798
|
+
*/
|
|
699
799
|
function childrenToHTML(children) {
|
|
700
800
|
var _a, _b;
|
|
701
801
|
return ((_b = (_a = Children.map(children, (child) => {
|
|
@@ -703,12 +803,19 @@ function childrenToHTML(children) {
|
|
|
703
803
|
return String(child);
|
|
704
804
|
}
|
|
705
805
|
if (isValidElement(child) && child.type === "em") {
|
|
706
|
-
const inner = typeof child.props.children === "string"
|
|
806
|
+
const inner = typeof child.props.children === "string"
|
|
807
|
+
? child.props.children
|
|
808
|
+
: "";
|
|
707
809
|
return `<em>${inner}</em>`;
|
|
708
810
|
}
|
|
709
811
|
return "";
|
|
710
812
|
})) === null || _a === void 0 ? void 0 : _a.join("")) !== null && _b !== void 0 ? _b : "");
|
|
711
813
|
}
|
|
814
|
+
/**
|
|
815
|
+
* Standard (no-animation) render path.
|
|
816
|
+
* Clones <em> children with explicit inline styles so the font switch is
|
|
817
|
+
* guaranteed — parent fontFamily cannot override a child's own inline style.
|
|
818
|
+
*/
|
|
712
819
|
function renderChildrenWithEmStyles(children, italic, accentColor, headingFont) {
|
|
713
820
|
const italicStyle = {
|
|
714
821
|
fontFamily: "'Instrument Serif', serif",
|
|
@@ -729,6 +836,11 @@ function renderChildrenWithEmStyles(children, italic, accentColor, headingFont)
|
|
|
729
836
|
return child;
|
|
730
837
|
});
|
|
731
838
|
}
|
|
839
|
+
/**
|
|
840
|
+
* Animation (dangerouslySetInnerHTML) render path.
|
|
841
|
+
* After the DOM is written we walk it and stamp inline styles onto every
|
|
842
|
+
* <em> and <em > span — guaranteed to beat any inherited fontFamily.
|
|
843
|
+
*/
|
|
732
844
|
function applyEmStylesDOM(container, italic, accentColor, headingFont) {
|
|
733
845
|
const apply = (el) => {
|
|
734
846
|
if (italic) {
|
|
@@ -738,7 +850,9 @@ function applyEmStylesDOM(container, italic, accentColor, headingFont) {
|
|
|
738
850
|
el.style.color = accentColor;
|
|
739
851
|
}
|
|
740
852
|
else {
|
|
741
|
-
el.style.fontFamily = headingFont
|
|
853
|
+
el.style.fontFamily = headingFont
|
|
854
|
+
? `'${headingFont}', sans-serif`
|
|
855
|
+
: "inherit";
|
|
742
856
|
el.style.fontStyle = "normal";
|
|
743
857
|
el.style.fontWeight = "inherit";
|
|
744
858
|
el.style.color = "inherit";
|
|
@@ -747,61 +861,98 @@ function applyEmStylesDOM(container, italic, accentColor, headingFont) {
|
|
|
747
861
|
container.querySelectorAll("em").forEach(apply);
|
|
748
862
|
container.querySelectorAll("em > span").forEach(apply);
|
|
749
863
|
}
|
|
750
|
-
// ─── Component
|
|
864
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
751
865
|
const Typography = (_a) => {
|
|
752
|
-
var _b;
|
|
753
|
-
var { variant = "Body", font: fontProp, color: colorProp, animation: animationProp, italic: italicProp, accentColor: accentColorProp, align, className, style, children, as, truncate, maxLines } = _a, rest = __rest(_a, ["variant", "font", "color", "animation", "italic", "accentColor", "align", "className", "style", "children", "as", "truncate", "maxLines"]);
|
|
866
|
+
var _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
867
|
+
var { variant = "Body", font: fontProp, color: colorProp, animation: animationProp, motionConfig, motionRef, italic: italicProp, accentColor: accentColorProp, align, className, style, children, as, truncate, maxLines } = _a, rest = __rest(_a, ["variant", "font", "color", "animation", "motionConfig", "motionRef", "italic", "accentColor", "align", "className", "style", "children", "as", "truncate", "maxLines"]);
|
|
754
868
|
const theme = useTypographyTheme();
|
|
755
869
|
const isHero = variant === "Display" || variant === "H1";
|
|
756
870
|
const ref = useRef(null);
|
|
757
|
-
// Prop wins
|
|
871
|
+
// Prop wins → theme → built-in default
|
|
758
872
|
const font = fontProp !== null && fontProp !== void 0 ? fontProp : (theme.font || undefined);
|
|
759
873
|
const color = colorProp !== null && colorProp !== void 0 ? colorProp : (theme.color || undefined);
|
|
760
|
-
const animation = isHero
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
//
|
|
766
|
-
//
|
|
767
|
-
// 1. Server safety — useInsertionEffect (like all effects) is never called
|
|
768
|
-
// on the server, so document.createElement / document.head never run
|
|
769
|
-
// during SSR. The isBrowser guard in ssr.ts is a belt-and-suspenders
|
|
770
|
-
// backup, but the effect boundary is the real guarantee.
|
|
771
|
-
//
|
|
772
|
-
// 2. Correctness — React 18 concurrent mode can call the render function
|
|
773
|
-
// multiple times before committing. Doing DOM work in render can fire
|
|
774
|
-
// those side-effects redundantly or out of order. useInsertionEffect
|
|
775
|
-
// fires synchronously before the browser paints, once per commit.
|
|
776
|
-
//
|
|
777
|
-
// 3. No FOUC — because it fires before paint (earlier than useLayoutEffect),
|
|
778
|
-
// the <style> tag is in the DOM before any text is visible, so there is
|
|
779
|
-
// no flash of unstyled / wrong-font text.
|
|
874
|
+
const animation = isHero
|
|
875
|
+
? ((_b = animationProp !== null && animationProp !== void 0 ? animationProp : theme.animation) !== null && _b !== void 0 ? _b : undefined)
|
|
876
|
+
: undefined;
|
|
877
|
+
const italic = (_c = italicProp !== null && italicProp !== void 0 ? italicProp : theme.italic) !== null && _c !== void 0 ? _c : false;
|
|
878
|
+
const accentColor = (_d = accentColorProp !== null && accentColorProp !== void 0 ? accentColorProp : theme.accentColor) !== null && _d !== void 0 ? _d : "#c8b89a";
|
|
879
|
+
// ── Font & style injection ─────────────────────────────────────────────────
|
|
780
880
|
useInsertionEffect(() => {
|
|
781
|
-
// Instrument Serif — always pre-load for hero so toggling italic is instant
|
|
782
881
|
if (isHero) {
|
|
783
882
|
injectFont(INSTRUMENT_SERIF_URL);
|
|
784
883
|
}
|
|
785
|
-
// Heading Google Font
|
|
786
884
|
if (font && GOOGLE_FONTS.includes(font)) {
|
|
787
885
|
injectFont(buildFontUrl(font));
|
|
788
886
|
}
|
|
789
|
-
// Animation keyframe stylesheet
|
|
790
887
|
if (animation && isHero) {
|
|
791
888
|
injectAnimationStyles();
|
|
792
889
|
}
|
|
793
|
-
|
|
794
|
-
|
|
890
|
+
// Inject custom keyframes as soon as the prop arrives
|
|
891
|
+
if (motionConfig === null || motionConfig === void 0 ? void 0 : motionConfig.keyframes) {
|
|
892
|
+
injectCustomKeyframes(motionConfig.keyframes);
|
|
893
|
+
}
|
|
894
|
+
}, [isHero, font, animation, motionConfig === null || motionConfig === void 0 ? void 0 : motionConfig.keyframes]);
|
|
895
|
+
// ── Re-stamp <em> inline styles after every relevant change ───────────────
|
|
795
896
|
useEffect(() => {
|
|
796
897
|
if (!isHero || !animation || !ref.current)
|
|
797
898
|
return;
|
|
798
899
|
applyEmStylesDOM(ref.current, italic, accentColor, font);
|
|
900
|
+
if (animation === "thread")
|
|
901
|
+
applyThreadOffsets(ref.current);
|
|
799
902
|
}, [italic, accentColor, font, animation, isHero]);
|
|
903
|
+
// ── motionRef callback — fires after mount and on every re-render ──────────
|
|
904
|
+
// motionRef wins over animation and motionConfig — the user drives the DOM.
|
|
905
|
+
useEffect(() => {
|
|
906
|
+
if (!motionRef)
|
|
907
|
+
return;
|
|
908
|
+
motionRef(ref.current);
|
|
909
|
+
});
|
|
910
|
+
// ── Strip lingering CSS properties after animation ends ───────────────────
|
|
911
|
+
useEffect(() => {
|
|
912
|
+
const el = ref.current;
|
|
913
|
+
if (!el || !animation)
|
|
914
|
+
return;
|
|
915
|
+
const cleanup = () => {
|
|
916
|
+
if (animation === "maskSweep") {
|
|
917
|
+
el.style.setProperty("mask-image", "none");
|
|
918
|
+
el.style.setProperty("-webkit-mask-image", "none");
|
|
919
|
+
}
|
|
920
|
+
if (animation === "gradSweep") {
|
|
921
|
+
el.style.animation = "none";
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
el.addEventListener("animationend", cleanup, { once: true });
|
|
925
|
+
return () => el.removeEventListener("animationend", cleanup);
|
|
926
|
+
}, [animation]);
|
|
800
927
|
const Tag = (as !== null && as !== void 0 ? as : variantTagMap[variant]);
|
|
801
|
-
// ──
|
|
928
|
+
// ── Build inner HTML — priority: motionRef > motionConfig > animation ──────
|
|
802
929
|
let animClass = "";
|
|
803
930
|
let heroHTML = null;
|
|
804
|
-
|
|
931
|
+
let customAnimStyle;
|
|
932
|
+
// motionRef — no HTML manipulation needed, user handles everything via ref
|
|
933
|
+
if (motionRef) ;
|
|
934
|
+
// motionConfig — works on any variant, not just heroes
|
|
935
|
+
else if (motionConfig === null || motionConfig === void 0 ? void 0 : motionConfig.keyframes) {
|
|
936
|
+
const keyframeName = injectCustomKeyframes(motionConfig.keyframes);
|
|
937
|
+
const duration = (_e = motionConfig.duration) !== null && _e !== void 0 ? _e : "0.8s";
|
|
938
|
+
const easing = (_f = motionConfig.easing) !== null && _f !== void 0 ? _f : "cubic-bezier(0.16,1,0.3,1)";
|
|
939
|
+
const delay = (_g = motionConfig.delay) !== null && _g !== void 0 ? _g : "0s";
|
|
940
|
+
const fillMode = (_h = motionConfig.fillMode) !== null && _h !== void 0 ? _h : "both";
|
|
941
|
+
const split = (_j = motionConfig.split) !== null && _j !== void 0 ? _j : "none";
|
|
942
|
+
const staggerDelay = (_k = motionConfig.staggerDelay) !== null && _k !== void 0 ? _k : (split === "chars" ? 0.04 : 0.07);
|
|
943
|
+
const rawHTML = childrenToHTML(children);
|
|
944
|
+
const { html, baseAnimation } = buildCustomHTML(rawHTML, keyframeName, duration, easing, delay, fillMode, split, staggerDelay);
|
|
945
|
+
if (split === "none") {
|
|
946
|
+
// Apply animation directly on the element via inline style
|
|
947
|
+
customAnimStyle = baseAnimation;
|
|
948
|
+
heroHTML = rawHTML;
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
heroHTML = html;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
// Built-in animation preset
|
|
955
|
+
else if (animation && isHero) {
|
|
805
956
|
const rawHTML = childrenToHTML(children);
|
|
806
957
|
if (isSplitAnimation(animation)) {
|
|
807
958
|
heroHTML = buildSplitHTML(animation, rawHTML);
|
|
@@ -812,8 +963,12 @@ const Typography = (_a) => {
|
|
|
812
963
|
}
|
|
813
964
|
}
|
|
814
965
|
// ── Computed container styles ─────────────────────────────────────────────
|
|
815
|
-
const computedStyle = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, variantStyleMap[variant]), (font ? { fontFamily: `'${font}', sans-serif` } : {})), (color ? { color } : {})), (align ? { textAlign: align } : {})), (truncate
|
|
816
|
-
? {
|
|
966
|
+
const computedStyle = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, variantStyleMap[variant]), (font ? { fontFamily: `'${font}', sans-serif` } : {})), (color ? { color } : {})), (align ? { textAlign: align } : {})), (truncate
|
|
967
|
+
? {
|
|
968
|
+
overflow: "hidden",
|
|
969
|
+
textOverflow: "ellipsis",
|
|
970
|
+
whiteSpace: "nowrap",
|
|
971
|
+
}
|
|
817
972
|
: {})), (maxLines && !truncate
|
|
818
973
|
? {
|
|
819
974
|
display: "-webkit-box",
|
|
@@ -821,17 +976,21 @@ const Typography = (_a) => {
|
|
|
821
976
|
WebkitBoxOrient: "vertical",
|
|
822
977
|
overflow: "hidden",
|
|
823
978
|
}
|
|
824
|
-
: {})), { margin: 0, padding: 0 }), style);
|
|
825
|
-
// ── Render: animation path
|
|
979
|
+
: {})), (customAnimStyle ? { animation: customAnimStyle } : {})), { margin: 0, padding: 0 }), style);
|
|
980
|
+
// ── Render: animation path ────────────────────────────────────────────────
|
|
981
|
+
//
|
|
982
|
+
// key={animation} forces React to unmount + remount the element when the
|
|
983
|
+
// animation value changes, guaranteeing the CSS keyframe fires from frame 0
|
|
984
|
+
// on every switch.
|
|
826
985
|
if (heroHTML !== null) {
|
|
827
986
|
return (jsx(Tag, Object.assign({ ref: ref, className: [animClass, className].filter(Boolean).join(" "), style: computedStyle, dangerouslySetInnerHTML: { __html: heroHTML } }, rest), animation));
|
|
828
987
|
}
|
|
829
|
-
// ── Render: standard path
|
|
988
|
+
// ── Render: standard path ─────────────────────────────────────────────────
|
|
830
989
|
const processedChildren = isHero
|
|
831
990
|
? renderChildrenWithEmStyles(children, italic, accentColor, font)
|
|
832
991
|
: children;
|
|
833
992
|
return (jsx(Tag, Object.assign({ ref: ref, className: className, style: computedStyle }, rest, { children: processedChildren })));
|
|
834
993
|
};
|
|
835
994
|
|
|
836
|
-
export { GOOGLE_FONTS, Typography, TypographyProvider, buildFontUrl, Typography as default, injectFont, preloadFonts };
|
|
995
|
+
export { GOOGLE_FONTS, Typography, TypographyProvider, buildFontUrl, Typography as default, injectFont, preloadFonts, useTypographyTheme };
|
|
837
996
|
//# sourceMappingURL=index.esm.js.map
|