@edwinvakayil/calligraphy 1.2.5 → 1.3.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 +76 -0
- package/dist/Context.d.ts +24 -0
- package/dist/animation.d.ts +6 -12
- package/dist/index.d.ts +33 -2
- package/dist/index.esm.js +190 -111
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +189 -109
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +15 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -131,28 +131,55 @@ function preloadFonts(families) {
|
|
|
131
131
|
|
|
132
132
|
const STYLE_ID = "rts-hero-animations";
|
|
133
133
|
const CSS = `
|
|
134
|
-
@keyframes rts-rise{from{opacity:0;transform:translateY(32px)}to{opacity:1;transform:translateY(0)}}
|
|
135
|
-
@keyframes rts-clip{from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 0% 0 0)}}
|
|
136
|
-
@keyframes rts-pop{0%{opacity:0;transform:scale(0.75)}60%{opacity:1;transform:scale(1.04)}100%{transform:scale(1)}}
|
|
137
|
-
@keyframes rts-blur{from{opacity:0;filter:blur(14px);transform:scale(1.04)}to{opacity:1;filter:blur(0);transform:scale(1)}}
|
|
138
|
-
@keyframes rts-flip{from{opacity:0;transform:perspective(600px) rotateX(30deg) translateY(20px)}to{opacity:1;transform:perspective(600px) rotateX(0) translateY(0)}}
|
|
139
|
-
@keyframes rts-swipe{from{opacity:0;transform:translateX(60px)}to{opacity:1;transform:translateX(0)}}
|
|
140
|
-
@keyframes rts-bounce{0%{opacity:0;transform:translateY(-60px)}60%{opacity:1;transform:translateY(10px)}80%{transform:translateY(-5px)}100%{transform:translateY(0)}}
|
|
141
|
-
@keyframes rts-type{from{width:0}to{width:100%}}
|
|
142
|
-
@keyframes rts-blink{50%{border-color:transparent}}
|
|
143
|
-
@keyframes rts-word-rise{from{opacity:0;transform:translateY(24px)}to{opacity:1;transform:translateY(0)}}
|
|
144
|
-
@keyframes rts-letter-in{from{opacity:0;transform:translateX(-16px) rotate(-4deg)}to{opacity:1;transform:none}}
|
|
134
|
+
@keyframes rts-rise { from{opacity:0;transform:translateY(32px)} to{opacity:1;transform:translateY(0)} }
|
|
135
|
+
@keyframes rts-clip { from{clip-path:inset(0 100% 0 0)} to{clip-path:inset(0 0% 0 0)} }
|
|
136
|
+
@keyframes rts-pop { 0%{opacity:0;transform:scale(0.75)} 60%{opacity:1;transform:scale(1.04)} 100%{transform:scale(1)} }
|
|
137
|
+
@keyframes rts-blur { from{opacity:0;filter:blur(14px);transform:scale(1.04)} to{opacity:1;filter:blur(0);transform:scale(1)} }
|
|
138
|
+
@keyframes rts-flip { from{opacity:0;transform:perspective(600px) rotateX(30deg) translateY(20px)} to{opacity:1;transform:perspective(600px) rotateX(0) translateY(0)} }
|
|
139
|
+
@keyframes rts-swipe { from{opacity:0;transform:translateX(60px)} to{opacity:1;transform:translateX(0)} }
|
|
140
|
+
@keyframes rts-bounce { 0%{opacity:0;transform:translateY(-60px)} 60%{opacity:1;transform:translateY(10px)} 80%{transform:translateY(-5px)} 100%{transform:translateY(0)} }
|
|
141
|
+
@keyframes rts-type { from{width:0} to{width:100%} }
|
|
142
|
+
@keyframes rts-blink { 50%{border-color:transparent} }
|
|
143
|
+
@keyframes rts-word-rise { from{opacity:0;transform:translateY(24px)} to{opacity:1;transform:translateY(0)} }
|
|
144
|
+
@keyframes rts-letter-in { from{opacity:0;transform:translateX(-16px) rotate(-4deg)} to{opacity:1;transform:none} }
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
146
|
+
/* ── New modern animations ─────────────────────────────────────────────── */
|
|
147
|
+
|
|
148
|
+
@keyframes rts-velvet { from{opacity:0;transform:translate(-12px,20px) skewX(4deg)} to{opacity:1;transform:translate(0,0) skewX(0deg)} }
|
|
149
|
+
@keyframes rts-curtain { from{clip-path:inset(0 0 100% 0)} to{clip-path:inset(0 0 0% 0)} }
|
|
150
|
+
@keyframes rts-morph { 0%{opacity:0;transform:scaleY(0.3) scaleX(1.3) translateY(10px)} 60%{opacity:1;transform:scaleY(1.08) scaleX(0.97)} 100%{transform:scaleY(1) scaleX(1)} }
|
|
151
|
+
@keyframes rts-ground { from{transform:translateY(110%);opacity:0} to{transform:translateY(0);opacity:1} }
|
|
152
|
+
@keyframes rts-cascade { from{opacity:0;transform:translateY(-28px) translateX(10px) rotate(8deg)} to{opacity:1;transform:none} }
|
|
153
|
+
@keyframes rts-spotlight { 0%{opacity:0;letter-spacing:0.3em;transform:scaleX(1.15)} 100%{opacity:1;letter-spacing:-0.03em;transform:scaleX(1)} }
|
|
154
|
+
@keyframes rts-ink { 0%{opacity:0;transform:translateY(6px) scale(0.96)} 100%{opacity:1;transform:translateY(0) scale(1)} }
|
|
155
|
+
@keyframes rts-hinge { from{opacity:0;transform:perspective(400px) rotateY(-40deg) translateX(-20px)} to{opacity:1;transform:perspective(400px) rotateY(0) translateX(0)} }
|
|
156
|
+
@keyframes rts-stretch { 0%{opacity:0;transform:scaleX(0.05)} 60%{transform:scaleX(1.04)} 100%{opacity:1;transform:scaleX(1)} }
|
|
157
|
+
@keyframes rts-peel { from{clip-path:inset(100% 0 0 0)} to{clip-path:inset(0% 0 0 0)} }
|
|
158
|
+
|
|
159
|
+
/* ── Whole-element classes (no splitting needed) ───────────────────────── */
|
|
160
|
+
.rts-rise { animation: rts-rise 0.9s cubic-bezier(0.16,1,0.3,1) both }
|
|
161
|
+
.rts-clip { animation: rts-clip 1.1s cubic-bezier(0.77,0,0.18,1) both }
|
|
162
|
+
.rts-pop { animation: rts-pop 0.7s cubic-bezier(0.34,1.56,0.64,1) both }
|
|
163
|
+
.rts-blur { animation: rts-blur 1s cubic-bezier(0.16,1,0.3,1) both }
|
|
164
|
+
.rts-flip { animation: rts-flip 0.9s cubic-bezier(0.16,1,0.3,1) both; transform-origin: center bottom }
|
|
165
|
+
.rts-swipe { animation: rts-swipe 0.8s cubic-bezier(0.16,1,0.3,1) both }
|
|
166
|
+
.rts-bounce { animation: rts-bounce 0.9s cubic-bezier(0.36,0.07,0.19,0.97) both }
|
|
167
|
+
.rts-typewriter { overflow: hidden; white-space: nowrap; border-right: 2px solid currentColor; width: 0; animation: rts-type 1.6s steps(22,end) both, rts-blink 0.7s step-end 1.6s 3 }
|
|
168
|
+
.rts-morph { animation: rts-morph 0.8s cubic-bezier(0.34,1.56,0.64,1) both }
|
|
169
|
+
.rts-spotlight { animation: rts-spotlight 1s cubic-bezier(0.16,1,0.3,1) both }
|
|
170
|
+
.rts-stretch { animation: rts-stretch 0.9s cubic-bezier(0.34,1.56,0.64,1) both }
|
|
171
|
+
|
|
172
|
+
/* ── Per-word / per-character span classes ─────────────────────────────── */
|
|
173
|
+
.rts-word { display:inline-block;opacity:0;transform:translateY(24px);animation:rts-word-rise 0.7s cubic-bezier(0.16,1,0.3,1) both }
|
|
174
|
+
.rts-letter { display:inline-block;opacity:0;transform:translateX(-16px) rotate(-4deg);animation:rts-letter-in 0.5s cubic-bezier(0.16,1,0.3,1) both }
|
|
175
|
+
.rts-velvet-word { display:inline-block;opacity:0;animation:rts-velvet 0.65s cubic-bezier(0.16,1,0.3,1) both }
|
|
176
|
+
.rts-curtain-word { display:inline-block;overflow:hidden;animation:rts-curtain 0.7s cubic-bezier(0.77,0,0.18,1) both }
|
|
177
|
+
.rts-ground-wrap { display:inline-block;overflow:hidden;vertical-align:bottom }
|
|
178
|
+
.rts-ground-inner { display:inline-block;animation:rts-ground 0.65s cubic-bezier(0.16,1,0.3,1) both }
|
|
179
|
+
.rts-cascade-ch { display:inline-block;opacity:0;animation:rts-cascade 0.45s cubic-bezier(0.34,1.56,0.64,1) both }
|
|
180
|
+
.rts-ink-word { display:inline-block;opacity:0;animation:rts-ink 0.9s cubic-bezier(0.16,1,0.3,1) both }
|
|
181
|
+
.rts-hinge-word { display:inline-block;opacity:0;transform-origin:left center;animation:rts-hinge 0.6s cubic-bezier(0.16,1,0.3,1) both }
|
|
182
|
+
.rts-peel-word { display:inline-block;overflow:hidden;animation:rts-peel 0.6s cubic-bezier(0.77,0,0.18,1) both }
|
|
156
183
|
`;
|
|
157
184
|
function injectAnimationStyles() {
|
|
158
185
|
if (typeof document === "undefined")
|
|
@@ -164,52 +191,47 @@ function injectAnimationStyles() {
|
|
|
164
191
|
style.textContent = CSS;
|
|
165
192
|
document.head.appendChild(style);
|
|
166
193
|
}
|
|
194
|
+
// ─── Whole-element class map ──────────────────────────────────────────────────
|
|
195
|
+
const WHOLE_CLASS_MAP = {
|
|
196
|
+
rise: "rts-rise",
|
|
197
|
+
clip: "rts-clip",
|
|
198
|
+
pop: "rts-pop",
|
|
199
|
+
blur: "rts-blur",
|
|
200
|
+
flip: "rts-flip",
|
|
201
|
+
swipe: "rts-swipe",
|
|
202
|
+
bounce: "rts-bounce",
|
|
203
|
+
typewriter: "rts-typewriter",
|
|
204
|
+
morph: "rts-morph",
|
|
205
|
+
spotlight: "rts-spotlight",
|
|
206
|
+
stretch: "rts-stretch",
|
|
207
|
+
};
|
|
208
|
+
/** Returns the CSS class for whole-element animations, or "" for split ones. */
|
|
167
209
|
function getAnimationClass(animation) {
|
|
168
210
|
var _a;
|
|
169
|
-
|
|
170
|
-
rise: "rts-rise",
|
|
171
|
-
clip: "rts-clip",
|
|
172
|
-
pop: "rts-pop",
|
|
173
|
-
blur: "rts-blur",
|
|
174
|
-
flip: "rts-flip",
|
|
175
|
-
swipe: "rts-swipe",
|
|
176
|
-
bounce: "rts-bounce",
|
|
177
|
-
typewriter: "rts-typewriter",
|
|
178
|
-
stagger: "",
|
|
179
|
-
letters: "",
|
|
180
|
-
};
|
|
181
|
-
return (_a = map[animation]) !== null && _a !== void 0 ? _a : "";
|
|
211
|
+
return (_a = WHOLE_CLASS_MAP[animation]) !== null && _a !== void 0 ? _a : "";
|
|
182
212
|
}
|
|
183
|
-
/**
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
function
|
|
213
|
+
/** True if the animation needs the HTML to be split into word/char spans. */
|
|
214
|
+
function isSplitAnimation(animation) {
|
|
215
|
+
return !(animation in WHOLE_CLASS_MAP);
|
|
216
|
+
}
|
|
217
|
+
// ─── HTML builders for split animations ──────────────────────────────────────
|
|
218
|
+
function wrapWords(html, cls, delayStep) {
|
|
189
219
|
var _a;
|
|
190
220
|
const tokens = (_a = html.match(/(<em>[\s\S]*?<\/em>|[^\s]+)/g)) !== null && _a !== void 0 ? _a : [];
|
|
191
221
|
return tokens
|
|
192
222
|
.map((tok, i) => {
|
|
193
|
-
const delay = (i *
|
|
223
|
+
const delay = (i * delayStep).toFixed(2);
|
|
194
224
|
if (tok.startsWith("<em>")) {
|
|
195
|
-
|
|
196
|
-
const inner = tok.slice(4, -5);
|
|
197
|
-
return `<em><span class="rts-word" style="animation-delay:${delay}s">${inner}</span></em>`;
|
|
225
|
+
return `<em><span class="${cls}" style="animation-delay:${delay}s">${tok.slice(4, -5)}</span></em>`;
|
|
198
226
|
}
|
|
199
|
-
return `<span class="
|
|
227
|
+
return `<span class="${cls}" style="animation-delay:${delay}s">${tok}</span>`;
|
|
200
228
|
})
|
|
201
229
|
.join(" ");
|
|
202
230
|
}
|
|
203
|
-
|
|
204
|
-
* Wraps each character in an animated span.
|
|
205
|
-
* <em> tags are preserved in the output — Typography's useEffect applies
|
|
206
|
-
* the actual italic/non-italic inline styles after the DOM is ready.
|
|
207
|
-
*/
|
|
208
|
-
function buildLettersHTML(html) {
|
|
231
|
+
function wrapChars(html, cls, delayStep) {
|
|
209
232
|
const result = [];
|
|
210
233
|
let inEm = false;
|
|
211
234
|
let delay = 0;
|
|
212
|
-
const step = 0.04;
|
|
213
235
|
let i = 0;
|
|
214
236
|
while (i < html.length) {
|
|
215
237
|
if (html.startsWith("<em>", i)) {
|
|
@@ -228,14 +250,76 @@ function buildLettersHTML(html) {
|
|
|
228
250
|
i++;
|
|
229
251
|
continue;
|
|
230
252
|
}
|
|
231
|
-
const span = `<span class="
|
|
232
|
-
// Preserve <em> wrapper in DOM — styles applied by useEffect
|
|
253
|
+
const span = `<span class="${cls}" style="animation-delay:${delay.toFixed(2)}s">${ch}</span>`;
|
|
233
254
|
result.push(inEm ? `<em>${span}</em>` : span);
|
|
234
|
-
delay +=
|
|
255
|
+
delay += delayStep;
|
|
235
256
|
i++;
|
|
236
257
|
}
|
|
237
258
|
return result.join("");
|
|
238
259
|
}
|
|
260
|
+
function wrapGround(html, delayStep) {
|
|
261
|
+
var _a;
|
|
262
|
+
const tokens = (_a = html.match(/(<em>[\s\S]*?<\/em>|[^\s]+)/g)) !== null && _a !== void 0 ? _a : [];
|
|
263
|
+
return tokens
|
|
264
|
+
.map((tok, i) => {
|
|
265
|
+
const delay = (i * delayStep).toFixed(2);
|
|
266
|
+
const inner = tok.startsWith("<em>")
|
|
267
|
+
? `<em>${tok.slice(4, -5)}</em>`
|
|
268
|
+
: tok;
|
|
269
|
+
return `<span class="rts-ground-wrap"><span class="rts-ground-inner" style="animation-delay:${delay}s">${inner}</span></span>`;
|
|
270
|
+
})
|
|
271
|
+
.join(" ");
|
|
272
|
+
}
|
|
273
|
+
function buildSplitHTML(animation, html) {
|
|
274
|
+
switch (animation) {
|
|
275
|
+
case "stagger": return wrapWords(html, "rts-word", 0.07);
|
|
276
|
+
case "letters": return wrapChars(html, "rts-letter", 0.04);
|
|
277
|
+
case "velvet": return wrapWords(html, "rts-velvet-word", 0.08);
|
|
278
|
+
case "curtain": return wrapWords(html, "rts-curtain-word", 0.10);
|
|
279
|
+
case "ground": return wrapGround(html, 0.09);
|
|
280
|
+
case "cascade": return wrapChars(html, "rts-cascade-ch", 0.05);
|
|
281
|
+
case "ink": return wrapWords(html, "rts-ink-word", 0.10);
|
|
282
|
+
case "hinge": return wrapWords(html, "rts-hinge-word", 0.09);
|
|
283
|
+
case "peel": return wrapWords(html, "rts-peel-word", 0.10);
|
|
284
|
+
default: return html;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ─── Defaults ─────────────────────────────────────────────────────────────────
|
|
289
|
+
const DEFAULT_THEME = {
|
|
290
|
+
font: "",
|
|
291
|
+
accentColor: "#c8b89a",
|
|
292
|
+
italic: false,
|
|
293
|
+
animation: "rise",
|
|
294
|
+
color: "",
|
|
295
|
+
};
|
|
296
|
+
// ─── Context ──────────────────────────────────────────────────────────────────
|
|
297
|
+
const TypographyContext = react.createContext(DEFAULT_THEME);
|
|
298
|
+
const TypographyProvider = ({ theme, children, }) => {
|
|
299
|
+
const resolved = react.useMemo(() => {
|
|
300
|
+
var _a, _b, _c, _d, _e;
|
|
301
|
+
return ({
|
|
302
|
+
font: (_a = theme.font) !== null && _a !== void 0 ? _a : DEFAULT_THEME.font,
|
|
303
|
+
accentColor: (_b = theme.accentColor) !== null && _b !== void 0 ? _b : DEFAULT_THEME.accentColor,
|
|
304
|
+
italic: (_c = theme.italic) !== null && _c !== void 0 ? _c : DEFAULT_THEME.italic,
|
|
305
|
+
animation: (_d = theme.animation) !== null && _d !== void 0 ? _d : DEFAULT_THEME.animation,
|
|
306
|
+
color: (_e = theme.color) !== null && _e !== void 0 ? _e : DEFAULT_THEME.color,
|
|
307
|
+
});
|
|
308
|
+
}, [theme.font, theme.accentColor, theme.italic, theme.animation, theme.color]);
|
|
309
|
+
// Pre-load the theme font as soon as the provider mounts
|
|
310
|
+
if (resolved.font && GOOGLE_FONTS.includes(resolved.font)) {
|
|
311
|
+
injectFont(buildFontUrl(resolved.font));
|
|
312
|
+
}
|
|
313
|
+
return (jsxRuntime.jsx(TypographyContext.Provider, { value: resolved, children: children }));
|
|
314
|
+
};
|
|
315
|
+
// ─── Hook ─────────────────────────────────────────────────────────────────────
|
|
316
|
+
/**
|
|
317
|
+
* Returns the resolved theme from the nearest TypographyProvider.
|
|
318
|
+
* Falls back to DEFAULT_THEME if used outside a provider.
|
|
319
|
+
*/
|
|
320
|
+
function useTypographyTheme() {
|
|
321
|
+
return react.useContext(TypographyContext);
|
|
322
|
+
}
|
|
239
323
|
|
|
240
324
|
// ─── Static maps ─────────────────────────────────────────────────────────────
|
|
241
325
|
const variantTagMap = {
|
|
@@ -330,10 +414,6 @@ const variantStyleMap = {
|
|
|
330
414
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
331
415
|
const INSTRUMENT_SERIF_URL = "https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&display=swap";
|
|
332
416
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
333
|
-
/**
|
|
334
|
-
* Serialise React children → raw HTML string.
|
|
335
|
-
* Preserves <em>text</em> nodes for animation builders.
|
|
336
|
-
*/
|
|
337
417
|
function childrenToHTML(children) {
|
|
338
418
|
var _a, _b;
|
|
339
419
|
return ((_b = (_a = react.Children.map(children, (child) => {
|
|
@@ -347,11 +427,6 @@ function childrenToHTML(children) {
|
|
|
347
427
|
return "";
|
|
348
428
|
})) === null || _a === void 0 ? void 0 : _a.join("")) !== null && _b !== void 0 ? _b : "");
|
|
349
429
|
}
|
|
350
|
-
/**
|
|
351
|
-
* Re-map React children so that <em> elements get explicit inline styles.
|
|
352
|
-
* This is used for the no-animation render path where we keep real React nodes.
|
|
353
|
-
* Inline styles on the <em> itself beat any inherited font-family from the parent.
|
|
354
|
-
*/
|
|
355
430
|
function renderChildrenWithEmStyles(children, italic, accentColor, headingFont) {
|
|
356
431
|
const italicStyle = {
|
|
357
432
|
fontFamily: "'Instrument Serif', serif",
|
|
@@ -372,13 +447,8 @@ function renderChildrenWithEmStyles(children, italic, accentColor, headingFont)
|
|
|
372
447
|
return child;
|
|
373
448
|
});
|
|
374
449
|
}
|
|
375
|
-
/**
|
|
376
|
-
* After dangerouslySetInnerHTML renders, walk the DOM and apply inline styles
|
|
377
|
-
* to every <em> and <em> > span so the font switch is guaranteed.
|
|
378
|
-
*/
|
|
379
450
|
function applyEmStylesDOM(container, italic, accentColor, headingFont) {
|
|
380
|
-
|
|
381
|
-
container.querySelectorAll("em").forEach((el) => {
|
|
451
|
+
const apply = (el) => {
|
|
382
452
|
if (italic) {
|
|
383
453
|
el.style.fontFamily = "'Instrument Serif', serif";
|
|
384
454
|
el.style.fontStyle = "italic";
|
|
@@ -391,59 +461,68 @@ function applyEmStylesDOM(container, italic, accentColor, headingFont) {
|
|
|
391
461
|
el.style.fontWeight = "inherit";
|
|
392
462
|
el.style.color = "inherit";
|
|
393
463
|
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
container.querySelectorAll("em > span").forEach(
|
|
397
|
-
if (italic) {
|
|
398
|
-
el.style.fontFamily = "'Instrument Serif', serif";
|
|
399
|
-
el.style.fontStyle = "italic";
|
|
400
|
-
el.style.fontWeight = "400";
|
|
401
|
-
el.style.color = accentColor;
|
|
402
|
-
}
|
|
403
|
-
else {
|
|
404
|
-
el.style.fontFamily = headingFont ? `'${headingFont}', sans-serif` : "inherit";
|
|
405
|
-
el.style.fontStyle = "normal";
|
|
406
|
-
el.style.fontWeight = "inherit";
|
|
407
|
-
el.style.color = "inherit";
|
|
408
|
-
}
|
|
409
|
-
});
|
|
464
|
+
};
|
|
465
|
+
container.querySelectorAll("em").forEach(apply);
|
|
466
|
+
container.querySelectorAll("em > span").forEach(apply);
|
|
410
467
|
}
|
|
411
468
|
// ─── Component ───────────────────────────────────────────────────────────────
|
|
412
469
|
const Typography = (_a) => {
|
|
413
|
-
var
|
|
470
|
+
var _b;
|
|
471
|
+
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"]);
|
|
472
|
+
const theme = useTypographyTheme();
|
|
414
473
|
const isHero = variant === "Display" || variant === "H1";
|
|
415
474
|
const ref = react.useRef(null);
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
//
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
//
|
|
430
|
-
//
|
|
475
|
+
// Prop wins; fall back to theme; fall back to built-in default
|
|
476
|
+
const font = fontProp !== null && fontProp !== void 0 ? fontProp : (theme.font || undefined);
|
|
477
|
+
const color = colorProp !== null && colorProp !== void 0 ? colorProp : (theme.color || undefined);
|
|
478
|
+
const animation = isHero ? ((_b = animationProp !== null && animationProp !== void 0 ? animationProp : theme.animation) !== null && _b !== void 0 ? _b : undefined) : undefined;
|
|
479
|
+
const italic = italicProp !== null && italicProp !== void 0 ? italicProp : theme.italic;
|
|
480
|
+
const accentColor = accentColorProp !== null && accentColorProp !== void 0 ? accentColorProp : theme.accentColor;
|
|
481
|
+
// ── useInsertionEffect: inject <link> and <style> tags ────────────────────
|
|
482
|
+
//
|
|
483
|
+
// WHY useInsertionEffect instead of plain render-phase calls:
|
|
484
|
+
//
|
|
485
|
+
// 1. Server safety — useInsertionEffect (like all effects) is never called
|
|
486
|
+
// on the server, so document.createElement / document.head never run
|
|
487
|
+
// during SSR. The isBrowser guard in ssr.ts is a belt-and-suspenders
|
|
488
|
+
// backup, but the effect boundary is the real guarantee.
|
|
489
|
+
//
|
|
490
|
+
// 2. Correctness — React 18 concurrent mode can call the render function
|
|
491
|
+
// multiple times before committing. Doing DOM work in render can fire
|
|
492
|
+
// those side-effects redundantly or out of order. useInsertionEffect
|
|
493
|
+
// fires synchronously before the browser paints, once per commit.
|
|
494
|
+
//
|
|
495
|
+
// 3. No FOUC — because it fires before paint (earlier than useLayoutEffect),
|
|
496
|
+
// the <style> tag is in the DOM before any text is visible, so there is
|
|
497
|
+
// no flash of unstyled / wrong-font text.
|
|
498
|
+
react.useInsertionEffect(() => {
|
|
499
|
+
// Instrument Serif — always pre-load for hero so toggling italic is instant
|
|
500
|
+
if (isHero) {
|
|
501
|
+
injectFont(INSTRUMENT_SERIF_URL);
|
|
502
|
+
}
|
|
503
|
+
// Heading Google Font
|
|
504
|
+
if (font && GOOGLE_FONTS.includes(font)) {
|
|
505
|
+
injectFont(buildFontUrl(font));
|
|
506
|
+
}
|
|
507
|
+
// Animation keyframe stylesheet
|
|
508
|
+
if (animation && isHero) {
|
|
509
|
+
injectAnimationStyles();
|
|
510
|
+
}
|
|
511
|
+
}, [isHero, font, animation]);
|
|
512
|
+
// ── useEffect: re-stamp inline styles on <em> after DOM updates ───────────
|
|
431
513
|
react.useEffect(() => {
|
|
432
514
|
if (!isHero || !animation || !ref.current)
|
|
433
515
|
return;
|
|
434
516
|
applyEmStylesDOM(ref.current, italic, accentColor, font);
|
|
435
517
|
}, [italic, accentColor, font, animation, isHero]);
|
|
436
518
|
const Tag = (as !== null && as !== void 0 ? as : variantTagMap[variant]);
|
|
437
|
-
// ──
|
|
519
|
+
// ── Animation path: build inner HTML ─────────────────────────────────────
|
|
438
520
|
let animClass = "";
|
|
439
521
|
let heroHTML = null;
|
|
440
522
|
if (animation && isHero) {
|
|
441
523
|
const rawHTML = childrenToHTML(children);
|
|
442
|
-
if (animation
|
|
443
|
-
heroHTML =
|
|
444
|
-
}
|
|
445
|
-
else if (animation === "letters") {
|
|
446
|
-
heroHTML = buildLettersHTML(rawHTML);
|
|
524
|
+
if (isSplitAnimation(animation)) {
|
|
525
|
+
heroHTML = buildSplitHTML(animation, rawHTML);
|
|
447
526
|
}
|
|
448
527
|
else {
|
|
449
528
|
heroHTML = rawHTML;
|
|
@@ -465,7 +544,7 @@ const Typography = (_a) => {
|
|
|
465
544
|
if (heroHTML !== null) {
|
|
466
545
|
return (jsxRuntime.jsx(Tag, Object.assign({ ref: ref, className: [animClass, className].filter(Boolean).join(" "), style: computedStyle, dangerouslySetInnerHTML: { __html: heroHTML } }, rest), animation));
|
|
467
546
|
}
|
|
468
|
-
// ── Render: standard path
|
|
547
|
+
// ── Render: standard path ────────────────────────────────────────────────
|
|
469
548
|
const processedChildren = isHero
|
|
470
549
|
? renderChildrenWithEmStyles(children, italic, accentColor, font)
|
|
471
550
|
: children;
|
|
@@ -474,6 +553,7 @@ const Typography = (_a) => {
|
|
|
474
553
|
|
|
475
554
|
exports.GOOGLE_FONTS = GOOGLE_FONTS;
|
|
476
555
|
exports.Typography = Typography;
|
|
556
|
+
exports.TypographyProvider = TypographyProvider;
|
|
477
557
|
exports.buildFontUrl = buildFontUrl;
|
|
478
558
|
exports.default = Typography;
|
|
479
559
|
exports.injectFont = injectFont;
|